有朋友提出一个问题: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.
你这个是编译自己的代码遇到的问题?我实验过没问题。