Files
86Box/src/video/vid_hercules_plus.c

782 lines
23 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.
*
* Hercules Plus emulation.
*
*
*
* Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
* Miran Grca, <mgrca8@gmail.com>
*
* Copyright 2008-2018 Sarah Walker.
* Copyright 2016-2025 Miran Grca.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/lpt.h>
#include <86box/pit.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/video.h>
#include <86box/plat_unused.h>
/* extended CRTC registers */
#define HERCULESPLUS_CRTC_XMODE 20 /* xMode register */
#define HERCULESPLUS_CRTC_UNDER 21 /* Underline */
#define HERCULESPLUS_CRTC_OVER 22 /* Overstrike */
/* character width */
#define HERCULESPLUS_CW ((dev->crtc[HERCULESPLUS_CRTC_XMODE] & HERCULESPLUS_XMODE_90COL) ? 8 : 9)
/* mode control register */
#define HERCULESPLUS_CTRL_GRAPH 0x02
#define HERCULESPLUS_CTRL_ENABLE 0x08
#define HERCULESPLUS_CTRL_BLINK 0x20
#define HERCULESPLUS_CTRL_PAGE1 0x80
/* CRTC status register */
#define HERCULESPLUS_STATUS_HSYNC 0x01 /* horizontal sync */
#define HERCULESPLUS_STATUS_LIGHT 0x02
#define HERCULESPLUS_STATUS_VIDEO 0x08
#define HERCULESPLUS_STATUS_ID 0x10 /* Card identification */
#define HERCULESPLUS_STATUS_VSYNC 0x80 /* -vertical sync */
/* configuration switch register */
#define HERCULESPLUS_CTRL2_GRAPH 0x01
#define HERCULESPLUS_CTRL2_PAGE1 0x02
/* extended mode register */
#define HERCULESPLUS_XMODE_RAMFONT 0x01
#define HERCULESPLUS_XMODE_90COL 0x02
typedef struct {
mem_mapping_t mapping;
uint8_t crtc[32];
int crtcreg;
uint8_t ctrl, ctrl2, status;
uint64_t dispontime, dispofftime;
pc_timer_t timer;
int firstline, lastline;
int linepos, displine;
int vc, scanline;
uint16_t memaddr, memaddr_backup;
int cursorvisible, cursoron;
int dispon, blink;
int vsynctime;
int vadj;
int monitor_index, prev_monitor_index;
int cols[256][2][2];
uint8_t *vram;
} herculesplus_t;
#define VIDEO_MONITOR_PROLOGUE() \
{ \
dev->prev_monitor_index = monitor_index_global; \
monitor_index_global = dev->monitor_index; \
}
#define VIDEO_MONITOR_EPILOGUE() \
{ \
monitor_index_global = dev->prev_monitor_index; \
}
static video_timings_t timing_herculesplus = { .type = VIDEO_ISA, .write_b = 8, .write_w = 16, .write_l = 32, .read_b = 8, .read_w = 16, .read_l = 32 };
static void
recalc_timings(herculesplus_t *dev)
{
double disptime;
double _dispontime;
double _dispofftime;
disptime = dev->crtc[0] + 1;
_dispontime = dev->crtc[1];
_dispofftime = disptime - _dispontime;
_dispontime *= HERCCONST;
_dispofftime *= HERCCONST;
dev->dispontime = (uint64_t) (_dispontime);
dev->dispofftime = (uint64_t) (_dispofftime);
}
static void
herculesplus_out(uint16_t port, uint8_t val, void *priv)
{
herculesplus_t *dev = (herculesplus_t *) priv;
uint8_t old;
switch (port) {
case 0x3b0:
case 0x3b2:
case 0x3b4:
case 0x3b6:
dev->crtcreg = val & 31;
return;
case 0x3b1:
case 0x3b3:
case 0x3b5:
case 0x3b7:
if (dev->crtcreg > 22)
return;
old = dev->crtc[dev->crtcreg];
dev->crtc[dev->crtcreg] = val;
if (dev->crtc[10] == 6 && dev->crtc[11] == 7) {
/*Fix for Generic Turbo XT BIOS,
*which sets up cursor registers wrong*/
dev->crtc[10] = 0xb;
dev->crtc[11] = 0xc;
}
if (old ^ val)
recalc_timings(dev);
return;
case 0x3b8:
old = dev->ctrl;
dev->ctrl = val;
if (old ^ val)
recalc_timings(dev);
return;
case 0x3bf:
dev->ctrl2 = val;
if (val & 2)
mem_mapping_set_addr(&dev->mapping, 0xb0000, 0x10000);
else
mem_mapping_set_addr(&dev->mapping, 0xb0000, 0x08000);
return;
default:
break;
}
}
static uint8_t
herculesplus_in(uint16_t port, void *priv)
{
const herculesplus_t *dev = (herculesplus_t *) priv;
uint8_t ret = 0xff;
switch (port) {
case 0x3b0:
case 0x3b2:
case 0x3b4:
case 0x3b6:
ret = dev->crtcreg;
break;
case 0x3b1:
case 0x3b3:
case 0x3b5:
case 0x3b7:
if (dev->crtcreg <= 22)
ret = dev->crtc[dev->crtcreg];
break;
case 0x3ba:
/* 0x10: Hercules Plus card identity */
ret = (dev->status & 0xf) | ((dev->status & 8) << 4) | 0x10;
break;
default:
break;
}
return ret;
}
static void
herculesplus_write(uint32_t addr, uint8_t val, void *priv)
{
herculesplus_t *dev = (herculesplus_t *) priv;
dev->vram[addr & 0xffff] = val;
}
static uint8_t
herculesplus_read(uint32_t addr, void *priv)
{
const herculesplus_t *dev = (herculesplus_t *) priv;
return dev->vram[addr & 0xffff];
}
static void
draw_char_rom(herculesplus_t *dev, int x, uint8_t chr, uint8_t attr)
{
unsigned ull;
unsigned val;
unsigned ifg;
unsigned ibg;
const uint8_t *fnt;
int elg;
int blk;
int cw = HERCULESPLUS_CW;
int blink = dev->ctrl & HERCULESPLUS_CTRL_BLINK;
blk = 0;
if (blink) {
if (attr & 0x80)
blk = (dev->blink & 16);
attr &= 0x7f;
}
/* MDA-compatible attributes */
ibg = 0;
ifg = 7;
if ((attr & 0x77) == 0x70) { /* Invert */
ifg = 0;
ibg = 7;
}
if (attr & 8)
ifg |= 8; /* High intensity FG */
if (attr & 0x80)
ibg |= 8; /* High intensity BG */
if ((attr & 0x77) == 0) /* Blank */
ifg = ibg;
ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
if (dev->crtc[HERCULESPLUS_CRTC_XMODE] & HERCULESPLUS_XMODE_90COL)
elg = 0;
else
elg = ((chr >= 0xc0) && (chr <= 0xdf));
fnt = &(fontdatm[chr][dev->scanline]);
if (blk) {
val = 0x000; /* Blinking, draw all background */
} else if (dev->scanline == ull) {
val = 0x1ff; /* Underscore, draw all foreground */
} else {
val = fnt[0] << 1;
if (elg)
val |= (val >> 1) & 1;
}
for (int i = 0; i < cw; i++) {
buffer32->line[dev->displine][x * cw + i] = (val & 0x100) ? ifg : ibg;
val = val << 1;
}
}
static void
draw_char_ram4(herculesplus_t *dev, int x, uint8_t chr, uint8_t attr)
{
unsigned ull;
unsigned val;
unsigned ifg;
unsigned ibg;
const uint8_t *fnt;
int elg;
int blk;
int cw = HERCULESPLUS_CW;
int blink = dev->ctrl & HERCULESPLUS_CTRL_BLINK;
blk = 0;
if (blink) {
if (attr & 0x80)
blk = (dev->blink & 16);
attr &= 0x7f;
}
/* MDA-compatible attributes */
ibg = 0;
ifg = 7;
if ((attr & 0x77) == 0x70) { /* Invert */
ifg = 0;
ibg = 7;
}
if (attr & 8)
ifg |= 8; /* High intensity FG */
if (attr & 0x80)
ibg |= 8; /* High intensity BG */
if ((attr & 0x77) == 0) /* Blank */
ifg = ibg;
ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
if (dev->crtc[HERCULESPLUS_CRTC_XMODE] & HERCULESPLUS_XMODE_90COL)
elg = 0;
else
elg = ((chr >= 0xc0) && (chr <= 0xdf));
fnt = dev->vram + 0x4000 + 16 * chr + dev->scanline;
if (blk) {
val = 0x000; /* Blinking, draw all background */
} else if (dev->scanline == ull) {
val = 0x1ff; /* Underscore, draw all foreground */
} else {
val = fnt[0] << 1;
if (elg)
val |= (val >> 1) & 1;
}
for (int i = 0; i < cw; i++) {
buffer32->line[dev->displine][x * cw + i] = (val & 0x100) ? ifg : ibg;
val = val << 1;
}
}
static void
draw_char_ram48(herculesplus_t *dev, int x, uint8_t chr, uint8_t attr)
{
unsigned ull;
unsigned val;
unsigned ifg;
unsigned ibg;
const uint8_t *fnt;
int elg;
int blk;
int cw = HERCULESPLUS_CW;
int blink = dev->ctrl & HERCULESPLUS_CTRL_BLINK;
int font = (attr & 0x0F);
if (font >= 12)
font &= 7;
attr = (attr >> 4) ^ 0x0f;
blk = 0;
if (blink) {
if (attr & 0x80)
blk = (dev->blink & 16);
attr &= 0x7f;
}
/* MDA-compatible attributes */
ibg = 0;
ifg = 7;
if ((attr & 0x77) == 0x70) { /* Invert */
ifg = 0;
ibg = 7;
}
if (attr & 8)
ifg |= 8; /* High intensity FG */
if (attr & 0x80)
ibg |= 8; /* High intensity BG */
if ((attr & 0x77) == 0) /* Blank */
ifg = ibg;
ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
if (dev->crtc[HERCULESPLUS_CRTC_XMODE] & HERCULESPLUS_XMODE_90COL)
elg = 0;
else
elg = ((chr >= 0xc0) && (chr <= 0xdf));
fnt = dev->vram + 0x4000 + 16 * chr + 4096 * font + dev->scanline;
if (blk) {
val = 0x000; /* Blinking, draw all background */
} else if (dev->scanline == ull) {
val = 0x1ff; /* Underscore, draw all foreground */
} else {
val = fnt[0] << 1;
if (elg)
val |= (val >> 1) & 1;
}
for (int i = 0; i < cw; i++) {
buffer32->line[dev->displine][x * cw + i] = (val & 0x100) ? ifg : ibg;
val = val << 1;
}
}
static void
text_line(herculesplus_t *dev, uint16_t cursoraddr)
{
int drawcursor;
uint8_t chr;
uint8_t attr;
uint32_t col;
for (uint8_t x = 0; x < dev->crtc[1]; x++) {
if (dev->ctrl & 8) {
chr = dev->vram[(dev->memaddr << 1) & 0x3fff];
attr = dev->vram[((dev->memaddr << 1) + 1) & 0x3fff];
} else
chr = attr = 0;
drawcursor = ((dev->memaddr == cursoraddr) && dev->cursorvisible && dev->cursoron);
switch (dev->crtc[HERCULESPLUS_CRTC_XMODE] & 5) {
case 0:
case 4: /* ROM font */
draw_char_rom(dev, x, chr, attr);
break;
case 1: /* 4k RAMfont */
draw_char_ram4(dev, x, chr, attr);
break;
case 5: /* 48k RAMfont */
draw_char_ram48(dev, x, chr, attr);
break;
default:
break;
}
++dev->memaddr;
if (drawcursor) {
int cw = HERCULESPLUS_CW;
col = dev->cols[attr][0][1];
for (int c = 0; c < cw; c++)
buffer32->line[dev->displine][x * cw + c] = col;
}
}
}
static void
graphics_line(herculesplus_t *dev)
{
uint16_t cursoraddr;
int c;
int plane = 0;
uint16_t val;
/* Graphics mode. */
cursoraddr = (dev->scanline & 3) * 0x2000;
if ((dev->ctrl & HERCULESPLUS_CTRL_PAGE1) && (dev->ctrl2 & HERCULESPLUS_CTRL2_PAGE1))
cursoraddr += 0x8000;
for (uint8_t x = 0; x < dev->crtc[1]; x++) {
if (dev->ctrl & 8)
val = (dev->vram[((dev->memaddr << 1) & 0x1fff) + cursoraddr + 0x10000 * plane] << 8)
| dev->vram[((dev->memaddr << 1) & 0x1fff) + cursoraddr + 0x10000 * plane + 1];
else
val = 0;
dev->memaddr++;
for (c = 0; c < 16; c++) {
buffer32->line[dev->displine][(x << 4) + c] = (val & 0x8000) ? 7 : 0;
val <<= 1;
}
for (c = 0; c < 16; c += 8)
video_blend((x << 4) + c, dev->displine);
}
}
static void
herculesplus_poll(void *priv)
{
herculesplus_t *dev = (herculesplus_t *) priv;
uint16_t cursoraddr = (dev->crtc[15] | (dev->crtc[14] << 8)) & 0x3fff;
int x;
int oldvc;
int scanline_old;
int cw = HERCULESPLUS_CW;
VIDEO_MONITOR_PROLOGUE();
if (!dev->linepos) {
timer_advance_u64(&dev->timer, dev->dispofftime);
dev->status |= 1;
dev->linepos = 1;
scanline_old = dev->scanline;
if ((dev->crtc[8] & 3) == 3)
dev->scanline = (dev->scanline << 1) & 7;
if (dev->dispon) {
if (dev->displine < dev->firstline) {
dev->firstline = dev->displine;
video_wait_for_buffer();
}
dev->lastline = dev->displine;
if ((dev->ctrl & HERCULESPLUS_CTRL_GRAPH) && (dev->ctrl2 & HERCULESPLUS_CTRL2_GRAPH))
graphics_line(dev);
else
text_line(dev, cursoraddr);
if ((dev->ctrl & HERCULESPLUS_CTRL_GRAPH) && (dev->ctrl2 & HERCULESPLUS_CTRL2_GRAPH))
x = dev->crtc[1] << 4;
else
x = dev->crtc[1] * cw;
video_process_8(x, dev->displine);
}
dev->scanline = scanline_old;
if (dev->vc == dev->crtc[7] && !dev->scanline)
dev->status |= 8;
dev->displine++;
if (dev->displine >= 500)
dev->displine = 0;
} else {
timer_advance_u64(&dev->timer, dev->dispontime);
if (dev->dispon)
dev->status &= ~1;
dev->linepos = 0;
if (dev->vsynctime) {
dev->vsynctime--;
if (!dev->vsynctime)
dev->status &= ~8;
}
if (dev->scanline == (dev->crtc[11] & 31) || ((dev->crtc[8] & 3) == 3 && dev->scanline == ((dev->crtc[11] & 31) >> 1))) {
dev->cursorvisible = 0;
}
if (dev->vadj) {
dev->scanline++;
dev->scanline &= 31;
dev->memaddr = dev->memaddr_backup;
dev->vadj--;
if (!dev->vadj) {
dev->dispon = 1;
dev->memaddr = dev->memaddr_backup = (dev->crtc[13] | (dev->crtc[12] << 8)) & 0x3fff;
dev->scanline = 0;
}
} else if (dev->scanline == dev->crtc[9] || ((dev->crtc[8] & 3) == 3 && dev->scanline == (dev->crtc[9] >> 1))) {
dev->memaddr_backup = dev->memaddr;
dev->scanline = 0;
oldvc = dev->vc;
dev->vc++;
dev->vc &= 127;
if (dev->vc == dev->crtc[6])
dev->dispon = 0;
if (oldvc == dev->crtc[4]) {
dev->vc = 0;
dev->vadj = dev->crtc[5];
if (!dev->vadj)
dev->dispon = 1;
if (!dev->vadj)
dev->memaddr = dev->memaddr_backup = (dev->crtc[13] | (dev->crtc[12] << 8)) & 0x3fff;
if ((dev->crtc[10] & 0x60) == 0x20)
dev->cursoron = 0;
else
dev->cursoron = dev->blink & 16;
}
if (dev->vc == dev->crtc[7]) {
dev->dispon = 0;
dev->displine = 0;
dev->vsynctime = 16;
if (dev->crtc[7]) {
if ((dev->ctrl & HERCULESPLUS_CTRL_GRAPH) && (dev->ctrl2 & HERCULESPLUS_CTRL2_GRAPH))
x = dev->crtc[1] << 4;
else
x = dev->crtc[1] * cw;
dev->lastline++;
if ((dev->ctrl & 8) && ((x != xsize) || ((dev->lastline - dev->firstline) != ysize) || video_force_resize_get())) {
xsize = x;
ysize = dev->lastline - dev->firstline;
if (xsize < 64)
xsize = 656;
if (ysize < 32)
ysize = 200;
set_screen_size(xsize, ysize);
if (video_force_resize_get())
video_force_resize_set(0);
}
video_blit_memtoscreen(0, dev->firstline, xsize, dev->lastline - dev->firstline);
frames++;
if ((dev->ctrl & HERCULESPLUS_CTRL_GRAPH) && (dev->ctrl2 & HERCULESPLUS_CTRL2_GRAPH)) {
video_res_x = dev->crtc[1] * 16;
video_res_y = dev->crtc[6] * 4;
video_bpp = 1;
} else {
video_res_x = dev->crtc[1];
video_res_y = dev->crtc[6];
video_bpp = 0;
}
}
dev->firstline = 1000;
dev->lastline = 0;
dev->blink++;
}
} else {
dev->scanline++;
dev->scanline &= 31;
dev->memaddr = dev->memaddr_backup;
}
if (dev->scanline == (dev->crtc[10] & 31) || ((dev->crtc[8] & 3) == 3 && dev->scanline == ((dev->crtc[10] & 31) >> 1)))
dev->cursorvisible = 1;
}
VIDEO_MONITOR_EPILOGUE();
}
static void *
herculesplus_init(UNUSED(const device_t *info))
{
herculesplus_t *dev;
dev = (herculesplus_t *) malloc(sizeof(herculesplus_t));
memset(dev, 0, sizeof(herculesplus_t));
dev->vram = (uint8_t *) malloc(0x10000); /* 64k VRAM */
dev->monitor_index = monitor_index_global;
switch(device_get_config_int("font")) {
case 0:
loadfont(FONT_IBM_MDA_437_PATH, 0);
break;
case 1:
loadfont(FONT_IBM_MDA_437_NORDIC_PATH, 0);
break;
case 2:
loadfont(FONT_KAM_PATH, 0);
break;
case 3:
loadfont(FONT_KAMCL16_PATH, 0);
break;
case 4:
loadfont(FONT_TULIP_DGA_PATH, 0);
break;
}
timer_add(&dev->timer, herculesplus_poll, dev, 1);
mem_mapping_add(&dev->mapping, 0xb0000, 0x08000,
herculesplus_read, NULL, NULL,
herculesplus_write, NULL, NULL,
dev->vram, MEM_MAPPING_EXTERNAL, dev);
io_sethandler(0x03b0, 16,
herculesplus_in, NULL, NULL, herculesplus_out, NULL, NULL, dev);
for (uint16_t c = 0; c < 256; c++) {
dev->cols[c][0][0] = dev->cols[c][1][0] = dev->cols[c][1][1] = 16;
if (c & 8)
dev->cols[c][0][1] = 15 + 16;
else
dev->cols[c][0][1] = 7 + 16;
}
dev->cols[0x70][0][1] = 16;
dev->cols[0x70][0][0] = dev->cols[0x70][1][0] = dev->cols[0x70][1][1] = 16 + 15;
dev->cols[0xF0][0][1] = 16;
dev->cols[0xF0][0][0] = dev->cols[0xF0][1][0] = dev->cols[0xF0][1][1] = 16 + 15;
dev->cols[0x78][0][1] = 16 + 7;
dev->cols[0x78][0][0] = dev->cols[0x78][1][0] = dev->cols[0x78][1][1] = 16 + 15;
dev->cols[0xF8][0][1] = 16 + 7;
dev->cols[0xF8][0][0] = dev->cols[0xF8][1][0] = dev->cols[0xF8][1][1] = 16 + 15;
dev->cols[0x00][0][1] = dev->cols[0x00][1][1] = 16;
dev->cols[0x08][0][1] = dev->cols[0x08][1][1] = 16;
dev->cols[0x80][0][1] = dev->cols[0x80][1][1] = 16;
dev->cols[0x88][0][1] = dev->cols[0x88][1][1] = 16;
herc_blend = device_get_config_int("blend");
cga_palette = device_get_config_int("rgb_type") << 1;
if (cga_palette > 6)
cga_palette = 0;
cgapal_rebuild();
video_inform(VIDEO_FLAG_TYPE_MDA, &timing_herculesplus);
/* Force the LPT3 port to be enabled. */
lpt3_setup(LPT_MDA_ADDR);
return dev;
}
static void
herculesplus_close(void *priv)
{
herculesplus_t *dev = (herculesplus_t *) priv;
if (!dev)
return;
if (dev->vram)
free(dev->vram);
free(dev);
}
static void
speed_changed(void *priv)
{
herculesplus_t *dev = (herculesplus_t *) priv;
recalc_timings(dev);
}
static const device_config_t herculesplus_config[] = {
// clang-format off
{
.name = "rgb_type",
.description = "Display type",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "Default", .value = 0 },
{ .description = "Green", .value = 1 },
{ .description = "Amber", .value = 2 },
{ .description = "Gray", .value = 3 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "font",
.description = "Font",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "US (CP 437)", .value = 0 },
{ .description = "IBM Nordic (CP 437-Nordic)", .value = 1 },
{ .description = "Czech Kamenicky (CP 895) #1", .value = 2 },
{ .description = "Czech Kamenicky (CP 895) #2", .value = 3 },
{ .description = "Tulip DGA", .value = 4 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "blend",
.description = "Blend",
.type = CONFIG_BINARY,
.default_string = NULL,
.default_int = 1,
.file_filter = NULL,
.spinner = { 0 },
.selection = { { 0 } },
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
const device_t herculesplus_device = {
.name = "Hercules Plus",
.internal_name = "hercules_plus",
.flags = DEVICE_ISA,
.local = 0,
.init = herculesplus_init,
.close = herculesplus_close,
.reset = NULL,
.available = NULL,
.speed_changed = speed_changed,
.force_redraw = NULL,
.config = herculesplus_config
};