Step to UEFI (69) —– 动态加载修改Application

标题看起来非常拗口,具体来说描述起来就是下面的问题:

“我想写一个简单的程序,先把某个app的Load进内存,然后在内存里爆搜一个特征字串,搜到之后将该内存第一个字节替换。以下为代码片段,碰到一个问题就是,我搜到特征字串之后,修改其内存的内容一直改不了,请问各位大大,是不是UEFI有相应的保护策略,不能修改LoadImage的内存?我个人觉得是不应该,因为我是LoadImage的宿主,我Load的内存应该是可以被我修改的。请大牛们指教啊!!!

Status=gBS->LoadImage(TRUE, ImageHandle, DstDevicePath, NULL, 0, &DstImageHandle); //LoadImage
if (!EFI_ERROR(Status))
{
Print(L”Load Image success\n”);
}
Status=gBS->HandleProtocol(DstImageHandle, &gEfiLoadedImageProtocolGuid,(void **) &LoadedImage);
if (EFI_ERROR(Status)) {
Print(L”Can not retrieve a LoadedImageProtocol handle for ImageHandle\n”);
gBS->Exit(ImageHandle,EFI_SUCCESS,0,NULL);
}
//Get the loaded image base address
imageBase=LoadedImage->ImageBase;
size=LoadedImage->ImageSize;

temp=(char *)imageBase;

//Search the sig;, replace the first byte
for (; temp<(char *)imageBase+size; temp++) { if (*temp==0x55 && *(temp+1)==0x00 && *(temp+2)==0x45 && *(temp+3)==0x00) { Print(L"Find sig\n"); *temp=0x45; Print(L"addr %x\n",temp); break; } } 上述问题来自【参考1】 这个一个有趣的问题,也不知道那个朋友最后是否成功。根据上面的问题,我做一下实验。 首先,准备一个被修改的 App。当然,根据之前的知识,这个 App 不能使用 CLIB 库,目前为止我还是不知道为什么无法加载调用这个库的 Application.

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>


/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

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

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
                                       
  CHAR8  *s1= "This code comes from www.lab-z.com";
  CHAR16 *s2=L"                                  ";
  CHAR16 *Result;
  
  Result=AsciiStrToUnicodeStr(s1,s2);
  Print(L"%s\n",s2);

  return EFI_SUCCESS;
}

 

代码非常简单,将一个 ASCII 字符串转化为 Unicode 的,然后显示出来。使用 ASCII 的原因是为了便于查找。

上面的程序编译之后,使用十六进制工具打开可以直接查看到 ASCII 字符。

exec3

然后,继续编写加载和修改的程序如下:

#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>

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

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL 	*DevicePath;
  EFI_HANDLE				NewHandle;
  EFI_STATUS				Status;
  UINTN			ExitDataSizePtr;  
  CHAR16 					*R=L"HelloWorld.efi";
  EFI_LOADED_IMAGE_PROTOCOL	*ImageInfo = NULL;
  CHAR8						*temp;
  
  Print(L"File [%s]\n",R);

  DevicePath=ShellGetDevicePath(R);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  Status = gBS -> HandleProtocol (
						NewHandle,
						&gEfiLoadedImageProtocolGuid,
						&ImageInfo
						);
  Print(L"ImageBase [%lX]\n",ImageInfo->ImageBase);
  Print(L"ImageSize [%lX]\n",ImageInfo->ImageSize);

  temp=(char *)ImageInfo->ImageBase;
  //Search the sig;, replace the first byte
  for (; temp<(char *)ImageInfo->ImageBase+ImageInfo->ImageSize; temp++)
        {
				//"lab" 6C 61 62
                if (*temp==0x6C && *(temp+1)==0x61 && *(temp+2)==0x62)
                {
                        Print(L"Find sig\n");
                        Print(L"addr %x\n",temp);
						*(temp  )=0x2D;
						*(temp+1)=0x2D;
						*(temp+2)=0x2D;
                        break;
                } 
        }  
  
  
  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\n",Status);
    return (Status);
  }
  
  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

加载部分的代码使用的是 【参考2】的框架,搜索部分的代码用的是前面问题中给出来的示例。我们在加载后的 Application 的空间中搜索 “lab” 字符串并且替换为 “—”。

运行结果如下,我们先执行了一次 HelloWorld.efi,可以看到他能正常打印字符串,之后再用我们的程序加载一次,可以看到字符串被修改掉了。

exec3r

看起来并没有什么保护之类的,轻而易举的改掉了 Application 的内容。猜测之前提出问题的朋友有可能是被加载的代码用到了 CLIB, 或者是代码中的字符串是按照 UNICODE 给出来的,所以无法找到。

这样的动态加载可以用在一些特殊的地方,比如,我见过一款 DOS 下的测试软件,有一个主程序 EXE 和 n多个独立的 EXE 构成。主程序可以调用其他的 EXE 进行测试,但是单独的 EXE 无法执行,这样的好处是开发时可以独立开发单独模块,分发之后有主程序进行控制只在需要的环境中运行。

本文提到的代码下载

exec3

HelloWorld

参考:

1. http://biosren.com/thread-4564-1-31.html
2. http://www.lab-z.com/efiloadedimageprotocol/ Step to UEFI (46) —– EFILOADEDIMAGEPROTOCOL的使用

Step to UEFI (68) —– 编译一个能在 QEMU 上跑的BIOS

最近在看 《UEFI 原理与编程》,上面提到一款虚拟机可以运行指定的BIOS,那就是 QEMU (之前我也研究过如何替换 VirtualBox 的BIOS,结果非常沮丧,他不支持独立的 BIOS ,这意味即便是要在ASL中修改一些代码也要花费几个小时重新编译整个VirtualBox)。
书中对于如何在QEMU中跑起来没有详述,我花了一点时间搞定了,下面介绍一下方法:

第1步:下载最主要的软件

1.1 你需要下载 QEMU 这个虚拟机,下载的地址是

http://qemu.weilnetz.de/

我下载的版本是 qemu-w32-setup-20150925.exe

1.2 还要下载一套能够编译出供虚拟机使用的BIOS,这套代码的名字是 OVMF (刚开始我以为普通的EDK2代码即可,研究一段才发现理解错误)

下载的地址是

http://sourceforge.net/p/tianocore/edk2/ci/master/tarball?path=/OvmfPkg

第2步,编译 (大环境来说就是我一直用来编译使用 UDK2014 的环境)

2.1 解压 1.1 的代码 (我解压在名为 “OVMF” 的文件夹中)

2.2 运行 edk2setup.bat (此外还有一个 edksetup.bat ,我不清楚有什么差别)

会提示无法找到关于 python 的设置,我索性在 edk2setup.bat 开始处加入

set PYTHONHOME=C:\Python27 (当然,你需要先安装一套 python2.7 才行)

直接在批处理中添加语句,对整个编译环境没有影响

2.3 编译命令

build -a IA32 -p OvmfPkg\OvmfPkgIa32.dsc

遇到的第一个错误是无法找到 nasm ,我大概看了一下,这套代码的编译除了vc的ml还用到了 nasm 来处理汇编语言。

在 http://www.nasm.us/ 下载,我使用的版本是 nasm-2.11.08-win32。其中有用的只是 nasm.exe 我把它直接放在

C:\ovmf\BaseTools\Bin\Win32 目录下面,这个位置在编译过程中会加入到 path 中,所以一定能访问到。

遇到的第二个错误是无法找到 \asl\iasl.exe

在 https://acpica.org/downloads 可以下载到, 将 iasl.exe 放在 c:\asl\ 下面即可。

按照上述方法设置之后,即可正常编译

ovmf

2.4 运行方法

在 build 下面找到 ovmf.fd ,拷贝到 qemu 的安装目录下

命令行中运行

qemu-system-x86_64.exe -bios “OVMF.fd”

ovmf2

ovmf3

工作的视频

http://www.tudou.com/programs/view/acRlyVBYLz8/?resourceId=414535982_06_02_99

上面提到的工具,我放在Baidu网盘上,可以从这里下载 http://pan.baidu.com/s/1sjsZhr3 密码:uav4

如果你在具体操作中遇到任何问题可以直接给我留言,我会长期维护本文。

Step to UEFI (67) —– zLib (上)

zlib 是一款开源的压缩解压库,在《UEFI原理与编程》第8章提到了他。我去书上提到的网站下载到了修改后的 zlib.inf 文件,然后尝试在AppPkg中重新编译之。

首先根据 zlib.inf 中[Sources]节给出的文件名提取出来需要用的文件。

[Sources]
#uefientry.c
adler32.c
crc32.c
deflate.c
infback.c
inffast.c
inflate.c
inftrees.c
trees.c
zutil.c
compress.c
uncompr.c
gzclose.c
gzlib.c
gzread.c
gzwrite.c

然后,把 zlib.inf 加入到 AppPkg.dsc 中。之后用 Build -a IA32 -p AppPkg\AppPkg.dsc 编译。编译过程中会出现很多错误,经过研究发现出现的都是一些 warning 而已,可以通过在文件头上加入编译开关来忽略掉。

#pragma warning(disable:4131)
#pragma warning(disable:4142)
#pragma warning(disable:4244)

 

* 很多 Warning 是因为老的语法格式导致的,所以不会对代码产生任何影响,比如下面这种,没有在函数名称中定义参数类型:

/* Open a gzip file either by name or file descriptor. */
local gzFile gz_open(path, fd, mode)
    const void *path;
    int fd;
    const char *mode;

 

此外,在 gzguts.h 中,需要删掉这一行。

#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32)
  include <io.h>
#endif

 

完成上面的设置后,重新运行命令,可以正常编译:

zlibc

在 \Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\zsource\zlib\OUTPUT 下面看到 zlib.lib 文件

zlib

后续我们就可以在自己的程序中调用这个压缩库了。

本文修改后的 zlib zsource

修改之前的 zlib,版本是 1.2.8.0

zlib128

VBS 获取网页并保存

编写一个简单的VBS文件,能够自动保存网页,并且根据当前时间起不同的文件名

 Set fso = CreateObject("Scripting.FileSystemObject")
 Set Outp = Wscript.Stdout
 On Error Resume Next
 Set File = WScript.CreateObject("Microsoft.XMLHTTP")
 File.Open "GET", "http://api.huobi.com/staticmarket/btc_kline_001_json.js", False
 File.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET4.0C; .NET4.0E; BCD2000; BCD2000)"
 File.Send
 If err.number <> 0 then 
  Outp.writeline "" 
  Outp.writeline "Error getting file" 
  Outp.writeline "==================" 
  Outp.writeline "" 
  Outp.writeline "Error " & err.number & "(0x" & hex(err.number) & ") " & err.description 
  Outp.writeline "Source " & err.source 
  Outp.writeline "" 
  Outp.writeline "HTTP Error " & File.Status & " " & File.StatusText
  Outp.writeline  File.getAllResponseHeaders
  Outp.writeline Arg(1)
 End If

On Error Goto 0

 Set BS = CreateObject("ADODB.Stream")
 BS.type = 1
 BS.open
 BS.Write File.ResponseBody
 BS.SaveToFile "c:\uefi\"&year(Now)&Month(Now)&Day(Now)&Hour(Now)&Minute(Now)&Second(Now)&".txt", 2

 

参考:

1.http://stackoverflow.com/questions/27977752/download-and-execute-with-vbs

Step to UEFI (65) —– ShellWriteFile的使用

前面介绍过使用 ShellReadFile 读取文件的内容,这里介绍一下 ShellWriteFile 的使用。

例子是使用 ShellOpenFileByName 打开当前的 EFI Application,把内容读取到内存之后,创建一个名为 Test.efi 的文件,使用 ShellWriteFile 函数把内容写进去。

代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellLib.h>
#include  <Library/MemoryAllocationLib.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;
  EFI_HANDLE        *HandleBuffer=NULL;
  UINTN  			ReadSize;
  
  //Open the file given by the parameter
  Status = ShellOpenFileByName(Argv[0], 
		(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);
  
  //Close the source file
  ShellCloseFile(&FileHandle);

  //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,
			&ReadSize,
			HandleBuffer
			);
  //Close the source file
  ShellCloseFile(&FileHandle);
  
  return EFI_SUCCESS;
}

 

运行结果(运行结束之后我比较了一下生成文件和源文件是相同的):

ctf

特别注意:使用 ShellOpenFuleByName 创建一个文件时,要同时使用 EFI_FILE_MODE_READ ,EFI_FILE_MODE_WRITE 和 EFI_FILE_MODE_CREATE ,否则可能出现“Invalid Parameter”的错误【参考1】

完整代码下载
CreateFile

1.http://feishare.com/efimail/messages/20120331-0611-Re__edk2__Problems_creating_file_using_ShellOpenFileByName-_Bekefi__Stephen_C_.html

Step to UEFI (64) —– Print 直接输出错误信息

最近发现一个挺有意思的功能,Print 使用 %r 参数可以直接输出错误信息的含义。这样的话,我们可以直接取得错误信息,省去不少麻烦。例如,下面的代码

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  Print(L"%r\n",EFI_SUCCESS);
  Print(L"%r\n",RETURN_WARN_WRITE_FAILURE);
  Print(L"%r\n",RETURN_COMPROMISED_DATA);
  
  return EFI_SUCCESS;
}

 

运行结果

printr

我们再仔细研究一下 %r 的具体实现。

查看 PrintR 编译生成的 printf.map ,可以看到 _Print 是链接到 UefiLibPrint.obj 中。用这个文件名,我们确定是在 \MdePkg\Library\UefiLib\UefiLibPrint.c 这个文件中

0001:000000ce _ShellCEntryLib 0000032e f UefiShellCEntryLib:UefiShellCEntryLib.obj
0001:00000140 _InternalPrint 000003a0 f UefiLib:UefiLibPrint.obj
0001:0000018b _Print 000003eb f UefiLib:UefiLibPrint.obj
0001:000001a8 _ShellFindSE2 00000408 f UefiShellLib:UefiShellLib.obj
0001:000002d8 _ShellLibConstructorWorker 00000538 f UefiShellLib:UefiShellLib.obj
0001:0000048d _ShellLibDestructor 000006ed f UefiShellLib:UefiShellLib.obj
0001:00000535 _ShellOpenFileByDevicePath 00000795 f UefiShellLib:UefiShellLib.obj

Print 函数:

/** 
  Prints a formatted Unicode string to the console output device specified by 
  ConOut defined in the EFI_SYSTEM_TABLE.

  This function prints a formatted Unicode string to the console output device 
  specified by ConOut in EFI_SYSTEM_TABLE and returns the number of Unicode 
  characters that printed to ConOut.  If the length of the formatted Unicode 
  string is greater than PcdUefiLibMaxPrintBufferSize, then only the first 
  PcdUefiLibMaxPrintBufferSize characters are sent to ConOut.
  If Format is NULL, then ASSERT().
  If Format is not aligned on a 16-bit boundary, then ASSERT().
  If gST->ConOut is NULL, then ASSERT().

  @param Format   A Null-terminated Unicode format string.
  @param ...      A Variable argument list whose contents are accessed based 
                  on the format string specified by Format.
  
  @return The number of Unicode characters printed to ConOut.

**/
UINTN
EFIAPI
Print (
  IN CONST CHAR16  *Format,
  ...
  )
{
  VA_LIST Marker;
  UINTN   Return;

  VA_START (Marker, Format);

  Return = InternalPrint (Format, gST->ConOut, Marker);

  VA_END (Marker);

  return Return;
}

 

InternalPrint 函数在同一个文件中

/**
  Internal function which prints a formatted Unicode string to the console output device
  specified by Console

  This function prints a formatted Unicode string to the console output device
  specified by Console and returns the number of Unicode characters that printed
  to it.  If the length of the formatted Unicode string is greater than PcdUefiLibMaxPrintBufferSize,
  then only the first PcdUefiLibMaxPrintBufferSize characters are sent to Console.
  If Format is NULL, then ASSERT().
  If Format is not aligned on a 16-bit boundary, then ASSERT().

  @param Format   A Null-terminated Unicode format string.
  @param Console  The output console.
  @param Marker   A VA_LIST marker for the variable argument list.

  @return The number of Unicode characters in the produced
          output buffer, not including the Null-terminator.
**/
UINTN
InternalPrint (
  IN  CONST CHAR16                     *Format,
  IN  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *Console,
  IN  VA_LIST                          Marker
  )
{
  EFI_STATUS  Status;
  UINTN       Return;
  CHAR16      *Buffer;
  UINTN       BufferSize;

  ASSERT (Format != NULL);
  ASSERT (((UINTN) Format & BIT0) == 0);
  ASSERT (Console != NULL);

  BufferSize = (PcdGet32 (PcdUefiLibMaxPrintBufferSize) + 1) * sizeof (CHAR16);

  Buffer = (CHAR16 *) AllocatePool(BufferSize);
  ASSERT (Buffer != NULL);

  Return = UnicodeVSPrint (Buffer, BufferSize, Format, Marker);

  if (Console != NULL && Return > 0) {
    //
    // To be extra safe make sure Console has been initialized
    //
    Status = Console->OutputString (Console, Buffer);
    if (EFI_ERROR (Status)) {
      Return = 0;
    }
  }

  FreePool (Buffer);

  return Return;
}

 

处理输出的核心是 UnicodeVSPrint 函数

/**
  Produces a Null-terminated Unicode string in an output buffer based on 
  a Null-terminated Unicode format string and a VA_LIST argument list
  
  Produces a Null-terminated Unicode string in the output buffer specified by StartOfBuffer
  and BufferSize.  
  The Unicode string is produced by parsing the format string specified by FormatString.  
  Arguments are pulled from the variable argument list specified by Marker based on the 
  contents of the format string.  
  The number of Unicode characters in the produced output buffer is returned not including
  the Null-terminator.
  If BufferSize is 0 or 1, then no output buffer is produced and 0 is returned.

  If BufferSize > 1 and StartOfBuffer is NULL, then ASSERT().
  If BufferSize > 1 and StartOfBuffer is not aligned on a 16-bit boundary, then ASSERT().
  If BufferSize > 1 and FormatString is NULL, then ASSERT().
  If BufferSize > 1 and FormatString is not aligned on a 16-bit boundary, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and FormatString contains more than 
  PcdMaximumUnicodeStringLength Unicode characters not including the Null-terminator, then
  ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and produced Null-terminated Unicode string
  contains more than PcdMaximumUnicodeStringLength Unicode characters not including the
  Null-terminator, then ASSERT().

  @param  StartOfBuffer   A pointer to the output buffer for the produced Null-terminated 
                          Unicode string.
  @param  BufferSize      The size, in bytes, of the output buffer specified by StartOfBuffer.
  @param  FormatString    A Null-terminated Unicode format string.
  @param  Marker          VA_LIST marker for the variable argument list.
  
  @return The number of Unicode characters in the produced output buffer not including the
          Null-terminator.

**/
UINTN
EFIAPI
UnicodeVSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  IN  VA_LIST       Marker
  )
{
  ASSERT_UNICODE_BUFFER (StartOfBuffer);
  ASSERT_UNICODE_BUFFER (FormatString);
  return BasePrintLibSPrintMarker ((CHAR8 *)StartOfBuffer, BufferSize >> 1, FORMAT_UNICODE | OUTPUT_UNICODE, (CHAR8 *)FormatString, Marker, NULL);
}

 

BasePrintLibSPrintMarker 函数在 \MdePkg\Library\BasePrintLib\PrintLib.c

/**
  Worker function that produces a Null-terminated string in an output buffer 
  based on a Null-terminated format string and a VA_LIST argument list.

  VSPrint function to process format and place the results in Buffer. Since a 
  VA_LIST is used this routine allows the nesting of Vararg routines. Thus 
  this is the main print working routine.

  If COUNT_ONLY_NO_PRINT is set in Flags, Buffer will not be modified at all.

  @param[out] Buffer          The character buffer to print the results of the 
                              parsing of Format into.
  @param[in]  BufferSize      The maximum number of characters to put into 
                              buffer.
  @param[in]  Flags           Initial flags value.
                              Can only have FORMAT_UNICODE, OUTPUT_UNICODE, 
                              and COUNT_ONLY_NO_PRINT set.
  @param[in]  Format          A Null-terminated format string.
  @param[in]  VaListMarker    VA_LIST style variable argument list consumed by
                              processing Format.
  @param[in]  BaseListMarker  BASE_LIST style variable argument list consumed
                              by processing Format.

  @return The number of characters printed not including the Null-terminator.
          If COUNT_ONLY_NO_PRINT was set returns the same, but without any
          modification to Buffer.

**/
UINTN
BasePrintLibSPrintMarker (
  OUT CHAR8        *Buffer,
  IN  UINTN        BufferSize,
  IN  UINTN        Flags,
  IN  CONST CHAR8  *Format,
  IN  VA_LIST      VaListMarker,   OPTIONAL
  IN  BASE_LIST    BaseListMarker  OPTIONAL
  )

 

处理 %r 的代码如下

      case 'r':
        if (BaseListMarker == NULL) {
          Status = VA_ARG (VaListMarker, RETURN_STATUS);
        } else {
          Status = BASE_ARG (BaseListMarker, RETURN_STATUS);
        }
        ArgumentString = ValueBuffer;
        if (RETURN_ERROR (Status)) {
          //
          // Clear error bit
          //
          Index = Status & ~MAX_BIT;
          if (Index > 0 && Index <= ERROR_STATUS_NUMBER) {
            ArgumentString = mStatusString [Index + WARNING_STATUS_NUMBER];
          }
        } else {
          Index = Status;
          if (Index <= WARNING_STATUS_NUMBER) {
            ArgumentString = mStatusString [Index];
          }
        }
        if (ArgumentString == ValueBuffer) {
          BasePrintLibSPrint ((CHAR8 *) ValueBuffer, MAXIMUM_VALUE_CHARACTERS, 0, "%08X", Status);
        }
        break;

 

核心就是查 mStatusString 表,在 \MdePkg\Library\BasePrintLib\PrintLibInternal.c 中有定义


GLOBAL_REMOVE_IF_UNREFERENCED CONST CHAR8 *mStatusString[] = {
  "Success",                      //  RETURN_SUCCESS                = 0
  "Warning Unknown Glyph",        //  RETURN_WARN_UNKNOWN_GLYPH     = 1
  "Warning Delete Failure",       //  RETURN_WARN_DELETE_FAILURE    = 2
  "Warning Write Failure",        //  RETURN_WARN_WRITE_FAILURE     = 3
  "Warning Buffer Too Small",     //  RETURN_WARN_BUFFER_TOO_SMALL  = 4
  "Warning Stale Data",           //  RETURN_WARN_STALE_DATA        = 5
  "Load Error",                   //  RETURN_LOAD_ERROR             = 1  | MAX_BIT
  "Invalid Parameter",            //  RETURN_INVALID_PARAMETER      = 2  | MAX_BIT
  "Unsupported",                  //  RETURN_UNSUPPORTED            = 3  | MAX_BIT
  "Bad Buffer Size",              //  RETURN_BAD_BUFFER_SIZE        = 4  | MAX_BIT
  "Buffer Too Small",             //  RETURN_BUFFER_TOO_SMALL,      = 5  | MAX_BIT
  "Not Ready",                    //  RETURN_NOT_READY              = 6  | MAX_BIT
  "Device Error",                 //  RETURN_DEVICE_ERROR           = 7  | MAX_BIT
  "Write Protected",              //  RETURN_WRITE_PROTECTED        = 8  | MAX_BIT
  "Out of Resources",             //  RETURN_OUT_OF_RESOURCES       = 9  | MAX_BIT
  "Volume Corrupt",               //  RETURN_VOLUME_CORRUPTED       = 10 | MAX_BIT
  "Volume Full",                  //  RETURN_VOLUME_FULL            = 11 | MAX_BIT
  "No Media",                     //  RETURN_NO_MEDIA               = 12 | MAX_BIT
  "Media changed",                //  RETURN_MEDIA_CHANGED          = 13 | MAX_BIT
  "Not Found",                    //  RETURN_NOT_FOUND              = 14 | MAX_BIT
  "Access Denied",                //  RETURN_ACCESS_DENIED          = 15 | MAX_BIT
  "No Response",                  //  RETURN_NO_RESPONSE            = 16 | MAX_BIT
  "No mapping",                   //  RETURN_NO_MAPPING             = 17 | MAX_BIT
  "Time out",                     //  RETURN_TIMEOUT                = 18 | MAX_BIT
  "Not started",                  //  RETURN_NOT_STARTED            = 19 | MAX_BIT
  "Already started",              //  RETURN_ALREADY_STARTED        = 20 | MAX_BIT
  "Aborted",                      //  RETURN_ABORTED                = 21 | MAX_BIT
  "ICMP Error",                   //  RETURN_ICMP_ERROR             = 22 | MAX_BIT
  "TFTP Error",                   //  RETURN_TFTP_ERROR             = 23 | MAX_BIT
  "Protocol Error",               //  RETURN_PROTOCOL_ERROR         = 24 | MAX_BIT
  "Incompatible Version",         //  RETURN_INCOMPATIBLE_VERSION   = 25 | MAX_BIT
  "Security Violation",           //  RETURN_SECURITY_VIOLATION     = 26 | MAX_BIT
  "CRC Error",                    //  RETURN_CRC_ERROR              = 27 | MAX_BIT
  "End of Media",                 //  RETURN_END_OF_MEDIA           = 28 | MAX_BIT
  "Reserved (29)",                //  RESERVED                      = 29 | MAX_BIT
  "Reserved (30)",                //  RESERVED                      = 30 | MAX_BIT
  "End of File",                  //  RETURN_END_OF_FILE            = 31 | MAX_BIT
  "Invalid Language",             //  RETURN_INVALID_LANGUAGE       = 32 | MAX_BIT
  "Compromised Data"              //  RETURN_COMPROMISED_DATA       = 33 | MAX_BIT
};

 

具体的实现就是这样。

Step to UEFI (63) —– 常用的字符串函数(下)

继续介绍 EFI 下面的常用字符串函数

\MdePkg\Include\Library\BaseLib.h

1.StrStr 函数作用:在字符串中查找另外的字符串


/**
  Returns the first occurrence of a Null-terminated Unicode sub-string
  in a Null-terminated Unicode string.

  This function scans the contents of the Null-terminated Unicode string
  specified by String and returns the first occurrence of SearchString.
  If SearchString is not found in String, then NULL is returned.  If
  the length of SearchString is zero, then String is returned.

  If String is NULL, then ASSERT().
  If String is not aligned on a 16-bit boundary, then ASSERT().
  If SearchString is NULL, then ASSERT().
  If SearchString is not aligned on a 16-bit boundary, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and SearchString
  or String contains more than PcdMaximumUnicodeStringLength Unicode
  characters, not including the Null-terminator, then ASSERT().

  @param  String          The pointer to a Null-terminated Unicode string.
  @param  SearchString    The pointer to a Null-terminated Unicode string to search for.

  @retval NULL            If the SearchString does not appear in String.
  @return others          If there is a match.

**/
CHAR16 *
EFIAPI
StrStr (
  IN      CONST CHAR16              *String,
  IN      CONST CHAR16              *SearchString
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW.";
  CHAR16 *s2=L"COM";  
  CHAR16 *s3=L"com";    
  CHAR16 s4[40]=L"LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrStr(s4,s1);
  Print(L"%s\n",Result);

  Result=StrStr(s4,s2);
  Print(L"%s\n",Result);

  Result=StrStr(s4,s3);
  Print(L"%s\n",Result);

  return EFI_SUCCESS;
}

 

运行结果

strtesta

2.StrDecimalToUintn 作用:把字符串转换为数字

/**
  Convert a Null-terminated Unicode decimal string to a value of
  type UINTN.

  This function returns a value of type UINTN by interpreting the contents
  of the Unicode string specified by String as a decimal number. The format
  of the input Unicode string String is:

                  [spaces] [decimal digits].

  The valid decimal digit character is in the range [0-9]. The
  function will ignore the pad space, which includes spaces or
  tab characters, before [decimal digits]. The running zero in the
  beginning of [decimal digits] will be ignored. Then, the function
  stops at the first character that is a not a valid decimal character
  or a Null-terminator, whichever one comes first.

  If String is NULL, then ASSERT().
  If String is not aligned in a 16-bit boundary, then ASSERT().
  If String has only pad spaces, then 0 is returned.
  If String has no pad spaces or valid decimal digits,
  then 0 is returned.
  If the number represented by String overflows according
  to the range defined by UINTN, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and String contains
  more than PcdMaximumUnicodeStringLength Unicode characters not including
  the Null-terminator, then ASSERT().

  @param  String      The pointer to a Null-terminated Unicode string.

  @retval Value translated from String.

**/
UINTN
EFIAPI
StrDecimalToUintn (
  IN      CONST CHAR16              *String
  );

 

实例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"100";
  CHAR16 *s2=L"77F";  
  CHAR16 *s3=L"77f";    
  CHAR16 *s4=L"77z";      
  UINTN Result;
  
  Result=StrDecimalToUintn(s1);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s2);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s3);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s4);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果:

strtestb

具体实现的代码可以在下面找到:

\MdePkg\Library\BaseLib\String.c

 while (InternalIsDecimalDigitCharacter (*String)) {
    //
    // If the number represented by String overflows according 
    // to the range defined by UINTN, then ASSERT().
    //
    ASSERT (Result <= ((((UINTN) ~0) - (*String - L'0')) / 10));

    Result = Result * 10 + (*String - L'0');
    String++;
  }


/**
  Check if a Unicode character is a decimal character.

  This internal function checks if a Unicode character is a 
  decimal character. The valid decimal character is from
  L'0' to L'9'.

  @param  Char  The character to check against.

  @retval TRUE  If the Char is a decmial character.
  @retval FALSE If the Char is not a decmial character.

**/
BOOLEAN
EFIAPI
InternalIsDecimalDigitCharacter (
  IN      CHAR16                    Char
  )
{
  return (BOOLEAN) (Char >= L'0' && Char <= L'9');
}

 

从上面可以看到:最后一个字符会被忽略,所以你写 77f 或者 77z都能得到同样的结果

再测试一下具体取值的大小

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"100000000";
  CHAR16 *s2=L"987654321Z";  
  CHAR16 *s3=L"2147483647";   // == 7FFFFFFF
  CHAR16 *s4=L"2147483648";   // == 80000000
  CHAR16 *s5=L"-1";    
  UINTN Result;
  
  Result=StrDecimalToUintn(s1);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s2);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s3);
  Print(L"%d\n",Result);

  Result=StrDecimalToUintn(s4);
  Print(L"%d\n",Result);
  Result=StrDecimalToUintn(s5);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtestc

3.StrDecimalToUint64 函数用途:将字符串转换为数值,和前面的函数相比,可用范围更大

/**
  Convert a Null-terminated Unicode decimal string to a value of
  type UINT64.

  This function returns a value of type UINT64 by interpreting the contents
  of the Unicode string specified by String as a decimal number. The format
  of the input Unicode string String is:

                  [spaces] [decimal digits].

  The valid decimal digit character is in the range [0-9]. The
  function will ignore the pad space, which includes spaces or
  tab characters, before [decimal digits]. The running zero in the
  beginning of [decimal digits] will be ignored. Then, the function
  stops at the first character that is a not a valid decimal character
  or a Null-terminator, whichever one comes first.

  If String is NULL, then ASSERT().
  If String is not aligned in a 16-bit boundary, then ASSERT().
  If String has only pad spaces, then 0 is returned.
  If String has no pad spaces or valid decimal digits,
  then 0 is returned.
  If the number represented by String overflows according
  to the range defined by UINT64, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and String contains
  more than PcdMaximumUnicodeStringLength Unicode characters not including
  the Null-terminator, then ASSERT().

  @param  String          The pointer to a Null-terminated Unicode string.

  @retval Value translated from String.

**/
UINT64
EFIAPI
StrDecimalToUint64 (
  IN      CONST CHAR16              *String
  );

 

实例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"100000000";
  CHAR16 *s2=L"9876543210000Z";  
  CHAR16 *s3=L"2147483647";   // == 7FFFFFFF
  CHAR16 *s4=L"2147483648";   // == 80000000
  CHAR16 *s5=L"-1";    
  UINT64 Result;
  
  Result=StrDecimalToUint64(s1);
  Print(L"%ld\n",Result);

  Result=StrDecimalToUint64(s2);
  Print(L"\n%Ld\n",Result);
  Result=StrDecimalToUint64(s2);
  Print(L"%LX\n\n",Result);

  
  Result=StrDecimalToUint64(s3);
  Print(L"%Ld\n",Result);

  Result=StrDecimalToUint64(s4);
  Print(L"%Ld\n",Result);
  
  Result=StrDecimalToUint64(s5);
  Print(L"%Ld\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果:
strtestd

4.StrHexToUintn 函数作用:将字符串按照十六进制处理转换为数值

/**
  Convert a Null-terminated Unicode hexadecimal string to a value of type UINTN.

  This function returns a value of type UINTN by interpreting the contents
  of the Unicode string specified by String as a hexadecimal number.
  The format of the input Unicode string String is:

                  [spaces][zeros][x][hexadecimal digits].

  The valid hexadecimal digit character is in the range [0-9], [a-f] and [A-F].
  The prefix "0x" is optional. Both "x" and "X" is allowed in "0x" prefix.
  If "x" appears in the input string, it must be prefixed with at least one 0.
  The function will ignore the pad space, which includes spaces or tab characters,
  before [zeros], [x] or [hexadecimal digit]. The running zero before [x] or
  [hexadecimal digit] will be ignored. Then, the decoding starts after [x] or the
  first valid hexadecimal digit. Then, the function stops at the first character 
  that is a not a valid hexadecimal character or NULL, whichever one comes first.

  If String is NULL, then ASSERT().
  If String is not aligned in a 16-bit boundary, then ASSERT().
  If String has only pad spaces, then zero is returned.
  If String has no leading pad spaces, leading zeros or valid hexadecimal digits,
  then zero is returned.
  If the number represented by String overflows according to the range defined by
  UINTN, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and String contains more than
  PcdMaximumUnicodeStringLength Unicode characters not including the Null-terminator,
  then ASSERT().

  @param  String          The pointer to a Null-terminated Unicode string.

  @retval Value translated from String.

**/
UINTN
EFIAPI
StrHexToUintn (
  IN      CONST CHAR16              *String
  );

 

示例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"0x234";
  CHAR16 *s2=L"1024";  
  UINTN Result;
  
  Result=StrHexToUintn(s1);
  Print(L"%d\n",Result);

  Result=StrHexToUintn(s2);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果
strteste

5.StrHexToUint64 函数作用:把字符串按照十六进制处理转化为数值

/**
  Convert a Null-terminated Unicode hexadecimal string to a value of type UINT64.

  This function returns a value of type UINT64 by interpreting the contents
  of the Unicode string specified by String as a hexadecimal number.
  The format of the input Unicode string String is

                  [spaces][zeros][x][hexadecimal digits].

  The valid hexadecimal digit character is in the range [0-9], [a-f] and [A-F].
  The prefix "0x" is optional. Both "x" and "X" is allowed in "0x" prefix.
  If "x" appears in the input string, it must be prefixed with at least one 0.
  The function will ignore the pad space, which includes spaces or tab characters,
  before [zeros], [x] or [hexadecimal digit]. The running zero before [x] or
  [hexadecimal digit] will be ignored. Then, the decoding starts after [x] or the
  first valid hexadecimal digit. Then, the function stops at the first character that is
  a not a valid hexadecimal character or NULL, whichever one comes first.

  If String is NULL, then ASSERT().
  If String is not aligned in a 16-bit boundary, then ASSERT().
  If String has only pad spaces, then zero is returned.
  If String has no leading pad spaces, leading zeros or valid hexadecimal digits,
  then zero is returned.
  If the number represented by String overflows according to the range defined by
  UINT64, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and String contains more than
  PcdMaximumUnicodeStringLength Unicode characters not including the Null-terminator,
  then ASSERT().

  @param  String          The pointer to a Null-terminated Unicode string.

  @retval Value translated from String.

**/
UINT64
EFIAPI
StrHexToUint64 (
  IN      CONST CHAR16             *String
  );

 

示例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"0x234";
  CHAR16 *s2=L"0xFFFFFFFFFFFFFFFF";  
  UINT64 Result;
  
  Result=StrHexToUint64(s1);
  Print(L"%ld\n",Result);

  Result=StrHexToUint64(s2);
  Print(L"%ld\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果:

strtestf

6.UnicodeStrToAsciiStr 函数作用:将Unicode的字符串转换为 ASCII的字符串

/**
  Convert a Null-terminated Unicode string to a Null-terminated
  ASCII string and returns the ASCII string.

  This function converts the content of the Unicode string Source
  to the ASCII string Destination by copying the lower 8 bits of
  each Unicode character. It returns Destination.

  The caller is responsible to make sure Destination points to a buffer with size
  equal or greater than ((StrLen (Source) + 1) * sizeof (CHAR8)) in bytes.

  If any Unicode characters in Source contain non-zero value in
  the upper 8 bits, then ASSERT().

  If Destination is NULL, then ASSERT().
  If Source is NULL, then ASSERT().
  If Source is not aligned on a 16-bit boundary, then ASSERT().
  If Source and Destination overlap, then ASSERT().

  If PcdMaximumUnicodeStringLength is not zero, and Source contains
  more than PcdMaximumUnicodeStringLength Unicode characters not including
  the Null-terminator, then ASSERT().

  If PcdMaximumAsciiStringLength is not zero, and Source contains more
  than PcdMaximumAsciiStringLength Unicode characters not including the
  Null-terminator, then ASSERT().

  @param  Source        The pointer to a Null-terminated Unicode string.
  @param  Destination   The pointer to a Null-terminated ASCII string.

  @return Destination.

**/
CHAR8 *
EFIAPI
UnicodeStrToAsciiStr (
  IN      CONST CHAR16              *Source,
  OUT     CHAR8                     *Destination
  );

 

示例代码

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"www.lab-z.com";
  CHAR8  *s2="              ";  
  CHAR8 *Result;
  
  Result=UnicodeStrToAsciiStr(s1,s2);
  Print(L"%a\n",s2);
  Print(L"%a\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtestg

7.AsciiStrToUnicodeStr 函数作用:将 ASCII的字符串转换为 Unicode的字符串

/**
  Convert one Null-terminated ASCII string to a Null-terminated
  Unicode string and returns the Unicode string.

  This function converts the contents of the ASCII string Source to the Unicode
  string Destination, and returns Destination.  The function terminates the
  Unicode string Destination by appending a Null-terminator character at the end.
  The caller is responsible to make sure Destination points to a buffer with size
  equal or greater than ((AsciiStrLen (Source) + 1) * sizeof (CHAR16)) in bytes.

  If Destination is NULL, then ASSERT().
  If Destination is not aligned on a 16-bit boundary, then ASSERT().
  If Source is NULL, then ASSERT().
  If Source and Destination overlap, then ASSERT().
  If PcdMaximumAsciiStringLength is not zero, and Source contains more than
  PcdMaximumAsciiStringLength ASCII characters not including the Null-terminator,
  then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Source contains more than
  PcdMaximumUnicodeStringLength ASCII characters not including the
  Null-terminator, then ASSERT().

  @param  Source        The pointer to a Null-terminated ASCII string.
  @param  Destination   The pointer to a Null-terminated Unicode string.

  @return Destination.

**/
CHAR16 *
EFIAPI
AsciiStrToUnicodeStr (
  IN      CONST CHAR8               *Source,
  OUT     CHAR16                    *Destination
  );

 

示例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR8  *s1="www.lab-z.com";
  CHAR16 *s2=L"             ";  
  CHAR16 *Result;
  
  Result=AsciiStrToUnicodeStr (s1,s2);
  Print(L"%s\n",s2);
  Print(L"%s\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果和前面的相同。

Step to UEFI (62) —– 常用的字符串函数(上)

这里介绍一下常用的处理字符串的函数。这篇介绍 StrCpy/StrnCpy StrSize StrLen StrCmp/StrnCmp StrCat/StrnCat 。这些函数都是给 Unicode16 字符串设计的( CHAR16 定义的, 或者 L“www.lab-z.com” 这样定义的)。同样还有一组带有 Ascii 前缀的函数是给普通的字符串使用的。

函数定义原型可以在 \MdePkg\Include\Library\BaseLib.h 这个文件中找到

1.StrCpy (D<-S) 作用:将 Source 定义的字符串拷贝到 Destination 定义的字符串中(覆盖之)

//
// String Services
//

/**
  Copies one Null-terminated Unicode string to another Null-terminated Unicode
  string and returns the new Unicode string.

  This function copies the contents of the Unicode string Source to the Unicode
  string Destination, and returns Destination. If Source and Destination
  overlap, then the results are undefined.

  If Destination is NULL, then ASSERT().
  If Destination is not aligned on a 16-bit boundary, then ASSERT().
  If Source is NULL, then ASSERT().
  If Source is not aligned on a 16-bit boundary, then ASSERT().
  If Source and Destination overlap, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Source contains more than
  PcdMaximumUnicodeStringLength Unicode characters not including the
  Null-terminator, then ASSERT().

  @param  Destination The pointer to a Null-terminated Unicode string.
  @param  Source      The pointer to a Null-terminated Unicode string.

  @return Destination.

**/
CHAR16 *
EFIAPI
StrCpy (
  OUT     CHAR16                    *Destination,
  IN      CONST CHAR16              *Source
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{

  CHAR16 *s1=L"WWW.";
  CHAR16 *s2=L".LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrCpy(s1,s2);
  Print(L"%s\n",s1);  
  Print(L"%s\n",Result);

  return EFI_SUCCESS;
}

 

strtest1

换一种字符串定义方式

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{

  CHAR16 s1[20]=L"WWW.";
  CHAR16 s2[20]=L".LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrCpy(s1,s2);
  Print(L"%s\n",s1);  
  Print(L"%s\n",Result);

  return EFI_SUCCESS;
}

 

运行结果和上面的相同。

2.StrnCpy 作用:将 Source 定义的字符串中,前n个拷贝到 Destination 定义的字符串中(覆盖之)

/**
  Copies up to a specified length from one Null-terminated Unicode string to 
  another Null-terminated Unicode string and returns the new Unicode string.

  This function copies the contents of the Unicode string Source to the Unicode
  string Destination, and returns Destination. At most, Length Unicode
  characters are copied from Source to Destination. If Length is 0, then
  Destination is returned unmodified. If Length is greater that the number of
  Unicode characters in Source, then Destination is padded with Null Unicode
  characters. If Source and Destination overlap, then the results are
  undefined.

  If Length > 0 and Destination is NULL, then ASSERT().
  If Length > 0 and Destination is not aligned on a 16-bit boundary, then ASSERT().
  If Length > 0 and Source is NULL, then ASSERT().
  If Length > 0 and Source is not aligned on a 16-bit boundary, then ASSERT().
  If Source and Destination overlap, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Length is greater than 
  PcdMaximumUnicodeStringLength, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Source contains more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the Null-terminator,
  then ASSERT().

  @param  Destination The pointer to a Null-terminated Unicode string.
  @param  Source      The pointer to a Null-terminated Unicode string.
  @param  Length      The maximum number of Unicode characters to copy.

  @return Destination.

**/
CHAR16 *
EFIAPI
StrnCpy (
  OUT     CHAR16                    *Destination,
  IN      CONST CHAR16              *Source,
  IN      UINTN                     Length
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW.";
  CHAR16 *s2=L".LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrnCpy(s1,s2,4);
  Print(L"%s\n",s1);  
  Print(L"%s\n",Result);

  return EFI_SUCCESS;
}

 

strtest2

3.StrLen 作用:返回字符串中字符的个数,注意:数量不包括结尾的 NULL。

/**
  Returns the length of a Null-terminated Unicode string.

  This function returns the number of Unicode characters in the Null-terminated
  Unicode string specified by String.

  If String is NULL, then ASSERT().
  If String is not aligned on a 16-bit boundary, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and String contains more than
  PcdMaximumUnicodeStringLength Unicode characters not including the
  Null-terminator, then ASSERT().

  @param  String  Pointer to a Null-terminated Unicode string.

  @return The length of String.

**/
UINTN
EFIAPI
StrLen (
  IN      CONST CHAR16              *String
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW.";
  CHAR16 s2[20]=L".LAB-Z.COM";
  UINTN Result;
  
  Result=StrLen(s1);
  Print(L"%d\n",Result);

  Result=StrLen(s2);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtest3

4. StrSize 作用:返回字符串占用的内存字节数,注意:包括 NULL。

/**
  Returns the size of a Null-terminated Unicode string in bytes, including the
  Null terminator.

  This function returns the size, in bytes, of the Null-terminated Unicode string 
  specified by String.

  If String is NULL, then ASSERT().
  If String is not aligned on a 16-bit boundary, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and String contains more than
  PcdMaximumUnicodeStringLength Unicode characters not including the
  Null-terminator, then ASSERT().

  @param  String  The pointer to a Null-terminated Unicode string.

  @return The size of String.

**/
UINTN
EFIAPI
StrSize (
  IN      CONST CHAR16              *String
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW";
  CHAR16 s2[40]=L"LAB-Z.COM";
  UINTN Result;
  
  Result=StrSize(s1);
  Print(L"%d\n",Result);

  Result=StrSize(s2);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtest4

定义的字符串实际上是: “www\0” ,每个字符都是 CHAR16, 算下来需要占用 8个字节。

5. StrCmp 作用:比较字符串是否相同。相同的话返回 0

/**
  Compares two Null-terminated Unicode strings, and returns the difference
  between the first mismatched Unicode characters.

  This function compares the Null-terminated Unicode string FirstString to the
  Null-terminated Unicode string SecondString. If FirstString is identical to
  SecondString, then 0 is returned. Otherwise, the value returned is the first
  mismatched Unicode character in SecondString subtracted from the first
  mismatched Unicode character in FirstString.

  If FirstString is NULL, then ASSERT().
  If FirstString is not aligned on a 16-bit boundary, then ASSERT().
  If SecondString is NULL, then ASSERT().
  If SecondString is not aligned on a 16-bit boundary, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and FirstString contains more
  than PcdMaximumUnicodeStringLength Unicode characters not including the
  Null-terminator, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and SecondString contains more
  than PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().

  @param  FirstString   The pointer to a Null-terminated Unicode string.
  @param  SecondString  The pointer to a Null-terminated Unicode string.

  @retval 0      FirstString is identical to SecondString.
  @return others FirstString is not identical to SecondString.

**/
INTN
EFIAPI
StrCmp (
  IN      CONST CHAR16              *FirstString,
  IN      CONST CHAR16              *SecondString
  );

 

实例代码:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW";
  CHAR16 s2[40]=L"LAB-Z.COM";
  CHAR16 s3[40]=L"LAB-Z";
  CHAR16 s4[40]=L"MAB-Z";  
  UINTN Result;
  
  Result=StrCmp(s1,s2);
  Print(L"%d\n",Result);

  Result=StrCmp(s2,s3);
  Print(L"%d\n",Result);

  Result=StrCmp(s3,s4);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果:

strtest5

6. StrnCmp 作用:和上面的比较函数类似,只是这个函数只比较指定的前n个字符

/**
  Compares up to a specified length the contents of two Null-terminated Unicode strings,
  and returns the difference between the first mismatched Unicode characters.
  
  This function compares the Null-terminated Unicode string FirstString to the
  Null-terminated Unicode string SecondString. At most, Length Unicode
  characters will be compared. If Length is 0, then 0 is returned. If
  FirstString is identical to SecondString, then 0 is returned. Otherwise, the
  value returned is the first mismatched Unicode character in SecondString
  subtracted from the first mismatched Unicode character in FirstString.

  If Length > 0 and FirstString is NULL, then ASSERT().
  If Length > 0 and FirstString is not aligned on a 16-bit boundary, then ASSERT().
  If Length > 0 and SecondString is NULL, then ASSERT().
  If Length > 0 and SecondString is not aligned on a 16-bit boundary, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Length is greater than
  PcdMaximumUnicodeStringLength, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and FirstString contains more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the Null-terminator,
  then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and SecondString contains more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the Null-terminator,
  then ASSERT().

  @param  FirstString   The pointer to a Null-terminated Unicode string.
  @param  SecondString  The pointer to a Null-terminated Unicode string.
  @param  Length        The maximum number of Unicode characters to compare.

  @retval 0      FirstString is identical to SecondString.
  @return others FirstString is not identical to SecondString.

**/
INTN
EFIAPI
StrnCmp (
  IN      CONST CHAR16              *FirstString,
  IN      CONST CHAR16              *SecondString,
  IN      UINTN                     Length
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW";
  CHAR16 s2[40]=L"LAB-Z.COM";
  CHAR16 s3[40]=L"LAB-Z";
  CHAR16 s4[40]=L"MAB-Z";  
  
  Result=StrnCmp(s1,s2,7);
  Print(L"%d\n",Result);

  Result=StrnCmp(s2,s3,5);
  Print(L"%d\n",Result);

  Result=StrnCmp(s3,s4,5);
  Print(L"%d\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtest6

7. StrCat 作用:将 Source 字符串的内容拼接到 Destination 字符串上。

/**
  Concatenates one Null-terminated Unicode string to another Null-terminated
  Unicode string, and returns the concatenated Unicode string.

  This function concatenates two Null-terminated Unicode strings. The contents
  of Null-terminated Unicode string Source are concatenated to the end of
  Null-terminated Unicode string Destination. The Null-terminated concatenated
  Unicode String is returned. If Source and Destination overlap, then the
  results are undefined.

  If Destination is NULL, then ASSERT().
  If Destination is not aligned on a 16-bit boundary, then ASSERT().
  If Source is NULL, then ASSERT().
  If Source is not aligned on a 16-bit boundary, then ASSERT().
  If Source and Destination overlap, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Destination contains more
  than PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Source contains more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and concatenating Destination
  and Source results in a Unicode string with more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().

  @param  Destination The pointer to a Null-terminated Unicode string.
  @param  Source      The pointer to a Null-terminated Unicode string.

  @return Destination.

**/
CHAR16 *
EFIAPI
StrCat (
  IN OUT  CHAR16                    *Destination,
  IN      CONST CHAR16              *Source
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW.";
  CHAR16 s2[40]=L"LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrCat(s1,s2);
  Print(L"%s\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtest7

8.StrnCat 作用:将 Source 字符串前面n个字符的内容拼接到 Destination 字符串上。

/**
  Concatenates up to a specified length one Null-terminated Unicode to the end 
  of another Null-terminated Unicode string, and returns the concatenated 
  Unicode string.

  This function concatenates two Null-terminated Unicode strings. The contents
  of Null-terminated Unicode string Source are concatenated to the end of
  Null-terminated Unicode string Destination, and Destination is returned. At
  most, Length Unicode characters are concatenated from Source to the end of
  Destination, and Destination is always Null-terminated. If Length is 0, then
  Destination is returned unmodified. If Source and Destination overlap, then
  the results are undefined.

  If Destination is NULL, then ASSERT().
  If Length > 0 and Destination is not aligned on a 16-bit boundary, then ASSERT().
  If Length > 0 and Source is NULL, then ASSERT().
  If Length > 0 and Source is not aligned on a 16-bit boundary, then ASSERT().
  If Source and Destination overlap, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Length is greater than 
  PcdMaximumUnicodeStringLength, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Destination contains more
  than PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and Source contains more than
  PcdMaximumUnicodeStringLength Unicode characters, not including the
  Null-terminator, then ASSERT().
  If PcdMaximumUnicodeStringLength is not zero, and concatenating Destination
  and Source results in a Unicode string with more than PcdMaximumUnicodeStringLength
  Unicode characters, not including the Null-terminator, then ASSERT().

  @param  Destination The pointer to a Null-terminated Unicode string.
  @param  Source      The pointer to a Null-terminated Unicode string.
  @param  Length      The maximum number of Unicode characters to concatenate from
                      Source.

  @return Destination.

**/
CHAR16 *
EFIAPI
StrnCat (
  IN OUT  CHAR16                    *Destination,
  IN      CONST CHAR16              *Source,
  IN      UINTN                     Length
  );

 

实例:

#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;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16 *s1=L"WWW.";
  CHAR16 s2[40]=L"LAB-Z.COM";
  CHAR16 *Result;
  
  Result=StrnCat(s1,s2,3);
  Print(L"%s\n",Result);
  
  return EFI_SUCCESS;
}

 

运行结果

strtest9

本文参考 《UEFI 原理与编程》。

Arduino 控制USB设备(7)解析USB鼠标的例子(下)

前面实现了HP 鼠标数据的读取,下面继续研究 DELL 的鼠标。还是首先运行取得描述符的程序,结果如下:

Start
Device addressed… Requesting device descriptor.
Device descriptor:

Descriptor Length: 12
USB version: 1.10
Class: 00 Use class information in the Interface Descriptor
Subclass: 00
Protocol: 00
Max.packet size: 08
Vendor ID: 2188
Product ID: 0AE1
Revision ID: 0100
Mfg.string index: 00
Prod.string index: 01 Length: 38 Contents: USB OPTICAL MOUSE
Serial number index: 00
Number of conf.: 01

Configuration number 0
Total configuration length: 34 bytes

Configuration descriptor:
Total length: 0022
Number of interfaces: 01
Configuration value: 01
Configuration string: 00
Attributes: A0 Remote Wakeup
Max.power: 32 100ma

Interface descriptor:
Interface number: 00
Alternate setting: 00
Endpoints: 01
Class: 03 HID (Human Interface Device)
Subclass: 01
Protocol: 02
Interface string: 00

HID descriptor:
Descriptor length: 09 9 bytes
HID version: 1.11
Country Code: 0 Not Supported
Class Descriptors: 1
Class Descriptor Type: 22 Report
Class Descriptor Length:66 bytes

HID report descriptor:

Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 1 Type: Local Tag: Usage Data: 02
Length: 1 Type: Main Tag: Collection Application (mouse, keyboard) Data: 01
Length: 1 Type: Global Tag: Report ID Data: 01
Length: 1 Type: Local Tag: Usage Data: 01
Length: 1 Type: Main Tag: Collection Physical (group of axes) Data: 00
Length: 1 Type: Global Tag: Usage Page Button Data: 09
Length: 1 Type: Local Tag: Usage Minimum Data: 01
Length: 1 Type: Local Tag: Usage Maximum Data: 03
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 1 Type: Global Tag: Logical Maximum Data: 01
Length: 1 Type: Global Tag: Report Count Data: 03
Length: 1 Type: Global Tag: Report Size Data: 01
Length: 1 Type: Main Tag: Input Data,Variable,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 02
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Global Tag: Report Size Data: 05
Length: 1 Type: Main Tag: Input Constant,Array,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 01
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 1 Type: Local Tag: Usage Data: 30
Length: 1 Type: Local Tag: Usage Data: 31
Length: 2 Type: Global Tag: Logical Minimum Data: 00 Data: F8
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 07
Length: 1 Type: Global Tag: Report Size Data: 0C
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Main Tag: Input Data,Variable,Relative,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 06
Length: 1 Type: Local Tag: Usage Data: 38
Length: 1 Type: Global Tag: Logical Minimum Data: 81
Length: 1 Type: Global Tag: Logical Maximum Data: 7F
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Main Tag: Input Data,Variable,Relative,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 06
Length: 0 Type: Main Tag: End Collection
Length: 0 Type: Main Tag: End Collection

Endpoint descriptor:
Endpoint address: 01 Direction: IN
Attributes: 03 Transfer type: Interrupt
Max.packet size: 0006
Polling interval: 0A 10 ms

同样,尝试之前的 Get_Report 方式的程序,得到的却是不停的输出的错误信息:

Setup packet error: 7
Mouse Poll Error: 7

没有办法直接了解这个错误编号的含义,最后只能用逻辑分析仪分析产生问题的原因:

usb71

可以看到当发送Get_Report之后,Device 会返回STALL 。

对比之前能够正常工作的 HP 鼠标,收到 Get_Report 后,会返回 ACK 还会继续通讯。

usb72

查看资料上说,返回STALL有可能是因为设备不支持该指令…… Windows的设备经常会出现这样的情况:可以正常工作,但是未必完整的 follow 工业标准。

上面的方法行不通,只能用中断方式来获取数据。我去掉了切换 Boot Protocol 的代码

/* Mouse communication via interrupt endpoint  */
/* Assumes EP1 as interrupt IN ep              */
#include "max3421e.h"
#include "usb.h"
 
#define DEVADDR 1
#define CONFVALUE 1
#define EP_MAXPKTSIZE 5
EP_RECORD ep_record[ 2 ];  //endpoint record structure for the mouse
 
 
void setup();
void loop();
 
MAX3421E Max;
USB Usb;
 
void setup()
{
    Serial.begin( 115200 );
    Serial.println("Start");
    Max.powerOn();
    delay( 200 );
}
 
void loop()
{
 byte rcode;
    Max.Task();
    Usb.Task();
    if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
        mouse1_init();
    }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
    if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  //poll the keyboard
        rcode = mouse1_poll();
        if( rcode ) {
          Serial.print("Mouse Poll Error: ");
          Serial.println( rcode, HEX );
        }//if( rcode...
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse1_init( void )
{
 byte rcode = 0;  //return code
 byte tmpdata;
 byte* byte_ptr = &tmpdata;
  /**/
  ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 ));  //copy endpoint 0 parameters
  ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[ 1 ].sndToggle = bmSNDTOG0;
  ep_record[ 1 ].rcvToggle = bmRCVTOG0;
  Usb.setDevTableEntry( 1, ep_record );              //plug kbd.endpoint parameters to devtable
  /* Configure device */
  rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  if( rcode ) {
    Serial.print("Error configuring mouse. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }//if( rcode...
  
  rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
  if( rcode ) {
    Serial.print("Set Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  
  rcode = Usb.getIdle( DEVADDR, 0, 0, 0, (char *)byte_ptr );
  if( rcode ) {
    Serial.print("Get Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  Serial.print("Idle Rate: ");
  Serial.print(( tmpdata * 4 ), DEC );        //rate is returned in multiples of 4ms
  Serial.println(" ms");
  tmpdata = 0;
  rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
  if( rcode ) {
    Serial.print("Set Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  Usb.setUsbTaskState( USB_STATE_RUNNING );
  return;
}
/* Poll mouse via interrupt endpoint and print result */
/* assumes EP1 as interrupt endpoint                  */
byte mouse1_poll( void )
{
  byte rcode,i;
  char buf[ 6 ] = { 0 };                          //mouse report buffer

  unsigned long int libuf[ sizeof(buf) ];
  unsigned long int x;
  unsigned long int y;

  
  /* poll mouse */
  rcode = Usb.inTransfer( DEVADDR, 1, sizeof(buf), buf, 1 );  //

    if( rcode ) {  //error
      if( rcode == 0x04 ) {  //NAK
        rcode = 0;
      }
      return( rcode );
    }
    /* print buffer */
    if( buf[ 1 ] & 0x01 ) {
      Serial.println("Button1 pressed ");
    }
    if( buf[ 1 ] & 0x02 ) {
      Serial.println("Button2 pressed ");
    }
    if( buf[ 1 ] & 0x04 ) {
      Serial.println("Button3 pressed ");
    }
    
    for (int i=0;i<sizeof(buf);i++) {
       libuf[i]=(buf[i]) & 0xff;
    }
    
    /*  
    Serial.print(libuf[0],HEX); Serial.print("  "); 
    Serial.print(libuf[1],HEX); Serial.print("  "); 
    Serial.print(libuf[2],HEX); Serial.print("  "); 
    Serial.print(libuf[3],HEX); Serial.print("  ");     
    Serial.print(libuf[4],HEX); Serial.print("  ");         
    Serial.print(libuf[5],HEX); Serial.print("  ");             
    Serial.println("");
    */
    
    Serial.print("X-axis: ");
    x=((libuf[3] & 0xF)<<8)+(libuf[2] & 0xFF );
    if (x & 0x800) {
      Serial.print("-");
      x = ((~x) & 0x7FF) +1;
    }
   
    Serial.print(x, HEX);  Serial.print("   ");
    
    Serial.print("Y-axis: ");    
    y=(((libuf[3]>>4) & 0xF) +((libuf[4] & 0xFF )<<4));
    if (y & 0x800) {
      Serial.print("-");
      y = ((~y) & 0x7FF) +1;
    }    
    Serial.print(y, HEX); Serial.print("   ");

    Serial.print("Wheel: ");    
    Serial.println(buf [5] & 0xFF, HEX);    

    return( rcode );
}

 

和前面那个程序相比,这个特别之处在于返回值多了 Report_ID ,对于我们处理数据来说,只是有效数据从 Buf[1] 开始,其他地方并无特别。

运行结果

usb73

完整代码下载

mousec

基本上,鼠标的玩法就是这些。可以用鼠标做很多好玩的东西。