Working with the Keyboard Controller The keyboard controller is by sdaferv

VIEWS: 14 PAGES: 5

									Working with the Keyboard Controller

The keyboard controller is responsible for interacting with the PS/2 mouse. The mouse
functionality wasn't present in the original controller design so the methods of accessing
the mouse are somewhat indirect.

On the lowest level, the keyboard controller has two ports of interest, the data port and
the status port.

      0x60        Keyboard Controller Data Port
      0x64        Keyboard Controller Status Port

The primary channel of communication between the software and the controller is
through the data port. The status port is used to regulate this communication. It is
possible to obtain the status of the keyboard controller by reading the status byte from
port 0x64. This byte is a bit mask. The important parts are outlined below:

      bit               meaning
      0                 there is data waiting to be read on port 0x60
                              (ie: you can read the data from port 0x60)
      1                 the controller is currently processing data
                              (ie: don't write any new data to either port)
      5                 the data waiting to be read is from the mouse
                              (ie: mouse data is available on port 0x60)

You have to check the status byte of the controller before you do almost anything else.
The process of checking the status byte should take the form of a loop in which you
repeatedly read a byte from port 0x64 and test this byte to see if it has the bits that you
require to be set. The loop must wait until the desired bits are set. Since it's possible
that the keyboard controller is stuck and will never reach the status code that you're
waiting for, you need to allow for a timeout. The timeout should occur after attempting
to read the status 0xa0000 times.

Before you send any data to the keyboard controller (on either port) you must first
check that it is safe to send (ie: by continuously reading the status byte until bit 1 is
clear, up to 0xa0000 times). Before you read any data from port 0x60 you must first
check that data is available to be read (ie: by continuously reading the status byte until
bit 0 is set, up to 0xa0000 times). If you want to read mouse data, you must insure
that both bits 0 and 5 are set.

In the event of a timeout your driver should behave gracefully. For example, if a
timeout occurs anywhere during the process of initialising the keyboard controller or the
mouse then the program should restore the old values of any interrupt handlers it has
registered and exit fully (ie: not become a TSR)

The act of reading or writing data to port 0x60 is usually associated with receiving or
sending data to the keyboard. You can write a command to port 0x64 to change the
meaning of data to be written to or read from port 0x60. The following are a list of
commands you will need to use:
        command          meaning
        0x20             read the internal control byte of the keyboard controller
                               (the current control byte is put on port 0x60 to be read)
        0x60             write new internal control byte
                               (next byte sent to port 0x60 is the new control byte)
        0xd4             write next byte to mouse instead of keyboard
                               (next byte sent to port 0x60 is sent to the mouse)

The "internal control byte" above is used to control the long-term operating mode of the
controller. It is a single byte that acts as a bit mask. There are 2 bits relevant to writing
a mouse driver.

        bit              meaning
        5                hold mouse clock signal low
                               (set to 0 to enable the mouse)
        1                generate IRQ12 events on mouse input
                               (set to 1 to enable generation of the mouse IRQ)

In order to modify the internal control byte of the keyboard controller you must first
read it from the controller, apply AND and OR masks to change the bits that you want to
change, without changing non-relevant bits, and then write the modified value back to
the controller.

The PS/2 Mouse Protocol

The mouse itself is essentially a serial device. It interacts with the computer solely by
sending and receiving bytes. The mouse is able to receive a number of commands for
various reasons. The only one that is critically important is the initialisation command
(0xf4). The mouse replies to each command with an acknowledgement byte (0xfa) that
you should wait for.

For example, after performing the other actions that are required to enable the PS/2
port and enable interrupts, a sequence of what you'd do to initialise the mouse:

1.   read port 0x64 repeatedly until it is safe to send (as detailed above)
2.   send 0xd4 to port 0x64 to indicate that the next byte is for the mouse
3.   read port 0x64 repeatedly until it is safe to send
4.   send the initialisation command (0xf4) to port 0x60
5.   read port 0x64 repeatedly until there is data from the mouse
6.   read the data from port 0x60 and make sure it's the acknowledgement (0xfa)

After the mouse has been initialised it will start sending event notifications to the
computer. An event is when a mouse button is clicked or released or when the mouse
moves. The mouse sends data in packets of 3 bytes.

The first byte is a bit mask. In normal operation, we are only interested in the bottom 3
bits of the mask. These bits represent the left, right and middle mouse buttons
respectively. If the bit is set then the corresponding mouse button is currently held
down. The mouse does not send explicit click and release events. You have to
remember what state the buttons were in and compare the old state to the new state to
notice differences.

The next two bytes of the packet are simply the X and Y deltas. They are single-
character signed integers (-128 to 127) signifying how far the mouse moved. The
coordinates are as you'd expect them to be on a Cartesian plane (ie: positive Y is up).

Servicing Interrupt Requests

Hardware IRQs are interrupts just like any normal interrupt (for example, int 0x21)
except that instead of being called explicitly from an assembly program, they are
invoked automatically by hardware.

The IRQ numbers don't map directly to the interrupt numbers that they correspond to.
IRQs 0 through 7 map to interrupt numbers 0x08 through 0x0f. IRQs 8 through 15 map
to interrupt numbers 0x70 through 0x77. This means that IRQ12 actually triggers
interrupt number 0x74 (ie: as if you had called 'int 0x74' from an assembly program).

When the processor services an interrupt (either from software or hardware) it looks in
the interrupt vector table (IVT) to find the code responsible for handling the interrupt.
The IVT is a table stored at memory location 0000:0000 and extending for 0x400 bytes.
It contains 0x100 entries (for interrupts 0x00 through 0xff) and each entry is 4 bytes in
size. To find a specific entry, you multiply the interrupt number by 4.

For example, to find IRQ12 (interrupt 0x74) in the table, you multiple 0x74 by 4 to get
an address of 0x1d0. This means that the 4 bytes at 0000:01d0 are the pointer to the
handler for IRQ12.

For any given entry, the address of the handler is encoded in little endian form. The
segment comes second and the offset comes first. If for example, your handler for
IRQ12 was located at memory address 3c58:0220, then at address 0000:01d0 you
would see the following bytes:

20 02 58 3C

In order to place these bytes in memory, you need to take the address of your IRQ
handler function as if it were just a normal piece of data. You also need the current
contents of the CS register (as the code segment of your IRQ handler routine will be the
same as the code segment of the rest of the program). Here's a small example of how
you might install your IRQ handler:

install_handler:
     push ax
     push es

      xor ax, ax                    ; set es to the IVT
      mov es, ax
      cli                      ; disable interrupts
      mov ax, irq_handler      ; irq_handler is declared elsewhere
      mov [es:0x1d0], ax       ; store pointer to the offset
      mov ax, cs               ; we also need the CS value
      mov [es:0x1d2], ax       ; store segment at 0x1d0 + 2
      ; interrupt handler is now fully installed
      sti                      ; enable interrupts

      pop es
      pop ax

      ret

The above example includes the 'cli' and 'sti' commands. These commands, respectivly,
clear and set the interrupt flag. If the interrupt flag is cleared then the CPU won't
respond to IRQ requests. It's important to do this while changing the IVT since if an
IRQ12 occurred in between the time you changed the offset and the segment then the
processor would jump off into some invalid region of memory.

Once your handler itself is done executing, then you return control to the interrupted
program using the 'iret' instruction. This function is to 'int' as 'ret' is to 'call'. Because
your routine could be interrupting other code at any point, it is critical that you make
sure you have not modified the contents of any registers. If you use a register be sure
to save its value and restore it before returning.

You do not need to worry about another IRQ12 occurring during the time that you're
processing one. The interrupt controller ensures that your interrupt handler is not
invoked again while it's already running. This means that your code doesn't have to be
reentrant. It also means that you need to signal the interrupt controller when you are
done.

This is done by issuing an EOI (end of interrupt) command to the interrupt controller
(this should be the absolute last thing that you do before executing 'iret'). Due to the
history of the PC and the need to maintain compatibility with older versions, there are
two separate interrupt controllers that you need to send the EOI command to for IRQ12.
One is at address 0x20 and the other at 0xa0. The EOI is sent simply by writing the
byte '0x20' to both of these ports. Due to the extremely esoteric nature of this subject,
code is included here. Call this function just before you iret:

send_eoi:
     push ax
     mov al, 0x20
     out 0xa0, al
     out 0x20, al
     pop ax
     ret

Extra Notes/Tips

The mouse typically sends 3 bytes at a time. However, the mouse is a relatively slow
serial device. There are several milliseconds in time between the receipt of each of
these bytes. The keyboard controller will also generate a separate IRQ12 for each byte
received. This means that you can not simply read all 3 bytes within a single interrupt
call. You need to read a single byte each time and buffer them in memory until you
have 3, returning control to the interrupted process in between each byte.

The PS/2 mouse is quite sensitive on its own. The sensitivity is designed more for a
pixel level where, for example, you would have to move 1600 units to get to the other
side of the screen. When the screen is only 80 characters wide and 25 tall, the mouse
cursor moves too rapidly. You'll want to keep track of the position of the mouse as
reported from the PS/2 protocol in a higher granularity than you actually use it. For
example, you might want to keep track of the horizontal position from 0 to 1279 and
divide by 16 to determine the column on the screen that it corresponds to. Find a value
that works and hardcode it. The sensitivity does not need to be adjustable at run time
(but should be easy to change in the code if the need should arise).

During initialisation of the mouse you might find yourself in a situation where your
routine is manually waiting for events to occur (for example, the ACK byte from the
mouse in response to the initialisation command). If the keyboard controller generates
an IRQ during this time then you might not be able to read the byte (since it will be read
by your IRQ handler). If you wait until later to install the IRQ handler then things are
even worse since the byte might get read by the old IRQ handler (if one existed). There
are several approaches to solving this problem. Some things you should think about are
not enabling IRQ12 on the keyboard controller until after the mouse is initialised or
having a special case in your handler to deal with the situation.

You also have to deal with the fact that IRQ1 will be generated when you ask for the
control byte from the keyboard controller. In order to deal with this you will have to
disable interrupts at this time or write a stub IRQ1 handler that is active during the
initialisation phase of your code and restores control to the normal handler before
returning to DOS.

Modular design will help a lot here. You should implement generic functions for things
like waiting for the controller to become available (not busy). Also functions for
reading/writing to the keyboard, mouse, control byte, etc. If you do a good design then
this is a project for which it is very easy to divide up the work.

As another example, you could easily have one person handle the low-level keyboard
controller interaction and call a function 'ps2_gotbyte' every time a single byte is
received. Another person can then develop the PS2 protocol parts of the driver (ie:
interpreting what buttons are pressed and keeping track of how far the mouse moves).
You will need to document your designs well enough so that you can work independently
on different routines. The group will have to reach a consensus on how values get
passed between routines (eg: what registers will be used for parameters).

								
To top