/* * 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 PIC chip emulation, partially * ported from reenigne's XTCE. * * Authors: Andrew Jenner, * Miran Grca, * * Copyright 2015-2020 Andrew Jenner. * Copyright 2016-2020 Miran Grca. */ #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include "cpu.h" #include <86box/machine.h> #include <86box/io.h> #include <86box/pci.h> #include <86box/pic.h> #include <86box/timer.h> #include <86box/pit.h> #include <86box/device.h> #include <86box/apm.h> #include <86box/nvr.h> #include <86box/acpi.h> enum { STATE_NONE = 0, STATE_ICW2, STATE_ICW3, STATE_ICW4 }; pic_t pic, pic2; static pc_timer_t pic_timer; static int shadow = 0, elcr_enabled = 0, tmr_inited = 0, latched = 0, pic_pci = 0; static uint16_t smi_irq_mask = 0x0000, smi_irq_status = 0x0000; static void (*update_pending)(void); #ifdef ENABLE_PIC_LOG int pic_do_log = ENABLE_PIC_LOG; static void pic_log(const char *fmt, ...) { va_list ap; if (pic_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else #define pic_log(fmt, ...) #endif void pic_reset_smi_irq_mask(void) { smi_irq_mask = 0x0000; } void pic_set_smi_irq_mask(int irq, int set) { if ((irq >= 0) && (irq <= 15)) { if (set) smi_irq_mask |= (1 << irq); else smi_irq_mask &= ~(1 << irq); } } uint16_t pic_get_smi_irq_status(void) { return smi_irq_status; } void pic_clear_smi_irq_status(int irq) { if ((irq >= 0) && (irq <= 15)) smi_irq_status &= ~(1 << irq); } void pic_elcr_write(uint16_t port, uint8_t val, void *priv) { pic_t *dev = (pic_t *) priv; pic_log("ELCR%i: WRITE %02X\n", port & 1, val); if (port & 1) val &= 0xde; else val &= 0xf8; dev->elcr = val; pic_log("ELCR %i: %c %c %c %c %c %c %c %c\n", port & 1, (val & 1) ? 'L' : 'E', (val & 2) ? 'L' : 'E', (val & 4) ? 'L' : 'E', (val & 8) ? 'L' : 'E', (val & 0x10) ? 'L' : 'E', (val & 0x20) ? 'L' : 'E', (val & 0x40) ? 'L' : 'E', (val & 0x80) ? 'L' : 'E'); } uint8_t pic_elcr_read(uint16_t port, void *priv) { pic_t *dev = (pic_t *) priv; pic_log("ELCR%i: READ %02X\n", port & 1, dev->elcr); return dev->elcr; } int pic_elcr_get_enabled(void) { return elcr_enabled; } void pic_elcr_set_enabled(int enabled) { elcr_enabled = enabled; } void pic_elcr_io_handler(int set) { io_handler(set, 0x04d0, 0x0001, pic_elcr_read, NULL, NULL, pic_elcr_write, NULL, NULL, &pic); io_handler(set, 0x04d1, 0x0001, pic_elcr_read, NULL, NULL, pic_elcr_write, NULL, NULL, &pic2); } static uint8_t pic_cascade_mode(pic_t *dev) { return !(dev->icw1 & 2); } static __inline uint8_t pic_slave_on(pic_t *dev, int channel) { pic_log("pic_slave_on(%i): %i, %02X, %02X\n", channel, pic_cascade_mode(dev), dev->icw4 & 0x0c, dev->icw3 & (1 << channel)); return pic_cascade_mode(dev) && (dev->is_master || ((dev->icw4 & 0x0c) == 0x0c)) && (dev->icw3 & (1 << channel)); } static __inline int find_best_interrupt(pic_t *dev) { uint8_t b; uint8_t intr; int i, j; int ret = -1; for (i = 0; i < 8; i++) { j = (i + dev->priority) & 7; b = 1 << j; if (dev->isr & b) break; else if ((dev->state == 0) && ((dev->irr & ~dev->imr) & b)) { ret = j; break; } } intr = dev->interrupt = (ret == -1) ? 0x17 : ret; if (dev->at && (ret != 1)) { if (dev == &pic2) intr += 8; if (cpu_fast_off_flags & (1 << intr)) cpu_fast_off_count = cpu_fast_off_val + 1; } return ret; } static __inline void pic_update_pending_xt(void) { if (find_best_interrupt(&pic) != -1) { latched++; if (latched == 1) timer_on_auto(&pic_timer, 0.35); } else if (latched == 0) pic.int_pending = 0; } static __inline void pic_update_pending_at(void) { pic2.int_pending = (find_best_interrupt(&pic2) != -1); if (pic2.int_pending) pic.irr |= (1 << pic2.icw3); else pic.irr &= ~(1 << pic2.icw3); pic.int_pending = (find_best_interrupt(&pic) != -1); } static void pic_callback(void *priv) { pic_t *dev = (pic_t *) priv; dev->int_pending = 1; latched--; if (latched > 0) timer_on_auto(&pic_timer, 0.35); } void pic_reset() { int is_at = IS_AT(machine); memset(&pic, 0, sizeof(pic_t)); memset(&pic2, 0, sizeof(pic_t)); pic.is_master = 1; pic.interrupt = pic2.interrupt = 0x17; if (is_at) pic.slaves[2] = &pic2; if (tmr_inited) timer_on_auto(&pic_timer, 0.0); memset(&pic_timer, 0x00, sizeof(pc_timer_t)); timer_add(&pic_timer, pic_callback, &pic, 0); tmr_inited = 1; update_pending = is_at ? pic_update_pending_at : pic_update_pending_xt; pic.at = pic2.at = is_at; smi_irq_mask = smi_irq_status = 0x0000; shadow = 0; pic_pci = 0; } void pic_set_shadow(int sh) { shadow = sh; } void pic_set_pci_flag(int pci) { pic_pci = pci; } static uint8_t pic_level_triggered(pic_t *dev, int irq) { if (elcr_enabled) return !!(dev->elcr & (1 << irq)); else return !!(dev->icw1 & 8); } int picint_is_level(int irq) { return pic_level_triggered(((irq > 7) ? &pic2 : &pic), irq & 7); } static void pic_acknowledge(pic_t *dev) { int pic_int = dev->interrupt & 7; int pic_int_num = 1 << pic_int; dev->isr |= pic_int_num; if (!pic_level_triggered(dev, pic_int) || !(dev->lines & pic_int_num)) dev->irr &= ~pic_int_num; } /* Find IRQ for non-specific EOI (either by command or automatic) by finding the highest IRQ priority with ISR bit set, that is also not masked if the PIC is in special mask mode. */ static uint8_t pic_non_specific_find(pic_t *dev) { int i, j; uint8_t b, irq = 0xff; for (i = 0; i < 8; i++) { j = (i + dev->priority) & 7; b = (1 << j); if ((dev->isr & b) && (!dev->special_mask_mode || !(dev->imr & b))) { irq = j; break; } } return irq; } /* Do the EOI and rotation, if either is requested, on the given IRQ. */ static void pic_action(pic_t *dev, uint8_t irq, uint8_t eoi, uint8_t rotate) { uint8_t b = (1 << irq); if (irq != 0xff) { if (eoi) dev->isr &= ~b; if (rotate) dev->priority = (irq + 1) & 7; update_pending(); } } /* Automatic non-specific EOI. */ static __inline void pic_auto_non_specific_eoi(pic_t *dev) { uint8_t irq; if (dev->icw4 & 2) { irq = pic_non_specific_find(dev); pic_action(dev, irq, 1, dev->auto_eoi_rotate); } } /* Do the PIC command specified by bits 7-5 of the value written to the OCW2 register. */ static void pic_command(pic_t *dev) { uint8_t irq = 0xff; if (dev->ocw2 & 0x60) { /* SL and/or EOI set */ if (dev->ocw2 & 0x40) /* SL set, specific priority level */ irq = (dev->ocw2 & 0x07); else /* SL clear, non-specific priority level (find highest with ISR set) */ irq = pic_non_specific_find(dev); pic_action(dev, irq, dev->ocw2 & 0x20, dev->ocw2 & 0x80); } else /* SL and EOI clear */ dev->auto_eoi_rotate = !!(dev->ocw2 & 0x80); } uint8_t pic_read(uint16_t addr, void *priv) { pic_t *dev = (pic_t *) priv; if (shadow) { /* VIA PIC shadow read */ if (addr & 0x0001) dev->data_bus = ((dev->icw2 & 0xf8) >> 3) << 0; else { dev->data_bus = ((dev->ocw3 & 0x20) >> 5) << 4; dev->data_bus |= ((dev->ocw2 & 0x80) >> 7) << 3; dev->data_bus |= ((dev->icw4 & 0x10) >> 4) << 2; dev->data_bus |= ((dev->icw4 & 0x02) >> 1) << 1; dev->data_bus |= ((dev->icw4 & 0x08) >> 3) << 0; } } else { /* Standard 8259 PIC read */ #ifndef UNDEFINED_READ /* Put the IRR on to the data bus by default until the real PIC is probed. */ dev->data_bus = dev->irr; #endif if (dev->ocw3 & 0x04) { if (dev->int_pending) { dev->data_bus = 0x80 | (dev->interrupt & 7); pic_acknowledge(dev); dev->int_pending = 0; update_pending(); } else dev->data_bus = 0x00; dev->ocw3 &= ~0x04; } else if (addr & 0x0001) dev->data_bus = dev->imr; else if (dev->ocw3 & 0x02) { if (dev->ocw3 & 0x01) dev->data_bus = dev->isr; #ifdef UNDEFINED_READ else dev->data_bus = 0x00; #endif } /* If A0 = 0, VIA shadow is disabled, and poll mode is disabled, simply read whatever is currently on the data bus. */ } pic_log("pic_read(%04X, %08X) = %02X\n", addr, priv, dev->data_bus); return dev->data_bus; } static void pic_write(uint16_t addr, uint8_t val, void *priv) { pic_t *dev = (pic_t *) priv; pic_log("pic_write(%04X, %02X, %08X)\n", addr, val, priv); dev->data_bus = val; if (addr & 0x0001) { switch (dev->state) { case STATE_ICW2: dev->icw2 = val; if (pic_cascade_mode(dev)) dev->state = STATE_ICW3; else dev->state = (dev->icw1 & 1) ? STATE_ICW4 : STATE_NONE; break; case STATE_ICW3: dev->icw3 = val; dev->state = (dev->icw1 & 1) ? STATE_ICW4 : STATE_NONE; break; case STATE_ICW4: dev->icw4 = val; dev->state = STATE_NONE; break; case STATE_NONE: dev->imr = val; update_pending(); break; } } else { if (val & 0x10) { /* Treat any write with any of the bits 7 to 5 set as invalid if PCI. */ if (pic_pci && (val & 0xe0)) return; dev->icw1 = val; dev->icw2 = dev->icw3 = 0x00; if (!(dev->icw1 & 1)) dev->icw4 = 0x00; dev->ocw2 = dev->ocw3 = 0x00; dev->irr = dev->lines; dev->imr = dev->isr = 0x00; dev->ack_bytes = dev->priority = 0x00; dev->auto_eoi_rotate = dev->special_mask_mode = 0x00; dev->interrupt = 0x17; dev->int_pending = 0x00; dev->state = STATE_ICW2; update_pending(); } else if (val & 0x08) { dev->ocw3 = val; if (dev->ocw3 & 0x40) dev->special_mask_mode = !!(dev->ocw3 & 0x20); } else { dev->ocw2 = val; pic_command(dev); } } } void pic_set_pci(void) { int i; for (i = 0x0024; i < 0x0040; i += 4) { io_sethandler(i, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic); io_sethandler(i + 0x0080, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic2); } for (i = 0x1120; i < 0x1140; i += 4) { io_sethandler(i, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic); io_sethandler(i + 0x0080, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic2); } } void pic_init(void) { pic_reset(); shadow = 0; io_sethandler(0x0020, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic); } void pic_init_pcjr(void) { pic_reset(); shadow = 0; io_sethandler(0x0020, 0x0008, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic); } void pic2_init(void) { io_sethandler(0x00a0, 0x0002, pic_read, NULL, NULL, pic_write, NULL, NULL, &pic2); pic.slaves[2] = &pic2; } void picint_common(uint16_t num, int level, int set) { int i, raise; uint8_t b, slaves = 0; /* Make sure to ignore all slave IRQ's, and in case of AT+, translate IRQ 2 to IRQ 9. */ for (i = 0; i < 8; i++) { b = (1 << i); raise = num & b; if (pic.icw3 & b) { slaves++; if (raise) { num &= ~b; if (pic.at && (i == 2)) num |= (1 << 9); } } } if (!slaves) num &= 0x00ff; if (!num) { pic_log("Attempting to %s null IRQ\n", set ? "raise" : "lower"); return; } if (num & 0x0100) acpi_rtc_status = !!set; if (set) { if (smi_irq_mask & num) { smi_line = 1; smi_irq_status |= num; } if (num & 0xff00) { if (level) pic2.lines |= (num >> 8); pic2.irr |= (num >> 8); } if (num & 0x00ff) { if (level) pic.lines |= (num >> 8); pic.irr |= num; } } else { smi_irq_status &= ~num; if (num & 0xff00) { pic2.lines &= ~(num >> 8); pic2.irr &= ~(num >> 8); } if (num & 0x00ff) { pic.lines &= ~num; pic.irr &= ~num; } } if (!(pic.interrupt & 0x20) && !(pic2.interrupt & 0x20)) update_pending(); } void picint(uint16_t num) { picint_common(num, 0, 1); } void picintlevel(uint16_t num) { picint_common(num, 1, 1); } void picintc(uint16_t num) { picint_common(num, 0, 0); } static uint8_t pic_i86_mode(pic_t *dev) { return !!(dev->icw4 & 1); } static uint8_t pic_irq_ack_read(pic_t *dev, int phase) { uint8_t intr = dev->interrupt & 0x47; uint8_t slave = intr & 0x40; intr &= 0x07; pic_log(" pic_irq_ack_read(%08X, %i)\n", dev, phase); if (dev != NULL) { if (phase == 0) { dev->interrupt |= 0x20; /* Freeze it so it still takes interrupts but they do not override the one currently being processed. */ pic_acknowledge(dev); if (slave) dev->data_bus = pic_irq_ack_read(dev->slaves[intr], phase); else dev->data_bus = pic_i86_mode(dev) ? 0xff : 0xcd; } else if (pic_i86_mode(dev)) { dev->int_pending = 0; if (slave) dev->data_bus = pic_irq_ack_read(dev->slaves[intr], phase); else dev->data_bus = intr + (dev->icw2 & 0xf8); pic_auto_non_specific_eoi(dev); } else if (phase == 1) { if (slave) dev->data_bus = pic_irq_ack_read(dev->slaves[intr], phase); else if (dev->icw1 & 0x04) dev->data_bus = (intr << 2) + (dev->icw1 & 0xe0); else dev->data_bus = (intr << 3) + (dev->icw1 & 0xc0); } else if (phase == 2) { dev->int_pending = 0; if (slave) dev->data_bus = pic_irq_ack_read(dev->slaves[intr], phase); else dev->data_bus = dev->icw2; pic_auto_non_specific_eoi(dev); } } return dev->data_bus; } uint8_t pic_irq_ack(void) { int ret; ret = pic_irq_ack_read(&pic, pic.ack_bytes); pic.ack_bytes = (pic.ack_bytes + 1) % (pic_i86_mode(&pic) ? 2 : 3); if (pic.ack_bytes == 0) { pic.interrupt = 0x17; update_pending(); } return ret; } int picinterrupt() { int i, ret = -1; if (pic.int_pending) { if (pic_slave_on(&pic, pic.interrupt)) { if (!pic.slaves[pic.interrupt]->int_pending) { /* If we are on AT, IRQ 2 is pending, and we cannot find a pending IRQ on PIC 2, fatal out. */ fatal("IRQ %i pending on AT without a pending IRQ on PIC %i (normal)\n", pic.interrupt, pic.interrupt); exit(-1); return -1; } pic.interrupt |= 0x40; /* Mark slave pending. */ } if ((pic.interrupt == 0) && (pit2 != NULL)) pit_ctr_set_gate(&pit2->counters[0], 0); /* Two ACK's - do them in a loop to avoid potential compiler misoptimizations. */ for (i = 0; i < 2; i++) { ret = pic_irq_ack_read(&pic, pic.ack_bytes); pic.ack_bytes = (pic.ack_bytes + 1) % (pic_i86_mode(&pic) ? 2 : 3); if (pic.ack_bytes == 0) { if (pic.interrupt & 0x40) pic2.interrupt = 0x17; pic.interrupt = 0x17; update_pending(); } } } return ret; }