2.20 also makes two LOAD_CPM calls — not unique to 2.23

5 min read
6502cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 4 — The Handoff: 6502 to Z-80.

Earlier I found that 2.23’s loader does a second JSR $BBEB (LOAD_CPM-equivalent) at $191E, after the main 29-sector read at $1416. I’d hypothesized this might be 2.23-specific — the mechanism by which it loads handler templates that 2.20 doesn’t need (since 2.20 has static handlers in BIOS).

Today I went looking for the analogous mechanism in 2.20 to verify. The result is more nuanced.

2.20’s LOAD_CPM-equivalent is at $0E10, not $BBEB. The address differs because 2.20 has a different staging layout (PREP_HANDOFF puts disk routines at different positions). Searching the 2.20 loader for JSR $0E10:

$1608: JSR $0E10
$17D0: JSR $0E10

Two calls. Both versions of the loader do two LOAD_CPM calls.

The 2.20 call at $17D0 is the “main” load (analog of 2.23’s $1416):

$17C6: LDA #$0B           ; sector start = trk0:$0B
$17C8: STA $03E1
$17CB: LDA #$1C           ; ← 28 sectors (2.23 reads 29)
$17CD: PHA
$17CE: PHP
$17CF: SEI
$17D0: JSR $0E10           ; LOAD_CPM

So 2.20 reads 28 sectors in its main load, vs 2.23’s 29. And it has a second call at $1608, wrapped in language-card bank-switching:

$1603: LDA $C083           ; bank in LC RAM (write+read)
$1606: PHP
$1607: SEI
$1608: JSR $0E10            ; LOAD_CPM
$160B: LDA $C081            ; switch back to LC ROM/RAM bank 2
$160E: PLP
$160F: RTS

The bank-switch wrap is interesting — $1608’s caller has parameters set up such that the call reads into LC RAM. That’s where the BIOS at Z-80 $FAB8 would live (Z-80 high addresses are served by Apple LC RAM under SoftCard’s hardware). So $1608’s second load may be specifically loading the BIOS bytes into LC RAM.

This changes the interpretation of 2.23’s second load. In 2.23, I had hypothesized the second JSR $BBEB at $191E was loading runtime-handler templates that 2.20 wouldn’t need. But 2.20 also does a second load — wrapped in LC bank-switching — which is consistent with both versions doing the same architectural thing: the main LOAD_CPM populates Apple staging; a second LOAD_CPM populates LC RAM with BIOS bytes.

What might still differ: what the second load actually puts in LC RAM. If 2.20’s second load is loading the static-handler-containing 2 KB BIOS, and 2.23’s second load is loading the smaller (1.35 KB) BIOS plus separately loaded handler templates, the sector counts and source positions differ. But the mechanism — bank-switch + LOAD_CPM call — is shared.

Sector counts so far:

VersionMain load ($1416/$17D0)Second load ($191E/$1608)Total
2.2028 sectors (LDA #$1C)unknown count ($1608 setup is in caller)unknown
2.2329 sectors (LDA #$1D)unknown count ($191E uses LDA #$80 as parameter — meaning unclear)unknown

Implication for the inventory in Part 9. The “static vs runtime handler” architectural shift is still real (2.20 has BIOS page 6 of static handlers, 2.23 doesn’t). But the loading mechanism may not be the place where that shift shows up — both versions load via two LOAD_CPM calls. The shift shows up in the BIOS content (static handler code in 2.20’s code page 6 vs not present in 2.23) and in the cold-boot generator’s dispatch (static handlers in 2.20 dispatch directly to BIOS code, runtime-generated handlers in 2.23 dispatch into trap-marker pages).

So the loading mechanism is shared between versions; the what gets loaded differs. That’s a more accurate framing.

Status: 2.20’s two LOAD_CPM calls confirmed. Both versions share the dual-load mechanism. The architectural difference between versions is in the BIOS content, not in the boot-pipeline shape. The inventory in Part 9 still holds; this devlog refines its claim about the second load being 2.23-specific.