Step to UEFI (30) ---- UEFI 下SMBIOS的读取

有朋友提出一个问题:UEFI 下如何读取SMBIOS信息?

smbiosq

查阅了一下资料,在 EFI_CONFIGURATION_TABLE 中有 SMBIOS_TABLE_GUID 可以用来获得SMBIOS信息。此外,推荐看一下 http://blog.chinaunix.net/uid-20757375-id-3531318.html 。

可供直接参考的代码有 ShellPkg\Library\UefiShellDebug1CommandsLib\SmbiosView 如果你看着觉得过于复杂的话,还可以参考 https://github.com/fpmurphy/UEFI-Utilities 。这个程序结构清晰,功能简单。每种不足是直接下载的代码有一些错误之类的,需要修正。下面就是修改之后的代码(代码很多是从EDK中直接copy出来的,因为装一些头文件和调用会很复杂,用到的只是那么一两个函数,所以最简单的就是直接copy,放在代码中了.其中头文件 smbios.h 可以在\MdePkg\Include\IndustryStandard 中找到)

//
//  Copyright (c) 2012  Finnbarr P. Murphy.   All rights reserved.
//
//  Display  Firmware Information Vendor, Version and Release Date via SMBIOS
//
//  Any source code included from EDK2 is copyright Intel Corporation
// 
//  Licence: BSD License
//
 
 
#define GUID EFI_GUID     // hack for SmBios.h
#include "IndustryStandard/SmBios.h"       // from EDK2. GNU_EFI libsmbios.h is defective
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>

#define EFI_GLOBAL_VARIABLE \
  { \
    0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } \
  }

#define EFI_SMBIOS_TABLE_GUID \
  { \
    0xeb9d2d31, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d} \
  }
  
#define BUFSIZE 64
 
#define INVALID_HANDLE  (UINT16) (-1)
#define DMI_INVALID_HANDLE  0x83
#define DMI_SUCCESS 0x00
 
STATIC SMBIOS_TABLE_ENTRY_POINT *mSmbiosTable = 0; //NULL;
STATIC SMBIOS_STRUCTURE_POINTER m_SmbiosStruct;
STATIC SMBIOS_STRUCTURE_POINTER *mSmbiosStruct = &m_SmbiosStruct;
 
EFI_GUID  SMBIOSTableGuid = EFI_SMBIOS_TABLE_GUID;

extern EFI_SYSTEM_TABLE			 *gST;

int
Strlen(const char *str)
{
    const char *s;
 
    for (s = str; *s; ++s)
        ;
    return (s - str);
}
 
 
//
// Modified from EDK2 source code.  Copyright Intel Corporation.
//
CHAR8*
LibGetSmbiosString ( SMBIOS_STRUCTURE_POINTER *Smbios,
                     UINT16 StringNumber)
{
    UINT16  Index;
    CHAR8   *String;
 
    //ASSERT (Smbios != NULL);
 
    String = (CHAR8 *) (Smbios->Raw + Smbios->Hdr->Length);
 
    for (Index = 1; Index <= StringNumber; Index++) {
        if (StringNumber == Index) {
            return String;
        }
        for (; *String != 0; String++)
             ;
        String++;
 
        if (*String == 0) {
            Smbios->Raw = (UINT8 *)++String;
            return NULL;
        }
    }
 
    return NULL;
}
 
 
//
// Modified from EDK2 source code. Copyright Intel Corporation.
//
EFI_STATUS
LibGetSmbiosStructure ( UINT16 *Handle,
                        UINT8  **Buffer,
                        UINT16 *Length)
{
     SMBIOS_STRUCTURE_POINTER  Smbios;
     SMBIOS_STRUCTURE_POINTER  SmbiosEnd;
     UINT8 *Raw;
 
     if (*Handle == INVALID_HANDLE) {
          *Handle =  mSmbiosStruct->Hdr->Handle;
          return DMI_INVALID_HANDLE;
     }
 
     if ((Buffer == NULL) || (Length == NULL)) {
          Print(L"Invalid handle\n");
          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;
             LibGetSmbiosString (&Smbios, (UINT16) (-1));
             *Length = (UINT16) (Smbios.Raw - Raw);
             *Buffer = Raw;
             if (Smbios.Raw < SmbiosEnd.Raw) {
                  *Handle = Smbios.Hdr->Handle;
             } else {
                  *Handle = INVALID_HANDLE;
             }
             return DMI_SUCCESS;
         }
         LibGetSmbiosString (&Smbios, (UINT16) (-1));
    }
 
    *Handle = INVALID_HANDLE;
 
    return DMI_INVALID_HANDLE;
}
 
 
CHAR16 *
ASCII_to_UCS2(const char *s, int len)
{
    CHAR16 *ret = NULL;
    int i;
 
    ret = AllocateZeroPool(len*2 + 2);
    if (!ret)
        return NULL;
 
    for (i = 0; i < len; i++)
        ret[i] = s[i];
 
    return ret;
}

BOOLEAN
EfiCompareGuid (
  IN EFI_GUID *Guid1,
  IN EFI_GUID *Guid2
  )
/*++

Routine Description:

  Compares two GUIDs

Arguments:

  Guid1 - guid to compare

  Guid2 - guid to compare

Returns:
  TRUE     if Guid1 == Guid2
  FALSE    if Guid1 != Guid2

--*/
{
  UINTN Index;

  //
  // compare byte by byte
  //
  for (Index = 0; Index < 16; ++Index) {
    if (*(((UINT8*) Guid1) + Index) != *(((UINT8*) Guid2) + Index)) {
      return FALSE;
    }
  }
  return TRUE;
}

 
EFI_STATUS
EfiLibGetSystemConfigurationTable (
  IN EFI_GUID *TableGuid,
  OUT VOID **Table
  )
/*++

Routine Description:

  Get table from configuration table by name

Arguments:

  TableGuid       - Table name to search
  
  Table           - Pointer to the table caller wants

Returns: 

  EFI_NOT_FOUND   - Not found the table
  
  EFI_SUCCESS     - Found the table

--*/
{
  UINTN Index;

  *Table = NULL;
  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    if (EfiCompareGuid (TableGuid, &(gST->ConfigurationTable[Index].VendorGuid))==TRUE) {
      *Table = gST->ConfigurationTable[Index].VendorTable;
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Compares two memory buffers of a given length.

  @param  DestinationBuffer The first memory buffer.
  @param  SourceBuffer      The second memory buffer.
  @param  Length            Length of DestinationBuffer and SourceBuffer memory
                            regions to compare. Must be non-zero.

  @return 0                 All Length bytes of the two buffers are identical.
  @retval Non-zero          The first mismatched byte in SourceBuffer subtracted from the first
                            mismatched byte in DestinationBuffer.

**/
INTN
EFIAPI
InternalMemCompareMemZ (
  IN      CONST VOID                *DestinationBuffer,
  IN      CONST VOID                *SourceBuffer,
  IN      UINTN                     Length
  )
{
  while ((--Length != 0) &&
         (*(INT8*)DestinationBuffer == *(INT8*)SourceBuffer)) {
    DestinationBuffer = (INT8*)DestinationBuffer + 1;
    SourceBuffer = (INT8*)SourceBuffer + 1;
  }
  return (INTN)*(UINT8*)DestinationBuffer - (INTN)*(UINT8*)SourceBuffer;
}

/**
  Compares the contents of two buffers.

  This function compares Length bytes of SourceBuffer to Length bytes of DestinationBuffer.
  If all Length bytes of the two buffers are identical, then 0 is returned.  Otherwise, the
  value returned is the first mismatched byte in SourceBuffer subtracted from the first
  mismatched byte in DestinationBuffer.
  
  If Length > 0 and DestinationBuffer is NULL, then ASSERT().
  If Length > 0 and SourceBuffer is NULL, then ASSERT().
  If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT().
  If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT().

  @param  DestinationBuffer A pointer to the destination buffer to compare.
  @param  SourceBuffer      A pointer to the source buffer to compare.
  @param  Length            The number of bytes to compare.

  @return 0                 All Length bytes of the two buffers are identical.
  @retval Non-zero          The first mismatched byte in SourceBuffer subtracted from the first
                            mismatched byte in DestinationBuffer.
                            
**/
INTN
EFIAPI
CompareMem (
  IN CONST VOID  *DestinationBuffer,
  IN CONST VOID  *SourceBuffer,
  IN UINTN       Length
  )
{
  if (Length == 0 || DestinationBuffer == SourceBuffer) {
    return 0;
  }

  return InternalMemCompareMemZ (DestinationBuffer, SourceBuffer, Length);
}
 
 
EFI_STATUS
UefiMain (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
    EFI_STATUS status = EFI_SUCCESS;
    CHAR16 *ptr;
    CHAR8  *str;
    UINT16  Handle;
    UINTN   Index;
    UINT16  Length;
    UINT8   *Buffer;
    SMBIOS_STRUCTURE_POINTER SmbiosStruct;
 
    //InitializeLib(image, systab);
 
    status = EfiLibGetSystemConfigurationTable(&SMBIOSTableGuid, (VOID**)&mSmbiosTable);
    if ((status != EFI_SUCCESS || mSmbiosTable == NULL) ||
        (CompareMem (mSmbiosTable->AnchorString, "_SM_", 4) != 0)) {
        Print(L"ERROR: SMBIOS table not found.\n");
        return status;
    }
 
    mSmbiosStruct->Raw  = (UINT8 *) (UINTN) (mSmbiosTable->TableAddress);
 
    Print(L"SMBIOS Ver: %x.%x  Rev: %x  Table Count: %d\n",
            mSmbiosTable->MajorVersion,
            mSmbiosTable->MinorVersion,
            mSmbiosTable->EntryPointRevision,
            mSmbiosTable->NumberOfSmbiosStructures);
 
    Handle  = INVALID_HANDLE;
    LibGetSmbiosStructure (&Handle, NULL, NULL);
 
    // loop though the tables looking for a type 0 table.
    for (Index = 0; Index < mSmbiosTable->NumberOfSmbiosStructures; Index++) {
        if (Handle == INVALID_HANDLE) {
            break;
        }
        if (LibGetSmbiosStructure (&Handle, &Buffer, &Length) != DMI_SUCCESS) {
            break;
        }
        SmbiosStruct.Raw = Buffer;
        if (SmbiosStruct.Hdr->Type == 0) {     // Type 0 - BIOS
 
             /* vendor string */
             str = LibGetSmbiosString(&SmbiosStruct, 1);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Vendor: %s\n", ptr);   
             FreePool(ptr);
                        
             /* version string */
             str = LibGetSmbiosString(&SmbiosStruct, 2);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Version: %s\n", ptr);
             FreePool(ptr);
 
             /* release string */
             str = LibGetSmbiosString(&SmbiosStruct, 3);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Release: %s\n", ptr);
             FreePool(ptr);
 
             break;
        }
    }
 
    return status;
}


 

运行结果

smbios

对比 SMBIOSVIEW

smbiosview

smbiosview2

可以看到二者是相同的。

代码下载
GetVer

顺便多说一点:

SMBIOS中可以获得一些硬件信息,比如当前的内存插槽信息等等。但是,因为这个是BIOS在POST过程中加入的,很可能出现错误,甚至就是忘记做这个东西(MS的软件非常不信任BIOS提供的信息,如果有可能他们宁可自己重新获取一下)。如果只是想做个参考,并且你的软件需要兼容大量的设备,从这里获得信息是完全没问题的(比如MemTest86);但是如果你期望准确一些,最好还是自己手工做一下。另外一方面,如果你使用了从SMBIOS获得信息的方法,结果在客户现场遇到了问题,不妨考虑一下是否为 SMBIOS 不正确导致的。

《Step to UEFI (30) ---- UEFI 下SMBIOS的读取》有15个想法

  1. 當build (build -a X64 -p AppPkg\AppPkg.dsc)過程會failed在以下的 code
    int Strlen(const char *str)
    {
    const char *s;
    for (s = str; *s; ++s)
    ;
    return (s - str);
    }

    改寫一下,就可以build成功 ( 如下)
    int Strlen(IN const char *str)
    {
    const char *s;

    for (s = str; *s; ++s)
    ;
    return (int)((unsigned long long)s - (unsigned long long)str);
    }

  2. 請問我呼叫EFI_SMBIOS_PROTOCOL底下的 GetNext,remove,UpdateString 為啥全部都失敗呢?
    拿到shell下跑,都回停住

    .inf和上面一樣

    #define GUID EFI_GUID // hack for SmBios.h
    #include // from EDK2. GNU_EFI libsmbios.h is defective
    #include
    #include
    #include
    #include
    #define EFI_SMBIOS_PROTOCOL_GUID \
    { 0x3583ff6, 0xcb36, 0x4940, { 0x94, 0x7e, 0xb9, 0xb3, 0x9f, 0x4a, 0xfa, 0xf7 }}

    EFI_GUID SMBIOSProtocolGuid = EFI_SMBIOS_PROTOCOL_GUID;

    extern EFI_SYSTEM_TABLE *gST;
    extern EFI_BOOT_SERVICES *gBS;
    typedef struct _EFI_SMBIOS_PROTOCOL EFI_SMBIOS_PROTOCOL;

    EFI_STATUS
    UefiMain (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
    {

    EFI_STATUS status ;

    // CHAR8 *STRING = "Edison";
    UINT16 Handle=SMBIOS_HANDLE_PI_RESERVED;

    EFI_SMBIOS_TABLE_HEADER *Buf;
    // UINTN K=1;
    // UINTN *StringNumber=&K;

    EFI_SMBIOS_PROTOCOL *Smbios;
    EFI_SMBIOS_TYPE RecordType = EFI_SMBIOS_TYPE_SYSTEM_INFORMATION;

    gBS->LocateProtocol(
    &SMBIOSProtocolGuid,
    NULL,
    (VOID **)&Smbios);

    CpuDeadLoop ();

    status=Smbios->GetNext (
    Smbios,
    &Handle,
    &RecordType,
    &Buf,
    NULL
    );
    Print(L"Handle is %d\n",Handle);

    // Smbios->Remove(
    // Smbios,
    // Handle);
    //
    //
    // Smbios->UpdateString(
    // Smbios,
    // &Handle,
    // StringNumber,
    // STRING
    // );
    // Print(L"status is %d\n",status);

    return status;
    }

  3. Hi :
    请问一下,如果要Add一个SMBIOS Structure Data(假如机器上没有Type32,现在加上Type32),调用SMBIOS Protocol的add()函数,写成 UEFI APP在shell下run,是可以看到Type32是被Add到SMBIOS Table中,但是如果机器断电重启,那么Add的Type32就会丢失,我的理解是:Add()函数操作的是内存,它只是把数据加在已经存在内存中的SMBIOS table中,如果要在重启之后继续生效,那要如何处理?
    另外一个问题是:SMBIOS的数据,是存储在什么地方的呢?

    1. 之前的 Legacy BIOS 中这样的加入操作最后是要写入到 nvram 也就是 spi rom 中的。uefi 的话我没有研究过,但是应该也能写入 spi 中,回头我研究一下。

      1. Hi ziv,有个相关的问题请教一下,uefi shell中如何去写nvram?我现在遇到一个问题是需要在uefi shell中用程序修改主setup界面上的参数,比如说内存频率、Timing参数等,不知该如何实现,特来请教,盼复。多谢!

          1. 你好,请问对于SevenYan的问题你想到解决办法了吗?我也对这个问题很困扰,没想到解决办法

          2. 我看了一下 EDK2 上的 OVMF 的这部分代码, SMBIOS 是在 Post 过程中创建起来的,因此没有办法直接加入然后下次直接就有。不过我不清楚IBV 是否有解决方案,另外,我觉得可以在代码中加入从 Variable 中读取特定内容作为 SMBIOS 的代码,这样在 Shell 下可以通过修改 Variable 的方式来增加 SMBIOS 的内容。

  4. 你好,你写的资料真的太好了,
    请教下在 UEFI 或 Win 如何读取主板开机时显示一行信息 Sign on Message(有些主板将机种名称,版本,日期等信息写在此处),盼复,多谢了。

    1. UEFI 的话,通常是开机界面显示这个信息需要改BIOS; Windows 的话,你可以查看通过 WMI 的方法。具体你可以搜索 “wmi 读取系统信息”

      1. 感谢回复。 windows 下用 wmic 读到不到Sign On message 一行信息。

        请教下 BIOS Sign On Message 这行字串,开机后保存在什么地址,在UEFI SHELL 如何读出此行信息,谢谢。
        比如:BIOS BIN FILE用AMIBCP查看BIOS Features的Sign On Message为Intel B760 Ver:1.71 BIOS Date: 09/10/2022 15:29:59

        在windows下用 wmic 查看不到此行信息,部份信息,不完整:
        wmic bios get /value 只读到 BIOS Date: 09/10/2022 15:29:59
        wmic baseboard get /value 只读到 B760
        wmic csproduct get /value 只读到 B760

        1. 噢, Sign On Message 不是一个行业标准,代码中通常也是合成的。所以需要你自己编写代码合成出来。

          或者直接写一个工具在 BIOS Binary File 中指定位置查找输出一下

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注