SMBIOS driver code analysis

1. Overview

After the motherboard is powered on, UEFI will store HW information, including CPU, Memory, FW, PM and other information in a Memory area in the form of SMBIOS table. After entering the OS, the OS can obtain the relevant configuration by parsing the Memory.
SMBIOS is loaded into the system table through the DXE driver. The entry function is SmbiosDriverEntryPoint, and the driver generates an instance. The instance contains functions to add, update, remove and obtain SMBIOS. The entry function also initializes the data linked list and handle header linked list. Finally, the implemented instance is installed on the new handle and called in the DXE stage.

EFI_STATUS
EFIAPI
SmbiosDriverEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS            Status;

  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);
  
  //
  // Make a new handle and install the protocol
  //
  mPrivateData.Handle = NULL;
  Status = gBS->InstallProtocolInterface (
                  &mPrivateData.Handle,
                  &gEfiSmbiosProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mPrivateData.Smbios
                  );

  return Status;
}

2. Create table structure

SmbiosCreateTable (
  OUT VOID    **TableEntryPointStructure
  )
  {
  .....
  EntryPointStructureData.MajorVersion  = mPrivateData.Smbios.MajorVersion;
  EntryPointStructureData.MinorVersion  = mPrivateData.Smbios.MinorVersion;
  EntryPointStructureData.SmbiosBcdRevision = (UINT8) ((PcdGet16 (PcdSmbiosVersion) >> 4) & 0xf0) | (UINT8) (PcdGet16 (PcdSmbiosVersion) & 0x0f);
  PhysicalAddress = 0xffffffff;
  Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiRuntimeServicesData,
                    EFI_SIZE_TO_PAGES (sizeof (SMBIOS_TABLE_ENTRY_POINT)),
                    &PhysicalAddress
                    );
  ....
  }

The main functions of creating a table structure are
1. Fill in the fields according to the EPS table structure

positionnamelengthdescribe
00Hkeyword4BYTEFixed is "SM"
04HChecksum1BYTEUsed to verify data
05HTable structure length1BYTELength of Entry Point Structure table
06HMajor version number1BYTEUsed to determine SMBIOS version
07HMinor version number1BYTEUsed to determine SMBIOS version
08HTable structure size2BYTEIt is used to obtain the length of data table structure by plug and play interface method
0AHEPS correction1BYTE
0B-0FHFormat area5BYTEStore information explaining EPS correction
10Hkeyword5BYTEFixed to "DMI"
15HChecksum1BYTEChecksum of Intermediate Entry Point Structure (IEPS)
16HStructure table length2BYTELength of SMBIOS structure table
18HStructure table address4BYTETrue memory location of SMBIOS structure table
1CHNumber of structure tables2BYTENumber of SMBIOS structure tables
1EHSmbios BCD correction1BYTE

SMBIOS with SMBIOS_ TABLE_ ENTRY_ Corresponding to the point structure

typedef struct {
  UINT8   AnchorString[4];
  UINT8   EntryPointStructureChecksum;
  UINT8   EntryPointLength;
  UINT8   MajorVersion;
  UINT8   MinorVersion;
  UINT16  MaxStructureSize;
  UINT8   EntryPointRevision;
  UINT8   FormattedArea[5];
  UINT8   IntermediateAnchorString[5];
  UINT8   IntermediateChecksum;
  UINT16  TableLength;
  UINT32  TableAddress;
  UINT16  NumberOfSmbiosStructures;
  UINT8   SmbiosBcdRevision;
} SMBIOS_TABLE_ENTRY_POINT;

2. Create table header

positionnamelengthdescribe
00HTYPE number1BYTETYPE number of structure
01Hlength1BYTEThe length of this structure, as far as the structure of TYPE number is concerned
02Hhandle2BYTEIt is used to obtain the SMBIOS structure, and its value is uncertain
 EndStructure.Header.Type = SMBIOS_TYPE_END_OF_TABLE;
  EndStructure.Header.Length = (UINT8) sizeof (EFI_SMBIOS_TABLE_HEADER);
  EndStructure.Header.Handle = SmbiosHandle;

3. Add SMBIOS table

mPrivateData.Smbios.Add = SmbiosAdd;

EFI_STATUS
EFIAPI
SmbiosAdd (
  IN CONST EFI_SMBIOS_PROTOCOL  *This,
  IN EFI_HANDLE                 ProducerHandle, OPTIONAL
  IN OUT EFI_SMBIOS_HANDLE      *SmbiosHandle,
  IN EFI_SMBIOS_TABLE_HEADER    *Record
  )
   Add an SMBIOS record.

  @param  This                  The EFI_SMBIOS_PROTOCOL instance.
  @param  ProducerHandle        The handle of the controller or driver associated with the SMBIOS information. NULL
                                means no handle.
  @param  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  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 due to lack of system resources.
  @retval EFI_ALREADY_STARTED   The SmbiosHandle passed in was already in use.

1. Verify that the SmbiosHandle is in use
2. Calculate the size and character number of record
Found 1 0, which is the length of a field
Find two zeros, which is the size of a record
3. Allocate memory space for record
TotalSize = sizeof (EFI_SMBIOS_ENTRY) + sizeof (EFI_SMBIOS_RECORD_HEADER) + StructureSize;
4. Build a handle entry and add it to the linked list

 HandleEntry->Signature     = SMBIOS_HANDLE_ENTRY_SIGNATURE;
  HandleEntry->SmbiosHandle  = *SmbiosHandle;
  InsertTailList(&Private->AllocatedHandleListHead, &HandleEntry->Link);

5. Build the header structure of record and add it to the internal linked list

InternalRecord->Version     = EFI_SMBIOS_RECORD_HEADER_VERSION;
  InternalRecord->HeaderSize  = (UINT16) sizeof (EFI_SMBIOS_RECORD_HEADER);
  InternalRecord->RecordSize  = RecordSize;
  InternalRecord->ProducerHandle = ProducerHandle;
  InternalRecord->NumberOfStrings = NumberOfStrings;
  SmbiosEntry->Signature    = EFI_SMBIOS_ENTRY_SIGNATURE;
  SmbiosEntry->RecordHeader = InternalRecord;
  SmbiosEntry->RecordSize   = TotalSize;
  SmbiosEntry->Smbios32BitTable = Smbios32BitTable;
  SmbiosEntry->Smbios64BitTable = Smbios64BitTable;
  InsertTailList (&Private->DataListHead, &SmbiosEntry->Link);

6. Install SMBIOS table into system table

typedef
EFI_STATUS
(EFIAPI *EFI_INSTALL_CONFIGURATION_TABLE)(
  IN EFI_GUID                 *Guid,
  IN VOID                     *Table
  );
  Adds, updates, or removes a configuration table entry from the EFI System Table.

  @param[in]  Guid              A pointer to the GUID for the entry to add, update, or remove.
  @param[in]  Table             A pointer to the configuration table for the entry to add, update, or
                                remove. May be NULL.

  @retval EFI_SUCCESS           The (Guid, Table) pair was added, updated, or removed.
  @retval EFI_NOT_FOUND         An attempt was made to delete a nonexistent entry.
  @retval EFI_INVALID_PARAMETER Guid is NULL.
  @retval EFI_OUT_OF_RESOURCES  There is not enough memory available to complete the operation.

4. Get SMBIOS Record

mPrivateData.Smbios.GetNext = SmbiosGetNext;

EFI_STATUS
EFIAPI
GetNextSmbiosRecord (
  IN CONST EFI_SMBIOS_PROTOCOL         *This,
  IN OUT EFI_SMBIOS_ENTRY              **CurrentSmbiosEntry,
  OUT EFI_SMBIOS_TABLE_HEADER          **Record
  )

1. CurrentSmbiosEntry: when exiting, the pointer points to the entry address containing smbios record information. If the CurrentSmbiosEntry pointer is empty at the entry, the entry address of the first smbios will be returned.
2. Record, when exiting, points to the SMBIOS record, which consists of a formatted area, and then
Unformatted area. The unformatted area optionally contains a text string.
SMBIOS record is obtained through linked list traversal

5. Update or remove SMBIOS Record

mPrivateData.Smbios.UpdateString = SmbiosUpdateString;
mPrivateData.Smbios.Remove = SmbiosRemove;

Update: rebuild the InternalRecord and load it into the system table

6. Add a SMBIOS table record

After the smbios driver is loaded, how to actually add a type0/type1/type2... Record
1. First, obtain the mSmbios interface according to the gEfiSmbiosProtocolGuid
Status = gBS->LocateProtocol(&gEfiSmbiosProtocolGuid, NULL, (VOID **)&mSmbios);
2. Then load it into the system table through the add function in the interface
EFI_SMBIOS_PROTOCOL *mSmbios;
Status = mSmbios->Add (
mSmbios,
NULL,
&SmbiosHandle,
(EFI_SMBIOS_TABLE_HEADER *)SmbiosRecord
);

Tags: bios

Posted on Wed, 24 Nov 2021 00:48:06 -0500 by dpearcepng