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.
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.
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 */ ... }
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); }
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 A simple
keyboard device driver. |