/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of the Intel 8253/8254 Programmable Interval * Timer. * * * * Authors: Miran Grca, * * Copyright 2019 Miran Grca. */ #include #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include "cpu.h" #include <86box/device.h> #include <86box/timer.h> #include <86box/cassette.h> #include <86box/dma.h> #include <86box/io.h> #include <86box/nmi.h> #include <86box/pic.h> #include <86box/timer.h> #include <86box/pit.h> #include <86box/pit_fast.h> #include <86box/ppi.h> #include <86box/machine.h> #include <86box/sound.h> #include <86box/snd_speaker.h> #include <86box/video.h> #define PIT_PS2 16 /* The PIT is the PS/2's second PIT. */ #define PIT_EXT_IO 32 /* The PIT has externally specified port I/O. */ #define PIT_CUSTOM_CLOCK 64 /* The PIT uses custom clock inputs provided by another provider. */ #define PIT_SECONDARY 128 /* The PIT is secondary (ports 0048-004B). */ #ifdef ENABLE_PIT_LOG int pit_do_log = ENABLE_PIT_LOG; static void pit_log(const char *fmt, ...) { va_list ap; if (pit_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define pit_log(fmt, ...) #endif static void pitf_ctr_set_out(ctrf_t *ctr, int out, void *priv) { pitf_t *pit = (pitf_t *)priv; if (ctr == NULL) return; if (ctr->out_func != NULL) ctr->out_func(out, ctr->out, pit); ctr->out = out; } static void pitf_ctr_set_load_func(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)) { if (data == NULL) return; pitf_t *pit = (pitf_t *) data; ctrf_t *ctr = &pit->counters[counter_id]; ctr->load_func = func; } static uint16_t pitf_ctr_get_count(void *data, int counter_id) { const pitf_t *pit = (pitf_t *) data; const ctrf_t *ctr = &pit->counters[counter_id]; return (uint16_t) ctr->l; } void pitf_ctr_set_out_func(void *data, int counter_id, void (*func)(int new_out, int old_out, void *priv)) { if (data == NULL) return; pitf_t *pit = (pitf_t *) data; ctrf_t *ctr = &pit->counters[counter_id]; ctr->out_func = func; } void pitf_ctr_set_using_timer(void *data, int counter_id, int using_timer) { if (tsc > 0) timer_process(); pitf_t *pit = (pitf_t *) data; ctrf_t *ctr = &pit->counters[counter_id]; ctr->using_timer = using_timer; } static int pitf_read_timer(ctrf_t *ctr) { if (ctr->using_timer && !(ctr->m == 3 && !ctr->gate) && timer_is_enabled(&ctr->timer)) { int read = (int) ((timer_get_remaining_u64(&ctr->timer)) / ctr->pit_const); if (ctr->m == 2) read++; if (read < 0) read = 0; if (read > 0x10000) read = 0x10000; if ((ctr->m == 3) && ctr->using_timer) read <<= 1; return read; } if (ctr->m == 2) return ctr->count + 1; return ctr->count; } /*Dump timer count back to pit->count[], and disable timer. This should be used when stopping a PIT timer, to ensure the correct value can be read back.*/ static void pitf_dump_and_disable_timer(ctrf_t *ctr) { if (ctr->using_timer && timer_is_enabled(&ctr->timer)) { ctr->count = pitf_read_timer(ctr); if (ctr->m == 2) ctr->count--; /* Don't store the offset from pitf_read_timer */ timer_disable(&ctr->timer); } } static void pitf_ctr_load(ctrf_t *ctr, void *priv) { pitf_t *pit = (pitf_t *)priv; int l = ctr->l ? ctr->l : 0x10000; ctr->newcount = 0; ctr->disabled = 0; switch (ctr->m) { case 0: /*Interrupt on terminal count*/ ctr->count = l; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); pitf_ctr_set_out(ctr, 0, pit); ctr->thit = 0; ctr->enabled = ctr->gate; break; case 1: /*Hardware retriggerable one-shot*/ ctr->enabled = 1; break; case 2: /*Rate generator*/ if (ctr->initial) { ctr->count = l - 1; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) ((l - 1) * ctr->pit_const)); pitf_ctr_set_out(ctr, 1, pit); ctr->thit = 0; } ctr->enabled = ctr->gate; break; case 3: /*Square wave mode*/ if (ctr->initial) { ctr->count = l; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * ctr->pit_const)); else ctr->newcount = (l & 1); pitf_ctr_set_out(ctr, 1, pit); ctr->thit = 0; } ctr->enabled = ctr->gate; break; case 4: /*Software triggered stobe*/ if (!ctr->thit && !ctr->initial) ctr->newcount = 1; else { ctr->count = l; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); pitf_ctr_set_out(ctr, 0, pit); ctr->thit = 0; } ctr->enabled = ctr->gate; break; case 5: /*Hardware triggered stobe*/ ctr->enabled = 1; break; default: break; } if (ctr->load_func != NULL) ctr->load_func(ctr->m, l); ctr->initial = 0; ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; if (ctr->using_timer && !ctr->running) pitf_dump_and_disable_timer(ctr); } static void pitf_set_gate_no_timer(ctrf_t *ctr, int gate, void *priv) { pitf_t *pit = (pitf_t *)priv; int l = ctr->l ? ctr->l : 0x10000; if (ctr->disabled) { ctr->gate = gate; return; } switch (ctr->m) { case 0: /*Interrupt on terminal count*/ case 4: /*Software triggered stobe*/ if (ctr->using_timer && !ctr->running) timer_set_delay_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); ctr->enabled = gate; break; case 1: /*Hardware retriggerable one-shot*/ case 5: /*Hardware triggered stobe*/ if (gate && !ctr->gate) { ctr->count = l; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); pitf_ctr_set_out(ctr, 0, pit); ctr->thit = 0; ctr->enabled = 1; } break; case 2: /*Rate generator*/ if (gate && !ctr->gate) { ctr->count = l - 1; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); pitf_ctr_set_out(ctr, 1, pit); ctr->thit = 0; } ctr->enabled = gate; break; case 3: /*Square wave mode*/ if (gate && !ctr->gate) { ctr->count = l; if (ctr->using_timer) timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * ctr->pit_const)); else ctr->newcount = (l & 1); pitf_ctr_set_out(ctr, 1, pit); ctr->thit = 0; } ctr->enabled = gate; break; default: break; } ctr->gate = gate; ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; if (ctr->using_timer && !ctr->running) pitf_dump_and_disable_timer(ctr); } void pitf_ctr_set_gate(void *data, int counter_id, int gate) { pitf_t *pit = (pitf_t *) data; ctrf_t *ctr = &pit->counters[counter_id]; if (ctr->disabled) { ctr->gate = gate; return; } pitf_set_gate_no_timer(ctr, gate, pit); } static void pitf_over(ctrf_t *ctr, void *priv) { pitf_t *pit = (pitf_t *)priv; int l = ctr->l ? ctr->l : 0x10000; if (ctr->disabled) { ctr->count += 0xffff; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * ctr->pit_const)); return; } switch (ctr->m) { case 0: /*Interrupt on terminal count*/ case 1: /*Hardware retriggerable one-shot*/ if (!ctr->thit) pitf_ctr_set_out(ctr, 1, pit); ctr->thit = 1; ctr->count += 0xffff; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * ctr->pit_const)); break; case 2: /*Rate generator*/ ctr->count += l; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); pitf_ctr_set_out(ctr, 0, pit); pitf_ctr_set_out(ctr, 1, pit); break; case 3: /*Square wave mode*/ if (ctr->out) { pitf_ctr_set_out(ctr, 0, pit); if (ctr->using_timer) { ctr->count += (l >> 1); timer_advance_u64(&ctr->timer, (uint64_t) ((l >> 1) * ctr->pit_const)); } else { ctr->count += l; ctr->newcount = (l & 1); } } else { pitf_ctr_set_out(ctr, 1, pit); ctr->count += ((l + 1) >> 1); if (ctr->using_timer) { ctr->count += (l >> 1); timer_advance_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * ctr->pit_const)); } else { ctr->count += l; ctr->newcount = (l & 1); } } #if 0 if (!t) pclog("pit_over: square wave mode c=%x %lli %f\n", pit.c[t], tsc, ctr->pit_const); #endif break; case 4: /*Software triggered strove*/ if (!ctr->thit) { pitf_ctr_set_out(ctr, 0, pit); pitf_ctr_set_out(ctr, 1, pit); } if (ctr->newcount) { ctr->newcount = 0; ctr->count += l; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (l * ctr->pit_const)); } else { ctr->thit = 1; ctr->count += 0xffff; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * ctr->pit_const)); } break; case 5: /*Hardware triggered strove*/ if (!ctr->thit) { pitf_ctr_set_out(ctr, 0, pit); pitf_ctr_set_out(ctr, 1, pit); } ctr->thit = 1; ctr->count += 0xffff; if (ctr->using_timer) timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * ctr->pit_const)); break; default: break; } ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; if (ctr->using_timer && !ctr->running) pitf_dump_and_disable_timer(ctr); } static __inline void pitf_ctr_latch_count(ctrf_t *ctr) { ctr->rl = pitf_read_timer(ctr); #if 0 pclog("Timer latch %f %04X %04X\n",pit->c[0],pit->rl[0],pit->l[0]); pit->ctrl |= 0x30; #endif ctr->rereadlatch = 0; ctr->rm = 3; ctr->latched = 1; } static __inline void pitf_ctr_latch_status(ctrf_t *ctr) { ctr->read_status = (ctr->ctrl & 0x3f) | (ctr->out ? 0x80 : 0); ctr->do_read_status = 1; } static void pitf_write(uint16_t addr, uint8_t val, void *priv) { pitf_t *dev = (pitf_t *) priv; int t = (addr & 3); ctrf_t *ctr; pit_log("[%04X:%08X] pit_write(%04X, %02X, %08X)\n", CS, cpu_state.pc, addr, val, priv); cycles -= ISA_CYCLES(8); switch (addr & 3) { case 3: /* control */ t = val >> 6; if (t == 3) { if (dev->flags & PIT_8254) { /* This is 8254-only. */ if (!(val & 0x20)) { if (val & 2) pitf_ctr_latch_count(&dev->counters[0]); if (val & 4) pitf_ctr_latch_count(&dev->counters[1]); if (val & 8) pitf_ctr_latch_count(&dev->counters[2]); pit_log("PIT %i: Initiated readback command\n", t); } if (!(val & 0x10)) { if (val & 2) pitf_ctr_latch_status(&dev->counters[0]); if (val & 4) pitf_ctr_latch_status(&dev->counters[1]); if (val & 8) pitf_ctr_latch_status(&dev->counters[2]); } } } else { dev->ctrl = val; ctr = &dev->counters[t]; if (!(dev->ctrl & 0x30)) { pitf_ctr_latch_count(ctr); dev->ctrl |= 0x30; pit_log("PIT %i: Initiated latched read, %i bytes latched\n", t, ctr->latched); } else { ctr->ctrl = val; ctr->rm = ctr->wm = (ctr->ctrl >> 4) & 3; ctr->m = (val >> 1) & 7; if (ctr->m > 5) ctr->m &= 3; if (!(ctr->rm)) { ctr->rm = 3; ctr->rl = pitf_read_timer(ctr); } ctr->rereadlatch = 1; ctr->initial = 1; if (!ctr->m) pitf_ctr_set_out(ctr, 0, dev); else pitf_ctr_set_out(ctr, 1, dev); ctr->disabled = 1; pit_log("PIT %i: M = %i, RM/WM = %i, State = %i, Out = %i\n", t, ctr->m, ctr->rm, ctr->state, ctr->out); } ctr->thit = 0; } break; case 0: case 1: case 2: /* the actual timers */ ctr = &dev->counters[t]; switch (ctr->wm) { case 1: ctr->l = val; pitf_ctr_load(ctr, dev); break; case 2: ctr->l = (val << 8); pitf_ctr_load(ctr, dev); break; case 0: ctr->l &= 0xFF; ctr->l |= (val << 8); pitf_ctr_load(ctr, dev); ctr->wm = 3; break; case 3: ctr->l &= 0xFF00; ctr->l |= val; ctr->wm = 0; break; default: break; } break; default: break; } } uint8_t pitf_read_reg(void *priv, uint8_t reg) { pitf_t *dev = (pitf_t *) priv; uint8_t ret = 0xff; switch (reg) { case 0x00: case 0x02: case 0x04: ret = dev->counters[reg >> 1].l & 0xff; break; case 0x01: case 0x03: case 0x05: ret = (dev->counters[reg >> 1].l >> 8) & 0xff; break; case 0x06: ret = dev->ctrl; break; case 0x07: /* The SiS 551x datasheet is unclear about how exactly this register is structured. Update: But the SiS 5571 datasheet is clear. */ ret = (dev->counters[0].rm & 0x80) ? 0x01 : 0x00; ret |= (dev->counters[1].rm & 0x80) ? 0x02 : 0x00; ret |= (dev->counters[2].rm & 0x80) ? 0x04 : 0x00; ret |= (dev->counters[0].wm & 0x80) ? 0x08 : 0x00; ret |= (dev->counters[1].wm & 0x80) ? 0x10 : 0x00; ret |= (dev->counters[2].wm & 0x80) ? 0x20 : 0x00; break; } return ret; } static uint8_t pitf_read(uint16_t addr, void *priv) { pitf_t *dev = (pitf_t *) priv; uint8_t ret = 0xff; int t = (addr & 3); ctrf_t *ctr; cycles -= ISA_CYCLES(8); switch (addr & 3) { case 3: /* Control. */ /* This is 8254-only, 8253 returns 0x00. */ ret = (dev->flags & PIT_8254) ? dev->ctrl : 0x00; break; case 0: case 1: case 2: /* The actual timers. */ ctr = &dev->counters[t]; if (ctr->do_read_status) { ctr->do_read_status = 0; ret = ctr->read_status; break; } if (ctr->rereadlatch && !ctr->latched) { ctr->rereadlatch = 0; ctr->rl = pitf_read_timer(ctr); } switch (ctr->rm) { case 0: ret = ctr->rl >> 8; ctr->rm = 3; ctr->latched = 0; ctr->rereadlatch = 1; break; case 1: ret = (ctr->rl) & 0xFF; ctr->latched = 0; ctr->rereadlatch = 1; break; case 2: ret = (ctr->rl) >> 8; ctr->latched = 0; ctr->rereadlatch = 1; break; case 3: ret = (ctr->rl) & 0xFF; if (ctr->m & 0x80) ctr->m &= 7; else ctr->rm = 0; break; default: break; } break; default: break; } pit_log("[%04X:%08X] pit_read(%04X, %08X) = %02X\n", CS, cpu_state.pc, addr, priv, ret); return ret; } static void pitf_timer_over(void *priv) { ctrf_t *ctr = (ctrf_t *) priv; pit_t *pit = (pit_t *)ctr->priv; pitf_over(ctr, pit); } void pitf_ctr_clock(void *data, int counter_id) { pitf_t *pit = (pitf_t *) data; ctrf_t *ctr = &pit->counters[counter_id]; if (ctr->thit || !ctr->enabled) return; if (ctr->using_timer) return; if ((ctr->m == 3) && ctr->newcount) { ctr->count -= ctr->out ? 1 : 3; ctr->newcount = 0; } else ctr->count -= (ctr->m == 3) ? 2 : 1; if (!ctr->count) pitf_over(ctr, pit); } static void ctr_reset(ctrf_t *ctr) { ctr->ctrl = 0; ctr->m = 0; ctr->gate = 0; ctr->l = 0xffff; ctr->thit = 1; ctr->using_timer = 1; } static void pitf_reset(pitf_t *dev) { memset(dev, 0, sizeof(pitf_t)); for (uint8_t i = 0; i < 3; i++) ctr_reset(&dev->counters[i]); /* Disable speaker gate. */ dev->counters[2].gate = 0; } void pitf_set_pit_const(void *data, uint64_t pit_const) { pitf_t *pit = (pitf_t *) data; ctrf_t *ctr; for (uint8_t i = 0; i < 3; i++) { ctr = &pit->counters[i]; ctr->pit_const = pit_const; } } static void pitf_speed_changed(void *priv) { pitf_set_pit_const(priv, PITCONST); } static void pitf_close(void *priv) { pitf_t *dev = (pitf_t *) priv; if (dev == pit_devs[0].data) pit_devs[0].data = NULL; if (dev == pit_devs[1].data) pit_devs[1].data = NULL; if (dev != NULL) free(dev); } void pitf_handler(int set, uint16_t base, int size, void *priv) { io_handler(set, base, size, pitf_read, NULL, NULL, pitf_write, NULL, NULL, priv); } static void * pitf_init(const device_t *info) { pitf_t *dev = (pitf_t *) malloc(sizeof(pitf_t)); pitf_reset(dev); pitf_set_pit_const(dev, PITCONST); dev->flags = info->local; if (!(dev->flags & PIT_PS2) && !(dev->flags & PIT_CUSTOM_CLOCK)) { for (int i = 0; i < 3; i++) { ctrf_t *ctr = &dev->counters[i]; ctr->priv = dev; timer_add(&ctr->timer, pitf_timer_over, (void *) ctr, 0); } } if (!(dev->flags & PIT_EXT_IO)) { io_sethandler((dev->flags & PIT_SECONDARY) ? 0x0048 : 0x0040, 0x0004, pitf_read, NULL, NULL, pitf_write, NULL, NULL, dev); } return dev; } const device_t i8253_fast_device = { .name = "Intel 8253/8253-5 Programmable Interval Timer", .internal_name = "i8253_fast", .flags = DEVICE_ISA | DEVICE_PIT, .local = PIT_8253, .init = pitf_init, .close = pitf_close, .reset = NULL, { .available = NULL }, .speed_changed = pitf_speed_changed, .force_redraw = NULL, .config = NULL }; const device_t i8254_fast_device = { .name = "Intel 8254 Programmable Interval Timer", .internal_name = "i8254_fast", .flags = DEVICE_ISA | DEVICE_PIT, .local = PIT_8254, .init = pitf_init, .close = pitf_close, .reset = NULL, { .available = NULL }, .speed_changed = pitf_speed_changed, .force_redraw = NULL, .config = NULL }; const device_t i8254_sec_fast_device = { .name = "Intel 8254 Programmable Interval Timer (Secondary)", .internal_name = "i8254_sec_fast", .flags = DEVICE_ISA, .local = PIT_8254 | PIT_SECONDARY, .init = pitf_init, .close = pitf_close, .reset = NULL, { .available = NULL }, .speed_changed = pitf_speed_changed, .force_redraw = NULL, .config = NULL }; const device_t i8254_ext_io_fast_device = { .name = "Intel 8254 Programmable Interval Timer (External I/O)", .internal_name = "i8254_ext_io_fast", .flags = DEVICE_ISA, .local = PIT_8254 | PIT_EXT_IO, .init = pitf_init, .close = pitf_close, .reset = NULL, { .available = NULL }, .speed_changed = NULL, .force_redraw = NULL, .config = NULL }; const device_t i8254_ps2_fast_device = { .name = "Intel 8254 Programmable Interval Timer (PS/2)", .internal_name = "i8254_ps2_fast", .flags = DEVICE_ISA, .local = PIT_8254 | PIT_PS2 | PIT_EXT_IO, .init = pitf_init, .close = pitf_close, .reset = NULL, { .available = NULL }, .speed_changed = pitf_speed_changed, .force_redraw = NULL, .config = NULL }; const pit_intf_t pit_fast_intf = { &pitf_read, &pitf_write, &pitf_ctr_get_count, &pitf_ctr_set_gate, &pitf_ctr_set_using_timer, &pitf_ctr_set_out_func, &pitf_ctr_set_load_func, &pitf_ctr_clock, &pitf_set_pit_const, NULL, };