Intel 8042 PS/2键盘鼠标控制器详细介绍

本文主体转自http://shanzy.bokee.com/834368.html因为我需要对龙芯1A上的PS/2控制器移植驱动,参考该文件时,发现其对一些地方说的不够全面且没有对i8042的兼容芯片或控制器做一些说明,所以我又对其进行了一些补充说明,补充说明的一些地方描述可能不够准确,欢迎批评指正。

 

ps/2 键盘硬件概述

 

    对于驱动来说,和键盘相关的最重要的硬件是两个芯片。一个是 intel 8042 芯片,位于主板上,CPU 通过 IO 端口直接和这个芯片通信,获得按键的扫描码或者发送各种键盘命令。另一个是 intel 8048 芯片或者其兼容芯片,位于键盘中,这个芯片主要作用是从键盘的硬件中得到被按的键所产生的扫描码,与 i8042 通信,控制键盘本身。

 

    当键盘上有键被按下时,i8048 直接获得键盘硬件产生的扫描码。i8048 也负责键盘本身的控制,比如点亮 LED 指示灯,熄灭 LED 指示灯。i8048 通过 ps/2 口和 i8042 通信,把得到的扫描码传送给 i8042CPU 通过读写端口,可以直接把 i8042 中的数据读入到 CPU 的寄存器中,或者把 CPU 寄存器中的数据写入 i8042 中。ps/2 口一共有 个引脚,可以拔下 ps/2 插头看一下,这 个引脚分别为,时钟,数据,电源地,电源+5V,还有2个引脚没有被用到。由于只有一个引脚传输数据,所以 ps/2 口上的数据传输是串行的。

 

    下面几幅图是一个键盘的内部,可以看到用来产生扫描码的按键矩阵( Key Martix ),可以看到键盘中的芯片(这里不是i8048,是一个兼容的其他型号的芯片)。 

 

详细的图见:http://jiurl.nease.net/document/KbdDriver/JiurlKbd1.htm

 

 

i8042 键盘控制器

 

键盘驱动直接读写 i8042 芯片,通过 i8042 间接的向键盘中的 i8048 发命令。所以对于驱动来说,直接发生联系的只有 i8042 ,因此我们只介绍 i8042 ,不介绍 i8048

PS:因为最早的时候没有鼠标,所以当时i8042只是一个键盘控制器,后来才又添加了对鼠标的控制,后来i8042寄存器各位的定义稍有变化,且后来出现的一些PS/2键盘鼠标控制器一般也都采用了这样的兼容模式,下边会详细说明。)

 

    象 i8042i8048 这样的芯片,本身就是一个小的处理器,它的内部有自己的处理器,有自己的 Ram,有自己的寄存器,等等。

 

    i8042 有 个 8 bits 的寄存器,他们是 Status Register(状态寄存器),Output Buffer(输出缓冲器),Input Buffer(输入缓冲器),Control Register(控制寄存器)。使用两个 IO 端口,60h 和 64h(端口是X86 CPU上独有的一种说法,其他的嵌入式CPU上没有端口这一说,都是实现成了内存读写,在其他的一些非Intel 公司实现的i8042兼容的PS/2控制器上,这两个端口号被实现成了两个地址,用IO内存读写即可实现与读写0x60,0x64端口相同的功能)

 

 

Status Register(状态寄存器)

 

状态寄存器是一个8位只读寄存器,任何时刻均可被cpu读取。其各位定义如下

 

Bit7: PARITY-EVEN(P_E): 从键盘获得的数据奇偶校验错误

Bit6: RCV-TMOUT(R_T): 接收超时,置1

PS:Bit6: 该位兼容芯片改为 无超时错误, 出现超时错误)

Bit5: TRANS_TMOUT(T_T): 发送超时,置1

PS: Bit5:该位兼容芯片改为 鼠标输出缓冲区空, 鼠标缓冲区满, linux内核自带的i8042控制器驱动采用该兼容模式的定义编写)

Bit4: KYBD_INH(K_I): 1,键盘没有被禁止。为0,键盘被禁止。

PS: Bit4: 该位兼容芯片定义可能为 禁止与设备通信,为 使能与设备通信,一般不影响程序的编写)

Bit3: CMD_DATA(C_D): 1,输入缓冲器中的内容为命令,为0,输入缓冲器中的内容为数据。

Bit2: SYS_FLAG(S_F): 系统标志,加电启动置0,自检通过后置1

Bit1: INPUT_BUF_FULL(I_B_F): 输入缓冲器满置1i8042 取走后置0

Bit0: OUT_BUF_FULL(O_B_F): 输出缓冲器满置1CPU读取后置0

PS: Bit0: 如果使能中断的话,当数据来时会触发中断, CPU读取后把该位置0,同时i8042自动清除中断)

 

 

Output Buffer(输出缓冲器)

 

输出缓冲器是一个8位只读寄存器。驱动从这个寄存器中读取数据。这些数据包括,扫描码,发往 i8042 命令的响应,间接的发往 i8048 命令的响应。

 

 

Input Buffer(输入缓冲器)

 

输入缓冲器是一个8位只写寄存器。缓冲驱动发来的内容。这些内容包括,发往 i8042 的命令,通过 i8042 间接发往 i8048 的命令,以及作为命令参数的数据。

 

 

Control Register(控制寄存器)

 

也被称作 Controller Command Byte (控制器命令字节)。其各位定义如下

PS:下文所述的Command Byte 即指该控制寄存器)

Bit7: 保留,应该为0

Bit6: 将第二套扫描码翻译为第一套

Bit5: 1,禁止鼠标

Bit4: 1,禁止键盘

Bit3: 1,忽略状态寄存器中的 Bit4

PS:Bit3 该位,i8042兼容的芯片可能保留,linux内核自带的i8042芯片驱动没有涉及该位)

Bit2: 设置状态寄存器中的 Bit2

Bit1: 1enable 鼠标中断

BitO: 1enable 键盘中断

 

2个端口 0x60,0x64

 

驱动中把 0x60 叫数据端口

驱动中把 0x64 叫命令端口

PS: 在兼容芯片或控制器中这两个端口可能会用两个内存地址来表示,用IO内存的读写方式即可读写)

1.5 命令

 

驱动可以直接给 i8042 发命令,可以通过 i8042 间接给 i8048 发命令。命令这部分内容直接来自 参考资料 [1] >

 

(PS: 通过访问PS2控制器的接口寄存器来实现命令控制,不外乎是三类操作: 

发命令给键盘,发命令给鼠标,发命令给控制器自身。 

在软件所统当怎样实现这三类功能呢?下面给出实现流程的描述 

1.  发送命令给键盘。 

通过直接将一个Byte写到输入寄存器,就可以实现向键盘发送读定命令

的功能。 

2. 发送命令给鼠标。 

发命令给鼠标要特殊一些,首先要向命令寄存器写入一个控制命令

(D4H),接着再向输入寄存器写入一个命令,这个命令就会被发送给鼠标。 

3. 发送命令给控制器自身。 

直接向0x64端口(如果i8042兼容的控制器没有实现成端口的话向其提供的与0x64端口对应的地址)写入命令即可。如果该命令需要参数的话,再接着向

输入寄存器所写入参数,如果该命令对应有返回值的话,返回值会放在

输出寄存器所。)

 

1.5.1 发给i8042的命令

 

驱动对键盘控制器发送命令是通过写端口64h实现的,共有12条命令,分别为

 

20h 

准备读取8042芯片的Command Byte;其行为是将当前8042 Command Byte的内容放置于Output Register中,下一个从60H端口的读操作将会将其读取出来。

 

60h 

准备写入8042芯片的Command Byte;下一个通过60h写入的字节将会被放入Command Byte

(PS:下边与密码有关的命令一般不用,i8042兼容控制器可能会没有实现)

A4h 

测试一下键盘密码是否被设置;测试结果放置在Output Register,然后可以通过60h读取出来。测试结果可以有两种值:FAh=密码被设置;F1h=没有密码。

 

A5h 

设置键盘密码。其结果被按照顺序通过60h端口一个一个被放置在Input Register中。密码的最后是一个空字节(内容为0)。

 

A6h

让密码生效。在发布这个命令之前,必须首先使用A5h命令设置密码。

 

AAh

自检。诊断结果放置在Output Register中,可以通过60h读取。55h=OK

 

ADh

禁止键盘接口。Command Bytebit-4被设置。当此命令被发布后,Keyboard将被禁止发送数据到Output Register

 

AEh

打开键盘接口。Command Bytebit-4被清除。当此命令被发布后,Keyboard将被允许发送数据到Output Register

 

C0h

准备读取Input PortInput Port的内容被放置于Output Register中,随后可以通过60h端口读取。

 

D0h

准备读取Outport端口。结果被放在Output Register中,随后通过60h端口读取出来。

 

D1h

准备写Output端口。随后通过60h端口写入的字节,会被放置在Output Port中。

 

 

D2h

准备写数据到Output Register中。随后通过60h写入到Input Register的字节会被放入到Output Register中,此功能被用来模拟来自于Keyboard发送的数据。如果中断被允许,则会触发一个中断。

(PS:以下是兼容控制器的一些添加的命令:

0xD3:   写鼠标缓冲区命令。把紧随该命令的参数写到输出缓冲区就

像是从鼠标接收到的一样。 

0xD4: 写鼠标设备命令。把紧随该命令的参数发给鼠标。)

 

 

1.5.2 发给8048(即发送给键盘)的命令

 

共有10条命令,分别为

 

EDh

设置LEDKeyboard收到此命令后,一个LED设置会话开始。Keyboard首先回复一个ACKFAh),然后等待从60h端口写入的LED设置字节,如果等到一个,则再次回复一个ACK,然后根据此字节设置LED。然后接着等待。。。直到等到一个非LED设置字节(高位被设置),此时LED设置会话结束。

 

EEh

诊断Echo。此命令纯粹为了检测Keyboard是否正常,如果正常,当Keyboard收到此命令后,将会回复一个EEh字节。

 

F0h

选择Scan code setKeyboard系统共可能有3Scan code set。当Keyboard收到此命令后,将回复一个ACK,然后等待一个来自于60h端口的Scan code set代码。系统必须在此命令之后发送给Keyboard一个Scan code set代码。当Keyboard收到此代码后,将再次回复一个ACK,然后将Scan code set设置为收到的Scan code set代码所要求的。

 

F2h

读取Keyboard ID。由于8042芯片后不仅仅能够接Keyboard。此命令是为了读取8042后所接的设备ID。设备ID2个字节,Keyboard ID83ABhPS:现在的一些PS/2键盘可能回复的ID不止两个字节,Intel做的带PS/2键盘鼠标控制器的芯片如83627等在这里可能直接就屏蔽了无用的不符合兼容性的数据,对上次软件屏蔽了下层的差异,但如果你用的一些兼容I8042PS/2控制器没这个功能的话就需要你在linux内核里自己修改了,一般在读取ID这一步增加判断是否是键盘,如果是,就把ID写成上边说的Keyboard ID 就可以了,如果是鼠标,则继续读取ID即可。毕竟现在所用的键盘一般都是标准键盘,否则的话,如果驱动收到的ID不正常的话,驱动可能会报错,退出对当前键盘的控制)当键盘收到此命令后,会首先回复一个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

 

PS:  发送到鼠标的命令列表 

0xFF(Reset):复位鼠标命令。 

0xF6(Set Defaults):设置鼠标的默认工作模式。 

0xF5(Disable Data Reporting):禁止鼠标的数据报告功能并复位它的

位移算数器。 

0xF4(Enable Data Reporting): 使能鼠标的数据报告功能并复位它的

位移算数器这条命令只对Stream 模式下的数据报告科效。 

0xF3(Set Sample Rate): 设置鼠标采样率。鼠标用0xFA 回统,然后从

主机指入一个或更多字节作为新的采样速率。在收到采样速率后鼠标再次用

统答0xFA 回统并复位它的位移算数器。有效的采样速率是10, 20, 40,60, 80, 

100200采样点/秒。 

0xF2(Get Device ID):  指取鼠标的设备ID。 

0xF0(Set Remote Mode): 设置鼠标进入Remote模式。 

0xE7设置缩放比例

0xE8 设置分辨率

0xEE(Set Wrap Mode): 设置鼠标进入Wrap模式。 

0xEC(Reset Wrap Mode):重设鼠标的工作模式为进入Wrap模式之前的模

式。 

0xEB(Read Data): 指取鼠标采样到的位移数据包。 

0xEA(Set Stream Mode): 设置鼠标的工作模式为Stream 模式。)

1.5.3 读到的数据

 

 

00h/FFh

当击键或释放键时检测到错误时,则在Output Bufer后放入此字节,如果Output Buffer已满,则会将Output Buffer的最后一个字节替代为此字节。使用Scan code set 1时使用00hScan code 2Scan Code 3使用FFh

 

AAh

BAT完成代码。如果键盘检测成功,则会将此字节发送到8042 Output Register中。

 

EEh

Echo响应。Keyboard使用EEh响应从60h发来的Echo请求。

 

F0h

Scan code set 2Scan code set 3中,被用作Break Code的前缀。

 

FAh

ACK。当Keyboard任何时候收到一个来自于60h端口的合法命令或合法数据之后,都回复一个FAh

 

FCh

BAT失败代码。如果键盘检测失败,则会将此字节发送到8042 Output Register中。

 

FEh

Resend。当Keyboard任何时候收到一个来自于60h端口的非法命令或非法数据之后,或者数据的奇偶交验错误,都回复一个FEh,要求系统重新发送相关命令或数据。

 

83ABh

当键盘收到一个来自于60hF2h命令之后,会依次回复83hABh83AB是键盘的ID

(PS:linux 2.6.27驱动代码看的话,默认标准键盘收到一个来自于60hF2h命令之后会先回应一个0xfa ACK,然后开始回应两个字节的ID,第一个回应的应该是0xab(意为Regular keyboards;若为 0xac:  NCD Sun keyboard; 0x2b: Trust keyboard,translated; 0x5d: Trust keyboard ; 0x60:  NMB SGI keyboard,translated; 0x47: NMB SGI keyboard,第二个回应的数驱动不太关心)

Scan code

除了上述那些特殊字节以外,剩下的都是Scan code

 

 

1.6 端口操作

 

    首先介绍一下端口的读写操作,驱动中使用函数 READ_PORT_UCHAR 进行读操作,READ_PORT_UCHAR 中使用CPU读端口指令,inb(PS:当不是端口,而是IO内存地址时用 ioread8)。驱动中使用函数 WRITE_PORT_UCHAR 进行写操作,WRITE_PORT_UCHAR 中使用CPU写端口指令,outb(iowrite8)

 

1.6.1 读取状态寄存器

 

读取状态寄存器的方法,对64h端口进行读操作。

 

1.6.2 读数据

 

需要读取的数据有,i8042i8048得到的按键的扫描码,i8042命令的ACKi8042i8048得到的i8048命令的ACK,需要命令重发的RESEND,一些需要返回结果的命令得到的结果。

 

当有数据需要被驱动读走的时候,数据被放入输出缓冲器,同时将状态寄存器的bit0OUTPUT_BUFFER_FULL)置1,引发键盘中断(键盘中断的IRQ1)。由于键盘中断,引起由键盘驱动提供的键盘中断服务例程被执行。在键盘中断服务例程中,驱动会从i8042读走数据。一旦数据读取完成,状态寄存器的bit0会被清0

 

读数据的方法,首先,读取状态寄存器,判断bit0,状态寄存器bit01,说明输出缓冲器中有数据。保证状态寄存器bit01,然后对60h端口进行读操作,读取数据。

 

这里我们要谈一点很有用的题外话,前面提到的 IRQ,是 Interrupt Request line,中断请求线,是一个硬件线,它和中断向量是不同的。中断向量是用来在中断描述符表(IDT)中查找中断服务例程的那个序号。键盘的 IRQ 1,键盘中断服务例程对应的中断向量可不是1。这点要弄清楚。

 

1.6.3 i8042发命令

 

当命令被发往i8042的时候,命令被放入输入缓冲器,同时引起状态寄存器的 Bit1 1,表示输入缓冲器满,同时引起状态寄存器的 Bit2 1,表示写入输入缓冲器的是一个命令。

 

i8042发命令的方法,首先,读取状态寄存器,判断bit1,状态寄存器bit10,说明输入缓冲器为空,可以写入。保证状态寄存器bit10,然后对64h端口进行写操作,写入命令。

 

 

1.6.4 间接向i8048发命令

 

i8042发这些命令,i8042会转发i8048,命令被放入输入缓冲器,同时引起状态寄存器的Bit1 1,表示输入缓冲器满,同时引起状态寄存器的 Bit2 1,表示写入输入缓冲器的是一个命令。这里我们要注意,向i8048发命令,是通过写60h端口,而后面发命令的参数,也是写60h端口。i8042如何判断输入缓冲器中的内容是命令还是参数呢,我们在后面发命令的参数中一起讨论。

 

i8048发命令的方法,首先,读取状态寄存器,判断bit1,状态寄存器bit10,说明输入缓冲器为空,可以写入。保证状态寄存器bit10,然后对60h端口进行写操作,写入命令。

 

1.6.5 发命令的参数

 

某些命令需要参数,我们在发送命令之后,发送它的参数,参数被放入输入缓冲器,同时引起状态寄存器的Bit1 1,表示输入缓冲器满。这里我们要注意,向i8048发命令,是通过写60h端口,发命令的参数,也是写60h端口。i8042如何判断输入缓冲器中的内容是命令还是参数呢。i8042是这样判断的,如果当前状态寄存器的Bit3 1,表示之前已经写入了一个命令,那么现在通过写60h端口放入输入缓冲器中的内容,就被当做之前命令的参数,并且引起状态寄存器的 Bit3 0。如果当前状态寄存器的 Bit3 0,表示之前没有写入命令,那么现在通过写60h端口放入输入缓冲器中的内容,就被当做一个间接发往i8048的命令,并且引起状态寄存器的 Bit3 1

 

i8048发参数的方法,首先,读取状态寄存器,判断bit1,状态寄存器bit10,说明输入缓冲器为空,可以写入。保证状态寄存器bit10,然后对60h端口进行写操作,写入参数。

 

 

 

"1 ps/2 键盘的硬件主要参考下面的资料,关于 ps/2 键盘硬件更多的内容也请参考下面的资料

 

[1] http://pagoda-ooos.51.net/os_book/driver/driver-keyboard_2.htm (中文)

[2] http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/PS2.pdf (中文)

[3] http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/ (英文)

2 ps/2 键盘的驱动“ 

i8042 最底层的操作代码在linux内核中的位置为: driver/input/serio/i8042.c,该键盘驱动的层次架构如下:

在 i8042.c中申请了一个platform设备和驱动,直接操作i8042,并对其进行自检和初始化操作,成功后,申请了一个serio总线设备,被赋予的type SERIO_8042,该与设备匹配的驱动为 drivers/input/keyboard/atkbd.c ,然后在该驱动中,又把i8042申请成了一个input设备,该设备对应的驱动为:dirvers/char/keyboard.c,该驱动为虚拟键盘驱动。PS/2鼠标驱动的层次结构可以参照键盘的驱动结构。

 

该键盘控制器驱动的详细参考资料如下:

[1] http://blog.csdn.net/easyblue99/article/details/7206090  (中文)

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值