2.20's BIOS dispatch table has 6 entries vs 2.23's 4
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:
| Aspect | 2.20 | 2.23 |
|---|---|---|
| Dispatch table entries | 6 | 4 |
| Static handlers | Entries 0-3 (4 entries) | None |
| Runtime-installed handlers | Entries 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.