Keyboard Support

Keyboard Support

Robbert Haarman

2010-12-11


Introduction

The simple kernel from the previous chapter can write, but not read. In this chapter we will develop a kernel that reads characters from the keyboard and displays them on screen.


IRQs

In the bootsector, we used an interrupt call to load the kernel into memory. This is an example of a software interrupt. You guessed it, there are also hardware interrupts. These are generated by the machine in response to certain events, such as keypresses.

When a component in the computer wishes to issue a hardware interrupt, which involves putting a signal on an interrupt request line. This sends an interrupt request (IRQ) to the CPU. Just like software interrupts, IRQs have numbers. The keyboard controller uses IRQ 1. The x86 interrupt controller maps this to interrupt 9, so receiving an IRQ from the keyboard controller has the same effect as executing int 9.


Interrupt Handlers

When an interrupt is triggered (either through an interrupt call or an IRQ), the CPU stops doing whatever it is doing and invokes the interrupt handler. This is just like making a function call, except that the flags are also pushed on the stack, and the return is done using iret rather than ret.

The addresses of the interrupt handlers are stored at the beginning of RAM. For each interrupt (there are 256 of them), a segment and an offset are stored. To invoke an interrupt handler, the system multiplies the interrupt number by 4 and jumps to the address stored at that location (after saving the flags and the return address).


The Keyboard IRQ

The keyboard controller generates an IRQ 1 each time a key is pressed or released. To catch these IRQs, we need to override the interrupt handler for interrupt 9. This is accomplished by overwriting the address for that handler with the address of our own service routine.


push word 0
pop ds
cli
mov [4 * KEYBOARD_INTERRUPT], word keyboardHandler
mov [4 * KEYBOARD_INTERRUPT + 2], cs
sti

Note that, while we are messing with the interrupt vector, we disable interrupts. This prevents the keyboard driver from getting called while it points to a nonsense address. The routine for handling the keyboard interrupt looks as follows:


keyboardHandler:
; save our registers!
pusha

; Read code
in al, 60h

; Ignore codes with high bit set
test al, 80h
jnz .end

; Read the ASCII code from the table
mov bl, al
xor bh, bh
mov al, [cs:bx + keymap]

; Print code
push di
mov di, [cs:cursor_pos]
push es
push word SCREEN_SEGMENT
pop es
mov [es:di], al
pop es
pop di
add word [cs:cursor_pos], 2

.end:
; Send EOI
mov al, 61h
out 20h, al
; return
popa
iret

The part that prints the code should contain no surprises. There are a couple of things to note about the general form of an IRQ handler. The pusha instruction saves all general purpose registers (ax, bx, cx, dx, si, di and bp), which are later restored to their old values by popa. This is to prevent IRQs from interfering with normal program execution (you wouldn't want the values of your registers to change without warning at random moments). The other specialty is the EOI, which stands for End Of Interrupt. When an IRQ triggers an interrupt handler, the IRQ is disabled, so that the handler is not invoked again while it's still running. The EOI is used to indicate that the IRQ can now be handled again.

Also new are the in and out instructions. These deal with I/O ports. I/O ports are used by the CPU to communicate with peripherals. Port 60h is where we read data from the keyboard controller, port 20h is for sending commands to the interrupt controller.

One final piece of explanation pertains to the keymap part. As mentioned at the beginning of this section, the keyboard controller generates an IRQ each time a key is pressed or released. The IRQ indicates that data can be read from port 60h. The data read from this port consists of scan-codes. The scan code indicates wheter a key was pressed or released, and which key it was. It does not indicate the ASCIIcode of the key, which is what we need to display. So we need to translate the scancode to an ASCII code. This is done by looking up the scancode in a table. This table is what is called the keymap. Different keyboard layouts can be accounted for simply by using a different keymap.


Files

The file keyboard_kernel.asm contains the source for a kernel incorporating the keyboard driver just developed. You also need a keymap file; choose the one that best suits your keyboard layout, rename it to keymap.inc (or change the reference in the kernel source):

Compilation and installation proceed as usual. In the next part, we will develop a kernel that allows simple interactive programs.

Valid XHTML 1.1! Valid CSS! Viewable with Any Browser