一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?

首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。

typedef struct _EFI_USB_IO_PROTOCOL {
  EFI_USB_IO_CONTROL_TRANSFER            UsbControlTransfer;
  EFI_USB_IO_BULK_TRANSFER               UsbBulkTransfer;
  EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER    UsbAsyncInterruptTransfer;
  EFI_USB_IO_SYNC_INTERRPUT_TRANSFER     UsbSyncInterruptTransfer;
  EFI_USB_IO_ISOCHRONOUS_TRANSFER        UsbIsochronousTransfer;
  EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER  UsbAsyncIsochronousTransfer;
  EFI_USB_IO_GET_DEVICE_DESCRIPTOR       UsbGetDeviceDescriptor;
  EFI_USB_IO_GET_CONFIG_DESCRIPTOR       UsbGetConfigDescriptor;
  EFI_USB_IO_GET_INTERFACE_DESCRIPTOR    UsbGetInterfaceDescriptor;
  EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR     UsbGetEndpointDescriptor;
  EFI_USB_IO_GET_STRING_DESCRIPTOR       UsbGetStringDescriptor;
  EFI_USB_IO_GET_SUPPORTED_LANGUAGES     UsbGetSupportedLanguages;
  EFI_USB_IO_PORT_RESET                  UsbPortReset;
} EFI_USB_IO_PROTOCOL;

来自【参考2】。

我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。

image001

我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。

image002

每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_GUID  gEfiUsbIoProtocolGuid   = 
	{ 0x2B2F68D6, 0x0CD2, 0x44CF, 
		{ 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};

UINTN GetUSB( )
{
  EFI_STATUS  Status;
  UINTN       HandleIndex, HandleCount;
  EFI_HANDLE  *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL 			*USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;

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

      Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Print(L"VendorID = %04X, ProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);      

  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  GetUSB( );
  return EFI_SUCCESS;
}

最终运行结果如下:

image003

完整的代码和程序下载:
GetPIDVID

参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义

  1. eos33 says:

    請問前輩,有試過使用EDK2的USB IO PROTOCOL,存取USB HID(MCU)裝置嗎?
    譬如用usb interrupt transfer,寫值到MCU,MCU再parse資料後做相對應動作。

  2. eos33 says:

    看前輩有用Arduino,不知道它有沒有USB接口(Arduino),或許可以試試看...
    我手邊有Microchip的實驗板,參考前輩的sample code可以讀取USB裝置資訊,
    但怎麼用interrupt transfer就不太了解了,,
    手邊的AMI BIOS code也找不到相關範例可以參考...

  3. kali says:

    你好,我想请教一个问题,我在使用HandleProtocol或LocateProtocol的时候,总是得到的EFI_STATUS总是错的:0x8000000e。小弟刚入门,还请大哥帮我出坑。感激涕零!

      • kali says:

        谢谢您的回复。我有试过,打印的结果为Not Found , 原因应该是和楼下darren所遇到的问题一致。另外,我在尝试 EFI_PCI_IO_PROTOCOL 和 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 这两个pci protocol的时候也是如此情况,Not Found。但是有些Protocol是可以用的,如:BLOCKIO 和 SIMPLE FILE SYSTEM 等。综上我有一个理解上的问题:
        在NT32环境中有些程序无出现Not Found,是NT32环境中没有对应Protocol,还是没有设备挂载这个Protocol?
        请您帮我释疑,万分感激。

  4. darren says:

    请问是不是在,EDKII 模拟环境下,这个USB demo程序也是没办法运行的 ? 是不是也是在拟环境没有设备挂USBIO Protocol 这个原因造成的?

  5. zheng says:

    你好, 我在模拟的板子, 最后会boot到shell envorinment
    Edk2\ShellPkg\Application\Shell\Shell.c
    目前问题是在Shell环境键盘没有回应
    我参考你的方式在Shell.c UefiMain 尝试去LocateHandleBuffer
    结果发现Get USBIO count fail
    追到最后发现
    CoreLocateHandleBuffer
    CoreLocateHandle

    //
    // Look up the protocol entry and set the head pointer
    //
    Position.ProtEntry = CoreFindProtocolEntry (Protocol, FALSE);
    if (Position.ProtEntry == NULL) {
    Status = EFI_NOT_FOUND;
    break;
    }
    这段回传 EFI_NOT_FOUND;
    看起来是mProtocolDatabase 里面找不到gEfiUsbIoProtocolGuid
    不太理解为什么会找不到, gEfiUsbIoProtocolGuid前面应该很多地方有去Locate
    不知道大大有没有什么想法or方向

      • zheng says:

        是的, 用的是Simics模擬器, 但不確定是模擬器本身沒有Usb controller 還是Bios Code 問題, 目前就開到shell, keyboard無法輸入, 所以用cmd drivers去檢查
        目前想法是Shell.c 去list 所有的handle 以及 protocol
        還是有其他的方式去驗證呢?

          • zheng says:

            目前手邊沒有PS2鍵盤, 我使用
            Status = gBS->LocateHandleBuffer (
            AllHandles,
            NULL,
            NULL,
            &HandleCount,
            &HandleBuffer);

            ProtocolsPerHandle(HandleBuffer, &ProtocolBuffer, &ProtocolCount);

            列出所有handle下所掛的protocol GUID 以及Name
            把這功能寫成Application 在Notebook 上跑可以找到UsbIo 和Controller Protocol, 寫在模擬器上Shell.c嘗試log 則都沒有Usb相關的Protocol
            代表模擬器沒有USB Controller? 還是可能BIOS某些地方沒設定好呢?

            謝謝

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>