Skip to main content

UEFI 获取磁盘信息

·757 words·4 mins
UEFI SATA IDE EDK2
Table of Contents

笔者研究了一下磁盘相关的 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

Related

uEFI BIOS简介
·503 words·3 mins
Hardware UEFI BIOS
14 High-Performance CSV Processing Techniques in Python (2025)
·639 words·3 mins
Python Data Engineering CSV Performance ETL
Architect’s Guide to Generative AI Tech Stack
·604 words·3 mins
GenAI AI Architecture Data Lake MLOps