Interrupt Handling

Name

cyg_interrupt_create, cyg_interrupt_delete, cyg_interrupt_attach, cyg_interrupt_detach, cyg_interrupt_configure, cyg_interrupt_acknowledge, cyg_interrupt_enable, cyg_interrupt_disable, cyg_interrupt_mask, cyg_interrupt_mask_intunsafe, cyg_interrupt_unmask, cyg_interrupt_unmask_intunsafe, cyg_interrupt_set_cpu, cyg_interrupt_get_cpu, cyg_interrupt_get_vsr, cyg_interrupt_set_vsr -- Manage interrupt handlers

Synopsis

#include <cyg/kernel/kapi.h>
        

void cyg_interrupt_create(cyg_vector_t vector, cyg_priority_t priority, cyg_addrword_t data, cyg_ISR_t* isr, cyg_DSR_t* dsr, cyg_handle_t* handle, cyg_interrupt* intr);

void cyg_interrupt_delete(cyg_handle_t interrupt);

void cyg_interrupt_attach(cyg_handle_t interrupt);

void cyg_interrupt_detach(cyg_handle_t interrupt);

void cyg_interrupt_configure(cyg_vector_t vector, cyg_bool_t level, cyg_bool_t up);

void cyg_interrupt_acknowledge(cyg_vector_t vector);

void cyg_interrupt_disable(void);

void cyg_interrupt_enable(void);

void cyg_interrupt_mask(cyg_vector_t vector);

void cyg_interrupt_mask_intunsafe(cyg_vector_t vector);

void cyg_interrupt_unmask(cyg_vector_t vector);

void cyg_interrupt_unmask_intunsafe(cyg_vector_t vector);

void cyg_interrupt_set_cpu(cyg_vector_t vector, cyg_cpu_t cpu);

cyg_cpu_t cyg_interrupt_get_cpu(cyg_vector_t vector);

void cyg_interrupt_get_vsr(cyg_vector_t vector, cyg_VSR_t** vsr);

void cyg_interrupt_set_vsr(cyg_vector_t vector, cyg_VSR_t* vsr);

Description

The kernel provides an interface for installing interrupt handlers and controlling when interrupts occur. This functionality is used primarily by eCos device drivers and by any application code that interacts directly with hardware. However in most cases it is better to avoid using this kernel functionality directly, and instead the device driver API provided by the common HAL package should be used. Use of the kernel package is optional, and some applications such as RedBoot work with no need for multiple threads or synchronization primitives. Any code which calls the kernel directly rather than the device driver API will not function in such a configuration. When the kernel package is present the device driver API is implemented as #define's to the equivalent kernel calls, otherwise it is implemented inside the common HAL package. The latter implementation can be simpler than the kernel one because there is no need to consider thread preemption and similar issues.

The exact details of interrupt handling vary widely between architectures. The functionality provided by the kernel abstracts away from many of the details of the underlying hardware, thus simplifying application development. However this is not always successful. For example, if some hardware does not provide any support at all for masking specific interrupts then calling cyg_interrupt_mask may not behave as intended: instead of masking just the one interrupt source it might disable all interrupts, because that is as close to the desired behaviour as is possible given the hardware restrictions. Another possibility is that masking a given interrupt source also affects all lower-priority interrupts, but still allows higher-priority ones. The documentation for the appropriate HAL packages should be consulted for more information about exactly how interrupts are handled on any given hardware. The HAL header files will also contain useful information.

Interrupt Handlers

Interrupt handlers are created by a call to cyg_interrupt_create. This takes the following arguments:

cyg_vector_t vector

The interrupt vector, a small integer, identifies the specific interrupt source. The appropriate hardware documentation or HAL header files should be consulted for details of which vector corresponds to which device.

cyg_priority_t priority

Some hardware may support interrupt priorities, where a low priority interrupt handler can in turn be interrupted by a higher priority one. Again hardware-specific documentation should be consulted for details about what the valid interrupt priority levels are.

cyg_addrword_t data

When an interrupt occurs eCos will first call the associated interrupt service routine or ISR, then optionally a deferred service routine or DSR. The data argument to cyg_interrupt_create will be passed to both these functions. Typically it will be a pointer to some data structure.

cyg_ISR_t isr

When an interrupt occurs the hardware will transfer control to the appropriate vector service routine or VSR, which is usually provided by eCos. This performs any appropriate processing, for example to work out exactly which interrupt occurred, and then as quickly as possible transfers control the installed ISR. An ISR is a C function which takes the following form:

cyg_uint32
isr_function(cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_bool_t dsr_required = 0;

    …

    return dsr_required ?
        (CYG_ISR_CALL_DSR | CYG_ISR_HANDLED) :
        CYG_ISR_HANDLED;
}
          

The first argument identifies the particular interrupt source, especially useful if there multiple instances of a given device and a single ISR can be used for several different interrupt vectors. The second argument is the data field passed to cyg_interrupt_create, usually a pointer to some data structure. The exact conditions under which an ISR runs will depend partly on the hardware and partly on configuration options. Interrupts may currently be disabled globally, especially if the hardware does not support interrupt priorities. Alternatively interrupts may be enabled such that higher priority interrupts are allowed through. The ISR may be running on a separate interrupt stack, or on the stack of whichever thread was running at the time the interrupt happened.

A typical ISR will do as little work as possible, just enough to meet the needs of the hardware and then acknowledge the interrupt by calling cyg_interrupt_acknowledge. This ensures that interrupts will be quickly reenabled, so higher priority devices can be serviced. For some applications there may be one device which is especially important and whose ISR can take much longer than normal. However eCos device drivers usually will not assume that they are especially important, so their ISRs will be as short as possible.

The return value of an ISR is normally a bit mask containing zero, one or both of the following bits: CYG_ISR_CALL_DSR or CYG_ISR_HANDLED. The former indicates that further processing is required at DSR level, and the interrupt handler's DSR will be run as soon as possible. The latter indicates that the interrupt was handled by this ISR so there is no need to call other interrupt handlers which might be chained on this interrupt vector. If this ISR did not handle the interrupt it should not set the CYG_ISR_HANDLED bit so that other chained interrupt handlers may handle the interrupt.

An ISR is allowed to make very few kernel calls. It can manipulate the interrupt mask, and on SMP systems it can use spinlocks. However an ISR must not make higher-level kernel calls such as posting to a semaphore, instead any such calls must be made from the DSR. This avoids having to disable interrupts throughout the kernel and thus improves interrupt latency.

cyg_DSR_t dsr

If an interrupt has occurred and the ISR has returned a value with CYG_ISR_CALL_DSR bit being set, the system will call the DSR associated with this interrupt handler. If the scheduler is not currently locked then the DSR will run immediately. However if the interrupted thread was in the middle of a kernel call and had locked the scheduler, then the DSR will be deferred until the scheduler is again unlocked. This allows the DSR to make certain kernel calls safely, for example posting to a semaphore or signalling a condition variable. A DSR is a C function which takes the following form:

void
dsr_function(cyg_vector_t vector,
             cyg_ucount32 count,
             cyg_addrword_t data)
{
}
          

The first argument identifies the specific interrupt that has caused the DSR to run. The second argument indicates the number of these interrupts that have occurred and for which the ISR requested a DSR. Usually this will be 1, unless the system is suffering from a very heavy load. The third argument is the data field passed to cyg_interrupt_create.

cyg_handle_t* handle

The kernel will return a handle to the newly created interrupt handler via this argument. Subsequent operations on the interrupt handler such as attaching it to the interrupt source will use this handle.

cyg_interrupt* intr

This provides the kernel with an area of memory for holding this interrupt handler and associated data.

The call to cyg_interrupt_create simply fills in a kernel data structure. A typical next step is to call cyg_interrupt_attach using the handle returned by the create operation. This makes it possible to have several different interrupt handlers for a given vector, attaching whichever one is currently appropriate. Replacing an interrupt handler requires a call to cyg_interrupt_detach, followed by another call to cyg_interrupt_attach for the replacement handler. cyg_interrupt_delete can be used if an interrupt handler is no longer required.

Some hardware may allow for further control over specific interrupts, for example whether an interrupt is level or edge triggered. Any such hardware functionality can be accessed using cyg_interrupt_configure: the level argument selects between level versus edge triggered; the up argument selects between high and low level, or between rising and falling edges.

Usually interrupt handlers are created, attached and configured during system initialization, while global interrupts are still disabled. On most hardware it will also be necessary to call cyg_interrupt_unmask, since the sensible default for interrupt masking is to ignore any interrupts for which no handler is installed.

Controlling Interrupts

eCos provides two ways of controlling whether or not interrupts happen. It is possible to disable and reenable all interrupts globally, using cyg_interrupt_disable and cyg_interrupt_enable. Typically this works by manipulating state inside the cpu itself, for example setting a flag in a status register or executing special instructions. Alternatively it may be possible to mask a specific interrupt source by writing to one or to several interrupt mask registers. Hardware-specific documentation should be consulted for the exact details of how interrupt masking works, because a full implementation is not possible on all hardware.

The primary use for these functions is to allow data to be shared between ISRs and other code such as DSRs or threads. If both a thread and an ISR need to manipulate either a data structure or the hardware itself, there is a possible conflict if an interrupt happens just when the thread is doing such manipulation. Problems can be avoided by the thread either disabling or masking interrupts during the critical region. If this critical region requires only a few instructions then usually it is more efficient to disable interrupts. For larger critical regions it may be more appropriate to use interrupt masking, allowing other interrupts to occur. There are other uses for interrupt masking. For example if a device is not currently being used by the application then it may be desirable to mask all interrupts generated by that device.

There are two functions for masking a specific interrupt source, cyg_interrupt_mask and cyg_interrupt_mask_intunsafe. On typical hardware masking an interrupt is not an atomic operation, so if two threads were to perform interrupt masking operations at the same time there could be problems. cyg_interrupt_mask disables all interrupts while it manipulates the interrupt mask. In situations where interrupts are already know to be disabled, cyg_interrupt_mask_intunsafe can be used instead. There are matching functions cyg_interrupt_unmask and cyg_interrupt_unmask_intsafe.

SMP Support

On SMP systems the kernel provides an additional two functions related to interrupt handling. cyg_interrupt_set_cpu specifies that a particular hardware interrupt should always be handled on one specific processor in the system. In other words when the interrupt triggers it is only that processor which detects it, and it is only on that processor that the VSR and ISR will run. If a DSR is requested then it will also run on the same CPU. The function cyg_interrupt_get_cpu can be used to find out which interrupts are handled on which processor.

VSR Support

When an interrupt occurs the hardware will transfer control to a piece of code known as the VSR, or Vector Service Routine. By default this code is provided by eCos. Usually it is written in assembler, but on some architectures it may be possible to implement VSRs in C by specifying an interrupt attribute. Compiler documentation should be consulted for more information on this. The default eCos VSR will work out which ISR function should process the interrupt, and set up a C environment suitable for this ISR.

For some applications it may be desirable to replace the default eCos VSR and handle some interrupts directly. This minimizes interrupt latency, but it requires application developers to program at a lower level. Usually the best way to write a custom VSR is to copy the existing one supplied by eCos and then make appropriate modifications. The function cyg_interrupt_get_vsr can be used to get hold of the current VSR for a given interrupt vector, allowing it to be restored if the custom VSR is no longer required. cyg_interrupt_set_vsr can be used to install a replacement VSR. Usually the vsr argument will correspond to an exported label in an assembler source file.

Valid contexts

In a typical configuration interrupt handlers are created and attached during system initialization, and never detached or deleted. However it is possible to perform these operations at thread level, if desired. Similarly cyg_interrupt_configure, cyg_interrupt_set_vsr, and cyg_interrupt_set_cpu are usually called only during system initialization, but on typical hardware may be called at any time. cyg_interrupt_get_vsr and cyg_interrupt_get_cpu may be called at any time.

The functions for enabling, disabling, masking and unmasking interrupts can be called in any context, when appropriate. It is the responsibility of application developers to determine when the use of these functions is appropriate.