Step to UEFI (111)Shell下面的HDD菜单

最近在尝试编写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 平台上试验过,工作也是正常的。

hddmenu

最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载:

hddmenu