Step to UEFI (270)EDK2: Win Host卡死的问题的解决方法

古语有云:“内事不决问百度,外事不决问谷歌”。对于我来说 Windows 下面有搞不懂的问题就直接问Windows专家天杀了。

最近偶然用到EDK2自带的模拟环境,偶尔会遇到运行WinHost.exe之后卡死,键盘无法输入的问题。之前也有热心的朋友提到过,这是因为输入法导致的。果真,在运行WinHost.exe 之前关闭输入法即可解决问题。

这两个都要关闭

很明显问题是WinHost  Windows下输入法有冲突,于是请教天杀,经过一夜的分析天杀很快给出了建议:

产生问题的原因是在代码在处理WM_IME_SETCONTEXT时出问题了,自绘制窗口需要响应输入法的这个消息。在模拟器中,用户没有输入中文的需求,因此直接禁用输入法是最简单的解决方法。

代码修改的方法是在\EmulatorPkg\Win\Host\WinGopScreen.c 文件中首先使用LoadLibraryEx()函数加载ImmDisableIME函数,然后调用之即可。

typedef WINBASEAPI BOOL  (WINAPI *ImmDisableIMEProc) (DWORD unnamedParam1);
typedef WINBASEAPI HIMC  (WINAPI *ImmAssociateContextProc) (HWND unnamedParam1, HIMC unnamedParam2);

/**
  This thread simulates the end of WinMain () application. Each Window needs
  to process its events. The messages are dispatched to
  WinNtGopThreadWindowProc ().
  Be very careful since WinNtGopThreadWinMain () and WinNtGopThreadWindowProc ()
  are running in a separate thread. We have to do this to process the events.

  @param  lpParameter            Handle of window to manage.

  @return if a WM_QUIT message is returned exit.

**/
DWORD
WINAPI
WinNtGopThreadWinMain (
  LPVOID  lpParameter
  )
{
  MSG                    Message;
  GRAPHICS_PRIVATE_DATA  *Private;
  RECT                   Rect;
  
//LABZ_Debug_Start
  HMODULE				Module;
  ImmDisableIMEProc		ImmDisable;
  
  Module = LoadLibraryEx (L"imm32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
  ImmDisable = (ImmDisableIMEProc)GetProcAddress(Module, "ImmDisableIME");

  if (ImmDisable)
	  ImmDisable(GetCurrentThreadId ());
//LABZ_Debug_End

  Private = (GRAPHICS_PRIVATE_DATA *)lpParameter;
  ASSERT (NULL != Private);

经过测试,上述代码运行之后不会有卡死的问题。有兴趣的朋友可以自行实验。

Step to UEFI (176)memset的实现方法

之前的文章“哪里来的的 memset”【参考1】提到过因为编译器擅作主张使用memset优化引起了很诡异的问题。可以通过关闭编译优化来避免错误,这里从代码的角度分析 EDK2 是如何实现 memset 功能的。

  1. \MdePkg\Library\BaseMemoryLib\MemLibGeneric.c 提供了三个函数

InternalMemSetMem16

 InternalMemSetMem32

InternalMemSetMem64

以 InternalMemSetMem16  为例:

/**
  Fills a target buffer with a 16-bit value, and returns the target buffer.

  @param  Buffer  The pointer to the target buffer to fill.
  @param  Length  The count of 16-bit value to fill.
  @param  Value   The value with which to fill Length bytes of Buffer.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem16 (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Length,
  IN      UINT16                    Value
  )
{
  for (; Length != 0; Length--) {
    ((UINT16*)Buffer)[Length - 1] = Value;
  }
  return Buffer;
}

看起来for (; Length != 0; Length--) 这样的定义足够“迷惑”编译器避免优化。

2. \MdePkg\Library\BaseMemoryLib\SetMem.c 提供了InternalMemSetMem()

/**
  Set Buffer to Value for Size bytes.

  @param  Buffer   The memory to set.
  @param  Length   The number of bytes to set.
  @param  Value    The value of the set operation.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Length,
  IN      UINT8                     Value
  )
{
  //
  // Declare the local variables that actually move the data elements as
  // volatile to prevent the optimizer from replacing this function with
  // the intrinsic memset()
  //
  volatile UINT8                    *Pointer8;
  volatile UINT32                   *Pointer32;
  volatile UINT64                   *Pointer64;
  UINT32                            Value32;
  UINT64                            Value64;

  if ((((UINTN)Buffer & 0x7) == 0) && (Length >= 8)) {
    // Generate the 64bit value
    Value32 = (Value << 24) | (Value << 16) | (Value << 8) | Value;
    Value64 = LShiftU64 (Value32, 32) | Value32;

    Pointer64 = (UINT64*)Buffer;
    while (Length >= 8) {
      *(Pointer64++) = Value64;
      Length -= 8;
    }

    // Finish with bytes if needed
    Pointer8 = (UINT8*)Pointer64;
  } else if ((((UINTN)Buffer & 0x3) == 0) && (Length >= 4)) {
    // Generate the 32bit value
    Value32 = (Value << 24) | (Value << 16) | (Value << 8) | Value;

    Pointer32 = (UINT32*)Buffer;
    while (Length >= 4) {
      *(Pointer32++) = Value32;
      Length -= 4;
    }

    // Finish with bytes if needed
    Pointer8 = (UINT8*)Pointer32;
  } else {
    Pointer8 = (UINT8*)Buffer;
  }
  while (Length-- > 0) {
    *(Pointer8++) = Value;
  }
  return Buffer;
}

避免被编译器优化的方法和上面的类似,此外还可以看出这个函数特地用 8 bytes填充提升效率。

3. \MdePkg\Library\UefiMemoryLib\MemLib.c 中的InternalMemSetMem 函数直接调用 gBS 提供的服务

/**
  Fills a target buffer with a byte value, and returns the target buffer.

  This function wraps the gBS->SetMem().

  @param  Buffer    Memory to set.
  @param  Size      The number of bytes to set.
  @param  Value     Value of the set operation.

  @return Buffer.

**/
VOID *
EFIAPI
InternalMemSetMem (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Size,
  IN      UINT8                     Value
  )
{
  gBS->SetMem (Buffer, Size, Value);
  return Buffer;
}

4. 通过volatile 申明变量避免编译器的优化,简单粗暴,很前面2提到的没有本质差别。volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。【参考2】

 \EdkCompatibilityPkg\Foundation\Library\EdkIIGlueLib\Library\BaseMemoryLib\Ebc\SetMem.c

/**
  Set Buffer to Value for Size bytes.

  @param  Buffer Memory to set.
  @param  Size Number of bytes to set
  @param  Value Value of the set operation.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem (
  IN      VOID                      *Buffer,
  IN      UINTN                     Size,
  IN      UINT8                     Value
  )
{
  //
  // Declare the local variables that actually move the data elements as
  // volatile to prevent the optimizer from replacing this function with
  // the intrinsic memset()
  //
  volatile UINT8                    *Pointer;

  Pointer = (UINT8*)Buffer;
  while (Size-- != 0) {
    *(Pointer++) = Value;
  }
  return Buffer;
}

5.汇编语言实现

\EdkCompatibilityPkg\Foundation\Library\CompilerStub\X64\memset.asm

\EdkCompatibilityPkg\Foundation\Library\CompilerStub\Ia32\memset.asm

IA32汇编的实现

    .686
    .model  flat,C
    .mmx
    .code

;------------------------------------------------------------------------------
;  VOID *
;  memset (
;    OUT VOID   *Buffer,
;    IN  UINT8  Value,
;    IN  UINTN  Count
;    )
;------------------------------------------------------------------------------
memset   PROC    USES    edi
    mov     al, [esp + 12]
    mov     ah, al
    shrd    edx, eax, 16
    shld    eax, edx, 16
    mov     ecx, [esp + 16]             ; ecx <- Count
    cmp     ecx, 0                      ; if Count == 0, do nothing
    je      @SetDone
    mov     edi, [esp + 8]              ; edi <- Buffer
    mov     edx, ecx
    and     edx, 7
    shr     ecx, 3                      ; # of Qwords to set
    jz      @SetBytes
    add     esp, -10h
    movq    [esp], mm0                  ; save mm0
    movq    [esp + 8], mm1              ; save mm1
    movd    mm0, eax
    movd    mm1, eax
    psllq   mm0, 32
    por     mm0, mm1                    ; fill mm0 with 8 Value's
@@:
    movq    [edi], mm0
    add     edi, 8
    loop    @B
    movq    mm0, [esp]                  ; restore mm0
    movq    mm1, [esp + 8]              ; restore mm1
    add     esp, 10h                    ; stack cleanup
@SetBytes:
    mov     ecx, edx
    rep     stosb
@SetDone:    
    mov     eax, [esp + 8]              ; eax <- Buffer as return value
    ret
memset   ENDP

    END

上面就是实现 SetMem 函数的基本方法,如果在 Porting 代码到 UEFI时遇到 MemSet 的错误,不妨试试直接将上面的代码搬迁到程序中。

参考:

  1. http://www.lab-z.com/stu136/  Step to UEFI (136)哪里来的的 memset 
  2. https://baike.baidu.com/item/volatile/10606957?fr=aladdin volatile

Step to UEFI (115)zLib 的压缩功能

有朋友留言我才想起来很早之前介绍过 zLib 的编译,但是没有编写一个完整的例子。这次补上这一块。

首先需要安装好zLib(现在都是在 UDK2015下面进行编译),我在 AppPkg.dsc 中加入下面的语句:

  CacheMaintenanceLib|MdePkg/Library/BaseCacheMaintenanceLib/BaseCacheMaintenanceLib.inf

  UefiHandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
  
  zLib|AppPkg/Applications/zsource/zlib.inf 
###################################################################################################
#
# Components Section - list of the modules and components that will be processed by compilation
#                      tools and the EDK II tools to generate PE32/PE32+/Coff image files.

 

之后就可以直接使用了。
压缩很简单,用下面这个函数即可

int compress (Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen);

 

特别需要注意的是,压缩是在内存中进行的,所以需要开辟一段用来存放压缩结果的空间,这个空间的大小也就是上面的destLen。因此,在运行这个函数之前最好线运行一下预测压缩之后大小的函数:

 uLong compressBound (uLong sourceLen);

 

压缩之后的结果不会超过这个函数返回值(一般情况下要小得多)

压缩很简单,下面介绍解压缩:

int uncompress (Bytef *dest,   uLongf *destLen,const Bytef *source, uLong sourceLen);

 

和压缩函数非常类似,但是有一点特别注意的:这个函数用到的 destLen 是解压之后的大小,但是zLib没有提供预测解压之后大小的函数。据说原因是因为解压缩是压缩的“反函数”,因此,在压缩的时候应该已经知道解压后的大小。具体在使用的时候需要想办法自己定义结构体之类的将原始大小传递给解压函数以便开辟内存空间。譬如,在存放压缩的buffer前面加上4字节的大小之类的。

基本概念了解完就上代码,例子实现的功能是将 zlibtest.efi 压缩为test.lbz。然后再解压保存为 test.efi。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Library/ShellLib.h>

#include  "zlib.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_FILE_HANDLE   FileHandle;
	RETURN_STATUS     Status;  
	EFI_FILE_INFO     *FileInfo = NULL;
	UINT32  			SourceSize;  
	UINT32  			preSize;    
	CHAR8  *source=NULL;	
	CHAR8  *dest=NULL;	
//Read a source file
	//Open the file given by the parameter
	Status = 
		ShellOpenFileByName(
				L"zlibtest.efi", 
				(SHELL_FILE_HANDLE *)&FileHandle,
                EFI_FILE_MODE_READ , 
				0);
	if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }	
	  
	//Get file size	  
	FileInfo = 
		ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle);	

	//Allocate a memory buffer
	source = 
		AllocateZeroPool((UINTN) FileInfo-> FileSize);
	if (source == NULL) {
	  Print(L"Allocate source memory error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}

	SourceSize = (UINT32) (FileInfo-> FileSize & 0xFFFFFFFF);

	//Load the whole file to the buffer
	Status = ShellReadFile(FileHandle,&SourceSize,source);
	if (EFI_ERROR(Status)) {
		Print(L"Read file error [%r]\n",Status);
		ShellCloseFile(&FileHandle);
		return (EFI_SUCCESS);
	}
	ShellCloseFile(&FileHandle);
//Compress	
	preSize = compressBound((UINT32)SourceSize);
	Print(L"Source file size   : %d\n",SourceSize);  
	Print(L"Compress Bound Size: %d\n",preSize); 
	
	dest = AllocateZeroPool(preSize);	
	if (dest == NULL) {
	  Print(L"Allocate dest memory error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}
	  
	//Output
	Print(L"Compressing.........\n");	
	Status=compress(dest,&preSize,source,SourceSize);
	if (Status != Z_OK) {
	  Print(L"Compress error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}	
	Print(L"Compressing complete!\n");	
	Print(L"Compressed size: %d\n",preSize);  
	
//Save compressed result to a file
	//Create a new file
	Status = ShellOpenFileByName(L"test.lbz", 
                            (SHELL_FILE_HANDLE *)&FileHandle,
                            EFI_FILE_MODE_READ |
	 					    EFI_FILE_MODE_WRITE|
							EFI_FILE_MODE_CREATE, 
							0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
	}	
					
	Status = ShellWriteFile(FileHandle,
					&preSize,
					dest
				);
			
	//Close the source file
	ShellCloseFile(&FileHandle);
	
//This program is just a demo for showing how to use zlib.
//Uncompress test
	Status=uncompress(source,&SourceSize,dest,preSize);
	if (Status != Z_OK) {
	  Print(L"Decompress error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}
	
	//Output
	Print(L"decompress Size: %d\n",SourceSize);

	//Create a new file
	Status = ShellOpenFileByName(L"test.efi", 
                            (SHELL_FILE_HANDLE *)&FileHandle,
                            EFI_FILE_MODE_READ |
	 					    EFI_FILE_MODE_WRITE|
							EFI_FILE_MODE_CREATE, 
							0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
	}	
					
	Status = ShellWriteFile(FileHandle,
					&SourceSize,
					source
				);
			
	//Close the source file
	ShellCloseFile(&FileHandle);	
	
	FreePool(dest);  
	FreePool(source);   
  return EFI_SUCCESS;
}

 

运行结果:

zlib1

我们直接比较压缩前和解压后的结果,是相同的。

完整的代码下载:

zlibtest

参考:
1. http://www.cppblog.com/woaidongmao/archive/2009/09/07/95495.html zlib用法简单说明

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

Step to UEFI (107)取得USB DISK 的序列号

继续前面的话题,这次研究如何取得一个USB DISK的信息。最先想到的还是使用 DISK INFO PROTOCOL,不过到了实际编写代码的时候发现:我根本不知道返回值是什么格式。在代码中搜索了几次 gEfiDiskInfoUsbInterfaceGuid 发现无人使用。之后为了弄清楚格式,直接创建了一个256bytes大小的内存,还是用 identify 来获得返回值,最后发现:返回值是空的。因此,也就是说虽然 USB DISK上有安装这个 protocol,但是根本就是一个空的而已。
再调整思路,首先用 DISK INFO PROTOCOL取得USB DISK 的 Handle ,然后,在这个Handle上打开 USBIO,EFI_USB_IO_PROTOCOL.UsbGetStringDescriptor 可以用来取得描述符中相关的字符串【参考1】,具体结构可以在 \EdkCompatibilityPkg\Foundation\Include\IndustryStandard\usb.h 找到:

typedef struct {
  UINT8           Length;
  UINT8           DescriptorType;
  UINT16          BcdUSB;
  UINT8           DeviceClass;
  UINT8           DeviceSubClass;
  UINT8           DeviceProtocol;
  UINT8           MaxPacketSize0;
  UINT16          IdVendor;
  UINT16          IdProduct;
  UINT16          BcdDevice;
  UINT8           StrManufacturer;
  UINT8           StrProduct;
  UINT8           StrSerialNumber;
  UINT8           NumConfigurations;
} EFI_USB_DEVICE_DESCRIPTOR;

 

最后,用这个 函数来取得需要的字符串。

完整的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/DiskInfo.h>
#include  <Library/BaseMemoryLib.h>
#include  <Protocol/IdeControllerInit.h>
#include <Library/MemoryAllocationLib.h>
#include  <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiDiskInfoProtocolGuid = { 0xD432A67F, 0x14DC, 0x484B, 
					{ 0xB3, 0xBB, 0x3F, 0x02, 0x91, 0x84, 0x93, 0x27 }};
EFI_GUID gEfiDiskInfoUsbInterfaceGuid   = { 0xCB871572, 0xC11A, 0x47B5, 
					{ 0xB4, 0x92, 0x67, 0x5E, 0xAF, 0xA7, 0x77, 0x27 }};

EFI_GUID  gEfiUsbIoProtocolGuid   = { 0x2B2F68D6, 0x0CD2, 0x44CF, 
					{ 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};					

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS 	Status;
    UINTN 		HandleIndex, NumHandles;
    EFI_HANDLE 	*ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;	
	EFI_USB_IO_PROTOCOL 			*USBIO;
	EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;
	CHAR16                       *Manufacturer;
	CHAR16                       *Product;
	CHAR16                       *SerialNumber;
	
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiDiskInfoProtocolGuid,
            NULL,
            &NumHandles,
            &ControllerHandle);
	
    for (HandleIndex = 0; HandleIndex < NumHandles; HandleIndex++) {
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiDiskInfoProtocolGuid, 
                (VOID**)&DiskInfoProtocol,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
            continue;
        } 

		//We only deal with USB
		if (!(CompareGuid (
				&DiskInfoProtocol->Interface, 
				&gEfiDiskInfoUsbInterfaceGuid))) {	
				continue;
			}	
			
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiUsbIoProtocolGuid, 
                (VOID**)&USBIO,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
			Print(L"ERROR : Open USBIO fail.\n");
            continue;
        } 
		
		Status = USBIO->UsbGetDeviceDescriptor
							(USBIO, &DeviceDescriptor);     
		if (EFI_ERROR(Status))
		{
			Print(L"ERROR : Get Device Descriptor fail.\n");
			return 0;
		}		
		
		Print(L"VendorID = %04X\nProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);  

		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrManufacturer,
                    &Manufacturer
                    );
		if (EFI_ERROR (Status)) {
				Manufacturer = L"";
		}

		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrProduct,
                    &Product
                    );
		if (EFI_ERROR (Status)) {
				Product = L"";	}
				
				
		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrSerialNumber,
                    &SerialNumber
                    );
		if (EFI_ERROR (Status)) {
				SerialNumber = L"";}
		
		Print(L"     Manufacturer :  %s\n",Manufacturer);
		Print(L"     Product      :  %s\n",Product);
		Print(L"     Serial Number:  %s\n",SerialNumber);		
						  
							  
	}
  return EFI_SUCCESS;
}

 

运行结果:
ditu1

完整的代码和程序下载:
diskinfousb

另外,还可以直接枚举 USBIO 然后检查对应的 Class发现是USB MASS Storage 即是U盘,然后再重复上面取得字符串信息的动作。

参考:
1. UEFI Spec 2.4 P835 EFI_USB_IO_PROTOCOL.UsbGetStringDescriptor()

Step to UEFI (106)取得AHCI SATA 的序列号

继续前面的话题,现在尝试直接输出AHCI HDD 的信息。资料上标明AHCI和IDE HDD输出的信息格式是相同的,所以这里会一同处理。

原理上:找到 DISK INFO PROTOCOL 后,判断 GUID 是否为 IDE和 AHCI 的,如果是,那么用Identify 来取得型号信息。返回构体EFI_ATAPI_IDENTIFY_DATA,具体定义在下面这个文件中:

\EdkCompatibilityPkg\Foundation\Framework\Protocol\IdeControllerInit\IdeControllerInit.h 中。

typedef struct {
    UINT16  config;             // General Configuration
    UINT16  obsolete_1;
    UINT16  specific_config;
    UINT16  obsolete_3;   
    UINT16  retired_4_5[2];
    UINT16  obsolete_6;   
    UINT16  cfa_reserved_7_8[2];
    UINT16  retired_9;
    CHAR8   SerialNo[20];       // ASCII 
    UINT16  retired_20_21[2];
    UINT16  obsolete_22;
    CHAR8   FirmwareVer[8];     // ASCII 
    CHAR8   ModelName[40];      // ASCII 
    UINT16  multi_sector_cmd_max_sct_cnt;
    UINT16  reserved_48;
    UINT16  capabilities_49;
    UINT16  capabilities_50;
    UINT16  obsolete_51_52[2];   
    UINT16  field_validity;
    UINT16  obsolete_54_58[5];
    UINT16  mutil_sector_setting;
    UINT16  user_addressable_sectors_lo;
    UINT16  user_addressable_sectors_hi;
    UINT16  obsolete_62;
    UINT16  multi_word_dma_mode;
    UINT16  advanced_pio_modes;
    UINT16  min_multi_word_dma_cycle_time;
    UINT16  rec_multi_word_dma_cycle_time;
    UINT16  min_pio_cycle_time_without_flow_control;
    UINT16  min_pio_cycle_time_with_flow_control;
    UINT16  reserved_69_74[6];
    UINT16  queue_depth;
    UINT16  reserved_76_79[4];
    UINT16  major_version_no;
    UINT16  minor_version_no;
    UINT16  cmd_set_support_82;
    UINT16  cmd_set_support_83;
    UINT16  cmd_feature_support;
    UINT16  cmd_feature_enable_85;
    UINT16  cmd_feature_enable_86;
    UINT16  cmd_feature_default;
    UINT16  ultra_dma_select;
    UINT16  time_required_for_sec_erase;
    UINT16  time_required_for_enhanced_sec_erase;
    UINT16  current_advanced_power_mgmt_value;
    UINT16  master_pwd_revison_code;
    UINT16  hardware_reset_result;
    UINT16  current_auto_acoustic_mgmt_value;
    UINT16  reserved_95_99[5];
    UINT16  max_user_lba_for_48bit_addr[4];
    UINT16  reserved_104_126[23];
    UINT16  removable_media_status_notification_support;
    UINT16  security_status;
    UINT16  vendor_data_129_159[31];
    UINT16  cfa_power_mode;
    UINT16  cfa_reserved_161_175[15];
    UINT16  current_media_serial_no[30];
    UINT16  reserved_206_254[49];
    UINT16  integrity_word;
} EFI_ATAPI_IDENTIFY_DATA;

 

代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/DiskInfo.h>
#include  <Library/BaseMemoryLib.h>
#include  <Protocol/IdeControllerInit.h>

extern EFI_BOOT_SERVICES         *gBS;

extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiDiskInfoProtocolGuid = { 0xD432A67F, 0x14DC, 0x484B, 
					{ 0xB3, 0xBB, 0x3F, 0x02, 0x91, 0x84, 0x93, 0x27 }};
EFI_GUID gEfiDiskInfoAhciInterfaceGuid  = { 0x9e498932, 0x4abc, 0x45af, 
					{ 0xa3, 0x4d, 0x02, 0x47, 0x78, 0x7b, 0xe7, 0xc6 }};
EFI_GUID gEfiDiskInfoIdeInterfaceGuid   = { 0x5E948FE3, 0x26D3, 0x42B5, 
					{ 0xAF, 0x17, 0x61, 0x02, 0x87, 0x18, 0x8D, 0xEC }};

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS Status;
    UINTN HandleIndex, NumHandles;
    EFI_HANDLE *ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;
	UINT32                       BufferSize;	
    EFI_ATAPI_IDENTIFY_DATA      IdentifyData;	
	UINT32				i;
	
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiDiskInfoProtocolGuid,
            NULL,
            &NumHandles,
            &ControllerHandle);
	
    for (HandleIndex = 0; HandleIndex < NumHandles; HandleIndex++) {
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiDiskInfoProtocolGuid, 
                (VOID**)&DiskInfoProtocol,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
            continue;
        } 

		//We only deal with AHCI and IDE
		if (!(CompareGuid (
				&DiskInfoProtocol->Interface, 
				&gEfiDiskInfoAhciInterfaceGuid)||
			(CompareGuid (
				&DiskInfoProtocol->Interface,
			    &gEfiDiskInfoIdeInterfaceGuid)
			))) {	
				continue;
			}	

		BufferSize   = sizeof (EFI_ATAPI_IDENTIFY_DATA);
		Status = DiskInfoProtocol->Identify (
                         DiskInfoProtocol,
                         &IdentifyData,
                         &BufferSize
                         );			
		
		Print(L"Model Name :");
		for (i=0;i<40;i=i+2) {
			Print(L"%c%c",
				IdentifyData.ModelName[i+1],
				IdentifyData.ModelName[i]);
		}
		Print(L"\n");
	}

  return EFI_SUCCESS;
}

 

在 KabyLake HDK 板子上运行上述代码,结果如下:
ahci1

细心的朋友可能注意到,输出并不是直接输出序列号,而是有一个顺序上的调整:
Print(L"%c%c", IdentifyData.ModelName[i+1], IdentifyData.ModelName[i]);
原因是,刚开始我试验的是直接顺序输出,但是发现结果是下面这样的:
ahci2

开始以为是 CHAR 对 CHAR16转换上的问题,后来查阅资料【参考1】,发现这里的行医比较特别。排列是 2/1/4/3/6/5……. 这样的:
ahci3

所以,修改代码手工做一次反转就可以了。
完整的代码下载:
diskinfoahci

参考:
1. http://www.t13.org/Documents/UploadedDocuments/docs2013/d2161r5-ATAATAPI_Command_Set_-_3.pdf

Step to UEFI (105)DiskinfoProtocol

这次介绍一下用来取得系统上硬盘信息的 Protocol: EFI_DISK_INFO_PROTOCOL。

在\MdePkg\Include\Protocol\DiskInfo.h 有他的原型:

///
/// Forward declaration for EFI_DISK_INFO_PROTOCOL
///
typedef struct _EFI_DISK_INFO_PROTOCOL  EFI_DISK_INFO_PROTOCOL;
///
/// The EFI_DISK_INFO_PROTOCOL provides controller specific information.
///
struct _EFI_DISK_INFO_PROTOCOL {
  ///
  /// A GUID that defines the format of buffers for the other member functions 
  /// of this protocol.
  ///
  EFI_GUID                  Interface;
  ///
  /// Return the results of the Inquiry command to a drive in InquiryData. Data
  /// format of Inquiry data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_INQUIRY     Inquiry;
  ///
  /// Return the results of the Identify command to a drive in IdentifyData. Data
  /// format of Identify data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_IDENTIFY    Identify;
  ///
  /// Return the results of the Request Sense command to a drive in SenseData. Data
  /// format of Sense data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_SENSE_DATA  SenseData;
  ///
  /// Specific controller. 
  ///
  EFI_DISK_INFO_WHICH_IDE   WhichIde;
};

 

更详细的介绍可以在 PI Specification 1.4 上找到。
对于不同类型的设备,比如 IDE 和 USB ,返回的数据格式是不同的。枚举到这个 PROTOCOL 之后需要检查EFI_GUID Interface 通过不同的GUID得知当前设备的类型。
下面先编写一个简单的 Demo,检查 GUID ,判断当前设备的类型:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/DiskInfo.h>
#include  <Library/BaseMemoryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiDiskInfoProtocolGuid = { 0xD432A67F, 0x14DC, 0x484B, 
					{ 0xB3, 0xBB, 0x3F, 0x02, 0x91, 0x84, 0x93, 0x27 }};

EFI_GUID gEfiDiskInfoIdeInterfaceGuid   = { 0x5E948FE3, 0x26D3, 0x42B5, 
					{ 0xAF, 0x17, 0x61, 0x02, 0x87, 0x18, 0x8D, 0xEC }};
EFI_GUID gEfiDiskInfoScsiInterfaceGuid  = { 0x08F74BAA, 0xEA36, 0x41D9, 
					{ 0x95, 0x21, 0x21, 0xA7, 0x0F, 0x87, 0x80, 0xBC }};
EFI_GUID gEfiDiskInfoUsbInterfaceGuid   = { 0xCB871572, 0xC11A, 0x47B5, 
					{ 0xB4, 0x92, 0x67, 0x5E, 0xAF, 0xA7, 0x77, 0x27 }};
EFI_GUID gEfiDiskInfoAhciInterfaceGuid  = { 0x9e498932, 0x4abc, 0x45af, 
					{ 0xa3, 0x4d, 0x02, 0x47, 0x78, 0x7b, 0xe7, 0xc6 }};
EFI_GUID gEfiDiskInfoNvmeInterfaceGuid  = { 0x3ab14680, 0x5d3f, 0x4a4d, 
					{ 0xbc, 0xdc, 0xcc, 0x38, 0x0, 0x18, 0xc7, 0xf7 }};
EFI_GUID gEfiDiskInfoUfsInterfaceGuid   = { 0x4b3029cc, 0x6b98, 0x47fb, 
					{ 0xbc, 0x96, 0x76, 0xdc, 0xb8, 0x4, 0x41, 0xf0 }};
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS Status;
    UINTN HandleIndex, NumHandles;
    EFI_HANDLE *ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;
	
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiDiskInfoProtocolGuid,
            NULL,
            &NumHandles,
            &ControllerHandle);
	
    for (HandleIndex = 0; HandleIndex < NumHandles; HandleIndex++) {
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiDiskInfoProtocolGuid, 
                (VOID**)&DiskInfoProtocol,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
            continue;
        } 
		
		Print(L"Device[%d] GUID: %g",
				HandleIndex,
				DiskInfoProtocol->Interface);
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoIdeInterfaceGuid)) {	
				Print(L" IDE HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoScsiInterfaceGuid)) {	
				Print(L" Scsi HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUsbInterfaceGuid)) {	
				Print(L" USB HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoAhciInterfaceGuid)) {	
				Print(L" AHCI HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoNvmeInterfaceGuid)) {	
				Print(L" NVME HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUfsInterfaceGuid)) {	
				Print(L" Ufs HDD\n");
			}				
	}

  return EFI_SUCCESS;
}

 

上述代码在UDK2014中编译通过,但是无法在 NT32环境下运行。于是在实体机KabyLake HDK 上实验, 板子上挂载了一个 SATA HDD 一个eMMC和两个USB Disk。运行结果如下:

dit

可以看出,当前的SATA 是AHCI 模式。此外, eMMC 设备是无法被识别出来的,也许后面会扩展到这种设备吧。
完整的代码下载:

diskinfotest

Step to UEFI (104)eMMC in the Shell

eMMC 应用在平板电脑上已经有一段时间了,本文给出了一个取得 eMMC CID 的例子。当然,你在其他资料上还会发现一些类似 MMC_INFO 之类的Protocol,但是根据我的实验,目前大多数系统只支持SD HOST IO 这一个 Protocol。此外,强烈建议一定在实体机上进行实验,避免出现费了很大力气编写代码,但是实际上第一步就无法完成的问题。
原理介绍:首先查找系统中的 SD_HOST_IO 的Protocol .目前看起来系统中只有一个这样的 Protocol ,所以我们用 LocateProtocol 就足够了。取得之后,通过这个 Protocol,对 eMMC 发送command。这个做法和 ATA 设备的 PassThrough Protocol很像。下面的代码是取得CID信息的,CID是用来识别eMMC一些基本信息的寄存器,比如 Serial Number,具体定义在 eMMC Specification中可以找到【参考1】 。
“8.2 CID register
The Card IDentification (CID) register is 128 bits wide. It contains the card identification information used during the card identification phase (MultiMediaCard protocol). Every individual flash or I/O card shall have an unique identification number. Every type of MultiMediaCard ROM cards (defined by content) shall have an unique identification number. Table 41 on page 112 lists these identifiers.The structure of the CID register is defined in the following sections”
image001

代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include  <Protocol/BlockIo.h>

#include "SDHostIo.h"
#include "mmc.h"


EFI_GUID gEfiSdHostIoProtocolGuid = { 0xb63f8ec7, 0xa9c9, 0x4472, 
					{0xa4, 0xc0, 0x4d, 0x8b, 0xf3, 0x65, 0xcc, 0x51}};
 
//
// Command timeout will be max 100 ms 
//
#define  TIMEOUT_COMMAND     100

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
SendCommand (
  IN   EFI_SD_HOST_IO_PROTOCOL    *This,
  IN   UINT16                     CommandIndex,
  IN   UINT32                     Argument,
  IN   TRANSFER_TYPE              DataType,
  IN   UINT8                      *Buffer, OPTIONAL
  IN   UINT32                     BufferSize,    
  IN   RESPONSE_TYPE              ResponseType,
  IN   UINT32                     TimeOut,  
  OUT  UINT32                     *ResponseData
  )
/*++

  Routine Description:
    Send command by using Host IO protocol
  
  Arguments:
    This           - Pointer to EFI_SD_HOST_IO_PROTOCOL
    CommandIndex   - The command index to set the command index field of command register
    Argument       - Command argument to set the argument field of command register
    DataType       - TRANSFER_TYPE, indicates no data, data in or data out
    Buffer         - Contains the data read from / write to the device
    BufferSize     - The size of the buffer
    ResponseType   - RESPONSE_TYPE
    TimeOut        - Time out value in 1 ms unit
    ResponseData   - Depending on the ResponseType, such as CSD or card status

  Returns:  
    EFI_INVALID_PARAMETER
    EFI_UNSUPPORTED
    EFI_DEVICE_ERROR
    EFI_SUCCESS
 --*/   
{

  EFI_STATUS    Status;

  Status = This->SendCommand (
           This,
           CommandIndex,
           Argument,
           DataType,
           Buffer,
           BufferSize,
           ResponseType,
           TimeOut,
           ResponseData
           );
  if (!EFI_ERROR (Status)) {
    if (ResponseType == ResponseR1 || ResponseType == ResponseR1b) {
      //ASSERT(ResponseData != NULL);
	  Print(L"Error with code [%d]",(*ResponseData));
    }
  } else {
    This->ResetSdHost (This, Reset_DAT_CMD);
  }

  return Status;
}

int
EFIAPI
main (
  IN UINT32 Argc,
  IN CHAR16 **Argv
  )
{
	EFI_SD_HOST_IO_PROTOCOL   *SdHostIo;
	EFI_STATUS 		Status;	
	CID             CIDReg;
    OCR             OCRReg;
	UINT32          TimeOut=5000;
	
	Status = gBS->LocateProtocol (       
                  &gEfiSdHostIoProtocolGuid,
                  NULL,
                  &SdHostIo
                  );
	if (EFI_ERROR (Status)) {
		Print(L"No SdHost driver, Application is exiting!\n");
		return Status;
	}  	

    SdHostIo->EnableAutoStopCmd (SdHostIo, TRUE);
    SdHostIo->SetupDevice (SdHostIo);

    //
    // Go to Idle
    //
	SendCommand (
                SdHostIo,
                GO_IDLE_STATE,
                0,
                NoData,
                NULL,
                0,  
                ResponseNo,
                TIMEOUT_COMMAND,
                NULL
            );

    gBS->Stall (100 * 1000);

    //
    // Check voltage support, first time we use 0x40FF8080
    //
    SendCommand (
                SdHostIo,
				SEND_OP_COND,
                0x40FF8080,
                NoData,
                NULL,
                0,  
                ResponseR3,
                TIMEOUT_COMMAND,
                (UINT32*)&(OCRReg)
            );

    while (OCRReg.Busy != 1) {
		OCRReg.AccessMode = 0x02; // sector mode;
		SendCommand (
				  SdHostIo,
                  SEND_OP_COND,
                  *(UINT32*)&(OCRReg),
                  NoData,
                  NULL,
                  0,  
                  ResponseR3,
                  TIMEOUT_COMMAND,
                  (UINT32*)&(OCRReg)
                  );
						  
        gBS->Stall(100);

        TimeOut--;
        if (TimeOut == 0) {
            Print(L"Card is always busy\n");
            Status = EFI_TIMEOUT;
            goto Exit;
        } 
    } 

	SendCommand(
          SdHostIo,
          ALL_SEND_CID,
		0,
          NoData,
          NULL,
          0,  
          ResponseR2,
          TIMEOUT_COMMAND,
          (UINT32*)&(CIDReg)
          );
  
    Print (L" Product serial number      : %X\n",CIDReg.PSN);
    Print (L" Product revision           : %X\n",CIDReg.PRV);	
    Print (L" Product name               : %c%c%c%c%c%c\n",
								CIDReg.PNM[0],
								CIDReg.PNM[1],
								CIDReg.PNM[2],
								CIDReg.PNM[3],
								CIDReg.PNM[4],
								CIDReg.PNM[5]);
    Print (L" Manufacturer ID            : %X\n",CIDReg.MID);	
	
Exit:	
	return EFI_SUCCESS;
}

在kabyLake HDK 上运行结果:
image002

完整代码下载,其中还有代码生成的EFI程序,是 X64的。
getemmc

本文使用的头文件都是来自新版的 EDK2(比 UDK2015要新一些,我觉得UDK2017有可能会正式加入吧)【参考2】,有兴趣的读者可以自行查阅。
最后,既然有了CID,那么还可以读取一些关于 eMMC的其他信息,比如容量。请读者自己尝试完成。
最后的最后,推荐 Lenovo 出品的一款擦除 eMMC的工具,在 Shell 下运行,可以很快擦掉全部内容(应该使用 eMMC 的 Command 直接Erase的,所以能够达到很快的速度)。
gufd01ww

参考:
1. http://www.jedec.org/standards-documents/results/jesd84-b51 JESD84-A44.pdf
2. https://github.com/tianocore/edk2
3. http://support.lenovo.com/us/en/downloads/ds100934

Step to UEFI (103)Protocol 的私有数据

阅读《UEFI原理与编程》,第八章,开发UEFI服务。其中提到了 Protocol的私有数据。
之前我们介绍过 EFI_LOADED_IMAGE_PROTOCOL,在【参考1】的程序中,就有涉及到LOADED_IMAGE_PRIVATE_DATA,简单的说,定义的 PROTOCOL是这个结构体的一部分,就能够找到整个LOADED_IMAGE_PRIVATE_DATA的结构体,从而获得一些额外的信息。
总结一下,这样的私有数据是这样定义的:

#define PROTOCOLNAME_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')
typedef struct {
  UINTN     Signature;
  UINTN	    Var1;
  PROTOCOLNAME _PROTOCOL   PROTOCOLNAME;           
} PROTOCOLNAME_PRIVATE_DATA;

#define PROTOCOLNAME _PRIVATE_DATA_FROM_THIS(a) \
     CR(a, PROTOCOLNAME_PRIVATE_DATA, PROTOCOLNAME, PROTOCOLNAME _PRIVATE_DATA_SIGNATURE)

 

在初始化的时候,要创建一个实际的PROTOCOLNAME_PRIVATE_DATA,然后初始化需要的变量,最后像其他的Protocol安装一样,将PROTOCOLNAME_PRIVATE_DATA. PROTOCOLNAME 安装到合适的Handle上即可。
编写代码测试一下,基于之前我们写的 PrintDriver 代码,先修改 Print9.h。加入了下面的定义:

#define PRINT9_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')

typedef struct {
  UINTN     Signature;
  UINTN		Var1;
  /// loaded PROTOCOLNAME
  EFI_PRINT9_PROTOCOL   PRINT9;           
} EFI_PRINT9_PRIVATE_DATA;

#define EFI_PRINT9_PRIVATE_DATA_FROM_THIS(a) \
          CR(a, EFI_PRINT9_PRIVATE_DATA, PRINT9, PRINT9_PRIVATE_DATA_SIGNATURE)

 

之后修改print.c。 这个 driver实现的功能很简单,每次调用UnicodeSPrint 函数的时候,会自动显示 EFI_PRINT9_PRIVATE_DATA 中的 Var1,并且增加1.

#include <PiDxe.h>
#include  <Library/UefiLib.h>
#include "Print9.h"
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
EFI_PRINT9_PRIVATE_DATA  *Image;
EFI_HANDLE  mPrintThunkHandle = NULL;
extern EFI_SYSTEM_TABLE			 *gST;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyUnicodeSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  ...
  )
{
  VA_LIST Marker;
  UINTN   NumberOfPrinted=1;
  CHAR16  *Buffer=L"12345678";
  
  VA_START (Marker, FormatString);
  //NumberOfPrinted = UnicodeVSPrint (StartOfBuffer, BufferSize, FormatString, Marker);
  VA_END (Marker);
  
  UnicodeSPrint(Buffer,8,L"%d",Image->Var1);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  Image->Var1++;
  return NumberOfPrinted;
}

/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @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 Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
	EFI_STATUS  Status=EFI_SUCCESS;

	//
	// Allocate a new image structure
	//
	Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
	if (Image == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		goto Done;
	}	
	
    Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
	Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
	Image->PRINT9.UnicodeSPrint=MyUnicodeSPrint;
  	Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeSPrintAsciiFormat=UnicodeSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeValueToString=UnicodeValueToString;	
  	Image->PRINT9.AsciiBSPrint=AsciiBSPrint;	
  	Image->PRINT9.AsciiSPrint=AsciiSPrint;	
  	Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiValueToString=AsciiValueToString;	
	
	Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
				  &Image->PRINT9,
                  NULL
                  );
    ASSERT_EFI_ERROR (Status);

Done:

  return Status;
}

 

测试这个 Protocol 使用的还是之前的 pdt.efi,运行结果如下:

stu103

完整的代码下载

printdriver3

参考:
1. Step to UEFI (48) —– 被加载程序的ENTRYPOINT

Step to UEFI (102)Application 释放Driver

Windows下是不允许应用程序直接访问硬件的,必须通过驱动。类似 RW Everything这样的需要访问硬件的工具实际上是自带驱动的,当运行应用程序的时候会自动把驱动释放出去,然后通过加载驱动的方式再进行硬件的访问的。本文就介绍一下,如何在UEFI 中实现同样的功能。
我们有之前做出来的PrintDriver,用一个 Application 在编译期将它包进去,然后运行期释放到硬盘上,然后Load之,再按照Protocol的方式调用。
特别注意的地方是:我将之前的 PrintDriver.efi 用工具转换为C的字节定义,放在文件头中。用 Const 定义,保证它编译后会处于 .rdata段中。

代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellLib.h>

#include "Print9.h"

EFI_GUID gEfiPrint9ProtocolGuid =
		{ 0xf05976ef, 0x83f1, 0x4f3d, 
			{ 0x86, 0x19, 0xf7, 0x59, 
				0x5d, 0x41, 0xe5, 0x61 } };

							
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_HANDLE 					 gImageHandle;

const CHAR8 MyDriver[] ={
#include	"Mydriver.h"
};

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_PRINT9_PROTOCOL	*Print9Protocol;
	CHAR16			  *Buffer=L"12345678";
	RETURN_STATUS     Status;
	EFI_FILE_HANDLE   FileHandle;
	UINTN			  FileSize=sizeof(MyDriver);
	EFI_HANDLE        *HandleBuffer=(EFI_HANDLE)&MyDriver;
	CHAR16	  		  *CommandLine=L"load MyDriver.efi";
	EFI_STATUS  	  CmdStat;
  
    Print(L"Length of driver = %d \n",sizeof(MyDriver));

	//Create a new file
	Status = ShellOpenFileByName(L"MyDriver.efi", 
                               (SHELL_FILE_HANDLE *)&FileHandle,
                               EFI_FILE_MODE_READ |
							   EFI_FILE_MODE_WRITE|
							   EFI_FILE_MODE_CREATE, 
							   0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
      }	

	Status = ShellWriteFile(FileHandle,
			&FileSize,
			HandleBuffer
			);
	if(Status != RETURN_SUCCESS) {
			Print(L"Writefile failed [%r]!\n",Status);
			return EFI_SUCCESS;
      }				
    Print(L"Driver has been released to the disk!\n");	  
	
	//Close the source file
	ShellCloseFile(&FileHandle);
  
    Status = ShellExecute( &gImageHandle, CommandLine, FALSE, NULL, &CmdStat);
	if(Status != RETURN_SUCCESS) {
			Print(L"Driver load error!\n",Status);
			return EFI_SUCCESS;
      }		
	  
	// Search for the Print9 Protocol
    //
    Status = gBS->LocateProtocol(
      &gEfiPrint9ProtocolGuid,
      NULL,
      (VOID **)&Print9Protocol
     );
    if (EFI_ERROR(Status)) {
      Print9Protocol = NULL;
	  Print(L"Can't find Print9Protocol.\n");
	  return EFI_SUCCESS;
     }
	Print(L"Find Print9Protocol.\n"); 
	Print9Protocol->UnicodeSPrint(Buffer,8,L"%d",200);
	Print(L"%s\n",Buffer); 
	
	return EFI_SUCCESS;
}

 

运行结果:
stu102

第一次加载失败的原因是因为当时处于 shell 下面,没有盘符,这样无法正常释放文件。第二次,在fsnt0: 下运行,驱动正常释放,可能够正常加载。所以取得了期望的结果。
最后提一下,PE格式段的问题。打开一个代码,比如之前测试驱动的Application PDT.EFI,查看编译期生成的 pdt.map :
Preferred load address is 00000000

Start Length Name Class
0001:00000000 000045e5H .text CODE
0002:00000000 0000186eH .rdata DATA
0002:00001870 0000006bH .rdata$debug DATA
0003:00000000 00000350H .data DATA
0003:00000360 00002850H .bss DATA

这些段的含义如下【参考1】:
.text 可执行代码段
数据段.bss、.rdata、.data
.rdata段表示只读的数据,比如字符串文字量、常量和调试目录信息。
.bss段表示应用程序的未初始化数据,包括所有函数或源模块中声明为static的变量。
.data段存储所有其它变量(除了出现在栈上的自动变量)。基本上,这些是应用程序或模块的全局变量。
所以我们希望,定义的数据段出现在 rdata 中,再查看我们的 pdt2.map,其中的 rdata段因为包括了我们定义的 Driver长度明显变大了。
Preferred load address is 00000000

Start Length Name Class
0001:00000000 000046c5H .text CODE
0002:00000000 00002e16H .rdata DATA
0002:00002e18 0000006eH .rdata$debug DATA
0003:00000000 00000350H .data DATA
0003:00000360 00002850H .bss DATA

完整的代码下载:
pdt2

参考:
1. http://blog.csdn.net/feidegengao/article/details/16966357 PE文件格式详解(下)