UEFI开发探索88- YIE002USB开发板(11 UEFI下访问HID设备)

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

通过前面几个篇章的博客,制作好了USB HID设备,并使用Windows下的上位机工具UsbHID,测试了设备的工作状态。

终于,可以在UEFI系统下构建访问USB HID设备的工程了。

我们所制作的USB HID设备,在Windows系统下可以成功通信,这也意味着,在UEFI环境下,我们也可以与之通信。

首先,使用linux下的lsusb工具,查看下我们之前实现的USB HID设备:

robin@robin-virtual-machine:~$ sudo lsusb -v -d 0x8765:4321
Bus 002 Device 004: ID 8765:4321  
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x8765 
  idProduct          0x4321 
  bcdDevice            2.00
  iManufacturer           1 Robin
  iProduct                2 Robin's UEFI Explorer
  iSerial                 3 My123
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xe0
      Self Powered
      Remote Wakeup
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      33
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              32
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              32
Device Status:     0x0003
  Self Powered
  Remote Wakeup Enabled

从中可以看出,自制的USB HID设备,端点1可以使用中断传输的方式进行通信。另外,在上述的信息中没有列出报表描述符,此设备支持通过Output report& Input report、Feature report的方式进行通信,传输字节为16字节。

本篇介绍如何在UEFI系统下,实现对应这三种通信方式的代码,主要实现步骤如下。

1 添加访问USB HID设备的库和头文件

在EDK2的MdePkg 中,提供了支持USB HID设备访问的库UefiUsbLib,其库函数定义在头文件\MdePkg\Include\Library\UefiUsbLib.h中。

在UefiUsbLib中,提供了HID的标准命令和类命令的对应函数。比如,对应标准命令Get_Descriptor的函数为UsbGetDescriptor(),对应类命令Get_Report的函数为UsbGetReportRequest(),其余的USB命令,都可以从函数名上得知对应函数。

在访问USB HID设备的时候,我们可以直接使用这些函数进行通信。因此,需要将库声明和头文件声明添加到示例工程中。在示例工程的INF文件中添加如下声明:

[Packages]
  MdePkg/MdePkg.dec
  ......            //其他Package
[LibraryClasses]
  UefiUsbLib      //添加支持USB HID设备访问的函数库          
  ......           //其他库

并在头文件Common.h中,添加包含头文件声明:

#include <Library/UefiUsbLib.h>

完成上述工作后,即可在代码中调用访问USB HID设备的函数。

2 定位USB HID设备

类似于上位机的测试工具UsbHID,我们通过HID设备的厂商ID和产品ID来定位设备。当
厂商ID为0x8765,而且产品ID为0x4321,说明所找到的设备就是我们制作的HID设备。其实现代码如示例1所示。

示例1 定位自己的HID设备

BOOLEAN findMyHidDevice(OUT INT16 *index,IN UINT16 MyVID,IN UINT16 MyPID)
{
  EFI_STATUS Status;
  INT16 i;
  EFI_USB_DEVICE_DESCRIPTOR     UsbDevDesc;
  if(gUsbIOCount == 0)  //没有USB设备
    return FALSE;
  for(i=0;i<gUsbIOCount;i++) //轮询是否为指定的设备
  {
    Status = gUsbIO[i]->UsbGetDeviceDescriptor(gUsbIO[i], &UsbDevDesc);     
    if(Status == EFI_SUCCESS)
    {
      if((UsbDevDesc.IdVendor == MyVID) && (UsbDevDesc.IdProduct == MyPID))
      {
        *index = i;
        return TRUE;
      }
    }
  }
  return FALSE;
}

示例1中的函数findMyHidDevice(),从全局数组gUsbIO[]中,找到产品ID为MyVID、产品ID为MyPID的USB设备。数组gUsbIO[]是EFI_USB_IO_PROTOCOL型指针数组,每个元素相当于是一个USB设备的接口。我们所制作的USB HID设备,只有一个接口,因此在数组中只占据了一个元素。

在找到对应的设备后,函数将返回其相应的数组下标(参数INT16 *index)。由此,我们得到了USB HID设备对应的EFI_USB_IO_PROTOCOL Protocol实例,可以调用其接口函数与HID设备通信了。

3 与USB HID设备通信

得到USB设备的Protocol实例后,可以使用类命令Set_Report和Get_Report对应的函数,向
HID设备发送数据和接收来自HID设备的数据。实现代码如示例2所示。

示例2 与HID设备通信(Output report&Input report)

VOID Output_Input_Report (IN INT16 index)
{
  EFI_STATUS Status;
  UINT8   ReportId, myBuffer[16];
  INTN i;
  gBS->SetMem(myBuffer,16,0xA0);
  ReportId = 0;
  Status = UsbSetReportRequest(
    gUsbIO[index],  //Protocol实例
    0,                //接口
    ReportId,       //报告ID
    HID_OUTPUT_REPORT, //报告类型
    16,                   //缓冲区长度
    myBuffer             //缓冲区
  );
  if(EFI_ERROR(Status)) return;
  gBS->SetMem(myBuffer,16,0x00);
  Status = UsbGetReportRequest(
    gUsbIO[index], //Protocol实例
    0,               //接口
    ReportId,       //报告ID
    HID_INPUT_REPORT, //报告类型
    16,                  //缓冲区长度
    myBuffer            //缓冲区
  );
  if(EFI_ERROR(Status)) return;
  Print(L"Get data from MyHidDevice:\n");
  for(i=0;i<16;i++)
    Print(L"0x%02x ",myBuffer[i]);
  Print(L"\n");
}

在示例2的函数Output_Input_Report()中,我们调用了UsbSetReportRequest()和UsbGetReportRequest(),通过Output报告和Input报告与HID设备通信。需要注意的是,在调用这两个函数的时,报告ID(也即ReportID)设置为0。这是因为在我们设计HID设备时,报告描述符中并没有设置报告ID的项,因此只需要将其设置为0即可。

库函数UsbSetReportRequest()和UsbGetReportRequest(),它们的实现代码在EDK2的源文件\MdePkg\Library\UefiUsbLib\Hid.c中。这两个函数是调用了EFI_USB_IO_PROTOCOL的接口函数UsbControlTransfer(),来实现与HID设备通信的。

至此,我们完成了与HID设备通信的核心代码。在主函数中,直接调用findMyHidDevice()和Output_Input_Report(),即可访问HID设备。

而使用端点1(中断传输)方式和Feature report方式,在示例代码中也实现了。Feature report的实现代码,与上述的Output_Input_Report()没有什么区别,只不过把report类型修改下就行了。

使用端点1通信,也即类似于Windows的ReadFile()&Write()通信方式,是使用UEFI USB Protocol的接口函数UsbSyncInterruptTransfer()来实现的。此接口函数的使用方法,可以查看UEFI规范USB的章节。

端点1通信的代码如下:

示例3 与HID设备通信(端点1中断传输)

VOID Endpoint_OutIn(IN INT16 index)
{
  EFI_STATUS Status;
  // UINT8   ReportId;
  UINT8 myBuffer[16];
  UINTN lenBuffer;
  UINTN i;
  UINT32 result;

  gBS->SetMem(myBuffer,16,0xA0);
  lenBuffer=16;
  Status = gUsbIO[index]->UsbSyncInterruptTransfer(
    gUsbIO[index],
    0x01, //EP1 OUT
    myBuffer,
    &lenBuffer,
    32,
    &result
  );
  if(EFI_ERROR(Status))
  {
    Print(L"OUT:UsbSyncInterruptTransfer Error!\n");
    Print(L"Status:%r\n",Status);
    return;
  }
  if(EFI_ERROR(result))
  {
    Print(L"UsbSyncInterruptTransfer result:%r\n",result);
    Print(L"\n");
    return;
  }
  
  gBS->SetMem(myBuffer,16,0x00);
  Status = gUsbIO[index]->UsbSyncInterruptTransfer(
    gUsbIO[index],
    0x81, //EP1 IN
    myBuffer,
    &lenBuffer,
    32,
    &result
  );
  if(EFI_ERROR(Status))
  {
    Print(L"IN:UsbSyncInterruptTransfer Error!\n");
    Print(L"Status:%r\n",Status);
    return;
  }
  if(EFI_ERROR(result))
  {
    Print(L"UsbSyncInterruptTransfer result:%r\n",result);
    Print(L"\n");
    return;
  }
  else
  {
    Print(L"Endpoint_OutIn,data from MyHidDevice=%d:\n",lenBuffer);
    for(i=0;i<lenBuffer;i++)
      Print(L"0x%02x ",myBuffer[i]);
    Print(L"\n");
  }

需要注意的是,本章针对端点1进行代码编写,是因为自制的HID设备中,设置了端点1位通信端点。如果USB HID设备使用其他端点来通信,代码必须做相应的修改。

编译本篇提供的示例:

C:\UEFIWorkspace\edk2\build -p RobinPkg\RobinPkg.dsc \
-m RobinPkg\Applications\ListUSB\HelloHid.inf -a X64

HelloHid程序只能在实际的机器上运行。可将自制的HID设备插入计算机,进入UEFI Shell环境,运行HelloHid,观察与HID设备通信的结果。

需要注意的是,有些UEFI BIOS对USB Protocol支持得并不好。我是在Intel NUC(NUC6CAYHC)上进行实验的,结果还不错,如图1所示。

图1 测试HelloHid

Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/HelloHid下

1,539 total views, 1 views today

发表评论

电子邮件地址不会被公开。