2.20's BIOS dispatch table has 6 entries vs 2.23's 4

5 min read
z80cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 9 — Every Difference: A Complete Inventory.

Per-device dispatch is the pattern by which the BIOS routes I/O calls to the right per-slot handler. Earlier I found 2.23’s dispatch table at $FAEB-$FB29 — 4 entries of 16 bytes each, totaling 64 bytes. Today I located the analog in 2.20’s BIOS.

2.20 dispatch table at $DAFF-$DB5E (96 bytes):

$DAFF: 00 00 00 00 00 00 00 00  BA DE 93 DA 9A DF 3A DF  ← entry 0
$DB0F: 00 00 00 00 00 00 00 00  BA DE 93 DA A6 DF 4A DF  ← entry 1
$DB1F: 00 00 00 00 00 00 00 00  BA DE 93 DA B2 DF 5A DF  ← entry 2
$DB2F: 00 00 00 00 00 00 00 00  BA DE 93 DA BE DF 6A DF  ← entry 3
$DB3F: 00 00 00 00 00 00 00 00  BA DE 93 DA CA DF 7A DF  ← entry 4
$DB4F: 00 00 00 00 00 00 00 00  BA DE 93 DA D6 DF 8A DF  ← entry 5

Each entry is 16 bytes:

  • Bytes 0-7: zero (presumably runtime-mutable state slots — counters, current values, etc.)
  • Bytes 8-9: $DEBA (a state-byte address common to all entries)
  • Bytes 10-11: $DA93 (a routine common to all entries — probably an entry-point trampoline)
  • Bytes 12-13: per-entry handler 1 address ($DF9A, $DFA6, $DFB2, $DFBE, $DFCA, $DFD6)
  • Bytes 14-15: per-entry handler 2 address ($DF3A, $DF4A, $DF5A, $DF6A, $DF7A, $DF8A)

Handler 1 stride is 12 bytes ($DF9A → $DFA6 is +12). Handler 2 stride is 16 bytes ($DF3A → $DF4A is +16). So each per-device handler set is exactly 12 + 16 = 28 bytes, and the 6 entries occupy 6 × 28 = 168 bytes of handler code starting at $DF3A.

The handler bytes: at $DF3A-$DFC9 there’s real Z-80 code for entries 0-3. But starting at $DFCA (entry 4’s handler 1 location), the bytes are:

$DFCA: 20 32 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5  ← entry 4 handler 1 (mostly $E5)
$DFD6: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5  ← entry 5 handler 1 (all $E5)

So entries 4 and 5’s handler-1 slots are filled with $E5 (CP/M deleted-file marker / PUSH HL opcode). Entries 0-3 have static code; entries 4-5 are runtime slots.

Compare to 2.23. 2.23’s dispatch table has only 4 entries (64 bytes vs 2.20’s 96 bytes). All 4 of 2.23’s dispatch targets land in trap-marker pages — meaning all of 2.23’s per-device handlers are runtime-installed, with no static fallback. 2.20 has more entries, with some static and some runtime.

What this means architecturally:

Aspect2.202.23
Dispatch table entries64
Static handlersEntries 0-3 (4 entries)None
Runtime-installed handlersEntries 4-5 (2 entries)All 4 entries
Total handler code (bytes)~168 (28 × 6, with last 2 sets E5 fill)Variable (depends on what’s installed)

So 2.20’s design is “ship the common handlers statically; provide 2 slots for runtime-installed handlers.” 2.23’s design is “make everything runtime-installable, generate per device on boot.” The 2.23 approach is more flexible but requires more boot-time work; the 2.20 approach is simpler at boot but constrained by what’s pre-baked in.

The Pascal 1.1 device (Videx) would need a runtime slot in 2.20 — but 2.20’s slot scanner doesn’t recognize Pascal 1.1 devices, so it never assigns one to those slots. Even if it did, only entries 4-5 are runtime-fillable (per the dispatch table layout), so 2.20 could have supported at most 2 unrecognized device classes — and Microsoft never added Pascal 1.1 to its detection logic.

Refines the Part 9 inventory. That article framed this as “2.20 has static handlers, 2.23 has runtime-installed.” That’s directionally right but incomplete. 2.20 has runtime-installed slots — just only 2 of them, and the slot scanner never picked Pascal 1.1 to put in one. 2.23 expanded the runtime-install architecture to all handlers, then plugged Pascal 1.1 into it.

Status: 2.20 dispatch table located and decoded. The static-vs-runtime mix in 2.20 (4+2) clarifies why the architectural shift to all-runtime in 2.23 was necessary to support the slot scanner’s new Pascal 1.1 detection.