Date: Thu, 23 Mar 1995 14:18:16 EST From: THE MASKED PROGRAMMER To: djgpp AT sun DOT soe DOT clarkson DOT edu Cc: badcoe AT bsa DOT bristol DOT ac DOT uk Subject: Int 0x8 problems Hi, I'm still having trouble with interrupts (but the trouble's got a lot more subtle now). The following source file is supposed to implement a technique for using the PIT and int 0x8 to refresh the screen. Basically the idea is to run the PIT at about ~140Hz (twice the VGA refresh rate) and in odd-numbered interrupts ScreenBuffer is blasted onto the video memory, whereas in even numbered interrupts the PIT is resynchronised with the VGA retrace. (This was "C", I'll now add "C++"-style comments to explain things to you: ***** timerint.c *************************************************************** #include #include #include #include #include #define _TIMERINT_C_ #include "sb.h" // This just declares ScreenBuffer #include "timerint.h" // This file is appended static volatile unsigned int LastWait = 0; static volatile unsigned int altern = 1; static volatile unsigned int tics = 0; static unsigned int Clock_Inc=0x10000; static volatile unsigned int CCL = 0, CCH = 0, CV = 0x34; static _go32_dpmi_seginfo old_rm_handler; static _go32_dpmi_seginfo new_pm_handler, new_rm_handler; static _go32_dpmi_registers dummy, old; static _go32_dpmi_registers t_old; // The following routine just programs the PIT, val specifies the mode and // ch and cl are the high/low bytes of the counter (or rather their low-bytes // are). static inline void timer_ports(register unsigned int val, register unsigned int ch, register unsigned int cl) { asm("movl %%ebx, %%eax; outb %%al, $0x43; movl %%ecx, %%eax; outb %%al, $0x40; movl %%edx, %%eax; outb %%al, $0x40;" : /* no outputs */ : "b" (val), "c" (cl), "d" (ch) : "eax"); } // This routine sets the PIT to do one interrupt after 1/freq seconds. static void SetOneShotTimer(unsigned int freq) { Clock_Count = CLOCK_RATE / freq; timer_ports((CV = 0x30), (CCL = Clock_Count & 0xff), (CCH = Clock_Count >> 8)); Clock_Inc = CLOCK_RATE / 140; /* should be kept to 140 by VGA retrace ? */ } // This tidies up when we exit. static void TimerCleanUp() { timer_ports((CV = 0x34), (CCL = 0), (CCH = 0)); _go32_dpmi_set_real_mode_interrupt_vector(8, &old_rm_handler); puts("TimerCleanUp"); } // This is the interrupt routine, if altern = 1, it resynchronises, if // altern = 0, it draws the screen. static void TimerHandler() { altern ^= 1; // flip the flag for what we do if (!altern) { // This asm resynch's; it seems to work. asm("xor %%ebx,%%ebx; mov $0x3da,%%dx; l_resynch_%=_2: incl %%ebx; inb %%dx,%%al; test $0x08,%%al; jz l_resynch_%=_2;" : "=b" (LastWait) : : "edx", "eax", "ebx"); // Previous routine also keeps a count of how many cycles it has to wait for // this is then used for picking the best rate for the PIT, the problem seems // to be associated with this. } else { if (ScreenChanged) { // if there is actually anything to draw // This 'asm' blasts the screen (it works) asm("rep; movsl;" : : "c" (16000), "S" (ScreenBuffer), "D" (SCREEN_MEMORY) : "ecx", "esi", "edi"); ScreenChanged = 0; } } tics += Clock_Inc; // keep a track of time if (tics >= 0x10000) { // if necessary, call the original interrupt // routine memcpy(&t_old, &old, sizeof(_go32_dpmi_registers)); _go32_dpmi_simulate_fcall_iret(&t_old); tics -= 0x10000; } else { // otherwise acknowledge the interrupt asm("movb $0x20, %%al; outb %%al, $0x20" : : : "eax"); } MyTime++; // just count calls timer_ports(CV, CCL, CCH); // ask for another interrupt after the same // delay } // This routine sets up the interrupt-vectors static void SetTimerVectors() { /* _go32_dpmi_get_protected_mode_interrupt_vector(8, &old_pm_handler); */ _go32_dpmi_get_real_mode_interrupt_vector(8, &old_rm_handler); memset(&old, 0, sizeof(_go32_dpmi_registers)); old.x.cs = old_rm_handler.rm_segment; old.x.ip = old_rm_handler.rm_offset; atexit(TimerCleanUp); new_pm_handler.pm_offset = (int)TimerHandler; new_pm_handler.pm_selector = _go32_my_cs(); new_rm_handler.pm_offset = (int)TimerHandler; new_rm_handler.pm_selector = _go32_my_cs(); _go32_dpmi_allocate_real_mode_callback_iret(&new_rm_handler, &dummy); _go32_dpmi_allocate_iret_wrapper(&new_pm_handler); _go32_dpmi_set_protected_mode_interrupt_vector(8, &new_pm_handler); _go32_dpmi_set_real_mode_interrupt_vector(8, &new_rm_handler); } // This is the routine that is called from the main program. int SetUpTimer() { int x, old_tics = 0, speed; unsigned int cumulative, best_speed, best; SetTimerVectors(); // the previous routine best = 0xffffffff; best_speed = 140; // The following loop gives the problems, it's supposed to try some PIT // delays and pick the one that gives the closest match to the VGA rate // (after allowing for the time we spend in the interrupt routine). for (speed = 140; speed < 155; speed++) { // suitable range of Hz SetOneShotTimer(speed); // set pit old_tics = MyTime + 10; // 10 cycles in the future cumulative = 0; // zero a counter for (x = 0; x < 5; x++) { // repeat 5 times while(MyTime <= old_tics) // wait for a new cycle ; printf("%i ", LastWait); // without this it's more stable cumulative += LastWait; // add the time wasted to total // The previous was for timing without actually doing any drawing, with the // following uncommented the routine gets less stable. /* ScreenChanged = 1; // Call this section-Q while(ScreenChanged) ; old_tics = MyTime; printf("%i ", LastWait); cumulative += LastWait; */ } printf(" <%i>\n", speed); if (cumulative < best) { // just find the PIT rate that best_speed = speed; // comes closest to hitting the best = cumulative; // retrace. } } SetOneShotTimer(best_speed); // use best rate return best_speed; // let caller know. } ****** end of file ************************************************************* ****** this is timerint.h file ************************************************* #ifndef _TIMERINT_H_ #define _TIMERINT_H_ #ifdef _TIMERINT_C_ #define CLOCK_RATE 1193181 volatile int ScreenChanged = 0; volatile unsigned int MyTime = 0; unsigned int Clock_Count=0x10000; #else /* _TIMERINT_C_ */ extern volatile int ScreenChanged; extern volatile unsigned int MyTime; extern unsigned int Clock_Count; #endif /* _TIMERINT_C_ */ #ifdef __cplusplus extern "C" { #endif int SetUpTimer(); #ifdef __cplusplus } #endif #endif /* _TIMERINT_H_ */ ****** end of file ************************************************************* That's it. The main routine (in this test-case) just calls SetUpTimer and prints the return value. The problem is that it crashes somewhere in the "for(speed = " loop. The crash seems to be connected with interrupts that occur while the CPU is in RM. If I take the printf's out of the loop it behaves, if I add a "getkey()" or similar to the loop it crashes. However, in a simpler test-program (before I started calculating LastWait) PM/RM interrupts seem to both be handled correctly. Without the printf's but with "section-Q" uncommented, the program also crashes, seemingly in the same place as before (n.b the actual place seems to be in the memcpy in TimerHandler, which doesn't make a lot of sense). Sorry if this sounds vague but I'm having difficulty thinking of ways of testing for whats wrong. I've played around with a lot of details, shifting the order of the parts of TimerHandler, etc etc and the problem hasn't yet presented itself in a format I can understand. I suspect variuos things but can't prove any of them. Does anybody have suggestions for a sensible way of approaching debugging this ? (or is the problem obvious to anybody ?). Badders