本文讨论键盘和鼠标 HID 客户端驱动程序。 键盘和鼠标表示在 HID 使用情况表中标准化并在 Windows作系统中实现的第一组 HID 客户端。
键盘和鼠标 HID 客户端驱动程序以 HID 映射器驱动程序的形式实现。 HID 映射器驱动程序是内核模式筛选器驱动程序,它为非 HID 类驱动程序和 HID 类驱动程序之间的 I/O 请求提供双向接口。 映射器驱动程序将一个系统的I/O请求和数据协议映射到另一个系统。
Windows 为 HID 键盘和 HID 鼠标设备提供系统提供的 HID 映射器驱动程序。
体系结构和概述
下图演示了 USB 键盘、鼠标和触摸板设备的系统提供的驱动程序堆栈。
下图显示了以下组件:
- KBDHID.sys:用于键盘的 HID 客户端映射器驱动程序。 将 HID 用法转换为扫描代码,以与现有键盘类驱动程序进行交互。
- MOUHID.sys:用于鼠标/触摸板的 HID 客户端映射器驱动程序。 将 HID 用法转换为鼠标命令(X/Y、按钮、滚轮)以与现有键盘类驱动程序进行交互。
- KBDCLASS.sys: 键盘类驱动程序 以安全方式为系统上的所有键盘和键盘提供功能。
- MOUCLASS.sys: 鼠标类驱动程序 为系统上的所有鼠标和触摸板提供功能。 驱动程序支持绝对和相对指向设备。 MOUCLASS.sys 不是触摸屏的 Windows 驱动程序。
- HIDCLASS.sys: HID 类驱动程序。 HID 类驱动程序是 KBDHID.sys 和 MOUHID.sys HID 客户端与各种传输(例如 USB、蓝牙等)之间的粘附。
系统生成驱动程序堆栈,如下所示:
- 传输堆栈为每个附加的 HID 设备创建物理设备对象(PDO),并加载相应的 HID 传输驱动程序,后者又加载 HID 类驱动程序。
- HID 类驱动程序为每个键盘或鼠标 顶级集合(TLC)创建 PDO。 复杂的 HID 设备(具有多个 TLC)会被 HID 类驱动程序暴露为多个 PDO。 例如,具有集成鼠标的键盘可能具有一个标准键盘控件的集合,以及鼠标的不同集合。
- 键盘或鼠标 HID 客户端映射器驱动程序将加载到适当的功能设备对象(FDO) 上。
- HID 映射器驱动程序为键盘和鼠标创建 FDO,并加载类驱动程序。
重要说明
- 符合支持的 HID 用法和顶级集合的键盘和鼠标不需要供应商驱动程序。
- 供应商可以选择在 HID 堆栈中提供筛选器驱动程序,以更改/增强这些特定 TLC 的功能。
- 供应商应创建单独的特定于供应商的 TLC,以在其 HID 客户端和设备之间交换专有数据。 除非必要,否则请避免使用过滤驱动程序。
- 系统将打开所有键盘和鼠标集合供其独占使用。
- 系统阻止禁用/启用键盘。
- 系统支持水平/垂直滚轮的平滑滚动功能。
驾驶指导
Microsoft为编写驱动程序的独立硬件供应商(IHV)提供本指南:
允许驱动程序开发人员以筛选器驱动程序或新的 HID 客户端驱动程序的形式添加更多驱动程序。
筛选器驱动程序:驱动程序开发人员应确保其增值驱动程序是筛选器驱动程序,不会替换(或代替)输入堆栈中的现有 Windows HID 驱动程序。
- 在这些方案中允许筛选器驱动程序:
- 作为 kbdhid/mouhid 的上层过滤器
- 作为 kbdclass/mouclass 的上部筛选器
- 不建议将筛选器驱动程序用作 HIDCLASS 和 HID 传输微型驱动程序之间的过滤器
- 在这些方案中允许筛选器驱动程序:
函数驱动程序:或者供应商可以创建函数驱动程序(而不是筛选器驱动程序),但仅适用于供应商特定的 HID PDO(必要时使用用户模式服务)。
在这些方案中允许函数驱动程序:
- 仅在特定供应商的硬件上加载
传输驱动程序:Windows 团队不建议创建更复杂的 HID 传输微型驱动程序来编写和维护。 如果合作伙伴正在创建新的 HID 传输微型驱动程序,尤其是在 SoC 系统上,我们建议进行详细的体系结构评审,以了解推理并确保正确开发驱动程序。
驱动程序开发人员应使用驱动程序框架(KMDF 或 UMDF),而不依赖 WDM 来获取筛选器驱动程序。
驱动程序开发人员应减少其服务与驱动程序堆栈之间的内核用户转换数。
驱动程序开发人员应确保能够通过键盘和触摸板功能(最终用户(设备管理器)或电脑制造商调整)唤醒系统。 此外,在 SoC 系统上,这些设备必须能够在系统处于工作 S0 状态时从低功率状态唤醒自己。
驱动程序开发人员应确保其硬件得到高效的电源管理。
- 当设备处于空闲状态时,设备可以进入其最低电源状态。
- 当系统处于低功率状态(例如备用(S3)或连接待机时,设备处于最低电源状态。
键盘布局
键盘布局完全描述了Microsoft Windows 2000 及更高版本的键盘输入特征。 例如,键盘布局指定语言、键盘类型和版本、修饰符、扫描代码等。
有关键盘布局的信息,请参阅以下资源:
- Windows 驱动程序开发工具包(DDK)中的键盘头文件 kdb.h,记录了关于键盘布局的一般信息。
- 示例键盘 布局。
若要可视化特定键盘的布局,请参阅 Windows 键盘布局。
有关键盘布局的更多详细信息,请访问控制面板\时钟、语言和区域\语言。
鼠标上支持的按钮和滚轮
此列表标识 Windows 支持的鼠标功能:
- 按钮 1-5
- 垂直滚轮
- 水平滚轮
- 平滑滚轮支持(水平和垂直)
在 PS/2 鼠标上激活按钮 4-5 和滚轮
Windows 用于激活新的四个和五个按钮和滚轮模式的方法是用于激活 IntelliMouse 兼容鼠标中的第三个按钮和滚轮的方法的扩展:
通过首先将报表速率设置为每秒 200 次,然后改为每秒 100 次,再改为每秒 80 次,将鼠标设置为三键滚轮模式。 然后读取鼠标的ID。 完成此序列后,鼠标应报告 ID 为 3。
然后将鼠标设置为五个按钮滚轮模式,方法是将报表速率设置为每秒 200 个报表,然后再次设置为每秒 200 个报表,再设置为每秒 80 个报表。 然后,从鼠标读取 ID。 完成序列后,五按钮滚轮鼠标应报告 ID 为 4(而 IntelliMouse 兼容的三按钮滚轮鼠标仍报告 ID 为 3)。
此方法仅适用于 PS/2 老鼠,不适用于 HID 老鼠。 HID 鼠标必须在报表描述符中报告准确的使用情况。
标准 PS/2 兼容的鼠标数据包格式(两个按钮)
字节(Byte) | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 注释 |
---|---|---|---|---|---|---|---|---|---|
1 | Yover | Xover | Ysign | Xsign | 标记 | M | R | L | X/Y 溢出和信号、按钮 |
2 | X7 | X6 | X5 | X4 | X3 | X2 | X1 | X0 | X 数据字节 |
3 | Y7 | Y6 | Y5 | Y4 | Y3 | Y2 | Y1 | Y0 | Y 数据字节 |
注释
Windows 鼠标驱动程序不会检查溢出位。 如果存在溢出,则鼠标应发送最大有符号位移值。
标准 PS/2 兼容的鼠标数据包格式(三个按钮 + 垂直滚轮)
字节(Byte) | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 注释 |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | Ysign | Xsign | 1 | M | R | L | X/Y 符号和 R/L/M 按钮 |
2 | X7 | X6 | X5 | X4 | X3 | X2 | X1 | X0 | X 数据字节 |
3 | Y7 | Y6 | Y5 | Y4 | Y3 | Y2 | Y1 | Y0 | Y 数据字节 |
4 | Z7 | Z6 | Z5 | Z4 | Z3 | Z2 | Z1 | Z0 | Z/wheel 数据字节 |
标准 PS/2 兼容的鼠标数据包格式(五个按钮 + 垂直滚轮)
字节(Byte) | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 注释 |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | Ysign | Xsign | 1 | M | R | L | X/Y 符号和 R/L/M 按钮 |
2 | X7 | X6 | X5 | X4 | X3 | X2 | X1 | X0 | X 数据字节 |
3 | Y7 | Y6 | Y5 | Y4 | Y3 | Y2 | Y1 | Y0 | Y 数据字节 |
4 | 0 | 0 | B5 | B4 | Z3 | Z2 | Z1 | Z0 | Z/wheel 数据和按钮 4 和 5 |
重要
五按钮滚轮鼠标的 Z/wheel 数据减少到四位,而不是 IntelliMouse 兼容的三按钮滚轮模式中使用的八位。 这种减少之所以成为可能,是由于轮子在任意给定的中断期间通常无法生成超出范围 +7/-8 的值。 当鼠标处于五按钮滚轮模式时,Windows 鼠标驱动程序会对四个 Z/wheel 数据位进行符号扩展。 当鼠标以三按钮滚轮模式运行时,它们会对完整的 Z/滚轮数据字节进行符号扩展。
按钮 4 和 5 映射到 WM_APPCOMMAND 消息,对应于 App_Back 和 App_Forward。
不需要供应商驱动程序的设备
以下设备不需要供应商驱动程序:
- 符合 HID 标准的设备。
- 由系统提供的非 HIDClass 驱动程序运行的键盘、鼠标或游戏端口设备。
Kbfiltr 示例
Kbfiltr 与用于键盘设备的系统类驱动程序 Kbdclass 以及 PS/2 样式键盘的函数驱动程序 I8042prt 一起使用。 Kbfiltr 演示如何筛选 I/O 请求,以及如何添加回调例程以修改 Kbdclass 和 I8042prt 的操作。
有关 Kbfiltr 操作的详细信息,请参阅:
Kbfiltr IO 控制代码
Kbfiltr 使用以下 IOCTL。
IOCTL_INTERNAL_I8042_HOOK_KEYBOARD
IOCTL_INTERNAL_I8042_HOOK_KEYBOARD 请求:
- 将初始化回调例程添加到 I8042prt 键盘初始化例程。
- 将 ISR 回调例程添加到 I8042prt 键盘 ISR。
初始化和 ISR 回调是可选的,由 PS/2 样式键盘设备的高级筛选器驱动程序提供。
I8042prt 收到 IOCTL_INTERNAL_KEYBOARD_CONNECT 请求后,它会向键盘设备堆栈顶部发送同步 IOCTL_INTERNAL_I8042_HOOK_KEYBOARD 请求。
Kbfiltr 收到挂钩键盘请求后,Kbfiltr 将按以下方式筛选请求:
- 保存传递给 Kbfiltr 的上层信息,其中包括上层设备对象的上下文、指向初始化回调的指针以及指向 ISR 回调的指针。
- 用其自身的信息替换上层信息。
- 保存 I8042prt 的上下文以及 Kbfiltr ISR 回调可用的回调指针。
IOCTL_INTERNAL_KEYBOARD_CONNECT (内部键盘连接控制代码)
IOCTL_INTERNAL_KEYBOARD_CONNECT请求将 Kbdclass 服务连接到键盘设备。 Kbdclass 在打开键盘设备之前,将此请求发送到键盘设备堆栈。
Kbfiltr 收到键盘连接请求后,Kbfiltr 将按以下方式筛选连接请求:
- 保存 Kbdclass 的 CONNECT_DATA (Kbdclass) 结构的副本,该结构通过 Kbdclass 传递给筛选器驱动程序。
- 用自身的连接信息替代类驱动程序的连接信息。
- 将 IOCTL_INTERNAL_KEYBOARD_CONNECT 请求向下发送设备堆栈。
如果请求未成功,Kbfiltr 会以适当的错误状态完成请求。
Kbfiltr 为筛选器服务回调例程提供了一个模板,该例程可以补充 KeyboardClassServiceCallback(Kbdclass 类服务回调例程)的作。 筛选器服务回调可以筛选从设备输入缓冲区传输到类数据队列的输入数据。
内部键盘断开控制码 (IOCTL_INTERNAL_KEYBOARD_DISCONNECT)
IOCTL_INTERNAL_KEYBOARD_DISCONNECT请求已被完成,状态为STATUS_NOT_IMPLEMENTED。 即插即用管理器可以添加或删除即插即用键盘。
对于所有其他设备控制请求,Kbfiltr 会跳过当前的 IRP 堆栈,并在设备堆栈中发送请求,而无需进一步处理。
Kbfiltr 实现的回调例程
Kbfiltr 实现以下回调例程。
KbFilter_InitializationRoutine
请参阅 PI8042_KEYBOARD_INITIALIZATION_ROUTINE
如果键盘的 I8042prt 默认初始化已足够,则不需要 KbFilter_InitializationRoutine 。
初始化键盘时,I8042prt 调用KbFilter_InitializationRoutine 。 默认键盘初始化包括以下作:
- 重置键盘
- 设置类型化速率和延迟
- 设置发光二极管(LED)
/*
Parameters
DeviceObject [in]
Pointer to the device object that is the context for this callback.
SynchFuncContext [in]
Pointer to the context for the routines pointed to by ReadPort and Writeport.
ReadPort [in]
Pointer to the system-supplied PI8042_SYNCH_READ_PORT callback that reads from the port.
WritePort [in]
Pointer to the system-supplied PI8042_SYNCH_WRITE_PORT callback that writes to the port.
TurnTranslationOn [out]
Specifies, if TRUE, to turn translation on. Otherwise, translation is turned off.
Return value
KbFilter_InitializationRoutine returns an appropriate NTSTATUS code.
*/
NTSTATUS KbFilter_InitializationRoutine(
In PDEVICE_OBJECT DeviceObject,
In PVOID SynchFuncContext,
In PI8042_SYNCH_READ_PORT ReadPort,
In PI8042_SYNCH_WRITE_PORT WritePort,
Out PBOOLEAN TurnTranslationOn
);
KbFilter_IsrHook
请参阅 PI8042_KEYBOARD_ISR。 如果 I8042prt 的默认操作足够,则不需要此回调。
I8042prt 键盘 ISR 在验证中断并读取扫描代码后调用 KbFilter_IsrHook。
KbFilter_IsrHook 在 I8042prt 键盘的 IRQL 的内核模式下运行。
/*
Parameters
DeviceObject [in]
Pointer to the filter device object of the driver that supplies this callback.
CurrentInput [in]
Pointer to the input KEYBOARD_INPUT_DATA structure that is being constructed by the ISR.
CurrentOutput [in]
Pointer to an OUTPUT_PACKET structure that specifies the bytes that are being written to the hardware device.
StatusByte [in, out]
Specifies the status byte that is read from I/O port 60 when an interrupt occurs.
DataByte [in]
Specifies the data byte that is read from I/O port 64 when an interrupt occurs.
ContinueProcessing [out]
Specifies, if TRUE, to continue processing in the I8042prt keyboard ISR after this callback returns; otherwise, processing is not continued.
ScanState [in]
Pointer to a KEYBOARD_SCAN_STATE structure that specifies the keyboard scan state.
Return value
KbFilter_IsrHook returns TRUE if the interrupt service routine should continue; otherwise it returns FALSE.
*/
KbFilter_IsrHook KbFilter_IsrHook(
In PDEVICE_OBJECT DeviceObject,
In PKEYBOARD_INPUT_DATA CurrentInput,
In POUTPUT_PACKET CurrentOutput,
Inout UCHAR StatusByte,
In PUCHAR DataByte,
Out PBOOLEAN ContinueProcessing,
In PKEYBOARD_SCAN_STATE ScanState
);
KbFilter_ServiceCallback
请参阅 PSERVICE_CALLBACK_ROUTINE。
函数驱动程序的 ISR 调度完成例程调用 KbFilter_ServiceCallback,接着调用键盘类驱动程序对 PSERVICE_CALLBACK_ROUTINE 的实现。 供应商可以实现筛选器服务回调,以修改从设备的输入缓冲区传输到类数据队列的输入数据。 例如,回调可以删除、转换或插入数据。
/*
Parameters
DeviceObject [in]
Pointer to the class device object.
InputDataStart [in]
Pointer to the first keyboard input data packet in the input data buffer of the port device.
InputDataEnd [in]
Pointer to the keyboard input data packet that immediately follows the last data packet in the input data buffer of the port device.
InputDataConsumed [in, out]
Pointer to the number of keyboard input data packets that are transferred by the routine.
Return value
None
*/
VOID KbFilter_ServiceCallback(
In PDEVICE_OBJECT DeviceObject,
In PKEYBOARD_INPUT_DATA InputDataStart,
In PKEYBOARD_INPUT_DATA InputDataEnd,
Inout PULONG InputDataConsumed
);
Moufiltr 示例
Moufiltr 与鼠标设备的系统类驱动程序 Mouclass 协同工作。 它还适用于 I8042prt,它是 PS/2 样式鼠标的功能驱动程序。 这两个驱动程序都与 Windows 2000 及更高版本一起使用。 Moufiltr 演示如何筛选 I/O 请求,并添加用于修改 Mouclass 和 I8042prt 操作的回调例程。
有关 Moufiltr 操作的详细信息,请参阅以下资源:
Moufiltr IO 控制代码
Moufiltr 使用以下 IOCTL。
IOCTL_INTERNAL_I8042_HOOK_MOUSE
IOCTL_INTERNAL_I8042_HOOK_MOUSE 请求将一个 ISR 回调函数例程添加到 I8042prt 鼠标 ISR。 ISR 回调是可选的,由高级鼠标筛选器驱动程序提供。
I8042prt 在收到 IOCTL_INTERNAL_MOUSE_CONNECT 请求后发送此请求。 I8042prt 向鼠标设备堆栈顶部发送同步 IOCTL_INTERNAL_I8042_HOOK_MOUSE 请求。
在 Moufiltr 收到挂钩鼠标请求后,它会按以下方式筛选请求:
- 保存传递给 Moufiltr 的上层信息,其中包括上层设备对象的上下文和指向 ISR 回调的指针。
- 将上层信息替换为其自己的信息。
- 保存 I8042prt 的上下文以及 Moufiltr ISR 回调可使用的回调指针。
IOCTL_INTERNAL_MOUSE_CONNECT
IOCTL_INTERNAL_MOUSE_CONNECT请求将 Mouclass 服务连接到鼠标设备。
IOCTL_INTERNAL_MOUSE_DISCONNECT(鼠标断开)
Moufiltr 完成IOCTL_INTERNAL_MOUSE_DISCONNECT请求,错误状态为STATUS_NOT_IMPLEMENTED。
对于所有其他请求,Moufiltr 会跳过当前的 IRP 堆栈,并在不进一步处理的情况下将请求发送到设备堆栈。
Moufiltr 回调例程
MouFiltr 实现以下回调例程。
MouFilter_IsrHook
请参阅 PI8042_MOUSE_ISR。
/*
Parameters
DeviceObject
Pointer to the filter device object of the driver that supplies this callback.
CurrentInput
Pointer to the input MOUSE_INPUT_DATA structure being constructed by the ISR.
CurrentOutput
Pointer to the OUTPUT_PACKET structure that specifies the bytes being written to the hardware device.
StatusByte
Specifies a status byte that is read from I/O port 60 when the interrupt occurs.
DataByte
Specifies a data byte that is read from I/O port 64 when the interrupt occurs.
ContinueProcessing
Specifies, if TRUE, that the I8042prt mouse ISR continues processing after this callback returns. Otherwise, processing is not continued.
MouseState
Pointer to a MOUSE_STATE enumeration value, which identifies the state of mouse input.
ResetSubState
Pointer to MOUSE_RESET_SUBSTATE enumeration value, which identifies the mouse reset substate. See the Remarks section.
Return value
MouFilter_IsrHook returns TRUE if the interrupt service routine should continue; otherwise it returns FALSE.
*/
BOOLEAN MouFilter_IsrHook(
PDEVICE_OBJECT DeviceObject,
PMOUSE_INPUT_DATA CurrentInput,
POUTPUT_PACKET CurrentOutput,
UCHAR StatusByte,
PUCHAR DataByte,
PBOOLEAN ContinueProcessing,
PMOUSE_STATE MouseState,
PMOUSE_RESET_SUBSTATE ResetSubState
);
如果 I8042prt 的默认操作足够,则不需要MouFilter_IsrHook 回调。
I8042prt 鼠标 ISR 在验证中断后调用函数 MouFilter_IsrHook。
若要重置鼠标,I8042prt 会经历一系列操作子状态。 MOUSE_RESET_SUBSTATE枚举值标识每个子州。 有关如何 I8042prt 重置鼠标和相应的鼠标重置子状态的详细信息,请参阅 ntdd8042.h 中MOUSE_RESET_SUBSTATE的文档。
MouFilter_IsrHook 在 I8042prt 鼠标 ISR 的 IRQL 的内核模式下运行。
MouFilter_ServiceCallback
请参阅 PSERVICE_CALLBACK_ROUTINE
/*
Parameters
DeviceObject [in]
Pointer to the class device object.
InputDataStart [in]
Pointer to the first mouse input data packet in the input data buffer of the port device.
InputDataEnd [in]
Pointer to the mouse input data packet immediately following the last data packet in the port device's input data buffer.
InputDataConsumed [in, out]
Pointer to the number of mouse input data packets that are transferred by the routine.
Return value
None
*/
VOID MouFilter_ServiceCallback(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PMOUSE_INPUT_DATA InputDataStart,
_In_ PMOUSE_INPUT_DATA InputDataEnd,
_Inout_ PULONG InputDataConsumed
);
I8042prt 的 ISR DPC 调用 MouFilter_ServiceCallback,然后调用 MouseClassServiceCallback。 可以将筛选器服务回调配置为修改从设备的输入缓冲区传输到类数据队列的输入数据。 例如,回调可以删除、转换或插入数据。