Date: Mon, 3 Apr 1995 10:07:13 +0500 From: hvb AT netrix DOT com To: chan1355 AT cs DOT cuhk DOT hk, jk55 AT cornell DOT edu (Jim Karpinski) Cc: djgpp AT sun DOT soe DOT clarkson DOT edu Subject: Re: Code for controlling PC Timer - Complete version References: <9503312208 DOT AA07914 AT azalea> <9504010251 DOT AA10100 AT cucs18 DOT cs DOT cuhk DOT hk> Dear all, Here is the code that I cut and paste from my code. You may have to declare the static variables here and there to get it compiled. There are two versions of this code, one uses the TOD and one uses the RTC. The limitations/problems to each version are described. Sorry for the duplication, I accidentally sent off an incomplete version of this mail. /************************************************************************* Using the TOD 8253/4 timer. Works but had problems under heavy load of disk access. *************************************************************************/ #define DOSX_GO32_RAM_OFFSET 0xe0000000 /*--------------------------------------------------------------------------- Definitions for The PC INTERRUPT CONTROLLER ---------------------------------------------------------------------------*/ #define PC_INT_CONTROLLER 0x20 /* Interrupt controller I/O Address */ #define PC_8253_TIMER_CHANNEL 0x40 /* Timer channel (#0) of the timer chip */ #define PC_8253_COMMAND_REG = 0x43 /* Command register of the timer chip */ #define PC_8253_CH_0_SEQ = 0x36 /* 00110110 : ch 0, 2 bytes, mode 3, bin */ #define PC_8253_ONE_MSEC_COUNT = 1193 /* The PC 8253 clock input is 1.193180 MHz */ #define PC_8253_CLOCK_RATE = 1193180 #define TO_KICK_DOS_CLOCK = 0xffff /********************************************************************* * PM IRQ 8 * * DESCRIPTION: * This function is called when the Time Of Day interrupt occurs * while the 386 CPU is in Protected mode. * We need to call the DOS real mode interrupt whenever the * * NOTES: * 1. The Rel_1ms clock is not counted every so often to take into * account that the real time clock is fast. * 2. alias_irq_regs & regs are declared sattic as the 'dpmi' code * assumes they belong to the 'DS' selector. * 3. Interrupt is explicitly disabled with an "cli" instruction * as when int86 is called, the processor may get another clock * interrupt when switched back from the real mode. *********************************************************************/ static void pm_irq_08 (void) { asm ("cli"); asm ("push %eax"); asm ("push %ecx"); asm ("push %edx"); asm ("push %ds"); asm ("push $0x48"); asm ("pop %ds"); if ((*One_msec_clock_remainder += *Delta_per_tick) >= PC_8253_CLOCK_RATE) *One_msec_clock_remainder -= PC_8253_CLOCK_RATE; else *Rel_1ms += *Msec_per_tick; if ((*Dos_clock_remainder += *Timer_counter) >= TO_KICK_DOS_CLOCK) { asm ("pusha"); asm ("push %gs"); *Dos_clock_remainder -= TO_KICK_DOS_CLOCK; /*------------------------------------------------------------------- This is equivalent to _go32_dpmi_simulate_fcall_iret -------------------------------------------------------------------*/ Alias_irq_regs.x.cs = Rm_old_irq8.rm_segment; Alias_irq_regs.x.ip = Rm_old_irq8.rm_offset; Alias_irq_regs.x.ss = 0; Alias_irq_regs.x.sp = 0; Alias_irq_regs.x.flags = 0; Irq_regs.x.ax = 0x302; Irq_regs.h.bh = 0; Irq_regs.x.cx = 0; Irq_regs.x.di = (uint32)(&Alias_irq_regs); int86 (0x31, &Irq_regs, &Irq_regs); asm ("add $12, %esp"); asm ("pop %gs"); asm ("popa"); } else { asm ("mov $0x20,%al"); asm ("out %al,$0x20"); } asm ("pop %ds"); asm ("pop %edx"); asm ("pop %ecx"); asm ("pop %eax"); asm ("leave"); asm ("iret"); } /********************************************************************* * INSTALL TOD TIMER INTERRUPT * * DESCRIPTION: * This function will set up the PC time of day (tod) interrupt to * generate interrupt approximately every one millisecond. * There are the protected mode and real mode interrupts. * The protected mode interrupt needs to call the DOS tod interrupt * roughly every 18msec to keep the DOS clock happy. A set of * counters are stored in the DOS low memory so the protected mode * and real mode code can share. * In real mode, we allocate a block of DOS low memory to create * the real mode interrupt code. This code basically will update * the Rel_1ms count and call the DOS tod interrupt every 18 msec. * * NOTES: * *********************************************************************/ static void install_tod_timer_interrupt (void) { _go32_dpmi_seginfo rm_si; _go32_dpmi_seginfo pm_si; uint32 linear_addr; uint32 go32_addr; uint16 *p16; uint32 *p32; /*--------------------------------------------------------------------------- This data space is the code for the real mode clock interrupt. The C equivalent form is: extern void dos_interrupt (void); static unsigned long Timer_counter; static unsigned long Dos_clock_remainder; static unsigned long One_msec_clock_remainder; static unsigned long Delta_per_tick; static unsigned long Rel_1ms; static unsigned long Msec_per_tick; #define PC_8253_CLOCK_RATE (1193180L) #define TO_KICK_DOS_CLOCK (0xffffL) extern void x (void); extern void outp (unsigned reg, unsigned value); void x (void) { if ((One_msec_clock_remainder += Delta_per_tick) >= PC_8253_CLOCK_RATE) One_msec_clock_remainder -= PC_8253_CLOCK_RATE; else Rel_1ms += Msec_per_tick; if ((Dos_clock_remainder += Timer_counter) >= TO_KICK_DOS_CLOCK) { Dos_clock_remainder -= TO_KICK_DOS_CLOCK; goto dos_interrupt; } else outp (0x20, 0x20); } ---------------------------------------------------------------------------*/ static uint8 real_mode_code[] = { /* offset opcode Assembly Mnemonic */ 0x00,0x00,0x00,0x00, /* 0000 00000000 Timer_counter dd 0 */ 0x00,0x00,0x00,0x00, /* 0004 00000000 Dos_clock_remainder dd 0 */ 0x00,0x00,0x00,0x00, /* 0008 00000000 One_msec_clock_remainder dd 0 */ 0x00,0x00,0x00,0x00, /* 000C 00000000 Delta_per_tick dd 0 */ 0x00,0x00,0x00,0x00, /* 0010 00000000 Rel_1ms dd 0 */ 0x00,0x00,0x00,0x00, /* 0014 00000000 Msec_per_tick dd 0 */ 0xfa, /* 0018 FA cli */ 0x50, /* 0019 50 push ax */ 0x52, /* 001A 52 push dx */ 0x2e,0xa1,0x0c,0x00, /* 001B 2E: A1 000C R mov ax,WORD PTR cs:Delta_per_tick */ 0x2e,0x8b,0x16,0x0e,0x00, /* 001F 2E: 8B 16 000E R mov dx,WORD PTR cs:Delta_per_tick+2 */ 0x2e,0x01,0x06,0x08,0x00, /* 0024 2E: 01 06 0008 R add WORD PTR cs:One_msec_clock_remainder,ax */ 0x2e,0x11,0x16,0x0a,0x00, /* 0029 2E: 11 16 000A R adc WORD PTR cs:One_msec_clock_remainder+2,dx */ 0x2e,0x83,0x3e,0x0a,0x00,0x12, /* 002E 2E: 83 3E 000A R 12 cmp WORD PTR cs:One_msec_clock_remainder+2,18 */ 0x72,0x1b, /* 0034 72 1B jb $I112 */ 0x77,0x09, /* 0036 77 09 ja $L20000 */ 0x2e,0x81,0x3e,0x08,0x00,0xdc,0x34, /* 0038 2E: 81 3E 0008 R 34DC cmp WORD PTR cs:One_msec_clock_remainder,13532 */ 0x72,0x10, /* 003F 72 10 jb $I112 */ /* 0041 $L20000: */ 0x2e,0x81,0x2e,0x08,0x00,0xdc,0x34, /* 0041 2E: 81 2E 0008 R 34DC sub WORD PTR cs:One_msec_clock_remainder,13532 */ 0x2e,0x83,0x1e,0x0a,0x00,0x12, /* 0048 2E: 83 1E 000A R 12 sbb WORD PTR cs:One_msec_clock_remainder+2,18 */ 0xeb,0x14, /* 004E EB 14 jmp SHORT $I113 */ 0x90, /* 0050 90 nop */ /* 0051 $I112: */ 0x2e,0xa1,0x14,0x00, /* 0051 2E: A1 0014 R mov ax,WORD PTR cs:Msec_per_tick */ 0x2e,0x8b,0x16,0x16,0x00, /* 0055 2E: 8B 16 0016 R mov dx,WORD PTR cs:Msec_per_tick+2 */ 0x2e,0x01,0x06,0x10,0x00, /* 005A 2E: 01 06 0010 R add WORD PTR cs:Rel_1ms,ax */ 0x2e,0x11,0x16,0x12,0x00, /* 005F 2E: 11 16 0012 R adc WORD PTR cs:Rel_1ms+2,dx */ /* 0064 $I113: */ 0x2e,0xa1,0x00,0x00, /* 0064 2E: A1 0000 R mov ax, WORD PTR cs:Timer_counter */ 0x2e,0x8b,0x16,0x02,0x00, /* 0068 2E: 8B 16 0002 R mov dx, WORD PTR cs:Timer_counter+2 */ 0x2e,0x01,0x06,0x04,0x00, /* 006D 2E: 01 06 0004 R add WORD PTR cs:Dos_clock_remainder, ax */ 0x2e,0x11,0x16,0x06,0x00, /* 0072 2E: 11 16 0006 R adc WORD PTR cs:Dos_clock_remainder+2, dx */ 0x2e,0x83,0x3e,0x06,0x00,0x00, /* 0077 2E: 83 3E 0006 R 00 cmp WORD PTR cs:Dos_clock_remainder+2, 0 */ 0x75,0x08, /* 007D 75 08 jne $L20002 */ 0x2e,0x83,0x3e,0x04,0x00,0xff, /* 007F 2E: 83 3E 0004 R FF cmp WORD PTR cs:Dos_clock_remainder, TO_KICK_DOS_CLOCK */ 0x72,0x13, /* 0085 72 13 jb $I114 */ /* 0087 $L20002: */ 0x2e,0x83,0x2e,0x04,0x00,0xff, /* 0087 2E: 83 2E 0004 R FF sub WORD PTR cs:Dos_clock_remainder, TO_KICK_DOS_CLOCK */ 0x2e,0x83,0x1e,0x06,0x00,0x00, /* 008D 2E: 83 1E 0006 R 00 sbb WORD PTR cs:Dos_clock_remainder+2, 0 */ 0x5a, /* 0093 5A pop dx */ 0x58, /* 0094 58 pop ax */ 0xea,0x00,0x00,0x00,0x00, /* 0095 EA 0000 ---- E jmp */ /* 009A $I114: */ 0xb0,0x20, /* 009A B0 20 mov al,20H */ 0xe6,0x20, /* 009C E6 20 out 20H,al */ 0x5a, /* 009E 5A pop dx */ 0x58, /* 009F 58 pop ax */ 0xcf /* 00A0 CF iret */ }; /*--------------------------------------------------------------------------- allocate DOS low memory to store the code above. ---------------------------------------------------------------------------*/ Dosx_rm_clock_int_seginfo.size = (sizeof (real_mode_code) + 0x10) / 0x10; if (_go32_dpmi_allocate_dos_memory (&Dosx_rm_clock_int_seginfo)) exit (1); /*--------------------------------------------------------------------------- make sure where we put the code is paragraph (16-byte) aligned fill memory with INT 3 opcode. ---------------------------------------------------------------------------*/ linear_addr = (uint16)Dosx_rm_clock_int_seginfo.rm_segment * 0x10; if (linear_addr & 0x0000000f) linear_addr = (linear_addr + 0x10) & 0xfffffff0; go32_addr = linear_addr + DOSX_GO32_RAM_OFFSET; memset ((uint8 *)go32_addr, 0xcc, Dosx_rm_clock_int_seginfo.size * 0x10); /*--------------------------------------------------------------------------- set up pointers to the shared timer variables, and initialize variables ---------------------------------------------------------------------------*/ p32 = (uint32 *)go32_addr; Timer_counter = p32++; Dos_clock_remainder = p32++; One_msec_clock_remainder = p32++; Delta_per_tick = p32++; Rel_1ms = p32++; Msec_per_tick = p32; /*--------------------------------------------------------------------------- write the code to the DOS low memory ---------------------------------------------------------------------------*/ _go32_dpmi_get_real_mode_interrupt_vector(8, &Rm_old_irq8); memcpy ((uint8 *)go32_addr, real_mode_code, sizeof (real_mode_code)); *Timer_counter = 1193; *Delta_per_tick = PC_8253_CLOCK_RATE % Timer_counter_value; *Msec_per_tick = 1000 / (PC_8253_CLOCK_RATE / Timer_counter_value); p16 = (uint16 *)(go32_addr + 0x96); *p16++ = Rm_old_irq8.rm_offset; *p16 = Rm_old_irq8.rm_segment; /*--------------------------------------------------------------------------- setup real and protected mode interrupt vectors ---------------------------------------------------------------------------*/ memset (&rm_si, 0, sizeof (rm_si)); rm_si.rm_offset = 0x18; rm_si.rm_segment = (uint16)(linear_addr >> 4); memset (&pm_si, 0, sizeof (pm_si)); pm_si.pm_offset = (int) pm_irq_08; pm_si.pm_selector = _go32_my_cs(); disable(); _go32_dpmi_set_real_mode_interrupt_vector(8, &rm_si); _go32_dpmi_set_protected_mode_interrupt_vector(8, &pm_si); outpb (PC_8253_COMMAND_REG, PC_8253_CH_0_SEQ); outpb (PC_8253_TIMER_CHANNEL, Timer_counter_value & 0xff); outpb (PC_8253_TIMER_CHANNEL, Timer_counter_value >> 8); enable(); } /************************************************************************* Using the RTC timer. Worked on one PC platform but I found it did not work on another platform. Still under investigation. *************************************************************************/ #define DOSX_GO32_RAM_OFFSET 0xe0000000 /*--------------------------------------------------------------------------- MC146818A Real Time Clock (RTC) This RTC device can generate IRQ8 interrupt on a periodic basis to the PC. See: - Dallas Semiconductor handbook for device DS1287 (MC146818A equiv.) - ISA System Architecture, by Tom Shanley & Don Anderson - Mind Share Inc 1993. (Chapter 21). ---------------------------------------------------------------------------*/ #define RTC_ADDR_PORT 0x70 /* RTC chip address port */ #define RTC_DATA_PORT = 0x71 /* RTC chip data port */ #define RTC_STATUS_REG_A 0xa /* RTC Status Register A RAM offset */ #define RTC_STATUS_REG_B 0xb /* RTC Status Register B RAM offset */ #define RTC_STATUS_REG_C 0xc /* RTC Status Register C RAM offset */ /* status A register definition */ enum uint8 { RTC_REG_A_RATE_MASK = 0x0f, /* mask to the DS0-DS3 bits */ RTC_REG_A_RATE_1K = 0x06, /* DS0-DS3 value for 1.024 kHz clock rate */ RTC_REG_A_UIP_BIT = 0x80, /* =1 Update in Progress, Read only */ RTC_REG_A_DV_MASK = 0x70, /* oscillator on and off bit */ RTC_REG_A_DV_32K = 0x20 /* DV bits to be set to 32.768khz timebase */ }; /* status B register definition */ #define RTC_REG_B_PIE_BIT 0x40 /* 1=> Periodic Interrupt enable */ #define RTC_REG_B_AIE_BIT 0x20 /* 1=> Alarm Interrupt enable */ #define RTC_REG_B_UEIE_BIT 0x10 /* 1=> Update-Ended Interrupt Enable */ #define RTC_INT_TYPE 0x70 /* RTC interrupt type */ #define RTC_REMAINDER_PER_TICK 24 /* count remainder per tick */ /********************************************************************* * PM RTC IRQ * * DESCRIPTION: * This function is called when the RTC interrupt occurs * while the 386 CPU is in Protected mode. * * NOTES: * 1. The Rel_1ms clock is not counted every so often to take into * account that the real time clock is fast. * 2. There is a bug in the gcc compiler used that prohibits the use * of "out %al,immed8". This instruction always generates * "out %ax,immed16". Therefore we have to use the * "out %al,%dx" format. * *********************************************************************/ static void pm_rtc_irq (void) { asm ("cli \n" "push %eax \n" "push %edx \n" "push %ds \n" "push $0x48 \n" "pop %ds \n" ); if ((*One_msec_clock_remainder += RTC_REMAINDER_PER_TICK) >= 1000) *One_msec_clock_remainder -= 1000; else ++(*Rel_1ms); /*-------------------------------------------------------------------- reset the RTC IRQ pin, and the interrupt controller --------------------------------------------------------------------*/ asm ("mov $0x0c,%al \n" "outb %al,$0x70 \n" "in $0x71,%al \n" "mov $0x20,%al \n" "outb %al,$0xa0 \n" "outb %al,$0x20 \n" "pop %ds \n" "pop %edx \n" "pop %eax \n" "leave \n" "iret \n" ); } /********************************************************************* * INSTALL RTC INTERRUPT * * DESCRIPTION: * This function will set up the PC time of day (tod) interrupt to * generate interrupt approximately every one millisecond. * There are the protected mode and real mode interrupts. * The protected mode interrupt needs to call the DOS tod interrupt * roughly every 18msec to keep the DOS clock happy. A set of * counters are stored in the DOS low memory so the protected mode * and real mode code can share. * In real mode, we allocate a block of DOS low memory to create * the real mode interrupt code. This code basically will update * the Rel_1ms count and call the DOS tod interrupt every 18 msec. * * NOTES: * *********************************************************************/ static void install_rtc_interrupt (void) { _go32_dpmi_seginfo rm_si; _go32_dpmi_seginfo pm_si; uint32 linear_addr; uint32 go32_addr; uint8 temp8; /*--------------------------------------------------------------------------- This data space is the code for the real mode clock interrupt. The C equivalent form is: static unsigned long Rel_1ms; static unsigned One_msec_clock_remainder; #define RTC_REMAINDER_PER_TICK (24) #define RTC_ADDR_PORT (0x70) #define RTC_DATA_PORT (0x71) #define RTC_STATUS_REG_C (0xc) extern void x (void); extern void outp (unsigned reg, unsigned value); extern void inp (unsigned reg); void x (void) { if ((*One_msec_clock_remainder += RTC_REMAINDER_PER_TICK) >= 1000) *One_msec_clock_remainder -= 1000; else ++(*Rel_1ms); outp (0x70, 0x0c); inp (0x71); outp (0xa0, 0x20); outp (0x20, 0x20); } ---------------------------------------------------------------------------*/ static uint8 real_mode_code[] = { /* offset opcode Assembly Mnemonic */ 0x00,0x00,0x00,0x00, /* 0000 00000000 Rel_1ms dd 0 */ 0x00,0x00, /* 0004 0000 One_msec_clock_remainder dw 0 */ 0xfa, /* 0006 FA cli */ 0x50, /* 0007 50 push ax */ 0x2e,0x83,0x06,0x04,0x00,0x18, /* 0008 2E: 83 06 0004 R 18 add WORD PTR cs:One_msec_clock_remainder,RTC_REMAINDER_PER_TICK */ 0x2e,0x81,0x3e,0x04,0x00,0xe8,0x03, /* 000E 2E: 81 3E 0004 R 03E8 cmp WORD PTR cs:One_msec_clock_remainder,1000 */ 0x72,0x09, /* 0015 72 09 jb $I110 */ 0x2e,0x81,0x2e,0x04,0x00,0xe8,0x03, /* 0017 2E: 81 2E 0004 R 03E8 sub WORD PTR cs:One_msec_clock_remainder,1000 */ 0xeb,0x0c, /* 001E EB 0C jmp SHORT $I111 */ /* 0020 $I110: */ 0x2e,0x83,0x06,0x00,0x00,0x01, /* 0020 2E: 83 06 0000 R 01 add WORD PTR cs:Rel_1ms,1 */ 0x2e,0x83,0x16,0x02,0x00,0x00, /* 0026 2E: 83 16 0002 R 00 adc WORD PTR cs:Rel_1ms+2,0 */ /* 002C $I111: */ 0xb0,0x0c, /* 002C B0 0C mov al,RTC_STATUS_REG_C */ 0xe6,0x70, /* 002E E6 70 out RTC_ADDR_PORT,al */ 0xe4,0x71, /* 0030 E4 71 in al,RTC_DATA_PORT */ 0xb0,0x20, /* 0032 B0 20 mov al,20H */ 0xe6,0xa0, /* 0034 E6 A0 out 0a0H,al */ 0xe6,0x20, /* 0036 E6 20 out 020H,al */ 0x58, /* 0038 58 pop ax */ 0xcf /* 0039 CF iret */ }; /*--------------------------------------------------------------------------- allocate DOS low memory to store the code above. ---------------------------------------------------------------------------*/ Dosx_rm_clock_int_seginfo.size = (sizeof (real_mode_code) + 0x10) / 0x10; if (_go32_dpmi_allocate_dos_memory (&Dosx_rm_clock_int_seginfo)) n_sys_error (Dosx_out_of_dos_memory); /*--------------------------------------------------------------------------- make sure where we put the code is paragraph (16-byte) aligned fill memory with INT 3 opcode. ---------------------------------------------------------------------------*/ linear_addr = (uint16)Dosx_rm_clock_int_seginfo.rm_segment * 0x10; go32_addr = linear_addr + DOSX_GO32_RAM_OFFSET; memset ((uint8 *)go32_addr, 0xcc, Dosx_rm_clock_int_seginfo.size * 0x10); /*--------------------------------------------------------------------------- set up pointers to the shared timer variables, and initialize variables ---------------------------------------------------------------------------*/ Rel_1ms = (uint32 *)go32_addr; One_msec_clock_remainder = (uint16 *)(go32_addr + 4); memcpy ((uint8 *)go32_addr, real_mode_code, sizeof (real_mode_code)); memset (&rm_si, 0, sizeof (rm_si)); rm_si.rm_offset = 0x06; rm_si.rm_segment = (uint16)(linear_addr >> 4); memset (&pm_si, 0, sizeof (pm_si)); pm_si.pm_offset = (int) pm_rtc_irq; pm_si.pm_selector = _go32_my_cs(); /*--------------------------------------------------------------------------- set up the RTC registers for Periodic Interrupt @ 1.024khz ---------------------------------------------------------------------------*/ disable (); outpb (RTC_ADDR_PORT, RTC_STATUS_REG_A); temp8 = inpb (RTC_DATA_PORT); if ((temp8 & RTC_REG_A_DV_MASK) != RTC_REG_A_DV_32K) n_sys_error (Dosx_krnl_rtc_conflict); temp8 &= ~RTC_REG_A_RATE_MASK; temp8 |= RTC_REG_A_RATE_1K; outpb (RTC_ADDR_PORT, RTC_STATUS_REG_A); outpb (RTC_DATA_PORT, temp8); outpb (RTC_ADDR_PORT, RTC_STATUS_REG_C); inpb (RTC_DATA_PORT); outpb (RTC_ADDR_PORT, RTC_STATUS_REG_B); temp8 = inpb (RTC_DATA_PORT); if (temp8 & (RTC_REG_B_PIE_BIT | RTC_REG_B_AIE_BIT | RTC_REG_B_UEIE_BIT)) n_sys_error (Dosx_krnl_rtc_conflict); /*--------------------------------------------------------------------------- setup real and protected mode interrupt vectors ---------------------------------------------------------------------------*/ _go32_dpmi_set_real_mode_interrupt_vector (RTC_INT_TYPE, &rm_si); _go32_dpmi_set_protected_mode_interrupt_vector (RTC_INT_TYPE, &pm_si); outpb (RTC_ADDR_PORT, RTC_STATUS_REG_B); outpb (RTC_DATA_PORT, temp8 | RTC_REG_B_PIE_BIT); outpb (0xa1, (inpb (0xa1) & 0xfe)); enable(); } ============================================================== Hung Bui Internet: h DOT bui AT ieee DOT org Netrix Corporation Phone: +1 703 793 1016 13595 Dulles Technology Drive Fax: +1 703 713 3805 Herndon Va 22071 ==============================================================