UEFI学习——SMBIOS笔记

       SMBIOS是一套规范,对于符合 SMBIOS 规范的计算机,可以通过访问 SMBIOS 的结构获得系统信息,这里对其介绍不再赘述。本篇文章是将我自己对EDKII代码中SMBIOS的结构体的理解进行一个总结,并结合EDKII里的两个函数对读取SMBIOS信息的程序逻辑提供一个大概的思路。

本博客介绍的SMBIOS是使用32位的EPS表(即根据SMBIOS 2.1规范)

1.SMBIOS结构介绍

UEFI学习——SMBIOS笔记_第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字节。

2.SMBIOS相关函数

       要读取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结构的理解,以及对相关函数做的笔记,比较粗糙,有时间会加上代码,用到上面列出的三个函数。

你可能感兴趣的:(UEFI学习,UEFI,SMBIOS,2.1)