General Interrupt mechanism in Processors...
Interrupts are crucial for high performant system and we shall see how it works from a high level.
This article is not for the most basic programmers. Please read the introduction to device driver before reading this article.
Abstract
Interrupt mechanics work partially in the hardware land and partially in software. Knowledge of both enables writing efficient driver writing and mitigating and troubleshooting bugs. Keep in mind that all the code is to be written inside a Linux module.
In an overview,
- Interrupts are the signals that the CPU receives from hardware devices in order to get its attention.
- After the signal is received by the CPU, it stops whatever was being executed, saves the state of the process running into the Program Control Block(PCB).
- The CPU looks for the interrupt handler in the Interrupt Descriptor Table, which is a map of interrupt lines and interrupt descriptors.
- After mapping to the corresponding interrupt descriptor, the CPU starts executing the handler provided in the descriptor.
This article is a chapter summary of the topic Interrupts in Linux Device Drivers 3rd Edition.
Interrupt lines
Interrupt lines are a limited resource with only 15 or 16 of them. The kernel keeps an account of interrupt lines, similar to that of I/O ports. A module/device driver requests an interrupt line
before using it and releases it when finished.
Requesting an Interrupt line
A device driver can request an interrupt line by calling,
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *,struct pt_regs *), unsigned long flags, const char *dev_name,void *dev_id);
The second parameter passed to this function, irqreturn_t (*handler)(int, void *,struct pt_regs *)
is the handler function.
The interrupt handler can be installed either at driver initialization or when the device is first opened. Although, the correct place to call request_irq
is when the device is first opened, before the
hardware is instructed to generate interrupts.
All the reported interrupts are listed in the /proc/interrupts
file. Every interrupt to the system increments an integer in this file. The file /proc/stat
also registers interrupts in the systems.
Now, handling interrupts in software requires that you register your interrupt line and wait for the interrupts on that line, but sometimes you may not know beforehand which line is going to be actually used by the device to send interrupts. In that case, one may contort to probing
of interrupt lines by
//This function returns a bit mask of unassigned interrupts. The driver must pre-serve the returned bit mask, and pass it to probe_irq_off later. After this call, the driver should arrange for its device to generate at least one interrupt. unsigned long probe_irq_on(void); //After the device has requested an interrupt, the driver calls this function, passing as its argument the bit mask previously returned by probe_irq_on. probe_irq_off returns the number of the interrupt that was issued after “probe_on.” int probe_irq_off(unsigned long);
To actually use these functions in action, we are probing a serial port,
//call request_irq() first and then, mask = probe_irq_on( ); outb_p(0x10,base+2); /* enable reporting */ outb_p(0x00,base);/* clear the bit */ outb_p(0xFF,base);/* set the bit: interrupt! */ outb_p(0x00,base+2); /* disable reporting */ udelay(10); /* give it some time */ my_irq = probe_irq_off(mask); if (my_irq = = 0) { /* none of them? */ printk(KERN_INFO "short: no irq reported by probe\n"); my_irq = -1; }
To probe for all interrupts, you have to probe from IRQ 0 to IRQ NR_IRQS-1, where NR_IRQS is defined in <asm/irq.h>
and is platform-dependent.
Interrupt handling on the x86
Full code in action can be found inside the Linux kernel source tree, particularly in files arch/i386/kernel/irq.c
, rch/i386/kernel/
,
apic.carch/i386/kernel/entry.S
, arch/i386/kernel/i8259.c
, and include/asm-i386/hw_irq.h
.
The lowest level of interrupt handling can be found inentry.S
, an assembly-language file that handles much of the machine-level work. A bit of code is assigned to every possible interrupt. In each case, the code pushes the interrupt number on the stack and jumps to a common
segment, which calls do_IRQ, defined inirq.c
.
The first thing do_IRQ does is to acknowledge the interrupt so that the interrupt controller can go on to other things. It then obtains a spinlock for the given IRQ number, thus preventing any other CPU from handling this IRQ. It clears the IRQ_WAITING and then looks up the handler(s) for this particular IRQ. If there is no handler, there’s nothing to do; the spinlock is released, any pending software interrupts are handled, and do_IRQ returns.
Example of an Interrupt Handler
irqreturn_t my_interrupt(int irq, void *dev_id, struct pt_regs *regs) { // Do whatever you want here...then, wake_up_interruptible(&my_queue);/* awake any reading process */ return IRQ_HANDLED; }
Usually, however, if a device is interrupting, there is at least one handler registered for its IRQ as well. The function handle_IRQ_event is called to actually invoke the handlers. If the handler is of the slow variety, interrupts are re-enabled in the hardware, and the handler is invoked. Then it’s just a matter of cleaning up, running software interrupts, and getting back to regular work. The regular work may well have changed as a result of an interrupt (the handler could wake_up a process, for example), so the last thing that happens on return from an interrupt is a possible rescheduling of the processor.