码农的自我修养 - USB键盘和鼠标的数据包格式

USB Human Interface Devices

USB人机接口设备(HID)是一种设备,正如其名称所暗示的那样,允许人类与计算机互动的接口。常见的例子包括USB鼠标、USB键盘、USB操纵杆和其他此类设备。USB HID设备使用的协议是在USB HID规范中定义的。一些芯片组支持将USB键盘和鼠标模拟成标准的PS/2设备,但许多芯片组不支持。因此,在一些可能根本没有PS/2端口的PC中,需要一个USB HID驱动程序。

准备知识

USB描述符是在USB设备插入后,主机将从USB设备读取其配置信息,这些配置信息就叫做USB Descriptor,是枚举USB设备成功所必须的。

在这些信息中,包含了Interface descriptor,叫做接口描述符,表明这个设备的接口相关信息。

接口描述符下,还有端口描述符,表示用来传数据的具体通道。比如Endpoint 0用来传输控制数据。

如下图:

码农的自我修养 - USB键盘和鼠标的数据包格式_第1张图片

 1. 协议

USB HID设备主要基于两个协议:report protocol和boot protocol。

这两种协议都是用report来传输数据,不过是用两种不同类型的report。

report是一种数据结构,从设备发送给主机,也可以从主机发送给设备。当设备向主机发送报告时,它通常包含状态变化信息,如按键、鼠标移动等。当主机向设备发送报告时,它通常包含配置设备的命令,例如设置键盘上的LED。这个协议当然取决于标准的USB框架。USB HID设备使用中断传输进行通信,因为它们并不总是传输数据,但当它们传输时,需要软件做出非常快的响应,同时传输的数据通常也很小。

Report protocol是基于 "items"的概念,其结构在报告描述符(USB描述符里包含的)中定义。

boot protocol要简单得多,它遵循鼠标和键盘的标准结构。为了简单起见,本文至少在目前只讨论boot protocol。

boot protocol就是指在系统启动时就支持的协议类型,比如电脑只要上电,在BIOS阶段就可以使用键盘鼠标。这种协议是基本的简单的HID协议。

检测HID设备

HID设备在其设备描述符中的类/子类值都是0,而在其接口描述符中的类/子类值是有效的。请记住,接口描述符不能被手动请求,必须与配置和端点描述符一起获得。接口描述符中标识HID设备的类值是3,接口描述符中的子类值可以是1,表示设备支持boot protocol.,也可以是0,表示设备只支持report protocol。接口描述符中的协议字段也决定了它是一个鼠标还是一个键盘。具体来说,1表示HID设备是一个键盘,而2表示HID设备是一个鼠标。

"SetProtocol "请求

假设一个USB HID设备支持boot protocol,如上节所述,它的类值为3,子类值为1,驱动软件可以选择要使用的协议。它使用 "SetProtocol "请求来告诉设备它是想使用report protocol还是boot protocol。为了简单起见,本文暂时只描述boot protocol。为了发送 "SetProtocol "请求,软件向设备的控制端点0发送了一个常规的SETUP事务。SETUP数据包的请求类型包含0x21,"SetProtocol "的请求代码是0x0B,SETUP数据包的值字段应该包含0来表示boot protocol,或者1来表示report protocol。索引和长度字段都必须是0,因为索引是未使用的,这个请求没有数据阶段。这条命令只在完全支持引导协议的设备上支持。使用这个命令后,所有从设备发送到主机的报告都是boot reports或 regular reports,这取决于软件要求的类型。

"GetReport "请求

软件可以使用控制端点和常规的SETUP数据包向USB设备请求报告。SETUP数据包的请求类型将包含0xA1,"GetReport "的请求代码是1,SETUP数据包的 "值 "字段将包含0x0100,以请求一个ID为0的输入数据包,而长度字段是主机希望接收的数据阶段的长度。SETUP数据包应该发送到设备端点0(控制端点)。 对于键盘,数据阶段通常是8个字节,而对于鼠标,数据阶段的前3个字节是标准格式,而其余部分可能被设备的特定功能所使用。推荐只是为了测试设备初始化是否成功完成,或者类似的情况,才用这种方式接收报告。"GetReport "请求不应该被用来轮询HID设备的变化,因为SETUP和STATUS阶段浪费了太多的时间。相反,软件应该使用中断传输轮询HID,使用中断IN端点。

中断端点 / Interrupt endpoint

一般建议HID使用中断传输向软件报告,软件通常应避免使用上面提到的 "GetReport "请求。驱动软件应该请求HID设备的配置描述符。一个HID设备必须至少支持一种配置。然后,软件应该扫描端点描述符,寻找表示 "中断IN "类型的描述符,这是一个使用中断传输将设备数据发送给主机的端点。软件应该保存该端点的4位ID,以及该端点的8位间隔。间隔值以毫秒(ms)为单位编码时间,在这个时间空间内,软件应该轮询一次报告数据包。例如,如果间隔值是8,软件应该每8毫秒向设备请求一份报告。如果软件过早地请求报告,例如,在6毫秒之后,设备可能会发送与之前相同的数据包,或者它可能不发送任何东西,而是返回NAK。如果软件在时间范围之后要求报告,例如9毫秒,那么设备将发送新的数据包。软件使用这种描述的方法不断轮询USB HID设备。这也是优化你的USB代码的好机会,因为轮询功能将每秒运行数百次。比如有的USB鼠标的间隔值是10毫秒,所以软件每秒钟轮询USB设备100次。

------------------------------------------------ 

 2. USB键盘

USB键盘使用报告与软件进行通信,就像其他HID设备一样。在接口描述符中,USB键盘的类别代码为3,协议值为1,可以被检测到。为了简单起见,在这里仅描述启动协议.

Report格式

这个报告必须由软件使用中断传输请求,每隔若干毫秒一次,间隔时间在USB键盘的中断IN描述符中定义。USB键盘报告的大小可以达到8个字节,尽管并不是所有这些字节都被使用,只用前三或四个字节实现一个适当的实现也是可以的。

Offset

Size

Description

0

Byte

Modifier keys status.

1

Byte

Reserved field.

2

Byte

Keypress #1.

3

Byte

Keypress #2.

4

Byte

Keypress #3.

5

Byte

Keypress #4.

6

Byte

Keypress #5.

7

Byte

Keypress #6.

Modifier keys status:

这个字节是一个位域,每个位对应一个特定的modifier key。当一个位被设置为1时,相应的modifier key正在被按下。与PS/2键盘不同,USB键盘没有modifier keys的 "scancodes"。这个字节的位结构是:

Bit

Bit Length

Description

0

1

Left Ctrl.

1

1

Left Shift.

2

1

Left Alt.

3

1

Left GUI (Windows/Super key.)

4

1

Right Ctrl.

5

1

Right Shift.

6

1

Right Alt.

7

1

Right GUI (Windows/Super key.)

当软件收到一个中断,例如,其中一个Shift modifier keys被设置为1时,软件应在scancode表中获取相应的shift modifier keys对应的scancode。

Reserved field:这个字节是由USB HID规范保留的,因此软件应该忽略它。

按键字段:一个键盘报告最多可以显示6个按键。所有这些值都是无符号的8位值(与PS/2扫描码不同,PS/2扫描码大多是7位),表示被按下的键。请参考USB编码到ASCII字符转换表。

按键机制

当按键被按下或释放时,USB键盘会发送中断,就像PS/2键盘一样。然而,与PS/2键盘不同,USB键盘没有 "make "和 "break"的scancodes概念。当用户按下一个键时,中断会在其中一个按键字段中出现一个扫描码值。当一个键被释放时,相应的按键字段在下一个数据包中被返回为零。为了更清楚地说明这一点,并说明为什么有一个以上的按键扫描码字段,让我们看一下下面的例子。假设用户按下了 "A "键,即0x04的scancode。返回的中断数据包会是这样的:

00 00 04 00 00 00 00 00

注意modifier keys是零,因为用户没有按任何modifier keys。保留字段也是零,正如USB HID规范所建议的。第一个按键字段包含0x04,对应于 "A "键。现在,让我们假设用户放开了 "A "键。发送的数据包是:

00 00 00 00 00 00 00 00

现在,让我们假设用户按了 "A "键,然后按了 "B "键(scancode 0x05)而没有放开 "A "键:

00 00 04 05 00 00 00 00

注意到一个中断数据包是如何能够将两个按键一起传输的。现在我们假设用户按下 "C "键(scancode 0x06),而没有放开 "A "或 "B "键:

00 00 04 05 06 00 00 00

现在,如果用户放开 "A "键,但继续按 "B "和 "C "键,会怎样?键盘将显示 "A "不再被按下,而 "B "和 "C "将向数据包的开头移动:

00 00 05 06 00 00 00 00

现在可能已经很明显了,USB键盘按照先按下哪个键的顺序返回扫描码。因此,如果第一个按键字段为零,就说明没有任何按键被按下发送扫描码。如果它不是零,软件可以检查接下来的字段,看看是否有另一个键也被按下。

Modifier keys 的概念可能是显而易见的,但为了完整起见,让我们假设用户同时按下了 "X "键(扫描码0x1B)和left shift键。 发送的中断数据包将包含:

02 00 1B 00 00 00 00 00

请注意,修改器字段的bit 1(值为0x02)被设置,以表明左移键正在被按下。

还有一个 "phantom condition",你可以把它看作是溢出。一个USB键盘数据包在一次传输中最多可以显示6个按键,但我们设想一下,有人掉了键盘,一次按了超过6个键。键盘将进入phantom condition状态,在这种情况下,所有报告的按键将是无效的scancode 0x01。然而,Modifier keys仍将被报告。想象一下,有8个键(或超过6个的任何随机数字)被按下,并且右移键也被按下。发送的数据包将看起来像:

20 00 01 01 01 01 01 01

请注意,modifier keys仍在被指示,但实际的代码都返回了phantom condition。除了phantom condition,还有其他特殊的代码。0x00表示没有编码,也没有按键被按下,0x01表示我们刚刚解释过的phantom condition,0x02表示键盘自检失败,0x03表示发生了一个未定义的错误。从0x04开始,扫描码是有效的,对应于 "真正的 "按键。如果发生phantom condition,设备驱动程序可以忽略它。

自动重复 / Auto-repeat

让USB键盘感到痛苦的一点是,在硬件中没有自动重复和自动重复延迟的机制,这必须完全在软件中实现,这与PS/2键盘不同。以自动重复延迟500毫秒和自动重复速度每秒10个字符为例,当软件意识到某个键被按下而一直没有被释放时,它就会在500毫秒后(或任何你喜欢的自动重复延迟)忽略除了这个按下超过500ms的其他按键,之后如果这个键还保持按下,驱动程序就会报告每1秒按10次键,或任何你想要的自动重复速度。这将使用户感到自然,就像你按住一个键一样;第一个键出现在屏幕上,计算机等待一个短暂的延迟,然后键就会不断出现。

LED灯

LED灯也是在软件中处理的,根据硬件,NumLock、CapsLock和ScrollLock是正常的按键,可以发送正常的scancode。当这些键被按下时,驱动程序负责操作LED灯。当按下CpasLock按键,发送给PC,然后PC上的驱动程序再向键盘发送设置Led灯的指令。所以有时判断PC是否死机,只需按下这几个和LED灯显示有关的按键,如果不能操作,则操作系统一般就是挂了,没法再通过驱动程序来控制LED灯。

为了设置LED灯,驱动程序使用标准的USB setup事务向设备发送一个SetReport请求,其中有一个字节的data stage。设置包的请求类型应该包含0x21,SetReport的请求代码是0x09。设置数据包的值域在低字节中包含报告ID,它应该是0。高字节包含报告类型,应该是0x02,表示一个输出报告,或者一个从软件发送到硬件的报告。

索引字段应包含USB键盘的接口号,这是在接口描述符中存在的数字,表明这个设备是一个USB键盘。数据阶段应该是1个字节,是一个位域。这个设置事务应该被传输到控制端点0,这在所有的硬件上都适用。其他硬件可能支持也可能不支持可选的中断OUT端点。如果硬件支持中断输出端点,你可以只将1字节的数据stage传输到中断输出端点,而不需要SETUP阶段和STATUS阶段的额外开销。如果硬件支持中断输出端点,你应该尽可能避免使用控制端点,因为中断输出端点速度更快,可以用中断传输来编程,而不是setup传输。1字节的数据stage(用于SETUP事务)或1字节的中断OUT传输的格式如下所示。当一个位被设置为1时,相应的LED被打开。

Bit

Bit Length

Description

0

1

Num Lock.

1

1

Caps Lock.

2

1

Scroll Lock.

3

1

Compose.

4

1

Kana.

5

3

Reserved, must be zero.

------------------------------------------------ 

3. USB鼠标

USB鼠标,就像其他HID设备一样,使用reports与软件进行通信,reports通过中断端点发送,或者可以通过 "GetReport "请求手动请求。USB鼠标在接口描述符中的协议值为2。

报告格式

该报告必须由主机使用中断传输来请求,每隔若干毫秒一次。间隔是在USB鼠标设备的中断IN描述符中定义的。USB鼠标报告中只有前三个字节被定义。其余的字节,如果存在,可用于设备特定的功能。软件可以在中断传输中只请求三个字节,即使实际数据包较大,也不会造成错误。下表定义了使用boot protocol操作的USB鼠标的报告格式。

Offset

Size

Description

0

Byte

Button status.

1

Byte

X movement.

2

Byte

Y movement.

Button status:这个字节是一个位域,其中最低的三个位是标准格式。其余的5位可用于设备的特定用途。

Bit

Bit Length

Description

0

1

When set to 1, indicates the left mouse button is being clicked.

1

1

When set to 1, indicates the right mouse button is being clicked.

2

1

When set to 1, indicates the middle mouse button is being clicked.

3

5

These bits are reserved for device-specific features.

X movement:

这是一个有符号的8位整数,代表X运动。第7位(值为0x80)决定了该值的符号。当这个值为负数时,鼠标被向左移动。当这个值为正数时,鼠标被向右移动。注意,与PS/2鼠标不同,USB鼠标的移动值是8位有符号整数,而不是9位整数。

Y movement:

这也是一个有符号的8位整数,代表Y移动。当这个值为负数时,鼠标被向上移动。当该值为正时,鼠标被向下移动(朝向用户)。

------------------------------------------------ 

附录:

USB规范为键盘位置规定了16位的按键代码(但实际上就是用一个字节,其他范围的保留或未定义),并为通常的美国布局确定了按键说明。下面的值是以十进制给出的。0-3是协议值,分别是NoEvent、ErrorRollOver、POSTFail、ErrorUndefined。224-231是modifier keys的值。

0

1

2

3

4

5

6

7

8

9

10

11

-

err

err

err

A

B

C

D

E

F

G

H

12

13

14

15

16

17

18

19

20

21

22

23

I

J

K

L

M

N

O

P

Q

R

S

T

24

25

26

27

28

29

30

31

32

33

34

35

U

V

W

X

Y

Z

1

2

3

4

5

6

36

37

38

39

40

41

42

43

44

45

46

47

7

8

9

0

Enter

Esc

BSp

Tab

Space

- / _

= / +

[ / {

48

49

50

51

52

53

54

55

56

57

58

59

] / }

\ / |

...

; / :

' / "

` / ~

, / <

. / >

/ / ?

Caps Lock

F1

F2

60

61

62

63

64

65

66

67

68

69

70

71

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

PrtScr

Scroll Lock

72

73

74

75

76

77

78

79

80

81

82

83

Pause

Insert

Home

PgUp

Delete

End

PgDn

Right

Left

Down

Up

Num Lock

84

85

86

87

88

89

90

91

92

93

94

95

KP /

KP *

KP -

KP +

KP Enter

KP 1 / End

KP 2 / Down

KP 3 / PgDn

KP 4 / Left

KP 5

KP 6 / Right

KP 7 / Home

96

97

98

99

100

101

102

103

104

105

106

107

KP 8 / Up

KP 9 / PgUp

KP 0 / Ins

KP . / Del

...

Applic

Power

KP =

F13

F14

F15

F16

108

109

110

111

112

113

114

115

116

117

118

119

F17

F18

F19

F20

F21

F22

F23

F24

Execute

Help

Menu

Select

120

121

122

123

124

125

126

127

128

129

130

131

Stop

Again

Undo

Cut

Copy

Paste

Find

Mute

Volume Up

Volume Down

Locking Caps Lock

Locking Num Lock

132

133

134

135

136

137

138

139

140

141

142

143

Locking Scroll Lock

KP ,

KP =

Internat

Internat

Internat

Internat

Internat

Internat

Internat

Internat

Internat

144

145

146

147

148

149

150

151

152

153

154

155

LANG

LANG

LANG

LANG

LANG

LANG

LANG

LANG

LANG

Alt Erase

SysRq

Cancel

156

157

158

159

160

161

162

163

164

165

166

167

Clear

Prior

Return

Separ

Out

Oper

Clear / Again

CrSel / Props

ExSel

224

225

226

227

228

229

230

231

LCtrl

LShift

LAlt

LGUI

RCtrl

RShift

RAlt

RGUI

参考:

USB Human Interface Devices - OSDev Wikiicon-default.png?t=LA92https://wiki.osdev.org/USB_Human_Interface_Devices

Keyboard scancodes: USBicon-default.png?t=LA92https://www.win.tue.nl/~aeb/linux/kbd/scancodes-14.html

 

你可能感兴趣的:(软件工程师,驱动开发)