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