5

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

 3 years ago
source link: http://yiiyee.cn/blog/2021/05/13/uefi%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a288-yie002usb%e5%bc%80%e5%8f%91%e6%9d%bf%ef%bc%8811-uefi%e4%b8%8b%e8%ae%bf%e9%97%aehid%e8%ae%be%e5%a4%87%ef%bc%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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-exolorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/HelloHid下

159 total views, 1 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK