SMBIOS的定义
SMBIOS的全称是System Management BIOS,关于它的理解包括:
- 它不是一个BIOS,之所以出现了BIOS字样,是因为它跟BIOS有关,仅此而已。
- 它是一个规范,定义了BIOS传递给操作系统的系统管理信息,具体有哪些信息,可以参考SMBIOS规范。
- 它也可以表示一系列的数据结构,包含了各类信息,由BIOS启动过程中创建并放到特定的内存,之后操作系统可以拿来用。
如果到这里还不是很明白,最好的办法就是查看SMBIOS规范,可以在系统管理BIOS(SMBIOS) | DMTF下载到规范的各个版本文档,文档中定义了各种类型的数据,每一个类型称为一个Type,通过Type x(x表示一个数字)来定义各个不同类型的数据。比如Type 0表示的是BIOS信息(注意这个截图不全,后面的内容略去了):
其中包含了一些BIOS的基础信息,比如BIOS的版本,BIOS的供应商,等等。其中一部分是字符串,而另外一些是固定的数据类型。
不过需要注意,当下SMBIOS有几个不同的版本,比如SMBIOS2.x版本是32位的,SMBIOS3.x是64位的。且对于UEFI和Legacy版本的BIOS,SMBIOS数据存放的位置也不尽相同。本文只关注目前最常用的一种,即UEFI BIOS传递的SMBIOS3.x版本的SMBIOS。
查看SMBIOS
我们可以通过一些基本的工具查看SMBIOS的信息,比如Linux下可以通过dmidecode
命令:
再比如Windows下可以通过RW工具(RWEverything – Read & Write Everything)查看:
至于操作系统下是如何找到这段空间的,可以根据规范中的说法:
On UEFI-based systems, the SMBIOS Entry Point structure can be located by looking in the EFI 738 Configuration Table for the SMBIOS 3.x GUID (SMBIOS3_TABLE_GUID, {F2FD1544-9794-4A2C-992E-739 E5BBCF20E394}) and using the associated pointer.
也就是说,可以通过UEFI的接口并指定GUID来查找。这个GUID可以在Linux的源代码(include/linux/efi.h)中找到:
#define SMBIOS3_TABLE_GUID EFI_GUID(0xf2fd1544, 0x9794, 0x4a2c, 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94)
由于只有Linux有源码(注意是找Linux的源码,而不是dmidecode
的源码,后者只是解析了Linux生成的DMI表而已),所以下面就根据它来确定操作系统是如何找到SMBIOS信息的。通过上述的GUID,可以找到内核会获取UEFI下的很多表,SMBIOS是其中的一张,对应到全局变量common_tables
:
static const efi_config_table_type_t common_tables[] __initconst = {
{ACPI_20_TABLE_GUID, &efi.acpi20, "ACPI 2.0" },
{ACPI_TABLE_GUID, &efi.acpi, "ACPI" },
{SMBIOS_TABLE_GUID, &efi.smbios, "SMBIOS" },
{SMBIOS3_TABLE_GUID, &efi.smbios3, "SMBIOS 3.0" }, // 这里存放SMBIOS3.x的数据,可以看到前面还有一个SMBIOS2.x的
上述代码中efi
对应的结构体:
/*
* All runtime access to EFI goes through this structure:
*/
extern struct efi {
const efi_runtime_services_t *runtime; /* EFI runtime services table */
unsigned int runtime_version; /* Runtime services version */
unsigned int runtime_supported_mask;
unsigned long acpi; /* ACPI table (IA64 ext 0.71) */
unsigned long acpi20; /* ACPI table (ACPI 2.0) */
unsigned long smbios; /* SMBIOS table (32 bit entry point) */
unsigned long smbios3; /* SMBIOS table (64 bit entry point) */
这些参数的赋值来自函数efi_config_parse_tables()
:
int __init efi_config_parse_tables(const efi_config_table_t *config_tables, // 注意这个参数
int count,
const efi_config_table_type_t *arch_tables)
{
const efi_config_table_64_t *tbl64 = (void *)config_tables;
const efi_config_table_32_t *tbl32 = (void *)config_tables;
const efi_guid_t *guid;
unsigned long table;
int i;
pr_info("");
for (i = 0; i < count; i++) {
if (!IS_ENABLED(CONFIG_X86)) {
guid = &config_tables[i].guid;
table = (unsigned long)config_tables[i].table;
} else if (efi_enabled(EFI_64BIT)) {
guid = &tbl64[i].guid;
table = tbl64[i].table;
if (IS_ENABLED(CONFIG_X86_32) &&
tbl64[i].table > U32_MAX) {
pr_cont("\n");
pr_err("Table located above 4GB, disabling EFI.\n");
return -EINVAL;
}
} else {
guid = &tbl32[i].guid;
table = tbl32[i].table;
}
if (!match_config_table(guid, table, common_tables) && arch_tables)
match_config_table(guid, table, arch_tables);
}
重点在于通过config_tables
这个变量中的值来给common_tables
中的成员赋值,而前者是BIOS传递过去的,它是UEFI System Table中的一部分(下面是BIOS代码了):
///
/// EFI System Table
///
typedef struct {
/// 前面的略
///
/// The number of system configuration tables in the buffer ConfigurationTable.
///
UINTN NumberOfTableEntries;
///
/// A pointer to the system configuration tables.
/// The number of entries in the table is NumberOfTableEntries.
///
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
而EFI_CONFIGURATION_TABLE
结构体如下:
///
/// Contains a set of GUID/pointer pairs comprised of the ConfigurationTable field in the
/// EFI System Table.
///
typedef struct {
///
/// The 128-bit GUID value that uniquely identifies the system configuration table.
///
EFI_GUID VendorGuid;
///
/// A pointer to the table associated with VendorGuid.
///
VOID *VendorTable;
} EFI_CONFIGURATION_TABLE;
前一个参数是GUID,后一个参数是地址,这就跟Linux代码中的efi_config_table_type_t
结构体对应起来了。
总结来说就是:BIOS传递UEFI System Table中的Configuration Table给内核,内核遍历这个表,找到对应GUID的地址,这样就可以访问对应GUID的表的数据了。至于BIOS是如何传递这个Configuration Table给内核的,这部分不是本文的重点,所以不会介绍;而BIOS是如何填充这个System Table的Configuration Table,将在后面的SMBIOS的实现中进一步说明。
SMBIOS的实现
BIOS下SMBIOS实现的基本模块是edk2\MdeModulePkg\Universal\SmbiosDxe\SmbiosDxe.inf,其入口是SmbiosDriverEntryPoint()
,它主要包括以下的几个步骤:
- 初始化
mPrivateData
:
mPrivateData.Signature = SMBIOS_INSTANCE_SIGNATURE;
mPrivateData.Smbios.Add = SmbiosAdd;
mPrivateData.Smbios.UpdateString = SmbiosUpdateString;
mPrivateData.Smbios.Remove = SmbiosRemove;
mPrivateData.Smbios.GetNext = SmbiosGetNext;
mPrivateData.Smbios.MajorVersion = (UINT8)(PcdGet16 (PcdSmbiosVersion) >> 8);
mPrivateData.Smbios.MinorVersion = (UINT8)(PcdGet16 (PcdSmbiosVersion) & 0x00ff);
InitializeListHead (&mPrivateData.DataListHead);
InitializeListHead (&mPrivateData.AllocatedHandleListHead);
EfiInitializeLock (&mPrivateData.DataLock, TPL_NOTIFY);
其中包含Protocol、列表和锁。后面两个是代码实现相关的,而第一个是安装之后被其它模块使用的,后面会进一步介绍。
- 安装SMBIOS接口,就是上一步初始化的Protocol:
//
// Make a new handle and install the protocol
//
mPrivateData.Handle = NULL;
Status = gBS->InstallProtocolInterface (
&mPrivateData.Handle,
&gEfiSmbiosProtocolGuid,
EFI_NATIVE_INTERFACE,
&mPrivateData.Smbios
);
- 判断是否已经有SMBIOS数据了,如果有就添加到SMBIOS数据中。数据是通过HOB的方式传递到这模块的,之所以会有这样的HOB,是因为UEFI BIOS可以作为Payload在coreboot、Slimbootloader等中使用,后者负责硬件初始化,所以包含了硬件信息,它们将其包装成HOB传递给UEFI Payload,这样SMBIOS数据才能够使用。上述的操作来自函数
RetrieveSmbiosFromHob()
:
EFI_STATUS
RetrieveSmbiosFromHob (
IN EFI_HANDLE ImageHandle
)
{
for (Index = 0; Index < ARRAY_SIZE (mIsSmbiosTableValid); Index++) {
GuidHob = GetFirstGuidHob (mIsSmbiosTableValid[Index].Guid);
if (GuidHob == NULL) {
continue;
}
GenericHeader = (UNIVERSAL_PAYLOAD_GENERIC_HEADER *)GET_GUID_HOB_DATA (GuidHob);
if ((sizeof (UNIVERSAL_PAYLOAD_GENERIC_HEADER) <= GET_GUID_HOB_DATA_SIZE (GuidHob)) && (GenericHeader->Length <= GET_GUID_HOB_DATA_SIZE (GuidHob))) {
if (GenericHeader->Revision == UNIVERSAL_PAYLOAD_SMBIOS_TABLE_REVISION) {
//
// UNIVERSAL_PAYLOAD_SMBIOS_TABLE structure is used when Revision equals to UNIVERSAL_PAYLOAD_SMBIOS_TABLE_REVISION
//
SmBiosTableAdress = (UNIVERSAL_PAYLOAD_SMBIOS_TABLE *)GET_GUID_HOB_DATA (GuidHob);
if (GenericHeader->Length >= UNIVERSAL_PAYLOAD_SIZEOF_THROUGH_FIELD (UNIVERSAL_PAYLOAD_SMBIOS_TABLE, SmBiosEntryPoint)) {
if (mIsSmbiosTableValid[Index].IsValid ((VOID *)(UINTN)SmBiosTableAdress->SmBiosEntryPoint, &TableAddress, &TableMaximumSize, &MajorVersion, &MinorVersion)) {
Smbios.Raw = TableAddress;
Status = ParseAndAddExistingSmbiosTable (ImageHandle, Smbios, TableMaximumSize, MajorVersion, MinorVersion);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "RetrieveSmbiosFromHob: Failed to parse preinstalled tables from Guid Hob\n"));
Status = EFI_UNSUPPORTED;
} else {
return EFI_SUCCESS;
}
}
}
}
}
}
}
重点是ParseAndAddExistingSmbiosTable()
这个函数,而它会进一步调用SmbiosAdd()
,后者会构建SMBIOS表:
//
// Some UEFI drivers (such as network) need some information in SMBIOS table.
// Here we create SMBIOS table and publish it in
// configuration table, so other UEFI drivers can get SMBIOS table from
// configuration table without depending on PI SMBIOS protocol.
//
SmbiosTableConstruction (Smbios32BitTable, Smbios64BitTable);
其实现:
VOID
EFIAPI
SmbiosTableConstruction (
BOOLEAN Smbios32BitTable,
BOOLEAN Smbios64BitTable
)
{
if (Smbios32BitTable) {
Status = SmbiosCreateTable ((VOID **)&Eps);
if (!EFI_ERROR (Status)) {
gBS->InstallConfigurationTable (&gEfiSmbiosTableGuid, Eps);
}
}
if (Smbios64BitTable) {
Status = SmbiosCreate64BitTable ((VOID **)&Eps64Bit);
if (!EFI_ERROR (Status)) {
gBS->InstallConfigurationTable (&gEfiSmbios3TableGuid, Eps64Bit);
}
}
}
以SMBIOS3.x为例,它创建SMBIOS表:
EFI_STATUS
EFIAPI
SmbiosCreate64BitTable (
OUT VOID **TableEntryPointStructure
)
{
UINT8 *BufferPointer;
UINTN RecordSize;
UINTN NumOfStr;
EFI_STATUS Status;
EFI_SMBIOS_HANDLE SmbiosHandle;
EFI_SMBIOS_PROTOCOL *SmbiosProtocol;
EFI_PHYSICAL_ADDRESS PhysicalAddress;
EFI_SMBIOS_TABLE_HEADER *SmbiosRecord;
EFI_SMBIOS_TABLE_END_STRUCTURE EndStructure;
EFI_SMBIOS_ENTRY *CurrentSmbiosEntry;
Status = EFI_SUCCESS;
BufferPointer = NULL;
if (Smbios30EntryPointStructure == NULL) {
//
// Initialize the Smbios30EntryPointStructure with initial values.
// It should be done only once.
// Allocate memory at any address.
//
DEBUG ((DEBUG_INFO, "SmbiosCreateTable: Initialize 64-bit entry point structure\n"));
Smbios30EntryPointStructureData.MajorVersion = mPrivateData.Smbios.MajorVersion;
Smbios30EntryPointStructureData.MinorVersion = mPrivateData.Smbios.MinorVersion;
Smbios30EntryPointStructureData.DocRev = PcdGet8 (PcdSmbiosDocRev);
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiRuntimeServicesData,
EFI_SIZE_TO_PAGES (sizeof (SMBIOS_TABLE_3_0_ENTRY_POINT)),
&PhysicalAddress
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SmbiosCreate64BitTable() could not allocate Smbios30EntryPointStructure\n"));
return EFI_OUT_OF_RESOURCES;
}
Smbios30EntryPointStructure = (SMBIOS_TABLE_3_0_ENTRY_POINT *)(UINTN)PhysicalAddress;
CopyMem (
Smbios30EntryPointStructure,
&Smbios30EntryPointStructureData,
sizeof (SMBIOS_TABLE_3_0_ENTRY_POINT)
);
}
// 中间略
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiRuntimeServicesData,
EFI_SIZE_TO_PAGES (Smbios30EntryPointStructure->TableMaximumSize),
&PhysicalAddress
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SmbiosCreateTable() could not allocate SMBIOS 64-bit table\n"));
Smbios30EntryPointStructure->TableAddress = 0;
return EFI_OUT_OF_RESOURCES;
} else {
Smbios30EntryPointStructure->TableAddress = PhysicalAddress;
mPre64BitAllocatedPages = EFI_SIZE_TO_PAGES (Smbios30EntryPointStructure->TableMaximumSize);
}
这里从EfiRuntimeServicesData
这个类型的内存段中分配了内存,这很重要,因为其它的内存类型操作系统可能是无法访问的。
然后安装:
gBS->InstallConfigurationTable (&gEfiSmbios3TableGuid, Eps64Bit);
所谓的安装其实就是放到UEFI System Table中的Configuration Table而已。
到这里就跟前面获取SMBIOS数据联系起来了。不过还有一些内容需要补充。
- 首先是分配内存的代码:
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiRuntimeServicesData,
EFI_SIZE_TO_PAGES (sizeof (SMBIOS_TABLE_3_0_ENTRY_POINT)),
&PhysicalAddress
);
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiRuntimeServicesData,
EFI_SIZE_TO_PAGES (Smbios30EntryPointStructure->TableMaximumSize),
&PhysicalAddress
);
这里有多次不同的分配,第一次中包含结构体SMBIOS_TABLE_3_0_ENTRY_POINT
,它一般被称为SMBIOS Entry,下面是SMBIOS3.x版本:
typedef struct {
UINT8 AnchorString[SMBIOS_3_0_ANCHOR_STRING_LENGTH];
UINT8 EntryPointStructureChecksum;
UINT8 EntryPointLength;
UINT8 MajorVersion;
UINT8 MinorVersion;
UINT8 DocRev;
UINT8 EntryPointRevision;
UINT8 Reserved;
UINT32 TableMaximumSize;
UINT64 TableAddress;
} SMBIOS_TABLE_3_0_ENTRY_POINT;
这些成员的说明如下:
由于每个成员都比较简单,这里不再赘述。
这个结构体不大,不过实际分配的内存经过了EFI_SIZE_TO_PAGES
,所以大小最小是4K。SMBIOS_TABLE_3_0_ENTRY_POINT
之后就跟随着其它的SMBIOS数据。但是4K并不一定够存放所有的SMBIOS数据,实际上规范中有对SMBIOS数据的最大尺寸作定义:
//
// The length of the entire structure table (including all strings) must be reported
// in the Structure Table Length field of the SMBIOS Structure Table Entry Point,
// which is a WORD field limited to 65,535 bytes.
//
#define SMBIOS_TABLE_MAX_LENGTH 0xFFFF
所以当数据不够的时候,还会重新分配内存,使用的大小是Smbios30EntryPointStructure->TableMaximumSize
的4K对齐版本。
- 其次,
SmbiosTableConstruction()
会被很多的SMBIOS接口调用:
因为保存了SMBIOS数据的HOB并不一定存在,此时就需要调用上述的基本接口来完成最终的SmbiosTableConstruction()
。
BIOS下的SMBIOS接口
SMBIOS的接口由一个Protocol完成:
struct _EFI_SMBIOS_PROTOCOL {
EFI_SMBIOS_ADD Add;
EFI_SMBIOS_UPDATE_STRING UpdateString;
EFI_SMBIOS_REMOVE Remove;
EFI_SMBIOS_GET_NEXT GetNext;
UINT8 MajorVersion; ///< The major revision of the SMBIOS specification supported.
UINT8 MinorVersion; ///< The minor revision of the SMBIOS specification supported.
};
其中Add
用来增加SMBIOS类型,UpdateString
用来更新现有的SMBIOS类型,Remove
用来删除已有的SMBIOS类型,GetNext()
用来遍历SMBIOS类型。这些接口本身都比较简单,这里将通过代码举例说明。
Add
Add
接口用于新增SMBIOS类型,其接口如下:
/**
Add an SMBIOS record.
This function allows any agent to add SMBIOS records. The caller is responsible for ensuring
Record is formatted in a way that matches the version of the SMBIOS specification as defined in
the MajorRevision and MinorRevision fields of the EFI_SMBIOS_PROTOCOL.
Record must follow the SMBIOS structure evolution and usage guidelines in the SMBIOS
specification. Record starts with the formatted area of the SMBIOS structure and the length is
defined by EFI_SMBIOS_TABLE_HEADER.Length. Each SMBIOS structure is terminated by a
double-null (0x0000), either directly following the formatted area (if no strings are present) or
directly following the last string. The number of optional strings is not defined by the formatted area,
but is fixed by the call to Add(). A string can be a place holder, but it must not be a NULL string as
two NULL strings look like the double-null that terminates the structure.
@param[in] This The EFI_SMBIOS_PROTOCOL instance.
@param[in] ProducerHandle The handle of the controller or driver associated with the SMBIOS information. NULL means no handle.
@param[in, out] SmbiosHandle On entry, the handle of the SMBIOS record to add. If FFFEh, then a unique handle
will be assigned to the SMBIOS record. If the SMBIOS handle is already in use,
EFI_ALREADY_STARTED is returned and the SMBIOS record is not updated.
@param[in] Record The data for the fixed portion of the SMBIOS record. The format of the record is
determined by EFI_SMBIOS_TABLE_HEADER.Type. The size of the formatted
area is defined by EFI_SMBIOS_TABLE_HEADER.Length and either followed
by a double-null (0x0000) or a set of null terminated strings and a null.
@retval EFI_SUCCESS Record was added.
@retval EFI_OUT_OF_RESOURCES Record was not added.
@retval EFI_ALREADY_STARTED The SmbiosHandle passed in was already in use.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SMBIOS_ADD)(
IN CONST EFI_SMBIOS_PROTOCOL *This,
IN EFI_HANDLE ProducerHandle OPTIONAL,
IN OUT EFI_SMBIOS_HANDLE *SmbiosHandle,
IN EFI_SMBIOS_TABLE_HEADER *Record
);
其中的重要参数是后面两个,第一个参数SmbiosHandle
对于Add
接口来说,其值是固定的SMBIOS_HANDLE_PI_RESERVED
,这个宏的说明如下:
///
/// Reference SMBIOS 2.7, chapter 6.1.2.
/// The UEFI Platform Initialization Specification reserves handle number FFFEh for its
/// EFI_SMBIOS_PROTOCOL.Add() function to mean "assign an unused handle number automatically."
/// This number is not used for any other purpose by the SMBIOS specification.
///
#define SMBIOS_HANDLE_PI_RESERVED 0xFFFE
第二个参数Record
就是需要添加的SMBIOS类型的数据。
下面是一个代码示例,用于新增SMIBOS Type11:
EFI_STATUS
EFIAPI
SmbiosTestDxeEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_ABORTED;
EFI_SMBIOS_PROTOCOL *Smbios = NULL;
UINTN StringSize = 0;
UINTN Size = 0;
EFI_SMBIOS_TABLE_HEADER *Record = NULL;
CHAR8 *Str = NULL;
EFI_SMBIOS_HANDLE SmbiosHandle = 0;
Status = gBS->LocateProtocol (
&gEfiSmbiosProtocolGuid,
NULL,
(VOID **)(&Smbios)
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
return Status;
}
Size = gSmbiosType11Template.Hdr.Length;
StringSize = AsciiStrSize (gSmbiosType11Strings);
Size += StringSize;
Size += 1;
Record = (EFI_SMBIOS_TABLE_HEADER *)(AllocateZeroPool (Size));
if (NULL == Record) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Out of memory\n", __FUNCTION__, __LINE__));
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Record, &gSmbiosType11Template, gSmbiosType11Template.Hdr.Length);
Str = ((CHAR8 *)Record) + Record->Length;
CopyMem (Str, gSmbiosType11Strings, StringSize);
SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
Status = Smbios->Add (
Smbios,
NULL,
&SmbiosHandle,
Record
);
return Status;
}
Type11是SMBIOS规范中比较简单的一个,其初始结构体:
///
/// OEM Strings (Type 11).
/// This structure contains free form strings defined by the OEM. Examples of this are:
/// Part Numbers for Reference Documents for the system, contact information for the manufacturer, etc.
///
typedef struct {
SMBIOS_STRUCTURE Hdr;
UINT8 StringCount;
} SMBIOS_TABLE_TYPE11;
对应到代码中的是gSmbiosType11Template
:
SMBIOS_TABLE_TYPE11 gSmbiosType11Template = {
{ EFI_SMBIOS_TYPE_OEM_STRINGS, sizeof (SMBIOS_TABLE_TYPE11), 0 },
1 // StringCount
};
前一行是SMBIOS头部,所有SMBIOS Type都是固定的,后面是一个StringCount
,这里只添加一个字符串,所以值是1。再之后的内容没有明确定义,不过显而易见就是一个字符串,根据这个字符串的大小,最终能够计算出SMBIOS Type11的大小,额外需要注意的是,最后需要两个'\0'
,所以有如下的代码:
Size += 1; // 字符串本身已经有一个'\0'了,还需要添加一个'\0'
最终通过Add
接口将SMBIOS Type11添加到SMBIOS数据库中。通过BIOS Shell下的smbiosview
命令可以查看:
GetNext
GetNext()
用于获取SMBIOS类型,其原型如下:
/**
Allow the caller to discover all or some of the SMBIOS records.
This function allows all of the SMBIOS records to be discovered. It's possible to find
only the SMBIOS records that match the optional Type argument.
@param[in] This The EFI_SMBIOS_PROTOCOL instance.
@param[in, out] SmbiosHandle On entry, points to the previous handle of the SMBIOS record. On exit, points to the
next SMBIOS record handle. If it is FFFEh on entry, then the first SMBIOS record
handle will be returned. If it returns FFFEh on exit, then there are no more SMBIOS records.
@param[in] Type On entry, it points to the type of the next SMBIOS record to return. If NULL, it
indicates that the next record of any type will be returned. Type is not
modified by the this function.
@param[out] Record On exit, points to a pointer to the the SMBIOS Record consisting of the formatted area
followed by the unformatted area. The unformatted area optionally contains text strings.
@param[out] ProducerHandle On exit, points to the ProducerHandle registered by Add(). If no
ProducerHandle was passed into Add() NULL is returned. If a NULL pointer is
passed in no data will be returned.
@retval EFI_SUCCESS SMBIOS record information was successfully returned in Record.
SmbiosHandle is the handle of the current SMBIOS record
@retval EFI_NOT_FOUND The SMBIOS record with SmbiosHandle was the last available record.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SMBIOS_GET_NEXT)(
IN CONST EFI_SMBIOS_PROTOCOL *This,
IN OUT EFI_SMBIOS_HANDLE *SmbiosHandle,
IN EFI_SMBIOS_TYPE *Type OPTIONAL,
OUT EFI_SMBIOS_TABLE_HEADER **Record,
OUT EFI_HANDLE *ProducerHandle OPTIONAL
);
其中比较重要的是中间三个参数:第一个参数SmbiosHandle
作为输入就是一个SMBIOS_HANDLE_PI_RESERVED
,表示找到第一个匹配的SMBIOS类型,然后作为输出就是一个特定的表示该SMBIOS的值,而如果返回的值是SMBIOS_HANDLE_PI_RESERVED
,则表示遍历SMBIOS结束了;第二个参数Type
表示要找的SMBIOS类型,如果没有指定,就表示直接返回下一个任意类型的SMBIOS;第三个参数Record
就是返回的SMBIOS类型数据。
本例中获取SMBIOS Type0:
SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
Type = SMBIOS_TYPE_BIOS_INFORMATION;
Status = Smbios->GetNext (
Smbios,
&SmbiosHandle,
&Type,
&Header,
NULL
);
if (!EFI_ERROR (Status)) {
Type0 = (SMBIOS_TABLE_TYPE0 *)Header;
Str = ((CHAR8 *)Type0) + Type0->Hdr.Length;
DEBUG ((EFI_D_ERROR, "Vendor: %a\n", Str));
Str += AsciiStrSize (Str);
DEBUG ((EFI_D_ERROR, "BiosVersion: %a\n", Str));
}
得到的结果:
Vendor: EDK II
BiosVersion: unknown
跟BIOS Shell下的比较:
两者是一致的。
这里需要注意一点,即获取字符串字段的方式:
Type0 = (SMBIOS_TABLE_TYPE0 *)Header;
Str = ((CHAR8 *)Type0) + Type0->Hdr.Length;
DEBUG ((EFI_D_ERROR, "Vendor: %a\n", Str));
而不是:
DEBUG ((EFI_D_ERROR, "Vendor: %a\n", Type0->Vendor));
因为这里的Vendor
并不是字符串,仅仅是一个数字:
///
/// Text strings associated with a given SMBIOS structure are returned in the dmiStrucBuffer, appended directly after
/// the formatted portion of the structure. This method of returning string information eliminates the need for
/// application software to deal with pointers embedded in the SMBIOS structure. Each string is terminated with a null
/// (00h) BYTE and the set of strings is terminated with an additional null (00h) BYTE. When the formatted portion of
/// a SMBIOS structure references a string, it does so by specifying a non-zero string number within the structure's
/// string-set. For example, if a string field contains 02h, it references the second string following the formatted portion
/// of the SMBIOS structure. If a string field references no string, a null (0) is placed in that string field. If the
/// formatted portion of the structure contains string-reference fields and all the string fields are set to 0 (no string
/// references), the formatted section of the structure is followed by two null (00h) BYTES.
///
typedef UINT8 SMBIOS_TABLE_STRING;
而它的值是1,表示第一个字符串。而BiosVersion
是第二个字符串,以此类推,下面所有SMBIOS_TABLE_STRING
类型的都表示字符串的一个Index,而Index也意味着本结构体之后的字符串的堆叠顺序。
///
/// BIOS Information (Type 0).
///
typedef struct {
SMBIOS_STRUCTURE Hdr;
SMBIOS_TABLE_STRING Vendor; // 字符串1
SMBIOS_TABLE_STRING BiosVersion; // 字符串2
UINT16 BiosSegment;
SMBIOS_TABLE_STRING BiosReleaseDate; // 字符串3
UINT8 BiosSize;
MISC_BIOS_CHARACTERISTICS BiosCharacteristics;
UINT8 BIOSCharacteristicsExtensionBytes[2];
UINT8 SystemBiosMajorRelease;
UINT8 SystemBiosMinorRelease;
UINT8 EmbeddedControllerFirmwareMajorRelease;
UINT8 EmbeddedControllerFirmwareMinorRelease;
//
// Add for smbios 3.1.0
//
EXTENDED_BIOS_ROM_SIZE ExtendedBiosSize;
} SMBIOS_TABLE_TYPE0;
因此,上述结构体对应到到真正的SMBIOS数据如下:
注意每个字符串都以'\0'
结尾,而SMBIOS数据的最后还有一个'\0'
。因此为了打印这些字符串需要用指针+字符串长度的方式。
UpdateString
UpdateString
接口用于更新原有的SMBIOS类型,其原型如下:
/**
Update the string associated with an existing SMBIOS record.
This function allows the update of specific SMBIOS strings. The number of valid strings for any
SMBIOS record is defined by how many strings were present when Add() was called.
@param[in] This The EFI_SMBIOS_PROTOCOL instance.
@param[in] SmbiosHandle SMBIOS Handle of structure that will have its string updated.
@param[in] StringNumber The non-zero string number of the string to update.
@param[in] String Update the StringNumber string with String.
@retval EFI_SUCCESS SmbiosHandle had its StringNumber String updated.
@retval EFI_INVALID_PARAMETER SmbiosHandle does not exist.
@retval EFI_UNSUPPORTED String was not added because it is longer than the SMBIOS Table supports.
@retval EFI_NOT_FOUND The StringNumber.is not valid for this SMBIOS record.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SMBIOS_UPDATE_STRING)(
IN CONST EFI_SMBIOS_PROTOCOL *This,
IN EFI_SMBIOS_HANDLE *SmbiosHandle,
IN UINTN *StringNumber,
IN CHAR8 *String
);
其中比较重要的是后面三个参数:第一个参数SmbiosHandle
是通过GetNext()
获取到的SMBIOS类型对应的特征值;第二个参数是SMBIOS类型中对应字符串的Index(从1开始,不过实际上也不用特别指定数值,后面会说明);第三个参数是需要更新的字符串。
在GetNext章节中,获取SMBIOS Type0的结果中可以看到BIOS Version是unknown
,这里将编写代码更新该值。
StringIndex = Type0->BiosVersion;
Status = Smbios->UpdateString (
Smbios,
&SmbiosHandle, // 通过GetNext()返回的值
&StringIndex,
"V1.0.0" // 更新之后的字符串
);
本代码中直接用到了Type0->BiosVersion
作为Index,这样就不需要自己去指定数值了。更新之后的结果:
需要注意这个接口只是用来更新SMBIOS中的字符串的,那么非字符串的普通接口如何更新呢?答案很简单,GetNext()
获取到结构体之后直接修改即可。