Sunday, April 12, 2009

Writing a PS/2 mouse driver for your hobby OS

It took me a while to search for a good tutorial on how to write a PS/2 mouse driver for a hobby OS. Luckily, I was able to make an existing driver work. So for those who are having trouble writing one, I came up with this guide. However, I do not take full credit in the codes that I will be posting.

I assume that your hobby OS is 32-bit targetted on x86 machines. The following outlines the steps.
  1. Program the 8259 controller to enable IRQ2, the cascade line. The PS/2 mouse IRQ is IRQ12 which is in the slave PIC. Normally the IRQ numbers are remapped in 32-bit OSes.
    #define IRQ_TIMER 1
    #define IRQ_CASCADE 4
    #define IRQ_KEYBOARD 2
    #define IRQ_FDC 64
    #define IRQ_MOUSE 16
    void program8259(unsigned char b){
    unsigned char b1=0xFF;
    //remap the IRQs
    outportb(0x20,0x11);
    outportb(0xA0,0x11);
    outportb(0x21,0x20); //IRQ0-IRQ7 -> interrupts 0x20-0x27
    outportb(0xA1,0x28); //IRQ8-IRQ15 -> interrupts 0x28-0x2F
    outportb(0x21,4);
    outportb(0xA1,2);
    outportb(0x21,1);
    outportb(0xA1,1);
    b1^=b;
    outportb(0x21,b1);
    outportb(0xA1, inportb(0xA1) & ~0x10);  //enable IRQ12
    };
    
    //Here's the call to enable needed IRQ Lines called usually in the main() function
    program8259(IRQ_TIMER | IRQ_KEYBOARD | IRQ_FDC | IRQ_CASCADE);
    

  2. And here's the driver code courtesy of SANiK.
    //Mouse.inc by SANiK
    //License: Use as you wish, except to cause damage
    unsigned char mouse_cycle=0;
    signed char mouse_byte[3];
    signed char mouse_x=0;
    signed char mouse_y=0;
    
    /* The interrupt handler */
    void mouse_irq(){
    DWORD flags;
    switch(mouse_cycle){
    case 0:
    mouse_byte[0]=inportb(0x60);
    mouse_cycle++;
    break;
    case 1:
    mouse_byte[1]=inportb(0x60);
    mouse_cycle++;
    break;
    case 2:
    mouse_byte[2]=inportb(0x60);
    mouse_x=mouse_byte[1];
    mouse_y=mouse_byte[2];
    mouse_cycle=0;
    break;
    }
    }
    
    inline void mouse_wait(unsigned char  a_type){
    unsigned int _time_out=100000; //unsigned int
    if(a_type==0){
    while(_time_out--){ //Data
    if((inportb(0x64) & 1)==1){
    return;
    }
    }
    return;
    }else{
    while(_time_out--){ //Signal
    if((inportb(0x64) & 2)==0){
    return;
    }
    }
    return;
    }
    }
    
    inline void mouse_write(unsigned char a_write){
    //Wait to be able to send a command
    mouse_wait(1);
    //Tell the mouse we are sending a command
    outportb(0x64, 0xD4);
    //Wait for the final part
    mouse_wait(1);
    //Finally write
    outportb(0x60, a_write);
    }
    
    unsigned char mouse_read(){
    //Get's response from mouse
    mouse_wait(0);
    return inportb(0x60);
    }
    
    void installmouse(){ /* This enables the mouse, called in main() */
    unsigned char _status;
    
    //Enable the auxiliary mouse device
    mouse_wait(1);
    outportb(0x64, 0xA8);
    
    //Enable the interrupts
    mouse_wait(1);
    outportb(0x64, 0x20);
    mouse_wait(0);
    _status=(inportb(0x60) | 2);
    mouse_wait(1);
    outportb(0x64, 0x60);
    mouse_wait(1);
    outportb(0x60, _status);
    
    //Tell the mouse to use default settings
    mouse_write(0xF6);
    mouse_read();  //Acknowledge
    
    //Enable the mouse
    mouse_write(0xF4);
    mouse_read();  //Acknowledge
    //Depends on your OS!
    setinterruptvector(0x2C,idtbase,0x8E,mousewrapper,SYS_CODE_SEL);
    printf("Mouse successfully initialized.\n");
    }
    
    void get_mouse_pos(signed char *x,signed char *y){
    *x=mouse_x;
    *y=mouse_y;
    }
    

  3. The mousewrapper code is important for the IRET so it is written in assembly. It just actually calls mouse_irq defined in the driver above..
    global mousewrapper
    mousewrapper:
    push ebp
    push gs
    push fs
    push es
    push ss
    push ds
    pusha
    call mouse_irq
    ;re-enable interrupts, this is IMPORTANT!
    mov al,0x20
    out 0xA0,al
    out 0x20,al
    popa
    pop ds
    pop ss
    pop es
    pop fs
    pop gs
    pop ebp
    iret
    
That's it!Enjoy your mouse!

1 comment:

jazz said...

HUGE BOON.


Dos uses int33 and Linux uses hidden obscure code that cant be found.

HID references are nice, but I need to start with a base of operations as PS2 keyboard and mouse are on same controller. THANK YOU!!
--FPOS is at rjasmin.net and fpos.firmos.at/fpos for SVN, you need a client like tortoise.

Might be broken for a little while yet, but its taking off.