-
Notifications
You must be signed in to change notification settings - Fork 121
Expand file tree
/
Copy pathatonce.cpp
More file actions
1296 lines (1205 loc) · 52.4 KB
/
Copy pathatonce.cpp
File metadata and controls
1296 lines (1205 loc) · 52.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* 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)
{