【UEFI基础】SMBIOS基础和使用

2024-09-05 08:28
文章标签 基础 使用 uefi smbios

本文主要是介绍【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 ensuringRecord is formatted in a way that matches the version of the SMBIOS specification as defined inthe MajorRevision and MinorRevision fields of the EFI_SMBIOS_PROTOCOL.Record must follow the SMBIOS structure evolution and usage guidelines in the SMBIOSspecification. Record starts with the formatted area of the SMBIOS structure and the length isdefined by EFI_SMBIOS_TABLE_HEADER.Length. Each SMBIOS structure is terminated by adouble-null (0x0000), either directly following the formatted area (if no strings are present) ordirectly 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 astwo 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 handlewill 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 isdetermined by EFI_SMBIOS_TABLE_HEADER.Type. The size of the formattedarea is defined by EFI_SMBIOS_TABLE_HEADER.Length and either followedby 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 findonly 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 thenext SMBIOS record handle. If it is FFFEh on entry, then the first SMBIOS recordhandle 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, itindicates that the next record of any type will be returned. Type is notmodified by the this function.@param[out]       Record          On exit, points to a pointer to the the SMBIOS Record consisting of the formatted areafollowed by the unformatted area. The unformatted area optionally contains text strings.@param[out]       ProducerHandle  On exit, points to the ProducerHandle registered by Add(). If noProducerHandle was passed into Add() NULL is returned. If a NULL pointer ispassed 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;          // 字符串1SMBIOS_TABLE_STRING          BiosVersion;     // 字符串2UINT16                       BiosSegment;SMBIOS_TABLE_STRING          BiosReleaseDate; // 字符串3UINT8                        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 anySMBIOS 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()获取到结构体之后直接修改即可。

这篇关于【UEFI基础】SMBIOS基础和使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1138440

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念