From 406db23856e4e0d9793eeaaf6c24d84ffecd964d Mon Sep 17 00:00:00 2001 From: Toni Wilen Date: Fri, 19 Jun 2026 17:08:37 +0300 Subject: [PATCH] Vortex ATonce plus merge --- atonce.cpp | 1296 ++++++++++++++++++++ cfgfile.cpp | 2 +- cia.cpp | 38 + expansion.cpp | 29 + include/atonce.h | 17 + include/rommgr.h | 1 + od-win32/winuae_msvc15/winuae_msvc.vcxproj | 1 + pcem/pic.cpp | 10 + scsi.cpp | 2 +- x86.cpp | 28 +- 10 files changed, 1421 insertions(+), 3 deletions(-) create mode 100644 atonce.cpp create mode 100644 include/atonce.h diff --git a/atonce.cpp b/atonce.cpp new file mode 100644 index 00000000..78d1c44f --- /dev/null +++ b/atonce.cpp @@ -0,0 +1,1296 @@ +/* +* UAE - The Un*x Amiga Emulator +* +* Vortex ATonce Plus emulation +* (80286 PC emulator board for the Amiga 500 68000 socket, 1991) +* +* The board consists of an 80C286-16, optional 80C287, 512KB local DRAM and +* a Xilinx XC2018 LCA acting as bridge/chipset. There is NO PC support +* hardware at all: every IN/OUT executed by the 286 (except the 287 range +* 0xF8-0xFF) freezes the CPU and is emulated by the Amiga-side 68k driver +* through a polling protocol on two "magic" addresses in CIA space. +* +* Hardware model reverse engineered from the original LCA bitstream +* (configuration "atplus2"), the GAL equations and the original v2.20/v3.00 +* driver disassembly: https://github.com/na103/Dueottosei +* The address translation below is ported 1:1 from the formally verified +* RTL (xc2018/rtl/atplus2_core.v). +* +* Magic ports (GAL U22, decodes only A6/A7/A15 within /VPA space): +* PORT_A = base+0x41 (byte): write = mode bits, read = status +* PORT_B = base+0x80 (word): write = resume(+INT), read = stop + trap info +* default base 0xBFF000 -> 0xBFF041/0xBFF080 (aliases all over CIA space) +* +* Uses the PCem v16 core already present for the A1060/A2088/A2286/A2386 +* bridgeboards. Mutually exclusive with those boards (PCem is single +* instance). +*/ + +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef WITH_X86 + +#include "options.h" +#include "custom.h" +#include "memory.h" +#include "newcpu.h" +#include "debug.h" +#include "x86.h" +#include "atonce.h" +#include "uae.h" +#include "rommgr.h" +#include "zfile.h" +#include "devices.h" +#include "autoconf.h" +#include "filesys.h" + +#include "pcem/ibm.h" +#include "pcem/device.h" +#include "pcem/cpu.h" +#include "pcem/pic.h" +#include "pcem/timer.h" +#include "pcem/pcemglue.h" +#include "pcem/model.h" +#include "pcem/mem.h" +#include "pcem/codegen.h" +#include "pcem/x86.h" +#include "pcem/io.h" + +/* +* INTEGRATION STATUS (2026-06-13): structurally complete, compiles into the +* expansion list as "ATonce Plus" (Vortex). Points to validate once running: +* 1. PCem core bring-up: minimal init (no pit/dma/kbc). Confirm exec386 runs +* and the 286 starts from F000:FFF0 (= chip RAM 0x3FFF0 via the F000 map). +* 2. Trap/resume timing: atonce_trap ends the slice (cycles=0) AFTER the I/O +* instruction; data flows through the pusha frame in shared RAM, so this is +* equivalent to the hardware freeze-inside-OUT for BIOS service traps. +* Verify raw (non-BIOS) IN/OUT paths behave too. +* 3. Magic-port base is hardwired to the v2.20 default (A6/A7/A15 in 0xBFxxxx). +* The driver can relocate it; make it configurable if a disk uses another base. +* 4. 287/FPU: fpu_type=FPU_NONE; the err287/setup287 path is not wired yet. +* Acceptance test: original floppies in Disk/ -> driver loads, Vortex BIOS POST +* beacon (OUT 0xBB) reached, DOS prompt. +*/ + +#define ONBOARD_SIZE 0x80000 // 512KB local DRAM + +// Cycles run synchronously inside a PORT_B resume: the 286 advances until its +// next I/O instruction traps (atonce_trap forces the slice to end) or this +// budget is exhausted. On real hardware the 286 runs FREELY at 16 MHz between +// I/O instructions; the 68k only intervenes on a trap. The 68k driver stops the +// 286 (PORT_B read) right after each resume, so atonce_hsync cannot continue a +// budget-exhausted run (it is frozen, not merely idle) -> the per-resume budget +// IS the throughput unit. PERFORMANCE: at 2000 the 286 effectively ran at only +// 0.2-0.85 MHz (resumes/s x 2000), making DOS sluggish. A trap ends the burst +// early anyway, so this is just the cap on no-I/O stretches; sized so the 286 +// reaches ~16 MHz effective without long stretches blocking the Amiga thread. +#define ATONCE_RUN_CYCLES 65536 + +// PORT_A write bits (68k D0-D7 = 286 D8-D15, byte-swapped by U24/U25) +#define PORTA_DIN 0x01 // config DIN / runtime: 286 reset pulse +#define PORTA_SETUP 0x02 // 287 reset / MEMMODE setup mode +#define PORTA_A20 0x04 // gate A20 enable +#define PORTA_ACK287 0x08 // 287 error acknowledge +#define PORTA_PROG 0x10 // DONE / -PROG + +// PORT_A read (status) bits +#define PORTA_ST_ERR287_OR_DIR 0x01 // pre-stop: 287 error; post-stop: 1=IN 0=OUT +#define PORTA_ST_TRAP 0x02 // I/O trap pending + +struct atonce_s { + int configured; // LCA configured (DONE) + uae_u8 mode; // last PORT_A write + int a20en; + int setup; // MEMMODE setup mode (PORT_A D1) + int frozen; // 286 stalled: trap pending or stopped by PORT_B read + int stopped; // explicitly stopped by PORT_B read + int in_reset; // 286 held in reset + int trap_pending; + uae_u16 trap_port; // latched 10-bit port + int trap_dir; // 1 = IN + int refresh_div; // hsync counter for the idle video-refresh trap + uae_u32 text_hash; // last-rendered hash of the 286 text page (change detect) + int refresh_heartbeat; // frames since last refresh (slow cursor-blink heartbeat) + int refresh_due; // a video refresh is owed (set per frame, serviced + // at the next safe point: idle hsync or 286 resume) + int fake_active; // a synthetic CRTC refresh trap is in flight + int err287; + // MEMMODE shift register, stages AH/BH/CH/DH (AH = last shifted bit) + int mm_a, mm_b, mm_c, mm_d; + // translation table: x86 64K bank -> Amiga bank base, -1 = onboard DRAM + int bank_amiga[256]; + uae_u8 *dram; // 512KB onboard DRAM (= PCem "ram") + uae_u8 *vmem; // DIAGNOSTIC: private backing for the 286's sub-1MB + // chip windows (banks 0-6), so video/conventional + // writes can't corrupt Amiga chip RAM. F000 mailbox + // (chip 0x30000) stays on real chip RAM. + int ext_memsize; // configured extended memory (bytes), 0 = none + uae_u8 *emem; // DIAGNOSTIC: private flat buffer backing the 286's + // >1MB extended memory (ATONCE_ISOLATE_EXT), with a + // real exec pointer so getpccache works in the HMA. + struct romconfig *rc; +}; + +// DIAGNOSTIC toggle: when 1, the 286's sub-1MB chip windows (except the F000 +// mailbox) are backed by a private buffer instead of Amiga chip RAM. If the +// 68k Guru disappears, it proves the crash is the 286 stamping on OS chip RAM. +// Now 0: the wild-jump root cause (missing OUT-data shadow) is fixed, and the +// driver's video bridge reads the 286 framebuffer FROM real chip RAM (chip +// 0x18000) to render the Amiga screen, so the 286 must write video there (not +// into the private vmem) or the screen stays black. +#define ATONCE_ISOLATE_CHIP 0 +#define ATONCE_VMEM_SIZE 0x70000 // covers chip banks 0..6 (0x00000-0x6FFFF) + +// DIAGNOSTIC toggle: when 1, the 286's extended memory (>1MB) is backed by a +// private, flat buffer instead of the dynamic MEMMODE translation onto Amiga +// fast RAM. This buffer is mapped with a REAL exec pointer, so PCem's code fetch +// (getpccache) works when himem.sys relocates/executes in the HMA (>1MB). It +// also fully isolates the 286's extended memory from the Amiga (no sharing of +// fast RAM with the 286, as on the video-isolation test). Use to bring up XMS / +// himem.sys before wiring the extended memory back onto shared Amiga RAM. +#define ATONCE_ISOLATE_EXT 1 +#define ATONCE_EMEM_SIZE 0xf00000 // covers the whole >1MB window (0x100000-0xFFFFFF) + +static struct atonce_s *atonce; + +int atonce_intr_vector = -1; // consumed by picinterrupt() (pcem/pic.cpp patch) + +// Allocates pcemmap (the PCem memory-map block table) + the PCem video infra. +// The bridgeboards reach it via their video card; the ATonce has no PCem video, +// so we call it directly (priv=NULL is safe: it is only stored as gfxboard_priv, +// never dereferenced unless PCem video is rendered, which we never do). +extern void initpcemvideo(void *p, bool swapped); + +/*-------------------------------------------------------------------------*/ +/* Address translation 286 -> Amiga, ported from atplus2_core.v */ +/* (formally equivalent to the golden LCA netlist). */ +/* Translation depends only on x86 A16-A23 + board state, so it is */ +/* precomputed per 64K bank and rebuilt when MEMMODE/A20 change. */ +/*-------------------------------------------------------------------------*/ + +// Drive PCem's physical A20 mask from the LCA gate (PORT_A D2). On the ATonce +// there is no keyboard controller: the LCA A20 gate IS the A20 line. PCem masks +// bit 20 of the physical address via rammask (mem_a20_recalc), so this makes the +// 286 wrap 0x100000->0x000000 when A20 is gated off - exactly what himem.sys's +// A20-control test checks (else: "Unable to control A20 line!"). We own A20 +// entirely: keep mem_a20_key cleared (PCem defaults it to 2 = A20 always on). +static void atonce_apply_a20(struct atonce_s *xb) +{ + mem_a20_key = 0; + mem_a20_alt = xb->a20en ? 1 : 0; + mem_a20_recalc(); +} + +static void atonce_rebuild_map(struct atonce_s *xb) +{ + for (int bank = 0; bank < 256; bank++) { + int a16 = (bank >> 0) & 1, a17 = (bank >> 1) & 1; + int a18 = (bank >> 2) & 1, a19 = (bank >> 3) & 1; + int a20 = (bank >> 4) & 1, a21 = (bank >> 5) & 1; + int a22 = (bank >> 6) & 1, a23 = (bank >> 7) & 1; + + // U15: A20 gated by PORT_A D2 (DI.Q) + int a20g = a20 & xb->a20en; + + // U45/U43 glue: onboard DRAM select for x86 0-512K (memory cycles) + if (!a23 && !a22 && !a21 && !a20g && !a19) { + xb->bank_amiga[bank] = -1; + continue; + } + + // U28/U45 glue: P67 = A23 XOR NOR(A23,A22,A21,A20g); ~P67 = 1MB-8MB + int p67 = a23 ^ ((a23 | a22 | a21 | a20g) ? 0 : 1); + // LCA DI.X: EXT = M/IO & ~P67 & A20-enable + int ext = (!p67 && xb->a20en) ? 1 : 0; + + int mm_a = xb->mm_a, mm_b = xb->mm_b, mm_c = xb->mm_c, mm_d = xb->mm_d; + + // CLB AF + int af_x = (!a19 & a17) | (!a16 & a17) | (a18 & a17) | (a19 & !a16 & a18); + // CLB FC (memory cycle: mio=1) + int za16 = a16; + // CLB BF + int za17 = (af_x & a17) | (af_x & !ext) | (a17 & ext); + // CLB BG + int za18 = (!a19 & a18) | (ext & a18) | (a19 & !a17 & !ext & !a18); + // CLB AH (G) + int za19 = (a19 & ext) | (ext & mm_a); + // CLB CI + int ci_x = (a20g & !a21) | (mm_d & !a21) | (!a20g & !mm_d & a21); + int za20 = (!a20g & !mm_d & ext) | (a20g & mm_d & ext); + // CLB CJ + int cj_x = (a22 & !mm_d) | (a22 & !a21) | (!a22 & mm_d & a21) | (!a22 & a21 & a20g); + // CLB BH (G) + int za21 = ext & ci_x & mm_b; + // CLB DH (G) + int za23 = mm_c & ext; + // CLB BJ + int za22 = za23 | (!za23 & cj_x & ext & mm_b); + + xb->bank_amiga[bank] = + ((za23 << 7) | (za22 << 6) | (za21 << 5) | (za20 << 4) | + (za19 << 3) | (za18 << 2) | (za17 << 1) | za16) << 16; + } +} + +/*-------------------------------------------------------------------------*/ +/* x86-side memory access (PCem handlers). Byte order: the data bus is */ +/* byte-swapped by wiring (U24/U25), which makes byte addresses map 1:1; */ +/* reading consecutive Amiga bytes as a little-endian word is exactly what */ +/* the hardware produces. */ +/*-------------------------------------------------------------------------*/ + +static uint8_t atonce_mem_readb(uint32_t addr, void *priv) +{ + struct atonce_s *xb = (struct atonce_s*)priv; + if (!xb || !xb->dram) { + write_log(_T("ATonce: ext readb @%06x with NULL ctx\n"), addr); + return 0xff; + } + int base = xb->bank_amiga[(addr >> 16) & 0xff]; + if (base < 0) + return xb->dram[addr & (ONBOARD_SIZE - 1)]; + return get_byte(base | (addr & 0xffff)); +} +static uint16_t atonce_mem_readw(uint32_t addr, void *priv) +{ + return atonce_mem_readb(addr, priv) | (atonce_mem_readb(addr + 1, priv) << 8); +} +static uint32_t atonce_mem_readl(uint32_t addr, void *priv) +{ + return atonce_mem_readw(addr, priv) | (atonce_mem_readw(addr + 2, priv) << 16); +} +static void atonce_mem_writeb(uint32_t addr, uint8_t val, void *priv) +{ + struct atonce_s *xb = (struct atonce_s*)priv; + if (!xb || !xb->dram) { + write_log(_T("ATonce: ext writeb @%06x with NULL ctx\n"), addr); + return; + } + int base = xb->bank_amiga[(addr >> 16) & 0xff]; + if (base < 0) { + xb->dram[addr & (ONBOARD_SIZE - 1)] = val; + return; + } + put_byte(base | (addr & 0xffff), val); +} +static void atonce_mem_writew(uint32_t addr, uint16_t val, void *priv) +{ + atonce_mem_writeb(addr, val & 0xff, priv); + atonce_mem_writeb(addr + 1, val >> 8, priv); +} +static void atonce_mem_writel(uint32_t addr, uint32_t val, void *priv) +{ + atonce_mem_writew(addr, val & 0xffff, priv); + atonce_mem_writew(addr + 2, val >> 16, priv); +} + +static mem_mapping_t atonce_mapping; // extended memory (1MB+), dynamic + +/*-------------------------------------------------------------------------*/ +/* Sub-1MB chip-RAM windows (static map). The 286 executes code from here */ +/* (BIOS at F000 = chip 0x30000, conventional 512-640K = chip 0x40000), and */ +/* PCem's code fetch (getpccache) needs a real pointer, not the function */ +/* handlers. So each window exposes WinUAE chip RAM directly via an "exec" */ +/* pointer; data goes through funcs that index the same buffer. The U24/U25 */ +/* byte-swap is 1:1 at byte level, so a direct pointer matches the handlers. */ +/* This map is MEMMODE-independent: for banks 0-0xF the LCA EXT term is 0. */ +/* priv = chipbase + chipoff - xbase, so priv[addr] is the chip byte. */ +/*-------------------------------------------------------------------------*/ + +static uint8_t atonce_chip_readb(uint32_t addr, void *priv) +{ + return ((uae_u8*)priv)[addr]; +} +static uint16_t atonce_chip_readw(uint32_t addr, void *priv) +{ + uae_u8 *p = (uae_u8*)priv; + return p[addr] | (p[addr + 1] << 8); +} +static uint32_t atonce_chip_readl(uint32_t addr, void *priv) +{ + uae_u8 *p = (uae_u8*)priv; + return p[addr] | (p[addr + 1] << 8) | (p[addr + 2] << 16) | (p[addr + 3] << 24); +} +static void atonce_chip_writeb(uint32_t addr, uint8_t v, void *priv) +{ + uae_u8 *p = (uae_u8*)priv; + p[addr] = v; +} +static void atonce_chip_writew(uint32_t addr, uint16_t v, void *priv) +{ + uae_u8 *p = (uae_u8*)priv; + p[addr] = (uae_u8)v; + p[addr + 1] = (uae_u8)(v >> 8); +} +static void atonce_chip_writel(uint32_t addr, uint32_t v, void *priv) +{ + uae_u8 *p = (uae_u8*)priv; + p[addr] = (uae_u8)v; + p[addr + 1] = (uae_u8)(v >> 8); + p[addr + 2] = (uae_u8)(v >> 16); + p[addr + 3] = (uae_u8)(v >> 24); +} + +// Sub-1MB x86 bank -> Amiga chip bank. This MUST match the hardware LCA +// translation (atonce_rebuild_map, ported from the verified RTL atplus2_core.v): +// these exec-pointer windows and the function-handler map model the same chip. +// x86 8->chip 0x40000 9->0x50000 A->0x20000 B->0x10000 +// C->0x20000 D->0x10000 E->0x20000 F->0x30000 (BIOS) +// NOTE banks A and C: the .dsg software translation table the 68k driver shifts +// in says 0x60000 (A) / 0x00000 (C), but those are SOFTWARE-only aliases; the +// LCA hardware decodes both to 0x20000 . +// Both banks are unused aliases (no EGA/VGA), so this is a fidelity fix, not a +// functional one for DOS - but the emulator models the hardware, so use the LCA value. +static const struct { uae_u32 xbase, size, chipoff; } atonce_win_map[] = { + { 0x80000, 0x20000, 0x40000 }, // banks 8,9 -> chip 0x40000,0x50000 (contiguous) + { 0xA0000, 0x10000, 0x20000 }, // bank A -> chip 0x20000 (LCA; .dsg software says 0x60000) + { 0xB0000, 0x10000, 0x10000 }, // bank B -> chip 0x10000 (MDA/CGA video shadow) + { 0xC0000, 0x10000, 0x20000 }, // bank C -> chip 0x20000 (LCA; .dsg software says 0x00000) + { 0xD0000, 0x10000, 0x10000 }, // bank D -> chip 0x10000 (hardware alias of B0000) + { 0xE0000, 0x10000, 0x20000 }, // bank E -> chip 0x20000 + { 0xF0000, 0x10000, 0x30000 }, // bank F -> chip 0x30000 (BIOS; FFFF0->chip 0x3FFF0) +}; +#define ATONCE_NWIN (sizeof(atonce_win_map) / sizeof(atonce_win_map[0])) +static mem_mapping_t atonce_win[ATONCE_NWIN]; + +static void atonce_map_windows(void) +{ + uae_u8 *chip = chipmem_bank.baseaddr; + uae_u32 chipsize = chipmem_bank.allocated_size; + if (!chip || chipsize < 0x60000) { + write_log(_T("ATonce: chip RAM too small (%uK) for shared windows; ") + _T("286 cannot fetch the BIOS\n"), chipsize / 1024); + return; + } + for (unsigned i = 0; i < ATONCE_NWIN; i++) { + uae_u8 *base = chip; +#if ATONCE_ISOLATE_CHIP + // Back every window except the F000 mailbox (chip 0x30000) with a private + // buffer, so the 286's video/conventional writes never touch Amiga chip RAM. + if (atonce && atonce->vmem && atonce_win_map[i].chipoff != 0x30000) + base = atonce->vmem; +#endif + uae_u8 *lin = base + atonce_win_map[i].chipoff - atonce_win_map[i].xbase; + // NOTE: mem_mapping_add (stock PCem), NOT mem_mapping_addx (WinUAE glue). + // The 286 core's getpccache/readmembl consult read_mapping[]/_mem_exec[] + // filled by the stock mem_mapping_add; the glue's addx fills a separate + // pcemmap[] table and self-disables on re-add. The bridgeboards use add. + mem_mapping_add(&atonce_win[i], atonce_win_map[i].xbase, atonce_win_map[i].size, + atonce_chip_readb, atonce_chip_readw, atonce_chip_readl, + atonce_chip_writeb, atonce_chip_writew, atonce_chip_writel, + base + atonce_win_map[i].chipoff, // exec ptr: indexed [addr - xbase] + MEM_MAPPING_EXTERNAL, lin); // priv: indexed [addr] + } + // Mark 512K-1MB as EXTERNAL RAM, else mem_mapping_recalc refuses our + // EXTERNAL mappings (default state is INTERNAL) and getpccache fails. + mem_set_mem_state(0x80000, 0x80000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + mem_mapping_t *w = &atonce_win[ATONCE_NWIN - 1]; // F000 window + write_log(_T("ATonce: mapped %u windows, chip=%p %uK; ") + _T("phys 0=%02x 10000=%02x 80000=%02x F0000=%02x FFFF0=%02x; ") + _T("win[F].base=%x size=%x flags=%x en=%d directRB=%02x\n"), + (unsigned)ATONCE_NWIN, chip, chipsize / 1024, + mem_readb_phys(0), mem_readb_phys(0x10000), mem_readb_phys(0x80000), + mem_readb_phys(0xF0000), mem_readb_phys(0xFFFF0), + (unsigned)w->base, (unsigned)w->size, w->flags, w->enable, + w->read_b(0xFFFF0, w->p)); +} + +/*-------------------------------------------------------------------------*/ +/* x86-side I/O: every access freezes the 286 (the LCA holds READY) and */ +/* latches port + direction. The 68k driver polls PORT_A, stops/reads the */ +/* trap info from PORT_B, emulates the device and resumes with PORT_B */ +/* write (optionally delivering an INT whose stub patches the register */ +/* frame in shared RAM). The data returned to a trapped IN here is junk */ +/* (0xFF) exactly like on real hardware: the BIOS stub overwrites AX in */ +/* the pusha frame afterwards. */ +/*-------------------------------------------------------------------------*/ + +static void atonce_trap(struct atonce_s *xb, uae_u16 port, int dir) +{ + xb->trap_pending = 1; + xb->trap_port = port & 0x3ff; + xb->trap_dir = dir; + xb->frozen = 1; + // End the current PCem slice right after this instruction. exec386's inner + // loop exits on cycdiff (= oldcyc - cycles) >= cycle_period, not on cycles + // alone, so a huge-negative value forces cycdiff past any cycle_period and + // bails out of both the inner and the outer (cycles>0) loop immediately. + cycles = -0x40000000; +} + +// Drive the 286 for up to `budget` cycles. PCem's exec386 paces itself against +// the timer subsystem: its inner loop runs until cycdiff >= cycle_period, where +// cycle_period = (timer_target - tsc) + 1, and timer_process() only advances +// timer_target while a timer is registered. We register no PCem timers (the PIT +// lives behind the I/O trap and is emulated by the 68k), so timer_target stays +// behind the ever-growing tsc, cycle_period goes <= 0, and exec386 spins +// forever in its outer loop without ever issuing an instruction. Keeping +// timer_target a budget ahead of tsc makes each burst actually execute, then +// return when the budget is spent or a trap forces it out (atonce_trap). +static void atonce_run(struct atonce_s *xb, int budget) +{ + (void)xb; + timer_target = (uint32_t)tsc + (uint32_t)budget + 16; + cycles = 0; + exec386(budget); +} + +static uint8_t atonce_io_inb(uint16_t addr, void *priv) +{ + atonce_trap((struct atonce_s*)priv, addr, 1); + return 0xff; +} +static uint16_t atonce_io_inw(uint16_t addr, void *priv) +{ + atonce_trap((struct atonce_s*)priv, addr, 1); + return 0xffff; +} +static uint32_t atonce_io_inl(uint16_t addr, void *priv) +{ + atonce_trap((struct atonce_s*)priv, addr, 1); + return 0xffffffff; +} +// On a trapped 286 OUT the board latches the written data into the shared F000 +// I/O shadow at F000:[port] (= chip 0x30000 + port). The 68k driver reads this +// shadow byte to recover the OUT value: e.g. the CRTC handler reads the register +// index from F000:03D4 and the data from F000:03D5 (it never reads the pusha +// frame for these). WinUAE previously discarded the OUT data, so the shadow kept +// the reject handler's 0xFF -> the driver's signed index bound-check (cmp.b #$f / +// bgt) let 0xFF through, indexed 0xFF*4 past its 16-entry jump table, and did a +// wild jmp (a4)=0x264ad7c4 -> stack-overflow MOVEM sled -> Task corruption -> +// Guru #3. Mirroring the data into the shadow on every OUT fixes that at the +// source. trap_port is the 10-bit PC I/O address, so the shadow lives in +// F000:0000-03FF, well below the BIOS mailboxes at F000:1400+. (For IN the data +// flows the other way, via the BIOS pusha-frame edit, and needs no shadow here.) +static void atonce_io_shadow(uae_u16 port, uae_u32 val, int size) +{ + uae_u8 *chip = chipmem_bank.baseaddr; + if (!chip || chipmem_bank.allocated_size < 0x30400) + return; + uae_u8 *sh = chip + 0x30000; + for (int i = 0; i < size; i++) + sh[(port + i) & 0x3ff] = (uae_u8)(val >> (8 * i)); +} + +// Symmetric to the OUT shadow: a trapped 286 IN reads back the value the driver +// pre-staged in the shared F000 I/O shadow (F000:[port]). E.g. on an Amiga key +// the driver writes the PC scancode to F000:0060 and the 8042 status to +// F000:0064, then injects keyboard IRQ1; the 286's keyboard ISR does IN 0x60 / +// IN 0x64 and must read those, not junk. (Returning 0xff broke every device +// poll that reads back driver-maintained state: keyboard, 8042, PIC mask, RTC, +// CGA retrace 0x3da, ...). The 0xBx BIOS services are OUTs, not INs, so the +// pusha-frame path is unaffected. +static uae_u32 atonce_io_shadow_read(uae_u16 port, int size) +{ + uae_u8 *chip = chipmem_bank.baseaddr; + if (!chip || chipmem_bank.allocated_size < 0x30400) + return 0xffffffff; + uae_u8 *sh = chip + 0x30000; + uae_u32 v = 0; + for (int i = 0; i < size; i++) + v |= (uae_u32)sh[(port + i) & 0x3ff] << (8 * i); + return v; +} + +static void atonce_io_outb(uint16_t addr, uint8_t val, void *priv) +{ + atonce_io_shadow(addr & 0x3ff, val, 1); + atonce_trap((struct atonce_s*)priv, addr, 0); +} +static void atonce_io_outw(uint16_t addr, uint16_t val, void *priv) +{ + atonce_io_shadow(addr & 0x3ff, val, 2); + atonce_trap((struct atonce_s*)priv, addr, 0); +} +static void atonce_io_outl(uint16_t addr, uint32_t val, void *priv) +{ + atonce_io_shadow(addr & 0x3ff, val, 4); + atonce_trap((struct atonce_s*)priv, addr, 0); +} + +/*-------------------------------------------------------------------------*/ +/* HDD bypass: serve the PC C: drive by reading the disk image the user attached */ +/* in WinUAE's HDD settings, replacing the 68k driver's scsi.device path. The VHD */ +/* MUST be attached as a WinUAE hardfile (the Amiga boots from its Amiga partition */ +/* and loads the atonce driver from there); we read the PC partition through */ +/* WinUAE's own hardfile handle (get_hardfile_data/hdf_read), so there is no */ +/* file-lock / double-open conflict. Nothing geometry-related is hardcoded: we */ +/* parse the RDB and pick the partition whose first block is a PC MBR (0x55AA); its */ +/* byte offset comes from the RDB DosEnvVec and its CHS geometry from the MBR */ +/* partition table's ending CHS. The BIOS forwards INT 13h as OUT 0xB0 (data) / */ +/* OUT 0xB9 (control) with the registers live; for drive >= 0x80 we do the I/O */ +/* against the hardfile, write the result into the 286 PUSHA frame and skip the trap.*/ +/* NOTE: the board's 286 DRAM window at 0x900000-0x9FFFFF must not overlap OS-usable */ +/* Z2 fast RAM, or the OS may allocate there and corrupt (see atonce_init mapping). */ +/*-------------------------------------------------------------------------*/ +#define ATONCE_HDD_DRIVE 0x80 // BIOS drive number of the first HDD (C:) + +static struct hardfiledata *atonce_hfd; // WinUAE hardfile backing the PC C: drive +static int atonce_hdd_tried; +static uae_u64 atonce_part_off; // byte offset of PC LBA 0 inside the hardfile +static int atonce_hdd_heads; // PC geometry (from the MBR ending CHS) +static int atonce_hdd_spt; +static uae_u32 atonce_hdd_sectors; // total PC sectors (for AH=08 max cylinder) + +static uae_u32 atonce_be32(const uae_u8 *p) +{ + return ((uae_u32)p[0] << 24) | ((uae_u32)p[1] << 16) | + ((uae_u32)p[2] << 8) | (uae_u32)p[3]; +} + +// Resolve the PC C: drive from WinUAE's attached hardfiles (once). Returns 1 if found. +static int atonce_hdd_resolve(void) +{ + if (atonce_hdd_tried) + return atonce_hfd != NULL; + atonce_hdd_tried = 1; + + for (int nr = 0; nr < MAX_FILESYSTEM_UNITS; nr++) { + struct hardfiledata *hfd = get_hardfile_data(nr); + if (!hfd) + continue; + write_log(_T("ATonce HDD: examine unit %d '%s' handle_valid=%d virtsize=%llu\n"), + nr, hfd->ci.rootdir, hfd->handle_valid, hfd->virtsize); + if (!hfd->handle_valid) + continue; + + uae_u8 blk[512]; + uae_u32 err = 0; + // Find the RDSK block within the first 16 sectors. + int found_rdb = 0; + for (int b = 0; b < 16; b++) { + if (hdf_read(hfd, blk, (uae_u64)b * 512, 512, &err) != 512) + break; + if (blk[0] == 'R' && blk[1] == 'D' && blk[2] == 'S' && blk[3] == 'K') { + found_rdb = 1; + break; + } + } + if (!found_rdb) + continue; + + // Walk the partition list looking for one that starts with a PC MBR. + uae_u32 part = atonce_be32(blk + 0x1c); // rdb_PartitionList (RDSK off 0x1C) + for (int guard = 0; part != 0xffffffff && guard < 64; guard++) { + if (hdf_read(hfd, blk, (uae_u64)part * 512, 512, &err) != 512) + break; + if (!(blk[0] == 'P' && blk[1] == 'A' && blk[2] == 'R' && blk[3] == 'T')) + break; + uae_u32 next = atonce_be32(blk + 0x10); + uae_u32 surfaces = atonce_be32(blk + 0x8c); // de_Surfaces + uae_u32 bpt = atonce_be32(blk + 0x94); // de_BlocksPerTrack + uae_u32 lowcyl = atonce_be32(blk + 0xa4); // de_LowCyl + uae_u32 highcyl = atonce_be32(blk + 0xa8); // de_HighCyl + uae_u32 bsize = atonce_be32(blk + 0x84) * 4; // de_SizeBlock (longs) -> bytes + if (bsize < 512) + bsize = 512; + uae_u64 part_off = (uae_u64)lowcyl * surfaces * bpt * bsize; + + // Partition name (BSTR at pb_DriveName, off 0x24: len byte + chars). + char pname[32]; + int pl = blk[0x24]; if (pl > 31) pl = 31; + for (int i = 0; i < pl; i++) pname[i] = blk[0x25 + i]; + pname[pl] = 0; + uae_u8 mbr[512]; + int rd = hdf_read(hfd, mbr, part_off, 512, &err); + write_log(_T("ATonce HDD: PART '%hs' surf=%u bpt=%u lcyl=%u hcyl=%u ") + _T("off=%llu rd=%d sig=%02x%02x\n"), + pname, surfaces, bpt, lowcyl, highcyl, + (unsigned long long)part_off, rd, mbr[0x1fe], mbr[0x1ff]); + if (rd == 512 && mbr[0x1fe] == 0x55 && mbr[0x1ff] == 0xaa) { + // PC disk found. CHS geometry from a partition entry's ending CHS + // (the standard heuristic DOS/fdisk use). Scan all 4 entries and + // take the first non-empty one, preferring the active (0x80) one, + // so an empty/extended entry 0 doesn't give bogus 0/0 geometry. + const uae_u8 *pe = NULL; + for (int e = 0; e < 4; e++) { + const uae_u8 *p = mbr + 0x1be + e * 16; + if (p[4] == 0) // type 0 = unused entry + continue; + if (!pe) + pe = p; // first non-empty: fallback choice + if (p[0] == 0x80) { // active partition: preferred + pe = p; + break; + } + } + int heads = pe ? pe[5] + 1 : 0; + int spt = pe ? (pe[6] & 0x3f) : 0; + if (heads < 1) heads = 1; + if (spt < 1) spt = 1; + atonce_hfd = hfd; + atonce_part_off = part_off; + atonce_hdd_heads = heads; + atonce_hdd_spt = spt; + atonce_hdd_sectors = (uae_u32)(((uae_u64)(highcyl - lowcyl + 1) * + surfaces * bpt) * (bsize / 512)); + write_log(_T("ATonce HDD: unit %d '%s' PC disk at byte %llu, ") + _T("CHS=%d/%d, %u sectors (%u MB)\n"), + nr, hfd->ci.rootdir, (unsigned long long)part_off, + heads, spt, atonce_hdd_sectors, atonce_hdd_sectors / 2048); + return 1; + } + part = next; + } + } + write_log(_T("ATonce HDD: no PC partition (MBR) found in any WinUAE hardfile; ") + _T("falling back to the 68k driver\n")); + return 0; +} + +// Write the INT 13h result into the 286 PUSHA frame (the BIOS published its +// SS:SP, so it is at ss+SP). Byte offsets match the 68k driver's a3-relative +// reads (AH=status @+0x13, AL=count @+0x12, FLAGS @+0x18; CF bit0 = error); +// 286 byte addressing is 1:1 with the driver's view. +static void atonce_disk_result(uint8_t ah, uint8_t al, int error) +{ + uint32_t frame = ss + SP; + mem_writeb_phys(frame + 0x13, ah); + mem_writeb_phys(frame + 0x12, al); + uint8_t fl = mem_readb_phys(frame + 0x18); + if (error) + fl |= 0x01; + else + fl &= ~0x01; + mem_writeb_phys(frame + 0x18, fl); +} + +// Returns 1 if the disk service was fully handled here (skip the 68k trap). +static int atonce_disk_bypass(uint16_t port) +{ + int drive = DL; + if (drive < ATONCE_HDD_DRIVE || (drive & 0x7f) != 0) + return 0; // floppy / non-C: HDD -> let the 68k driver run + if (!atonce_hdd_resolve()) + return 0; // no PC disk configured -> fall back to the driver + + int func = AH; + int cyl = CH | ((CL & 0xc0) << 2); + int sec = CL & 0x3f; + int head = DH; + int count = AL; + + switch (func) { + case 0x02: // read sectors + case 0x03: { // write sectors + if (sec == 0) { atonce_disk_result(0x01, 0, 1); return 1; } + uint32_t lba = (uint32_t)(cyl * atonce_hdd_heads + head) * atonce_hdd_spt + + (sec - 1); + uint32_t buf = es + BX; + uae_u64 pos = atonce_part_off + (uae_u64)lba * 512u; + int ok = 1, done = 0; + uint8_t sb[512]; + uae_u32 err = 0; + for (int s = 0; s < count; s++) { + uae_u64 sp = pos + (uae_u64)s * 512; + if (func == 0x02) { + if (hdf_read(atonce_hfd, sb, sp, 512, &err) != 512) { ok = 0; break; } + for (int i = 0; i < 512; i++) + mem_writeb_phys(buf + s * 512 + i, sb[i]); + } else { + for (int i = 0; i < 512; i++) + sb[i] = mem_readb_phys(buf + s * 512 + i); + if (hdf_write(atonce_hfd, sb, sp, 512, &err) != 512) { ok = 0; break; } + } + done++; + } + atonce_disk_result(ok ? 0x00 : 0x04, (uint8_t)done, ok ? 0 : 1); + return 1; + } + case 0x08: { // get drive parameters + int percyl = atonce_hdd_heads * atonce_hdd_spt; + int maxcyl = (percyl > 0 ? (int)(atonce_hdd_sectors / percyl) : 1) - 1; + if (maxcyl > 1023) maxcyl = 1023; + uint32_t frame = ss + SP; + uint8_t ch = (uint8_t)(maxcyl & 0xff); + uint8_t cl = (uint8_t)(atonce_hdd_spt | ((maxcyl >> 2) & 0xc0)); + mem_writeb_phys(frame + 0x10, cl); // CL + mem_writeb_phys(frame + 0x11, ch); // CH + mem_writeb_phys(frame + 0x0e, 0x01); // DL = 1 HDD + mem_writeb_phys(frame + 0x0f, atonce_hdd_heads - 1); // DH = max head + atonce_disk_result(0x00, 0, 0); + return 1; + } + case 0x00: // reset + case 0x01: // get last status + case 0x04: // verify (no transfer) + case 0x09: // set drive params + case 0x0c: // seek + case 0x0d: // alternate reset + case 0x10: // test drive ready + case 0x11: // recalibrate + atonce_disk_result(0x00, (uint8_t)count, 0); + return 1; + default: // unsupported function: claim success rather than stall the BIOS + atonce_disk_result(0x00, (uint8_t)count, 0); + return 1; + } +} + +// Entry point from pcem/x86.cpp's portin/portout. The 286's IN/OUT opcodes go +// through outb()/inb() -> portout()/portin() in x86.cpp, which are the *bridge- +// board* dispatchers and dereference bridges[0] (NULL for the ATonce). Those +// functions now fall through to here when no PC bridgeboard owns the x86 side. +// Every x86 I/O freezes the 286 (the LCA holds READY) and is emulated by the +// 68k driver; the 287 ports 0xf8-0xff are handled by the FPU and not trapped. +// dir: 1 = IN, 0 = OUT. On OUT the board latches the written data into the +// shared F000 I/O shadow (F000:[port] = chip 0x30000+port); the 68k driver +// reads it back (CRTC index from F000:03D4, data from F000:03D5, etc.). This is +// the ACTUAL OUT path: x86.cpp's portout/portin short-circuit here before the +// stock io_sethandlerx table, so the shadow must be read/written here. Returns +// the IN data (the driver-staged shadow byte/word) for dir=1; 0 for OUT. +uint32_t atonce_x86_io(uint16_t port, int dir, uint32_t val, int size) +{ + struct atonce_s *xb = atonce; + if (!xb) + return 0xffffffff; + if ((port & 0xfff8) == 0xf8) + return 0xffffffff; // 287 coprocessor range: no trap (no FPU configured) + if (!dir) { + // HDD bypass: serve drive >= 0x80 from the host VHD and skip the 68k trap. + if ((port == 0xb0 || port == 0xb9) && atonce_disk_bypass(port)) + return 0; + atonce_io_shadow(port & 0x3ff, val, size); + atonce_trap(xb, port, dir); + return 0; + } + // IN: return the value the driver pre-staged in the F000 I/O shadow, then + // trap so it can post-update device state (e.g. clear the 8042 data-ready). + uint32_t v = atonce_io_shadow_read(port & 0x3ff, size); + atonce_trap(xb, port, dir); + return v; +} + +/*-------------------------------------------------------------------------*/ +/* 68k side: magic ports. Called from cia.cpp for accesses in CIA space. */ +/* GAL U22 decode: only A6/A7/A15 (+ /VPA + any data strobe): */ +/* PORT_A: A15=1 A6=1 A7=0 -> (addr & 0x80c0) == 0x8040 */ +/* PORT_B: A15=1 A7=1 A6=0 -> (addr & 0x80c0) == 0x8080 */ +/*-------------------------------------------------------------------------*/ + +static uae_u8 atonce_porta_read(struct atonce_s *xb) +{ + uae_u8 v = 0; + if (xb->trap_pending) + v |= PORTA_ST_TRAP; + if (xb->stopped) { + if (xb->trap_dir) + v |= PORTA_ST_ERR287_OR_DIR; + } else { + if (xb->err287) + v |= PORTA_ST_ERR287_OR_DIR; + } + return v; +} + +static void atonce_porta_write(struct atonce_s *xb, uae_u8 v) +{ + uae_u8 old = xb->mode; + xb->mode = v; + + // D2: gate A20 + int a20 = (v & PORTA_A20) ? 1 : 0; + if (a20 != xb->a20en) { + xb->a20en = a20; + atonce_rebuild_map(xb); + atonce_apply_a20(xb); + } + + // D1: 287 reset / MEMMODE setup mode + xb->setup = (v & PORTA_SETUP) ? 1 : 0; + + // D3: 287 error acknowledge + if (v & PORTA_ACK287) + xb->err287 = 0; + + // D0: at config time = DIN; at runtime, a 0->1 edge = explicit 286 reset + // (the driver's boot-retry pulses it when the 286 fails to POST). Edge- + // triggered so the per-poll mode-byte write does not reset every time. + if (xb->configured && (v & PORTA_DIN) && !(old & PORTA_DIN)) { + xb->in_reset = 1; + resetx86(); // 286 restarts at FFFF0 + } +} + +// Fabricate a synthetic CRTC register-12 (display-start-address-high) write so the +// 68k driver runs a FULL-screen change-detection re-render of the CGA text page, +// flushing it to the Amiga screen even when the 286 issues no real video I/O (DOS +// prompt idle, BIOS "press any key" boot wait). +// +// Why not OUT 0xBA: that handler (0x694C->0x5AAE) only re-renders ONE row (the +// bottom row, the post-scroll case) -- it never touches an arbitrary row like the +// boot prompt at row 14 or a pre-scroll "A>" prompt. The full-screen render is +// 0x5B70 (25 rows, chip 0x18000 vs shadow 0x10000 -> bitplane 0x20000), reached via +// the driver's CRTC path: a write to reg 12/13 in text mode routes to 0x5174, which +// only sets the full-render request flags ($16C6 bit2 / $15B2 bit2) -- it does NOT +// apply the data to the display start -- and the main loop services the request. +// This is exactly the path DOS scrolling uses (which renders correctly). So we +// pre-stage the CRTC index in the I/O shadow (chip 0x303D4 = F000:03D4) and trap on +// the data port 0x3D5 with the current start value (a no-op write, no scroll). +// Cheap hash of the 286's live CGA text page (x86 B8000 = chip 0x18000), plus the +// CRTC start address and video mode. Used to skip the (expensive, 68k-side +// graphics.library) full-screen re-render when nothing on screen changed. +static uae_u32 atonce_text_hash(void) +{ + uae_u8 *chip = chipmem_bank.baseaddr; + if (!chip || chipmem_bank.allocated_size < 0x30400) + return 0; + const uae_u8 *p = chip + 0x18000; // live CGA text page + uae_u32 h = 2166136261u; // FNV-1a + for (int i = 0; i < 80 * 25 * 2; i++) { + h ^= p[i]; + h *= 16777619u; + } + // fold in CRTC start (hardware scroll) + mode so those also trigger a refresh + h ^= (uae_u32)chip[0x3158c] | ((uae_u32)chip[0x3158d] << 8) | + ((uae_u32)chip[0x315b5] << 16); + return h; +} + +static void atonce_fire_refresh(struct atonce_s *xb) +{ + uae_u8 *chip = chipmem_bank.baseaddr; + if (!chip || chipmem_bank.allocated_size < 0x30400) + return; + chip[0x303d4] = 0x0c; // CRTC index = reg 12 (start address high) + chip[0x303d5] = chip[0x3158c]; // data = current start high -> no actual scroll + xb->refresh_due = 0; + xb->fake_active = 1; + xb->trap_port = 0x3d5; + xb->trap_dir = 0; // OUT + xb->trap_pending = 1; + xb->frozen = 1; +} + +// A synthetic refresh trap must not be injected from a resume that is part of a +// multi-step Vortex service or a disk/CRTC sequence: those handlers depend on the +// 286 PUSHA frame (which we transiently redirect) and on running the 286 promptly, +// so interposing a render there can derail a boot. Only inject after a "plain" +// device trap (keyboard/PIC/timer/RTC idle polls), where a resume cleanly continues. +static int atonce_refresh_safe_port(uae_u16 p) +{ + if (p >= 0x0b0 && p <= 0x0bf) return 0; // Vortex private services (disk/video) + if (p >= 0x3f0 && p <= 0x3ff) return 0; // floppy controller -> trackdisk + if (p >= 0x1f0 && p <= 0x1f7) return 0; // AT hard disk + if (p == 0x3d4 || p == 0x3d5) return 0; // CGA CRTC + if (p == 0x3b4 || p == 0x3b5) return 0; // MDA CRTC + return 1; +} + +static uae_u16 atonce_portb_read(struct atonce_s *xb) +{ + // stop the 286 + latch trap info. + xb->frozen = 1; + xb->stopped = 1; + // readback format: driver does AND #$FF03 + ROR #8 to recover the port + return ((xb->trap_port & 0xff) << 8) | ((xb->trap_port >> 8) & 3); +} + +static void atonce_portb_write(struct atonce_s *xb, uae_u16 v) +{ + if (xb->setup) { + // MEMMODE shift: data = 68k D12, LSB first, into AH->BH->CH->DH + xb->mm_d = xb->mm_c; + xb->mm_c = xb->mm_b; + xb->mm_b = xb->mm_a; + xb->mm_a = (v >> 12) & 1; + atonce_rebuild_map(xb); + // first shift releases the 286 reset line (LCA DA flip-flop) + xb->in_reset = 0; + return; + } + // resume the 286; bit0 = also deliver INT, vector in the high byte + // was_fake marks a resume that consumes a synthetic video-refresh trap, so we + // don't immediately re-fire another one on its own resume. + int was_fake = xb->fake_active; + xb->fake_active = 0; + xb->frozen = 0; + xb->stopped = 0; + xb->trap_pending = 0; + xb->in_reset = 0; + // The first resume after config/MEMMODE means "let the 286 run". There is + // no real bitstream to load here (we ARE the LCA logic), so this is where + // the board becomes live and the 286 starts at its reset vector (FFFF0). + if (!xb->configured) { + xb->configured = 1; + atonce_rebuild_map(xb); + resetx86(); + // resetx86() resets PCem's _mem_state (back to INTERNAL), which drops our + // EXTERNAL chip-RAM windows from _mem_exec -> the 286 can't fetch the BIOS + // (Bad getpccache at FFFF0). Re-assert the state so recalc reinstalls the + // still-listed mappings. (Don't re-add the mappings: that corrupts the list.) + mem_set_mem_state(0x80000, 0x80000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + mem_set_mem_state(0x100000, 0x1000000 - 0x100000, + MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + } + if (v & 1) { + atonce_intr_vector = (v >> 8) & 0xff; + pic_intpending = 1; + } + // If a video refresh is owed (set once per frame by atonce_hsync), inject it + // here instead of running the 286: a resume is a safe point (the trap just + // cleared, no real trap in flight). The driver picks up the synthetic 0xBA on + // its next PORT_A poll, renders, then resumes again (refresh no longer due) and + // the 286 runs. This covers busy 286 I/O-poll loops (e.g. the BIOS "press any + // key" boot wait) where atonce_hsync never catches an idle moment. Gate it to + // plain device traps (atonce_refresh_safe_port): never interpose a render in a + // disk/video/service sequence, which derails the boot. Skip on a fake's own + // resume (was_fake) so two renders don't chain. + if (xb->refresh_due && !was_fake && atonce_refresh_safe_port(xb->trap_port)) { + atonce_fire_refresh(xb); + return; + } + // Trap-driven execution: run the 286 until its next I/O instruction traps + // (atonce_trap forces the slice to end) or the cycle budget is spent. + atonce_run(xb, ATONCE_RUN_CYCLES); +} + +// returns 1 if the access was claimed (magic port hit) +int atonce_cia_access(uaecptr addr, uae_u32 *value, int size, int iswrite) +{ + struct atonce_s *xb = atonce; + if (!xb) + return 0; + if ((addr & 0xff0000) != 0xbf0000) + return 0; + int hit_a = (addr & 0x80c0) == 0x8040; + int hit_b = (addr & 0x80c0) == 0x8080; + if (!hit_a && !hit_b) + return 0; + if (hit_a) { + if (iswrite) + atonce_porta_write(xb, (uae_u8)*value); + else + *value = atonce_porta_read(xb); + } else { + if (iswrite) + atonce_portb_write(xb, size == 2 ? (uae_u16)*value : (uae_u8)*value); + else + *value = atonce_portb_read(xb); + } + return 1; +} + +/*-------------------------------------------------------------------------*/ +/* Amiga side: 68k window 0x900000-0x9FFFFF exposes the onboard DRAM */ +/* (LCA comparator AJ cuts the motherboard strobes via U43/U42). */ +/*-------------------------------------------------------------------------*/ + +// The 0x900000 DRAM window stays mapped (map_banks) even while atonce is NULL: +// before the first atonce_init and after every atonce_free (the board re-inits +// once per Amiga reset). Kickstart/expansion scans and stray 68k reads can hit +// it in that gap, so every handler must tolerate atonce == NULL / dram == NULL. +// While the board is unconfigured (DONE not asserted) the 286 DRAM window must +// stay inert and let any Z2 fast RAM underneath show through, so the OS can use +// that RAM during boot (it shadows only once the driver brings the board up). +// fastmem_bank[0] is looked up live (not captured at init, where it isn't mapped +// yet) so the range test reflects the actual configured fast RAM. +static addrbank *atonce_dram_under(uaecptr addr) +{ + addrbank *fb = &fastmem_bank[0]; + if (fb->baseaddr && addr >= fb->start && + addr < fb->start + fb->allocated_size) + return fb; + return NULL; +} + +static uae_u32 REGPARAM2 atonce_dram_lget(uaecptr addr) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return 0xffffffff; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) return u->lget(addr); + } + addr &= ONBOARD_SIZE - 1; + return (xb->dram[addr] << 24) | (xb->dram[addr + 1] << 16) | + (xb->dram[addr + 2] << 8) | xb->dram[addr + 3]; +} +static uae_u32 REGPARAM2 atonce_dram_wget(uaecptr addr) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return 0xffff; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) return u->wget(addr); + } + addr &= ONBOARD_SIZE - 1; + return (xb->dram[addr] << 8) | xb->dram[addr + 1]; +} +static uae_u32 REGPARAM2 atonce_dram_bget(uaecptr addr) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return 0xff; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) return u->bget(addr); + } + return xb->dram[addr & (ONBOARD_SIZE - 1)]; +} +static void REGPARAM2 atonce_dram_lput(uaecptr addr, uae_u32 l) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) { u->lput(addr, l); return; } + } + addr &= ONBOARD_SIZE - 1; + xb->dram[addr] = l >> 24; + xb->dram[addr + 1] = l >> 16; + xb->dram[addr + 2] = l >> 8; + xb->dram[addr + 3] = (uae_u8)l; +} +static void REGPARAM2 atonce_dram_wput(uaecptr addr, uae_u32 w) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) { u->wput(addr, w); return; } + } + addr &= ONBOARD_SIZE - 1; + xb->dram[addr] = w >> 8; + xb->dram[addr + 1] = (uae_u8)w; +} +static void REGPARAM2 atonce_dram_bput(uaecptr addr, uae_u32 b) +{ + struct atonce_s *xb = atonce; + if (!xb || !xb->dram) + return; + if (!xb->configured) { + addrbank *u = atonce_dram_under(addr); + if (u) { u->bput(addr, b); return; } + } + xb->dram[addr & (ONBOARD_SIZE - 1)] = (uae_u8)b; +} + +static addrbank atonce_dram_bank = { + atonce_dram_lget, atonce_dram_wget, atonce_dram_bget, + atonce_dram_lput, atonce_dram_wput, atonce_dram_bput, + default_xlate, default_check, NULL, NULL, _T("ATonce Plus DRAM"), + atonce_dram_lget, atonce_dram_wget, + ABFLAG_RAM | ABFLAG_SAFE, 0, 0 +}; + +/*-------------------------------------------------------------------------*/ +/* Execution scheduling: the 286 advances mainly inside a PORT_B resume */ +/* (synchronous, low latency). This hsync slice is the continuation path: */ +/* it keeps a still-running 286 going across hsyncs when a resume slice did */ +/* not reach an I/O trap (long computation). It stays off while frozen, so */ +/* it never double-runs against the resume slice. */ +/*-------------------------------------------------------------------------*/ + +static void atonce_hsync(void) +{ + static float totalcycles; + struct atonce_s *xb = atonce; + if (!xb || !xb->configured || xb->in_reset) + return; + + // Idle video refresh: the 68k driver only re-renders the Amiga screen on real + // 286 video activity (a CRTC scroll, or the bottom-row OUT 0xBA). At the DOS + // prompt the 286 sits idle, or text lands on a row that never triggers a render, + // so the boot prompt / pre-scroll "A>" / typed chars stay invisible. So we owe a + // synthetic full-screen refresh (atonce_fire_refresh fakes a CRTC reg-12 write), + // serviced at the next safe point. + // + // PERFORMANCE: that re-render runs on the 68k via graphics.library and is heavy + // (25 rows, char->bitplane). Forcing it every frame (50Hz) saturated the 68k -> + // the prompt felt sluggish, keys took ~1s to appear and arrived in bursts (the + // 68k was too busy rendering to promptly inject the keyboard IRQ / service the + // 286's key traps / show the new text). Fix: only refresh when the 286's text + // page ACTUALLY changed (cheap FNV hash once per frame), plus a slow ~2Hz + // heartbeat so the blinking cursor still shows. Idle = no render -> 68k free. + if (++xb->refresh_div >= maxvpos) { + xb->refresh_div = 0; + uae_u32 h = atonce_text_hash(); + xb->refresh_heartbeat++; + int changed = (h != xb->text_hash); + if (changed || xb->refresh_heartbeat >= 25) { + xb->text_hash = h; + xb->refresh_heartbeat = 0; + xb->refresh_due = 1; + } + } + // If the 286 happens to be idle right now, fire it immediately (low latency). + // Otherwise atonce_portb_write services refresh_due at the next resume, which + // keeps firing even through a busy poll loop. atonce_fire_refresh fakes a CRTC + // reg-12 write -> driver full-screen re-render; see its comment. + if (xb->refresh_due && !xb->frozen && !xb->trap_pending) { + atonce_fire_refresh(xb); + return; // driver will render + resume the 286 + } + + if (xb->frozen) + return; + + float cycles_to_run = (float)cpu_get_speed() / (vblank_hz * maxvpos); + totalcycles += cycles_to_run; + int cycs = (int)totalcycles; + if (cycs > 0) + atonce_run(xb, cycs); // same timer_target priming as the resume path + totalcycles -= (int)totalcycles; +} + +static void atonce_reset(int hardreset) +{ + struct atonce_s *xb = atonce; + if (!xb) + return; + xb->configured = 0; + xb->frozen = 0; + xb->stopped = 0; + xb->trap_pending = 0; + xb->in_reset = 1; + xb->a20en = 0; + xb->setup = 0; + xb->mm_a = xb->mm_b = xb->mm_c = xb->mm_d = 0; + atonce_intr_vector = -1; + atonce_rebuild_map(xb); + atonce_apply_a20(xb); +} + +static void atonce_free(void) +{ + if (atonce) { + pcem_close(); + xfree(atonce->vmem); + xfree(atonce->emem); + xfree(atonce); + atonce = NULL; + } + // Re-resolve the PC C: drive next boot (the hardfile handle is owned by the + // filesystem layer; the user may have changed WinUAE's HDD settings). + atonce_hfd = NULL; + atonce_hdd_tried = 0; +} + +/*-------------------------------------------------------------------------*/ +/* Device init (expansion system, non-autoconfig board). */ +/*-------------------------------------------------------------------------*/ + +bool atonce_init(struct autoconfig_info *aci) +{ + aci->zorro = 0; + aci->addrbank = &expamem_null; + if (!aci->doinit) + return true; + + struct atonce_s *xb = xcalloc(struct atonce_s, 1); + atonce = xb; + xb->rc = aci->rc; + + atonce_hdd_tried = (xb->rc->device_settings & 1) ? 0 : -1; + + // PCem setup: bare 286, no PC support devices (the LCA has none; the + // PIT/PIC/DMA/KBC/RTC are all emulated by the 68k driver through traps) + romset = ROM_AMI286; // model index only; no ROM is loaded + model = 1; // [286] entry of models[] (model_init is empty) + cpu_manufacturer = 0; + cpu = 4; // cpus_286[4] = "286/16" (16 MHz); the board is 286-16 + fpu_type = FPU_NONE; // TODO: optional 287 (FPU_287) + err287 path + AT = 1; + cpu_set(); + + // allocate the PCem memory-map table (pcemmap) before any mem_mapping_add + initpcemvideo(NULL, false); + + mem_size = ONBOARD_SIZE / 1024; + mem_init(); + mem_alloc(); + xb->dram = ram; // PCem-owned 512KB = the board's local DRAM +#if ATONCE_ISOLATE_CHIP + if (!xb->vmem) + xb->vmem = xcalloc(uae_u8, ATONCE_VMEM_SIZE); +#endif + + // Minimal PCem core bring-up. We deliberately do NOT init the PC support + // chips (pit/dma/keyboard/nvr): the LCA has none, the 68k driver emulates + // them via the I/O traps. pic is still init'd because the CPU core calls + // picinterrupt() (patched in pcem/pic.cpp to deliver atonce_intr_vector). + timer_reset(); + pic_init(); + pic_reset(); + codegen_init(); // CPU recompiler tables + // Turbo ON: cpu_get_speed() (which paces atonce_run via atonce_hsync) returns + // cpu_turbo_speed = cpu_s->rspeed = the full 16 MHz. With turbo OFF, PCem caps + // cpu_nonturbo_speed at 8 MHz (cpu.cpp ~229) for any 286 -> the emulated 286 + // ran at half clock (the DOS prompt felt 2-3x slower than real hardware). + cpu_set_turbo(1); + + // catch-all I/O traps, except 287 range 0xF8-0xFF + io_sethandlerx(0x0000, 0x00f8, + atonce_io_inb, atonce_io_inw, atonce_io_inl, + atonce_io_outb, atonce_io_outw, atonce_io_outl, xb); + io_sethandlerx(0x0100, 0xff00, + atonce_io_inb, atonce_io_inw, atonce_io_inl, + atonce_io_outb, atonce_io_outw, atonce_io_outl, xb); + + // Sub-1MB chip-RAM windows (static, with exec pointers so the 286 can run + // the BIOS from F000). Must come after mem_init (chip RAM is already + // allocated by the WinUAE memory system at this point). + atonce_map_windows(); + + // Extended memory above 1MB. +#if ATONCE_ISOLATE_EXT + // DIAGNOSTIC: back the whole >1MB window with a private, flat buffer mapped + // with a REAL exec pointer, so PCem's code fetch (getpccache) works when + // himem.sys relocates/runs in the HMA (>1MB). Fully isolates the 286's + // extended memory from Amiga RAM (no shared fast RAM, no MEMMODE translation). + // The atonce_chip_* handlers are generic linear-buffer handlers: + // exec ptr = emem -> getpccache fetches emem[addr - 0x100000] + // priv = emem - 0x100000 -> handlers index priv[addr] = emem[addr-0x100000] + if (!xb->emem) + xb->emem = xcalloc(uae_u8, ATONCE_EMEM_SIZE); + mem_mapping_add(&atonce_mapping, 0x100000, ATONCE_EMEM_SIZE, + atonce_chip_readb, atonce_chip_readw, atonce_chip_readl, + atonce_chip_writeb, atonce_chip_writew, atonce_chip_writel, + xb->emem, MEM_MAPPING_EXTERNAL, xb->emem - 0x100000); + mem_set_mem_state(0x100000, ATONCE_EMEM_SIZE, + MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); + write_log(_T("ATonce: extended memory ISOLATED, private %uMB flat buffer at x86 0x100000 (exec=%p)\n"), + ATONCE_EMEM_SIZE / (1024 * 1024), xb->emem); +#else + // MEMMODE-placed, dynamic translation onto Amiga RAM, function handlers (no + // exec ptr - code fetch from >1MB needs the private buffer above). + mem_mapping_add(&atonce_mapping, 0x100000, 0x1000000 - 0x100000, + atonce_mem_readb, atonce_mem_readw, atonce_mem_readl, + atonce_mem_writeb, atonce_mem_writew, atonce_mem_writel, + NULL, MEM_MAPPING_EXTERNAL, xb); + mem_set_mem_state(0x100000, 0x1000000 - 0x100000, + MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL); +#endif + + atonce_reset(1); + + // 68k window to the onboard DRAM at 0x900000-0x9FFFFF. The handlers delegate to + // the Z2 fast RAM underneath (fastmem_bank[0], looked up live - see + // atonce_dram_under) while the board is unconfigured, so the OS can use that fast + // RAM during boot; the window shadows only once the driver brings the board up. + // This mirrors real HW (board inert from power-on). Without it, the OS allocates + // at 0x900000 during early boot (e.g. an attached hardfile's RDB filesystem load) + // and the accesses hit the 286 DRAM -> corruption -> crash / "no system disk". + map_banks(&atonce_dram_bank, 0x900000 >> 16, 0x100000 >> 16, ONBOARD_SIZE); + + device_add_hsync(atonce_hsync); + device_add_reset(atonce_reset); + device_add_exit(atonce_free, NULL); + + write_log(_T("ATonce Plus initialized\n")); + return true; +} + +bool atonce_available(void) +{ + return atonce != NULL; +} + +#endif /* WITH_X86 */ diff --git a/cfgfile.cpp b/cfgfile.cpp index d676dc65..d5c5f4a8 100644 --- a/cfgfile.cpp +++ b/cfgfile.cpp @@ -6813,7 +6813,7 @@ void cfgfile_compatibility_romtype(struct uae_prefs *p) ROMTYPE_ARIADNE2, ROMTYPE_XSURF, ROMTYPE_XSURF100Z2, ROMTYPE_XSURF100Z3, ROMTYPE_HYDRA, ROMTYPE_LANROVER, 0 }; - static const int restricted_x86[] = { ROMTYPE_A1060, ROMTYPE_A2088, ROMTYPE_A2088T, ROMTYPE_A2286, ROMTYPE_A2386, 0 }; + static const int restricted_x86[] = { ROMTYPE_A1060, ROMTYPE_A2088, ROMTYPE_A2088T, ROMTYPE_A2286, ROMTYPE_A2386, ROMTYPE_ATONCE, 0 }; static const int restricted_pci[] = { ROMTYPE_GREX, ROMTYPE_MEDIATOR, ROMTYPE_PROMETHEUS, ROMTYPE_PROMETHEUSFS, 0 }; romtype_restricted(p, restricted_net); romtype_restricted(p, restricted_x86); diff --git a/cia.cpp b/cia.cpp index 251fa9d1..3e5739ec 100644 --- a/cia.cpp +++ b/cia.cpp @@ -19,6 +19,9 @@ #include "custom.h" #include "newcpu.h" #include "cia.h" +#ifdef WITH_X86 +#include "atonce.h" +#endif #ifdef SERIAL_PORT #include "serial.h" #endif @@ -2649,6 +2652,16 @@ static uae_u32 REGPARAM2 cia_bget(uaecptr addr) uae_u8 v = 0; uae_u32 flags = 0; +#ifdef WITH_X86 + // ATonce sits in the 68000 socket: it decodes before Gary + { + uae_u32 av; + if (atonce_cia_access(addr, &av, 1, 0)) { + return (uae_u8)av; + } + } +#endif + if (isgarynocia(addr)) return dummy_get(addr, 1, false, 0); @@ -2713,6 +2726,15 @@ static uae_u32 REGPARAM2 cia_wget(uaecptr addr) uae_u16 v = 0; uae_u32 flags = 0; +#ifdef WITH_X86 + { + uae_u32 av; + if (atonce_cia_access(addr, &av, 2, 0)) { + return (uae_u16)av; + } + } +#endif + if (isgarynocia(addr)) return dummy_get(addr, 2, false, 0); @@ -2812,6 +2834,14 @@ static void REGPARAM2 cia_bput(uaecptr addr, uae_u32 value) if (cia_debug(addr, value, sz_byte)) return; +#ifdef WITH_X86 + { + uae_u32 av = value; + if (atonce_cia_access(addr, &av, 1, 1)) + return; + } +#endif + if (isgarynocia(addr)) { dummy_put(addr, 1, false); return; @@ -2855,6 +2885,14 @@ static void REGPARAM2 cia_wput(uaecptr addr, uae_u32 v) if (cia_debug(addr, v, sz_word)) return; +#ifdef WITH_X86 + { + uae_u32 av = v; + if (atonce_cia_access(addr, &av, 2, 1)) + return; + } +#endif + if (isgarynocia(addr)) { dummy_put(addr, 2, false); return; diff --git a/expansion.cpp b/expansion.cpp index 57c0ae55..5ee4d4c9 100644 --- a/expansion.cpp +++ b/expansion.cpp @@ -60,6 +60,7 @@ #endif #ifdef WITH_X86 #include "x86.h" +#include "atonce.h" #endif #include "filesys.h" #include "ethernet.h" @@ -4563,6 +4564,23 @@ static const struct expansionboardsettings x86vga_settings[] = { } }; +static const struct expansionboardsettings atonce_bridge_settings[] = { + { + // 0 + _T("Automount first MBR hardfile"), + _T("automounthdf"), + }, + { + // 19 (not yet implemented) + _T("FPU"), + _T("fpu"), + false, false, 19 + }, + { + NULL + } +}; + static const struct expansionboardsettings x86at286_bridge_settings[] = { { @@ -6180,6 +6198,17 @@ const struct expansionromtype expansionroms[] = { 0, 0, 0, false, NULL, false, 0, x86at386_bridge_settings }, + { + // Vortex ATonce Plus: 286 board in the A500 68000 socket. Not a Zorro + // bridgeboard: no autoconfig, no BIOS ROM file (the x86 BIOS is loaded + // from the floppy .dsg into Amiga chip RAM by the 68k driver). + _T("atonceplus"), _T("ATonce Plus"), _T("Vortex"), + NULL, atonce_init, NULL, NULL, ROMTYPE_ATONCE | ROMTYPE_NONE, 0, 0, BOARD_NONAUTOCONFIG_BEFORE, true, + NULL, 0, + false, EXPANSIONTYPE_X86_BRIDGE, + 0, 0, 0, false, NULL, + false, 0, atonce_bridge_settings + }, #endif // only here for rom selection and settings diff --git a/include/atonce.h b/include/atonce.h new file mode 100644 index 00000000..f1d2b5d0 --- /dev/null +++ b/include/atonce.h @@ -0,0 +1,17 @@ +/* +* UAE - The Un*x Amiga Emulator +* +* Vortex ATonce Plus emulation (286 board in the A500 68000 socket) +*/ + +#ifndef UAE_ATONCE_H +#define UAE_ATONCE_H + +bool atonce_init(struct autoconfig_info *aci); +bool atonce_available(void); +// hook called from cia.cpp; returns 1 if the access hit a magic port +int atonce_cia_access(uaecptr addr, uae_u32 *value, int size, int iswrite); + +extern int atonce_intr_vector; + +#endif /* UAE_ATONCE_H */ diff --git a/include/rommgr.h b/include/rommgr.h index 57995f30..659333c0 100644 --- a/include/rommgr.h +++ b/include/rommgr.h @@ -220,6 +220,7 @@ extern int decode_cloanto_rom_do(uae_u8 *mem, int size, int real_size); #define ROMTYPE_RIPPLE 0x00100095 #define ROMTYPE_512KWOM 0x00100096 #define ROMTYPE_AIDE 0x00100097 +#define ROMTYPE_ATONCE 0x00100098 #define ROMTYPE_NOT 0x00800000 #define ROMTYPE_QUAD 0x01000000 diff --git a/od-win32/winuae_msvc15/winuae_msvc.vcxproj b/od-win32/winuae_msvc15/winuae_msvc.vcxproj index 0246bf68..cdbbb396 100644 --- a/od-win32/winuae_msvc15/winuae_msvc.vcxproj +++ b/od-win32/winuae_msvc15/winuae_msvc.vcxproj @@ -1164,6 +1164,7 @@ + diff --git a/pcem/pic.cpp b/pcem/pic.cpp index 97db7d6e..501d2734 100644 --- a/pcem/pic.cpp +++ b/pcem/pic.cpp @@ -416,6 +416,16 @@ void picintc(uint16_t num) uint8_t picinterrupt() { +#ifdef UAE + // ATonce: the LCA supplies INTR vectors directly (no 8259 on board) + extern int atonce_intr_vector; + if (atonce_intr_vector >= 0) { + uint8_t v = (uint8_t)atonce_intr_vector; + atonce_intr_vector = -1; + pic_intpending = 0; + return v; + } +#endif uint8_t temp=pic.pend&~pic.mask; int c; for (c = 0; c < 8; c++) diff --git a/scsi.cpp b/scsi.cpp index 25d8d173..72754b1e 100644 --- a/scsi.cpp +++ b/scsi.cpp @@ -5719,7 +5719,7 @@ uae_u8 x86_rt1000_bget(int portnum) extern void x86_rt1000_bios(struct zfile*, struct romconfig *rc); bool x86_rt1000_init(struct autoconfig_info *aci) { - static const int parent[] = { ROMTYPE_A1060, ROMTYPE_A2088, ROMTYPE_A2088T, ROMTYPE_A2286, ROMTYPE_A2386, 0 }; + static const int parent[] = { ROMTYPE_A1060, ROMTYPE_A2088, ROMTYPE_A2088T, ROMTYPE_A2286, ROMTYPE_A2386, ROMTYPE_ATONCE, 0 }; aci->parent_romtype = parent; scsi_add_reset(); if (!aci->doinit) diff --git a/x86.cpp b/x86.cpp index 4156808a..68574fc7 100644 --- a/x86.cpp +++ b/x86.cpp @@ -1962,9 +1962,17 @@ static uae_u8 vlsi_in(struct x86_bridge *xb, int portnum) return v; } +// Defined in atonce.cpp: routes x86 I/O to the ATonce 286 trap mechanism when +// no PC bridgeboard (A2088/A2286/...) owns the x86 side, i.e. bridges[0]==NULL. +extern uint32_t atonce_x86_io(uint16_t port, int dir, uint32_t val, int size); + void portout(uint16_t portnum, uint8_t v) { struct x86_bridge *xb = bridges[0]; + if (!xb) { + atonce_x86_io(portnum, 0, v, 1); + return; + } uae_u8 *io = xb->io_ports; int aio = -1; uae_u8 enables = xb->amiga_io[IO_MODE_REGISTER]; @@ -2319,6 +2327,10 @@ end: void portout16(uint16_t portnum, uint16_t value) { struct x86_bridge *xb = bridges[0]; + if (!xb) { + atonce_x86_io(portnum, 0, value, 2); + return; + } if (portnum >= MAX_IO_PORT) return; @@ -2348,6 +2360,10 @@ void portout16(uint16_t portnum, uint16_t value) void portout32(uint16_t portnum, uint32_t value) { + if (!bridges[0]) { + atonce_x86_io(portnum, 0, value, 4); + return; + } if (portnum >= MAX_IO_PORT) return; @@ -2368,6 +2384,9 @@ void portout32(uint16_t portnum, uint32_t value) uint8_t portin(uint16_t portnum) { struct x86_bridge *xb = bridges[0]; + if (!xb) { + return (uint8_t)atonce_x86_io(portnum, 1, 0, 1); + } int aio = -1; uae_u8 enables = xb->amiga_io[IO_MODE_REGISTER]; bool mda_emu = (enables & 8) != 0; @@ -2718,6 +2737,9 @@ end: uint16_t portin16(uint16_t portnum) { struct x86_bridge *xb = bridges[0]; + if (!xb) { + return (uint16_t)atonce_x86_io(portnum, 1, 0, 2); + } uae_u16 v = 0; if (portnum >= MAX_IO_PORT) @@ -2749,6 +2771,9 @@ uint32_t portin32(uint16_t portnum) { uint32_t v = 0; + if (!bridges[0]) { + return atonce_x86_io(portnum, 1, 0, 4); + } if (portnum >= MAX_IO_PORT) return v; @@ -3490,7 +3515,8 @@ int is_x86_cpu(struct uae_prefs *p) is_device_rom(p, ROMTYPE_A2088, 0) < 0 && is_device_rom(p, ROMTYPE_A2088T, 0) < 0 && is_device_rom(p, ROMTYPE_A2286, 0) < 0 && - is_device_rom(p, ROMTYPE_A2386, 0) < 0) { + is_device_rom(p, ROMTYPE_A2386, 0) < 0 && + is_device_rom(p, ROMTYPE_ATONCE, 0) < 0) { if (p == &currprefs) x86_found = -1; return X86_STATE_INACTIVE; -- 2.47.3