UEFI开发学习 28 – 获取磁盘信息
UEFI 开发系列文章,写了二十几篇,发现还没写过磁盘、文件相关的,今天花了点时间,研究了一下磁盘相关的 Protocol,就先写一篇如何获取磁盘信息的文章。
简介
与本次探究相关的主要有三个 Protocol:EFI_BLOCK_IO_PROTOCOL、EFI_DISK_IO_PROTOCOL 以及 EFI_DISK_INFO_PROTOCOL 。前两个是在 UEFI SPEC 定义的,最后一个则是在 PI SPEC。
BlockIo 与 DiskIo
BlockIo 与 DiskIo 两个Protocol 均是用于访问存储设备的协议,只是它们可以进行操作的级别不同。前者能以 块 的级别对存储设备进行操作,而后者则提供更加底层的能力,它可以以 字节 为单位对设备进行访问。
DiskInfo
该 两个Protocol 的主要目的是提供一种标准化的方式来获取磁盘设备的信息。这些信息可能包括磁盘的类型、制造商、序列号、固件版本等。
实例
描述
写一个程序,枚举出所有物理磁盘,并打印磁盘的型号,SN 以及容量大小。
思路
使用 BlockIo 获取所有块设备的实例,然后再使用 DiskIo 进行筛选,得到所有的物理磁盘。接着将物理磁盘的实例传给 DiskInfo,通过 Identify 函数可获取 Identify Data,再根据磁盘的接口类型对数据进行解析,便能打印型号等信息。
代码
/** * @file DiskInfo.c * * @author https://ay123.net * @version 0.1 * @date 2024-09-08 * * @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; }
上面代码参考了 MdeModulePkgLibraryUefiBootManagerLibBmBootDescription.c ,其中 BmGetDescriptionFromDiskInfo 函数的实现,需要注意的是,EDK2 默认的代码中只对 IDE / AHCI 两种有解析的定义,对于 NVME、SD 之类的还没有,所以只写了这一部分。
代码中有个解析算法也值得说一下:
// 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 2) { ModelName[Index] = (CHAR16) IdentifyData.ModelName[Index + 1]; ModelName[Index + 1] = (CHAR16) IdentifyData.ModelName[Index]; } for (Index = 0; Index + 1 2) { SerialNo[Index] = (CHAR16) IdentifyData.SerialNo[Index + 1]; SerialNo[Index + 1] = (CHAR16) IdentifyData.SerialNo[Index]; }
在ATA规范中,型号名称和序列号等字段存储为16位的数组。每个16位字以小端格式存储,这意味着最低有效字节(LSB)先存储,然后是最高有效字节(MSB)。
源码
https://gitee.com/ay123net/uefistudy
版权声明:
作者:bin
链接:https://ay123.net/mystudy/uefi/1848/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论