SMBIOS

SMBIOS(System Management BIOS) 规范是由DMTF(Distributed Management Task Force) 制定,目的是在英特尔

架构系统中向系统中向系统管理软件提供主板和硬件厂商信息。第一个版本是在1995年9月14号发布的。根据SMBIOS

规范,BIOS 开发者可以创建包含硬件和其他系统信息SMBIOS 表。

SMBIOS 表以一个标准的数据结构作为起始(SMBIOS Table Entry Point) ,在EFI 系统中,SMBIOS 表的起始地址可以通

过SMBIOS GUID 在EFI 配置表中找到,

    SMBIOS 表中SMBIOS 记录总个数可以从SMBIOS 表的起始数据结构(SMBIOS Entry Point Structure) 中获得。应用

程序可以从SMBIOS 表的起始数据结构中获得所有SMBIOS记录的起始地址,并且通过遍历每一个SMBIOS记录来获得各

种系统信息。

      每一个SMBIOS 记录包含两个部分,第一个是SMBIOS 规范定义的部分(formatted section), 该部分是必须存在的,

另一个是非规范定义的部分(unformed section), 该部分是可选的,SMBIOS 规范没有定义该部分的内容和长度。这个

区域一般用来存放SMBIOS 记录中的全部字符串内容。每一个SMBIOS 记录都必须以0000h做为结束标志。

    

EFI_SMBIOS_PROTOCOL 数据结构

     EFI_SMBIOS_PROTOCOL 是PI 规范中定义的协议。它给用户提供了接口用来添加,获取或者都删除

SMBIOS表。产生EFI_SMBIOS_PROTOCOL 协议的驱动负责创建SMBIOS 表并且将SMBIOS 表的指针

安装到EFI 系统配置表(EFI System Configuration Table) 中。

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 记录(SMBIOS Record) 到SMBIOS 表中。

UpdatedString: 更新某条SMBIOS 记录中字符串内容

Remove: 删除某条SMBIOS 记录。

GetNext: 遍历所有的SMBIOS 记录

EFI_SMBIOS_ENTRY 数据结构

所有SMBIOS 记录构成一个单向循环链表,链表中每一个节点的数据结构定义如下.

RecordHeader 指向某条SMIBOS 记录,link 用来构成该单向循环链表,并且可以通过SMBIOS_ENTRY_FROM_LINK

来获取link 所在的数据结构SMBIOS_FROM_LINK.

EFI_SMBIOS_ENTRY 定义在 SmbiosDxe.h

#define EFI_SMBIOS_ENTRY_SIGNATURE  SIGNATURE_32 ('S', 'r', 'e', 'c')
typedef struct {
  UINT32                    Signature;
  LIST_ENTRY                Link;
  EFI_SMBIOS_RECORD_HEADER  *RecordHeader;
  UINTN                     RecordSize;
  //
  // Indicate which table this record is added to.
  //
  BOOLEAN                   Smbios32BitTable;
  BOOLEAN                   Smbios64BitTable;
} EFI_SMBIOS_ENTRY;

SMBIOS_INSTANCE 数据结构

实现EFI_SMBIOS_PROTOCOL 协议的驱动定义了SMBIOS_INSTANCE 内部数据结构。

Signature 用来标识这个数据结构,其定义如下:

#define SMBIOS_INSTANCE_SIGNATURE SIGNATURE_32 ('S', 'B', 'i', 's')
typedef struct {
  UINT32                Signature;
  EFI_HANDLE            Handle;
  //
  // Produced protocol
  //
  EFI_SMBIOS_PROTOCOL   Smbios;
  //
  // Updates to record list must be locked.
  //
  EFI_LOCK              DataLock;
  //
  // List of EFI_SMBIOS_ENTRY structures.
  //
  LIST_ENTRY            DataListHead;
  //
  // List of allocated SMBIOS handle.
  //
  LIST_ENTRY            AllocatedHandleListHead;
} SMBIOS_INSTANCE;

Handle 用来安装EFI_SMBIOS_PROTOCOL 协议。Smbios 是EFI_SMBIOS_PROTOCOL

协议的实现,DataListHead 是由多个SMBIOS 记录组成的链表。AllocatedHandleListHead

用来记录已经分配组SMBIOS 记录的句柄(handle).

SMBIOS_HANDLE_ENTRY 数据结构

SMBIOS_HANDLE_ENTRY 数据结构组成一个链表,记录了已经使用的SMBIOS 句柄。

我们可以通过查找这个链表知道一个SMBIOS 句柄是否已经被一个SMBIOS 记录使用。可

以通过SMBIOS_HANDLE_ENTRY_FROM_LINK 来获取Link 所在的数据结构

SMBIOS_HANDLE_ENTRY.

SMBIOS_HANDLE_ENTRY 定义在SmbiosDxe.h

//
// Private data to contain the Smbios handle that already allocated.
//
#define SMBIOS_HANDLE_ENTRY_SIGNATURE  SIGNATURE_32 ('S', 'h', 'r', 'd')

typedef struct {
  UINT32               Signature;
  LIST_ENTRY           Link;
  //
  // Filter driver will register what record guid filter should be used.
  //
  EFI_SMBIOS_HANDLE    SmbiosHandle;

} SMBIOS_HANDLE_ENTRY;

EFI_SMBIOS_TABLE_HEADER 数据结构

每一个SMBIOS 记录都是以该数据结构作为开始,然后是每一个SMBIOS 记录特定的数据。

Type 是SMBIOS 记录的类型,0-127 是SMBIOS 规范使用的,128-256 可以让BIOS 厂商

使用。Length 是该SMBIOS 记录的大小。Handle 是标识该SMBIOS 记录唯一的句柄,每

一个SMBIOS 记录的句柄都是不相同的。


///
/// The Smbios structure header.
///
typedef struct {
  SMBIOS_TYPE    Type;
  UINT8          Length;
  SMBIOS_HANDLE  Handle;
} SMBIOS_STRUCTURE;

数据结构关系图

SMBIOS 驱动模块用两个链表维护系统中所有产生的SMBIOS 记录。以DataListHead 为

开始的链表记录了系统中已经产生的SMBIOS 记录,以DataListHead 为开始的链表记录了

系统中已经产生的SMBIOS 记录,以AllocateHandleListHead为开始的链表记录了产生的

SMBIOS 记录的句柄,因为SMBIOS 规范要求每一个SMBIOS 记录的句柄不能重复,所以

当用户添加一个新的SMBIOS 记录,SMBIOS 驱动用AllocateHaneleListHead 链表来

获得一个未用的句柄,用来产生新的SMBIOS 记录。DataListHead 链表中的数据结构定义

为EFI_SMBIOS_ENTRY. AllocatedHandleListHead 链表中的数据结构定义为

SMBIOS_HANDLE_ENTRY.

SmbiosGetNext 函数

SmbiosGetNext 用于遍历所有的SMBIOS 记录

//

// If smbiosHandle is zero, the first matched SMBIOS record handle will be returned.

//

if (*SmbiosHandle == 0) {

 if ((Type != NULL) && (*Type != SmbiosTableHeader->Type)) {

    continue;

}

*SmbiosHandle = SmbiosTableHeader->Handle;

*Record = SmbiosTableHeader;

if (ProducerHandle != NULL) {

   *ProducerHandle = SmbiosEntry->RecordHeader->ProducerHandle;

}

return EFI_SUCCESS;

}

如果传入的SMBIOS 句柄为0, 则返回第一个类型匹配的SMBIOS 记录。

已经找到类型匹配的SMBIOS 记录,返回相关信息。

//

// Start this round search from the next smbios handle

//

if (!StartPointFound && (*SmbiosHandle == SmbiosTableHeader->Handle)) {

    StartPointFound = TRUE;

     continue;

}

if (StartPointFound) {

  if ((Type != NULL) && (*Type != SmbiosTableHeader->Type)) {

    continue;

}

*smbiosHandle = smbiosTableHeader->Handle;

*Record = SmbiosTableHeader;

  if (ProducerHandle != NULL) {

    *ProducerHandle = SmbiosEntry->RecordHeader->ProducerHandle;

}

return EFI_SUCCESS;

}

如果传入的SMBIOS 句柄不为0, 则找到SMBIOS 句柄对应的SMBIOS 记录,然后以此

SMBIOS 记录为起点,查找匹配的SMBIOS 记录。

如果类型不匹配,继续遍历下一个SMBIOS 记录。

如果找到类型匹配的SMBIOS 记录,则返回SMBIOS 记录相关的信息。

SmbiosTableConstruction 函数

SmbiosTableConstruction 是ReadyToBoot 事件的回调函数,当ReadyToboot 事件被触发

时被调用。它根据所有的SMBIOS 记录创建最终的SMBIOS 表,并且安装到EFI 系统配置表。

VOID

EFIAPI

SmbiosTableConstruction(

  IN EFI_EVENT     Event,

   IN VOID                 *Context;

    UINT8      *Eps;

    EFI_STATUS  Status;

   Status = SmbiosCreateTable((VOID **) &Eps);

   if (!EFI_ERROR(Status)) {

     gBS->InstallConfigureationTable(&gEfismbiosTableGuid, Eps);

  }

}

调用SmbiosCreateTable 创建最终的SMBIOS 表。

安装SMBIOS 表到EFI 系统配置表(EFI System Configuraiton Table) 中。

SmbiosCrateTable 函数

SmbiosCreateTable 根据所有的SMBIOS 记录创建最终的SMBIOS 表。

    PhysicalAddress = 0xffffffff;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiRuntimeServicesData,
                    EFI_SIZE_TO_PAGES (sizeof (SMBIOS_TABLE_ENTRY_POINT)),
                    &PhysicalAddress
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "SmbiosCreateTable () could not allocate EntryPointStructure < 4GB\n"));
      Status = gBS->AllocatePages (
                      AllocateAnyPages,
                      EfiRuntimeServicesData,
                      EFI_SIZE_TO_PAGES (sizeof (SMBIOS_TABLE_ENTRY_POINT)),
                      &PhysicalAddress
                      );
     if (EFI_ERROR (Status)) {
        return EFI_OUT_OF_RESOURCES;
      }
    }

    EntryPointStructure = (SMBIOS_TABLE_ENTRY_POINT *) (UINTN) PhysicalAddress;

    CopyMem (
      EntryPointStructure,
      &EntryPointStructureData,
      sizeof (SMBIOS_TABLE_ENTRY_POINT)
      );
首先为SMBIOS 表的头部分配内存,内存地址在4GB以下。

初始化SMBIOS 表的头部的数据结构,即将已经准备好的模板EntryPointStructureData 拷贝到

分配的内存里。

  //
  // Calculate EPS Table Length
  //
  CurrentSmbiosEntry = NULL;
  do {
    Status = GetNextSmbiosRecord (SmbiosProtocol, &CurrentSmbiosEntry, &SmbiosRecord);

    if ((Status == EFI_SUCCESS) && (CurrentSmbiosEntry->Smbios32BitTable)) {
      GetSmbiosStructureSize(SmbiosProtocol, SmbiosRecord, &RecordSize, &NumOfStr);
      //
      // Record NumberOfSmbiosStructures, TableLength and MaxStructureSize
      //
      EntryPointStructure->NumberOfSmbiosStructures++;
      EntryPointStructure->TableLength = (UINT16) (EntryPointStructure->TableLength + RecordSize);
      if (RecordSize > EntryPointStructure->MaxStructureSize) {
        EntryPointStructure->MaxStructureSize = (UINT16) RecordSize;
      }
    }
  } while (!EFI_ERROR(Status));

利用GetNextSmbiosRecord 遍历所有的SMBIOS 记录。

计算当前得到的SMBIOS 记录的大小。

猜你喜欢

转载自blog.csdn.net/robinsongsog/article/details/99443799