PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 1/6 TITLE : Understanding ISR Programming An INTERRUPT is a signal to the CPU from a program or hardware device (eg. keyboard) directing the CPU to temporarily suspend what it is doing, in order to perform specific processing. For each INTERRUPT, a section of code (known as iNTERRUPT Service Routine or ISR) is executed each time the INTERRUPT occurs. TSRs(a.k.a. Memory Resident Programs) rely on Interrupts/ISRs to perform *background processing* or be activated/popped up. ISRs are also used in non-TSR programs whenever the default ones prove inadequate (eg. Serial Communications), there's a need to communicate with a new device etc. etc. A MINIMAL ISR in C/C++: ----------------------- The following is the minimum code for an ISR in C/C++: void INTERRUPT MyISR(void) { } The following displays the code actually generated for the above example: // An minimal ISR in C/C++ ³ // Assembly Code generated ³ ³ void INTERRUPT MyISR(void) ³ _MyISR proc far ³ push ax ³ push bx ³ push cx ³ push dx ³ push es ³ push ds ³ push si ³ push di ³ push bp ³ mov bp,DGROUP ³ mov ds,bp ³ mov bp,sp { ³ // BODY OF ISR // ³ } ³ PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 2/6 TITLE : Understanding ISR Programming ³ pop bp ³ pop di ³ pop si ³ pop ds ³ pop es ³ pop dx ³ pop cx ³ pop bx ³ pop ax ³ iret ³ _MyISR endp A TYPICAL ISR: -------------- A more typical ISR, however, chains to the Old ISR and looks like the following: void INTERRUPT (*OldISR)(); void INTERRUPT MyISR(void) { // DO SOMETHING OldISR(); } The following code is generated for the above: void INTERRUPT MyISR(void) _MyISR proc far push ax push bx push cx push dx push es push ds push si push di push bp mov bp,DGROUP mov ds,bp mov bp,sp { PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 3/6 TITLE : Understanding ISR Programming // DO SOMETHING OldISR(); pushf call dword ptr DGROUP:_OldISR } pop bp pop di pop si pop ds pop es pop dx pop cx pop bx pop ax iret _MyISR endp The compiler pushes all registers (actually all except SS & SP) on the stack upon entry into the ISR and pops them off upon exit... This ensures that the foreground's process registers are preserved. However, consider the following: Since registers are pushed upon entry and popped, how do I make my ISR return results in registers if it needs to? Since the value of DS and BP are destroyed by the code generated (NOTE: this is required in order to give the ISR access to the program's Global Variables and create a stack frame to allow for local variables), what would happen if the INTERRUPT I am trapping and chaining to expects information in those registers? RETURNING VALUES IN REGISTERS FROM AN ISR ----------------------------------------- To make your ISR return values in registers one can take advantage of one fact: since the stack frame for C/C++ ISRs is only created after the registers have been pushed on the stack, one can almost consider the registers as variables passed to the ISR.. Declaring your ISR as follows allows you to modify the PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 4/6 TITLE : Understanding ISR Programming actual words on the stack which will be popped into the various registers upon exiting the ISR. void INTERRUPT NewISR(unsigned bp, unsigned di, unsigned si, unsigned ds, unsigned es, unsigned dx, unsigned cx, unsigned bx, unsigned ax, unsigned ip, unsigned cs, unsigned flags) The following example is an ISR for INT 13H (DISK BIOS SERVICES). It is customary to trap iNTERRUPT 13H so that one may monitor DISK I/O activity and therefore determine whether it is safe to do asynchronous processing. In this example, we merely set a flag whenever INT 13H is busy: void INTERRUPT (*Old13Handler)(); unsigned Int13BusyFlag = 0x00; void INTERRUPT New13Handler( unsigned bp, unsigned di, unsigned si, unsigned ds, unsigned es, unsigned dx, unsigned cx, unsigned bx, unsigned ax, unsigned ip, unsigned cs, unsigned flags) { Int13BusyFlag++; // Increment the Int13BusyFlag (*Old13Handler)(); // Call Old Handler ax = _AX; // Return value of AX bx = _BX; // Return value of BX cx = _CX; // Return value of CX dx = _DX; // Return value of DX es = _ES; // Return value of ES di = _DI; // Return value of DI flags = _FLAGS; // Return the Flags --Int13BusyFlag; // Restore the Int13BusyFlag } CHAINING TO AN interrupt WITH ALL REGISTERS INTACT -------------------------------------------------- Often one will trap an INTERRUPT with expects some values in particular registers. As shown above, the setup for a C/C++ ISR results in the modification of the values of BP and DS... The BIOS Video iNTERRUPT 10H may have important data in BP when called (see Function 13H) while several of the INT 21H functions PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 5/6 TITLE : Understanding ISR Programming have various values passed in DS...Trapping these Interrupts therefore requires that the value of BP and DS (and other registers whenever appropriate) be restored to their original values prior to chaining to the Old ISR. The following Code gives an example where all registers are restored prior to chaining: #include #define INT_NUMBER 0x21 // Trap iNTERRUPT 21H (!) void INTERRUPT (*OldISR)(); // For Storing address of Old Vector void INTERRUPT NewISR(unsigned bp, unsigned di, unsigned si, unsigned ds, unsigned es, unsigned dx, unsigned cx, unsigned bx, unsigned ax, unsigned ip, unsigned cs, unsigned flags) { // Insert your ISR Code Here... // . // . // The following Code indirectly chains to the Old Vector by: // 1. Putting the Address of the Old ISR on the Stack // 2. Restoring the Value of Registers Pushed on the Stack // 3. Executing a Far Return which translates into a jump to // the address on the Stack (i.e. the Old Vector's Address) _BX = bx; // Restore value of BX register _CX = ax; // Save value of AX in CX ax = FP_SEG((void far *)OldISR); // Place Address of OldISR bx = FP_OFF((void far *)OldISR); // on the stack... _AX = _CX ; // Restore value of AX register __emit__(0x5D); // asm POP BP -> Restore BP __emit__(0x5F); // asm POP DI -> Restore DI __emit__(0x5E); // asm POP SI -> Restore SI __emit__(0x1F); // asm POP DS -> Restore DS __emit__(0x07); // asm POP ES -> Restore ES __emit__(0x5A); // asm POP DX -> Restore DX __emit__(0x59); // asm POP CX -> Restore CX __emit__(0xCB); // asm RETF -> Indirect Far Jmp to OldISR //NOTE: Any Code of the ISR beyond this point will not be executed. // The above does not *CALL* the Old ISR but rather *JUMPS* to it. PRODUCT : Borland C++ NUMBER : 1009 VERSION : 3.0 OS : DOS DATE : October 19, 1993 PAGE : 6/6 TITLE : Understanding ISR Programming // When the OldISR executes its IRET, control will resume at the // original location prior to the INTERRUPTion !! } int main(void) { unsigned CountDown = 999; OldISR = getvect(INT_NUMBER); setvect(INT_NUMBER, ( void INTERRUPT (*)() )NewISR); while(CountDown--) printf("This is the countdown: [%03d]\r", CountDown); printf("\nThis is the end of the countdown !\n"); setvect(INT_NUMBER, OldISR); return 0; } DISCLAIMER: You have the right to use this technical information subject to the terms of the No-Nonsense License Statement that you received with the Borland product to which this information pertains.