Home | History | Annotate | Line # | Download | only in libx86emu
x86emu_i8254.c revision 1.1.2.2
      1 /* $NetBSD: x86emu_i8254.c,v 1.1.2.2 2008/01/09 01:21:38 matt Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2007 Joerg Sonnenberger <joerg (at) 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