Turbo interrupt handler code I've had several requests for the Turbo interrupt handler installation code I mentioned a few weeks back, and I've been having increasing difficulty constructing return addresses. So, at the suggestion of several correspondents, I'm sending the code to you. It provides two procedures which allow the installation and removal of ordinary Turbo procedures as interrupt handlers. Optionally the interrupt handler may use its own internal stack and chain the old interrupt vector on exit. Procedures dealing with external interrupts will need to handle the 8259 interrupt controller themselves. Since writing the code below I have sorted out more or less how to do this, and should there be any interest I'll put together a summary of what to do. Jim Hague jmh@ukc.uucp -------------------------------------------------------------------- { A Turbo package to ease the use of interrupt routines written in Turbo Pascal by allowing you to install ordinary Turbo procedures as interrupt handlers without the need for gobs of inline statements within each procedure. As far as the outside world is concerned, it provides type interrupt_workspace ; procedure InstallInterruptHandler ( routine_offset, intrrupt_no : integer ; chain_old_vector, enable_ints_during, new_stack : boolean ; var workspace : interrupt_workspace ) This installs a Turbo procedure whose offset is routine_ptr (ie ofs(RoutineName) ) to be activated by interrupt no intr_no. Note that if the interrupt is an external one the 8259 is not touched - you must do this yourself. If chain_old_vector is true then the previous interrupt vector is chained to at the end of the interrupt procedure. If enable_ints_during is true then interrupts are enabled during the course of the interrupt procedure. If new_stack is true then then the stack is switched to an internal one within 'workspace' for the duration of the interrupt (you won't need this only for interrupts called with INT instructions when you know their stack has room enough). Interrupts arriving at unpredictable times may arrive during a DOS call and overflow the DOS stack, hence the need to switch stacks. Each interrupt must have a variable of type interrupt_workspace accompanying it - it MUST appear in a var declaration, not 'new'ed off the stack. procedure RemoveInterruptHandler ( interrupt_no : integer ; var workspace : interrupt_workspace ) ; Removes the interrupt handler at interrupt no intr_no that was installed by InstallInterruptHandler and replaces it with the vector that was there previously, as held in workspace. FEED THIS THE CORRECT WORKSPACE ! Also provided are constants CLI and STI. Inline(CLI) will disable interrupts until the next Inline(STI). *** NOTE *** Interrupt routines installed with new_stack true (eg for where interrupts may arrive during DOS) and all routines they call which in turn call other routines MUST be compiled with the K- switch to disable stack checking - since a new stack is in use it confuses Turbo, so off with the checking. The stack depth with 'new_stack' is limited to 'intr_stack_size' bytes (see below), so be careful with over-generous use of local variables or recursion. Procedures responding to external interrupts will have to inform the 8259 of the end of the interrupt themselves - issueing a generic EOI via a Port[$20] := $20 should usually be sufficient. A summary of how it works. ========================== Code is placed into link_code in workspace for each routine that switches the stack if necessary, saves DS and SI on the stack, places the offset of the routine being called into SI and the current segment (the Turbo data segment) into DS, and then does a far call to code in the typed constant (and thus in the code segment) at goto_si. This saves the rest of the registers and does a near call to the handling routine, which means the code generated by the compiler to enter and exit the handling routine will work ok. On exit it back to goto_si there is a far ret back to the code in workspace which then restores DS and SI, restores the stack if necessary and does either an IRET or a jump to the old vector depending on the setup instructions. } const { Actual constants } intr_stack_size = $40 ; { Size of 'new_stack's } max_link_code_size = 60 ; { Max size of link code space } STI = $fb ; { Interrupt flag instructions } CLI = $fa ; { op codes } type routine_ptr = ^byte ; { Any pointer really } interrupt_workspace = record link_code : array [0 .. max_link_code_size] of byte ; old_routine : routine_ptr ; sp, ss : integer ; int_stack : array [0 .. intr_stack_size] of byte end ; const { Type const in code seg } goto_si : array [1 .. 15] of byte = ( $50, { push ax } $53, { push bx } $51, { push cx } $52, { push dx } $57, { push di } $06, { push es } $ff,$d6, { call si } $07, { pop es } $5f, { pop di } $5a, { pop dx } $59, { pop cx } $5b, { pop bx } $58, { pop ax } $cb ) ; { ret far } procedure InstallInterruptHandler ( routine_offset, interrupt_no : integer ; chain_old_vector, enable_ints_during, new_stack : boolean ; var workspace : interrupt_workspace ) ; var i : integer ; interrupt_vector : array [0 .. 255] of routine_ptr absolute 0:0 ; { Add a byte of code to the link, giving error if overflow } procedure l ( b : byte ) ; begin if i > max_link_code_size then begin writeln ('Link code overflow') ; halt (1) ; { Give error and stop } end else begin workspace.link_code[i] := b ; i := i + 1 end end ; { Add a word of code to the link (lo:hi), giving error if overflow } procedure w ( w : integer ) ; begin l (lo(w)) ; l (hi(w)) end ; begin i := 0 ; { Reset link code buffer counter } with workspace do begin { Code to swap stacks, if necessary } if new_stack then begin l($2e) ; l($8c) ; { mov cs:[ss],ss save SS } l($16) ; w(ofs(ss)) ; l($2e) ; l($a3) ; { mov cs:[sp],ax save AX } w(ofs(sp)) ; l($8c) ; l($c8) ; { mov ax,cs set SS to CS } l($8e) ; l($d0) ; { mov ss,ax } l($89) ; l($e0) ; { mov ax,sp save SP and } l($2e) ; l($87) ; { xchg ax,cs:[sp] recover AX } l($06) ; w(ofs(sp)) ; l($bc) ; { mov sp, } w(ofs(int_stack) + intr_stack_size) end ; { Now save DS and SI and set them to CS and the routine offset } l($56) ; { push si } l($1e) ; { push ds } l($0e) ; { push cs } l($1f) ; { pop ds } l($be) ; w(routine_offset) ; { mov si,routine_offset } { Re-enable interrupts if desired } if enable_ints_during then l(STI) ; { sti } { Far call to the code at goto_si } l($9a) ; w(ofs(goto_si)) ; { call far goto_si } w(Cseg) ; { Recover DS and SI } l($1f) ; { pop ds } l($5e) ; { pop si } { Reset stack to original if necessary } if new_stack then begin l($2e) ; l($8e) ; { mov ss,cs:[ss] recover SS } l($16) ; w(ofs(ss)) ; l($2e) ; l($8b) ; { mov sp,cs:[sp] and SP } l($26) ; w(ofs(sp)) end ; { Bung in either an IRET or a jump to next link } if chain_old_vector then begin l($2e) ; l($ff) ; { jmp far cs:[old_routine] } l($2e) ; w(ofs(old_routine)) end else l($cf) ; { iret } { Now we can install the vector to link_code, saving the old vector in old_routine. Interrupts are switched off for this bit, just in case. } inline (CLI) ; old_routine := interrupt_vector[interrupt_no] ; interrupt_vector[interrupt_no] := addr (link_code) ; inline (STI) { Interrupts back on } end end ; procedure RemoveInterruptHandler ( interrupt_no : integer ; var workspace : interrupt_workspace ) ; var interrupt_vector : array [0 .. 255] of routine_ptr absolute 0:0 ; begin inline (CLI) ; { Interrupts off } interrupt_vector[interrupt_no] := workspace.old_routine ; inline (STI) { and on again } end ;