对于HID设备的虚拟化,主要包括:
本文我们来探讨上述HID设备虚拟化的基本实现原理(关于HID设备的虚拟化,需要了解HID描述符相关基础知识,可以参见另外一篇文章:关于HID描述符)。
HID Clients包括有驱动,服务以及应用程序,他们都和HIDClass.sys进行通信;通常来说每一次通信的对象都是一个指定的设备(例如键盘,鼠标等)。通常通过hardware ID或者HID Collection来标识一个设备,通过通信遵循如下规则:
HidD_xxx
来获取HID Collection。HidP_xxx
,内核驱动程序使用HID 类驱动的IOCTL来处理HID报表。Mode | Drivers | Applications |
---|---|---|
User Mode | HidD_Xxx |
HidP_Xxx |
Kernel Mode | HidD_Xxx IOCTL_HID_xxx |
N/A |
HID Transport是具体的硬件设备,我们HID Transport向上提供硬件相关信息,HID Client从来不主动和HID Transport直接通信,都是通过HIDClass.sys来进行中转的,这样给我们HID Client和HID Transport的开发都带来了非常大的便利;这也是Windows MiniPort驱动实现的基本框架。
对于HID的设备的虚拟,我们主要是需要实现HID Transport。
Windows提供了如下的HID函数接口来或者和操作HID设备:
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 | 设置驱动程序用于存储输入报表的环形缓冲区的大小 |
API | 描述 |
---|---|
HidD_GetInputReport | 从设备读取一个特征报表 |
HidD_SetFeature | 向设备传送一个特征报表 |
HidD_SetOutputReport | 向设备传送输出报表 |
WriteFile | 向设备传送输出报表 |
ReadFile | 从设备读取输入报表 |
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的数据 |
对于一个HID的Miniport硬件驱动来说,都是通过HidRegisterMinidriver
函数来完成注册的,这个函数是HIDClass.sys提供,也就是说HIDClass.sys提供了框架,给我们MiniPort驱动的开发。
对于HidRegisterMinidriver
工作大致可以总结为如下:
IoAllocateDriverObjectExtension
创建驱动的上下文HIDCLASS_DRIVER_EXTENSION
。HIDCLASS_DRIVER_EXTENSION
结构中,如下:RtlCopyMemory(hidDriverExtension->MajorFunction,
minidriverObject->MajorFunction,sizeof( PDRIVER_DISPATCH ) * (IRP_MJ_MAXIMUM_FUNCTION + 1) );
minidriverObject->DriverUnload = HidpDriverUnload;
hidDriverExtension->AddDevice = driverExtension->AddDevice;
对于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最重要的两个描述符是:
IOCTL_HID_GET_DEVICE_DESCRIPTOR
向上层提供。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
};
通过实现相关的HID硬件特性之后,我们可以虚拟化如下HID设备: