x86emu_i8254.c revision 1.1.2.2 1 1.1.2.2 matt /* $NetBSD: x86emu_i8254.c,v 1.1.2.2 2008/01/09 01:21:38 matt Exp $ */
2 1.1.2.2 matt
3 1.1.2.2 matt /*-
4 1.1.2.2 matt * Copyright (c) 2007 Joerg Sonnenberger <joerg (at) NetBSD.org>.
5 1.1.2.2 matt * All rights reserved.
6 1.1.2.2 matt *
7 1.1.2.2 matt * Redistribution and use in source and binary forms, with or without
8 1.1.2.2 matt * modification, are permitted provided that the following conditions
9 1.1.2.2 matt * are met:
10 1.1.2.2 matt *
11 1.1.2.2 matt * 1. Redistributions of source code must retain the above copyright
12 1.1.2.2 matt * notice, this list of conditions and the following disclaimer.
13 1.1.2.2 matt * 2. Redistributions in binary form must reproduce the above copyright
14 1.1.2.2 matt * notice, this list of conditions and the following disclaimer in
15 1.1.2.2 matt * the documentation and/or other materials provided with the
16 1.1.2.2 matt * distribution.
17 1.1.2.2 matt *
18 1.1.2.2 matt * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 1.1.2.2 matt * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 1.1.2.2 matt * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 1.1.2.2 matt * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 1.1.2.2 matt * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 1.1.2.2 matt * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 1.1.2.2 matt * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 1.1.2.2 matt * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 1.1.2.2 matt * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 1.1.2.2 matt * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28 1.1.2.2 matt * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 1.1.2.2 matt * SUCH DAMAGE.
30 1.1.2.2 matt */
31 1.1.2.2 matt
32 1.1.2.2 matt #include <x86emu/x86emu_i8254.h>
33 1.1.2.2 matt
34 1.1.2.2 matt #ifndef _KERNEL
35 1.1.2.2 matt #include <assert.h>
36 1.1.2.2 matt #define KASSERT(x) assert(x)
37 1.1.2.2 matt #endif
38 1.1.2.2 matt
39 1.1.2.2 matt #define I8254_FREQ 1193182 /* Hz */
40 1.1.2.2 matt
41 1.1.2.2 matt static uint16_t
42 1.1.2.2 matt bcd2bin(uint16_t bcd_val)
43 1.1.2.2 matt {
44 1.1.2.2 matt return bcd_val % 0x10 + (bcd_val / 0x10 % 0x10 * 10) +
45 1.1.2.2 matt (bcd_val / 0x100 % 0x10 * 100) + (bcd_val / 0x1000 % 0x10 * 1000);
46 1.1.2.2 matt }
47 1.1.2.2 matt
48 1.1.2.2 matt static uint16_t
49 1.1.2.2 matt bin2bcd(uint16_t bin_val)
50 1.1.2.2 matt {
51 1.1.2.2 matt return (bin_val % 10) + (bin_val / 10 % 10 * 0x10) +
52 1.1.2.2 matt (bin_val / 100 % 10 * 0x100) + (bin_val / 1000 % 10 * 0x1000);
53 1.1.2.2 matt }
54 1.1.2.2 matt
55 1.1.2.2 matt /*
56 1.1.2.2 matt * Compute tick of the virtual timer based on start time and
57 1.1.2.2 matt * current time.
58 1.1.2.2 matt */
59 1.1.2.2 matt static uint64_t
60 1.1.2.2 matt x86emu_i8254_gettick(struct x86emu_i8254 *sc)
61 1.1.2.2 matt {
62 1.1.2.2 matt struct timespec curtime;
63 1.1.2.2 matt uint64_t tick;
64 1.1.2.2 matt
65 1.1.2.2 matt (*sc->gettime)(&curtime);
66 1.1.2.2 matt
67 1.1.2.2 matt tick = (curtime.tv_sec - sc->base_time.tv_sec) * I8254_FREQ;
68 1.1.2.2 matt tick += (uint64_t)(curtime.tv_nsec - sc->base_time.tv_nsec) * I8254_FREQ / 1000000000;
69 1.1.2.2 matt
70 1.1.2.2 matt return tick;
71 1.1.2.2 matt }
72 1.1.2.2 matt
73 1.1.2.2 matt /* Compute current counter value. */
74 1.1.2.2 matt static uint16_t
75 1.1.2.2 matt x86emu_i8254_counter(struct x86emu_i8254_timer *timer, uint64_t curtick)
76 1.1.2.2 matt {
77 1.1.2.2 matt uint16_t maxtick;
78 1.1.2.2 matt
79 1.1.2.2 matt /* Initial value if timer is disabled or not yet started */
80 1.1.2.2 matt if (timer->gate_high || timer->start_tick > curtick)
81 1.1.2.2 matt return timer->active_counter;
82 1.1.2.2 matt
83 1.1.2.2 matt /* Compute maximum value based on BCD/binary mode */
84 1.1.2.2 matt if (timer->active_is_bcd)
85 1.1.2.2 matt maxtick = 9999;
86 1.1.2.2 matt else
87 1.1.2.2 matt maxtick = 0xffff;
88 1.1.2.2 matt
89 1.1.2.2 matt curtick -= timer->start_tick;
90 1.1.2.2 matt
91 1.1.2.2 matt /* Check if first run over the time counter is over. */
92 1.1.2.2 matt if (curtick <= timer->active_counter)
93 1.1.2.2 matt return timer->active_counter - curtick;
94 1.1.2.2 matt /* Now curtick > 0 as both values above are unsigned. */
95 1.1.2.2 matt
96 1.1.2.2 matt /* Special case of active_counter == maxtick + 1 */
97 1.1.2.2 matt if (timer->active_counter == 0 && curtick - 1 <= maxtick)
98 1.1.2.2 matt return maxtick + 1 - curtick;
99 1.1.2.2 matt
100 1.1.2.2 matt /* For periodic timers, compute current periode. */
101 1.1.2.2 matt if (timer->active_mode & 2)
102 1.1.2.2 matt return timer->active_counter - curtick % timer->active_counter;
103 1.1.2.2 matt
104 1.1.2.2 matt /* For one-shot timers, compute overflow. */
105 1.1.2.2 matt curtick -= maxtick + 1;
106 1.1.2.2 matt return maxtick - curtick % maxtick + 1;
107 1.1.2.2 matt }
108 1.1.2.2 matt
109 1.1.2.2 matt static bool
110 1.1.2.2 matt x86emu_i8254_out(struct x86emu_i8254_timer *timer, uint64_t curtick)
111 1.1.2.2 matt {
112 1.1.2.2 matt uint16_t maxtick;
113 1.1.2.2 matt
114 1.1.2.2 matt /*
115 1.1.2.2 matt * TODO:
116 1.1.2.2 matt * Mode 0:
117 1.1.2.2 matt * After the write of the LSB and before the write of the MSB,
118 1.1.2.2 matt * this should return LOW.
119 1.1.2.2 matt */
120 1.1.2.2 matt
121 1.1.2.2 matt /*
122 1.1.2.2 matt * If the timer was not started yet or is disabled,
123 1.1.2.2 matt * only Mode 0 is LOW
124 1.1.2.2 matt */
125 1.1.2.2 matt if (timer->gate_high || timer->start_tick > curtick)
126 1.1.2.2 matt return (timer->active_mode != 0);
127 1.1.2.2 matt
128 1.1.2.2 matt /* Max tick based on BCD/binary mode */
129 1.1.2.2 matt if (timer->active_is_bcd)
130 1.1.2.2 matt maxtick = 9999;
131 1.1.2.2 matt else
132 1.1.2.2 matt maxtick = 0xffff;
133 1.1.2.2 matt
134 1.1.2.2 matt curtick -= timer->start_tick;
135 1.1.2.2 matt
136 1.1.2.2 matt /* Return LOW until counter is 0, afterwards HIGH until reload. */
137 1.1.2.2 matt if (timer->active_mode == 0 || timer->active_mode == 1)
138 1.1.2.2 matt return curtick >= timer->start_tick;
139 1.1.2.2 matt
140 1.1.2.2 matt /* Return LOW until the counter is 0, raise to HIGH and go LOW again. */
141 1.1.2.2 matt if (timer->active_mode == 5 || timer->active_mode == 7)
142 1.1.2.2 matt return curtick != timer->start_tick;
143 1.1.2.2 matt
144 1.1.2.2 matt /*
145 1.1.2.2 matt * Return LOW until the counter is 1, raise to HIGH and go LOW
146 1.1.2.2 matt * again. Afterwards reload the counter.
147 1.1.2.2 matt */
148 1.1.2.2 matt if (timer->active_mode == 2 || timer->active_mode == 3) {
149 1.1.2.2 matt curtick %= timer->active_counter;
150 1.1.2.2 matt return curtick + 1 != timer->active_counter;
151 1.1.2.2 matt }
152 1.1.2.2 matt
153 1.1.2.2 matt /*
154 1.1.2.2 matt * If the initial counter is even, return HIGH for the first half
155 1.1.2.2 matt * and LOW for the second. If it is even, bias the first half.
156 1.1.2.2 matt */
157 1.1.2.2 matt curtick %= timer->active_counter;
158 1.1.2.2 matt return curtick < (timer->active_counter + 1) / 2;
159 1.1.2.2 matt }
160 1.1.2.2 matt
161 1.1.2.2 matt static void
162 1.1.2.2 matt x86emu_i8254_latch_status(struct x86emu_i8254_timer *timer, uint64_t curtick)
163 1.1.2.2 matt {
164 1.1.2.2 matt if (timer->status_is_latched)
165 1.1.2.2 matt return;
166 1.1.2.2 matt timer->latched_status = timer->active_is_bcd ? 1 : 0;
167 1.1.2.2 matt timer->latched_status |= timer->active_mode << 1;
168 1.1.2.2 matt timer->latched_status |= timer->rw_status;
169 1.1.2.2 matt timer->latched_status |= timer->null_count ? 0x40 : 0;
170 1.1.2.2 matt }
171 1.1.2.2 matt
172 1.1.2.2 matt static void
173 1.1.2.2 matt x86emu_i8254_latch_counter(struct x86emu_i8254_timer *timer, uint64_t curtick)
174 1.1.2.2 matt {
175 1.1.2.2 matt if (!timer->counter_is_latched)
176 1.1.2.2 matt return; /* Already latched. */
177 1.1.2.2 matt timer->latched_counter = x86emu_i8254_counter(timer, curtick);
178 1.1.2.2 matt timer->counter_is_latched = true;
179 1.1.2.2 matt }
180 1.1.2.2 matt
181 1.1.2.2 matt static void
182 1.1.2.2 matt x86emu_i8254_write_command(struct x86emu_i8254 *sc, uint8_t val)
183 1.1.2.2 matt {
184 1.1.2.2 matt struct x86emu_i8254_timer *timer;
185 1.1.2.2 matt int i;
186 1.1.2.2 matt
187 1.1.2.2 matt if ((val >> 6) == 3) {
188 1.1.2.2 matt /* Read Back Command */
189 1.1.2.2 matt uint64_t curtick;
190 1.1.2.2 matt
191 1.1.2.2 matt curtick = x86emu_i8254_gettick(sc);
192 1.1.2.2 matt for (i = 0; i < 3; ++i) {
193 1.1.2.2 matt timer = &sc->timer[i];
194 1.1.2.2 matt
195 1.1.2.2 matt if ((val & (2 << i)) == 0)
196 1.1.2.2 matt continue;
197 1.1.2.2 matt if ((val & 0x10) != 0)
198 1.1.2.2 matt x86emu_i8254_latch_status(timer, curtick);
199 1.1.2.2 matt if ((val & 0x20) != 0)
200 1.1.2.2 matt x86emu_i8254_latch_counter(timer, curtick);
201 1.1.2.2 matt }
202 1.1.2.2 matt return;
203 1.1.2.2 matt }
204 1.1.2.2 matt
205 1.1.2.2 matt timer = &sc->timer[val >> 6];
206 1.1.2.2 matt
207 1.1.2.2 matt switch (val & 0x30) {
208 1.1.2.2 matt case 0:
209 1.1.2.2 matt x86emu_i8254_latch_counter(timer, x86emu_i8254_gettick(sc));
210 1.1.2.2 matt return;
211 1.1.2.2 matt case 1:
212 1.1.2.2 matt timer->write_lsb = timer->read_lsb = true;
213 1.1.2.2 matt timer->write_msb = timer->read_msb = false;
214 1.1.2.2 matt break;
215 1.1.2.2 matt case 2:
216 1.1.2.2 matt timer->write_lsb = timer->read_lsb = false;
217 1.1.2.2 matt timer->write_msb = timer->read_msb = true;
218 1.1.2.2 matt break;
219 1.1.2.2 matt case 3:
220 1.1.2.2 matt timer->write_lsb = timer->read_lsb = true;
221 1.1.2.2 matt timer->write_msb = timer->read_msb = true;
222 1.1.2.2 matt break;
223 1.1.2.2 matt }
224 1.1.2.2 matt timer->rw_status = val & 0x30;
225 1.1.2.2 matt timer->null_count = true;
226 1.1.2.2 matt timer->new_mode = (val >> 1) & 0x7;
227 1.1.2.2 matt timer->new_is_bcd = (val & 1) == 1;
228 1.1.2.2 matt }
229 1.1.2.2 matt
230 1.1.2.2 matt static uint8_t
231 1.1.2.2 matt x86emu_i8254_read_counter(struct x86emu_i8254 *sc,
232 1.1.2.2 matt struct x86emu_i8254_timer *timer)
233 1.1.2.2 matt {
234 1.1.2.2 matt uint16_t val;
235 1.1.2.2 matt uint8_t output;
236 1.1.2.2 matt
237 1.1.2.2 matt /* If status was latched by Read Back Command, return it. */
238 1.1.2.2 matt if (timer->status_is_latched) {
239 1.1.2.2 matt timer->status_is_latched = false;
240 1.1.2.2 matt return timer->latched_status;
241 1.1.2.2 matt }
242 1.1.2.2 matt
243 1.1.2.2 matt /*
244 1.1.2.2 matt * The value of the counter is either the latched value
245 1.1.2.2 matt * or the current counter.
246 1.1.2.2 matt */
247 1.1.2.2 matt if (timer->counter_is_latched)
248 1.1.2.2 matt val = timer->latched_counter;
249 1.1.2.2 matt else
250 1.1.2.2 matt val = x86emu_i8254_counter(&sc->timer[2],
251 1.1.2.2 matt x86emu_i8254_gettick(sc));
252 1.1.2.2 matt
253 1.1.2.2 matt if (timer->active_is_bcd)
254 1.1.2.2 matt val = bin2bcd(val);
255 1.1.2.2 matt
256 1.1.2.2 matt /* Extract requested byte. */
257 1.1.2.2 matt if (timer->read_lsb) {
258 1.1.2.2 matt output = val & 0xff;
259 1.1.2.2 matt timer->read_lsb = false;
260 1.1.2.2 matt } else if (timer->read_msb) {
261 1.1.2.2 matt output = val >> 8;
262 1.1.2.2 matt timer->read_msb = false;
263 1.1.2.2 matt } else
264 1.1.2.2 matt output = 0; /* Undefined value. */
265 1.1.2.2 matt
266 1.1.2.2 matt /* Clean latched status if all requested bytes have been read. */
267 1.1.2.2 matt if (!timer->read_lsb && !timer->read_msb)
268 1.1.2.2 matt timer->counter_is_latched = false;
269 1.1.2.2 matt
270 1.1.2.2 matt return output;
271 1.1.2.2 matt }
272 1.1.2.2 matt
273 1.1.2.2 matt static void
274 1.1.2.2 matt x86emu_i8254_write_counter(struct x86emu_i8254 *sc,
275 1.1.2.2 matt struct x86emu_i8254_timer *timer, uint8_t val)
276 1.1.2.2 matt {
277 1.1.2.2 matt /* Nothing to write, undefined. */
278 1.1.2.2 matt if (!timer->write_lsb && !timer->write_msb)
279 1.1.2.2 matt return;
280 1.1.2.2 matt
281 1.1.2.2 matt /* Update requested bytes. */
282 1.1.2.2 matt if (timer->write_lsb) {
283 1.1.2.2 matt timer->new_counter &= ~0xff;
284 1.1.2.2 matt timer->new_counter |= val;
285 1.1.2.2 matt timer->write_lsb = false;
286 1.1.2.2 matt } else {
287 1.1.2.2 matt KASSERT(timer->write_msb);
288 1.1.2.2 matt timer->new_counter &= ~0xff00;
289 1.1.2.2 matt timer->new_counter |= val << 8;
290 1.1.2.2 matt timer->write_msb = false;
291 1.1.2.2 matt }
292 1.1.2.2 matt
293 1.1.2.2 matt /* If all requested bytes have been written, update counter. */
294 1.1.2.2 matt if (!timer->write_lsb && !timer->write_msb) {
295 1.1.2.2 matt timer->null_count = false;
296 1.1.2.2 matt timer->counter_is_latched = false;
297 1.1.2.2 matt timer->status_is_latched = false;
298 1.1.2.2 matt timer->active_is_bcd = timer->new_is_bcd;
299 1.1.2.2 matt timer->active_mode = timer->new_mode;
300 1.1.2.2 matt timer->start_tick = x86emu_i8254_gettick(sc) + 1;
301 1.1.2.2 matt if (timer->new_is_bcd)
302 1.1.2.2 matt timer->active_counter = bcd2bin(timer->new_counter);
303 1.1.2.2 matt }
304 1.1.2.2 matt }
305 1.1.2.2 matt
306 1.1.2.2 matt static uint8_t
307 1.1.2.2 matt x86emu_i8254_read_nmi(struct x86emu_i8254 *sc)
308 1.1.2.2 matt {
309 1.1.2.2 matt uint8_t val;
310 1.1.2.2 matt
311 1.1.2.2 matt val = (sc->timer[2].gate_high) ? 1 : 0;
312 1.1.2.2 matt if (x86emu_i8254_out(&sc->timer[2], x86emu_i8254_gettick(sc)))
313 1.1.2.2 matt val |= 0x20;
314 1.1.2.2 matt
315 1.1.2.2 matt return val;
316 1.1.2.2 matt }
317 1.1.2.2 matt
318 1.1.2.2 matt static void
319 1.1.2.2 matt x86emu_i8254_write_nmi(struct x86emu_i8254 *sc, uint8_t val)
320 1.1.2.2 matt {
321 1.1.2.2 matt bool old_gate;
322 1.1.2.2 matt
323 1.1.2.2 matt old_gate = sc->timer[2].gate_high;
324 1.1.2.2 matt sc->timer[2].gate_high = (val & 1) == 1;
325 1.1.2.2 matt if (!old_gate && sc->timer[2].gate_high)
326 1.1.2.2 matt sc->timer[2].start_tick = x86emu_i8254_gettick(sc) + 1;
327 1.1.2.2 matt }
328 1.1.2.2 matt
329 1.1.2.2 matt void
330 1.1.2.2 matt x86emu_i8254_init(struct x86emu_i8254 *sc, void (*gettime)(struct timespec *))
331 1.1.2.2 matt {
332 1.1.2.2 matt struct x86emu_i8254_timer *timer;
333 1.1.2.2 matt int i;
334 1.1.2.2 matt
335 1.1.2.2 matt sc->gettime = gettime;
336 1.1.2.2 matt (*sc->gettime)(&sc->base_time);
337 1.1.2.2 matt
338 1.1.2.2 matt for (i = 0; i < 3; ++i) {
339 1.1.2.2 matt timer = &sc->timer[i];
340 1.1.2.2 matt timer->gate_high = false;
341 1.1.2.2 matt timer->start_tick = 0;
342 1.1.2.2 matt timer->active_counter = 0;
343 1.1.2.2 matt timer->active_mode = 0;
344 1.1.2.2 matt timer->active_is_bcd = false;
345 1.1.2.2 matt timer->counter_is_latched = false;
346 1.1.2.2 matt timer->read_lsb = false;
347 1.1.2.2 matt timer->read_msb = false;
348 1.1.2.2 matt timer->status_is_latched = false;
349 1.1.2.2 matt timer->null_count = false;
350 1.1.2.2 matt }
351 1.1.2.2 matt }
352 1.1.2.2 matt
353 1.1.2.2 matt uint8_t
354 1.1.2.2 matt x86emu_i8254_inb(struct x86emu_i8254 *sc, uint16_t port)
355 1.1.2.2 matt {
356 1.1.2.2 matt KASSERT(x86emu_i8254_claim_port(sc, port));
357 1.1.2.2 matt if (port == 0x40)
358 1.1.2.2 matt return x86emu_i8254_read_counter(sc, &sc->timer[0]);
359 1.1.2.2 matt if (port == 0x41)
360 1.1.2.2 matt return x86emu_i8254_read_counter(sc, &sc->timer[1]);
361 1.1.2.2 matt if (port == 0x42)
362 1.1.2.2 matt return x86emu_i8254_read_counter(sc, &sc->timer[2]);
363 1.1.2.2 matt if (port == 0x43)
364 1.1.2.2 matt return 0xff; /* unsupported */
365 1.1.2.2 matt return x86emu_i8254_read_nmi(sc);
366 1.1.2.2 matt }
367 1.1.2.2 matt
368 1.1.2.2 matt void
369 1.1.2.2 matt x86emu_i8254_outb(struct x86emu_i8254 *sc, uint16_t port, uint8_t val)
370 1.1.2.2 matt {
371 1.1.2.2 matt KASSERT(x86emu_i8254_claim_port(sc, port));
372 1.1.2.2 matt if (port == 0x40)
373 1.1.2.2 matt x86emu_i8254_write_counter(sc, &sc->timer[0], val);
374 1.1.2.2 matt else if (port == 0x41)
375 1.1.2.2 matt x86emu_i8254_write_counter(sc, &sc->timer[1], val);
376 1.1.2.2 matt else if (port == 0x42)
377 1.1.2.2 matt x86emu_i8254_write_counter(sc, &sc->timer[2], val);
378 1.1.2.2 matt else if (port == 0x43)
379 1.1.2.2 matt x86emu_i8254_write_command(sc, val);
380 1.1.2.2 matt else
381 1.1.2.2 matt x86emu_i8254_write_nmi(sc, val);
382 1.1.2.2 matt }
383 1.1.2.2 matt
384 1.1.2.2 matt /* ARGSUSED */
385 1.1.2.2 matt bool
386 1.1.2.2 matt x86emu_i8254_claim_port(struct x86emu_i8254 *sc, uint16_t port)
387 1.1.2.2 matt {
388 1.1.2.2 matt /* i8254 registers */
389 1.1.2.2 matt if (port >= 0x40 && port < 0x44)
390 1.1.2.2 matt return true;
391 1.1.2.2 matt /* NMI register, used to control timer 2 and the output of it */
392 1.1.2.2 matt if (port == 0x61)
393 1.1.2.2 matt return true;
394 1.1.2.2 matt return false;
395 1.1.2.2 matt }
396