The Z-80 callbacks read and write the BIOS second half

5 min read
z80cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 6 — The BIOS Factory.

Following up on the BIOS-runtime-generated finding: the second 1 KB of BIOS at $FEB8-$FFFF is all zeros on disk. The hypothesis was that runtime cold-boot code generates the per-device handler routines into that area.

Searched the Z-80 disk callback code (newdisk_223.bin, the 1.5 KB at Apple $0A00) for any instructions that write into $FE/$FF addresses or load HL with addresses in that range. Several hits:

$1CEF: LD HL,$FA03         ; load HL with BIOS cold-boot area
$1EAA: LD HL,$FECB         ; load HL with BIOS second half
$1EAD: LD (HL),A           ; → store A at $FECB
$1EBA: LD HL,$FED4         ; another second-half address
$1EF5: LD ($FED2),A        ; direct store to $FED2

So the callbacks DO write into $FE/$FF area — confirming there’s data flow into the BIOS second half from the runtime callback path. But these aren’t the cold-boot code generator; they’re individual stores to specific bytes. That’s the pattern of per-call state storage, not code generation.

The interpretation is: the BIOS second half serves a dual purpose. The cold-boot code generator (which we still need to find) populates the executable parts — BOOT, HOME, SELDSK, the per-device handlers at $FF64-$FFDF. The runtime disk callbacks then read and write specific bytes within that area as state — for instance, $FED2 might hold the current sector number, $FECB the current track, $FED4 the current DMA address. The BIOS second half is essentially a small per-device “control block” with code interleaved with state.

This is consistent with how a normal CP/M BIOS organizes things. CP/M’s BDOS calls SETTRK, SETSEC, SETDMA before issuing READ or WRITE — and the implementations of those routines typically just store the parameters in BIOS-local variables that READ/WRITE then read out. So LD ($FED2),A looks exactly like what SETSEC would do with the sector number passed in A.

What this confirms: the second half of the BIOS isn’t a uniform block of generated code. It’s a mix of:

  • Generated routines (the per-device handlers — populated by the cold-boot code generator)
  • Per-device state variables (current track, sector, DMA address, etc. — read/written by the BDOS-facing routines and read by the per-device handlers)

The callbacks at $1A00 are the BDOS-facing side: they accept BDOS calls, store the parameters into the BIOS state area, and signal the 6502 (via the inter-CPU sync at $1E36-$1E44) to do the actual disk I/O using the original 6502 RWTS routines preserved at $BA00-$BFFF.

So the cooperative-CPU disk-I/O model has more structure than I initially described. The Z-80 doesn’t just call back to the 6502 — it stages the parameters in BIOS state, signals the 6502, waits for completion, and reads results. The 6502 reads the parameters (from the SAME state area, which it sees at the same Apple addresses), does the disk operation, signals completion. Both CPUs share the BIOS second half as their communication channel.

Status: confirmed the BIOS second half is structured as code + state, not pure code. The runtime cold-boot generator populates the code parts; the runtime callbacks populate the state parts; the 6502 reads both during cooperative disk I/O. The cold-boot generator itself remains the next concrete trace target.