xref: /netbsd/common/lib/libx86emu/x86emu_i8254.c (revision 6550d01e)
1 /* $NetBSD: x86emu_i8254.c,v 1.1 2007/12/21 17:45:50 joerg 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 
41 static uint16_t
42 bcd2bin(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 
48 static uint16_t
49 bin2bcd(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  */
59 static uint64_t
60 x86emu_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. */
74 static uint16_t
75 x86emu_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 
109 static bool
110 x86emu_i8254_out(struct x86emu_i8254_timer *timer, uint64_t curtick)
111 {
112 	uint16_t maxtick;
113 
114 	/*
115 	 * TODO:
116 	 * Mode 0:
117 	 * After the write of the LSB and before the write of the MSB,
118 	 * this should return LOW.
119 	 */
120 
121 	/*
122 	 * If the timer was not started yet or is disabled,
123 	 * only Mode 0 is LOW
124 	 */
125 	if (timer->gate_high || timer->start_tick > curtick)
126 		return (timer->active_mode != 0);
127 
128 	/* Max tick based on BCD/binary mode */
129 	if (timer->active_is_bcd)
130 		maxtick = 9999;
131 	else
132 		maxtick = 0xffff;
133 
134 	curtick -= timer->start_tick;
135 
136 	/* Return LOW until counter is 0, afterwards HIGH until reload. */
137 	if (timer->active_mode == 0 || timer->active_mode == 1)
138 		return curtick >= timer->start_tick;
139 
140 	/* Return LOW until the counter is 0, raise to HIGH and go LOW again. */
141 	if (timer->active_mode == 5 || timer->active_mode == 7)
142 		return curtick != timer->start_tick;
143 
144 	/*
145 	 * Return LOW until the counter is 1, raise to HIGH and go LOW
146 	 * again. Afterwards reload the counter.
147 	 */
148 	if (timer->active_mode == 2 || timer->active_mode == 3) {
149 		curtick %= timer->active_counter;
150 		return curtick + 1 != timer->active_counter;
151 	}
152 
153 	/*
154 	 * If the initial counter is even, return HIGH for the first half
155 	 * and LOW for the second. If it is even, bias the first half.
156 	 */
157 	curtick %= timer->active_counter;
158 	return curtick < (timer->active_counter + 1) / 2;
159 }
160 
161 static void
162 x86emu_i8254_latch_status(struct x86emu_i8254_timer *timer, uint64_t curtick)
163 {
164 	if (timer->status_is_latched)
165 		return;
166 	timer->latched_status = timer->active_is_bcd ? 1 : 0;
167 	timer->latched_status |= timer->active_mode << 1;
168 	timer->latched_status |= timer->rw_status;
169 	timer->latched_status |= timer->null_count ? 0x40 : 0;
170 }
171 
172 static void
173 x86emu_i8254_latch_counter(struct x86emu_i8254_timer *timer, uint64_t curtick)
174 {
175 	if (!timer->counter_is_latched)
176 		return; /* Already latched. */
177 	timer->latched_counter = x86emu_i8254_counter(timer, curtick);
178 	timer->counter_is_latched = true;
179 }
180 
181 static void
182 x86emu_i8254_write_command(struct x86emu_i8254 *sc, uint8_t val)
183 {
184 	struct x86emu_i8254_timer *timer;
185 	int i;
186 
187 	if ((val >> 6) == 3) {
188 		/* Read Back Command */
189 		uint64_t curtick;
190 
191 		curtick = x86emu_i8254_gettick(sc);
192 		for (i = 0; i < 3; ++i) {
193 			timer = &sc->timer[i];
194 
195 			if ((val & (2 << i)) == 0)
196 				continue;
197 			if ((val & 0x10) != 0)
198 				x86emu_i8254_latch_status(timer, curtick);
199 			if ((val & 0x20) != 0)
200 				x86emu_i8254_latch_counter(timer, curtick);
201 		}
202 		return;
203 	}
204 
205 	timer = &sc->timer[val >> 6];
206 
207 	switch (val & 0x30) {
208 	case 0:
209 		x86emu_i8254_latch_counter(timer, x86emu_i8254_gettick(sc));
210 		return;
211 	case 1:
212 		timer->write_lsb = timer->read_lsb = true;
213 		timer->write_msb = timer->read_msb = false;
214 		break;
215 	case 2:
216 		timer->write_lsb = timer->read_lsb = false;
217 		timer->write_msb = timer->read_msb = true;
218 		break;
219 	case 3:
220 		timer->write_lsb = timer->read_lsb = true;
221 		timer->write_msb = timer->read_msb = true;
222 		break;
223 	}
224 	timer->rw_status = val & 0x30;
225 	timer->null_count = true;
226 	timer->new_mode = (val >> 1) & 0x7;
227 	timer->new_is_bcd = (val & 1) == 1;
228 }
229 
230 static uint8_t
231 x86emu_i8254_read_counter(struct x86emu_i8254 *sc,
232     struct x86emu_i8254_timer *timer)
233 {
234 	uint16_t val;
235 	uint8_t output;
236 
237 	/* If status was latched by Read Back Command, return it. */
238 	if (timer->status_is_latched) {
239 		timer->status_is_latched = false;
240 		return timer->latched_status;
241 	}
242 
243 	/*
244 	 * The value of the counter is either the latched value
245 	 * or the current counter.
246 	 */
247 	if (timer->counter_is_latched)
248 		val = timer->latched_counter;
249 	else
250 		val = x86emu_i8254_counter(&sc->timer[2],
251 		    x86emu_i8254_gettick(sc));
252 
253 	if (timer->active_is_bcd)
254 		val = bin2bcd(val);
255 
256 	/* Extract requested byte. */
257 	if (timer->read_lsb) {
258 		output = val & 0xff;
259 		timer->read_lsb = false;
260 	} else if (timer->read_msb) {
261 		output = val >> 8;
262 		timer->read_msb = false;
263 	} else
264 		output = 0; /* Undefined value. */
265 
266 	/* Clean latched status if all requested bytes have been read. */
267 	if (!timer->read_lsb && !timer->read_msb)
268 		timer->counter_is_latched = false;
269 
270 	return output;
271 }
272 
273 static void
274 x86emu_i8254_write_counter(struct x86emu_i8254 *sc,
275     struct x86emu_i8254_timer *timer, uint8_t val)
276 {
277 	/* Nothing to write, undefined. */
278 	if (!timer->write_lsb && !timer->write_msb)
279 		return;
280 
281 	/* Update requested bytes. */
282 	if (timer->write_lsb) {
283 		timer->new_counter &= ~0xff;
284 		timer->new_counter |= val;
285 		timer->write_lsb = false;
286 	} else {
287 		KASSERT(timer->write_msb);
288 		timer->new_counter &= ~0xff00;
289 		timer->new_counter |= val << 8;
290 		timer->write_msb = false;
291 	}
292 
293 	/* If all requested bytes have been written, update counter. */
294 	if (!timer->write_lsb && !timer->write_msb) {
295 		timer->null_count = false;
296 		timer->counter_is_latched = false;
297 		timer->status_is_latched = false;
298 		timer->active_is_bcd = timer->new_is_bcd;
299 		timer->active_mode = timer->new_mode;
300 		timer->start_tick = x86emu_i8254_gettick(sc) + 1;
301 		if (timer->new_is_bcd)
302 			timer->active_counter = bcd2bin(timer->new_counter);
303 	}
304 }
305 
306 static uint8_t
307 x86emu_i8254_read_nmi(struct x86emu_i8254 *sc)
308 {
309 	uint8_t val;
310 
311 	val = (sc->timer[2].gate_high) ? 1 : 0;
312 	if (x86emu_i8254_out(&sc->timer[2], x86emu_i8254_gettick(sc)))
313 		val |= 0x20;
314 
315 	return val;
316 }
317 
318 static void
319 x86emu_i8254_write_nmi(struct x86emu_i8254 *sc, uint8_t val)
320 {
321 	bool old_gate;
322 
323 	old_gate = sc->timer[2].gate_high;
324 	sc->timer[2].gate_high = (val & 1) == 1;
325 	if (!old_gate && sc->timer[2].gate_high)
326 		sc->timer[2].start_tick = x86emu_i8254_gettick(sc) + 1;
327 }
328 
329 void
330 x86emu_i8254_init(struct x86emu_i8254 *sc, void (*gettime)(struct timespec *))
331 {
332 	struct x86emu_i8254_timer *timer;
333 	int i;
334 
335 	sc->gettime = gettime;
336 	(*sc->gettime)(&sc->base_time);
337 
338 	for (i = 0; i < 3; ++i) {
339 		timer = &sc->timer[i];
340 		timer->gate_high = false;
341 		timer->start_tick = 0;
342 		timer->active_counter = 0;
343 		timer->active_mode = 0;
344 		timer->active_is_bcd = false;
345 		timer->counter_is_latched = false;
346 		timer->read_lsb = false;
347 		timer->read_msb = false;
348 		timer->status_is_latched = false;
349 		timer->null_count = false;
350 	}
351 }
352 
353 uint8_t
354 x86emu_i8254_inb(struct x86emu_i8254 *sc, uint16_t port)
355 {
356 	KASSERT(x86emu_i8254_claim_port(sc, port));
357 	if (port == 0x40)
358 		return x86emu_i8254_read_counter(sc, &sc->timer[0]);
359 	if (port == 0x41)
360 		return x86emu_i8254_read_counter(sc, &sc->timer[1]);
361 	if (port == 0x42)
362 		return x86emu_i8254_read_counter(sc, &sc->timer[2]);
363 	if (port == 0x43)
364 		return 0xff; /* unsupported */
365 	return	x86emu_i8254_read_nmi(sc);
366 }
367 
368 void
369 x86emu_i8254_outb(struct x86emu_i8254 *sc, uint16_t port, uint8_t val)
370 {
371 	KASSERT(x86emu_i8254_claim_port(sc, port));
372 	if (port == 0x40)
373 		x86emu_i8254_write_counter(sc, &sc->timer[0], val);
374 	else if (port == 0x41)
375 		x86emu_i8254_write_counter(sc, &sc->timer[1], val);
376 	else if (port == 0x42)
377 		x86emu_i8254_write_counter(sc, &sc->timer[2], val);
378 	else if (port == 0x43)
379 		x86emu_i8254_write_command(sc, val);
380 	else
381 		x86emu_i8254_write_nmi(sc, val);
382 }
383 
384 /* ARGSUSED */
385 bool
386 x86emu_i8254_claim_port(struct x86emu_i8254 *sc, uint16_t port)
387 {
388 	/* i8254 registers */
389 	if (port >= 0x40 && port < 0x44)
390 		return true;
391 	/* NMI register, used to control timer 2 and the output of it */
392 	if (port == 0x61)
393 		return true;
394 	return false;
395 }
396