xref: /netbsd-src/common/lib/libx86emu/x86emu_i8254.c (revision 7c7c6fd12562229e57767946e34127147edef718)
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 
41 static uint16_t
bcd2bin(uint16_t bcd_val)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
bin2bcd(uint16_t bin_val)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
x86emu_i8254_gettick(struct x86emu_i8254 * sc)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
x86emu_i8254_counter(struct x86emu_i8254_timer * timer,uint64_t curtick)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
x86emu_i8254_out(struct x86emu_i8254_timer * timer,uint64_t curtick)110 x86emu_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 
153 static void
x86emu_i8254_latch_status(struct x86emu_i8254_timer * timer,uint64_t curtick)154 x86emu_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 
164 static void
x86emu_i8254_latch_counter(struct x86emu_i8254_timer * timer,uint64_t curtick)165 x86emu_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 
173 static void
x86emu_i8254_write_command(struct x86emu_i8254 * sc,uint8_t val)174 x86emu_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 
222 static uint8_t
x86emu_i8254_read_counter(struct x86emu_i8254 * sc,struct x86emu_i8254_timer * timer)223 x86emu_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 
265 static void
x86emu_i8254_write_counter(struct x86emu_i8254 * sc,struct x86emu_i8254_timer * timer,uint8_t val)266 x86emu_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 
298 static uint8_t
x86emu_i8254_read_nmi(struct x86emu_i8254 * sc)299 x86emu_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 
310 static void
x86emu_i8254_write_nmi(struct x86emu_i8254 * sc,uint8_t val)311 x86emu_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 
321 void
x86emu_i8254_init(struct x86emu_i8254 * sc,void (* gettime)(struct timespec *))322 x86emu_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 
345 uint8_t
x86emu_i8254_inb(struct x86emu_i8254 * sc,uint16_t port)346 x86emu_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 
360 void
x86emu_i8254_outb(struct x86emu_i8254 * sc,uint16_t port,uint8_t val)361 x86emu_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 */
377 bool
x86emu_i8254_claim_port(struct x86emu_i8254 * sc,uint16_t port)378 x86emu_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