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