The Dark Art of writing DJGPP Hardware Interrupt Handlers (Which might conceivably be useful for other DPMI environments) Version 1.0 (C) Alaric B. Williams (alaric@abwillms.demon.co.uk) Copy in unmodified form, as long as you give all due credit. PREFACE This tutorial assumes an understanding of what a hardware interrupt is for, and how it works, along with the usual programming skills. Ideally, you will have coded a hardware interrupt handler in real mode DOS before. No guarantee of the correctness of information in this tutorial is given; if any errors are found, please report them to alaric@abwillms.demon.co.uk, and I will fix them for the next release. I cannot accept responsibility for any damages caused as a result, direct or indirect, of using the information presented herein. Anyway, onto the interesting parts! INTRODUCTION Hardware interrupt handling under DPMI is not all that different to the same under real mode. There are just a few more things to consider. All interrupts in DPMI protected mode go through the DPMI host, first of all. The DPMI host tells the CPU to stop using the normal interrupt vector table at real mode address 0000:0000, by setting the 386+ IDT (Interrupt Descriptor Table) register to a vector table somewhere else in memory. The DPMI host also maintains a 'virtual interrupt vector table', which lists the addresses of protected mode handlers. When an interrupt comes in, the DPMI host scans it's internal table; if that interrupt number is claimed by a protected mode function, it switches to protected mode if the CPU is in real mode (for example, when you use getch(), the CPU is running the real mode getch() routine in the BIOS), and simulates the interrupt. If the interrupt is not claimed by protmode, it uses the interrupt vector table in DOS memory to perform the jump to a real mode handler, after first making sure the CPU is in real mode. For example, if our protected mode application is working away, and the timer tick interrupt goes off (IRQ 0, INT 8): 1) CPU stores protected mode state and looks up the address to jump to in the IDT. 2) As expected, it jumps to the DPMI host's interrupt wrapper for INT8. 3) Wrapper checks to see if its interrupt has been claimed by a protected mode routine. 4) It hasn't, so the wrapper switches to real mode, and jumps to the address stored in interrupt vector table entry 8, thus executing the real BIOS interrupt handler. 5) Upon return from the BIOS handler, the wrapper cleans up after itself, returning to protected mode and restoring the machine state so our protected mode application continues to run smoothly. Evidently, a lot of switches between protected mode and real mode can be incurred; the DPMI host keeps the workings of this hidden from us (thankfully!), but the performance hit can be significant. Think twice about installing a timer tick interrupt at 20Khz! While we're on the topic, I'd like to dispel a myth. People are often heard moaning "Oh! Protmode's so slow! All that switching about, just to call a DOS interrupt, or whenever the user presses a key and invokes IRQ1, the keyboard interrupt! I'll stick to real mode programming, thankyou!". Well, if you hear them saying that, snigger quietly, and let them write their 'fast' real mode games, because yours will quite likely run faster. Think about it: most PCs do not run in real mode at all these days; it's all V86 mode, if they're running any kind of 386 memory manager, DPMI host, or Windows product; every time an interrupt occurs, their DPMI/VCPI/whatever host takes a slice of time to see if any protected mode code wants that interrupt. And you get the advantages of large flat virtual memory spaces, coupled with more control about where your interrupts go. A DPMI application that's really hungry for speed can install a protected mode interrupt, or place a real mode routine in DOS memory hooked to the real mode handler, depending on where their program spends the most time. If it's a DOS I/O bound application, the real mode handler will run faster, as it does not incur a mode switch; and if it's a compute bound application, sitting in protected mode, you can write a true protected mode handler, which will execute sooner than a real mode one. LOCKING The second important thing to consider with DPMI interrupt handlers is the vexed issue of paging. DPMI memory (in which your DJGPP application executes) can be paged out onto disk, making 'virtual memory' possible. Normally, if any 'paged out' RAM is requested, the DPMI host can bring it back in invisibly; but this cannot be done during the execution of an interrupt handler. If an interrupt handler, any other functions it invokes, or any variables it touches, is 'paged out', your code will bomb out with a page fault. The solution is to 'lock' the memory regions that must be available, telling the DPMI host to keep them in active memory at all times. This can be done with the DJGPP library functions _go32_dpmi_lock_code(void *,unsigned long) and _go32_dpmi_lock_data(void *,unsigned long), which each take a pointer and a length, specifying the block of memory to be locked. Before installing your handler, lock all code and data it touches. To lock a static variable, use: _go32_dpmi_lock_data(&my_var, sizeof(my_var)); To lock a dynamically allocated memory block, you must know its size: char *buffer = malloc(buflen); assert(buffer); _go32_dpmi_lock_data(buffer,buflen); _go32_dpmi_lock_data(&buffer,sizeof(buffer)); Don't forget to lock the pointer variable, as well as the data! Locking code is slightly different; we can't sizeof() a function, so we have to use a trick: void my_handler() { } void lock_my_handler() { _go32_dpmi_lock_code(my_handler, (unsigned long)(lock_my_handler - my_handler)); } (Copied directly from the info page). In other words, declare a function after your handler - hopefully a dedicated dummy function, in case you forget later on and move the function you're using as a marker - and use pointer arithmetic to deduce the code size of your handler function, while praying that gcc puts the functions in the order you specify! The Allegro games programming library uses a set of convenient macros to automate this. They are: #define END_OF_FUNCTION(x) void x##_end() { } #define LOCK_VARIABLE(x) \ _go32_dpmi_lock_data((void*)&x, sizeof(x)) #define LOCK_FUNCTION(x) \ _go32_dpmi_lock_code(x, (long)x##_end - (long)x) LOCK_VARIABLE is pretty self explanatory; END_OF_FUNCTION is used like so: void my_function() { ... } END_OF_FUNCTION(my_function) and creates a dummy function named my_function_end. LOCK_FUNCTION uses this to expand: LOCK_FUNCTION(my_function) into: _go32_dpmi_lock_code(my_function, (long)my_function_end \ - (long)my_function); thus taking care of the paperwork for you. Perhaps the easiest and safest method of locking I have seen is to tell GCC to put everything you want locked into a special section of the COFF executable, which can then be locked all in one go. This is accomplished using the GCC "__attribute__ ((section "name"))" feature; however, in order to make this work (with the current version of DJGPP, 2.00), you have to download the GCC source code and fix a little bug. This fix is the brainchild of Bill Currie, and consists of adding the lines: #define ASM_OUTPUT_SECTION_NAME(FILE,DECL,NAME)\ do { \ fprintf(FILE,"\t.section %s\n",NAME); \ } while(0) to the file "configure/i386/go32.h" in the gcc source, then recompiling. You also need to modify /lib/djgpp.lnk to tell the linker where to put these special 'locked' sections. I have written a library to do all this for you - "libints". It should be included with this document. See the sample code (in the directory /src/libints) for instructions. THE ACTUAL ACT OF INTERRUPT CLAIMING This is pleasantly painless. As GCC does not support an 'interrupt' modifier on function names to tell them to provide the correct wrapper (with an IRET on the end), you must use a wrapper; but the DJGPP libc.a can do this for you in one fell swoop. If you merely want to chain onto a vector, and still have it passed back to the previous handler (which may be the original real mode one), simply use the following code: #define TIMER_INT 8 void my_int_handler() { ... } ... _go32_dpmi_seginfo my_handler; my_handler.pm_offset = (int)my_int_handler; my_handler.pm_selector = _go32_my_cs(); _go32_dpmi_chain_protected_mode_interrupt_vector( \ TIMER_INT, &my_handler); If you want to totally take an interrupt over, it gets a little more complex. Say we were installing the same handler - we would use: _go32_dpmi_seginfo my_handler; my_handler.pm_offset = my_int_handler; _go32_dpmi_allocate_iret_wrapper(&my_handler); _go32_dpmi_set_protected_mode_interrupt_handler( \ TIMER_INT, &my_handler); Since we've allocated a wrapper, we must, at some point: _go32_dpmi_free_iret_wrapper(&my_handler); Just make sure you've removed your interrupt handler before doing that! Please note: The wrapper DJGPP provides is not locked in memory, and is as such unreliable. Included in libints is a proper locked iret-wrapper system which is a little easier to use than the standard _go32_dpmi functions, and includes a useful function for simulating a protected mode interrupt, which makes it easy to chain to the original handler. This is more flexible than the GO32 chaining function used above, as you have the option of not chaining (useful for timer interrupts), and can do things like executing some code before chaining and some after. See the libints sample source for further instructions. If we want to preserve an interrupt vector, in order to restore it to its original value (which is kinda good practice!), we can simply use the code: _go32_dpmi_seginfo old_handler; _go32_get_protected_mode_interrupt_handler(TIMER_INT, \ &old_handler); ... _go32_set_protected_mode_interrupt_handler(TIMER_INT, \ &old_handler); EXAMPLE #include #include #include #include #include #include _go32_dpmi_seginfo old_handler,my_callback; volatile int counter; char *string; void my_int() { counter++; char *ch = string; int addr = 0xA0000; while(*ch) { _farpokeb(_dos_ds,addr,*ch); ch++; addr += 2; } } void end_int() {} void init_handler() { _go32_dpmi_lock_data(&counter, sizeof(counter)); _go32_dpmi_lock_data(&string, sizeof(string)); _go32_dpmi_lock_code(my_int, (long)end_int - (long) my_int); _go32_dpmi_get_protected_mode_interrupt_handler(8, &old_handler); my_callback.pm_offset = (int)my_int; _go32_dpmi_allocate_iret_wrapper(&my_int); _go32_dpmi_set_protected_mode_interrupt_handler(8, &my_int); } void done_handler() { _go32_dpmi_set_protected_mode_interrupt_handler(8, &old_handler); _go32_dpmi_free_iret_wrapper(&my_int); } void update_text(char *s) { int ints = disable(); string = s; if(ints) enable(); } main() { counter = 0; update_text(""); init_handler(); char buffer[80],temp[80]; _go32_lock_data(buffer,80); cout << "Type some text followed by a CR,"; cout << " or EXIT to quit" << endl; do { cout << "Counter = " << counter << endl; cin >> temp; int ints = disable(); //clear interrupts strcpy(buffer,temp); if(ints) enable(); //reenable interrupts } while(!stricmp(buffer,"EXIT")); done_handler(); } POINTS TO NOTE In a hardware interrupt handler, you must not: - longjmp out of the handler - use library functions like printf() that may invoke DOS calls When chaining an interrupt handler with the _go32_dpmi_chain... function, don't forget to fill in the pm_selector field of the parameter struct with _go32_my_cs(), or else you'll have some interesting crashes. ACKNOWLEDGEMENTS Allegro is an excellent graphics/sound/related stuff library for DJGPPv2 games programming. It can generally be found on x2ftp.oulu.fi, in /pub/msdos/programmer/djgpp2/. It's author, Shawn Hargreaves, is currently between email addresses, but his snail address is: Shawn Hargreaves, 1 Salisbury Road, Market Drayton, Shropshire, England, TF9 1AJ. The copyright section, reproduced here since I'm including his LOCK_ macros, is: "Allegro is swap-ware. You may use, modify, redistribute, and generally hack it about in any way you like, but if you do you must send me something in exchange. This could be a complimentary copy of a game, an addition or improvement to Allegro, a bug report, some money (this is particularly encouraged if you use Allegro in a commercial product), or just a copy of your autoexec.bat if you don't have anything better. If you redistribute parts of Allegro or make a game using it, it would be nice if you mentioned me somewhere in the credits, but if you just want to pinch a few routines that is OK too. I'll trust you not to rip me off." Seeing as I used his code in my tutorial, I'll send him my tips on stereo boosting with phase shifts :-) Bill Currie came up with the patch to make GCC support __attribute__ ((section)) properly, thus making libints possible. Mark Habersack's ideas about using custom COFF sections for loadable resources led me to think of the locked- sections scheme used in libints. And thanks to everyone on comp.os.msdos.djgpp who answered my questions about interrupt handling! section 1 of uuencode 5.22 of file ints100s.zip by R.E.M. begin 644 ints100s.zip M4$L#!!0``````#>]`R$````````````````(````24Y#3%5$12]02P,$%``` M````-[T#(0````````````````0```!34D,O4$L#!!0``````#B]`R$````` M```````````$````3$E"+U!+`P04```````XO0,A````````````````"0`` M`$U!3DE&15-4+U!+`P04``````"@O0,A````````````````#````$E.0TQ5 M1$4O4UE3+U!+`P04``(`"``HO`,A)FVDD8\"``#M!```$P```$E.0TQ51$4O M4UE3+TA724Y4+DAM5,%JW#`0O1O\#Y/D4#ML'-)"(>VE:0K=%EH"3?/>X#-JK,8&RG+]\.7[?;E.DS,YD\5E M28K4S+"Z-X./OS3!WP&=A=/;4_@C"+2:FHB]/%\KIT?E$,@*Q`U]@%99;="! MHC;\0"CLB)`>3`LS>2O,@;T@%`[U!1`WFQHB[9(DV^L)]T@;SVQ]>!;'HR& M"J%3[E%NY+T?ZA:HD7:X@SA3X`C0Y(.C:@BHA6JMM@C-$%G3Y/QR=C)-/`88 M>AB=ZGOI`5D=U;"M,9]A6R8MCE`HMU@'G^7O#T_K5HE3TDO9YTFD2:^\GQT0 M7=!WI453[TDA.Y$("^V>N@C)Y'<0%Z6LEF=N_&(NHZ@338;K:/#>FDGUWJV7 M!XYC'O:P53&C()B8Q,PDF4^)CQ1:H=A!AQV['4CD@25"]"=I<@,.P^!LM.7B M2B#*1@-5B"Y*M#%>R]`XQ*?05L#Q)^NR7(!HIR5MX[HX[(VJ4<^SL2-)1IGGLQ>SW=%1<6CFBH!8E7IAP6B66&?9<)W`OUKSR:2*:'?SB MZ@36TY6?G'HRH-H=E>7%8CO^FW^Q$K/$E[Y+?YL/^^.U)R M%"5/>TU/E"8F=-4=MBN,OAU>O7WRX7'SU":%7BN&]%P!TZV-L! M[H4)IR`\\1$9_0NM00X(M4.I`I#/K=JA*9:+7ZQ4C4()Y.N5-1Y\:P5&>P$PS+IG#LUHHEPM2JV_W7M5"0TX&'SBT,BHHH96?P-YC5^D]U%8B)>!;;A-5 MV7)14@0QQ1KED5AK%H+5-+&J,9'E8E5HLX;;$.;SI$_I=A27!W3F[,>]$N\O-=-O96?;ON^T.9N5K\/ M#U)=BZXGEL&+6WS#"YS#FA\`"!J@L1;*\D!1EI!E8^?^V/R>Y_`=G)U'],XJ M"95PU(XO./SX\==MGI]'FLTUK&'D*29YHGSC8DG,;])VG.6SIV"2<@(3;P+3 MQV%T4:C^U"%X0RM%5A8]U!*)*;)#9[5@.=&_J0 MVH#.?\4T)R_YEZ^#HQL`XIVA(G,"3;?(@WZ'PZ M1D\-7[^*=B.4'AP"JS$&X5-]="S.O\3[WYQL>OH?4$L#!!0``````#>]`R$` M```````````````,````4U)#+TQ)0DE.5%,O4$L#!!0``@`(`-.]`R&?2_=T MG@$``+<"```2````4U)#+TQ)0DE.5%,O3$]#2RY#95)MBQ,Q$/X>V/\P(&CW MJ%OO!.$X%6M;]'S!`RN*7Y;9S6PW-IL_2THQ$.-L`>C9\".O9C,[Y1:Y"!H!U) M*@_,V:@=F:H0'ZU4G2()S'7*&@>NMT%+:`@&'+=<8;X+;0^J8SLZ`([L:V.# M5.YF4,E2;W%'T(6H6HB363R=3V,KX]/H).L#>7@!3R[R[E8<(NIRIA;-H]P5 M74"BQVEZUHY:GY*Q%V^4K>^Z6BMI6@B&I$U`0\[##]O<*.84M_9W2I,2?A4" M(`7K4&F2%_G=38XY2QC)A]'D4F[C[/7&/CVKX\^HDVA,.GGH=$HO5M_2_[WNI.,Y:C'<_7IR%^XQ*(C%'S#2G)US;ZGR:#5ZN[V2")MPX\D\V;F M[)5+>T(7"F^"G"9;Q0EHWE#520PS@%!IK>;KSE*$`(1_'XL_OWZBW\\/19;E MOEV4E@L*W&!OVY;ZG.?EQU.P(,^+/"$RM.:R4D`)@E@IB:`?N$%8)SC_IXDK MS*=$`:,::AF7->@,K3HQG8=:#W=UE<<1H1VSDDM@`P&(!+'?\`:Y2\$1VW7; M.&1!L55Z=J2KJC+4!L(TV7G"QHT^D,5XHB&8'4T>:CY]-Q=;KJ2!61X-W**M M$P]*V$;AU=2B5BOK>BA!C2+4\U*MN[9W*@T7_VZ*V>4(-4=DMW&H'6L%)H3H/?`^^ MDI5>RTC&F+SU?#*[U!+ M`P04``(`"`!%O`,AI:J+LS@#```6$@``%0```%-20R],24))3E13+TE.5%=2 M05`N4\V7T6_;-A#&WP7H?S@@"QH+GFRG;89Z+\O2H$F#%&Z(+ABFA=,(XC4HM9Y9F'!4BY1@Q219GKCSKO^=/_E[GP23N%' MF(IE)D4BD/=!JR@WMD]3_IHBTCF%X/W'#Y.)[YU<].!<,BUB^#6$+T)*P98& M3E@9^X5%!866)N2X5&D8JS!_Z,'HW;LSW_O-(-B%,)`H#<6"65Q1AAN50\%2 M2H`9D(HN1M],2N`Y0JR1"PLT9RY6F(:^=ZMXF3?07"-4:L`L5"XY1`A+IA_H M'YIO\G@!(J'+X0:<)JO<"5P8JT646^2$NF(KA"1W5-\+!N[H>Z'!V!(70LF9 MI?%+CB4X&22)74V0:&.<:C0%5"OWN M.4DJ)?3!H*T@[N]'6U>K-OI2.9-T MG9S]YXKJ72IRB]FX)VHE>=4WH%)(L:@&?]=#.Q1G4KR&@^]U974;SPY46[9^ M$25V[S!!.6T0E(.:^**]KB./5%87=Q!HM!K%KD__F-`CI3(:_A<>E;ETL(Y< M79Z]=/Y5CUK<[)]2DDXHV`F%MZ>T>#C<4H1&V\:D$N)[3]Z"A[U&8-0,G#8# MKYN!-\W`VV;@K!GXJ>?>R[\!4$L#!!0``@`(`'"]`R$=X6VIKP```-X!```4 M````4U)#+TQ)0DE.5%,O34%+149)3$5]D#$.PC`,1>=:\AT\,,"0,+!U[@(C MG"!$5:FH&I1&*L?'$<$U$B525,?Y[\>_J9W2$/S=ML^6:I)CH,VV.37'\VX_ M]->\^S%-UB%4G?=DNH/6FB'?YD^84B03Y!(!00G5"UY9&:_Z&K:N@N0ZCIVTZ5HW M1;MV6#:L0;&NZ(>U$&B1B@^E2(TO=HTB_WTD]>HEP!PCI],=[[F[YTCJ`8J" M6\K@!:TKG&]>QM%@NI'G9_\QZ;T^W>Q0F&"/H]/I%5%T1Q0#9V1*V=K`A@C* MF0*.:T74WJ^[^O3;]5_S-W`"'["J.9;(Z`R47%MM9B[@?@P4-\X$;W__]?W[ M.$K?9/":$X4%_#R'3\@YDDI#2H+M%5GOG*G2<\HJ*>:%G-NO&2R?/;N(HX^: M@=F@AE(JV&V(85M7WUY:V!'A"B`:N'3)W)-P#M0R*!2C:,#%W."6B7DC/0+*&JC<&T-HP[JBFP9 ME-:CQM'TU,L'E)4H&%Q_?)=?O;Y^^\Y$]I#/1PN%K(>WYW`Z!2%!6E-;H\$S%,P3-8%T MU'XV.S1Y!KJE13%Q7D:^3;)5'-UZ)EUBI@2,.5O,.FW9:V>]=MYKCWOM2:]= M]-K3U0$XJG\I.AL*3H?)D*#T0<<1?,O28#$TF0Y>]^O1V7&K8E].=(G5]?ZT-#4G#0](0 MD31,'%L;)`UG24-:TK`6'J&8(U$.Z+6:T;O5+F8'O]O5T=!'+VQWB!4:;P2C M_OI0)I0DK5C,7-N*4I*].EP4 MK9`HC]8DQFEN6T?;`O!$V0D`U-TCA.9Y_!;A#7V*@C#*JX0H\7X]Y/1#@&WLMSO:KN2=!\!P_I@[J%@BYBOY6\5@A"O@U/*=B MG_O#@58?+1=T)YKVXF1/N6-E74P(QTO8]1(VN61**(.>A%#JU*8@!<<%#<2X M(,F9@B[G?S7TSCF`_G&G%#"'(`RS`2U[V1%N^L>+E*BY*U@<: M`!*$VI*,*@@DRHL2"_2*487!EJ,IS4W,3DW+S$E%%2T!6@I3#0!02P,$"@`` M````[KP#(6W&)=$K````*P```!4```!-04Y)1D535"])3E13,3`P4RY615)! M;&%R:6,G2!6,2XP,`T*4$L! M`A0`%```````-[T#(0````````````````@``````````0`P`````````$E. M0TQ51$4O4$L!`A0`%```````-[T#(0````````````````0``````````0`P M````)@```%-20R]02P$"%``4```````XO0,A````````````````!``````` M```!`#````!(````3$E"+U!+`0(4`!0``````#B]`R$````````````````) M``````````$`,````&H```!-04Y)1D535"]02P$"%``4``````"@O0,A```` M````````````#``````````!`#````"1````24Y#3%5$12]365,O4$L!`A0` M%``"``@`*+P#(29MI)&/`@``[00``!,``````````0`@````NP```$E.0TQ5 M1$4O4UE3+TA724Y4+DA02P$"%``4``(`"``."0,APG0""4X#```?!P``$@`` M```````!`"````![`P``24Y#3%5$12]365,O3$]#2RY(4$L!`A0`%``````` M-[T#(0````````````````P``````````0`P````^08``%-20R],24))3E13 M+U!+`0(4`!0``@`(`-.]`R&?2_=TG@$``+<"```2``````````$`(````",' M``!34D,O3$E"24Y44R],3T-++D-02P$"%``4``(`"`",O`,A*=F6!Y(!``"; M`P``%@`````````!`"````#Q"```4U)#+TQ)0DE.5%,O5$535$Q/0TLN0U!+ M`0(4`!0``@`(`$6\`R&EJHNS.`,``!82```5``````````$`(````+<*``!3 M4D,O3$E"24Y44R])3E174D%0+E-02P$"%``4``(`"`!PO0,A'>%MJ:\```#> M`0``%``````````!`"`````B#@``4U)#+TQ)0DE.5%,O34%+149)3$502P$" M%``4``(`"`#/O0,AW-