The cold-boot generator: $FB3A in 2.23, $DB6E in 2.20 — and the device-code 6 branch only 2.23 has

5 min read
z80cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 6 — The BIOS Factory.

Continuing past the static-vs-generated-handlers finding. 2.23 doesn’t have static device-handler code in BIOS; the handlers must be written into the trap-marker pages at boot. So where is the writer?

Disassembling 2.23 page 0 past the dispatch table (which ends at $FB29) lands on a routine at $FB3A:

$FB3A: 11 07 00     LD DE,0007        ; counter E = 7
$FB3D: 21 B8 F3     LD HL,F3B8         ; HL = slot-info table base
$FB40: 19           ADD HL,DE          ; HL = F3B8 + E
$FB41: 7E           LD A,(HL)          ; A = device code at slot E
$FB42: D6 03        SUB 03             ; if A == 3...
$FB44: 20 07        JR NZ,FB4D
$FB46: CD 81 FE     CALL FE81           ; ...init handler for device code 3
$FB49: 36 03        LD (HL),03          ; (and rewrite slot byte)
$FB4B: 36 15        LD (HL),15
$FB4D: 3D           DEC A               ; if A == 4...
$FB4E: 20 0B        JR NZ,FB5B
$FB50: CD 83 FD     CALL FD83           ; ...init handler for device code 4 (Pascal 1.0)
$FB53: 21 00 C8     LD HL,C800
$FB56: CD 45 FB     CALL FB45           ; helper that loads expansion ROM area
$FB59: 18 0A        JR FB65
$FB5B: FE 02        CP 02               ; if A == 6 (i.e., subtract-3-then-dec gave 2)...
$FB5D: 20 06        JR NZ,FB65
$FB5F: 21 D0 0D     LD HL,0DD0          ; ...init handler for device code 6 (Pascal 1.1)
$FB62: CD B0 FD     CALL FDB0           ; ← THE PASCAL 1.1 BRANCH
$FB65: 1D           DEC E               ; next slot
$FB66: 20 D5        JR NZ,FB3D
$FB68: C9           RET

This is the cold-boot generator. It iterates through slots 7→1 in the slot-info table at $F3B8+E (built by the 6502 boot loader’s slot scanner) and for each detected device code, calls a specific init routine that writes handler bytes into the BIOS trap-marker pages.

The branch decoding works through subtractive comparisons:

  • After SUB 03, A == 0 means the original was 3.
  • After the subsequent DEC A (which always runs), A == 0 means original was 4.
  • Falling further to CP 02 compares A (= original − 4) against 2; equality means original was 6.

So 2.23 dispatches on device codes 3, 4, and 6.

2.20 has the same scan loop, in the same structural shape, at $DB6E:

$DB6E: 11 07 00     LD DE,0007        ; counter E = 7
$DB71: 21 B8 F3     LD HL,F3B8         ; HL = slot-info table base
$DB74: 19           ADD HL,DE
$DB75: 7E           LD A,(HL)
$DB76: D6 03        SUB 03             ; if A == 3...
$DB78: 20 07        JR NZ,DB81
$DB7A: CD 60 DD     CALL DD60           ; ...init handler for device 3
$DB7D: 36 03        LD (HL),03
$DB7F: 36 15        LD (HL),15
$DB81: 3D           DEC A               ; if A == 4...
$DB82: 20 09        JR NZ,DB8D
$DB84: CD EE DC     CALL DCEE           ; ...init handler for device 4 (Pascal 1.0)
$DB87: 21 00 C8     LD HL,C800
$DB8A: CD 3B DB     CALL DB3B           ; helper
$DB8D: 1D           DEC E
$DB8E: 20 E1        JR NZ,DB71
$DB90: C9           RET

Same prologue, same loop, same handlers for codes 3 and 4 — and no branch for device code 6. After the device-4 path’s JR $DB8D (or its fall-through), the loop just decrements E and continues. There’s no Pascal 1.1 case.

So the version difference is 20 bytes of additional handler-dispatch code in 2.23’s generator (the FE 02 / 20 06 / 21 D0 0D / CD B0 FD / JR FB65 sequence between the device-4 path and the loop’s bottom).

Combined with the 11-byte slot-scanner delta on the 6502 side, the entire Videx fix in 2.23 is roughly 31 bytes of code, in two coordinated changes:

  1. 6502 side (boot loader): 11-byte branch that detects Pascal 1.1 by reading $Cn0B and tags the slot with device code $06 instead of $04.
  2. Z-80 side (BIOS cold-boot generator): ~20-byte branch that dispatches device code $06 to a Pascal 1.1 init routine at $FDB0 (with handler-code parameters at $0DD0).

That’s the entire story. Microsoft’s CP/M 2.23 release fixed Videx (and any other Pascal 1.1 card) by adding detection of a third firmware-protocol byte plus a corresponding handler-init path in the BIOS factory.

The Pascal 1.1 init at $FDB0 is the next thing to disassemble — that routine writes the actual Pascal-1.1-aware handler code into the trap-marker pages, where the BIOS’s static device-scan dispatch jumps to it. Reading $FDB0 will tell us what code 2.23 generates for a Pascal 1.1 card. That’ll close the loop on the original “what does 2.23 actually do for a Videx?” question.

Status: generator located in both versions; per-device-code dispatch identified; the missing Pascal 1.1 branch in 2.20 confirmed as the BIOS-side half of the fix. Combined with the 6502-side detection delta, the full Videx-recognition path in 2.23 is now visible end-to-end. Next: disassemble $FDB0 (the Pascal 1.1 handler init in 2.23) and write Part 5 of the article series, “The BIOS Factory.”