Purpose of this note
This notes outlines some important features of interrupt handling in DACS-80x86 systems and proposes various good practices that will ensure the success of the application in which the interrupts are used.
Interrupt types
The DACS-80x86 Run-Time System (RTS) offers two interrupt handler flavours, the Normal and the Fast interrupt. In both cases the DACS-80x86 compiler generates an interrupt routine that is called, when the interrupt in question occurs, and which starts with saving off the processor state, so that the interrupted task can be properly restored, when the interrupt handling completes. The interrupt routines get names that are automatically recognized by the 80x86 target linker which inserts them in the application's Interrupt Descriptor Table (IDT). If a particular interrupt can be held back until proper elaboration of the interrupt handler code has been achieved, this will ease the use of such interrupts. Such holding-back can on some boards be achieved by not opening for interrupts in the interrupt controller, until you are ready to receive them. Another technique is to use a global variable and only actually do something in an interrupt handler, if the global variable has received a specific (unusual) value.
Normal interrupts
The Normal interrupt handler calls into the RTS with notification about which interrupt number occurred, and if the task that handles the interrupt is ready to accept, control is passed to this immediately. Otherwise the interrupt is queued, until the recipient task accepts the call. Because of priority inversion or heavy load it may take a while, before the interrupt accept code is entered, and possibly a new interrupt from the same device may cause overrun. By giving normal interrupt handling tasks high priority, such situations may be avoided. The RTS assigns an interrupt specific queuing element to the interrupt and queues this to the called entry - overruns are automatically ignored inside the RTS.
Fast interrupts
The Fast interrupt handler rides "on the back" of the interrupted task, so no task scheduling takes place. The handler starts execution of the accept statements (disabled) and should - as is the case for normal handlers as well - enable a possible interrupt controller entry/bit, when handling is complete, to assure that the device does not fire prematurely.
Alerting other tasks
A special situation occurs, if the interrupt is supposed to trigger another task as part of the interrupt handling. For normal interrupt handlers this can be done "the Ada way" by calling the task entries in question directly, as the interrupt itself was complete some time back, and the called entry now behaves as any other called entry. Fast interrupt handlers however must use special means to assure RTS integrity, if other task entries are to be called. The DACS model is that a special SELECT form is used that has the effect of generating a special entry call for interrupt, instead of the normal entry call. The RTS then takes an interrupt queuing element and queues it to the called task. Since the code is executing disabled this requires special attention, and once the interrupt is left using a compiler generated IRET instruction, control is directed to the RTS for post-interrupt handling. This is necessary, if the interrupted task has lower priority than any of the tasks that have been called inside the interrupt, because the highest of the latter should now be granted control. The interrupted task will eventually be (re)scheduled, when its priority allows, and will then finally complete its interruption and restore.
Using semaphores
To even avoid the use of a queuing element the DACS-80x86 RTS offers another synchronization scheme with the use of a semaphore. Tasks that wait for an interrupt makes an agreement with the interrupt handler in question about the use of a (global) semaphore, and they issue P-operation to wait until the semaphore becomes free (initial value for the semaphore is then 0). Inside the interrupt handler itself the semaphore is signalled using a special interrupt variant of the V-operation called VI (the semaphore operations are available in the RTS_Entrypoints package). The VI-operation does something similar to the interrupt-entry-call, and a single interrupt may signal several semaphores and/or make interrupt-entry-calls. The advantage of the semaphore is that the synchronization points are easy to understand and well-defined in behaviour, as opposed to e.g. an timed select statement. The disadvantage is that waiting for a semaphore causes a wait until the semaphore is signalled, and is not designed for time-out. If a time-out is required, an extra task must be introduced to make the P-operation, and the original recipient task then synchronizes with the new task using a timed SELECT call, since this is now a normal Ada task synchronization. Once the P-operation falls through it shall call the proper task entry - preferably at highest priority - and then loop back for a renewed P-operation that now makes it wait again for the interrupt to occur.
Common data synchronization
Since interrupt handlers get activated at unpredictable times, any data they might manipulate must be protected in some way against simultaneous use or update. For normal tasks this may be achieved via semaphores, but semaphores do not prevent interrupts from coming through. A useful technique is then to create disabled critical regions inside which global data is manipulated. The interrupt handler can be viewed as a single critical region too. At the entry to such a critical region the machine flags can be saved, the processor is disabled and the critical region entered. At the end of the critical region the saved flags are restored, whereby the interrupts may be enabled again. The CIFO packages contain subprograms that allow disabling and enabling, but using machine code insertions it is simple to create a flags-save-and-disable sequence as well as a flags-restore sequence. The amount of instructions executed while being disabled in a critical region should be minimized, as well as it should be assured that no subprograms (Ada or RTS) are called which might accidentally enable the processor. This is particular important inside Fast interrupt handlers, since the whole schema about making post-interrupt handling will fail, if interrupts are enabled prematurely.
More reading
For more information and reading the DACS-80x86 Compiler User's Guide, Appendix F, describes in more detail how interrupt handlers may be written.
Customer Quote:
"DDC-I has a good solution for us and your willingness to get down to the issues and help us get what we need has made it a clear choice."