]> git.unchartedbackwaters.co.uk Git - francis/winuae.git/commitdiff
Vortex ATonce plus merge
authorToni Wilen <twilen@winuae.net>
Fri, 19 Jun 2026 14:08:37 +0000 (17:08 +0300)
committerToni Wilen <twilen@winuae.net>
Fri, 19 Jun 2026 14:08:37 +0000 (17:08 +0300)
atonce.cpp [new file with mode: 0644]
cfgfile.cpp
cia.cpp
expansion.cpp
include/atonce.h [new file with mode: 0644]
include/rommgr.h
od-win32/winuae_msvc15/winuae_msvc.vcxproj
pcem/pic.cpp
scsi.cpp
x86.cpp

diff --git a/atonce.cpp b/atonce.cpp
new file mode 100644 (file)
index 0000000..78d1c44
--- /dev/null
@@ -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 */
index d676dc65d74f9ca26e49cf9c03636d67d7df1036..d5c5f4a80a9ad30a4330af06770a104e5d464e2a 100644 (file)
@@ -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 251fa9d1b4c3aa8be1542d49c26b9586b171de9a..3e5739ecc00cb225e50a33af9f8eb808280d21ce 100644 (file)
--- 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;
index 57c0ae558e37c3453f23d67d1dee218ea6ccdb2b..5ee4d4c941dcbb1f14544dd7771c55f0f9efae17 100644 (file)
@@ -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 (file)
index 0000000..f1d2b5d
--- /dev/null
@@ -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 */
index 57995f3061812d942497a653e7d278dd3acb8fcb..659333c0415058802c3803cbdc4e320e15200108 100644 (file)
@@ -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
index 0246bf6878d528e047d7b34f790cd89e6fdf6c6f..cdbbb39690a341f2220bf91e62bd3bc11577b28b 100644 (file)
     <ClCompile Include="..\..\uaenative.cpp" />
     <ClCompile Include="..\..\vm.cpp" />
     <ClCompile Include="..\..\x86.cpp" />
+    <ClCompile Include="..\..\atonce.cpp" />
     <ClCompile Include="..\ahidsound_dsonly.cpp" />
     <ClCompile Include="..\ahidsound_new.cpp" />
     <ClCompile Include="..\avioutput.cpp" />
index 97db7d6e290d619cc47483abd5504fd12dd3e93b..501d2734d169772cbaf27bc7cb829ebaa7e19b0e 100644 (file)
@@ -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++)
index 25d8d173fbe92b99e79f9adcd8b9380c1664cdbf..72754b1eaedc3b7ce6ec10456183bba930c4bbcc 100644 (file)
--- 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 4156808a1031515e3d66a81af09dfc686403920a..68574fc7b92883b067d3fecc25ce585f75c8dae3 100644 (file)
--- 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;