跳过正文

UEFI 获取磁盘信息

UEFI SATA IDE EDK2
目录

笔者研究了一下磁盘相关的 Protocol,本文描述了uEFI下如何获取磁盘信息。

简介
#

与本次探究相关的主要有三个 Protocol:EFI_BLOCK_IO_PROTOCOLEFI_DISK_IO_PROTOCOL 以及 EFI_DISK_INFO_PROTOCOL 。前两个是在 UEFI SPEC 定义的,最后一个则是在 PI SPEC。

BlockIo 与 DiskIo
#

BlockIoDiskIo 两个Protocol 均是用于访问存储设备的协议,只是它们可以进行操作的级别不同。前者能以 的级别对存储设备进行操作,而后者则提供更加底层的能力,它可以以 字节 为单位对设备进行访问。

DiskInfo
#

这两个Protocol 的主要目的是提供一种标准化的方式来获取磁盘设备的信息。这些信息可能包括磁盘的类型制造商序列号固件版本等。

实例
#

描述
#

写一个程序,枚举出所有物理磁盘,并打印磁盘的型号,SN 以及容量大小。

思路
#

使用 BlockIo 获取所有块设备的实例,然后再使用 DiskIo 进行筛选,得到所有的物理磁盘。接着将物理磁盘的实例传给 DiskInfo,通过 Identify 函数可获取 Identify Data,再根据磁盘的接口类型对数据进行解析,便能打印型号等信息。

代码
#

/**
 * @file DiskInfo.c
 * 
 * @version 0.2
 * @date 2024-09-09
 * 
 * @copyright Copyright (c) 2015 - 2024
 * 
 */

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/BlockIo.h>
#include <Protocol/DiskIo.h>
#include <Protocol/DiskInfo.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PrintLib.h>
#include <Protocol/IdeControllerInit.h>

VOID HexDump (UINT8 *Buffer, UINT8 RowNum)
{
  UINT8   Cols;
  UINT8   Rows;

  for (Rows = 0; Rows < RowNum; Rows ++) {
    for (Cols = 0; Cols < 16; Cols ++) {
      Print (L"%2X ", Buffer[Cols+Rows*16]);
    }

    Print (L"  ");

    for (Cols = 0; Cols < 16; Cols ++) {
      if ((Buffer[Cols+Rows*16] >= '0' && Buffer[Cols+Rows*16] <= '9') ||
         (Buffer[Cols+Rows*16] >= 'a' && Buffer[Cols+Rows*16] <= 'z') ||
         (Buffer[Cols+Rows*16] >= 'A' && Buffer[Cols+Rows*16] <= 'Z')) 
        Print (L"%c", Buffer[Cols+Rows*16]);
      else 
        Print (L".");
    }
    Print (L"\n\r");
  }
  Print (L"\n\r");
}

/**
  Eliminate the extra spaces in the Str to one space.

  @param    Str     Input string info.
**/
VOID
BmEliminateExtraSpaces (
  IN CHAR16                    *Str
  )
{
  UINTN                        Index;
  UINTN                        ActualIndex;

  for (Index = 0, ActualIndex = 0; Str[Index] != L'\0'; Index++) {
    if ((Str[Index] != L' ') || ((ActualIndex > 0) && (Str[ActualIndex - 1] != L' '))) {
      Str[ActualIndex++] = Str[Index];
    }
  }
  Str[ActualIndex] = L'\0';
}

EFI_STATUS
EFIAPI
GetDiskIdentifyData (EFI_HANDLE Handle)
{
  EFI_STATUS                  Status;
  EFI_DISK_INFO_PROTOCOL      *DiskInfo;
  EFI_ATAPI_IDENTIFY_DATA     IdentifyData;
  UINT32                      IdentifyDataSize;
  CHAR16                      *ModelName;
  CHAR16                      *SerialNo;
  CONST UINTN                 ModelNameLength = 40;
  CONST UINTN                 SerialNoLength  = 20;
  UINTN                       Index;

  Status = gBS->HandleProtocol(Handle, &gEfiDiskInfoProtocolGuid, (VOID**)&DiskInfo);
  if (EFI_ERROR(Status)) {
      return Status;
  }

  //            
  // AHCI or IDE
  //
  if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoAhciInterfaceGuid) ||
      CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoIdeInterfaceGuid)) {

    IdentifyDataSize = sizeof (EFI_ATAPI_IDENTIFY_DATA);
    Status = DiskInfo->Identify(DiskInfo, &IdentifyData, &IdentifyDataSize);
    if (!EFI_ERROR (Status)) {

      ModelName = AllocatePool(sizeof(CHAR16) * ModelNameLength);
      SerialNo  = AllocatePool(sizeof(CHAR16) * SerialNoLength);

      // According to the ATA specification, the model name and serial number fields 
      // in the identify data are stored as an array of 16-bit words.
      // Each word is stored in little-endian format, meaning the least significant byte comes first.
      
      for (Index = 0; Index + 1 < ModelNameLength; Index += 2) {
        ModelName[Index]     = (CHAR16) IdentifyData.ModelName[Index + 1];
        ModelName[Index + 1] = (CHAR16) IdentifyData.ModelName[Index];
      }

      for (Index = 0; Index + 1 < SerialNoLength; Index += 2) {
        SerialNo[Index]     = (CHAR16) IdentifyData.SerialNo[Index + 1];
        SerialNo[Index + 1] = (CHAR16) IdentifyData.SerialNo[Index];
      }

      BmEliminateExtraSpaces(ModelName);
      BmEliminateExtraSpaces(SerialNo);

      HexDump((UINT8 *)&IdentifyData, 16);

      Print (L"Model Name: %s\n\r", ModelName);
      Print (L"Serial No : %s\n\r", SerialNo);
      Print (L"Disk type: %g\n\r", DiskInfo->Interface);
    }
    else {
      return Status;
    }
  }

  return EFI_SUCCESS;
}

/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                   Status;
  UINTN                        HandleCount = 0;
  EFI_HANDLE                   *HandleBuffer = NULL;
  UINTN                        Index;
  EFI_BLOCK_IO_PROTOCOL       *BlockIo;
  EFI_DISK_IO_PROTOCOL        *DiskIo;

  Status = gBS->LocateHandleBuffer(
    ByProtocol,
    &gEfiBlockIoProtocolGuid,
    NULL,
    &HandleCount,
    &HandleBuffer
  );

  if (EFI_ERROR(Status)) {
    Print(L"Failed to locate block I/O handles: %r\n", Status);
    return Status;
  }

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol(
      HandleBuffer[Index],
      &gEfiBlockIoProtocolGuid,
      (VOID**)&BlockIo
    );

    if (EFI_ERROR(Status) || BlockIo == NULL || BlockIo->Media == NULL) {
      continue;
    }

    Status = gBS->HandleProtocol(
      HandleBuffer[Index],
      &gEfiDiskIoProtocolGuid,
      (VOID**)&DiskIo
    );

    if (!EFI_ERROR(Status) && DiskIo != NULL && !BlockIo->Media->RemovableMedia) {

      if (BlockIo->Media->LogicalPartition == FALSE &&
          BlockIo->Media->BlockSize > 0 &&
          BlockIo->Media->LastBlock > 0) {
          GetDiskIdentifyData(HandleBuffer[Index]);
          Print (L"Size : %d MB\n\r", (BlockIo->Media->LastBlock + 1) * BlockIo->Media->BlockSize / (1024 * 1024));
          Print(L"\n\r");
      }
    }
  }

  if (HandleBuffer != NULL) {
    gBS->FreePool(HandleBuffer);
  }

  return EFI_SUCCESS;
}

上面代码参考了 MdeModulePkg\Library\UefiBootManagerLib\BmBootDescription.c ,其中 BmGetDescriptionFromDiskInfo 函数的实现,需要注意的是,EDK2 默认的代码中只对 IDE / AHCI 两种有解析的定义,对于 NVMESD 之类的还没有,所以只写了这一部分。

代码中有个解析算法也值得说一下:

// According to the ATA specification, the model name and serial number fields 
// in the identify data are stored as an array of 16-bit words.
// Each word is stored in little-endian format, meaning the least significant byte comes first.
      
for (Index = 0; Index + 1 < ModelNameLength; Index += 2) {
    ModelName[Index]     = (CHAR16) IdentifyData.ModelName[Index + 1];
    ModelName[Index + 1] = (CHAR16) IdentifyData.ModelName[Index];
}

for (Index = 0; Index + 1 < SerialNoLength; Index += 2) {
    SerialNo[Index]     = (CHAR16) IdentifyData.SerialNo[Index + 1];
    SerialNo[Index + 1] = (CHAR16) IdentifyData.SerialNo[Index];
}

在ATA规范中,型号名称和序列号等字段存储为16位的数组。每个16位字以小端格式存储,这意味着最低有效字节(LSB)先存储,然后是最高有效字节(MSB)。

uEFI Disk info

相关文章

Python处理CSV文件的14个高效技巧
Python CSV
生成式AI技术栈架构师指南
GenAI
数据中心GPU的寿命最多只有3年
Google GPU H100