OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 5:03 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: PS2 Mouse init problems
PostPosted: Tue Apr 28, 2009 4:45 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
In the past I've primarily tested things with VMware, but recently I've decided to test in bochs and qemu as well so that bugs present themselves more quickly. I've come across one which I have no clue about :-/. It involves my PS2 mouse initialization code.

Basically here's the problem:
Sometimes when I startup my OS, sometimes after booting the keyboard no longer works. I am at the prompt, the boot is complete. But I cannot input anything. The odd thing is, that it never happens if I disable my PS2 mouse init code. Specifically one line of it (the Compaq status byte part).

In VMware, the problem has never surfaced.
In bochs, the problem sometimes surfaces.
In QEmu, the problem often surfaces.

This leads me to believe that it may be some type of timing issue, but maybe not. Anyway, here's my init code.

Code:
   const AutoLock LOCK(SysLock::instance());

   // enable the aux mouse device
   ps2::wait_write();
   out<uint8_t>(ps2::PORT_COMMAND, 0xa8);
   
   // enable interrupts/"compaq status byte"
   ps2::wait_write();
   out<uint8_t>(ps2::PORT_COMMAND, 0x20);
   const uint8_t status = mouse_read() | 0x02;
   
   ps2::wait_write();
   out<uint8_t>(ps2::PORT_COMMAND, 0x60);
   
   ps2::wait_write();
   out<uint8_t>(ps2::PORT_IO, status); // NOTE: commenting this line out, makes the issue go away!

   // set defaults
   mouse_write(0xf6);   
   mouse_read();
      
   // enable mouse
   mouse_write(0xf4);
   mouse_read();


Some noteworthy definitions (NOTE: pause is just a "rep nop" to let the CPU know it is in a spinlock):
Code:
   static const uint8_t PORT_STATUS   = 0x64;
   static const uint8_t PORT_COMMAND   = 0x64;
   static const uint8_t PORT_IO      = 0x60;

   void ps2::wait_write() {
      int timeout = 10000;
      while(timeout-- && (in<uint8_t>(PORT_STATUS) & 2) != 0) {
         pause();
      }
   }

   void ps2::wait_read() {
      int timeout = 10000;
      while(timeout-- && (in<uint8_t>(PORT_STATUS) & 1) == 0) {
         pause();
      }
   }

   void ps2::enable_keyboard() {
      wait_write();
      out<uint8_t>(PORT_COMMAND, 0xae);
   }

   void ps2::disable_keyboard() {
      wait_write();
      out<uint8_t>(PORT_COMMAND, 0xad);
   }


This code is executed after the keyboard is initialized. That code looks like this:

Code:
   const AutoLock LOCK(SysLock::instance());
   sendData(0xff);         // restart keyboard
   sendData(0xf4);         // enables keyboard and Scanning
   setRate(default_rate);   // sets default scanning rate
   updateLeds(0);         // makes sure no leds are on


Keyboard related definitions:
Code:
void CharacterDevice_Keyboard::sendData(uint8_t data) const {
   ps2::wait_write();
   out(ps2::PORT_IO, data);
}

void CharacterDevice_Keyboard::setRate(uint8_t rate) const {
   sendData(0xf3);
   sendData(rate);
}

void CharacterDevice_Keyboard::updateLeds(uint8_t led) {
   if(led == 0) {
      m_LEDs = 0;
   } else {
      m_LEDs ^= led;
   }
   
   sendData(0xed);
   sendData(m_LEDs);
}


Any ideas on what could be wrong?


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Tue Apr 28, 2009 4:48 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
Another note, I know the OS isn't halted. because if I have it print a char in the timer interrupt I still see them flying by even when the keyboard is dead.


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Tue Apr 28, 2009 5:10 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
Also, don't know if it's relevent, but my PS2 mouse interrupt handler looks like this:

Code:
   const AutoLock LOCK(SysLock::instance());
   
   static Event mouseEvent;
   static int cycle = 0;

   while((in<uint8_t>(ps2::PORT_STATUS) & 1) != 0) {
   
      switch(cycle) {
      case 0:
         mouseEvent.buttons = in<uint8_t>(ps2::PORT_IO);
         break;
      case 1:
         mouseEvent.changeX = in<uint8_t>(ps2::PORT_IO);
         break;
      case 2:
         mouseEvent.changeY = in<uint8_t>(ps2::PORT_IO);
         // allow one more read from the queue
         m_Events.push(mouseEvent);
         break;
      }

      cycle = (cycle + 1) % 3;
   }
   return 0;


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed Apr 29, 2009 1:28 am 
Offline
Member
Member

Joined: Mon Oct 15, 2007 3:04 pm
Posts: 296
I'm having similar huge problems with kybd/mouse via ps/2. I followed Brendan's advice about building a full ps/2 controller base driver first eventually which is responsible for configuring the ps/2 controller and determining if it's a single or dual port and identifying the devices attached to each port etc. I've got it mostly working but once again there are issues...

In bochs/qemu the ps2 controller init code works perfectly, devices reset and identified correctly, then when i enable the mouse the keyboard stops working altogether (ints dont even fire).. sometimes there are left over bytes on the data port (60h) which no matter what I do I can't get rid of.. even looked at Linux ps2 code to see how there timings work and 8042 buffer flush which is identical to mine.

And it varies too which is annoying, sometimes neither mouse or keyboard will work, sometimes mouse works but no kybd, sometimes only kybd...
I've tested it on real h/w with ps2 devices (laptop with ps2 pad and kybd..) and with usb devices in ps2 emulation.. and on those setups the identify doesn't work at all..

Another thing that i noticed in the Linux code is they look at the ps2 mux options, and they seem to allow up to 4 ps2 ports.. but i've not been able to find any reference/tus that detail the usage/config of these additional ports? (not sure if anyone has the details on this?)

I'm 100% happy to share all the code I have for ps2 sofar... It would be really great if we (as a forum) could sort this ps2 nonsense out once and for all and update the wiki with a COMPLETE working PS2 init/kybd/mouse base driver..


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed Apr 29, 2009 7:12 am 
Offline
Member
Member
User avatar

Joined: Wed Feb 07, 2007 1:45 pm
Posts: 1401
Location: Eugene, OR, US
@proxy: try setting the compaq status *before* you send the A8 command to turn on the mouse.

johnsa wrote:
... and update the wiki with a COMPLETE working PS2 init/kybd/mouse base driver..


I don't think it's possible. If you look over the many mouse driver example codes that have been posted, they are all utterly different. The way that mouse/keyboard command ACK bytes (which are VERY important!) are handled is completely OS dependent. Mouse position can be updated instantaneously, but keyboard input needs to be queued ... and how that is implemented is fundamental to the driver design AND is completely OS dependent. PS2 mouse init shouldn't even be part of the driver. It should be a daemon ... and that entire concept is completely OS dependent.
If you use USB->PS2 mouse SMM emulation, then you can't combine the mouse and keyboard drivers. This is an OS dependent design decision, but it adds tremendous complexity to avoid races on IO port 0x60 -- especially since some mouse commands generate response bytes from the KEYBOARD. I certainly would have posted a mouse/keyboard driver with my wiki article (which needs a rewrite, I know) -- but my driver has so many hooks and dependencies into my OS that I decided at the time that it would be worthless to post it.

I spent a couple of months writing my own mouse driver/init daemon, and getting it functional. I wrote the wiki article the day after I got it working, as a brain dump of everything I had to take into account to get it running. The point of it was that the mouse/keyboard interconnected mess is logically COMPLICATED. My combined keyboard/mouse driver is 500 lines of ASM, and my mouse init daemon is another 500 lines -- about 40K of code. Which is more than you are really supposed to attach to a wiki article.


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed Apr 29, 2009 7:38 am 
Offline
Member
Member

Joined: Mon Oct 15, 2007 3:04 pm
Posts: 296
I agree regarding the OS specific decisions that should be taken in terms of design, but we could at least have a starting point, there seems to be a lot more to getting ps2 working than any doc/tut i've seen covers. A lot of the additional work I've done was thanks the Brendan's input, now looking at the linux code it seems there are even more things which aren't documented anywhere.

Perhaps we should just start somewhere, do a working ps2 init module for the wiki... how to get it up and how to know what ports are available (1/2/4) mux options, and identify devices etc.
Then from there it could be up to each person to do his/her own specific keyboard mouse drivers based on the sample handlers that support 3/4/6 byte mouse packets.. and kybds with/without translation etc.


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed Apr 29, 2009 2:28 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
@bewing: I tried putting the compaq byte stuff before the A8 code. Unfortunately, that just broke it across the board :-(.

Is there no examples out there that cover this completely? All I want for now is how to setup an ordinary PS2 mouse in 3-byte read mode.
Do I need to do something silly like disable the keyboard at certain times? Surely someone has gotten this to work.


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Thu Apr 30, 2009 3:10 am 
Offline
Member
Member

Joined: Mon Oct 15, 2007 3:04 pm
Posts: 296
I had the same issue with the order of the A8 vs. setting the compaq status byte.. I eventually had the a8 first, but it's still very suspect and only works now and then.

If it wasn't for the fact that a lot of machines still have REAL ps2 ports I'd completely ignore this as it seems to be highly suspect.. I'm not sure if there is such a thing as a working ps2 init tbh :)

In any event here is my ps2 init code (based on my work and Brendans input) - perhaps this can be a starting point for us to get this working 100%? : -

Code:

;======================================================================================
; Call before writing to 60h/64h.
;======================================================================================
kybd_ctrl_in_empty:
   push rcx
   push rbx
   mov rbx,[k_ms_since_boot]
   add rbx,100
   clc
kybd_wait0:
   in al,64h
   test al,02h
   jz short kybd_wait0_done
   mov rcx,[k_ms_since_boot]
   cmp rcx,rbx
   jl short kybd_wait0
   stc
kybd_wait0_done:
   pop rbx
   pop rcx
   ret

;======================================================================================
; Call before reading from 60h.
;======================================================================================
kybd_ctrl_out_full:
   push rcx
   push rbx
   mov rbx,[k_ms_since_boot]
   add rbx,100
   clc
kybd_wait1:
   in al,64h
   test al,01h
   jnz short kybd_wait1_done
   mov rcx,[k_ms_since_boot]
   cmp rcx,rbx
   jl short kybd_wait1
   stc
kybd_wait1_done:
   pop rbx
   pop rcx
   ret

;======================================================================================
; Empty the 8042 output buffer.
;======================================================================================
kybd_wait_8042:
   push rcx
   push rbx
   xor rcx,rcx
   mov rbx,16                  ; 16bytes = maximum size of 8042 buffer.
flush8042:   
   in al,64h
   test al,01h
   jz short no_8042_data
   in al,60h
   inc rcx
   cmp rcx,rbx
   jle short flush8042
no_8042_data:
   pop rbx
   pop rcx
   ret

;======================================================================================
; Wait for keyboard controller or mouse to return ACK.
;======================================================================================
kybd_wait_ack:
   push rcx
   push rbx
   mov rbx,[k_ms_since_boot]
   add rbx,500
   clc
kybd_wait2:
   in al,60h
   cmp al,0fah
   je short kybd_wait2_done
   mov rcx,[k_ms_since_boot]
   cmp rcx,rbx
   jl short kybd_wait2
   stc
kybd_wait2_done:
   pop rbx
   pop rcx
   ret

;======================================================================================
; Wait for Keyboard/Mouse to send Reset 0aah,00h
;======================================================================================
kybd_wait_reset_ack:
   push rcx
   push rbx
   mov rbx,[k_ms_since_boot]         ; Current ms since boot.
   add rbx,500                     ; Maximum ms to wait.
   clc
kybd_wait3:
   in al,60h                     ; This should be 0aah BAT passed.
   cmp al,0aah
   je short reset_ackok
   cmp al,0fch
   je short reset_ack_fail
   mov rcx,[k_ms_since_boot]
   cmp rcx,rbx
   jl short kybd_wait3
reset_ack_fail:                     ; Device returned 0fch instead of 0aah or timeout.
   stc
reset_ackok:
   in al,60h                     ; Read in 00h (Device ID) too.
   pop rbx
   pop rcx
   ret

;======================================================================================
; Write to PS2 Device B (mouse).
; -> Command Byte in BL.
;======================================================================================
ps2_deviceB_write:
   call kybd_ctrl_in_empty
   mov al,0d4h
   out 64h,al
   call kybd_ctrl_in_empty
   mov al,bl
   out 60h,al   
   ret

;======================================================================================
; Return Device B ID
; -> BL is ID.
;======================================================================================
ps2_get_deviceB_id:
   mov bl,0f2h                     ; Get MouseID command.
   call ps2_deviceB_write
   call kybd_wait_ack
   call kybd_ctrl_out_full
   in al,60h                     ; Read MouseID byte.
   ret

;======================================================================================
; Sample Rate in BL
; (10,20,40,60,80,100,200)
;======================================================================================
ps2_deviceB_set_sample:
   push rbx
   mov bl,0f3h                     ; Set Packet Sample-Rate Command.
   call ps2_deviceB_write
   call kybd_wait_ack
   pop rbx
   call ps2_deviceB_write
   call kybd_wait_ack
   ret

;======================================================================================
; Resolution in BL
; (0=1pixel/mm,1=2pixel/mm,2=4pixel/mm,3=8pixel/mm)
;======================================================================================
ps2_deviceB_set_resolution:
   push rbx
   mov bl,0e8h                     ; Set Packet Sample-Rate Command.
   call ps2_deviceB_write
   call kybd_wait_ack
   pop rbx
   call ps2_deviceB_write
   call kybd_wait_ack
   ret

;======================================================================================
; Set PS/2 Device B (Mouse) Scaling 2:1
;======================================================================================
ps2_deviceB_set_scaling21:
   mov bl,0e7h
   call ps2_deviceB_write
   call kybd_wait_ack
   ret
   
;======================================================================================
; Set PS/2 Device B (Mouse) Scaling 1:1
;======================================================================================
ps2_deviceB_set_scaling11:
   mov bl,0e6h
   call ps2_deviceB_write
   call kybd_wait_ack
   ret

;======================================================================================
; Enable PS/2 Streaming Mode.
;======================================================================================
ps2_deviceB_set_streammode:
   call kybd_ctrl_in_empty
   mov bl,0eah                     ; Ensure stream mode is enabled.
   call ps2_deviceB_write
   call kybd_wait_ack
   ret
   
;======================================================================================
; Enable PS/2 Device B (Mouse).
;======================================================================================
ps2_deviceB_enable:
   mov bl,0f4h
   call ps2_deviceB_write
   call kybd_wait_ack
   ret

;======================================================================================
; Disable PS/2 Device B (Mouse).
;======================================================================================   
ps2_deviceB_disable:
   mov bl,0f5h
   call ps2_deviceB_write
   call kybd_wait_ack
   ret

;======================================================================================
; Enable Keyboard (Device A).
;======================================================================================
ps2_deviceA_enable:
   call kybd_ctrl_in_empty
   mov al,0aeh
   out 64h,al
   ret
   
;======================================================================================
; Disable Keyboard (Device A).
;======================================================================================
ps2_deviceA_disable:
   call kybd_ctrl_in_empty
   mov al,0adh
   out 64h,al
   ret

;======================================================================================
; Set active scancode set on device A.
;======================================================================================
ps2_deviceA_set_scancodeset:
   ret

;======================================================================================
; Set typematic rate/delay on device A.
;======================================================================================
ps2_deviceA_set_ratedelay:
   ret


;======================================================================================
; Set typematic rate/delay on device B (dual keyboard PS2).
;======================================================================================
ps2_deviceB_set_ratedelay:
   ret

;======================================================================================
; Set LED states on Device A - Keyboard.
; BH -> Input Keyboard LEDS
; Bit 0: Scroll lock LED | Bit 1: Num lock LED | Bit 2: Caps lock LED
;======================================================================================
ps2_deviceA_set_leds:
   call kybd_ctrl_in_empty   
   mov al,0edh
   out 60h,al
   call kybd_ctrl_in_empty
   mov al,bh
   out 60h,al      
   ret

;======================================================================================
; Clear LED states on Device A - Keyboard.
;======================================================================================
ps2_deviceA_clear_leds:
   call kybd_ctrl_in_empty   
   mov al,0edh
   out 60h,al
   call kybd_ctrl_in_empty
   mov al,0
   out 60h,al
   call kybd_wait_ack
   ret
   
;======================================================================================
; Set LED states on Device B - (dual keyboard PS2).
;======================================================================================
ps2_deviceB_set_leds:
   ret

;======================================================================================
; Reset the PC via Keyboard Controller.
;======================================================================================
ps2_system_reset:
   mov bl,0d1h
   call kybd_ctrl_in_empty
   mov al,0d1h
   out 64h,al
   ; writes 11111110 to the output port (sets reset system line low)
   call kybd_ctrl_in_empty
   mov al,0feh
   out 60h,al
   ret
   
;======================================================================================
; Initialize the PS/2 Controller Interface and Attached Devices.
;======================================================================================
ps2_ctrl_init:

   ;--------------------------------------------------------
   ; Begin Initialization - Enable Aux Port and
   ; Determine PS2 Controller Type.
   ;--------------------------------------------------------
   call kybd_wait_8042               ; Ensure we empty out the 8042 data port if it still has data in it.
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   mov al,0a8h                     ; Enable PS2 Aux Port and Mouse.
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   jnc short ps2_init00
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init00:
   mov al,020h                     ; Send command 20h (read configuration byte).
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_out_full            ; Wait for the data to be ready.
   jnc short ps2_init01

   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret

ps2_init01:   
   in al,60h                     ; read configuration byte (no ACK).
   
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   jnc short ps2_init02
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init02:
   mov al,060h                     ; Send command 60h (set configuration byte).
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_in_empty
   jnc short ps2_init03
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init03:
   mov al,0                     ; New configuration byte (Enable PS/2 device A and B and no BAT flag).
   out 60h,al
   call kybd_ctrl_in_empty
   jnc short ps2_init04
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init04:
   mov al,0a7h                     ; Send command a7h (disable device B).
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   jnc short ps2_init05
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init05:
   mov al,020h                     ; Send command 20h (read configuration byte).
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_out_full            ; Wait for the data to be ready.
   jnc short ps2_init06
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init06:
   in al,60h                     ; read configuration byte.
   test al,00100000b               ; Is Device B still Enabled?
   jnz short dual_ps2

   ;--------------------------------------------------------
   ; Single Port PS/2 Controller.
   ;--------------------------------------------------------
   mov [ps2_ctrl_type],0
   mov [ps2_deviceB_type],0         ; Since single port, device B must be none.
   mov [ps2_deviceB_use],0            ; Ensure Device B is marked as unusable.
   jmp short got_ps2_type
   
   ;--------------------------------------------------------
   ; Dual Port PS/2 Controller.
   ;--------------------------------------------------------
dual_ps2:
   mov [ps2_ctrl_type],1
   
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   jnc short ps2_init07
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret

ps2_init07:
   mov al,060h
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_in_empty            ; Wait for keyboard controller to be ready to receive a command.
   jnc short ps2_init08
   
   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_init08:
   mov al,00110000b               ; disable ? Re-Enable both devices but leave IRQs off.
   out 60h,al
   call kybd_ctrl_in_empty
   jnc short ps2_dualdone

   mov [ps2_init_failed],1            ; Timeout when trying to communicate with ps2 controller.
   ret
   
ps2_dualdone:
   mov [ps2_deviceA_use],1            ; Ensure so-far that both devices are flagged usable.
   mov [ps2_deviceB_use],1
   
   ;--------------------------------------------------------
   ; We now know if the PS2 Controller is single/dual device
   ; -> Perform Interface tests on attached devices.
   ; -> From here it's safe to ignore the general
   ; -> PS2 timeouts as it should be in place.
   ; -> We'll ack. device specific timeouts now.
   ;--------------------------------------------------------
got_ps2_type:

   call kybd_ctrl_in_empty
   mov al,0f5h                     ; Disable scanning for device A (keyboard).
   out 60h,al
   call kybd_wait_ack

   ;--------------------------------------------------------
   ; Perform Interface Test on Device A.
   ;--------------------------------------------------------
   call kybd_ctrl_in_empty
   mov al,0abh                     ; Device A Interface Test command.
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_out_full
   in al,60h

   mov [ps2_deviceA_res],al
   mov al,[ps2_deviceA_res]
   test al,al
   jz short devA_int_ok
   mov [ps2_deviceA_use],0            ; Device A Interface Test failed, mark device unusable.
   jmp test_devB
devA_int_ok:

   ;--------------------------------------------------------
   ; Perform Reset of Device A.
   ;--------------------------------------------------------
reset_devA:
   call kybd_ctrl_in_empty
   mov al,0ffh                     ; Reset device A command.
   out 60h,al
   call kybd_wait_reset_ack         ; Keyboard might not ACK reset, so wait for 0aah instead.
   jnc short deDevA

   mov [ps2_deviceA_type],0         ; ACK from Device A failed, mark it as unusable for now.
   mov [ps2_deviceA_use],0

   ;--------------------------------------------------------
   ; Disable Scanning on Device A again.
   ; -> Only if device responded to reset (use=1).
   ;--------------------------------------------------------
deDevA:
   cmp [ps2_deviceA_use],1
   jne short test_devB         
   call kybd_ctrl_in_empty
   mov al,0f5h                     ; Disable scanning for device A (keyboard).
   out 60h,al
   call kybd_wait_ack
   jnc short test_devB
   
   mov [ps2_deviceA_use],0            ; Disable Device A scanning failed, mark as unusable.
   
   ;--------------------------------------------------------
   ; Perform Interface Test on Device B.
   ;--------------------------------------------------------
test_devB:
   cmp [ps2_deviceB_use],0
   je no_disable_devB               ; No Device B so skip.

   call kybd_ctrl_in_empty            ; Disable packet sending (mouse) / disable device B.
   mov al,0d4h
   out 64h,al
   call kybd_ctrl_in_empty
   mov al,0f5h
   out 60h,al
   call kybd_wait_ack

   call kybd_ctrl_in_empty
   mov al,0a9h                     ; Test Mouse Port (Device B) command.
   out 64h,al
   call kybd_wait_ack
   call kybd_ctrl_out_full
   in al,60h

   mov [ps2_deviceB_res],al
   mov al,[ps2_deviceB_res]
   test al,al
   jz short no_disable_devB
   mov [ps2_deviceB_use],0            ; Device B Interface Test failed, mark device unsuable.
   jmp no_devB_reset
   
   ;--------------------------------------------------------
   ; Perform Reset of Device B if present.
   ;--------------------------------------------------------
   call kybd_ctrl_in_empty
   mov al,0d4h
   out 64h,al
   call kybd_ctrl_in_empty
   mov al,0ffh
   out 60h,al   
   call kybd_wait_reset_ack         ; Reset Command might not ACK, rather wait for 0aah.
   jnc short no_disable_devB
   
   mov [ps2_deviceB_use],0            ; Reset of Device A failed, so mark it not usable.
   mov [ps2_deviceB_type],0

no_disable_devB:
no_devB_reset:
   
   ;--------------------------------------------------------
   ; Disable Scanning on Device B again.
   ; -> Only if device responded to reset (use=1).
   ;--------------------------------------------------------
no_descanA:
   cmp [ps2_deviceB_use],1
   jne short no_descanB
   call kybd_ctrl_in_empty            ; Disable packet sending (mouse) / disable device B.
   mov al,0d4h
   out 64h,al
   call kybd_ctrl_in_empty
   mov al,0f5h
   out 60h,al
   call kybd_wait_ack
   jnc short no_descanB
   
   mov [ps2_deviceB_use],0            ; Disable Device B scanning failed, mark as unusable.
   
   ;--------------------------------------------------------
   ; Identify Device A if it's still usable.
   ;--------------------------------------------------------
no_descanB:
   cmp [ps2_deviceA_use],1
   jne short identifyB               ; Only identify device A if still present and usable.
   
   call kybd_ctrl_in_empty
   mov al,0f2h                     ; Send the get keyboard ID command to encoder.
   out 60h,al
     
     mov rdi,offset ps2_deviceA_type
     mov dword [rdi],0               ; Ensure device type is 0.
     call kybd_wait_ack               ; Wait for the ACK byte 0fah.
     jnc short ps2_idA00
     
   mov [ps2_deviceA_type],0         ; ACK from Device A failed, mark it as unusable for now.
   mov [ps2_deviceA_use],0
   jmp short identifyB               ; Go on to Device B identification.
   
ps2_idA00:
     mov [rdi+0],al                  ; byte 0 = 0fah.
     call kybd_ctrl_out_full
     jc short identifyB               ; If this happens ID was only 1 byte.
     in al,60h
     cmp al,0fah
     je short ps2_idA00               ; Sometimes we get a double ACK? so ignore it..
   mov [rdi+1],al                  ; byte 1 = ?
     call kybd_ctrl_out_full
     jc short identifyB               ; ID was 2 bytes long.
   in al,60h
     mov [rdi+2],al                  ; byte 2 = ?
     call kybd_ctrl_out_full
     jc short identifyB               ; ID was 3 bytes long.
     in al,60h
     mov [rdi+3],al                  ; byte 3 = ? (shouldn't happen as ids are 3 bytes or less).
     
   ;--------------------------------------------------------
   ; Identify Device B if it's still usable.
   ;--------------------------------------------------------
identifyB:
   cmp [ps2_deviceB_use],1
   jne short no_devB_identify         ; No device B present or responding so skip identify.
   
   call kybd_ctrl_in_empty
   mov al,0d4h
   out 64h,al
   call kybd_ctrl_in_empty
   mov al,0f2h                     ; Send Get Mouse ID / identify command.
   out 60h,al
   
   mov rdi,offset ps2_deviceB_type
   mov dword [rdi],0
   call kybd_wait_ack               ; Wait for the ACK byte 0fah.
   jnc short ps2_idB00
   
   mov [ps2_deviceB_type],0         ; ACK from Device B failed, mark it as unusable for now.
   mov [ps2_deviceB_use],0
   jmp short no_devB_identify         ; Identify Device B Failed.

ps2_idB00:
   mov [rdi+0],al                  ; byte 0 = 0fah.
     call kybd_ctrl_out_full
     jc short no_devB_identify         ; If this happens ID was only 1 byte.
   in al,60h
   cmp al,0fah
     je short ps2_idB00               ; Sometimes we get a double ACK? so ignore it..
   mov [rdi+1],al                  ; byte 1 = ?
     call kybd_ctrl_out_full
     jc short no_devB_identify         ; ID was 2 bytes long.
     in al,60h
     mov [rdi+2],al                  ; byte 2 = ?
     call kybd_ctrl_out_full
     jc short no_devB_identify         ; ID was 3 bytes long.
     in al,60h
     mov [rdi+3],al                  ; byte 3 = ? (shouldn't happen as ids are 3 bytes or less).

   ;--------------------------------------------------------
   ; If Device B is a PS2 Mouse and
   ; it's ID = 0, perform mouse specific init sequence
   ; to determin enhanced type (wheel, 5 button).
   ;--------------------------------------------------------
no_devB_identify:
   cmp [ps2_deviceB_use],1            ; No Device B so skip.
   jne short ps2_done

   cmp [ps2_deviceB_type],000000fah   ; Mouse ID reported seems to be correct, no need to determine enhanced state (Could've been inited by another pc on kvm?).
   jne short ps2_done

   mov bl,200
   call ps2_deviceB_set_sample
   mov bl,100
   call ps2_deviceB_set_sample
   mov bl,80
   call ps2_deviceB_set_sample
   call ps2_get_deviceB_id
   cmp al,3
   jne short ps2_done               ; ID remained 0 so we've got a std. PS/2 mouse.
   
   mov [ps2_deviceB_packet],4         ; Update the packet size as we know we have at least a scroll wheel.
   mov rdi,offset ps2_deviceB_type
   mov [rdi+1],al                  ; Store the updated ID byte.
   
   mov bl,200
   call ps2_deviceB_set_sample
   mov bl,200
   call ps2_deviceB_set_sample
   mov bl,80
   call ps2_deviceB_set_sample
   call ps2_get_deviceB_id
   cmp al,4
   jne short ps2_done               ; ID remained 3 so we've got a PS/2 mouse with scroll wheel.

   mov [ps2_deviceB_packet],4         ; Update the packet size as we know we have a scroll wheel + 5 buttons.
   mov rdi,offset ps2_deviceB_type
   mov [rdi+1],al                  ; Store the updated ID byte.
   
ps2_done:
   cli
   MONITOR_WRITE_BYTE [ps2_init_failed],00ff0000h
   MONITOR_WRITE_BYTE [ps2_ctrl_type],00ffff00h
   MONITOR_WRITE_DWORD [ps2_deviceA_type],00f0ff00h
   MONITOR_WRITE_DWORD [ps2_deviceB_type],00ff00f0h
   MONITOR_WRITE_BYTE [ps2_deviceA_res],00ffff0fh
   MONITOR_WRITE_BYTE [ps2_deviceB_res],00fffff0h
   MONITOR_WRITE_BYTE [ps2_deviceA_use],00ffff00h
   MONITOR_WRITE_BYTE [ps2_deviceB_use],00ffff00h
   ret

;######################################################################################
; PS/2 Controller Driver Data / Variables.
;######################################################################################

   ; Possible Identification ID's returned.
   ;0xFA               AT keyboard with translation (not possible for device B)
     ;0xFA, 0xAB, 0x41   MF2 keyboard with translation (not possible for device B)
     ;0xFA, 0xAB, 0xC1   MF2 keyboard with translation (not possible for device B)
     ;0xFA, 0xAB, 0x83   MF2 keyboard without translation
     ;0xFA, 0x00         Standard mouse
     ;0xFA, 0x03         Mouse with a scroll wheel
     ;0xFA, 0x04         5 button mouse with a scroll wheel
   ;0xFA, 0x08         Typhoon 6byte packet mouse
   
ps2_init_failed    db 0               ; [ 0 = ps2 controller is ready, 1 = ps2 controller init failed ]
ps2_ctrl_type      db 0               ; [ 0 = single port, 1 = dual port ]
ps2_deviceA_type   dd 0               ; [ 0 = none, else use table above ]
ps2_deviceB_type   dd 0               ; [ 0 = none, else use table above ]
ps2_deviceA_res    db 0               ; [ Result Byte from device A interface test (0=ok) all else = h/w failure ]
ps2_deviceB_res    db 0               ; [ Result Byte from device B interface test (0=ok) all else = h/w failure ]
ps2_deviceA_use    db 1               ; [ 0 = Not usable, 1 = usable ]
ps2_deviceB_use    db 1               ; [ 0 = Not usable, 1 = usable ]
ps2_deviceA_packet dd 1               ; [ Size in bytes of Device A packet ]
ps2_deviceB_packet dd 3               ; [ Size in bytes of Device B packet ]



It's written for FASM .. and you can just replace or remove the monitor write macro calls...


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Thu Apr 30, 2009 8:41 am 
Offline
Member
Member
User avatar

Joined: Wed Feb 07, 2007 1:45 pm
Posts: 1401
Location: Eugene, OR, US
OK, here's mine -- it's a daemon that supports hot plugging and also handles the keyboard LEDs. But I doubt you'll be able to get anything out of it -- because it uses my timeslice yielding mechanism, and no keyboard disabling during command/ACK sequences. It's in NASM (basically identical to FASM).

You might also want to note that it's much easier to debug things that fail 100%, than things that fail intermittently.

And, to the best of my knowledge, this code works 100%. I have not had it fail on any emulators or on any real hardware I've tried it on. But my driver does not support USB mice.


Attachments:
MouseLed.txt [17.81 KiB]
Downloaded 155 times
Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 11:58 am 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
I've come across something interesting. I basically was trying to see what happens when I send an 0xff (reset) command and then try to read the ACK, 0xaa, and 0x00 that it responds with, then doing my mouse init. I was doing this in a loop where each time it failed, it would resend the 0xff and try the reads again.

Well during my debugging I decided to print the values I was getting to see how the mouse was reacting and noticed something odd.

I currently disable the PS2 keyboard during mouse init, because I'm trying to be extra sure everything goes smoothly. In both VMware and bochs, if I press keys during my initialization code, the values on port 0x60 do not change. This makes sense since I've disabled the keyboard ;).

However, in qemu, port 0x60 continues to report scancodes even with the keyboard disabled! Is it possible that my qemu problems are due to a faulty PS2 implementation in qemu?


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 2:15 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
OK, also I've been reading some docs from these pages and have some questions, some of them seem obvious, but I feel to get this working right for everyone, we need some clear cut answers for these:

http://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html
http://www.win.tue.nl/~aeb/linux/kbd/scancodes-12.html
http://www.win.tue.nl/~aeb/linux/kbd/scancodes-13.html

1) when waiting to see if a read is ready for the mouse, do i need to check bit 0x20 of the status flag? I see this note when looking at the status byte documentation, but no example I've seen out there bothers:

Quote:
Bit 5: Auxiliary output buffer full

On PS/2 systems: Bit 0 tells whether a read from port 0x60 will be valid. If it is valid, this bit 5 tells what data will be read from port 0x60. 0: Keyboard data. 1: Mouse data.

On AT systems: 0: OK. 1: Timeout on transmission from keyboard controller to keyboard. This may indicate that no keyboard is present.


2) Is the 0xA8 necessary? The documentation seems to indicate that it is really only for MCA systems. However, QEmu doesn't seem to do *anything* with the mouse if I don't do this.

3) what is the proper way to enable/disable the mouse itself. This link looks promising:

http://www.win.tue.nl/~aeb/linux/kbd/sc ... .html#mcf4

If that is correct, is the proper sequence this:

Code:
wait_write
outb 0x64, 0xd4
wait_write
outb 0x60, 0xf4
wait_mouse_read
in 0x60 ; should be 0xfa


which leads to my next question...

4) Are mouse commands ACKed in the same way as the keyboard? Or do I need do some 0xd4 nonsense to let the controller know I want mouse data? If it is just a plain read of port 0x60...does the 0x20 bit need to be set for mouse ACKs? Or is 0x20 only applicable to mouse movement/click data? My gut feeling (and examples back it up) is that no 0xd4 stuff is needed and 0x20 is not needed for ACKs.

5) should the keyboard be disabled in the mouse interrupt code. I suppose you could just read while both bits 0x20 and 0x01 are set in the status reg. But I think that might mean the keyboard ISR would have to ensure that bit 0x20 is not set. Also, if I do attempt the disable the keyboard, reading the ACKs messes up my mouse data. So I think the answer is likely no.

6) does the keyboard have to be disabled during mouse init? Seems like a good idea.

7) If i do a mouse reset, what exactly is the sequence in the response? is there an ACKs per byte, or one ACK followed by the BAT and ID?
I so far have seen neither reliably.

So based on this, I have the following sequence to init, which conceptually should work, but does not (I am shooting for modern hardware, simplest code to get it up and going):

Code:
// disable keyboard
wait_write
outb 0x64, 0xad
wait_read
in 0x60 // should be 0xfa

// enable the aux mouse port
wait_write
outb 0x64, 0xa8
wait_read
in 0x60 // should be 0xfa

// set the mouse to defaults
wait_write
out 0x64, 0xd4
wait_write
out 0x64, 0xf6
wait_read
in 0x60 // should be 0xfa

// read the CCB
wait_write
out 0x64, 0x20
wait_read
ccb = in 0x60

// set the mouse interrupt bit
ccb = ccb | 0x02

// write back the CCB
wait_write
out 0x64, 0x60
wait_write
out 0x60, ccb

// enable the keyboard again
// disable keyboard
wait_write
outb 0x64, 0xae
wait_read
in 0x60 // should be 0xfa



Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 2:25 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
The interesting this is, simply disabling/enabling the mouse/keyboard doesn't yeild the results I expect at all. Give the following code:

Code:
static const uint8_t PORT_STATUS   = 0x64;
static const uint8_t PORT_COMMAND   = 0x64;
static const uint8_t PORT_IO      = 0x60;

void ps2::wait_read() {
   while((in<uint8_t>(PORT_STATUS) & 0x01) == 0x00) {
      pause();
   }
}

void ps2::wait_write() {
   while((in<uint8_t>(PORT_STATUS) & 0x02) != 0x00) {
      pause();
   }
}

void ps2::enable_keyboard() {
   wait_write();
   out<uint8_t>(PORT_COMMAND, 0xae);
   wait_read();
   uint8_t status = in<uint8_t>(0x60);
   printf("[enable_keyboard] %02x\n", status);
}

void ps2::disable_keyboard() {
   wait_write();
   out<uint8_t>(PORT_COMMAND, 0xad);
   wait_read();
   uint8_t status = in<uint8_t>(0x60);
   printf("[disable_keyboard] %02x\n", status);
}

void ps2::enable_mouse() {
   wait_write();
   out<uint8_t>(PORT_COMMAND, 0xa8);
   wait_read();
   uint8_t status = in<uint8_t>(0x60);
   printf("[enable_mouse] %02x\n", status);
}

void ps2::disable_mouse() {
   wait_write();
   out<uint8_t>(PORT_COMMAND, 0xa7);
   wait_read();
   uint8_t status = in<uint8_t>(0x60);
   printf("[disable_mouse] %02x\n", status);
}


and making my mouse init basically just this:

Code:
ps2::disable_keyboard();
ps2::enable_mouse();
ps2::enable_keyboard();


I get the following output in Qemu:

Code:
[disable_keyboard] fa
[enable_mouse] aa
[enable_keyboard] fa


or sometimes:

Code:
[disable_keyboard] 9c
[enable_mouse] fa
[enable_keyboard] aa


Why did the mouse return 0xaa? Is it still in reset mode and trying to give me a BAT? Is the 0xa8 command supposed to have an ACK? What could have possibly made the results vary?


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 4:58 pm 
Offline
Member
Member
User avatar

Joined: Wed Jan 19, 2005 12:00 am
Posts: 106
I also have an 8th and 9th question:
8) What is the proper way to get the ACK? Do I just read port 0x60 in a loop until it is 0xfa? (that's what one of the code samples above appears to do) or do I do a wait_read, then just read port 0x60 once? The tight loop approach seems to be more effective at the moment, but I don't know if this is "right"

9) when reading/writing the CCB (also known as compaq status byte) do the following:

[code]
wait_write
out 0x64, 0x20 ; 0x60 if writing
[code]

is there supposed to be an ACK there before I read/write the CCB?


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 6:23 pm 
Offline
On Probation

Joined: Wed Feb 11, 2009 1:04 am
Posts: 119
You should not need to disable the keyboard to read the mouse bytes. Your read mouse data function should differentiate the devices for you. In fact one of the quotes in a post you made tells us that. I personally consider both its own driver, separate from one another. As far as the ACK thing, that is a good question. I don't and mine works in VPC and real hardware, so maybe not.


Top
 Profile  
 
 Post subject: Re: PS2 Mouse init problems
PostPosted: Wed May 06, 2009 7:24 pm 
Offline
Member
Member
User avatar

Joined: Wed Feb 07, 2007 1:45 pm
Posts: 1401
Location: Eugene, OR, US
My answers:
1a) You have to deal with the bytes in whatever order they come.
1b) No. If you are polling, you test for bit 0 first, to see if a data byte "esists". If bit 0 is set, then you additionally check bit 5 (just so long as you are not polling a USB mouse in SMM ps2 emulation mode).

2) Absolutely necessary. However, the BIOS might send it for you. The MCA standard has been adopted for all ps2 mouse interfaces. If RBIL says "MCA specific" that means you have to do it.

3) The mouse does not need to be enabled or disabled, precisely. It is always enabled. You need to enable the PORT with the 0xA8 command, and then turn on streaming mode with 0xf4. Once streaming mode is activated, you will get packets.

4) NO!!! To SEND a mouse command byte, you SEND a 0xd4 to port 0x64. The 0xd4 byte is not ACKed. Then you SEND the command byte to port 0x60. The command byte generates an ACK reply from the mouse (ie. yes, with bit 5 on port 0x64 set, unless you are using USB SMM emulation mode) that you must wait for.

5 & 6) Disabling the keyboard is the lazy way to potentially solve the race conditions inherent in waiting for mouse command ACK bytes. It might work, but deciding to do it is entirely OS specific. You shouldn't need to send mouse commands under normal operating conditions.

7) There IS NO exact sequence. All you know is that it ends with a 0xAA byte. Wait for it. Then proceed with the init command sequence.

Quote:
Is it still in reset mode and trying to give me a BAT?

Yes.
Quote:
Is the 0xa8 command supposed to have an ACK?

From the keyboard controller, yes. And you must wait for it before proceeding with mouse init.
Quote:
What could have possibly made the results vary?

The keyboard controller and mouse are two completely separate devices, and the order that the two of them return bytes through port 0x60 is completely random.

8) You poll or get IRQs from the PS2 port, until someday one of the input bytes comes from the mouse, and has a value of 0xfa. Then you set a flag. And your code waits for the flag to be set. It does not poll the port directly -- unless you are willing to have your keyboard and mouse completely fail to operate until the ACK bytes arrive. Which is an OS-dependent design decision.

Quote:
9) is there supposed to be an ACK there before I read/write the CCB?

The keyboard controller does not send one for compaq status bytes, no.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 74 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group