/* * 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 National Semiconductor PC87311 Super I/O * * Authors: Tiseno100 * Copyright 2020 Tiseno100 */ #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/io.h> #include <86box/timer.h> #include <86box/device.h> #include <86box/lpt.h> #include <86box/serial.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/sio.h> #define HAS_IDE_FUNCTIONALITY dev->ide_function /* Basic Functionalities */ #define FUNCTION_ENABLE dev->regs[0x00] #define FUNCTION_ADDRESS dev->regs[0x01] #define POWER_TEST dev->regs[0x02] /* Base Addresses */ #define LPT_BA (FUNCTION_ADDRESS & 0x03) #define UART1_BA ((FUNCTION_ADDRESS >> 2) & 0x03) #define UART2_BA ((FUNCTION_ADDRESS >> 4) & 0x03) #define COM_BA ((FUNCTION_ADDRESS >> 6) & 0x03) #ifdef ENABLE_PC87311_LOG int pc87311_do_log = ENABLE_PC87311_LOG; static void pc87311_log(const char *fmt, ...) { va_list ap; if (pc87311_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else #define pc87311_log(fmt, ...) #endif typedef struct { uint8_t index, regs[256], cfg_lock, ide_function; uint16_t base, irq; fdc_t *fdc_controller; serial_t *uart[2]; } pc87311_t; void pc87311_fdc_handler(pc87311_t *dev); void pc87311_uart_handler(uint8_t num, pc87311_t *dev); void pc87311_lpt_handler(pc87311_t *dev); void pc87311_ide_handler(pc87311_t *dev); void pc87311_enable(pc87311_t *dev); static void pc87311_write(uint16_t addr, uint8_t val, void *priv) { pc87311_t *dev = (pc87311_t *)priv; switch (addr) { case 0x398: case 0x26e: dev->index = val; break; case 0x399: case 0x26f: switch (dev->index) { case 0x00: FUNCTION_ENABLE = val; break; case 0x01: FUNCTION_ADDRESS = val; break; case 0x02: POWER_TEST = val; break; } break; } pc87311_enable(dev); } static uint8_t pc87311_read(uint16_t addr, void *priv) { pc87311_t *dev = (pc87311_t *)priv; return dev->regs[dev->index]; } void pc87311_fdc_handler(pc87311_t *dev) { fdc_remove(dev->fdc_controller); fdc_set_base(dev->fdc_controller, (FUNCTION_ENABLE & 0x20) ? 0x0370 : 0x03f0); pc87311_log("PC87311-FDC: BASE %04x\n", (FUNCTION_ENABLE & 0x20) ? 0x0370 : 0x03f0); } uint16_t com3(pc87311_t *dev) { switch (COM_BA) { case 0: return 0x03e8; case 1: return 0x0338; case 2: return 0x02e8; case 3: return 0x0220; default: return 0x03e8; } } uint16_t com4(pc87311_t *dev) { switch (COM_BA) { case 0: return 0x02e8; case 1: return 0x0238; case 2: return 0x02e0; case 3: return 0x0228; default: return 0x02e8; } } void pc87311_uart_handler(uint8_t num, pc87311_t *dev) { serial_remove(dev->uart[num & 1]); switch (!(num & 1) ? UART1_BA : UART2_BA) { case 0: dev->base = 0x03f8; dev->irq = 4; break; case 1: dev->base = 0x02f8; dev->irq = 3; break; case 2: dev->base = com3(dev); dev->irq = 4; break; case 3: dev->base = com4(dev); dev->irq = 3; break; } serial_setup(dev->uart[num & 1], dev->base, dev->irq); pc87311_log("PC87311-UART%01x: BASE %04x IRQ %01x\n", num & 1, dev->base, dev->irq); } void pc87311_lpt_handler(pc87311_t *dev) { lpt1_remove(); switch (LPT_BA) { case 0: dev->base = 0x0378; dev->irq = (POWER_TEST & 0x08) ? 7 : 5; break; case 1: dev->base = 0x03bc; dev->irq = 7; break; case 2: dev->base = 0x0278; dev->irq = 5; break; } lpt1_init(dev->base); lpt1_irq(dev->irq); pc87311_log("PC87311-LPT: BASE %04x IRQ %01x\n", dev->base, dev->irq); } void pc87311_ide_handler(pc87311_t *dev) { ide_pri_disable(); ide_sec_disable(); ide_set_base(0, 0x1f0); ide_set_side(0, 0x3f6); ide_pri_enable(); if (FUNCTION_ENABLE & 0x80) { ide_set_base(1, 0x170); ide_set_side(1, 0x376); ide_sec_enable(); } pc87311_log("PC87311-IDE: PRI %01x SEC %01x\n", (FUNCTION_ENABLE >> 6) & 1, (FUNCTION_ENABLE >> 7) & 1); } void pc87311_enable(pc87311_t *dev) { (FUNCTION_ENABLE & 0x01) ? pc87311_lpt_handler(dev) : lpt1_remove(); (FUNCTION_ENABLE & 0x02) ? pc87311_uart_handler(0, dev) : serial_remove(dev->uart[0]); (FUNCTION_ENABLE & 0x04) ? pc87311_uart_handler(1, dev) : serial_remove(dev->uart[1]); (FUNCTION_ENABLE & 0x08) ? pc87311_fdc_handler(dev) : fdc_remove(dev->fdc_controller); if (FUNCTION_ENABLE & 0x20) pc87311_fdc_handler(dev); if (HAS_IDE_FUNCTIONALITY) { (FUNCTION_ENABLE & 0x40) ? pc87311_ide_handler(dev) : ide_pri_disable(); (FUNCTION_ADDRESS & 0x80) ? pc87311_ide_handler(dev) : ide_sec_disable(); } } static void pc87311_close(void *priv) { pc87311_t *dev = (pc87311_t *)priv; free(dev); } static void * pc87311_init(const device_t *info) { pc87311_t *dev = (pc87311_t *)malloc(sizeof(pc87311_t)); memset(dev, 0, sizeof(pc87311_t)); /* Avoid conflicting with machines that make no use of the PC87311 Internal IDE */ HAS_IDE_FUNCTIONALITY = info->local; dev->fdc_controller = device_add(&fdc_at_nsc_device); dev->uart[0] = device_add_inst(&ns16450_device, 1); dev->uart[1] = device_add_inst(&ns16450_device, 2); if (HAS_IDE_FUNCTIONALITY) device_add(&ide_isa_2ch_device); io_sethandler(0x0398, 0x0002, pc87311_read, NULL, NULL, pc87311_write, NULL, NULL, dev); io_sethandler(0x026e, 0x0002, pc87311_read, NULL, NULL, pc87311_write, NULL, NULL, dev); pc87311_enable(dev); return dev; } const device_t pc87311_device = { "National Semiconductor PC87311", "pc87311", 0, 0, pc87311_init, pc87311_close, NULL, {NULL}, NULL, NULL, NULL}; const device_t pc87311_ide_device = { "National Semiconductor PC87311 with IDE functionality", "pc87311_ide", 0, 1, pc87311_init, pc87311_close, NULL, {NULL}, NULL, NULL, NULL};