最近在尝试编写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的下载:
hddmenu