The <intr.h> header file
This header file contains the following functions:
AUTO_INT ExecuteHandler GetIntVec SetIntVec
the following language extensions (implemented as macros):
DEFINE_INT_HANDLER
and the following constants and predefined types:
AUTO_INT_COUNT AutoInts Bool DUMMY_HANDLER
FIRST_AUTO_INT INT_HANDLER LAST_AUTO_INT
Functions
INT_HANDLER GetIntVec (long IntVec);
Gets an interrupt vector.
GetIntVec gets the content of the interrupt vector located at the absolute address
IntVec (typical values of IntVec are defined in enum
AutoInts). Typical usage of this function is to get the
current content of the interrupt vector to be restored later. See
DEFINE_INT_HANDLER for an example of usage.
void SetIntVec (long IntVec, INT_HANDLER Handler);
Sets an interrupt vector.
SetIntVec sets the interrupt vector located at the absolute address
IntVec (typical values of IntVec are defined in enum
AutoInts) to the interrupt handler pointed to by
Handler. Handler should be either the value returned from
GetIntVec, or address of an user-defined interrupt
handler defined using DEFINE_INT_HANDLER.
Note that Handler must not be address of an ordinary C function.
See DEFINE_INT_HANDLER for an example of usage.
Gets an address of an interrupt vector.
AUTO_INT returns the absolute address where the interrupt vector for Auto-Int IntNo
is located.
void ExecuteHandler (INT_HANDLER Handler);
Executes an interrupt handler.
ExecuteHandler executes the interrupt handler pointed to by Handler. The
only purpose of this function is to allow calling the previous
interrupt handler (usually the default one) from the user-defined interrupt
handler. This function must not be executed from anywhere out of the user-defined
interrupt handler (defined using DEFINE_INT_HANDLER).
Else, you will get the "Privilege Violation" crash, because all interrupt
handlers expect to be executed in the supervisor CPU mode. Parameter Handler
should be either the value returned from GetIntVec, or address
of an user-defined interrupt handler defined using DEFINE_INT_HANDLER.
It must not be address of an ordinary C function.
See DEFINE_INT_HANDLER for an example of usage.
Language extensions
DEFINE_INT_HANDLER is a language extension macro which is used for defining interrupt
handlers. The syntax is similar like function definition:
DEFINE_INT_HANDLER (HandlerName)
{
// The body of the handler...
}
Note, however, that DEFINE_INT_HANDLER does not define a standard function: it constructs
an object named HandlerName of INT_HANDLER type, which is initialized to point
to the handler body (implemented internally as a function, but unaccessable
directly to the rest of the program).
So, you can not call the interrupt handler using a standard call construction like
HandlerName ();
Such behaviour is implemented due to safety reasons: interrupt handlers are not supposed to
be executed directly. If you need to call the interrupt handler anyway, you can use
ExecuteHandler function. Here is an example of the program
which installs the new (user-defined) interrupt handler for auto interrupt 5 (the programable
timer interrupt), in which the old (default) handler is called too:
#include <intr.h>
#include <stdio.h>
#include <kbd.h>
int _ti89, _ti92plus;
INT_HANDLER OldInt5 = NULL;
volatile int Counter = 0;
DEFINE_INT_HANDLER (MyInt5)
{
Counter++;
ExecuteHandler (OldInt5);
}
void _main (void)
{
OldInt5 = GetIntVec (AUTO_INT_5);
SetIntVec (AUTO_INT_5, MyInt5);
while (!kbhit()) printf_xy (50, 50, "Counter=%d ", Counter);
SetIntVec (AUTO_INT_5, OldInt5);
GKeyFlush ();
}
The only legal usage of INT_HANDLER objects is to be passed
as arguments to the functions SetIntVec or
ExecuteHandler.
Be aware that the variable Counter in above example is declared as volatile
.
In fact, any global variable which may be changed by the
interrupt handler should be declared as volatile
, especially if it is accessed
from the other parts of the program (i.e. out of the interrupt handler). This is necessary
to prevent various optimizations which may be fooled by the fact
that the variable may be changed in a way which is completely unpredictable from the aspect
of the normal program flow. For example, various optimizations may force keeping the variable
content in a register, so if the variable is changed asynchronously, the compiler will not
know anything about it. volatile
will prevent keeping the variable in a
register, so it will be reloaded from the memory on each access. The example given above
will still work if you omit volatile
keyword, but more complicated programs
will probably not work correctly without it.
Constants and predefined types
Bool is enumerated type for describing true or false values. It is defined as
enum Bool {FALSE, TRUE};
AutoInts is enumerated type for easier access to the standard interrupt vectors.
So far, this enum is incomplete, and includes only Auto-Int vectors (not traps,
CPU exceptions, etc.). It is currently defined as
enum AutoInts {AUTO_INT_1 = 0x64, AUTO_INT_2 = 0x68, AUTO_INT_3 = 0x6C,
AUTO_INT_4 = 0x70, AUTO_INT_5 = 0x74, AUTO_INT_6 = 0x78, AUTO_INT_7 = 0x7C};
INT_HANDLER is a pointer type which represents the address of the interrupt handler.
It might be logical that INT_HANDLER is defined as a pointer to a void function, i.e.
typedef void (*INT_HANDLER)(void);
But this is not true. Instead, INT_HANDLER is a pointer to a strange structure
(its shape is completely irrelevant, as this structure is never used, neither
explicitely nor implicitely). Such unusual behaviour is implemented due to safety reasons.
First, with such implementation it is impossible to call an
interrupt handler using a simple function call (which would be possible if INT_HANDLER
is implemented as a pointer to a function). Second, as INT_HANDLER is a pointer to an
unusual structure, the compiler can emit a warning if you try to pass anything which
is not created using DEFINE_INT_HANDLER or returned
from GetIntVec to the functions SetIntVec
and ExecuteHandler. For example, you will be warned if
you try to pass an ordinary void function instead of properly-defined interrupt handler
to the SetIntVec.
FIRST_AUTO_INT is the constant with value 1 (the index of the first Auto-Int vector).
LAST_AUTO_INT is the constant with value 7 (the index of the last Auto-Int vector).
AUTO_INT_COUNT is the constant with value 7 (the total number of Auto-Int vectors).
DUMMY_HANDLER is the constant of type INT_HANDLER (more precise,
it is a macro which behaves as a constant), which points to a dummy interrupt handler, i.e.
to the handler which consists only of 'rte'
. The purpose of this constant is
to redirect an interrupt vector to "nothing", in cases when disabling interrupts is not
possible. For example, you can not disable auto-int 1 in grayscale programs, because grayscale
support is based on it. Grayscale support installs its own auto-int 1 handler, which executes
previously installed handler at the end. Suppose that you don't want calling default auto-int 1
handler, which trashes the status line by displaying keyboard status indicators. You can
redirect auto-int 1 to the dummy handler before enabling grayscale, so
after the grayscale interrupt, the dummy handler (i.e. nothing) will be called instead of the
default auto-int 1 handler:
INT_HANDLER save_int_1;
...
save_int_1 = GetIntVec (AUTO_INT_1);
SetIntVec (AUTO_INT_1, DUMMY_HANDLER); // redirect auto-int 1 to "nothing"
// enable grayscale
// do whatever you want in grayscale
// disable grayscale
SetIntVec (AUTO_INT_1, save_int_1);