关于HID的描述符
1. 前言
HID(Human Interface Device,人机接口设备)是USB设备中常用的设备类型,是直接与人交互的USB设备(例如键盘、鼠标与游戏杆等),他是一种相对简单的USB设备(相对USB摄像头这些来说)。当我们插入USB键盘或者鼠标之后,Windows就能自动识别HID设备插入,加载相关驱动;例如我们可以在设备管理器中看到如下:
那么Windows是怎么知道插入的设备是U盘还是键盘鼠标呢?这是因为USB插入的时候,主机会请求相关的描述符,一般来说设备描述符和接口描述中就会存在一个Type的字段,表示插入设备的类型。
对于一个HID设备,如果主机的驱动程序要与HID设备通信,设备的固件必须符合下列需求:
- 设备的描述符必须识别该设备包含有HID接口。
- 除了默认控制管道外,固件必须另外支持一个中断输入管道。
- 固件必须包含一个报表描述符来定义要传送与接收的设备数据;所有的HID数据都必须使用定义过的报表格式来定义报表中数据的大小与内容。
因此,HID设备存在两个重要的描述符:
- HID设备描述符。
- HID报表描述符。
下面我们具体看一下这两个描述符的具体格式。
2. HID描述符
HID描述符在Windows中被定义为如下:
typedef struct _HID_DESCRIPTOR
{
UCHAR bLength;
UCHAR bDescriptorType;
USHORT bcdHID;
UCHAR bCountry;
UCHAR bNumDescriptors;
//
// An array of one OR MORE descriptors.
//
struct _HID_DESCRIPTOR_DESC_LIST {
UCHAR bReportType;
USHORT wReportLength;
} DescriptorList [1];
} HID_DESCRIPTOR, * PHID_DESCRIPTOR;
该结构中,每一个项的含义如下:
偏移 | 字段 | 字节数 | 说明 |
---|---|---|---|
0 | bLength | 1 | 描述符的长度(字节数目) |
1 | bDescriptorType | 1 | 描述符的类型 0x21 = HID |
2 | bcdHID | 2 | HID设备所遵循的HID版本号,为4位16进制的BCD码。1.0即0x0100,1.1即0x0101,2.0即0x0200。 |
4 | bCountry | 1 | 硬件设备所在国家的国家代码 |
5 | bNumDescriptors | 1 | 类别描述符数目(至少有一个报表描述符) |
6 | bReportType bDescriptorType | 1 | 类别描述符的类型 |
7 | wReportLength wDescriptorLength | 2 | 报表描述符的总长度 |
其中DescriptorList
可以有多个,表示有多个类别描述符。
关于bNumDescriptor
表示HID设备支持的下级描述符的数量,下一级描述符的类型有两种:
- 报告描述符。
- 物理描述符。
一般来说,我们都使用报表描述符,物理描述符极少用到。
3. HID报表描述符
USB主机一般是以中断的方式向HID设备发送或者索取数据;也就是说USB主机发送一个请求,设备要根据硬件操作,向主机提交自己的状态变化,例如鼠标,当主机给鼠标设备发送请求后,鼠标需要把自己当前位置信息发送给主机。
报告描述符就是描述我们说的传输事务中的原生数据,如果是鼠标的话,这份报告则为鼠标左移、鼠标右移、鼠标滑轮滚动、鼠标左键、鼠标右键的当前状态数据的集合。
报告描述符是由一个一个通用项(Item)组成,每一个通用项可以描述一个或者多个相同功能的数据(比如同时描述8个按钮),包括数据的用途和各种属性。
Item的结构分为两部分:
- 前缀:分为bTag,bType,bSize三部分组成。
- 数据部分:前缀bSize描述的长度(短项目格式)。
Item有两种类型:
- Short Item。
- Long Item。
一般情况下我们使用Short Item的格式,这种Item格式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCHgtRUU-1689645290674)(assets/images/c423d445717ea4f3f4b56a9e7e416fbcd3e3419d61d17689c7940e460dbf21e8.png)]
每一个Item使用bType来进行细分,称之为标签,主要有三种:
- 0(Bit为00)Main:分为Input、Output、Feature、Collection、End Collection。
- 1(Bit为01)Global:分为Usage Page、 Logical Minimum、Logical Maximum、Physical Minimum、Physical Maximum、Report Size、Report ID等等。
- 2(Bit为10)Local:分为Usage 、Usage Minimum、Usage Maximum、String 等等。
Main项目的功能如下:
项目标志(Tag) | 项目前缀(nn为数据长度) | 功能说明 |
---|---|---|
Input | 0x8? 1000 00 nn | 定义输入报表,主机利用该信息解析设备提供的数据。主机向控制端口发送Get_Report 实现输入。 |
Output | 0x9? 1001 00 nn | 创建输出报表,通过向设备发送Set_Report 实现输出。 |
Feature | 0xb? 1011 00 nn | 定义送往设备的设置信息。 |
Collection | 0xa1 1010 00 nn | Collection 开始一个集合。 |
End Collection | 0xc? 1100 00 nn | End Collection 结束集合。 |
定义2个以上数据(Input、 Output 和 Feature)的关系为集合,Collection 开始一个集合,之后的End Collection结束集合。Collection项目的数据部分说明Collection的类型。
Global项目的功能如下:
项目标志(Tag) | 项目前缀(nn为数据长度) | 功能说明 |
---|---|---|
Usage Page | 0x0? 0000 01 nn | 指定设备的功能 另外由于Usage项目有32位数据值,Usage Page项目用于为Usage项目在报表描述符中占居存储空间。用于存放后续Usage项目的高16位。 |
Logical Minimum | 0x1? 0001 01 nn | 定义变量或数组项目的逻辑最小值 |
Logical Maximum | 0x2? 0010 01 nn | 定义变量或数组项目的逻辑最大值 |
Physical Minimum | 0x3? 0011 01 nn | 定义变量或数组项目的物理最小值和Logical Minimum对应 |
Physical Maximum | 0x4? 0100 01 nn | 定义变量或数组项目的物理最大值和Logical Maximum对应 |
Unit Exponent | 0x5 0101 01 nn | 定义数值是基于 10 的指数 |
Unit | 0x6? 0110 01 nn | 单位 |
Report Size | 0x7? 0111 01 nn | 指定报表数据区域所包含的位数 |
Report ID | 0x8? 1000 01 nn | 报表 ID,该项目在报表中插入一个字节的报表ID |
Report Count | 0x9? 1001 01 nn | 报表中数据域的数目 |
Push | 0xa? 1010 01 nn | 将 Global 项目状态表送入堆栈 |
Pop | 0xb? 1011 01 nn | 从堆栈恢复 Global 项目状态表 |
Local项目的功能如下:
项目标志(Tag) | 项目前缀(nn为数据长度) | 功能说明 |
---|---|---|
Usage | 0x0? 0000 10 nn | 用法索引值,表示对项目或集合建议的用法,用于当一个项目描述多个控制,对每一个变量和数组元素都有建议的用法。 |
Usage Minimum | 0x1? 0001 10 nn | 定义阵列或位图中控制操作的第一个用法 |
Usage Maximum | 0x2? 0010 10 nn | 定义阵列或位图中控制操作的最后一个用法 |
Designator Index | 0x3? 0011 10 nn | 确定用于控制的实体,指向物理描述符中的目标。 |
Designator Minimum | 0x4? 0100 10 nn | 定义阵列或位图目标的起始索引值 |
Designator Maximum | 0x5 0101 10 nn | 定义阵列或位图目标的终止索引值 |
String Index | 0x7? 0111 10 nn | 确定字符串描述符中的索引值 |
String Minimum | 0x8? 1000 10 nn | 定义用于阵列或位图控制中字符串序列索引值的最小值和最大值 |
String Maximum | 0x9? 1001 10 nn | 定义用于阵列或位图控制中字符串序列索引值的最大值 |
Delimiter | 0xa? 1010 10 nn | 定义一组 Local 项目的开始和结束, 1=开始, 0=结束。 |
在这些项目中,Usage Page用来指定设备的功能,而Usage项目用来指定个别报表的功能。Usage Page项目相当于是HID的子集合,Usage相当于是Usage Page的子集合。
关于报表描述符他是非常复杂的,主要是报表描述符需要描述不同HID设备的信息(各个厂商设备都可以定义自己的格式);因此我们无需从头自己写一个报表描述符,可以看懂报表描述符和修改定制报表描述符就行了,这里我们可以看一下一个键盘的报表描述符的定义:
;=========================================
;HID Reports Descriptor 报表描述符
;=========================================
DB 0x05, 1 ; Usage Page (1: Generic Desktop)
DB 0x09, 6 ; Usage (6: Keyboard) 表示报表定义的是HID键盘
DB 0xA1, 1 ; Collection (1: Application) ====================集合开始
;
; 以下定义了键盘的修饰键输入报表,共有8个键,组成一个字节
; 用法见HID Usage Table中的第10节中的键盘用法定义
DB 0x05, 7 ; Usage page (7: Key Codes)
DB 0x19, 224 ; Usage Minimum (224)
DB 0x29, 231 ; Usage Maximum (231)
DB 0x15, 0 ; Logical Minimum (0)
DB 0x25, 1 ; Logical Maximum (1)
DB 0x75, 1 ; Report Size (1)
DB 0x95, 8 ; Report Count (8)
DB 0x81, 2 ; Input (Data,Variable,Absolute)
;;
以下定义了一个保留字节的输入报表
DB 0x95, 1 ; Report Count (1)
DB 0x75, 8 ; Report Size (8),
DB 0x81, 1 ; Input (Constant) = Reserved Byte
;;
以下定义了键盘的LED指示灯输出报表项目,共有5个指示灯
; 用法见HID Usage Table中的第11节中的LED用法定义
DB 0x95, 5 ; Report Count (5)
DB 0x75, 1 ; Report Size (1)
DB 0x05, 8 ; Usage Page (Page# for LEDs)
DB 0x19, 1 ; Usage Minimum (1)
DB 0x29, 5 ; Usage Maximum (5)
DB 0x91, 2 ; Output (Data, Variable, Absolute)
;;
以下定义了3个填充位,与前面的5个LED指示灯数据组成一个完整的字节
DB 0x95, 1 ; Report Count (1)
DB 0x75, 3 ; Report Size (3)
DB 0x91, 1 ; Output (Constant)
;;
以下定义了键盘的按键值输入报表项目,共6个字节,存放键编号(0~101)
; 用法见HID Usage Table中的第10节中的键盘用法定义
; 这样的设计可以允许一次输入6个按键的键值
DB 0x95, 6 ; Report Count (6)
DB 0x75, 8 ; Report Size (8)
DB 0x15, 0 ; Logical Minimum (0)
DB 0x25, 101 ; Logical Maximum (101)
DB 0x05, 7 ; Usage Page (7: Key Codes)
DB 0x19, 0 ; Usage Minimum (0)
DB 0x29, 101 ; Usage Maximum (101)
DB 0x81, 0 ; Input (Data, Array)
DB 0xC0 ; End_Collection ================================ 集合结束
4. 总结
HID设备通过HID设备描述符和HID报表描述符来描述HID设备的具体信息;对于如果进一步了解HID设备,掌握这两个描述符是必不可少的技能,尤其是我们后面讨论的键盘,鼠标,手写板等硬件设备的虚拟。