Your interrupt handler should not care what stack setup is in use or what the size of the kernel stack is. Always use an absolute minimum amount of stack space.
Implementing Interrupt Handlers
Perhaps not surprising, the implementation of the interrupt handling system in Linux is architecture-dependent.The implementation depends on the processor, the type of inter-rupt controller used, and the design of the architecture and machine.
Figure 7.1 is a diagram of the path an interrupt takes through hardware and the kernel.
A device issues an interrupt by sending an electric signal over its bus to the interrupt controller. If the interrupt line is enabled (they can be masked out), the interrupt con-troller sends the interrupt to the processor. In most architectures, this is accomplished by an electrical signal sent over a special pin to the processor. Unless interrupts are disabled in the processor (which can also happen), the processor immediately stops what it is doing, disables the interrupt system, and jumps to a predefined location in memory and executes the code located there.This predefined point is set up by the kernel and is the entry point for interrupt handlers.
The interrupt’s journey in the kernel begins at this predefined entry point, just as system calls enter the kernel through a predefined exception handler. For each interrupt line, the processor jumps to a unique location in memory and executes the code located there. In this manner, the kernel knows the IRQ number of the incoming interrupt.
The initial entry point simply saves this value and stores the current register values (which belong to the interrupted task) on the stack; then the kernel calls do_IRQ(). From here onward, most of the interrupt handling code is written in C; however, it is still architecture-dependent.
Figure 7.1 The path that an interrupt takes from hardware and on through the kernel.
ptg The do_IRQ() function is declared as
unsigned int do_IRQ(struct pt_regs regs)
Because the C calling convention places function arguments at the top of the stack, the
pt_regs structure contains the initial register values that were previously saved in the assembly entry routine. Because the interrupt value was also saved, do_IRQ() can extract it. After the interrupt line is calculated, do_IRQ() acknowledges the receipt of the inter-rupt and disables interinter-rupt delivery on the line. On normal PC machines, these operations are handled by mask_and_ack_8259A().
Next, do_IRQ() ensures that a valid handler is registered on the line and that it is enabled and not currently executing. If so, it calls handle_IRQ_event(), defined in
kernel/irq/handler.c, to run the installed interrupt handlers for the line.
/**
* handle_IRQ_event - irq action chain handler
* @irq: the interrupt number
* @action: the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) {
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
if (!(action->flags & IRQF_DISABLED)) local_irq_enable_in_hardirq();
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
switch (ret) { case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret = IRQ_HANDLED;
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
ptg Implementing Interrupt Handlers 125
warn_no_thread(irq, action);
break;
} /*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM) add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
First, because the processor disabled interrupts, they are turned back on unless
IRQF_DISABLED was specified during the handler’s registration. Recall that
IRQF_DISABLED specifies that the handler must be run with interrupts disabled. Next, each potential handler is executed in a loop. If this line is not shared, the loop terminates after the first iteration. Otherwise, all handlers are executed. After that,
add_interrupt_randomness() is called if IRQF_SAMPLE_RANDOM was specified during registration.This function uses the timing of the interrupt to generate entropy for the ran-dom number generator. Finally, interrupts are again disabled (do_IRQ() expects them still
ptg to be off) and the function returns. Back in do_IRQ(), the function cleans up and returns
to the initial entry point, which then jumps to ret_from_intr().
The routine ret_from_intr() is, as with the initial entry code, written in assembly.
This routine checks whether a reschedule is pending. (Recall from Chapter 4,“Process Scheduling,” that this implies that need_resched is set). If a reschedule is pending, and the kernel is returning to user-space (that is, the interrupt interrupted a user process),
schedule() is called. If the kernel is returning to kernel-space (that is, the interrupt inter-rupted the kernel itself), schedule() is called only if the preempt_count is zero. Other-wise it is not safe to preempt the kernel. After schedule() returns, or if there is no work pending, the initial registers are restored and the kernel resumes whatever was interrupted.
On x86, the initial assembly routines are located in arch/x86/kernel/entry_64.S
(entry_32.S for 32-bit x86) and the C methods are located in arch/x86/kernel/irq.c. Other supported architectures are similar.