Add VDB (VWE LBE4 video splitter) device + fault disassembly findings
The VDB is the ISA card that fans the PC's Cirrus Logic framebuffer to the six secondary cockpit displays (5 mono + 1 color), dividing the pixel clock. From the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM, L4VB16.CPP; "Adam's port decoder design" -- Adam G., VWE hardware): 0x300/0x308/0x310 three VGA-DAC-style palette groups 0x31A / 0x319 splitter high-color clock divider ON / OFF New emulated VDB device in vpxlog.cpp at I/O 0x300-0x31A (active when logging, disable with VDB=0): decodes palette write-address/data/mask/read-address and the clock strobes, recording palette contents + splitter state so the six- display encoding can be decoded later. Validated: with gauges enabled (gauge.conf, setenv arg4=g) the game issues "splitter clock ON (0x31A)" -> captured. The board is write-only (game never reads it, per driver + owner), so its absence is not the crash. Crash diagnosis (BTL4OPT.EXE CODE+0x123B): the faulting routine is a heap free() with boundary-tag coalescing; it was handed a garbage block pointer (value 2). Symptom of upstream corruption, reached only when the RIO is in sync and the sim advances. HISTORY.md gains a "Cockpit display hardware -- the VDB" section with the attribution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
[sdl]
|
||||
output=opengl
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[serial]
|
||||
serial1=disabled
|
||||
serial2=disabled
|
||||
[autoexec]
|
||||
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||
c:
|
||||
cd \REL410\BT
|
||||
set VIDEOFORMAT=svga
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
rem arg4=g enables the gauge/secondary displays -> exercises the VDB splitter
|
||||
call setenv.bat r s n g
|
||||
32rtm.exe -x
|
||||
btl4opt.exe -egg test.egg
|
||||
32rtm.exe -u
|
||||
echo ALPHA1-RUN-DONE
|
||||
pause
|
||||
@@ -716,6 +716,97 @@ static void scene_reset(void) {}
|
||||
static void vpx_render_start(void) {}
|
||||
#endif
|
||||
|
||||
/* ================= VDB: VWE LBE4 video splitter board ==================== *
|
||||
* ISA card (I/O 0x300-0x31A) that taps the PC's Cirrus Logic VGA output off
|
||||
* the feature connector and fans the single framebuffer out to the six
|
||||
* secondary cockpit displays (5 mono + 1 color), dividing the pixel clock.
|
||||
* Register map from the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM + L4VB16.CPP;
|
||||
* "Adam's port decoder design" -- Adam G., VWE hardware):
|
||||
*
|
||||
* 0x300 / 0x308 / 0x310 three palette register groups, each VGA-DAC-like:
|
||||
* +0 write-address +1 data (R,G,B triplets) +2 pixel-mask +3 read-address
|
||||
* 0x319 write => splitter high-color clock divider OFF (VWE_HC_OFF)
|
||||
* 0x31A write => splitter high-color clock divider ON (VWE_HC_ON)
|
||||
*
|
||||
* The game only WRITES to the VDB (fire-and-forget, no status/ACK -- confirmed
|
||||
* from the driver and by the hardware owner). This device records the palette
|
||||
* contents + clock-divider state so the six-display encoding can be decoded
|
||||
* later; reads return 0xFF. Active when VPXLOG is set; logs to the same file. */
|
||||
static const io_port_t VDB_BASE = 0x300;
|
||||
|
||||
struct VDBPalette { unsigned char waddr, raddr, sub, mask; unsigned char ram[768]; };
|
||||
static VDBPalette vdb_pal[3];
|
||||
static bool vdb_splitter_on = false;
|
||||
/* lazy coalescing of palette data-byte writes so a 768-byte load is one line */
|
||||
static int vdb_data_group = -1;
|
||||
static unsigned long vdb_data_count = 0;
|
||||
|
||||
static int vdb_group_of(io_port_t off) { /* group index for 0x300/0x308/0x310 */
|
||||
if (off <= 3) return 0;
|
||||
if (off >= 8 && off <= 11) return 1;
|
||||
if (off >= 16 && off <= 19) return 2;
|
||||
return -1;
|
||||
}
|
||||
static void vdb_flush_data(void) {
|
||||
if (vpx_fp && vdb_data_group >= 0 && vdb_data_count) {
|
||||
flush_run();
|
||||
fprintf(vpx_fp, "# VDB pal%d loaded %lu data bytes\n",
|
||||
vdb_data_group, vdb_data_count);
|
||||
fflush(vpx_fp);
|
||||
}
|
||||
vdb_data_group = -1; vdb_data_count = 0;
|
||||
}
|
||||
static void vdb_note(const char *msg) {
|
||||
if (vpx_fp) { vdb_flush_data(); flush_run();
|
||||
fprintf(vpx_fp, "# VDB %s\n", msg); fflush(vpx_fp); }
|
||||
}
|
||||
|
||||
static void vdb_write(Bitu port, Bitu val, Bitu /*iolen*/) {
|
||||
io_port_t off = (io_port_t)port - VDB_BASE;
|
||||
unsigned char v = (unsigned char)val;
|
||||
if (port == 0x319) { vdb_splitter_on = false; vdb_note("splitter clock OFF (0x319)"); return; }
|
||||
if (port == 0x31A) { vdb_splitter_on = true; vdb_note("splitter clock ON (0x31A)"); return; }
|
||||
int g = vdb_group_of(off);
|
||||
if (g < 0) return;
|
||||
VDBPalette &p = vdb_pal[g];
|
||||
switch (off & 3) {
|
||||
case 0: /* write-address */
|
||||
vdb_flush_data();
|
||||
p.waddr = v; p.sub = 0;
|
||||
if (vpx_fp) { flush_run();
|
||||
fprintf(vpx_fp, "# VDB pal%d write-addr=%u\n", g, v); fflush(vpx_fp); }
|
||||
break;
|
||||
case 1: /* data: R,G,B triplet with auto-increment */
|
||||
p.ram[((unsigned)p.waddr * 3 + p.sub) % 768] = v;
|
||||
if (++p.sub == 3) { p.sub = 0; p.waddr++; }
|
||||
if (vdb_data_group != g) { vdb_flush_data(); vdb_data_group = g; }
|
||||
vdb_data_count++;
|
||||
break;
|
||||
case 2: /* pixel-mask */
|
||||
p.mask = v; vdb_note("pixel-mask set");
|
||||
break;
|
||||
case 3: /* read-address */
|
||||
p.raddr = v; p.sub = 0; vdb_note("read-addr set");
|
||||
break;
|
||||
}
|
||||
}
|
||||
static Bitu vdb_read(Bitu port, Bitu /*iolen*/) {
|
||||
/* the game does not read the VDB; provide DAC-style read-back anyway. */
|
||||
io_port_t off = (io_port_t)port - VDB_BASE;
|
||||
int g = vdb_group_of(off);
|
||||
if (g >= 0 && (off & 3) == 1) {
|
||||
VDBPalette &p = vdb_pal[g];
|
||||
unsigned char v = p.ram[((unsigned)p.raddr * 3 + p.sub) % 768];
|
||||
if (++p.sub == 3) { p.sub = 0; p.raddr++; }
|
||||
return v;
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
static void vdb_reset(void) {
|
||||
memset(vdb_pal, 0, sizeof vdb_pal);
|
||||
vdb_splitter_on = false; vdb_data_group = -1; vdb_data_count = 0;
|
||||
}
|
||||
|
||||
void VPXLOG_Init(void) {
|
||||
const char *env = getenv("VPXLOG");
|
||||
if (env == NULL || env[0] == '\0') return;
|
||||
@@ -748,6 +839,18 @@ void VPXLOG_Init(void) {
|
||||
IO_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18);
|
||||
IO_RegisterWriteHandler(VPX_BASE, vpx_write, IO_MB, 18);
|
||||
|
||||
/* VDB video splitter board (0x300-0x31A). Registered whenever we are
|
||||
* logging, unless VDB=0. Records palette + splitter-clock state. */
|
||||
const char *vdbenv = getenv("VDB");
|
||||
if (!(vdbenv && vdbenv[0] == '0')) {
|
||||
vdb_reset();
|
||||
IO_RegisterReadHandler(VDB_BASE, vdb_read, IO_MB, 0x1B); /* 0x300-0x31A */
|
||||
IO_RegisterWriteHandler(VDB_BASE, vdb_write, IO_MB, 0x1B);
|
||||
if (vpx_fp) { fprintf(vpx_fp,
|
||||
"# VDB video splitter board at 0x%03X-0x31A (palettes 300/308/310, clock 319/31A)\n",
|
||||
VDB_BASE); fflush(vpx_fp); }
|
||||
}
|
||||
|
||||
if (vpx_fp) {
|
||||
fprintf(vpx_fp, "# VPX link adapter, base 0x%03X, respond=%d handshakes=%d\n",
|
||||
VPX_BASE, vpx_respond ? 1 : 0, vpx_max_handshakes);
|
||||
|
||||
Reference in New Issue
Block a user