A keyboard interrupt handler

Back to main page

Emanuele Altieri (ealtieri@cs.smith.edu)
Prof. Nicholas Howe (nhowe@cs.smith.edu)
Smith College, June 2002

Contents

  1. Design of a keyboard driver
  2. How to catch an interrupt
  3. How to sleep
  4. How to wake up a process
  5. Source Code

Design of a keyboard driver

In this section we will discuss the design and implementation of a simple keyboard device driver. The driver listens for keyboard interrupts on IRQ1, and notifies processes when an interrupt is raised. User processes do not have enough privileges to catch interrupts directly, therefore a device driver is required as mediator.

The driver creates a /dev/kbd entry through which processes can receive keyboard interrupts. Upon reading from this entry, a process will sleep until an interrupt occurs. At this point, the read() function will return and deliver the key scancode and keyboard status to the process.

From the driver's point of view, the read() file operation has to put the current process to sleep in a waiting queue. The driver's interrupt handler, called when a keyboard interrupt occurs, will then awake all of the processes sleeping in the queue at once.

The source code for our keyboard device driver is provided at the end of this document.

How to catch an interrupt

The linux functions to request and free an interrupt line are defined in include/linux/sched.h (line 689):

int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), 
                unsigned long flags, const char *dev_name, void *dev_id);

void free_irq(unsigned int irq, void *dev_id);

The interrupt line is specified by the irq argument. handler is the function in charge of handling the interrupt. dev_name is a string used by /proc/interrupts to show the owner of the interrupt, and the dev_id pointer is used as a unique handler identifier for shared interrupt lines.

To find out which interrupt line is associated with the keyboard, look in /proc/interrupts:

[ealtieri@italia os]$ cat /proc/interrupts 
           CPU0       
  0:    1093906          XT-PIC  timer
  1:      39873          XT-PIC  keyboard
  2:          0          XT-PIC  cascade
  9:     705007          XT-PIC  EMU10K1, usb-uhci, usb-uhci, eth0
 12:     121507          XT-PIC  PS/2 Mouse
 14:      84402          XT-PIC  ide0
 15:          7          XT-PIC  ide1
 ...

As you can see, the keyboard is on interrupt line 1 (first column). The second column shows the number of times that the interrupt has occurred. The last column identifies the owner of the interrupt line. You can see that the keyboard interrupt is already handled by the native Linux keyboard driver.

To request the keyboard interrupt line we need to call request_irq() at load time with the following arguments:

/* request keyboard interrupt line */
request_irq(1, kbd_irq_handler, SA_SHIRQ, "kbd", (void*) &kbd_handle);

where kbd_irq_handler() is the function handling the interrupt. Notice that we passed the SA_SHIRQ flag to the function, indicating that we are willing to share the keyboard interrupt line with other handlers. This is necessary because we saw before that the keyboard line is already being handled by the native linux driver.

The interrupt handler must have the following interface:

void kbd_irq_handler(int irq, void* dev_id, struct pt_regs *regs)

irq identifies the interrupt being handled, useful if the same function is used to handle different interrupts. dev_id identifies the owner of the interrupt handler, and regs holds the value of the processor registers at the time the interrupt occurred.

How to sleep

The device driver needs to put the current process to sleep upon a read() operation. This is achieved by interruptible_sleep_on(), as shown below:

...
wait_queue_head_t kbd_irq_waitq;
...
ssize_t kbd_read(struct file *filp, char *buf, size_t count, loff_t* f_pos)
{
        interruptible_sleep_on(&kbd_irq_waitq);  /* put current process to sleep */
        ...
}

The sleep function takes one argument, the waiting queue where the process has to be put to sleep. "Interruptible" means that the process can be woken up by a signal, such as CTRL^C (SIGINT).

When interruptible_sleep_on() is called, the process that issued the read() operation is put to sleep. It can be woken up explicitly by calling wake_up_interruptible() (see next section) or by sending a signal to the process.

Before the waiting queue can be used, it needs to be initialized:

int __init kbd_init(void)
{
        ...
        init_waitqueue_head(&kbd_irq_waitq);  /* initialize waiting queue */
        ...
}

Waking up

Upon delivery of a keyboard interrupt, the device driver must awake all of the processes sleeping in the kb_irq_waitq queue. This is done by the interrupt handler function:

void kbd_irq_handler(int irq, void* dev_id, struct pt_regs *regs)
{
        ...
        wake_up_interruptible(&kbd_irq_waitq);  /* wake up processes */
}

Another task of the interrupt handler is to retrieve the key scancode and keyboard status from the keyboard. The scancode can be obtained from port 0x60, and the status from port 0x64.

void kbd_irq_handler(int irq, void* dev_id, struct pt_regs *regs)
{
        unsigned char status, scancode;
        status = inb(0x64);
        scancode = inb(0x60);
        ...
        wake_up_interruptible(&kbd_irq_waitq);
}

Source Code

The example below has been developed for the Linux 2.4.18 kernel. It requires support for the Device Filesystem, and a small hack in the kernel to allow the keyboard interrupt to be shared between different handlers. The hack involves include/asm/keyboard.h. Change the line

...
#define kbd_request_irq(handler) request_irq(KEYBOARD_IRQ, handler, 0, "keyboard", NULL)
...

to the following:

...
#define kbd_request_irq(handler) request_irq(KEYBOARD_IRQ, handler, SA_SHIRQ, "keyboard", NULL)
...

Then recompile the kernel.

keyboard.c keyboard.c A simple keyboard device driver.

Valid
   XHTML 1.0!   Powered by RedHat Linux