Home | History | Annotate | Line # | Download | only in libx86emu
x86emu_i8254.c revision 1.1
      1  1.1  joerg /* $NetBSD: x86emu_i8254.c,v 1.1 2007/12/21 17:45:50 joerg 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 	uint16_t maxtick;
    113  1.1  joerg 
    114  1.1  joerg 	/*
    115  1.1  joerg 	 * TODO:
    116  1.1  joerg 	 * Mode 0:
    117  1.1  joerg 	 * After the write of the LSB and before the write of the MSB,
    118  1.1  joerg 	 * this should return LOW.
    119  1.1  joerg 	 */
    120  1.1  joerg 
    121  1.1  joerg 	/*
    122  1.1  joerg 	 * If the timer was not started yet or is disabled,
    123  1.1  joerg 	 * only Mode 0 is LOW
    124  1.1  joerg 	 */
    125  1.1  joerg 	if (timer->gate_high || timer->start_tick > curtick)
    126  1.1  joerg 		return (timer->active_mode != 0);
    127  1.1  joerg 
    128  1.1  joerg 	/* Max tick based on BCD/binary mode */
    129  1.1  joerg 	if (timer->active_is_bcd)
    130  1.1  joerg 		maxtick = 9999;
    131  1.1  joerg 	else
    132  1.1  joerg 		maxtick = 0xffff;
    133  1.1  joerg 
    134  1.1  joerg 	curtick -= timer->start_tick;
    135  1.1  joerg 
    136  1.1  joerg 	/* Return LOW until counter is 0, afterwards HIGH until reload. */
    137  1.1  joerg 	if (timer->active_mode == 0 || timer->active_mode == 1)
    138  1.1  joerg 		return curtick >= timer->start_tick;
    139  1.1  joerg 
    140  1.1  joerg 	/* Return LOW until the counter is 0, raise to HIGH and go LOW again. */
    141  1.1  joerg 	if (timer->active_mode == 5 || timer->active_mode == 7)
    142  1.1  joerg 		return curtick != timer->start_tick;
    143  1.1  joerg 
    144  1.1  joerg 	/*
    145  1.1  joerg 	 * Return LOW until the counter is 1, raise to HIGH and go LOW
    146  1.1  joerg 	 * again. Afterwards reload the counter.
    147  1.1  joerg 	 */
    148  1.1  joerg 	if (timer->active_mode == 2 || timer->active_mode == 3) {
    149  1.1  joerg 		curtick %= timer->active_counter;
    150  1.1  joerg 		return curtick + 1 != timer->active_counter;
    151  1.1  joerg 	}
    152  1.1  joerg 
    153  1.1  joerg 	/*
    154  1.1  joerg 	 * If the initial counter is even, return HIGH for the first half
    155  1.1  joerg 	 * and LOW for the second. If it is even, bias the first half.
    156  1.1  joerg 	 */
    157  1.1  joerg 	curtick %= timer->active_counter;
    158  1.1  joerg 	return curtick < (timer->active_counter + 1) / 2;
    159  1.1  joerg }
    160  1.1  joerg 
    161  1.1  joerg static void
    162  1.1  joerg x86emu_i8254_latch_status(struct x86emu_i8254_timer *timer, uint64_t curtick)
    163  1.1  joerg {
    164  1.1  joerg 	if (timer->status_is_latched)
    165  1.1  joerg 		return;
    166  1.1  joerg 	timer->latched_status = timer->active_is_bcd ? 1 : 0;
    167  1.1  joerg 	timer->latched_status |= timer->active_mode << 1;
    168  1.1  joerg 	timer->latched_status |= timer->rw_status;
    169  1.1  joerg 	timer->latched_status |= timer->null_count ? 0x40 : 0;
    170  1.1  joerg }
    171  1.1  joerg 
    172  1.1  joerg static void
    173  1.1  joerg x86emu_i8254_latch_counter(struct x86emu_i8254_timer *timer, uint64_t curtick)
    174  1.1  joerg {
    175  1.1  joerg 	if (!timer->counter_is_latched)
    176  1.1  joerg 		return; /* Already latched. */
    177  1.1  joerg 	timer->latched_counter = x86emu_i8254_counter(timer, curtick);
    178  1.1  joerg 	timer->counter_is_latched = true;
    179  1.1  joerg }
    180  1.1  joerg 
    181  1.1  joerg static void
    182  1.1  joerg x86emu_i8254_write_command(struct x86emu_i8254 *sc, uint8_t val)
    183  1.1  joerg {
    184  1.1  joerg 	struct x86emu_i8254_timer *timer;
    185  1.1  joerg 	int i;
    186  1.1  joerg 
    187  1.1  joerg 	if ((val >> 6) == 3) {
    188  1.1  joerg 		/* Read Back Command */
    189  1.1  joerg 		uint64_t curtick;
    190  1.1  joerg 
    191  1.1  joerg 		curtick = x86emu_i8254_gettick(sc);
    192  1.1  joerg 		for (i = 0; i < 3; ++i) {
    193  1.1  joerg 			timer = &sc->timer[i];
    194  1.1  joerg 
    195  1.1  joerg 			if ((val & (2 << i)) == 0)
    196  1.1  joerg 				continue;
    197  1.1  joerg 			if ((val & 0x10) != 0)
    198  1.1  joerg 				x86emu_i8254_latch_status(timer, curtick);
    199  1.1  joerg 			if ((val & 0x20) != 0)
    200  1.1  joerg 				x86emu_i8254_latch_counter(timer, curtick);
    201  1.1  joerg 		}
    202  1.1  joerg 		return;
    203  1.1  joerg 	}
    204  1.1  joerg 
    205  1.1  joerg 	timer = &sc->timer[val >> 6];
    206  1.1  joerg 
    207  1.1  joerg 	switch (val & 0x30) {
    208  1.1  joerg 	case 0:
    209  1.1  joerg 		x86emu_i8254_latch_counter(timer, x86emu_i8254_gettick(sc));
    210  1.1  joerg 		return;
    211  1.1  joerg 	case 1:
    212  1.1  joerg 		timer->write_lsb = timer->read_lsb = true;
    213  1.1  joerg 		timer->write_msb = timer->read_msb = false;
    214  1.1  joerg 		break;
    215  1.1  joerg 	case 2:
    216  1.1  joerg 		timer->write_lsb = timer->read_lsb = false;
    217  1.1  joerg 		timer->write_msb = timer->read_msb = true;
    218  1.1  joerg 		break;
    219  1.1  joerg 	case 3:
    220  1.1  joerg 		timer->write_lsb = timer->read_lsb = true;
    221  1.1  joerg 		timer->write_msb = timer->read_msb = true;
    222  1.1  joerg 		break;
    223  1.1  joerg 	}
    224  1.1  joerg 	timer->rw_status = val & 0x30;
    225  1.1  joerg 	timer->null_count = true;
    226  1.1  joerg 	timer->new_mode = (val >> 1) & 0x7;
    227  1.1  joerg 	timer->new_is_bcd = (val & 1) == 1;
    228  1.1  joerg }
    229  1.1  joerg 
    230  1.1  joerg static uint8_t
    231  1.1  joerg x86emu_i8254_read_counter(struct x86emu_i8254 *sc,
    232  1.1  joerg     struct x86emu_i8254_timer *timer)
    233  1.1  joerg {
    234  1.1  joerg 	uint16_t val;
    235  1.1  joerg 	uint8_t output;
    236  1.1  joerg 
    237  1.1  joerg 	/* If status was latched by Read Back Command, return it. */
    238  1.1  joerg 	if (timer->status_is_latched) {
    239  1.1  joerg 		timer->status_is_latched = false;
    240  1.1  joerg 		return timer->latched_status;
    241  1.1  joerg 	}
    242  1.1  joerg 
    243  1.1  joerg 	/*
    244  1.1  joerg 	 * The value of the counter is either the latched value
    245  1.1  joerg 	 * or the current counter.
    246  1.1  joerg 	 */
    247  1.1  joerg 	if (timer->counter_is_latched)
    248  1.1  joerg 		val = timer->latched_counter;
    249  1.1  joerg 	else
    250  1.1  joerg 		val = x86emu_i8254_counter(&sc->timer[2],
    251  1.1  joerg 		    x86emu_i8254_gettick(sc));
    252  1.1  joerg 
    253  1.1  joerg 	if (timer->active_is_bcd)
    254  1.1  joerg 		val = bin2bcd(val);
    255  1.1  joerg 
    256  1.1  joerg 	/* Extract requested byte. */
    257  1.1  joerg 	if (timer->read_lsb) {
    258  1.1  joerg 		output = val & 0xff;
    259  1.1  joerg 		timer->read_lsb = false;
    260  1.1  joerg 	} else if (timer->read_msb) {
    261  1.1  joerg 		output = val >> 8;
    262  1.1  joerg 		timer->read_msb = false;
    263  1.1  joerg 	} else
    264  1.1  joerg 		output = 0; /* Undefined value. */
    265  1.1  joerg 
    266  1.1  joerg 	/* Clean latched status if all requested bytes have been read. */
    267  1.1  joerg 	if (!timer->read_lsb && !timer->read_msb)
    268  1.1  joerg 		timer->counter_is_latched = false;
    269  1.1  joerg 
    270  1.1  joerg 	return output;
    271  1.1  joerg }
    272  1.1  joerg 
    273  1.1  joerg static void
    274  1.1  joerg x86emu_i8254_write_counter(struct x86emu_i8254 *sc,
    275  1.1  joerg     struct x86emu_i8254_timer *timer, uint8_t val)
    276  1.1  joerg {
    277  1.1  joerg 	/* Nothing to write, undefined. */
    278  1.1  joerg 	if (!timer->write_lsb && !timer->write_msb)
    279  1.1  joerg 		return;
    280  1.1  joerg 
    281  1.1  joerg 	/* Update requested bytes. */
    282  1.1  joerg 	if (timer->write_lsb) {
    283  1.1  joerg 		timer->new_counter &= ~0xff;
    284  1.1  joerg 		timer->new_counter |= val;
    285  1.1  joerg 		timer->write_lsb = false;
    286  1.1  joerg 	} else {
    287  1.1  joerg 		KASSERT(timer->write_msb);
    288  1.1  joerg 		timer->new_counter &= ~0xff00;
    289  1.1  joerg 		timer->new_counter |= val << 8;
    290  1.1  joerg 		timer->write_msb = false;
    291  1.1  joerg 	}
    292  1.1  joerg 
    293  1.1  joerg 	/* If all requested bytes have been written, update counter. */
    294  1.1  joerg 	if (!timer->write_lsb && !timer->write_msb) {
    295  1.1  joerg 		timer->null_count = false;
    296  1.1  joerg 		timer->counter_is_latched = false;
    297  1.1  joerg 		timer->status_is_latched = false;
    298  1.1  joerg 		timer->active_is_bcd = timer->new_is_bcd;
    299  1.1  joerg 		timer->active_mode = timer->new_mode;
    300  1.1  joerg 		timer->start_tick = x86emu_i8254_gettick(sc) + 1;
    301  1.1  joerg 		if (timer->new_is_bcd)
    302  1.1  joerg 			timer->active_counter = bcd2bin(timer->new_counter);
    303  1.1  joerg 	}
    304  1.1  joerg }
    305  1.1  joerg 
    306  1.1  joerg static uint8_t
    307  1.1  joerg x86emu_i8254_read_nmi(struct x86emu_i8254 *sc)
    308  1.1  joerg {
    309  1.1  joerg 	uint8_t val;
    310  1.1  joerg 
    311  1.1  joerg 	val = (sc->timer[2].gate_high) ? 1 : 0;
    312  1.1  joerg 	if (x86emu_i8254_out(&sc->timer[2], x86emu_i8254_gettick(sc)))
    313  1.1  joerg 		val |= 0x20;
    314  1.1  joerg 
    315  1.1  joerg 	return val;
    316  1.1  joerg }
    317  1.1  joerg 
    318  1.1  joerg static void
    319  1.1  joerg x86emu_i8254_write_nmi(struct x86emu_i8254 *sc, uint8_t val)
    320  1.1  joerg {
    321  1.1  joerg 	bool old_gate;
    322  1.1  joerg 
    323  1.1  joerg 	old_gate = sc->timer[2].gate_high;
    324  1.1  joerg 	sc->timer[2].gate_high = (val & 1) == 1;
    325  1.1  joerg 	if (!old_gate && sc->timer[2].gate_high)
    326  1.1  joerg 		sc->timer[2].start_tick = x86emu_i8254_gettick(sc) + 1;
    327  1.1  joerg }
    328  1.1  joerg 
    329  1.1  joerg void
    330  1.1  joerg x86emu_i8254_init(struct x86emu_i8254 *sc, void (*gettime)(struct timespec *))
    331  1.1  joerg {
    332  1.1  joerg 	struct x86emu_i8254_timer *timer;
    333  1.1  joerg 	int i;
    334  1.1  joerg 
    335  1.1  joerg 	sc->gettime = gettime;
    336  1.1  joerg 	(*sc->gettime)(&sc->base_time);
    337  1.1  joerg 
    338  1.1  joerg 	for (i = 0; i < 3; ++i) {
    339  1.1  joerg 		timer = &sc->timer[i];
    340  1.1  joerg 		timer->gate_high = false;
    341  1.1  joerg 		timer->start_tick = 0;
    342  1.1  joerg 		timer->active_counter = 0;
    343  1.1  joerg 		timer->active_mode = 0;
    344  1.1  joerg 		timer->active_is_bcd = false;
    345  1.1  joerg 		timer->counter_is_latched = false;
    346  1.1  joerg 		timer->read_lsb = false;
    347  1.1  joerg 		timer->read_msb = false;
    348  1.1  joerg 		timer->status_is_latched = false;
    349  1.1  joerg 		timer->null_count = false;
    350  1.1  joerg 	}
    351  1.1  joerg }
    352  1.1  joerg 
    353  1.1  joerg uint8_t
    354  1.1  joerg x86emu_i8254_inb(struct x86emu_i8254 *sc, uint16_t port)
    355  1.1  joerg {
    356  1.1  joerg 	KASSERT(x86emu_i8254_claim_port(sc, port));
    357  1.1  joerg 	if (port == 0x40)
    358  1.1  joerg 		return x86emu_i8254_read_counter(sc, &sc->timer[0]);
    359  1.1  joerg 	if (port == 0x41)
    360  1.1  joerg 		return x86emu_i8254_read_counter(sc, &sc->timer[1]);
    361  1.1  joerg 	if (port == 0x42)
    362  1.1  joerg 		return x86emu_i8254_read_counter(sc, &sc->timer[2]);
    363  1.1  joerg 	if (port == 0x43)
    364  1.1  joerg 		return 0xff; /* unsupported */
    365  1.1  joerg 	return	x86emu_i8254_read_nmi(sc);
    366  1.1  joerg }
    367  1.1  joerg 
    368  1.1  joerg void
    369  1.1  joerg x86emu_i8254_outb(struct x86emu_i8254 *sc, uint16_t port, uint8_t val)
    370  1.1  joerg {
    371  1.1  joerg 	KASSERT(x86emu_i8254_claim_port(sc, port));
    372  1.1  joerg 	if (port == 0x40)
    373  1.1  joerg 		x86emu_i8254_write_counter(sc, &sc->timer[0], val);
    374  1.1  joerg 	else if (port == 0x41)
    375  1.1  joerg 		x86emu_i8254_write_counter(sc, &sc->timer[1], val);
    376  1.1  joerg 	else if (port == 0x42)
    377  1.1  joerg 		x86emu_i8254_write_counter(sc, &sc->timer[2], val);
    378  1.1  joerg 	else if (port == 0x43)
    379  1.1  joerg 		x86emu_i8254_write_command(sc, val);
    380  1.1  joerg 	else
    381  1.1  joerg 		x86emu_i8254_write_nmi(sc, val);
    382  1.1  joerg }
    383  1.1  joerg 
    384  1.1  joerg /* ARGSUSED */
    385  1.1  joerg bool
    386  1.1  joerg x86emu_i8254_claim_port(struct x86emu_i8254 *sc, uint16_t port)
    387  1.1  joerg {
    388  1.1  joerg 	/* i8254 registers */
    389  1.1  joerg 	if (port >= 0x40 && port < 0x44)
    390  1.1  joerg 		return true;
    391  1.1  joerg 	/* NMI register, used to control timer 2 and the output of it */
    392  1.1  joerg 	if (port == 0x61)
    393  1.1  joerg 		return true;
    394  1.1  joerg 	return false;
    395  1.1  joerg }
    396