Changes from ANFS 4.18

← Acorn ANFS 4.21 (variant 1) disassembly

Changes from ANFShref="glossary.html#term-nfs" class="glossary-ref" data-tip="Network Filing System: The Econet filing system ROM that provides remote file access over the Econet network.">NFS 4.18 to ANFS 4.21 (variant 1)

ANFS 4.21 (variant 1) is the first ANFS for Acorn's BBC Master 128 series. The two 16 KB sideways ROMhref="glossary.html#term-rom" class="glossary-ref" data-tip="Read-Only Memory: In Acorn context, typically refers to a sideways ROM — a 16 KB slot in the BBC Micro's paged ROM bank (&8000-&BFFF).">ROMs share 86.7 % of their opcode structure but the 4.21 variant 1 build is 65C02-aware, drops the page 4-6 relocated workspace in favour of HAZEL hidden RAM at &C000-&C2FF, and gates the service-call handler with a Master-only OS-version check.

The "variant 1" suffix reflects that this build refuses to install on anything other than a Master 128 (or Master Econet Terminal): a later variant 2 build, byte-identical except for an extra OS-version branch in the gate, exists for the Compact.

Summary statistics

Metric Value
ROM size 16384 bytes
Identical bytes at same offset 285 (1.7 %)
Byte-level similarity 75.8 %
Opcode-level similarity 86.7 %
Full instruction similarity 69.0 %
Instructions (4.18 / 4.21) 8189 / 8270
Structural change blocks 234
Subroutines (4.18 / 4.21) 326 / 454

The low identical-byte count (1.7 %) reflects the +5-byte ROM-title shift cascading through almost every opcode operand, plus the relocation of every workspace access from &0Dxx-&10xx (4.18) to &C0xx-&C2xx (4.21). The opcode-level similarity (86.7 %) is the meaningful structural measure: roughly one instruction in seven has been substantively changed.

ROM header

Field 4.18 4.21 variant 1
Service entry &8A15 &8A54
ROM type &82 &82
Copyright offset &19 &19
Binary version &04 &04
Title "Acorn ANFS 4.18" "Acorn ANFS 4.21"
Copyright "(C)1985 Acorn" "(C)1986 Acorn"

Changes

1. Master 128 Bad-ROM gate at the service handler

The most visible structural change. The service handler at &8A54 (was &8A15 in 4.18) now begins with an OSBYTE 0 OS-version check:

OS version returned Action
3.x (Master 128 MOS) proceed with service dispatch
4.0 (Master Econet Terminal) proceed
anything else silently return unclaimed

The gate explicitly rejects OS 1.00, 1.20, 2.00 (BBC B / B+) and OS 5.0 (Master Compact). Pre-Master OSes lack the CMOS RAM that 4.21 uses to store station and protection state, so a "soft" failure is preferable to running and corrupting workspace.

4.18 had no equivalent gate -- its service handler dispatched directly. The "variant 1" naming captures that this build is not universal across the Master series.

2. CPU upgrade to 65C02 with R65C02 extensions

The Master 128 ships with Rockwell's R65C02 (the GTE65SC02 silicon variant on later units behaves identically for the opcodes ANFS uses). The driver loads with load(addr, file, "65c02"); beebasm assembles under CPU 1.

65C12 / R65C02 instructions adopted throughout:

Routines visibly rewritten with 65C12 prologues include copy_fs_cmd_name (&9463), parse_fs_ps_args (&A3C4), and help_wrap_if_serial (around &8C29).

3. Workspace migration from MOS RAM to HAZEL hidden RAM

4.18 used MOS RAM pages &0E.. &10 for its filing-system workspace (parse buffer, TX buffer base, FS lib flags, FCB attributes, saved catalogue buffer). 4.21 moves the entire workspace to HAZEL -- the Master 128's 8 KB hidden-RAM region at &C000-&DFFF, paged over the MOS VDU drivers when ACCCON bit Y is set. ANFS occupies the first 768 bytes of HAZEL (&C000-&C2FF); HAZEL's documented upper limit for filing-system static workspace is page &DB, so the choice of &C8 as ANFS's claim base leaves room for other filing-system claimants.

Workspace data 4.18 4.21 variant 1
Parse buffer &0E30 &C030
TX buffer base &0F00 &C100
FS lib flags &1071 &C271
FCB attributes &10C9 &C2C9
Saved catalogue buffer &10D9 &C2D9

The +&B200 offset is uniform: 4.18's &0Dxx-&10xx range maps to 4.21's &BFxx-&C2xx. The migration removes the entire page-copy loop that 4.18 used to set up the relocated blocks (formerly around &BE94); 4.21 has no equivalent code in that region.

4. svc5_irq_check rewritten around the Master deferred-work flag

svc5_irq_check lives at the same address [&8028](4.18.html#addr-8028) in both ROMs but the body is completely different.

4.18 (svc5_irq_check, runtime &8028):

    LDA &FE4D            ; VIA IFR
    AND #&02             ; Bit 1: shift register IRQ
    BEQ no_sr_irq        ; Not us
    ...                  ; Service the SR completion

4.21 (svc5_irq_check, runtime &8028):

    LDY [&0D65](4.21_variant_1.html#addr-0D65)            ; Master deferred-work flag
    BEQ no_deferred      ; Nothing pending
    TRB ACCCON           ; Clear bit 7 of [&FE34](4.21_variant_1.html#addr-BFFF) (shadow-RAM enable)
    STZ &0D65            ; Consume the deferred flag
    BMI dispatch_svc5    ; Y had bit 7 set on entry: PHA/PHA/RTS dispatch
    LDA #&FE             ; Otherwise fire Econet RX event &FE
    JSR generate_event
    JMP tx_done_exit

The 4.18 SR-IRQ handling is gone; the new mechanism uses a Master- specific workspace flag at &0D65 set by IRQ and NMI paths and consumed here during deferred service. As a side effect, the 4.18 set_jsr_protection prologue at &805D (which protected the SVC5 dispatch via shadow-VIA state) is gone too -- no JMP-protection mechanism is needed in the new design. The shared [&0D68](4.21_variant_1.html#addr-0D68) / [&0D69](4.21_variant_1.html#addr-0D69) shadow-pair body that fed JSR-protection now lives at setup_sr_tx (&8512) for an unrelated TX-prep purpose.

The &27 Master service call ("Reset has occurred") that runs nfs_init_body after a hard reset uses this same flag-passing discipline: ANFS uses the post-reset call specifically to claim NMIs for Econet receive handling, since the Master MOS no longer offers workspace on a soft BREAK.

5. OSWORD &13 sub-handlers auto-select the FS

4.21's OSWORD &13 sub-handlers all begin with a JSR ensure_fs_selected (&8B4D) prologue. ensure_fs_selected tests bit 7 of fs_flags ([&0D6C](4.21_variant_1.html#addr-0D6C)); if clear, it calls cmd_net_fs to AUTO-SELECT ANFS as the active filing system. On selection failure it raises a 'net checksum' error (&AA).

4.18's equivalent handlers inlined BIT &0D6C / BPL <return> and returned a zero in PB[0] if the FS was inactive -- a quiet failure. 4.21's behaviour is louder but more robust: client code that uses OSWORD &13 will reliably get either real data or a clean error, not silent zeroes.

6. setup_sr_tx via shadow VIA pair

Master MOS owns the System VIA's auxiliary control register and shift register; ANFS can't touch them directly without fighting MOS. 4.21 introduces a shadow pair at workspace bytes &0D68 (mirror of ACR) and &0D69 (mirror of SR), updated by setup_sr_tx (&8512). The shadow is flushed into the live VIA inside the Master's IRQ dispatch path. 4.18 wrote system_via_acr / system_via_sr directly.

7. Protection state moved to CMOS RAM

4.18's cmd_prot / cmd_unprot (4.18 &B30C / &B33D) parsed attribute keywords (L, W, R, etc.) and accumulated the protection bits into a workspace mask at ws_0d68 / ws_0d69.

4.21's cmd_prot (&B6D2) and cmd_unprot (&B6D6) take no arguments. They just toggle bit 6 of CMOS RAM byte &11 (the Econet station-flags byte) via OSBYTE &A1 (read CMOS) and OSBYTE &A2 (write CMOS), mirroring the new value into the shadow ACR/IER pair through set_via_shadow_pair.

Because the state lives in CMOS, protection now survives BREAK and power-cycle without ANFS having to manage persistence. The keyword parser and the per-keyword bit-encoding table that fed it in 4.18 are gone.

8. Star commands removed in 4.21

The Master MOS handles *CLOSE, *PRINT, and *TYPE natively, so the ANFS ROM no longer wraps them. Removed entries:

The strings "Close", "Print", "Type" do not appear anywhere in the 4.21 ROM.

cmd_table_nfs shrinks correspondingly. The 4.21 utility sub-table also drops *Close-adjacent entries; the freed bytes contribute to the space budget for the new Bad-ROM gate and CMOS handlers.

9. *RUN argument with & prefix is URD-relative

The 4.21 cmd_run handler splits into two entries:

If the first arg char is &, control JMPs to cmd_run_via_urd, which clears fs_lib_flags bit 1 before parsing the rest (so the file is looked up relative to the user's URD, not the current LIB). Otherwise control falls through to pass_send_cmd for normal FS-command dispatch.

4.18's check_escape (&9570) bundled its escape-flag BIT-test prologue and the escape-acknowledge action; 4.21 splits them apart. Each call site now does its own BIT-test against the FS-options / escapable flag, then JSRs raise_escape_error (&9895) on hit. The action half emits OSBYTE &7E and tail-jumps to classify_reply_error with A=6.

10. svc_2_private_workspace split

4.18's svc_2_private_workspace (around &8EB8) bundled the workspace-allocation prologue and the ANFS bring-up sequence in a single body. 4.21 splits them:

The latter is reachable in 4.21 only via PHA/PHA/RTS dispatch (as table[22] in svc_dispatch_lo / svc_dispatch_hi); no static call exists.

11. svc_dispatch table relocated and re-keyed

The PHA/PHA/RTS service dispatch table moved:

The +&23/&31 shift comes from the longer ROM title plus the inserted Bad-ROM gate code. The table content also gained six new dispatch slots for OSWORD-style sub-handlers, language reply 2 (palette/VDU state), and the help-suffix matcher; see the 4.21 svc_dispatch_lo declaration in the driver for the full mapping.

fs_vector_table similarly shifts from 4.18's [&8E6F](4.18.html#addr-8E6F) to 4.21's [&8EB5](4.21_variant_1.html#addr-8EB5) ([&8E9A](4.21_variant_1.html#addr-8E9A) + &1B, the offset that the dispatcher's svc_dispatch_lo_offset+&1B indexed-access uses).

12. Other dispatch-table layout shifts

Table 4.18 4.21 Notes
imm_op_dispatch_lo &8488 &848B +3-byte shift
tx_done_dispatch_lo &853E &853B -3-byte shift
tx_ctrl_dispatch_lo &8681 &867E -3-byte shift
tx_ctrl_machine_type &8689 &8686 -3-byte shift

For each, the dispatcher operand uses an expr_label of the form <table_label>-<base_offset> so renames flow through cleanly.

13. dir_op_dispatch Y value

dir_op_dispatch at &8E5B sets Y=&18 (was &0E in 4.18). The indices reachable through this dispatch path therefore shift from the old 15..19 range to the new 25..29 range (language replies 0-4).

14. read_paged_rom uses LDX &028D directly

4.21 reads the current ROM number at &028D inline rather than via OSBYTE &FD. Same optimisation 4.18 made vs 4.08.53 for the break- type read; here applied to the ROM-number read in a couple more sites.

The 4.18 read_paged_rom helper (4.18 &8AA0) is removed entirely -- no JMP [&FFB9](4.21_variant_1.html#addr-BFFF) (osrdsc) instruction appears anywhere in the 4.21 ROM.

15. Address-mapping table (recovered routines)

Routine 4.18 4.21 variant 1 Notes
svc5_irq_check &8028 &8028 rewritten body (see #4)
tx_done_jsr &8543 &8540 -3 byte shift
tx_calc_transfer &88F2 &8900
get_ws_page &8CB9 &8CAD body extends with ROL/PHP/ROR/PLP
issue_svc_15 &8D17 &8D24
osbyte_x0_y0 &8E8C &8ED2 same body
osbyte_x0 &8E83 &8EC9 same body
svc_2_private_workspace_pages &8EB8 (part) &8F10 split (see #10)
nfs_init_body &8EB8 (part) &8F38 split (see #10)
print_station_id &8FF1 &90C7
copy_fs_cmd_name &9327 &9463 65C12 PHY
read_filename_char (TX buf) &0F05 &C105 workspace migrated (see #3)
parse_fs_ps_args &A0A7 &A3C4 65C12 PHX/PHY
osword_13_read_station &A660 &A9CC
osword_13_set_station &A673 &A9DA FS-active gate factored out (#5)
osword_13_read_handles &A734 &AAC2
osword_13_set_handles &A744 &AAD0
svc_8_osword &A4EE &A83B
ensure_fs_selected &8B0D &8B45
mask_owner_access &AF32 &B2CF fs_lib_flags migrated (see #3)
process_all_fcbs &B799 &BB38 rewritten -- uses fs_options + fs_block_offset
svc_18_fs_select related &AD10 &B0A0
cmd_prot &B30C &B6D2 reduced to CMOS toggle (see #7)
cmd_unprot &B33D &B6D6 reduced to CMOS toggle (see #7)

16. ROM-tail workspace shift

In 4.18 the relocated blocks for pages 4 / 5 / 6 lived at:

4.21 has none of these. The bytes at the end of the 4.21 ROM ([&BFC0](4.21_variant_1.html#addr-BFC0)..[&BFFF](4.21_variant_1.html#addr-BFFF)) are a small stub plus 33 bytes of &FF padding plus three labels (hazel_minus_1a at &BFE6, hazel_minus_2 at &BFFE, hazel_minus_1 at &BFFF) used as indexing bases for accesses into HAZEL. The trick: HAZEL begins at &C000, so an indexed instruction like STA hazel_minus_2,Y with Y ≥ 2 lands at &BFFE + Y ≥ &C000 -- inside HAZEL. Routines like loop_copy_fs_ctx (9 bytes at &C000..&C007), osword_13_set_station (2 bytes at &C000..&C001), and loop_copy_ws_to_pb (3 bytes at &C002..&C004) all use this pattern to copy small blocks between HAZEL and other buffers without burning a separate zero-page pointer for the HAZEL base address. Each loop's CPY/BNE guard stops Y before it would land inside the ROM tail. See the rom_tail_padding and hazel_idx_bases banners in the 4.21 driver for the full mapping.

Annotation / structural notes

svc_dispatch / fs_vector_table converted to symbolic equb pairs

The 4.21 disassembly emits the lo/hi halves of the service-call dispatch table as equb <(target-1) / equb >(target-1) rather than raw bytes, and fs_vector_table as equw label rather than raw addresses. All target labels appear inline in the per-entry comments; renames flow through cleanly.

osbyte_a1 is dual-use code + table base

The 5 bytes at &8E9A (A9 A1 4C F4 FF = LDA #&A1 / JMP OSBYTE) are the routine's body AND the leading slot of a vector-dispatch read accessed via LDA c8e9a,Y. The driver expresses this with expr_label so the code, the table, and osbyte_a1's callers all stay consistent under renaming.

print_inline_no_spool needed a stringhi_hook

The *SPOOL-bypassing variant of print_inline at [&928A](4.21_variant_1.html#addr-928A) follows the same inline-string + high-bit-terminator + resume-on-terminator contract as print_inline ([&9261](4.21_variant_1.html#addr-9261)) but lacked the hook_subroutine(0x928A, "print_inline_no_spool", stringhi_hook) declaration in the early 4.21 driver. Without it, py8dis stopped tracing past every JSR &928A site, leaving the resume code byte-classified across roughly two dozen sites. Adding the hook recovered ~96 code items in a single change.

Open issues