前面的文章介绍了如何使用 CH55X 制作一个 USB 提醒器【参考1】,这次介绍如何在 UEFI Shell 下编写 Application 来控制使用它。
从思路上来说,可以使用加载驱动,然后调用驱动引入的 protocol 来进行控制。比如,FT232 有一个驱动,可以在 UEFI Shell 下直接调用【参考2】.但是,这次的USB 提醒器并没有这样的Driver(估计需要等待 WCH 来进行开发吧)。于是,我们只能尝试直接对其发送数据。
首先使用 USBView 查看一下:

这里需要特别关注的是 Endpoint, 可以看到有下面三个 Endpoint, 第一个是 interrupt, 用于传送控制信息;后面两个分别是 Bulk 的输入和输出用于传输真正的串口数据。
===>Endpoint Descriptor<===
bLength: 0x07
bDescriptorType: 0x05
bEndpointAddress: 0x81 -> Direction: IN - EndpointID: 1
bmAttributes: 0x03 -> Interrupt Transfer Type
wMaxPacketSize: 0x0008 = 0x08 bytes
bInterval: 0x40
===>Endpoint Descriptor<===
bLength: 0x07
bDescriptorType: 0x05
bEndpointAddress: 0x02 -> Direction: OUT - EndpointID: 2
bmAttributes: 0x02 -> Bulk Transfer Type
wMaxPacketSize: 0x0040 = 0x40 bytes
bInterval: 0x00
===>Endpoint Descriptor<===
bLength: 0x07
bDescriptorType: 0x05
bEndpointAddress: 0x82 -> Direction: IN - EndpointID: 2
bmAttributes: 0x02 -> Bulk Transfer Type
wMaxPacketSize: 0x0040 = 0x40 bytes
bInterval: 0x00
另外因为 USBNotifier 实际上是一个假串口设备(意思是并非对外转接为串口数据),所以即使初始化时如果没有设置波特率等等这样的参数,仍然能够正确收到数据。所以,我们只需要将数据丢到Bulk 的 Endpoint 就可以正常收到并处理。关键步骤如下:
- 枚举当前系统中有 USBIo 的全部 handle
- 使用 UsbGetDeviceDescriptor() 取得每个设备的 PID 和 VID
- 使用 UsbGetInterfaceDescriptor() 找到 USBNotifier 的 EndPoint
- 使用 UsbBulkTransfer() 针对找到的 Endpoint 发送数据
需要特别注意的是:在 UEFI Shell 下, USBNotifier 会被认为两个设备,Interrupt 的Endpoint 为一个设备,另外2个 Bulk 的 Endpoint 被识别为一个设备。
完整的代码:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UsbIo.h>
extern EFI_BOOT_SERVICES *gBS;
#define USB_ENDPOINT_ADDR(EpAddr) ((EpAddr) & 0x7F)
EFI_GUID gEfiUsbIoProtocolGuid =
{ 0x2B2F68D6, 0x0CD2, 0x44CF, { 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
UINTN HandleIndex, HandleCount;
//UINT8 i,j;
EFI_HANDLE *DevicePathHandleBuffer = NULL;
EFI_USB_IO_PROTOCOL *USBIO;
EFI_USB_DEVICE_DESCRIPTOR DeviceDescriptor;
EFI_USB_INTERFACE_DESCRIPTOR IfDesc;
UINT8 arr[64];
UINTN LengthInBytes;
UINT32 TransferStatus;
//Get all the Handles that have UsbIO Protocol
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;
}
//Get USB Device Descriptor
Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Usb Get Device Descriptor fail.\n");
gBS->FreePool(DevicePathHandleBuffer);
return EFI_SUCCESS;
}
//Find the device which VID and PID is USB notifier
if ((0x1209==DeviceDescriptor.IdVendor) && (0xC550==DeviceDescriptor.IdProduct))
{
//Show the PID and VID
Print(L"Found a USB Notifier = %04X, ProductID = %04X\n",
DeviceDescriptor.IdVendor,
DeviceDescriptor.IdProduct);
//
// Get Interface Descriptor
//
Status = USBIO->UsbGetInterfaceDescriptor (USBIO, &IfDesc);
if (EFI_ERROR (Status))
{
continue;
}
if (IfDesc.NumEndpoints==2)
{
Print(L"Found OUTPUT Endpoint, send the data!\n");
arr[0]=0x5B; arr[1]=0x63; arr[2]=0xFF;
arr[3]=0x00; arr[4]=0x00; arr[5]=0x5D;
LengthInBytes=6;
Status = USBIO->UsbBulkTransfer (
USBIO,
2,
&arr[0],
&LengthInBytes,
3000,
&TransferStatus );
Print(L"Red color\n");
gBS->Stall(3000000UL);
arr[2]=00; arr[3]=0xFF; arr[4]=0x00;
Status = USBIO->UsbBulkTransfer (
USBIO,
2,
&arr[0],
&LengthInBytes,
3000,
&TransferStatus );
Print(L"Green color\n");
gBS->Stall(3000000UL);
arr[2]=0x00; arr[3]=0x00; arr[4]=0xFF;
Status = USBIO->UsbBulkTransfer (
USBIO,
2,
&arr[0],
&LengthInBytes,
3000,
&TransferStatus );
Print(L"Blue color\n");
gBS->Stall(3000000UL);
arr[2]=0x00; arr[3]=0x00; arr[4]=0x00;
Status = USBIO->UsbBulkTransfer (
USBIO,
2,
&arr[0],
&LengthInBytes,
3000,
&TransferStatus );
}
}
}
gBS->FreePool(DevicePathHandleBuffer);
return EFI_SUCCESS;
}
工作的视频可以在 B 站看到。
https://www.bilibili.com/video/BV1dQ4y1X7XG/
完整工程下载:
参考:
- https://www.lab-z.com/usbnt/ 做一个 USB 提醒器
- https://www.lab-z.com/stufdti/ Step to UEFI (93)FTDI 串口驱动