最近在尝试编写Shell下面的一些关于磁盘工具,设计了一个Shell 下面硬盘的选择菜单。这个程序会枚举当前系统中的全部硬盘,然后以菜单的形式提供给客户操作。
程序流程介绍,首先,枚举系统中全部Block Io 的 Handle,之后检查每个 Block Io 的Media,看看是否为物理硬盘,因为我们的操作目标是完整的硬盘而不是某个分区。接下来,再针对每个Handle枚举上面的 DevicePath Protocol, 我们需要这个 Protocol 转化出来的String作为给用户的提示信息。最后,结合之前我们编写过的一个菜单程序的框架,将HDDx的选择和提示信息展示在屏幕上。用户通过上下键和回车键来选择希望操作的硬盘。
具体代码如下,其中包括了一些简单的注视,还有一些用于Debug的位置。
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/ShellLib.h> #include <Protocol/BlockIo.h> #include <Library/DevicePathLib.h> #include <Protocol/DevicePath.h> #include <Protocol/DevicePathToText.h> extern EFI_SYSTEM_TABLE *gST; extern EFI_BOOT_SERVICES *gBS; extern EFI_HANDLE gImageHandle; EFI_GUID gEfiDevicePathToTextProtocolGuid = { 0x8B843E20, 0x8132, 0x4852, { 0x90, 0xCC, 0x55, 0x1A, 0x4E, 0x4A, 0x7F, 0x1C }}; EFI_GUID gEfiBlockIoProtocolGuid = { 0x964E5B21, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}; #define MAXDEVICEPATH 255 typedef struct { CHAR16 Name[6]; CHAR16 DevicePathString[MAXDEVICEPATH]; EFI_HANDLE Controller; EFI_BLOCK_IO_PROTOCOL *BlockIo; } tItem; #define NUM 9 tItem Items[]= { {L"HDD-0",L"",NULL,NULL}, {L"HDD-1",L"",NULL,NULL}, {L"HDD-2",L"",NULL,NULL}, {L"HDD-3",L"",NULL,NULL}, {L"HDD-4",L"",NULL,NULL}, {L"HDD-5",L"",NULL,NULL}, {L"HDD-6",L"",NULL,NULL}, {L"HDD-7",L"",NULL,NULL}, {L"HDD-8",L"",NULL,NULL} }; void DrawMenu(UINTN x1,UINTN y1,UINTN Count) { UINTN i; EFI_INPUT_KEY Key; EFI_STATUS Status; int current=0; ShellPrintEx((UINT32)x1+40,(UINT32)y1 ,L"%S",L"Choose the DISK you want to ERASE"); ShellPrintEx((UINT32)x1+40,(UINT32)y1+1,L"%S",L"Arrow Up/Down, Enter Select, ESC Quit"); ShellPrintEx((UINT32)x1,(UINT32)y1,L"%H%S",Items[0].Name); for (i=1;i<Count;i++) { ShellPrintEx((UINT32)x1,(UINT32)(y1+i),L"%N%S",Items[i].Name); } ShellPrintEx( 0,(UINT32)(y1+Count), L"%s \n", Items[current].DevicePathString ); Key.ScanCode=SCAN_NULL; while (SCAN_ESC!=Key.ScanCode) { Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key); if (Status == EFI_SUCCESS) { ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%N%S",Items[current].Name); if (SCAN_UP == Key.ScanCode) {current = (UINT32)(current-1+Count)%Count;} if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%Count;} ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%H%S",Items[current].Name); /* ShellPrintEx( x1,y1+Count, L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r", current, Key.ScanCode, Key.UnicodeChar); */ ShellPrintEx( (UINT32)0,(UINT32)(y1+Count), L" "); for (i=0;i<MAXDEVICEPATH-1;i++) { Print(L" "); } ShellPrintEx( (UINT32)0,(UINT32)(y1+Count), L"%s \n", Items[current].DevicePathString ); } if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) { //ShellPrintEx(x1,y1+Count,L"You have chosen: %N%S",Items[current].Name); break; } }; } int EFIAPI main ( IN int Argc, IN CHAR16 **Argv ) { EFI_STATUS Status; UINTN HandleCount,HandleIndex,ItemIndex=0; EFI_HANDLE *BlockControllerHandles = NULL; EFI_BLOCK_IO_PROTOCOL *BlockIo; EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *Device2TextProtocol = 0; //Get the Protcol for DevicePath to String Status = gBS->LocateProtocol( &gEfiDevicePathToTextProtocolGuid, NULL, (VOID**)&Device2TextProtocol ); //Enumate all the handle which has Block Io Protocol Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &HandleCount, &BlockControllerHandles); if (!EFI_ERROR(Status)) { for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) { //Get all the Block Io Protocol Status = gBS->HandleProtocol( BlockControllerHandles[HandleIndex], &gEfiBlockIoProtocolGuid, (VOID**)&BlockIo); //Check if it's a logical disk, //we only operate on a physical disk if (BlockIo->Media->LogicalPartition == 0) { EFI_DEVICE_PATH_PROTOCOL* DiskDevicePath=NULL; //Get the Block Io Protocol Status = gBS->HandleProtocol( BlockControllerHandles[HandleIndex], &gEfiDevicePathProtocolGuid, (VOID*)&DiskDevicePath ); if (EFI_ERROR(Status)) { Print(L"Get device path error [%r]\n",Status); continue; } { CHAR16* TextDevicePath = 0; //Convert Disk path to a human readable string TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, FALSE, TRUE); //Print(L"%s\n", TextDevicePath); //Save all the data to a struct Items[ItemIndex].Controller=BlockControllerHandles[HandleIndex]; Items[ItemIndex].BlockIo=BlockIo; StrCpy(Items[ItemIndex].DevicePathString,TextDevicePath); ItemIndex++; if(TextDevicePath)gBS->FreePool(TextDevicePath); } }//if (BlockIo->Media->LogicalPartition == 0) } //for (HandleIndex = 0; //You can output all the data you have gotten here //UINT32 i; //for (i=0;i<ItemIndex;i++) { // Print(L"%d %s %s\n",i,Items[i].Name,Items[i].DevicePathString); //} //Print(L"[%d]",ItemIndex); gST->ConOut->ClearScreen(gST->ConOut); //Set our menu from (0,0) to (21,6), and there should be ItemIndex items DrawMenu(0,0,ItemIndex); gBS->FreePool(BlockControllerHandles); } return EFI_SUCCESS; }
程序在模拟其中运行结果如下,同样的我也在 Intel ApolloLake HDK 平台上试验过,工作也是正常的。
最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载: