backported fixes from rtxon
[dosdemo] / src / dos / timer.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <conio.h>
5 #include <dos.h>
6
7 #ifdef __WATCOMC__
8 #include <i86.h>
9 #endif
10
11 #ifdef __DJGPP__
12 #include <stdint.h>
13 #include <dpmi.h>
14 #include <go32.h>
15 #include <pc.h>
16 #endif
17
18 #include "pit8254.h"
19
20 #define PIT_TIMER_INTR  8
21 #define DOS_TIMER_INTR  0x1c
22
23 /* macro to divide and round to the nearest integer */
24 #define DIV_ROUND(a, b) \
25         ((a) / (b) + ((a) % (b)) / ((b) / 2))
26
27 static void set_timer_reload(int reload_val);
28 static void cleanup(void);
29
30 #ifdef __WATCOMC__
31 #define INTERRUPT       __interrupt __far
32
33 static void INTERRUPT dos_timer_intr();
34
35 static void (INTERRUPT *prev_timer_intr)();
36 #endif
37
38 #ifdef __DJGPP__
39 #define INTERRUPT
40
41 static _go32_dpmi_seginfo intr, prev_intr;
42
43 #define outp(p, v)      outportb(p, v)
44 #endif
45
46 static void INTERRUPT timer_irq();
47
48 static unsigned long ticks;
49 static unsigned long tick_interval, ticks_per_dos_intr;
50 static int inum;
51
52 void init_timer(int res_hz)
53 {
54         _disable();
55
56         if(res_hz > 0) {
57                 int reload_val = DIV_ROUND(OSC_FREQ_HZ, res_hz);
58                 set_timer_reload(reload_val);
59
60                 tick_interval = DIV_ROUND(1000, res_hz);
61                 ticks_per_dos_intr = DIV_ROUND(65535L, reload_val);
62
63                 inum = PIT_TIMER_INTR;
64 #ifdef __WATCOMC__
65                 prev_timer_intr = _dos_getvect(inum);
66                 _dos_setvect(inum, timer_irq);
67 #endif
68 #ifdef __DJGPP__
69                 _go32_dpmi_get_protected_mode_interrupt_vector(inum, &prev_intr);
70                 intr.pm_offset = (intptr_t)timer_irq;
71                 intr.pm_selector = _go32_my_cs();
72                 _go32_dpmi_allocate_iret_wrapper(&intr);
73                 _go32_dpmi_set_protected_mode_interrupt_vector(inum, &intr);
74 #endif
75         } else {
76                 tick_interval = 55;
77
78                 inum = DOS_TIMER_INTR;
79 #ifdef __WATCOMC__
80                 prev_timer_intr = _dos_getvect(inum);
81                 _dos_setvect(inum, dos_timer_intr);
82 #endif
83 #ifdef __DJGPP__
84                 assert(0);
85 #endif
86         }
87         _enable();
88
89         atexit(cleanup);
90 }
91
92 static void cleanup(void)
93 {
94         if(!inum) {
95                 return; /* init hasn't ran, there's nothing to cleanup */
96         }
97
98         _disable();
99         if(inum == PIT_TIMER_INTR) {
100                 /* restore the original timer frequency */
101                 set_timer_reload(65535);
102         }
103
104         /* restore the original interrupt handler */
105 #ifdef __WATCOMC__
106         _dos_setvect(inum, prev_timer_intr);
107 #endif
108 #ifdef __DJGPP__
109         _go32_dpmi_set_protected_mode_interrupt_vector(inum, &prev_intr);
110         _go32_dpmi_free_iret_wrapper(&intr);
111 #endif
112
113         _enable();
114 }
115
116 void reset_timer(void)
117 {
118         ticks = 0;
119 }
120
121 unsigned long get_msec(void)
122 {
123         return ticks * tick_interval;
124 }
125
126 static void set_timer_reload(int reload_val)
127 {
128         outp(PORT_CMD, CMD_CHAN0 | CMD_ACCESS_BOTH | CMD_OP_SQWAVE);
129         outp(PORT_DATA0, reload_val & 0xff);
130         outp(PORT_DATA0, (reload_val >> 8) & 0xff);
131 }
132
133 #ifdef __WATCOMC__
134 static void INTERRUPT dos_timer_intr()
135 {
136         ticks++;
137         _chain_intr(prev_timer_intr);   /* DOES NOT RETURN */
138 }
139 #endif
140
141 /* first PIC command port */
142 #define PIC1_CMD        0x20
143 /* end of interrupt control word */
144 #define OCW2_EOI        (1 << 5)
145
146 static void INTERRUPT timer_irq()
147 {
148         static unsigned long dos_ticks;
149
150         ticks++;
151
152 #ifdef __WATCOMC__
153         if(++dos_ticks >= ticks_per_dos_intr) {
154                 /* I suppose the dos irq handler does the EOI so I shouldn't
155                  * do it if I am to call the previous function
156                  */
157                 dos_ticks = 0;
158                 _chain_intr(prev_timer_intr);   /* XXX DOES NOT RETURN */
159                 return; /* just for clarity */
160         }
161 #endif
162
163         /* send EOI to the PIC */
164         outp(PIC1_CMD, OCW2_EOI);
165 }