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