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