Runtime System Topics This chapter is an overview of the Apex Runtime System (RTS) from a user perspective. Use it as an aid for developing and tuning applications using Tornado. It does not require access to the source code defining the runtime algorithms.
The Apex Runtime System is a collection of services that are available to programmers. This chapter documents those services and ties together different functional parts of the runtime system.
Knowledge of Ada is assumed, particularly in the use of the tasking terms master, terminate, complete, abort, rendezvous, select, accept and call.
The term thread denotes a thread of execution and represents the execution state (stack and registers) of a task.
The following topics are covered in this chapter:
- Overview of the Tornado Runtime Structure
- The Apex Runtime Structure
- Software Floating Point
- Interrupt Processing
- Tasking
- Register Conventions
Figure 2 Components of the Target System
Figure 2 shows the runtime components that make up an Ada application and their relationships. Figure 3 illustrates the relation of these components to the total Tornado environment. In Figure 2, the box labeled "User Program(s)" represents the load module produced as the output from the linker. The box labeled "Target-Resident Services" in Figure 2 represents the Tornado and Apex runtime support with which your program is linked when it is loaded onto the target. These components are described briefly in Table 1.
Table 1 Runtime Components
When the Rational Apex for Tornado system boots, the Wind kernel and the Tornado runtime libraries are downloaded. The Ada runtime library is downloaded in a separate step.
The linking of an Ada program pulls all the application's objects, the user library configuration, and the memory manager (if included) together into a single relocatable VOX object module. When this module is loaded using the Tornado loader, it is dynamically linked to the services residing on the target.
Figure 3 Tornado Cross-Development Model
Overview of the Tornado Runtime StructureThe second major part of the RTS is the user library, a collection of Ada tasking, Ada I/O and traditional support routines. These routines are linked into the user program, as required, to perform specific functions and are executed within the user program space.
After the user program begins in V_Start_Program, it eventually calls the Ada tasking initialization routine, Ts_Initialize, to initialize the Ada tasking data structures. After initialization, all the packages in the user program are elaborated.
Elaboration consists of a call to each address in a table built by the Apex prelinker. The next-to-last entry in the table is the user's main program; the last entry is an exit routine that cleans up the user's resources and returns to the OS.
The Apex Runtime StructureFor a complete overview of the Apex runtime system please refer to the "Runtime System Overview" in Using the Ada Runtime.
Software Floating PointRational Apex for Tornado supports MPC860 as well as other software floating point PowerPC chips. The example below reflects the MPC860 variant. The MPC860 is similar to other implementations of the PowerPC but does not have hardware support for floating–point operations. Floating point operations are done with software.
There must be a separate copy of all the standard views (lrm, predefined, etc.) so that compiled code using floating point operations does not attempt to use floating point hardware.
Views that work with PowerPC chips with hardware floating point have the compiler_variant identifier vw_ppc and views for chips like the MPC860 which do not have hardware floating point are identified with compiler_variant vw_ppcsfp (Tornado_PowerPCSoftwareFloatingPoint).
Note: If your system requires floating point math, a separate archive of code and a license must be obtained from U.S. Software that implements the floating point functions (add, subtract, multiply, divide, conversions from int to float, and from float to int, etc.) The archive is called gofast.var and is sold independently of Rational Software.
Contact your Rational sales representative for assistance or Call U.S. Software at (800) 356-7097.
The gofast.var archive is copied into the location in the Apex installation with the other runtime archives:
$APEX_BASE/ada/runtimes/vw_ppcsfp.4.2.0/krn $APEX_BASE/ada/runtimes/vw_ppcsfp.4.2.0/no_krn $APEX_BASE/ada/runtimes/vw_ppcsfp.4.2.0/usr $APEX_BASE/ada/runtimes/vw_ppcsfp.4.2.0/tdm $APEX_BASE/ada/runtimes/vw_ppcsfp.4.2.0/ntdm
If the gofast.var archive is not present in the runtime location, linking the kernel, TDM or the user program will result in errors.
Rational provides the BSP mpc680ads for the Motorola mpc860ads board which uses the MPC860 chip. This BSP supports serial TDM and the HP software probe.
Software Floating Point Parameter Passing Conventions
Using the GNU C compiler for the MPC860 target with software floating point:
- 1 . Registers r3-r10 are the int/float parameter registers.
- 2 . For 32-bit integers, registers r3-r10 are used consecutively. When exhausted, we use increasing modulo 4 locations beginning with 8(sp).
- 3 . For 64-bit integers, registers r3-r10 are used pairwise (r3/r4, r5/r6,...). The first register can be odd or even numbered (so r4/r5 is a legal pair). If only register r10 is available, we skip it and store the long pair on the stack. Stack locations are modulo 8 beginning with 8(sp).
- 4 . For 32-bit floats, registers r3-10 are used in the same manner as for passing 32-bit integer arguments. Stack locations are modulo 4.
- 5 . For 64-bit floats, registers r3-10 are used pairwise identically in the manner employed for 64-bit integers. Stack usage differs slightly. Stack locations for 64-bit floats are modulo 4, not modulo 8.
- 6 . 32-bit values are returned in register r3. 64-bit values are returned in register pair r3/r4.
Interrupt ProcessingIntroduction
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 signal 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 4 shows this environment. The following sections will elaborate on each of the components of the system.
- Ada Priority Ceiling
- Protected Object Environment for Interrupt Handling
- User Configuration
- Ada Interrupt Handler Example
- Other Interrupt Handlers
- Program Deadlock
- Debugging Interrupt Handlers
Figure 4 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 that has pragma Interrupt_Handler or Attach_Handler 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.
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 in the kernel configuration file v_krn_conf.2.ada (in the krn_conf.ss subsystem). This can be configured, but the version provided with Apex will generally be sufficient.
Priorities are mapped to either interrupt masks or interrupt priority levels. Interrupt priority levels are implemented using the VxWorks intLevelSet() and intLevelGet() operations, but these are not always available. In particular, VxWorks for PowerPC does not support these operations. In such cases, interrupts are masked individually using intDisable().
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.
V_I_Handler.Handler_T: type Handler_T is record Prot : System.Address; Procedure_Address : V_I_Prot.Procedure_Address_T; Wrapper : System.Address; Old_Handler : A_Handler_T; -- managed by runtime Interrupt : Integer; -- managed by runtime end record;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 (see Apex_Systems_Programming.Interrupts_Wrappers).
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 priorities.
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:
Interrupt_Ids : constant V_I_Handler.Interrupt_Id_Array := V_I_Handler.Interrupt_Id_Array' ( -- numeric_signal_enabled => Psignal.Sigill, Psignal.Sigfpe, -- storage_signal_enabled => Psignal.Sigsegv, Psignal.Sigbus, -- system won't allow kill and STOP to be taken => Psignal.Sigkill, Psignal.Sigstop, -- The runtime owns SIGALRM, do not take this signal! Psignal.Sigalrm); Interrupt_Ids_Reserved : constant Boolean := True;
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:
Interrupt_Ids : constant V_I_Handler.Interrupt_Id_Array := V_I_Handler.Interrupt_Id_Array' ( -- SCSI disk interrupt Scsi_Interrupt_Vector ); Interrupt_Ids_Reserved : constant Boolean := False;
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):
procedure Interrupts_Handler_Wrapper_Float_True_Lock_True; procedure Interrupts_Handler_Wrapper_Float_True_Lock_False; procedure Interrupts_Handler_Wrapper_Float_False_Lock_True; procedure Interrupts_Handler_Wrapper_Float_False_Lock_False;
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:
pragma Interrupt_Handler_Wrapper(Handler_Name, Wrapper)
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 it 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:
function Set_Reserve_Handler (New_Value : Boolean; Interrupt : in Interrupt_Id) return Boolean;
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.
with Apex_Systems_Programming.Interrupts_Wrappers;
use Apex_Systems_Programming.Interrupts_Wrappers;
with Ada.Interrupts.Names;
package body P is
protected Protected_Example is
pragma Interrupt_Priority (256);
procedure Handler_Isr1;
procedure Handler_Isr2;
pragma Attach_Handler (Handler_Is1, Ada.Interrupts.Names.Is1);
pragma Attach_Handler (Handler_Is2, Ada.Interrupts.Names.Is2);
pragma Interrupt_Handler_Wrapper (Handler_Is1,
Interrupts_Handler_Wrapper_Float_True_Lock_True'Access);
pragma Interrupt_Handler_Wrapper (Handler_Is2,
Interrupts_Handler_Wrapper_Float_True_Lock_False'Access);
end Protected_Example;
end P;
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.
The 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: Dependent application examples can be found in ada_examples.ss/<view>/interrupts.
Other Interrupt Handlers
Rational Apex for Tornado also provides three other mechanisms for handling hardware interrupts. Two mechanisms are provided by Rational and the third by Wind River.
- V_Interrupts Package supports the traditional interrupt service routine (ISR) approach.
- Task Interrupt Entries is an implementation of the Ada interrupt entry feature.
- Tornado intConnect() Service is a Tornado operation that installs a handler; it is incompatible with some features of Apex Ada code.
V_Interrupts Package
The V_Interrupts package found in rts_vads_exec.ss supports a conventional approach to interrupt handling using interrupt service routines. Rational Apex for Tornado provides the Isr, Fast_Isr, and Float_Wrapper generics.
The ISR wrappers from the V_Interrupts package are designed to be the primary ISR wrappers used with VADS Exec. They are optimized for very fast performance. You should 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 very easy: 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 (except on i386) 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. Remember, the source code for the wrappers is part of the product, so if neither of these routines quite meets your needs, feel free to use one of them as a starting point for your own wrapper for interrupt handlers.
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.
On the PowerPC and MIPS architectures, Tornado saves the registers that represent the state of the task when an interrupt occurs.
On M68000 Family and i386 Family architectures, Tornado does not save these registers. This task is accomplished by the Rational Apex for Tornado ISR generics.
In addition, two generics, Trap_Isr and Fast_Trap_Isr, are provided for handling software traps on some architectures. Software traps, such as those generated by the M68000 Family trap instruction, may be handled differently from interrupts. The ISR and Fast_Isr generics cannot be used for handling these traps.
Trap handlers must not call intEnt() or intExit(). These services assume a transition to the interrupt stack has occurred, and one does not occur when a trap is generated.
Interrupt handlers must not perform any operations which might block, including semaphore takes and I/O operations. Output can be printed using the Tornado logMsg() service. Interrupt handlers cannot engage in rendezvous with Ada tasks.
If you want to write your own version of the ISR generic, study those provided in the release as well as the Wind River documentation to make sure that you save all of the necessary registers.
Float_Wrapper is not a complete wrapper for an interrupt handler. What it does is wrap 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:
Float_Tty is new V_Interrupts.Float_Wrapper(Serial_Handler); Tty_Handler is new V_Interrupts.Isr(Float_Tty);
In this example, Serial_Handler is a device specific interrupt handler that needs to do floating point calculations to correctly handle an interrupt. First Serial_Handler is wrapped so it saves and restores the state of the floating point hardware. The resulting procedure, Float_Tty, 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 Tornado kernel takes care of saving and restoring the floating point hardware state on task switches.
Task Interrupt Entries
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.
Task interrupt entries are implemented for hardware interrupts. Interrupt entries are not currently supported for Tornado signals.
Task interrupt entries are also referred to as signal ISR's since the implementation involves an implicit interrupt service routine that "signals" the interrupt entry when the interrupt occurs. This use of the term "signal" refers to an internal runtime mechanism, and not a Tornado signal. The "signal" mechanism results in a scheduling of the task waiting on that entry.
When an interrupt entry is declared in a task, the compiler generates an ISR that looks like:
<<INTERRUPT_ENTRY_ISR>> PUSH sig_header'address CALL V_Usr_Conf.V_Signal_Isr
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; 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 Tornado kernel calls the above generated ISR which calls V_Usr_Conf.V_Signal_Isr.
The sig_header is a data structure built by the compiler. There is one such structure per interrupt entry. 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 usr_conf.ss subsystem. 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 very simple example shows a conventional ISR and an Ada interrupt entry, each of which simply increments a global variable. Although simple, this example does contrast the difference in the way your Ada code would look for each of the two interrupt handling methods.
Contrast ISR with Interrupt Entry
---Example Contrasting an ISR with an Interrupt Entry-------- ------- -- ISR: ------- with V_Interrupts; with System; package body Isr_Example is Cnt: Intger := 0; procedure My_Handler is -- interrupt handler begin Cnt := Cnt + 1; end My_Handler; -- The following instantiation "wraps" my_handler -- procedure My_Isr is new V_Interrupts.Isr(My_Handler); begin V_Interrupts.Attach_Isr(16#90#, My_Isr'Address); end Isr_Example;
----------------------- -- Ada Interrupt Entry: ----------------------- with System; with Ada_Krn_Defs; package body Interrupt_Entry_Example is Cnt: Integer := 0; task A is entry My_Handler; for My_Handler'Address use System.Address'Ref(16#90#); end A; task body A is begin loop accept My_Handler do Cnt := Cnt + 1; end My_Handler; end loop; end A; end Interrupt_Entry_Example;
The code in the interrupt entry's accept body executes in the context of the task and not in the context of the ISR. As a result, blocking operations may be performed in the accept body, although any interrupts received while the task is not waiting at the accept are dropped. Exceptions raised in the accept body are propagated in the task's context. An important consequence of the task interrupt entry's implementation is that actions which must take place in the interrupt context, such as acknowledgment of the interrupt, cannot be performed in the body of the interrupt entry, since the entry code is not executed until after the signal ISR completes. Instead these actions must be performed in the routine V_Signal_Isr (above).
Tornado intConnect() Service
The intConnect() service provides roughly the same functionality as the Fast_Isr generic subprogram provided in V_Interrupts. intConnect() may be used with handlers written in C which do not call Ada subprograms, but should not be used with typical Ada subprograms.
Warning: Use of the Tornado intConnect() service is not supported by Apex Ada. The Ada context (for example Stack_Limit and the unhandled exception backstop) is not initialized. Some Ada constructs (for example rendezvous) can not be used.
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:
- There are tasks that have not yet completed and/or the main task subprogram has not returned.
- No task (including the environment task) is ready to run.
- No task is suspended at an Ada delay or delay until clause (including those in timed entry calls, ATC triggering alternatives, and open delay alternatives) or at a call to Xcalendar.Delay_Until.
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:
- A task is suspended at an accept or select with an open interrupt entry. Interrupt entries at a select with terminate are considered closed.
- The Ada RTS's Exit_Disable_Flag is True. This flag is initialized to False. It is normally set to True by the application program if it attaches an ISR. This flag can be read or set by the VADS Exec services in V_Xtasking, Current_Exit_Disabled, and Set_Exit_Disabled. Alternatively, this flag may be referenced using V_I_Tasks in rational.ss (Get_Exit_Disabled_Flag and Set_Exit_Disabled_Flag)
- The Inhibit_Exit_Depth count for one or more tasks is greater than zero. This count is incremented each time a task makes a call to an entry in an interrupt level protected object and decremented when such a call is completed or cancelled. Note that Ada ATC allows a task to have more than one such call pending at once (see LRM 9.7.4).
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.
Debugging Interrupt Handlers
The Apex debugger and the Tornado on-target debugging tools cannot be used to debug interrupt handlers as this would require suspending the interrupt handler and returning to a task context, which is not possible.
The best mechanism for debugging interrupt-related problems is hardware-analysis tools such as in-circuit emulators or logic analyzers. Symbolic information may be obtained using the Tornado shell. pragma Export may be used to associate a symbol name with an Ada subprogram or static data object.
If an interrupt-handler problem does not crash the system, then static objects can be used to measure activity inside the interrupt handler and then examined from the Tornado shell. For instance, if the statement
pragma Export (Ada, dropped, "dropped");was added immediately after the declaration of the variable dropped in *auxclkmbox.1.ada, then its value could be determined from the Tornado shell while the program was running by simply entering the symbol name:
-> dropped dropped = 0x3ff398: value = 1 = 0x1Static objects can be used to determine the number of iterations through a path of code. An array of static objects can be used along with the d() command from the shell. For example, the contents of the array buffer in the utilities package in util.1.ada could be displayed using the command:
-> d &bufferSome interrupt problems may crash the system and return to the Tornado boot monitor. If static variables are used as described above and their addresses are determined before running the program, then the d() command can be used from the boot monitor to determine their values after the system crashes.
The diagnostics produced by an interrupt-related bug can often be misleading, since the bug may simply corrupt the system, resulting in a subsequent failure in an unrelated task. The following guidelines should be used to help locate interrupt handling problems:
- Ada exceptions in interrupt handlers are not handled, and most likely result in a bus error at interrupt level. For interrupt-handlers (including watchdog timer routines) which do not use Rational-supplied interrupt entry/exit code, or which use the FAST generic subprograms in V_Interrupts, stack checks must be disabled using pragma Suppress (Storage_Check). This also applies to any routines which may be called from these handlers.
- Some devices continue to assert an interrupt until acknowledged by the interrupt handler. If the device is not acknowledged, then the interrupt recurs infinitely when exiting the handler, and the system hangs. If the interrupt handler attempts to enter the kernel and it has interrupted the kernel, this problem may manifest itself with the diagnostic workQPanic: kernel work queue overflow.
- When using task interrupt entries, acknowledgments must be performed from the V_Signal_Isr routine in the V_Usr_Conf package (see Task Interrupt Entries)
- Interrupt service routines cannot perform any operation which might attempt to block (such as a semaphore take). This includes any memory allocation or I/O operation. It is safe to give a semaphore or write to a mailbox/message queue from an interrupt handler. The Tornado routine logMsg() should be used to print messages from within interrupt handlers.
TaskingThis section contains the following topics:
For detailed information on Tasking see Using the Ada Runtime.
Ada Tasking Overview
Ada tasking layers on top of the multi-tasking services exported by Tornado. During development, Tornado targets are booted using boot PROMs supplied with Tornado. The PROMs boot Tornado by copying a load module across a network from a host workstation and jumping to the start of the load module's code. The load module initializes Tornado and executes the routine usrRoot() which starts the system in accordance with the options selected in the file configAll.h.
For deployment, an Ada program may be linked with Tornado and loaded into PROM.
Ada programs consist of one or more Ada tasks. The initial task (created as described below) is known as the main task and executes the main subprogram. Each Ada task executes as a separate Tornado process.
In the following discussion, the term "program" is used to refer to the collection of Ada tasks which make up an Ada program, "task" is used to refer to an Ada task, "process" is used to refer to a Tornado task, and "main task" is used to refer to the Ada task which executes in the context of the Tornado process spawned as described below (or in the context of the Tornado shell process).
Ada programs may be created from the Tornado shell in the same manner as non-Ada programs. The Tornado taskSpawn() primitive or the sp usrLib function can be used with the entry point specified as the main subprogram name. Programs may also be spawned by other programs, which first need to call the function symFindByName() to determine the address of the entry point. Ada programs can be run in the context of the shell by entering __start at the shell prompt.
An Ada program goes through three phases during its execution: creation, elaboration, and completion. These phases are discussed in detail below.
Program Creation
The spawned process (or the shell process if the program is run from the shell) starts execution in the startup routine V_Start_Program which is included in the V_Usr_Conf configuration package described in V_Start_Program and V_Start_Program_Continue Routines.
V_Start_Program initializes the startup task's context and saves information which needs to be restored when the startup task completes (in case it is executing in the context of the shell). It then gathers program-specific data into a structure and passes this data to the routine __Ada_Program_Init which is currently linked into the program. The first step in program creation is the allocation of a Program Control Block (PCB) which is associated with the program and threaded onto a list. The startup data passed to __Ada_Program_Init from V_Start_Program is stored in the program control block along with additional information derived from the task's context. This information is used by the runtime system during program execution. Next, the startup task creates the main task and the idle task, and then waits on a semaphore until the program exits. Program execution continues in the main task.
Next, the task control block is modified to make the current Tornado process an Ada task. This involves initializing an Ada-specific extension to the TCB and setting option flags to indicate that the task is an Ada task. The priority of the process is changed to the priority specified for the Ada main task.
Each Ada program maintains its own heap using a memory manager linked with the program. A list in the program control block is used to link heap segments allocated for the program. During creation, the initialization routine in the default or supplied memory manager is called and this routine allocates the program's first heap segment.
Program Elaboration
Once the program is created, it is executed by elaborating all of its library packages in an order determined by the Apex prelinker. The prelinker provides a table containing the entry point for the elaboration code for each unit. The runtime calls each entry in the table to elaborate the associated package. The next-to-last entry in the table is the body of the main program, and the last entry is a suitable exit routine.
During the elaboration of the program, Ada tasks may be created either from within library units or the main program itself.
Program Completion
The main task terminates after all packages have been elaborated, the main subprogram has been completed, and all dependent tasks of the main subprogram (tasks declared within the main subprogram) and library-level tasks have either terminated or are ready to terminate.
The main subprogram returns to the runtime system elaboration routine which then calls the last entry in the elaboration table. The last entry is a routine in the runtime system which is selected based on whether the main was a procedure or a function. If the main is a procedure, then the status eventually returned to the shell (if the program was run in the context of the shell) is zero; otherwise, it is the value returned by the function.
The main task terminates by performing any task and program exit call-outs that have been registered by VADS Exec, restoring the state of the process to as it was before the call to __Ada_Program_Init, deleting its program, and returning to its caller. If the process was spawned, it is deleted like any other Tornado process. If it was invoked directly from the shell, execution of the shell continues.
Program deletion terminates any tasks which have not already terminated, closes any files opened by the Ada program, frees the memory used for the program's heap, and returns the memory used for the program control block.
Since files opened with Text_Io are automatically closed and memory allocated using the Ada new operator is automatically freed, other Tornado tasks must not reference file descriptors or objects created by the Ada program after the program completes. Objects which need to persist beyond the lifetime of the program should be created with direct calls to Tornado. The memory containing the code and static data for a program is not freed when the program completes, and the symbols associated with the program are not removed from the symbol table. When using the Tornado loader these resources can be reclaimed using the Tornado unld() utility as follows:
-> unld "filename"where filename is the name of the file that was loaded.
Register Conventions
- PowerPC Register ConventionsPowerPC Register Conventions
- MIPS I CP0 Status Register Usage
- MIPS II/III/IV CP0 Status Register Usage
- RH32 CPU Status Register Usage
- i386 Family CPU Registers and Data Structures
PowerPC Register Conventions
Apex Embedded for the PowerPC uses the register conventions defined in the Motorola Embedded Applications Binary Interface (EABI).
Table 2 PowerPC Register Conventions
PowerPC Machine State Register (MSR) Usage
Apex for Rational Exec requires the MSR to use Big-Endian byte ordering. Also, note that the integer multiply instruction (mul) is implemented by the PowerPC's floating point unit. This instruction is used by the compiler for some array indexing instruction sequences. Therefore, it is unsafe to execute Ada code with the floating point unit disabled. Of course, the shadow registers are enabled during normal execution.
Otherwise the rest of the bits in the MSR can be customized to your application. The default settings while user code is executing is:
bit 16 = 1 -- extern interrupts enabled bit 17 = 0 -- supervisor mode bit 18 = 0 -- fp disabled until referenced
MIPS I CP0 Status Register Usage
The layout of the CP0 status register is shown in Figure 5 . This figure is taken from the 1988 edition of Gary Kane's MIPS RISC Architecture. It includes a breakdown of the diagnostic field. It is included for your reference.
- The processor interrupt mask is changed via the following instruction sequence:
code_2'(mfc0, t0, cp0_status); code_2'(li, t1, tv_cpu_conf.NEG_SR_INT_MASK); code_3'(and_op, t0, t0, t1); code_3'(ori, t0, t0, +your_int_mask); code_2'(mtc0, t0, cp0_status); code_0'(op => nop); code_0'(op => nop);
Since the above sequence is executed with interrupts enabled, it can be interrupted. As a consequence, interrupt handlers must restore c0_status to its original state before returning.
- The IEc (Interrupt Enable current) bit should not be used for disabling/enabling interrupts. Use the Intr5 .. 0 and Sw1 .. 0 bits instead.
- The IEo (Interrupt Enable old) bit is lost when an interrupt occurs, so it cannot be relied upon to stay the same between any two instructions unless all interrupts are disabled.
- The Cu1 bit is used by the floating point support code, so it should not be changed.
Figure 5 The MIPS I Status Register
MIPS II/III/IV CP0 Status Register Usage
The layout of the CP0 status register is shown in Figure 9, "The MIPS II Status Register," on page 82. This figure is taken from the IDT MIPS R4000 Microprocessor User's Manual. It includes a breakdown of the diagnostic field. We include it for your reference.
- The processor interrupt mask is changed via the following instruction sequence:
code_2'(mfc0, t0, cp0_status); code_3'(addiv, t1, zero, -(v_cpu_conf.SR_INT_MASK + 1)); code_2'(and_op, t0, t0, t1); code_3'(ori, t0, t0, +your_int_mask); code_2'(mtc0, t0, cp0_status); code_0'(op => nop); code_0'(op => nop);
Since the above sequence is executed with interrupts enabled, it can be interrupted. As a consequence, interrupt handlers must restore c0_status to its original state before returning.
- The IE (Interrupt Enable) bit should not be used for disabling/enabling interrupts. Use the Intr5 .. 0 and Sw1 .. 0 bits instead.
- The Cu1 bit is used by the floating point support code, so it should not be changed.
Figure 6 The MIPS II Status Register
RH32 CPU Status Register Usage
The layout of the CPU status register is shown in Figure 7 . This figure is taken from the 1988 edition of Gary Kane's MIPS RISC Architecture. It includes a breakdown of the diagnostic field. We include it for your reference.
Figure 7 The MIPS Status Register
- The processor interrupt mask is changed via the following instruction sequence:
code_2'(read, t0, cpu_status); code_2'(ldi, t1, +NEG_SR_INT_MASK); code_3'(and_op, t0, t0, t1); code_3'(ori, t0, t0, +your_int_mask); code_2'(write, t0, cpu_status);
Since the above sequence is executed with interrupts enabled, it can be interrupted. As a consequence, interrupt handlers must restore Cpu_Status to its original state before returning.
The Current Interrupt Enable bit should not be used for disabling/enabling interrupts. Use the Intr7 .. 0 bits instead. The Old Interrupt Enable bit is lost when an interrupt occurs, so it cannot be relied upon to stay the same between any two instructions unless all interrupts are disabled. i386 Family CPU Registers and Data Structures
Segment Registers
CS kernel code (RPL = 0) or user code (RPL = 3) selector
SS kernel data (RPL = 0) or user data (RPL = 3) selector
DS, ES user data (RPL = 3) selector
FS, GS not used
EFLAGS
CR0
CR1
Not used.
CR2, CR3
DR0 - DR7
Debug registers used by TDM.
TR6, TR7
Test registers are not used.
GDT Entries
0 NULL
1 .. 19 NULL or monitor entries
20 kernel code, DPL = 0
21 kernel data and stack, DPL = 0
22 user code, DPL = 3
23 user data and stack, DPL = 3
24 TSS
Code, data and stack segments address entire linear address space
The base and length of the monitor entries copied into the GDT are defined by the configuration parameters Monitor_Gdt_Base and Monitor_Gdt_Length.
0 .. 2 ** 32 - 1
LDT
Null, not used.
TSS
The kernel and user program share the same TSS. The TSS is initialized at startup and never changed thereafter.
TSS fields:
ss0 kernel data selector
esp0 top of kernel/ISR stack
All other fields are set to NULL/0.
IDT
The base and length of the interrupt descriptor table are defined by the Idt_Base and Interrupt_Vector_Size configuration parameters. Since the table is updated during run-time execution it must be located in RAM.
All IDT descriptors are i386 interrupt gates are formatted as follows:
selector = kernel code selector
offset = 32 bit linear address of interrupt handler
type = i386 interrupt gate (interrupt handler is entered with interrupts disabled)
Interrupt handlers execute in the kernel code segment. When the user program is interrupted, SS:ESP is set to ss0:esp0 found in TSS.
M68000 Register Conventions
Apex Ada observes the following conventions:
When interfacing to the C compiler observe the following conventions:
Rational Software Corporation http://www.rational.com support@rational.com techpubs@rational.com Copyright © 1993-2002, Rational Software Corporation. All rights reserved. |