401 lines
11 KiB
C
401 lines
11 KiB
C
/*
|
|
* 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 Chips&Technology's 82C100 chipset.
|
|
*
|
|
*
|
|
*
|
|
* Authors: Miran Grca, <mgrca8@gmail.com>
|
|
*
|
|
* Copyright 2021 Miran Grca.
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/device.h>
|
|
#include "cpu.h"
|
|
#include "x86.h"
|
|
#include <86box/io.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/nmi.h>
|
|
#include <86box/plat_unused.h>
|
|
#include <86box/port_92.h>
|
|
#include <86box/rom.h>
|
|
#include <86box/chipset.h>
|
|
|
|
typedef struct ems_page_t {
|
|
int enabled;
|
|
uint32_t virt;
|
|
uint32_t phys;
|
|
} ems_page_t;
|
|
|
|
typedef struct ct_82c100_t {
|
|
uint8_t index;
|
|
uint8_t access;
|
|
uint16_t ems_io_base;
|
|
uint32_t ems_window_base;
|
|
uint8_t ems_page_regs[4];
|
|
uint8_t regs[256];
|
|
ems_page_t ems_pages[4];
|
|
mem_mapping_t ems_mappings[4];
|
|
} ct_82c100_t;
|
|
|
|
#ifdef ENABLE_CT_82C100_LOG
|
|
int ct_82c100_do_log = ENABLE_CT_82C100_LOG;
|
|
|
|
static void
|
|
ct_82c100_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (ct_82c100_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define ct_82c100_log(fmt, ...)
|
|
#endif
|
|
|
|
static void
|
|
ct_82c100_ems_pages_recalc(ct_82c100_t *dev)
|
|
{
|
|
uint32_t page_base;
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
page_base = dev->ems_window_base + (i << 14);
|
|
if ((i == 1) || (i == 2))
|
|
page_base ^= 0xc000;
|
|
if (dev->ems_page_regs[i] & 0x80) {
|
|
dev->ems_pages[i].virt = page_base;
|
|
dev->ems_pages[i].phys = 0xa0000 + (((uint32_t) (dev->ems_page_regs[i] & 0x7f)) << 14);
|
|
ct_82c100_log("Enabling EMS page %i: %08X-%08X -> %08X-%08X\n", i,
|
|
dev->ems_pages[i].virt, dev->ems_pages[i].virt + 0x00003fff,
|
|
dev->ems_pages[i].phys, dev->ems_pages[i].phys + 0x00003fff);
|
|
mem_mapping_set_addr(&(dev->ems_mappings[i]), dev->ems_pages[i].virt, 0x4000);
|
|
mem_mapping_set_exec(&(dev->ems_mappings[i]), &(ram[dev->ems_pages[i].phys]));
|
|
mem_set_mem_state_both(page_base, 0x00004000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
|
|
} else {
|
|
ct_82c100_log("Disabling EMS page %i\n", i);
|
|
mem_mapping_disable(&(dev->ems_mappings[i]));
|
|
mem_set_mem_state_both(page_base, 0x00004000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);
|
|
}
|
|
}
|
|
|
|
flushmmucache_nopc();
|
|
}
|
|
|
|
static void
|
|
ct_82c100_ems_out(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
|
|
ct_82c100_log("[%04X] dev->ems_page_regs[%i] = %02X\n", port, port >> 14, val);
|
|
dev->ems_page_regs[port >> 14] = val;
|
|
ct_82c100_ems_pages_recalc(dev);
|
|
}
|
|
|
|
static uint8_t
|
|
ct_82c100_ems_in(uint16_t port, void *priv)
|
|
{
|
|
const ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
ret = dev->ems_page_regs[port >> 14];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
ct_82c100_ems_update(ct_82c100_t *dev)
|
|
{
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
ct_82c100_log("Disabling EMS I/O handler %i at %04X\n", i, dev->ems_io_base + (i << 14));
|
|
io_handler(0, dev->ems_io_base + (i << 14), 1,
|
|
ct_82c100_ems_in, NULL, NULL, ct_82c100_ems_out, NULL, NULL, dev);
|
|
}
|
|
|
|
dev->ems_io_base = 0x0208 + (dev->regs[0x4c] & 0xf0);
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
ct_82c100_log("Enabling EMS I/O handler %i at %04X\n", i, dev->ems_io_base + (i << 14));
|
|
io_handler(1, dev->ems_io_base + (i << 14), 1,
|
|
ct_82c100_ems_in, NULL, NULL, ct_82c100_ems_out, NULL, NULL, dev);
|
|
}
|
|
|
|
dev->ems_window_base = 0xc0000 + (((uint32_t) (dev->regs[0x4c] & 0x0f)) << 14);
|
|
|
|
ct_82c100_ems_pages_recalc(dev);
|
|
}
|
|
|
|
static void
|
|
ct_82c100_reset(void *priv)
|
|
{
|
|
ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
|
|
ct_82c100_log("Reset\n");
|
|
|
|
memset(dev->regs, 0x00, sizeof(dev->regs));
|
|
memset(dev->ems_page_regs, 0x00, sizeof(dev->ems_page_regs));
|
|
|
|
dev->index = dev->access = 0x00;
|
|
|
|
/* INTERNAL CONFIGURATION/CONTROL REGISTERS */
|
|
dev->regs[0x40] = 0x01; /* Defaults to 8086/V30 mode. */
|
|
dev->regs[0x43] = 0x30;
|
|
dev->regs[0x48] = 0x01;
|
|
|
|
use_custom_nmi_vector = 0;
|
|
ct_82c100_ems_update(dev);
|
|
|
|
/* ADDITIONAL I/O REGISTERS */
|
|
}
|
|
|
|
static void
|
|
ct_82c100_out(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
|
|
if (port == 0x0022) {
|
|
dev->index = val;
|
|
dev->access = 1;
|
|
} else if (port == 0x0023) {
|
|
if (dev->access) {
|
|
switch (dev->index) {
|
|
/* INTERNAL CONFIGURATION/CONTROL REGISTERS */
|
|
case 0x40:
|
|
dev->regs[0x40] = val & 0xc7;
|
|
/* TODO: Clock stuff - needs CPU speed change functionality that's
|
|
going to be implemented in 86box v4.0.
|
|
Bit 0 is 0 for 8088/V20 and 1 for 8086/V30. */
|
|
break;
|
|
case 0x41:
|
|
dev->regs[0x41] = val & 0xed;
|
|
/* TODO: Where is the Software Reset Function that's enabled by
|
|
setting bit 6 to 1? */
|
|
break;
|
|
case 0x42:
|
|
dev->regs[0x42] = val & 0x01;
|
|
break;
|
|
case 0x43:
|
|
dev->regs[0x43] = val;
|
|
break;
|
|
case 0x44:
|
|
dev->regs[0x44] = val;
|
|
custom_nmi_vector = (custom_nmi_vector & 0xffffff00) | ((uint32_t) val);
|
|
break;
|
|
case 0x45:
|
|
dev->regs[0x45] = val;
|
|
custom_nmi_vector = (custom_nmi_vector & 0xffff00ff) | (((uint32_t) val) << 8);
|
|
break;
|
|
case 0x46:
|
|
dev->regs[0x46] = val;
|
|
custom_nmi_vector = (custom_nmi_vector & 0xff00ffff) | (((uint32_t) val) << 16);
|
|
break;
|
|
case 0x47:
|
|
dev->regs[0x47] = val;
|
|
custom_nmi_vector = (custom_nmi_vector & 0x00ffffff) | (((uint32_t) val) << 24);
|
|
break;
|
|
case 0x48:
|
|
case 0x49:
|
|
dev->regs[dev->index] = val;
|
|
break;
|
|
case 0x4b:
|
|
dev->regs[0x4b] = val;
|
|
use_custom_nmi_vector = !!(val & 0x40);
|
|
break;
|
|
case 0x4c:
|
|
ct_82c100_log("CS4C: %02X\n", val);
|
|
dev->regs[0x4c] = val;
|
|
ct_82c100_ems_update(dev);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
dev->access = 0;
|
|
}
|
|
} else if (port == 0x72)
|
|
dev->regs[0x72] = val & 0x7e;
|
|
else if (port == 0x7e)
|
|
dev->regs[0x7e] = val;
|
|
else if (port == 0x7f) {
|
|
/* Bit 3 is Software Controlled Reset, asserted if set. Will be
|
|
done in the feature/machine_and_kb branch using hardresetx86(). */
|
|
dev->regs[0x7f] = val;
|
|
if ((dev->regs[0x41] & 0x40) && (val & 0x08)) {
|
|
softresetx86();
|
|
cpu_set_edx();
|
|
ct_82c100_reset(dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint8_t
|
|
ct_82c100_in(uint16_t port, void *priv)
|
|
{
|
|
ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
if (port == 0x0022)
|
|
ret = dev->index;
|
|
else if (port == 0x0023) {
|
|
if (dev->access) {
|
|
switch (dev->index) {
|
|
/* INTERNAL CONFIGURATION/CONTROL REGISTERS */
|
|
case 0x40 ... 0x49:
|
|
case 0x4b:
|
|
case 0x4c:
|
|
ret = dev->regs[dev->index];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
dev->access = 0;
|
|
}
|
|
} else if (port == 0x72)
|
|
ret = dev->regs[0x72];
|
|
else if (port == 0x7e)
|
|
ret = dev->regs[0x7e];
|
|
else if (port == 0x7f)
|
|
ret = dev->regs[0x7f];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint8_t
|
|
mem_read_emsb(uint32_t addr, void *priv)
|
|
{
|
|
const ems_page_t *page = (ems_page_t *) priv;
|
|
uint8_t ret = 0xff;
|
|
#ifdef ENABLE_CT_82C100_LOG
|
|
uint32_t old_addr = addr;
|
|
#endif
|
|
|
|
addr = addr - page->virt + page->phys;
|
|
|
|
if (addr < (mem_size << 10))
|
|
ret = ram[addr];
|
|
|
|
ct_82c100_log("mem_read_emsb(%08X = %08X): %02X\n", old_addr, addr, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint16_t
|
|
mem_read_emsw(uint32_t addr, void *priv)
|
|
{
|
|
const ems_page_t *page = (ems_page_t *) priv;
|
|
uint16_t ret = 0xffff;
|
|
#ifdef ENABLE_CT_82C100_LOG
|
|
uint32_t old_addr = addr;
|
|
#endif
|
|
|
|
addr = addr - page->virt + page->phys;
|
|
|
|
if (addr < (mem_size << 10))
|
|
ret = *(uint16_t *) &ram[addr];
|
|
|
|
ct_82c100_log("mem_read_emsw(%08X = %08X): %04X\n", old_addr, addr, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
mem_write_emsb(uint32_t addr, uint8_t val, void *priv)
|
|
{
|
|
const ems_page_t *page = (ems_page_t *) priv;
|
|
#ifdef ENABLE_CT_82C100_LOG
|
|
uint32_t old_addr = addr;
|
|
#endif
|
|
|
|
addr = addr - page->virt + page->phys;
|
|
|
|
if (addr < (mem_size << 10))
|
|
ram[addr] = val;
|
|
|
|
ct_82c100_log("mem_write_emsb(%08X = %08X, %02X)\n", old_addr, addr, val);
|
|
}
|
|
|
|
static void
|
|
mem_write_emsw(uint32_t addr, uint16_t val, void *priv)
|
|
{
|
|
const ems_page_t *page = (ems_page_t *) priv;
|
|
#ifdef ENABLE_CT_82C100_LOG
|
|
uint32_t old_addr = addr;
|
|
#endif
|
|
|
|
addr = addr - page->virt + page->phys;
|
|
|
|
if (addr < (mem_size << 10))
|
|
*(uint16_t *) &ram[addr] = val;
|
|
|
|
ct_82c100_log("mem_write_emsw(%08X = %08X, %04X)\n", old_addr, addr, val);
|
|
}
|
|
|
|
static void
|
|
ct_82c100_close(void *priv)
|
|
{
|
|
ct_82c100_t *dev = (ct_82c100_t *) priv;
|
|
|
|
free(dev);
|
|
}
|
|
|
|
static void *
|
|
ct_82c100_init(UNUSED(const device_t *info))
|
|
{
|
|
ct_82c100_t *dev;
|
|
|
|
dev = (ct_82c100_t *) malloc(sizeof(ct_82c100_t));
|
|
memset(dev, 0x00, sizeof(ct_82c100_t));
|
|
|
|
ct_82c100_reset(dev);
|
|
|
|
io_sethandler(0x0022, 2,
|
|
ct_82c100_in, NULL, NULL, ct_82c100_out, NULL, NULL, dev);
|
|
io_sethandler(0x0072, 1,
|
|
ct_82c100_in, NULL, NULL, ct_82c100_out, NULL, NULL, dev);
|
|
io_sethandler(0x007e, 2,
|
|
ct_82c100_in, NULL, NULL, ct_82c100_out, NULL, NULL, dev);
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
mem_mapping_add(&(dev->ems_mappings[i]), (i + 28) << 14, 0x04000,
|
|
mem_read_emsb, mem_read_emsw, NULL,
|
|
mem_write_emsb, mem_write_emsw, NULL,
|
|
ram + 0xa0000 + (i << 14), MEM_MAPPING_INTERNAL, &dev->ems_pages[i]);
|
|
mem_mapping_disable(&(dev->ems_mappings[i]));
|
|
}
|
|
|
|
mem_mapping_disable(&ram_mid_mapping);
|
|
|
|
device_add(&port_92_device);
|
|
|
|
return dev;
|
|
}
|
|
|
|
const device_t ct_82c100_device = {
|
|
.name = "C&T 82C100",
|
|
.internal_name = "ct_82c100",
|
|
.flags = 0,
|
|
.local = 0,
|
|
.init = ct_82c100_init,
|
|
.close = ct_82c100_close,
|
|
.reset = ct_82c100_reset,
|
|
{ .available = NULL },
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = NULL
|
|
};
|