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).
Pages to are hidden for
"Working with the Keyboard Controller The keyboard controller is "Please download to view full document