Home | History | Annotate | Line # | Download | only in libx86emu
      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 (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 	/*
    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