|
|
|
|
The Linux Kernel Module Programming
Guide |
Prev |
Chapter 12.
Interrupt Handlers |
Next |
12.1.
Interrupt Handlers
12.1.1.
Interrupt Handlers
Except for the last chapter, everything we did in the kernel so
far we've done as a response to a process asking for it, either by
dealing with a special file, sending an ioctl(), or issuing a system call. But the job of
the kernel isn't just to respond to process requests. Another job,
which is every bit as important, is to speak to the hardware
connected to the machine.
There are two types of interaction between the CPU and the rest
of the computer's hardware. The first type is when the CPU gives
orders to the hardware, the other is when the hardware needs to
tell the CPU something. The second, called interrupts, is much
harder to implement because it has to be dealt with when convenient
for the hardware, not the CPU. Hardware devices typically have a
very small amount of RAM, and if you don't read their information
when available, it is lost.
Under Linux, hardware interrupts are called IRQ's
(InterruptRe quests). There are two types of IRQ's, short and
long. A short IRQ is one which is expected to take a very
short period of time, during which the rest of the machine will be
blocked and no other interrupts will be handled. A long IRQ is one
which can take longer, and during which other interrupts may occur
(but not interrupts from the same device). If at all possible, it's
better to declare an interrupt handler to be long.
When the CPU receives an interrupt, it stops whatever it's doing
(unless it's processing a more important interrupt, in which case
it will deal with this one only when the more important one is
done), saves certain parameters on the stack and calls the
interrupt handler. This means that certain things are not allowed
in the interrupt handler itself, because the system is in an
unknown state. The solution to this problem is for the interrupt
handler to do what needs to be done immediately, usually read
something from the hardware or send something to the hardware, and
then schedule the handling of the new information at a later time
(this is called the "bottom half") and
return. The kernel is then guaranteed to call the bottom half as
soon as possible -- and when it does, everything allowed in kernel
modules will be allowed.
The way to implement this is to call request_irq() to get your interrupt handler called
when the relevant IRQ is received. This function receives the IRQ number, the
name of the function, flags, a name for /proc/interrupts and a parameter to pass to the
interrupt handler. Usually there is a certain number of IRQs
available. How many IRQs there are is hardware-dependent. The flags
can include SA_SHIRQ to indicate
you're willing to share the IRQ with other interrupt handlers
(usually because a number of hardware devices sit on the same IRQ)
and SA_INTERRUPT to indicate this
is a fast interrupt. This function will only succeed if there isn't
already a handler on this IRQ, or if you're both willing to
share.
Then, from within the interrupt handler, we communicate with the
hardware and then use queue_work()
mark_bh(BH_IMMEDIATE) to schedule the
bottom half.
12.1.2.
Keyboards on the Intel Architecture
The rest of this chapter is completely Intel specific. If you're
not running on an Intel platform, it will not work. Don't even try
to compile the code here.
I had a problem with writing the sample code for this chapter.
On one hand, for an example to be useful it has to run on
everybody's computer with meaningful results. On the other hand,
the kernel already includes device drivers for all of the common
devices, and those device drivers won't coexist with what I'm going
to write. The solution I've found was to write something for the
keyboard interrupt, and disable the regular keyboard interrupt
handler first. Since it is defined as a static symbol in the kernel
source files (specifically, drivers/char/keyboard.c), there is no way to
restore it. Before insmod'ing
this code, do on another terminal sleep
120; reboot if you value your file system.
This code binds itself to IRQ 1, which is the IRQ of the
keyboard controlled under Intel architectures. Then, when it
receives a keyboard interrupt, it reads the keyboard's status
(that's the purpose of the inb(0x64)) and the scan code, which is the
value returned by the keyboard. Then, as soon as the kernel thinks
it's feasible, it runs got_char which
gives the code of the key used (the first seven bits of the scan
code) and whether it has been pressed (if the 8th bit is zero) or
released (if it's one).
Example 12-1. intrpt.c
/*
* intrpt.c - An interrupt handler.
*
* Copyright (C) 2001 by Peter Jay Salzman
*/
/*
* The necessary header files
*/
/*
* Standard in kernel modules
*/
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h> /* Specifically, a module */
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h> /* We want an interrupt */
#include <asm/io.h>
#define MY_WORK_QUEUE_NAME "WQsched.c"
static struct workqueue_struct *my_workqueue;
/*
* This will get called by the kernel as soon as it's safe
* to do everything normally allowed by kernel modules.
*/
static void got_char(void *scancode)
{
printk(KERN_INFO "Scan Code %x %s.\n",
(int)*((char *)scancode) & 0x7F,
*((char *)scancode) & 0x80 ? "Released" : "Pressed");
}
/*
* This function services keyboard interrupts. It reads the relevant
* information from the keyboard and then puts the non time critical
* part into the work queue. This will be run when the kernel considers it safe.
*/
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* This variables are static because they need to be
* accessible (through pointers) to the bottom half routine.
*/
static int initialised = 0;
static unsigned char scancode;
static struct work_struct task;
unsigned char status;
/*
* Read keyboard status
*/
status = inb(0x64);
scancode = inb(0x60);
if (initialised == 0) {
INIT_WORK(&task, got_char, &scancode);
initialised = 1;
} else {
PREPARE_WORK(&task, got_char, &scancode);
}
queue_work(my_workqueue, &task);
return IRQ_HANDLED;
}
/*
* Initialize the module - register the IRQ handler
*/
int init_module()
{
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
/*
* Since the keyboard handler won't co-exist with another handler,
* such as us, we have to disable it (free its IRQ) before we do
* anything. Since we don't know where it is, there's no way to
* reinstate it later - so the computer will have to be rebooted
* when we're done.
*/
free_irq(1, NULL);
/*
* Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
* SA_SHIRQ means we're willing to have othe handlers on this IRQ.
* SA_INTERRUPT can be used to make the handler into a fast interrupt.
*/
return request_irq(1, /* The number of the keyboard IRQ on PCs */
irq_handler, /* our handler */
SA_SHIRQ, "test_keyboard_irq_handler",
(void *)(irq_handler));
}
/*
* Cleanup
*/
void cleanup_module()
{
/*
* This is only here for completeness. It's totally irrelevant, since
* we don't have a way to restore the normal keyboard interrupt so the
* computer is completely useless and has to be rebooted.
*/
free_irq(1, NULL);
}
/*
* some work_queue related functions are just available to GPL licensed Modules
*/
MODULE_LICENSE("GPL");
|
|
|
|