【UEFI基础】SMBIOS基础和使用

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(),它主要包括以下的几个步骤:

  1. 初始化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、列表和锁。后面两个是代码实现相关的,而第一个是安装之后被其它模块使用的,后面会进一步介绍。

  1. 安装SMBIOS接口,就是上一步初始化的Protocol:
  //
  // Make a new handle and install the protocol
  //
  mPrivateData.Handle = NULL;
  Status              = gBS->InstallProtocolInterface (
                               &mPrivateData.Handle,
                               &gEfiSmbiosProtocolGuid,
                               EFI_NATIVE_INTERFACE,
                               &mPrivateData.Smbios
                               );
  1. 判断是否已经有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接口调用:
SmbiosRemove
SmbiosTableConstruction
SmbiosUpdateString
SmbiosAdd

因为保存了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()获取到结构体之后直接修改即可。

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值