This article explores how to retrieve disk-related information in a UEFI environment by leveraging standard firmware protocols. The focus is on identifying physical disks and extracting metadata such as model name, serial number, firmware interface type, and capacity.
🧭 Introduction #
UEFI provides several protocols for interacting with storage devices at different abstraction levels. The three most relevant protocols for disk enumeration and identification are:
- EFI_BLOCK_IO_PROTOCOL – Provides block-level access to storage devices.
- EFI_DISK_IO_PROTOCOL – Enables byte-level disk access.
- EFI_DISK_INFO_PROTOCOL – Exposes hardware-specific disk information.
The first two protocols are defined in the UEFI Specification, while EFI_DISK_INFO_PROTOCOL is part of the PI (Platform Initialization) Specification.
🧱 BlockIo vs. DiskIo #
Both BlockIo and DiskIo are used to access storage devices, but they differ in purpose and abstraction:
- BlockIo operates at the block level and is commonly used for reading or writing logical blocks.
- DiskIo provides lower-level, byte-granular access to the disk media.
In practice, BlockIo is useful for enumerating devices and determining media properties, while DiskIo helps confirm whether a handle represents a physical disk rather than a logical partition.
💽 DiskInfo Protocol Overview #
The primary role of the DiskInfo protocol is to expose standardized, hardware-specific disk metadata. This information typically includes:
- Disk interface type (IDE, AHCI, etc.)
- Manufacturer and model name
- Serial number
- Firmware or identify data
This protocol is essential when you need to retrieve descriptive information rather than raw data blocks.
🧪 Practical Example #
Objective #
Enumerate all physical disks and print the following details:
- Model name
- Serial number (SN)
- Disk interface type
- Total capacity
Approach #
- Use BlockIo to locate all block device handles.
- Filter out removable media and logical partitions.
- Use DiskIo to confirm access to physical disks.
- Query DiskInfo and call
Identify()to retrieve raw identify data. - Parse the identify data based on the disk interface type.
- Calculate disk capacity using media block information.
🧩 Code Implementation #
/**
* @file DiskInfo.c
* @version 0.2
* @date 2024-09-09
*/
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PrintLib.h>
#include <Protocol/BlockIo.h>
#include <Protocol/DiskIo.h>
#include <Protocol/DiskInfo.h>
#include <Protocol/IdeControllerInit.h>
VOID HexDump (UINT8 *Buffer, UINT8 RowNum)
{
UINT8 Cols, 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");
}
VOID BmEliminateExtraSpaces (IN CHAR16 *Str)
{
UINTN Index, 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;
UINTN Index;
Status = gBS->HandleProtocol(
Handle,
&gEfiDiskInfoProtocolGuid,
(VOID**)&DiskInfo
);
if (EFI_ERROR(Status)) {
return Status;
}
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)) {
return Status;
}
ModelName = AllocatePool(sizeof(CHAR16) * 40);
SerialNo = AllocatePool(sizeof(CHAR16) * 20);
for (Index = 0; Index + 1 < 40; Index += 2) {
ModelName[Index] = (CHAR16)IdentifyData.ModelName[Index + 1];
ModelName[Index + 1] = (CHAR16)IdentifyData.ModelName[Index];
}
for (Index = 0; Index + 1 < 20; Index += 2) {
SerialNo[Index] = (CHAR16)IdentifyData.SerialNo[Index + 1];
SerialNo[Index + 1] = (CHAR16)IdentifyData.SerialNo[Index];
}
BmEliminateExtraSpaces(ModelName);
BmEliminateExtraSpaces(SerialNo);
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);
}
return EFI_SUCCESS;
}
🧠 Parsing Logic Explained #
According to the ATA specification, fields such as Model Name and Serial Number are stored as arrays of 16-bit words. Each word is encoded in little-endian format, meaning the least significant byte appears first. As a result, each pair of bytes must be swapped to produce readable strings.
for (Index = 0; Index + 1 < ModelNameLength; Index += 2) {
ModelName[Index] = (CHAR16) IdentifyData.ModelName[Index + 1];
ModelName[Index + 1] = (CHAR16) IdentifyData.ModelName[Index];
}
Without this byte swap, the extracted strings would appear garbled or reversed.
📌 Limitations and Notes #
- The default EDK2 implementation only includes parsing definitions for IDE and AHCI devices.
- NVMe and SD card parsing logic is not natively provided in
UefiBootManagerLiband must be implemented separately. - The example logic is derived from
BmGetDescriptionFromDiskInfoinMdeModulePkg/Library/UefiBootManagerLib/BmBootDescription.c.
By combining BlockIo, DiskIo, and DiskInfo, firmware applications can reliably enumerate physical disks and extract meaningful hardware metadata in UEFI environments.