TOC PREV NEXT INDEX DOC LIST MASTER INDEX



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:

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
User Application Code
This is the software that you write using Apex. This includes any Apex supplied packages explicitly referenced by your code by a context clause, such as Text_Io.
Apex Extensions
This is an interface to the VADS Exec services described in the Extended Runtime Services Guide (VADS Exec extensions). This interface is also supported by Rational's Apex Native and Apex Embedded products, providing a portable interface from Apex to a set of conventional real-time executive services.
User Library Configuration
This is the code and data generated for the User Configuration package V_Usr_Conf. The configuration module contains the program entry point, and passes its program-specific startup data to the Apex Runtime library.
User Memory Allocator
This is the user-configurable memory allocator.
Apex/Tornado Runtime Libraries
The Apex portion of the runtime libraries is the set of routines which provide Ada-specific support. These services include program creation and elaboration, exception handling, attributes, Ada arithmetic, etc. The Apex Runtime library also performs Ada tasking operations. These routines implement their services using the Wind kernel. The Tornado libraries are the standard libraries provided with Tornado as described in Volume 4 Tornado Reference Manual.
Kernel Configuration
This is the code and data produced for the kernel configuration package V_Krn_Conf. The configuration module contains the time-slicing parameters and interrupt management package.
Wind Kernel
The Tornado Wind kernel.

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 Structure

The 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 Structure

For a complete overview of the Apex runtime system please refer to the "Runtime System Overview" in Using the Ada Runtime.


Software Floating Point

Rational 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:

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 Processing

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 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.

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.

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:

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 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:

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.

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

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:

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:

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

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:

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.

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

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:

Static 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:

Some 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:


Tasking

This 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:

where filename is the name of the file that was loaded.


Register Conventions

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
Register Conventions
r1 is the stack pointer.
r2 contains the base address of the section .sdata2, if the object file has that section. User routines should not modify r2. Apex does not currently support small data sections but may in future releases.
r3 is the first discrete parameter register and discrete function return register.
r4 is the second discrete parameter register.
r10 is the last discrete parameter register.
r13 is the small data area pointer (currently unused by Apex).
r14 is the Ada stack limit pointer.
r30 is the argument pointer.
r31 is the frame pointer.
r0, and r3 through r12 are volatile "scratch" registers.
r13 - r31 are non-volatile registers which must be saved in each procedure.
fpr0 .. fpr13 are volatile scratch fp registers.
fpr14 .. fpr31 are non-volatile registers which must be saved in each procedure.
fpr1 is 1st fp param register and fp function result register.
fpr2 is 2nd fp param register.
fpr8 is last fp param register.
LR,CTR,MQ,XER and FPSCR are killed and not required to be saved across subprogram calls.
Condition Register (CR) Fields:
CR0, CR1, CR5, CR6, CR7 - Volatile - Not required to be saved.
CR2, CR3, CR4 - NON-Volatile - Saved if used.

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:

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.

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.

Figure 5 The MIPS I Status Register

The MIPS I Status Register Key
CU
Coprocessor Useability. These bits control usability of the four possible coprocessors: Cu3, Cu2, Cu1 and Cu0. If a CU bit is set (=1), that coprocessor is usable.
0
Reserved. Must be written as zeroes, returns zeroes when read.
*BEV
Bootstrap Exception Vector. If set to 1, R3000 uses the alternate, bootstrap vectors for UTLB Miss and general exceptions.
*TS
TLB Shutdown. Set to 1 if R3000 has disabled TLB due to catastrophic error. Cleared by reset.
*PE
Parity Error. Set to 1 if cache parity error occurs. Reset by writing a 1 to this bit.
*CM
Cash Miss. Set to 1 if most recent D-Cache load resulted in a miss (only when the D-Cache is isolated).
*PZ
Parity Zero. When set to 1, causes zero to replace normal outgoing parity bits.
*Swc
Swap Caches. Control switching of control signals for I-Cache and D-Cache.
*IsC
Isolate Cache. When set to 1, isolated D-Cache from main memory system.
IntMask
Interrupt Mask. When a bit is set to 1, the corresponding hardware interrupt [Intr5..0] or software interrupt [Sw1..0] is enabled.
0
Reserved. Must be written as zeroes, returns zeroes when read.
KUo
Kernel/User mode, old. Set to 0 if Kernel, 1 if User.
IEo
Interrupt Enable, old. Set to 0 if Kernel, 1 if User.
KUp
Kernel/User mode, old. previous. Set to 0 if Kernel, 1 if User.
IEp
Interrupt Enable, previous. Set to 1 to enable, 0 to disable.
KUc
Kernel/User Mode, current. Set to 0 if Kernel, 1 if User.
IEc
Interrupt Enable, current. Set to 1 to enable, 0 to disable.

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.

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.

Figure 6 The MIPS II Status Register

The MIPS II Status Register
CU
Controls the usability for each of the four coprocessor unit numbers (1 -> usable; 0 -> unusable).
RP
Enables reduced-power operation by reducing the internal clock frequency (0 -> full speed; 1 -> reduced clock). The clock divisor is programmable at boot time.
FR
Enables additional floating point registers (0 -> 16 registers; 1 -> 32 registers).
RE
Reverse Endian in User mode.
0
Reserved. Must be written as zeroes, returns zeroes when read.
BEV
Controls the location of TLB refill and general exception vectors. (0 -> normal; 1 -> bootstrap).
TS
TLB shutdown has occurred (read-only)
SR
A soft reset has occurred.
CH
"hit" (tag match and valid state) or "miss" indication for last CACHE Hit Invalidate, Hit Write Back Invalidate, Hit Write Back, Hit Set Virtual, or Create Dirty Exclusive for a secondary cache.
CE
Contents of the ECC register are used to set or modify the check bits of the caches when CE equals 1; see the ECC register description.
DE
Specifies that cache parity or ECC errors are not to cause exceptions.
IM
Interrupt Mask: controls the enabling of each of the external, internal, coprocessor and software interrupts (0 -> disabled; 1 -> enabled). The Interrupt Mask (IM) field is an 8-bit field that controls the enabling of eight interrupt conditions. An interrupt is taken if interrupts are enabled, and the corresponding bits are set in both the Interrupt Mast field of the Status register and the Interrupt Pending field of the Cause register.
KX
Enables 64-bit addressing in kernel mode. The Extended addressing TLB refill exception is used for TLB misses on kernel addresses (0 -> 32-bit,; 1 -> 64-bit).
SX
Enables 64-bit addressing and operations in supervisor mode. The Extended addressing TLB refill exception is used for TLB misses on supervisor addresses (0 -> 32-bit,; 1 -> 64-bit).
UX
Enables 64-bit addressing and operations in user mode. The Extended addressing TLB refill exception is used for TLB misses on supervisor addresses (0 -> 32-bit,; 1 -> 64-bit).
KSU
Mode (10 -> User; 01 -> Supervisor; 00 -> Kernel).
ERL
Error Level (0 -> normal; 1 -> error).
EXL
Exception Level (0 -> normal; 1 -> exception).
IE
Interrupt Enable (0 -> disable; 1 -> enable).

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 MIPS Status Register Key
CU
Coprocessor Useability. These bits control usability of the four possible coprocessors: Cu3, Cu2, Cu1 and Cu0. If a CU bit is set (=1), that coprocessor is usable.
0
Reserved. Must be written as zeroes, returns zeroes when read.
*BEV
Bootstrap Exception Vector. If set to 1, R3000 uses the alternate, bootstrap vectors for UTLB Miss and general exceptions.
*TS
TLB Shutdown. Set to 1 if R3000 has disabled TLB due to catastrophic error. Cleared by reset.
*PE
Parity Error. Set to 1 if cache parity error occurs. Reset by writing a 1 to this bit.
*CM
Cash Miss. Set to 1 if most recent D-Cache load resulted in a miss (only when the D-Cache is isolated).
*PZ
Parity Zero. When set to 1, causes zero to replace normal outgoing parity bits.
*Swc
Swap Caches. Control switching of control signals for I-Cache and D-Cache.
*IsC
Isolate Cache. When set to 1, isolated D-Cache from main memory system.
IntMask
Interrupt Mask. When a bit is set to 1, the corresponding hardware interrupt [Intr5..0] or software interrupt [Sw1..0] is enabled.
0
Reserved. Must be written as zeroes, returns zeroes when read.
KUo
Kernel/User mode, old. Set to 0 if Kernel, 1 if User.
IEo
Interrupt Enable, old. Set to 0 if Kernel, 1 if User.
KUp
Kernel/User mode, old. previous. Set to 0 if Kernel, 1 if User.
IEp
Interrupt Enable, previous. Set to 1 to enable, 0 to disable.
KUc
Kernel/User Mode, current. Set to 0 if Kernel, 1 if User.
IEc
Interrupt Enable, current. Set to 1 to enable, 0 to disable.

  • 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

    VM = 0
    (Virtual 8086 Mode) always execute in i386 protected mode
    RF
    (Resume Flag) updated by TDM
    NT = 0
    (Nested Task) not used, kernel and user programs execute as single i386 task
    IOPL = 3
    (I/O Privilege Level) any I/O instructions may be executed within user program
    DF
    (Direction Flag) should be updated before each string instruction
    IF
    (Interrupt Flag) set/cleared to enable/disable external interrupts

    CR0

    PG
    (PaGing) set during kernel startup if configuration parameter Page_Protection_Enabled is True
    ET
    (Extension Type) preserved at startup to differentiate between 80287 or 80387
    TS
    (Task Switched) set by kernel for each Ada task switch. Next floating point instruction causes exception #7. Exception handler does a deferred save/restore of the task floating point registers.
    EM
    (Emulation) set if 80287/80387 is not present. A 287/387 is required for floating point operations. No emulation software is provided.
    MP
    (Math Present) set if 80287/80387 is present.
    PE = 1
    (Protection Enable) always execute in i386 protected mode

    CR1

    Not used.

    CR2, CR3

    If Page_Protection_Enabled is False, CR2 and CR3 are not used.
    If Page_Protection_Enabled is True, CR3 is initialized with the starting address of the page directory specified by the kernel configuration parameter Page_Table_Array_Base. In CR2, the processor stores the linear address where the page fault occurred.

    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

    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:

    Register
    Use and Linkage
    D0
    Scratch register. Not preserved across calls. Used to return integer result values.
    D1
    Scratch register.
    D2
    Scratch register. Used to pass the first integer parameter.
    D3
    Scratch register. Used to pass the second integer parameter.
    D4..D7
    Non-volatile registers. Must be preserved across calls.
    A0..A1
    Scratch registers. Not preserved across calls.
    A2..A5
    Non-volatile registers. Must be preserved across calls.
    A6
    Frame Pointer (FP)
    A7
    Stack Pointer (SP)
    FP0..FP3
    Used to pass the first four float-point parameters.
    FP4..FP7
    Non-volatile floating-point registers.

    When interfacing to the C compiler observe the following conventions:

    Register
    Use and Linkage
    D0
    Scratch register. Not preserved across calls. Used to return function results (integer, single-precision, or upper-half of double precision result).
    D1
    Scratch register. Used to return the lower-half of a double precision result.
    D2..D7
    Non-volatile registers. Must be preserved across calls.
    A0..A1
    Scratch registers. Not preserved across calls.
    A2..A5
    Non-volatile registers. Must be preserved across calls.
    A6
    Frame Pointer (FP)
    A7
    Stack Pointer (SP)
    FP0..FP1
    Scratch registers. Not preserved across calls.
    FP4..FP7
    Non-volatile floating-point registers. Must be preserved across calls.


  • 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