有朋友提出一个问题:UEFI 下如何读取SMBIOS信息?
查阅了一下资料,在 EFI_CONFIGURATION_TABLE 中有 SMBIOS_TABLE_GUID 可以用来获得SMBIOS信息。此外,推荐看一下 http://blog.chinaunix.net/uid-20757375-id-3531318.html 。
可供直接参考的代码有 ShellPkg\Library\UefiShellDebug1CommandsLib\SmbiosView 如果你看着觉得过于复杂的话,还可以参考 https://github.com/fpmurphy/UEFI-Utilities 。这个程序结构清晰,功能简单。每种不足是直接下载的代码有一些错误之类的,需要修正。下面就是修改之后的代码(代码很多是从EDK中直接copy出来的,因为装一些头文件和调用会很复杂,用到的只是那么一两个函数,所以最简单的就是直接copy,放在代码中了.其中头文件 smbios.h 可以在\MdePkg\Include\IndustryStandard 中找到)
// // Copyright (c) 2012 Finnbarr P. Murphy. All rights reserved. // // Display Firmware Information Vendor, Version and Release Date via SMBIOS // // Any source code included from EDK2 is copyright Intel Corporation // // Licence: BSD License // #define GUID EFI_GUID // hack for SmBios.h #include "IndustryStandard/SmBios.h" // from EDK2. GNU_EFI libsmbios.h is defective #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/MemoryAllocationLib.h> #define EFI_GLOBAL_VARIABLE \ { \ 0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } \ } #define EFI_SMBIOS_TABLE_GUID \ { \ 0xeb9d2d31, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d} \ } #define BUFSIZE 64 #define INVALID_HANDLE (UINT16) (-1) #define DMI_INVALID_HANDLE 0x83 #define DMI_SUCCESS 0x00 STATIC SMBIOS_TABLE_ENTRY_POINT *mSmbiosTable = 0; //NULL; STATIC SMBIOS_STRUCTURE_POINTER m_SmbiosStruct; STATIC SMBIOS_STRUCTURE_POINTER *mSmbiosStruct = &m_SmbiosStruct; EFI_GUID SMBIOSTableGuid = EFI_SMBIOS_TABLE_GUID; extern EFI_SYSTEM_TABLE *gST; int Strlen(const char *str) { const char *s; for (s = str; *s; ++s) ; return (s - str); } // // Modified from EDK2 source code. Copyright Intel Corporation. // CHAR8* LibGetSmbiosString ( SMBIOS_STRUCTURE_POINTER *Smbios, UINT16 StringNumber) { UINT16 Index; CHAR8 *String; //ASSERT (Smbios != NULL); String = (CHAR8 *) (Smbios->Raw + Smbios->Hdr->Length); for (Index = 1; Index <= StringNumber; Index++) { if (StringNumber == Index) { return String; } for (; *String != 0; String++) ; String++; if (*String == 0) { Smbios->Raw = (UINT8 *)++String; return NULL; } } return NULL; } // // Modified from EDK2 source code. Copyright Intel Corporation. // EFI_STATUS LibGetSmbiosStructure ( UINT16 *Handle, UINT8 **Buffer, UINT16 *Length) { SMBIOS_STRUCTURE_POINTER Smbios; SMBIOS_STRUCTURE_POINTER SmbiosEnd; UINT8 *Raw; if (*Handle == INVALID_HANDLE) { *Handle = mSmbiosStruct->Hdr->Handle; return DMI_INVALID_HANDLE; } if ((Buffer == NULL) || (Length == NULL)) { Print(L"Invalid handle\n"); return DMI_INVALID_HANDLE; } *Length = 0; Smbios.Hdr = mSmbiosStruct->Hdr; SmbiosEnd.Raw = Smbios.Raw + mSmbiosTable->TableLength; while (Smbios.Raw < SmbiosEnd.Raw) { if (Smbios.Hdr->Handle == *Handle) { Raw = Smbios.Raw; LibGetSmbiosString (&Smbios, (UINT16) (-1)); *Length = (UINT16) (Smbios.Raw - Raw); *Buffer = Raw; if (Smbios.Raw < SmbiosEnd.Raw) { *Handle = Smbios.Hdr->Handle; } else { *Handle = INVALID_HANDLE; } return DMI_SUCCESS; } LibGetSmbiosString (&Smbios, (UINT16) (-1)); } *Handle = INVALID_HANDLE; return DMI_INVALID_HANDLE; } CHAR16 * ASCII_to_UCS2(const char *s, int len) { CHAR16 *ret = NULL; int i; ret = AllocateZeroPool(len*2 + 2); if (!ret) return NULL; for (i = 0; i < len; i++) ret[i] = s[i]; return ret; } BOOLEAN EfiCompareGuid ( IN EFI_GUID *Guid1, IN EFI_GUID *Guid2 ) /*++ Routine Description: Compares two GUIDs Arguments: Guid1 - guid to compare Guid2 - guid to compare Returns: TRUE if Guid1 == Guid2 FALSE if Guid1 != Guid2 --*/ { UINTN Index; // // compare byte by byte // for (Index = 0; Index < 16; ++Index) { if (*(((UINT8*) Guid1) + Index) != *(((UINT8*) Guid2) + Index)) { return FALSE; } } return TRUE; } EFI_STATUS EfiLibGetSystemConfigurationTable ( IN EFI_GUID *TableGuid, OUT VOID **Table ) /*++ Routine Description: Get table from configuration table by name Arguments: TableGuid - Table name to search Table - Pointer to the table caller wants Returns: EFI_NOT_FOUND - Not found the table EFI_SUCCESS - Found the table --*/ { UINTN Index; *Table = NULL; for (Index = 0; Index < gST->NumberOfTableEntries; Index++) { if (EfiCompareGuid (TableGuid, &(gST->ConfigurationTable[Index].VendorGuid))==TRUE) { *Table = gST->ConfigurationTable[Index].VendorTable; return EFI_SUCCESS; } } return EFI_NOT_FOUND; } /** Compares two memory buffers of a given length. @param DestinationBuffer The first memory buffer. @param SourceBuffer The second memory buffer. @param Length Length of DestinationBuffer and SourceBuffer memory regions to compare. Must be non-zero. @return 0 All Length bytes of the two buffers are identical. @retval Non-zero The first mismatched byte in SourceBuffer subtracted from the first mismatched byte in DestinationBuffer. **/ INTN EFIAPI InternalMemCompareMemZ ( IN CONST VOID *DestinationBuffer, IN CONST VOID *SourceBuffer, IN UINTN Length ) { while ((--Length != 0) && (*(INT8*)DestinationBuffer == *(INT8*)SourceBuffer)) { DestinationBuffer = (INT8*)DestinationBuffer + 1; SourceBuffer = (INT8*)SourceBuffer + 1; } return (INTN)*(UINT8*)DestinationBuffer - (INTN)*(UINT8*)SourceBuffer; } /** Compares the contents of two buffers. This function compares Length bytes of SourceBuffer to Length bytes of DestinationBuffer. If all Length bytes of the two buffers are identical, then 0 is returned. Otherwise, the value returned is the first mismatched byte in SourceBuffer subtracted from the first mismatched byte in DestinationBuffer. If Length > 0 and DestinationBuffer is NULL, then ASSERT(). If Length > 0 and SourceBuffer is NULL, then ASSERT(). If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT(). If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT(). @param DestinationBuffer A pointer to the destination buffer to compare. @param SourceBuffer A pointer to the source buffer to compare. @param Length The number of bytes to compare. @return 0 All Length bytes of the two buffers are identical. @retval Non-zero The first mismatched byte in SourceBuffer subtracted from the first mismatched byte in DestinationBuffer. **/ INTN EFIAPI CompareMem ( IN CONST VOID *DestinationBuffer, IN CONST VOID *SourceBuffer, IN UINTN Length ) { if (Length == 0 || DestinationBuffer == SourceBuffer) { return 0; } return InternalMemCompareMemZ (DestinationBuffer, SourceBuffer, Length); } EFI_STATUS UefiMain (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { EFI_STATUS status = EFI_SUCCESS; CHAR16 *ptr; CHAR8 *str; UINT16 Handle; UINTN Index; UINT16 Length; UINT8 *Buffer; SMBIOS_STRUCTURE_POINTER SmbiosStruct; //InitializeLib(image, systab); status = EfiLibGetSystemConfigurationTable(&SMBIOSTableGuid, (VOID**)&mSmbiosTable); if ((status != EFI_SUCCESS || mSmbiosTable == NULL) || (CompareMem (mSmbiosTable->AnchorString, "_SM_", 4) != 0)) { Print(L"ERROR: SMBIOS table not found.\n"); return status; } mSmbiosStruct->Raw = (UINT8 *) (UINTN) (mSmbiosTable->TableAddress); Print(L"SMBIOS Ver: %x.%x Rev: %x Table Count: %d\n", mSmbiosTable->MajorVersion, mSmbiosTable->MinorVersion, mSmbiosTable->EntryPointRevision, mSmbiosTable->NumberOfSmbiosStructures); Handle = INVALID_HANDLE; LibGetSmbiosStructure (&Handle, NULL, NULL); // loop though the tables looking for a type 0 table. for (Index = 0; Index < mSmbiosTable->NumberOfSmbiosStructures; Index++) { if (Handle == INVALID_HANDLE) { break; } if (LibGetSmbiosStructure (&Handle, &Buffer, &Length) != DMI_SUCCESS) { break; } SmbiosStruct.Raw = Buffer; if (SmbiosStruct.Hdr->Type == 0) { // Type 0 - BIOS /* vendor string */ str = LibGetSmbiosString(&SmbiosStruct, 1); ptr = ASCII_to_UCS2(str, Strlen(str)); Print(L"Firmware Vendor: %s\n", ptr); FreePool(ptr); /* version string */ str = LibGetSmbiosString(&SmbiosStruct, 2); ptr = ASCII_to_UCS2(str, Strlen(str)); Print(L"Firmware Version: %s\n", ptr); FreePool(ptr); /* release string */ str = LibGetSmbiosString(&SmbiosStruct, 3); ptr = ASCII_to_UCS2(str, Strlen(str)); Print(L"Firmware Release: %s\n", ptr); FreePool(ptr); break; } } return status; }
运行结果
对比 SMBIOSVIEW
可以看到二者是相同的。
代码下载
GetVer
顺便多说一点:
SMBIOS中可以获得一些硬件信息,比如当前的内存插槽信息等等。但是,因为这个是BIOS在POST过程中加入的,很可能出现错误,甚至就是忘记做这个东西(MS的软件非常不信任BIOS提供的信息,如果有可能他们宁可自己重新获取一下)。如果只是想做个参考,并且你的软件需要兼容大量的设备,从这里获得信息是完全没问题的(比如MemTest86);但是如果你期望准确一些,最好还是自己手工做一下。另外一方面,如果你使用了从SMBIOS获得信息的方法,结果在客户现场遇到了问题,不妨考虑一下是否为 SMBIOS 不正确导致的。
當build (build -a X64 -p AppPkg\AppPkg.dsc)過程會failed在以下的 code
int Strlen(const char *str)
{
const char *s;
for (s = str; *s; ++s)
;
return (s - str);
}
改寫一下,就可以build成功 ( 如下)
int Strlen(IN const char *str)
{
const char *s;
for (s = str; *s; ++s)
;
return (int)((unsigned long long)s - (unsigned long long)str);
}
請問我呼叫EFI_SMBIOS_PROTOCOL底下的 GetNext,remove,UpdateString 為啥全部都失敗呢?
拿到shell下跑,都回停住
.inf和上面一樣
#define GUID EFI_GUID // hack for SmBios.h
#include // from EDK2. GNU_EFI libsmbios.h is defective
#include
#include
#include
#include
#define EFI_SMBIOS_PROTOCOL_GUID \
{ 0x3583ff6, 0xcb36, 0x4940, { 0x94, 0x7e, 0xb9, 0xb3, 0x9f, 0x4a, 0xfa, 0xf7 }}
EFI_GUID SMBIOSProtocolGuid = EFI_SMBIOS_PROTOCOL_GUID;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_BOOT_SERVICES *gBS;
typedef struct _EFI_SMBIOS_PROTOCOL EFI_SMBIOS_PROTOCOL;
EFI_STATUS
UefiMain (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
EFI_STATUS status ;
// CHAR8 *STRING = "Edison";
UINT16 Handle=SMBIOS_HANDLE_PI_RESERVED;
EFI_SMBIOS_TABLE_HEADER *Buf;
// UINTN K=1;
// UINTN *StringNumber=&K;
EFI_SMBIOS_PROTOCOL *Smbios;
EFI_SMBIOS_TYPE RecordType = EFI_SMBIOS_TYPE_SYSTEM_INFORMATION;
gBS->LocateProtocol(
&SMBIOSProtocolGuid,
NULL,
(VOID **)&Smbios);
CpuDeadLoop ();
status=Smbios->GetNext (
Smbios,
&Handle,
&RecordType,
&Buf,
NULL
);
Print(L"Handle is %d\n",Handle);
// Smbios->Remove(
// Smbios,
// Handle);
//
//
// Smbios->UpdateString(
// Smbios,
// &Handle,
// StringNumber,
// STRING
// );
// Print(L"status is %d\n",status);
return status;
}
建议你每一步操作都主动检查一下结果,看看能否获得错误信息。
你的GetNext函数注册过了么?注册之后是可以调用到的
Hi :
请问一下,如果要Add一个SMBIOS Structure Data(假如机器上没有Type32,现在加上Type32),调用SMBIOS Protocol的add()函数,写成 UEFI APP在shell下run,是可以看到Type32是被Add到SMBIOS Table中,但是如果机器断电重启,那么Add的Type32就会丢失,我的理解是:Add()函数操作的是内存,它只是把数据加在已经存在内存中的SMBIOS table中,如果要在重启之后继续生效,那要如何处理?
另外一个问题是:SMBIOS的数据,是存储在什么地方的呢?
之前的 Legacy BIOS 中这样的加入操作最后是要写入到 nvram 也就是 spi rom 中的。uefi 的话我没有研究过,但是应该也能写入 spi 中,回头我研究一下。
Hi ziv,有个相关的问题请教一下,uefi shell中如何去写nvram?我现在遇到一个问题是需要在uefi shell中用程序修改主setup界面上的参数,比如说内存频率、Timing参数等,不知该如何实现,特来请教,盼复。多谢!
参考这篇文章 http://www.lab-z.com/stugt/
你好,请问对于SevenYan的问题你想到解决办法了吗?我也对这个问题很困扰,没想到解决办法
我看了一下 EDK2 上的 OVMF 的这部分代码, SMBIOS 是在 Post 过程中创建起来的,因此没有办法直接加入然后下次直接就有。不过我不清楚IBV 是否有解决方案,另外,我觉得可以在代码中加入从 Variable 中读取特定内容作为 SMBIOS 的代码,这样在 Shell 下可以通过修改 Variable 的方式来增加 SMBIOS 的内容。
好的,我再看看,谢谢您的指导
你好,你写的资料真的太好了,
请教下在 UEFI 或 Win 如何读取主板开机时显示一行信息 Sign on Message(有些主板将机种名称,版本,日期等信息写在此处),盼复,多谢了。
UEFI 的话,通常是开机界面显示这个信息需要改BIOS; Windows 的话,你可以查看通过 WMI 的方法。具体你可以搜索 “wmi 读取系统信息”
感谢回复。 windows 下用 wmic 读到不到Sign On message 一行信息。
请教下 BIOS Sign On Message 这行字串,开机后保存在什么地址,在UEFI SHELL 如何读出此行信息,谢谢。
比如:BIOS BIN FILE用AMIBCP查看BIOS Features的Sign On Message为Intel B760 Ver:1.71 BIOS Date: 09/10/2022 15:29:59
在windows下用 wmic 查看不到此行信息,部份信息,不完整:
wmic bios get /value 只读到 BIOS Date: 09/10/2022 15:29:59
wmic baseboard get /value 只读到 B760
wmic csproduct get /value 只读到 B760
噢, Sign On Message 不是一个行业标准,代码中通常也是合成的。所以需要你自己编写代码合成出来。
或者直接写一个工具在 BIOS Binary File 中指定位置查找输出一下
up,VS2019环境/EDK2-2021环境下编译报错时什么原因呢
BaseMemoryLibRepStr.lib(CompareMemWrapper.obj) : error LNK2005: CompareMem already defined in SMBiosTestNew.lib(GetVer.obj)
UefiShellCEntryLib.lib(UefiShellCEntryLib.obj) : error LNK2001: unresolved external symbol ShellAppMain
d:\edk2\edk2-stable202105\Build\OvmfX64\DEBUG_VS2019\X64\OvmfPkg\GetVer\GetVer\DEBUG\SMBiosTestNew.dll : fatal error LNK1120: 1 unresolved externals
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x64\link.exe"' : return code '0x460'
Stop.
你这个是编译自己的代码遇到的问题?我实验过没问题。