Keyboard Support
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):
- us_keymap.inc for US qwerty keyboards
- dvorak_keymap.inc for Dvorak
Compilation and installation proceed as usual. In the next part, we will develop a kernel that allows simple interactive programs.