Previous Home Next


Interrupt Handling

Interrupt handling is part of the processor-specific port, since the interrupt controller is part of the Vx115 SoC. The interrupt handling is contained in two files, vx115_intr.h and vx115_intr.c, in the arch/arm/vx115 directory. Writing the interrupt handling code has two primary components: developing code to handle system priority level (spl) changes, and writing the actual interrupt handler. The SPL management is here because it involves enabling and disabling subsets of interrupts.

System Priority Level (SPL) Handlers

The SPL handlers are used by the system to change the system priority level (no surprise...), which amounts to masking interrupts corresponding to lower-priority sources. This is the principal mechanism that NetBSD uses to synchronize access to critical data structures in drivers by code running in thread context and code running in interrupt context: by preventing interrupts, the code is protected from access by another task (preemption is disabled with interrupts disabled) and from access by an interrupt handler (if the relevant interrupt has priority at or below the current level, which is the natural situation). It also protects timing-sensitive code against unexpected delays due to background (lower-priority) interrupt activity.

A block of code is protected by enclosing it between calls to spl<level>( ) and splx( ), where there's a separate spl<level> function for each of the available system levels - splserial( ), splclock( ), splnet( ), etc. The complete list can be found on the spl man page.

For example, to protect against network-related interrupts and those at lower priority, code like the following would be used:

int oldSPL = splnet(newSPL);
/* protected code */
splx(oldCode);

This functions somewhat like the Linux functions local_irq_save( ) / local_irq_restore( ) and disable_irq( ) / enable_irq( ). It gives finer-grained control over which interrupts are disabled than local_irq_save( ), which just disables all interrupts, but provides more timing control than disable_irq, which disables just a single interrupt and thus doesn't suppress interrupts at lower priority.

The various programmer-accessible spl functions are implemented in terms of several low-level functions that are implemented for the platform.
int _splraise(int);
int _spllower(int);
void splx(int);
These functions do what you'd expect: they raise, lower, or set absolutely the system priority level by masking lower-priority interrupts and leaving those above enabled. The raise and lower functions are very similar to the splx( ) function, but first check to see if a change needs to be made. The splx( ) and _spllower( ) functions also check to see if any soft interrupts have been unmasked, as discussed below.)

There are really only two issues for the spl functions:
The first issue is hardware dependent, and usually quite straightforward: the system interrupt controller usually has a mask register that allows interrupts to be masked or unmasked - writing a '1' in a bit enables the interrupt corresponding to that bit, while a '0' disables the interrupt, or some such approach. The second issue - determining which interrupts to mask at a specific system priority level - is only a bit more involved, but leads to some somewhat ugly code.

For the ARM architecture, most platforms specify the interrupts to enable at each system priority level using an array that maps the system priority level to the specific interrupt mask to be used at that level.
int vx115_pic_spl_mask[NIPL];
int vx115_pic_spl_soft_mask[NIPL];
Here, NIPL is the numbr of system priority levels (defined in arch/evbarm/intr.h). The vx115_pic_spl_mask array then holds the mask to use for the interrupt controller corresponding to each system priority level. When the priority level is changed, the mask in the array corresponding to the new level is written to the interrupt controller:

vx115_splx(int new)
{
    ...

current_spl_level = new;
    /* enable hardware interrupts appropriate to current spl level */
vx115_write_pic(PIC_ENABLE_SET, vx115_pic_spl_mask[current_spl_level]);

/* disable hardware interrupts appropriate to current spl level */
vx115_write_pic(PIC_ENABLE_CLEAR, ~vx115_pic_spl_mask[current_spl_level]);
    ...
}
(The Vx115 interrupt controller has separate registers for enabling and disabling interrupts, hence the two write operations.)

The second array, vx115_pic_spl_soft_mask, is needed because in addition to masking hardware interrupts, the spl functions must also manage "soft" interrupts. Here, a soft interrupt isn't the same thing as an ARM software interrupt (the SWI instruction), but refers to a lower-priority routine used in conjunction with a higher-priority hardware interrupt handler. NetBSD soft interrupts are provided so a high-priority interrupt service routine can defer some lower-priority processing to avoid holding the system at a high priority level for an extended period. The device initialization code calls softintr_establish( ) to register the soft interrupt routine at one of four soft interrupt priority levels, and then the hardware interrupt handler calls softintr_schedule( ) when it wants the routine executed. Soft interrupts are treated just like hardware interrupts, being called from within the platform's interrupt dispatch code (discussed below), but after all pending hardware interrupts have been handled. 

Just as with hardware interrupts, soft interrupts are associated with a priority level, but the soft interrupt priorities are lower than any of the hardware interrupt priorities. Thus when the system is at a specific priority level, all soft interrupts at or below that level must be blocked. The vx115_pic_spl_soft_mask array values specify which soft interrupts should be enabled at each system priority level. The hardware interrupt dispatcher (discussed in more detail below) tests to see if any software interrupts are waiting to run, and executes them if any are currently enabled, i.e., unmasked according to the value in the vx115_pic_spl_soft_mask array corresponding to the current system level:

/* if there are software interrupts pending, process */
if (softint_pending & vx115_pic_spl_soft_mask[current_spl_level])
    vx115_do_pending();
Probably the best way to view the soft interrupt masks is as extensions of the hardware interrupt masks: it's as if the mask register has 64 bits instead of just 32 bits, with the lower 32 corresponding to hardware interrupts and the upper 32 corresponding to software interrupts (though only four bits are used at present). Some other evbarm platforms utilize unused bits within the hardware mask to represent software interrupts, avoiding the need for "extension" of the mask field. However, for the Vx115, all 32 bits in the hardware mask correspond to existing hardware interrupt sources, so the "extension" masks were needed. This also helps to logically separate the hardware interrupts from the software interrupts (but complicates the process of initializing the masks).

How the arrays are initialized and updated is the messy part. I used code for vx115_pic_init_interrupt_masks( ) and vx115_update_intr_masks( ) that has seemingly been propagated from system to system, and modified it to handle initialization of the dual arrays. The process of array initialization and modification is the following. Note that a '1' bit in a mask indicates that the interrupt corresponding to the bit is to be enabled, and a '0' indicates it is to be disabled.

vx115_pic_init_interrupt_masks( )

vx115_update_intr_masks( )


Interrupt Registration and Dispatching

Interrupt handlers are registered (usually in device attach routines) using vx115_intr_establish( ). The platform interrupt subsystem maintains an array of the handlers registered for each irq number. The Vx115 platform currently allows only one handler per irq; however, there's nothing preventing the use of a linked list per irq to allow the registration of multiple handlers per irq, if desired. Each handler is registered along with a "cookie", an arbitrary void* argument that's supplied to it when it's run, and the priority level for the interrupt. The vx115_intr_establish( ) routine calls vx115_update_intr_masks( ), which sets the system priority level masks to take into account the new irq at the specified priority level.

vx115_intr_establish( )


The main interrupt dispatcher, vx115_irq_dispatcher( ), is called from the low-level IRQ vector assembly routine to dispatch interrupts to the interrupt handlers corresponding to the currently asserted interrupts. The dispatcher has a simple structure.

vx115_irq_dispatcher( )


Soft Interrupt Registration and Dispatching

Soft interrupts are registered through a call to the generic ARM soft interrupt routine softintr_establish( ), which manages the list of soft interrupts. Note that the generic soft interrupt handling code specifically supports the registration of multiple handlers for each soft interrupt level. The platform-specific code therefore doesn't play a role in soft-interrupt registration.

Soft interrupts are handled much like hard interrupts: when a soft interrupt is pending and the system priority level is lower than the priority of the interrupt, any associated handlers are executed. The platform maintains a static bitmask named softint_pending to determine which soft interrupts are currently asserted, just like the hardware interrupt status register. Of course, since this is just a software variable, there's no hardware mechanism for informing the system when the soft interrupt status has changed, i.e., when a new bit has been set in the softint_pending variable. The system thus must test the variable at appropriate times to determine if there's a soft interrupt waiting:
In each of these places, there's a call to vx115_do_pending( ) to execute any asserted unmasked soft interrupt(s).

_setsoftintr( )

A soft interrupt is scheduled for execution through a call to the generic ARM soft interrupt routine softintr_schedule( ), and this calls the platform-specific routine _setsoftintr( ). For the vx115, this routine is quite simple:

vx115_do_pending( )

 The vx115_do_pending( ) routine is called to execute any pending soft interrupts. It's called at specific times, as discussed above. The routine is simple:

Initialization

The interrupt controller is really just a peripheral on the Vx115's peripheral bus (APB), so it's treated like any other peripheral as far as initialization is concerned. The interrupt controller registers a match and attach function with the autoconfig system using the standard CFATTACH_DECL macro.
CFATTACH_DECL(vx115_pic, sizeof(struct vx115_pic_softc), vx115_pic_match, vx115_pic_attach, NULL, NULL);
However, the SPL services are called early in the boot process, by the debug printf statements, before the autoconfig stuff is run. Even though the SPL functions just set the interrupt mask, this involves a register write, which uses the bus-write functions associated with the interrupt controller's parent bus, which isn't assigned until the controller's attach function is called. So there's an early-initialization function, vx115_intr_bootstrap( ),  that's called to initialize just the bus-operations field in the softc struct so the SPL functions will work (even though they'll have no effect since the interrupt controller itself isn't initialized). Note that other platforms just bypass the whole autoconfig process and "hardwire" the interrupt controller.




Previous Home Next


Comments/questions: jsevy@cs.drexel.edu