SMBIOS是一套规范,对于符合 SMBIOS 规范的计算机,可以通过访问 SMBIOS 的结构获得系统信息,这里对其介绍不再赘述。本篇文章是将我自己对EDKII代码中SMBIOS的结构体的理解进行一个总结,并结合EDKII里的两个函数对读取SMBIOS信息的程序逻辑提供一个大概的思路。
本博客介绍的SMBIOS是使用32位的EPS表(即根据SMBIOS 2.1规范)
上面的图包含了几个独立的结构体,因为在读取SMBIOS信息时会将它们结合起来读取,所以我将他们结合到一张图上。从左到右第一个是SMBIOS的EPS(Entry Point Structure)表的结构体,其结构体内容即简单注释如下:
typedef struct {
UINT8 AnchorString[4]; //关键字 固定是”_SM_”
UINT8 EntryPointStructureChecksum; //校验和 用于校验数据
UINT8 EntryPointLength; //表结构长度 Entry Point Structure 表的长度
UINT8 MajorVersion; //Major版本号 用于判断SMBIOS 版本
UINT8 MinorVersion; //Minor版本号 用于判断SMBIOS 版本
UINT16 MaxStructureSize; //表结构大小
UINT8 EntryPointRevision; //EPS修正
UINT8 FormattedArea[5]; //格式区域 存放解释EPS修正的信息
UINT8 IntermediateAnchorString[5]; //关键字 固定为“_DMI_”
UINT8 IntermediateChecksum; //校验和 Intermediate Entry Point Structure (IEPS)的校验和
UINT16 TableLength; //结构表长度 SMBIOS 结构表的长度(总长度)
UINT32 TableAddress; //结构表地址 SMBIOS 结构表的真实内存位置
UINT16 NumberOfSmbiosStructures; //结构表个数 SMBIOS 结构表数目
UINT8 SmbiosBcdRevision; //Smbios BCD 修正
} SMBIOS_TABLE_ENTRY_POINT;
此结构体中的TableAddress成员存放的就是SMBIOS结构表在内存中的位置,凭借它就可以知道SMBIOS结构表的首地址,并且可以开始读取SMBIOS结构表信息。
中间的是一个联合体(共用体),结构体内容如下:
typedef union {
SMBIOS_STRUCTURE *Hdr;
SMBIOS_TABLE_TYPE0 *Type0;
SMBIOS_TABLE_TYPE1 *Type1;
SMBIOS_TABLE_TYPE2 *Type2;
SMBIOS_TABLE_TYPE3 *Type3;
SMBIOS_TABLE_TYPE4 *Type4;
SMBIOS_TABLE_TYPE5 *Type5;
SMBIOS_TABLE_TYPE6 *Type6;
SMBIOS_TABLE_TYPE7 *Type7;
SMBIOS_TABLE_TYPE8 *Type8;
SMBIOS_TABLE_TYPE9 *Type9;
SMBIOS_TABLE_TYPE10 *Type10;
SMBIOS_TABLE_TYPE11 *Type11;
SMBIOS_TABLE_TYPE12 *Type12;
SMBIOS_TABLE_TYPE13 *Type13;
SMBIOS_TABLE_TYPE14 *Type14;
SMBIOS_TABLE_TYPE15 *Type15;
SMBIOS_TABLE_TYPE16 *Type16;
SMBIOS_TABLE_TYPE17 *Type17;
SMBIOS_TABLE_TYPE18 *Type18;
SMBIOS_TABLE_TYPE19 *Type19;
SMBIOS_TABLE_TYPE20 *Type20;
SMBIOS_TABLE_TYPE21 *Type21;
SMBIOS_TABLE_TYPE22 *Type22;
SMBIOS_TABLE_TYPE23 *Type23;
SMBIOS_TABLE_TYPE24 *Type24;
SMBIOS_TABLE_TYPE25 *Type25;
SMBIOS_TABLE_TYPE26 *Type26;
SMBIOS_TABLE_TYPE27 *Type27;
SMBIOS_TABLE_TYPE28 *Type28;
SMBIOS_TABLE_TYPE29 *Type29;
SMBIOS_TABLE_TYPE30 *Type30;
SMBIOS_TABLE_TYPE31 *Type31;
SMBIOS_TABLE_TYPE32 *Type32;
SMBIOS_TABLE_TYPE33 *Type33;
SMBIOS_TABLE_TYPE34 *Type34;
SMBIOS_TABLE_TYPE35 *Type35;
SMBIOS_TABLE_TYPE36 *Type36;
SMBIOS_TABLE_TYPE37 *Type37;
SMBIOS_TABLE_TYPE38 *Type38;
SMBIOS_TABLE_TYPE39 *Type39;
SMBIOS_TABLE_TYPE40 *Type40;
SMBIOS_TABLE_TYPE41 *Type41;
SMBIOS_TABLE_TYPE42 *Type42;
SMBIOS_TABLE_TYPE43 *Type43;
SMBIOS_TABLE_TYPE126 *Type126;
SMBIOS_TABLE_TYPE127 *Type127;
UINT8 *Raw;
} SMBIOS_STRUCTURE_POINTER;
其第一个成员是SMBIOS_STRUCTURE类型(结构体代码在下面)的变量,SMBIOS结构表的每个Type都有一个此类型的成员,也就是上图最右边的结构体,SMBIOS_STRUCTURE定义了每个SMBIOS_TABLE_TYPE的Type,Length和Handle。SMBIOS_STRUCTURE_POINTER中间的成员定义了SMBIOS结构表的Type,SMBIOS结构表是由一个个Type组成的,每个Type有自己的结构体,但是各个Type结构体的成员不完全一样,最后一个成员UINT8 *Raw在EDKII代码中是要被赋值为EPS结构体中TableAddress成员的内容,也就是SMBIOS结构体初始地址。
第三个结构体内容为:
typedef struct {
SMBIOS_TYPE Type;
UINT8 Length;
SMBIOS_HANDLE Handle;
} SMBIOS_STRUCTURE;
Type表示成员的类型(Type0~Typen),每个结构表都分为格式区域和字符串区域,UINT8 Length的内容只是格式区域的长度, 格式区域就是一些本结构的信息,字符串区域是紧随在格式区域后的一个区域,字符串区域的长度是不固定的。有的结构有字符串区域,有的则没有,字符串以00H结尾,字符串区域也是以00H结尾,所以只要在字符串区域找到连续的0000H,就可以找到下一个Type。
下面是我用RW软件查看本机的SMBIOS信息,以Type 0为例说明结构表的大致分布情况:
Type、Length、Handle在本结构表的前四字节,Length的值是0x1A即本结构表的格式区域大小为26字节。
要读取SMBIOS信息首先要从系统配置表里获得SMBIOS EPS表,所需用到的函数为:
/**
This function searches the list of configuration tables stored in the EFI System
Table for a table with a GUID that matches TableGuid. If a match is found,
then a pointer to the configuration table is returned in Table, and EFI_SUCCESS
is returned. If a matching GUID is not found, then EFI_NOT_FOUND is returned.
@param TableGuid Pointer to table's GUID type..
@param Table Pointer to the table associated with TableGuid in the EFI System Table.
@retval EFI_SUCCESS A configuration table matching TableGuid was found.
@retval EFI_NOT_FOUND A configuration table matching TableGuid could not be found.
**/
EFI_STATUS
EFIAPI
EfiGetSystemConfigurationTable (
IN EFI_GUID *TableGuid,
OUT VOID **Table
);
例:Status = EfiGetSystemConfigurationTable (&gEfiSmbiosTableGuid, (VOID**)&SmbiosEpsTable);
获取到SMBIOS EPS表后就可以找到SMBIOS结构表的起始地址,将EPS表的TableAddress成员赋值给结构表的Raw成员。
SmbiosStruct->Raw = (UINT8 *) (UINTN) (SmbiosEpsTable->TableAddress);
找到起始地址就找到结构表的第一个Type,而要找其他Type就需要另一个函数:
/**
Get SMBIOS structure for the given Handle,
Handle is changed to the next handle or 0xFFFF when the end is
reached or the handle is not found.
@param[in, out] Handle 0xFFFF: get the first structure
Others: get a structure according to this value.
@param[out] Buffer The pointer to the pointer to the structure.
@param[out] Length Length of the structure.
@retval DMI_SUCCESS Handle is updated with next structure handle or
0xFFFF(end-of-list).
@retval DMI_INVALID_HANDLE Handle is updated with first structure handle or
0xFFFF(end-of-list).
**/
EFI_STATUS
LibGetSmbiosStructure (
IN OUT UINT16 *Handle,
OUT UINT8 **Buffer,
OUT UINT16 *Length
);
函数具体内容如下
EFI_STATUS
LibGetSmbiosStructure (
IN OUT UINT16 *Handle,
OUT UINT8 **Buffer,
OUT UINT16 *Length
)
{
SMBIOS_STRUCTURE_POINTER Smbios;
SMBIOS_STRUCTURE_POINTER SmbiosEnd;
UINT8 *Raw;
if (*Handle == INVALID_HANDLE) { //如果handle的值是0xFFFF则返回第一个结构表的Handle
*Handle = mSmbiosStruct->Hdr->Handle;
return DMI_INVALID_HANDLE;
}
if ((Buffer == NULL) || (Length == NULL)) {
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;
//
// Walk to next structure
//
LibGetSmbiosString (&Smbios, (UINT16) (-1)); //此函数返回给定字符串编号的SMBIOS字符串,目前没看懂。。。
//
// Length = Next structure head - this structure head
//
*Length = (UINT16) (Smbios.Raw - Raw);
*Buffer = Raw;
//
// update with the next structure handle.
//
if (Smbios.Raw < SmbiosEnd.Raw) {
*Handle = Smbios.Hdr->Handle;
} else {
*Handle = INVALID_HANDLE;
}
return DMI_SUCCESS;
}
//
// Walk to next structure
//
LibGetSmbiosString (&Smbios, (UINT16) (-1));
}
*Handle = INVALID_HANDLE;
return DMI_INVALID_HANDLE;
}
最后一个要用到的函数是LibGetSmbiosString,其内容如下:
/**
Return SMBIOS string for the given string number.
@param[in] Smbios Pointer to SMBIOS structure.
@param[in] StringNumber String number to return. -1 is used to skip all strings and
point to the next SMBIOS structure.
@return Pointer to string, or pointer to next SMBIOS strcuture if StringNumber == -1
**/
CHAR8*
LibGetSmbiosString (
IN SMBIOS_STRUCTURE_POINTER *Smbios,
IN UINT16 StringNumber
)
{
UINT16 Index;
CHAR8 *String;
ASSERT (Smbios != NULL);
//
// Skip over formatted section
//
String = (CHAR8 *) (Smbios->Raw + Smbios->Hdr->Length);
//
// Look through unformated section
//
for (Index = 1; Index <= StringNumber; Index++) {
if (StringNumber == Index) {
return String;
}
//
// Skip string
//
for (; *String != 0; String++);
String++;
if (*String == 0) {
//
// If double NULL then we are done.
// Return pointer to next structure in Smbios.
// if you pass in a -1 you will always get here
//
Smbios->Raw = (UINT8 *)++String;
return NULL;
}
}
return NULL;
}
在EDKII源码里这个函数都是这样被调用的LibGetSmbiosString (&Smbios, (UINT16) (-1));,第二个参数是-1,查看其参数说明,是这样解释的-1 is used to skip all strings and point to the next SMBIOS structure.(-1用于跳过所有字符串,指向下一个SMBIOS结构。)此函数在源码里应该是用来跳过字符串区域,从而可以找到下一个Type。
以上是我对SMBIOS结构的理解,以及对相关函数做的笔记,比较粗糙,有时间会加上代码,用到上面列出的三个函数。