HID设备是什么? HID是英文“Human Interface Devices”的缩写,翻译为“人机界面设备”。这是指任何一种用来与计算机或其他电子设备交互的输入或输出设备。常见的HID设备包括鼠标、键盘、游戏手柄、数字笔、扫描仪等。 HID设备与计算机或其他电子设备的交互依赖于驱动程序,这些驱动程序为HID设备提供指令和信息。HID设备通常采用标准接口协议来与计算机或其他电子设备进行通信,这使得开发人员能够方便地编写驱动程序来支持不同的设备。 在远程控制的协议中,如果我们需要在控制端,使用键鼠、手柄、触摸板(手写笔)等外设控制云端电脑的话,必须使用HID的虚拟驱动,来实现: 1、识别操作端的外设输入设备信息
2、通过协议传输给受控端
3、受控端设备拦截识别输入设备信息,进行系统层面的操作 本文我们来探讨上述HID设备虚拟化的基本实现原理(关于HID设备的虚拟化,需要了解HID描述符相关基础知识,可以参见另外一篇文章:关于HID描述符)。 1. HID架构在Windows下面HID的基本架构如下: 
在应用层的一个完整的HID应用(HID Clients)包括有驱动、服务以及应用程序,他们需要与HIDClass.sys进行通信;通常来说每一次通信的对象都是一个指定的设备(例如键盘,鼠标等)。通常系统是通过hardware ID或者HID Collection来标识一个设备,设备之间的通信遵循如下规则: - 用户模式驱动或者应用程序通过HIDClass提供的HidD_xxx来获取HID Collection。
- 内核驱动或者用户驱动以及应用程序通过HID分析支持程序HidP_xxx,内核驱动程序使用HID 类驱动的IOCTL来处理HID报表。
| Mode | Drivers | Applications | | User Mode | HidD_Xxx | HidP_Xxx | | Kernel Mode | HidD_XxxIOCTL_HID_xxx | N/A |
HID Transport是具体的硬件设备,我们HID Transport向上提供硬件相关信息,HID Client从来不主动和HID Transport直接通信,都是通过HIDClass.sys来进行中转的,这样给我们HID Client和HID Transport的开发都带来了非常大的便利;这也是Windows MiniPort驱动实现的基本框架。 对于HID的设备的虚拟,我们主要是需要实现HID Transport。 2. HID接口函数Windows提供了如下的HID函数接口来或者和操作HID设备: 1、Device Discovery and Setup。 2、Data Movement。 3、Report Creation and Interpretation。 2.1 查找和设置设备(Device Discovery and Setup)| API | 描述 | | HidD_GetAttributes | 请求获得HID设备的厂商ID、产品ID和版本号 | | HidD_GetHidGuid | 请求获得HID设备的GUID | | HidD_GetIndexString | 请求获得由索引识别的字符串 | | HidD_GetManufactureString | 请求获得设备制造商字符串 | | HidD_GetPhysicalDescriptor | 请求获得设备实体字符串 | | HidD_GetPreparsedData | 请求获得与设备能力信息相关的缓冲区的代号 | | HidD_GetProductString | 请求获得产品字符串 | | HidD_GetSerialNumberString | 请求获得产品序列号字符串 | | HidD_GetNumInputBuffer | 获得驱动程序用于存储输入报表的环形缓冲区的大小,默认值是8 | | HidD_SetNumInputBuffer | 设置驱动程序用于存储输入报表的环形缓冲区的大小 | 2.2 数据传输转移(Data Movement)| API | 描述 | | HidD_GetInputReport | 从设备读取一个特征报表 | | HidD_SetFeature | 向设备传送一个特征报表 | | HidD_SetOutputReport | 向设备传送输出报表 | | WriteFile | 向设备传送输出报表 | | ReadFile | 从设备读取输入报表 | 2.3 反馈信息创建及说明(Report Creation and Interpretation)| API | 描述 | | HidP_GetButtonCaps | 请求获得HID报表中所有按钮的能力 | | HidP_GetButtons | 从设备读取包含每个按下的按钮的用法(Usage)的缓冲区的指针,该请求可以设定一个Usage Page | | HidP_GetButtonEx | 从设备读取包含每个按下的按钮的Usage和Usage Page的缓冲区的指针 | | HidP_GetCaps | 请求获得用于描述设备能力的结构的指针 | | HidP_GetLinkCollectionNotes | 请求获得描述在顶层集合中的连接集合(Link Collection)关系的结构的数组 | | HidP_GetSpecificButtonCaps | 请求获得报表中按钮的能力,该请求可以设定一个Usage Page、Usage或是Link Collection | | HidP_GetSpecificValueCaps | 请求获得报表中数值的能力,该请求可以设定一个Usage Page、Usage或是Link Collection | | HidP_GetValueCaps | 请求获得 HID 报表中所有数值的能力 | | HidP_MaxUsageListLength | 请求获得 HID 报表中可以回传的按钮的最大数目,该请求可以设定一个Usage Page | | HidP_UsageListDifference | 比较两个按钮列表,并且求出在一个列表中设定而在另一个列表中没有设定的按钮 | | HidP_GetScaledUsageValue | 从设备读取一个已经经过比例因子调整的有符号数值 | | HidP_GetUsageValue | 从设备读取一个指向数值的指针 | | HidP_GetUsageValueArray | 从设备读取包含多个数据项的Usage的数据 | | HidP_SetButtons | 向设备传送设置按钮的数据 | | HidP_SetScaledUsageValue | 将一个实际数值转换成设备使用的逻辑数值,并将其插入到报表中 | | HidP_SetUsageValue | 向设备传送数据 | | HidP_SetUsageValueArray | 向设备传送包含多个数据项的Usage的数据 | 3. 创建虚拟设备由于windows提供这些接口函数,所以我们可以据此创建虚拟驱动,在系统内识别和创建安装对应的HID设备,这样我们通过远程输入设备,就可以正常操控受控电脑了。 以下是创建虚拟设备的主要函数: 3.1 HidRegisterMinidriver对于一个HID的Miniport硬件驱动来说,都是通过HidRegisterMinidriver函数来完成注册的,这个函数是HIDClass.sys提供,也就是说HIDClass.sys提供了框架,给我们MiniPort驱动的开发。 对于 HidRegisterMinidriver 工作大致可以总结为如下: 1、IoAllocateDriverObjectExtension创建驱动的上下文HIDCLASS_DRIVER_EXTENSION。 2、保存MiniPort驱动的相关信息到HIDCLASS_DRIVER_EXTENSION结构中,如下: - RtlCopyMemory(hidDriverExtension->MajorFunction,
- minidriverObject->MajorFunction,sizeof( PDRIVER_DISPATCH ) * (IRP_MJ_MAXIMUM_FUNCTION + 1) );
- minidriverObject->DriverUnload = HidpDriverUnload;
- hidDriverExtension->AddDevice = driverExtension->AddDevice;
复制代码
3.2 IRP_MJ_INTERNAL_DEVICE_CONTROL对于HID的虚拟硬件设备,对上层实现的接口都是通过IRP_MJ_INTERNAL_DEVICE_CONTROL来提供的,HID需要实现的相关IOCTL如下: - //
- // Internal IOCTLs for the class/mini driver interface.
- //
- #define IOCTL_HID_GET_DEVICE_DESCRIPTOR HID_CTL_CODE(0)
- #define IOCTL_HID_GET_REPORT_DESCRIPTOR HID_CTL_CODE(1)
- #define IOCTL_HID_READ_REPORT HID_CTL_CODE(2)
- #define IOCTL_HID_WRITE_REPORT HID_CTL_CODE(3)
- #define IOCTL_HID_GET_STRING HID_CTL_CODE(4)
- #define IOCTL_HID_ACTIVATE_DEVICE HID_CTL_CODE(7)
- #define IOCTL_HID_DEACTIVATE_DEVICE HID_CTL_CODE(8)
- #define IOCTL_HID_GET_DEVICE_ATTRIBUTES HID_CTL_CODE(9)
- #define IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST HID_CTL_CODE(10)
复制代码
我们知道HID最重要的两个描述符是: 1、HID设备描述符。HID设备描述符通过IOCTL_HID_GET_DEVICE_DESCRIPTOR向上层提供。 2、HID报表描述符。HID报表描述符通过IOCTL_HID_GET_REPORT_DESCRIPTOR向上层提供。 其他操作包括IOCTL_HID_READ_REPORT实现用来读取报表描述符,IOCTL_HID_WRITE_REPORT用来写入报表描述符。 这里我们需要定义两个描述符,例如可以定义如下: - HID_REPORT_DESCRIPTOR DefaultReportDescriptor[] = {
- 0x06,0x00, 0xFF, // USAGE_PAGE (Vender Defined Usage Page)
- 0x09,0x01, // USAGE (Vendor Usage 0x01)
- 0xA1,0x01, // COLLECTION (Application)
- 0x85,CONTROL_FEATURE_REPORT_ID, // REPORT_ID (1)
- 0x09,0x01, // USAGE (Vendor Usage 0x01)
- 0x15,0x00, // LOGICAL_MINIMUM(0)
- 0x26,0xff, 0x00, // LOGICAL_MAXIMUM(255)
- 0x75,0x08, // REPORT_SIZE (0x08)
- 0x95,0x01, // REPORT_COUNT (0x01)
- 0xB1,0x00, // FEATURE (Data,Ary,Abs)
- 0x09,0x01, // USAGE (Vendor Usage 0x01)
- 0x75,0x08, // REPORT_SIZE (0x08)
- 0x95,INPUT_REPORT_BYTES, // REPORT_COUNT (0x01)
- 0x81,0x00, // INPUT (Data,Ary,Abs)
- 0xC0 // END_COLLECTION
- };
- HID_DESCRIPTOR DefaultHidDescriptor = {
- 0x09, // length of HID descriptor
- 0x21, // descriptor type == HID 0x21
- 0x0100, // hid spec release
- 0x00, // country code == Not Specified
- 0x01, // number of HID class descriptors
- { 0x22, // report descriptor type 0x22
- sizeof(DefaultReportDescriptor) } // total length of report descriptor
- };
复制代码
3.3 效果展示通过实现相关的HID硬件特性之后,我们可以虚拟化如下HID设备: 1、虚拟游戏手柄 2、虚拟键盘 3、虚拟鼠标 4、虚拟显示器 5、虚拟触摸屏 6、虚拟触摸板 7、虚拟手写笔 
并且我们可以直接操作虚拟化的驱动来模拟各种HID设备信息,如下我们利用虚拟HID键盘向系统开始菜单输入一个Hello: 
这张图即是通过虚拟HID进行输入Hello字符4、更多HID驱动Nanos目前已经实现了成熟的商业化产品中,多种HID设备的驱动程序,可以提供完整的驱动解决方案。 现在随着云桌面、嵌入式设备等串流技术发展,HID相关驱动技术也越来越丰富,欢迎大家一起进行交流
|