无法从我的 PS/2 鼠标驱动程序接收输出

问题描述

我正在关注关于 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 ...