644 lines
19 KiB
C
644 lines
19 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 ISA ROM card Expansions.
|
|
*
|
|
* Authors: Jasmine Iwanek, <jriwanek@gmail.com>
|
|
*
|
|
* Copyright 2025 Jasmine Iwanek.
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/io.h>
|
|
#include <86box/device.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/rom.h>
|
|
#include <86box/nvr.h>
|
|
#include <86box/isarom.h>
|
|
|
|
#define ISAROM_CARD 0
|
|
#define ISAROM_CARD_DUAL 1
|
|
#define ISAROM_CARD_QUAD 2
|
|
|
|
#ifdef ENABLE_ISAROM_LOG
|
|
int isarom_do_log = ENABLE_ISAROM_LOG;
|
|
|
|
static void
|
|
isarom_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (isarom_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define isarom_log(fmt, ...)
|
|
#endif
|
|
|
|
typedef struct isarom_t {
|
|
struct {
|
|
rom_t rom;
|
|
uint32_t addr;
|
|
const char *fn;
|
|
uint32_t size;
|
|
uint32_t len;
|
|
char nvr_path[64];
|
|
uint8_t wp;
|
|
} socket[4];
|
|
uint8_t inst;
|
|
uint8_t type;
|
|
} isarom_t;
|
|
|
|
static inline uint8_t
|
|
get_limit(uint8_t type)
|
|
{
|
|
if (type == ISAROM_CARD_DUAL)
|
|
return 2;
|
|
if (type == ISAROM_CARD_QUAD)
|
|
return 4;
|
|
return 1;
|
|
}
|
|
|
|
static inline void
|
|
isarom_save_nvr(char *path, uint8_t *data, size_t size)
|
|
{
|
|
if (path[0] == 0x00)
|
|
return;
|
|
|
|
FILE *fp = nvr_fopen(path, "wb");
|
|
if (fp) {
|
|
fwrite(data, 1, size, fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
void
|
|
isarom_close(void *priv)
|
|
{
|
|
isarom_t *dev = (isarom_t *) priv;
|
|
if (!priv)
|
|
return;
|
|
|
|
for (uint8_t i = 0; i < get_limit(dev->type); i++)
|
|
if (dev->socket[i].rom.rom) {
|
|
isarom_log("isarom[%u]: saving NVR for socket %u -> %s (%u bytes)\n",
|
|
dev->inst, i, dev->socket[i].nvr_path, dev->socket[i].size);
|
|
isarom_save_nvr(dev->socket[i].nvr_path, dev->socket[i].rom.rom, dev->socket[i].size);
|
|
}
|
|
|
|
free(dev);
|
|
}
|
|
|
|
static void *
|
|
isarom_init(const device_t *info)
|
|
{
|
|
isarom_t *dev = (isarom_t *) calloc(1, sizeof(isarom_t));
|
|
if (!dev)
|
|
return NULL;
|
|
|
|
dev->inst = device_get_instance();
|
|
dev->type = (uint8_t) info->local;
|
|
|
|
isarom_log("isarom[%u]: initializing device (type=%u)\n", dev->inst, dev->type);
|
|
|
|
for (uint8_t i = 0; i < get_limit(dev->type); i++) {
|
|
char key_fn[12];
|
|
char key_addr[14];
|
|
char key_size[14];
|
|
char key_writes[22];
|
|
char suffix[4] = "";
|
|
if (i > 0)
|
|
snprintf(suffix, sizeof(suffix), "%d", i + 1);
|
|
|
|
snprintf(key_fn, sizeof(key_fn), "bios_fn%s", suffix);
|
|
snprintf(key_addr, sizeof(key_addr), "bios_addr%s", suffix);
|
|
snprintf(key_size, sizeof(key_size), "bios_size%s", suffix);
|
|
snprintf(key_writes, sizeof(key_writes), "rom_writes_enabled%s", suffix);
|
|
|
|
dev->socket[i].fn = device_get_config_string(key_fn);
|
|
dev->socket[i].addr = device_get_config_hex20(key_addr);
|
|
dev->socket[i].size = device_get_config_int(key_size);
|
|
// Note: 2K is the smallest ROM I've found, but 86box's memory granularity is 4k, the number below is fine
|
|
// as we'll end up allocating no less than 4k due to the device config limits.
|
|
dev->socket[i].len = (dev->socket[i].size > 2048) ? dev->socket[i].size - 1 : 0;
|
|
dev->socket[i].wp = (uint8_t) device_get_config_int(key_writes) ? 1 : 0;
|
|
|
|
isarom_log("isarom[%u]: socket %u: addr=0x%05X size=%u wp=%u fn=%s\n",
|
|
dev->inst, i, dev->socket[i].addr, dev->socket[i].size,
|
|
dev->socket[i].wp, dev->socket[i].fn ? dev->socket[i].fn : "(null)");
|
|
|
|
if (dev->socket[i].addr != 0 && dev->socket[i].fn != NULL) {
|
|
rom_init(&dev->socket[i].rom,
|
|
dev->socket[i].fn,
|
|
dev->socket[i].addr,
|
|
dev->socket[i].size,
|
|
dev->socket[i].len,
|
|
0,
|
|
MEM_MAPPING_EXTERNAL);
|
|
|
|
isarom_log("isarom[%u]: ROM initialized for socket %u\n", dev->inst, i);
|
|
|
|
if (dev->socket[i].wp) {
|
|
mem_mapping_set_write_handler(&dev->socket[i].rom.mapping, rom_write, rom_writew, rom_writel);
|
|
snprintf(dev->socket[i].nvr_path, sizeof(dev->socket[i].nvr_path), "isarom_%i_%i.nvr", dev->inst, i + 1);
|
|
FILE *fp = nvr_fopen(dev->socket[i].nvr_path, "rb");
|
|
if (fp != NULL) {
|
|
fread(dev->socket[i].rom.rom, 1, dev->socket[i].size, fp);
|
|
fclose(fp);
|
|
isarom_log("isarom[%u]: loaded %zu bytes from %s\n", dev->inst, read_bytes, dev->socket[i].nvr_path);
|
|
} else
|
|
isarom_log("isarom[%u]: NVR not found, skipping load (%s)\n", dev->inst, dev->socket[i].nvr_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
#define BIOS_FILE_FILTER "ROM files (*.bin *.rom)|*.bin,*.rom"
|
|
|
|
#define BIOS_ADDR_SELECTION { \
|
|
{ "Disabled", 0x00000 }, \
|
|
{ "C000H", 0xc0000 }, \
|
|
{ "C200H", 0xc2000 }, \
|
|
{ "C400H", 0xc4000 }, \
|
|
{ "C600H", 0xc6000 }, \
|
|
{ "C800H", 0xc8000 }, \
|
|
{ "CA00H", 0xca000 }, \
|
|
{ "CC00H", 0xcc000 }, \
|
|
{ "CE00H", 0xce000 }, \
|
|
{ "D000H", 0xd0000 }, \
|
|
{ "D200H", 0xd2000 }, \
|
|
{ "D400H", 0xd4000 }, \
|
|
{ "D600H", 0xd6000 }, \
|
|
{ "D800H", 0xd8000 }, \
|
|
{ "DA00H", 0xda000 }, \
|
|
{ "DC00H", 0xdc000 }, \
|
|
{ "DE00H", 0xde000 }, \
|
|
{ "E000H", 0xe0000 }, \
|
|
{ "E200H", 0xe2000 }, \
|
|
{ "E400H", 0xe4000 }, \
|
|
{ "E600H", 0xe6000 }, \
|
|
{ "E800H", 0xe8000 }, \
|
|
{ "EA00H", 0xea000 }, \
|
|
{ "EC00H", 0xec000 }, \
|
|
{ "EE00H", 0xee000 }, \
|
|
{ "", 0 } \
|
|
}
|
|
|
|
#define BIOS_SIZE_SELECTION { \
|
|
{ "4K", 4096 }, \
|
|
{ "8K", 8192 }, \
|
|
{ "16K", 16384 }, \
|
|
{ "32K", 32768 }, \
|
|
{ "64K", 65536 }, \
|
|
{ "", 0 } \
|
|
}
|
|
|
|
// clang-format off
|
|
static const device_config_t isarom_config[] = {
|
|
{
|
|
.name = "bios_fn",
|
|
.description = "BIOS File",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr",
|
|
.description = "BIOS Address",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size",
|
|
.description = "BIOS Size:",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled",
|
|
.description = "Enable BIOS extension ROM Writes",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{ .name = "", .description = "", .type = CONFIG_END }
|
|
};
|
|
|
|
static const device_config_t isarom_dual_config[] = {
|
|
{
|
|
.name = "bios_fn",
|
|
.description = "BIOS File (ROM #1)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr",
|
|
.description = "BIOS Address (ROM #1)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size",
|
|
.description = "BIOS Size (ROM #1):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #1)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_fn2",
|
|
.description = "BIOS File (ROM #2)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr2",
|
|
.description = "BIOS Address (ROM #2)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size2",
|
|
.description = "BIOS Size (ROM #2):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled2",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #2)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{ .name = "", .description = "", .type = CONFIG_END }
|
|
};
|
|
|
|
static const device_config_t isarom_quad_config[] = {
|
|
{
|
|
.name = "bios_fn",
|
|
.description = "BIOS File (ROM #1)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr",
|
|
.description = "BIOS Address (ROM #1)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size",
|
|
.description = "BIOS Size (ROM #1):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #1)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_fn2",
|
|
.description = "BIOS File (ROM #2)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr2",
|
|
.description = "BIOS Address (ROM #2)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size2",
|
|
.description = "BIOS Size (ROM #2):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled2",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #2)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_fn3",
|
|
.description = "BIOS File (ROM #3)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = "ROM files (*.bin *.rom)|*.bin,*.rom",
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr3",
|
|
.description = "BIOS Address (ROM #3)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size3",
|
|
.description = "BIOS Size (ROM #3):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled3",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #3)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_fn4",
|
|
.description = "BIOS File (ROM #4)",
|
|
.type = CONFIG_FNAME,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = BIOS_FILE_FILTER,
|
|
.spinner = { 0 },
|
|
.selection = { },
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_addr4",
|
|
.description = "BIOS Address (ROM #4)",
|
|
.type = CONFIG_HEX20,
|
|
.default_string = NULL,
|
|
.default_int = 0x00000,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_ADDR_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "bios_size4",
|
|
.description = "BIOS Size (ROM #4):",
|
|
.type = CONFIG_INT,
|
|
.default_string = NULL,
|
|
.default_int = 8192,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = BIOS_SIZE_SELECTION,
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "rom_writes_enabled4",
|
|
.description = "Enable BIOS extension ROM Writes (ROM #4)",
|
|
.type = CONFIG_BINARY,
|
|
.default_string = NULL,
|
|
.default_int = 0,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{ .name = "", .description = "", .type = CONFIG_END }
|
|
};
|
|
// clang-format on
|
|
|
|
static const device_t isarom_device = {
|
|
.name = "Generic ISA ROM Board",
|
|
.internal_name = "isarom",
|
|
.flags = DEVICE_ISA,
|
|
.local = ISAROM_CARD,
|
|
.init = isarom_init,
|
|
.close = isarom_close,
|
|
.reset = NULL,
|
|
.available = NULL,
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = isarom_config
|
|
};
|
|
|
|
static const device_t isarom_dual_device = {
|
|
.name = "Generic Dual ISA ROM Board",
|
|
.internal_name = "isarom_dual",
|
|
.flags = DEVICE_ISA,
|
|
.local = ISAROM_CARD_DUAL,
|
|
.init = isarom_init,
|
|
.close = isarom_close,
|
|
.reset = NULL,
|
|
.available = NULL,
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = isarom_dual_config
|
|
};
|
|
|
|
static const device_t isarom_quad_device = {
|
|
.name = "Generic Quad ISA ROM Board",
|
|
.internal_name = "isarom_quad",
|
|
.flags = DEVICE_ISA,
|
|
.local = ISAROM_CARD_QUAD,
|
|
.init = isarom_init,
|
|
.close = isarom_close,
|
|
.reset = NULL,
|
|
.available = NULL,
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = isarom_quad_config
|
|
};
|
|
|
|
static const struct {
|
|
const device_t *dev;
|
|
} boards[] = {
|
|
// clang-format off
|
|
{ &device_none },
|
|
{ &isarom_device },
|
|
{ &isarom_dual_device },
|
|
{ &isarom_quad_device },
|
|
{ NULL }
|
|
// clang-format on
|
|
};
|
|
|
|
void
|
|
isarom_reset(void)
|
|
{
|
|
for (uint8_t i = 0; i < ISAROM_MAX; i++) {
|
|
if (isarom_type[i] == 0)
|
|
continue;
|
|
|
|
/* Add the device instance to the system. */
|
|
device_add_inst(boards[isarom_type[i]].dev, i + 1);
|
|
}
|
|
}
|
|
|
|
const char *
|
|
isarom_get_name(int board)
|
|
{
|
|
if (boards[board].dev == NULL)
|
|
return NULL;
|
|
|
|
return (boards[board].dev->name);
|
|
}
|
|
|
|
const char *
|
|
isarom_get_internal_name(int board)
|
|
{
|
|
return device_get_internal_name(boards[board].dev);
|
|
}
|
|
|
|
int
|
|
isarom_get_from_internal_name(const char *str)
|
|
{
|
|
int c = 0;
|
|
|
|
while (boards[c].dev != NULL) {
|
|
if (!strcmp(boards[c].dev->internal_name, str))
|
|
return c;
|
|
c++;
|
|
}
|
|
|
|
/* Not found. */
|
|
return 0;
|
|
}
|
|
|
|
const device_t *
|
|
isarom_get_device(int board)
|
|
{
|
|
/* Add the device instance to the system. */
|
|
return boards[board].dev;
|
|
}
|
|
|
|
int
|
|
isarom_has_config(int board)
|
|
{
|
|
if (boards[board].dev == NULL)
|
|
return 0;
|
|
|
|
return (boards[board].dev->config ? 1 : 0);
|
|
}
|