2.5   Interrupt Service Code

Hardware interrupt handling is of key significance in real-time systems, because it is usually through interrupts that the system is informed of external events. For the fastest possible response to interrupts, interrupt service routines (ISRs) in VxWorks run in a special context outside of any task's context. Thus, interrupt handling involves no task context switch. The interrupt routines, listed in Table 2-22, are provided in intLib and intArchLib.

Table 2-22:  Interrupt Routines


Call
Description

intConnect( )  
Connect a C routine to an interrupt vector. 
intContext( )  
Return TRUE if called from interrupt level. 
intCount( )  
Get the current interrupt nesting depth. 
intLevelSet( )  
Set the processor interrupt mask level. 
intLock( )  
Disable interrupts. 
intUnlock( )  
Re-enable interrupts. 
intVecBaseSet( )  
Set the vector base address. 
intVecBaseGet( )  
Get the vector base address. 
intVecSet( )  
Set an exception vector. 
intVecGet( )  
Get an exception vector. 

For boards with an MMU, the optional product VxVMI provides write protection for the interrupt vector table; see 7. Virtual Memory Interface.

2.5.1   Connecting Application Code to Interrupts

You can use system hardware interrupts other than those used by VxWorks. VxWorks provides the routine intConnect( ), which allows C functions to be connected to any interrupt. The arguments to this routine are the byte offset of the interrupt vector to connect to, the address of the C function to be connected, and an argument to pass to the function. When an interrupt occurs with a vector established in this way, the connected C function is called at interrupt level with the specified argument. When the interrupt handling is finished, the connected function returns. A routine connected to an interrupt in this way is called an interrupt service routine (ISR).

Interrupts cannot actually vector directly to C functions. Instead, intConnect( ) builds a small amount of code that saves the necessary registers, sets up a stack entry (either on a special interrupt stack, or on the current task's stack) with the argument to be passed, and calls the connected function. On return from the function it restores the registers and stack, and exits the interrupt; see Figure 2-16.

For target boards with VME backplanes, the BSP provides two standard routines for controlling VME bus interrupts, sysIntEnable( ) and sysIntDisable( ).

2.5.2   Interrupt Stack

Whenever the architecture allows it, all ISRs use the same interrupt stack. This stack is allocated and initialized by the system at start-up according to specified configuration parameters. It must be large enough to handle the worst possible combination of nested interrupts.

Some architectures, however, do not permit using a separate interrupt stack. On such architectures, ISRs use the stack of the interrupted task. If you have such an architecture, you must create tasks with enough stack space to handle the worst possible combination of nested interrupts and the worst possible combination of ordinary nested calls. See the reference entry for your BSP to determine whether your architecture supports a separate interrupt stack.

Use the checkStack( ) facility during development to see how close your tasks and ISRs have come to exhausting the available stack space.

2.5.3   Special Limitations of ISRs

Many VxWorks facilities are available to ISRs, but there are some important limitations. These limitations stem from the fact that an ISR does not run in a regular task context: it has no task control block, for example, and all ISRs share a single stack.

Table 2-23:  Routines that Can Be Called by Interrupt Service Routines


Library
Routines

bLib  
All routines 
errnoLib  
errnoGet( ), errnoSet( ) 
fppArchLib  
fppSave( ), fppRestore( ) 
intLib  
intContext( ), intCount( ), intVecSet( ), intVecGet( ) 
intArchLib 
intLock( ), intUnlock( ) 
logLib  
logMsg( ) 
lstLib  
All routines except lstFree( ) 
mathALib  
All routines, if fppSave( )/fppRestore( ) are used 
msgQLib  
msgQSend( ) 
pipeDrv  
write( ) 
rngLib  
All routines except rngCreate( ) and rngDelete( ) 
selectLib  
selWakeup( ), selWakeupAll( ) 
semLib  
semGive( ) except mutual-exclusion semaphores, semFlush( ) 
sigLib  
kill( ) 
taskLib  
taskSuspend( ), taskResume( ), taskPrioritySet( ), taskPriorityGet( ),
taskIdVerify( ), taskIdDefault( ), taskIsReady( ), taskIsSuspended( ),
taskTcb( ) 
tickLib  
tickAnnounce( ), tickSet( ), tickGet( ) 
tyLib 
tyIRd( ), tyITx( ) 
vxLib  
vxTas( ), vxMemProbe( ) 
wdLib  
wdStart( ), wdCancel( ) 

For this reason, the basic restriction on ISRs is that they must not invoke routines that might cause the caller to block. For example, they must not try to take a semaphore, because if the semaphore is unavailable, the kernel tries to switch the caller to the pended state. However, ISRs can give semaphores, releasing any tasks waiting on them.

Because the memory facilities malloc( ) and free( ) take a semaphore, they cannot be called by ISRs, and neither can routines that make calls to malloc( ) and free( ). For example, ISRs cannot call any creation or deletion routines.

ISRs also must not perform I/O through VxWorks drivers. Although there are no inherent restrictions in the I/O system, most device drivers require a task context because they might block the caller to wait for the device. An important exception is the VxWorks pipe driver, which is designed to permit writes by ISRs.

VxWorks supplies a logging facility, in which a logging task prints text messages to the system console. This mechanism was specifically designed so that ISRs could use it, and is the most common way to print messages from ISRs. For more information, see the reference entry for logLib.

An ISR also must not call routines that use a floating-point coprocessor. In VxWorks, the interrupt driver code created by intConnect( ) does not save and restore floating-point registers; thus, ISRs must not include floating-point instructions. If an ISR requires floating-point instructions, it must explicitly save and restore the registers of the floating-point coprocessor using routines in fppArchLib.

All VxWorks utility libraries, such as the linked-list and ring-buffer libraries, can be used by ISRs. As discussed earlier (2.3.7 Task Error Status: errno), the global variable errno is saved and restored as a part of the interrupt enter and exit code generated by the intConnect( ) facility. Thus errno can be referenced and modified by ISRs as in any other code. Table 2-23 lists routines that can be called from ISRs.

2.5.4   Exceptions at Interrupt Level

When a task causes a hardware exception such as illegal instruction or bus error, the task is suspended and the rest of the system continues uninterrupted. However, when an ISR causes such an exception, there is no safe recourse for the system to handle the exception. The ISR has no context that can be suspended. Instead, VxWorks stores the description of the exception in a special location in low memory and executes a system restart.

The VxWorks boot ROMs test for the presence of the exception description in low memory and if it is detected, display it on the system console. The e command in the boot ROMs re-displays the exception description; see Tornado User's Guide: Setup and Startup.

One example of such an exception is the message:

workQPanic: Kernel work queue overflow.

This exception usually occurs when kernel calls are made from interrupt level at a very high rate. It generally indicates a problem with clearing the interrupt signal or a similar driver problem.

2.5.5   Reserving High Interrupt Levels

The VxWorks interrupt support described earlier in this section is acceptable for most applications. However, on occasion, low-level control is required for events such as critical motion control or system failure response. In such cases it is desirable to reserve the highest interrupt levels to ensure zero-latency response to these events. To achieve zero-latency response, VxWorks provides the routine intLockLevelSet( ), which sets the system-wide interrupt-lockout level to the specified level. If you do not specify a level, the default is the highest level supported by the processor architecture.


*

CAUTION: Some hardware prevents masking certain interrupt levels; check the hardware manufacturer's documentation. For example, on MC680x0 chips, interrupt level 7 is non-maskable. Because level 7 is also the highest interrupt level on this architecture, VxWorks uses 7 as the default lockout level--but this is in fact equivalent to a lockout level of 6, since the hardware prevents locking out level 7.

2.5.6   Additional Restrictions for ISRs at High Interrupt Levels

ISRs connected to interrupt levels that are not locked out (either an interrupt level higher than that set by intLockLevelSet( ), or an interrupt level defined in hardware as non-maskable) have special restrictions:

2.5.7   Interrupt-to-Task Communication

While it is important that VxWorks support direct connection of ISRs that run at interrupt level, interrupt events usually propagate to task-level code. Many VxWorks facilities are not available to interrupt-level code, including I/O to any device other than pipes. The following techniques can be used to communicate from ISRs to task-level code:

  • Shared Memory and Ring Buffers.  
    ISRs can share variables, buffers, and ring buffers with task-level code.

  • Semaphores.  
    ISRs can give semaphores (except for mutual-exclusion semaphores and VxMP shared semaphores) that tasks can take and wait for.

  • Message Queues.  
    ISRs can send messages to message queues for tasks to receive (except for shared message queues using VxMP). If the queue is full, the message is discarded.

  • Pipes.  
    ISRs can write messages to pipes that tasks can read. Tasks and ISRs can write to the same pipes. However, if the pipe is full, the message written is discarded because the ISR cannot block. ISRs must not invoke any I/O routine on pipes other than write( ).

  • Signals.  
    ISRs can "signal" tasks, causing asynchronous scheduling of their signal handlers.