4.2 Keyboard
[Home] [Top] [Previous] [Next]
4.2.1 Overview
键盘是计算机系统的重要输入设备,所有的IBM PC及其兼容机都有一个键盘。所以键盘驱动是一个面向IBM PC机OS的必不可少的部分。
当IBM从1981年开始,每次推出其新的PC机架构,同时也推出其新的键盘设计——最早的“IBM PC”,到稍后的“IBM XT”,所使用的键盘被称作"XT Keyboard",现在这种键盘已经完全过时,我们现在写键盘驱动程序时可以完全不用考虑它。随后1984年IBM推出了“IBM AT”,它所使用的键盘被称作“AT Keyboard”;1987年IBM推出的“IBM PS/2”使用的键盘被称作“PS/2 Keyboard”。“AT Keyboard”和“PS/2 Keyboard”大同小异,被称作IBM兼容键盘,所有的现代IBM PC/兼容机都支持他,它的接口相对简单,是本部分内容的重点。而当今最新的PC上都支持USB接口的键盘,但他的接口相对复杂的多,并且也不向后兼容,所以本部分内容不涉及它。
4.2.2 History
IBM从1981年发布它的第一款个人计算机“IBM PC”以来,它所使用的键盘也在不断的更新。如下表所示:
机型 | IBM PC/XT | IBM AT | IBM PS/2 |
发布年份 | 1981 | 1984 | 1987 |
按键数 | 81 | 83-101 | 83-101 |
串行协议 | 单向 | 双向 | 双向 |
Scan code set | 1 | 2 | 2(增加了可选的Scan code set 3) |
接口芯片 | 8255 | 8042 | 8042 |
主机到键盘的命令数 | 无 | 8 | 17 |
"PS/2 Keyboard"最初只是对“AT Keyboard”作了一些扩展,它完全兼容“AT Keyboard”。但对于数量众多的键盘生产商来说,他们所生产的键盘并非完全遵照"PS/2 Keyboard"或“AT Keyboard”的标准,而是只需要做到对它们兼容即可。比如,某些使用PS/2接口的键盘却只实现了7个主要的“主机到键盘的命令”,对于剩下了10个只是简单的回馈ACK。而某些使用AT接口的键盘却完全实现了“PS/2 Keyboard”的17个命令。所以,你所拥有的键盘即使是IBM兼容键盘,而未必会完全实现了标准所规定的所有功能。
所以,现在的IBM兼容键盘有如下特征:
- 任意数量的按键(通常是101到104);
- 双向串行协议;
- 仅仅保证支持Scan code set 2;
- 接口芯片可能不是8042,但保证都和8042兼容。
- 对所有的17个命令都会回复“ACK”,但未必会完全实现它们。
“XT Keyboard”使用的协议和“AT-PS/2 Keyboard”完全不同,所以它们之间完全不兼容。但在过渡时期,有一些键盘生产厂商生产的"AT-PS/2 Keyboard",可以通过设置跳线,使其兼容"XT Keyboard"。这只不过在一个键盘里实现了两种方式,并不意味着“XT Keyboard”和"AT-PS/2 Keyboard"是兼容的。
由于“XT Keyboard”已经完全过时,我们下面不会再介绍“XT Keyboard”相关的细节,我们的内容定位于“AT-PS/2 Keyboard”。
4.2.3 Architechure
在IBM AT和IBM PS/2键盘系统中,CPU并不直接和Keyboard进行通信,而是通过一个8042芯片或者其它与之兼容的芯片。增加这么一个中间层,就可以屏蔽掉不同键盘之间实现的差别,并可以增加新的特性。如下图所示:
CPU直接和8042芯片进行通信,以实现对整个键盘的控制;键盘从外界输入得到的数据也可以通过8042芯片通知给CPU,然后CPU可以通过8042芯片读取这些数据。另外,CPU也直接向8042芯片发送命令,以使用8042芯片自身所提供的功能。
键盘自身也有自己的芯片(Intel 8048及其兼容芯片),此芯片的功能主要是检索来自于Key Matrix的外界输入(击键(Press key)或释放键(Release Key))所产生的Scan code,并将这些Scan code存放于键盘自身的内部缓冲;还负责和外部系统(i8042)之间的通信,以及自身的控制(Self Test, Reset, etc)等等。
4.2.4 Mechanism
对于PC机的操作用户来说,与键盘的接口就是键盘上的按键。操作键盘的方式就是敲击这些按键。对于键盘系统而言,操作用户对键盘的敲击分为两种动作:Press key和Release key。这两个动作之间,还有一个时间段,被称为Press key delay。我们将这2个动作和1个时间段称为一个“击键过程”。
你可以设想一下这个动作——按下一个键,保持一段时间,再松开这个键——这就是你在大多数情况下快速击键动作的一个慢镜头,它明确的出现了2个动作和1个时间段。但无论你的击键动作有多快,都是上述2个动作和1个时间段的组合。
对于除了Pause键之外的所有键而言,键盘针对Press Key和Release Key两个动作会分别产生两个Scan code,被称作Make Code和Break Code。在这两个动作之间的时间段里,会按照一定的频率产生Repeat code。在大多数情况下,由于你的击键速度非常快,所以不会产生Repeat code;但在任何情况下,肯定会产生Make code和Break Code。
键盘都有一个Repeat code的“产生延迟”设置,这个“产生延迟”指的是两次产生“Repeat code”之间的时间间隔,比如,如果"产生延迟"被设置为0.25秒,则当一个键被保持Press状态时,键盘系统会每0.25秒产生一个针对此键的Repeat code。
有时候,你会同时按下多个键,对于键盘系统而言,针对这些键的Press key动作总有现有之分。但你有可能对这些按键按下之后会保持一段时间才放开。这时候,键盘总是对你最后发生Press key动作的键产生Repeat code。而对你之前按下而没有松开的键,在它的当前“击键过程”内不会再产生Repeat code,即使在它之后被按下的键都已经完全松开。
比如,你现在按下了"A"键,产生了一个Press key的动作,键盘系统会为之产生一个Make code;随后,你保持按着"A"键不放开,键盘系统将会按照一定的频率产生针对"A"键的Repeat code。这个时候,你由按下了"B"键,键盘随即停止产生"A"键的Repeat code,然后产生一个"B"键的Make code。从此以后,在"A"键的当前“击键过程”中,"A"的Repeat code再也不会产生,即使"B"在"A"被Release之前松开也是这样。而之后如果“B”被保持按着的话,则键盘系统会按照一定的频率产生"B"的Repeat code,直到"B"被松开,或又有一个键被Press为止。但无论"A"或"B"在任何时候被松开,都必然会产生一个Break code。
结论是:在键盘被打开的情况下,只要一个键发生了Press key的动作,就一定会产生一个Make code;只要一个键(除了"Pause/Break"键)发生了Rlease key的动作,就一定会产生一个Break code;无论它们在什么时候发生。而对于Repeat Code,则有两个条件,一是,到当前的时刻为止,最后被按下的键;二是,这个最后被按下的键,在被松开之前被按的时间超过键盘所设置的Repeat code“产生延迟”。
在键盘系统中,由两根线,Data line和Clock line,用来控制对Scan code的检索和传递。如果Data line和Clock line都处于高电平状态,则每次8048每次检索到一个Scan code,就会立即将其发送给8042芯片。如果Data line为高电平,而Clock line为低电平,则每次8048每次检索到一个Scan code,不会立即将其发送给8042芯片,而是先将其存放在键盘的内部缓冲中,等Clock line变成高电平后,再将缓冲中的Scan code发送给8042。如果Data line为低电平,则8048停止对Scan code的检索,转而等待接收来自于8042的命令,这种情况下,如果Clock line为高电平,8048则会将接到的命令的回复数据发送给8042,否则,则无法回复这些命令。所以在8042需要向8048发送命令时,必须保证Clock line为高电平状态。
如果8042芯片收到一个来自于8048芯片的Scan code或者命令回复字节,经过处理后(可能存在的解码操作),会将其放入8042的Output buffer中,8042芯片会首先将状态寄存器(Status Register)的OBF(Output Buffer Full)标志设置,随后将Output port的IBF(bit-4)设为1,表示将产生一个IRQ1,然后将Clock line置为低电平,以禁止8042进一步接收8048的数据;然后发送一个IRQ1给Intel 8059A可编程中断控制器,由它将中断提交给CPU,CPU收到此IRQ后,将调用此IRQ对应的ISR(中断服务程序,这就是我们键盘Driver的一个重要部分)。随后此ISR可以从8042的数据端口60H中将Output buffer数据读取出来,并进行进一步的处理。当Output buffer中的数据被读取出来之后,8042会将状态寄存器的OBF标志清0,然后将Clock line置为高电平,以允许进一步接收8048发送来的数据。
4.2.5 Scan Code Set
迄今为止,IBM PC键盘共有3套Scan Code Set,最早的IBM PC/XT使用Scan Code Set 1,在随后的系统中,默认的都是Scan Code Set 2,后来出现了Scan Code Set 3,但并非所有的键盘都支持。为了保证正确性,我们应该以Scan Code Set 2为开发对象。
Scan Code Set 1和Scan Code Set 2是不相同的,但你可以让8042芯片帮你将从8048芯片得到的属于Scan Code Set 2的Scan Code转换为Scan Code Set 1中对应的Scan Code,这样,你的驱动程序只需要都以一种方式——Scan Code set 1处理就行了。设置Scan Code 转换的方法为将8042 Command Byte的bit-6清0。如果你设置了Scan Code转换,则8042在得到一个来自于8048的Scan Code之后,会首先将其转换为Scan Code set 1中对应的Scan Code,然后再将其放入8042的Output buffer中。这样Keyboard Driver从中读出的时候,就已经是属于Scan Code Set 1的Scan Code了。
需要注意的是,Scan Code和ASCII码完全不相同,所以Keyboard Driver的一个重要任务是将Scan Code和ASCII之间建立一种映射关系,将从8042读到的Scan Code转换为ASCII码。这就提供了一个特性:你可以通过建立不同的映射表,将键映射成你所喜欢的方式。比如你完全可以将键“A”映射为字母“B”的ASCII码。事实上,你也可以其映射到扩展ASCII码上,以实现其它语言的输入,比如简体中文——假如你的键盘驱动中实现了GB2312的映射,则你敲击两次键盘则可以输入一个汉字。
4.2.5.1 Scan code set 1
在Scan Code Set 1中,对于绝大多数键而言,其Make Code,Break Code,以及Repeat Code都是单字节的。其规则为:如果Make Code为nn,则其Repeat code与Make Code相同也是nn,而其Break Code则是将nn与80h进行按位OR运算,也就是将Make Code的最高位bit-7设置为1。比如:键"A"的Make Code位1Eh,其Repeat Code也为1Eh,而其Break Code则为1Eh|80h=9Eh。
还有一些键的Scan Code是双字节的。其规则为:它们的第一个字节都是E0h,对于第2个字节,其规则与单字节Scan Code的规则一样。
对PrtSc/SysRq键而言,其make code = E02AE037,repeat code = E037, break code = E0B7E0AA。
Pause/Break键没有Repeat Code,也没有Break Code,只有Make Code。其Make Code很长,为E11D45E19DC5。
101-, 102-, and 104-key keyboards:
| | | | | | | | | | |
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | | | | | | | ||
| | | SCRN | E0,37 | E0,AA | | | | ||
| | | | | | | | | ||
| | | | E1,9D,C5 | |
ACPI Scan Codes:
Key Make Code Break Code Power E0, 5E E0, DE Sleep E0, 5F E0, DF Wake E0, 63 E0, E3
Windows Multimedia Scan Codes:
Key Make Code Break Code Next Track E0, 19 E0, 99 Previous Track E0, 10 E0, 90 Stop E0, 24 E0, A4 Play/Pause E0, 22 E0, A2 Mute E0, 20 E0, A0 Volume Up E0, 30 E0, B0 Volume Down E0, 2E E0, AE Media Select E0, 6D E0, ED E0, 6C E0, EC Calculator E0, 21 E0, A1 My Computer E0, 6B E0, EB WWW Search E0, 65 E0, E5 WWW Home E0, 32 E0, B2 WWW Back E0, 6A E0, EA WWW Forward E0, 69 E0, E9 WWW Stop E0, 68 E0, E8 WWW Refresh E0, 67 E0, E7 WWW Favorites E0, 66 E0, E6
4.2.5.2 Scan code set 2
在Scan Code Set 2中,对于绝大多数键而言,其Make Code,Repeat Code都是单字节的,而其Break Code为双字节的。其规则为:如果Make Code为nn,则其Repeat code与Make Code相同也是nn,而其Break Code的第一个字节为F0,而第二个字节与Make Code相同。比如:键"A"的Make Code位1Ch,其Repeat Code也为1Ch,而其Break Code则为F01Ch。
还有一些键的Make Code,Repeat Code是双字节的,其Break Code则是3字节的。其规则为:它们的第一个字节都是E0h,对于后两个字节,其规则与单字节Scan Code的规则一样。
对PrtSc/SysRq键而言,其Make code = E012E07C, repeat code = E07C, break code = E0F07CE0F012
Pause/Break键没有Repeat Code,也没有Break Code,只有Make Code。其Make Code很长,为E11477E1F014F077。
101-, 102-, and 104-key keyboards:
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | SCRN | E0,7C | 7C,E0, F0,12 | | | | |
| | | | | | | | | | |
| | | | | E1,F0,14, F0,77 | -NONE-
| | |
ACPI Scan Codes:
Key Make Code Break Code Power E0, 37 E0, F0, 37 Sleep E0, 3F E0, F0, 3F Wake E0, 5E E0, F0, 5E
Windows Multimedia Scan Codes:
Key Make Code Break Code Next Track E0, 4D E0, F0, 4D Previous Track E0, 15 E0, F0, 15 Stop E0, 3B E0, F0, 3B Play/Pause E0, 34 E0, F0, 34 Mute E0, 23 E0, F0, 23 Volume Up E0, 32 E0, F0, 32 Volume Down E0, 21 E0, F0, 21 Media Select E0, 50 E0, F0, 50 E0, 48 E0, F0, 48 Calculator E0, 2B E0, F0, 2B My Computer E0, 40 E0, F0, 40 WWW Search E0, 10 E0, F0, 10 WWW Home E0, 3A E0, F0, 3A WWW Back E0, 38 E0, F0, 38 WWW Forward E0, 30 E0, F0, 30 WWW Stop E0, 28 E0, F0, 28 WWW Refresh E0, 20 E0, F0, 20 WWW Favorites E0, 18 E0, F0, 18 4.2.5.3 Scan code set 3
Scan Code Set 3的Scan Code只有一种规则,与Scan Code Set 2的单字节Make Code的规则一样。这非常有利于Keyboard driver的实现。可惜的就是只有一部分键盘支持它。
KEY MAKE BREAK ----- KEY MAKE BREAK ----- KEY MAKE BREAK A 1C F0,1C 9 46 F0,46 [ 54 F0,54 B 32 F0,32 ` 0E F0,0E INSERT 67 F0,67 C 21 F0,21 - 4E F0,4E HOME 6E F0,6E D 23 F0,23 = 55 F0,55 PG UP 6F F0,6F E 24 F0,24 / 5C F0,5C DELETE 64 F0,64 F 2B F0,2B BKSP 66 F0,66 END 65 F0,65 G 34 F0,34 SPACE 29 F0,29 PG DN 6D F0,6D H 33 F0,33 TAB 0D F0,0D U ARROW 63 F0,63 I 43 F0,48 CAPS 14 F0,14 L ARROW 61 F0,61 J 3B F0,3B L SHFT 12 F0,12 D ARROW 60 F0,60 K 42 F0,42 L CTRL 11 F0,11 R ARROW 6A F0,6A L 4B F0,4B L WIN 8B F0,8B NUM 76 F0,76 M 3A F0,3A L ALT 19 F0,19 KP / 4A F0,4A N 31 F0,31 R SHFT 59 F0,59 KP * 7E F0,7E O 44 F0,44 R CTRL 58 F0,58 KP - 4E F0,4E P 4D F0,4D R WIN 8C F0,8C KP + 7C F0,7C Q 15 F0,15 R ALT 39 F0,39 KP EN 79 F0,79 R 2D F0,2D APPS 8D F0,8D KP . 71 F0,71 S 1B F0,1B ENTER 5A F0,5A KP 0 70 F0,70 T 2C F0,2C ESC 08 F0,08 KP 1 69 F0,69 U 3C F0,3C F1 07 F0,07 KP 2 72 F0,72 V 2A F0,2A F2 0F F0,0F KP 3 7A F0,7A W 1D F0,1D F3 17 F0,17 KP 4 6B F0,6B X 22 F0,22 F4 1F F0,1F KP 5 73 F0,73 Y 35 F0,35 F5 27 F0,27 KP 6 74 F0,74 Z 1A F0,1A F6 2F F0,2F KP 7 6C F0,6C 0 45 F0,45 F7 37 F0,37 KP 8 75 F0,75 1 16 F0,16 F8 3F F0,3F KP 9 7D F0,7D 2 1E F0,1E F9 47 F0,47 ] 5B F0,5B 3 26 F0,26 F10 4F F0,4F ; 4C F0,4C 4 25 F0,25 F11 56 F0,56 ' 52 F0,52 5 2E F0,2E F12 5E F0,5E , 41 F0,41 6 36 F0,36 PRNT
SCRN57 F0,57 . 49 F0,49 3D F0,3D SCROLL 5F F0,5F / 4A F0,4A 8 3E F0,3E PAUSE 62 F0,62
4.2.6 8042 Controller
4.2.4节所描述的过程就是如何把操作用户的外部输入转化为系统软件(也就是操作系统)可以获取的数据的过程。除了上述的外部输入的转化过程之外,操作系统还可以通过向8042芯片发送命令来分别控制8042芯片和8048芯片。
下图表现的就是8042芯片的内部结构,图中Processor不是指计算机的CPU,而是8042芯片自身的处理器。
8042芯片除了被用来控制键盘,作为键盘和CPU之间的桥梁之外,还可以被用来控制A20 Gate,以决定CPU是否可以访问以MB为单位的偶数内存;以及向系统发送Reset信号,让主机重新启动。另外,8042芯片还可以支持PS/2类型的鼠标,但这一点本部分不会进行讨论。
8042有4个寄存器:
- 1个8-bit长的Input buffer;Write-Only;
- 1个8-bit长的Output buffer; Read-Only;
- 1个8-bit长的Status Register;Read-Only;
- 1个8-bit长的Control Register;Read/Write。
其中Control Register在上图中没有表现,又被称作Command Byte。
另外,8042有2个端口:60h和64h。
| | |
0x60 | Read | Read Output Buffer |
0x60 | Write | Write Input Buffer(8042 Data&8048 Command) |
0x64 | Read | Read Status Register |
0x64 | Write | Write Input Buffer(8042 Command) |
上面提到的4个寄存器中,前3个寄存器都可以通过60h和64h直接访问,但第4个寄存器只能够通过向64h端口发送命令,然后通过60h端口存取。
Status Register中存放的是一些与缓冲状态,数据状态,及键盘状态有关的状态信息,是一个8-bit长的积存器。它对于OS来说是只读的,并且只能够通过64h端口读取,任何时候,只要读取64h端口,都会读到Status Register的内容。
bit | Meaning |
0 | output register (60h) has data for system |
1 | input register (60h/64h) has data for 8042 |
2 | system flag (set to 0 after power on reset) |
3 | data in input register is command (1) or data (0) |
4 | 1=keyboard enabled, 0=keyboard disabled (via switch) |
5 | 1=transmit timeout (data transmit not complete) |
6 | 1=receive timeout (data transmit not complete) |
7 | 1=even parity rec'd, 0=odd parity rec'd (should be odd) |
Status Register
Input buffer被用来向8042芯片发送命令与数据,以控制8042芯片和8048芯片。Input buffer可以通过60h端口和64h端口写入,其中通过64h端口写入的是用来控制8042芯片的命令,而通过60h写入的数据有两种:一种是通过64h端口发送的被用来控制8042芯片的命令所需要的进一步数据;另一种则是直接发给键盘用来控制8048芯片的命令。
Output buffer被存放可以通过60h端口读取的数据。这些数据分为2大类:一类是那些通过64h端口发送的,被用来控制8042芯片的命令的返回结果;另一类则是8048芯片所发过来的数据。后者的数据又分为Scan code,和对那些通过60h端口发送给8048的命令的回复结果。
8042自身有少量的RAM,以及一些ROM,这些内存与我们常说的内存(称为系统内存)没有任何关系,它们和系统内存不使用相同的地址空间。这些RAM和ROM是8042芯片处理器自身运算的需要。
除了上述元素之外,8042还有3个内部端口:Input port, Output port和Test Port。Output port有System Reset和A20 Gate两个与键盘无关的重要的控制位,其它的位都是向8048芯片输出,让8048芯片参考的控制位。程序员最好不要动这些除了System Reset和A20 Gate之外的控制位。IBM AT和IBM PS/2的这3个端口的格式有少许不同,主要因为在IBM PS/2上,8048同时支持PS/2鼠标。
Pin | Name | PS/2 Function | AT Function |
0 | P10 | Keyboard Data | Undefined |
1 | P11 | Mouse Data | Undefined |
2 | P12 | Undefined | Undefined |
3 | P13 | Undefined | Undefined |
4 | P14 | External RAM 1: Enable external RAM 0: Disable external RAM | External RAM 1: Enable external RAM 0: Disable external RAM |
5 | P15 | Manufacturing Setting 1: Setting enabled 0: Setting disabled | Manufacturing Setting 1: Setting enabled 0: Setting disabled |
6 | P16 | Display Type Switch 1: Color display 0: Monochrome | Display Type Switch 1: Color display 0: Monochrome |
7 | P17 | Keyboard Inhibit Switch 1: Keyboard enabled 0: Keyboard inhibited | Keyboard Inhibit Switch 1: Keyboard enabled 0: Keyboard inhibited |
Pin | Name | PS/2 Function | AT Function |
0 | P20 | System Reset 1: Normal 0: Reset computer | System Reset 1: Normal 0: Reset computer |
1 | P21 | A20 Gate | A20 Gate |
2 | P22 | Mouse Data: 1: Pull Data low 0: High-Z | Undefined |
3 | P23 | Mouse Clock: 1: Pull Clock low 0: High-Z | Undefined |
4 | P24 | Keyboard IBF interrupt: 1: Assert IRQ 1 0: De-assert IRQ 1 | Output Buffer Full |
5 | P25 | Mouse IBF interrupt: 1: Assert IRQ 12 0: De-assert IRQ 12 | Input Buffer Empty |
6 | P26 | Keyboard Clock 1: Pull Clock low 0: High-Z | Keyboard Clock 1: Pull Clock low 0: High-Z |
7 | P27 | Keyboard Data: 1: Pull Data low 0: High-Z | Keyboard Data: 1: Pull Data low 0: High-Z |
Pin | Name | Function |
0 | T0 | Keyboard Clock |
1 | T1 | Mouse Clock |
2 | -- | Undefined |
3 | -- | Undefined |
4 | -- | Undefined |
5 | -- | Undefined |
6 | -- | Undefined |
7 | -- | Undefined |
bit | Meaning |
0 | 1=enable output register full interrupt |
1 | should be 0 |
2 | 1=set status register system, 0=clear |
3 | 1=override keyboard inhibit, 0=allow inhibit |
4 | disable keyboard I/O by driving clock line low |
5 | disable auxiliary device, drives clock line low |
6 | IBM scancode translation 0=AT, 1=PC/XT |
7 | reserved, should be 0 |
Commnand Byte
4.2.7 Command
通过8042芯片,可以:
Ÿ 向8042芯片发布命令(通过64h),并通过60h读取命令的返回结果(如果有的话),或通过60h端口写入命令所需的数据(如果需要的话)。
Ÿ 读取Status Register的内容(通过64h);
Ÿ 向8048发布命令(通过60h);
Ÿ 读取来自于Keyboard的数据(通过60h)。这些数据包括Scan Code(由按键和释放键引起的),对8048发送的命令的确认字节(ACK)及回复数据。
再次强调一遍,Command(命令)分为发送给8042芯片的命令和发送给8048的命令。它们是不相同的,并且使用的端口也是不相同的(分别为64h和60h)。
- 64h端口(读操作)
对64h端口进行读操作,会读取Status Register的内容。
inb %0x64
执行这个指令之后,AL寄存器中存放的就是Status Register的内容。
- 64h端口(写操作)
向64h端口写入的字节,被认为是对8042芯片发布的命令(Command):
Ÿ 写入的字节将会被存放在Input Register中;
Ÿ 同时会引起Status Register的Bit-3自动被设置为1,表示现在放在Input Register中的数据是一个Command,而不是一个Data;
Ÿ 在向64h端口写某些命令之前必须确保键盘是被禁止的,因为这些被写入的命令的返回结果将会放到Output Register中,而键盘如果不被禁止,则也会将数据放入到Output Register中,会引起相互之间的数据覆盖;
Ÿ 在向64h端口写数据之前必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。
void wait_input_empty(void)
{
char __b;
do{
__b = inb(0x64);
}while(!(__b&0x02));
}
void disable_keyboard(void)
{
wait_input_empty();
outb(0x64, 0xAD);
}
- 60h端口(读操作)
对60h端口进行读操作,将会读取Output Register的内容。Output Register的内容可能是:
Ÿ 来自于8048的数据。这些数据包括Scan Code,对8048发送的命令的确认字节(ACK)及回复数据。
Ÿ 通过64h端口对8042发布的命令的返回结果。
在向60h端口读取数据之前必须确保Output Register中有数据(通过判断Status Register的Bit-0是否为1)。
void wait_output_full(void)
{
char __b;
do{
__b = inb(0x64);
}while(__b&0x01);
}
unsigned char read_output(void)
{
wait_output_full();
return inb(0x60);
}
- 60h端口(写操作)
向60h端口写入的字节,有两种可能:
1.如果之前通过64h端口向8042芯片发布的命令需要进一步的数据,则此时写入的字节就被认为是数据;
2.否则,此字节被认为是发送给8048的命令。
在向60h端口写数据之前,必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。
4.2.7.1 发给8042的命令
- 20h
准备读取8042芯片的Command Byte;其行为是将当前8042 Command Byte的内容放置于Output Register中,下一个从60H端口的读操作将会将其读取出来。
unsigned char read_command_byte(void)
{
wait_input_empty();
outb(0x64,0x20);
wait_output_full();
return inb(0x60);
}
- 60h
准备写入8042芯片的Command Byte;下一个通过60h写入的字节将会被放入Command Byte。
void write_command_byte(unsigned char command_byte)
{
wait_input_empty();
outb(0x64,0x60);
wait_input_empty();
outb(0x60,command_byte);
}
- A4h
测试一下键盘密码是否被设置;测试结果放置在Output Register,然后可以通过60h读取出来。测试结果可以有两种值:FAh=密码被设置;F1h=没有密码。
bool is_set_password(void)
{
wait_input_empty();
outb(0x64,0xA4);
wait_output_full();
return inb(0x60)==0xFA?true:false;
}
- A5h
设置键盘密码。其结果被按照顺序通过60h端口一个一个被放置在Input Register中。密码的最后是一个空字节(内容为0)。
void set_password(unsigned char* password)
{
char* p = password;
if(p == NULL)
return;
wait_input_empty();
outb(0x64,0xA5);
do{
wait_input_empty();
outb(0x60, *p);
}while(*p++ != 0);
}
- A6h
让密码生效。在发布这个命令之前,必须首先使用A5h命令设置密码。
void enable_password(void)
{
if(!is_set_password())
return;
wait_input_empty();
outb(0x64,0xA6);
}
- AAh
自检。诊断结果放置在Output Register中,可以通过60h读取。55h=OK。
bool is_test_ok(void)
{
wait_input_empty();
outb(0x64,0xAA);
wait_output_full();
return inb(0x60)==0x55?true:false;
}
- ADh
禁止键盘接口。Command Byte的bit-4被设置。当此命令被发布后,Keyboard将被禁止发送数据到Output Register。
void disable_keyboard(void)
{
wait_input_empty();
outb(0x64,0xAD);
}
- AEh
打开键盘接口。Command Byte的bit-4被清除。当此命令被发布后,Keyboard将被允许发送数据到Output Register。
void enable_keyboard(void)
{
wait_input_empty();
outb(0x64,0xAE);
}
- C0h
准备读取Input Port。Input Port的内容被放置于Output Register中,随后可以通过60h端口读取。
unsigned char read_input_port(void)
{
wait_input_empty();
outb(0x64,0xC0);
wait_output_full();
return inb(0x60);
}
- D0h
准备读取Outport端口。结果被放在Output Register中,随后通过60h端口读取出来。
unsigned char read_output_port(void)
{
wait_input_empty();
outb(0x64,0xD0);
wait_output_full();
return inb(0x60);
}
- D1h
准备写Output端口。随后通过60h端口写入的字节,会被放置在Output Port中。
void write_output_port(unsigned char __c)
{
wait_input_empty();
outb(0x64,0xD1);
wait_input_empty();
outb(0x60,__c);
}
- D2h
准备写数据到Output Register中。随后通过60h写入到Input Register的字节会被放入到Output Register中,此功能被用来模拟来自于Keyboard发送的数据。如果中断被允许,则会触发一个中断。
void put_data_to_output_register(unsigned char __data)
{
wait_input_empty();
outb(0x64,0xD2);
wait_input_empty();
outb(0x60,__c);
}
4.2.7.2 发给8048的命令
- EDh
设置LED。Keyboard收到此命令后,一个LED设置会话开始。Keyboard首先回复一个ACK(FAh),然后等待从60h端口写入的LED设置字节,如果等到一个,则再次回复一个ACK,然后根据此字节设置LED。然后接着等待。。。直到等到一个非LED设置字节(高位被设置),此时LED设置会话结束。
- EEh
诊断Echo。此命令纯粹为了检测Keyboard是否正常,如果正常,当Keyboard收到此命令后,将会回复一个EEh字节。
- F0h
选择Scan code set。Keyboard系统共可能有3个Scan code set。当Keyboard收到此命令后,将回复一个ACK,然后等待一个来自于60h端口的Scan code set代码。系统必须在此命令之后发送给Keyboard一个Scan code set代码。当Keyboard收到此代码后,将再次回复一个ACK,然后将Scan code set设置为收到的Scan code set代码所要求的。
- F2
读取Keyboard ID。由于8042芯片后不仅仅能够接Keyboard。此命令是为了读取8042后所接的设备ID。设备ID为2个字节,Keyboard ID为83ABh。当键盘收到此命令后,会首先回复一个ACK,然后,将2字节的Keyboard ID一个一个回复回去。
- F3h
设置Typematic Rate/Delay。当Keyboard收到此命令后,将回复一个ACK。然后等待来自于60h的设置字节。一旦收到,将回复一个ACK,然后将Keyboard Rate/Delay设置为相应的值。
- F4h
清理键盘的Output Buffer。一旦Keyboard收到此命令,将会将Output buffer清空,然后回复一个ACK。然后继续接受Keyboard的击键。
- F5h
设置默认状态(w/Disable)。一旦Keyboard收到此命令,将会将Keyboard完全初始化成默认状态。之前所有对它的设置都将失效——Output buffer被清空,Typematic Rate/Delay被设置成默认值。然后回复一个ACK,接着等待下一个命令。需要注意的是,这个命令被执行后,键盘的击键接受是禁止的。如果想让键盘接受击键输入,必须Enable Keyboard。
- F6h
设置默认状态。和F5命令唯一不同的是,当此命令被执行之后,键盘的击键接收是允许的。
- FEh
Resend。如果Keyboard收到此命令,则必须将刚才发送到8042 Output Register中的数据重新发送一遍。当系统检测到一个来自于Keyboard的错误之后,可以使用自命令让Keyboard重新发送刚才发送的字节。
- FFh
Reset Keyboard。如果Keyboard收到此命令,则首先回复一个ACK,然后启动自身的Reset程序,并进行自身基本正确性检测(BAT-Basic Assurance Test)。等这一切结束之后,将返回给系统一个单字节的结束码(AAh=Success, FCh=Failed),并将键盘的Scan code set设置为2。
4.2.6.3 8048到8042的数据
- 00h/FFh
当击键或释放键时检测到错误时,则在Output Bufer后放入此字节,如果Output Buffer已满,则会将Output Buffer的最后一个字节替代为此字节。使用Scan code set 1时使用00h,Scan code 2和Scan Code 3使用FFh。
- AAh
BAT完成代码。如果键盘检测成功,则会将此字节发送到8042 Output Register中。
- EEh
Echo响应。Keyboard使用EEh响应从60h发来的Echo请求。
- F0h
在Scan code set 2和Scan code set 3中,被用作Break Code的前缀。
- FAh
ACK。当Keyboard任何时候收到一个来自于60h端口的合法命令或合法数据之后,都回复一个FAh。
- FCh
BAT失败代码。如果键盘检测失败,则会将此字节发送到8042 Output Register中。
- FEh
Resend。当Keyboard任何时候收到一个来自于60h端口的非法命令或非法数据之后,或者数据的奇偶交验错误,都回复一个FEh,要求系统重新发送相关命令或数据。
- 83ABh
当键盘收到一个来自于60h的F2h命令之后,会依次回复83h,ABh。83AB是键盘的ID。
- Scan code
除了上述那些特殊字节以外,剩下的都是Scan code。