               Hooking Interrupts in Turbo Pascal
                         Michael Day                    09/23/92


There are a number of ways to hook an interrupt. In most cases 
they all result in the same effect. They simply differ in how to 
get there.

Writing the Interrupt:

The method supplied by Pascal itself is to use the Interrupt tag 
on a procedure to tell the compiler that it is being used as an 
interrupt procedure. 

The Interrupt does several things. First it causes the procedure 
return to be declared as a IRET instead of the normal RET that 
would be used. When an interrupt is called, it not only saves the 
current code address on the stack, it also saves the current 
flags on the stack. The reasoning behind this is so that the 
state of the flags will be automatically restored on exit from 
the interrupt call. This is needed mainly to manage the interrupt 
flag itself. With out the save, you could never know what the 
previous condition of the interrupt flag was.

The other thing the Interrupt tag does is to tell the compiler to 
push all the registers on entry to the procedure and pop them all 
off on exit. This is needed because when calling a Pascal 
procedure, there is no knowing what registers will get destroyed, 
and all registers MUST be restored to their original condition 
before the interrupt was called. 

Finally, the Interrupt tag tells the compiler to reload the data 
segment register at the start of the interrupt procedure (after 
pushing the registers). This is needed because in an interrupt 
procedure you don't know where you came from, and the registers 
may not be what they were in the main program. In fact, it is 
highly unlikely that they will be in any recognizable condition. 

What the Interrupt tag does NOT do is reload the stack segment. 
This is on purpose, because there is no way to know where in the 
program stack you are, and it would be disasterous to attach to 
the stack at an improper location. It is up to the programmer to 
deal with stack issues. In most simple interrupt procedures, you 
can usually safely ignore the stack issue. 

The stack you will be using is the stack that was in effect at 
the time that the interrupt occurred. Because of this, you must 
use extreme caution with the stack in an interrupt. You should 
never use more than a dozen or so levels of the stack in an 
interrupt. If you expect that you will need more, you should set 
up your own stack. How to set up your own stack will be discussed 
later. 


Re-entrancy issues:

Extreme caution must be used when writing an interrupt procedure, 
there are many restrictions. You cannot write code in an 
interrupt procedure like you write code in a normal program. 

First, you must be aware of re-entrancy issues. Re-entrant means 
calling some other routine from your interrupt procedure. It is 
possible that the procedure you are calling was in operation at 
the time that the interrupt occurred, and so if you call the 
routine from the interrupt procedure, you would be re-entering 
the operating procedure. This can cause disruption to routines 
that don't take re-entrancy into account. 

Much of DOS and the programs that run under it are not re-
enterable. What that means is that you cannot generate code that 
calls those procedures. Do not call any BIOS routines (other 
interrupts). Do not call any DOS routines (Int $21). Do not call 
any run-time library functions. 

I will not go into designing re-entrant code in this discussion. 
That is a subject all its own. For now, just realize that you 
cannot call DOS or BIOS routines because they were not designed 
to be re-entrant.  As with DOS and the BIOS, the Turbo Pascal 
run-time library is not re-entrant, that means that you should 
avoid calling any Turbo Pascal Run-time calls. 

Some runtime calls are built-in to the compiled code, and you 
should avoid them as well. As an example, Stack checking must be 
avoided because you are not using the program stack, you are 
using an unknown stack. That will cause serious conflicts and 
most likely a stack error. This just compounds the problem 
because when you get a stack error, TP tries to process it. That 
means a run-time library call to the error handler, and your 
program ends up digging it's own grave. 

IO checking is the same way. So is Range checking. Anything that 
could cause a runtime library call must be avoided. That means 
that you should turn off the Stack, IO, and Range checking for 
any code used by the interrupt procedure.

In addition to not doing IO/Range/Stack checking, you should not 
call routines such as Writeln or Readln which are part of the 
run-time library. Doing so is an invitation to crash and burn. 
Nor should you overlay Interrupt procedures.

You should also be very careful with regard to re-entrancy in 
your own code. The same re-entrancy issues that are in the run-
time library holds for your own code. You can call other 
procedures from and interrupt procedure, but if you are calling 
procedures that are used by the mainline code, you must be 
extremely careful of re-entrancy problems. If you are uncertain 
of the effects, it is best to not share the code. Practice safe 
coding. The same is true of variables, don't share variables 
unless you fully understand the effects of doing so.

There are really only two primary issues that trip programmers 
up when writing interrupt procedures; re-entrancy, and time. 

Time: 

The other critical aspect about interrupts is time. An interrupt 
procedure MUST be as short in time as possible. You should strive 
to keep the process time to under 100 microseconds. Preferably 
faster if possible. Time resources on the computer are limited. 
Remember that there are other interrupts out there demanding 
service, and some of them expect to be serviced in a timely 
manner. If you have a RS232 serial communications task going on 
in the background say at 9600 baud, and the total interrupt 
service time of the computer exceeds one millisecond, you will 
lose data on the serial port. 

Time is a fixed quantity, you can't change it. If something like 
a serial port demands to be serviced, you must allow it to be 
serviced, or you will lose the data. 

Some interrupts are built into the in the system. Floppy disk, 
hard disk, keyboard and the clock timer. These all have their 
demands on the computer's time, and they must be serviced.

There are many add in boards as well that place their own demands 
on the interrupts. Two of the most common ones are the mouse and 
RS232 (modem). There are many other ones becoming popular too, 
such as Sound cards and CD-ROMs. All of these must be allowed to 
have their interrupts serviced.

The 100 microsecond (uS) figure is just a rule of thumb that I've 
developed over time with my experience in writing interrupt 
drivers. You can go longer, but care must be used if you do so. 
That doesn't mean that the system will crash if you go to 150uS, 
it won't, but the longer the time, the greater the chance of 
timing related problems occurring. 

Keep in mind that not only are the existing interrupts in the 
system, but other TSRs and programs in the system may be 
attaching themselves to the interrupts too, adding even more to 
the demands on the available time. 

To give you some ideas of what to expect in a typical situation, 
consider a serial port running at 9600 baud receiving a stream of 
data. 9600 baud means that the port is receiving data at 960 
characters a second. To make things easier for this discussion, 
we'll round that up to 1000 characters per second. That means 
that the port is receiving 1000 characters per second. The rs232 
software must respond to each character received if it is not to 
lose the received data. You can't stop the incoming data, you can 
only respond to it. That means that the interrupt must respond at 
a one millisecond rate. In other words, that means that if you 
don't want to lose data, the total interrupt service time in the 
computer must be less than one millisecond to be able to service 
the RS232 port at 9600 baud.

As you can imagine, at faster baudrates, the problem becomes even 
more sever. The fastest that the standard PC port can operate at 
is 115200 baud (although this is a bit unreliable, and the 
component manufactures do not guarantee the operation at that 
speed). The fastest reliable speed of operation is 57600 baud.
At 57600 baud, the serial port must be serviced at a rate of once 
every 173 microseconds. That is not much time to get anything 
done. As you can see, it is extremely important to pay attention 
to the timing issues in the interrupt service routines.  

Sometimes you may simply have no choice but to write a routine 
that takes longer than desired to service the interrupt. In that 
case is is important to tell the user so that they will be aware 
of the limitations of the program and won't be surprised to 
discover that their system has suddenly become flaky. 

The RS232 is normally the worst case service area in the PC. 
Usually you can use it as a bench mark for your code timing 
issues. 

The next important time level is the internal system clock. The 
bios maintains an internal clock to keep track of time. The clock 
is driven from an interrupt. The clock performs an interrupt once 
every 55 milliseconds. If your interrupt service routine takes 
longer than 55mS to perform, the computer will begin to lose 
time. You should never write an interrupt routine that takes 
longer than 55mS to perform. There are many things in the 
computer that are tied to the clock which will be disrupted. 

That being said, in extreme cases, you can get away with 
occasionally exceeding the 55mS time, but the overall average 
service time should never exceed 55mS. You should still avoid 
writing interrupt routines that take this long, however, because 
the computer will lose time because of the lost timer interrupts. 
Also keep in mind that interrupt service routines of this length 
in time are not going to be able to operate at the same time as 
an RS232 program since you won't be servicing the RS232 in a 
timely manner. 
     
The other issue to be aware of is that your interrupt service 
routine must be aware of itself. As an example, if you write an 
RS232 service routine, it must be fast enough to service the 
serial port. Failure to do so will cause lost data. 


Writing Safe Long Interrupts:

There are ways to safely write service routines that take longer 
than 100uS to perform, but they complicate things greatly. You do 
this by re-enabling the interrupt system as quickly as possible 
in your own service routine. This allows other service routines 
to have access to the computer while yours is being processed. 
This means that time critical routines such as RS232 can now 
interrupt your interrupt so they can be serviced.

In the BIOS, some routines are implemented that way. An example 
is the keyboard. The keyboard interrupt can take a long time to 
service the keyboard. Enough so that the RS232 would not be 
serviced in a timely fashion. Thus the keyboard interrupt re-
enables the interrupts as quick as it can so that other 
interrupts can have access to the system. This creates other 
problems though. It is now possible for the keyboard interrupt to 
interrupt itself. In fact, this problem has been the source of 
problems on the PC. Fast typists have caused the keyboard 
interrupt to be entered multiple times resulting in a system 
crash. The STACKS=n,n statement in CONFIG.SYS attempts to deal 
with the problem, but it is not a perfect solution. Anytime an 
interrupt can interrupt itself, the possibility exists of a stack  
crash as the service runs out of memory. 

In addition to the service issue, the interrupt must be aware of 
re-entrancy issues if it allows itself to be re-interrupted. 
Ideally the solution would be to not allow the re-interrupt to 
occur by servicing the interrupt in a fashion that would ensure 
that it would be serviced before the next interrupt. This is not 
always possible. In that case, the three options are to (1. Mask 
the interrupt to prevent it from happening.  (2. Ignore the extra 
interrupt, and deal with the fallout later.  or (3. Attempt to 
service the extra interrupt in some fashion. Except in a few 
special cases, attempting to service the extra interrupt can lead 
to serious problems. What do you do if the interrupted interrupt 
gets interrupted, etc... This is one of the common problems of 
dealing with interrupts. Time is a fixed quantity, you can't 
change it, so the system design must take into account the 
service time and allow for all tasks to be serviced within the 
allowed time period. 

Also keep in mind that if you do allow the interrupt to be re-
interrupted, you must now deal with the re-entrancy problem of 
shared code and variables. Because of this, most people simply 
mask out their own interrupt to prevent it from interrupting them 
again. You can mask your own interrupt while allowing other 
interrupts to occur in the system. If you are hooking into 
someone else's interrupt, such as the clock service interrupt, 
you should be extremely careful about this. The clock service 
interrupt still needs to be serviced at a 55mS rate, and masking 
the interrupt longer than 55mS will cause the interrupt to be 
lost and thus the internal clock will lose time. 


Creating Your Own Stack:

Normally you should keep the code used in an interrupt as small 
and as simple as possible. If however you find that you need to 
make use of a stack more than a dozen or so levels deep, you must 
create your own stack or you will be subject to the wrath of the 
computer Gods. Resulting in a crash and burn at the most 
inopportune time. 

It is not that hard to create your own stack. First you must 
decide how much stack you will need. Microsoft used to say that 
512 bytes was needed to service DOS and BIOS functions. That has 
been creeping up over the years. These days 1024 bytes is 
considered a safer area to be in. you should add 1000 bytes of 
stack space to your on internal requirements if you allow other 
interrupts to interrupt your service routine. 

If you do not allow any other interrupts to occur inside your 
interrupt, then you don't need to worry about the stack 
requirements of outside code. You only need to worry about your 
own stack requirements. 

Once you decide how much stack you need, you must decide where 
you will put it in memory. The problem with the interrupt stack 
is that it must be a fixed area of memory. You never know when 
the interrupt will happen, so the memory must be there ready and 
waiting for the interrupt to happen. Also, heap allocation is a 
run-time function, so you cannot call it inside the interrupt 
procedure. That means that you must allocate the stack as fixed 
memory that never changes location. The easiest way to do this is 
to put it in the data segment as a global variable.

By having it in the data segment, it is predefined and it is 
always there waiting for you. 

Here is an example:

     var MyStack:array[0..1000] of word;
         OldStack:pointer;  
     procedure MyInt; Interrupt;
     begin
       asm
         mov word ptr [OldStack+2],sp  {save the current stack}
         mov word ptr [OldStack],ss
         mov ss,SEG @Mystack           {set up our own stack}
         mov sp,OFFSET @MyStack+2000   { note that the stack} 
       end;                            { starts at end of array} 

       {...your code here...}

       asm   
         mov sp,word ptr [OldStack]    {restore original stack}
         mov ss,word ptr [OldStack+2]   
       end;
     end;


Note that if you re-enabled interrupts you must disable the 
interrupts before changing the stack pointer. This is critical. 
Failure to do so will cause the computer to crash and burn as the 
stack may be pointing to the wrong place in memory if the 
interrupt happens in the middle of the change over.


Another area to place the stack is to put it on the heap. This is 
similar to having it in the data segment, except that you would 
have a pointer in the data segment pointing to the array on the 
heap and you would get the address from the pointer rather than 
getting the offset of the array. You must however allocate the 
stack on the heap before you allow the interrupts to happen, it 
must stay in effect as long as the interrupt routine is in 
effect. The advantage is that you can recover the memory used if 
you no longer need the interrupt service routine. 


Here is an example:

     type StackArray = array[0..1000] of word;
     var MyStack:^StackArray;
         OldStack:pointer;  
     begin
       new(MyStack);
     end;

     procedure MyInt; Interrupt;
     begin
       asm
         mov word ptr [OldStack+2],sp   {save the current stack}
         mov word ptr [OldStack],ss
         mov ss,word ptr [Mystack]      {set up our own stack}
         mov sp,word ptr [MyStack+2]    { note that the stack} 
         add sp,2000                    { starts at end of array} 
       end;                             

       {...your code here...}

       asm   
         mov sp,word ptr [OldStack]    {restore original stack}
         mov ss,word ptr [OldStack+2]   
       end;
     end;



A More Efficient Interrupt:

While there is no question that the Interrupt directive available 
in Turbo Pascal makes writing an Interrupt service routine 
easier, it does cost a bit in overhead on the stack and in code 
generated. When all you want to do is increment a variable and 
get out, it can be a bit wasteful. A more efficient way to write 
the service routine would be to do it entirely in assembler. With 
the advent of the built in assembler available in version 6.0 of 
Turbo Pascal, this becomes even easier. In fact, with that, there 
becomes less of a reason to use the Interrupt directive even in 
more complex interrupt service routines.


A Simple ISR:

The following is a simple unit that can be included in your 
programs which provides a tick counter that is similar to the 
GetTickCount function in Windows. It provides a counter which 
tells you how long the program has been running in milliseconds. 
Once every clock tick, the counter gets 55 added to it. 

{---------------------------------------------------------------}
{ This Unit will track in milliseconds how long the program has } 
{ running since it was turned on (similar to the Windows        } 
{ GetTickCount function). The resolution is 55mS. That is, 55mS }
{ is added to the count on each system clock tick.              }
{---------------------------------------------------------------}

{$R-,S-,I-,O-}
unit Ticker;
interface

const TickCount : longint = 0;

implementation
uses dos;

var Old1Cvec:pointer;    {storage for previous $1C int vector}
var ExitSave : pointer;  {storage for previous exit procedure}

procedure TickerExit; far;
begin
  SetIntVec($1C,Old1Cvec);  {restore the previous exit procedure}
  ExitProc := ExitSave;     {on exit from the program}
end;

{This is the interrupt service routine}
procedure TickerHook; far; assembler;
asm
  push ds               {save the current registers}
  push ax
  mov ax,SEG @Data      {get the TP data segment}
  mov ds,ax
  add word ptr [TickCount],55   {add 55 to TickCount} 
  jnc @done
  inc word ptr [TickCount + 2]  {(it's a longint)}

 @done:                                     
  pushf                 {push flags to simulate interrupt call}
  call dword ptr [Old1Cvec]   {call the previous clock vector}
  pop ax                {restore the saved regs}
  pop ds                                    
  iret                  {return from the interrupt}
end;

{----------}
begin
  GetIntVec($1C,Old1Cvec);    {save current clock int vector}
  ExitSave := ExitProc;       {save current exit proc}
  ExitProc := @TickerExit;    {poke in our own exit proc}
  SetIntVec($1C,@TickerHook); {poke in our own clock int vector}
end.


Note that an IRET instruction is used inside the procedure 
instead of relying on the normal procedure return. This is 
because without the Interrupt directive, the compiler will 
generate a RET instruction, but an interrupt service routine must 
always end with an IRET instruction so that the original flags 
are poped off the stack. Thus the procedure RET instruction will 
be ignored since the IRET instruction is coded ahead of it.

One potential problem with coding the way shown above is that the 
stack is used for the call to the previous interrupt vector so 
that it can be serviced. Normally this is not a problem, but if 
stack space is precious, or if you expect to chain to a lot of 
little routines like this, it may be a problem. One way to solve 
the problem is to dispose of everything off the stack before 
linking to the previous service vector. 

To do that requires storing the link address in the code segment 
rather than using a variable in the data segment. We do this 
because without the availability of the data segment, we cannot 
access the data segment pointer. So we transfer it to the local 
code segment before disposing of the data segment. 


{This is an alternate interrupt service routine}
procedure TickerHook; far; assembler;
asm
  push ds                        {save the current registers}
  push ax
  mov ax,SEG @Data               {get the TP data segment}
  mov ds,ax
  add word ptr [TickCount],55    {add 55 to TickCount} 
  jnc @done
  inc word ptr [TickCount + 2]   {(it's a longint)}

 @done:
  lds ax,[Old1Cvec]              {get previous clk int vector}
  mov word ptr @Local,ax         {save it to a code seg var}
  mov word ptr @Local+2,ds
  pop ax                         {restore registers} 
  pop ds
  jmp Dword ptr cs:[@Local]      {go process previous vector} 
 @Local: DD 0                    {local code segment variable}     
end;


There is another simpler way to do this that relies on creating 
another assembler procedure that contains nothing more than a 
code segment variable. The problem with that though is that it is 
dependent upon all the procedures in the unit being in the same 
code segment. That may not hold for future implementations or in 
other versions of Pascal. It definitely won't work in 
environments such as Windows which disallow writing to the code 
segment. 

{This is an example code seg var interrupt service routine}
procedure TickerVar; far; assembler;
asm
  DD 0
end;
procedure TickerHook; far; assembler;
asm
  push ds                        {save the current registers}
  push ax
  mov ax,SEG @Data               {get the TP data segment}
  mov ds,ax
  add word ptr [TickCount],55    {add 55 to TickCount} 
  jnc @done
  inc word ptr [TickCount + 2]   {(it's a longint)}

 @done:
  pop ax                         {restore registers} 
  pop ds
  jmp Dword ptr cs:[@TickerVar]  {go process previous vector} 
end;

begin
  GetIntVec($1C,Old1Cvec);    {save current clock int vector}
  asm
    les ax,[Old1Cvec]               {get prev clock vector}
    mov word ptr cs:[TickerVar],ax  {poke its address into}
    mov word ptr cs:[TickerVar],es  {code seg var}
  end;
  ExitSave := ExitProc;       {save current exit proc}
  ExitProc := @TickerExit;    {poke in our own exit proc}
  SetIntVec($1C,@TickerHook); {poke in our own clock int vector}
end.


<eof>

