TOC PREV NEXT INDEX DOC LIST MASTER INDEX



Writing Interrupt Handlers

As its name implies, an interrupt is caused by an event so important, current activity must be suspended so the event can be handled immediately.

An Interrupt Service Routine (ISR), or interrupt handler, is a routine written to respond to a specific event that interrupts the processor. Typically an ISR performs only the actions required to service the interrupt. Data or control information made available by the event is passed on to a task (or tasks) for further processing.

Apex supplies the mechanisms for ISRs to communicate with tasks and to control the scheduling of critical tasks.

In general, the interrupt handlers in Apex are sufficient as they are. However, if it is necessary for you to provide additional or different functionality, the following sections discuss the routines and requirements of interrupt handlers to assist you.

Apex supports three distinct but complementary methods for writing an interrupt handler. These methods are discussed in the sections:

Following this are discussions of runtime implications of interrupt handling:

Finally, aspects of interrupt handling specific to individual architectures are described:


Ada 95 Protected Procedure Interrupt Handlers

Introduction

 Ada 95 introduced a standardized method for handling interrupts using protected objects (also new to Ada 95). This facility is described in LRM C.3; it also affects the interpretation of priority ceiling locking as described in LRM D.3. In essence, protected procedures can be invoked as interrupt handlers. This section describes the Apex implementation of Ada 95 protected procedure handlers and configuration options affecting it.

Overview

The compiler generated code and runtime provide an environment for each protected object which includes a data structure for each attached handler.

The user configuration provides a set of Rational interrupt wrappers and an environment for implementing priority ceiling and attaching protected procedure handlers to interrupts.

Figure 16 shows this environment. The following sections will elaborate on each of the components of the system.

Runtime Environment for Interrupt Handling

Figure 16 Runtime Environment for Interrupt Handling

Protected Object Data

There is one protected object data record per protected object. It contains the entry queue head, mutex, etc.

Protected Handler Array

There is one protected handler array per protected object. Each protected procedure named in an Interrupt_Handler or Attach_Handler pragma consumes one entry in the array.

Protected Procedure

The code generated for a protected procedure is a procedure with runtime calls inserted for entry into and exit from the associated protected object.

Handler_Table

This is an array of pointers to Handler_T's (see Protected Object Environment for Interrupt Handling). These provide sufficient information to allow the runtime to manage the attachment of interrupts to protected procedures, and to call the attached protected procedures in an interrupt context.

Wrapper

This procedure is called directly by the hardware, e.g. by placing its address in an interrupt vector table. It sets up the environment for the invocation of the appropriate protected procedure. The wrappers are configurable (see Apex_Systems_Programming.Interrupts_Wrappers).

Interrupt Vector Table

This is a table of subprogram addresses. These subprograms are invoked in response to interrupts. In Apex, the management of this table is the responsibility of the kernel; the protected procedure interrupt handler facility is layered over this kernel support. See Interrupt Handling for a description of kernel interrupt handling by platform.

Ada Priority Ceiling

Ada supports a priority ceiling interrupt model. Each interrupt priority n+1 masks all of the interrupts at priority n and, optionally, some additional interrupts. This can be implemented efficiently using a priority level register in the processor (for example, M68k), or interrupt controller (for example, i8259), or an interrupt mask register in the processor (for example, MIPS).

The Apex kernel provides a single facility, the priority ceiling mutex, to handle priority ceilings at both non-interrupt and interrupt levels. The mapping between interrupt priority levels and interrupt masking is done either in the Ada kernel (M68000 Family) or in the kernel configuration package V_Krn_Conf.V_Ext_Intr_Support (all other targets). In the latter case the mapping is configurable; kernel configuration is described in the "Configuration Guide for Rational Exec" under "Kernel Configuration Parameters".

Protected Object Environment for Interrupt Handling

For a protected type, the compiler generates code for each protected operation (subprogram or entry) and a protected object data record type to represent runtime object state. When a protected object is created at runtime, an object of this type is allocated and initialized by a combination of compiler generated and runtime system code.

The protected object data record contains a block of information managed by the runtime and an array of V_I_Handler.Handler_T, one for each pragma Attach_Handler or unique pragma Interrupt_Handler.

The fields Prot, Procedure_Address, and Wrapper are of interest to the user configuration. Prot and Procedure_Address identify the protected record and the protected procedure for the interrupt. These are used by the procedure at Wrapper to invoke the protected procedure interrupt handler.

Wrapper is inserted directly into the interrupt vector table. The default wrapper is the relatively expensive Interrupts_Handler_Wrapper_Float_True_Lock_True, but other wrappers can be specified.

User Configuration

The user configuration specific to Ada protected procedure interrupt handlers is contained in packages in the systems_programming.ss subsystem, part of an Apex Board Support Package (BSP). This section describes configuration options provided by each such package. The user configuration contains the Handler_Table, wrappers, and interrupt blocking based on Ada priority ceiling.

Ada.Interrupts.Names

This package, described in LRM C.3.2, contains constants of type Ada.Interrupts.Interrupt_Id for the interrupts that are supported by the implementation. These names can be used to specify interrupts in Attach_Handler pragmas and calls to subprograms in Ada.Interrupts, as described in LRM C.3.

In the Apex Native (self-hosted) and Apex Embedded for LynxOS this package cannot be configured, and the "interrupts" that it describes are actually POSIX signals. As such, it defines all signals not reserved by the runtime system.

In Apex Embedded products (other than LynxOS), this package is configurable. The default version supports a small number of commonly available interrupts, even in a Rational supported BSP that allows more to be handled. This package should be expanded to include more board-specific interrupt definitions if they are to be attached to protected procedure interrupts. This is usually (and by default) done by defining the required values in the Apex specific configuration file Apex_Systems_Programming.Interrupts_Id and importing them here.

Apex_Systems_Programming.Interrupts_Id

This package defines the type Interrupt_Id, a scalar type that defines the exact number of interrupts allowed in the environment. For Apex Native (and Embedded for LynxOS) products this defines the range of possible signals supported by the underlying POSIX implementation. For other embedded platforms it defines the number of entries in the interrupt vector table. This type is generally used as the LRM defined Ada.Interrupts.Interrupt_Id.

In Apex Embedded products, this package also defines constants of type Interrupt_Id to provide the values in the LRM defined Ada.Interrupts.Names package. This keeps most target-specific details out of the LRM defined packages.

Apex_Systems_Programming.Interrupts_Id also specifies default reserved interrupts (as defined LRM C.3). Interrupts_Ids is an array of Interrupt_Id values and Interrupts_Ids_Reserved is a boolean specifying whether the interrupts listed in Interrupts_Ids are reserved. The reserved state of all other interrupts is set to (not Interrupts_Ids_Reserved). For example, for an Apex Native product the values:

would indicate that the signals Sigill, Sigfpe, ... are all reserved and any other signals are not reserved.

For an Apex Embedded BSP, it is normally best to reserve all interrupts except those that the application is going to handle. For example:

Apex_Systems_Programming.Interrupts_Wrappers

Wrapper procedures are the ones actually installed in the interrupt vector table. They save the state of the machine and set up for the execution of the protected procedure. They then look up the appropriate protected procedure in the Handler Table (see Figure 4) and call it.

The default of the Apex_Systems_Programming.Interrupts_Wrappers package typically provides four wrappers, listed below from the most general (and therefore functionally expensive) to the most restrictive (and least expensive):

There are two capabilities: Lock True/False and Float True/False. These wrappers will be sufficient for most applications, but they can be modified, or new wrappers written, if application specific features are required.

The Lock_True handlers mask interrupts during the execution of the protected procedure as specified by the interrupt priority ceiling of the associated protected object. Some hardware does this masking automatically before invoking an interrupt handler, in which case one of the more efficient Lock_False wrappers can be used.

The Float_True handlers save the floating point context of the interrupted task to prevent the protected procedure handler from overwriting it. If the protected procedure handler (including any subprograms it invokes) does not perform floating point operations, one of the more efficient Float_False wrappers can be used.

The wrapper used to invoke a protected procedure handler can be specified using the Interrupt_Handler_Wrapper pragma:

where Handler_Name is the name of the protected procedure and Wrapper is an access value pointing to the wrapper procedure. Wrapper can usually be specified using the 'Access attribute (e.g. Interrupts_Handler_Wrapper_Float_False_Lock_False'Access) or as a value of type Ada.Interrupts.Rational.Wrapper. If a protected procedure handler is not named in an Interrupt_Handler_Wrapper pragma, Interrupts_Handler_Wrapper_Float_True_Lock_True is used. This will work for most applications, but may not be as efficient as possible.

Ada interrupt priority ceiling locking (see above) should be configured to impose an implicit priority on interrupts. A thread (task or interrupt handler) executing at a given interrupt priority should mask one or more interrupts, and threads executing at a higher interrupt priority should block at least those interrupts and possibly some additional "higher priority" interrupts. The priority of an interrupt is the lowest interrupt priority that masks it.

With this implementation of interrupt priorities, a protected object can assure that only one thread will be in a protected object by assigning it an interrupt ceiling priority at or above the highest priority interrupt that it can handle. Once an interrupt handler starts, it won't be preempted by interrupts at the same or lower priority because they are masked. It can be preempted by a higher priority interrupt handled by an object with a higher ceiling priority, but the higher priority interrupt thread can't disturb the lower priority handler since ceiling locking rules prevent it from entering a lower priority object.

When using Lock_True wrappers, this masking is done in software. However, some hardware platforms can be configured to do this kind of prioritized masking automatically; for example, MC680x0 processor interrupts are prioritized; before the hardware invokes the handler for an interrupt in disables all interrupts at the same or lower priority. This allows a Lock_False wrapper to be used for the highest priority interrupts handled by a given protected object. This is illustrated in Ada Interrupt Handler Example.

Other Related Packages

There are other packages used to implement Ada protected procedure interrupt handlers in the systems_programming.ss subsystem. All such packages are children of either Ada.Interrupts or Apex_Systems_Programming. Most of these are for internal runtime use, but one operation, Ada.Interrupts.Rational.Set_Reserve_Handler, can be useful in application code:

This function sets the reserved status of the specified interrupt to New_Value and returns the original status. This can be helpful if the available interrupts are not known at compile time. The use of this function is illustrated in the examples provided with Apex in the ada_examples.ss/<view>/interrupts directory.

Ada Interrupt Handler Example

 In the following example there are three interrupt sources IS1, IS2 and IS3. There are also three interrupt priority levels 255, 256 and 257. The interrupt logic has been arranged so that, before entering a handler for a given interrupt, the hardware masks that interrupt and all other interrupts at the same or lower priority. Interrupt priorities are assigned according to the following table:

Priority
Interrupts Masked
255
IS1
256
IS1 IS2
257
IS1 IS2 IS3

IS1 has priority 255, IS2 priority 256, and IS3 priority 257.

When the protected object Protected_Example is elaborated the Interrupt_Ids and Interrupt_Ids.Reserved variables are examined to determine if IS1 or IS1 are reserved. Program_Error is raised if either are.

V_Krn_Conf.V_Ext_Intr_Support.Interrupts_Priority_Disable_Status is then called to map the ceiling priority 256 to an Intr_Status_T value that masks IS1 and IS2 to be used in the interrupt mutex associated with the protected object. Locking this mutex masks these interrupts using V_Ext_Intr_Support.Interrupts_Set_Status.

The old handlers are stored away and the new ones installed. Apex_Systems_Programming.Interrupts_Attach.Isr_Attach is then called to install the wrappers into the appropriate places in the interrupt vector table.

The ceiling of 256 indicates that all protected operations on this object will be executed with IS1 and IS2 masked. Since that's done automatically by the hardware for IS2, the Lock_False wrapper can be used to invoke Handler_Is2. However, the hardware does not lock IS2 when invoking an IS1 handler, which would break the ceiling locking model. If Lock_False were used for Handler_Is1, its execution could be preempted by Handler_Is2, resulting in two threads of control executing in a protected object at the same time, contrary to Ada semantics. To prevent this, a Lock_True wrapper is used to mask both IS1 and IS2 (by locking the interrupt mutex associated with Protected_Example) before invoking Handler_Is1.

The use of Float_True wrappers in this example assumes that the bodies of these handlers perform floating point operations. If it is known that they never use the floating point hardware, the Float_False versions of these wrappers can be used.

Note: Platform dependent application examples can be found in ada_examples.ss/<view>/interrupts.


Task Interrupt Entries as Interrupt Handlers

Section J.7.1 in the Ada LRM defines the syntax and semantics for an interrupt entry. This is an obsolescent feature in the Ada 95 standard; newly written programs should use Ada 95 protected procedure interrupt handlers (above) instead.

The Apex implementation is defined there with the following interpretations and restrictions:

ada_krn_defs.1.ada has the following function for initializing the interrupt entry:

Interrupt Entry Details (Signal ISR)

When an interrupt entry is declared in a normal task, the compiler generates an ISR that looks like this:

During elaboration of the task specification, the starting address of this generated ISR is put into the Interrupt Vector Table using the V_Interrupts.Attach_Isr service. As mentioned above, the value in the address clause that defines the interrupt entry is used as the vector parameter to Attach_Isr. Also during elaboration, a call is made to the kernel's V_I_Sig.Create_Signal service to create a signal to associate with this interrupt entry.

When an interrupt occurs for this entry, the kernel configuration calls the above generated ISR which calls V_Usr_Conf.V_Signal_Isr.

The Sig_Header is a data structure built by the compiler. The type that describes this header data structure is in the file v_i_sig.1.ada found in the rational.ss subsystem. The name of the type is V_I_Sig.Isr_Header.

The code for V_Signal_Isr is included as part of v_usr_conf.2.ada, the configuration file for the user library. This file may be found in the board_conf directory. V_Signal_Isr is a customized version of the generic ISR wrapper code. After doing the ISR wrapper steps, it calls the RTS library routine, V_I_Sig.Post_Signal, which posts the interrupt entry's signal, that is, queues an entry call to the interrupt entry.

The source for V_Signal_Isr has a clearly marked area for adding any board specific logic that must be executed before the entry's signal is posted.

The following example shows an Ada interrupt entry and a conventional ISR, each of which simply increment a global variable. This example contrasts the difference in the way your Ada code would look for each of the two interrupt handling methods.

Example Contrasting an Ada Interrupt Entry with an ISR


Classic Runtime System Interrupt Service Routines (ISR)s

Functional Overview of an ISR

The classic runtime system interrupt services are to attach (install) and to detach (remove) an ISR from the Interrupt Vector Table (or Exception Vector Table or Interrupt Descriptor Table).

There are two functional parts to servicing an interrupt.

Device Independent Functions

First are device independent actions: saving the interrupted state of the processor, setting up the processor for servicing the interrupt, and interacting with VADS Exec.

Differences in the way some targets handle interrupts and additional wrappers are discussed in:

Device Dependent Functions

The second part of servicing an interrupt requires that the processor interact directly with the interrupting device. I/O or control information from the device is typically passed on to a task for further, non-interrupt processing. Since interrupt handling is done at a very high priority, usually only those actions required to actually service the interrupt are performed in the ISR itself. This portion of an interrupt service routine is device specific and is discussed in the target specific sections:

ISR Generic Wrappers

Apex supplies generic ISR procedures referred to as "wrappers." These wrappers surround (wrap) the device specific ISR subprogram with code that takes care of the processor and VADS Exec specific (device independent) interrupt handling actions.

There are three wrapper routines declared in the V_Interrupts package.

Each wrapper is instantiated with a device specific interrupt handler subprogram, My_Handler in this example:

Here, My_Handler takes care of servicing interrupts for a specific device. The instantiated subprogram, My_Isr in the example, is a complete ISR; its address is placed in the Interrupt Vector Table.

Note: When you instantiate one of the wrappers, you supply the device specific interrupt handler subprogram, My_Isr. Do not make this subprogram inline (using pragma Inline) unless all the registers used by the interrupt handler subprogram are saved by the ISR wrapper.

(68k)The wrappers provided with VADS Exec save only D0, D1, D2, D3, A0 and A1.

Inline handlers must also avoid using stack local variables unless a stack frame is established by the wrapper. The wrappers provided with VADS Exec use pragma Implicit_Code(Off), which suppresses the generation of code setting up the subprogram's stack frame.

The source code of each wrapper routine is supplied as part of VADS Exec in the file v_interrupts.2.ada. The source for interrupt handling is in v_krn_conf.2.ada in the board's krn_conf.ss view. This code is commented. Use these routines as they are or make your own copy and modify it as necessary.

VADS Exec Wrappers: Isr, Fast_Isr and Float_Wrapper

These three routines from the V_Interrupts package are designed to be the primary ISR wrappers used with VADS Exec. They are optimized for very fast performance.

Use a parameterless subprogram from your application program as the interrupt handler. Because the interrupt handler is part of the application program, sharing data between the application and the ISR is straightforward: they both reference the same buffers and structures by name.

Fast_Isr is faster but less functional than Isr. The two additional functions Isr performs are switching to a common interrupt stack and setting up for stack limit checking. Stack limit checking code is generated by the compiler to check that you are not overflowing the call stack. In order for this code to work, it needs to know the bottom-of-stack value, Isr sets this up but Fast_Isr does not.


Warning: When using the Fast_Isr wrapper for any of your ISRs, all of the code executed by any of your ISRs must be compiled with runtime checks suppressed. This can be done with pragma Suppress(All_Checks). Because the Fast_Isr wrapper does not set up stack limit checking, nested interrupts that occur within the Fast_Isr handler are not able to perform these checks correctly.

Float_Wrapper is not a complete wrapper for an interrupt handler. It wraps an interrupt handler with the logic necessary to save and restore the state of the floating point hardware. The resulting instantiation can then be made into a complete interrupt handler by wrapping it with either Isr or Fast_Isr. For example:

In this example, My_Handler is a device specific interrupt handler that does floating point calculations to correctly handle an interrupt. First My_Handler is wrapped so it saves and restores the state of the floating point hardware. The resulting procedure, Float_Handler is then made into a full-fledged interrupt handler by wrapping it with Isr.

Float_Wrapper is only needed if the interrupt handler itself is going to perform floating point calculations. In all other cases, the kernel takes care of saving and restoring the floating point hardware state on task switches.

Calling VADS Exec Functions from an ISR

As mentioned previously, many real-time systems spend as little time as possible in an ISR. Only the processing required to directly service the interrupt is performed there. All I/O data and control information made available by the external device is passed on to a task for processing.

One of the functions of VADS Exec is to make available mechanisms to synchronize ISRs and tasks. Many of the VADS Exec calls can be made from within an ISR, these are listed below. These services are performed identically whether they are called from an ISR or from a task.

As an example, suppose that a task is in charge of managing a UART. It may be waiting at a VADS Exec mailbox for input from the serial port. When a character is received, the serial port hardware generates an interrupt which is handled by the serial port ISR. The ISR reads the character from the port and sends it to the mailbox as a message (V_Mailbox.Write_Mailbox). Since a message has now arrived at the mailbox, the task transitions from waiting to ready. Note, however, that if this newly readied task is of higher priority than the interrupted task, the task switch does not occur until the end of interrupt processing, in the call to Isr_Complete.

There are two primary paths into the Apex embedded kernel. One is by calling any of the functions in the VADS Exec interface (the packages V_Interrupts, V_Xtasking, V_Semaphores, etc.). The other is through certain Ada language constructs, which may enter the kernel implicitly (delay, rendezvous, etc.).

In the following four tables, we list VADS Exec functions that:


Interrupt Latency Times

A subset of the Apex embedded kernel services can be called from within an interrupt handler. There are certain critical regions of code in these routines that cannot be interrupted, unless it can be guaranteed that the interrupt handler does not enter the kernel. This applies to handlers that execute in an interrupt context, that is, Ada protected procedure handlers and classical ISRs. It does not apply to the sequence of statements associated with the accept of an interrupt entry, since this code executes in a task context.

To protect these critical regions, the kernel disables external interrupts, then executes the critical region and then resets the interrupt priority level.

Interrupt latency for an interrupt priority level is the amount of time that interrupts for that level (or a higher level) are disabled. This is sometimes also referred to as the Interrupts-off time. Interrupt latency numbers are very important to real-time systems programmers; latency must be considered when calculating worst-case response to interrupts.

The following are high level descriptions of each critical region in the Apex embedded kernel where interrupts or traps are disabled.

Pending_Ring_Add

This is a low level routine called when an ISR requests a non-reentrant kernel service. Some examples are a resume task suspended on a semaphore, a resume task suspended with V_Xtasking.Task_Suspend or posting an alarm. The request is added to a circular ring buffer so it can be handled at the completion of the ISR and after the kernel completes execution of the current kernel service (if any). This allows the majority of the kernel code to be non-reentrant and executed with interrupts enabled.

Since ISRs can be nested, interrupts must be disabled when adding to the ring buffer. However, interrupts do not need to be disabled when the kernel removes a pending service request from the ring buffer since this logic is non-reentrant and has serialized access.

Enter_From_User

Entered from the trap table with interrupts disabled. Interrupts remain disabled until the kernel discards the trap window and gets back into the caller's window.

Restore_Context

Interrupts are disabled when restoring the context of an interrupted task.

Isr_Complete

Interrupts are disabled around code deciding if the kernel needs to or can be entered to process a pending task resume or pending signal.


Program Deadlock

The Apex runtime is able to detect when a multitasking program is unable to make further progress, that is, when it is deadlocked. In the absence of interrupt handlers, it will terminate a program with the message "Deadlock detected" when the following conditions are satisfied:

However, even if these conditions are satisfied, it may still be possible for a waiting task to be awakened by an interrupt. To accommodate interrupt entries, attached ISRs, and protected procedure handlers, any of the following conditions inhibits a program from exiting:

The Exit_Disabled flag can also be used by the application to inhibit deadlock detection if it knows of other external mechanisms that may wake tasks in an apparently deadlocked system.


Writing Interrupt Handlers for PowerPC

ISR Generic Wrappers on PowerPC

Isr, Fast_Isr, and Float_Wrapper

The generic wrappers, Isr, Fast_Isr, and Float_Wrapper do not do much on the PowerPC because the device independent portion of servicing an interrupt, the wrapping function, is taken care of by V_Level1_External, V_Level1_Normal, or V_Level1_Program.

Kernel ISR Wrapper

The Apex embedded kernel comes with an ISR generic wrapper. The kernel's ISR wrapper is supplied for the timer interrupt handler that must be present as part of the kernel program. You can use this wrapper to build other ISRs that link with the kernel; however, communication of information between the ISRs linked with the kernel and applications tasks is much more difficult than using the wrappers from V_Interrupts and installing them dynamically using V_Interrupts.Attach_Isr.

This wrapper is declared in V_Krn_Conf_I, in the kernel configuration file, v_krn_conf.2.ada.

As with Isr, Fast_Isr, and Float_Wrapper, this wrapper is instantiated with a device specific interrupt handler program.

VADS Exec Parameter Wrappers

The generic procedures Parameters_Isr and Parameters_Float_Wrapper are also available on the PowerPC. These routines pass the Vector_Id and the context as parameters. These can be useful when having the same interrupt handler handle more then one interrupt vector.

Requirements of an ISR on the PowerPC

In this section, the steps performed by V_Interrupts.Isr and V_Interrupts.Fast_Isr are listed. Since most of the trap and ISR wrapper logic is contained in the kernel configuration, these routines do not need to do much. Before an ISR is entered the following has happened:

Upon return, processing completes by jumping to the kernel service, V_Krn_Conf_I.Complete_Interrupt.

Note: On the PowerPC Fast_Isr is identical to Isr, it's not any faster.

V_Interrupts.Isr and V_Interrupts.Fast_Isr perform the following steps:

1 . Update the exception unwinding stop address to point to the top of the ISR instead of the top of the program. Apex supports the raising and propagation of an Ada exception in the subprograms called from an ISR. This step is only here to inhibit an Ada exception from being propagated back to the interrupted program. This step can be eliminated if no exceptions are raised or the ISR subprograms have a handler for all Ada exceptions. If no exception handler is found, then, the program exits printing the message:

2 . Replace the stack limit with the interrupt stack limit.

3 . Service the interrupt. In this step the user supplied subprogram is called to perform device specific actions.

4 . Restore exception unwinding stop address. The stop address before the interrupt occurred is restored.

5 . Replace the stack limit. The stack limit before the interrupt is replaced.

6 . Clear the return value to indicate normal interrupt completion. The Isr and Fast_Isr generics are declared in the CPU independent VADS Exec V_Interrupts package as having a parameterless procedural interface, while in fact on the PowerPC, an ISR is called as a function as follows:

Both Isr and Fast_Isr are written in machine code. By using machine code, Ada's external view as a parameterless procedure is satisfied and by disabling Implicit_Code (pragma Implicit_Code(Off)) it's viewed internally as a function having two parameters.

This can be changed to return a nonzero value to persuade V_Interrupt_Trap_Handler to call another ISR or to jump to a trap handler. The Interrupt Vector Table contains a description of the conditions where this would be beneficial.

Interrupt Stack Switching

When an interrupt occurs, a register save area is pushed on the current stack. For the first interrupt (interrupt depth = 0), the kernel configuration switches to the common interrupt stack. All subsequent trap or interrupt processing uses this interrupt stack. A nested interrupt pushes its register save area on the interrupt stack. Therefore, an application task only needs to reserve space on its stack for a single register save area for interrupt handling regardless of the expected depth of interrupt nesting.

Installing an ISR in the Interrupt Vector Table (PowerPC)

The Interrupt Vector Table (IVT) is where the kernel's general trap handlers look to find the address of every interrupt handler. When kernel initialization is complete, before starting the application program, every entry in the IVT points to a valid interrupt handler.

Most point to the kernel's default ISR which makes a call to TDM which signals the host debugger about the unexpected trap.

Rational Software Corporation recommends using the procedure V_Interrupts.Attach_Isr from the VADS Exec interface to put new ISRs into the IVT. Attach_Isr is called from the application program and is used to install an interrupt handler that is part of the application.

A big advantage of making subprograms from the application into interrupt handlers is the ease with which these routines communicate with the application. Both the ISR and routines from the application can reference the same static global data structures simply by using the name of Ada objects (for example, buffers and variables in library level packages).

Contrast this with interrupt handlers linked with the kernel. Because the kernel is a separately linked program, it does not share any code with the application. Therefore, for an ISR that is linked directly with the kernel to communicate with the application, both the application and the ISR must agree on an address in memory where this communication takes place. If this address changes, both the kernel and the application must be recompiled and relinked.

If you use Ada interrupt entries as interrupt handlers, these interrupt handlers are installed into the IVT automatically during elaboration of the task specification.


Writing Interrupt Handlers for MIPS

ISR Generic Wrappers on MIPS

Isr, Fast_Isr, and Float_Wrapper

On the MIPS, the device independent portion of servicing an interrupt, saving the interrupted state of the processor, setting up the processor for servicing the interrupt and interacting with VADS Exec is taken care of by the generalized interrupt (MIPS exception) handler, V_Gen_Except.

When the interrupt exception arrives, the CPU transfers control to the generalized trap handler, V_Gen_Except, by a jmp instruction installed at the General exception vector (address 16#8000_0080# (MIPS I Family) or 16#8000_0180# (MIPS II/III/IV/64 Family)). After executing the preamble code, V_Gen_Except transfers control to the start of the attached ISR.

Since these wrapper functions have been moved to V_Gen_Except that calls the ISR, Isr and Fast_Isr are not very useful on the MIPS. The Float_Wrapper generic, however, contains real code for saving and restoring the floating point state.

Kernel ISR Wrapper

The Apex embedded kernel comes with an ISR generic wrapper. The kernel's ISR wrapper is supplied for the timer interrupt handler that must be present as part of the kernel program. You can use this wrapper to build other ISRs that link with the kernel; however, communication of information between the ISRs linked with the kernel and applications tasks is much more difficult than using the wrappers from V_Interrupts and installing them dynamically using V_Interrupts.Attach_Isr.

This wrapper is declared in V_Krn_Conf_I, in the kernel configuration file, v_krn_conf.2.ada.

As with ISR, Fast_Isr, and Float_Wrapper, this wrapper is instantiated with a device specific interrupt handler program.

Requirements of an ISR on MIPS

In this section we walk through the steps performed by V_Interrupts.Isr and V_Interrupts.Fast_Isr. Since most of the exception and ISR wrapper logic is contained in V_Gen_Except, these routines do not need to do much. Before an ISR is entered the following has happened:

Upon return, V_Gen_Except looks at the Next_Id field in the exception frame. If Next_Id was not changed from No_Vector_Id, V_Gen_Except completes interrupt processing by jumping to V_Restore_Ef to restore the processor context. Otherwise, V_Gen_Except redispatches to the Next_Id ISR. If the Next_Id points to an Untouchable vector, then the Next_Id is handled in TDM. The Next_Id field allows multiple devices in either the user, kernel or TDM programs to share the same interrupt.

Note: On the MIPS, Fast_Isr is identical to Isr; it is not any faster.

V_Interrupts.Isr and V_Interrupts.Fast_Isr perform the following steps.

1 . Update the exception unwinding stop address to point to the top of the ISR instead of the top of the program.

Apex supports the raising and propagation of an Ada exception in the subprograms called from an ISR. This step is here to inhibit an Ada exception from being propagated back to the interrupted program. This step can be eliminated if no exceptions are raised or the ISR subprograms have a handler for all Ada exceptions. If no exception handler is found, the program exits printing the message:

2 . Service the interrupt

In this step, the user supplied subprogram is called to perform device specific actions.

3 . Restore exception unwinding stop address. The stop address before the interrupt occurred is restored.

4 . The Isr and Fast_Isr generics are declared in the CPU independent VADS Exec V_Interrupts package as having a parameterless procedural interface. While in fact on the MIPS, an ISR is called as a parameterized procedure as follows:

ISRs and the MIPS Interrupt Mask

The MIPS I chips have 8 different interrupts (6 external, and 2 internal). The interrupts that are currently enabled are specified by the Intr5..0, and Sw1..0 bits in conjunction with the IEc bit in the CP0 status register. If Intr5..0 and Sw1..0 bits are zero or the IEc bit is zero, all interrupts are disabled. By Apex convention, interrupt enabling and disabling is done with the Intr5..0 and Sw1..0 bits only; while in the user program, IEc is always set to one.

The MIPS II/III/IV/64 chips have 8 different interrupts (6 external, and 2 internal). The interrupts that are currently enabled are specified by the Intr5..0, and Sw1..0 bits in conjunction with the IE bit in the CP0 status register. If Intr5..0 and Sw1..0 bits are zero or the IE bit is zero, all interrupts are disabled. By Apex convention, interrupt enabling and disabling is done with the Intr5..0 and Sw1..0 bits only; while in the user program, IE is always set to one.

With the V_Decode_Interrupt routine in V_Krn_Conf, only higher priority interrupts are enabled when the ISR is entered. Apex defines the highest priority to be Intr7 and the lowest to be Sw0. This priority interpretation is modified by changing the code in V_Decode_Interrupt.

When hardware asserts an interrupt line, in order for it to be recognized, it must be enabled in the CP0 status register. If it is enabled, then the CPU is vectored to V_Gen_Except where the "exception" is decoded as being an interrupt, higher priority interrupts are enabled, and dispatches to the ISR corresponding to the interrupt. Usually the ISR executes entirely with the new interrupt mask and after it completes, the previous interrupt mask is restored (by VADS Exec as part of the Isr_Complete routine).

Sometimes lower level interrupts are pending and it is not desirable to block these interrupts during the total length of the ISR execution. An ISR enables lower priority interrupts by simply altering the interrupt mask in the CP0 status register. This permits devices with interrupts at a lower priority to get their interrupts serviced.

Conversely, it may be desirable for an interrupt handler to disable some or all higher priority interrupts. The Apex Embedded kernel does this to protect certain critical regions of code from being interrupted. During these critical regions, the kernel must disable interrupts that start up an ISR which makes calls to VADS Exec. When a critical region is going to be entered, the interrupt mask is loaded into the CP0 status register with V_Krn_Conf.Disable_Mask.

The value of Disable_Mask must be such that it masks out all interrupts which make calls to VADS Exec. Any ISR not masked by Disable_Mask must not make any calls to VADS Exec (explicitly or implicitly). Calls to VADS Exec by one of these ISRs which occur while VADS Exec is in a critical region would result in corrupted data structures.

Interrupt latency for an interrupt priority level is defined to be the length of time that interrupts for that level (or greater) are disabled. This is sometimes also referred to as the Interrupts-off time. These numbers are important because they must be considered when calculating worst-case response to interrupts.

Interrupt Stack Switching

When an interrupt (exception) occurs, an exception frame is pushed on the current stack. For the first interrupt (interrupt depth = 0), V_Gen_Except switches to the common interrupt stack. All subsequent interrupt processing uses this interrupt stack. A nested interrupt pushes its exception frame on the interrupt stack. Therefore, an application task only needs to reserve space on its stack for a single exception frame for interrupt handling regardless of the expected depth of interrupt nesting.

Installing an ISR in the Interrupt Vector Table

The Interrupt Vector Table (IVT) is where the kernel's general exception handler, V_Gen_Except, looks to find the address of every interrupt handler. When kernel initialization is complete, prior to starting the application program, every entry in the IVT points to a valid interrupt handler.

Apex provides two ways to handle interrupts that are not used and are therefore unexpected if they occur: V_Cpu_Conf.Untouchable_Vector, and V_Default_Isr.

If Untouchable_Vector is used, the kernel dispatches through the general exception vector installed before the kernel performed its initialization (presumably by either the onboard monitor or TDM).

If the kernel's V_Default_Isr is used, the kernel halts the program with an error message. For example:

However, since this routine is exposed in the package V_Krn_Conf, you can alter it to suit your needs.

Rational Software Corporation recommends using the procedure V_Interrupts.Attach_Isr from the VADS Exec interface to put new ISRs into the Ivt. Attach_Isr is called from the application program and is used to install an interrupt handler that is part of the application.

A big advantage of making subprograms from the application into interrupt handlers is the ease with which these routines communicate with the application. Both the ISR and routines from the application reference the same static global data structures simply by using the name of Ada objects (for example, buffers and variables in library level packages).

Contrast this with interrupt handlers linked with the kernel. Because the kernel is a separately linked program, it does not share any code with the application. Therefore, for an ISR linked directly with the kernel to communicate with the application, both the application and the ISR must agree on an address in memory where this communication can take place. If this address changes, both the kernel and the application must be recompiled and relinked.

If you use Ada interrupt entries as interrupt handlers, these interrupt handlers are installed into the IVT automatically during elaboration of the task specification.


Writing Interrupt Handlers for RH32

ISR Generic Wrappers on RH32

Isr, Fast_Isr, and Float_Wrapper

On the RH32, the device independent portion of servicing an interrupt, saving the interrupted state of the processor, setting up the processor for servicing the interrupt and interacting with VADS Exec is taken care of by the generalized interrupt (RH32 exception) handler, V_Gen_Except.

When the interrupt exception arrives, the CPU transfers control to the generalized trap handler, V_Gen_Except, by a jmp instruction installed at the interrupt surprise vector (address 16#0000_0040#). After executing the preamble code, V_Gen_Except transfers control to the start of the attached ISR.

Since these wrapper functions have been moved to V_Gen_Except, that calls the ISR, Isr, and Fast_Isr are not very useful on the RH32. The Float_Wrapper generic, however, contains real code for saving and restoring the floating point state.

Kernel ISR Wrapper

The Apex embedded kernel comes with an ISR generic wrapper. The kernel's ISR wrapper is supplied for the timer interrupt handler that must be present as part of the kernel program. You can use this wrapper to build other ISRs that link with the kernel; however, communication of information between the ISRs linked with the kernel and applications tasks is much more difficult than using the wrappers from V_Interrupts and installing them dynamically using V_Interrupts.Attach_Isr.

This wrapper is declared in V_Krn_Conf_I, in the kernel configuration file, v_krn_conf.2.ada.

As with Isr, Fast_Isr, and Float_Wrapper, this wrapper is instantiated with a device specific interrupt handler program.

Requirements of an ISR on RH32

In this section we walk through the steps performed by V_Interrupts.Isr and V_Interrupts.Fast_Isr. Since most of the exception and ISR wrapper logic is contained in V_Gen_Exception, these routines do not need to do much. Before an ISR is entered the following has to happen:

Upon return, V_Gen_Exception looks at the Next_Id field in the exception frame. If Next_Id was not changed from No_Vector_Id, V_Gen_Exception completes interrupt processing by jumping to V_Restore_Ef to restore the processor context.

Otherwise, V_Gen_Exception redispatches to the Next_Id's ISR. If the Next_Id points to an Untouchable vector, then the Next_Id is handled in TDM. The Next_Id field allows multiple devices in either the user, kernel or TDM programs to share the same interrupt.

Now it is time to explain what these two routines do. (On the RH32, Fast_Isr is identical to Isr; it is not any faster).

1 . Update the exception unwinding stop address to point to the top of the ISR instead of the top of the program. Apex supports the raising and propagation of an Ada exception in the subprograms called from an ISR. This step is here to inhibit an Ada exception from being propagated back to the interrupted program. This step can be eliminated if no exceptions are raised or the ISR subprograms have a handler for all Ada exceptions. If no exception handler is found, the program exits printing the message:

2 . Service the interrupt

In this step, the user supplied subprogram is called to perform device specific actions.

3 . Restore exception unwinding stop address. The stop address before the interrupt occurred is restored.

4 . The Isr and Fast_Isr generics are declared in the CPU independent VADS Exec V_Interrupts package as having a parameterless procedural interface. While in fact on the RH32, an ISR is called as a parameterized procedure as follows:

ISRs and the RH32 Interrupt Mask

The RH32 processor has 8 different interrupts. The interrupts that are currently enabled are specified by the Intr7..0 bits in conjunction with the Current Interrupt Enable (IE) bit in the CPU status register. If Intr7..0 are zero or the IE bit is zero, all interrupts are disabled. By Apex convention, interrupt enabling and disabling is done with the Intr7..0 while in the user program, IE is always set to one.

When hardware asserts an interrupt line, in order for it to be recognized, it must be enabled in the CPU status register. If it is enabled, then the CPU is vectored to V_Gen_Exception where the "exception" is decoded as being an interrupt, higher priority interrupts are enabled, and dispatches to the ISR corresponding to the interrupt. Usually the ISR executes entirely with the new interrupt mask and after it completes, the previous interrupt mask is restored (by VADS Exec as part of the Isr_Complete routine).

Sometimes lower level interrupts are pending and it is not desirable to block these interrupts during the total length of the ISR execution. An ISR enables lower priority interrupts by simply altering the interrupt mask in the CPU status register. This permits devices with interrupts at a lower priority to get their interrupts serviced.

Conversely, it may be desirable for an interrupt handler to disable some or all higher priority interrupts. The Apex Embedded kernel does this to protect certain critical regions of code from being interrupted. During these critical regions, the kernel must disable interrupts that start up an ISR which makes calls to VADS Exec. When a critical region is going to be entered, the interrupt mask is loaded into the CPU status register with V_Krn_Conf.Disable_Mask.

The value of Disable_Mask must be such that it masks out all interrupts which make calls to VADS Exec, that is, any ISR not masked by Disable_Mask must not make any calls to VADS Exec (explicitly or implicitly). Calls to VADS Exec by one of these ISRs which occur while VADS Exec is in a critical region would result in corrupted data structures.

Interrupt latency for an interrupt priority level is defined to be the length of time that interrupts for that level (or greater) are disabled. This is sometimes also referred to as the Interrupts-off time. These numbers are important because they must be considered when calculating worst-case response to interrupts.

Interrupt Stack Switching

When an interrupt (exception) occurs, an exception frame is pushed on the current stack. For the first interrupt (interrupt depth 0), V_Gen_Exception switches to the common interrupt stack. All subsequent interrupt processing uses this interrupt stack. A nested interrupt pushes its exception frame on the interrupt stack. Therefore, an application task only needs to reserve space on its stack for a single exception frame for interrupt handling regardless of the expected depth of interrupt nesting.

Installing an ISR in the Interrupt Vector Table

The Interrupt Vector Table (IVT) is where the kernel's general exception handler, V_Gen_Exception, looks to find the address of every interrupt handler. When kernel initialization is complete, prior to starting the application program, every entry in the IVT points to a valid interrupt handler.

Apex provides two ways to handle interrupts that are not used and are therefore unexpected if they occur: V_Cpu_Conf.Untouchable_Vector, and V_Default_Isr.

If Untouchable_Vector is used, the kernel dispatches through the general exception vector installed before the kernel performed its initialization (presumably by either the onboard monitor or TDM).

If the kernel's V_Default_Isr is used, the kernel halts the program with an error message. For example:

However, since this routine is exposed in the package V_Krn_Conf, you can alter it to suit your needs.

We recommend using the procedure V_Interrupts.Attach_Isr from the VADS Exec interface to put new ISRs into the IVT. Attach_Isr is called from the application program and is used to install an interrupt handler that is part of the application.

A big advantage of making subprograms from the application into interrupt handlers is the ease with which these routines communicate with the application. Both the ISR and routines from the application reference the same static global data structures simply by using the name of Ada objects (for example, buffers and variables in library level packages).

Contrast this with interrupt handlers linked with the kernel. Because the kernel is a separately linked program, it does not share any code with the application. Therefore, for an ISR linked directly with the kernel to communicate with the application, both the application and the ISR must agree on an address in memory where this communication can take place.

If this address changes, both the kernel and the application must be recompiled and relinked.If you use Ada interrupt entries as interrupt handlers, these interrupt handlers are installed into the IVT automatically during elaboration of the task specification.


Writing Interrupt Handlers for M68000 Family

ISR Generic Wrappers on M68000 Family

The M68000 Family uses the three wrapper routines declared in the V_Interrupts package.

Kernel and TDM ISR Wrappers

Both the kernel and TDM come with an ISR generic wrapper.

The kernel's ISR wrapper, declared in V_Krn_Conf_I, is supplied for the timer interrupt handler that needs to be present as part of the kernel program. You can use this wrapper to build other ISRs that link with the kernel; however, communication of information between the ISRs linked with the kernel and applications tasks is much more difficult than using the wrappers from V_Interrupts in VADS Exec and installing them dynamically using V_Interrupts.Attach_Isr.

TDM's ISR wrapper, declared in V_Tdm_Conf_I, is supplied for building a serial device interrupt handler for communicating between the target and the host -- this needs to be interrupt driven if TDM is going to handle Control-C.

As with Isr, Fast_Isr, and Float_Wrapper, these wrappers are instantiated with a device specific interrupt handler program.

Requirements and Layout of an ISR on M68000 Family

In this section we walk through the steps performed by V_Interrupts.Isr and V_Interrupts.Fast_Isr. The purpose is to explain what these two routines do and at the same time explain what actions are required and what actions are optional if you choose to write your own ISR instead of using one of these wrappers.

1 . Save all registers before use

Before jumping to the ISR, the processor hardware has saved the PC and the SR registers. An ISR must save and restore every register it uses so the interrupted operation can resume successfully after the interrupt is serviced.

The registers D0, D1, D2, D3, A0 and A1 are saved by the wrapper. This is sufficient for all activity in the wrapper itself and for all VADS Exec functions called from an ISR. The wrapper performs a normal procedure call to your Interrupt_Handler. If you use any register D4-D7 or A2-A5,the compiler generates an instruction to save these registers on the stack upon entry to your ISR, unless you use pragma Implicit_Code(Off), in which case you must make sure that you explicitly save and restore every register used.


Warning: If you declare your ISR as inline, you must be very careful to make sure that all registers used in your ISR (except D0, D1, D2, D3, A0 and A1) are saved and restored. The ISR wrappers in VADS Exec all use pragma Implicit_Code(Off). If your ISR is declared as inline, then this pragma prevents the compiler from generating code to automatically save and restore the registers your handler uses.

2 . Increment the interrupt depth

VADS Exec maintains an interrupt depth counter that tells how deeply nested the system is in interrupt handlers. If the value is zero (0), the kernel knows that it was called from a task, otherwise it was called from an ISR.

ISRs can call some VADS Exec services available to tasks. Some of these services result in a task becoming ready to run. If this ready task has a higher priority than the current task, then VADS Exec wants to preempt the current task. However, an ISR must never be preempted by a task. If the ISR does cause a higher priority task to become ready, the task scheduling occurs after interrupt handling, in the call to Isr_Enter.

    Note: This step is performed explicitly by Fast_Isr. However, V_Interrupts.Isr calls Isr_Enter which implicitly increments the interrupt depth. See Step 3.

3 . Switch interrupt stacks (Isr_Enter)

VADS Exec supports an optional feature where an interrupt handler can switch to a common system-wide interrupt handling stack, shared by ISRs. This can result in significant space savings in your application at only a slight penalty in performance. If master state is enabled, the hardware automatically switches to the common interrupt stack when an interrupt occurs. Otherwise, every ISR must explicitly call Isr_Enter to switch to the shared interrupt stack. Note that master state is available only on the MC68020, MC68030 and MC68040 processors.

    Note: If you write an ISR so that it does not switch to the interrupt stack but instead keeps using the interrupted task's private supervisor stack, every task must leave room on its supervisor stack for the space used by the ISR. Also, make sure and take into account the worst possible case of interrupt nesting; there must be enough space on the supervisor stack of every task to handle the worst-case stack usage of nested interrupts for ISRs that do not switch stacks.

    For maximum performance, the ISR wrappers supplied with VADS Exec call Isr_Enter through a special entry point made visible through the Debug_Block. This permits the kernel's general (but slower) entry point to be circumvented. The general entry point into the kernel is used for all functions except these very performance-critical ISR functions. See the code for more details.

    In addition to switching stacks, Isr_Enter automatically increments the interrupt depth, so Step 2 is not required. Likewise, Isr_Enter takes care of setting up the stack in the expected way, so Step 4 is also not required if Isr_Enter is called.

4 . Point to previous stack.

VADS Exec requires that the SP value for the previous stack be at the bottom of the interrupt stack. If Isr_Enter is called (see Step 3), it takes care of doing this. Fast_Isr does not call Isr_Enter, so it must push the current SP value onto the stack to meet this requirement.

5 . Set up frame pointer (register A6)

Both V_Interrupts.Isr and V_Interrupts.Fast_Isr set up the frame pointer, (register A6), to support debugging and exception unwinding. The instructions that do this are optional but strongly recommended. If the Apex debugger comes across an odd frame pointer, it subtracts one and assumes the result points to the stack frame of an ISR. This convention is supported so you can move down the call stack of an ISR while in the debugger. Apex Ada exception handling also supports this convention, so if an Ada exception is raised during ISR processing, it knows when it reaches the ISR.

If an exception is raised in an ISR or in a subprogram called from an ISR, it is propagated following normal rules of Ada. The stack frame corresponding to the ISR entry (the frame with the odd frame pointer value) is treated just like an Ada main subprogram; it is the last place searched for an exception handler. If no exception handler is found there, the program exits printing the message:

If an Ada unit is not compiled with pragma Suppress(All_Checks), stack limit checking is automatically enabled. All Rational Software Corporation supplied RTS units are compiled with pragma Suppress(All_Checks).

After setting up the frame pointer in this step, the state of the stack depicted in Figure 17 should be true.

6 . Establish interrupt stack limit

This step is shown only for V_Interrupts.Isr, not Fast_Isr. If the stack limit is not correctly set after an interrupt, it is important that every subprogram called during the handling of the ISR be compiled so that stack limit checks are suppressed. Use pragma Suppress(All_Checks).

If all tasks are executing in supervisor mode and not using the master state (that is, S bit in SR register always 1, M bit 0) an interrupt does not change stacks since the task is already using the interrupt stack. Therefore, the current stack limit is correct and does not need to be changed by the ISR. This is the case if the kernel configuration parameter Supervisor_Tasks_Enabled is set True and Master_State_Enabled is set False.

Figure 17 ISR Stack Frame Conventions

7 . Service the interrupt

In this step the two VADS Exec ISR wrappers call the user supplied subprogram to perform device specific actions.

8 . Restore stack limit

If the stack limit was previously changed, it must be restored here.

9 . Restore the frame pointer

If the frame pointer (register A6) was changed in Step 5, it must be restored here.

10 . Call Isr_Complete

This routine must be called to finish interrupt processing before returning from the ISR. Isr_Complete must be called if any VADS Exec calls were made during the ISR, including VADS Exec calls made by nested ISRs (ISRs for higher priority interrupts). Isr_Complete is a fast routine and if no rescheduling is required, it returns directly to the interrupted code.

    Note: The interrupt depth must be incremented before the call to Isr_Complete

    A window at the beginning of an ISR, prior to the completion of Step 2, exists where an ISR can be interrupted by a higher priority ISR but the interrupt depth has not been incremented. VADS Exec handles this by following this rule: If an ISR interrupted code executing with a non-zero interrupt priority level, Isr_Complete always returns to this interrupted code.

    For maximum performance, the ISR wrappers supplied with VADS Exec call Isr_Complete through a special entry point made visible through the Debug_Block. This permits the kernel's general TRAP entry point to be circumvented. The TRAP entry point into the kernel is used for all functions except these very performance critical ISR functions.

    Isr_Complete assumes that the SP points to the bottom of the interrupt stack and that state of the stack depicted in Figure 18 is true.

Figure 18 Interrupt Stack At Entry to Isr_Complete

ISRs and M68000 Family Interrupt Levels

Most M68000 family processors have seven possible interrupt levels, 1 through 7, with 7 being the highest priority. The current interrupt level is in bits 8-10 of the SR register and is called the interrupt priority mask or just interrupt mask. A value of 0 typically means that no interrupt processing is occurring.

When an interrupt occurs, the interrupting hardware must supply an interrupt priority to the processor. This priority must be greater than the current interrupt mask or the interrupt is ignored, unless the interrupt priority is 7, which is never ignored. If the interrupt is going to be processed (that is, the incoming priority is greater than the current priority mask value), the interrupt priority mask is raised to the new priority. The processor saves the previous interrupt mask by pushing a copy of the entire SR register, as it was before the interrupt occurred, onto the supervisor stack. Usually the ISR executes entirely at this new priority and after it is complete, the previous priority is restored (by VADS Exec as part of the Isr_Complete service).

Sometimes there are multiple devices that interrupt at the same priority level and it is not desirable for each ISR to disable interrupts during the total length of their execution. An ISR can lower its own priority simply by decreasing the interrupt priority mask value in the SR register. This permits devices with interrupts at the same priority to get their interrupts serviced. However, an ISR may only lower its priority to zero after the interrupt depth has been incremented.

Conversely, by raising the value in the interrupt priority mask field of the SR register, interrupts can be disabled. The kernel does this to protect certain critical regions of code from being interrupted. During these critical regions, the kernel must disable interrupts that start up an ISR which makes calls to VADS Exec. When VADS Exec is going to enter a critical region, it raises the priority mask to the value specified in Disable_Intr_Priority in the kernel configuration file. The value for Disable_Intr_Priority must be greater than or equal to the highest priority ISR that makes calls to VADS Exec.

Note: If an ISR is executing at an interrupt priority level greater than Disable_Intr_Priority, it may not make any VADS Exec calls, including calls caused implicitly by Ada Language features. Furthermore, Isr_Enter and Isr_Complete may not be called by an ISR with a priority greater than Disable_Intr_Priority.

Interrupts with priority less than or equal to Disable_Intr_Priority are disabled by VADS Exec during critical regions. Interrupts that have a greater priority do occur and their high-priority ISRs must not make calls to VADS Exec (explicitly or implicitly). Likewise, priority 7 ISRs must not make calls to VADS Exec, since interrupts at priority 7 cannot be disabled. Calls to VADS Exec by one of these ISRs could occur while VADS Exec is in a critical region, which would result in corrupted data structures.

Interrupt latency for an interrupt priority level is defined to be the length of time that interrupts for that level (or greater) are disabled. This is sometimes also referred to as the Interrupts-off time. These numbers are important because they must be considered when calculating worst-case response to interrupts.

For additional information, see Interrupt Stack Switching and Processor States

Installing an ISR in the Exception Vector Table

The Exception Vector Table (EVT) is where the processor looks to find the address of every interrupt handler. When kernel initialization is complete, prior to starting the application program, every entry in the EVT points to a valid interrupt handler. Most point to the kernel's default ISR which prints out a diagnostic if called. It is intended to detect unexpected interrupts. Initializing the EVT this way guarantees that you have evidence of unexpected interrupts when they occur.

Rational Software Corporation recommends using the procedure V_Interrupts.Attach_Isr from the VADS Exec interface to put new ISRs into the EVT. Attach_Isr is called from the application program and is used to install an interrupt handler that is part of the application.

A big advantage of making subprograms from the application into interrupt handlers is the ease with which these routines communicate with the application. Both the ISR and routines from the application reference the same static global data structures simply by using the name of Ada objects (for example, buffers and variables in library level packages).

Contrast this with interrupt handlers linked with the kernel. Because the kernel is a separately linked program, it does not share any code with the application. Therefore, for an ISR that is linked directly with the kernel to communicate with the application, both the application and the ISR must agree on an address in memory where this communication takes place. If this address changes, both the kernel and the application must be recompiled and relinked.

If you use Ada interrupt entries as interrupt handlers, these interrupt handlers are installed in the EVT automatically during elaboration of the task specification.


Writing Interrupt Handlers for i386

ISR Generic Wrappers on i386 Family

The i386 Family uses the three wrapper routines declared in the V_Interrupts package.

Kernel and TDM ISR Wrappers

Both the kernel and TDM come with an ISR generic wrapper.

The kernel's ISR wrapper, declared in V_Krn_Conf_I, is supplied for the timer interrupt handler that needs to be present as part of the kernel program. You can use this wrapper to build other ISRs that link with the kernel; however, communication of information between the ISRs linked with the kernel and applications tasks is much more difficult than using the wrappers from V_Interrupts in VADS Exec and installing them dynamically using V_Interrupts.Attach_Isr.

TDM's ISR wrapper, declared in V_Tdm_Conf_I, is supplied for building a serial device interrupt handler for communicating between the target and the host -- this needs to be interrupt driven if TDM is going to handle Control-c.

As with ISR, Fast_Isr, and Float_Wrapper, these wrappers are instantiated with a device specific interrupt handler program.

Requirements and Layout of an ISR on i386 Family

In this section we walk through the steps performed by V_Interrupts.Isr and V_Interrupts.Fast_Isr. The purpose is to explain what these two routines do and at the same time explain what actions are required and what actions are optional if you choose to write your own ISR instead of using one of these wrappers.

1 . Save all registers before use.

Before jumping to the ISR, the i386 hardware has saved the SS and ESP registers (for privilege transition) and the EFLAGS, CS and EIP registers. An ISR must save and restore every register it uses so the interrupted operation can resume successfully after the interrupt is serviced.

i386 registers: All general registers (EAX, ECX, etc.) are saved by the wrapper. This is necessary for activity in the wrapper and for all VADS Exec functions called from an ISR. All registers must be saved, because the Ada compiler does not save any registers upon entering a subprogram.

However, if the entire ISR is written in machine code and it skips step 6, call Isr_Complete, only those registers used by the ISR must be saved and restored.

The user program executes with the SS, DS and ES segment registers selecting the privilege level 3 user data segment. Because an ISR executes at privilege level 3, it can continue to use DS and ES without change (the ISR does not need to switch to privilege level 0 kernel data segment).

2 . Set up frame pointer (register EBP).

Both V_Interrupts.Isr and V_Interrupts.Fast_Isr set up the frame pointer, (register EBP), to support debugging and exception unwinding. The instructions that do this are optional but strongly recommended. (If you perform this step, be sure that you saved EBP in step 1.) If the debugger comes across an odd frame pointer, it subtracts one and assumes the result points to the stack frame of an ISR. This convention is supported so you can move down the call stack of an ISR while in the debugger. Apex Ada exception handling also supports this convention, so if an Ada exception is raised during ISR processing, it knows when it reaches the ISR.

If an exception is raised in an ISR or in a subprogram called from an ISR, it is propagated following normal rules of Ada. The stack frame corresponding to the ISR entry (the frame with the odd frame pointer value) is treated just like an Ada main subprogram; it is the last place searched for an exception handler. If no exception handler is found there, the program exits printing the message:

If an Ada unit is not compiled with pragma Suppress_All, stack limit checking is automatically enabled. All Rational Software Corporation supplied RTS units are compiled with pragma Suppress_All.

After setting up the frame pointer in this step, the state of the stack depicted in Figure 19 should be true.

Figure 19 ISR Stack Frame Conventions

3 . Establish interrupt stack limit

This step is required only for V_Interrupts.Isr, not Fast_Isr. If the stack limit is not correctly set after an interrupt, it is important that every subprogram called during the handling of the ISR be compiled so that stack limit checks are suppressed. Use pragma Suppress_All.

4 . Service the interrupt

In this step the two VADS Exec ISR wrappers call the user supplied subprogram to perform device specific actions. Control is transferred to the subprogram, with interrupts still disabled.

Any time during its execution, the ISR can enable or disable interrupts, with the STI and CLI instructions. Also, the subprogram has complete freedom in raising and lowering the priority of a board-specific interrupt controller.

5 . Restore stack limit

If the stack limit was previously changed, it must be restored here.

6 . Call Isr_Complete

This routine must be called to finish interrupt processing before returning from the ISR. Isr_Complete must be called if any VADS Exec calls were made during the ISR, including VADS Exec calls made by nested ISRs for higher priority interrupts. Isr_Complete is a fast routine and if no rescheduling is required, it returns directly to the interrupted code.

For maximum performance, the ISR wrappers supplied with VADS Exec call Isr_Complete through a special entry point made visible through the Debug_Block. This permits the kernel's general TRAP entry point to be circumvented. The TRAP entry point into the kernel is used for all functions except these very performance critical ISR functions.

Isr_Complete assumes that the ESP points to the bottom of the interrupt stack and that the stack structure shown in Figure 20 is correct.

Figure 20 Interrupt Stack At Entry To Isr_Complete

Isr_Complete checks the privilege level of the OLD CS in the interrupt frame that was pushed onto the stack. If the old privilege level is 0 (the kernel or another ISR was interrupted), saved registers are popped off the stack and a IRETD instruction is executed to restore the previous processor state. When the privilege level is other than 0, Isr_Complete checks for pending ISR kernel requests. If ISR kernel requests are pending, the kernel is entered, the state of the interrupted user task is saved, the pending request is serviced and task scheduling is re-evaluated. When no ISR kernel request is pending, saved registers are popped off the stack and an IRETD instruction is executed.

The overhead associated with the Isr_Complete call can be replaced with POPAD and IRETD instructions, provided that either of the following conditions are met:

ISRs and Board-specific Interrupt Priority Levels

The i386 contains no logic to support interrupt priority levels. However, most i386-based boards augment the CPU with an interrupt controller device, such as the Intel 8259A Programmable Interrupt Controller (PIC). If you want to use the priority level capability, you must set the configuration parameter Intr_Priority_Enabled to True and provide logic for the kernel's Get_Intr_Priority and Set_Intr_Priority callouts. When Intr_Priority_Enabled is True, the kernel protects its critical region by calling the Set_Intr_Priority callout, using the Disable_Intr_Priority configuration value.

In the default kernel configuration, the configuration parameter Intr_Priority_Enabled is set to False. The IF is set or cleared to enable or disable all external interrupts. Upon entry to a subprogram called from the ISR wrapper, IF is clear (all interrupts disabled). You must set the IF by using the STI instruction, to allow nested interrupts. This requirement extends to interrupt levels where the IF must be set to enable the interrupt controller hardware to generate an interrupt request that is at a higher level than the current interrupt priority.

In the remainder of this section, we assume that interrupt levels are used and that the kernel is configured appropriately.

When an interrupt occurs, the interrupting hardware must supply an interrupt priority to the interrupt controller. This priority must be greater than the current priority or the interrupt is ignored. If the interrupt is going to be processed (that is, the incoming priority is greater than the current priority value), the interrupt priority mask is raised to the new priority. Usually the ISR executes entirely at this new priority and after it is complete, the previous priority is restored by calling a board-specific priority restore routine.

Sometimes multiple devices interrupt at the same priority level and it is undesirable for each ISR to disable interrupts during the total length of their execution. An ISR can lower its priority by calling Set_Intr_Priority with a lower priority, which enables devices with interrupts at the same priority to get their interrupts serviced.

Conversely, by calling Set_Intr_Priority with a higher priority, you can disable interrupts. The Apex embedded kernel does this to protect certain critical regions of code from being interrupted. During these critical regions, the kernel must disable interrupts that start up an ISR which makes calls on VADS Exec. When VADS Exec is going to enter a critical region, it raises the priority to the value specified in Disable_Intr_Priority in the kernel configuration file. The value for Disable_Intr_Priority must be greater than or equal to the highest priority ISR that makes calls on VADS Exec.

Note: If an ISR is executing at an interrupt priority level greater than Disable_Intr_Priority, it may not make any VADS Exec calls, including calls caused implicitly by Ada Language features. However, it may call Isr_Complete from any priority level, since Isr_Complete disables all interrupts around critical code with CLI instructions. Disable_Intr_Priority is described in "Intel 80386 Kernel Configuration Components" in the Configuration Guide for VADS Exec.

Interrupts with priority less than or equal to Disable_Intr_Priority are disabled by VADS Exec during critical regions. Interrupts that have a greater priority occur and their high-priority ISRs must not make calls on VADS Exec (explicitly or implicitly). Calls on VADS Exec by one of these ISRs could occur while VADS Exec is in a critical region, which would result in corrupted data structures.

Installing an ISR in the Interrupt Descriptor Table

The Interrupt Descriptor Table (IDT) is where the processor looks to find the address of every interrupt handler. When kernel initialization is complete, prior to starting the application program, every entry in the IDT points to a valid interrupt handler. Most point to the kernel's default ISR which prints out a diagnostic if called. It is intended to detect unexpected interrupts. Initializing the IDT this way guarantees that you have evidence of unexpected interrupts when they occur.

Rational Software Corporation recommends using the procedure V_Interrupts.Attach_Isr from the VADS Exec interface to put new ISRs into the IDT. Attach_Isr is called from the application program and is used to install an interrupt handler that is part of the application.

A big advantage of making subprograms from the application into interrupt handlers is the ease with which these routines communicate with the application. Both the ISR and routines from the application reference the same static global data structures simply by using the name of Ada objects (for example, buffers and variables in library level packages).

Contrast this with interrupt handlers linked with the kernel. Because the kernel is a separately linked program, it does not share any code with the application. Therefore, for an ISR that is linked directly with the kernel to communicate with the application, both the application and the ISR must agree on an address in memory where this communication takes place. If this address changes, both the kernel and the application must be recompiled and relinked.

If you use Ada interrupt entries as interrupt handlers, these interrupt handlers are installed in the IDT automatically during elaboration of the task specification.


Rational Software Corporation 
http://www.rational.com
support@rational.com
techpubs@rational.com
Copyright © 1993-2002, Rational Software Corporation. All rights reserved.
TOC PREV NEXT INDEX DOC LIST MASTER INDEX TECHNOTES APEX TIPS