问题描述
我正在关注关于 PS/2
鼠标驱动程序的 this OSDev Wiki 教程,我正在尝试编写我的驱动程序,以便将其添加到我的 C 内核(在保护模式下)。
首先,我将命令字节 0x60
发送到端口 0x64
,然后将此状态字节发送到端口 0x60
:
0 1 1 1 0 0 1 1
| | | | | | | |
| | | | | | | |
UN TRAN ME KE IGKL SF MIE KIE
哪里:
-
UN
:未使用(总是0
) -
TRAN
: 翻译键盘扫描码 -
ME
: 鼠标启用 -
KE
: 键盘启用 -
IGKL
: 忽略键盘锁定(PS/2 未使用,所以是0
) -
SF
: 系统标志 -
MIE
: 鼠标中断使能(缓冲区满时发送 IRQ12) -
KIE
: 键盘中断使能(缓冲区满时发送 IRQ1)
这样,我想我启用了鼠标和键盘。对? r.r..rr..right..?
通过简单地检查状态寄存器的第一位,就可以得到输出缓冲区的状态。如果已设置(输出缓冲区已满),则检查第 5 位。如果已设置,则输出来自鼠标。如果没有,它来自键盘。
这是我的代码:(我可以使用内联汇编将其添加到内核中):
mov al,0x60 ;= send command byte 0x60... =;
out 0x64,al ;= to port 0x64 =;
mov al,01110011b ;= and send the status byte... =;
out 0x60,al ;= to port 0x60 =;
mov al,0xA8 ;= double-check! :P =;
out 0x64,al ;= double-checking is great! i...ii..isn't it..? Or...? (SUS) =;
jmp lp ;= do the loop again =;
lp:
in al,0x64 ;= read the status register =;
bt ax,0 ;= check the first bit (is the output buffer full?) =;
jnc lp ;= if not,then repeat. =;
bt ax,5 ;= check the 5th bit (is the output coming from the mouse?) =;
jc draw ;= then.. uhh.. Idk.. maybe draw a pixel ;-)
jmp lp ;= If it's coming from the keyboard,repeat. =;
我希望从鼠标接收输出,因为它会发送数据包来传达鼠标移动,但什么也没发生。但是,这很好用(使用键盘):
bt ax,5 ;= check the 5th bit (is the output coming from the keyboard?) =;
jnc draw
jmp lp ;= If it's coming from the mouse,repeat. =;
(在 QEMU 上测试,在 Ubuntu 上运行。所以,也许 QEMU 不支持 PS/2?)。
注意:我在 SO 上看到过一些这样的问题,但没有一个回答我的。
解决方法
您没有遵循 the linked article 中给出的建议。
等待向端口 0x60 和 0x64 发送字节
所有到端口 0x60 或 0x64 的输出必须在等待端口 0x64 的第 1 位被清除之前。同样,在设置了端口 0x64 的位 0 之前,无法从端口 0x60 读取字节。
0xD4 字节、命令字节、数据字节
向鼠标(到端口 0x60)发送命令或数据字节之前,必须先向端口 0x64 发送 0xD4 字节(在发送每个输出字节之前,在端口 0x64 第 1 位进行适当的等待)。 注意:这个 0xD4 字节不会从键盘或鼠标生成任何 ACK。
等待鼠标的 ACK
在发送下一个字节之前,需要等到鼠标在每个命令或数据字节后发回 0xFA 确认字节。一些命令需要一个额外的数据字节,两个字节都会产生一个 ACK。
; The loop avoids waiting forever and allows adding error processing. Remove at will.
WaitForOutput:
xor cx,cx
.again:
in al,0x64
test al,2
loopnz .again
ret
WaitForInput:
xor cx,1
loopz .again
ret
WaitForACK:
call WaitForInput
in al,0x60 ; AL = 0xFA
ret
初始化 PS2 鼠标
您需要在端口 0x64 上将命令字节 0x20 发送到 PS2 控制器。 此命令不会生成 0xFA ACK 字节。返回的下一个字节应该是状态字节。获得状态字节后,您需要设置位号 1(值=2,启用 IRQ12),并清除位号 5(值=0x20,禁用鼠标时钟)。然后将命令字节 0x60 发送到端口 0x64,然后将修改后的状态字节发送到端口 0x60。 这可能会从键盘生成一个 0xFA ACK 字节。
将启用辅助设备命令 (0xA8) 发送到端口 0x64。 这将生成来自键盘的 ACK 响应,您必须等待接收。
mov al,0x60 ;= send command byte 0x60... =;
out 0x64,al ;= to port 0x64 =;
mov al,01110011b ;= and send the status byte... =;
out 0x60,al ;= to port 0x60 =;
mov al,0xA8 ;= double-check! :P =;
out 0x64,al ;= double-checking is great! i...ii..isn't it..? Or...? (SUS)
上面启用鼠标的代码过于简单,您发送到 0x60 的值应该在文章后面清除第 5 位。 (您不是“重新编程”鼠标,因此您不需要通过设置状态字节的第 5 位来禁用“主鼠标时钟”)。此外,很多时候,在处理这些位向量时,使用像 mov al,01110011b
这样的硬编码值并不是一个好主意。始终只修改您感兴趣的位。
call WaitForOutput
mov al,0x20 ; PS2.GetStatusByte (no ACK)
out 0x64,al
call WaitForInput
in al,0x60
or al,00000010b ; Set bit 1 (Enable IRQ12)
and al,11011111b ; Clear bit 5 (Disable Mouse Clock)
push ax ; (1)
call WaitForOutput
mov al,0x60 ; PS2.SetStatusByte (ACK)
out 0x64,al
call WaitForACK
call WaitForOutput
mov al,0xD4 ; PS2.WriteToMouseInsteadOfKeyboard (no ACK)
out 0x64,al
call WaitForOutput
pop ax ; (1)
out 0x60,al ; SendToMouse (ACK)
call WaitForACK
call WaitForOutput
mov al,0xA8 ; PS2.EnableMousePort (ACK)
out 0x64,al
call WaitForACK
Again:
in al,0x64
and al,00100001b
cmp al,00100001b
jne Again
call WaitForInput
in al,0x60
; draw ...