diff --git a/src/86box.c b/src/86box.c index cf992df4c..0f72ddef1 100644 --- a/src/86box.c +++ b/src/86box.c @@ -1870,6 +1870,27 @@ update_mouse_msg(void) (mouse_get_buttons() > 2) ? plat_get_string(STRING_MOUSE_RELEASE) : plat_get_string(STRING_MOUSE_RELEASE_MMB)); swprintf(mouse_msg[2], sizeof_w(mouse_msg[2]), L"%ls v%ls - %%i%%%% - %ls - %ls/%ls", EMU_NAME_W, EMU_VERSION_FULL_W, wmachine, wcpufamily, wcpu); +#else +#ifdef __APPLE__ + /* + * On macOS, BSD swprintf fails (returns -1) when the format string + * or a %ls argument contains non-ASCII wide characters (e.g. the + * native key symbols ⌘ U+2318, ⌫ U+232B) and the C locale is + * active. Store just the message suffixes here; the title update + * path in pc_render_monitor_dispatch() builds the full string + * without swprintf. + */ + wcsncpy(mouse_msg[0], plat_get_string(STRING_MOUSE_CAPTURE), sizeof_w(mouse_msg[0]) - 1); + mouse_msg[0][sizeof_w(mouse_msg[0]) - 1] = L'\0'; + + { + wchar_t *rel = (mouse_get_buttons() > 2) ? plat_get_string(STRING_MOUSE_RELEASE) + : plat_get_string(STRING_MOUSE_RELEASE_MMB); + wcsncpy(mouse_msg[1], rel, sizeof_w(mouse_msg[1]) - 1); + mouse_msg[1][sizeof_w(mouse_msg[1]) - 1] = L'\0'; + } + + mouse_msg[2][0] = L'\0'; #else swprintf(mouse_msg[0], sizeof_w(mouse_msg[0]), L"%%i%%%% - %ls", plat_get_string(STRING_MOUSE_CAPTURE)); @@ -1877,6 +1898,7 @@ update_mouse_msg(void) (mouse_get_buttons() > 2) ? plat_get_string(STRING_MOUSE_RELEASE) : plat_get_string(STRING_MOUSE_RELEASE_MMB)); wcsncpy(mouse_msg[2], L"%i%%", sizeof_w(mouse_msg[2])); #endif +#endif } void @@ -2009,11 +2031,20 @@ pc_run(void) else fps = ((fps + 20) / 50) * 50; #endif - swprintf(temp, sizeof_w(temp), mouse_msg[mouse_msg_idx], fps / (force_10ms ? 1 : 10)); #ifdef __APPLE__ + /* + * mouse_msg[] stores suffixes only on macOS (see update_mouse_msg). + * Build the title without passing non-ASCII chars through swprintf. + */ + swprintf(temp, sizeof_w(temp), L"%i%%", fps / (force_10ms ? 1 : 10)); + if (mouse_msg[mouse_msg_idx][0]) { + wcsncat(temp, L" - ", sizeof_w(temp) - wcslen(temp) - 1); + wcsncat(temp, mouse_msg[mouse_msg_idx], sizeof_w(temp) - wcslen(temp) - 1); + } /* Needed due to modifying the UI on the non-main thread is a big no-no. */ dispatch_async_f(dispatch_get_main_queue(), wcsdup((const wchar_t *) temp), _ui_window_title); #else + swprintf(temp, sizeof_w(temp), mouse_msg[mouse_msg_idx], fps / (force_10ms ? 1 : 10)); ui_window_title(temp); #endif title_update = 0; diff --git a/src/chipset/opti499.c b/src/chipset/opti499.c index 132754ac7..d9fc61907 100644 --- a/src/chipset/opti499.c +++ b/src/chipset/opti499.c @@ -84,7 +84,10 @@ opti499_recalc(opti499_t *dev) base = 0xd0000 + (i << 14); if ((dev->regs[0x22] & ((base >= 0xe0000) ? 0x20 : 0x40)) && (dev->regs[0x23] & (1 << i))) { - shflags = MEM_READ_INTERNAL; + if (dev->regs[0x2d] & (1 << ((i >> 1) + 2))) + shflags = MEM_READ_EXTANY; + else + shflags = MEM_READ_INTERNAL; shflags |= (dev->regs[0x22] & ((base >= 0xe0000) ? 0x08 : 0x10)) ? MEM_WRITE_DISABLED : MEM_WRITE_INTERNAL; } else { if (dev->regs[0x2d] & (1 << ((i >> 1) + 2))) diff --git a/src/chipset/vl82c480.c b/src/chipset/vl82c480.c index cf3682754..fccf8f1ab 100644 --- a/src/chipset/vl82c480.c +++ b/src/chipset/vl82c480.c @@ -12,11 +12,17 @@ * * Copyright 2020 Miran Grca. */ + +#ifdef ENABLE_VL82C48X_LOG +#include +#endif + #include #include #include #include #include +#define HAVE_STDARG_H #include <86box/86box.h> #include "cpu.h" #include <86box/timer.h> @@ -27,11 +33,31 @@ #include <86box/nmi.h> #include <86box/port_92.h> #include <86box/chipset.h> +#include <86box/log.h> + +#ifdef ENABLE_VL82C48X_LOG +int vl82c48x_do_log = ENABLE_VL82C48X_LOG; + +static void +vl82c48x_log(void *priv, const char *fmt, ...) +{ + if (vl82c48x_do_log) { + va_list ap; + va_start(ap, fmt); + log_out(priv, fmt, ap); + va_end(ap); + } +} +#else +# define vl82c48x_log(fmt, ...) +#endif typedef struct vl82c480_t { uint8_t idx; uint8_t regs[256]; uint32_t banks[4]; + + void * log; // New logging system } vl82c480_t; static int @@ -77,6 +103,14 @@ vl82c480_recalc_shadow(vl82c480_t *dev) } } + /* Implement ROMCS# disable portion of ROMMOV behavior */ + if ((dev->regs[0x11] == 0x00) && ((dev->regs[0x0c] & 0x20) || (dev->regs[0x0c] & 0x10))) + mem_set_mem_state(0xe0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + if (!(dev->regs[0x0f] & 0x0f) && !(dev->regs[0x0c] & 0x20)) + mem_set_mem_state(0xc0000, 0x8000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + if (!(dev->regs[0x0f] & 0xf0) && !((dev->regs[0x0c] & 0x30) == 0x30)) + mem_set_mem_state(0xc8000, 0x8000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + flushmmucache(); } @@ -116,6 +150,8 @@ vl82c480_write(uint16_t addr, uint8_t val, void *priv) { vl82c480_t *dev = (vl82c480_t *) priv; + vl82c48x_log(dev->log, "[%04X:%08X] VL82c48x: [W] %04X = %02X\n", CS, cpu_state.pc, addr, val); + switch (addr) { case 0xec: dev->idx = val; @@ -146,6 +182,10 @@ vl82c480_write(uint16_t addr, uint8_t val, void *priv) case 0x07: dev->regs[dev->idx] = (dev->regs[dev->idx] & 0x40) | (val & 0xbf); break; + case 0x0c: + dev->regs[dev->idx] = val; + vl82c480_recalc_shadow(dev); + break; case 0x0d ... 0x12: dev->regs[dev->idx] = val; vl82c480_recalc_shadow(dev); @@ -195,6 +235,8 @@ vl82c480_read(uint16_t addr, void *priv) break; } + vl82c48x_log(dev->log, "[%04X:%08X] VL82c48x: [R] %04X = %02X\n", CS, cpu_state.pc, addr, ret); + return ret; } @@ -203,6 +245,11 @@ vl82c480_close(void *priv) { vl82c480_t *dev = (vl82c480_t *) priv; + if (dev->log != NULL) { + log_close(dev->log); + dev->log = NULL; + } + free(dev); } @@ -217,6 +264,8 @@ vl82c480_init(const device_t *info) uint8_t min_j = (machines[machine].init == machine_at_monsoon_init) ? 2 : 2; uint8_t max_j = (machines[machine].init == machine_at_monsoon_init) ? 7 : 7; + dev->log = log_open("VL82c48x"); + dev->regs[0x00] = info->local; dev->regs[0x01] = 0xff; dev->regs[0x02] = 0x8a; diff --git a/src/chipset/vl82c59x.c b/src/chipset/vl82c59x.c index 30cc63065..d75556e76 100644 --- a/src/chipset/vl82c59x.c +++ b/src/chipset/vl82c59x.c @@ -12,7 +12,7 @@ * win2kgamer * * Copyright 2020-2025 Miran Grca. - * Copyright 2025 win2kgamer + * Copyright 2025-2026 win2kgamer */ #ifdef ENABLE_VL82C59X_LOG #include @@ -80,6 +80,9 @@ typedef struct vl82c59x_t { port_92_t *port_92; nvr_t *nvr; + uint32_t banks[4]; + uint8_t bankcfg[4]; + void * log; /* New logging system */ } vl82c59x_t; @@ -207,6 +210,52 @@ vl82c59x_set_pm_io(void *priv) } +void +vl82c59x_recalc_banks(vl82c59x_t *dev) +{ + uint32_t sizes[8] = { 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144 }; + uint8_t shifts[4] = { 0, 4, 0, 4 }; + uint8_t regs[4] = { 0x58, 0x58, 0x59, 0x59 }; + uint32_t total = 0; + + for (uint8_t i = 0; i < 4; i++) { + uint8_t shift = shifts[i]; + uint8_t reg = regs[i]; + uint8_t cfg = (dev->pci_conf[reg] >> shift) & 0x03; /* Module types */ + uint8_t dbl = ((dev->pci_conf[reg] >> shift) & 0x08) ? 1 : 0; /* Single/double sided */ + uint8_t conf = (cfg << 1) | (dbl & 0x01); + uint32_t size = sizes[conf]; + + vl82c59x_log(dev->log, "VL82c59x DRAM recalc: bank %i, cfg = %02X, dbl = %i, conf = %02X, size = %i, RAMCTL0 = %02X\n", i, cfg, dbl, conf, size, dev->pci_conf[0x5c]); + vl82c59x_log(dev->log, "VL82c59x DRAM recalc: bank %i size = %i\n", i, dev->banks[i]); + + /* Handle case where the BIOS programs RAMCFGx for dual-sided/ranked memory when single-sided memory is installed */ + if (((dev->bankcfg[i] & 0x08) == 0x00) && dbl == 1) { + size = sizes[conf - 1]; + vl82c59x_log(dev->log, "VL82c59x DRAM recalc: bank %i mismatch, bankcfg = %02X, reg = %02X\n", dev->bankcfg[i], dev->pci_conf[reg]); + } + + if (!(dev->pci_conf[0x5c] & (1 << i))) + size = 0; + + total += MIN(dev->banks[i], size); + vl82c59x_log(dev->log, "VL82c59x DRAM recalc: bank %i, total = %i\n", i, total); + } + + if (total > 1024) { + mem_mapping_set_addr(&ram_low_mapping, 0x00000000, 0x000a0000); + mem_mapping_set_addr(&ram_high_mapping, 0x00100000, (total - 1024) << 10); + } else { + if (total >= 1024) + mem_mapping_set_addr(&ram_low_mapping, 0x00000000, 0x000a0000); + else + mem_mapping_disable(&ram_low_mapping); + mem_mapping_disable(&ram_high_mapping); + } + + flushmmucache(); +} + static void vl82c59x_write(int func, int addr, UNUSED(int len), uint8_t val, void *priv) { @@ -231,12 +280,21 @@ vl82c59x_write(int func, int addr, UNUSED(int len), uint8_t val, void *priv) case 0x58: /* RAMCFG0 */ case 0x59: /* RAMCFG1 */ dev->pci_conf[addr] = val; + vl82c59x_log(dev->log, "VL82c59x: RAMCFGx write, RAMCFG0 = %02X, RAMCFG1 = %02X\n", dev->pci_conf[0x58], dev->pci_conf[0x59]); + if (machines[machine].init == machine_at_bravoms586_init) + vl82c59x_recalc_banks(dev); break; case 0x5a: /* Wildcat EDO RAM control */ if (dev->type == 0x01) dev->pci_conf[addr] = val; break; - case 0x5c ... 0x5e: /* RAMCTL0-RAMCTL2 */ + case 0x5c: /* RAMCTL0 */ + vl82c59x_log(dev->log, "VL82c59x: RAMCTL0 write\n"); + dev->pci_conf[addr] = val; + if (machines[machine].init == machine_at_bravoms586_init) + vl82c59x_recalc_banks(dev); + break; + case 0x5d ... 0x5e: /* RAMCTL1-RAMCTL2 */ case 0x5f ... 0x60: case 0x62: /* Apricot XEN-PC Ruby/Jade BIOS requires bit 2 to be set or */ @@ -483,6 +541,47 @@ vl82c59x_reset(void *priv) cpu_cache_int_enabled = 1; cpu_cache_ext_enabled = 1; cpu_update_waitstates(); + + /* Init DRAM bank registers */ + uint32_t sizes[8] = { 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144 }; + uint8_t reg_vals[8] = { 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b }; + uint32_t ms = mem_size; + + dev->pci_conf[0x58] = 0x00; /* RAMCFG0 */ + dev->pci_conf[0x59] = 0x00; /* RAMCFG1 */ + dev->pci_conf[0x5c] = 0x00; /* RAMCTL0 */ + + + if (ms > 0) for (uint8_t i = 0; i < 4; i++ ) { + for (uint8_t j = 0; j < 8; j++ ) { + if (ms >= sizes[j] ) { + dev->banks[i] = sizes[j]; + dev->bankcfg[i] = reg_vals[j]; + dev->pci_conf[0x5c] |= (1 << i); + } else + break; + } + + ms -= dev->banks[i]; + + if ((ms == 0 || dev->banks[i] == 0)) + break; + } + + for (uint8_t i = 0; i < 4; i++ ) { + if (dev->banks[0] != 0) + dev->pci_conf[0x58] = (dev->bankcfg[0] & 0x0f); + if (dev->banks[1] != 0) + dev->pci_conf[0x58] |= ((dev->bankcfg[1] & 0x0f) << 4); + if (dev->banks[2] != 0) + dev->pci_conf[0x59] = (dev->bankcfg[2] & 0x0f); + if (dev->banks[3] != 0) + dev->pci_conf[0x59] |= ((dev->bankcfg[3] & 0x0f) << 4); + } + + vl82c59x_log(dev->log, "VL82c59x: DRAM bank init, RAMCFG0 = %02X, RAMCFG1 = %02X, RAMCTL0 = %02X\n", dev->pci_conf[0x58], dev->pci_conf[0x59], dev->pci_conf[0x5c]); + vl82c59x_log(dev->log, "VL82c59x: DRAM bank vals: %i, %i, %i, %i\n", dev->banks[0], dev->banks[1], dev->banks[2], dev->banks[3]); + } static void diff --git a/src/config.c b/src/config.c index edd713755..ce8ed3671 100644 --- a/src/config.c +++ b/src/config.c @@ -1016,27 +1016,37 @@ load_image_file(char *dest, char *p, uint8_t *ui_wp) int ret = 0; char *slash = NULL; char *above = NULL; + char *above2 = NULL; char *use = NULL; if ((slash = memrmem(usr_path + strlen(usr_path) - 2, usr_path, "/")) != NULL) { slash++; above = (char *) calloc(1, slash - usr_path + 1); memcpy(above, usr_path, slash - usr_path); + + if ((slash = memrmem(above + strlen(above) - 2, above, "/")) != NULL) { + slash++; + above2 = (char *) calloc(1, slash - above + 1); + memcpy(above2, above, slash - above); + } } if (strstr(p, "wp://") == p) { - p += 5; - prefix = "wp://"; - if (ui_wp != NULL) - *ui_wp = 1; + p += 5; + prefix = "wp://"; + if (ui_wp != NULL) + *ui_wp = 1; } else if ((ui_wp != NULL) && *ui_wp) - prefix = "wp://"; + prefix = "wp://"; if (strstr(p, "ioctl://") == p) { - if (strlen(p) > (MAX_IMAGE_PATH_LEN - 11)) - ret = 1; - else - snprintf(dest, MAX_IMAGE_PATH_LEN, "%s", p); + if (strlen(p) > (MAX_IMAGE_PATH_LEN - 11)) + ret = 1; + else + snprintf(dest, MAX_IMAGE_PATH_LEN, "%s", p); + + if (above2 != NULL) + free(above2); if (above != NULL) free(above); @@ -1051,6 +1061,13 @@ load_image_file(char *dest, char *p, uint8_t *ui_wp) else snprintf(dest, MAX_IMAGE_PATH_LEN, "%s%s%s%s", prefix, exe_path, path_get_slash(exe_path), p + strlen("/")); + } else if (memcmp(p, "../../", strlen("../../")) == 0) { + use = (above2 == NULL) ? usr_path : above2; + if ((strlen(prefix) + strlen(use) + strlen(path_get_slash(use)) + strlen(p + strlen("../../"))) > + (MAX_IMAGE_PATH_LEN - 11)) + ret = 1; + else + snprintf(dest, MAX_IMAGE_PATH_LEN, "%s%s%s%s", prefix, use, path_get_slash(use), p + strlen("../../")); } else if (memcmp(p, "../", strlen("../")) == 0) { use = (above == NULL) ? usr_path : above; if ((strlen(prefix) + strlen(use) + strlen(path_get_slash(use)) + strlen(p + strlen("../"))) > @@ -1072,6 +1089,9 @@ load_image_file(char *dest, char *p, uint8_t *ui_wp) path_normalize(dest); + if (above2 != NULL) + free(above2); + if (above != NULL) free(above); @@ -3146,11 +3166,25 @@ save_image_file(char *cat, char *var, char *src) char *prefix = ""; char *slash = NULL; char *above = NULL; + char *above2 = NULL; + char *above3 = NULL; if ((slash = memrmem(usr_path + strlen(usr_path) - 2, usr_path, "/")) != NULL) { slash++; above = (char *) calloc(1, slash - usr_path + 1); memcpy(above, usr_path, slash - usr_path); + + if ((slash = memrmem(above + strlen(above) - 2, above, "/")) != NULL) { + slash++; + above2 = (char *) calloc(1, slash - above + 1); + memcpy(above2, above, slash - above); + + if ((slash = memrmem(above2 + strlen(above2) - 2, above2, "/")) != NULL) { + slash++; + above3 = (char *) calloc(1, slash - above2 + 1); + memcpy(above3, above2, slash - above2); + } + } } path_normalize(src); @@ -3164,7 +3198,11 @@ save_image_file(char *cat, char *var, char *src) sprintf(temp, "%s", src); else if (!strnicmp(src, usr_path, strlen(usr_path))) sprintf(temp, "%s%s", prefix, &src[strlen(usr_path)]); - else if ((above != NULL) && !strnicmp(src, above, strlen(above))) + /* Do not relativize to root. */ + else if ((above2 != NULL) && (above3 != NULL) && !strnicmp(src, above2, strlen(above2))) + sprintf(temp, "../../%s%s", prefix, &src[strlen(above2)]); + /* Do not relativize to root. */ + else if ((above != NULL) && (above2 != NULL) && !strnicmp(src, above, strlen(above))) sprintf(temp, "../%s%s", prefix, &src[strlen(above)]); else if (!strnicmp(src, exe_path, strlen(exe_path))) sprintf(temp, "/%s%s", prefix, &src[strlen(exe_path)]); @@ -3173,6 +3211,12 @@ save_image_file(char *cat, char *var, char *src) ini_section_set_string(cat, var, temp); + if (above3 != NULL) + free(above3); + + if (above2 != NULL) + free(above2); + if (above != NULL) free(above); } diff --git a/src/device/kbc_at.c b/src/device/kbc_at.c index d50dfb5d1..06824fe5e 100644 --- a/src/device/kbc_at.c +++ b/src/device/kbc_at.c @@ -2184,7 +2184,7 @@ read_p1(atkbc_t *dev) ----------------- IBM PS/1: xxxxxxxx IBM PS/2 MCA: xxxxx1xx - IBM PS/2 Model 30-286: xxxxx1xx + IBM PS/2 Model 30-286: x0xxx1xx Intel AMI Pentium BIOS'es with AMI MegaKey KB-5 keyboard controller: x1x1xxxx Acer: xxxxx0xx Packard Bell PB450: xxxxx1xx @@ -2199,7 +2199,8 @@ read_p1(atkbc_t *dev) Acer: Pull down bit 6 if primary display is MDA. Pull down bit 2 always (must be so to enable CMOS Setup). IBM PS/1: Pull down bit 6 if current floppy drive is 3.5". - IBM PS/2 Model 30-286: Pull down bits 5 and 4 based on planar memory size. + IBM PS/2 Model 30-286: Pull down bit 6 always (for 1.44M floppy). + Pull down bits 5 and 4 based on planar memory size. Epson Action Tower 2600: Pull down bit 3 always (for Epson logo). NCR: Pull down bit 5 always (power-on default speed = high). Pull down bit 3 if there is no FPU. @@ -2215,6 +2216,7 @@ read_p1(atkbc_t *dev) Bit 6: Mostly, display: 0 = CGA, 1 = MDA, inverted on Xi8088 and Acer KBC's; Intel AMI MegaKey KB-5: Used for green features, SMM handler expects it to be set; IBM PS/1 Model 2011: 0 = current FDD is 3.5", 1 = current FDD is 5.25"; + IBM PS/2 Model 30-286: 0 = drive A is 1.44M, 1 = drive A is 720k; Compaq: 0 = Compaq dual-scan display, 1 = non-Compaq display. Bit 5: Mostly, manufacturing jumper: 0 = installed (infinite loop at POST), 1 = not installed; NCR: power-on default speed: 0 = high, 1 = low; diff --git a/src/device/phoenix_486_jumper.c b/src/device/phoenix_486_jumper.c index ed4349082..b4300981e 100644 --- a/src/device/phoenix_486_jumper.c +++ b/src/device/phoenix_486_jumper.c @@ -52,6 +52,14 @@ Bit 0 = ???? */ +/* + Intel Monsoon bit meanings: + Bit 5 = Password enable + Bit 4 = Onboard video: 1 = disabled, 0 = enabled + Bit 3 = CMOS Setup enable + Bit 2 = CMOS clear: 1 = normal, 0 = clear CMOS +*/ + typedef struct phoenix_486_jumper_t { uint8_t type; uint8_t jumper; @@ -84,7 +92,11 @@ phoenix_486_jumper_write(UNUSED(uint16_t addr), uint8_t val, void *priv) dev->jumper = val & 0xbf; else if (dev->type == 2) /* PB600 */ dev->jumper = ((val & 0xbf) | 0x02); - else + else if (dev->type == 3) { /* Intel Monsoon */ + dev->jumper = ((val & 0xef) | 0x2c); + if (gfxcard[0] != 0x01) + dev->jumper |= 0x10; + } else dev->jumper = val; } @@ -106,7 +118,11 @@ phoenix_486_jumper_reset(void *priv) dev->jumper = 0x00; else if (dev->type == 2) /* PB600 */ dev->jumper = 0x02; - else { + else if (dev->type == 3) { /* Intel Monsoon */ + dev->jumper = 0x2c; + if (gfxcard[0] != 0x01) + dev->jumper |= 0x10; + } else { dev->jumper = 0x9f; if (gfxcard[0] != 0x01) dev->jumper |= 0x40; @@ -176,3 +192,18 @@ const device_t phoenix_486_jumper_pci_pb600_device = { .force_redraw = NULL, .config = NULL }; + +const device_t phoenix_486_jumper_monsoon_device = { + .name = "Phoenix 486 Jumper Readout (Monsoon)", + .internal_name = "phoenix_486_jumper_monsoon", + .flags = 0, + .local = 3, + .init = phoenix_486_jumper_init, + .close = phoenix_486_jumper_close, + .reset = phoenix_486_jumper_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + diff --git a/src/disk/hdc_xta_ps1.c b/src/disk/hdc_xta_ps1.c index 4a4273333..c37bf3186 100644 --- a/src/disk/hdc_xta_ps1.c +++ b/src/disk/hdc_xta_ps1.c @@ -381,6 +381,7 @@ typedef struct hdc_t { pc_timer_t timer; int8_t state; /* controller state */ int8_t reset; /* reset state counter */ + int8_t ready; /* ready state counter */ /* Data transfer. */ int16_t buf_idx; /* buffer index and pointer */ @@ -723,6 +724,15 @@ hdc_callback(void *priv) uint8_t cmd = ccb->cmd & 0x0f; #endif + /* If we are returning from a RESET, handle this first. */ + if (dev->reset) { + ps1_hdc_log("XTA reset.\n"); + dev->status &= ~ASR_BUSY; + dev->reset = 0; + do_finish(dev); + return; + } + /* Clear the SSB error bits. */ dev->ssb.track_0 = 0; dev->ssb.cylinder_err = 0; @@ -754,6 +764,12 @@ hdc_callback(void *priv) return; } + if (!(dev->ready | no_data)) { + /* Delay a bit, transfer not ready. */ + timer_advance_u64(&dev->timer, HDC_TIME); + return; + } + switch (dev->state) { case STATE_IDLE: /* Seek to cylinder if requested. */ @@ -937,9 +953,6 @@ do_send: break; case CMD_WRITE_VERIFY: - no_data = 1; - fallthrough; - case CMD_WRITE_SECTORS: if (!drive->present) { dev->ssb.not_ready = 1; @@ -947,6 +960,12 @@ do_send: return; } + if (!(dev->ready | no_data)) { + /* Delay a bit, transfer not ready. */ + timer_advance_u64(&dev->timer, HDC_TIME); + return; + } + switch (dev->state) { case STATE_IDLE: /* Seek to cylinder if requested. */ @@ -1231,24 +1250,21 @@ hdc_write(uint16_t port, uint8_t val, void *priv) if (val & ACR_INT_EN) set_intr(dev, 0); /* clear IRQ */ - if (dev->reset != 0) { - if (++dev->reset == 3) { - dev->reset = 0; - - set_intr(dev, 1); - } - break; + if (val & ACR_RESET) { + dev->reset = 1; + dev->status |= ASR_BUSY; + /* Schedule command execution. */ + timer_set_delay_u64(&dev->timer, HDC_TIME); } - if (val & ACR_RESET) - dev->reset = 1; break; case 4: /* ATTN */ dev->status &= ~ASR_INT_REQ; - if (val & ATT_DATA) { - /* Dunno. Start PIO/DMA now? */ - } + if (val & ATT_DATA) + dev->ready = 1; + else + dev->ready = 0; if (val & ATT_SSB) { if (dev->attn & ATT_CCB) { diff --git a/src/include/86box/chipset.h b/src/include/86box/chipset.h index 4ded831bd..6fa7881ca 100644 --- a/src/include/86box/chipset.h +++ b/src/include/86box/chipset.h @@ -234,6 +234,7 @@ extern const device_t nec_mate_unk_device; extern const device_t phoenix_486_jumper_device; extern const device_t phoenix_486_jumper_pci_device; extern const device_t phoenix_486_jumper_pci_pb600_device; +extern const device_t phoenix_486_jumper_monsoon_device; extern const device_t ast_readout_device; extern const device_t ast_nvr_device; diff --git a/src/include/86box/pci.h b/src/include/86box/pci.h index f5f5aee04..d07dff7eb 100644 --- a/src/include/86box/pci.h +++ b/src/include/86box/pci.h @@ -31,6 +31,43 @@ #define PCI_REG_LATENCY_TIMER 0x0d #define PCI_REG_HEADER_TYPE 0x0e #define PCI_REG_BIST 0x0f +#define PCI_REG_BAR0_BYTE0 0x10 +#define PCI_REG_BAR0_BYTE1 0x11 +#define PCI_REG_BAR0_BYTE2 0x12 +#define PCI_REG_BAR0_BYTE3 0x13 +#define PCI_REG_BAR1_BYTE0 0x14 +#define PCI_REG_BAR1_BYTE1 0x15 +#define PCI_REG_BAR1_BYTE2 0x16 +#define PCI_REG_BAR1_BYTE3 0x17 +#define PCI_REG_BAR2_BYTE0 0x18 +#define PCI_REG_BAR2_BYTE1 0x19 +#define PCI_REG_BAR2_BYTE2 0x1a +#define PCI_REG_BAR2_BYTE3 0x1b +#define PCI_REG_BAR3_BYTE0 0x1c +#define PCI_REG_BAR3_BYTE1 0x1d +#define PCI_REG_BAR3_BYTE2 0x1e +#define PCI_REG_BAR3_BYTE3 0x1e +#define PCI_REG_BAR4_BYTE0 0x20 +#define PCI_REG_BAR4_BYTE1 0x21 +#define PCI_REG_BAR4_BYTE2 0x22 +#define PCI_REG_BAR4_BYTE3 0x23 +#define PCI_REG_BAR5_BYTE0 0x24 +#define PCI_REG_BAR5_BYTE1 0x25 +#define PCI_REG_BAR5_BYTE2 0x26 +#define PCI_REG_BAR5_BYTE3 0x27 +#define PCI_REG_SUBVEN_ID_L 0x2c +#define PCI_REG_SUBVEN_ID_H 0x2d +#define PCI_REG_SUBSYS_ID_L 0x2e +#define PCI_REG_SUBSYS_ID_H 0x2f +#define PCI_REG_ROM_BAR_BYTE0 0x30 +#define PCI_REG_ROM_BAR_BYTE1 0x31 +#define PCI_REG_ROM_BAR_BYTE2 0x32 +#define PCI_REG_ROM_BAR_BYTE3 0x33 +#define PCI_REG_CAPS_PTR 0x34 +#define PCI_REG_INT_LINE 0x3c +#define PCI_REG_INT_PIN 0x3d +#define PCI_REG_MIN_GRANT 0x3e +#define PCI_REG_MAX_LAT 0x3f #define PCI_COMMAND_L_IO 0x01 #define PCI_COMMAND_L_MEM 0x02 diff --git a/src/include/86box/scsi_ncr5380.h b/src/include/86box/scsi_ncr5380.h index 6453340ae..151a9873c 100644 --- a/src/include/86box/scsi_ncr5380.h +++ b/src/include/86box/scsi_ncr5380.h @@ -84,6 +84,7 @@ typedef struct ncr_t { int (*dma_send_ext)(void *priv, void *ext_priv); int (*dma_initiator_receive_ext)(void *priv, void *ext_priv); void (*timer)(void *ext_priv, double period); + int (*irq_ena)(void *priv, void *ext_priv, int state); scsi_bus_t scsibus; } ncr_t; diff --git a/src/include/86box/scsi_qlogic.h b/src/include/86box/scsi_qlogic.h new file mode 100644 index 000000000..f3c0a695e --- /dev/null +++ b/src/include/86box/scsi_qlogic.h @@ -0,0 +1,19 @@ +/* + * 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. + * + * Emulation of QLogic QLA1x40/QLA1x80/QLA1x160 SCSI HBA. + * + * Authors: Dmitry Borisov, + * + * Copyright 2026 Dmitry Borisov + */ +#pragma once + +extern const device_t qla1040b_device; +extern const device_t qla1080_device; +extern const device_t qla1240_device; +extern const device_t qla1280_device; +extern const device_t qla12160a_device; diff --git a/src/include/86box/snd_sb.h b/src/include/86box/snd_sb.h index 51822f210..a5783d010 100644 --- a/src/include/86box/snd_sb.h +++ b/src/include/86box/snd_sb.h @@ -15,7 +15,7 @@ * * Copyright 2008-2018 Sarah Walker. * Copyright 2016-2018 Miran Grca. - * Copyright 2024-2025 Jasmine Iwanek. + * Copyright 2024-2026 Jasmine Iwanek. */ #ifndef SOUND_SND_SB_H #define SOUND_SND_SB_H @@ -28,12 +28,14 @@ enum { SADLIB = 1, /* No DSP */ - SB_DSP_105, /* DSP v1.05, Original CT1320 (Also known as CT1310) */ + SB_DSP_103, /* DSP v1.03, "killer card" prototype (Also known as CT1310) */ + SB_DSP_105, /* DSP v1.05, Original CT1320 */ SB_DSP_200, /* DSP v2.00 */ SB_DSP_201, /* DSP v2.01 - needed for high-speed DMA, Seen on CT1350B with CT1336 */ SB_DSP_202, /* DSP v2.02 - Seen on CT1350B with CT1336A */ SBPRO_DSP_300, /* DSP v3.00 */ - SBPRO2_DSP_302, /* DSP v3.02 + OPL3 */ + SBPRO_DSP_301, /* DSP v3.01 */ + SBPRO_DSP_302, /* DSP v3.02 */ SB16_DSP_404, /* DSP v4.05 + OPL3 */ SB16_DSP_405, /* DSP v4.05 + OPL3 */ SB16_DSP_406, /* DSP v4.06 + OPL3 */ diff --git a/src/include/86box/snd_sb_dsp.h b/src/include/86box/snd_sb_dsp.h index 200912c42..6e4df0eb1 100644 --- a/src/include/86box/snd_sb_dsp.h +++ b/src/include/86box/snd_sb_dsp.h @@ -9,8 +9,9 @@ #define SB_SUBTYPE_CLONE_AZT1605_0X0C 2 /* Aztech Sound Galaxy Nova 16 Extra / Packard Bell Forte 16, DSP 2.1 - SBPRO2 clone */ #define SB_SUBTYPE_CLONE_AZTPR16_0X09 3 /* Aztech Sound Galaxy Pro 16 Extra */ -#define SB_SUBTYPE_ESS_ES688 4 /* ESS Technology ES688 */ -#define SB_SUBTYPE_ESS_ES1688 5 /* ESS Technology ES1688 */ +#define SB_SUBTYPE_MVD201 4 /* Mediavision MVD201, found on the thunderboard and PAS16 */ +#define SB_SUBTYPE_ESS_ES688 5 /* ESS Technology ES688 */ +#define SB_SUBTYPE_ESS_ES1688 6 /* ESS Technology ES1688 */ /* ESS-related */ #define IS_ESS(dsp) ((dsp)->sb_subtype >= SB_SUBTYPE_ESS_ES688) /* Check for future ESS cards here */ @@ -20,6 +21,9 @@ #define IS_AZTECH(dsp) ((dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT2316A_0X11 || (dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT1605_0X0C || (dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) /* check for future AZT cards here */ #define AZTECH_EEPROM_SIZE 36 +/* MediaVision related */ +#define IS_MV201(dsp) ((dsp)->sb_subtype >= SB_SUBTYPE_MVD201) + typedef struct sb_dsp_t { int sb_type; int sb_subtype; /* which clone */ @@ -102,6 +106,7 @@ typedef struct sb_dsp_t { int sbreset; uint8_t sbreaddat; uint8_t sb_command; + uint8_t sb_last_command; uint8_t sb_test; int sb_timei; int sb_timeo; diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index d83d00a24..b69785353 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -228,6 +228,9 @@ extern const device_t entertainer_device; /* Mindscape Music Board */ extern const device_t mmb_device; +/* MediaVision ThunderBoard */ +extern const device_t thunderboard_device; + /* OPTi 82c93x */ extern const device_t acermagic_s20_device; extern const device_t mirosound_pcm10_device; diff --git a/src/include/86box/vid_8514a.h b/src/include/86box/vid_8514a.h index d0cd25547..f0c9fdf24 100644 --- a/src/include/86box/vid_8514a.h +++ b/src/include/86box/vid_8514a.h @@ -65,6 +65,7 @@ typedef union { typedef struct ibm8514_t { rom_t bios_rom; + rom_t bios_rom2; uint8_t *rom1; uint8_t *rom2; hwcursor8514_t hwcursor; @@ -187,6 +188,7 @@ typedef struct ibm8514_t { uint32_t dst_ge_offset; uint16_t src_pitch; uint16_t dst_pitch; + uint16_t read_pixel; int64_t cur_x_24bpp; int64_t cur_y_24bpp; int64_t dest_x_24bpp; diff --git a/src/include/86box/vid_voodoo_codegen_x86-64.h b/src/include/86box/vid_voodoo_codegen_x86-64.h index 0e43c15bd..a9701860c 100644 --- a/src/include/86box/vid_voodoo_codegen_x86-64.h +++ b/src/include/86box/vid_voodoo_codegen_x86-64.h @@ -1300,7 +1300,7 @@ voodoo_generate(uint8_t *code_block, voodoo_t *voodoo, voodoo_params_t *params, addbyte(0x35); /*XOR EAX, 0xff*/ addlong(0xff); } - addbyte(0x8e); /*ADD EAX, 1*/ + addbyte(0x83); /*ADD EAX, 1*/ addbyte(0xc0); addbyte(1); addbyte(0x0f); /*IMUL EAX, EBX*/ @@ -2327,7 +2327,7 @@ voodoo_generate(uint8_t *code_block, voodoo_t *voodoo, voodoo_params_t *params, addlong(offsetof(voodoo_state_t, z)); addbyte(0xc1); /*SHR EAX, 12*/ addbyte(0xe8); - addbyte(12); + addbyte(20); addbyte(0x25); /*AND EAX, 0xff*/ addlong(0xff); #if 0 diff --git a/src/include/86box/video.h b/src/include/86box/video.h index b7f86ac18..a83aa9371 100644 --- a/src/include/86box/video.h +++ b/src/include/86box/video.h @@ -375,6 +375,7 @@ extern const device_t gd5401_isa_device; extern const device_t gd5401_onboard_device; extern const device_t gd5402_isa_device; extern const device_t gd5402_onboard_device; +extern const device_t gd5402_onboard_commodore_device; extern const device_t gd5420_isa_device; extern const device_t gd5420_onboard_device; extern const device_t gd5422_isa_device; @@ -383,9 +384,11 @@ extern const device_t gd5424_onboard_device; extern const device_t gd5426_isa_device; extern const device_t gd5426_diamond_speedstar_pro_a1_isa_device; extern const device_t gd5426_vlb_device; +extern const device_t gd5426_onboard_isa_device; extern const device_t gd5426_onboard_device; extern const device_t gd5428_isa_device; extern const device_t gd5428_vlb_onboard_device; +extern const device_t gd5428_vlb_onboard_tandy_device; extern const device_t gd5428_vlb_device; extern const device_t gd5428_diamond_speedstar_pro_b1_vlb_device; extern const device_t gd5428_boca_isa_device; diff --git a/src/machine/m_at_386sx.c b/src/machine/m_at_386sx.c index 6b5c4414c..d996a14bc 100644 --- a/src/machine/m_at_386sx.c +++ b/src/machine/m_at_386sx.c @@ -586,7 +586,7 @@ machine_at_cmdsl386sx25_init(const machine_t *model) return ret; if (gfxcard[0] == VID_INTERNAL) - device_add(&gd5402_onboard_device); + device_add(&gd5402_onboard_commodore_device); machine_at_common_init_ex(model, 2); diff --git a/src/machine/m_at_socket1.c b/src/machine/m_at_socket1.c index 563227f2e..7bb69d368 100644 --- a/src/machine/m_at_socket1.c +++ b/src/machine/m_at_socket1.c @@ -294,11 +294,13 @@ machine_at_vect486vl_init(const machine_t *model) // has HDC problems int ret; ret = bios_load_linear("roms/machines/vect486vl/aa0500.ami", - 0x000e0000, 131072, 0); + 0x000c0000, 262144, 0); if (bios_only || !ret) return ret; + memcpy(&rom[0x00020000], rom, 131072); + if (gfxcard[0] == VID_INTERNAL) device_add(machine_get_vid_device(machine)); @@ -311,6 +313,15 @@ machine_at_vect486vl_init(const machine_t *model) // has HDC problems device_add(&ide_isa_device); device_add_params(&fdc37c6xx_device, (void *) (FDC37C651 | FDC37C6XX_IDE_PRI)); + video_reset(gfxcard[0]); + + if (gfxcard[0] != VID_INTERNAL) { + for (uint16_t i = 0; i < 32768; i++) + rom[i] = mem_readb_phys(0x000c0000 + i); + } + mem_mapping_set_addr(&bios_mapping, 0x0c0000, 0x40000); + mem_mapping_set_exec(&bios_mapping, rom); + return ret; } @@ -321,11 +332,13 @@ machine_at_d824_init(const machine_t *model) int ret; ret = bios_load_linear("roms/machines/d824/fts-biosupdated824noflashbiosepromv320-320334-160.bin", - 0x000e0000, 131072, 0); + 0x000c0000, 262144, 0); if (bios_only || !ret) return ret; + memcpy(&rom[0x00020000], rom, 131072); + if (gfxcard[0] == VID_INTERNAL) device_add(machine_get_vid_device(machine)); @@ -342,6 +355,15 @@ machine_at_d824_init(const machine_t *model) device_add(&ide_isa_device); device_add_params(&fdc37c6xx_device, (void *) FDC37C651); + video_reset(gfxcard[0]); + + if (gfxcard[0] != VID_INTERNAL) { + for (uint16_t i = 0; i < 32768; i++) + rom[i] = mem_readb_phys(0x000c0000 + i); + } + mem_mapping_set_addr(&bios_mapping, 0x0c0000, 0x40000); + mem_mapping_set_exec(&bios_mapping, rom); + return ret; } diff --git a/src/machine/m_at_socket2.c b/src/machine/m_at_socket2.c index 5c4a19f17..5b871d298 100644 --- a/src/machine/m_at_socket2.c +++ b/src/machine/m_at_socket2.c @@ -389,6 +389,7 @@ machine_at_monsoon_init(const machine_t *model) device_add_params(&fdc37c6xx_device, (void *) (FDC37C651 | FDC37C6XX_IDE_PRI)); device_add(&intel_flash_bxt_device); + device_add(&phoenix_486_jumper_monsoon_device); if (gfxcard[0] == VID_INTERNAL) device_add(machine_get_vid_device(machine)); diff --git a/src/machine/m_at_sockets7.c b/src/machine/m_at_sockets7.c index 550da59b9..725874a07 100644 --- a/src/machine/m_at_sockets7.c +++ b/src/machine/m_at_sockets7.c @@ -59,12 +59,12 @@ machine_at_p5a_init(const machine_t *model) pci_register_slot(0x0F, PCI_CARD_SOUTHBRIDGE_IDE, 1, 2, 3, 4); pci_register_slot(0x03, PCI_CARD_SOUTHBRIDGE_PMU, 1, 2, 3, 4); pci_register_slot(0x02, PCI_CARD_SOUTHBRIDGE_USB, 1, 2, 3, 4); - pci_register_slot(0x0C, PCI_CARD_NORMAL, 1, 2, 3, 4); - pci_register_slot(0x0B, PCI_CARD_NORMAL, 2, 3, 4, 1); - pci_register_slot(0x0A, PCI_CARD_NORMAL, 3, 4, 1, 2); pci_register_slot(0x09, PCI_CARD_NORMAL, 4, 1, 2, 3); + pci_register_slot(0x0A, PCI_CARD_NORMAL, 3, 4, 1, 2); + pci_register_slot(0x0B, PCI_CARD_NORMAL, 2, 3, 4, 1); + pci_register_slot(0x0C, PCI_CARD_NORMAL, 1, 2, 3, 4); pci_register_slot(0x0D, PCI_CARD_NORMAL, 4, 1, 2, 3); - pci_register_slot(0x06, PCI_CARD_NORMAL, 3, 4, 1, 2); + pci_register_slot(0x06, PCI_CARD_SOUND, 3, 4, 1, 2); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ diff --git a/src/machine/m_ps2_isa.c b/src/machine/m_ps2_isa.c index 64a8da2ed..ec3a53894 100644 --- a/src/machine/m_ps2_isa.c +++ b/src/machine/m_ps2_isa.c @@ -264,18 +264,18 @@ machine_ps2_isa_p1_handler(void) switch (mem_size / 1024) { case 0: /*256Kx2*/ - mem_p1 = 0xf0; + mem_p1 = 0xb0; break; case 1: /*256Kx4*/ - mem_p1 = 0xe0; + mem_p1 = 0xa0; break; case 2: /*1Mx2*/ case 3: - mem_p1 = 0xd0; + mem_p1 = 0x90; break; case 4: /*1Mx4*/ default: - mem_p1 = 0xc0; + mem_p1 = 0x80; break; } diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index 26e4dcda6..276653ffe 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -2167,7 +2167,7 @@ const machine_t machines[] = { .kbd_device = &keyboard_pc_xt_device, .fdc_device = NULL, .sio_device = NULL, - .vid_device = &cga_device, + .vid_device = &v6355d_device, .snd_device = NULL, .net_device = NULL }, @@ -5905,7 +5905,7 @@ const machine_t machines[] = { .kbd_device = NULL, .fdc_device = NULL, .sio_device = NULL, - .vid_device = &gd5402_onboard_device, + .vid_device = &gd5402_onboard_commodore_device, .snd_device = NULL, .net_device = NULL }, @@ -7779,7 +7779,7 @@ const machine_t machines[] = { .kbd_device = NULL, .fdc_device = NULL, .sio_device = NULL, /*Has SIO (sorta): VLSI VL82C113A SCAMP Combination I/O*/ - .vid_device = &gd5428_onboard_device, + .vid_device = &gd5426_onboard_isa_device, .snd_device = NULL, .net_device = NULL }, @@ -7893,7 +7893,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE | MACHINE_SOUND | MACHINE_GAMEPORT | MACHINE_APM, + .flags = MACHINE_IDE | MACHINE_SOUND | MACHINE_GAMEPORT, .ram = { .min = 2048, .max = 32768, @@ -8620,7 +8620,7 @@ const machine_t machines[] = { .kbd_device = NULL, .fdc_device = NULL, .sio_device = NULL, - .vid_device = &gd5428_vlb_onboard_device, + .vid_device = &gd5428_onboard_vlb_device, .snd_device = NULL, .net_device = NULL }, @@ -8670,7 +8670,7 @@ const machine_t machines[] = { }, /* Has a VLSI VL82C113A SCAMP Combination I/O which holds the KBC. */ { - .name = "[VLSI 82C486] Tandy Sensation! II (25-1651)/3100/MMPC", + .name = "[VLSI 82C486] Tandy Sensation! II (25-1651)", .internal_name = "sensation2", .type = MACHINE_TYPE_486_S2, .chipset = MACHINE_CHIPSET_VLSI_VL82C486, @@ -8690,7 +8690,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE | MACHINE_VIDEO | MACHINE_APM, + .flags = MACHINE_IDE | MACHINE_VIDEO, .ram = { .min = 2048, .max = 65536, @@ -8708,7 +8708,7 @@ const machine_t machines[] = { .kbd_device = NULL, .fdc_device = NULL, .sio_device = NULL, - .vid_device = &gd5428_vlb_onboard_device, + .vid_device = &gd5428_vlb_onboard_tandy_device, .snd_device = NULL, .net_device = NULL }, diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index b7d39109e..3a651b480 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -348,8 +348,9 @@ endif() if(WIN32) target_sources(plat PRIVATE win_cdrom_ioctl.c) +elseif(UNIX AND NOT APPLE) + target_sources(plat PRIVATE ../unix/linux_cdrom_ioctl.c) else() -# Replace with proper *nix and mac handler files once they are done. target_sources(plat PRIVATE dummy_cdrom_ioctl.c) endif() diff --git a/src/qt/icons/active.ico b/src/qt/icons/active.ico index ebd5bb452..999c94ee6 100644 Binary files a/src/qt/icons/active.ico and b/src/qt/icons/active.ico differ diff --git a/src/qt/languages/fr-FR.po b/src/qt/languages/fr-FR.po index 453b52de4..e8eee00c1 100644 --- a/src/qt/languages/fr-FR.po +++ b/src/qt/languages/fr-FR.po @@ -1006,7 +1006,7 @@ msgid "About %1" msgstr "À propos de %1" msgid "An emulator of old computers\n\nAuthors: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nWith previous core contributions from Sarah Walker, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." -msgstr "Un émulateur d'ordinateurs du passé\n\nAuteurs : Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nAvec les contributions de Sarah Walker, leilei, JohnElliott, greatpsycho et d'autres.\n\nLibéré sous la licence GNU General Public License version 2 ou ultérieure. Pour plus d'informations, voir le fichier LICENSE." +msgstr "Un émulateur d'ordinateurs du passé\n\nAuteurs : Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne et d'autres.\n\nAvec les contributions de Sarah Walker, leilei, JohnElliott, greatpsycho et d'autres.\n\nLibéré sous la licence GNU General Public License version 2 ou ultérieure. Pour plus d'informations, voir le fichier LICENSE." msgid "Hardware not available" msgstr "Matériel non disponible" diff --git a/src/qt/languages/nl-NL.po b/src/qt/languages/nl-NL.po index e48409dcd..fced0fe84 100644 --- a/src/qt/languages/nl-NL.po +++ b/src/qt/languages/nl-NL.po @@ -55,10 +55,10 @@ msgid "&Resizeable window" msgstr "&Venster met aanpasbare grootte" msgid "R&emember size && position" -msgstr "&Onthoud grootte && positie" +msgstr "&Onthoud grootte en positie" msgid "Remember size && position" -msgstr "Onthoud grootte && positie" +msgstr "Onthoud grootte en positie" msgid "Re&nderer" msgstr "Re&nderer" @@ -73,7 +73,7 @@ msgid "&VNC" msgstr "&VNC" msgid "Specify &dimensions…" -msgstr "Afmetingen opgeven…" +msgstr "&Afmetingen opgeven…" msgid "Force &4:3 display ratio" msgstr "Forceer &4:3 beeldverhouding" @@ -154,7 +154,7 @@ msgid "VGA screen &type" msgstr "VGA-scherm &type" msgid "RGB &Color" -msgstr "RGB &Kleur" +msgstr "RGB &kleur" msgid "RGB (no brown)" msgstr "RGN (geen bruin)" @@ -421,7 +421,7 @@ msgid "IBM 8514/A Graphics" msgstr "IBM 8514/A-graphics" msgid "XGA Graphics" -msgstr "XGA Graphics" +msgstr "XGA-graphics" msgid "IBM PS/55 Display Adapter Graphics" msgstr "IBM PS/55 Display Adapter Graphics" @@ -469,13 +469,13 @@ msgid "Sound card #4:" msgstr "Geluidskaart #4:" msgid "MIDI Out Device:" -msgstr "MIDI Uitvoerapparaat:" +msgstr "MIDI uitvoerapparaat:" msgid "MIDI In Device:" -msgstr "MIDI Invoerapparaat:" +msgstr "MIDI invoerapparaat:" msgid "MIDI Out:" -msgstr "Midi Uit:" +msgstr "Midi uit:" msgid "Standalone MPU-401" msgstr "Standalone MPU-401" @@ -580,7 +580,7 @@ msgid "Hard disks:" msgstr "Harde schijven:" msgid "Firmware Version" -msgstr "Firmware Versie" +msgstr "Firmware versie" msgid "&New…" msgstr "&Nieuw…" @@ -673,10 +673,10 @@ msgid "Generic ISA ROM Board" msgstr "Generieke ISA ROM-kaart" msgid "Generic Dual ISA ROM Board" -msgstr "Generieke Dubbele ISA ROM-kaart" +msgstr "Generieke dubbele ISA ROM-kaart" msgid "Generic Quad ISA ROM Board" -msgstr "Generieke Viervoudige ISA ROM-kaart" +msgstr "Generieke viervoudige ISA ROM-kaart" msgid "ISABugger device" msgstr "ISABugger-apparaat" @@ -1006,7 +1006,7 @@ msgid "About %1" msgstr "Over %1" msgid "An emulator of old computers\n\nAuthors: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nWith previous core contributions from Sarah Walker, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." -msgstr "Een emulator van oude computers\n\nAuteurs: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nMet eerdere bijdragen van Sarah Walker, leilei, JohnElliott, greatpsycho en anderen.\n\nUitgebracht onder de GNU General Public License versie 2 of later. Zie LICENSE voor meer informatie." +msgstr "Een emulator van oude computers\n\nAuteurs: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, en anderen.\n\nMet eerdere bijdragen van Sarah Walker, leilei, JohnElliott, greatpsycho en anderen.\n\nUitgebracht onder de GNU General Public License versie 2 of later. Zie LICENSE voor meer informatie." msgid "Hardware not available" msgstr "Hardware niet beschikbaar" @@ -1312,7 +1312,7 @@ msgid "An error has occurred while checking for updates: %1" msgstr "Er is een fout opgetreden tijdens het controleren op updates: %1" msgid "An update to 86Box is available!" -msgstr " Een update van 86Box is beschikbaar!" +msgstr "Een update van 86Box is beschikbaar!" msgid "Warning" msgstr "Waarschuwing" @@ -2065,7 +2065,7 @@ msgid "Dynamic Sample Loading" msgstr "Dynamische sample-lading" msgid "Reverb Output Gain" -msgstr "Reverbuitgang Versterking" +msgstr "Reverbuitgang versterking" msgid "Reversed stereo" msgstr "Omgekeerde stereo" @@ -2362,7 +2362,7 @@ msgid "Always at selected speed" msgstr "Altijd op geselecteerde snelheid" msgid "BIOS setting + Hotkeys (off during POST)" -msgstr "BIOS-instelling + Sneltoetsen (niet actief tijdens POST)" +msgstr "BIOS-instelling + sneltoetsen (niet actief tijdens POST)" msgid "64 KB starting from F0000" msgstr "64 KB vanaf F0000" @@ -2401,16 +2401,16 @@ msgid "Wheel" msgstr "Wiel" msgid "Five + Wheel" -msgstr "Vijf + Wiel" +msgstr "Vijf + wiel" msgid "Five + 2 Wheels" -msgstr "Vijf + 2 Wielen" +msgstr "Vijf + 2 wielen" msgid "A3 - SMT2 Serial / SMT3(R)V" -msgstr "A3 - SMT2 Serieel / SMT3(R)V" +msgstr "A3 - SMT2 serieel / SMT3(R)V" msgid "Q1 - SMT3(R) Serial" -msgstr "Q1 - SMT3(R) Serieel" +msgstr "Q1 - SMT3(R) serieel" msgid "8 KB" msgstr "8 KB" @@ -2482,7 +2482,7 @@ msgid "Green Monochrome" msgstr "Groen monochroom" msgid "Amber Monochrome" -msgstr "Amber Monochroom" +msgstr "Amber monochroom" msgid "Gray Monochrome" msgstr "Grijs monochroom" @@ -2551,7 +2551,7 @@ msgid "Color" msgstr "Kleur" msgid "U.S. English" -msgstr "Amerikaans Engels" +msgstr "Amerikaans engels" msgid "Scandinavian" msgstr "Scandinavisch" @@ -2566,16 +2566,16 @@ msgid "Apply overscan deltas" msgstr "Overscan-delta’s toepassen" msgid "Mono Interlaced" -msgstr "Mono Interlaced" +msgstr "Mono interlaced" msgid "Mono Non-Interlaced" -msgstr "Mono Niet-Interlaced" +msgstr "Mono niet-interlaced" msgid "Color Interlaced" -msgstr "Kleur Interlaced" +msgstr "Kleur interlaced" msgid "Color Non-Interlaced" -msgstr "Kleur Niet-Interlaced" +msgstr "Kleur niet-interlaced" msgid "3Dfx Voodoo Graphics" msgstr "3Dfx Voodoo Graphics" @@ -2635,7 +2635,7 @@ msgid "Passthrough Mode" msgstr "Doorgeefmodus" msgid "Host Serial Device" -msgstr "Host Serieel Apparaat" +msgstr "Host serieel apparaat" msgid "Name of pipe" msgstr "Naam van de pipe" @@ -2650,10 +2650,10 @@ msgid "Baud Rate of Passthrough" msgstr "Baud-snelheid van doorvoer" msgid "Named Pipe (Server)" -msgstr "Named Pipe (Server)" +msgstr "Genoemd pipe (server)" msgid "Named Pipe (Client)" -msgstr "Named Pipe (Client)" +msgstr "Genoemd pipe (client)" msgid "Host Serial Passthrough" msgstr "Host seriële doorgave" @@ -2833,10 +2833,10 @@ msgid "SoundFont files" msgstr "SoundFont-bestanden" msgid "Local Switch" -msgstr "Lokale Switch" +msgstr "Lokale-switch" msgid "Remote Switch" -msgstr "Externe Switch" +msgstr "Externe-switch" msgid "Shared secret:" msgstr "Gedeeld geheim:" @@ -2869,16 +2869,16 @@ msgid "An error occurred trying to wipe the NVRAM contents of the virtual machin msgstr "Fout bij Wissen van NVRAM-inhoud van de virtuele machine \"%1\"" msgid "%1 VM Manager" -msgstr "%1 VM Beheerder" +msgstr "%1 VM-beheerder" msgid "%n disk(s)" msgstr "%n schijf/schijven" msgid "Unknown Status" -msgstr "Onbekende Status" +msgstr "Onbekende status" msgid "No Machines Found!" -msgstr "Geen Machines Gevonden!" +msgstr "Geen machines gevonden!" msgid "Check for updates on startup" msgstr "Controleer op updates bij starten" @@ -2932,7 +2932,7 @@ msgid "Checking for updates…" msgstr "Controleren op updates…" msgid "86Box Update" -msgstr "86Box Update" +msgstr "86Box-update" msgid "Release notes:" msgstr "Release-opmerkingen:" diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index c9250f44c..cdff36469 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -20,6 +20,7 @@ #include "qt_machinestatus.hpp" #include +#include #include #include #include @@ -176,7 +177,15 @@ MediaMenu::refresh(QMenu *parentMenu) menu->addAction(QIcon(":/settings/qt/icons/cdrom_host.ico"), tr("&Host CD/DVD Drive (%1:)").arg(letter), [this, i, letter] { cdromMount(i, 2, QString(R"(\\.\%1:)").arg(letter)); })->setCheckable(false); } menu->addSeparator(); -#endif // Q_OS_WINDOWS +#elif defined(Q_OS_LINUX) + /* Enumerate Linux CD/DVD drives (/dev/sr0 .. /dev/sr15). */ + for (int sr = 0; sr < 16; sr++) { + QString devPath = QString("/dev/sr%1").arg(sr); + if (QFile::exists(devPath)) + menu->addAction(QIcon(":/settings/qt/icons/cdrom_host.ico"), tr("&Host CD/DVD Drive (%1)").arg(devPath), [this, i, devPath] { cdromMount(i, 2, devPath); })->setCheckable(false); + } + menu->addSeparator(); +#endif cdromEjectPos = menu->children().count(); menu->addAction(tr("E&ject"), [this, i]() { cdromEject(i); })->setCheckable(false); cdromMenus[i] = menu; diff --git a/src/qt/qt_openglrenderer.cpp b/src/qt/qt_openglrenderer.cpp index 788a74744..56bf51479 100644 --- a/src/qt/qt_openglrenderer.cpp +++ b/src/qt/qt_openglrenderer.cpp @@ -1726,7 +1726,8 @@ OpenGLRenderer::render() glw.glFinish(); glw.glReadPixels(window_rect.x, window_rect.y, width, height, GL_RGB, GL_UNSIGNED_BYTE, rgb); - QImage image((uchar*)rgb, width, height, width * 3, QImage::Format_RGB888); + int pitch_adj = (4 - ((width * 3) & 3)) & 3; + QImage image((uchar*)rgb, width, height, (width * 3) + pitch_adj, QImage::Format_RGB888); image.mirrored(false, true).save(path, "png"); monitors[r_monitor_index].mon_screenshots--; free(rgb); @@ -1739,7 +1740,8 @@ OpenGLRenderer::render() glw.glFinish(); glw.glReadPixels(window_rect.x, window_rect.y, width, height, GL_RGB, GL_UNSIGNED_BYTE, rgb); - QImage image((uchar*)rgb, width, height, width * 3, QImage::Format_RGB888); + int pitch_adj = (4 - ((width * 3) & 3)) & 3; + QImage image((uchar*)rgb, width, height, (width * 3) + pitch_adj, QImage::Format_RGB888); QClipboard *clipboard = QApplication::clipboard(); clipboard->setImage(image.mirrored(false, true), QClipboard::Clipboard); monitors[r_monitor_index].mon_screenshots_clipboard--; diff --git a/src/scsi/CMakeLists.txt b/src/scsi/CMakeLists.txt index c710f758d..63bf9c225 100644 --- a/src/scsi/CMakeLists.txt +++ b/src/scsi/CMakeLists.txt @@ -28,5 +28,6 @@ add_library(scsi OBJECT scsi_t128.c scsi_ncr53c8xx.c scsi_pcscsi.c + scsi_ql1xxx.c scsi_spock.c ) diff --git a/src/scsi/scsi.c b/src/scsi/scsi.c index 36e1d0638..89ea9fc29 100644 --- a/src/scsi/scsi.c +++ b/src/scsi/scsi.c @@ -38,6 +38,7 @@ #include <86box/scsi_ncr5380.h> #include <86box/scsi_ncr53c8xx.h> #include <86box/scsi_pcscsi.h> +#include <86box/scsi_qlogic.h> #include <86box/scsi_spock.h> int scsi_card_current[SCSI_CARD_MAX] = { 0, 0, 0, 0 }; @@ -91,6 +92,11 @@ static SCSI_CARD scsi_cards[] = { { &ncr53c860_pci_device, }, { &ncr53c875_pci_device, }, { &dc390_pci_device, }, + { &qla1040b_device, }, + { &qla1080_device, }, + { &qla1240_device, }, + { &qla1280_device, }, + { &qla12160a_device, }, { NULL, }, // clang-format on }; diff --git a/src/scsi/scsi_ncr5380.c b/src/scsi/scsi_ncr5380.c index 102e637c4..2a29b070d 100644 --- a/src/scsi/scsi_ncr5380.c +++ b/src/scsi/scsi_ncr5380.c @@ -62,13 +62,9 @@ void ncr5380_irq(ncr_t *ncr, int set_irq) { if (set_irq) { - ncr->irq_state = 1; - ncr->isr |= STATUS_INT; if (ncr->irq != -1) picint(1 << ncr->irq); } else { - ncr->irq_state = 0; - ncr->isr &= ~STATUS_INT; if (ncr->irq != 1) picintc(1 << ncr->irq); } @@ -104,7 +100,12 @@ ncr5380_reset(ncr_t *ncr) scsi_bus->data = 0; scsi_bus->command_issued = 0; - ncr5380_irq(ncr, 0); + if (ncr->irq_ena) + ncr->irq_ena(ncr, ncr->priv, 0); + else + ncr5380_irq(ncr, 0); + + ncr->isr &= ~STATUS_INT; } uint32_t @@ -164,7 +165,7 @@ ncr5380_write(uint16_t port, uint8_t val, ncr_t *ncr) if ((val & 0x80) && !(ncr->icr & 0x80)) { ncr5380_log("Resetting the 5380\n"); ncr5380_reset(ncr); - ncr5380_irq(ncr, 1); + ncr->isr |= STATUS_INT; } ncr->icr = val; ncr5380_log("ICR WaitData=%d, ClearReq=%d.\n", scsi_bus->wait_data, scsi_bus->clear_req); @@ -301,7 +302,7 @@ ncr5380_read(uint16_t port, ncr_t *ncr) if (bus & BUS_MSG) bus_state |= TCR_MSG; if ((ncr->tcr & 7) != bus_state) { - ncr5380_irq(ncr, 1); + ncr->isr |= STATUS_INT; ncr5380_log("IRQ issued\n"); } } @@ -321,7 +322,12 @@ ncr5380_read(uint16_t port, ncr_t *ncr) case 7: /* reset Parity/Interrupt */ ncr->isr &= ~(STATUS_BUSY_ERROR | 0x20); - ncr5380_irq(ncr, 0); + if (ncr->irq_ena) + ncr->irq_ena(ncr, ncr->priv, 0); + else + ncr5380_irq(ncr, 0); + + ncr->isr &= ~STATUS_INT; ncr5380_log("Reset Interrupt\n"); break; diff --git a/src/scsi/scsi_ncr53c400.c b/src/scsi/scsi_ncr53c400.c index 31a2de8b3..c8520d082 100644 --- a/src/scsi/scsi_ncr53c400.c +++ b/src/scsi/scsi_ncr53c400.c @@ -72,6 +72,7 @@ typedef struct ncr53c400_t { int8_t type; uint8_t block_count; uint8_t status_ctrl; + uint8_t irq_config; int block_count_loaded; @@ -103,6 +104,23 @@ ncr53c400_log(const char *fmt, ...) # define ncr53c400_log(fmt, ...) #endif +static int +ncr53c400_irq_enable(void *priv, void *ext_priv, int state) +{ + ncr53c400_t *ncr400 = (ncr53c400_t *) ext_priv; + ncr_t *ncr = (ncr_t *) priv; + + if (ncr->irq_state != state) { + ncr->irq_state = state; + ncr400->status_ctrl &= ~0x01; + ncr400->status_ctrl |= (state << 0); + ncr53c400_log("Status Control bit 4=%02x.\n", ncr400->status_ctrl); + if (ncr400->status_ctrl & 0x10) + ncr5380_irq(ncr, state); + } + return 1; +} + static void ncr53c400_timer_on_auto(void *ext_priv, double period) { @@ -263,7 +281,8 @@ ncr53c400_read(uint32_t addr, void *priv) ncr->isr |= STATUS_END_OF_DMA; if (ncr->mode & MODE_ENA_EOP_INT) { ncr53c400_log("NCR read irq\n"); - ncr5380_irq(ncr, 1); + ncr53c400_irq_enable(ncr, ncr400, 1); + ncr->isr |= STATUS_INT; } } else if (!timer_is_enabled(&ncr400->timer)) { ncr53c400_log("Timer re-enabled.\n"); @@ -277,15 +296,16 @@ ncr53c400_read(uint32_t addr, void *priv) case 0x3980: switch (addr) { case 0x3980: /* status */ + if (ncr400->reset) { + ncr400->reset = 0; + ncr53c400_irq_enable(ncr, ncr400, 1); + } + ret = ncr400->status_ctrl; ncr53c400_log("NCR status ctrl read=%02x.\n", ncr400->status_ctrl & STATUS_BUFFER_NOT_READY); if (!ncr400->busy) ret |= STATUS_5380_ACCESSIBLE; - if (ncr400->reset) { - ncr400->reset = 0; - ret |= 0x01; - } ncr53c400_log("NCR 53c400 status=%02x.\n", ret); break; @@ -295,10 +315,7 @@ ncr53c400_read(uint32_t addr, void *priv) break; case 0x3982: /* switch register read */ - if (ncr->irq != -1) { - ret = 0xf8; - ret += ncr->irq; - } + ret = ((ncr400->irq_config >> 5) & 7) | 0xf8; ncr53c400_log("Switches read=%02x.\n", ret); break; @@ -519,7 +536,8 @@ ncr53c400_callback(void *priv) ncr->isr |= STATUS_END_OF_DMA; if (ncr->mode & MODE_ENA_EOP_INT) { ncr53c400_log("NCR 53c400 write irq\n"); - ncr5380_irq(ncr, 1); + ncr53c400_irq_enable(ncr, ncr400, 1); + ncr->isr |= STATUS_INT; } } break; @@ -573,7 +591,8 @@ ncr53c400_callback(void *priv) ncr->isr |= STATUS_END_OF_DMA; if (ncr->mode & MODE_ENA_EOP_INT) { ncr53c400_log("NCR read irq\n"); - ncr5380_irq(ncr, 1); + ncr53c400_irq_enable(ncr, ncr400, 1); + ncr->isr |= STATUS_INT; } } else timer_on_auto(&ncr400->timer, 1.0); @@ -675,7 +694,8 @@ ncr53c400_init(const device_t *info) switch (ncr400->type) { case ROM_LCS6821N: /* Longshine LCS6821N */ ncr400->rom_addr = device_get_config_hex20("bios_addr"); - ncr->irq = device_get_config_int("irq"); + ncr400->irq_config = device_get_config_hex16("irq"); + ncr->irq = (ncr400->irq_config >> 5) & 7; rom_init(&ncr400->bios_rom, LCS6821N_ROM, ncr400->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL); @@ -689,7 +709,8 @@ ncr53c400_init(const device_t *info) case ROM_LS2000: /* Corel LS2000 */ ncr400->rom_addr = device_get_config_hex20("bios_addr"); - ncr->irq = device_get_config_int("irq"); + ncr400->irq_config = device_get_config_hex16("irq"); + ncr->irq = (ncr400->irq_config >> 5) & 7; rom_init(&ncr400->bios_rom, COREL_LS2000_ROM, ncr400->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL); @@ -702,7 +723,9 @@ ncr53c400_init(const device_t *info) case ROM_RT1000B: /* Rancho RT1000B/MC */ ncr400->rom_addr = device_get_config_hex20("bios_addr"); - ncr->irq = device_get_config_int("irq"); + ncr400->irq_config = device_get_config_hex16("irq"); + ncr->irq = (ncr400->irq_config >> 5) & 7; + if (info->flags & DEVICE_MCA) { rom_init(&ncr400->bios_rom, RT1000B_820R_ROM, 0xd8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL); @@ -729,7 +752,8 @@ ncr53c400_init(const device_t *info) case ROM_T130B: /* Trantor T130B */ ncr400->rom_addr = device_get_config_hex20("bios_addr"); ncr400->base = device_get_config_hex16("base"); - ncr->irq = device_get_config_int("irq"); + ncr400->irq_config = device_get_config_hex16("irq"); + ncr->irq = (ncr400->irq_config >> 5) & 7; if (ncr400->rom_addr > 0x00000) { rom_init(&ncr400->bios_rom, T130B_ROM, @@ -754,6 +778,7 @@ ncr53c400_init(const device_t *info) ncr->dma_send_ext = NULL; ncr->dma_initiator_receive_ext = NULL; ncr->timer = ncr53c400_timer_on_auto; + ncr->irq_ena = ncr53c400_irq_enable; scsi_bus->bus_device = ncr->bus; scsi_bus->timer = ncr->timer; scsi_bus->priv = ncr->priv; @@ -839,16 +864,16 @@ static const device_config_t ncr53c400_mmio_config[] = { { .name = "irq", .description = "IRQ", - .type = CONFIG_SELECTION, + .type = CONFIG_HEX16, .default_string = NULL, - .default_int = 5, + .default_int = 0xa0, .file_filter = NULL, .spinner = { 0 }, .selection = { - { .description = "None", .value = -1 }, - { .description = "IRQ 3", .value = 3 }, - { .description = "IRQ 5", .value = 5 }, - { .description = "IRQ 7", .value = 7 }, + { .description = "IRQ 3", .value = 0x60 }, + { .description = "IRQ 4", .value = 0x80 }, + { .description = "IRQ 5", .value = 0xa0 }, + { .description = "IRQ 7", .value = 0xe0 }, { .description = "" } }, .bios = { { 0 } } @@ -910,16 +935,16 @@ static const device_config_t rt1000b_config[] = { { .name = "irq", .description = "IRQ", - .type = CONFIG_SELECTION, + .type = CONFIG_HEX16, .default_string = NULL, - .default_int = 5, + .default_int = 0xa0, .file_filter = NULL, .spinner = { 0 }, .selection = { - { .description = "None", .value = -1 }, - { .description = "IRQ 3", .value = 3 }, - { .description = "IRQ 5", .value = 5 }, - { .description = "IRQ 7", .value = 7 }, + { .description = "IRQ 3", .value = 0x60 }, + { .description = "IRQ 4", .value = 0x80 }, + { .description = "IRQ 5", .value = 0xa0 }, + { .description = "IRQ 7", .value = 0xe0 }, { .description = "" } }, .bios = { { 0 } } @@ -931,16 +956,16 @@ static const device_config_t rt1000b_mc_config[] = { { .name = "irq", .description = "IRQ", - .type = CONFIG_SELECTION, + .type = CONFIG_HEX16, .default_string = NULL, - .default_int = 5, + .default_int = 0xa0, .file_filter = NULL, .spinner = { 0 }, .selection = { - { .description = "None", .value = -1 }, - { .description = "IRQ 3", .value = 3 }, - { .description = "IRQ 5", .value = 5 }, - { .description = "IRQ 7", .value = 7 }, + { .description = "IRQ 3", .value = 0x60 }, + { .description = "IRQ 4", .value = 0x80 }, + { .description = "IRQ 5", .value = 0xa0 }, + { .description = "IRQ 7", .value = 0xe0 }, { .description = "" } }, .bios = { { 0 } } @@ -987,16 +1012,16 @@ static const device_config_t t130b_config[] = { { .name = "irq", .description = "IRQ", - .type = CONFIG_SELECTION, + .type = CONFIG_HEX16, .default_string = NULL, - .default_int = 5, + .default_int = 0xa0, .file_filter = NULL, .spinner = { 0 }, .selection = { - { .description = "None", .value = -1 }, - { .description = "IRQ 3", .value = 3 }, - { .description = "IRQ 5", .value = 5 }, - { .description = "IRQ 7", .value = 7 }, + { .description = "IRQ 3", .value = 0x60 }, + { .description = "IRQ 4", .value = 0x80 }, + { .description = "IRQ 5", .value = 0xa0 }, + { .description = "IRQ 7", .value = 0xe0 }, { .description = "" } }, .bios = { { 0 } } diff --git a/src/scsi/scsi_ql1xxx.c b/src/scsi/scsi_ql1xxx.c new file mode 100644 index 000000000..a01318210 --- /dev/null +++ b/src/scsi/scsi_ql1xxx.c @@ -0,0 +1,4580 @@ +/* + * 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. + * + * QLogic QLA1x40/QLA1x80/QLA1x160 SCSI HBA emulation. + * + * Register definitions are derived from the Matthew Jacob's + * multiplatform driver for ISP chipsets. + * + * Authors: Dmitry Borisov, + * + * Copyright 2026 Dmitry Borisov + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/io.h> +#include <86box/mem.h> +#include <86box/dma.h> +#include <86box/rom.h> +#include <86box/timer.h> +#include <86box/nvr.h> +#include <86box/pci.h> +#include <86box/scsi.h> +#include <86box/scsi_device.h> +#include <86box/scsi_qlogic.h> +#include <86box/nmc93cxx.h> +#include <86box/fifo8.h> +#include <86box/plat_unused.h> +#include <86box/plat_fallthrough.h> + +#include "cpu.h" + +#define ARRAY_SIZE(x) sizeof(x)/sizeof(x[0]) + +/* + * Device configuration + */ +#define QL_CFG_BIOS_ENABLE "bios" +#define QL_CFG_BIOS_REVISION "bios_rev" + +/* + * Device info->local definitions + */ +#define QL_DEV_CHIP_TYPE_MASK 0x000000FF +#define QL_DEV_CHIP_REV_MASK 0x00000F00 +#define QL_DEV_FLASH_TYPE_MASK 0x0000F000 +#define QL_DEV_CHIP_REV_SHIFT 8 +#define QL_DEV_FLASH_TYPE_SHIFT 12 + +#define QL_ISP1040 0x00000000 +#define QL_ISP1080 0x00000001 +#define QL_ISP1240 0x00000002 +#define QL_ISP1280 0x00000003 +#define QL_ISP12160 0x00000004 +#define QL_REV_ISP1080 0x00000100 +#define QL_REV_ISP1020 0x00000000 +#define QL_REV_ISP1020A 0x00000100 +#define QL_REV_ISP1040 0x00000200 +#define QL_REV_ISP1040A 0x00000300 +#define QL_REV_ISP1040B 0x00000400 +#define QL_REV_ISP1040C 0x00000500 +#define QL_FLASH_AM29F010 0x00000000 +#define QL_FLASH_AM29LV010B 0x00000100 + +/* ISP firmware version extracted from the BIOS */ +#define ISP_FW_VER(Major, Minor, Micro) (((Major) << 16) | ((Minor) << 8) | (Micro)) + +/* Address of the IOCB handler in firmware for the 1040 ISP chips */ +#define QL_IOCB_FW_BASE 0x0700 + +/* Maximum SCSI devices supported by the chip */ +#define QL_MAX_TID 16 + +#define QL_PCI_PM_BASE 0x44 +#define QL_PCI_IO_BAR_SIZE 0x100 +#define QL_PCI_MMIO_BAR_SIZE 0x1000 +#define QL_PCI_ROM_BAR_64K_SIZE 0x10000 +#define QL_PCI_ROM_BAR_128K_SIZE 0x20000 + +#define QL_IO_DECODE_MASK (QL_PCI_IO_BAR_SIZE - 1) + +/* The hardware register layout consists of a set of 2-byte aligned registers */ +#define REG_TO_IDX(x) ((x) / 2) +#define IDX_TO_REG(x) ((x) * 2) + +/* + * Flash command definitions + */ +#define FLASH_CMD_CHIP_ERASE_CONFIRM 0x10 +#define FLASH_CMD_BLOCK_ERASE_CONFIRM 0x30 +#define FLASH_CMD_SETUP_ERASE 0x80 +#define FLASH_CMD_AUTO_SELECT 0x90 +#define FLASH_CMD_PROGRAM 0xA0 +#define FLASH_CMD_READ_ARRAY 0xF0 + +/* + * Flash status register definitions + */ +#define FLASH_STATUS_ERASE_TIMEOUT_EXPIRED 0x08 // DQ3 +#define FLASH_STATUS_ERROR 0x20 // DQ5 +#define FLASH_STATUS_TOGGLE 0x40 // DQ6 +#define FLASH_STATUS_DATA_POLLING 0x80 // DQ7 + +/* + * Flash block brotection status definitions + */ +#define FLASH_BLOCK_STATUS_NOT_PROTECTED 0x00 +#define FLASH_BLOCK_STATUS_PROTECTED 0x01 + +/* All boards use Am29F010 or Am29LV010B, which are 128KB in size */ +#define AM29_FLASH_SIZE 0x20000 +#define AM29_MAX_BLOCKS 8 + +#define AM29_MANUFACTURER_ID 0x01 // AMD +#define AM29F010_MODEL_ID 0x20 +#define AM29LV010B_MODEL_ID 0x6E + +#define AM29_BLOCK_ERASE_ACCEPT_TIMEOUT_US 50.0 // 50 us + +/* A0-A1 */ +#define AM29_AUTOSEL_ADDR_MASK 0x3 + +/* Read the manufacturer ID. A0 = 0, A1 = 0 */ +#define AM29_AUTOSEL_ADDR_MANUFACTURER_ID 0x0 + +/* Read the model ID. A0 = 1, A1 = 0 */ +#define AM29_AUTOSEL_ADDR_MODEL_ID 0x1 + +/* + * Hardware registers + */ +#define QL_REG_ID_LOW REG_TO_IDX(0x00) +#define QL_REG_ID_HIGH REG_TO_IDX(0x02) +#define QL_REG_CFG0 REG_TO_IDX(0x04) +#define QL_REG_CFG1 REG_TO_IDX(0x06) +#define QL_REG_INT_CTRL REG_TO_IDX(0x08) +#define QL_REG_INT_STATUS REG_TO_IDX(0x0A) +#define QL_REG_SEMAPHORE REG_TO_IDX(0x0C) +#define QL_REG_NVRAM REG_TO_IDX(0x0E) +#define QL_REG_FLASH_BIOS_DATA REG_TO_IDX(0x10) +#define QL_REG_FLASH_BIOS_ADDRESS REG_TO_IDX(0x12) +#define QL_REG_MAILBOX0 REG_TO_IDX(0x70) +#define QL_REG_MAILBOX1 REG_TO_IDX(0x72) +#define QL_REG_MAILBOX2 REG_TO_IDX(0x74) +#define QL_REG_MAILBOX3 REG_TO_IDX(0x76) +#define QL_REG_MAILBOX4 REG_TO_IDX(0x78) +#define QL_REG_MAILBOX5 REG_TO_IDX(0x7A) +#define QL_REG_MAILBOX6 REG_TO_IDX(0x7C) +#define QL_REG_MAILBOX7 REG_TO_IDX(0x7E) + +/* RISC bank registers (at offset 0x80) */ +#define QL_REG_RISC_PSR REG_TO_IDX(0x20) +#define QL_REG_RISC_PCR REG_TO_IDX(0x24) +#define QL_REG_RISC_PC REG_TO_IDX(0x2C) +#define QL_REG_RISC_MTR REG_TO_IDX(0x2E) +#define QL_REG_HOST_CMD REG_TO_IDX(0x40) +#define QL_REG_GPIO_DATA REG_TO_IDX(0x4C) +#define QL_REG_GPIO_ENABLE REG_TO_IDX(0x4E) + +/* SXP bank registers (at offset 0x80) */ +#define QL_REG_SXP_PINS_DIFF REG_TO_IDX(0x76) + +/* DMA bank registers (at offset 0x20 for the 1040, for other chips at offset 0x80) */ +#define QL_REG_CDMA_CFG REG_TO_IDX(0x00) +#define QL_REG_CDMA_CTRL REG_TO_IDX(0x02) +#define QL_REG_CDMA_STATUS REG_TO_IDX(0x04) +#define QL_REG_CDMA_FIFO_STATUS REG_TO_IDX(0x06) +#define QL_REG_CDMA_COUNT REG_TO_IDX(0x08) +#define QL_REG_CDMA_ADDR0 REG_TO_IDX(0x0C) +#define QL_REG_CDMA_ADDR1 REG_TO_IDX(0x0E) +#define QL_REG_CDMA_ADDR2 REG_TO_IDX(0x10) +#define QL_REG_CDMA_ADDR3 REG_TO_IDX(0x12) +#define QL_REG_DDMA_CFG REG_TO_IDX(0x20) +#define QL_REG_DDMA_CTRL REG_TO_IDX(0x22) +#define QL_REG_DDMA_STATUS REG_TO_IDX(0x24) +#define QL_REG_DDMA_FIFO_STATUS REG_TO_IDX(0x26) +#define QL_REG_DDMA_XFER_COUNT_LOW REG_TO_IDX(0x28) +#define QL_REG_DDMA_XFER_COUNT_HIGH REG_TO_IDX(0x2A) +#define QL_REG_DDMA_ADDR0 REG_TO_IDX(0x2C) +#define QL_REG_DDMA_ADDR1 REG_TO_IDX(0x2E) +#define QL_REG_DDMA_ADDR2 REG_TO_IDX(0x30) +#define QL_REG_DDMA_ADDR3 REG_TO_IDX(0x32) +#define QL_REG_DDMA_DATA_PORT REG_TO_IDX(0x42) + +/* QL_REG_SXP_PINS_DIFF */ +#define SXP_PINS_SE_MODE 0x0400 +#define SXP_PINS_HVD_MODE 0x0800 +#define SXP_PINS_LVD_MODE 0x1000 + +/* QL_REG_CFG0 */ +#define BIU_CONF0_HW_MASK 0x000F /* Hardware revision mask */ + +#define BIU_CONF0_REV_1020 0x0000 +#define BIU_CONF0_REV_1020A 0x0001 +#define BIU_CONF0_REV_1040 0x0002 +#define BIU_CONF0_REV_1040A 0x0003 +#define BIU_CONF0_REV_1040B 0x0004 +#define BIU_CONF0_REV_1040C 0x0005 + +#define BIU_CONF0_REV_1080 0x0001 + +/* QL_REG_CFG1 */ +#define BIU_BURST_ENABLE 0x0004 +#define BIU_PCI_CONF1_SXP 0x0008 /* SXP bank select (1040 only) */ +#define BIU_PCI_CONF1_FIFO_16 0x0010 +#define BIU_PCI_CONF1_FIFO_32 0x0020 +#define BIU_PCI_CONF1_FIFO_64 0x0030 +#define BIU_PCI_CONF1_FIFO_128 0x0040 + +#define BIU_PCI1080_CONF1_SXP0 0x0100 /* SXP bank #1 select */ +#define BIU_PCI1080_CONF1_SXP1 0x0200 /* SXP bank #2 select */ +#define BIU_PCI1080_CONF1_DMA 0x0300 /* DMA bank select */ +#define BIU_PCI1080_REG_BANK_MASK 0x0700 /* Bank mask */ + +/* QL_REG_INT_CTRL */ +#define QL_IFACE_SOFT_RESET 0x0001 +#define QL_IFACE_FLASH_ENABLE 0x0100 + +/* QL_REG_INT_STATUS and QL_REG_INT_CTRL */ +#define QL_INTR_REQ 0x0002 +#define QL_RISC_INTR_REQ 0x0004 + +/* QL_REG_SEMAPHORE */ +#define QL_SEMAPHORE_LOCK 0x0001 +#define QL_SEMAPHORE_STATUS 0x0002 + +/* QL_REG_NVRAM */ +#define QL_EEPROM_SK 0x0001 +#define QL_EEPROM_CS 0x0002 +#define QL_EEPROM_DI 0x0004 +#define QL_EEPROM_DO 0x0008 + +/* QL_REG_HOST_CMD read */ +#define QL_HC_FLAG_RISC_EXT 0x0010 +#define QL_HC_FLAG_RISC_PAUSE 0x0020 +#define QL_HC_FLAG_RISC_RESET 0x0040 +#define QL_HC_FLAG_HOST_INTR 0x0080 + +/* QL_REG_HOST_CMD write */ +#define QL_HC_RESET_RISC 0x1000 +#define QL_HC_PAUSE_RISC 0x2000 +#define QL_HC_RELEASE_RISC 0x3000 +#define QL_HC_SET_HOST_INTR 0x5000 +#define QL_HC_CLEAR_HOST_INTR 0x6000 +#define QL_HC_CLEAR_RISC_INTR 0x7000 +#define QL_HC_DISABLE_BIOS 0x9000 + +/* + * All IOCB Queue entries are this size + */ +#define QENTRY_LEN 64 + +/* + * Mailbox commands + */ +#define QL_CMD_NOP 0x0000 +#define QL_CMD_LOAD_RAM 0x0001 +#define QL_CMD_EXEC_FIRMWARE 0x0002 +#define QL_CMD_DUMP_RAM 0x0003 +#define QL_CMD_WRITE_RAM_WORD 0x0004 +#define QL_CMD_READ_RAM_WORD 0x0005 +#define QL_CMD_REGISTER_TEST 0x0006 +#define QL_CMD_VERIFY_CHECKSUM 0x0007 +#define QL_CMD_ABOUT_FIRMWARE 0x0008 +#define QL_CMD_LOAD_RAM_A64 0x0009 +#define QL_CMD_DUMP_RAM_A64 0x000A +#define QL_CMD_INIT_REQ_QUEUE 0x0010 +#define QL_CMD_INIT_RSP_QUEUE 0x0011 +#define QL_CMD_EXECUTE_IOCB 0x0012 +#define QL_CMD_ABORT_COMMAND 0x0015 +#define QL_CMD_ABORT_DEVICE 0x0016 +#define QL_CMD_ABORT_TARGET 0x0017 +#define QL_CMD_BUS_RESET 0x0018 +#define QL_CMD_START_QUEUE 0x001A +#define QL_CMD_GET_FIRMWARE_STATUS 0x001F +#define QL_CMD_GET_RETRY_COUNT 0x0022 +#define QL_CMD_GET_ACT_NEG_STATE 0x0025 +#define QL_CMD_GET_TARGET_PARAMETERS 0x0028 +#define QL_CMD_SET_INITIATOR_ID 0x0030 +#define QL_CMD_SET_SELECTION_TIMEOUT 0x0031 +#define QL_CMD_SET_RETRY_COUNT 0x0032 +#define QL_CMD_SET_TAG_AGE_LIMIT 0x0033 +#define QL_CMD_SET_CLOCK_RATE 0x0034 +#define QL_CMD_SET_ACTIVE_NEGATION 0x0035 +#define QL_CMD_SET_ASYNC_DATA_SETUP 0x0036 +#define QL_CMD_SET_PCI_CONTROL 0x0037 +#define QL_CMD_SET_TARGET_PARAMETERS 0x0038 +#define QL_CMD_SET_DEVICE_QUEUE 0x0039 +#define QL_CMD_RETURN_BIOS_BLOCK_ADDR 0x0040 +#define QL_CMD_WRITE_FOUR_RAM_WORDS 0x0041 +#define QL_CMD_EXEC_BIOS_IOCB 0x0042 +#define QL_CMD_SET_SYSTEM_PARAMETER 0x0045 +#define QL_CMD_SET_FIRMWARE_FEATURES 0x004A +#define QL_CMD_INIT_REQ_QUEUE_A64 0x0052 +#define QL_CMD_INIT_RSP_QUEUE_A64 0x0053 +#define QL_CMD_EXECUTE_IOCB_A64 0x0054 +#define QL_CMD_GET_TRANSFER_MODE 0x0059 +#define QL_CMD_SET_DATA_OVERRUN_RECOVERY 0x005A + +/* + * Mailbox command complete status codes + */ +#define QL_MBOX_STATUS_COMPLETE 0x4000 +#define QL_MBOX_STATUS_INVALID 0x4001 +#define QL_MBOX_STATUS_HOST_IFACE_ERROR 0x4002 +#define QL_MBOX_STATUS_TEST_FAILED 0x4003 +#define QL_MBOX_STATUS_CMD_ERROR 0x4005 +#define QL_MBOX_STATUS_CMD_PARAM_ERROR 0x4006 +#define QL_MBOX_STATUS_PENDING 0xFFFF // Invalid, for internal use only + +/* + * Mailbox asynchronous event status codes + */ +#define QL_ASYNC_STATUS_BUS_RESET 0x8001 +#define QL_ASYNC_STATUS_SYSTEM_ERROR 0x8002 +#define QL_ASYNC_STATUS_REQ_XFER_ERROR 0x8003 +#define QL_ASYNC_STATUS_RSP_XFER_ERROR 0x8004 +#define QL_ASYNC_STATUS_SCSI_CMD_COMPLETE 0x8020 + +/* + * Mailbox I/O interface registers + */ +#define QL_MBOX_STATUS 0 +#define QL_MBOX_HNDL_LOW 1 +#define QL_MBOX_HNDL_HIGH 2 +#define QL_MBOX_RQST 4 +#define QL_MBOX_RESP 5 +#define QL_MBOX_REGS_MAX 8 + +/* + * Request and response ring helpers + */ +#define QL_RQST_CONS(dev) ((dev)->reg_mbox_out[QL_MBOX_RQST]) // consumer (HW) +#define QL_RQST_PROD(dev) ((dev)->reg_mbox_in[QL_MBOX_RQST]) // producer (SW) +#define QL_RESP_CONS(dev) ((dev)->reg_mbox_in[QL_MBOX_RESP]) // consumer (SW) +#define QL_RESP_PROD(dev) ((dev)->reg_mbox_out[QL_MBOX_RESP]) // producer (HW) + +/* + * Firmware features flags (QL_CMD_SET_FIRMWARE_FEATURES) + */ +#define FW_FEATURE_FAST_POST 0x1 +#define FW_FEATURE_LVD_NOTIFY 0x2 +#define FW_FEATURE_RIO_32BIT 0x4 +#define FW_FEATURE_RIO_16BIT 0x8 + +/* + * Request header flags definitions + */ +#define RQSFLAG_CONTINUATION 0x01 +#define RQSFLAG_FULL 0x02 +#define RQSFLAG_BADHEADER 0x04 +#define RQSFLAG_BADPACKET 0x08 +#define RQSFLAG_BADCOUNT 0x10 +#define RQSFLAG_BADORDER 0x20 +#define RQSFLAG_MASK 0x3F + +/* + * Request header entry_type definitions + */ +#define RQSTYPE_REQUEST 0x01 +#define RQSTYPE_DATASEG 0x02 +#define RQSTYPE_RESPONSE 0x03 +#define RQSTYPE_MARKER 0x04 +#define RQSTYPE_CMDONLY 0x05 +#define RQSTYPE_ATIO 0x06 // Target Mode +#define RQSTYPE_CTIO 0x07 // Target Mode +#define RQSTYPE_REQUEST_A64 0x09 +#define RQSTYPE_A64_CONT 0x0A +#define RQSTYPE_ENABLE_LUN 0x0B // Target Mode +#define RQSTYPE_MODIFY_LUN 0x0C // Target Mode +#define RQSTYPE_NOTIFY 0x0D // Target Mode +#define RQSTYPE_NOTIFY_ACK 0x0E // Target Mode +#define RQSTYPE_CTIO_A64 0x0F // Target Mode + +/* + * Request flags values + */ +#define REQFLAG_NODISCON 0x0001 +#define REQFLAG_HTAG 0x0002 +#define REQFLAG_OTAG 0x0004 +#define REQFLAG_STAG 0x0008 +#define REQFLAG_TARGET_RTN 0x0010 + +#define REQFLAG_NODATA 0x0000 +#define REQFLAG_DATA_IN 0x0020 +#define REQFLAG_DATA_OUT 0x0040 +#define REQFLAG_DATA_BIDIRECTIONAL 0x0060 + +#define REQFLAG_DISARQ 0x0100 +#define REQFLAG_FRC_ASYNC 0x0200 +#define REQFLAG_FRC_SYNC 0x0400 +#define REQFLAG_FRC_WIDE 0x0800 +#define REQFLAG_NOPARITY 0x1000 +#define REQFLAG_STOPQ 0x2000 +#define REQFLAG_XTRASNS 0x4000 +#define REQFLAG_PRIORITY 0x8000 + +/* + * Request completion status cdes + */ +#define RQCS_COMPLETE 0x0000 +#define RQCS_INCOMPLETE 0x0001 +#define RQCS_DMA_ERROR 0x0002 +#define RQCS_TRANSPORT_ERROR 0x0003 +#define RQCS_RESET_OCCURRED 0x0004 +#define RQCS_ABORTED 0x0005 +#define RQCS_TIMEOUT 0x0006 +#define RQCS_DATA_OVERRUN 0x0007 +#define RQCS_COMMAND_OVERRUN 0x0008 +#define RQCS_STATUS_OVERRUN 0x0009 +#define RQCS_BAD_MESSAGE 0x000A +#define RQCS_NO_MESSAGE_OUT 0x000B +#define RQCS_EXT_ID_FAILED 0x000C +#define RQCS_IDE_MSG_FAILED 0x000D +#define RQCS_ABORT_MSG_FAILED 0x000E +#define RQCS_REJECT_MSG_FAILED 0x000F +#define RQCS_NOP_MSG_FAILED 0x0010 +#define RQCS_PARITY_ERROR_MSG_FAILED 0x0011 +#define RQCS_DEVICE_RESET_MSG_FAILED 0x0012 +#define RQCS_ID_MSG_FAILED 0x0013 +#define RQCS_UNEXP_BUS_FREE 0x0014 +#define RQCS_DATA_UNDERRUN 0x0015 +#define RQCS_XACT_ERR1 0x0018 +#define RQCS_XACT_ERR2 0x0019 +#define RQCS_XACT_ERR3 0x001A +#define RQCS_BAD_ENTRY 0x001B +#define RQCS_PHASE_SKIPPED 0x001D +#define RQCS_ARQS_FAILED 0x001E +#define RQCS_WIDE_FAILED 0x001F +#define RQCS_QUEUE_FULL 0x001C +#define RQCS_SYNCXFER_FAILED 0x0020 +#define RQCS_LVD_BUSERR 0x0021 + +/* + * Request State Flags + */ +#define RQSF_GOT_BUS 0x0100 +#define RQSF_GOT_TARGET 0x0200 +#define RQSF_SENT_CDB 0x0400 +#define RQSF_XFRD_DATA 0x0800 +#define RQSF_GOT_STATUS 0x1000 +#define RQSF_GOT_SENSE 0x2000 +#define RQSF_XFER_COMPLETE 0x4000 + +/* + * Request Status Flags + */ +#define RQSTF_DISCONNECT 0x0001 +#define RQSTF_SYNCHRONOUS 0x0002 +#define RQSTF_PARITY_ERROR 0x0004 +#define RQSTF_BUS_RESET 0x0008 +#define RQSTF_DEVICE_RESET 0x0010 +#define RQSTF_ABORTED 0x0020 +#define RQSTF_TIMEOUT 0x0040 +#define RQSTF_NEGOTIATION 0x0080 + +/* + * Device Flags (QL_CMD_SET_TARGET_PARAMETERS, QL_CMD_GET_TARGET_PARAMETERS) + */ +#define DPARM_PPR 0x0020 +#define DPARM_ASYNC 0x0040 +#define DPARM_NARROW 0x0080 +#define DPARM_RENEG 0x0100 +#define DPARM_QFRZ 0x0200 +#define DPARM_ARQ 0x0400 +#define DPARM_TQING 0x0800 +#define DPARM_SYNC 0x1000 +#define DPARM_WIDE 0x2000 +#define DPARM_PARITY 0x4000 +#define DPARM_DISC 0x8000 + +/* + * Generic mailbox command completion time. + * On QLA1080 real hardware the loading of firmware image (15675 words) + * via PIO takes approximately 1878 ms. + */ +#define QL_MBOX_GENERIC_TIME_US 110 // 110 us + +/* + * Command completion time when the SCSI device does not exists, + * measured on QLA1080 real hardware. + */ +#define QL_CMD_SELECTION_TIMEOUT_TIME_US 370000 // 370 ms + +#define SXP_FLAG_ENGINE_ACTIVE 0x00000001 +#define SXP_FLAG_PICK_UP_MBOX 0x00000002 +#define SXP_FLAG_FAST_POSTING 0x00000004 +#define SXP_FLAG_MBOX_IOCB 0x00000008 +#define SXP_FLAG_BIOS_IOCB 0x00000010 +#define SXP_FLAG_WRITE_RESP_IOCB 0x00000020 +#define SXP_FLAG_INC_RESP_RING 0x00000040 +#define SXP_FLAG_ABORTED_CMD 0x00000080 + +#define QL_BIT(b) (1 << (b)) + +#define ql_dma_write(address, buffer, size) dma_bm_write(address, (uint8_t *)(buffer), size, 4); +#define ql_dma_write8(address, buffer) dma_bm_write(address, (uint8_t *)(buffer), 1, 4); +#define ql_dma_write16(address, buffer) dma_bm_write(address, (uint8_t *)(buffer), 2, 4); +#define ql_dma_write32(address, buffer) dma_bm_write(address, (uint8_t *)(buffer), 4, 4); + +#define ql_dma_read(address, buffer, size) dma_bm_read(address, (uint8_t *)(buffer), size, 4); +#define ql_dma_read8(address, buffer) dma_bm_read(address, (uint8_t *)(buffer), 1, 4); +#define ql_dma_read16(address, buffer) dma_bm_read(address, (uint8_t *)(buffer), 2, 4); +#define ql_dma_read32(address, buffer) dma_bm_read(address, (uint8_t *)(buffer), 4, 4); + +typedef enum FlashMode { + M_READ_ARRAY, + M_AUTO_SELECT, + M_PROGRAM, + M_BLOCK_ERASE, + M_CHIP_ERASE, + M_MAX, +} FlashMode; + +typedef enum FlashBusCycleState { + CYCLE_INVALID, + CYCLE_CHECK_55, + CYCLE_CHECK_AA, + CYCLE_CHECK_FIRST_CMD, + CYCLE_CHECK_SECOND_CMD, + CYCLE_ENTER_PROGRAM, +} FlashBusCycleState; + +typedef struct flash_block_t { + /* Block index */ + uint32_t number; + /* Starting address */ + uint32_t start_addr; + /* Ending address */ + uint32_t end_addr; + /* Block protection status */ + uint8_t protection_status; +} flash_block_t; + +typedef struct flash_t { + /* Data access cycles */ + int access_cycles; + /* BIOS image (memory array data) */ + uint8_t *array_data; + /* Operation mode */ + FlashMode mode; + /* Command bus cycle */ + uint8_t bus_cycle; + /* Command decoder state */ + uint8_t cmd_cycle; + /* Write operation status register */ + uint8_t status_reg; + /* Manufacturer ID */ + uint8_t manufacturer_id; + /* Model ID */ + uint8_t model_id; + /* Block select address mask for erase operations */ + uint32_t block_select_addr_mask; + /* Address mask for command decode */ + uint32_t cmd_cycle_addr_mask; + /* Physical address of the AAAA coded cycle */ + uint32_t addr_AAAA_phys; + /* Physical address of the 5555 coded cycle */ + uint32_t addr_5555_phys; + /* Bitmap of pending blocks for erase operation */ + uint32_t blocks_to_erase_bitmap; + /* Byte programming time */ + double program_time_us; + /* Block erase time */ + double block_erase_time_us; + /* Chip erase time */ + double chip_erase_time_us; + /* Erase timer */ + pc_timer_t erase_accept_timeout_timer; + /* Command completion timer */ + pc_timer_t cmd_complete_timer; + /* Blocks (sectors) */ + flash_block_t block[AM29_MAX_BLOCKS]; + /* Name of BIOS image file */ + char filename[1024]; +} flash_t; + +typedef struct isp_hdr_t { + uint8_t entry_type; + uint8_t entry_count; + uint8_t seqno; + uint8_t flags; +} isp_hdr_t; + +typedef struct isp_data_seg_t { + uint32_t address; + uint32_t length; +} isp_data_seg_t; + +/* RQSTYPE_RESPONSE HW structure */ +typedef struct isp_req_status_t { + isp_hdr_t hdr; + uint32_t handle; + uint16_t scsi_status; + uint16_t comp_status; + uint16_t state_flags; + uint16_t status_flags; + uint16_t time; + uint16_t sense_length; + uint32_t residual_length; + uint8_t response[8]; + uint8_t sense_data[32]; +} isp_req_status_t; + +typedef enum SxpState { + SXP_STATE_IDLE, + SXP_STATE_BAD_PACKET, + SXP_STATE_SELECT_DEVICE, + SXP_STATE_SELECTION_TIMEOUT, + SXP_STATE_SEND_CDB, + SXP_STATE_COMPLETE_COMMAND, + SXP_STATE_SCHEDULE_MBOX_INTERRUPT, + SXP_STATE_ACQUIRE_MBOX_SEMAPHORE, + SXP_STATE_SCHEDULE_RISC_INTERRUPT, + SXP_STATE_SET_RISC_INTERRUPT, + SXP_STATE_SCHEDULE_TIMER_SILENCE, + SXP_STATE_MBOX_WAIT_TIMER, + SXP_STATE_WAIT_TIMER, +} SxpState; + +/* Common structure for request entries (RQSTYPE_REQUEST, RQSTYPE_REQUEST_A64, RQSTYPE_MARKER) */ +typedef struct ql_sxp_req_t { + isp_hdr_t hdr; + uint32_t handle; + uint8_t lun; + uint8_t bus_target; + uint16_t cdb_length; + uint16_t flags; + uint16_t reserved; + uint16_t timeout; + uint16_t seg_count; + uint8_t cdb[12]; +} ql_sxp_req_t; + +typedef struct ql_fw_target_params { + uint16_t flags; + uint16_t sync_period; +} ql_fw_target_params; + +typedef struct ql_t { + /* Hardware registers */ + uint16_t reg_cdma_cfg; + uint16_t reg_cdma_ctrl; + uint16_t reg_ddma_cfg; + uint16_t reg_ddma_ctrl; + uint16_t reg_risc_psr; + uint16_t reg_risc_pcr; + uint16_t reg_risc_pc; + uint16_t reg_risc_mtr; + uint16_t reg_id_low; + uint16_t reg_id_high; + uint16_t reg_cfg0; + uint16_t reg_scsi_diff_pins; + uint16_t reg_gpio_data; + uint16_t reg_gpio_enable; + uint16_t reg_flash_bios_addr; + uint16_t reg_nvram; + uint16_t reg_cfg1; + uint16_t reg_host_cmd_flags; + uint16_t reg_intr_ctrl; + uint16_t reg_intr_status; + uint16_t reg_semaphore; + uint16_t reg_mbox_in[QL_MBOX_REGS_MAX]; + uint16_t reg_mbox_out[QL_MBOX_REGS_MAX]; + /* SCSI executive processor state */ + SxpState sxp_state; + /* SXP flags */ + uint32_t sxp_flags; + /* Physical address of the request ring */ + uint32_t rqst_ring_base; + /* Request ring size */ + uint16_t rqst_ring_size; + /* Response ring size */ + uint16_t resp_ring_size; + /* Physical address of the response ring */ + uint32_t resp_ring_base; + /* Physical address of the current request IOCB */ + uint32_t pkt_address; + /* Current request IOCB */ + ql_sxp_req_t pkt; + /* Current response IOCB */ + isp_req_status_t pkt_resp; + /* Holds the request queue indexes to abort */ + Fifo8 abort_iocbs_fifo; + /* Current Path ID */ + uint8_t curr_path_id; + /* Current Target ID */ + uint8_t curr_target_id; + /* Number of SCSI buses on this HBA */ + uint8_t max_bus_count; + /* SCSI bus emulation internal index */ + uint8_t scsi_bus; + /* ISP chip type */ + uint8_t isp_type; + /* ISP chip revision */ + uint8_t isp_rev; + /* IRQ emulation internal context */ + uint8_t irq_state; + /* PCI slot emulation internal context */ + uint8_t pci_slot; + /* Bit map of mailbox registers to return to the host */ + uint32_t mbox_out_mask; + /* Pending mailbox response */ + uint16_t mbox_data[QL_MBOX_REGS_MAX]; + /* Transfer speed in bytes per second */ + double xfer_rate_bps; + /* Command timer period in microsecounds */ + double timer_period; + /* Command timer */ + pc_timer_t cmd_timer; + /* Firmware device parameters */ + ql_fw_target_params fw_tid_params[2][QL_MAX_TID]; + /* Firmware retry count and delay settings */ + uint16_t fw_retry_params[4]; + /* Firmware version */ + uint32_t fw_version; + /* Size of the SCSI payload data */ + uint32_t scsi_data_size; + /* Current offset in the SCSI payload data */ + uint32_t scsi_data_offset; + /* Temporary buffer to hold payload data of the SCSI command */ + uint8_t *scsi_data_buffer; + /* PCI MMIO BAR mapping */ + mem_mapping_t mmio_bar_mapping; + /* PCI ROM BAR mapping */ + mem_mapping_t rom_bar_mapping; + /* PCI configuration space */ + uint8_t pci_cfg[256]; + /* Size of the area to map the expansion ROM */ + uint32_t pci_rom_area_size; + /* Writable bits of the ROM BAR */ + uint32_t rom_bar_mask; + /* Supports PCI capabilities */ + bool has_pci_caps; + /* NVRAM device */ + nmc93cxx_eeprom_t *eeprom_device; + /* Flash BIOS device */ + flash_t flash_device; + /* RISC CPU memory */ + uint16_t cpu_mem[0x10000]; +} ql_t; + +extern double cpuclock; + +static bool +ql_sxp_fetch_request(ql_sxp_req_t* pkt, uint32_t address); + +#ifdef ENABLE_QL_LOG +int ql_do_log = ENABLE_QL_LOG; + +static bool ql_fw_load_in_progress = false; +static bool ql_nvram_read_in_progress = false; + +static void +ql_log(const char *fmt, ...) +{ + va_list ap; + + if (ql_fw_load_in_progress || ql_nvram_read_in_progress) { + return; + } + + if (ql_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} + +static void +ql_debug_check_for_nvram_access(uint32_t addr) +{ + /* Suppress log messages during NVRAM access */ + if (!ql_nvram_read_in_progress) { + if (addr == QL_REG_NVRAM) { + ql_log("QL: Disable NVRAM log\n"); + ql_nvram_read_in_progress = true; + } + } else { + if (addr != QL_REG_NVRAM) { + ql_nvram_read_in_progress = false; + ql_log("QL: Enable NVRAM log\n"); + } + } +} + +#define MAKE_CASE(x) case x: return #x; +static char * +ql_debug_cmd_to_name(uint16_t opcode) +{ + switch (opcode) { + MAKE_CASE(QL_CMD_NOP ); + MAKE_CASE(QL_CMD_LOAD_RAM ); + MAKE_CASE(QL_CMD_EXEC_FIRMWARE ); + MAKE_CASE(QL_CMD_DUMP_RAM ); + MAKE_CASE(QL_CMD_WRITE_RAM_WORD ); + MAKE_CASE(QL_CMD_READ_RAM_WORD ); + MAKE_CASE(QL_CMD_REGISTER_TEST ); + MAKE_CASE(QL_CMD_VERIFY_CHECKSUM ); + MAKE_CASE(QL_CMD_ABOUT_FIRMWARE ); + MAKE_CASE(QL_CMD_LOAD_RAM_A64 ); + MAKE_CASE(QL_CMD_DUMP_RAM_A64 ); + MAKE_CASE(QL_CMD_INIT_REQ_QUEUE ); + MAKE_CASE(QL_CMD_INIT_RSP_QUEUE ); + MAKE_CASE(QL_CMD_EXECUTE_IOCB ); + MAKE_CASE(QL_CMD_ABORT_COMMAND ); + MAKE_CASE(QL_CMD_ABORT_DEVICE ); + MAKE_CASE(QL_CMD_ABORT_TARGET ); + MAKE_CASE(QL_CMD_BUS_RESET ); + MAKE_CASE(QL_CMD_START_QUEUE ); + MAKE_CASE(QL_CMD_GET_FIRMWARE_STATUS ); + MAKE_CASE(QL_CMD_GET_RETRY_COUNT ); + MAKE_CASE(QL_CMD_GET_ACT_NEG_STATE ); + MAKE_CASE(QL_CMD_GET_TARGET_PARAMETERS ); + MAKE_CASE(QL_CMD_SET_INITIATOR_ID ); + MAKE_CASE(QL_CMD_SET_SELECTION_TIMEOUT ); + MAKE_CASE(QL_CMD_SET_RETRY_COUNT ); + MAKE_CASE(QL_CMD_SET_TAG_AGE_LIMIT ); + MAKE_CASE(QL_CMD_SET_CLOCK_RATE ); + MAKE_CASE(QL_CMD_SET_ACTIVE_NEGATION ); + MAKE_CASE(QL_CMD_SET_ASYNC_DATA_SETUP ); + MAKE_CASE(QL_CMD_SET_PCI_CONTROL ); + MAKE_CASE(QL_CMD_SET_TARGET_PARAMETERS ); + MAKE_CASE(QL_CMD_SET_DEVICE_QUEUE ); + MAKE_CASE(QL_CMD_RETURN_BIOS_BLOCK_ADDR ); + MAKE_CASE(QL_CMD_WRITE_FOUR_RAM_WORDS ); + MAKE_CASE(QL_CMD_EXEC_BIOS_IOCB ); + MAKE_CASE(QL_CMD_SET_SYSTEM_PARAMETER ); + MAKE_CASE(QL_CMD_SET_FIRMWARE_FEATURES ); + MAKE_CASE(QL_CMD_INIT_REQ_QUEUE_A64 ); + MAKE_CASE(QL_CMD_INIT_RSP_QUEUE_A64 ); + MAKE_CASE(QL_CMD_EXECUTE_IOCB_A64 ); + MAKE_CASE(QL_CMD_GET_TRANSFER_MODE ); + MAKE_CASE(QL_CMD_SET_DATA_OVERRUN_RECOVERY); + default: + return ""; + } +} + +static char * +debug_sxp_state_to_name(SxpState state) +{ + switch (state) { + MAKE_CASE(SXP_STATE_IDLE); + MAKE_CASE(SXP_STATE_BAD_PACKET); + MAKE_CASE(SXP_STATE_SELECT_DEVICE); + MAKE_CASE(SXP_STATE_SELECTION_TIMEOUT); + MAKE_CASE(SXP_STATE_SEND_CDB); + MAKE_CASE(SXP_STATE_COMPLETE_COMMAND); + MAKE_CASE(SXP_STATE_SCHEDULE_MBOX_INTERRUPT); + MAKE_CASE(SXP_STATE_SCHEDULE_RISC_INTERRUPT); + MAKE_CASE(SXP_STATE_SET_RISC_INTERRUPT); + MAKE_CASE(SXP_STATE_ACQUIRE_MBOX_SEMAPHORE); + MAKE_CASE(SXP_STATE_SCHEDULE_TIMER_SILENCE); + MAKE_CASE(SXP_STATE_WAIT_TIMER); + MAKE_CASE(SXP_STATE_MBOX_WAIT_TIMER); + default: + return ""; + } +} +#undef MAKE_CASE +#else +# define ql_log(fmt, ...) +#endif + +static flash_block_t* +am29_address_to_block(flash_t *dev, uint32_t addr) +{ + for (uint32_t i = 0; i < AM29_MAX_BLOCKS; i++) { + flash_block_t *block = &dev->block[i]; + + if ((addr >= block->start_addr) && (addr <= block->end_addr)) + return block; + } + + /* Should not happen */ + assert(false); + return NULL; +} + +static void +am29_reset_cmd_sequence(flash_t *dev) +{ + dev->bus_cycle = 0; + dev->cmd_cycle = 0; +} + +static void +am29_set_mode(flash_t *dev, FlashMode mode) +{ + if (mode != dev->mode) { + ql_log("FLASH: Set mode %u\n", mode); + } + dev->mode = mode; + + am29_reset_cmd_sequence(dev); +} + +static void +am29_process_reset_or_complete_cmd(flash_t *dev) +{ + am29_set_mode(dev, M_READ_ARRAY); + + dev->status_reg = 0; + dev->blocks_to_erase_bitmap = 0; + + /* Terminate the block erase timeout */ + timer_stop(&dev->erase_accept_timeout_timer); +} + +static void +am29_reset(flash_t *dev) +{ + am29_process_reset_or_complete_cmd(dev); +} + +static void +am29_cmd_complete_timer_callback(void *priv) +{ + flash_t *dev = priv; + + ql_log("FLASH: Command completed with status %02X\n", dev->status_reg); + + /* The memory will return to the Read Mode, unless an error has occurred */ + if (dev->status_reg & FLASH_STATUS_ERROR) { + return; + } + am29_process_reset_or_complete_cmd(dev); +} + +static void +am29_erase_blocks(flash_t *dev) +{ + for (uint32_t i = 0; i < AM29_MAX_BLOCKS; i++) { + flash_block_t *block = &dev->block[i]; + void *block_start; + size_t block_length; + + if (!(dev->blocks_to_erase_bitmap & (1 << i))) { + continue; + } + + /* Protected block: The data remains unchanged, no error is given */ + if (block->protection_status == FLASH_BLOCK_STATUS_PROTECTED) { + continue; + } + + ql_log("FLASH: Erase block #%lu %lX-%lX\n", block->number, block->start_addr, block->end_addr); + + block_start = &dev->array_data[block->start_addr]; + block_length = (block->end_addr - block->start_addr) + 1; + + memset(block_start, 0xFF, block_length); + } +} + +static void +am29_erase_begin_timer_callback(void *priv) +{ + flash_t *dev = priv; + + ql_log("FLASH: Begin erase operation\n"); + + /* Finally, erase the blocks (fill with 0xFF) */ + am29_erase_blocks(dev); + + if (dev->mode == M_CHIP_ERASE) { + timer_on_auto(&dev->cmd_complete_timer, dev->chip_erase_time_us); + } else { + dev->status_reg |= FLASH_STATUS_ERASE_TIMEOUT_EXPIRED; + + timer_on_auto(&dev->cmd_complete_timer, dev->block_erase_time_us); + } +} + +static uint8_t +am29_status_register_read(flash_t *dev) +{ + switch (dev->mode) { + case M_PROGRAM: + case M_BLOCK_ERASE: + case M_CHIP_ERASE: { + dev->status_reg ^= FLASH_STATUS_TOGGLE; + break; + } + + default: + break; + } + + return dev->status_reg; +} + +static void +am29_accept_cmd(flash_t *dev, uint32_t addr, uint8_t val) +{ + flash_block_t *block; + + /* Single cycle command (write to any address inside the device) */ + if ((val == FLASH_CMD_READ_ARRAY) && (dev->mode != M_PROGRAM)) { + am29_process_reset_or_complete_cmd(dev); + return; + } + + switch (dev->mode) { + case M_PROGRAM: { + block = am29_address_to_block(dev, addr); + + /* Write to a protected block: The data remains unchanged, no error is given */ + if (block->protection_status == FLASH_BLOCK_STATUS_PROTECTED) { + ql_log("FLASH: Program failure - the block #lu is protected\n", block->number); + goto ProgramDone; + } + + /* The program command cannot change a bit set at '0' back to '1' */ + if (~dev->array_data[addr] & val) { + ql_log("FLASH: Program error - the address %lX " + "was not previously erased %04X <> %04X\n", + addr, + dev->array_data[addr], + val); + + dev->status_reg |= FLASH_STATUS_ERROR; + goto ProgramDone; + } + + /* Data# polling */ + if (!(val & FLASH_STATUS_DATA_POLLING)) { + dev->status_reg |= FLASH_STATUS_DATA_POLLING; + } + + /* Finally, program the value */ + ql_log("FLASH: Program %08lX to %04X\n", addr, val); + dev->array_data[addr] = val; + +ProgramDone: + timer_on_auto(&dev->cmd_complete_timer, dev->program_time_us); + break; + } + + case M_BLOCK_ERASE: { + /* We shouldn't get here if the operation has already started */ + assert(!(dev->status_reg & FLASH_STATUS_ERASE_TIMEOUT_EXPIRED)); + + addr &= dev->block_select_addr_mask; + block = am29_address_to_block(dev, addr); + + ql_log("FLASH: Queued block #%lu %lX-%lX for erase\n", + block->number, + block->start_addr, + block->end_addr); + + /* Add block to the list */ + dev->blocks_to_erase_bitmap |= 1 << block->number; + + /* Wait for a next block to erase (restart the timeout period) */ + timer_stop(&dev->erase_accept_timeout_timer); + timer_on_auto(&dev->erase_accept_timeout_timer, AM29_BLOCK_ERASE_ACCEPT_TIMEOUT_US); + break; + } + + case M_CHIP_ERASE: { + /* Add all blocks to the list */ + dev->blocks_to_erase_bitmap = 0xFFFFFFFF; + + /* Immediately start the erase operation */ + am29_erase_begin_timer_callback(dev); + break; + } + + default: + break; + } +} + +static void +am29_interpret_cmd_sequence(flash_t *dev, uint32_t addr, uint8_t val) +{ + // clang-format off + static const uint8_t cmd_seq_next_state[6][2] = { + // Phase 0 Phase 1 + { CYCLE_CHECK_AA, CYCLE_INVALID }, // Cycle 1 + { CYCLE_CHECK_55, CYCLE_INVALID }, // Cycle 2 + { CYCLE_CHECK_FIRST_CMD, CYCLE_INVALID }, // Cycle 3 + { CYCLE_CHECK_AA, CYCLE_ENTER_PROGRAM }, // Cycle 4 + { CYCLE_CHECK_55, CYCLE_INVALID }, // Cycle 5 + { CYCLE_CHECK_SECOND_CMD, CYCLE_INVALID }, // Cycle 6 + }; + // clang-format on + + addr &= dev->cmd_cycle_addr_mask; + + switch (cmd_seq_next_state[dev->bus_cycle][dev->cmd_cycle]) { + case CYCLE_CHECK_AA: { + if ((val == 0xAA) && (addr == dev->addr_5555_phys)) { + dev->bus_cycle++; + return; + } + break; + } + + case CYCLE_CHECK_55: { + if ((val == 0x55) && (addr == dev->addr_AAAA_phys)) { + dev->bus_cycle++; + return; + } + break; + } + + case CYCLE_CHECK_FIRST_CMD: { + if (addr != dev->addr_5555_phys) { + break; + } + + switch (val) { + case FLASH_CMD_AUTO_SELECT: + am29_set_mode(dev, M_AUTO_SELECT); + return; + + case FLASH_CMD_PROGRAM: + dev->bus_cycle++; + dev->cmd_cycle++; + return; + + case FLASH_CMD_SETUP_ERASE: + dev->bus_cycle++; + return; + + default: + break; + } + break; + } + + case CYCLE_ENTER_PROGRAM: { + am29_set_mode(dev, M_PROGRAM); + return; + } + + case CYCLE_CHECK_SECOND_CMD: { + switch (val) { + case FLASH_CMD_BLOCK_ERASE_CONFIRM: { + am29_set_mode(dev, M_BLOCK_ERASE); + return; + } + + case FLASH_CMD_CHIP_ERASE_CONFIRM: { + if (addr == dev->addr_5555_phys) { + am29_set_mode(dev, M_CHIP_ERASE); + return; + } + break; + } + + default: + break; + } + break; + } + + default: + break; + } + + am29_reset_cmd_sequence(dev); +} + +static void +am29_mmio_write8(uint32_t addr, uint8_t val, flash_t *dev) +{ + cycles -= dev->access_cycles; + + addr &= (AM29_FLASH_SIZE - 1); + + ql_log("FLASH: [%08lX] <-- %02X\n", addr, val); + + switch (dev->mode) { + /* Ignore all commands while the chip is being programmed or erased */ + case M_CHIP_ERASE: + case M_PROGRAM: { + /* A Read/Reset command can be issued to reset the error condition */ + if ((dev->status_reg & FLASH_STATUS_ERROR) && (val == FLASH_CMD_READ_ARRAY)) + break; + return; + } + + /* Ignore all commands during the Block Erase except the Read/Reset command */ + case M_BLOCK_ERASE: { + /* The command has not started yet, we keep accepting blocks to the erase list */ + if (!(dev->status_reg & FLASH_STATUS_ERASE_TIMEOUT_EXPIRED)) + break; + + if (val == FLASH_CMD_READ_ARRAY) + break; + return; + } + + default: + break; + } + + /* Receive the command sequence */ + if ((dev->mode == M_READ_ARRAY) || (dev->mode == M_AUTO_SELECT)) { + am29_interpret_cmd_sequence(dev, addr, val); + } + + /* Begin the operation */ + am29_accept_cmd(dev, addr, val); +} + +static uint8_t +am29_mmio_read8(uint32_t addr, flash_t *dev) +{ + uint8_t ret; + + cycles -= dev->access_cycles; + + addr &= (AM29_FLASH_SIZE - 1); + + switch (dev->mode) { + case M_READ_ARRAY: { + /* Read array data */ + ret = dev->array_data[addr]; + break; + } + + case M_AUTO_SELECT: { + switch (addr & AM29_AUTOSEL_ADDR_MASK) { + case AM29_AUTOSEL_ADDR_MANUFACTURER_ID: + ret = dev->manufacturer_id; + break; + + case AM29_AUTOSEL_ADDR_MODEL_ID: + ret = dev->model_id; + break; + + default: { + flash_block_t *block = am29_address_to_block(dev, addr); + + /* Read the block protection status */ + ret = block->protection_status; + break; + } + } + break; + } + + default: { + /* Return the status register during Program and Erase operations */ + ret = am29_status_register_read(dev); + break; + } + } + + if (dev->mode != M_READ_ARRAY) { + ql_log("FLASH: [%08lX] --> %02X\n", addr, ret); + } + return ret; +} + +static uint8_t +ql_rom_bar_mmio_read8(uint32_t addr, void* priv) +{ + ql_t *dev = priv; + + return am29_mmio_read8(addr, &dev->flash_device); +} + +static void +ql_update_irq(ql_t *dev) +{ + uint16_t isr = dev->reg_intr_status & dev->reg_intr_ctrl; + + /* NOTE: Unmasking QL_INTR_REQ does nothing on QLA1080 real hardware */ + isr &= QL_RISC_INTR_REQ; + + if (isr != 0) + pci_set_irq(dev->pci_slot, PCI_INTA, &dev->irq_state); + else + pci_clear_irq(dev->pci_slot, PCI_INTA, &dev->irq_state); +} + +static void +ql_reset_asic(ql_t *dev) +{ + ql_log("QL: Reset ASIC\n"); + + dev->reg_id_low = 0x1077; + dev->reg_id_high = 0x1240; + dev->reg_cfg0 = dev->isp_rev; + dev->reg_cfg1 = BIU_BURST_ENABLE | BIU_PCI_CONF1_FIFO_128; + dev->reg_intr_ctrl = 0; + dev->reg_intr_status = 0; + dev->reg_semaphore = 0; + dev->reg_nvram = 0; + dev->reg_flash_bios_addr = 0; + dev->reg_mbox_out[0] = 0; + dev->reg_mbox_out[1] = 0x4953; // 'IS' + dev->reg_mbox_out[2] = 0x5020; // 'P ' + dev->reg_mbox_out[3] = 0x2020; // ' ' + dev->reg_mbox_out[4] = 1; + dev->reg_mbox_out[5] = 7; + dev->reg_mbox_out[6] = 0; + dev->reg_mbox_out[7] = 1; + dev->reg_host_cmd_flags = 0; + dev->reg_gpio_data = 0x000F; + dev->reg_gpio_enable = 0; + dev->reg_scsi_diff_pins = SXP_PINS_SE_MODE; + dev->reg_cdma_cfg = 0x0001; + dev->reg_cdma_ctrl = 0; + dev->reg_ddma_cfg = 0x0008; + dev->reg_ddma_ctrl = 0; + dev->reg_risc_psr = 0x2000; + dev->reg_risc_pcr = 0; + dev->reg_risc_pc = 0; + dev->reg_risc_mtr = 0x0001; + + memset(dev->cpu_mem, 0, sizeof(dev->cpu_mem)); + /* FastUtil BIOS checks area 0xFF03-0xFFF3 if the word at 0xFF03 is invalid */ + dev->cpu_mem[0xFF03] = 0xFBFF; + + timer_stop(&dev->cmd_timer); + dev->rqst_ring_base = 0; + dev->resp_ring_base = 0; + dev->sxp_flags = 0; + dev->sxp_state = SXP_STATE_IDLE; + + ql_update_irq(dev); +} + +static bool +ql_sxp_abort_commands(ql_t *dev, uint8_t path_id, uint8_t target_id, uint8_t lun, uint32_t handle, bool is_handle_valid) +{ + bool success = false; + uint16_t cons; + + if (!(dev->sxp_flags & SXP_FLAG_ENGINE_ACTIVE)) { + return false; + } + + /* Iterate over the request IOCBs looking for a match */ + for (cons = QL_RQST_CONS(dev); cons != QL_RQST_PROD(dev); cons = (cons + 1) % dev->rqst_ring_size) { + uint32_t pkt_address = dev->rqst_ring_base + cons * QENTRY_LEN; + ql_sxp_req_t pkt; + uint8_t curr_path_id, curr_target_id; + + if (!ql_sxp_fetch_request(&pkt, pkt_address)) { + continue; + } + + curr_path_id = (pkt.bus_target & 0x80) >> 7; + curr_target_id = pkt.bus_target & ~0x80; + + /* Exact match? */ + if ((curr_path_id != path_id) || (curr_target_id != target_id)) { + continue; + } + if ((lun != 0xFF) && (pkt.lun != lun)) { + continue; + } + if (is_handle_valid && (pkt.handle != handle)) { + continue; + } + + /* Save the queue index, it will be checked before the next command is processed */ + fifo8_push(&dev->abort_iocbs_fifo, cons & 0xFF); + fifo8_push(&dev->abort_iocbs_fifo, (cons >> 8) & 0xFF); + + success = true; + } + + return success; +} + +static void +ql_sxp_initialize_queues(ql_t *dev) +{ + if ((dev->rqst_ring_base != 0) && (dev->resp_ring_base != 0)) { + /* Each queue index consists of 2 bytes (size of the mailbox register) */ + uint32_t fifo_size = dev->rqst_ring_size * sizeof(uint16_t); + + fifo8_destroy(&dev->abort_iocbs_fifo); + fifo8_create(&dev->abort_iocbs_fifo, fifo_size); + + dev->sxp_flags |= SXP_FLAG_ENGINE_ACTIVE; + } else { + dev->sxp_flags &= ~SXP_FLAG_ENGINE_ACTIVE; + } +} + +static uint16_t +ql_handle_cmd_nop(ql_t *dev) +{ + /* Nothing to do */ + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_write_ram_word(ql_t *dev) +{ +#ifdef ENABLE_QL_LOG + /* Suppress log messages during loading of firmware */ + if (!ql_fw_load_in_progress) { + if (dev->reg_mbox_in[1] == 0x1003) { + ql_log("QL: Disable log before FW load\n"); + ql_fw_load_in_progress = true; + } + } else { + if ((dev->reg_mbox_in[1] - 0x1000) == (dev->cpu_mem[0x1003] - 2)) { + ql_fw_load_in_progress = false; + ql_log("QL: Enable log after FW load\n"); + } + } +#endif + + dev->cpu_mem[dev->reg_mbox_in[1]] = dev->reg_mbox_in[2]; + + dev->mbox_out_mask |= QL_BIT(2); + dev->mbox_data[2] = dev->reg_mbox_in[2]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_write_four_ram_words(ql_t *dev) +{ + uint16_t address = dev->reg_mbox_in[1]; + + if (dev->isp_type != QL_ISP1040) { + return QL_MBOX_STATUS_INVALID; + } + + if (address >= (ARRAY_SIZE(dev->cpu_mem) - 4)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->cpu_mem[address + 0] = dev->reg_mbox_in[2]; + dev->cpu_mem[address + 1] = dev->reg_mbox_in[3]; + dev->cpu_mem[address + 2] = dev->reg_mbox_in[4]; + dev->cpu_mem[address + 3] = dev->reg_mbox_in[5]; + + if (address == (QL_IOCB_FW_BASE + 4)) { + /* Save the SCSI device address for later use */ + dev->cpu_mem[QL_IOCB_FW_BASE + 48] = dev->cpu_mem[address]; + + /* Return some status data */ + dev->cpu_mem[address] = 0x0300; + } + + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_read_ram_word(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(2) | QL_BIT(3); + dev->mbox_data[2] = dev->cpu_mem[dev->reg_mbox_in[1]]; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_load_ram_block(ql_t *dev, UNUSED(bool is_64bit_addr)) +{ + uint32_t offset = dev->reg_mbox_in[1]; + uint32_t block_size_words = dev->reg_mbox_in[4]; + uint8_t *dest_address = (uint8_t *)&dev->cpu_mem[offset]; + uint32_t src_address = ((uint32_t)dev->reg_mbox_in[2] << 16) | dev->reg_mbox_in[3]; + + if ((offset + block_size_words) > ARRAY_SIZE(dev->cpu_mem)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + ql_dma_read(src_address, dest_address, block_size_words * sizeof(uint16_t)); + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_dump_ram_block(ql_t *dev, UNUSED(bool is_64bit_addr)) +{ + uint32_t offset = dev->reg_mbox_in[1]; + uint32_t block_size_words = dev->reg_mbox_in[4]; + uint8_t *src_address = (uint8_t *)&dev->cpu_mem[offset]; + uint32_t dest_address = ((uint32_t)dev->reg_mbox_in[2] << 16) | dev->reg_mbox_in[3]; + + if ((offset + block_size_words) > ARRAY_SIZE(dev->cpu_mem)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + ql_dma_write(dest_address, src_address, block_size_words * sizeof(uint16_t)); + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_register_test(ql_t *dev) +{ + for (uint32_t i = 1; i < ARRAY_SIZE(dev->reg_mbox_in); i++) { + dev->mbox_out_mask |= QL_BIT(i); + dev->mbox_data[i] = dev->reg_mbox_in[i]; + } + + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_verify_checksum(ql_t *dev) +{ + uint32_t start_addr = dev->reg_mbox_in[1]; + uint16_t fw_size_words; + uint32_t end_addr; + uint16_t crc; + + if (start_addr >= (ARRAY_SIZE(dev->cpu_mem) - 3)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + fw_size_words = dev->cpu_mem[start_addr + 3]; + + end_addr = start_addr + fw_size_words; + if (end_addr > ARRAY_SIZE(dev->cpu_mem)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + crc = 0; + for (uint32_t i = start_addr; i < end_addr; i++) { + crc += dev->cpu_mem[i]; + } + dev->mbox_out_mask |= QL_BIT(1); + dev->mbox_data[1] = crc; + + if (crc != 0) { + ql_log("QL: Firmware crc 0x%X\n", crc); + return QL_MBOX_STATUS_TEST_FAILED; + } + + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_exec_firmware(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3) | QL_BIT(4) | QL_BIT(5); + dev->mbox_data[1] = 0x4953; // 'IS' + dev->mbox_data[2] = 0x5020; // 'P ' + dev->mbox_data[3] = 0x2020; // ' ' + dev->mbox_data[4] = 8; + dev->mbox_data[5] = 0x04FE; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_about_firmware(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = (dev->fw_version >> 16) & 0xFF; // Major revision + dev->mbox_data[2] = (dev->fw_version >> 8) & 0xFF; // Minor revision + dev->mbox_data[3] = (dev->fw_version >> 0) & 0xFF; // Micro revision + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_init_request_queue(ql_t *dev, UNUSED(bool is_64bit_addr)) +{ + uint32_t address = ((uint32_t)dev->reg_mbox_in[2] << 16) | dev->reg_mbox_in[3]; + uint16_t queue_length = dev->reg_mbox_in[1]; + + ql_log("QL: REQ queue address 0x%X, length %u\n", address, queue_length); + + if ((address == 0) || (queue_length == 0)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->rqst_ring_base = address; + dev->rqst_ring_size = queue_length; + ql_sxp_initialize_queues(dev); + + dev->mbox_out_mask |= 1 << QL_MBOX_RQST; + dev->mbox_data[QL_MBOX_RQST] = dev->reg_mbox_in[QL_MBOX_RQST]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_init_response_queue(ql_t *dev, UNUSED(bool is_64bit_addr)) +{ + uint32_t address = ((uint32_t)dev->reg_mbox_in[2] << 16) | dev->reg_mbox_in[3]; + uint16_t queue_length = dev->reg_mbox_in[1]; + + ql_log("QL: RSP queue address 0x%X, length %u\n", address, queue_length); + + if ((address == 0) || (queue_length == 0)) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->resp_ring_base = address; + dev->resp_ring_size = queue_length; + ql_sxp_initialize_queues(dev); + + dev->mbox_out_mask |= 1 << QL_MBOX_RESP; + dev->mbox_data[QL_MBOX_RESP] = dev->reg_mbox_in[QL_MBOX_RESP]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_abort_command(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + uint8_t lun = dev->reg_mbox_in[1] & 0xFF; + uint32_t handle; + bool success; + + ql_log("QL: [%u:%u] Abort command\n", path_id, target_id); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + /* Abort an active command on the device (PATH:TID:LUN) that match the handle */ + handle = dev->reg_mbox_in[QL_MBOX_HNDL_LOW]; + handle |= (uint32_t)dev->reg_mbox_in[QL_MBOX_HNDL_HIGH] << 16; + success = ql_sxp_abort_commands(dev, path_id, target_id, lun, handle, true); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = dev->reg_mbox_in[3]; + + if (!success) { + return QL_MBOX_STATUS_CMD_ERROR; + } + + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_abort_device(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + uint8_t lun = dev->reg_mbox_in[1] & 0xFF; + + ql_log("QL: [%u:%u:%u] Abort device\n", path_id, target_id, lun); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + /* Abort all active commands on the device (PATH:TID:LUN) */ + (void) ql_sxp_abort_commands(dev, path_id, target_id, lun, 0, false); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = dev->reg_mbox_in[3]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_abort_target(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + + ql_log("QL: [%u:%u] Abort target\n", path_id, target_id); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + /* Abort all active commands on the target (PATH:TID) */ + (void) ql_sxp_abort_commands(dev, path_id, target_id, 0xFF, 0, false); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = dev->reg_mbox_in[3]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_bus_reset(ql_t *dev) +{ + uint8_t path_id; + + if (dev->isp_type == QL_ISP1040) { + path_id = 0; + } else { + path_id = dev->reg_mbox_in[2]; + } + + ql_log("QL: Reset bus %u, delay %u\n", path_id, dev->reg_mbox_in[1]); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + /* 86Box supports one SCSI bus per controller for now */ + if (path_id == 0) { + for (uint32_t i = 0; i < MIN(QL_MAX_TID, SCSI_ID_MAX); i++) { + scsi_device_reset(&scsi_devices[dev->scsi_bus][i]); + } + } + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_get_firmware_status(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 0; + dev->mbox_data[2] = 527; // Max queue depth + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_initialor_id(ql_t *dev) +{ + ql_log("QL: Initialor ID 0x%X\n", dev->reg_mbox_in[1]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_selection_timeout(ql_t *dev) +{ + ql_log("QL: Selection timeout #1 %u\n", dev->reg_mbox_in[1]); + ql_log("QL: Selection timeout #2 %u\n", dev->reg_mbox_in[2]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_retry_count(ql_t *dev) +{ + ql_log("QL: Retry count #1 %u\n", dev->reg_mbox_in[1]); + ql_log("QL: Retry count #2 %u\n", dev->reg_mbox_in[2]); + ql_log("QL: Retry delay #1 %u\n", dev->reg_mbox_in[6]); + ql_log("QL: Retry delay #2 %u\n", dev->reg_mbox_in[7]); + + dev->fw_retry_params[0] = dev->reg_mbox_in[1]; + dev->fw_retry_params[1] = dev->reg_mbox_in[2]; + dev->fw_retry_params[2] = dev->reg_mbox_in[6]; + dev->fw_retry_params[3] = dev->reg_mbox_in[7]; + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 8; + dev->mbox_data[2] = 5; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_get_retry_count(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->fw_retry_params[0]; + dev->mbox_data[2] = dev->fw_retry_params[1]; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_get_act_neg_state(ql_t *dev) +{ + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 0; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_tag_age_limit(ql_t *dev) +{ + ql_log("QL: Tag age limit #1 %u\n", dev->reg_mbox_in[1]); + ql_log("QL: Tag age limit #2 %u\n", dev->reg_mbox_in[2]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_clock_rate(ql_t *dev) +{ + ql_log("QL: Clock rate %u\n", dev->reg_mbox_in[1]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_active_negation(ql_t *dev) +{ + ql_log("QL: Acq active negation 0x%X\n", dev->reg_mbox_in[1]); + ql_log("QL: Data line active negation 0x%X\n", dev->reg_mbox_in[2]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_async_data_setup(ql_t *dev) +{ + ql_log("QL: Async data setup %u %u\n", dev->reg_mbox_in[1], dev->reg_mbox_in[2]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_pci_control(ql_t *dev) +{ + ql_log("QL: PCI control %x %x\n", dev->reg_mbox_in[1], dev->reg_mbox_in[2]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_target_parameters(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + + ql_log("QL: [%u:%u] Set params 0x%X 0x%X\n", path_id, target_id, dev->reg_mbox_in[2], dev->reg_mbox_in[3]); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->fw_tid_params[path_id][target_id].flags = dev->reg_mbox_in[2]; + dev->fw_tid_params[path_id][target_id].sync_period = dev->reg_mbox_in[3]; + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = dev->reg_mbox_in[3]; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_get_target_parameters(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + + ql_log("QL: [%u:%u] Return params\n", path_id, target_id); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3) | QL_BIT(6); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->fw_tid_params[path_id][target_id].flags; + dev->mbox_data[3] = dev->fw_tid_params[path_id][target_id].sync_period; + dev->mbox_data[6] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_device_queue(ql_t *dev) +{ + ql_log("QL: Queue params 0x%X %u %u\n", dev->reg_mbox_in[1], dev->reg_mbox_in[2], dev->reg_mbox_in[3]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = dev->reg_mbox_in[2]; + dev->mbox_data[3] = 0x0064; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_return_bios_block_addr(ql_t *dev) +{ + if (dev->isp_type != QL_ISP1040) { + return QL_MBOX_STATUS_INVALID; + } + + dev->mbox_out_mask |= QL_BIT(1); + dev->mbox_data[1] = QL_IOCB_FW_BASE; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_system_parameter(ql_t *dev) +{ + ql_log("QL: System parameter 0x%X\n", dev->reg_mbox_in[1]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 2; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_firmware_features(ql_t *dev) +{ + ql_log("QL: FW features 0x%X\n", dev->reg_mbox_in[1]); + + if (dev->reg_mbox_in[1] & FW_FEATURE_FAST_POST) { + dev->sxp_flags |= SXP_FLAG_FAST_POSTING; + } else { + dev->sxp_flags &= ~SXP_FLAG_FAST_POSTING; + } + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 0; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_get_transfer_mode(ql_t *dev) +{ + uint8_t path_id = dev->reg_mbox_in[1] >> 15; + uint8_t target_id = (dev->reg_mbox_in[1] >> 8) & ~0x80; + + ql_log("QL: [%u:%u] Return xfer mode\n", path_id, target_id); + + if (path_id >= dev->max_bus_count) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + if (target_id >= QL_MAX_TID) { + return QL_MBOX_STATUS_CMD_PARAM_ERROR; + } + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3) | QL_BIT(6); + dev->mbox_data[1] = dev->reg_mbox_in[1]; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0x000C; + dev->mbox_data[6] = 0xFD10; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_cmd_set_data_overrun_recovery(ql_t *dev) +{ + ql_log("QL: Data overrun recovery 0x%X\n", dev->reg_mbox_in[1]); + + dev->mbox_out_mask |= QL_BIT(1) | QL_BIT(2) | QL_BIT(3); + dev->mbox_data[1] = 0; + dev->mbox_data[2] = 0; + dev->mbox_data[3] = 0; + return QL_MBOX_STATUS_COMPLETE; +} + +static uint16_t +ql_handle_exec_bios_iocb(ql_t *dev) +{ + if (dev->isp_type != QL_ISP1040) { + return QL_MBOX_STATUS_INVALID; + } + + dev->sxp_flags |= SXP_FLAG_BIOS_IOCB; + return QL_MBOX_STATUS_PENDING; +} + +static uint16_t +ql_handle_exec_mbox_iocb(ql_t *dev, bool is_64bit_addr) +{ + if (is_64bit_addr && (dev->reg_mbox_in[1] != QENTRY_LEN)) { + return QL_MBOX_STATUS_CMD_ERROR; + } + + dev->sxp_flags |= SXP_FLAG_MBOX_IOCB; + return QL_MBOX_STATUS_PENDING; +} + +static uint16_t +ql_process_mailbox(ql_t *dev) +{ + uint16_t status; + + ql_log("QL: Command %02X %s\n", dev->reg_mbox_in[0], ql_debug_cmd_to_name(dev->reg_mbox_in[0])); + + switch (dev->reg_mbox_in[0]) { + case QL_CMD_NOP: + status = ql_handle_cmd_nop(dev); + break; + case QL_CMD_LOAD_RAM: + status = ql_handle_cmd_load_ram_block(dev, false); + break; + case QL_CMD_EXEC_FIRMWARE: + status = ql_handle_cmd_exec_firmware(dev); + break; + case QL_CMD_DUMP_RAM: + status = ql_handle_cmd_dump_ram_block(dev, false); + break; + case QL_CMD_WRITE_RAM_WORD: + status = ql_handle_cmd_write_ram_word(dev); + break; + case QL_CMD_READ_RAM_WORD: + status = ql_handle_cmd_read_ram_word(dev); + break; + case QL_CMD_REGISTER_TEST: + status = ql_handle_cmd_register_test(dev); + break; + case QL_CMD_VERIFY_CHECKSUM: + status = ql_handle_cmd_verify_checksum(dev); + break; + case QL_CMD_ABOUT_FIRMWARE: + status = ql_handle_cmd_about_firmware(dev); + break; + case QL_CMD_LOAD_RAM_A64: + status = ql_handle_cmd_load_ram_block(dev, true); + break; + case QL_CMD_DUMP_RAM_A64: + status = ql_handle_cmd_dump_ram_block(dev, true); + break; + case QL_CMD_INIT_REQ_QUEUE: + status = ql_handle_cmd_init_request_queue(dev, false); + break; + case QL_CMD_INIT_RSP_QUEUE: + status = ql_handle_cmd_init_response_queue(dev, false); + break; + case QL_CMD_EXECUTE_IOCB: + status = ql_handle_exec_mbox_iocb(dev, false); + break; + case QL_CMD_ABORT_COMMAND: + status = ql_handle_cmd_abort_command(dev); + break; + case QL_CMD_ABORT_DEVICE: + status = ql_handle_cmd_abort_device(dev); + break; + case QL_CMD_ABORT_TARGET: + status = ql_handle_cmd_abort_target(dev); + break; + case QL_CMD_BUS_RESET: + status = ql_handle_cmd_bus_reset(dev); + break; + case QL_CMD_START_QUEUE: + status = ql_handle_cmd_nop(dev); + break; + case QL_CMD_GET_RETRY_COUNT: + status = ql_handle_cmd_get_retry_count(dev); + break; + case QL_CMD_GET_ACT_NEG_STATE: + status = ql_handle_cmd_get_act_neg_state(dev); + break; + case QL_CMD_GET_TARGET_PARAMETERS: + status = ql_handle_cmd_get_target_parameters(dev); + break; + case QL_CMD_GET_FIRMWARE_STATUS: + status = ql_handle_cmd_get_firmware_status(dev); + break; + case QL_CMD_SET_INITIATOR_ID: + status = ql_handle_cmd_set_initialor_id(dev); + break; + case QL_CMD_SET_SELECTION_TIMEOUT: + status = ql_handle_cmd_set_selection_timeout(dev); + break; + case QL_CMD_SET_RETRY_COUNT: + status = ql_handle_cmd_set_retry_count(dev); + break; + case QL_CMD_SET_TAG_AGE_LIMIT: + status = ql_handle_cmd_set_tag_age_limit(dev); + break; + case QL_CMD_SET_CLOCK_RATE: + status = ql_handle_cmd_set_clock_rate(dev); + break; + case QL_CMD_SET_ACTIVE_NEGATION: + status = ql_handle_cmd_set_active_negation(dev); + break; + case QL_CMD_SET_ASYNC_DATA_SETUP: + status = ql_handle_cmd_set_async_data_setup(dev); + break; + case QL_CMD_SET_PCI_CONTROL: + status = ql_handle_cmd_set_pci_control(dev); + break; + case QL_CMD_SET_TARGET_PARAMETERS: + status = ql_handle_cmd_set_target_parameters(dev); + break; + case QL_CMD_SET_DEVICE_QUEUE: + status = ql_handle_cmd_set_device_queue(dev); + break; + case QL_CMD_RETURN_BIOS_BLOCK_ADDR: + status = ql_handle_cmd_return_bios_block_addr(dev); + break; + case QL_CMD_WRITE_FOUR_RAM_WORDS: + status = ql_handle_cmd_write_four_ram_words(dev); + break; + case QL_CMD_EXEC_BIOS_IOCB: + status = ql_handle_exec_bios_iocb(dev); + break; + case QL_CMD_SET_SYSTEM_PARAMETER: + status = ql_handle_cmd_set_system_parameter(dev); + break; + case QL_CMD_SET_FIRMWARE_FEATURES: + status = ql_handle_cmd_set_firmware_features(dev); + break; + case QL_CMD_INIT_REQ_QUEUE_A64: + status = ql_handle_cmd_init_request_queue(dev, true); + break; + case QL_CMD_INIT_RSP_QUEUE_A64: + status = ql_handle_cmd_init_response_queue(dev, true); + break; + case QL_CMD_EXECUTE_IOCB_A64: + status = ql_handle_exec_mbox_iocb(dev, true); + break; + case QL_CMD_GET_TRANSFER_MODE: + status = ql_handle_cmd_get_transfer_mode(dev); + break; + case QL_CMD_SET_DATA_OVERRUN_RECOVERY: + status = ql_handle_cmd_set_data_overrun_recovery(dev); + break; + + default: + ql_log("Unhandled or invalid command %04X\n", dev->reg_mbox_in[0]); + status = QL_MBOX_STATUS_INVALID; + break; + } + + return status; +} + +/* RQSTYPE_REQUEST */ +static void +ql_pkt_get_sgl_req(uint32_t address, uint32_t idx, isp_data_seg_t *data_seg) +{ + uint32_t offset = address + 32 + idx * 8; + + ql_dma_read32(offset + 0, &data_seg->address); + ql_dma_read32(offset + 4, &data_seg->length); +} + +/* RQSTYPE_REQUEST_A64 */ +static void +ql_pkt_get_sgl_req64(uint32_t address, uint32_t idx, isp_data_seg_t *data_seg) +{ + uint32_t offset = address + 36 + idx * 12; + + /* The high part of the address is ignored for 86Box */ + ql_dma_read32(offset + 0, &data_seg->address); + ql_dma_read32(offset + 8, &data_seg->length); +} + +/* RQSTYPE_DATASEG */ +static void +ql_pkt_get_sgl_cont(uint32_t address, uint32_t idx, isp_data_seg_t *data_seg) +{ + uint32_t offset = address + 8 + idx * 8; + + ql_dma_read32(offset + 0, &data_seg->address); + ql_dma_read32(offset + 4, &data_seg->length); +} + +/* RQSTYPE_A64_CONT */ +static void +ql_pkt_get_sgl_cont64(uint32_t address, uint32_t idx, isp_data_seg_t *data_seg) +{ + uint32_t offset = address + 4 + idx * 12; + + /* The high part of the address is ignored for 86Box */ + ql_dma_read32(offset + 0, &data_seg->address); + ql_dma_read32(offset + 8, &data_seg->length); +} + +static void +ql_pkt_get_entry_type(uint32_t address, uint8_t *entry_type) +{ + ql_dma_read8(address + 0, entry_type); +} + +static void +ql_pkt_put_header(uint32_t address, isp_hdr_t *hdr) +{ + ql_dma_write8(address + 0, &hdr->entry_type); + ql_dma_write8(address + 1, &hdr->entry_count); + ql_dma_write8(address + 2, &hdr->seqno); + ql_dma_write8(address + 3, &hdr->flags); +} + +static void +ql_pkt_put_request_status(uint32_t address, isp_req_status_t *resp) +{ + ql_pkt_put_header(address, &resp->hdr); + + ql_dma_write32(address + 4, &resp->handle); + ql_dma_write16(address + 8, &resp->scsi_status); + ql_dma_write16(address + 10, &resp->comp_status); + ql_dma_write16(address + 12, &resp->state_flags); + ql_dma_write16(address + 14, &resp->status_flags); + ql_dma_write16(address + 16, &resp->time); + ql_dma_write16(address + 18, &resp->sense_length); + ql_dma_write32(address + 20, &resp->residual_length); + ql_dma_write(address + 24, &resp->response[0], sizeof(resp->response)); + ql_dma_write(address + 32, &resp->sense_data[0], sizeof(resp->sense_data)); + + ql_log("QL: RESP HDR type 0x%X cnt %u seq %u fl 0x%X\n", + resp->hdr.entry_type, + resp->hdr.entry_count, + resp->hdr.seqno, + resp->hdr.flags); + ql_log("QL: RESP h 0x%X scsi 0x%X comp 0x%X state 0x%X statfl 0x%X time %u sens %u resid %u\n", + resp->handle, + resp->scsi_status, + resp->comp_status, + resp->state_flags, + resp->status_flags, + resp->time, + resp->sense_length, + resp->residual_length); +} + +static bool +ql_sxp_fetch_request(ql_sxp_req_t* pkt, uint32_t address) +{ + /* Fetch the header */ + ql_dma_read8(address + 0, &pkt->hdr.entry_type); + ql_dma_read8(address + 1, &pkt->hdr.entry_count); + ql_dma_read8(address + 2, &pkt->hdr.seqno); + ql_dma_read8(address + 3, &pkt->hdr.flags); + + switch (pkt->hdr.entry_type) { + case RQSTYPE_REQUEST: + case RQSTYPE_REQUEST_A64: { + ql_dma_read32(address + 4, &pkt->handle); + ql_dma_read8(address + 8, &pkt->lun); + ql_dma_read8(address + 9, &pkt->bus_target); + ql_dma_read16(address + 10, &pkt->cdb_length); + ql_dma_read16(address + 12, &pkt->flags); + ql_dma_read16(address + 14, &pkt->reserved); + ql_dma_read16(address + 16, &pkt->timeout); + ql_dma_read16(address + 18, &pkt->seg_count); + ql_dma_read(address + 20, &pkt->cdb[0], sizeof(pkt->cdb)); + return true; + } + + case RQSTYPE_MARKER: { + ql_dma_read32(address + 4, &pkt->handle); + ql_dma_read8(address + 8, &pkt->lun); + ql_dma_read8(address + 9, &pkt->bus_target); + pkt->timeout = 0; + return true; + } + + default: + break; + } + + ql_log("QL: Unknown/invalid request type %u\n", pkt->hdr.entry_type); + return false; +} + +static void +ql_sxp_begin_response_entry(ql_sxp_req_t *pkt, isp_req_status_t *resp) +{ + memset(resp, 0, sizeof(*resp)); + resp->hdr.entry_type = RQSTYPE_RESPONSE; + resp->hdr.entry_count = pkt->hdr.entry_count; + resp->hdr.seqno = pkt->hdr.seqno; + resp->hdr.flags = pkt->hdr.flags; + resp->handle = pkt->handle; + resp->time = pkt->timeout; +} + +static double +ql_sxp_handle_state_send_cdb_bios(ql_t *dev, scsi_device_t *sd) +{ + ql_sxp_req_t *pkt = &dev->pkt; + isp_req_status_t *resp = &dev->pkt_resp; + double media_period = 10.0; + uint64_t bytes_xfered = 0; + + if (dev->scsi_data_buffer) { + free(dev->scsi_data_buffer); + dev->scsi_data_buffer = NULL; + } + + if ((sd->phase != SCSI_PHASE_STATUS) && (sd->buffer_length > 0)) { + uint32_t dev_buffer_length = sd->buffer_length; + uint8_t *dev_buffer = sd->sc->temp_buffer; + uint32_t host_buffer_size; + double p; + + p = scsi_device_get_callback(sd); + if (p <= 0.0) + bytes_xfered += sd->buffer_length; + else + media_period = p; + + host_buffer_size = dev->cpu_mem[QL_IOCB_FW_BASE + 2]; + host_buffer_size |= (uint32_t)dev->cpu_mem[QL_IOCB_FW_BASE + 3] << 16; + + if (dev->cpu_mem[QL_IOCB_FW_BASE + 12] == 3) { + /* PIO */ + dev->scsi_data_offset = 0; + dev->scsi_data_size = dev_buffer_length; + dev->scsi_data_buffer = malloc(dev_buffer_length); + memcpy(dev->scsi_data_buffer, dev_buffer, dev_buffer_length); + } else { + uint32_t address, block_size; + + address = dev->cpu_mem[QL_IOCB_FW_BASE + 0]; + address |= (uint32_t)dev->cpu_mem[QL_IOCB_FW_BASE + 1] << 16; + + block_size = MIN(host_buffer_size, dev_buffer_length); + + /* DMA */ + if (sd->phase == SCSI_PHASE_DATA_IN) { + ql_log("QL: DMA to 0x%lx %lu bytes\n", address, block_size); + ql_dma_write(address, dev_buffer, block_size); + } else if (sd->phase == SCSI_PHASE_DATA_OUT) { + ql_log("QL: DMA from 0x%lx %lu bytes\n", address, block_size); + ql_dma_read(address, dev_buffer, block_size); + } + } + scsi_device_command_phase1(sd); + + if (dev_buffer_length < host_buffer_size) { + /* + * Data underrun + * + * NOTE: It seems that there is no way to return residual_length to the software. + * It has been observed that the BIOS does a retry in PIO mode + * when a DMA underrun error occurs. + */ + resp->comp_status = RQCS_DATA_UNDERRUN; + } else if (dev_buffer_length > host_buffer_size) { + /* Data overrun */ + resp->comp_status = RQCS_DATA_OVERRUN; + } else { + /* Normal completion */ + resp->comp_status = RQCS_COMPLETE; + } + resp->scsi_status = sd->status; + } else { + resp->scsi_status = sd->status; + resp->comp_status = RQCS_COMPLETE; + } + + return media_period + (1000000.0 / dev->xfer_rate_bps) * (double)bytes_xfered; +} + +static double +ql_sxp_handle_state_send_cdb_sgl(ql_t *dev, scsi_device_t *sd) +{ + ql_sxp_req_t *pkt = &dev->pkt; + isp_req_status_t *resp = &dev->pkt_resp; + double media_period = 10.0; + uint64_t bytes_xfered = QENTRY_LEN; + + /* Read/write command */ + if ((sd->phase != SCSI_PHASE_STATUS) && (sd->buffer_length > 0)) { + uint32_t dev_buffer_length = sd->buffer_length; + uint8_t *dev_buffer = sd->sc->temp_buffer; + uint8_t entry_type = pkt->hdr.entry_type; + uint32_t pkt_address = dev->pkt_address; + uint16_t pkt_entry_idx = QL_RQST_CONS(dev); + uint32_t host_buffer_size = 0; + uint32_t bytes_moved = 0; + uint32_t max_seg_count; + double p; + + p = scsi_device_get_callback(sd); + if (p <= 0.0) + bytes_xfered += sd->buffer_length; + else + media_period = p; + + if (entry_type == RQSTYPE_REQUEST) { + max_seg_count = 4; + } else { + assert(entry_type == RQSTYPE_REQUEST_A64); + max_seg_count = 2; + } + + /* Process the S/G list */ + for (uint32_t i = 0, seg_idx = 0; i < pkt->seg_count; i++, seg_idx++) { + isp_data_seg_t data_seg; + uint32_t block_size; + + /* Fetch a continuation segment entry */ + if (seg_idx == max_seg_count) { + seg_idx = 0; + + pkt_entry_idx = (pkt_entry_idx + 1) % dev->rqst_ring_size; + pkt_address = dev->rqst_ring_base + pkt_entry_idx * QENTRY_LEN; + + ql_pkt_get_entry_type(pkt_address, &entry_type); + if (entry_type == RQSTYPE_DATASEG) { + max_seg_count = 7; + } else if (entry_type == RQSTYPE_A64_CONT) { + max_seg_count = 5; + } else { + /* Bad entry in the request ring */ + ql_log("QL: Expected continuation segment but got %u\n", entry_type); + break; + } + } + + /* Read the S/G fragment */ + switch (entry_type) { + case RQSTYPE_REQUEST: + ql_pkt_get_sgl_req(pkt_address, seg_idx, &data_seg); + break; + case RQSTYPE_REQUEST_A64: + ql_pkt_get_sgl_req64(pkt_address, seg_idx, &data_seg); + break; + case RQSTYPE_DATASEG: + ql_pkt_get_sgl_cont(pkt_address, seg_idx, &data_seg); + break; + case RQSTYPE_A64_CONT: + ql_pkt_get_sgl_cont64(pkt_address, seg_idx, &data_seg); + break; + + default: + /* Should not happen */ + assert(false); + break; + } + host_buffer_size += data_seg.length; + block_size = MIN(data_seg.length, dev_buffer_length - bytes_moved); + bytes_xfered += sizeof(data_seg); + + if (sd->phase == SCSI_PHASE_DATA_IN) { + ql_log("QL: DMA to 0x%lx %lu bytes\n", data_seg.address, block_size); + ql_dma_write(data_seg.address, &dev_buffer[bytes_moved], block_size); + } else if (sd->phase == SCSI_PHASE_DATA_OUT) { + ql_log("QL: DMA from 0x%lx %lu bytes\n", data_seg.address, block_size); + ql_dma_read(data_seg.address, &dev_buffer[bytes_moved], block_size); + } + bytes_moved += block_size; + } + scsi_device_command_phase1(sd); + + if (dev_buffer_length < host_buffer_size) { + /* Data underrun */ + resp->residual_length = host_buffer_size - dev_buffer_length; + resp->comp_status = RQCS_DATA_UNDERRUN; + } else if (dev_buffer_length > host_buffer_size) { + /* Data overrun */ + resp->comp_status = RQCS_DATA_OVERRUN; + } else { + /* Normal completion */ + resp->comp_status = RQCS_COMPLETE; + } + resp->scsi_status = sd->status; + resp->state_flags |= RQSF_GOT_STATUS | RQSF_XFER_COMPLETE; + } else { + resp->scsi_status = sd->status; + resp->comp_status = RQCS_COMPLETE; + resp->state_flags |= RQSF_GOT_STATUS; + } + + /* Auto request sense */ + if ((resp->scsi_status == SCSI_STATUS_CHECK_CONDITION) && !(pkt->flags & REQFLAG_DISARQ)) { + if (dev->fw_tid_params[dev->curr_path_id][dev->curr_target_id].flags & DPARM_ARQ) { + scsi_device_request_sense(sd, resp->sense_data, sizeof(resp->sense_data)); + + resp->state_flags |= RQSF_GOT_SENSE; + resp->sense_length = sizeof(resp->sense_data); + + bytes_xfered += resp->sense_length; + } + } + + return media_period + (1000000.0 / dev->xfer_rate_bps) * (double)bytes_xfered; +} + +static bool +ql_sxp_state_machine(ql_t *dev) +{ + switch (dev->sxp_state) { + case SXP_STATE_IDLE: { + ql_sxp_req_t *pkt = &dev->pkt; + + /* Do we have to pick mailbox registers up? */ + if (dev->sxp_flags & SXP_FLAG_PICK_UP_MBOX) { + dev->sxp_flags &= ~SXP_FLAG_PICK_UP_MBOX; + + dev->mbox_out_mask = 1 << QL_MBOX_STATUS; + dev->mbox_data[QL_MBOX_STATUS] = ql_process_mailbox(dev); + + /* Check for mailbox commands that do not need to go through the command processor */ + if (dev->mbox_data[QL_MBOX_STATUS] != QL_MBOX_STATUS_PENDING) { + dev->timer_period = QL_MBOX_GENERIC_TIME_US; + dev->sxp_state = SXP_STATE_SCHEDULE_MBOX_INTERRUPT; + break; + } + + assert(dev->sxp_flags & (SXP_FLAG_BIOS_IOCB | SXP_FLAG_MBOX_IOCB)); + + if (dev->sxp_flags & SXP_FLAG_BIOS_IOCB) { + uint8_t lun = dev->cpu_mem[QL_IOCB_FW_BASE + 48] & 0x0F; + uint8_t target_id = (dev->cpu_mem[QL_IOCB_FW_BASE + 48] & 0xF0) >> 4 ; + uint8_t *cdb_bytes = (uint8_t *)&dev->cpu_mem[QL_IOCB_FW_BASE + 5]; + + /* Translate the BIOS IOCB request into a common structure for easier processing */ + memset(pkt, 0, sizeof(*pkt)); + pkt->hdr.entry_type = RQSTYPE_REQUEST; + pkt->hdr.entry_count = 1; + pkt->lun = lun; + pkt->bus_target = target_id; + pkt->cdb_length = sizeof(pkt->cdb); + memcpy(pkt->cdb, cdb_bytes, sizeof(pkt->cdb)); + + dev->sxp_state = SXP_STATE_SELECT_DEVICE; + break; + } + } + + if (dev->sxp_flags & SXP_FLAG_MBOX_IOCB) { + dev->pkt_address = ((uint32_t)dev->reg_mbox_in[2] << 16) | dev->reg_mbox_in[3]; + } else { + /* Skip processing command queues if they are not initialized */ + if (!(dev->sxp_flags & SXP_FLAG_ENGINE_ACTIVE)) { + return false; + } + + /* No available entries in the request queue, try again later */ + if (QL_RQST_CONS(dev) == QL_RQST_PROD(dev)) { + return false; + } + + /* No room in the response queue, try again later */ + if (((QL_RESP_PROD(dev) + 1) % dev->resp_ring_size) == QL_RESP_CONS(dev)) { + return false; + } + + /* Check is this entry has been aborted */ + if (!fifo8_is_empty(&dev->abort_iocbs_fifo)) { + const uint8_t* idx_buf; + uint16_t rqst_idx; + + idx_buf = fifo8_peek_bufptr(&dev->abort_iocbs_fifo, sizeof(rqst_idx), NULL); + rqst_idx = ((uint16_t)idx_buf[1] << 8) | idx_buf[0]; + + if (rqst_idx == QL_RQST_CONS(dev)) { + fifo8_drop(&dev->abort_iocbs_fifo, sizeof(rqst_idx)); + + dev->sxp_flags |= SXP_FLAG_ABORTED_CMD; + + dev->timer_period = QL_MBOX_GENERIC_TIME_US; + dev->sxp_state = SXP_STATE_COMPLETE_COMMAND; + break; + } + } + + dev->pkt_address = dev->rqst_ring_base + QL_RQST_CONS(dev) * QENTRY_LEN; + } + + if (!ql_sxp_fetch_request(pkt, dev->pkt_address)) { + /* Skip this packet */ + pkt->hdr.entry_count = 1; + + dev->sxp_state = SXP_STATE_BAD_PACKET; + break; + } + + dev->sxp_state = SXP_STATE_SELECT_DEVICE; + break; + } + case SXP_STATE_SELECT_DEVICE: { + ql_sxp_req_t *pkt = &dev->pkt; + + ql_log("QL: RQST HDR type 0x%X cnt %u seq %u fl 0x%X\n", + pkt->hdr.entry_type, + pkt->hdr.entry_count, + pkt->hdr.seqno, + pkt->hdr.flags); + ql_log("QL: RQST h 0x%X lun %u bus_tid 0x%X cdb_len %u fl 0x%X time %u seq_cnt %u\n", + pkt->handle, + pkt->lun, + pkt->bus_target, + pkt->cdb_length, + pkt->flags, + pkt->timeout, + pkt->seg_count); + + /* Extract the SCSI address */ + dev->curr_path_id = (pkt->bus_target & 0x80) >> 7; + dev->curr_target_id = pkt->bus_target & ~0x80; + + if (dev->curr_path_id >= dev->max_bus_count) { + dev->sxp_state = SXP_STATE_SELECTION_TIMEOUT; + break; + } + + /* 86Box supports one SCSI bus per controller for now */ + if (dev->curr_path_id != 0) { + dev->sxp_state = SXP_STATE_SELECTION_TIMEOUT; + break; + } + + if (dev->curr_target_id >= MIN(QL_MAX_TID, SCSI_ID_MAX)) { + dev->sxp_state = SXP_STATE_SELECTION_TIMEOUT; + break; + } + + if (!scsi_device_present(&scsi_devices[dev->scsi_bus][dev->curr_target_id])) { + dev->sxp_state = SXP_STATE_SELECTION_TIMEOUT; + break; + } + + ql_log("QL: Selected target %u:%u\n", dev->curr_path_id, dev->curr_target_id); + dev->sxp_state = SXP_STATE_SEND_CDB; + break; + } + case SXP_STATE_SEND_CDB: { + scsi_device_t *sd = &scsi_devices[dev->scsi_bus][dev->curr_target_id]; + ql_sxp_req_t *pkt = &dev->pkt; + isp_req_status_t *resp = &dev->pkt_resp; + + ql_sxp_begin_response_entry(pkt, resp); + resp->state_flags = RQSF_GOT_BUS | RQSF_GOT_TARGET | RQSF_SENT_CDB; + + if (pkt->hdr.entry_type == RQSTYPE_MARKER) { + resp->scsi_status = SCSI_STATUS_OK; + resp->comp_status = RQCS_COMMAND_OVERRUN; + resp->status_flags = RQSTF_ABORTED; + + dev->timer_period = QL_MBOX_GENERIC_TIME_US; + dev->sxp_state = SXP_STATE_COMPLETE_COMMAND; + break; + } + + scsi_device_identify(sd, pkt->lun); + + for (uint32_t i = 0; i < sizeof(pkt->cdb); i++) { + ql_log("QL: SCSI CDB[%2lu]=%02X\n", i, pkt->cdb[i]); + } + + sd->buffer_length = -1; + scsi_device_command_phase0(sd, pkt->cdb); + + if (dev->sxp_flags & SXP_FLAG_BIOS_IOCB) { + dev->timer_period = ql_sxp_handle_state_send_cdb_bios(dev, sd); + } else { + dev->timer_period = ql_sxp_handle_state_send_cdb_sgl(dev, sd); + } + scsi_device_identify(sd, SCSI_LUN_USE_CDB); + + dev->sxp_state = SXP_STATE_COMPLETE_COMMAND; + break; + } + case SXP_STATE_SELECTION_TIMEOUT: { + isp_req_status_t *resp = &dev->pkt_resp; + + ql_sxp_begin_response_entry(&dev->pkt, resp); + resp->scsi_status = SCSI_STATUS_OK; + resp->comp_status = RQCS_INCOMPLETE; + resp->state_flags = RQSF_GOT_BUS; + resp->status_flags = RQSTF_TIMEOUT; + + dev->timer_period = QL_CMD_SELECTION_TIMEOUT_TIME_US; + dev->sxp_state = SXP_STATE_COMPLETE_COMMAND; + break; + } + case SXP_STATE_BAD_PACKET: { + isp_req_status_t *resp = &dev->pkt_resp; + + memset(resp, 0, sizeof(*resp)); + resp->hdr.entry_type = 0; + resp->hdr.flags = RQSFLAG_BADHEADER; + resp->comp_status = RQCS_INCOMPLETE; + + dev->timer_period = QL_MBOX_GENERIC_TIME_US; + dev->sxp_state = SXP_STATE_COMPLETE_COMMAND; + break; + } + case SXP_STATE_COMPLETE_COMMAND: { + isp_req_status_t *resp = &dev->pkt_resp; + ql_sxp_req_t *pkt = &dev->pkt; + + /* There are three possible ways of sending SCSI commands to ISP */ + if (dev->sxp_flags & SXP_FLAG_BIOS_IOCB) { + dev->mbox_out_mask = (1 << QL_MBOX_STATUS) | QL_BIT(1) | QL_BIT(2); + dev->mbox_data[QL_MBOX_STATUS] = QL_MBOX_STATUS_COMPLETE; + dev->mbox_data[1] = resp->comp_status; + dev->mbox_data[2] = resp->scsi_status; + + /* Return the internal queue index */ + dev->cpu_mem[QL_IOCB_FW_BASE + 13] = 0; + + dev->sxp_state = SXP_STATE_SCHEDULE_MBOX_INTERRUPT; + } else if (dev->sxp_flags & SXP_FLAG_MBOX_IOCB) { + dev->mbox_out_mask = (1 << QL_MBOX_STATUS); + dev->mbox_data[QL_MBOX_STATUS] = QL_MBOX_STATUS_COMPLETE; + + /* Mailbox IOCB commands always write a response queue entry */ + dev->sxp_flags |= SXP_FLAG_WRITE_RESP_IOCB; + dev->sxp_state = SXP_STATE_SCHEDULE_MBOX_INTERRUPT; + } else { + uint16_t num_consumed = MAX(pkt->hdr.entry_count, 1); + QL_RQST_CONS(dev) = (QL_RQST_CONS(dev) + num_consumed) % dev->rqst_ring_size; + + if ((dev->sxp_flags & SXP_FLAG_ABORTED_CMD) || (pkt->hdr.entry_type == RQSTYPE_MARKER)) { + /* + * Marker entries never raise an interrupt, even if the SCSI device does not exist. + * Aborted commands also never raise an interrupt. + */ + dev->sxp_state = SXP_STATE_SCHEDULE_TIMER_SILENCE; + } else if ((dev->sxp_flags & SXP_FLAG_FAST_POSTING) && + (resp->scsi_status == SCSI_STATUS_OK) && + (resp->comp_status == RQCS_COMPLETE)) { + /* Fast posting avoids having to deal with response queue for successful commands */ + dev->mbox_out_mask = (1 << QL_MBOX_STATUS) | (1 << QL_MBOX_HNDL_LOW) | 1 << (QL_MBOX_HNDL_HIGH); + dev->mbox_data[QL_MBOX_STATUS] = QL_ASYNC_STATUS_SCSI_CMD_COMPLETE; + dev->mbox_data[QL_MBOX_HNDL_LOW] = pkt->handle & 0xFFFF; + dev->mbox_data[QL_MBOX_HNDL_HIGH] = (pkt->handle >> 16) & 0xFFFF; + + dev->sxp_state = SXP_STATE_SCHEDULE_MBOX_INTERRUPT; + } else { + /* Normal command completion, write a response queue entry */ + dev->pkt_address = dev->resp_ring_base + QL_RESP_PROD(dev) * QENTRY_LEN; + dev->sxp_flags |= SXP_FLAG_WRITE_RESP_IOCB | SXP_FLAG_INC_RESP_RING; + dev->sxp_state = SXP_STATE_SCHEDULE_RISC_INTERRUPT; + } + } + break; + } + case SXP_STATE_SCHEDULE_RISC_INTERRUPT: { + dev->sxp_state = SXP_STATE_SET_RISC_INTERRUPT; + timer_on_auto(&dev->cmd_timer, dev->timer_period); + return false; + } + case SXP_STATE_SCHEDULE_MBOX_INTERRUPT: { + dev->sxp_state = SXP_STATE_MBOX_WAIT_TIMER; + timer_on_auto(&dev->cmd_timer, dev->timer_period); + return false; + } + case SXP_STATE_MBOX_WAIT_TIMER: { + dev->sxp_state = SXP_STATE_ACQUIRE_MBOX_SEMAPHORE; + break; + } + case SXP_STATE_ACQUIRE_MBOX_SEMAPHORE: { + /* Wait for the mailbox semaphore to be released */ + if (dev->reg_semaphore & QL_SEMAPHORE_LOCK) { + return false; + } + + /* Return mailbox registers to the host */ + for (uint32_t i = 0; i < ARRAY_SIZE(dev->reg_mbox_out); i++) { + if (dev->mbox_out_mask & (1 << i)) { + dev->reg_mbox_out[i] = dev->mbox_data[i]; + } + } + dev->reg_semaphore = QL_SEMAPHORE_LOCK; + fallthrough; + } + case SXP_STATE_SET_RISC_INTERRUPT: { + if (dev->sxp_flags & SXP_FLAG_WRITE_RESP_IOCB) { + ql_pkt_put_request_status(dev->pkt_address, &dev->pkt_resp); + + if (dev->sxp_flags & SXP_FLAG_INC_RESP_RING) { + QL_RESP_PROD(dev) = (QL_RESP_PROD(dev) + 1) % dev->resp_ring_size; + } + } + dev->sxp_flags &= ~(SXP_FLAG_WRITE_RESP_IOCB | SXP_FLAG_INC_RESP_RING + | SXP_FLAG_BIOS_IOCB | SXP_FLAG_MBOX_IOCB | SXP_FLAG_ABORTED_CMD); + + dev->reg_intr_status |= QL_INTR_REQ | QL_RISC_INTR_REQ; + ql_update_irq(dev); + + dev->sxp_state = SXP_STATE_IDLE; + break; + } + case SXP_STATE_SCHEDULE_TIMER_SILENCE: { + dev->sxp_state = SXP_STATE_WAIT_TIMER; + timer_on_auto(&dev->cmd_timer, dev->timer_period); + return false; + } + case SXP_STATE_WAIT_TIMER: { + dev->sxp_state = SXP_STATE_IDLE; + break; + } + + default: { + /* Should not happen */ + assert(false); + break; + } + } + + return true; +} + +static void +ql_sxp_run_state_machine(ql_t *dev) +{ + ql_log("QL: Enter with %s\n", debug_sxp_state_to_name(dev->sxp_state)); + + while (true) { + SxpState old_state = dev->sxp_state; + + if (!ql_sxp_state_machine(dev)) { + break; + } + + if (dev->sxp_state != old_state) { + ql_log("QL: State %s --> %s\n", debug_sxp_state_to_name(old_state), debug_sxp_state_to_name(dev->sxp_state)); + } + } + + ql_log("QL: Exit with %s\n", debug_sxp_state_to_name(dev->sxp_state)); +} + +static void +ql_sxp_kick_engine(ql_t *dev) +{ + if (dev->sxp_state == SXP_STATE_IDLE) { + ql_log("QL: SCSI kick\n"); + ql_sxp_run_state_machine(dev); + } +} + +static void +ql_sxp_timer_callback(void *priv) +{ + ql_t *dev = priv; + + ql_log("QL: Timer called\n"); + + assert(dev->sxp_state != SXP_STATE_IDLE); + + ql_sxp_run_state_machine(dev); +} + +static void +ql_sxp_write_semaphore(ql_t *dev, uint16_t val) +{ + if (val == 0) { + val = QL_SEMAPHORE_STATUS; + } else { + val &= QL_SEMAPHORE_STATUS | QL_SEMAPHORE_LOCK; + } + dev->reg_semaphore = val; + + if (dev->sxp_state == SXP_STATE_ACQUIRE_MBOX_SEMAPHORE) { + ql_sxp_run_state_machine(dev); + } +} + +static void +ql_sxp_write_mailbox(ql_t *dev, uint32_t idx, uint16_t val) +{ + dev->reg_mbox_in[idx] = val; + + /* Kick the command processor */ + if ((idx == QL_MBOX_RQST) || (idx == QL_MBOX_RESP)) { + ql_sxp_kick_engine(dev); + } +} + +static void +ql_write_host_command(ql_t *dev, uint16_t val) +{ + switch (val) { + case QL_HC_RESET_RISC: + dev->reg_host_cmd_flags = QL_HC_FLAG_RISC_RESET; + break; + case QL_HC_PAUSE_RISC: + dev->reg_host_cmd_flags |= QL_HC_FLAG_RISC_PAUSE; + break; + case QL_HC_RELEASE_RISC: + dev->reg_host_cmd_flags &= ~(QL_HC_FLAG_RISC_PAUSE | QL_HC_FLAG_RISC_RESET); + break; + case QL_HC_SET_HOST_INTR: + dev->reg_host_cmd_flags |= QL_HC_FLAG_HOST_INTR; + dev->sxp_flags |= SXP_FLAG_PICK_UP_MBOX; + ql_sxp_kick_engine(dev); + break; + case QL_HC_CLEAR_HOST_INTR: + /* This command does nothing */ + break; + case QL_HC_CLEAR_RISC_INTR: + dev->reg_host_cmd_flags &= ~QL_HC_FLAG_HOST_INTR; + dev->reg_intr_status = 0; + ql_update_irq(dev); + break; + case QL_HC_DISABLE_BIOS: + dev->reg_host_cmd_flags = 0; + break; + + default: { + ql_log("QL: Unknown host command %04X\n", val); + break; + } + } +} + +static void +ql_write_interface_control(ql_t *dev, uint16_t val) +{ + if (val & QL_IFACE_SOFT_RESET) { + ql_reset_asic(dev); + return; + } + + dev->reg_intr_ctrl = val & 0x01FF; + ql_update_irq(dev); +} + +static uint32_t +ql_get_flash_bios_addr(ql_t *dev) +{ + uint32_t address = dev->reg_flash_bios_addr; + + /* Select between two consecutive flash memory banks of 64KB each */ + if (dev->reg_nvram & QL_EEPROM_CS) { + address |= 0x10000; + } + return address; +} + +static uint16_t +ql_read_flash_bios_data(ql_t *dev) +{ + uint8_t byte; + + if (!(dev->reg_intr_ctrl & QL_IFACE_FLASH_ENABLE)) { + return 0xFFFF; + } + + byte = am29_mmio_read8(ql_get_flash_bios_addr(dev), &dev->flash_device); + return (byte << 8) | byte; +} + +static void +ql_write_flash_bios_data(ql_t *dev, uint16_t value) +{ + if (!(dev->reg_intr_ctrl & QL_IFACE_FLASH_ENABLE)) { + return; + } + + am29_mmio_write8(ql_get_flash_bios_addr(dev), (uint8_t)value, &dev->flash_device); +} + +static void +ql_write_bank_sxp(ql_t *dev, uint32_t addr, uint32_t bus_number, uint16_t val) +{ + if (bus_number >= dev->max_bus_count) { + return; + } + + ql_log("QL: [W16] Unhandled SPX bank write %lX <-- %X\n", IDX_TO_REG(addr), val); +} + +static void +ql_write_bank_dma(ql_t *dev, uint32_t addr, uint16_t val) +{ + switch (addr) { + case QL_REG_CDMA_CFG: + dev->reg_cdma_cfg = val; + break; + case QL_REG_CDMA_CTRL: + dev->reg_cdma_ctrl = val; + break; + case QL_REG_DDMA_CFG: + dev->reg_ddma_cfg = val; + break; + case QL_REG_DDMA_CTRL: + dev->reg_ddma_ctrl = val; + break; + + default: + ql_log("QL: [W16] Unhandled DMA bank write %lX <-- %X\n", IDX_TO_REG(addr), val); + break; + } +} + +static void +ql_write_bank_risc(ql_t *dev, uint32_t addr, uint16_t val) +{ + switch (addr) { + case QL_REG_RISC_PSR: + dev->reg_risc_psr = val; + break; + case QL_REG_RISC_PCR: + dev->reg_risc_pcr = val; + break; + case QL_REG_RISC_MTR: + dev->reg_risc_mtr = val; + break; + case QL_REG_HOST_CMD: + ql_write_host_command(dev, val); + break; + case QL_REG_GPIO_DATA: + dev->reg_gpio_data = val; + break; + case QL_REG_GPIO_ENABLE: + dev->reg_gpio_enable = val; + break; + + default: + ql_log("QL: [W16] Unhandled RISC bank write %lX <-- %X\n", IDX_TO_REG(addr), val); + break; + } +} + +static void +ql_mmio_write16(uint32_t addr, uint16_t val, void* priv) +{ + ql_t *dev = priv; + + /* The hw does not handle unaligned access */ + if (addr & 0x1) { + return; + } + + addr &= QL_IO_DECODE_MASK; + addr = REG_TO_IDX(addr); + +#ifdef ENABLE_QL_LOG + ql_debug_check_for_nvram_access(addr); +#endif + + ql_log("QL: [W16] [%02lX] <-- %4X\n", IDX_TO_REG(addr), val); + + switch (addr) { + case QL_REG_CFG1: + dev->reg_cfg1 = val & 0x03FF; + break; + case QL_REG_INT_CTRL: + ql_write_interface_control(dev, val); + break; + case QL_REG_SEMAPHORE: + ql_sxp_write_semaphore(dev, val); + break; + case QL_REG_NVRAM: + dev->reg_nvram = val & (QL_EEPROM_SK | QL_EEPROM_CS | QL_EEPROM_DI); + nmc93cxx_eeprom_write(dev->eeprom_device, + !!(val & QL_EEPROM_CS), + !!(val & QL_EEPROM_SK), + !!(val & QL_EEPROM_DI)); + break; + case QL_REG_FLASH_BIOS_DATA: + ql_write_flash_bios_data(dev, val); + break; + case QL_REG_FLASH_BIOS_ADDRESS: + dev->reg_flash_bios_addr = val; + break; + case QL_REG_MAILBOX0: + case QL_REG_MAILBOX1: + case QL_REG_MAILBOX2: + case QL_REG_MAILBOX3: + case QL_REG_MAILBOX4: + case QL_REG_MAILBOX5: + case QL_REG_MAILBOX6: + case QL_REG_MAILBOX7: + ql_sxp_write_mailbox(dev, addr - QL_REG_MAILBOX0, val); + break; + + default: { + if (dev->isp_type == QL_ISP1040) { + if (addr >= REG_TO_IDX(0x20) && addr < REG_TO_IDX(0x80)) { + /* DMA bank */ + addr -= REG_TO_IDX(0x20); + ql_write_bank_dma(dev, addr, val); + break; + } else if (addr >= REG_TO_IDX(0x80) && addr < REG_TO_IDX(0xFF)) { + /* RISC or SXP bank */ + addr -= REG_TO_IDX(0x80); + if (dev->reg_cfg1 & BIU_PCI_CONF1_SXP) { + ql_write_bank_sxp(dev, addr, 0, val); + } else { + ql_write_bank_risc(dev, addr, val); + } + break; + } + } else { + if (addr >= REG_TO_IDX(0x80) && addr < REG_TO_IDX(0xFF)) { + addr -= REG_TO_IDX(0x80); + + switch (dev->reg_cfg1 & BIU_PCI1080_REG_BANK_MASK) { + case BIU_PCI1080_CONF1_SXP0: + ql_write_bank_sxp(dev, addr, 0, val); + break; + case BIU_PCI1080_CONF1_SXP1: + ql_write_bank_sxp(dev, addr, 1, val); + break; + case BIU_PCI1080_CONF1_DMA: + ql_write_bank_dma(dev, addr, val); + break; + default: + ql_write_bank_risc(dev, addr, val); + break; + } + break; + } + } + + ql_log("QL: [W16] Unhandled write %lX <-- %X\n", IDX_TO_REG(addr), val); + break; + } + } +} + +static uint16_t +ql_read_bank_sxp(ql_t *dev, uint32_t addr, uint32_t bus_number) +{ + uint16_t ret; + + if (bus_number >= dev->max_bus_count) { + return 0; + } + + switch (addr) { + case QL_REG_SXP_PINS_DIFF: + ret = dev->reg_scsi_diff_pins; + break; + + default: + ql_log("QL: [R16] Unhandled SXP bank read %lX\n", IDX_TO_REG(addr)); + ret = 0; + break; + } + + return ret; +} + +static uint16_t +ql_read_bank_dma(ql_t *dev, uint32_t addr) +{ + uint16_t ret; + + switch (addr) { + case QL_REG_CDMA_CFG: + ret = dev->reg_cdma_cfg; + break; + case QL_REG_CDMA_CTRL: + ret = dev->reg_cdma_ctrl; + break; + case QL_REG_DDMA_CFG: + ret = dev->reg_ddma_cfg; + break; + case QL_REG_DDMA_CTRL: + ret = dev->reg_ddma_ctrl; + break; + + case QL_REG_DDMA_DATA_PORT: { + if (!dev->scsi_data_buffer) { + ret = 0; + break; + } + + /* Return a word via PIO interface */ + ret = 0; + for (uint32_t i = 0; i < sizeof(uint16_t); i++) { + uint8_t byte = dev->scsi_data_buffer[dev->scsi_data_offset++]; + + ql_log("QL: Return byte 0x%02X at offset %lu\n", byte, dev->scsi_data_offset - 1); + + ret |= (uint16_t)byte << (i * 8); + + /* Last byte, destroy the buffer */ + if (dev->scsi_data_offset >= dev->scsi_data_size) { + ql_log("QL: All data has been transmitted\n"); + + free(dev->scsi_data_buffer); + dev->scsi_data_buffer = NULL; + break; + } + } + break; + } + + default: + ql_log("QL: [R16] Unhandled DMA bank read %lX\n", IDX_TO_REG(addr)); + ret = 0; + break; + } + + return ret; +} + +static uint16_t +ql_read_bank_risc(ql_t *dev, uint32_t addr) +{ + uint16_t ret; + + switch (addr) { + case QL_REG_RISC_PSR: + ret = dev->reg_risc_psr; + break; + case QL_REG_RISC_PCR: + ret = dev->reg_risc_pcr; + break; + case QL_REG_RISC_PC: + ret = dev->reg_risc_pc; + break; + case QL_REG_RISC_MTR: + ret = dev->reg_risc_mtr; + break; + case QL_REG_HOST_CMD: + ret = dev->reg_host_cmd_flags; + break; + case QL_REG_GPIO_DATA: + ret = dev->reg_gpio_data; + break; + case QL_REG_GPIO_ENABLE: + ret = dev->reg_gpio_enable; + break; + + default: + ql_log("QL: [R16] Unhandled RISC bank read %lX\n", IDX_TO_REG(addr)); + ret = 0; + break; + } + + return ret; +} + +static uint16_t +ql_mmio_read16(uint32_t addr, void* priv) +{ + ql_t *dev = priv; + uint16_t ret; + + /* The hw does not handle unaligned access */ + if (addr & 0x1) { + return 0xFFFF; + } + + addr &= QL_IO_DECODE_MASK; + addr = REG_TO_IDX(addr); + + switch (addr) { + case QL_REG_ID_LOW: + ret = dev->reg_id_low; + break; + case QL_REG_ID_HIGH: + ret = dev->reg_id_high; + break; + case QL_REG_CFG0: + ret = dev->reg_cfg0; + break; + case QL_REG_CFG1: + ret = dev->reg_cfg1; + break; + case QL_REG_INT_CTRL: + ret = dev->reg_intr_ctrl; + break; + case QL_REG_INT_STATUS: + ret = dev->reg_intr_status; + break; + case QL_REG_SEMAPHORE: + ret = dev->reg_semaphore; + break; + case QL_REG_NVRAM: + ret = dev->reg_nvram; + if (nmc93cxx_eeprom_read(dev->eeprom_device)) { + ret |= QL_EEPROM_DO; + } + break; + case QL_REG_FLASH_BIOS_DATA: + ret = ql_read_flash_bios_data(dev); + break; + case QL_REG_FLASH_BIOS_ADDRESS: + ret = dev->reg_flash_bios_addr; + break; + case QL_REG_MAILBOX0: + case QL_REG_MAILBOX1: + case QL_REG_MAILBOX2: + case QL_REG_MAILBOX3: + case QL_REG_MAILBOX4: + case QL_REG_MAILBOX5: + case QL_REG_MAILBOX6: + case QL_REG_MAILBOX7: + ret = dev->reg_mbox_out[addr - QL_REG_MAILBOX0]; + break; + + default: { + uint32_t bank_addr; + + if (dev->isp_type == QL_ISP1040) { + if (addr >= REG_TO_IDX(0x20) && addr < REG_TO_IDX(0x80)) { + /* DMA bank */ + bank_addr = addr - REG_TO_IDX(0x20); + ret = ql_read_bank_dma(dev, bank_addr); + break; + } else if (addr >= REG_TO_IDX(0x80) && addr < REG_TO_IDX(0xFF)) { + /* RISC or SXP bank */ + bank_addr = addr - REG_TO_IDX(0x80); + if (dev->reg_cfg1 & BIU_PCI_CONF1_SXP) { + ret = ql_read_bank_sxp(dev, bank_addr, 0); + } else { + ret = ql_read_bank_risc(dev, bank_addr); + } + break; + } + } else { + if (addr >= REG_TO_IDX(0x80) && addr < REG_TO_IDX(0xFF)) { + bank_addr = addr - REG_TO_IDX(0x80); + + switch (dev->reg_cfg1 & BIU_PCI1080_REG_BANK_MASK) { + case BIU_PCI1080_CONF1_SXP0: + ret = ql_read_bank_sxp(dev, bank_addr, 0); + break; + case BIU_PCI1080_CONF1_SXP1: + ret = ql_read_bank_sxp(dev, bank_addr, 1); + break; + case BIU_PCI1080_CONF1_DMA: + ret = ql_read_bank_dma(dev, bank_addr); + break; + default: + ret = ql_read_bank_risc(dev, bank_addr); + break; + } + break; + } + } + + ql_log("QL: [R16] Unhandled read %lX\n", IDX_TO_REG(addr)); + ret = 0; + break; + } + } + +#ifdef ENABLE_QL_LOG + ql_debug_check_for_nvram_access(addr); +#endif + + ql_log("QL: [R16] [%02lX] --> %4X\n", IDX_TO_REG(addr), ret); + return ret; +} + +static uint8_t +ql_mmio_read8(uint32_t addr, void* priv) +{ + /* The hw only supports 16-bit R/W access */ + return 0xFF; +} + +static uint32_t +ql_mmio_read32(uint32_t addr, void* priv) +{ + /* The hw only supports 16-bit R/W access */ + return 0xFFFFFFFF; +} + +static void +ql_mmio_write8(uint32_t addr, uint8_t val, void* priv) +{ + /* The hw only supports 16-bit R/W access */ +} + +static void +ql_mmio_write32(uint32_t addr, uint32_t val, void* priv) +{ + /* The hw only supports 16-bit R/W access */ +} + +static void +ql_ioport_write32(uint16_t addr, uint32_t val, void *priv) +{ + ql_mmio_write32(addr, val, priv); +} + +static void +ql_ioport_write16(uint16_t addr, uint16_t val, void *priv) +{ + ql_mmio_write16(addr, val, priv); +} + +static void +ql_ioport_write8(uint16_t addr, uint8_t val, void *priv) +{ + ql_mmio_write8(addr, val, priv); +} + +static uint16_t +ql_ioport_read16(uint16_t addr, void* priv) +{ + return ql_mmio_read16(addr, priv); +} + +static uint32_t +ql_ioport_read32(uint16_t addr, void* priv) +{ + return ql_mmio_read32(addr, priv); +} + +static uint8_t +ql_ioport_read8(uint16_t addr, void* priv) +{ + return ql_mmio_read8(addr, priv); +} + +static void +ql_pci_remap_rom_mapping(ql_t *dev, bool do_enable) +{ + uint32_t rom_addr; + + if (do_enable) { + rom_addr = dev->pci_cfg[PCI_REG_ROM_BAR_BYTE0]; + rom_addr |= dev->pci_cfg[PCI_REG_ROM_BAR_BYTE1] << 8; + rom_addr |= dev->pci_cfg[PCI_REG_ROM_BAR_BYTE2] << 16; + rom_addr |= dev->pci_cfg[PCI_REG_ROM_BAR_BYTE3] << 24; + rom_addr &= ~0x01; + + ql_log("QL: ROM Base %08lX\n", rom_addr); + mem_mapping_set_addr(&dev->rom_bar_mapping, rom_addr, dev->pci_rom_area_size); + } else { + mem_mapping_disable(&dev->rom_bar_mapping); + } +} + +static void +ql_pci_remap_mmio_mapping(ql_t *dev, bool do_enable) +{ + uint32_t mmio_base; + + if (do_enable) { + mmio_base = dev->pci_cfg[PCI_REG_BAR1_BYTE0]; + mmio_base |= dev->pci_cfg[PCI_REG_BAR1_BYTE1] << 8; + mmio_base |= dev->pci_cfg[PCI_REG_BAR1_BYTE2] << 16; + mmio_base |= dev->pci_cfg[PCI_REG_BAR1_BYTE3] << 24; + mmio_base &= ~0x0F; + + ql_log("QL: MMIO I/O Base %08lX\n", mmio_base); + mem_mapping_set_addr(&dev->mmio_bar_mapping, mmio_base, QL_PCI_MMIO_BAR_SIZE); + } else { + mem_mapping_disable(&dev->mmio_bar_mapping); + } +} + +static void +ql_pci_remap_ioport_mapping(ql_t *dev, bool do_enable) +{ + uint32_t ioport_base; + + ioport_base = dev->pci_cfg[PCI_REG_BAR0_BYTE0]; + ioport_base |= dev->pci_cfg[PCI_REG_BAR0_BYTE1] << 8; + ioport_base |= dev->pci_cfg[PCI_REG_BAR0_BYTE2] << 16; + ioport_base |= dev->pci_cfg[PCI_REG_BAR0_BYTE3] << 24; + ioport_base &= ~0x03; + + if (do_enable) { + ql_log("QL: I/O Base %08lX\n", ioport_base); + io_sethandler(ioport_base, + QL_PCI_IO_BAR_SIZE, + ql_ioport_read8, + ql_ioport_read16, + ql_ioport_read32, + ql_ioport_write8, + ql_ioport_write16, + ql_ioport_write32, + dev); + } else { + io_removehandler(ioport_base, + QL_PCI_IO_BAR_SIZE, + ql_ioport_read8, + ql_ioport_read16, + ql_ioport_read32, + ql_ioport_write8, + ql_ioport_write16, + ql_ioport_write32, + dev); + } +} + +static void +ql_pci_write(UNUSED(int func), int addr, UNUSED(int len), uint8_t val, void *priv) +{ + ql_t *dev = priv; + uint8_t write_bits_mask; + + ql_log("QL: PCI [%2X] <-- %X\n", addr, val); + + assert(addr < 256); + + switch (addr) { + case PCI_REG_COMMAND_L: + write_bits_mask = 0x57; + break; + case PCI_REG_COMMAND_H: + write_bits_mask = 0x01; + break; + + case PCI_REG_CACHELINE_SIZE: + write_bits_mask = 0xFF; + break; + + case PCI_REG_LATENCY_TIMER: + write_bits_mask = 0xF8; + break; + + /* BAR[0] length 0x100 */ + case PCI_REG_BAR0_BYTE0: + write_bits_mask = 0; + break; + case PCI_REG_BAR0_BYTE1: + case PCI_REG_BAR0_BYTE2: + case PCI_REG_BAR0_BYTE3: + write_bits_mask = 0xFF; + break; + + /* BAR[1] length 0x1000 */ + case PCI_REG_BAR1_BYTE0: + write_bits_mask = 0; + break; + case PCI_REG_BAR1_BYTE1: + write_bits_mask = 0xF0; + break; + case PCI_REG_BAR1_BYTE2: + write_bits_mask = 0xFF; + break; + case PCI_REG_BAR1_BYTE3: + write_bits_mask = 0xFF; + break; + + /* ROM BAR */ + case PCI_REG_ROM_BAR_BYTE0: + write_bits_mask = (uint8_t)(dev->rom_bar_mask >> 0); + break; + case PCI_REG_ROM_BAR_BYTE1: + write_bits_mask = (uint8_t)(dev->rom_bar_mask >> 8); + break; + case PCI_REG_ROM_BAR_BYTE2: + write_bits_mask = (uint8_t)(dev->rom_bar_mask >> 16); + break; + case PCI_REG_ROM_BAR_BYTE3: + write_bits_mask = (uint8_t)(dev->rom_bar_mask >> 24); + break; + + case PCI_REG_INT_LINE: + write_bits_mask = 0xFF; + break; + + case 0x40: + write_bits_mask = 0xFF; + break; + case 0x41: + write_bits_mask = 0x03; + break; + + /* PMCSR */ + case QL_PCI_PM_BASE + 4: + write_bits_mask = dev->has_pci_caps ? 0x03 : 0; + break; + case QL_PCI_PM_BASE + 5: + write_bits_mask = dev->has_pci_caps ? 0x1E : 0; + break; + + default: + write_bits_mask = 0; + break; + } + + val &= write_bits_mask; + + /* Disable old BAR mapping and handle command change */ + switch (addr) { + case PCI_REG_COMMAND_L: + if ((val ^ dev->pci_cfg[addr]) & PCI_COMMAND_IO) { + ql_pci_remap_ioport_mapping(dev, !!(val & PCI_COMMAND_IO)); + } + if ((val ^ dev->pci_cfg[addr]) & PCI_COMMAND_MEM) { + ql_pci_remap_mmio_mapping(dev, !!(val & PCI_COMMAND_MEM)); + + if ((val & PCI_COMMAND_MEM) && (dev->pci_cfg[PCI_REG_ROM_BAR_BYTE0] & 0x01)) { + ql_pci_remap_rom_mapping(dev, true); + } else { + ql_pci_remap_rom_mapping(dev, false); + } + } + break; + + case PCI_REG_BAR0_BYTE0: + case PCI_REG_BAR0_BYTE1: + case PCI_REG_BAR0_BYTE2: + case PCI_REG_BAR0_BYTE3: + ql_pci_remap_ioport_mapping(dev, false); + break; + + case PCI_REG_BAR1_BYTE0: + case PCI_REG_BAR1_BYTE1: + case PCI_REG_BAR1_BYTE2: + case PCI_REG_BAR1_BYTE3: + ql_pci_remap_mmio_mapping(dev, false); + break; + + case PCI_REG_ROM_BAR_BYTE0: + case PCI_REG_ROM_BAR_BYTE1: + case PCI_REG_ROM_BAR_BYTE2: + case PCI_REG_ROM_BAR_BYTE3: + ql_pci_remap_rom_mapping(dev, false); + break; + + default: + break; + } + + /* Update PCI register value */ + val |= dev->pci_cfg[addr] & ~write_bits_mask; + dev->pci_cfg[addr] = val; + + /* Enable new BAR mapping */ + switch (addr) { + case PCI_REG_BAR0_BYTE0: + case PCI_REG_BAR0_BYTE1: + case PCI_REG_BAR0_BYTE2: + case PCI_REG_BAR0_BYTE3: + if (dev->pci_cfg[PCI_REG_COMMAND_L] & PCI_COMMAND_IO) { + ql_pci_remap_ioport_mapping(dev, true); + } + break; + + case PCI_REG_BAR1_BYTE0: + case PCI_REG_BAR1_BYTE1: + case PCI_REG_BAR1_BYTE2: + case PCI_REG_BAR1_BYTE3: + if (dev->pci_cfg[PCI_REG_COMMAND_L] & PCI_COMMAND_MEM) { + ql_pci_remap_mmio_mapping(dev, true); + } + break; + + case PCI_REG_ROM_BAR_BYTE0: + case PCI_REG_ROM_BAR_BYTE1: + case PCI_REG_ROM_BAR_BYTE2: + case PCI_REG_ROM_BAR_BYTE3: + if (dev->pci_cfg[PCI_REG_COMMAND_L] & PCI_COMMAND_MEM) { + if (dev->pci_cfg[PCI_REG_ROM_BAR_BYTE0] & 0x01) { + ql_pci_remap_rom_mapping(dev, true); + } + } + break; + + default: + break; + } +} + +static uint8_t +ql_pci_read(UNUSED(int func), int addr, UNUSED(int len), void *priv) +{ + ql_t *dev = priv; + uint8_t ret; + + assert(addr < 256); + + ret = dev->pci_cfg[addr]; + + ql_log("QL: PCI [%2X] --> %X\n", addr, ret); + return ret; +} + +static void ql_init_scsi(ql_t *dev) { + switch (dev->isp_type) { + case QL_ISP1040: + /* Ultra SCSI, 40 MB/s */ + dev->xfer_rate_bps = 40 * 1000000.0; + dev->max_bus_count = 1; + break; + case QL_ISP1080: + /* Ultra2 SCSI, 80 MB/s */ + dev->xfer_rate_bps = 80 * 1000000.0; + dev->max_bus_count = 1; + break; + case QL_ISP1240: + /* Ultra SCSI, 40 MB/s */ + dev->xfer_rate_bps = 40 * 1000000.0; + dev->max_bus_count = 2; + break; + case QL_ISP1280: + /* Ultra2 SCSI, 80 MB/s */ + dev->xfer_rate_bps = 80 * 1000000.0; + dev->max_bus_count = 2; + break; + case QL_ISP12160: + /* Ultra3 SCSI, 160 MB/s */ + dev->xfer_rate_bps = 160 * 1000000.0; + dev->max_bus_count = 2; + break; + + default: + /* Should not happen */ + assert(false); + break; + } + + /* 86Box supports one SCSI bus per controller for now */ + dev->scsi_bus = scsi_get_bus(); + + scsi_bus_set_speed(dev->scsi_bus, dev->xfer_rate_bps); + + timer_add(&dev->cmd_timer, ql_sxp_timer_callback, dev, 0); +} + +static uint8_t +ql_get_eeprom_checksum(const uint8_t* buffer, size_t size) +{ + size_t i; + uint8_t crc = 0; + + for (i = 0; i < size - 1; i++) { + crc += buffer[i]; + } + + return -crc; +} + +static void +ql_create_eeprom_image_1040(uint8_t* nvr) +{ + /* ID header */ + nvr[0x00] = 'I'; + nvr[0x01] = 'S'; + nvr[0x02] = 'P'; + nvr[0x03] = ' '; + /* NVRAM version */ + nvr[0x04] = 7; + + /* ISP config */ + nvr[0x05] = 0x7A; + + /* Bus reset delay */ + nvr[0x06] = 5; + /* Bus retry count */ + nvr[0x07] = 0; + /* Bus retry delay */ + nvr[0x08] = 0; + + /* Bus config */ + nvr[0x09] = 0xF9; + /* Tag age limit */ + nvr[0x0A] = 8; + /* Bus flags */ + nvr[0x0B] = 0x0B; + /* Bus selection timeout */ + nvr[0x0C] = 250; + nvr[0x0D] = 0; + /* Bus max queue depth */ + nvr[0x0E] = 0x00; + nvr[0x0F] = 0x01; + + /* Board type */ + nvr[0x10] = 0x17; + + /* System Vendor */ + nvr[0x14] = 0x77; + nvr[0x15] = 0x10; + /* System ID */ + nvr[0x12] = 0x01; + nvr[0x13] = 0x00; + + /* ISP paramrter */ + nvr[0x16] = 0x03; + nvr[0x17] = 0x00; + + /* FW features */ + nvr[0x18] = 0x01; + + /* Target settings */ + for (uint32_t target_id = 0; target_id < QL_MAX_TID; target_id++) { + const uint32_t tid_offset = 28 + target_id * 6; + + /* Config */ + nvr[tid_offset + 0] = 0xFD; + /* Execution throttle */ + nvr[tid_offset + 1] = 16; + /* Sync period */ + nvr[tid_offset + 2] = 12; + /* Flags */ + nvr[tid_offset + 3] = 0x18; + } + + /* System ID offset in words */ + nvr[0x7E] = 0x09; +} + +static void +ql_create_eeprom_image_1080(ql_t *dev, uint8_t* nvr) +{ + /* ID header */ + nvr[0x00] = 'I'; + nvr[0x01] = 'S'; + nvr[0x02] = 'P'; + nvr[0x03] = ' '; + /* NVRAM version */ + nvr[0x04] = 1; + + /* ISP config */ + nvr[0x10] = 0x44; + /* Bus termination */ + nvr[0x11] = 0x0C; + /* FW features */ + nvr[0x14] = 0x21; + + /* Bus settings */ + for (uint32_t path_id = 0; path_id < dev->max_bus_count; path_id++) { + const uint32_t bus_offset = path_id * 112; + + /* Bus config 1 */ + nvr[bus_offset + 0x18] = 0x67; + /* Bus reset delay */ + nvr[bus_offset + 0x19] = 5; + /* Bus retry count */ + nvr[bus_offset + 0x1A] = 0; + /* Bus retry delay */ + nvr[bus_offset + 0x1B] = 0; + /* Bus config 2 */ + nvr[bus_offset + 0x1C] = 0x39; + /* Bus selection timeout */ + nvr[bus_offset + 0x1E] = 250; + nvr[bus_offset + 0x1F] = 0; + /* Bus max queue depth */ + nvr[bus_offset + 0x20] = 0x00; + nvr[bus_offset + 0x21] = 0x01; + + /* Target settings */ + for (uint32_t target_id = 0; target_id < QL_MAX_TID; target_id++) { + const uint32_t tid_offset = bus_offset + 40 + target_id * 6; + + /* Config */ + nvr[tid_offset + 0] = 0xFD; + /* Execution throttle */ + nvr[tid_offset + 1] = 16; + /* Sync period */ + nvr[tid_offset + 2] = 10; + /* Flags */ + if (dev->isp_type == QL_ISP12160) { + nvr[tid_offset + 3] = 0x30; + } else { + nvr[tid_offset + 3] = 0x18; + } + } + } + + /* System Vendor */ + nvr[0xFA] = 0x77; + nvr[0xFB] = 0x10; + + /* System ID */ + switch (dev->isp_type) { + case QL_ISP1280: + nvr[0xFC] = 0x06; + nvr[0xFD] = 0x00; + break; + case QL_ISP12160: + nvr[0xFC] = 0x07; + nvr[0xFD] = 0x00; + break; + + default: + nvr[0xFC] = 0x01; + nvr[0xFD] = 0x00; + break; + } + + /* System Vendor offset in words */ + if (dev->isp_type != QL_ISP1080) { + nvr[0xFE] = 0xFA; + } +} + +static void +ql_register_eeprom_device(const device_t *info, ql_t *dev) +{ + int inst = device_get_instance(); + nmc93cxx_eeprom_params_t params; + char filename[1024] = { 0 }; + nmc93cxx_eeprom_type nvram_type; + size_t nvram_size; + + if (dev->isp_type == QL_ISP1040) { + nvram_type = NMC_93C46_x16_64; + nvram_size = 2 * 64; + } else { + nvram_type = NMC_93C56_x16_128; + nvram_size = 2 * 128; + } + + uint8_t* nvr = calloc(1, nvram_size); + + if (dev->isp_type == QL_ISP1040) { + ql_create_eeprom_image_1040(nvr); + } else { + ql_create_eeprom_image_1080(dev, nvr); + } + + /* Checksum */ + nvr[nvram_size - 1] = ql_get_eeprom_checksum(nvr, nvram_size); + + snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, inst); + params.type = nvram_type; + params.default_content = nvr; + params.filename = filename; + dev->eeprom_device = device_add_inst_params(&nmc93cxx_device, inst, ¶ms); + + free(nvr); +} + +static void +am29_create_flash_image(const device_t *info, flash_t *dev) +{ + FILE *fp; + size_t bytes_written; + const char *bios_path; + + dev->array_data = calloc(1, AM29_FLASH_SIZE); + + snprintf(dev->filename, + sizeof(dev->filename), + "am29f400_option_rom_%s_%d.bin", + info->internal_name, + device_get_instance()); + + /* Load the flash image, if it is already present in the system */ + fp = nvr_fopen(dev->filename, "rb"); + if (fp) { + bytes_written = fread(dev->array_data, 1, AM29_FLASH_SIZE, fp); + } else { + bios_path = device_get_bios_file(info, device_get_config_bios(QL_CFG_BIOS_REVISION), 0); + + /* Clone the ROM data to create a new image */ + fp = rom_fopen(bios_path, "rb"); + if (fp) { + bytes_written = fread(dev->array_data, 1, AM29_FLASH_SIZE, fp); + } else { + bytes_written = 0; + ql_log("Unable to load the AM29 Flash ROM file\n"); + } + } + if (fp) + fclose(fp); + + /* Fill the rest with 0xFF (make the memory content erased) */ + if (bytes_written < AM29_FLASH_SIZE) { + ql_log("Less than %lu bytes read from the AM29 Flash ROM file\n", (uint32_t)bytes_written); + + memset(dev->array_data + bytes_written, 0xFF, AM29_FLASH_SIZE - bytes_written); + } +} + +static void +am29_update_flash_image(flash_t *dev) +{ + FILE *fp = nvr_fopen(dev->filename, "wb"); + + /* Replace the original flash image with new version */ + if (fp) { + fwrite(dev->array_data, AM29_FLASH_SIZE, 1, fp); + fclose(fp); + } + + free(dev->array_data); +} + +static void +am29_init(const device_t *info, flash_t *dev) +{ + uint32_t flash_type = (info->local & QL_DEV_FLASH_TYPE_MASK) >> QL_DEV_FLASH_TYPE_SHIFT; + double access_time_us; + + dev->manufacturer_id = AM29_MANUFACTURER_ID; + dev->block_select_addr_mask = 0x1C000; // A14-A16 + + if (flash_type == QL_FLASH_AM29F010) { + dev->model_id = AM29F010_MODEL_ID; + dev->cmd_cycle_addr_mask = 0x1FFFF; // A0-A16 + dev->addr_5555_phys = 0x5555; + dev->addr_AAAA_phys = 0x2AAA; + dev->program_time_us = 14.0; + dev->block_erase_time_us = 200000.0; // 0.2 sec + dev->chip_erase_time_us = 1000000.0; // 1 sec + access_time_us = 0.045; + } else { + dev->model_id = AM29LV010B_MODEL_ID; + dev->cmd_cycle_addr_mask = 0x7FF; // A0-A10 + dev->addr_5555_phys = 0x555; + dev->addr_AAAA_phys = 0x2AA; + dev->program_time_us = 9.0; + dev->block_erase_time_us = 200000.0; // 0.2 sec + dev->chip_erase_time_us = 1000000.0; // 1 sec + access_time_us = 0.070; + } + dev->access_cycles = (cpuclock / (1000000.0 / access_time_us)); + + dev->block[0].start_addr = 0x00000; + dev->block[0].end_addr = 0x03FFF; + + dev->block[1].start_addr = 0x04000; + dev->block[1].end_addr = 0x07FFF; + + dev->block[2].start_addr = 0x08000; + dev->block[2].end_addr = 0x0BFFF; + + dev->block[3].start_addr = 0x0C000; + dev->block[3].end_addr = 0x0FFFF; + + dev->block[4].start_addr = 0x10000; + dev->block[4].end_addr = 0x13FFF; + + dev->block[5].start_addr = 0x14000; + dev->block[5].end_addr = 0x17FFF; + + dev->block[6].start_addr = 0x18000; + dev->block[6].end_addr = 0x1BFFF; + + dev->block[7].start_addr = 0x1C000; + dev->block[7].end_addr = 0x1FFFF; + + am29_create_flash_image(info, dev); + am29_set_mode(dev, M_READ_ARRAY); + + timer_add(&dev->erase_accept_timeout_timer, am29_erase_begin_timer_callback, dev, 0); + timer_add(&dev->cmd_complete_timer, am29_cmd_complete_timer_callback, dev, 0); + + /* Assign block numbers */ + for (uint32_t i = 0; i < AM29_MAX_BLOCKS; i++) { + flash_block_t *block = &dev->block[i]; + + /* We maintain a bitmap of blocks to erase */ + assert(i < 32); + assert(sizeof(dev->blocks_to_erase_bitmap) == sizeof(uint32_t)); + + block->number = i; + } +} + +static void +ql_init_pci_config(ql_t *dev) +{ + const uint8_t *eeprom_data = (const uint8_t *)nmc93cxx_eeprom_data(dev->eeprom_device); + + memset(dev->pci_cfg, 0, sizeof(dev->pci_cfg)); + + dev->pci_cfg[PCI_REG_STATUS_L] = PCI_STATUS_L_FAST_B2B | PCI_STATUS_L_CAPAB; + dev->pci_cfg[PCI_REG_STATUS_H] = PCI_DEVSEL_MEDIUM; + + /* QLA1xxx */ + switch (dev->isp_type) { + case QL_ISP1040: + dev->pci_cfg[PCI_REG_DEVICE_ID_L] = 0x20; + dev->pci_cfg[PCI_REG_DEVICE_ID_H] = 0x10; + dev->pci_cfg[PCI_REG_REVISION] = 0x05; + break; + case QL_ISP1080: + dev->pci_cfg[PCI_REG_DEVICE_ID_L] = 0x80; + dev->pci_cfg[PCI_REG_DEVICE_ID_H] = 0x10; + dev->pci_cfg[PCI_REG_REVISION] = 0x01; + break; + case QL_ISP1240: + dev->pci_cfg[PCI_REG_DEVICE_ID_L] = 0x40; + dev->pci_cfg[PCI_REG_DEVICE_ID_H] = 0x12; + dev->pci_cfg[PCI_REG_REVISION] = 0x01; + break; + case QL_ISP1280: + dev->pci_cfg[PCI_REG_DEVICE_ID_L] = 0x80; + dev->pci_cfg[PCI_REG_DEVICE_ID_H] = 0x12; + dev->pci_cfg[PCI_REG_REVISION] = 0x01; + break; + case QL_ISP12160: + dev->pci_cfg[PCI_REG_DEVICE_ID_L] = 0x16; + dev->pci_cfg[PCI_REG_DEVICE_ID_H] = 0x12; + dev->pci_cfg[PCI_REG_REVISION] = 0x06; + dev->pci_cfg[PCI_REG_STATUS_L] |= PCI_STATUS_L_66MHZ; + break; + + default: + /* Should not happen */ + assert(false); + break; + } + + /* Actual system ID comes from NVRAM. The ISP1040 system ID words are in swapped order */ + if (dev->isp_type == QL_ISP1040) { + dev->pci_cfg[PCI_REG_SUBVEN_ID_L] = eeprom_data[0x14]; + dev->pci_cfg[PCI_REG_SUBVEN_ID_H] = eeprom_data[0x15]; + dev->pci_cfg[PCI_REG_SUBSYS_ID_L] = eeprom_data[0x12]; + dev->pci_cfg[PCI_REG_SUBSYS_ID_H] = eeprom_data[0x13]; + } else { + dev->pci_cfg[PCI_REG_SUBVEN_ID_L] = eeprom_data[0xFA]; + dev->pci_cfg[PCI_REG_SUBVEN_ID_H] = eeprom_data[0xFB]; + dev->pci_cfg[PCI_REG_SUBSYS_ID_L] = eeprom_data[0xFC]; + dev->pci_cfg[PCI_REG_SUBSYS_ID_H] = eeprom_data[0xFD]; + } + + /* QLogic */ + dev->pci_cfg[PCI_REG_VENDOR_ID_L] = 0x77; + dev->pci_cfg[PCI_REG_VENDOR_ID_H] = 0x10; + + /* SCSI Controller */ + dev->pci_cfg[PCI_REG_CLASS] = 0x01; + + dev->pci_cfg[PCI_REG_CACHELINE_SIZE] = 64; + dev->pci_cfg[PCI_REG_LATENCY_TIMER] = 248; + dev->pci_cfg[PCI_REG_INT_PIN] = PCI_INTA; + + /* BAR[0] I/O ports */ + dev->pci_cfg[PCI_REG_BAR0_BYTE0] = 0x01; + + /* BAR[1] Memory */ + dev->pci_cfg[PCI_REG_BAR1_BYTE0] = 0; + + dev->pci_cfg[0x40] = 0x44; + + if (dev->has_pci_caps) { + dev->pci_cfg[PCI_REG_STATUS_L] |= PCI_STATUS_L_CAPAB; + + dev->pci_cfg[PCI_REG_CAPS_PTR] = QL_PCI_PM_BASE; + + /* Power management capabilities */ + dev->pci_cfg[QL_PCI_PM_BASE + 0] = 0x01; // POWER MANAGEMENT + dev->pci_cfg[QL_PCI_PM_BASE + 1] = 0x00; // Last entry + /* PMC */ + dev->pci_cfg[QL_PCI_PM_BASE + 2] = 0x01; // Version 1.0 + dev->pci_cfg[QL_PCI_PM_BASE + 3] = 0x00; + /* PMCSR */ + dev->pci_cfg[QL_PCI_PM_BASE + 4] = 0x00; + dev->pci_cfg[QL_PCI_PM_BASE + 5] = 0x00; + /* PMCSR_BSE */ + dev->pci_cfg[QL_PCI_PM_BASE + 6] = 0x00; + /* Data */ + dev->pci_cfg[QL_PCI_PM_BASE + 7] = 0x00; + } + + /* This area for some reason holds the VenID/DevID pair */ + for (uint32_t reg = 0x4C; reg < sizeof(dev->pci_cfg); reg += 4) { + dev->pci_cfg[reg + 0] = dev->pci_cfg[PCI_REG_VENDOR_ID_L]; + dev->pci_cfg[reg + 1] = dev->pci_cfg[PCI_REG_VENDOR_ID_H]; + dev->pci_cfg[reg + 2] = dev->pci_cfg[PCI_REG_DEVICE_ID_L]; + dev->pci_cfg[reg + 3] = dev->pci_cfg[PCI_REG_DEVICE_ID_H]; + } +} + +static void +ql_reset(void *priv) +{ + ql_t *dev = priv; + + /* Clear all BAR memory mappings and I/O handlers */ + ql_pci_remap_ioport_mapping(dev, false); + ql_pci_remap_mmio_mapping(dev, false); + ql_pci_remap_rom_mapping(dev, false); + + /* Reset PCI configuration registers */ + ql_init_pci_config(dev); + + ql_reset_asic(dev); + am29_reset(&dev->flash_device); +} + +static void * +ql_init(const device_t *info) +{ + ql_t *dev = calloc(1, sizeof(ql_t)); + + dev->isp_type = info->local & QL_DEV_CHIP_TYPE_MASK; + dev->isp_rev = (info->local & QL_DEV_CHIP_REV_MASK) >> QL_DEV_CHIP_REV_SHIFT; + + dev->fw_version = device_get_bios_local(device_context_get_device(), device_get_config_bios(QL_CFG_BIOS_REVISION)); + dev->has_pci_caps = (dev->isp_type != QL_ISP1040); + + /* + * Determine size of the area to map the expansion ROM. + * NOTE: On most ISP chips this area is smaller than the FLASH size. + * The QLogic boot code reads the required data directly from the FLASH at run-time. + */ + if (dev->isp_type == QL_ISP12160) { + dev->pci_rom_area_size = QL_PCI_ROM_BAR_128K_SIZE; + } else { + dev->pci_rom_area_size = QL_PCI_ROM_BAR_64K_SIZE; + } + + /* Determine writable bits of the ROM BAR */ + if (!device_get_config_int(QL_CFG_BIOS_ENABLE)) { + dev->rom_bar_mask = 0; + } else { + uint32_t length = dev->pci_rom_area_size; + uint32_t ln2size = 0; + + while (length != 1) { + ln2size++; + length >>= 1; + } + + dev->rom_bar_mask = ~((1 << ln2size) - 1); + dev->rom_bar_mask |= 1; // Expansion ROM enable bit + } + + ql_init_scsi(dev); + am29_init(info, &dev->flash_device); + ql_register_eeprom_device(info, dev); + ql_init_pci_config(dev); + ql_reset_asic(dev); + + mem_mapping_add(&dev->rom_bar_mapping, + 0, + 0, + ql_rom_bar_mmio_read8, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + MEM_MAPPING_EXTERNAL, + dev); + mem_mapping_disable(&dev->rom_bar_mapping); + + mem_mapping_add(&dev->mmio_bar_mapping, + 0, + 0, + ql_mmio_read8, + ql_mmio_read16, + ql_mmio_read32, + ql_mmio_write8, + ql_mmio_write16, + ql_mmio_write32, + NULL, + MEM_MAPPING_EXTERNAL, + dev); + mem_mapping_disable(&dev->mmio_bar_mapping); + + pci_add_card(PCI_CARD_NORMAL, ql_pci_read, ql_pci_write, dev, &dev->pci_slot); + return dev; +} + +static void +ql_close(void *priv) +{ + ql_t *dev = priv; + + am29_update_flash_image(&dev->flash_device); + + fifo8_destroy(&dev->abort_iocbs_fifo); + + if (dev->scsi_data_buffer) { + free(dev->scsi_data_buffer); + } + free(dev); +} + +// clang-format off +static const device_config_t qla1040b_config[] = { + { + .name = QL_CFG_BIOS_REVISION, + .description = "BIOS Revision", + .type = CONFIG_BIOS, + .default_string = "v6_26", + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .bios = { + { + .name = "Version 6.20", + .internal_name = "v6_20", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(4, 53, 0), + .size = 0x10000, + .files = { "roms/scsi/qlogic/qla1040_v6_20.bin", "" } + }, + { + .name = "Version 6.26", + .internal_name = "v6_26", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(4, 55, 0), + .size = 0x10000, + .files = { "roms/scsi/qlogic/qla1040_v6_26.bin", "" } + }, + { .files_no = 0 } + }, + }, + { + .name = QL_CFG_BIOS_ENABLE, + .description = "Enable BIOS", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +static const device_config_t qla1080_config[] = { + { + .name = QL_CFG_BIOS_REVISION, + .description = "BIOS Revision", + .type = CONFIG_BIOS, + .default_string = "v1_19", + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .bios = { + { + .name = "Version 1.11", + .internal_name = "v1_11", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(2, 13, 0), + .size = 0x10000, + .files = { "roms/scsi/qlogic/qla1080_v1_11.bin", "" } + }, + { + .name = "Version 1.16", + .internal_name = "v1_16", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(8, 3, 0), + .size = 0x20000, + .files = { "roms/scsi/qlogic/qla1080_v1_16.bin", "" } + }, + { + .name = "Version 1.19", + .internal_name = "v1_19", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(8, 9, 0), + .size = 0x20000, + .files = { "roms/scsi/qlogic/qla1080_v1_19.bin", "" } + }, + { .files_no = 0 } + }, + }, + { + .name = QL_CFG_BIOS_ENABLE, + .description = "Enable BIOS", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +static const device_config_t qla1240_config[] = { + { + .name = QL_CFG_BIOS_REVISION, + .description = "BIOS Revision", + .type = CONFIG_BIOS, + .default_string = "v1_26", + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .bios = { + { + .name = "Version 1.26", + .internal_name = "v1_26", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(2, 13, 0), + .size = 0x10000, + .files = { "roms/scsi/qlogic/qla1240_v1_26.bin", "" } + }, + { .files_no = 0 } + }, + }, + { + .name = QL_CFG_BIOS_ENABLE, + .description = "Enable BIOS", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +static const device_config_t qla1280_config[] = { + { + .name = QL_CFG_BIOS_REVISION, + .description = "BIOS Revision", + .type = CONFIG_BIOS, + .default_string = "v1_30", + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .bios = { + { + .name = "Version 1.30", + .internal_name = "v1_30", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(8, 15, 0), + .size = 0x20000, + .files = { "roms/scsi/qlogic/qla1280_v1_30.bin", "" } + }, + { .files_no = 0 } + }, + }, + { + .name = QL_CFG_BIOS_ENABLE, + .description = "Enable BIOS", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +static const device_config_t qla12160a_config[] = { + { + .name = QL_CFG_BIOS_REVISION, + .description = "BIOS Revision", + .type = CONFIG_BIOS, + .default_string = "v1_37", + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .bios = { + { + .name = "Version 1.34", + .internal_name = "v1_34", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(10, 4, 0), + .size = 0x20000, + .files = { "roms/scsi/qlogic/qla12160_v1_34.bin", "" } + }, + { + .name = "Version 1.37", + .internal_name = "v1_37", + .bios_type = BIOS_NORMAL, + .files_no = 1, + .local = ISP_FW_VER(10, 4, 0), + .size = 0x20000, + .files = { "roms/scsi/qlogic/qla12160_v1_37.bin", "" } + }, + { .files_no = 0 } + }, + }, + { + .name = QL_CFG_BIOS_ENABLE, + .description = "Enable BIOS", + .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 qla1040b_device = { + .name = "QLogic QLA1040B", + .internal_name = "qla1040b", + .flags = DEVICE_PCI, + .local = QL_ISP1040 | QL_REV_ISP1040B | QL_FLASH_AM29F010, + .init = ql_init, + .close = ql_close, + .reset = ql_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = qla1040b_config, +}; + +const device_t qla1080_device = { + .name = "QLogic QLA1080", + .internal_name = "qla1080", + .flags = DEVICE_PCI, + .local = QL_ISP1080 | QL_REV_ISP1080 | QL_FLASH_AM29F010, + .init = ql_init, + .close = ql_close, + .reset = ql_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = qla1080_config, +}; + +const device_t qla1240_device = { + .name = "QLogic QLA1240", + .internal_name = "qla1240", + .flags = DEVICE_PCI, + .local = QL_ISP1240 | QL_REV_ISP1080 | QL_FLASH_AM29LV010B, + .init = ql_init, + .close = ql_close, + .reset = ql_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = qla1240_config, +}; + +const device_t qla1280_device = { + .name = "QLogic QLA1280", + .internal_name = "qla1280", + .flags = DEVICE_PCI, + .local = QL_ISP1280 | QL_REV_ISP1080 | QL_FLASH_AM29LV010B, + .init = ql_init, + .close = ql_close, + .reset = ql_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = qla1280_config, +}; + +const device_t qla12160a_device = { + .name = "QLogic QLA12160A", + .internal_name = "qla12160a", + .flags = DEVICE_PCI, + .local = QL_ISP12160 | QL_REV_ISP1080 | QL_FLASH_AM29LV010B, + .init = ql_init, + .close = ql_close, + .reset = ql_reset, + .available = NULL, + .speed_changed = NULL, + .force_redraw = NULL, + .config = qla12160a_config, +}; diff --git a/src/scsi/scsi_t128.c b/src/scsi/scsi_t128.c index 5b893dd70..2c2451cc7 100644 --- a/src/scsi/scsi_t128.c +++ b/src/scsi/scsi_t128.c @@ -27,7 +27,6 @@ #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/io.h> -#include "cpu.h" #include <86box/timer.h> #include <86box/dma.h> #include <86box/pic.h> @@ -41,6 +40,7 @@ #include <86box/scsi_device.h> #include <86box/scsi_ncr5380.h> #include <86box/scsi_t128.h> +#include "cpu.h" #define T128_ROM "roms/scsi/ncr5380/trantor_t128_bios_v1.12.bin" @@ -75,7 +75,7 @@ t128_write(uint32_t addr, uint8_t val, void *priv) if ((addr >= 0x1800) && (addr < 0x1880)) t128->ext_ram[addr & 0x7f] = val; else if ((addr >= 0x1c00) && (addr < 0x1c20)) { - t128_log("T128 ctrl write=%02x, mode=%02x.\n", val, ncr->mode & MODE_DMA); + t128_log("T128 ctrl write=%02x, mode=%02x.\n", val & 0x10, ncr->mode & MODE_DMA); t128->ctrl = val; } else if ((addr >= 0x1d00) && (addr < 0x1e00)) ncr5380_write((addr - 0x1d00) >> 5, val, ncr); @@ -141,6 +141,7 @@ t128_read(uint32_t addr, void *priv) if (ncr->mode & MODE_ENA_EOP_INT) { t128_log("T128 read irq\n"); ncr5380_irq(ncr, 1); + ncr->isr |= STATUS_INT; } scsi_bus->bus_out |= BUS_CD; scsi_bus->tx_mode = PIO_TX_BUS; @@ -298,6 +299,7 @@ t128_callback(void *priv) if (ncr->mode & MODE_ENA_EOP_INT) { t128_log("T128 write irq\n"); ncr5380_irq(ncr, 1); + ncr->isr |= STATUS_INT; } scsi_bus->tx_mode = PIO_TX_BUS; timer_stop(&t128->timer); @@ -497,6 +499,7 @@ t128_init(const device_t *info) ncr->dma_send_ext = t128_dma_send_ext; ncr->dma_initiator_receive_ext = t128_dma_initiator_receive_ext; ncr->timer = t128_timer_on_auto; + ncr->irq_ena = NULL; scsi_bus->bus_device = ncr->bus; scsi_bus->timer = ncr->timer; scsi_bus->priv = ncr->priv; @@ -566,12 +569,13 @@ static const device_config_t t128_config[] = { .description = "IRQ", .type = CONFIG_SELECTION, .default_string = NULL, - .default_int = 5, + .default_int = -1, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "None", .value = -1 }, { .description = "IRQ 3", .value = 3 }, + { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 7", .value = 7 }, { .description = "IRQ 10", .value = 10 }, diff --git a/src/sound/snd_ad1816.c b/src/sound/snd_ad1816.c index 4e929794d..d47f70a04 100644 --- a/src/sound/snd_ad1816.c +++ b/src/sound/snd_ad1816.c @@ -819,7 +819,7 @@ ad1816_init(const device_t *info) ad1816->sb->opl_enabled = 1; sb_dsp_set_real_opl(&ad1816->sb->dsp, FM_YMF262); - sb_dsp_init(&ad1816->sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, ad1816); + sb_dsp_init(&ad1816->sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, ad1816); sb_dsp_setaddr(&ad1816->sb->dsp, ad1816->cur_sb_addr); sb_dsp_setirq(&ad1816->sb->dsp, ad1816->cur_irq); sb_dsp_setirq(&ad1816->sb->dsp, ad1816->cur_dma); diff --git a/src/sound/snd_azt2316a.c b/src/sound/snd_azt2316a.c index 08fca0f87..b7f981d5c 100644 --- a/src/sound/snd_azt2316a.c +++ b/src/sound/snd_azt2316a.c @@ -1835,7 +1835,7 @@ azt_init(const device_t *info) fm_driver_get(FM_YMF262, &azt2316a->sb->opl); sb_dsp_set_real_opl(&azt2316a->sb->dsp, 1); - sb_dsp_init(&azt2316a->sb->dsp, SBPRO2_DSP_302, azt2316a->type, azt2316a); + sb_dsp_init(&azt2316a->sb->dsp, SBPRO_DSP_302, azt2316a->type, azt2316a); sb_dsp_setaddr(&azt2316a->sb->dsp, azt2316a->cur_addr); sb_dsp_setirq(&azt2316a->sb->dsp, azt2316a->cur_irq); sb_dsp_setdma8(&azt2316a->sb->dsp, azt2316a->cur_dma); diff --git a/src/sound/snd_cmi8x38.c b/src/sound/snd_cmi8x38.c index d4490ac23..826f44bb0 100644 --- a/src/sound/snd_cmi8x38.c +++ b/src/sound/snd_cmi8x38.c @@ -877,7 +877,7 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv) dev->sb->dsp.sbleftright_default = !!(val & 0x02); /* Enable or disable SB16 mode. */ - dev->sb->dsp.sb_type = (val & 0x01) ? SBPRO2_DSP_302 : SB16_DSP_405; + dev->sb->dsp.sb_type = (val & 0x01) ? SBPRO_DSP_302 : SB16_DSP_405; break; case 0x22: diff --git a/src/sound/snd_mpu401.c b/src/sound/snd_mpu401.c index 87c191cec..2562f16cc 100644 --- a/src/sound/snd_mpu401.c +++ b/src/sound/snd_mpu401.c @@ -316,7 +316,9 @@ MPU401_Reset(mpu_t *mpu) mpu->ch_toref[i] = 4; /* Dummy reftable. */ } - MPU401_ClrQueue(mpu); + mpu->state.irq_pending = 0; + MPU401_UpdateIRQ(mpu, 0); + mpu->state.data_onoff = -1; mpu->state.req_mask = 0; diff --git a/src/sound/snd_optimc.c b/src/sound/snd_optimc.c index 5ea5d773c..0bad013ff 100644 --- a/src/sound/snd_optimc.c +++ b/src/sound/snd_optimc.c @@ -1095,7 +1095,7 @@ optimc_init(const device_t *info) optimc->fm_type = (info->local & OPTIMC_OPL4) ? FM_YMF278B : FM_YMF262; sb_dsp_set_real_opl(&optimc->sb->dsp, optimc->fm_type != FM_YMF278B); - sb_dsp_init(&optimc->sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, optimc); + sb_dsp_init(&optimc->sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, optimc); sb_dsp_setaddr(&optimc->sb->dsp, optimc->cur_addr); sb_dsp_setirq(&optimc->sb->dsp, optimc->cur_irq); sb_dsp_setdma8(&optimc->sb->dsp, optimc->cur_dma); diff --git a/src/sound/snd_pas16.c b/src/sound/snd_pas16.c index 9b82d580d..d3addf41a 100644 --- a/src/sound/snd_pas16.c +++ b/src/sound/snd_pas16.c @@ -2327,7 +2327,7 @@ pas16_init(const device_t *info) pas16->has_scsi = (!pas16->type) || (pas16->type == 0x0f); fm_driver_get(FM_YMF262, &pas16->opl); sb_dsp_set_real_opl(&pas16->dsp, 1); - sb_dsp_init(&pas16->dsp, SB_DSP_201, SB_SUBTYPE_DEFAULT, pas16); + sb_dsp_init(&pas16->dsp, SB_DSP_200, SB_SUBTYPE_MVD201, pas16); pas16->mpu = (mpu_t *) calloc(1, sizeof(mpu_t)); mpu401_init(pas16->mpu, 0, 0, M_UART, device_get_config_int("receive_input401")); sb_dsp_set_mpu(&pas16->dsp, pas16->mpu); diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index c2193869d..20f8b5694 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -15,7 +15,7 @@ * * Copyright 2008-2020 Sarah Walker. * Copyright 2016-2020 Miran Grca. - * Copyright 2024-2025 Jasmine Iwanek. + * Copyright 2024-2026 Jasmine Iwanek. */ #include #include @@ -45,9 +45,10 @@ #include <86box/plat_unused.h> #include <86box/snd_azt2316a.h> -#define SB_1 0 -#define SB_15 1 -#define SB_2 2 +#define SB_1 0 +#define SB_15 1 +#define SB_2 2 +#define THUNDERBOARD 3 #define SB_16_PNP_NOIDE 0 #define SB_16_PNP_IDE 1 @@ -2959,6 +2960,10 @@ sb_init(UNUSED(const device_t *info)) sb->cms_enabled = device_get_config_int("cms"); mixer_addr = device_get_config_int("mixaddr"); break; + case THUNDERBOARD: + model = SB_DSP_200; + sb->cms_enabled = 0; + break; } sb->opl_enabled = device_get_config_int("opl"); @@ -3006,7 +3011,7 @@ sb_init(UNUSED(const device_t *info)) &sb->cms); } - if (mixer_addr > 0x000) { + if (mixer_addr > 0x0000) { sb->mixer_enabled = 1; io_sethandler(mixer_addr + 4, 0x0002, sb_ct1335_mixer_read, NULL, NULL, @@ -3026,6 +3031,58 @@ sb_init(UNUSED(const device_t *info)) return sb; } +void * +thunderboard_init(UNUSED(const device_t *info)) +{ + /* ThunderBoard port mappings, 210h to 260h in 10h steps + 2x6, 2xA, 2xC, 2xE -> DSP chip */ + sb_t *sb = calloc(1, sizeof(sb_t)); + const uint16_t addr = device_get_config_hex16("base"); + + sb->opl_enabled = device_get_config_int("opl"); + if (sb->opl_enabled) + fm_driver_get(FM_YM3812, &sb->opl); + + sb_dsp_set_real_opl(&sb->dsp, 0); + sb_dsp_init(&sb->dsp, SB_DSP_200, SB_SUBTYPE_MVD201, sb); + /* DSP I/O handler is activated in sb_dsp_setaddr */ + sb_dsp_setaddr(&sb->dsp, addr); + sb_dsp_setirq(&sb->dsp, device_get_config_int("irq")); + sb_dsp_setdma8(&sb->dsp, 1); + + if (device_get_config_int("gameport")) { + sb->gameport = gameport_add(&gameport_device); + sb->gameport_addr = 0x200; + gameport_remap(sb->gameport, sb->gameport_addr); + } + + /* DSP I/O handler is activated in sb_dsp_setaddr */ + if (sb->opl_enabled) { + io_sethandler(addr, 0x0002, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_sethandler(addr + 8, 0x0002, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_sethandler(0x0388, 0x0002, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + } + + + sb->cms_enabled = 0; + sb->mixer_enabled = 0; + sound_add_handler(sb_get_buffer_sb2, sb); + if (sb->opl_enabled) + music_add_handler(sb_get_music_buffer_sb2, sb); + sound_set_cd_audio_filter(sb2_filter_cd_audio, sb); + + return sb; +} + void * sb_mcv_init(UNUSED(const device_t *info)) { @@ -3170,7 +3227,7 @@ sb_pro_v2_init(UNUSED(const device_t *info)) fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_set_real_opl(&sb->dsp, 1); - sb_dsp_init(&sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, sb); + sb_dsp_init(&sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); sb_dsp_setirq(&sb->dsp, device_get_config_int("irq")); sb_dsp_setdma8(&sb->dsp, device_get_config_int("dma")); @@ -3226,7 +3283,7 @@ sb_pro_mcv_init(UNUSED(const device_t *info)) fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_set_real_opl(&sb->dsp, 1); - sb_dsp_init(&sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, sb); + sb_dsp_init(&sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); sb->mixer_enabled = 1; @@ -3258,7 +3315,7 @@ sb_pro_compat_init(UNUSED(const device_t *info)) fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_set_real_opl(&sb->dsp, 1); - sb_dsp_init(&sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, sb); + sb_dsp_init(&sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); sb->mixer_enabled = 1; @@ -3938,7 +3995,7 @@ ess_x688_init(UNUSED(const device_t *info)) fm_driver_get(info->local ? FM_ESFM : FM_YMF262, &ess->opl); sb_dsp_set_real_opl(&ess->dsp, 1); - sb_dsp_init(&ess->dsp, SBPRO2_DSP_302, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); + sb_dsp_init(&ess->dsp, SBPRO_DSP_301, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); sb_dsp_setaddr(&ess->dsp, addr); sb_dsp_setirq(&ess->dsp, device_get_config_int("irq")); sb_dsp_setdma8(&ess->dsp, device_get_config_int("dma")); @@ -4050,7 +4107,7 @@ ess_x688_pnp_init(UNUSED(const device_t *info)) fm_driver_get(info->local ? FM_ESFM : FM_YMF262, &ess->opl); sb_dsp_set_real_opl(&ess->dsp, 1); - sb_dsp_init(&ess->dsp, SBPRO2_DSP_302, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); + sb_dsp_init(&ess->dsp, SBPRO_DSP_301, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); sb_dsp_setdma16_supported(&ess->dsp, 0); ess_mixer_reset(ess); @@ -4136,7 +4193,7 @@ ess_x688_mca_init(UNUSED(const device_t *info)) fm_driver_get(info->local ? FM_ESFM : FM_YMF262, &ess->opl); sb_dsp_set_real_opl(&ess->dsp, 1); - sb_dsp_init(&ess->dsp, SBPRO2_DSP_302, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); + sb_dsp_init(&ess->dsp, SBPRO_DSP_301, info->local ? SB_SUBTYPE_ESS_ES1688 : SB_SUBTYPE_ESS_ES688, ess); sb_dsp_setdma16_supported(&ess->dsp, 0); ess_mixer_reset(ess); @@ -4288,6 +4345,68 @@ static const device_config_t sb_config[] = { { .name = "", .description = "", .type = CONFIG_END } }; +static const device_config_t thunderboard_config[] = { + { + .name = "base", + .description = "Address", + .type = CONFIG_HEX16, + .default_string = NULL, + .default_int = 0x220, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "0x210", .value = 0x210 }, + { .description = "0x220", .value = 0x220 }, + { .description = "0x230", .value = 0x230 }, + { .description = "0x240", .value = 0x240 }, + { .description = "0x250", .value = 0x250 }, + { .description = "0x260", .value = 0x260 }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "irq", + .description = "IRQ", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = 7, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "IRQ 2", .value = 2 }, + { .description = "IRQ 3", .value = 3 }, + { .description = "IRQ 5", .value = 5 }, + { .description = "IRQ 7", .value = 7 }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { + .name = "gameport", + .description = "Enable Game port", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { + .name = "opl", + .description = "Enable OPL", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + static const device_config_t sb15_config[] = { { .name = "base", @@ -5576,6 +5695,20 @@ static const device_config_t ess_1688_pnp_config[] = { }; // clang-format on +const device_t thunderboard_device = { + .name = "MediaVision ThunderBoard", + .internal_name = "thunderboard", + .flags = DEVICE_ISA, + .local = THUNDERBOARD, + .init = thunderboard_init, + .close = sb_close, + .reset = NULL, + .available = NULL, + .speed_changed = sb_speed_changed, + .force_redraw = NULL, + .config = thunderboard_config +}; + const device_t sb_1_device = { .name = "Sound Blaster v1.0", .internal_name = "sb", diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index 8adb46c5d..c0ff417b3 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -85,20 +85,22 @@ char sb202_copyright[] = "COPYRIGHT(C) CREATIVE TECHNOLOGY PTE. LTD. (1991) char sb16_copyright[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992."; uint16_t sb_dsp_versions[] = { 0, /* Pad */ - 0, /* SADLIB - No DSP */ + 0, /* SADLIB - No DSP */ + 0x103, /* SB_DSP_103 - SB "killer card" prototype, DSP v1.03 */ 0x105, /* SB_DSP_105 - SB1/1.5, DSP v1.05 */ 0x200, /* SB_DSP_200 - SB1.5/2, DSP v2.00 */ 0x201, /* SB_DSP_201 - SB1.5/2, DSP v2.01 - needed for high-speed DMA */ 0x202, /* SB_DSP_202 - SB2, DSP v2.02 */ - 0x300, /* SB_PRO_DSP_300 - SB Pro, DSP v3.00 */ - 0x302, /* SBPRO2_DSP_302 - SB Pro 2, DSP v3.02 + OPL3 */ - 0x404, /* SB16_DSP_404 - DSP v4.04 + OPL3 */ - 0x405, /* SB16_405 - DSP v4.05 + OPL3 */ - 0x406, /* SB16_406 - DSP v4.06 + OPL3 */ - 0x40b, /* SB16_411 - DSP v4.11 + OPL3 */ - 0x40c, /* SBAWE32 - DSP v4.12 + OPL3 */ - 0x40d, /* SBAWE32PNP - DSP v4.13 + OPL3 */ - 0x410 /* SBAWE64 - DSP v4.16 + OPL3 */ + 0x300, /* SBPRO_DSP_300 - SB Pro, DSP v3.00 */ + 0x301, /* SBPRO_DSP_301 - SB Pro/Pro 2, DSP v3.01 */ + 0x302, /* SBPRO_DSP_302 - SB Pro/Pro 2, DSP v3.02 */ + 0x404, /* SB16_DSP_404 - DSP v4.04 + OPL3 */ + 0x405, /* SB16_DSP_405 - DSP v4.05 + OPL3 */ + 0x406, /* SB16_DSP_406 - DSP v4.06 + OPL3 */ + 0x40b, /* SB16_DSP_411 - DSP v4.11 + OPL3 */ + 0x40c, /* SBAWE32_DSP_412 - DSP v4.12 + OPL3 */ + 0x40d, /* SBAWE32_DSP_413 - DSP v4.13 + OPL3 */ + 0x410 /* SBAWE64_DSP_416 - DSP v4.16 + OPL3 */ }; /*These tables were 'borrowed' from DOSBox*/ @@ -1614,7 +1616,7 @@ sb_exec_command(sb_dsp_t *dsp) break; case 0xA0: /* Set input mode to mono */ case 0xA8: /* Set input mode to stereo */ - if ((dsp->sb_type < SBPRO_DSP_300) || (dsp->sb_type > SBPRO2_DSP_302)) + if ((dsp->sb_type < SBPRO_DSP_300) || (dsp->sb_type > SBPRO_DSP_302)) break; /* TODO: Implement. 3.xx-only command. */ break; @@ -1730,6 +1732,18 @@ sb_exec_command(sb_dsp_t *dsp) sb_add_data(dsp, ~dsp->sb_data[0]); break; case 0xE1: /* Get DSP version */ + if (IS_MV201(dsp)) { + if (dsp->sb_last_command == 0xE1) { + sb_add_data(dsp, 0x01); + sb_add_data(dsp, 0x30); + dsp->sb_last_command = 0x00; + } else { + sb_add_data(dsp, 0x02); + sb_add_data(dsp, 0x00); + dsp->sb_last_command = 0xE1; + } + break; + } if (IS_ESS(dsp)) { /* 0x03 0x01 (Sound Blaster Pro compatibility) confirmed by both the @@ -1859,12 +1873,12 @@ sb_exec_command(sb_dsp_t *dsp) * 059h Fetches the samples and then immediately plays them back. SB??? * 078h Auto-init DMA ADPCM SB2??? * 07Ah 2.6-bit ADPCM SB??? - * 0E3h DSP Copyright SBPro2??? (SBPRO2_DSP_302) - * 0F0h Sine Generator SB (SB_DSP_105, DSP20x) - * 0F1h DSP Auxiliary Status (Obsolete) SB-Pro2 (DSP20x, SBPRO2_DSP_302) - * 0F2h IRQ Request, 8-bit SB (SB_DSP_105, DSP20x) + * 0E3h DSP Copyright SBPro2??? (SBPRO_DSP_302) + * 0F0h Sine Generator SB (SB_DSP_105, SB_DSP_20x) + * 0F1h DSP Auxiliary Status (Obsolete) SB-Pro2 (SB_DSP_20x, SBPRO_DSP_302) + * 0F2h IRQ Request, 8-bit SB (SB_DSP_105, SB_DSP_20x) * 0F3h IRQ Request, 16-bit SB16 - * 0F4h Perform ROM checksum SB (SB_DSP_105, DSP20x) + * 0F4h Perform ROM checksum SB (SB_DSP_105, SB_DSP_20x) * 0FBh DSP Status SB16 * 0FCh DSP Auxiliary Status SB16 * 0FDh DSP Command Status SB16 @@ -2236,7 +2250,7 @@ sb_dsp_init(sb_dsp_t *dsp, int type, int subtype, void *parent) a set frequency command is sent. */ recalc_sb16_filter(0, 3200 * 2); } - if (IS_ESS(dsp) || (dsp->sb_type >= SBPRO2_DSP_302)) { + if (IS_ESS(dsp) || (dsp->sb_type >= SBPRO_DSP_302)) { /* OPL3 or dual OPL2 is stereo. */ if (dsp->sb_has_real_opl) recalc_opl_filter(FREQ_49716 * 2); diff --git a/src/sound/snd_sensation.c b/src/sound/snd_sensation.c index ea87c8391..aeb58fb46 100644 --- a/src/sound/snd_sensation.c +++ b/src/sound/snd_sensation.c @@ -808,6 +808,7 @@ sensation_visdac_read(uint16_t port, void *priv) sensation_t *visdac = (sensation_t *) priv; uint8_t ret; + ret = 0xff; switch (port & 0xF) { case 0x00: diff --git a/src/sound/snd_ymf701.c b/src/sound/snd_ymf701.c index c6b1467c8..c599eed9e 100644 --- a/src/sound/snd_ymf701.c +++ b/src/sound/snd_ymf701.c @@ -419,7 +419,7 @@ ymf701_init(const device_t *info) ymf701->sb->opl_enabled = 1; sb_dsp_set_real_opl(&ymf701->sb->dsp, 1); - sb_dsp_init(&ymf701->sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, ymf701); + sb_dsp_init(&ymf701->sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, ymf701); sb_dsp_setaddr(&ymf701->sb->dsp, ymf701->cur_sb_addr); sb_dsp_setirq(&ymf701->sb->dsp, ymf701->cur_sb_irq); sb_dsp_setdma8(&ymf701->sb->dsp, ymf701->cur_sb_dma); diff --git a/src/sound/snd_ymf71x.c b/src/sound/snd_ymf71x.c index 70ccf6872..4886e69b8 100644 --- a/src/sound/snd_ymf71x.c +++ b/src/sound/snd_ymf71x.c @@ -712,7 +712,7 @@ ymf71x_init(const device_t *info) ymf71x->sb->opl_enabled = 1; sb_dsp_set_real_opl(&ymf71x->sb->dsp, 1); - sb_dsp_init(&ymf71x->sb->dsp, SBPRO2_DSP_302, SB_SUBTYPE_DEFAULT, ymf71x); + sb_dsp_init(&ymf71x->sb->dsp, SBPRO_DSP_302, SB_SUBTYPE_DEFAULT, ymf71x); sb_ct1345_mixer_reset(ymf71x->sb); ymf71x->sb->opl_mixer = ymf71x; diff --git a/src/sound/sound.c b/src/sound/sound.c index c7aefc746..af2f958a9 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -126,6 +126,7 @@ static const SOUND_CARD sound_cards[] = { #ifdef USE_LIBSERIALPORT /*The following devices required LIBSERIALPORT*/ { &opl2board_device }, #endif + { &thunderboard_device }, { &pasplus_device }, { &sb_1_device }, { &sb_15_device }, diff --git a/src/unix/CMakeLists.txt b/src/unix/CMakeLists.txt index 1c79b42d2..5ec72bf97 100644 --- a/src/unix/CMakeLists.txt +++ b/src/unix/CMakeLists.txt @@ -47,8 +47,12 @@ add_library(ui OBJECT unix_sdl.c unix_osd.c unix_cdrom.c - dummy_cdrom_ioctl.c ) +if(APPLE) + target_sources(ui PRIVATE dummy_cdrom_ioctl.c) +else() + target_sources(ui PRIVATE linux_cdrom_ioctl.c) +endif() target_compile_definitions(ui PUBLIC _FILE_OFFSET_BITS=64) target_link_libraries(ui ${CMAKE_DL_LIBS}) diff --git a/src/unix/linux_cdrom_ioctl.c b/src/unix/linux_cdrom_ioctl.c new file mode 100644 index 000000000..fdd1c09e8 --- /dev/null +++ b/src/unix/linux_cdrom_ioctl.c @@ -0,0 +1,875 @@ +/* + * 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. + * + * Linux CD-ROM support via IOCTL (SG_IO). + * + * Authors: Miran Grca, + * TheCollector1995, + * + * Copyright 2023 TheCollector1995. + * Copyright 2023 Miran Grca. + * Copyright 2025 86Box contributors. + */ +#include +#ifdef ENABLE_IOCTL_LOG +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/cdrom.h> +#include <86box/log.h> +#include <86box/plat_cdrom_ioctl.h> +#include <86box/scsi_device.h> + +typedef struct ioctl_t { + cdrom_t *dev; + void *log; + int fd; + int is_dvd; + int has_audio; + int blocks_num; + uint8_t cur_rti[65536]; + char path[256]; + pthread_t poll_tid; + int poll_active; +} ioctl_t; + +static int ioctl_read_dvd_structure(const void *local, uint8_t layer, uint8_t format, + uint8_t *buffer, uint32_t *info); +static int ioctl_is_empty(const void *local); + +/* + * Wrapper for the system ioctl() call to avoid naming collisions + * with the local 'ioctl' variable of type ioctl_t*. + */ +static inline int +sys_ioctl(int fd, unsigned long request, void *arg) +{ + return ioctl(fd, request, arg); +} + +#ifdef ENABLE_IOCTL_LOG +int ioctl_do_log = ENABLE_IOCTL_LOG; + +void +ioctl_log(void *priv, const char *fmt, ...) +{ + if (ioctl_do_log) { + va_list ap; + va_start(ap, fmt); + log_out(priv, fmt, ap); + va_end(ap); + } +} +#else +# define ioctl_log(priv, fmt, ...) +#endif + +/* Internal functions. */ +static void +ioctl_close_handle(const ioctl_t *ioctl) +{ + if (ioctl->fd >= 0) + close(ioctl->fd); +} + +static int +ioctl_open_handle(ioctl_t *ioctl) +{ + ioctl_log(ioctl->log, "ioctl->path = \"%s\"\n", ioctl->path); + ioctl->fd = open(ioctl->path, O_RDONLY | O_NONBLOCK); + + ioctl_log(ioctl->log, "fd=%d, errno=%d\n", ioctl->fd, errno); + + return (ioctl->fd >= 0); +} + +/* + * Execute a SCSI command via the Linux SG_IO interface. + * Returns 1 on success, 0 on failure. + * sense_buf should be at least 64 bytes, sense_len receives actual sense length. + */ +static int +sg_io_cmd(int fd, const uint8_t *cdb, int cdb_len, + uint8_t *data_buf, int data_len, int direction, + uint8_t *sense_buf, int *sense_len) +{ + sg_io_hdr_t io_hdr; + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = cdb_len; + io_hdr.mx_sb_len = 64; + io_hdr.dxfer_direction = direction; + io_hdr.dxfer_len = data_len; + io_hdr.dxferp = data_buf; + io_hdr.cmdp = (unsigned char *) cdb; + io_hdr.sbp = sense_buf; + io_hdr.timeout = 6000; /* 6 seconds, matching Windows */ + + if (ioctl(fd, SG_IO, &io_hdr) < 0) + return 0; + + if (sense_len != NULL) + *sense_len = io_hdr.sb_len_wr; + + /* Check for SCSI errors. */ + if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) { + if (io_hdr.sb_len_wr > 0) { + if (sense_len != NULL) + *sense_len = io_hdr.sb_len_wr; + return 0; + } + return 0; + } + + return 1; +} + +static int +ioctl_read_normal_toc(ioctl_t *ioctl, uint8_t *toc_buf, int32_t *tracks_num) +{ + struct cdrom_tochdr tochdr; + struct cdrom_tocentry tocentry; + int status; + + *tracks_num = 0; + memset(toc_buf, 0x00, 65536); + + status = sys_ioctl(ioctl->fd, CDROMREADTOCHDR, &tochdr); + if (status < 0) + return 0; + + ioctl_log(ioctl->log, "TOC: first=%d, last=%d\n", + tochdr.cdth_trk0, tochdr.cdth_trk1); + + /* + * Build a cooked TOC buffer in the same format as the Windows + * CDROM_TOC structure: + * [0..1] = length (big-endian) + * [2] = first track + * [3] = last track + * [4..] = 8-byte track descriptors + * + * Each track descriptor: + * [0] = reserved + * [1] = Adr/Control + * [2] = track number + * [3] = reserved + * [4..7] = MSF address (0, M, S, F) + */ + toc_buf[2] = tochdr.cdth_trk0; + toc_buf[3] = tochdr.cdth_trk1; + + int count = 0; + for (int i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1; i++) { + memset(&tocentry, 0, sizeof(tocentry)); + tocentry.cdte_track = i; + tocentry.cdte_format = CDROM_MSF; + + if (sys_ioctl(ioctl->fd, CDROMREADTOCENTRY, &tocentry) < 0) + continue; + + uint8_t *t = &toc_buf[4 + count * 8]; + t[0] = 0; + t[1] = ((tocentry.cdte_adr & 0xf) << 4) | (tocentry.cdte_ctrl & 0xf); + t[2] = i; + t[3] = 0; + t[4] = 0; + t[5] = tocentry.cdte_addr.msf.minute; + t[6] = tocentry.cdte_addr.msf.second; + t[7] = tocentry.cdte_addr.msf.frame; + count++; + } + + /* Lead-out (track 0xAA). */ + memset(&tocentry, 0, sizeof(tocentry)); + tocentry.cdte_track = CDROM_LEADOUT; + tocentry.cdte_format = CDROM_MSF; + + if (sys_ioctl(ioctl->fd, CDROMREADTOCENTRY, &tocentry) >= 0) { + uint8_t *t = &toc_buf[4 + count * 8]; + t[0] = 0; + t[1] = ((tocentry.cdte_adr & 0xf) << 4) | (tocentry.cdte_ctrl & 0xf); + t[2] = 0xAA; + t[3] = 0; + t[4] = 0; + t[5] = tocentry.cdte_addr.msf.minute; + t[6] = tocentry.cdte_addr.msf.second; + t[7] = tocentry.cdte_addr.msf.frame; + count++; + } + + /* Set the length field (big-endian). */ + int length = 2 + count * 8; + toc_buf[0] = (length >> 8) & 0xff; + toc_buf[1] = length & 0xff; + *tracks_num = count; + + ioctl_log(ioctl->log, "%i tracks\n", *tracks_num); + + return 1; +} + +static void +ioctl_read_raw_toc(ioctl_t *ioctl) +{ + raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti; + uint8_t *buffer = (uint8_t *) calloc(1, 2052); + int status = 0; + + ioctl->is_dvd = (ioctl_read_dvd_structure(ioctl, 0, 0, buffer, NULL) > 0); + free(buffer); + + ioctl->has_audio = 0; + ioctl->blocks_num = 0; + memset(ioctl->cur_rti, 0x00, 65536); + + if (!ioctl->is_dvd) { + /* Try SG_IO with READ TOC command, Format=2 (full/raw TOC). */ + uint8_t cdb[12]; + uint8_t sense[64]; + uint8_t *raw_buf = (uint8_t *) calloc(1, 65536); + int sense_len = 0; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x43; /* READ TOC */ + cdb[1] = 0x02; /* MSF */ + cdb[2] = 0x02; /* Format = Full TOC (raw) */ + cdb[6] = 0x01; /* Session = 1 */ + cdb[7] = 0xff; /* Allocation length high */ + cdb[8] = 0xff; /* Allocation length low */ + + memset(sense, 0, sizeof(sense)); + status = sg_io_cmd(ioctl->fd, cdb, 10, raw_buf, 65535, + SG_DXFER_FROM_DEV, sense, &sense_len); + + if (status && sense_len == 0) { + int length = ((raw_buf[0] << 8) | raw_buf[1]) - 2; + ioctl->blocks_num = length / 11; + if (ioctl->blocks_num > 0) + memcpy(ioctl->cur_rti, &raw_buf[4], ioctl->blocks_num * 11); + } else { + status = 0; + } + + free(raw_buf); + } + + if (status == 0) { + /* + * Raw TOC read failed (or this is a DVD). Fall back to the + * cooked TOC, and construct raw_track_info_t entries from it, + * mirroring the Windows fallback path. + */ + uint8_t cur_toc[65536] = { 0 }; + int32_t tracks_num = 0; + + status = ioctl_read_normal_toc(ioctl, cur_toc, &tracks_num); + + if ((status > 0) && (tracks_num >= 1)) { + /* Last real entry (the lead-out). */ + const uint8_t *ct = &cur_toc[4 + (tracks_num - 1) * 8]; + + rti[0].adr_ctl = ct[1]; + rti[0].point = 0xa0; + rti[0].pm = cur_toc[2]; /* FirstTrack */ + + rti[1].adr_ctl = rti[0].adr_ctl; + rti[1].point = 0xa1; + rti[1].pm = cur_toc[3]; /* LastTrack */ + + rti[2].adr_ctl = rti[0].adr_ctl; + rti[2].point = 0xa2; + rti[2].pm = ct[5]; /* M */ + rti[2].ps = ct[6]; /* S */ + rti[2].pf = ct[7]; /* F */ + + ioctl->blocks_num = 3; + + for (int i = 0; i < (tracks_num - 1); i++) { + raw_track_info_t *crt = &(rti[ioctl->blocks_num]); + + ct = &cur_toc[4 + i * 8]; + + crt->adr_ctl = ct[1]; + crt->point = ct[2]; + crt->pm = ct[5]; + crt->ps = ct[6]; + crt->pf = ct[7]; + + ioctl->blocks_num++; + } + } else if (status > 0) + /* Announce that we've had a failure. */ + status = 0; + } + + if (ioctl->blocks_num) for (int i = 0; i < ioctl->blocks_num; i++) { + const raw_track_info_t *crt = &(rti[i]); + + if ((crt->point >= 1) && (crt->point <= 99) && !(crt->adr_ctl & 0x04)) { + ioctl->has_audio = 1; + break; + } + } + +#ifdef ENABLE_IOCTL_LOG + ioctl_log(ioctl->log, "%i blocks\n", ioctl->blocks_num); + + for (int i = 0; i < ioctl->blocks_num; i++) { + uint8_t *t = (uint8_t *) &rti[i]; + ioctl_log(ioctl->log, "Block %03i: %02X %02X %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X\n", + i, t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], + t[9], t[10]); + } +#endif +} + +static int +ioctl_get_track(const ioctl_t *ioctl, const uint32_t sector) +{ + raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti; + int track = -1; + + for (int i = (ioctl->blocks_num - 1); i >= 0; i--) { + const raw_track_info_t *ct = &(rti[i]); + const uint32_t start = (ct->pm * 60 * 75) + (ct->ps * 75) + ct->pf - 150; + + ioctl_log(ioctl->log, "ioctl_get_track(): ct: %02X, %08X\n", + ct->point, start); + + if ((ct->point >= 1) && (ct->point <= 99) && (sector >= start)) { + track = i; + ioctl_log(ioctl->log, "ioctl_get_track(): found track: %i\n", i); + break; + } + } + + return track; +} + +static int +ioctl_is_track_audio(const ioctl_t *ioctl, const uint32_t pos) +{ + const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti; + int ret = 0; + + if (ioctl->has_audio && !ioctl->is_dvd) { + const int track = ioctl_get_track(ioctl, pos); + const int control = rti[track].adr_ctl; + + ret = !(control & 0x04); + + ioctl_log(ioctl->log, "ioctl_is_track_audio(%08X, %02X): %i\n", pos, track, ret); + } + + return ret; +} + +/* Shared functions (cdrom_ops_t interface). */ +static int +ioctl_get_track_info(const void *local, const uint32_t track, + int end, track_info_t *ti) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti; + int ret = 1; + int trk = -1; + int next = -1; + + if ((track >= 1) && (track < 99)) + for (int i = 0; i < ioctl->blocks_num; i++) + if (rti[i].point == track) { + trk = i; + break; + } + + if ((track >= 1) && (track < 98)) + for (int i = 0; i < ioctl->blocks_num; i++) + if ((rti[i].point == (track + 1)) && (rti[i].session == rti[trk].session)) { + next = i; + break; + } + + if ((track >= 1) && (track < 99) && (trk != -1) && (next == -1)) + for (int i = 0; i < ioctl->blocks_num; i++) + if ((rti[i].point == 0xa2) && (rti[i].session == rti[trk].session)) { + next = i; + break; + } + + if ((track == 0xaa) || (trk == -1)) { + ioctl_log(ioctl->log, "ioctl_get_track_info(%02i)\n", track); + ret = 0; + } else { + if (end) { + if (next != -1) { + ti->m = rti[next].pm; + ti->s = rti[next].ps; + ti->f = rti[next].pf; + } + } else { + ti->m = rti[trk].pm; + ti->s = rti[trk].ps; + ti->f = rti[trk].pf; + } + + ti->number = rti[trk].point; + ti->attr = rti[trk].adr_ctl; + + ioctl_log(ioctl->log, "ioctl_get_track_info(%02i): %02i:%02i:%02i, %02i, %02X\n", + track, ti->m, ti->s, ti->f, ti->number, ti->attr); + } + + return ret; +} + +static void +ioctl_get_raw_track_info(const void *local, int *num, uint8_t *rti) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + + *num = ioctl->blocks_num; + memcpy(rti, ioctl->cur_rti, ioctl->blocks_num * 11); +} + +static int +ioctl_is_track_pre(const void *local, const uint32_t sector) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti; + int ret = 0; + + if (ioctl->has_audio && !ioctl->is_dvd) { + const int track = ioctl_get_track(ioctl, sector); + const int control = rti[track].adr_ctl; + + ret = control & 0x01; + + ioctl_log(ioctl->log, "ioctl_is_track_pre(%08X, %02X): %i\n", sector, track, ret); + } + + return ret; +} + +static int +ioctl_read_sector(const void *local, uint8_t *buffer, uint32_t const sector) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + const raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti; + const int sc_offs = (sector == 0xffffffff) ? 0 : 2352; + int len = (sector == 0xffffffff) ? 16 : 2368; + int m = 0; + int s = 0; + int f = 0; + uint32_t lba = sector; + int ret; + int data_len = 0; + + if (ioctl->is_dvd) { + int track; + + data_len = 0; + ret = 0; + + if (lba == 0xffffffff) { + lba = ioctl->dev->seek_pos; + track = ioctl_get_track(ioctl, lba); + + if (track != -1) { + data_len = len; + ret = 1; + } + } else { + len = COOKED_SECTOR_SIZE; + track = ioctl_get_track(ioctl, lba); + + if (track != -1) { + ssize_t nread = pread(ioctl->fd, &(buffer[16]), + COOKED_SECTOR_SIZE, + (off_t) lba * COOKED_SECTOR_SIZE); + if (nread > 0) { + data_len = (int) nread; + ret = 1; + } + } + } + + if (ret && (data_len >= len) && (track != -1)) { + const raw_track_info_t *ct = &(rti[track]); + const uint32_t start = (ct->pm * 60 * 75) + (ct->ps * 75) + ct->pf; + + m = s = f = 0; + + /* Construct sector header and sub-header. */ + if (sector != 0xffffffff) { + /* Sync bytes. */ + buffer[0] = 0x00; + memset(&(buffer[1]), 0xff, 10); + buffer[11] = 0x00; + + /* Sector header. */ + FRAMES_TO_MSF(lba + 150, &m, &s, &f); + buffer[12] = bin2bcd(m); + buffer[13] = bin2bcd(s); + buffer[14] = bin2bcd(f); + + /* Mode 1 data. */ + buffer[15] = 0x01; + } + + /* Construct Q. */ + buffer[sc_offs + 0] = (ct->adr_ctl >> 4) | ((ct->adr_ctl & 0xf) << 4); + buffer[sc_offs + 1] = bin2bcd(ct->point); + buffer[sc_offs + 2] = 1; + FRAMES_TO_MSF((int32_t) (lba + 150 - start), &m, &s, &f); + buffer[sc_offs + 3] = bin2bcd(m); + buffer[sc_offs + 4] = bin2bcd(s); + buffer[sc_offs + 5] = bin2bcd(f); + FRAMES_TO_MSF(lba + 150, &m, &s, &f); + buffer[sc_offs + 7] = bin2bcd(m); + buffer[sc_offs + 8] = bin2bcd(s); + buffer[sc_offs + 9] = bin2bcd(f); + } + } else { + /* CD: use SG_IO with READ CD (0xBE). */ + uint8_t cdb[12]; + uint8_t sense[64]; + int sense_len = 0; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0xbe; /* READ CD */ + cdb[1] = 0x00; + cdb[2] = (sector >> 24) & 0xff; + cdb[3] = (sector >> 16) & 0xff; + cdb[4] = (sector >> 8) & 0xff; + cdb[5] = sector & 0xff; /* Starting LBA */ + cdb[6] = 0x00; + cdb[7] = 0x00; + cdb[8] = 0x01; /* Transfer Length = 1 */ + /* If sector is FFFFFFFF, only return the subchannel. */ + cdb[9] = (sector == 0xffffffff) ? 0x00 : 0xf8; + cdb[10] = 0x02; + cdb[11] = 0x00; + +#ifdef ENABLE_IOCTL_LOG + ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X %02X %02X %02X\n", + cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], + cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]); +#endif + + memset(sense, 0, sizeof(sense)); + ret = sg_io_cmd(ioctl->fd, cdb, 12, buffer, len, + SG_DXFER_FROM_DEV, sense, &sense_len); + + ioctl_log(ioctl->log, "ioctl_read_sector: ret = %d, sense_len = %d\n", + ret, sense_len); + + if (sense_len >= 16) { + if ((sense[2] == 0x03) && (sense[12] == 0x11)) + /* Treat this as an error to correctly indicate CIRC error to the guest. */ + ret = 0; + ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[0], sense[1], sense[2], sense[3], + sense[4], sense[5], sense[6], sense[7]); + ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[8], sense[9], sense[10], sense[11], + sense[12], sense[13], sense[14], sense[15]); + } + + ret = ret ? 1 : -1; + data_len = len; /* sg_io_cmd handles this internally */ + } + + ioctl_log(ioctl->log, "ioctl_read_sector: final ret = %i\n", ret); + + /* Construct raw subchannel data from Q only. */ + if ((ret > 0) && !ioctl->is_dvd) + for (int i = 11; i >= 0; i--) + for (int j = 7; j >= 0; j--) + buffer[2352 + (i * 8) + j] = ((buffer[sc_offs + i] >> (7 - j)) & 0x01) << 6; + + return ret; +} + +static uint8_t +ioctl_get_track_type(const void *local, const uint32_t sector) +{ + ioctl_t *ioctl = (ioctl_t *) local; + int track = ioctl_get_track(ioctl, sector); + raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti; + const raw_track_info_t *trk = &(rti[track]); + uint8_t ret = 0x00; + + if (ioctl_is_track_audio(ioctl, sector)) + ret = CD_TRACK_AUDIO; + else if (track != -1) for (int i = 0; i < ioctl->blocks_num; i++) { + const raw_track_info_t *ct = &(rti[i]); + const raw_track_info_t *nt = &(rti[i + 1]); + + if (ct->point == 0xa0) { + uint8_t first = ct->pm; + uint8_t last = nt->pm; + + if ((trk->point >= first) && (trk->point <= last)) { + ret = ct->ps; + break; + } + } + } + + return ret; +} + +static uint32_t +ioctl_get_last_block(const void *local) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti; + uint32_t lb = 0; + + for (int i = (ioctl->blocks_num - 1); i >= 0; i--) + if (rti[i].point == 0xa2) { + lb = MSFtoLBA(rti[i].pm, rti[i].ps, rti[i].pf) - 151; + break; + } + + ioctl_log(ioctl->log, "LBCapacity=%d\n", lb); + + return lb; +} + +static int +ioctl_read_dvd_structure(const void *local, const uint8_t layer, const uint8_t format, + uint8_t *buffer, uint32_t *info) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + const int len = 2052; + uint8_t cdb[12]; + uint8_t sense[64]; + int sense_len = 0; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0xad; /* READ DVD STRUCTURE */ + cdb[6] = layer; /* Layer Number */ + cdb[7] = format; /* Format */ + cdb[8] = 0x08; /* Allocation Length high */ + cdb[9] = 0x04; /* Allocation Length low */ + +#ifdef ENABLE_IOCTL_LOG + ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X %02X %02X %02X\n", + cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], + cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]); +#endif + + memset(sense, 0, sizeof(sense)); + int ret = sg_io_cmd(ioctl->fd, cdb, 12, buffer, len, + SG_DXFER_FROM_DEV, sense, &sense_len); + + ioctl_log(ioctl->log, "ioctl_read_dvd_structure(): ret = %d, sense_len = %d\n", + ret, sense_len); + + if (sense_len >= 16) { + /* Return sense to the host as is. */ + ret = -((sense[2] << 16) | (sense[12] << 8) | sense[13]); + if (info != NULL) + *info = *(uint32_t *) &(sense[3]); + ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[0], sense[1], sense[2], sense[3], + sense[4], sense[5], sense[6], sense[7]); + ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[8], sense[9], sense[10], sense[11], + sense[12], sense[13], sense[14], sense[15]); + } else + ret = ret ? 1 : 0; + + return ret; +} + +static int +ioctl_is_dvd(const void *local) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + + return ioctl->is_dvd; +} + +static int +ioctl_has_audio(const void *local) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + + return ioctl->has_audio; +} + +static int +ioctl_is_empty(const void *local) +{ + const ioctl_t *ioctl = (const ioctl_t *) local; + uint8_t cdb[12]; + uint8_t sense[64]; + int sense_len = 0; + + /* TEST UNIT READY */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x00; + +#ifdef ENABLE_IOCTL_LOG + ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X %02X %02X %02X\n", + cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], + cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]); +#endif + + memset(sense, 0, sizeof(sense)); + int ret = sg_io_cmd(ioctl->fd, cdb, 6, NULL, 0, + SG_DXFER_NONE, sense, &sense_len); + + ioctl_log(ioctl->log, "ioctl_is_empty(): ret = %d, sense_len = %d\n", + ret, sense_len); + + if (sense_len >= 16) { + /* Check for NOT READY + MEDIUM NOT PRESENT. */ + ret = ((sense[2] == SENSE_NOT_READY) && (sense[12] == ASC_MEDIUM_NOT_PRESENT)); + ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[0], sense[1], sense[2], sense[3], + sense[4], sense[5], sense[6], sense[7]); + ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n", + sense[8], sense[9], sense[10], sense[11], + sense[12], sense[13], sense[14], sense[15]); + } else if (!ret) + ret = 1; /* SG_IO itself failed, assume empty */ + else + ret = 0; /* No sense data and command succeeded = media present */ + + return ret; +} + +/* Disc change polling thread. */ +static void * +ioctl_poll_thread(void *arg) +{ + ioctl_t *ioctl = (ioctl_t *) arg; + int was_empty = ioctl_is_empty(ioctl); + + while (ioctl->poll_active) { + sleep(2); /* Poll every 2 seconds. */ + if (!ioctl->poll_active) + break; + + int now_empty = ioctl_is_empty(ioctl); + if (now_empty != was_empty) { + if (now_empty) + cdrom_set_empty(ioctl->dev); + else + cdrom_update_status(ioctl->dev); + was_empty = now_empty; + } + } + + return NULL; +} + +static void +ioctl_close(void *local) +{ + ioctl_t *ioctl = (ioctl_t *) local; + + /* Stop the polling thread. */ + if (ioctl->poll_active) { + ioctl->poll_active = 0; + pthread_join(ioctl->poll_tid, NULL); + } + + ioctl_close_handle(ioctl); + ioctl->fd = -1; + + ioctl_log(ioctl->log, "Log closed\n"); + + log_close(ioctl->log); + ioctl->log = NULL; + + free(ioctl); +} + +static void +ioctl_load(const void *local) +{ + ioctl_t *ioctl = (ioctl_t *) local; + + if ((ioctl->fd >= 0) || ioctl_open_handle(ioctl)) { + /* Try to close the tray. */ + (void) sys_ioctl(ioctl->fd, CDROMCLOSETRAY, NULL); + + ioctl_read_raw_toc(ioctl); + } +} + +static const cdrom_ops_t ioctl_ops = { + ioctl_get_track_info, + ioctl_get_raw_track_info, + ioctl_is_track_pre, + ioctl_read_sector, + ioctl_get_track_type, + ioctl_get_last_block, + ioctl_read_dvd_structure, + ioctl_is_dvd, + ioctl_has_audio, + ioctl_is_empty, + ioctl_close, + ioctl_load +}; + +/* Public functions. */ +void * +ioctl_open(cdrom_t *dev, const char *drv) +{ + ioctl_t *ioctl = (ioctl_t *) calloc(1, sizeof(ioctl_t)); + + if (ioctl != NULL) { + char n[1024] = { 0 }; + + sprintf(n, "CD-ROM %i IOCtl", dev->id + 1); + ioctl->log = log_open(n); + + memset(ioctl->path, 0x00, sizeof(ioctl->path)); + ioctl->fd = -1; + + /* drv is "ioctl:///dev/sr0", extract the path part. */ + snprintf(ioctl->path, sizeof(ioctl->path), "%s", &(drv[8])); + ioctl_log(ioctl->log, "Path is %s\n", ioctl->path); + + ioctl->dev = dev; + dev->ops = &ioctl_ops; + + ioctl_load(ioctl); + + /* Start the disc change polling thread. */ + ioctl->poll_active = 1; + if (pthread_create(&ioctl->poll_tid, NULL, ioctl_poll_thread, ioctl) != 0) + ioctl->poll_active = 0; + } + + return ioctl; +} diff --git a/src/video/vid_8514a.c b/src/video/vid_8514a.c index d44d9e9d8..e152e98a5 100644 --- a/src/video/vid_8514a.c +++ b/src/video/vid_8514a.c @@ -1121,83 +1121,159 @@ ibm8514_accel_start(int count, int cpu_input, uint32_t mix_dat, uint32_t cpu_dat if (pixcntl == 1) { mix_dat = 0; - if (and3 == 3) { - if (dev->accel.multifunc[8] & 0x02) - mix_dat |= 0x08; - if (dev->accel.multifunc[8] & 0x04) - mix_dat |= 0x10; - if (dev->accel.multifunc[8] & 0x08) - mix_dat |= 0x20; - if (dev->accel.multifunc[8] & 0x10) - mix_dat |= 0x40; - if (dev->accel.multifunc[9] & 0x02) - mix_dat |= 0x80; - if (dev->accel.multifunc[9] & 0x04) - mix_dat |= 0x01; - if (dev->accel.multifunc[9] & 0x08) - mix_dat |= 0x02; - if (dev->accel.multifunc[9] & 0x10) - mix_dat |= 0x04; - } - if (and3 == 2) { - if (dev->accel.multifunc[8] & 0x02) - mix_dat |= 0x04; - if (dev->accel.multifunc[8] & 0x04) - mix_dat |= 0x08; - if (dev->accel.multifunc[8] & 0x08) - mix_dat |= 0x10; - if (dev->accel.multifunc[8] & 0x10) - mix_dat |= 0x20; - if (dev->accel.multifunc[9] & 0x02) - mix_dat |= 0x40; - if (dev->accel.multifunc[9] & 0x04) - mix_dat |= 0x80; - if (dev->accel.multifunc[9] & 0x08) - mix_dat |= 0x01; - if (dev->accel.multifunc[9] & 0x10) - mix_dat |= 0x02; - } - if (and3 == 1) { - if (dev->accel.multifunc[8] & 0x02) - mix_dat |= 0x02; - if (dev->accel.multifunc[8] & 0x04) - mix_dat |= 0x04; - if (dev->accel.multifunc[8] & 0x08) - mix_dat |= 0x08; - if (dev->accel.multifunc[8] & 0x10) - mix_dat |= 0x10; - if (dev->accel.multifunc[9] & 0x02) - mix_dat |= 0x20; - if (dev->accel.multifunc[9] & 0x04) - mix_dat |= 0x40; - if (dev->accel.multifunc[9] & 0x08) - mix_dat |= 0x80; - if (dev->accel.multifunc[9] & 0x10) - mix_dat |= 0x01; - } - if (and3 == 0) { - if (dev->accel.multifunc[8] & 0x02) - mix_dat |= 0x01; - if (dev->accel.multifunc[8] & 0x04) - mix_dat |= 0x02; - if (dev->accel.multifunc[8] & 0x08) - mix_dat |= 0x04; - if (dev->accel.multifunc[8] & 0x10) - mix_dat |= 0x08; - if (dev->accel.multifunc[9] & 0x02) - mix_dat |= 0x10; - if (dev->accel.multifunc[9] & 0x04) - mix_dat |= 0x20; - if (dev->accel.multifunc[9] & 0x08) - mix_dat |= 0x40; - if (dev->accel.multifunc[9] & 0x10) - mix_dat |= 0x80; + if (cmd == 6) { + if (and3_blt == 3) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x10; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x20; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x01; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x02; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x04; + } + if (and3_blt == 2) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x10; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x01; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x02; + } + if (and3_blt == 1) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x02; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x10; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x01; + } + if (and3_blt == 0) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x01; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x02; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x08; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x10; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x80; + } + } else { + if (and3 == 3) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x10; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x20; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x01; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x02; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x04; + } + if (and3 == 2) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x10; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x01; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x02; + } + if (and3 == 1) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x02; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x08; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x10; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x80; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x01; + } + if (and3 == 0) { + if (dev->accel.multifunc[8] & 0x02) + mix_dat |= 0x01; + if (dev->accel.multifunc[8] & 0x04) + mix_dat |= 0x02; + if (dev->accel.multifunc[8] & 0x08) + mix_dat |= 0x04; + if (dev->accel.multifunc[8] & 0x10) + mix_dat |= 0x08; + if (dev->accel.multifunc[9] & 0x02) + mix_dat |= 0x10; + if (dev->accel.multifunc[9] & 0x04) + mix_dat |= 0x20; + if (dev->accel.multifunc[9] & 0x08) + mix_dat |= 0x40; + if (dev->accel.multifunc[9] & 0x10) + mix_dat |= 0x80; + } } } old_mix_dat = mix_dat; - ibm8514_log("CMD=%d, full=%04x, pixcntl=%d, filling=%02x, ssvdraw=%02x.\n", cmd, dev->accel.cmd, pixcntl, dev->accel.multifunc[0x0a] & 0x06, dev->accel.ssv_draw); + if (!(dev->accel.cmd & 0x01)) + ibm8514_log("CMD=%d, full=%04x, pixcntl=%d, filling=%02x, ssvdraw=%02x.\n", cmd, dev->accel.cmd, pixcntl, dev->accel.multifunc[0x0a] & 0x06, dev->accel.ssv_draw); /*Bit 4 of the Command register is the draw yes bit, which enables writing to memory/reading from memory when enabled. When this bit is disabled, no writing to memory/reading from memory is allowed. (This bit is almost meaningless on diff --git a/src/video/vid_ati_mach8.c b/src/video/vid_ati_mach8.c index ab38e26f6..58a54fb48 100644 --- a/src/video/vid_ati_mach8.c +++ b/src/video/vid_ati_mach8.c @@ -3021,8 +3021,10 @@ ati8514_recalctimings(svga_t *svga) if (dev->ven_clock & 0x40) svga->clock_8514 *= 2.0; - if (dev->interlace) + if (dev->interlace) { dev->dispend >>= 1; + svga->clock_8514 /= 2.0; + } mach_log("cntl=%d, hv(%d,%d), pitch=%d, rowoffset=%d, gextconfig=%03x, shadow=%x interlace=%d.\n", dev->accel.advfunc_cntl & 0x04, dev->h_disp, dev->dispend, dev->pitch, dev->rowoffset, @@ -3183,8 +3185,10 @@ mach_recalctimings(svga_t *svga) mach_log("8514/A modes=%d, clocksel=%02x, clkselmode=%02x, divide reg ibm=%02x, divide reg vga=%02x, vgainterlace=%x, interlace=%x, htotal=%02x.\n", _8514_modes, mach->accel.clock_sel & 0xfe, mach->accel.clock_sel_mode & 0xfe, mach->accel.clock_sel & 0x40, mach->regs[0xb8] & 0x40, svga->interlace, dev->interlace, dev->htotal); - if (dev->interlace) + if (dev->interlace) { dev->dispend >>= 1; + svga->clock_8514 /= 2.0; + } if (ATI_MACH32) { switch ((mach->shadow_set >> 8) & 0x03) { diff --git a/src/video/vid_cl54xx.c b/src/video/vid_cl54xx.c index 0a3060467..edf49fe88 100644 --- a/src/video/vid_cl54xx.c +++ b/src/video/vid_cl54xx.c @@ -53,6 +53,7 @@ #define BIOS_GD5428_DIAMOND_B1_VLB_PATH "roms/video/cirruslogic/Diamond SpeedStar PRO VLB v3.04.bin" #define BIOS_GD5428_ISA_PATH "roms/video/cirruslogic/5428.bin" #define BIOS_GD5428_MCA_PATH "roms/video/cirruslogic/SVGA141.ROM" +#define BIOS_GD5428_ONBOARD_ACER_PATH "roms/machines/acera1g/4alo001.bin" #define BIOS_GD5428_PATH "roms/video/cirruslogic/vlbusjapan.BIN" #define BIOS_GD5428_BOCA_ISA_PATH_1 "roms/video/cirruslogic/boca_gd5428_1.30b_1.bin" #define BIOS_GD5428_BOCA_ISA_PATH_2 "roms/video/cirruslogic/boca_gd5428_1.30b_2.bin" @@ -1327,7 +1328,10 @@ gd54xx_in(uint16_t addr, void *priv) /* Scratch Pad 1 (Memory size for 5402/542x) */ ret = svga->seqregs[0x0a] & ~0x1a; if (svga->crtc[0x27] == CIRRUS_ID_CLGD5402) { - ret |= 0x01; /*512K of memory*/ + if ((gd54xx->vram_size >> 10) == 512) + ret |= 0x01; /*512K of memory*/ + else + ret &= 0xfe; /*256K of memory*/ } else if (svga->crtc[0x27] > CIRRUS_ID_CLGD5402) { switch (gd54xx->vram_size >> 10) { case 512: @@ -4284,6 +4288,8 @@ gd54xx_init(const device_t *info) case CIRRUS_ID_CLGD5402: if (info->local & 0x200) + romfn = NULL; + else if (info->local & 0x100) romfn = BIOS_GD5402_ONBOARD_PATH; else romfn = BIOS_GD5402_PATH; @@ -4326,7 +4332,10 @@ gd54xx_init(const device_t *info) case CIRRUS_ID_CLGD5428: if (info->local & 0x200) { - romfn = NULL; + if (machines[machine].init == machine_at_acera1g_init) + romfn = BIOS_GD5428_ONBOARD_ACER_PATH; + else + romfn = NULL; gd54xx->has_bios = 0; } else if (info->local & 0x100) if (gd54xx->vlb) @@ -4427,12 +4436,12 @@ gd54xx_init(const device_t *info) gd54xx->vram_size = vram << 10; } else { if (id <= CIRRUS_ID_CLGD5428) { - if ((id == CIRRUS_ID_CLGD5426) && (info->local & 0x200)) + if ((id == CIRRUS_ID_CLGD5428) && (info->local & 0x200) && (info->local & 0x1000)) + vram = 1024; + else if ((id == CIRRUS_ID_CLGD5426) && (info->local & 0x200) && (info->local & 0x1000)) vram = 1024; else if (id == CIRRUS_ID_CLGD5401) vram = 256; - else if (id == CIRRUS_ID_CLGD5402) - vram = 512; else vram = device_get_config_int("memory"); @@ -4768,6 +4777,25 @@ gd54xx_force_redraw(void *priv) } // clang-format off +static const device_config_t gd5402_config[] = { + { + .name = "memory", + .description = "Memory size", + .type = CONFIG_SELECTION, + .default_string = NULL, + .default_int = 512, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { + { .description = "256 KB", .value = 256 }, + { .description = "512 KB", .value = 512 }, + { .description = "" } + }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + static const device_config_t gd542x_config[] = { { .name = "memory", @@ -5015,7 +5043,7 @@ const device_t gd5402_isa_device = { .available = gd5402_available, .speed_changed = gd54xx_speed_changed, .force_redraw = gd54xx_force_redraw, - .config = NULL, + .config = gd5402_config, }; const device_t gd5402_onboard_device = { @@ -5029,7 +5057,21 @@ const device_t gd5402_onboard_device = { .available = NULL, .speed_changed = gd54xx_speed_changed, .force_redraw = gd54xx_force_redraw, - .config = NULL, + .config = gd5402_config, +}; + +const device_t gd5402_onboard_commodore_device = { + .name = "Cirrus Logic GD5402 (ISA) (ACUMOS AVGA2) (On-Board) (Commodore)", + .internal_name = "cl_gd5402_onboard_commodore", + .flags = DEVICE_ISA16, + .local = CIRRUS_ID_CLGD5402 | 0x100, + .init = gd54xx_init, + .close = gd54xx_close, + .reset = gd54xx_reset, + .available = NULL, + .speed_changed = gd54xx_speed_changed, + .force_redraw = gd54xx_force_redraw, + .config = gd5402_config, }; const device_t gd5420_isa_device = { @@ -5145,11 +5187,25 @@ const device_t gd5426_vlb_device = { .config = gd5426_config }; +const device_t gd5426_onboard_isa_device = { + .name = "Cirrus Logic GD5426 (ISA) (On-Board)", + .internal_name = "cl_gd5426_onboard", + .flags = DEVICE_ISA16, + .local = CIRRUS_ID_CLGD5426 | 0x200, + .init = gd54xx_init, + .close = gd54xx_close, + .reset = gd54xx_reset, + .available = gd5428_isa_available, + .speed_changed = gd54xx_speed_changed, + .force_redraw = gd54xx_force_redraw, + .config = gd542x_config +}; + const device_t gd5426_onboard_device = { .name = "Cirrus Logic GD5426 (VLB) (On-Board)", .internal_name = "cl_gd5426_onboard", .flags = DEVICE_VLB, - .local = CIRRUS_ID_CLGD5426 | 0x200, + .local = CIRRUS_ID_CLGD5426 | 0x200 | 0x1000, .init = gd54xx_init, .close = gd54xx_close, .reset = gd54xx_reset, @@ -5248,21 +5304,21 @@ const device_t gd5428_onboard_device = { .name = "Cirrus Logic GD5428 (ISA) (On-Board)", .internal_name = "cl_gd5428_onboard", .flags = DEVICE_ISA16, - .local = CIRRUS_ID_CLGD5428, + .local = CIRRUS_ID_CLGD5428 | 0x200, .init = gd54xx_init, .close = gd54xx_close, .reset = gd54xx_reset, .available = gd5428_isa_available, .speed_changed = gd54xx_speed_changed, .force_redraw = gd54xx_force_redraw, - .config = gd5426_config + .config = gd542x_config }; const device_t gd5428_vlb_onboard_device = { .name = "Cirrus Logic GD5428 (VLB) (On-Board)", .internal_name = "cl_gd5428_vlb_onboard", .flags = DEVICE_VLB, - .local = CIRRUS_ID_CLGD5428, + .local = CIRRUS_ID_CLGD5428 | 0x200, .init = gd54xx_init, .close = gd54xx_close, .reset = gd54xx_reset, @@ -5273,7 +5329,7 @@ const device_t gd5428_vlb_onboard_device = { }; const device_t gd5428_onboard_vlb_device = { - .name = "Cirrus Logic GD5428 (VLB) (On-Board) (Dell)", + .name = "Cirrus Logic GD5428 (VLB) (On-Board) (1MB)", .internal_name = "cl_gd5428_onboard_vlb", .flags = DEVICE_VLB, .local = CIRRUS_ID_CLGD5428 | 0x200, @@ -5286,6 +5342,20 @@ const device_t gd5428_onboard_vlb_device = { .config = gd542x_config }; +const device_t gd5428_vlb_onboard_tandy_device = { + .name = "Cirrus Logic GD5428 (VLB) (On-Board) (Tandy)", + .internal_name = "cl_gd5428_vlb_onboard_tandy", + .flags = DEVICE_VLB, + .local = CIRRUS_ID_CLGD5428 | 0x200 | 0x1000, + .init = gd54xx_init, + .close = gd54xx_close, + .reset = gd54xx_reset, + .available = NULL, + .speed_changed = gd54xx_speed_changed, + .force_redraw = gd54xx_force_redraw, + .config = NULL +}; + const device_t gd5429_isa_device = { .name = "Cirrus Logic GD5429 (ISA)", .internal_name = "cl_gd5429_isa",