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 |
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 | /* |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | |