请保留-> 【原文: 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所示。
Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/HelloHid下
1,776 total views, 3 views today