Home | History | Annotate | Line # | Download | only in libx86emu
x86emu_i8254.c revision 1.2.26.2
      1  1.2.26.1  martin /* $NetBSD: x86emu_i8254.c,v 1.2.26.2 2020/04/21 19:37:51 martin 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