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