3

I am following this OSDev Wiki tutorial about PS/2 Mouse drivers and I'm trying to write mine so I can add it to my C kernel (in protected mode).
First, I sent the command byte 0x60 to the port 0x64 followed by this status byte to the port 0x60:

                       0    1    1    1    0    0    1    1
                       |    |    |    |    |    |    |    | 
                       |    |    |    |    |    |    |    | 
                      UN   TRAN  ME   KE  IGKL  SF  MIE  KIE

Where:

  • UN : Unused (always 0)
  • TRAN : Translate keyboard scancodes
  • ME : Mouse enable
  • KE : Keyboard enable
  • IGKL : Ignore keyboard lock (Unused for PS/2, so it's 0)
  • SF : System flag
  • MIE : Mouse interrupt enable (send IRQ12 when buffer is full)
  • KIE : Keyboard interrupt enable (send IRQ1 when buffer is full)

With that, I guess I enabled the mouse and the keyboard. Right? r.r..rr..right..?
By simply checking the first bit of the status register, you can get the status of the output buffer. If it's set (output buffer is full) you check the 5th bit. If it's set, the output is coming from the mouse. If not, it's coming from the keyboard.

Here is my code: (I can add it to the kernel using inline assembly):

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. =;

I expected receiving output from the mouse because it will send packets to communicate mouse movement but nothing happened. However, this works fine (with the keyboard):

   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. =;

(Tested on QEMU, running on Ubuntu. So, maybe QEMU doesn't support PS/2?).

NOTE: I've seen some questions like this one on SO, but none of them answered mine.

MARSHMALLOW
  • 1,315
  • 2
  • 12
  • 24
  • 2
    What do you mean by “didn't work?” What behaviour did you observe? How does it differ from what you expected? – fuz Jun 28 '21 at 18:09
  • 2
    Not a bug, but for efficiency, `test al, 1<<0` / `jz` is more efficient than `bt ax,0`. Similarly `test al, 1<<5` / `jnz`. Especially for bits in the low byte. Or you can check that both bits are set `x & mask == mask`, but that would mean `mov` to copy AL if you need to keep the original, then `and al, 1| (1<<5)`/`cmp`/`je` – Peter Cordes Jun 30 '21 at 17:31
  • 2
    Note that regardless of what bounties you post, it is unlikely that you'll get help if you don't respond to people's comments asking for extra details. – fuz Jul 01 '21 at 10:40
  • 2
    @fuz Alright I edited it and I did my best to add details ;-). – MARSHMALLOW Jul 01 '21 at 14:47

1 Answers1

2

You are not following the advice given in the linked article.

Waiting to Send Bytes to Port 0x60 and 0x64

All output to port 0x60 or 0x64 must be preceded by waiting for bit 1 of port 0x64 to become clear. Similarly, bytes cannot be read from port 0x60 until bit 0 of port 0x64 is set.

0xD4 Byte, Command Byte, Data Byte

Sending a command or data byte to the mouse (to port 0x60) must be preceded by sending a 0xD4 byte to port 0x64 (with appropriate waits on port 0x64, bit 1, before sending each output byte). Note: this 0xD4 byte does not generate any ACK, from either the keyboard or mouse.

Wait for ACK from Mouse

It is required to wait until the mouse sends back the 0xFA acknowledgement byte after each command or data byte before sending the next byte. A few commands require an additional data byte, and both bytes will generate an 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, cx
.again:
  in      al, 0x64
  test    al, 1
  loopz   .again
  ret

WaitForACK:
  call    WaitForInput
  in      al, 0x60         ; AL = 0xFA
  ret

Initializing a PS2 Mouse

You need to send the command byte 0x20 to the PS2 controller on port 0x64. This command does not generate a 0xFA ACK byte. The very next byte returned should be the Status byte. After you get the Status byte, you need to set bit number 1 (value=2, Enable IRQ12), and clear bit number 5 (value=0x20, Disable Mouse Clock). Then send command byte 0x60 to port 0x64, followed by the modified Status byte to port 0x60. This might generate a 0xFA ACK byte from the keyboard.
Send the Enable Auxiliary Device command (0xA8) to port 0x64. This will generate an ACK response from the keyboard, which you must wait to receive.

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) 

The above code to enable the mouse is oversimplified and the value that you send to 0x60 ought to have bit 5 cleared following the article. (You are not "reprogramming" the mouse, so you don't need to disable the "master mouse clock" by setting bit 5 of the Status byte). Also, many a time, when dealing with these bit vectors, it's not a good idea to use hard-coded values like in mov al, 01110011b. Always only modifies the bits that are of interest to you.

  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 ...
Sep Roland
  • 33,889
  • 7
  • 43
  • 76