home *** CD-ROM | disk | FTP | other *** search
- { (C) Copyright 1986-1992 MetaWare Incorporated; Santa Cruz, CA 95060. }
-
- pragma C_include('implement.pf');
- pragma C_include('language.pf');
- package Interrupts;
- with Language;
- pragma Routine_aliasing_convention(Implement.RTE_aliasing);
- #if 0
- In this package we implement both general interrupt handling
- and a simple control/C trap routine.
- If you're just interested in the latter, go to the end.
- If you just want to call an arbitrary DOS interrupt and don't care
- to write interrupt routines, see msdos.pf.
- #endif
-
- -- To get inline CLI and STI, use the intrinsic Inline:
- #define CLI Loopholes.Inline(#fa)
- #define STI Loopholes.Inline(#fb)
-
- #if 0
- When an interrupt occurs, the registers in Machine_status
- are saved at the entry and restored at the exit of an interrupt
- routine. If the interrupt routine modifies the registers, the
- restored values will be the modified versions. Therefore it is
- possible to write an interrupt routine to completely replace, e.g.,
- MS-DOS's INT 21, which takes arguments from registers and returns
- results in registers.
- In addition to the registers, the address following the location
- of the interrupt and the flags register that were saved by the
- processor are available for modification or printing.
- #endif
-
- type Machine_status = record
- { In reverse of the order in which they're pushed: }
- ES,DS,DI,SI,Waste1,Waste2,BX,DX,CX,AX: Cardinal;
- { The wasted portions are due to the 286 PUSHA/POPA instructions }
- { used to effect the save. We match the arrangement on the 8086 }
- { for object code compatibility. }
- Interrupt_address: record
- Offset:Unsigned; Segment:Cardinal;
- end;
- Flags: Cardinal;
- end;
-
- const Pascal_interrupt = Pascal + [Interrupt];
-
- { Each interrupt is designated by an index into the hardware }
- { interrupt vector located at address 0: }
- { var Interrupt_vector: array[0.. ...] of 4-byte-address }
- { Type Interrupt_index subscripts this array. }
-
- type Interrupt_index = Cardinal;
- const Control_C_interrupt_index = #23; -- MS-DOS.
-
- #if 0
- To declare that a routine is an interrupt routine, it must
- have calling convention Pascal_interrupt and be of routine type
- Interrupt_routine.
- Because the representation of interrupt routines differ
- depending upon the 8086 memory model used, and
- each 8086 interrupt entry always is a segment-offset pair,
- the Interrupt_vector_element type represents the latter.
- Thus the latter cannot be an Interrupt_routine.
- On more reasonable machines, these two types are identical.
- #endif
-
- pragma Calling_convention(Pascal_interrupt);
- type Interrupt_routine = procedure(S:Machine_status);
- type Interrupt_vector_element = record Offset,Segment:Cardinal; end;
- pragma Calling_convention();
-
- { Enable/disable interrupts. }
- procedure Interrupt_enable; {STI} External;
- procedure Interrupt_disable; {CLI} External;
- { Generate an "int V" instruction and execute it. }
- procedure Int(V:Interrupt_index); External;
-
- { Value of Interrupt_vector[V]. }
- function Vector_element(V:Interrupt_index):Interrupt_vector_element;
- External;
-
- { Make P the interrupt handler for Interrupt_index V. }
-
- procedure Install_interrupt_routine
- (V:Interrupt_index; P:Interrupt_routine); External;
- procedure Install_interrupt_vector_element
- (V:Interrupt_index; P:Interrupt_vector_element); External;
- #if 0
- NOTE: It is not safe to install an Interrupt_routine P that is not
- a level-1 routine!!! The hardware will NOT supply the up-level
- context that P needs to access the local variables of its parent
- routine. It is OK to install P only if no up-level references are
- made -- and THIS INCLUDES any "goto" out of P. See examples
- below for a way to get around this.
-
- You can call either Pascal interrupt routines or 8086
- Interrupt_vector_elements directly with the following
- procedures that take arguments from and return results in
- the passed Machine_status.
- #endif
- procedure Call_interrupt_routine
- (var S:Machine_status; P:Interrupt_routine); External;
- procedure Call_interrupt_vector_element
- (var S:Machine_status; P:Interrupt_vector_element); External;
-
- #if 0
- For example, to define a routine to catch the MS-DOS control/C
- interrupt:
- with Interrupts; -- Open this package.
- pragma Calling_convention(Pascal_interrupt);
- procedure Catch_Control_C(S:Machine_status);
- pragma Calling_convention(); -- Restore default convention.
- begin
- writeln('Ouch!! You typed Control/C!!');
- writeln('Don''t do it again!');
- end;
- ...
- Install_interrupt_routine(Control_C_interrupt_index,Catch_Control_C);
- -- Now, when ^C is typed, Catch_control_C is invoked.
-
- The interrupt routine may not generally be a non-level-1 procedure,
- since the up-level context is not loaded by the hardware when the
- interrupt occurs. To achieve the same effect, store the non-level-1
- procedure in a global procedure variable, and call this variable
- from a level-1 interrupt routine. Since a procedure variable records
- the up-level context, such context can be re-loaded when the
- procedure variable is called.
- For example, here is a way to
- use ^C to get back into an editor command level:
-
-
- with Interrupts; -- Open this package.
-
- var Call_when_CC_occurs: procedure;
- pragma Calling_convention(Pascal_interrupt);
- procedure Catch_Control_C(S:Machine_status);
- pragma Calling_convention(); -- Restore default convention.
- begin
- Call_when_CC_occurs();
- end;
- procedure Editor;
- label 1;
- procedure CC;
- begin
- writeln('<INTERRUPT> -- command terminated.');
- goto 1; -- Non-local goto requires up-level reference to label.
- end;
- begin
- Call_when_CC_occurs := CC;
- Install_interrupt_routine(Control_C_interrupt_index,Catch_Control_C);
- 1: while True do begin
- Read_editor_command;
- Process_editor_command;
- end;
- end;
-
- Use Vector_element to retrieve the MS-DOS control/C routine BEFORE
- you insert your own, so that you can restore it before exiting:
-
- var Old_routine: Interrupt_vector_element;
- ...
- begin
- Old_routine := Vector_element(Control_C_interrupt_index);
- Install_interrupt_routine(Control_C_interrupt_index,My_routine);
- ...
- Install_interrupt_vector_element(Control_C_interrupt_index,Old_routine);
- end;
- #endif
-
- { Because MS assembler truncates at 31, we had to use shorter names: }
- pragma Alias(Install_interrupt_vector_element,Implement.RTE || 'install_ive');
- pragma Alias(Call_interrupt_vector_element,Implement.RTE || 'call_ive');
-
-
-
- -- Catching control/C:
-
- procedure On_user_interrupt(function F(): Boolean); external;
- #if 0
- When a user interrupt occurs (^C), function F will be invoked.
- If F returns "TRUE" then the program will be immediately aborted;
- otherwise, execution will continue
- at the point that the interrupt occurred.
-
- If F is NOT a level-one (outermost) routine, read this:
- F may contain up-level addressing, i.e., access to ancestor's
- local variables, and it may contain a non-local goto to an
- ancestor. HOWEVER, YOU MUST ENSURE that F's ancestry is intact
- when the interrupt occurs. For example, if the routine that contains
- F returns, then up-level addressing or non-local gotos from F will
- no longer work.
- If F is a level-one routine, this is not an issue, since F will
- then have no ancestry.
-
- ALSO: MS-DOS does not support recursive interrupts.
- #endif
- end;
-