Step to UEFI (80) —–取得汉字的字形

EFI 在设计之初就考虑了多语言的支持,使用HII可以轻松的实现汉字的显示。本篇文章介绍获得汉字字形的其他方法,掌握这种方法之后可以在没有HII支持的情况下显示汉字。当然,程序只是为了演示原理,介绍如何读取16×16的汉字字形信息,没有转为图形。
比如:“宋”字查询到的区位码是4346 【参考3】,意思是区码为43,位码是46。计算这个字在字库中的方法是:((43-1)*94+(46-1))*32=6828。之后,在字库文件的 6828偏移处连续读取32个字节即可。

shz

代码如下:

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

#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES *gBS;

#define FONT_SIZE (16)
#define HZ_INDEX(hz) ((hz[0] - 1) * 94 + (hz[1] -1))*32
#define DOTS_BYTES (FONT_SIZE * FONT_SIZE / 8)

int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_FILE_HANDLE FileHandle;
RETURN_STATUS Status;
EFI_FILE_INFO *FileInfo = NULL;
EFI_HANDLE *HandleBuffer=NULL;
UINTN ReadSize;
UINTN i,j;
UINT8 HZChar[2] = {43,46};
CHAR8 *c;
CHAR8 k;

//Open the file given by the parameter
Status = ShellOpenFileByName(L"HZK16K.BIN",
(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
HandleBuffer = AllocateZeroPool((UINTN) FileInfo-> FileSize);
if (HandleBuffer == NULL) {
return (SHELL_OUT_OF_RESOURCES); }

ReadSize=(UINTN) FileInfo-> FileSize;

//Load the whole file to the buffer
Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer);
if(Status != RETURN_SUCCESS) {
Print(L"ReadFile failed!\n");
return EFI_SUCCESS;
}

for (i=0;i<DOTS_BYTES;i++)
{
c=((UINT8*)HandleBuffer)+HZ_INDEX(HZChar)+i;
k=*c;
for (j=0;j<8;j++)
{
if (0 == (k & 0x80))
{
Print(L" ");
}
else
{
Print(L"OO");
}
k=k<<1;
}
if ((i+1)%2==0) {Print(L"\n");}
}

FreePool(HandleBuffer);
ShellCloseFile((SHELL_FILE_HANDLE *)&FileHandle);

}
 

 

运行结果(特别注意要把字库文件放在Fsnt0:这样的目录下):

image002

更换一下区位码,我们还可以取得“我”的字形。
image004

完整的代码下载:
HZ

最后,关于【参考1】的代码多说两句。其中有unsigned char word[3] = “我”; 这样直接的定义,这是因为很久很久之前,为了编便于 PC处理汉字定义一个汉字由两个大于127的ASCII码组成。组成的规则是:区码+A0,位码+A0。比如,我在中文环境下定义一个“宋”,
image008

然后切换到英文环境下打开,看到的是2个ASCII码,

image010

如果再切换到十六进制编辑,会看到 CB CE (前提是保存为 ANSI格式,如果你存为unicode,看到的又是另外的东西)

image012

时代已经变了,对于 Windows 编程来说上述的知识都已经过时,如果你需要搞嵌入式开发,还是值得认真学习和理解。
另外,PC刚开始流行的时候,很长一段时间都有汉字不适合PC处理等等的言论,对于普通用户来说,汉字的输入也是很大的困扰。而最终的解决,我认为是人们强烈的交流的需求使得这样的问题很快被克服掉了。时至今日,我仍然能记得同一个寝室的胖子在他的 Nokia手机上,在十几个按键上运指如飞和各种MM聊得火热。很快,没人再认为汉字在PC的普及上是一个问题。

参考 :
1.https://blog.twofei.com/embedded/hzk.html HZK16汉字16*16点阵字库的使用及示例程序
2.http://blog.csdn.net/turingo/article/details/8191712 图灵狗的专栏
3.http://www.jscj.com/index/gb2312.php 汉字区位码查询系统 (具体)

==============================================================
2018年12月30日 补充: 在【参考1】的文章中提供了一个取得字模的代码,我在 Win10 下实验过,很好用:

代码如下:

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

int main(void)
{
	FILE* fphzk = NULL;
	int i, j, k, offset;
	int flag;
	unsigned char buffer[32];
	unsigned char word[5];
	unsigned char key[8] = {
		0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
	};
	fphzk = fopen("hzk16", "rb");
	if(fphzk == NULL){
		fprintf(stderr, "error hzk16\n");
		return 1;
	}
	while(1){
		printf("输入要生成字模的汉字(多个):");
		for(;;){
			fgets((char*)word, 3, stdin);
			if(*word == '\n') 
				break;
			offset = (94*(unsigned int)(word[0]-0xa0-1)+(word[1]-0xa0-1))*32;
			fseek(fphzk, offset, SEEK_SET);
			fread(buffer, 1, 32, fphzk);
			for(k=0; k<16; k++){
				for(j=0; j<2; j++){
					for(i=0; i<8; i++){
						flag = buffer[k*2+j]&key[i];
						printf("%s", flag?"●":"○");
					}
				}
				printf("\n");
			}
			printf("uchar code key[32] = {");
			for(k=0; k<31; k++){
				printf("0x%02X,", buffer[k]);
			}
			printf("0x%02X};\n", buffer[31]);
			printf("\n");
		}
	}
	fclose(fphzk);
	fphzk = NULL;
	return 0;
}

 

如果你是英文的OS,需要先切换内码为 CP936

Step to UEFI (79) —–取得字形

我们可以通过EFI_HII_FONT_PROTOCOL【参考1】 中的 GetGlyph来取得一些字符的字形定义。

gly1

GetGlyph 的原型可以在 \MdePkg\Include\Protocol\HiiFont.h 中找到:

/**

  Convert the glyph for a single character into a bitmap.

  @param This       A pointer to the EFI_HII_FONT_PROTOCOL instance.

  @param Char       The character to retrieve.

  @param StringInfo Points to the string font and color
                    information or NULL if the string should use
                    the default system font and color.

  @param Blt        This must point to a NULL on entry. A buffer will
                    be allocated to hold the output and the pointer
                    updated on exit. It is the caller's responsibility
                    to free this buffer.

  @param Baseline   The number of pixels from the bottom of the bitmap
                    to the baseline.


  @retval EFI_SUCCESS             The glyph bitmap created.

  @retval EFI_OUT_OF_RESOURCES    Unable to allocate the output buffer Blt.

  @retval EFI_WARN_UNKNOWN_GLYPH  The glyph was unknown and was
                                  replaced with the glyph for
                                  Unicode character code 0xFFFD.

  @retval EFI_INVALID_PARAMETER   Blt is NULL, or Width is NULL, or
                                  Height is NULL


**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_GET_GLYPH)(
  IN CONST  EFI_HII_FONT_PROTOCOL *This,
  IN CONST  CHAR16                Char,
  IN CONST  EFI_FONT_DISPLAY_INFO *StringInfo,
  OUT       EFI_IMAGE_OUTPUT      **Blt,
  OUT       UINTN                 *Baseline OPTIONAL
);

 

其中 Char 是你要取对应字形的文字(特别注意是单个的CHAR16),StringInfo 为 NULL时取得的是系统默认的字体,输出结果在 Blt 中。最后一项含义我不清楚……
再看一下输出结果是 EFI_IMAGE_OUTPUT。它的定义在 \MdePkg\Include\Protocol\HiiImage.h中。其中含有一个Union的定义,在我们这里使用时,会按照 EFI_GRAPHICS_OUTPUT_BLT_PIXEL 给出。

最后写一个简单的测试程序如下,功能是取得“z”字符的字形。

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

#include <Protocol/HiiFont.h>

extern EFI_BOOT_SERVICES         *gBS;
 
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS	Status = 0;
	UINTN		BaseLine;
	UINTN		i,j;	
    EFI_HII_FONT_PROTOCOL	*HiiFont = 0;
	EFI_IMAGE_OUTPUT		*Blt=NULL;
	CHAR8		*c,*p;
	
	Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL, (VOID **) &HiiFont);
	if (Status!=EFI_SUCCESS) {
		Print(L"Error when LocateProtocol gEfiHiiFontProtocolGuid. Code[%r]\n",Status);
		return EFI_SUCCESS;
	}
   Status = HiiFont->GetGlyph (
				HiiFont,	//Protocol instance
				L'Z',		//Show char 'Z'
				NULL,		//Use the defualt system font and color
				&Blt,		//GLYPH information
				&BaseLine); //I don't know
				
	Print(L"This is [%d]x[%d]\n",Blt->Width,Blt->Height);
	
	c=(CHAR8*) Blt->Image.Bitmap;			
	for (j=0;j<Blt->Height;j++)
		{
			p=c;
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			Print(L"   ");	
			c=p+1;	
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			Print(L"   ");					
			c=p+2;	
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			c=p+(Blt->Width*4);
			Print(L"\n");	
		}	
		
	c=(CHAR8*) Blt->Image.Bitmap;			
	for (j=0;j<Blt->Height;j++)
		{
			for (i=0;i<Blt->Width;i++)
				{
					if (*c!=0) {
						Print(L"*");
					}
					else
					  {
						Print(L" ");
					  }	
					c=c+4;
				}
			Print(L"\n");	
		}			
		
	return EFI_SUCCESS;
}

 

运行结果,首先输出的是 R G B 数组,隐隐约约能看到其中有一个形状

gly2

程序后面有一个判断,直接输出星号和空格,结果如下,这样就看到非常清楚了。

gly3

完整的代码下载
GetGlyph

唯一的问题是:我还不知道取得这个东西能有什么用途…….

参考:
1.UEFI spec 2.4 P1711

Step to UEFI (78) —–SERIAL_IO_PROTOCOL

串口是非常有效和廉价的Debug手段,在开发中,几乎所有的UEFI 主板都会支持串口,本文介绍如何在Shell下面实现 串口通讯。
与之相关的是 EFI_SERIAL_IO_PROTOCOL,这个 Protocol 的定义可以在 UEFI Spec【参考1】中看到:

image001

代码如下:

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

#include  <Protocol/SerialIo.h>

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

EFI_GUID gEfiSerialIoProtocolGuid = { 0xBB25CF6F, 0xF1D4, 0x11D2, { 0x9A, 0x0C, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0xFD }};

EFI_STATUS
DumpSetting( 
  IN UINT64    BaudRate,
  IN UINT32    ReceiveFifoDepth,
  IN UINT32    Timeout,
  IN EFI_PARITY_TYPE    Parity,
  IN UINT8              DataBits,
  IN EFI_STOP_BITS_TYPE StopBits)
{
	Print(L"   Timeout: [%d]\n",Timeout);
	Print(L"   BaudRate:[%ld]\n",BaudRate);
	Print(L"   DataBits:[%d]\n",DataBits);
	Print(L"   Parity:  [%d]\n",Parity);
	Print(L"   StopBits:[%d]\n",StopBits);	
	Print(L"   ReceiveFifoDepth:[%d]\n",ReceiveFifoDepth);
	
	return EFI_SUCCESS;
}

  
int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
    EFI_STATUS          			Status;
	EFI_SERIAL_IO_PROTOCOL 			*Serial;
	CHAR8 							*Textbuf1 = "www.lab-z.com  waiting.........\n";
	CHAR8 							*Textbuf2 = "Continue............\n";
	CHAR8 							*Textbuf3 = "12345678";	
	CHAR16 							*Textbuf4 = L"                               ";	
	UINTN							BufferSize;
	EFI_TIME						TimeStart,TimeEnd;	
	
    Status = gBS->LocateProtocol(
						&gEfiSerialIoProtocolGuid, 
						NULL,
						(VOID **)&Serial);
				
	
    if (EFI_ERROR(Status)) {
		    Print(L"Cannot find EFI_SERIAL_IO_PROTOCOL \r\n");
            return Status;	
	}

	Print(L"Current Settings:\n");
	DumpSetting(
			Serial->Mode->BaudRate,
			Serial->Mode->ReceiveFifoDepth,
			Serial->Mode->Timeout,
			Serial->Mode->Parity,
			Serial->Mode->DataBits & 0xFF,			
			Serial->Mode->StopBits);

	// Baudrate 115200,Data Bits=8,Parity=None,Stop Bits=1,Flow Type= None
	Status=Serial->SetAttributes(  Serial,
							115200,
							Serial->Mode->ReceiveFifoDepth,
							Serial->Mode->Timeout,
							NoParity,
							8,
							OneStopBit);
	if (Status!=EFI_SUCCESS) {
		Print(L"[%d], %r",Status,Status);
	}
							
	Print(L"New Settings:\n");
	DumpSetting(
			Serial->Mode->BaudRate,
			Serial->Mode->ReceiveFifoDepth,
			Serial->Mode->Timeout,
			Serial->Mode->Parity,
			Serial->Mode->DataBits & 0xFF,			
			Serial->Mode->StopBits);							
	
	BufferSize=AsciiStrLen(Textbuf1);
	Serial->Write(Serial,&BufferSize,Textbuf1);
	
	gRT->GetTime(&TimeStart,NULL);
	TimeEnd=TimeStart;

	while ((TimeEnd.Hour - TimeStart.Hour) * 60 * 60 
			+ (TimeEnd.Minute - TimeStart.Minute)*60 
			+ (TimeEnd.Second - TimeStart.Second) < 30)
	  {
		 BufferSize=AsciiStrLen(Textbuf3);	  
		 Status=Serial->Read(Serial,&BufferSize,Textbuf3);
		 if ((Status==EFI_SUCCESS) && (BufferSize!=0))
			{
				Print(L"read [%d] %s\n",BufferSize,AsciiStrToUnicodeStr(Textbuf3,Textbuf4));
			}
			gRT->GetTime(&TimeEnd,NULL);	
	  }	

	BufferSize=AsciiStrLen(Textbuf2);	  
	Serial->Write(Serial,&BufferSize,Textbuf2);
	
    return EFI_SUCCESS;
}

 

上面程序的基本流程:首先检查一下串口设置,打印在屏幕上,然后设置为我们通常使用的 115200。最后测试用 Write从 Shell 下发送数据出来,再尝试用Read接收数据。

程序运行结果:

image002

完整代码下载:

SerialTest

特别注意:Shell 使用 Read 只能接收固定长度的数据。比如: Read(Serial,8,Textbuf) 那么只能接收8个字符,如果你只输入了7个bytes,不会有反应;如果输入了9个bytes,那么只能收到前面8个。目前不清楚为什么有这样的限制。

另外,对于普通的串口,使用GetControl 获得的当前的状态中并没有当前串口的发送接收状态。定义的 EFI_SERIAL_INPUT_BUFFER_EMPTY和EFI_SERIAL_OUTPUT_BUFFER_EMPTY 应该是给存在对应线路的串口使用的,是一种硬件线路的标志。如果你只用了 TX RX GND , 这里的状态是没有意义的。

参考:
1. UEFI Spec 2.4 P476

Step to UEFI (77) —–改造 Stall和 MV

我们在 EDK2 的代码中能看到 Shell下部分命令的代码,这里介绍如何把这样的代码提取出来做成能够独立编译和运行的程序。简单起见,以 Stall 命令和 MV 命令为例。

经过试验,这些命令中使用到的大部分函数都可以在 ShellLib.h 中找到,我们要做的只是把这个文件copy一份到我们程序下面。
最后修改之后的程序如下:

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] = L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] = L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]= L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_PROBLEM_VAL[] = L"Error. The argument '%s' has incorrect value.\r\n";
CONST CHAR16 STR_STALL_FAILED[]    = L"Error. BootService Stall() failed with %r.\r\n";

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS          Status;
	LIST_ENTRY          *Package;
	CHAR16              *ProblemParam;
    SHELL_STATUS        ShellStatus;
	UINT64              Intermediate;
  
	//
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM,ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  }  else {
    if (ShellCommandLineGetRawValue(Package, 2) != NULL) {
      Print(STR_GEN_TOO_MANY);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else if (ShellCommandLineGetRawValue(Package, 1) == NULL) {
      Print(STR_GEN_TOO_FEW);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      Status = ShellConvertStringToUint64(
				ShellCommandLineGetRawValue(Package, 1), &Intermediate, FALSE, FALSE);
      if (EFI_ERROR(Status) || ((UINT64)(UINTN)(Intermediate)) != Intermediate) {
        Print(STR_GEN_PROBLEM_VAL, ShellCommandLineGetRawValue(Package, 1));
        ShellStatus = SHELL_INVALID_PARAMETER;
      } else {
        Status = gBS->Stall((UINTN)Intermediate);
        if (EFI_ERROR(Status)) {
          Print(STR_STALL_FAILED, Status);
          ShellStatus = SHELL_DEVICE_ERROR;
        }
      }
    }
    ShellCommandLineFreeVarList (Package);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zstall

完整代码下载(特别注意,涉及到时钟的程序在NT32模拟环境中和实际环境中存在很大差别,不要在实际环境中使用为模拟环境编译的EFI文件)。
zStall

总结一下,如果想把一个命令改造为独立的程序,需要做下面的事情:

1. 增加 #define ASSERT(Expression) 这个定义,上面代码为了简单,我只是定义它为空
2. 拷贝 ShellLib.h 到你的代码目录下,然后用 “”直接使用
3. 改造程序中定义的字符串,这些字符串都是定义在 UNI 文件中。如果你没有多语言的需要,可以像我这样在代码中重新定义一次
4. 将所有的 ShellPrintHiiEx 都修改为 Print

下面再用 MV 命令的代码练习一下

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UnicodeCollation.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] =    L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]=    L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] =    L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_NO_CWD[]  =    L"Error. No current directory is specified.\r\n";
CONST CHAR16 STR_GEN_FILE_NF[] =    L"Error. File '%s' was not found.\r\n";
CONST CHAR16 STR_GEN_ERR_FILE[]=    L"Error. File '%s' error: %r\r\n";
CONST CHAR16 STR_MV_INV_SUB[]  = L"Error. Cannot move a directory into itself or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_RO[]   = L"Error. Cannot move a read-only File or Directory.\r\n";
CONST CHAR16 STR_MV_INV_CWD[]  = L"Error. Cannot move current working directory or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_FS[]   = L"Error. Cannot move between file systems.\r\n";
CONST CHAR16 STR_GEN_NO_MEM[]  = L"Error. Memory is not available.\r\n";
CONST CHAR16 STR_GEN_ERR_UK[]  = L"Error: %r\r\n";
CONST CHAR16 STR_MV_OUTPUT[]   = L"Moving %s -> %s\r\n";
CONST CHAR16 STR_GEN_RES_OK[]  = L"- [ok]\r\n";
CONST CHAR16 STR_GEN_MARG_ERROR[] = L"Error. The destination '%s' is ambigious.\r\n";
CONST CHAR16 STR_GEN_FILE_ERROR[] = L"Error. The destination is an existant file '%s'.\r\n";
      
//copy from \ShellPkg\Library\BasePathLib\BasePathLib.c
/**
  Removes the last directory or file entry in a path by changing the last
  L'\' to a CHAR_NULL.

  @param[in, out] Path    The pointer to the path to modify.

  @retval FALSE     Nothing was found to remove.
  @retval TRUE      A directory or file was removed.
**/
BOOLEAN
EFIAPI
PathRemoveLastItem(
  IN OUT CHAR16 *Path
  )
{
  CHAR16        *Walker;
  CHAR16        *LastSlash;
  //
  // get directory name from path... ('chop' off extra)
  //
  for ( Walker = Path, LastSlash = NULL
      ; Walker != NULL && *Walker != CHAR_NULL
      ; Walker++
     ){
    if (*Walker == L'\\' && *(Walker + 1) != CHAR_NULL) {
      LastSlash = Walker+1;
    }
  }
  if (LastSlash != NULL) {
    *LastSlash = CHAR_NULL;
    return (TRUE);
  }
  return (FALSE);
}

/**
  Function to take a destination path that might contain wildcards and verify
  that there is only a single possible target (IE we cant have wildcards that
  have 2 possible destination).

  if the result is sucessful the caller must free *DestPathPointer.

  @param[in] DestDir               The original path to the destination.
  @param[in, out] DestPathPointer  A pointer to the callee allocated final path.
  @param[in] Cwd                   A pointer to the current working directory.

  @retval SHELL_INVALID_PARAMETER  The DestDir could not be resolved to a location.
  @retval SHELL_INVALID_PARAMETER  The DestDir could be resolved to more than 1 location.
  @retval SHELL_INVALID_PARAMETER  Cwd is required and is NULL.
  @retval SHELL_SUCCESS            The operation was sucessful.
**/
SHELL_STATUS
EFIAPI
GetDestinationLocation(
  IN CONST CHAR16               *DestDir,
  IN OUT CHAR16                 **DestPathPointer,
  IN CONST CHAR16               *Cwd
  )
{
  EFI_SHELL_FILE_INFO       *DestList;
  EFI_SHELL_FILE_INFO       *Node;
  CHAR16                    *DestPath;
  UINTN                     NewSize;
  UINTN                     CurrentSize;

  DestList = NULL;
  DestPath = NULL;

  if (StrStr(DestDir, L"\\") == DestDir) {
    if (Cwd == NULL) {
      return SHELL_INVALID_PARAMETER;
    }
    DestPath = AllocateZeroPool(StrSize(Cwd));
    if (DestPath == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }
    StrCpy(DestPath, Cwd);
    while (PathRemoveLastItem(DestPath)) ;

    //
    // Append DestDir beyond '\' which may be present
    //
    CurrentSize = StrSize(DestPath);
    StrnCatGrow(&DestPath, &CurrentSize, &DestDir[1], 0);

    *DestPathPointer =  DestPath;
    return (SHELL_SUCCESS);
  }
  //
  // get the destination path
  //
  ShellOpenFileMetaArg((CHAR16*)DestDir, EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ|EFI_FILE_MODE_CREATE, &DestList);
  if (DestList == NULL || IsListEmpty(&DestList->Link)) {
    //
    // Not existing... must be renaming
    //
    if (StrStr(DestDir, L":") == NULL) {
      if (Cwd == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_INVALID_PARAMETER);
      }
      NewSize = StrSize(Cwd);
      NewSize += StrSize(DestDir);
      DestPath = AllocateZeroPool(NewSize);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Cwd);
      if (DestPath[StrLen(DestPath)-1] != L'\\' && DestDir[0] != L'\\') {
        StrCat(DestPath, L"\\");
      } else if (DestPath[StrLen(DestPath)-1] == L'\\' && DestDir[0] == L'\\') {
        ((CHAR16*)DestPath)[StrLen(DestPath)-1] = CHAR_NULL;
      }
      StrCat(DestPath, DestDir);
    } else {
      ASSERT(DestPath == NULL);
      DestPath = StrnCatGrow(&DestPath, NULL, DestDir, 0);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
    }
  } else {
    Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&DestList->Link);
    //
    // Make sure there is only 1 node in the list.
    //
    if (!IsNodeAtEnd(&DestList->Link, &Node->Link)) {
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_MARG_ERROR, DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
    if (ShellIsDirectory(Node->FullName)==EFI_SUCCESS) {
      DestPath = AllocateZeroPool(StrSize(Node->FullName)+sizeof(CHAR16));
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Node->FullName);
      StrCat(DestPath, L"\\");
    } else {
      //
      // cant move onto another file.
      //
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_FILE_ERROR,DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
  }

  *DestPathPointer =  DestPath;
  ShellCloseFileMetaArg(&DestList);

  return (SHELL_SUCCESS);
}


/**
  Function to clean up paths.  
  
  - Single periods in the path are removed.
  - Double periods in the path are removed along with a single parent directory.
  - Forward slashes L'/' are converted to backward slashes L'\'.

  This will be done inline and the existing buffer may be larger than required 
  upon completion.

  @param[in] Path       The pointer to the string containing the path.

  @retval NULL          An error occured.
  @return Path in all other instances.
**/
CHAR16*
EFIAPI
PathCleanUpDirectories(
  IN CHAR16 *Path
  )
{
  CHAR16  *TempString;
  UINTN   TempSize;
  if (Path==NULL) {
    return(NULL);
  }

  //
  // Fix up the '/' vs '\'
  //
  for (TempString = Path ; TempString != NULL && *TempString != CHAR_NULL ; TempString++) {
    if (*TempString == L'/') {
      *TempString = L'\\';
    }
  }

  //
  // Fix up the ..
  //
  while ((TempString = StrStr(Path, L"\\..\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 4;
    PathRemoveLastItem(Path);
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\..")) != NULL && *(TempString + 3) == CHAR_NULL) {
    *TempString = CHAR_NULL;
    PathRemoveLastItem(Path);
  }

  //
  // Fix up the .
  //
  while ((TempString = StrStr(Path, L"\\.\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 2;
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\.")) != NULL && *(TempString + 2) == CHAR_NULL) {
    *(TempString + 1) = CHAR_NULL;
  }



  return (Path);
}

STATIC EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

/**
  Function to compare 2 strings without regard to case of the characters.

  @param[in] Buffer1            Pointer to String to compare.
  @param[in] Buffer2            Pointer to second String to compare.

  @retval 0                     Buffer1 equal to Buffer2.
  @return < 0                   Buffer1 is less than Buffer2.
  @return > 0                   Buffer1 is greater than Buffer2.
**/
INTN
EFIAPI
StringNoCaseCompare (
  IN  CONST VOID             *Buffer1,
  IN  CONST VOID             *Buffer2
  )
{
  EFI_STATUS                Status;
  if (mUnicodeCollation == NULL) {
    Status = gBS->LocateProtocol(
      &gEfiUnicodeCollation2ProtocolGuid,
      NULL,
      (VOID**)&mUnicodeCollation);

    ASSERT(Status);
  }

  return (mUnicodeCollation->StriColl(
    mUnicodeCollation,
    *(CHAR16**)Buffer1,
    *(CHAR16**)Buffer2));
}

/**
  Function to validate that moving a specific file (FileName) to a specific
  location (DestPath) is valid.

  This function will verify that the destination is not a subdirectory of
  FullName, that the Current working Directory is not being moved, and that
  the directory is not read only.

  if the move is invalid this function will report the error to StdOut.

  @param FullName [in]    The name of the file to move.
  @param Cwd      [in]    The current working directory
  @param DestPath [in]    The target location to move to
  @param Attribute[in]    The Attribute of the file

  @retval TRUE        The move is valid
  @retval FALSE       The move is not
**/
BOOLEAN
EFIAPI
IsValidMove(
  IN CONST CHAR16   *FullName,
  IN CONST CHAR16   *Cwd,
  IN CONST CHAR16   *DestPath,
  IN CONST UINT64   Attribute
  )
{
  CHAR16  *Test;
  CHAR16  *Test1;
  CHAR16  *TestWalker;
  INTN    Result;
  UINTN   TempLen;
  if (Cwd != NULL && StrCmp(FullName, Cwd) == 0) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_CWD);
    return (FALSE);
  }
  Test = NULL;
  Test = StrnCatGrow(&Test, NULL, DestPath, 0);
  TestWalker = Test;
  ASSERT(TestWalker != NULL);
  while(*TestWalker == L'\\') {
    TestWalker++;
  }
  while(TestWalker != NULL && TestWalker[StrLen(TestWalker)-1] == L'\\') {
    TestWalker[StrLen(TestWalker)-1] = CHAR_NULL;
  }
  ASSERT(TestWalker != NULL);
  ASSERT(FullName   != NULL);
  if (StrStr(FullName, TestWalker) != 0) {
    TempLen = StrLen(FullName);
    if (StrStr(FullName, TestWalker) != FullName                    // not the first items... (could below it)
      && TempLen <= (StrLen(TestWalker) + 1)
      && StrStr(FullName+StrLen(TestWalker) + 1, L"\\") == NULL) {
      //
      // Invalid move
      //
      Print(STR_MV_INV_SUB);
      FreePool(Test);
      return (FALSE);
    }
  }
  FreePool(Test);
  if (StrStr(DestPath, FullName) != 0 && StrStr(DestPath, FullName) != DestPath) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_SUB);
    return (FALSE);
  }
  if ((Attribute & EFI_FILE_READ_ONLY) != 0) {
    //
    // invalid to move read only
    //
    Print(STR_MV_INV_RO);
    return (FALSE);
  }
  Test  = StrStr(FullName, L":");
  Test1 = StrStr(DestPath, L":");
  if (Test1 != NULL && Test  != NULL) {
    *Test  = CHAR_NULL;
    *Test1 = CHAR_NULL;
    Result = StringNoCaseCompare(&FullName, &DestPath);
    *Test  = L':';
    *Test1 = L':';
    if (Result != 0) {
      Print(STR_MV_INV_FS);
      return (FALSE);
    }
  }
  return (TRUE);
}

/**
  function to take a list of files to move and a destination location and do
  the verification and moving of those files to that location.  This function
  will report any errors to the user and continue to move the rest of the files.

  @param[in] FileList           A LIST_ENTRY* based list of files to move
  @param[out] Resp              pointer to response from question.  Pass back on looped calling
  @param[in] DestDir            the destination location

  @retval SHELL_SUCCESS             the files were all moved.
  @retval SHELL_INVALID_PARAMETER   a parameter was invalid
  @retval SHELL_SECURITY_VIOLATION  a security violation ocurred
  @retval SHELL_WRITE_PROTECTED     the destination was write protected
  @retval SHELL_OUT_OF_RESOURCES    a memory allocation failed
**/
SHELL_STATUS
EFIAPI
ValidateAndMoveFiles(
  IN CONST EFI_SHELL_FILE_INFO  *FileList,
  OUT VOID                      **Resp,
  IN CONST CHAR16               *DestDir
  )
{
  EFI_STATUS                Status;
  CHAR16                    *DestPath;
  CONST CHAR16              *Cwd;
  SHELL_STATUS              ShellStatus;
  CONST EFI_SHELL_FILE_INFO *Node;
  EFI_FILE_INFO             *NewFileInfo;
  CHAR16                    *TempLocation;
  UINTN                     NewSize;
  UINTN                     Length;
  VOID                      *Response;
  SHELL_FILE_HANDLE         DestHandle;
  CHAR16 STR_GEN_DEST_EXIST_OVR[] = L"Destination file already exists.  Overwrite? Yes, No, All, Cancel ";
  
  ASSERT(FileList != NULL);
  ASSERT(DestDir  != NULL);

  DestPath = NULL;
  Cwd      = ShellGetCurrentDir(NULL);
  Response = *Resp;

  //
  // Get and validate the destination location
  //
  ShellStatus = GetDestinationLocation(DestDir, &DestPath, Cwd);
  if (ShellStatus != SHELL_SUCCESS) {
    return (ShellStatus);
  }
  DestPath = PathCleanUpDirectories(DestPath);

  //
  // Go through the list of files and directories to move...
  //
  for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode(&FileList->Link)
    ;  !IsNull(&FileList->Link, &Node->Link)
    ;  Node = (EFI_SHELL_FILE_INFO *)GetNextNode(&FileList->Link, &Node->Link)
   ){
    if (ShellGetExecutionBreakFlag()) {
      break;
    }
    ASSERT(Node->FileName != NULL);
    ASSERT(Node->FullName != NULL);

    //
    // skip the directory traversing stuff...
    //
    if (StrCmp(Node->FileName, L".") == 0 || StrCmp(Node->FileName, L"..") == 0) {
      continue;
    }

    //
    // Validate that the move is valid
    //
    if (!IsValidMove(Node->FullName, Cwd, DestPath, Node->Info->Attribute)) {
      ShellStatus = SHELL_INVALID_PARAMETER;
      continue;
    }

    //
    // Chop off map info from "DestPath"
    //
    if ((TempLocation = StrStr(DestPath, L":")) != NULL) {
      CopyMem(DestPath, TempLocation+1, StrSize(TempLocation+1));
    }

    //
    // construct the new file info block
    //
    NewSize = StrSize(DestPath);
    NewSize += StrSize(Node->FileName) + SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
    NewFileInfo = AllocateZeroPool(NewSize);
    if (NewFileInfo == NULL) {
      Print(STR_GEN_NO_MEM);
      ShellStatus = SHELL_OUT_OF_RESOURCES;
    } else {
      CopyMem(NewFileInfo, Node->Info, SIZE_OF_EFI_FILE_INFO);
      if (DestPath[0] != L'\\') {
        StrCpy(NewFileInfo->FileName, L"\\");
        StrCat(NewFileInfo->FileName, DestPath);
      } else {
        StrCpy(NewFileInfo->FileName, DestPath);
      }
      Length = StrLen(NewFileInfo->FileName);
      if (Length > 0) {
        Length--;
      }
      if (NewFileInfo->FileName[Length] == L'\\') {
        if (Node->FileName[0] == L'\\') {
          //
          // Don't allow for double slashes. Eliminate one of them.
          //
          NewFileInfo->FileName[Length] = CHAR_NULL;
        }
        StrCat(NewFileInfo->FileName, Node->FileName);
      }
      NewFileInfo->Size = SIZE_OF_EFI_FILE_INFO + StrSize(NewFileInfo->FileName);
      Print(STR_MV_OUTPUT, Node->FullName, NewFileInfo->FileName);

      if (!EFI_ERROR(ShellFileExists(NewFileInfo->FileName))) {
        if (Response == NULL) {
          ShellPromptForResponse(ShellPromptResponseTypeYesNoAllCancel, STR_GEN_DEST_EXIST_OVR,  &Response);
        }
        switch (*(SHELL_PROMPT_RESPONSE*)Response) {
          case ShellPromptResponseNo:
            FreePool(NewFileInfo);
            continue;
          case ShellPromptResponseCancel:
            *Resp = Response;
            //
            // indicate to stop everything
            //
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return (SHELL_ABORTED);
          case ShellPromptResponseAll:
            *Resp = Response;
            break;
          case ShellPromptResponseYes:
            FreePool(Response);
            break;
          default:
            FreePool(Response);
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return SHELL_ABORTED;
        }
        Status = ShellOpenFileByName(NewFileInfo->FileName, &DestHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);
        ShellDeleteFile(&DestHandle);
      }


      //
      // Perform the move operation
      //
      Status = ShellSetFileInfo(Node->Handle, NewFileInfo);

      //
      // Free the info object we used...
      //
      FreePool(NewFileInfo);

      //
      // Check our result
      //
      if (EFI_ERROR(Status)) {
        Print(STR_GEN_ERR_UK, Status);
        ShellStatus = SHELL_INVALID_PARAMETER;
        if (Status == EFI_SECURITY_VIOLATION) {
          ShellStatus = SHELL_SECURITY_VIOLATION;
        } else if (Status == EFI_WRITE_PROTECTED) {
          ShellStatus = SHELL_WRITE_PROTECTED;
        } else if (Status == EFI_OUT_OF_RESOURCES) {
          ShellStatus = SHELL_OUT_OF_RESOURCES;
        } else if (Status == EFI_DEVICE_ERROR) {
          ShellStatus = SHELL_DEVICE_ERROR;
        } else if (Status == EFI_ACCESS_DENIED) {
          ShellStatus = SHELL_ACCESS_DENIED;
        }
      } else {
        Print( L"%s", STR_GEN_RES_OK);
      }
    }
  } // for loop

  FreePool(DestPath);
  return (ShellStatus);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS          Status;
  LIST_ENTRY          *Package;
  CHAR16              *ProblemParam;
  SHELL_STATUS        ShellStatus;
  UINTN               ParamCount;
  UINTN               LoopCounter;
  EFI_SHELL_FILE_INFO *FileList;
  VOID                *Response;

  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  ParamCount          = 0;
  FileList            = NULL;
  Response            = NULL;

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM , ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag(Package, L"-?")) {
      ASSERT(FALSE);
    }

    switch (ParamCount = ShellCommandLineGetCount(Package)) {
      case 0:
      case 1:
        //
        // we have insufficient parameters
        //
        Print(STR_GEN_TOO_FEW);
        ShellStatus = SHELL_INVALID_PARAMETER;
        break;
      case 2:
        //
        // must have valid CWD for single parameter...
        //
        if (ShellGetCurrentDir(NULL) == NULL){
          Print(STR_GEN_NO_CWD);
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, 1), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, 1));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            //
            ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellGetCurrentDir(NULL));
          }
        }

        break;
      default:
        ///@todo make sure this works with error half way through and continues...
        for (ParamCount--, LoopCounter = 1 ; LoopCounter < ParamCount ; LoopCounter++) {
          if (ShellGetExecutionBreakFlag()) {
            break;
          }
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, LoopCounter), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, LoopCounter));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            // Only change ShellStatus if it's sucessful
            //
            if (ShellStatus == SHELL_SUCCESS) {
              ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            } else {
              ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            }
          }
          if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
            Status = ShellCloseFileMetaArg(&FileList);
            if (EFI_ERROR(Status) && ShellStatus == SHELL_SUCCESS) {
              ShellStatus = SHELL_ACCESS_DENIED;
              Print(STR_GEN_ERR_FILE , ShellCommandLineGetRawValue(Package, 1), ShellStatus|MAX_BIT);
            }
          }
        }
        break;
    } // switch on parameter count

    if (FileList != NULL) {
      ShellCloseFileMetaArg(&FileList);
    }

    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  SHELL_FREE_NON_NULL(Response);

  if (ShellGetExecutionBreakFlag()) {
    return (SHELL_ABORTED);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zmv1

完整代码下载:

zMv

Step to UEFI (76) —–Dump ACPI DSDT

前面介绍了如何在 Shell下 Dump 系统中各种 ConfigurationTable的方法,这里介绍一下如何取得 ACPI 的 DSDT Table。
简单说一下原理:

1. 在ConfigurationTable中查找 GUID 为 ACPI_TABLE_GUID 和 EFI_ACPI_TABLE_GUID 的两个 Table. 检查获得 Table 的 Revision,只有 >=2 的才是我们需要的
2. 通过Table找到 XsdtAddress 这样我们能找到 XSDT Table
3. 在 XSDT 给出的Table中找到指向 FADT Table 的
4. 在 FADT 中直接给出了 DSDT 的地址
5. 最后将整个DSDT Table dump出来保存为文件即可

用图表简单描述上述过程

image001

代码看起来很复杂,主要是各种指针比较多。

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

#include <Protocol/SimpleFileSystem.h>

#define ACPI_TABLE_GUID \
  { \
    0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_ACPI_TABLE_GUID \
  { \
    0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } \
  }

#define zDebug 1

///
/// RSD_PTR Revision (as defined in ACPI 5.0 spec.)
///
#define EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION 0x02  
			///< ACPISpec (Revision 5.0) says current value is 2
  
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;

#pragma pack(1)
// \MdePkg\Include\IndustryStandard\Acpi50.h
//
// ACPI 5.0 table structures
//

///
/// Root System Description Pointer Structure
///
typedef struct {
  UINT64  Signature;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT8   Revision;
  UINT32  RsdtAddress;
  UINT32  Length;
  UINT64  XsdtAddress;
  UINT8   ExtendedChecksum;
  UINT8   Reserved[3];
} EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER;

// \MdePkg\Include\IndustryStandard\Acpi10.h
///
/// The common ACPI description table header. 
/// This structure prefaces most ACPI tables.
///
typedef struct {
  UINT32  Signature;
  UINT32  Length;
  UINT8   Revision;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT64  OemTableId;
  UINT32  OemRevision;
  UINT32  CreatorId;
  UINT32  CreatorRevision;
} EFI_ACPI_DESCRIPTION_HEADER;

// \MdePkg\Include\IndustryStandard\Acpi50.h
///
/// ACPI 5.0 Generic Address Space definition
///
typedef struct {
  UINT8   AddressSpaceId;
  UINT8   RegisterBitWidth;
  UINT8   RegisterBitOffset;
  UINT8   AccessSize;
  UINT64  Address;
} EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE;

// \MdePkg\Include\Protocol\AcpiSystemDescriptionTable.h  
///
/// Fixed ACPI Description Table Structure (FADT)
///
typedef struct {
  EFI_ACPI_DESCRIPTION_HEADER             Header;
  UINT32                                  FirmwareCtrl;
  UINT32                                  Dsdt;
  UINT8                                   Reserved0;
  UINT8                                   PreferredPmProfile;
  UINT16                                  SciInt;
  UINT32                                  SmiCmd;
  UINT8                                   AcpiEnable;
  UINT8                                   AcpiDisable;
  UINT8                                   S4BiosReq;
  UINT8                                   PstateCnt;
  UINT32                                  Pm1aEvtBlk;
  UINT32                                  Pm1bEvtBlk;
  UINT32                                  Pm1aCntBlk;
  UINT32                                  Pm1bCntBlk;
  UINT32                                  Pm2CntBlk;
  UINT32                                  PmTmrBlk;
  UINT32                                  Gpe0Blk;
  UINT32                                  Gpe1Blk;
  UINT8                                   Pm1EvtLen;
  UINT8                                   Pm1CntLen;
  UINT8                                   Pm2CntLen;
  UINT8                                   PmTmrLen;
  UINT8                                   Gpe0BlkLen;
  UINT8                                   Gpe1BlkLen;
  UINT8                                   Gpe1Base;
  UINT8                                   CstCnt;
  UINT16                                  PLvl2Lat;
  UINT16                                  PLvl3Lat;
  UINT16                                  FlushSize;
  UINT16                                  FlushStride;
  UINT8                                   DutyOffset;
  UINT8                                   DutyWidth;
  UINT8                                   DayAlrm;
  UINT8                                   MonAlrm;
  UINT8                                   Century;
  UINT16                                  IaPcBootArch;
  UINT8                                   Reserved1;
  UINT32                                  Flags;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  ResetReg;
  UINT8                                   ResetValue;
  UINT8                                   Reserved2[3];
  UINT64                                  XFirmwareCtrl;
  UINT64                                  XDsdt;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1aEvtBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1bEvtBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1aCntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1bCntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm2CntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPmTmrBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XGpe0Blk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XGpe1Blk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  SleepControlReg;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  SleepStatusReg;
} EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE;  
#pragma pack()

EFI_STATUS
PrintGuid (
  IN EFI_GUID *Guid
  )
  
/*++

Routine Description:

  This function prints a GUID to STDOUT.

Arguments:

  Guid    Pointer to a GUID to print.

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
  if (Guid == NULL) {
	Print(L"Parameter error!\n");
    return EFI_INVALID_PARAMETER;
  }

  Print (
    L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x ",
    (unsigned) Guid->Data1,
    Guid->Data2,
    Guid->Data3,
    Guid->Data4[0],
    Guid->Data4[1],
    Guid->Data4[2],
    Guid->Data4[3],
    Guid->Data4[4],
    Guid->Data4[5],
    Guid->Data4[6],
    Guid->Data4[7]
    );
  return EFI_SUCCESS;
}

UINTN
CompareGUID (
  IN EFI_GUID *Guid1,
  IN EFI_GUID *Guid2
  )
  
/*++

Routine Description:

  This function compares 2 GUIDs

Arguments:

  Guid1    Pointer to 1st GUID.
  Guid2    Pointer to 12st GUID.

Returns:

  0             The GUID was same.
  1  		    The input was different.
  2				PARAMETER error.

--*/
{

  if ((Guid1 == NULL)||(Guid2 == NULL)) {
	Print(L"Parameter error!\n");
    return 2;
  }

  if ((Guid1->Data1 != Guid2->Data1) ||
	  (Guid1->Data2 != Guid2->Data2) ||
	  (Guid1->Data3 != Guid2->Data3) ||
      (Guid1->Data4[0] != Guid2->Data4[0]) ||
      (Guid1->Data4[1] != Guid2->Data4[1]) ||
	  (Guid1->Data4[2] != Guid2->Data4[2]) ||
      (Guid1->Data4[3] != Guid2->Data4[3]) ||
      (Guid1->Data4[4] != Guid2->Data4[4]) ||
	  (Guid1->Data4[5] != Guid2->Data4[5]) ||
      (Guid1->Data4[6] != Guid2->Data4[6]) ||
      (Guid1->Data4[7] != Guid2->Data4[7]))	
	{
		return 1;
	}
	return 0;
}

EFI_STATUS
SaveDSDTToFile (
	CHAR8	*P,
	UINTN	length
  )
  
/*++

Routine Description:

  Write a memory to a file

Arguments:

  P          Pointer to the contant
  length	 Sizeof the contant

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
    EFI_STATUS          			Status;
	EFI_FILE_PROTOCOL 				*Root,*FileHandle=0;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;	
	
	Status = gBS->LocateProtocol(
					&gEfiSimpleFileSystemProtocolGuid, 
					NULL,
					(VOID **)&SimpleFileSystem);
						
	if (EFI_ERROR(Status)) {
			Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
			return Status;	
		}

	Status = SimpleFileSystem->OpenVolume(SimpleFileSystem,&Root);
	if (EFI_ERROR(Status)) {
			Print(L"OpenVolume error \r\n");
			return Status;	
		}
   
	Status = Root -> Open(Root,
				&FileHandle,
				(CHAR16 *) L"dsdt.aml",
				EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
				0);
			
	if (EFI_ERROR(Status) || (FileHandle==0)) {
			Print(L"Open error \r\n");
			return Status;	
		}	

	Status = FileHandle -> Write(FileHandle, &length, P);
	
	Status  = FileHandle -> Close (FileHandle);
	
	return EFI_SUCCESS;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	UINTN 		i,j,EntryCount;
	EFI_STATUS	Status;
	CHAR16		Sign[20];
	UINT64		*EntryPtr;
	EFI_GUID	AcpiTableGuid  = ACPI_TABLE_GUID;
	EFI_GUID	Acpi2TableGuid = EFI_ACPI_TABLE_GUID;
	EFI_CONFIGURATION_TABLE		*C=NULL;	
	EFI_ACPI_DESCRIPTION_HEADER				 		*XSDT,*Entry,*DSDT;
	EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE		*FADT;
	EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER	*Root;

  C=gST->ConfigurationTable;
  
  for (i=0;i<gST->NumberOfTableEntries-1;i++)
	{	  
		//Step1. Find the table for ACPI
		if ((CompareGUID(&C->VendorGuid,&AcpiTableGuid) == 0) ||
			(CompareGUID(&C->VendorGuid,&Acpi2TableGuid) == 0))
			{	
				Print(L"Found table:");
				Status=PrintGuid(&C->VendorGuid); 
				Print(L"@[0x%X]\n",C);
				
				Root=C->VendorTable;
				if (zDebug) {Print(L"ROOT SYSTEM DESCRIPTION @[0x%X]\n",Root);}
				
				if (zDebug) {
						ZeroMem(Sign,sizeof(Sign));
						for (j=0;j<8;j++) { Sign[j]=(Root->Signature >> (j*8) & 0xFF); }
						Print(L"Signature [%s]\n",Sign);
						Print(L"Revision [%d]\n",Root->Revision);
						ZeroMem(Sign,sizeof(Sign));
						for (j=0;j<6;j++) { Sign[j]= (Root->OemId[j] & 0xFF); }
						Print(L"OEMID [%s]\n",Sign);
					}
				
				Print(L"RSDT address= [0x%X], Length=[0x%X]\n",
										Root->RsdtAddress,Root->Length);
				Print(L"XSDT address= [0x%LX]\n",Root->XsdtAddress);
				
				// Step2. Check the Revision, we olny accept Revision >= 2
				if (Root->Revision>=EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION)
					{
						// Step3. Get XSDT address
						XSDT=(EFI_ACPI_DESCRIPTION_HEADER *)(UINTN) Root->XsdtAddress;
						EntryCount = (XSDT->Length - sizeof(EFI_ACPI_DESCRIPTION_HEADER)) 
											/ sizeof(UINT64);
						
						if (zDebug) {
							ZeroMem(Sign,sizeof(Sign));
							Sign[0]= (XSDT->Signature & 0xFF);
							Sign[1]= (XSDT->Signature >> 8 & 0xFF);
							Sign[2]= (XSDT->Signature >> 16 & 0xFF);
							Sign[3]= (XSDT->Signature >> 24 & 0xFF);
							Print(L"Sign [%s]\n",Sign);						
							Print(L"length [%d]\n",XSDT->Length);						
							Print(L"Counter [%d]\n",EntryCount);	
							}
						
						// Step4. Check the signature of every entry
						EntryPtr=(UINT64 *)(XSDT+1);
						for (j=0;j<EntryCount; j++,EntryPtr++)
						  {
							
							Entry=(EFI_ACPI_DESCRIPTION_HEADER *)((UINTN)(*EntryPtr));
							
							if (zDebug) {
								ZeroMem(Sign,sizeof(Sign));
								Sign[0]= (Entry->Signature & 0xFF);
								Sign[1]= (Entry->Signature >> 8 & 0xFF);
								Sign[2]= (Entry->Signature >> 16 & 0xFF);
								Sign[3]= (Entry->Signature >> 24 & 0xFF);
								Print(L"%d: [%s] @[%X]\n",j,Sign,Entry);
							 }
							
							// Step5. Find the FADT table
							if (Entry->Signature==0x50434146) { //'FACP'
								FADT = (EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE *)(UINTN) Entry;
								
								// Step6. Get DSDT address
								DSDT = (EFI_ACPI_DESCRIPTION_HEADER *) (FADT->Dsdt);
								if (zDebug) {
									Print(L"DSDT table @[%X]\n",DSDT);
								}
								
								// Step7. Save DSDT as a file
								SaveDSDTToFile((CHAR8 *)DSDT,DSDT->Length);
							}
						  }
					}
			}
		C++;
	}
	
  return EFI_SUCCESS;
}

 

运行结果(注意:上述代码是在NT32模拟环境下编译通过的,但是因为模拟坏境中没有对应的 ACPI Table,所以必须在实体机上运行才有结果):

image002

最后得到的 DSDT.AML 我们可以直接使用 ASL 工具反编译。

完整代码下载

GetACPI

本文代码参阅了【参考1】,这里表示感谢。

参考:
1. http://blog.fpmurphy.com/2015/01/list-acpi-tables-from-uefi-shell.html List ACPI Tables From UEFI Shell
2. http://www.cnblogs.com/junzhkevin/archive/2013/02/25/2932801.html ACPI Tables (不确定是否为文章出处。这里有对ACPI Table进行简单的介绍,值得阅读)

Step to UEFI (75) —–取得 ConfigurationTable

EFI_SYSTEM_TABLE 中定义了 EFI_CONFIGURATION_TABLE *ConfigurationTable;

这个结构体定义如下:

typedef struct{
EFI_GUID  VendorGuid;
VOID  *VendorTable;
} EFI_CONFIGURATION_TABLE;

 

整体看起来就是这样:

image001

根据上面的原理可以编写程序枚举系统中的 ConfigurationTable,代码如下:

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


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

EFI_STATUS
PrintGuid (
  IN EFI_GUID *Guid
  )
  
/*++

Routine Description:

  This function prints a GUID to STDOUT.

Arguments:

  Guid    Pointer to a GUID to print.

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
  if (Guid == NULL) {
	Print(L"Parameter error!\n");
    return EFI_INVALID_PARAMETER;
  }

  Print (
    L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
    (unsigned) Guid->Data1,
    Guid->Data2,
    Guid->Data3,
    Guid->Data4[0],
    Guid->Data4[1],
    Guid->Data4[2],
    Guid->Data4[3],
    Guid->Data4[4],
    Guid->Data4[5],
    Guid->Data4[6],
    Guid->Data4[7]
    );
  return EFI_SUCCESS;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  UINTN 	i;
  EFI_STATUS	Status;
  EFI_CONFIGURATION_TABLE	*C=NULL;
  
  Print(L"[%d] Tables were found!\n",gST->NumberOfTableEntries);
  C=gST->ConfigurationTable;
  
  for (i=0;i<gST->NumberOfTableEntries-1;i++)
	{
		Status=PrintGuid(&C->VendorGuid);
		C++;
	}
  return EFI_SUCCESS;
}

 

然后我们尝试在模拟环境中运行,结果如下:

image002

我们再看看找到的这些GUID是什么意思:

#define TIANO_CUSTOM_DECOMPRESS_GUID  \
  { 0xA31280AD, 0x481E, 0x41B6, { 0x95, 0xE8, 0x12, 0x7F, 0x4C, 0x98, 0x47, 0x79 } }

#define EFI_CRC32_GUIDED_SECTION_EXTRACTION_GUID \
  { 0xFC1BCDB0, 0x7D31, 0x49aa, {0x93, 0x6A, 0xA4, 0x60, 0x0D, 0x9D, 0xD0, 0x83 } }

#define DXE_SERVICES_TABLE_GUID \
  { \
    0x5ad34ba, 0x6f02, 0x4214, {0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9 } \
}
#define HOB_LIST_GUID \
  { \
    0x7739f24c, 0x93d7, 0x11d4, {0x9a, 0x3a, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_MEMORY_TYPE_INFORMATION_GUID \
  { 0x4c19049f,0x4137,0x4dd3, { 0x9c,0x10,0x8b,0x97,0xa8,0x3f,0xfd,0xfa } }

#define EFI_DEBUG_IMAGE_INFO_TABLE_GUID \
  { \
    0x49152e77, 0x1ada, 0x4764, {0xb7, 0xa2, 0x7a, 0xfe, 0xfe, 0xd9, 0x5e, 0x8b } \
  }
#define SMBIOS_TABLE_GUID \
  { \
    0xeb9d2d31, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

 

能看懂的只有SMBIOS…….我们再用实体机测试,结果如下:

image003

比上面的多了5个GUID,分别是:

#define LZMA_CUSTOM_DECOMPRESS_GUID  \
  { 0xEE4E5898, 0x3914, 0x4259, { 0x9D, 0x6E, 0xDC, 0x7B, 0xD7, 0x94, 0x03, 0xCF } }

#define EFI_TSC_FREQUENCY_GUID \
  { \
    0xdba6a7e3, 0xbb57, 0x4be7, { 0x8a, 0xf8, 0xd5, 0x78, 0xdb, 0x7e, 0x56, 0x87 } \
  }

#define ACPI_TABLE_GUID \
  { \
    0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_ACPI_TABLE_GUID \
  { \
    0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } \
  }

gEfiOfflineCrashDumpTblGuid             = { 0x3804CF02, 0x8538, 0x11E2, { 0x88, 0x47, 0x8D, 0xF1, 0x60, 0x88, 0x70, 0x9B } }

 

本文完整代码下载:

GetATBL

下一次会介绍 Shell 下如何获取ACPI Table。

Step to UEFI (74) —– 通过 OpenVolume访问FSx上的文件

题目有点绕口,简单的说目标就是:我打算用 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 中的 OpenVolume 打开 FsX: 上面的文件怎么办?
实现的思路是:

1. 查找系统中所有支持 FS Protocol的Device
2. 对于每一个有 FS Protocol 的 Device 用 DevicePathFromHandle 取得 DevicePath
3. 再用 GetFsName 功能取得 FS0 ,FS1 这样的名称,然后判断是否为我们希望的名称
4. 如果是的话,再取得这个设备上的 SimpleFileSystem protocol
5. 最后用 OpenVolue 打开文件。

具体代码:

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

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

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

EFI_STATUS
EFIAPI
PerformSingleMappingDisplay(
  IN CONST EFI_HANDLE Handle
  )
{
	EFI_DEVICE_PATH_PROTOCOL  *DevPath;
	CHAR16                    *CurrentName;
	CHAR16					*FSNAME=L"fsnt1";
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
	EFI_FILE_INFO     		*FileInfo = NULL;
	EFI_STATUS                Status;
	EFI_FILE_PROTOCOL   		*FileProtocol;
	EFI_FILE_HANDLE     		FileHandle;
	UINTN 					FileDataLength;
	CHAR16 					*FileData;

	CurrentName = NULL;
    DevPath = DevicePathFromHandle(Handle);
  
  //4. Covnver DevicePath to FSx (E.x FS2, FSNT1.....)
  mEfiShellEnvironment2->GetFsName(DevPath,FALSE,&CurrentName);

  //5. If the "FSx" string is what we want 
  if (StrCmp(CurrentName,FSNAME)==0) {
		Print (L"%s \r\n", CurrentName);  		
		
		//6. Open the SimpleFileSystem Protocol on it
		Status = gBS->OpenProtocol(
								Handle,
                                &gEfiSimpleFileSystemProtocolGuid,
                                (VOID**)&SimpleFileSystem,
								gImageHandle,
								NULL,
								EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                );
		if (EFI_ERROR(Status)) {
			Print (L"LocateProtocol SimpleFileSystem Error \r\n");  	
			return (EFI_NOT_FOUND);
		}
	   
	    //7. Use OpenVolue to get FileProtocol
		Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &FileProtocol);
		if (EFI_ERROR(Status)) {
			Print (L"SimpleFileSystem OpenVolume Error \r\n");
			return Status;
		}
    
		//8. At last we can operate file by FileProtocol
		Status = FileProtocol->Open(FileProtocol, 
                              &FileHandle, 
                              L"Hello.txt",
                              EFI_FILE_MODE_READ,
                              0);
		if (EFI_ERROR(Status)) {
			Print (L"FileProtocol Open Error [%r]\r\n",Status);
			return Status;
		}
    
		FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);	
		Print(L"Filesize [%ld] bytes\n",FileInfo-> FileSize);
		FileDataLength=(UINTN) FileInfo->FileSize;
	
		FileData  = AllocatePool((UINTN) FileInfo->FileSize);
		Status = FileHandle->Read(FileHandle, &FileDataLength, FileData );
		if (EFI_ERROR(Status)) {
			Print(L"Loading file error! \n");
		}
		
		FileData[FileDataLength/2 -1]=0x0;
		Print(L"File contants: [%s]",FileData);
		
		FreePool(FileData);
		FileProtocol->Close(FileHandle);
  }
	
  if ((CurrentName) != NULL) { FreePool((CurrentName)); CurrentName = NULL; }

  return EFI_SUCCESS;
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                *HandleBuffer=NULL;
  UINTN                     BufferSize=0;
  UINTN                     LoopVar;
  BOOLEAN                   Found;

  //1. We have to use some function in SE2 
  //
  // UEFI 2.0 shell interfaces (used preferentially)
  //
  Status = gBS->OpenProtocol(
    gImageHandle,
    &gEfiShellProtocolGuid,
    (VOID **)&gEfiShellProtocol,
    gImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
   
  if (EFI_ERROR(Status)) {
    //
    // Search for the shell protocol
    //
    Status = gBS->LocateProtocol(
      &gEfiShellProtocolGuid,
      NULL,
      (VOID **)&gEfiShellProtocol
     );
    if (EFI_ERROR(Status)) {
      gEfiShellProtocol = NULL;
     }
  }

  
  //
  //2. Look up all SimpleFileSystems in the platform
  //
  Status = gBS->LocateHandle(
    ByProtocol,
    &gEfiSimpleFileSystemProtocolGuid,
    NULL,
    &BufferSize,
    HandleBuffer);
	
  if (Status == EFI_BUFFER_TOO_SMALL) {
		HandleBuffer = AllocateZeroPool(BufferSize);
		if (HandleBuffer == NULL) {
			return (SHELL_OUT_OF_RESOURCES);
		}
		Status = gBS->LocateHandle(
			ByProtocol,
			&gEfiSimpleFileSystemProtocolGuid,
			NULL,
			&BufferSize,
			HandleBuffer);
   }

  //
  // Get the map name(s) for each one.
  //
  for ( LoopVar = 0, Found = FALSE
      ; LoopVar < (BufferSize / sizeof(EFI_HANDLE)) && HandleBuffer != NULL
      ; LoopVar ++
     ) {
	//3.Emulate every Handle which has SimpileFileSystem 
    Status = PerformSingleMappingDisplay(HandleBuffer[LoopVar]);
    if (!EFI_ERROR(Status)) {
      Found = TRUE;
    }
  }
  
  FreePool(HandleBuffer);
	
  return EFI_SUCCESS;
}

 

我是在虚拟机下实验的,运行之后打开并且读取 fsnt2:\hello.txt 的内容(内容是 www.lab-z.com123)。

openfs

特别注意的是:直接读取之后按照 CHAR16 的字符串来处理,但是读取内容没有 0x00 0x00的结尾。直接用Print 输出的时候字符串后面会有意料之外的字符。所以用下面这个语句 FileData[FileDataLength/2 -1]=0x0; 直接添加一个结尾。这也是为什么字符3被截掉的原因。

完整代码下载
OpenFSX

参考:

1. http://www.lab-z.com/esptest/ Step to UEFI (54) —– EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 写文件
2. http://www.lab-z.com/shellfsx/ Step to UEFI (36) —– 枚举Shell下的全部盘符
3. http://www.lab-z.com/stu63/ Step to UEFI (63) —– 常用的字符串函数(下)
4. http://www.lab-z.com/nstring/ Step to UEFI (62) —– 常用的字符串函数(上)

Step to UEFI —– TIPs

每次编译 Application 之后生成的 efi 文件都在

\Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\APPNAME\APPNAME 这样的目录中,每次需要手工 copy 到虚拟出来的目录下,这样比较麻烦。

经过研究发现可以在 \AppPkg\AppPkg.dsc 中做如下修改

#OUTPUT_DIRECTORY = Build/AppPkg
OUTPUT_DIRECTORY = Build/NT32IA32

这样,每次最后生成 efi 文件之后就会有一个自动 copy 的动作。

tips

Step to UEFI (72) —– MP_Service_Protocol 获得CPU信息

UEFI本身不支持多线程,据说主要原因是:UEFI设计本身是为了启动硬件,做一些比较简单的事情,如果支持多线程会使得设计复杂度直线上升,出现问题也不容易调试。

不过UEFI本身是有对应的Protocol 的,称作 EFI_MP_SERVICES_PROTOCOL 。 具体的这个 Protocol 可以在 PI spec 中找到(UEFI Spec中没有)。下面来自【参考1】

image001

同样可以作为参考的还有EFI_Toolkit_2.0.0.1 中的process.c 程序。

程序很简单,首先 LocateProtocol 到EFI_MP_SERVICES_PROTOCOL。然后,用 Protocol 提供的EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS 和EFI_MP_SERVICES_GET_PROCESSOR_INFO 来获得关于处理器的一些信息。需要注意的是EFI_MP_SERVICES_GET_PROCESSOR_INFO,输入的参数ProcessorNumber 意思是:当前要获得的 Processor 号,输入0 表示你要去的编号为 0 的Processor 的信息,输入3 表示你要去的编号为 3 的Processor 的信息,取值范围从0到NumberOfProcessors -1。

image002

最终的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Pi/PiDxeCis.h>
#include  <Protocol/MpService.h>

EFI_GUID  gEfiMpServiceProtocolGuid = { 0x3fdda605, 0xa76e, 0x4f46, 
						{ 0xad, 0x29, 0x12, 0xf4, 0x53, 0x1b, 0x3d, 0x08 }};
  
extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;
	EFI_MP_SERVICES_PROTOCOL	*MP=NULL;
	UINTN   i;	
	UINTN	NumProcessors;
	UINTN	NumberOfEnabledProcessors;

	EFI_PROCESSOR_INFORMATION	ProcessorInfo;
	
	//Get MP_Service Protocol
	Status = gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID**)&MP);
	if (EFI_ERROR (Status)) {
		Print(L"Unable to initialize MP protocol interface!");
		return EFI_UNSUPPORTED;
	}
	
	// Determine number of processors
	Status = MP->GetNumberOfProcessors( MP, &NumProcessors , &NumberOfEnabledProcessors );
	
	if (EFI_ERROR (Status))
	{
		Print( L"MP->GetNumEnabledProcessors:Unable to determine number of processors\n") ;
		return EFI_UNSUPPORTED;
	}	
	Print(L"Number of Processors %d\n",NumProcessors);
	Print(L"Number of Enabled Processors %d\n",NumberOfEnabledProcessors);
	
	//Get more information by GetProcessorInfo
	for (i=0;i<NumProcessors;i++)
	{
		Status = MP->GetProcessorInfo(MP, i , &ProcessorInfo);
		
		Print( L"Prcoessor #%d ACPI Processor ID = %lX, Flags = %x, Package = %x, Core = %x, Thread = %x \n", 
					i,
					ProcessorInfo.ProcessorId, 
					ProcessorInfo.StatusFlag,
					ProcessorInfo.Location.Package,
					ProcessorInfo.Location.Core,
					ProcessorInfo.Location.Thread);
	}
	
	return EFI_SUCCESS;
}

运行结果:

image003

完整的代码下载:

MPTest1

关于这部分程序,在《UEFI 原理与编程》第13章 深入了解多任务 有更详细的论述,如果你有这方面的需求,推荐直接阅读。

另外,如果你想获得更多CPU方面的信息,比如 Cache大小, 还可以通过 CPUID,但是非常不建议通过 SMBIOS 来获得,因为他并不可靠……

最后特别提醒:

开始编写程序时,我遇到了下面的错误,错误代码对于解决问题毫无帮助,最后经过分析发现是因为没有添加PiDxe.h头文件(感谢Marco指出)导致的。

c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(367) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(465) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(623) : error C2061: syntax error : identifier ‘EFI_MP_SERVICES_STARTUP_ALL_APS’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2061: syntax error : identifier ‘StartupThisAP’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(628) : error C2059: syntax error : ‘}’
NMAKE : fatal error U1077: ‘”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\cl.exe”‘ : return code ‘0x2’
Stop.

参考:

1. Vol2_DXE CIS_1_3.pdf P174

Step to UEFI (71) —– 获得 USB 设备的 PID 和 VID

一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?

首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。

typedef struct _EFI_USB_IO_PROTOCOL {
  EFI_USB_IO_CONTROL_TRANSFER            UsbControlTransfer;
  EFI_USB_IO_BULK_TRANSFER               UsbBulkTransfer;
  EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER    UsbAsyncInterruptTransfer;
  EFI_USB_IO_SYNC_INTERRPUT_TRANSFER     UsbSyncInterruptTransfer;
  EFI_USB_IO_ISOCHRONOUS_TRANSFER        UsbIsochronousTransfer;
  EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER  UsbAsyncIsochronousTransfer;
  EFI_USB_IO_GET_DEVICE_DESCRIPTOR       UsbGetDeviceDescriptor;
  EFI_USB_IO_GET_CONFIG_DESCRIPTOR       UsbGetConfigDescriptor;
  EFI_USB_IO_GET_INTERFACE_DESCRIPTOR    UsbGetInterfaceDescriptor;
  EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR     UsbGetEndpointDescriptor;
  EFI_USB_IO_GET_STRING_DESCRIPTOR       UsbGetStringDescriptor;
  EFI_USB_IO_GET_SUPPORTED_LANGUAGES     UsbGetSupportedLanguages;
  EFI_USB_IO_PORT_RESET                  UsbPortReset;
} EFI_USB_IO_PROTOCOL;

来自【参考2】。

我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。

image001

我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。

image002

每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:

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

#include  <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;

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

UINTN GetUSB( )
{
  EFI_STATUS  Status;
  UINTN       HandleIndex, HandleCount;
  EFI_HANDLE  *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL 			*USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;

  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiUsbIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);

  if (EFI_ERROR(Status)) 
  {
    Print(L"ERROR : Get USBIO count fail.\n");
    return 0;
  }   

  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
     Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiUsbIoProtocolGuid,
                      (VOID**)&USBIO);

      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open USBIO fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Print(L"VendorID = %04X, ProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);      

  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  GetUSB( );
  return EFI_SUCCESS;
}

最终运行结果如下:

image003

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

参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义