有朋友提出一个问题:UEFI 下如何读取SMBIOS信息?
查阅了一下资料,在 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; }
运行结果
对比 SMBIOSVIEW
可以看到二者是相同的。
代码下载
GetVer
顺便多说一点:
SMBIOS中可以获得一些硬件信息,比如当前的内存插槽信息等等。但是,因为这个是BIOS在POST过程中加入的,很可能出现错误,甚至就是忘记做这个东西(MS的软件非常不信任BIOS提供的信息,如果有可能他们宁可自己重新获取一下)。如果只是想做个参考,并且你的软件需要兼容大量的设备,从这里获得信息是完全没问题的(比如MemTest86);但是如果你期望准确一些,最好还是自己手工做一下。另外一方面,如果你使用了从SMBIOS获得信息的方法,结果在客户现场遇到了问题,不妨考虑一下是否为 SMBIOS 不正确导致的。