Interrupt handling
The trap_init() function copies the top-level exception handlers to the KSEG0 vector location based on the CPU type. The interrupt handling code is contained in $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/irq.c and int-handler.S. Most systems use a dedicated interrupt controller to handle the interrupts in the system. The interrupt controller is hooked to one of the external interrupt lines in the processor. The architecture-dependent code has to be modified to fit the interrupt controller into the kernel interrupt handling.
Listing 7 shows the platform-specific interrupt initialization code. The topmost interrupt handler has to be installed using set_except_vector(). The interrupt controller that is used in the platform has to be initialized next. If remote debugging is enabled, a call to set_debug_traps() has to be made to allow any breakpoints or error conditions to be properly intercepted and reported to the debugger. In addition, a breakpoint needs to be generated to begin communication with the debugger running on the host.
Listing 7: Platform-specific interrupt initialization
static void __init myplatform_irq_setup (void)
{
set_except_vector (0, myplatform_handle_int);
// Initialize InterruptController
InterruptController_Init(IsrTable);
#ifdef CONFIG_REMOTE_DEBUG
printk (*Setting debug traps - please connect the remote debugger.\n*);
set_debug_traps ();
breakpoint ();
#endif
}
The top-level interrupt handler (Listing 8) first saves all the registers and then disables further interrupts. The CAUSE register is examined to find the source of the interrupt. If it is a timer interrupt, the corresponding ISR is called. In case it is not a timer interrupt, it checks whether an interrupt has occurred on the line connected to the interrupt controller.
The interrupt handler for the interrupt controller (Listing 9) has to get the pending interrupt vector that caused the interrupt and then execute the handler for the particular interrupt source.
Listing 8: Top-level interrupt handler
NESTED(myplatform_handle_int, PT_SIZE, ra)
.set .noat
SAVE_ALL
CLI
.set .at
mfc0 .s0, CP0_CAUSE .# get irq mask
/* First, we check for counter/timer IRQ. */
andi .a0, s0, CAUSEF_IP5
beq .a0, zero, 1f
andi .a0, s0, CAUSEF_IP2 # delay slot, check hw0 interrupt
/* Wheee, a timer interrupt. */
move .a0, sp
jal .timer_interrupt
nop . .# delay slot
j ret_from_irq
nop . .# delay slot
1:
beq .a0, zero, 1f
nop
/* Wheee, combined hardware
level zero interrupt. */
jal .InterruptController_InterruptHandler
move .a0, sp .# delay slot
j .ret_from_irq
nop . .# delay slot
1:
/* Here by mistake? This is possible,
*what can happen is that by the time we
*take the exception the IRQ pin goes low, so
*just leave if this is the case.
*/
j .ret_from_irq
nop
END(myplatform_handle_int)
Listing 9: Interrupt handler for the interrupt controller
void
InterruptController_InterruptHandler (
struct pt_regs *regs
)
{
IntVector intvector;
struct irqaction *action;
int irq, cpu = smp_processor_id();
InterruptControllerGetPendingIntVector(&intvector);
InterruptControllerGetPendingIntSrc((&irq);
action = (struct irqaction *)intvector;
if ( action == NULL ) {
printk(*No handler for hw0 irq: %i\n*, irq);
return;
}
hardirq_enter(cpu);
action->handler(irq, action->dev_id, regs);
kstat.irqs[0]irq++;
hardirq_exit(cpu);
} // InterruptController_InterruptHandler ()
The functions request_irq(), free_irq(), enable_irq() and disable_irq() have to be implemented for your target platform. request_irq() is used to install an interrupt handler for a given interrupt source. free_irq() needs to free the memory allocated for the given interrupt. enable_irq() needs to make a call to the interrupt controller function that enables the given interrupt line and disable_irq() needs to disable the given interrupt line.
Timer interrupt File $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/time.c contains the platform-dependent timer code. The Linux kernel on MIPS requires a 100Hz timer interrupt. In the MIPS, one of the timers in coprocessor 0 is programmed to generate 100Hz interrupts. The count register and the compare register together make up the timer. When active, the count register contains a free running counter. On each processor clock-tick, the value in the register increments by one. The register is reset and an external hardware interrupt is generated when the values in the count register and compare register match. After the count register is reset, it restarts to count on the next processor clock-tick. The timer interrupt service routine (ISR) needs to call the do_timer() routine. Performing a write to the compare register clears the timer interrupt.
Serial console driver
The console runs on top of a serial driver. A polled serial driver can be used for printk() (kernel debug message) functionality. The minimum functions that this driver needs to provide are the following:
- serial_console_init()-for registering the console printing procedure for kernel printk() functionality, before the console driver is properly initialized
- serial_console_setup()-for initializing the serial port
- serial_console_write(struct console *console, const char *string, int count)-for writing "count" characters