/* * 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. * * Emulation of the VIA VT82C686A/B integrated Super I/O. * * * * Authors: RichardG, * * Copyright 2020 RichardG. */ #include #include #include #include #include #include <86box/86box.h> #include <86box/device.h> #include <86box/io.h> #include <86box/timer.h> #include <86box/pci.h> #include <86box/mem.h> #include <86box/rom.h> #include <86box/lpt.h> #include <86box/serial.h> #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/sio.h> #include <86box/plat_unused.h> typedef struct vt82c686_t { uint8_t cur_reg; uint8_t last_val; uint8_t regs[25]; uint8_t fdc_dma; uint8_t fdc_irq; uint8_t uart_irq[2]; uint8_t lpt_dma; uint8_t lpt_irq; fdc_t *fdc; serial_t *uart[2]; lpt_t *lpt; } vt82c686_t; static uint8_t get_lpt_length(vt82c686_t *dev) { uint8_t length = 4; /* non-EPP */ if ((dev->regs[0x02] & 0x03) == 0x02) length = 8; /* EPP */ return length; } static void vt82c686_fdc_handler(vt82c686_t *dev) { uint16_t io_base = (dev->regs[0x03] & 0xfc) << 2; fdc_remove(dev->fdc); if ((dev->regs[0x02] & 0x10) && !(dev->regs[0x0f] & 0x03)) fdc_set_base(dev->fdc, io_base); fdc_set_dma_ch(dev->fdc, dev->fdc_dma); fdc_set_irq(dev->fdc, dev->fdc_irq); fdc_set_swap(dev->fdc, dev->regs[0x16] & 0x01); } static void vt82c686_lpt_handler(vt82c686_t *dev) { uint16_t io_mask; uint16_t io_base = dev->regs[0x06] << 2; int io_len = get_lpt_length(dev); io_base &= (0xff8 | io_len); io_mask = 0x3fc; /* non-EPP */ if (io_len == 8) io_mask = 0x3f8; /* EPP */ lpt_port_remove(dev->lpt); lpt_set_ext(dev->lpt, !!(dev->regs[0x10] & 0x80)); switch (dev->regs[0x10] & 0x03) { case 0x01: lpt_set_epp(dev->lpt, !!(dev->regs[0x10] & 0x20)); lpt_set_ecp(dev->lpt, 1); break; case 0x02: lpt_set_epp(dev->lpt, 1); lpt_set_ecp(dev->lpt, !!(dev->regs[0x10] & 0x20)); break; } if (((dev->regs[0x02] & 0x03) != 0x03) && !(dev->regs[0x0f] & 0x11) && (io_base >= 0x100) && (io_base <= io_mask)) lpt_port_setup(dev->lpt, io_base); if (dev->lpt_irq) lpt_port_irq(dev->lpt, dev->lpt_irq); else lpt_port_irq(dev->lpt, 0xff); lpt_port_dma(dev->lpt, dev->lpt_dma); } static void vt82c686_serial_handler(vt82c686_t *dev, int uart) { serial_remove(dev->uart[uart]); if ((dev->regs[0x02] & (0x04 << uart)) && !(dev->regs[0x0f] & ((0x04 << uart) | 0x01))) serial_setup(dev->uart[uart], dev->regs[0x07 + uart] << 2, dev->uart_irq[uart]); } static void vt82c686_write(uint16_t port, uint8_t val, void *priv) { vt82c686_t *dev = (vt82c686_t *) priv; /* Store last written value for echo (see comment on read). */ dev->last_val = val; /* Write current register index on port 0. */ if (!(port & 1)) { dev->cur_reg = val; return; } /* NOTE: Registers are [0xE0:0xF8] but we store them as [0x00:0x18]. */ if ((dev->cur_reg < 0xe0) || (dev->cur_reg > 0xf8)) return; uint8_t reg = dev->cur_reg & 0x1f; /* Read-only registers. */ if ((reg < 0x02) || (reg == 0x0c)) return; /* Write current register value on port 1. */ dev->regs[reg] = val; /* Update device state. */ switch (reg) { case 0x02: dev->regs[reg] &= 0xbf; vt82c686_lpt_handler(dev); vt82c686_serial_handler(dev, 0); vt82c686_serial_handler(dev, 1); vt82c686_fdc_handler(dev); break; case 0x03: dev->regs[reg] &= 0xfc; vt82c686_fdc_handler(dev); break; case 0x04: dev->regs[reg] &= 0xfc; break; case 0x05: dev->regs[reg] |= 0x03; break; case 0x06: vt82c686_lpt_handler(dev); break; case 0x07: case 0x08: dev->regs[reg] &= 0xfe; vt82c686_serial_handler(dev, reg == 0x08); break; case 0x0d: dev->regs[reg] &= 0x0f; break; case 0x0f: dev->regs[reg] &= 0x7f; vt82c686_lpt_handler(dev); vt82c686_serial_handler(dev, 0); vt82c686_serial_handler(dev, 1); vt82c686_fdc_handler(dev); break; case 0x10: dev->regs[reg] &= 0xf4; vt82c686_lpt_handler(dev); break; case 0x11: dev->regs[reg] &= 0x3f; break; case 0x13: dev->regs[reg] &= 0xfb; break; case 0x14: case 0x17: dev->regs[reg] &= 0xfe; break; case 0x16: dev->regs[reg] &= 0xf7; vt82c686_fdc_handler(dev); break; default: break; } } static uint8_t vt82c686_read(uint16_t port, void *priv) { vt82c686_t *dev = (vt82c686_t *) priv; /* NOTE: Registers are [0xE0:0xF8] but we store them as [0x00:0x18]. Real 686B echoes the last read/written value when reading from registers outside that range. */ if (!(port & 1)) dev->last_val = dev->cur_reg; else if ((dev->cur_reg >= 0xe0) && (dev->cur_reg <= 0xf8)) dev->last_val = dev->regs[dev->cur_reg & 0x1f]; return dev->last_val; } /* Writes to Super I/O-related configuration space registers of the VT82C686 PCI-ISA bridge are sent here by via_pipc.c */ void vt82c686_sio_write(uint8_t addr, uint8_t val, void *priv) { vt82c686_t *dev = (vt82c686_t *) priv; switch (addr) { case 0x50: dev->fdc_dma = val & 0x03; vt82c686_fdc_handler(dev); dev->lpt_dma = (val >> 2) & 0x03; vt82c686_lpt_handler(dev); break; case 0x51: dev->fdc_irq = val & 0x0f; vt82c686_fdc_handler(dev); dev->lpt_irq = val >> 4; vt82c686_lpt_handler(dev); break; case 0x52: dev->uart_irq[0] = val & 0x0f; vt82c686_serial_handler(dev, 0); dev->uart_irq[1] = val >> 4; vt82c686_serial_handler(dev, 1); break; case 0x85: io_removehandler(FDC_PRIMARY_ADDR, 2, vt82c686_read, NULL, NULL, vt82c686_write, NULL, NULL, dev); if (val & 0x02) io_sethandler(FDC_PRIMARY_ADDR, 2, vt82c686_read, NULL, NULL, vt82c686_write, NULL, NULL, dev); break; default: break; } } static void vt82c686_reset(vt82c686_t *dev) { memset(dev->regs, 0, 21); dev->regs[0x00] = 0x3c; dev->regs[0x02] = 0x03; fdc_reset(dev->fdc); vt82c686_lpt_handler(dev); vt82c686_serial_handler(dev, 0); vt82c686_serial_handler(dev, 1); vt82c686_fdc_handler(dev); vt82c686_sio_write(0x85, 0x00, dev); } static void vt82c686_close(void *priv) { vt82c686_t *dev = (vt82c686_t *) priv; free(dev); } static void * vt82c686_init(UNUSED(const device_t *info)) { vt82c686_t *dev = (vt82c686_t *) calloc(1, sizeof(vt82c686_t)); dev->fdc = device_add(&fdc_at_smc_device); dev->fdc_dma = 2; dev->uart[0] = device_add_inst(&ns16550_device, 1); dev->uart[1] = device_add_inst(&ns16550_device, 2); dev->lpt = device_add_inst(&lpt_port_device, 1); dev->lpt_dma = 3; vt82c686_reset(dev); return dev; } const device_t via_vt82c686_sio_device = { .name = "VIA VT82C686 Integrated Super I/O", .internal_name = "via_vt82c686_sio", .flags = 0, .local = 0, .init = vt82c686_init, .close = vt82c686_close, .reset = NULL, .available = NULL, .speed_changed = NULL, .force_redraw = NULL, .config = NULL };