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