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:
- how to mask interrupts
- which interrupts to mask for a specified priority level
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( )
- called at system init time (from the interrupt controller attach function)
- sets up the initial soft interrupt hierarchy; this affects only
the soft-interrupt mask array, as soft interrupts are the lowest
priority in the system and masking at a soft interrupt priority level
won't affect hardware interrupts (at least not on our system)
vx115_update_intr_masks( )
- called whenever a new hardware interrupt is registered, with the irq number and associated priority level as arguments
- select appropriate array for modification: if the irq number is
below 32, it's a hardware interrupt, so the hardware interrupt array is
selected, while if the irq number is 32 or above, it's a software
interrupt and the software interrupt is selected
- clear the bit corresponding to the supplied irq
number in all the masks in the array for the specified priority
and below, and set the bit in the masks for higher priorities; in this
way the interrupt will be disabled at this and higher system priority
levels, and enabled at lower system priority levels
- do some "consistency magic": make sure that the settings at
certain priority levels include or exclude the settings at other
levels. This was copied verbatim from another platforms, and should
probably be revisited...
- reset the interrupt controller mask register to reflect the change
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( )
- store supplied handler, cookie, and priority in handler array entry for specified irq number
- call vx115_update_intr_masks(
) so SPL masks will reflect new irq at specified priority level
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( )
- save current spl (note that interrupts are enabled - not globally disabled - when the interrupt handler is called)
- while interrupt status register is nonzero
- select one of the asserted irqs
- raise the spl to the level corresponding to the selected irq
- call the handler registered for that irq
- clear the interrupt in the controller
- restore the saved spl
- if any soft interrupts are asserted, dispatch to associated soft interrupt handlers
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:
- at the end of the hardware dispatcher, since a soft interrupt may have been scheduled by one of the hardware interrupt handlers
- when the system priority is lowered, since there may be a soft
interrupt that has been "masked" because the system priority level was
too high for it to run
- when a new soft interrupt is scheduled
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:
- set the appropriate bit in a static variable indicating which soft interrupts are asserted
- if the system priority level is lower than the priority of the
associated soft interrupt, call vx115_do_pending( ) to execute the
asserted soft interrupt(s)
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:
- while softint_pending masked by the current vx115_pic_spl_soft_mask is nonzero (has bits set)
- check each of the four available soft interrupts; if it's asserted and its priority is above the system priority level
- clear the associated bit in softint_pending
- calls the handlers using the generic ARM soft interrupt function softintr_dispatch( ).
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.
Comments/questions: jsevy@cs.drexel.edu