WinUSB
+ -

使用微软系统描述符1.0制作免驱动自定义USB设备

2022-06-16 2074 0

本文作者XTOOLBOX,本站得到了作者本人的转载授权。

本文介绍如何使用微软的操作系统描述符来实现自定义USB设备在Windows系统上的免驱动使用。

前言

在Linux上开发USB设备是不需要特别的驱动的,Linux内核的USB驱动会将USB设备的基本操作都暴露到应用层,由应用层来完成实际的业务逻辑。libusb就是这样的一种通用USB设备访问程序。

为了在Windows上也实现这样的效果,libusb最初提供了Windows上通用的内核态驱动程序,将基本访问接口暴露到应用层,由应用层来实现访问逻辑。随着发展,微软也提供了这样的驱动程序,那就是WinUSB通用驱动程序。

为了给设备安装WinUSB驱动,还需要使用包含设备VID和PID的inf文件,以及签名的cat文件(ST-Link就是使用WinUSB作为驱动的,它的驱动就是一个典型的WinUSB设备驱动)。

对于使用WinUSB驱动的设备来说,这些驱动文件做了两件事,1. 告诉系统使用这个设备使用了WinUSB驱动,2. 告诉系统我这个设备接口的GUID是什么,应用程序就能通过GUID来操作设备。

既然WinUSB的驱动程序只做了这两件事,能不能让这两件事更加自动化一些,这样不作编写驱动程序也可以安装。答案是肯定的,微软的操作系统描述符就是处理这个事情的。

操作系统字符串描述符

Windows在枚举设备时会先读取设备描述符配置描述符,判断设备描述符中的bcdUSB字段,检查设备支持的USB版本号是否大于等于2.0,即bcdUSB大于等于0x0200。如果bcdUSB小于0x0200,则说明设备不支持,后续不会去请求 OS字符串描述符

如果大于等于,说明设备有可能支持OS字符串描述符,会在下一步去获取index为0xee的OS字符串描述符。OS字符串描述符的格式固定,总长度为18,字符串前面的内容为unicode编码的”MSFT100″,最一个字符的低8位是vendor code,由厂商自己定义。

153135221669

如果OS字符串描述符获取成功,会在注册表的这个位位置

[\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags]

建立一个以设备VID,PID,版本号为索引的项。 并在此项中建立一个osvc字段,字段值为01,<上报的vendor code>。

例如设备描述符中的idVendor为0x0483,idProduct为0x0001,bcdDevice为0x0100,OS字符串描述符中vendor code为0x17。注册表的[\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags]中会建立一个名为048300010100的项,里面会建立一个osvc字段,字段值为01 17。如下图所示
153156383957

如果注册表中已经有了这个字段,系统将不会再发起OS字符串描述符的获取请求。我们可以将上面的osvc中的值修改成00 00,表示不支持OS描述符。Windows获取到设备描述符后,发现对应的usbflags已经存在,并且不支持OS描述符。这个时候Windows不会再发送与OS描述符相关的请求。如果设备本来能够正常安装驱动,此时也会变得不正常。因此在开发USB设备的过程中,如果遇到驱动不正常,有时候并不是设备方面的问题,还有可能是因为用到了原来的一些信息,导致的错误。为了避免这种情况,一种办法是完全删除设备有关的旧内容。另一种办法是改设备的VID、PID或者设备版本号,让设备一直是最新的状态。

扩展兼容ID描述符

在获取到OS字符串描述,并且验证通过后,Windows会获取功能描述符。功能描述分类两种,一种是扩展兼容ID描述符(Extend Compat ID),wIndex值为4,一种是扩展属性(Extend Properties),wIndex为5。

Windows支持多种兼容描述符,如PTP、MTP、RNDIS,这里只说WinUSB的情况。兼容描述符由头和功能组成,头部内容固定为16字节,包含描述符总长度,版本号,接口数量等信息,如下

{
  ///////////////////////////////////////
  /// WCID descriptor
  ///////////////////////////////////////
  0x28, 0x00, 0x00, 0x00,                           /* dwLength */
  0x00, 0x01,                                       /* bcdVersion */
  0x04, 0x00,                                       /* wIndex */
  0x01,                                             /* bCount */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,         /* bReserved_7 */
}

上面的头说明描述符总长度为40字节,共有一个接口。

然后接下来是功能部分,功能部分可以有多个,在这里说明接口号和接口的兼容ID,如下:

///////////////////////////////////////
/// WCID function descriptor
///////////////////////////////////////
0x00,                                             /* bFirstInterfaceNumber */
0x01,                                             /* bReserved */
/* WINUSB */
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,         /* cCID_8 */
/*  */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   /* cSubCID_8 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,               /* bReserved_6 */

上面的功能部分表示0号接口的兼容ID为”WINUSB”,没有子兼容ID。

完整的兼容描述符如下,这个描述符由TeenyDT在线生成:

WEAK __ALIGN_BEGIN const uint8_t WINUSB_WCIDDescriptor [40] __ALIGN_END = {
  ///////////////////////////////////////
  /// WCID descriptor
  ///////////////////////////////////////
  0x28, 0x00, 0x00, 0x00,                           /* dwLength */
  0x00, 0x01,                                       /* bcdVersion */
  0x04, 0x00,                                       /* wIndex */
  0x01,                                             /* bCount */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,         /* bReserved_7 */

  ///////////////////////////////////////
  /// WCID function descriptor
  ///////////////////////////////////////
  0x00,                                             /* bFirstInterfaceNumber */
  0x01,                                             /* bReserved */
  /* WINUSB */
  'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,         /* cCID_8 */
  /*  */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   /* cSubCID_8 */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,               /* bReserved_6 */
};

获取兼容描述符的请求内容如下图所示:
153315700764

如上图所示,Windows会通过厂商请求来获取兼容ID,其中的bRequest即为OS字符串中上报的vendor code。上图中还可以看到,Windows先请求了头16个字节的数据,然后再根据实际长度请求了完整的数据。

扩展属性描述符

获取到兼容ID后,系统会根据兼容ID的情况请求属性描述符。对于WinUSB设备,前文中提到,需要做两件事,1. 告诉系统使用什么驱动,2. 告诉系统接口的Guid。通过前面的兼容ID描述符,系统已经知道了设备需要WinUSB驱动。通过扩展属性,告诉系统我们设备的GUID是什么。扩展属性描述符也是由头和属性部分组成的。头部告诉系统描述符的总长度以及属性的数量,然后后面跟着各个属性。属性描述符内容如下:

WEAK __ALIGN_BEGIN const uint8_t COMP_IF3_WCIDProperties [142] __ALIGN_END = {
  ///////////////////////////////////////
  /// WCID property descriptor
  ///////////////////////////////////////
  0x8e, 0x00, 0x00, 0x00,                           /* dwLength */
  0x00, 0x01,                                       /* bcdVersion */
  0x05, 0x00,                                       /* wIndex */
  0x01, 0x00,                                       /* wCount */

  ///////////////////////////////////////
  /// registry propter descriptor
  ///////////////////////////////////////
  0x84, 0x00, 0x00, 0x00,                           /* dwSize */
  0x01, 0x00, 0x00, 0x00,                           /* dwPropertyDataType */
  0x28, 0x00,                                       /* wPropertyNameLength */
  /* DeviceInterfaceGUID */
  'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00,       /* wcName_20 */
  'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00,       /* wcName_20 */
  't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00,       /* wcName_20 */
  'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00,       /* wcName_20 */
  'U', 0x00, 'I', 0x00, 'D', 0x00, 0x00, 0x00,      /* wcName_20 */
  0x4e, 0x00, 0x00, 0x00,                           /* dwPropertyDataLength */
  /* {1D4B2365-4749-48EA-B38A-7C6FDDDD7E26} */
  '{', 0x00, '1', 0x00, 'D', 0x00, '4', 0x00,       /* wcData_39 */
  'B', 0x00, '2', 0x00, '3', 0x00, '6', 0x00,       /* wcData_39 */
  '5', 0x00, '-', 0x00, '4', 0x00, '7', 0x00,       /* wcData_39 */
  '4', 0x00, '9', 0x00, '-', 0x00, '4', 0x00,       /* wcData_39 */
  '8', 0x00, 'E', 0x00, 'A', 0x00, '-', 0x00,       /* wcData_39 */
  'B', 0x00, '3', 0x00, '8', 0x00, 'A', 0x00,       /* wcData_39 */
  '-', 0x00, '7', 0x00, 'C', 0x00, '6', 0x00,       /* wcData_39 */
  'F', 0x00, 'D', 0x00, 'D', 0x00, 'D', 0x00,       /* wcData_39 */
  'D', 0x00, '7', 0x00, 'E', 0x00, '2', 0x00,       /* wcData_39 */
  '6', 0x00, '}', 0x00, 0x00, 0x00,                 /* wcData_39 */
};

属性主要由三个部分组成

  • 属性的数据类型,这个类型与注册表中的数据类型相同, 在”winnt.h”头文件中定义。其中比较常用的REG_SZ值为1, REG_MULTI_SZ值为7。
  • 属性的名称,以NULL结束的unicode字符串。
  • 属性的值,由属性的数据类型确定。

为了能让WinUSB设备被应用层软件访问,需要设置设备各个接口的DeviceInterfaceGUIDs,上述的描述符请求成功后,会在注册表的[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0483&PID_0001\TUSB123456\Device Parameters]项中建立一个名为DeviceInterfaceGUIDs的键,内容为我们上报的Guid。其中VID和PID为我们设备的VID和PID,TUSB123456为设备的序列号。根据这个DeviceInterfaceGUIDs中的GUID,就可以通过Windows的API发现并打开设备接口。
153356948720

仔细看上图会发现一个问题,我们上报的数据类型是1,应该是REG_SZ,而且属性名字是DeviceInterfaceGUID,在这里类型被替换成了REG_MULTI_SZ,名称也变成了DeviceInterfaceGUIDs。这个地方可能是兼容性的原因,Windows系统帮我们处理了。如果我们直接上报成类型为REG_MULTI_SZ的DeviceInterfaceGUIDs也是可以的。

获取设备扩展属性描述符的请求如下图所示
15341032631

和前面的兼容ID一样,Windows系统也是先获取了头部内容,得到实际长度后,再获取完整的描述符内容。

Windows系统对扩展属性描述和前面的兼容ID不太一样,如果设备的扩展属性已经有了,就不会再去获取。如果在开发过程中,不慎将获取扩展属性描述符的代码弄坏了,这个时候设备接在系统上还是一切正常,能够正常使用。但是一旦接入一个没有连接过这个设备的系统,就会出现无法使用的情况。因此在开发USB设备的过程中,一个设备能够正常工作也不一定说明设备没有问题,有可能是使用了原来的配置信息。前面我们还说过,一个设备不能使用,并不能说明设备有问题。

在调试描述符相关功能的过程中,为了消除旧配置的影响,最好是即时删除设备的配置信息,通过卸载设备可以去掉扩展属性的信息。而usbflags中的信息只能通过修改注册表来去掉。如果不知道怎么删除,那就每次都用不同的VID和PID,确保系统会把它当成新设备来处理。当描述符稳定了,就不用这么折腾了。

如果使用了WinUSB的兼容ID,但是没有有效的扩展属性,在任务管理器中会看到设备是正常的,但是没有办法在应用程序中访问。遇到任务管理器中设备正常,但是却无法访问的情况,可以查看一下此设备相关的注册表项中有没有DeviceInterfaceGUID字段,如果没有,说明设备没有正确安装,接口不能使用。

多接口的情况

对于多接口的情况,兼容ID描述符中会包含多个设备的兼容ID。而扩展属性描述符会通过多次请求,指定不同的接口号来获取。每个接口的GUID可以相同,也可以不同。完成的多接口描述符可以在TeenyDT的在线工具中看到,这里不再列出。在线工具地址为http://dt1.tusb.org,进入在线工具后,选择WinUSB,点击【==> TeenyUSB .c】按钮,就可以在生成的C语言格式描述符中看到兼容描述符和扩展描述符。

如何使用WinUSB设备

前面介绍了如何通过一些特殊描述符,来实现Windows上WinUSB驱动的自动安装。接下来介绍在Windows上如何使用WinUSB设备。

Windows为WinUSB设备提供了API,主要通过以下几个步骤访问设备。

  • 通过扩展描述符中的GUID查看接口的路径
  • 用接口的路径作为参数,调用CreateFile打开接口
  • 使用WinUsb_Initialize得到WinUSB句柄
  • 通过WinUsb_WritePipeWinUsb_ReadPipe对接口进行读写操作

下面代码完成了根据GUID查找设备路径,然后打开设备,向设备发送一些数据,再回读一些数据的功能。

// ensure data size is not multiple of endpoint Max Packet size or we will send a zero length packet
UCHAR test_data_out[256-1];

UCHAR test_data_in[1024];

int main()
{
  const TCHAR* path = get_first_interface_device_path(_T("{1D4B2365-4749-48EA-B38A-7C6FDDDD7E23}"));
  if (path == NULL) {
    printf("device not found\n");
    return 0;
  }
  _tprintf(_T("Got device interface %s\n"), path);


  HANDLE hDev = CreateFile(path,
    GENERIC_WRITE | GENERIC_READ,
    FILE_SHARE_WRITE | FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
    NULL);

  if (INVALID_HANDLE_VALUE == hDev) {

    printf("fail to open device, last error %d\n", GetLastError());
    return 0;
  }

  WINUSB_INTERFACE_HANDLE hWinusb;

  BOOL bResult = WinUsb_Initialize(hDev, &hWinusb);

  if (!bResult) {
    CloseHandle(hDev);
    printf("Fail to invoke WinUsb_Initialize, last error %d\n", GetLastError());
    return 0;
  }

  ULONG transferred;
  for (int i = 0; i<sizeof(test_data_out); i++) {
    test_data_out[i] = i;
  }
  bResult = WinUsb_WritePipe(hWinusb, 0x01, test_data_out, sizeof(test_data_out), &transferred, NULL);
  if (!bResult) {
    printf("Fail to invoke WinUsb_WritePipe, last error %d\n", GetLastError());
    goto error;
  }
  printf("Write %d bytes data\n", transferred);

  bResult = WinUsb_ReadPipe(hWinusb, 0x81, test_data_in, sizeof(test_data_in), &transferred, NULL);
  if (!bResult) {
    printf("Fail to invoke WinUsb_WritePipe, last error %d\n", GetLastError());
    goto error;
  }
  printf("Read back %d bytes data\n", transferred);

error:

  WinUsb_Free(hWinusb);
  CloseHandle(hDev);

  exit(0);
}

参考文档

原文转自:http://blog.xtoolbox.org/ms_os_10_descriptor_create_driverless_custom_usb_device/

HID人机交互QQ群:564808376    UAC音频QQ群:218581009    UVC相机QQ群:331552032    BOT&UASP大容量存储QQ群:258159197    STC-USB单片机QQ群:315457461    USB技术交流QQ群2:580684376    USB技术交流QQ群:952873936   

0 篇笔记 写笔记

USB WCID设备中特殊字符描述符
WCID全称”Windows Compatible ID,中文名为“Windows兼容ID”。 WCID设备是一种向Windows系统提供额外信息的USB设备,以便于自动安装驱动程序,并在某些情况下允许立即访问。USB设备驱动的匹配安装一般是以VID/PID进行驱动匹配的,但WCID设备却是根据C......
使用WinUSB读写USB设备
Windows为WinUSB设备提供了API,主要通过以下几个步骤访问设备。通过扩展描述符中的GUID查看接口的路径用接口的路径作为参数,调用CreateFile打开接口使用WinUsb_Initialize得到WinUSB句柄通过WinUsb_WritePipe和WinUsb_ReadPipe对......
WinUSB 简介
WinUSB是Windows从Vista操作系统版本以来,新增加的一个USB设备功能驱动程序。使用该驱动,可以将设备变成一个自定义通讯的设备,当然该驱动也可应用于自定义设备驱动程序。WinUSB的组成WinUSB包括两大部分,分别为内核部分和应用层部分:内核部分,主要是WinUSB.sys,该......
WinUSB的安装方法
使用工具zadig进行安装winusb以管理员方式运行zadig-2.4.exe,然后在菜单的选择中选择列出所有设备。这会软件会枚举系统中所有的USB设备,并显示在下列组合对话框中。我们选中我们需要安装winusb的设备,然后点击ReInstallDriver按钮进行安装。如本人选中一个USB扬声器......
USB Packet Viewer 连接方式及驱动安装
典型的设备连接方式监听 PC 上运行 USB Packet Viewer 协议解析软件,通过 Type-C 数据线与抓包设备相连,同时也向抓包设备提供电源。待测试的 USB 主设备通过 Type-C 数据线与抓包设备的 Host 接口相连待测试的 USB 从设备通过 USB-A 数据线与抓包设备......
Windows驱动usbstor硬件ID生成规则
如本人手中的金士顿U盘,在设备管理器中如下:我们查看设备的属性信息,可以看到,其使用的驱动是:USBPCapy.sysUSBSTOR.SYS从驱动来看,这是一个典型的BOT协议的U盘,更多详见:http://www.usbzh.com/article/detail-353.htmlUSBST......
WinUsb优缺点
WinUSB是Microsoft提供的通用USB 驱动程序,适用于从Windows Vista开始但也可用于 Windows XP 的操作系统。它针对的是一次只能由一个应用程序访问的简单设备。它使应用程序能够通过一个简单的软件库直接访问设备。该库提供对设备管道的访问。WinUSB 公开了一个客户端 ......
VS2019使用WinUSB模板创建WinUSB应用程序
当USB设备使用WinUSB驱动之后,可以使用WinUSB提供的应用层库winusb.lig来进行应用程序的开发,这样在应用层可以通过Winusb驱动程序与USB设备进行通讯。开发WinUSB应用程序时需要使用VS开发,不过在建一个示例WinUSB应用程序时,首先得安装WDK,安装完成之后会在创建......
使用WinUSB检测USB设备是什么设备速率?高速、低速、全速
WinUSB提供了检测USB设备的速率代码,详见:// Device Information types#define DEVICE_SPEED 0x01// Device Speeds#define LowSpeed 0x01#def......
WinUSB获取USB设备端点信息
使用WinUsb_QueryInterfaceSettings函数获取指定接口下的所有信息,然后解析端点信息并打印。struct PIPE_ID{ UCHAR PipeInId; UCHAR PipeOutId;};BOOL QueryDeviceEndpoints (......
WinUSB使用控制端点0发送USB标准请求
向默认端点发出控制请求来与设备通信。除了与接口关联的端点外,所有 USB 设备还有一个默认端点。 默认端点的主要用途是为主机提供可用来配置设备的信息。 不过,设备还可以将默认端点用于设备特定的用途。控制命令包含一个 8 字节设置数据包,其中包括指定特定请求的请求代码和可选的数据缓冲区。 请求代码......
WinUSB使用WinUsb_WritePipe给USB设备写数据
WinUSB 提供了以下用于发送写入请求请求的函数:WinUsb_WritePipe写数据WinUsb_WritePipe分配一个缓冲区并使用要写入到设备的数据进行填充。 如果应用程序未将缓冲区大小设置为管道的策略RAW_IO,则缓冲区大小没有限制。 如有必要,WinUSB 会将缓冲区划分为......
WinUSB释放设备句柄
完成对设备的全部所需调用后,需要释放设备的文件句柄和 WinUSB 接口句柄。 为此,请调用以下函数:CloseHandle 释放由 CreateFile 创建的句柄。WinUsb_Free释放设备的 WinUSB 接口句柄,该句柄由 WinUsb_Initialize。VOIDClose......
WinUSB使用WinUsb_ReadPipe从USB设备读数据
WinUSB 提供了以下用于接收读取请求请求的函数:WinUsb_ReadPipe调用 WinUsb_ReadPipe 从设备的批量终结点读取数据。 传递设备的 WinUSB 接口句柄、用于批量传入终结点的管道标识符,以及适当大小的空缓冲区。 当函数返回时,缓冲区会包含已从设备读取的数据。 已......
WinUSB与设备通讯的代码流程
int _tmain(int argc, _TCHAR* argv[]){ GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; //in the INF file BOOL bResult = TRUE; PIPE_ID ......
关注公众号
  • HID人机交互
  • Linux&USB
  • UAC音频
  • TYPE-C
  • USB规范
  • USB大容量存储
  • USB百科
  • USB周边
  • UVC摄像头
  • Windows系统USB
  • 音视频博客
  • 取消
    感谢您的支持,我会继续努力的!
    扫码支持
    扫码打赏,你说多少就多少

    打开支付宝扫一扫,即可进行扫码打赏哦

    您的支持,是我们前进的动力!