Date: Mon, 3 Apr 1995 10:01:03 +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 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. /************************************************************************* 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 */ RTC_DATA_PORT = 0x71; /* RTC chip data port */ RTC_STATUS_REG_A = 0xa; /* RTC Status Register A RAM offset */ RTC_STATUS_REG_B = 0xb; /* RTC Status Register B RAM offset */ 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 */ RTC_REG_B_PIE_BIT = 0x40; /* 1=> Periodic Interrupt enable */ RTC_REG_B_AIE_BIT = 0x20; /* 1=> Alarm Interrupt enable */ RTC_REG_B_UEIE_BIT = 0x10; /* 1=> Update-Ended Interrupt Enable */ RTC_INT_TYPE = 0x70; /* RTC interrupt type */ RTC_REMAINDER_PER_TICK = 24; /* count remainder per tick */