// // PCIRQ.CPP // // Source code from: // // Serial Communications: A C++ Developer's Guide, 2nd Edition // by Mark Nelson, IDG Books, 1999 // // Please see the book for information on usage. // // This module contains the interrupt management code. // ConnectToIRQ() is called to establish an interrupt handler // function, DisconnectFromIRQ() is called to break the // connection. #include #include "pcirq.h" typedef void ( __far __interrupt *HANDLER )( void ); // Prototypes for all the handlers defined here void __far __interrupt isr2( void ); void __far __interrupt isr3( void ); void __far __interrupt isr4( void ); void __far __interrupt isr5( void ); void __far __interrupt isr7( void ); void __far __interrupt isr10( void ); void __far __interrupt isr11( void ); void __far __interrupt isr15( void ); void __far __interrupt int1b( void ); void __far __interrupt int23( void ); // When any IRQs are hooked by one of our routines, the IRQ code // disables control-break termination by taking over the two // control-break vectors. The saved control-break state is // stored in the following three variables. static HANDLER old_int1b; static HANDLER old_int23; static unsigned char old_dos_break_state; // A count of the number of handlers currently in use. static int count = 0; // This structure keeps track of the state of each of the eight // possible IRQ lines our program can take over. This includes // the address of the new handler, the old handler, and most // importantly, the data pointer passed to the new handler when // it is invoked. struct { enum irq_name irq; void *isr_data; void ( *isr_routine)( void *isr_data ); HANDLER handler; HANDLER old_isr; int old_pic_enable_bit; } irq_data[] = { { IRQ2, 0, 0, isr2, 0, 0 }, { IRQ3, 0, 0, isr3, 0, 0 }, { IRQ4, 0, 0, isr4, 0, 0 }, { IRQ5, 0, 0, isr5, 0, 0 }, { IRQ7, 0, 0, isr7, 0, 0 }, { IRQ10, 0, 0, isr10, 0, 0 }, { IRQ11, 0, 0, isr11, 0, 0 }, { IRQ15, 0, 0, isr15, 0, 0 } }; // All of the new ISR handlers are called when the interrupt // occurs. All they do is call the hooked routine, passing it // a pointer to the data block it asked for in the // ConnectToIRQ() routine. When this is done, they take care // of issuing the EOI instruction and then exiting. The // "return 1" is necessary for Zortech's interrupt handlers. // If a "return 0" is used, the handler will then chain to the // old interrupt, which we don't want. void __far __interrupt isr2( void ) { irq_data[ 0 ].isr_routine( irq_data[ 0 ].isr_data ); _disable(); outp( 0x20, 0x20 ); } void __far __interrupt isr3( void ) { irq_data[ 1 ].isr_routine( irq_data[ 1 ].isr_data ); _disable(); outp( 0x20, 0x20 ); } void __far __interrupt isr4( void ) { irq_data[ 2 ].isr_routine( irq_data[ 2 ].isr_data ); _disable(); outp( 0x20, 0x20 ); } void __far __interrupt isr5( void ) { irq_data[ 3 ].isr_routine( irq_data[ 3 ].isr_data ); _disable(); outp( 0x20, 0x20 ); } void __far __interrupt isr7( void ) { irq_data[ 4 ].isr_routine( irq_data[ 4 ].isr_data ); _disable(); outp( 0x20, 0x20 ); } // These routines have to send an EOI to the second 8250 PIC // as well as the first. void __far __interrupt isr10( void ) { irq_data[ 5 ].isr_routine( irq_data[ 5 ].isr_data ); _disable(); outp( 0xa0, 0x20 ); outp( 0x20, 0x20 ); } void __far __interrupt isr11( void ) { irq_data[ 6 ].isr_routine( irq_data[ 6 ].isr_data ); _disable(); outp( 0xa0, 0x20 ); outp( 0x20, 0x20 ); } void __far __interrupt isr15( void ) { irq_data[ 7 ].isr_routine( irq_data[ 7 ].isr_data ); _disable(); outp( 0xa0, 0x20 ); outp( 0x20, 0x20 ); } // The two control-break vectors do nothing, so that Control-C // and Contrl-Break both have no effect on our program. void __far __interrupt int1b( void ) { } void __far __interrupt int23( void ) { } // This utility routine is only used internally to these // routines. It sets control of the given interrupt number to // the handler specifified as a parameter. It returns the // address of the old handler to the caller, so it can be // stored for later restoration. Note that Zortech stores the // old handler internally, so we just return a 0. HANDLER HookVector( int interrupt_number, HANDLER new_handler ) { union REGS r; struct SREGS s = { 0, 0, 0, 0 }; HANDLER old_handler = 0; r.h.al = (unsigned char) interrupt_number; r.h.ah = 0x35; int86x( 0x21, &r, &r, &s ); *( (unsigned __far *) old_handler + 1 ) = s.es; *( (unsigned __far *) old_handler ) = r.x.bx; s.ds = FP_SEG( new_handler ); r.x.dx = FP_OFF( new_handler ); r.h.al = (unsigned char) interrupt_number; r.h.ah = 0x25; int86x( 0x21, &r, &r, &s ); return old_handler; } // When we are done with an IRQ, we restore the old handler // here. Note once again that Zortech does this internally, so // we don't have to. void UnHookVector( int interrupt_number, HANDLER old_handler ) { union REGS r; struct SREGS s = { 0, 0, 0, 0 }; s.ds = FP_SEG( old_handler ); r.x.dx = FP_OFF( old_handler ); r.h.al = (unsigned char) interrupt_number; r.h.ah = 0x25; int86x( 0x21, &r, &r, &s ); } // When we have taken over an interrupt, we don't want keyboard // breaks to cause us to exit without properly restoring // vectors. This routine takes over the DOS and BIOS // control-break routines, and sets the DOS BREAK flag to 0. // The old state of all these variables is saved off so it can // be restored when the last interrupt routine is restored. void TrapKeyboardBreak( void ) { union REGS r; old_int1b = HookVector( 0x1b, int1b ); old_int23 = HookVector( 0x23, int23 ); r.h.ah = 0x33; r.h.al = 0; int86( 0x21, &r, &r ); old_dos_break_state = r.h.dl; r.h.ah = 0x33; r.h.al = 1; r.h.dl = 0; int86( 0x21, &r, &r ); } // When the last interrupt is restored, we can set the // control-break vectors back where they belong, and restore // the old setting of the DOS break flag. void RestoreKeyboardBreak( void ) { union REGS r; UnHookVector( 0x1b, old_int1b ); UnHookVector( 0x23, old_int23 ); r.h.ah = 0x33; r.h.al = 1; r.h.dl = old_dos_break_state; int86( 0x21, &r, &r ); } // When connecting to an IRQ, I pass it an irq number, plus a // pointer to a function that will handle the interrupt. The // function gets passed a pointer to a data block of its choice, // which will vary depending on what type of interrupt is being // handled. RS232Error ConnectToIrq( enum irq_name irq, void *isr_data, void ( *isr_routine )( void *isr_data ) ) { int i; int pic_mask; int pic_address; int interrupt_number; int temp; for ( i = 0 ; ; i++ ) { if ( irq_data[ i ].irq == irq ) break; if ( irq_data[ i ].irq == IRQ15 ) return RS232_ILLEGAL_IRQ; } if ( irq_data[ i ].isr_routine != 0 ) return RS232_IRQ_IN_USE; if ( count++ == 0 ) TrapKeyboardBreak(); irq_data[ i ].isr_data = isr_data; irq_data[ i ].isr_routine = isr_routine; pic_mask = 1 << ( irq % 8 ); if ( irq < IRQ8 ) { pic_address = 0x20; interrupt_number = irq + 8; } else { interrupt_number = irq + 104; pic_address = 0xa0; } irq_data[ i ].old_isr = HookVector( interrupt_number, irq_data[ i ].handler ); temp = inp( pic_address + 1 ); irq_data[ i ].old_pic_enable_bit = temp & pic_mask; outp( pic_address + 1, temp & ~pic_mask ); return RS232_SUCCESS; } // This routine restores an old interrupt vector. int DisconnectFromIRQ( enum irq_name irq ) { int i; int pic_mask; int pic_address; int interrupt_number; int temp; for ( i = 0 ; ; i++ ) { if ( irq_data[ i ].irq == irq ) break; if ( irq_data[ i ].irq == IRQ15 ) return 0; } if ( irq_data[ i ].isr_routine == 0 ) return 0; irq_data[ i ].isr_data = 0; irq_data[ i ].isr_routine = 0; pic_mask = 1 << ( irq % 8 ); if ( irq < IRQ8 ) { pic_address = 0x20; interrupt_number = irq + 8; } else { interrupt_number = irq + 104; pic_address = 0xa0; } temp = inp( pic_address + 1 ); temp &= ~pic_mask; temp |= irq_data[ i ].old_pic_enable_bit; outp( pic_address + 1, temp ); UnHookVector( interrupt_number, irq_data[ i ].old_isr ); if ( --count == 0 ) RestoreKeyboardBreak(); return 1; } // ******************** END OF PCIRQ.CPP ********************