1 1.2 christos /* $NetBSD: x86emu_i8254.c,v 1.2 2013/10/20 21:16:54 christos Exp $ */ 2 1.1 joerg 3 1.1 joerg /*- 4 1.1 joerg * Copyright (c) 2007 Joerg Sonnenberger <joerg (at) NetBSD.org>. 5 1.1 joerg * All rights reserved. 6 1.1 joerg * 7 1.1 joerg * Redistribution and use in source and binary forms, with or without 8 1.1 joerg * modification, are permitted provided that the following conditions 9 1.1 joerg * are met: 10 1.1 joerg * 11 1.1 joerg * 1. Redistributions of source code must retain the above copyright 12 1.1 joerg * notice, this list of conditions and the following disclaimer. 13 1.1 joerg * 2. Redistributions in binary form must reproduce the above copyright 14 1.1 joerg * notice, this list of conditions and the following disclaimer in 15 1.1 joerg * the documentation and/or other materials provided with the 16 1.1 joerg * distribution. 17 1.1 joerg * 18 1.1 joerg * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 1.1 joerg * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 1.1 joerg * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 1.1 joerg * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 1.1 joerg * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 1.1 joerg * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 1.1 joerg * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 1.1 joerg * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 1.1 joerg * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 1.1 joerg * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 28 1.1 joerg * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 1.1 joerg * SUCH DAMAGE. 30 1.1 joerg */ 31 1.1 joerg 32 1.1 joerg #include <x86emu/x86emu_i8254.h> 33 1.1 joerg 34 1.1 joerg #ifndef _KERNEL 35 1.1 joerg #include <assert.h> 36 1.1 joerg #define KASSERT(x) assert(x) 37 1.1 joerg #endif 38 1.1 joerg 39 1.1 joerg #define I8254_FREQ 1193182 /* Hz */ 40 1.1 joerg 41 1.1 joerg static uint16_t 42 1.1 joerg bcd2bin(uint16_t bcd_val) 43 1.1 joerg { 44 1.1 joerg return bcd_val % 0x10 + (bcd_val / 0x10 % 0x10 * 10) + 45 1.1 joerg (bcd_val / 0x100 % 0x10 * 100) + (bcd_val / 0x1000 % 0x10 * 1000); 46 1.1 joerg } 47 1.1 joerg 48 1.1 joerg static uint16_t 49 1.1 joerg bin2bcd(uint16_t bin_val) 50 1.1 joerg { 51 1.1 joerg return (bin_val % 10) + (bin_val / 10 % 10 * 0x10) + 52 1.1 joerg (bin_val / 100 % 10 * 0x100) + (bin_val / 1000 % 10 * 0x1000); 53 1.1 joerg } 54 1.1 joerg 55 1.1 joerg /* 56 1.1 joerg * Compute tick of the virtual timer based on start time and 57 1.1 joerg * current time. 58 1.1 joerg */ 59 1.1 joerg static uint64_t 60 1.1 joerg x86emu_i8254_gettick(struct x86emu_i8254 *sc) 61 1.1 joerg { 62 1.1 joerg struct timespec curtime; 63 1.1 joerg uint64_t tick; 64 1.1 joerg 65 1.1 joerg (*sc->gettime)(&curtime); 66 1.1 joerg 67 1.1 joerg tick = (curtime.tv_sec - sc->base_time.tv_sec) * I8254_FREQ; 68 1.1 joerg tick += (uint64_t)(curtime.tv_nsec - sc->base_time.tv_nsec) * I8254_FREQ / 1000000000; 69 1.1 joerg 70 1.1 joerg return tick; 71 1.1 joerg } 72 1.1 joerg 73 1.1 joerg /* Compute current counter value. */ 74 1.1 joerg static uint16_t 75 1.1 joerg x86emu_i8254_counter(struct x86emu_i8254_timer *timer, uint64_t curtick) 76 1.1 joerg { 77 1.1 joerg uint16_t maxtick; 78 1.1 joerg 79 1.1 joerg /* Initial value if timer is disabled or not yet started */ 80 1.1 joerg if (timer->gate_high || timer->start_tick > curtick) 81 1.1 joerg return timer->active_counter; 82 1.1 joerg 83 1.1 joerg /* Compute maximum value based on BCD/binary mode */ 84 1.1 joerg if (timer->active_is_bcd) 85 1.1 joerg maxtick = 9999; 86 1.1 joerg else 87 1.1 joerg maxtick = 0xffff; 88 1.1 joerg 89 1.1 joerg curtick -= timer->start_tick; 90 1.1 joerg 91 1.1 joerg /* Check if first run over the time counter is over. */ 92 1.1 joerg if (curtick <= timer->active_counter) 93 1.1 joerg return timer->active_counter - curtick; 94 1.1 joerg /* Now curtick > 0 as both values above are unsigned. */ 95 1.1 joerg 96 1.1 joerg /* Special case of active_counter == maxtick + 1 */ 97 1.1 joerg if (timer->active_counter == 0 && curtick - 1 <= maxtick) 98 1.1 joerg return maxtick + 1 - curtick; 99 1.1 joerg 100 1.1 joerg /* For periodic timers, compute current periode. */ 101 1.1 joerg if (timer->active_mode & 2) 102 1.1 joerg return timer->active_counter - curtick % timer->active_counter; 103 1.1 joerg 104 1.1 joerg /* For one-shot timers, compute overflow. */ 105 1.1 joerg curtick -= maxtick + 1; 106 1.1 joerg return maxtick - curtick % maxtick + 1; 107 1.1 joerg } 108 1.1 joerg 109 1.1 joerg static bool 110 1.1 joerg x86emu_i8254_out(struct x86emu_i8254_timer *timer, uint64_t curtick) 111 1.1 joerg { 112 1.1 joerg /* 113 1.1 joerg * TODO: 114 1.1 joerg * Mode 0: 115 1.1 joerg * After the write of the LSB and before the write of the MSB, 116 1.1 joerg * this should return LOW. 117 1.1 joerg */ 118 1.1 joerg 119 1.1 joerg /* 120 1.1 joerg * If the timer was not started yet or is disabled, 121 1.1 joerg * only Mode 0 is LOW 122 1.1 joerg */ 123 1.1 joerg if (timer->gate_high || timer->start_tick > curtick) 124 1.1 joerg return (timer->active_mode != 0); 125 1.1 joerg 126 1.1 joerg curtick -= timer->start_tick; 127 1.1 joerg 128 1.1 joerg /* Return LOW until counter is 0, afterwards HIGH until reload. */ 129 1.1 joerg if (timer->active_mode == 0 || timer->active_mode == 1) 130 1.1 joerg return curtick >= timer->start_tick; 131 1.1 joerg 132 1.1 joerg /* Return LOW until the counter is 0, raise to HIGH and go LOW again. */ 133 1.1 joerg if (timer->active_mode == 5 || timer->active_mode == 7) 134 1.1 joerg return curtick != timer->start_tick; 135 1.1 joerg 136 1.1 joerg /* 137 1.1 joerg * Return LOW until the counter is 1, raise to HIGH and go LOW 138 1.1 joerg * again. Afterwards reload the counter. 139 1.1 joerg */ 140 1.1 joerg if (timer->active_mode == 2 || timer->active_mode == 3) { 141 1.1 joerg curtick %= timer->active_counter; 142 1.1 joerg return curtick + 1 != timer->active_counter; 143 1.1 joerg } 144 1.1 joerg 145 1.1 joerg /* 146 1.1 joerg * If the initial counter is even, return HIGH for the first half 147 1.1 joerg * and LOW for the second. If it is even, bias the first half. 148 1.1 joerg */ 149 1.1 joerg curtick %= timer->active_counter; 150 1.1 joerg return curtick < (timer->active_counter + 1) / 2; 151 1.1 joerg } 152 1.1 joerg 153 1.1 joerg static void 154 1.1 joerg x86emu_i8254_latch_status(struct x86emu_i8254_timer *timer, uint64_t curtick) 155 1.1 joerg { 156 1.1 joerg if (timer->status_is_latched) 157 1.1 joerg return; 158 1.1 joerg timer->latched_status = timer->active_is_bcd ? 1 : 0; 159 1.1 joerg timer->latched_status |= timer->active_mode << 1; 160 1.1 joerg timer->latched_status |= timer->rw_status; 161 1.1 joerg timer->latched_status |= timer->null_count ? 0x40 : 0; 162 1.1 joerg } 163 1.1 joerg 164 1.1 joerg static void 165 1.1 joerg x86emu_i8254_latch_counter(struct x86emu_i8254_timer *timer, uint64_t curtick) 166 1.1 joerg { 167 1.1 joerg if (!timer->counter_is_latched) 168 1.1 joerg return; /* Already latched. */ 169 1.1 joerg timer->latched_counter = x86emu_i8254_counter(timer, curtick); 170 1.1 joerg timer->counter_is_latched = true; 171 1.1 joerg } 172 1.1 joerg 173 1.1 joerg static void 174 1.1 joerg x86emu_i8254_write_command(struct x86emu_i8254 *sc, uint8_t val) 175 1.1 joerg { 176 1.1 joerg struct x86emu_i8254_timer *timer; 177 1.1 joerg int i; 178 1.1 joerg 179 1.1 joerg if ((val >> 6) == 3) { 180 1.1 joerg /* Read Back Command */ 181 1.1 joerg uint64_t curtick; 182 1.1 joerg 183 1.1 joerg curtick = x86emu_i8254_gettick(sc); 184 1.1 joerg for (i = 0; i < 3; ++i) { 185 1.1 joerg timer = &sc->timer[i]; 186 1.1 joerg 187 1.1 joerg if ((val & (2 << i)) == 0) 188 1.1 joerg continue; 189 1.1 joerg if ((val & 0x10) != 0) 190 1.1 joerg x86emu_i8254_latch_status(timer, curtick); 191 1.1 joerg if ((val & 0x20) != 0) 192 1.1 joerg x86emu_i8254_latch_counter(timer, curtick); 193 1.1 joerg } 194 1.1 joerg return; 195 1.1 joerg } 196 1.1 joerg 197 1.1 joerg timer = &sc->timer[val >> 6]; 198 1.1 joerg 199 1.1 joerg switch (val & 0x30) { 200 1.1 joerg case 0: 201 1.1 joerg x86emu_i8254_latch_counter(timer, x86emu_i8254_gettick(sc)); 202 1.1 joerg return; 203 1.1 joerg case 1: 204 1.1 joerg timer->write_lsb = timer->read_lsb = true; 205 1.1 joerg timer->write_msb = timer->read_msb = false; 206 1.1 joerg break; 207 1.1 joerg case 2: 208 1.1 joerg timer->write_lsb = timer->read_lsb = false; 209 1.1 joerg timer->write_msb = timer->read_msb = true; 210 1.1 joerg break; 211 1.1 joerg case 3: 212 1.1 joerg timer->write_lsb = timer->read_lsb = true; 213 1.1 joerg timer->write_msb = timer->read_msb = true; 214 1.1 joerg break; 215 1.1 joerg } 216 1.1 joerg timer->rw_status = val & 0x30; 217 1.1 joerg timer->null_count = true; 218 1.1 joerg timer->new_mode = (val >> 1) & 0x7; 219 1.1 joerg timer->new_is_bcd = (val & 1) == 1; 220 1.1 joerg } 221 1.1 joerg 222 1.1 joerg static uint8_t 223 1.1 joerg x86emu_i8254_read_counter(struct x86emu_i8254 *sc, 224 1.1 joerg struct x86emu_i8254_timer *timer) 225 1.1 joerg { 226 1.1 joerg uint16_t val; 227 1.1 joerg uint8_t output; 228 1.1 joerg 229 1.1 joerg /* If status was latched by Read Back Command, return it. */ 230 1.1 joerg if (timer->status_is_latched) { 231 1.1 joerg timer->status_is_latched = false; 232 1.1 joerg return timer->latched_status; 233 1.1 joerg } 234 1.1 joerg 235 1.1 joerg /* 236 1.1 joerg * The value of the counter is either the latched value 237 1.1 joerg * or the current counter. 238 1.1 joerg */ 239 1.1 joerg if (timer->counter_is_latched) 240 1.1 joerg val = timer->latched_counter; 241 1.1 joerg else 242 1.1 joerg val = x86emu_i8254_counter(&sc->timer[2], 243 1.1 joerg x86emu_i8254_gettick(sc)); 244 1.1 joerg 245 1.1 joerg if (timer->active_is_bcd) 246 1.1 joerg val = bin2bcd(val); 247 1.1 joerg 248 1.1 joerg /* Extract requested byte. */ 249 1.1 joerg if (timer->read_lsb) { 250 1.1 joerg output = val & 0xff; 251 1.1 joerg timer->read_lsb = false; 252 1.1 joerg } else if (timer->read_msb) { 253 1.1 joerg output = val >> 8; 254 1.1 joerg timer->read_msb = false; 255 1.1 joerg } else 256 1.1 joerg output = 0; /* Undefined value. */ 257 1.1 joerg 258 1.1 joerg /* Clean latched status if all requested bytes have been read. */ 259 1.1 joerg if (!timer->read_lsb && !timer->read_msb) 260 1.1 joerg timer->counter_is_latched = false; 261 1.1 joerg 262 1.1 joerg return output; 263 1.1 joerg } 264 1.1 joerg 265 1.1 joerg static void 266 1.1 joerg x86emu_i8254_write_counter(struct x86emu_i8254 *sc, 267 1.1 joerg struct x86emu_i8254_timer *timer, uint8_t val) 268 1.1 joerg { 269 1.1 joerg /* Nothing to write, undefined. */ 270 1.1 joerg if (!timer->write_lsb && !timer->write_msb) 271 1.1 joerg return; 272 1.1 joerg 273 1.1 joerg /* Update requested bytes. */ 274 1.1 joerg if (timer->write_lsb) { 275 1.1 joerg timer->new_counter &= ~0xff; 276 1.1 joerg timer->new_counter |= val; 277 1.1 joerg timer->write_lsb = false; 278 1.1 joerg } else { 279 1.1 joerg KASSERT(timer->write_msb); 280 1.1 joerg timer->new_counter &= ~0xff00; 281 1.1 joerg timer->new_counter |= val << 8; 282 1.1 joerg timer->write_msb = false; 283 1.1 joerg } 284 1.1 joerg 285 1.1 joerg /* If all requested bytes have been written, update counter. */ 286 1.1 joerg if (!timer->write_lsb && !timer->write_msb) { 287 1.1 joerg timer->null_count = false; 288 1.1 joerg timer->counter_is_latched = false; 289 1.1 joerg timer->status_is_latched = false; 290 1.1 joerg timer->active_is_bcd = timer->new_is_bcd; 291 1.1 joerg timer->active_mode = timer->new_mode; 292 1.1 joerg timer->start_tick = x86emu_i8254_gettick(sc) + 1; 293 1.1 joerg if (timer->new_is_bcd) 294 1.1 joerg timer->active_counter = bcd2bin(timer->new_counter); 295 1.1 joerg } 296 1.1 joerg } 297 1.1 joerg 298 1.1 joerg static uint8_t 299 1.1 joerg x86emu_i8254_read_nmi(struct x86emu_i8254 *sc) 300 1.1 joerg { 301 1.1 joerg uint8_t val; 302 1.1 joerg 303 1.1 joerg val = (sc->timer[2].gate_high) ? 1 : 0; 304 1.1 joerg if (x86emu_i8254_out(&sc->timer[2], x86emu_i8254_gettick(sc))) 305 1.1 joerg val |= 0x20; 306 1.1 joerg 307 1.1 joerg return val; 308 1.1 joerg } 309 1.1 joerg 310 1.1 joerg static void 311 1.1 joerg x86emu_i8254_write_nmi(struct x86emu_i8254 *sc, uint8_t val) 312 1.1 joerg { 313 1.1 joerg bool old_gate; 314 1.1 joerg 315 1.1 joerg old_gate = sc->timer[2].gate_high; 316 1.1 joerg sc->timer[2].gate_high = (val & 1) == 1; 317 1.1 joerg if (!old_gate && sc->timer[2].gate_high) 318 1.1 joerg sc->timer[2].start_tick = x86emu_i8254_gettick(sc) + 1; 319 1.1 joerg } 320 1.1 joerg 321 1.1 joerg void 322 1.1 joerg x86emu_i8254_init(struct x86emu_i8254 *sc, void (*gettime)(struct timespec *)) 323 1.1 joerg { 324 1.1 joerg struct x86emu_i8254_timer *timer; 325 1.1 joerg int i; 326 1.1 joerg 327 1.1 joerg sc->gettime = gettime; 328 1.1 joerg (*sc->gettime)(&sc->base_time); 329 1.1 joerg 330 1.1 joerg for (i = 0; i < 3; ++i) { 331 1.1 joerg timer = &sc->timer[i]; 332 1.1 joerg timer->gate_high = false; 333 1.1 joerg timer->start_tick = 0; 334 1.1 joerg timer->active_counter = 0; 335 1.1 joerg timer->active_mode = 0; 336 1.1 joerg timer->active_is_bcd = false; 337 1.1 joerg timer->counter_is_latched = false; 338 1.1 joerg timer->read_lsb = false; 339 1.1 joerg timer->read_msb = false; 340 1.1 joerg timer->status_is_latched = false; 341 1.1 joerg timer->null_count = false; 342 1.1 joerg } 343 1.1 joerg } 344 1.1 joerg 345 1.1 joerg uint8_t 346 1.1 joerg x86emu_i8254_inb(struct x86emu_i8254 *sc, uint16_t port) 347 1.1 joerg { 348 1.1 joerg KASSERT(x86emu_i8254_claim_port(sc, port)); 349 1.1 joerg if (port == 0x40) 350 1.1 joerg return x86emu_i8254_read_counter(sc, &sc->timer[0]); 351 1.1 joerg if (port == 0x41) 352 1.1 joerg return x86emu_i8254_read_counter(sc, &sc->timer[1]); 353 1.1 joerg if (port == 0x42) 354 1.1 joerg return x86emu_i8254_read_counter(sc, &sc->timer[2]); 355 1.1 joerg if (port == 0x43) 356 1.1 joerg return 0xff; /* unsupported */ 357 1.1 joerg return x86emu_i8254_read_nmi(sc); 358 1.1 joerg } 359 1.1 joerg 360 1.1 joerg void 361 1.1 joerg x86emu_i8254_outb(struct x86emu_i8254 *sc, uint16_t port, uint8_t val) 362 1.1 joerg { 363 1.1 joerg KASSERT(x86emu_i8254_claim_port(sc, port)); 364 1.1 joerg if (port == 0x40) 365 1.1 joerg x86emu_i8254_write_counter(sc, &sc->timer[0], val); 366 1.1 joerg else if (port == 0x41) 367 1.1 joerg x86emu_i8254_write_counter(sc, &sc->timer[1], val); 368 1.1 joerg else if (port == 0x42) 369 1.1 joerg x86emu_i8254_write_counter(sc, &sc->timer[2], val); 370 1.1 joerg else if (port == 0x43) 371 1.1 joerg x86emu_i8254_write_command(sc, val); 372 1.1 joerg else 373 1.1 joerg x86emu_i8254_write_nmi(sc, val); 374 1.1 joerg } 375 1.1 joerg 376 1.1 joerg /* ARGSUSED */ 377 1.1 joerg bool 378 1.1 joerg x86emu_i8254_claim_port(struct x86emu_i8254 *sc, uint16_t port) 379 1.1 joerg { 380 1.1 joerg /* i8254 registers */ 381 1.1 joerg if (port >= 0x40 && port < 0x44) 382 1.1 joerg return true; 383 1.1 joerg /* NMI register, used to control timer 2 and the output of it */ 384 1.1 joerg if (port == 0x61) 385 1.1 joerg return true; 386 1.1 joerg return false; 387 1.1 joerg } 388