Acorn NFS 3.34B
Updated 31 Mar 2026
← All Acorn NFS and Acorn ANFS versions
- Acorn NFS 3.34B in The BBC Micro ROM Library
- Disassembly source on GitHub
- Discuss this disassembly on the Stardot Forums thread: A new annotated disassembly of Acorn NFS
- Changes from NFS 3.34
- Found a mistake or a comment that could be clearer? Report an issue.
| ; Sideways ROM header | |
| ; NFS ROM 3.34B disassembly (Acorn Econet filing system) | |
| ; ===================================================== | |
| 8000 | .rom_header←2← 048B LDA← 9BB8 JSR |
| .language_entry←2← 048B LDA← 9BB8 JSR | |
| .pydis_start←2← 048B LDA← 9BB8 JSR | |
| JMP language_handler ; JMP language_handler | |
| 8003 | .service_entry←1← 049A LDY |
| JMP service_handler ; JMP service_handler | |
| 8006 | .rom_type←1← 0482 AND |
| EQUB &82 | |
| 8007 | .copyright_offset←1← 0487 LDX |
| EQUB copyright - rom_header | |
| 8008 | .binary_version←2← 81A3 CMP← 81AC LDA |
| EQUB &03 | |
| 8009 | .title |
| EQUS "NET" | |
| 800C | .copyright |
| EQUB &00 | |
| ; The 'ROFF' suffix at copyright_string+3 is reused by | |
| ; the *ROFF command matcher (svc_4_star_command) — a | |
| ; space-saving trick that shares ROM bytes between the | |
| ; copyright string and the star command table. | |
| 800D | .copyright_string |
| EQUS "(C)ROFF" | |
| ; Error message offset table (9 entries). | |
| ; Each byte is a Y offset into error_msg_table. | |
| ; Entry 0 (Y=0, "Line Jammed") doubles as the | |
| ; copyright string null terminator. | |
| ; Indexed by TXCB status (AND #7), or hardcoded 8. | |
| 8014 | .error_offsets←1← 842E LDY |
| EQUB &00 ; "Line Jammed" | |
| 8015 | EQUB &0D ; "Net Error" |
| 8016 | EQUB &18 ; "Not listening" |
| 8017 | EQUB &27 ; "No Clock" |
| 8018 | EQUB &31 ; "Bad Txcb" |
| 8019 | EQUB &3B ; "Escape" |
| 801A | EQUB &3B ; "Escape" |
| 801B | EQUB &43 ; "Bad Option" |
| 801C | EQUB &4F ; "No reply" |
| ; Four bytes with unknown purpose. | |
| 801D | EQUB &01 ; Purpose unknown |
| 801E | EQUB &00 ; Purpose unknown |
| 801F | EQUB &3B ; Purpose unknown |
| 8020 | EQUB &03 ; Purpose unknown |
| ; Dispatch table: low bytes of (handler_address - 1) | |
| ; Each entry stores the low byte of a handler address minus 1, | |
| ; for use with the PHA/PHA/RTS dispatch trick at &809F. | |
| ; See dispatch_0_hi (&8045) for the corresponding high bytes. | |
| ; Five callers share this table via different Y base offsets: | |
| ; Y=&00 Service calls 0-12 (indices 1-13) | |
| ; Y=&0D Language entry reasons (indices 14-18) | |
| ; Y=&12 FSCV codes 0-7 (indices 19-26) | |
| ; Y=&16 FS reply handlers (indices 27-32) | |
| ; Y=&20 *NET1-4 sub-commands (indices 33-36) | |
| 8021 | .dispatch_0_lo |
| EQUB <(return_2-1) ; lo - Svc 0: already claimed (no-op) | |
| 8022 | EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace |
| 8023 | EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace |
| 8024 | EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot |
| 8025 | EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command |
| 8026 | EQUB <(svc_5_unknown_irq-1) ; lo - Svc 5: unrecognised interrupt |
| 8027 | EQUB <(return_2-1) ; lo - Svc 6: BRK (no-op) |
| 8028 | EQUB <(dispatch_net_cmd-1) ; lo - Svc 7: unrecognised OSBYTE |
| 8029 | EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD |
| 802A | EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP |
| 802B | EQUB <(return_2-1) ; lo - Svc 10: static workspace (no-op) |
| 802C | EQUB <(svc_11_nmi_claim-1) ; lo - Svc 11: NMI release (reclaim NMIs) |
| 802D | EQUB <(svc_12_nmi_release-1) ; lo - Svc 12: NMI claim (save NMI state) |
| 802E | EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube |
| 802F | EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup |
| 8030 | EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron) |
| 8031 | EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron) |
| 8032 | EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated |
| 8033 | EQUB <(fscv_0_opt-1) ; lo - FSCV 0: *OPT |
| 8034 | EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check |
| 8035 | EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 2: */ (run) |
| 8036 | EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command |
| 8037 | EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 4: *RUN |
| 8038 | EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT |
| 8039 | EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown |
| 803A | EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range |
| 803B | EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name |
| 803C | EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot |
| 803D | EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles |
| 803E | EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle |
| 803F | EQUB <(fsreply_4_notify_exec-1) ; lo - FS reply: notify + execute |
| 8040 | EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle |
| 8041 | EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet |
| 8042 | EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace |
| 8043 | EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle |
| 8044 | EQUB <(net_4_resume_remote-1) ; lo - *NET4: resume remote |
| ; Dispatch table: high bytes of (handler_address - 1) | |
| ; Paired with dispatch_0_lo (&8021). Together they form a table | |
| ; of 37 handler addresses, used via the PHA/PHA/RTS trick at | |
| ; &809F. | |
| 8045 | .dispatch_0_hi |
| EQUB >(return_2-1) ; hi - Svc 0: already claimed (no-op) | |
| 8046 | EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace |
| 8047 | EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace |
| 8048 | EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot |
| 8049 | EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command |
| 804A | EQUB >(svc_5_unknown_irq-1) ; hi - Svc 5: unrecognised interrupt |
| 804B | EQUB >(return_2-1) ; hi - Svc 6: BRK (no-op) |
| 804C | EQUB >(dispatch_net_cmd-1) ; hi - Svc 7: unrecognised OSBYTE |
| 804D | EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD |
| 804E | EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP |
| 804F | EQUB >(return_2-1) ; hi - Svc 10: static workspace (no-op) |
| 8050 | EQUB >(svc_11_nmi_claim-1) ; hi - Svc 11: NMI release (reclaim NMIs) |
| 8051 | EQUB >(svc_12_nmi_release-1) ; hi - Svc 12: NMI claim (save NMI state) |
| 8052 | EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube |
| 8053 | EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup |
| 8054 | EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron) |
| 8055 | EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron) |
| 8056 | EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated |
| 8057 | EQUB >(fscv_0_opt-1) ; hi - FSCV 0: *OPT |
| 8058 | EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check |
| 8059 | EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 2: */ (run) |
| 805A | EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command |
| 805B | EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 4: *RUN |
| 805C | EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT |
| 805D | EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown |
| 805E | EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range |
| 805F | EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name |
| 8060 | EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot |
| 8061 | EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles |
| 8062 | EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle |
| 8063 | EQUB >(fsreply_4_notify_exec-1) ; hi - FS reply: notify + execute |
| 8064 | EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle |
| 8065 | EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet |
| 8066 | EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace |
| 8067 | EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle |
| 8068 | EQUB >(net_4_resume_remote-1) ; hi - *NET4: resume remote |
*NET command dispatcherParses the character after *NET as '1'-'4', maps to table indices 33-36 via base offset Y=&20, and dispatches via &809F. Characters outside '1'-'4' fall through to return_1 (RTS). These are internal sub-commands used only by the ROM itself, not user-accessible star commands. The MOS command parser requires a space or terminator after 'NET', so *NET1 typed at the command line does not match; these are reached only via OSCLI calls within the ROM. *NET1 (&8DB0): read file handle from received packet (net_1_read_handle) *NET2 (&8DCA): read handle entry from workspace (net_2_read_handle_entry) *NET3 (&8DE0): close handle / mark as unused (net_3_close_handle) *NET4 (&8DF3): resume after remote operation (net_4_resume_remote) |
|
| 8069 | .dispatch_net_cmd |
| LDA osbyte_a_copy ; Read command character following *NET | |
| 806B | SBC #&31 ; Subtract ASCII '1' to get 0-based command index |
| 806D | BMI return_1 ; Negative: not a net command, exit |
| 806F | CMP #4 ; Command index >= 4: invalid *NET sub-command |
| 8071 | BCS return_1 ; Out of range: return via c80e3/RTS |
| 8073 | TAX ; X = command index (0-3) |
| 8074 | TYA ; Transfer Y to A for dispatch |
| 8075 | LDY #&20 ; Y=&20: base offset for *NET commands (index 33+) |
| 8077 | BNE dispatch ; ALWAYS branch |
Forward unrecognised * command to fileserver (COMERR)Copies command text from (fs_crc_lo) to &0F05+ via copy_filename, prepares an FS command with function code 0, and sends it to the fileserver to request decoding. The server returns a command code indicating what action to take (e.g. code 4=INFO, 7=DIR, 9=LIB, 5=load-as-command). This mechanism allows the fileserver to extend the client's command set without ROM updates. Called from the "I." and catch-all entries in the command match table at &8BD7, and from FSCV 2/3/4 indirectly. If CSD handle is zero (not logged in), returns without sending. |
|
| 8079 | .forward_star_cmd←1← 8D1D JMP |
| JSR copy_filename ; Copy command text to FS buffer | |
| 807C | TAY ; Y=function code for HDRFN |
| 807D | .prepare_cmd_dispatch |
| JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |
| 8080 | LDX fs_cmd_csd ; X=depends on function |
| 8083 | BEQ return_1 ; CSD handle zero: not logged in |
| 8085 | LDA fs_cmd_data ; A=function code (0-7) |
| 8088 | LDY #&16 ; Y=depends on function |
| 808A | BNE dispatch ; ALWAYS branch |
FSCV dispatch entryEntered via the extended vector table when the MOS calls FSCV. Stores A/X/Y via save_fscv_args, compares A (function code) against 8, and dispatches codes 0-7 via the shared dispatch table at &8020 with base offset Y=&12 (table indices 19-26). Function codes: 0=*OPT, 1=EOF, 2=*/, 3=unrecognised *, 4=*RUN, 5=*CAT, 6=shutdown, 7=read handles.
|
|||||||||||||||
| 808C | .fscv_handler | ||||||||||||||
| JSR save_fscv_args ; Store A/X/Y in FS workspace | |||||||||||||||
| 808F | CMP #8 ; Function code >= 8? Return (unsupported) | ||||||||||||||
| 8091 | BCS return_1 ; Function code >= 8? Return (unsupported) | ||||||||||||||
| 8093 | TAX ; X = function code for dispatch | ||||||||||||||
| 8094 | TYA ; Save Y (command text ptr hi) | ||||||||||||||
| 8095 | LDY #&12 ; Y=&12: base offset for FSCV dispatch (indices 19+) | ||||||||||||||
| 8097 | BNE dispatch ; ALWAYS branch | ||||||||||||||
Language entry dispatcherCalled when the NFS ROM is entered as a language. Although rom_type (&82) does not set the language bit, the MOS enters this point after NFS claims service &FE (Tube post-init). X = reason code (0-4). Dispatches via table indices 14-18 (base offset Y=&0D). |
|
| 8099 | .language_handler←1← 8000 JMP |
| .lang_entry_dispatch←1← 8000 JMP | |
| CPX #5 ; X >= 5: invalid reason code, return | |
| 809B | .svc_dispatch_range |
| BCS return_1 ; Out of range: return via RTS | |
| 809D | LDY #&0d ; Y=&0D: base offset for language handlers (index 14+) |
| fall through ↓ | |
PHA/PHA/RTS computed dispatchX = command index within caller's group (e.g. service number) Y = base offset into dispatch table (0, &0D, &20, etc.) The loop adds Y+1 to X, so final X = command index + base + 1. Then high and low bytes of (handler-1) are pushed onto the stack, and RTS pops them and jumps to handler_address. This is a standard 6502 trick: RTS increments the popped address by 1 before jumping, so the table stores (address - 1) to compensate. Multiple callers share one table via different Y base offsets. |
|
| 809F | .dispatch←5← 8077 BNE← 808A BNE← 8097 BNE← 80A1 BPL← 8139 JSR |
| INX ; Add base offset Y to index X (loop: X += Y+1) | |
| 80A0 | DEY ; Decrement base offset counter |
| 80A1 | BPL dispatch ; Loop until Y exhausted |
| 80A3 | TAY ; Y=&FF (no further use) |
| 80A4 | LDA dispatch_0_hi-1,x ; Load high byte of (handler - 1) from table |
| 80A7 | PHA ; Push high byte onto stack |
| 80A8 | LDA dispatch_0_lo-1,x ; Load low byte of (handler - 1) from table |
| 80AB | PHA ; Push low byte onto stack |
| 80AC | LDX fs_options ; Restore X (fileserver options) for use by handler |
| 80AE | .return_1←6← 806D BMI← 8071 BCS← 8083 BEQ← 8091 BCS← 809B BCS← 80B7 BEQ |
| RTS ; RTS pops address, adds 1, jumps to handler | |
Service handler entryIntercepts three special service calls before normal dispatch: &FE: Tube init — explode character definitions (OSBYTE &14, X=6) &FF: Full init — set up WRCHV/RDCHV/BRKV/EVNTV, copy NMI handler code from ROM to RAM pages &04-&06, copy workspace init to &0016-&0076, then fall through to select NFS. &12 with Y=5: Select NFS as active filing system. All other service calls dispatch via dispatch_service (&8127). |
|
| 80AF | .service_handler←1← 8003 JMP |
| .service_handler_entry←1← 8003 JMP | |
| .check_svc_high←1← 8003 JMP | |
| CMP #&fe ; Service >= &FE? | |
| 80B1 | BCC check_svc_12 ; Service < &FE: skip to &12/dispatch check |
| 80B3 | BNE init_vectors_and_copy ; Service &FF: full init (vectors + RAM copy) |
| 80B5 | CPY #0 ; Service &FE: Y=0? |
| 80B7 | BEQ return_1 ; Y=0: no Tube data, skip to &12 check |
| 80B9 | STX zp_temp_11 ; Save ROM number across OSBYTE |
| 80BB | STY zp_temp_10 ; Save Tube address across OSBYTE |
| 80BD | LDX #6 ; X=6 extra pages for char definitions |
| 80BF | LDA #osbyte_explode_chars ; OSBYTE &14: explode character RAM |
| 80C1 | JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6) |
| 80C4 | LDX zp_temp_11 ; Restore ROM number |
| 80C6 | BNE restore_y_check_svc ; Continue to vector setup |
| fall through ↓ | |
NFS initialisation (service &FF: full reset)Sets up OS vectors for Tube co-processor support: WRCHV = &051C (page 5 — WRCH handler) RDCHV = &04E7 (page 4 — RDCH handler) BRKV = &0016 (workspace — BRK/error handler) EVNTV = &06E8 (page 6 — event handler) Writes &8E to Tube control register (&FEE0). Then copies 3 pages of Tube host code from ROM (tube_code_page4, tube_dispatch_table, tube_code_page6) to RAM (&0400/&0500/&0600), calls tube_post_init (&0414), and copies 97 bytes of workspace init from ROM (nmi_workspace_start) to &0016-&0076. |
|
| 80C8 | .init_vectors_and_copy←1← 80B3 BNE |
| LDA #&1c ; Set WRCHV = &051C (Tube WRCH handler) | |
| 80CA | STA wrchv ; Set WRCHV low byte |
| 80CD | LDA #5 ; A=5: WRCHV high byte |
| 80CF | STA wrchv+1 ; Set WRCHV high byte |
| 80D2 | LDA #&e7 ; Set RDCHV = &04E7 (Tube RDCH handler) |
| 80D4 | STA rdchv ; Set RDCHV low byte |
| 80D7 | LDA #4 ; A=4: RDCHV high byte |
| 80D9 | STA rdchv+1 ; Set RDCHV high byte |
| 80DC | LDA #&16 ; Set BRKV = &0016 (BRK handler in workspace) |
| 80DE | STA brkv ; Set BRKV low byte |
| 80E1 | LDA #0 ; A=0: BRKV high byte (page zero) |
| 80E3 | STA brkv+1 ; Set BRKV high byte |
| 80E6 | LDA #&e8 ; Set EVNTV = &06E8 (event handler in page 6) |
| 80E8 | STA evntv ; Set EVNTV low byte |
| 80EB | LDA #6 ; A=6: EVNTV high byte |
| 80ED | STA evntv+1 ; Set EVNTV high byte |
| 80F0 | LDA #&8e ; Write &8E to Tube control register |
| 80F2 | STA tube_status_1_and_tube_control ; Write &8E to Tube control register |
| 80F5 | STY zp_temp_10 ; Save Y to temporary |
| 80F7 | LDY #0 ; Y=0: start ROM-to-RAM copy loop |
| ; Copy NMI handler code from ROM to RAM pages &04-&06 | |
| 80F9 | .cloop←1← 810C BNE |
| LDA reloc_p4_src,y ; Load ROM byte from page &93 | |
| 80FC | STA tube_code_page4,y ; Store to page &04 (Tube code) |
| 80FF | LDA l944d,y ; Load ROM byte from page &94 |
| 8102 | STA tube_dispatch_table,y ; Store to page &05 (dispatch table) |
| 8105 | LDA c954d,y ; Load ROM byte from page &95 |
| 8108 | STA tube_code_page6,y ; Store to page &06 |
| 810B | DEY ; DEY wraps 0 -> &FF on first iteration |
| 810C | BNE cloop ; Loop until 256 bytes copied per page |
| 810E | JSR tube_post_init ; Run post-init routine in copied code |
| 8111 | LDX #&60 ; X=&60: copy 97 bytes (&60..&00) |
| ; Copy NMI workspace initialiser from ROM to &0016-&0076 | |
| 8113 | .copy_nmi_workspace←1← 8119 BPL |
| LDA reloc_zp_src,x ; Load NMI workspace init byte from ROM | |
| 8116 | STA nmi_workspace_start,x ; Store to zero page &16+X |
| 8118 | DEX ; Next byte |
| 8119 | BPL copy_nmi_workspace ; Loop until all workspace bytes copied |
| 811B | .restore_y_check_svc←1← 80C6 BNE |
| LDY zp_temp_10 ; Restore Y (ROM number) | |
| 811D | .tube_chars_done |
| LDA #0 ; A=0: fall through to service &12 check | |
| 811F | .check_svc_12←1← 80B1 BCC |
| CMP #&12 ; Is this service &12 (select FS)? | |
| 8121 | BNE dispatch_service ; No: check if service < &0D |
| 8123 | CPY #5 ; Service &12: Y=5 (NFS)? |
| 8125 | BEQ select_nfs ; Y=5: select NFS |
| fall through ↓ | |
Service call dispatcher |
|
| 8127 | .dispatch_service←1← 8121 BNE |
| .not_svc_12_nfs←1← 8121 BNE | |
| CMP #&0d ; Service >= &0D? | |
| 8129 | .svc_unhandled_return |
| BCS return_2 ; Service >= &0D: not handled, return | |
| 812B | .do_svc_dispatch |
| TAX ; X = service number (dispatch index) | |
| 812C | LDA rom_svc_num ; Save &A9 (current service state) |
| 812E | PHA ; Push saved &A9 |
| 812F | LDA nfs_temp ; Save &A8 (workspace page number) |
| 8131 | PHA ; Push saved &A8 |
| 8132 | STX rom_svc_num ; Store service number to &A9 |
| 8134 | STY nfs_temp ; Store Y (page number) to &A8 |
| 8136 | TYA ; A = Y for dispatch table offset |
| 8137 | LDY #0 ; Y=0: base offset for service dispatch |
| 8139 | JSR dispatch ; JSR to dispatcher (returns here after handler completes) |
| 813C | LDX rom_svc_num ; Recover service claim status from &A9 |
| 813E | PLA ; Restore saved &A8 from stack |
| 813F | STA nfs_temp ; Write back &A8 |
| fall through ↓ | |
Service dispatch epilogueCommon return path for all dispatched service handlers. Restores rom_svc_num from the stack (pushed by dispatch_service), transfers X (ROM number) to A, then returns via RTS. |
|
| 8141 | .svc_dispatch_epilogue |
| PLA ; Restore saved A from service dispatch | |
| 8142 | STA rom_svc_num ; Save to workspace &A9 |
| 8144 | TXA ; Return ROM number in A |
| 8145 | .return_2←2← 8129 BCS← 814A BEQ |
| RTS ; Return (not our command) | |
Resume after remote operation / *ROFF handler (NROFF)Checks byte 4 of (net_rx_ptr): if non-zero, the keyboard was disabled during a remote operation (peek/poke/boot). Clears the flag, re-enables the keyboard via OSBYTE &C9, and sends function &0A to notify completion. Also handles *ROFF and the triple-plus escape sequence (+++), which resets system masks via OSBYTE &CE and returns control to the MOS, providing an escape route when a remote session becomes unresponsive. |
|
| 8146 | .resume_after_remote←2← 817B BEQ← 8DF3 JSR |
| LDY #4 ; Y=4: offset of keyboard disable flag | |
| 8148 | LDA (net_rx_ptr),y ; Read flag from RX buffer |
| 814A | BEQ return_2 ; Zero: keyboard not disabled, skip |
| 814C | LDA #0 ; A=0: value to clear flag and re-enable |
| 814E | TAX ; X=&00 |
| 814F | STA (net_rx_ptr),y ; Clear keyboard disable flag in buffer |
| 8151 | TAY ; Y=&00 |
| 8152 | LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable |
| 8154 | JSR osbyte ; Re-enable keyboard (X=0, Y=0) Enable keyboard (for Econet) |
| 8157 | LDA #&0a ; Function &0A: remote operation complete |
| 8159 | JSR setup_tx_and_send ; Send notification to controlling station |
| 815C | .clear_osbyte_ce_cf←1← 911C JSR |
| STX nfs_workspace ; Save X (return value from TX) | |
| 815E | LDA #&ce ; OSBYTE &CE: first system mask to reset |
| 8160 | .clear_osbyte_masks←1← 816B BEQ |
| LDX nfs_workspace ; Restore X for OSBYTE call | |
| 8162 | LDY #&7f ; Y=&7F: AND mask (clear bit 7) |
| 8164 | JSR osbyte ; Reset system mask byte |
| 8167 | ADC #1 ; Advance to next OSBYTE (&CE -> &CF) |
| 8169 | CMP #&d0 ; Reached &D0? (past &CF) |
| 816B | BEQ clear_osbyte_masks ; No: reset &CF too |
| 816D | .skip_kbd_reenable |
| LDA #0 ; A=0: clear remote state | |
| 816F | STA nfs_workspace ; Clear &A9 (service dispatch state) |
| 8171 | RTS ; Return from workspace reset |
Service 4: unrecognised * commandMatches the command text against ROM string table entries. Both entries reuse bytes from the ROM header to save space: X=8: matches "ROFF" at copyright_string+3 — the suffix
of "(C)ROFF" → *ROFF (Remote Off,
end remote session) — jumps to resume_after_remote
X=1: matches "NET" at &8009 — the ROM title string → *NET (select NFS) — falls through to select_nfs If neither matches, returns with the service call unclaimed. |
|
| 8172 | .svc_4_star_command |
| LDX #8 ; X=8: ROM offset for *ROFF match | |
| 8174 | JSR match_rom_string ; Match command against ROM string |
| 8177 | BNE match_net_cmd ; No match: try *NET command |
| 8179 | STA rom_svc_num ; Match found: claim service (A=0) |
| 817B | BEQ resume_after_remote ; ALWAYS branch |
| 817D | .match_net_cmd←1← 8177 BNE |
| LDX #1 ; X=1: ROM offset for "NET" match | |
| 817F | JSR match_rom_string ; Try matching *NET command |
| 8182 | BNE restore_y_return ; No match: return unclaimed |
| fall through ↓ | |
Select NFS as active filing system (INIT)Reached from service &12 (select FS) with Y=5, or when *NET command selects NFS. Notifies the current FS of shutdown via FSCV A=6 — this triggers the outgoing FS to save its context back to its workspace page, allowing restoration if re-selected later (the FSDIE handoff mechanism). Then sets up the standard OS vector indirections (FILEV through FSCV) to NFS entry points, claims the extended vector table entries, and issues service &0F (vectors claimed) to notify other ROMs. If nfs_temp is zero (auto-boot not inhibited), injects the synthetic command "I .BOOT" through the command decoder to trigger auto-boot login. |
|
| 8184 | .select_nfs←1← 8125 BEQ |
| JSR call_fscv_shutdown ; Notify current FS of shutdown (FSCV A=6) | |
| 8187 | SEC ; C=1 for ROR |
| 8188 | ROR nfs_temp ; Set bit 7 of l00a8 (inhibit auto-boot) |
| 818A | JSR issue_vectors_claimed ; Claim OS vectors, issue service &0F |
| 818D | LDY #&1d ; Y=&1D: top of FS state range |
| 818F | .initl←1← 8197 BNE |
| LDA (net_rx_ptr),y ; Copy FS state from RX buffer... | |
| 8191 | STA fs_state_deb,y ; ...to workspace (offsets &15-&1D) |
| 8194 | DEY ; Next byte (descending) |
| 8195 | CPY #&14 ; Loop until offset &14 done |
| 8197 | BNE initl ; Continue loop |
| 8199 | BEQ init_fs_vectors ; ALWAYS branch to init_fs_vectors ALWAYS branch |
Match command text against ROM string tableCompares characters from (os_text_ptr)+Y against bytes starting at binary_version+X (&8008+X). Input is uppercased via AND &DF. Returns with Z=1 if the ROM string's NUL terminator was reached (match), or Z=0 if a mismatch was found. On match, Y points past the matched text; on return, skips trailing spaces. |
|
| 819B | .match_rom_string←2← 8174 JSR← 817F JSR |
| LDY nfs_temp ; Y = saved text pointer offset | |
| 819D | .match_next_char←1← 81AA BNE |
| LDA (os_text_ptr),y ; Load next input character | |
| 819F | AND #&df ; Force uppercase (clear bit 5) |
| 81A1 | BEQ cmd_name_matched ; Input char is NUL/space: check ROM byte |
| 81A3 | CMP binary_version,x ; Compare with ROM string byte |
| 81A6 | BNE cmd_name_matched ; Mismatch: check if ROM string ended |
| 81A8 | INY ; Advance input pointer |
| 81A9 | INX ; Advance ROM string pointer |
| 81AA | BNE match_next_char ; Continue matching (always taken) |
| 81AC | .cmd_name_matched←2← 81A1 BEQ← 81A6 BNE |
| LDA binary_version,x ; Load ROM string byte at match point | |
| 81AF | BEQ skip_cmd_spaces ; Zero = end of ROM string = full match |
| 81B1 | RTS ; Non-zero = partial/no match; Z=0 |
| 81B2 | .skpspi←1← 81B7 BEQ |
| INY ; Skip this space | |
| 81B3 | .skip_cmd_spaces←1← 81AF BEQ |
| LDA (os_text_ptr),y ; Load next input character | |
| 81B5 | CMP #&20 ; Is it a space? |
| 81B7 | BEQ skpspi ; Yes: keep skipping |
| 81B9 | EOR #&0d ; XOR with CR: Z=1 if end of line |
| 81BB | RTS ; Return (not our service call) |
Service 9: *HELPPrints the ROM identification string using print_inline. |
|
| 81BC | .svc_9_help |
| JSR print_inline ; Print inline ROM identification string | |
| 81BF | EQUS ".NFS 3.34B." |
| 81CA | .restore_y_return←2← 8182 BNE← 81DF BNE |
| LDY nfs_temp ; Restore Y from temporary | |
| 81CC | RTS ; Return (service not claimed) |
Notify filing system of shutdownLoads A=6 (FS shutdown notification) and JMP (FSCV). The FSCV handler's RTS returns to the caller of this routine (JSR/JMP trick saves one level of stack). |
|
| 81CD | .call_fscv_shutdown←2← 8184 JSR← 81D2 JSR |
| LDA #6 ; FSCV reason 6 = FS shutdown | |
| 81CF | JMP (fscv) ; Tail-call via filing system control vector |
Service 3: auto-bootNotifies current FS of shutdown via FSCV A=6. Scans keyboard (OSBYTE &7A): if the 'N' key is pressed (matrix address &55), the keypress is forgotten via OSBYTE &78 and auto-boot proceeds. Any other key causes the auto-boot to be declined. If no key is pressed, auto-boot proceeds directly. Falls through to print_station_info, then init_fs_vectors. |
|
| 81D2 | .svc_3_autoboot |
| JSR call_fscv_shutdown ; Notify current FS of shutdown | |
| 81D5 | LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard |
| 81D7 | JSR osbyte ; Keyboard scan starting from key 16 |
| 81DA | TXA ; X is key number if key is pressed, or &ff otherwise |
| 81DB | BMI print_station_info ; No key pressed: proceed with auto-boot |
| fall through ↓ | |
Check boot keyChecks if the pressed key (in A) is 'N' (matrix address &55). If not 'N', returns to the MOS without claiming the service call (another ROM may boot instead). If 'N', forgets the keypress via OSBYTE &78 and falls through to print_station_info. |
|
| 81DD | .check_boot_key |
| EOR #&55 ; XOR with &55: result=0 if key is 'N' | |
| 81DF | BNE restore_y_return ; Not 'N': return without claiming |
| 81E1 | TAY ; Y=key |
| 81E2 | LDA #osbyte_write_keys_pressed ; OSBYTE &78: clear key-pressed state |
| 81E4 | JSR osbyte ; Write current keys pressed (X and Y) |
| fall through ↓ | |
Print station identificationPrints "Econet Station <n>" using the station number from the net receive buffer, then tests ADLC SR2 for the network clock signal — prints " No Clock" if absent. Falls through to init_fs_vectors. |
|
| 81E7 | .print_station_info←1← 81DB BMI |
| JSR print_inline ; Print 'Econet Station ' banner | |
| 81EA | EQUS "Econet Station " |
| 81F9 | LDA tx_clear_flag ; Load local station number |
| 81FC | JSR print_decimal ; Print station number as decimal |
| 81FF | LDA #&20 ; A=&20: test bit 5 of SR2 (clock) |
| 8201 | BIT econet_control23_or_status2 ; Test ADLC SR2 for network clock |
| 8204 | BEQ skip_no_clock_msg ; Clock present: skip warning msg |
| 8206 | JSR print_inline ; Print ' No Clock' warning |
| 8209 | EQUS " No Clock" |
| 8212 | NOP ; NOP (padding after inline string) |
| 8213 | .skip_no_clock_msg←1← 8204 BEQ |
| JSR print_inline ; Print two CRs (blank line) | |
| 8216 | EQUS ".." |
| fall through ↓ | |
Initialise filing system vectorsCopies 14 bytes from fs_vector_addrs (&824E) into FILEV-FSCV (&0212), setting all 7 filing system vectors to the extended vector dispatch addresses (&FF1B-&FF2D). Calls setup_rom_ptrs_netv to install the ROM pointer table entries with the actual NFS handler addresses. Also reached directly from select_nfs, bypassing the station display. Falls through to issue_vectors_claimed. |
|
| 8218 | .init_fs_vectors←1← 8199 BEQ |
| LDY #&0d ; Copy 14 bytes: FS vector addresses → FILEV-FSCV | |
| 821A | .dofsl1←1← 8221 BPL |
| LDA fs_vector_addrs,y ; Load vector address from ROM table | |
| 821D | STA filev,y ; Write to FILEV-FSCV vector table |
| 8220 | DEY ; Next byte (descending) |
| 8221 | BPL dofsl1 ; Loop until all 14 bytes copied |
| 8223 | JSR setup_rom_ptrs_netv ; Read ROM ptr table addr, install NETV |
| 8226 | LDY #&1b ; Install 7 handler entries in ROM ptr table |
| 8228 | LDX #7 ; 7 FS vectors to install |
| 822A | JSR store_rom_ptr_pair ; Install each 3-byte vector entry |
| 822D | STX rom_svc_num ; X=0 after loop; store as workspace offset |
| fall through ↓ | |
Issue 'vectors claimed' service and optionally auto-bootIssues service &0F (vectors claimed) via OSBYTE &8F, then service &0A. If nfs_temp is zero (auto-boot not inhibited), sets up the command string "I .BOOT" at &8246 and jumps to the FSCV 3 unrecognised-command handler (which matches against the command table at &8BD7). The "I." prefix triggers the catch-all entry which forwards the command to the fileserver. Falls through to run_fscv_cmd. |
|
| 822F | .issue_vectors_claimed←1← 818A JSR |
| LDA #osbyte_issue_service_request ; A=&8F: issue service request | |
| 8231 | LDX #&0f ; X=&0F: 'vectors claimed' service |
| 8233 | JSR osbyte ; Issue paged ROM service call, Reason X=15 - Vectors claimed |
| 8236 | LDX #&0a ; X=&0A: service &0A |
| 8238 | JSR osbyte ; Issue service &0A |
| 823B | LDX nfs_temp ; Non-zero after hard reset: skip auto-boot |
| 823D | BNE return_3 ; Non-zero: skip auto-boot |
| 823F | LDX #&46 ; X = lo byte of auto-boot string at &8292 |
| fall through ↓ | |
Run FSCV command from ROMSets Y to the ROM page high byte (&82) and jumps to fscv_3_star_cmd to execute the command string at (X, Y). X is pre-loaded by the caller with the low byte of the string address. Also used as a data base address by store_rom_ptr_pair for Y-indexed access to the handler address table. |
|
| 8241 | .run_fscv_cmd←2← 82E6 LDA← 82EC LDA |
| LDY #&82 ; Y=&82: ROM page high byte | |
| 8243 | JMP fscv_3_star_cmd ; Execute command string at (X, Y) |
| ; Synthetic auto-boot command string. "I " does not match any | |
| ; entry in NFS's local command table — "I." requires a dot, and | |
| ; "I AM" requires 'A' after the space — so fscv_3_star_cmd | |
| ; forwards the entire string to the fileserver, which executes | |
| ; the .BOOT file. | |
| 8246 | EQUS "I .BOOT." ; Auto-boot string tail / NETV handler data |
FS vector dispatch and handler addresses (34 bytes)Bytes 0-13: extended vector dispatch addresses, copied to FILEV-FSCV (&0212) by init_fs_vectors. Each 2-byte pair is a dispatch address (&FF1B-&FF2D) that the MOS uses to look up the handler in the ROM pointer table. Bytes 14-33: handler address pairs read by store_rom_ptr_pair. Each entry has addr_lo, addr_hi, then a padding byte that is overwritten with the current ROM bank number at runtime. The last entry (FSCV) has no padding byte. |
|
| 824E | .fs_vector_addrs←1← 821A LDA |
| EQUB &1B ; FILEV dispatch lo | |
| 824F | EQUB &FF ; FILEV dispatch hi |
| 8250 | EQUB &1E ; ARGSV dispatch lo |
| 8251 | EQUB &FF ; ARGSV dispatch hi |
| 8252 | EQUB &21 ; BGETV dispatch lo |
| 8253 | EQUB &FF ; BGETV dispatch hi |
| 8254 | EQUB &24 ; BPUTV dispatch lo |
| 8255 | EQUB &FF ; BPUTV dispatch hi |
| 8256 | EQUB &27 ; GBPBV dispatch lo |
| 8257 | EQUB &FF ; GBPBV dispatch hi |
| 8258 | EQUB &2A ; FINDV dispatch lo |
| 8259 | EQUB &FF ; FINDV dispatch hi |
| 825A | EQUB &2D ; FSCV dispatch lo |
| 825B | EQUB &FF ; FSCV dispatch hi |
| 825C | EQUB &95 ; FILEV handler lo (&8695) |
| 825D | EQUB &86 ; FILEV handler hi |
| 825E | EQUB &00 ; (ROM bank — overwritten) |
| 825F | EQUB &E2 ; ARGSV handler lo (&88E2) |
| 8260 | EQUB &88 ; ARGSV handler hi |
| 8261 | EQUB &00 ; (ROM bank — overwritten) |
| 8262 | EQUB &86 ; BGETV handler lo (&8486) |
| 8263 | EQUB &84 ; BGETV handler hi |
| 8264 | EQUB &00 ; (ROM bank — overwritten) |
| 8265 | EQUB &A3 ; BPUTV handler lo (&83A3) |
| 8266 | EQUB &83 ; BPUTV handler hi |
| 8267 | EQUB &00 ; (ROM bank — overwritten) |
| 8268 | EQUB &EB ; GBPBV handler lo (&89EB) |
| 8269 | EQUB &89 ; GBPBV handler hi |
| 826A | EQUB &00 ; (ROM bank — overwritten) |
| 826B | EQUB &4A ; FINDV handler lo (&894A) |
| 826C | EQUB &89 ; FINDV handler hi |
| 826D | EQUB &00 ; (ROM bank — overwritten) |
| 826E | EQUB &8C ; FSCV handler lo (&808C) |
| 826F | EQUB &80 ; FSCV handler hi |
Service 1: claim absolute workspaceClaims pages up to &10 for NMI workspace (&0D), FS state (&0E), and FS command buffer (&0F). If Y >= &10, workspace already allocated — returns unchanged.
|
|||||||
| 8270 | .svc_1_abs_workspace | ||||||
| CPY #&10 ; Compare Y against workspace boundary | |||||||
| 8272 | BCS return_3 ; Y>=&10: already allocated, return | ||||||
| 8274 | LDY #&10 ; Claim workspace up to page &10 | ||||||
| 8276 | .return_3←2← 823D BNE← 8272 BCS | ||||||
| RTS ; Return to caller | |||||||
| 8277 | EQUB &08, &90 | ||||||
Service 2: claim private workspace and initialise NFSY = next available workspace page on entry. Sets up net_rx_ptr (Y) and nfs_workspace (Y+1) page pointers. On soft break (OSBYTE &FD returns 0): skips FS state init, preserving existing login state, file server selection, and control block configuration — this is why pressing BREAK keeps the user logged in. On power-up/CTRL-BREAK (result non-zero): - Sets FS server station to &FE (FS, the default; no server) - Sets printer server to &EB (PS, the default) - Clears FS handles, OPT byte, message flag, SEQNOS - Initialises all RXCBs with &3F flag (available) In both cases: reads station ID from &FE18 (only valid during reset), calls adlc_init, enables user-level RX (LFLAG=&40).
|
|||||||
| 8279 | .svc_2_private_workspace | ||||||
| STY net_rx_ptr_hi ; Store RX buffer page pointer | |||||||
| 827B | INY ; Advance to next page | ||||||
| 827C | STY nfs_workspace_hi ; Store workspace page pointer | ||||||
| 827E | LDA #0 ; A=0 for clearing workspace | ||||||
| 8280 | LDY #4 ; Y=4: remote status offset | ||||||
| 8282 | STA (net_rx_ptr),y ; Clear status byte in net receive buffer | ||||||
| 8284 | LDY #&ff ; Y=&FF: used for later iteration | ||||||
| 8286 | STA net_rx_ptr ; Clear RX ptr low byte | ||||||
| 8288 | STA nfs_workspace ; Clear workspace ptr low byte | ||||||
| 828A | STA nfs_temp ; Clear RXCB iteration counter | ||||||
| 828C | STA tx_ctrl_status ; Clear TX semaphore (no TX in progress) | ||||||
| 828F | TAX ; X=0 for OSBYTE X=&00 | ||||||
| 8290 | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read type of last reset | ||||||
| 8292 | JSR osbyte ; Read type of last reset | ||||||
| 8295 | TXA ; X = break type from OSBYTE result X=value of type of last reset | ||||||
| 8296 | BEQ read_station_id ; Soft break (X=0): skip FS init | ||||||
| 8298 | LDY #&15 ; Y=&15: printer station offset in RX buffer | ||||||
| 829A | LDA #&fe ; &FE = no server selected | ||||||
| 829C | STA fs_server_stn ; Station &FE = no server selected | ||||||
| 829F | STA (net_rx_ptr),y ; Store &FE at printer station offset | ||||||
| 82A1 | LDY #2 ; Y=2: printer server offset | ||||||
| 82A3 | LDA #&eb ; A=&EB: default printer server | ||||||
| 82A5 | STA (nfs_workspace),y ; Store printer server at offset 2 | ||||||
| 82A7 | INY ; Y=&03 | ||||||
| 82A8 | LDA #0 ; A=0: clear remaining fields | ||||||
| 82AA | STA fs_server_net ; Clear FS server network number | ||||||
| 82AD | STA (nfs_workspace),y ; Clear workspace byte at offset 3 | ||||||
| 82AF | STA prot_status ; Clear protection status mask | ||||||
| 82B2 | STA fs_messages_flag ; Clear FS messages flag | ||||||
| 82B5 | .init_rxcb_entries←1← 82C2 BNE | ||||||
| LDA nfs_temp ; Load RXCB counter | |||||||
| 82B7 | JSR calc_handle_offset ; Convert to workspace byte offset | ||||||
| 82BA | BCS read_station_id ; C=1: past max handles, done | ||||||
| 82BC | LDA #&3f ; Mark RXCB as available | ||||||
| 82BE | STA (nfs_workspace),y ; Write &3F flag to workspace | ||||||
| 82C0 | INC nfs_temp ; Next RXCB number | ||||||
| 82C2 | BNE init_rxcb_entries ; Loop for all RXCBs | ||||||
| 82C4 | .read_station_id←2← 8296 BEQ← 82BA BCS | ||||||
| LDA station_id_disable_net_nmis ; Read station ID (also INTOFF) | |||||||
| 82C7 | STA tx_clear_flag ; Store station ID for TX scout | ||||||
| 82CA | JSR trampoline_adlc_init ; Initialise ADLC hardware | ||||||
| 82CD | LDA #&40 ; Enable user-level RX (LFLAG=&40) | ||||||
| 82CF | STA rx_status_flags ; Store to rx_flags | ||||||
| fall through ↓ | |||||||
Set up ROM pointer table and NETVReads the ROM pointer table base address via OSBYTE &A8, stores it in osrdsc_ptr (&F6). Sets NETV low byte to &36. Then copies one 3-byte extended vector entry (addr=&9008, rom=current) into the ROM pointer table at offset &36, installing osword_dispatch as the NETV handler. |
|
| 82D2 | .setup_rom_ptrs_netv←1← 8223 JSR |
| LDA #osbyte_read_rom_ptr_table_low ; OSBYTE &A8: read ROM pointer table address | |
| 82D4 | LDX #0 ; X=0: read low byte |
| 82D6 | LDY #&ff ; Y=&FF: read high byte |
| 82D8 | JSR osbyte ; Returns table address in X (lo) Y (hi) Read address of ROM pointer table |
| 82DB | STX osrdsc_ptr ; Store table base address low byte X=value of address of ROM pointer table (low byte) |
| 82DD | STY osrdsc_ptr_hi ; Store table base address high byte Y=value of address of ROM pointer table (high byte) |
| 82DF | LDY #&36 ; NETV extended vector offset in ROM ptr table |
| 82E1 | STY netv ; Set NETV low byte = &36 (vector dispatch) |
| 82E4 | LDX #1 ; Install 1 entry (NETV) in ROM ptr table |
| 82E6 | .store_rom_ptr_pair←2← 822A JSR← 82F8 BNE |
| LDA run_fscv_cmd,y ; Load handler address low byte from table | |
| 82E9 | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 82EB | INY ; Next byte |
| 82EC | LDA run_fscv_cmd,y ; Load handler address high byte from table |
| 82EF | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 82F1 | INY ; Next byte |
| 82F2 | LDA romsel_copy ; Write current ROM bank number |
| 82F4 | STA (osrdsc_ptr),y ; Store ROM number to ROM pointer table |
| 82F6 | INY ; Advance to next entry position |
| 82F7 | DEX ; Count down entries |
| 82F8 | BNE store_rom_ptr_pair ; Loop until all entries installed |
| 82FA | LDY nfs_workspace_hi ; Y = workspace high byte + 1 = next free page |
| 82FC | INY ; Advance past workspace page |
| 82FD | RTS ; Return; Y = page after NFS workspace |
FSCV 6: Filing system shutdown / save state (FSDIE)Called when another filing system (e.g. DFS) is selected. Saves the current NFS context (FSLOCN station number, URD/CSD/LIB handles, OPT byte, etc.) from page &0E into the dynamic workspace backup area. This allows the state to be restored when *NET is re-issued later, without losing the login session. Finally calls OSBYTE &7B (printer driver going dormant) to release the Econet network printer on FS switch. |
|
| 82FE | .fscv_6_shutdown |
| LDY #&1d ; Copy 10 bytes: FS state to workspace backup | |
| 8300 | .fsdiel←1← 8308 BNE |
| LDA fs_state_deb,y ; Load FS state byte at offset Y | |
| 8303 | STA (net_rx_ptr),y ; Store to workspace backup area |
| 8305 | DEY ; Next byte down |
| 8306 | CPY #&14 ; Offsets &15-&1D: server, handles, OPT, etc. |
| 8308 | BNE fsdiel ; Loop for offsets &1D..&15 |
| 830A | LDA #osbyte_printer_driver_going_dormant ; A=&7B: printer driver going dormant |
| 830C | JMP osbyte ; Printer driver going dormant |
Initialise TX control block for FS reply on port &90Loads port &90 (PREPLY) into A, calls init_tx_ctrl_block to set up the TX control block, stores the port and control bytes, then decrements the control flag. Used by send_fs_reply_cmd to prepare for receiving the fileserver's reply. |
|
| 830F | .init_tx_ctrl_data←1← 8382 JSR |
| .init_tx_reply_port←1← 8382 JSR | |
| LDA #&90 ; A=&90: FS reply port (PREPLY) | |
| 8311 | .init_tx_ctrl_port←2← 880F JSR← 8F79 LDA |
| JSR init_tx_ctrl_block ; Init TXCB from template | |
| 8314 | STA txcb_port ; Store port number in TXCB |
| 8316 | LDA #3 ; Control byte: 3 = transmit |
| 8318 | STA txcb_start ; Store control byte in TXCB |
| 831A | DEC txcb_ctrl ; Decrement TXCB flag to arm TX |
| 831C | RTS ; Return after port setup |
Initialise TX control block at &00C0 from template |
|
| 831D | .init_tx_ctrl_block←3← 8311 JSR← 8371 JSR← 83BA JSR |
| PHA ; Preserve A across call | |
| 831E | LDY #&0b ; Copy 12 bytes (Y=11..0) |
| 8320 | .fstxl1←1← 8331 BPL |
| LDA tx_ctrl_template,y ; Load template byte | |
| 8323 | STA txcb_ctrl,y ; Store to TX control block at &00C0 |
| 8326 | CPY #2 ; Y < 2: also copy FS server station/network |
| 8328 | BPL fstxl2 ; Skip station/network copy for Y >= 2 |
| 832A | LDA fs_server_stn,y ; Load FS server station (Y=0) or network (Y=1) |
| 832D | STA txcb_dest,y ; Store to dest station/network at &00C2 |
| 8330 | .fstxl2←1← 8328 BPL |
| DEY ; Next byte (descending) | |
| 8331 | BPL fstxl1 ; Loop until all 12 bytes copied |
| 8333 | PLA ; Restore A |
| 8334 | RTS ; Return |
| 8335 | .tx_ctrl_template←1← 8320 LDA |
| EQUB &80 ; Control flag | |
| 8336 | EQUB &99 ; Port (FS command = &99) |
| 8337 | EQUB &00 ; Station (filled at runtime) |
| 8338 | EQUB &00 ; Network (filled at runtime) |
| 8339 | EQUB &00 ; Buffer start low |
| 833A | EQUB &0F ; Buffer start high (page &0F) |
| 833B | .tx_ctrl_upper←3← 8890 BIT← 896A BIT← 915E BIT |
| EQUB &FF ; Buffer start pad (4-byte Econet addr) | |
| 833C | EQUB &FF ; Buffer start pad |
| 833D | EQUB &FF ; Buffer end low |
| 833E | EQUB &0F ; Buffer end high (page &0F) |
| 833F | EQUB &FF ; Buffer end pad |
| 8340 | EQUB &FF ; Buffer end pad |
Prepare FS command with carry setAlternate entry to prepare_fs_cmd that pushes A, loads &2A into fs_error_ptr, and enters with carry set (SEC). The carry flag is later tested by build_send_fs_cmd to select the byte-stream (BSXMIT) transmission path.
|
||||||
| 8341 | .prepare_cmd_with_flag←1← 8A3C JSR | |||||
| PHA ; Save flag byte for command | ||||||
| 8342 | LDA #&2a ; A=&2A: error ptr for retry | |||||
| 8344 | SEC ; C=1: include flag in FS command | |||||
| 8345 | BCS store_fs_hdr_fn ; ALWAYS branch to prepare_fs_cmd ALWAYS branch | |||||
| 8347 | .prepare_cmd_clv←2← 86D8 JSR← 8781 JSR | |||||
| CLV ; V=0: command has no flag byte | ||||||
| 8348 | BVC store_fs_hdr_clc ; ALWAYS branch to prepare_fs_cmd ALWAYS branch | |||||
*BYE handler (logoff)Closes any open *SPOOL and *EXEC files via OSBYTE &77 (FXSPEX), then falls into prepare_fs_cmd with Y=&17 (FCBYE: logoff code). Dispatched from the command match table at &8BD7 for "BYE". |
|
| 834A | .bye_handler |
| LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec | |
| 834C | JSR osbyte ; Close any *SPOOL and *EXEC files |
| 834F | LDY #&17 ; Y=function code for HDRFN |
| fall through ↓ | |
Prepare FS command buffer (12 references)Builds the 5-byte FS protocol header at &0F00: &0F00 HDRREP = reply port (set downstream, typically &90/PREPLY) &0F01 HDRFN = Y parameter (function code) &0F02 HDRURD = URD handle (from &0E02) &0F03 HDRCSD = CSD handle (from &0E03) &0F04 HDRLIB = LIB handle (from &0E04) Command-specific data follows at &0F05 (TXBUF). Also clears V flag. Called before building specific FS commands for transmission.
|
|||||||||||||
| 8351 | .prepare_fs_cmd←12← 807D JSR← 8835 JSR← 88AB JSR← 88FD JSR← 8924 JSR← 8997 JSR← 89C1 JSR← 8A9B JSR← 8B51 JSR← 8C19 JSR← 8C50 JSR← 8CC9 JSR | ||||||||||||
| CLV ; V=0: standard FS command path | |||||||||||||
| 8352 | .prepare_fs_cmd_v←2← 8893 JSR← 896D JSR | ||||||||||||
| LDA fs_urd_handle ; Copy URD handle from workspace to buffer | |||||||||||||
| 8355 | STA fs_cmd_urd ; Store URD at &0F02 | ||||||||||||
| 8358 | LDA #&2a ; A=&2A: error ptr for retry | ||||||||||||
| 835A | .store_fs_hdr_clc←1← 8348 BVC | ||||||||||||
| CLC ; CLC: no byte-stream path | |||||||||||||
| 835B | .store_fs_hdr_fn←1← 8345 BCS | ||||||||||||
| STY fs_cmd_y_param ; Store function code at &0F01 | |||||||||||||
| 835E | STA fs_error_ptr ; Store error ptr for TX poll | ||||||||||||
| 8360 | LDY #1 ; Y=1: copy CSD (offset 1) then LIB (offset 0) | ||||||||||||
| 8362 | .copy_dir_handles←1← 8369 BPL | ||||||||||||
| LDA fs_csd_handle,y ; Copy CSD and LIB handles to command buffer A=timeout period for FS reply | |||||||||||||
| 8365 | STA fs_cmd_csd,y ; Store at &0F03 (CSD) and &0F04 (LIB) | ||||||||||||
| 8368 | DEY ; Y=function code | ||||||||||||
| 8369 | BPL copy_dir_handles ; Loop for both handles | ||||||||||||
| fall through ↓ | |||||||||||||
Build and send FS command (DOFSOP)Sets reply port to &90 (PREPLY) at &0F00, initialises the TX control block, then adjusts TXCB's high pointer (HPTR) to X+5 -- the 5-byte FS header (reply port, function code, URD, CSD, LIB) plus the command data -- so only meaningful bytes are transmitted, conserving Econet bandwidth. If carry is set on entry (DOFSBX byte-stream path), takes the alternate path through econet_tx_retry for direct BSXMIT transmission. Otherwise sets up the TX pointer via setup_tx_ptr_c0 and falls through to send_fs_reply_cmd for reply handling. The carry flag is the sole discriminator between byte-stream and standard FS protocol paths -- set by SEC at the BPUTV/BGETV entry points. On return from WAITFS/BSXMIT, Y=0; INY advances past the command code to read the return code. Error &D6 ("not found", on_entry={"x": "buffer extent (command-specific data bytes)", "y": "function code", "a": "timeout period for FS reply", "c": "0 for standard FS path, 1 for byte-stream (BSXMIT)"}, on_exit={"a": "0 on success", "x": "0 on success, &D6 on not-found", "y": "1 (offset past command code in reply)"}) is detected via ADC #(&100-&D6) with C=0 -- if the return code was exactly &D6, the result wraps to zero (Z=1). This is a branchless comparison returning C=1, A=0 as a soft error that callers can handle, vs hard errors which go through FSERR.
|
|||||||||||||||||
| 836B | .build_send_fs_cmd←1← 8AF4 JSR | ||||||||||||||||
| PHP ; Save carry (FS path vs byte-stream) | |||||||||||||||||
| 836C | LDA #&90 ; Reply port &90 (PREPLY) | ||||||||||||||||
| 836E | STA fs_cmd_type ; Store at &0F00 (HDRREP) | ||||||||||||||||
| 8371 | JSR init_tx_ctrl_block ; Copy TX template to &00C0 | ||||||||||||||||
| 8374 | TXA ; A = X (buffer extent) | ||||||||||||||||
| 8375 | ADC #5 ; HPTR = header (5) + data (X) bytes to send | ||||||||||||||||
| 8377 | STA txcb_end ; Store to TXCB end-pointer low | ||||||||||||||||
| 8379 | PLP ; Restore carry flag | ||||||||||||||||
| 837A | BCS dofsl5 ; C=1: byte-stream path (BSXMIT) | ||||||||||||||||
| 837C | PHP ; Save flags for send_fs_reply_cmd | ||||||||||||||||
| 837D | JSR setup_tx_ptr_c0 ; Point net_tx_ptr to &00C0; transmit | ||||||||||||||||
| 8380 | PLP ; Restore flags | ||||||||||||||||
| 8381 | .send_fs_reply_cmd←2← 8791 JSR← 8A78 JSR | ||||||||||||||||
| PHP ; Save flags (V flag state) | |||||||||||||||||
| 8382 | JSR init_tx_ctrl_data ; Set up RX wait for FS reply | ||||||||||||||||
| 8385 | LDA fs_error_ptr ; Load error ptr for TX retry | ||||||||||||||||
| 8387 | JSR send_to_fs ; Transmit and wait (BRIANX) | ||||||||||||||||
| 838A | PLP ; Restore flags | ||||||||||||||||
| 838B | .dofsl7←1← 83A1 BCC | ||||||||||||||||
| INY ; Y=1: skip past command code byte | |||||||||||||||||
| 838C | LDA (txcb_start),y ; Load return code from FS reply | ||||||||||||||||
| 838E | TAX ; X = return code | ||||||||||||||||
| 838F | BEQ return_dofsl7 ; Zero: success, return | ||||||||||||||||
| 8391 | BVC check_fs_error ; V=0: standard path, error is fatal | ||||||||||||||||
| 8393 | ADC #&2a ; ADC #&2A: test for &D6 (not found) | ||||||||||||||||
| 8395 | .check_fs_error←1← 8391 BVC | ||||||||||||||||
| BNE store_fs_error ; Non-zero: hard error, go to FSERR | |||||||||||||||||
| 8397 | .return_dofsl7←1← 838F BEQ | ||||||||||||||||
| RTS ; Return (success or soft &D6 error) | |||||||||||||||||
| 8398 | .dofsl5←1← 837A BCS | ||||||||||||||||
| PLA ; Discard saved flags from stack | |||||||||||||||||
| 8399 | LDX #&c0 ; X=&C0: TXCB address for byte-stream TX | ||||||||||||||||
| 839B | INY ; Y++ past command code | ||||||||||||||||
| 839C | JSR econet_tx_retry ; Byte-stream transmit with retry | ||||||||||||||||
| 839F | STA fs_load_addr_3 ; Store result to &B3 | ||||||||||||||||
| 83A1 | BCC dofsl7 ; C=0: success, check reply code | ||||||||||||||||
| 83A3 | .bputv_handler | ||||||||||||||||
| CLC ; CLC for address addition | |||||||||||||||||
| fall through ↓ | |||||||||||||||||
Handle BPUT/BGET file byte I/OBPUTV enters at &83A3 (CLC; fall through) and BGETV enters at &8486 (SEC; JSR here). The carry flag is preserved via PHP/PLP through the call chain and tested later (BCS) to select byte-stream transmission (BSXMIT) vs normal FS transmission (FSXMIT) -- a control-flow encoding using processor flags to avoid an extra flag variable. BSXMIT uses handle=0 for print stream transactions (which sidestep the SEQNOS sequence number manipulation) and non-zero handles for file operations. After transmission, the high pointer bytes of the CB are reset to &FF -- "The BGET/PUT byte fix" which prevents stale buffer pointers corrupting subsequent byte-level operations.
|
|||||||||||||||
| 83A4 | .handle_bput_bget←1← 8487 JSR | ||||||||||||||
| PHA ; Save A (BPUT byte) on stack | |||||||||||||||
| 83A5 | STA fs_error_flags ; Also save byte at &0FDF for BSXMIT | ||||||||||||||
| 83A8 | TXA ; Transfer X for stack save | ||||||||||||||
| 83A9 | PHA ; Save X on stack | ||||||||||||||
| 83AA | TYA ; Transfer Y (handle) for stack save | ||||||||||||||
| 83AB | PHA ; Save Y (handle) on stack | ||||||||||||||
| 83AC | PHP ; Save P (C = BPUT/BGET selector) on stack | ||||||||||||||
| 83AD | JSR handle_to_mask_clc ; Convert handle Y to single-bit mask | ||||||||||||||
| 83B0 | STY fs_handle_mask ; Store handle bitmask at &0FDE | ||||||||||||||
| 83B3 | STY fs_spool0 ; Store handle bitmask for sequence tracking | ||||||||||||||
| 83B5 | LDY #&90 ; &90 = data port (PREPLY) | ||||||||||||||
| 83B7 | STY fs_putb_buf ; Store reply port in command buffer | ||||||||||||||
| 83BA | JSR init_tx_ctrl_block ; Set up 12-byte TXCB from template | ||||||||||||||
| 83BD | LDA #&dc ; CB reply buffer at &0FDC | ||||||||||||||
| 83BF | STA txcb_start ; Store reply buffer ptr low in TXCB | ||||||||||||||
| 83C1 | LDA #&e0 ; Error buffer at &0FE0 | ||||||||||||||
| 83C3 | STA txcb_end ; Store error buffer ptr low in TXCB | ||||||||||||||
| 83C5 | INY ; Y=1 (from init_tx_ctrl_block exit) | ||||||||||||||
| 83C6 | LDX #9 ; X=9: BPUT function code | ||||||||||||||
| 83C8 | PLP ; Restore C: selects BPUT (0) vs BGET (1) | ||||||||||||||
| 83C9 | BCC store_retry_count ; C=0 (BPUT): keep X=9 | ||||||||||||||
| 83CB | DEX ; X=&08 | ||||||||||||||
| 83CC | .store_retry_count←1← 83C9 BCC | ||||||||||||||
| STX fs_getb_buf ; Store function code at &0FDD | |||||||||||||||
| 83CF | LDA fs_handle_mask ; Load handle bitmask for BPUT/BGET | ||||||||||||||
| 83D2 | LDX #&c0 ; X=&C0: TXCB address for econet_tx_retry | ||||||||||||||
| 83D4 | JSR econet_tx_retry ; Transmit via byte-stream protocol | ||||||||||||||
| 83D7 | LDX fs_getb_buf ; Load reply byte from buffer | ||||||||||||||
| 83DA | BEQ update_sequence_return ; Zero reply = success, skip error handling | ||||||||||||||
| 83DC | LDY #&1f ; Copy 32-byte reply to error buffer at &0FE0 | ||||||||||||||
| 83DE | .error1←1← 83E5 BPL | ||||||||||||||
| LDA fs_putb_buf,y ; Load reply byte at offset Y | |||||||||||||||
| 83E1 | STA fs_error_buf,y ; Store to error buffer at &0FE0+Y | ||||||||||||||
| 83E4 | DEY ; Next byte (descending) | ||||||||||||||
| 83E5 | BPL error1 ; Loop until all 32 bytes copied | ||||||||||||||
| 83E7 | TAX ; X=File handle | ||||||||||||||
| 83E8 | LDA #osbyte_read_write_spool_file_handle ; A=&C7: read *SPOOL file handle | ||||||||||||||
| 83EA | JSR osbyte ; Read/Write *SPOOL file handle | ||||||||||||||
| 83ED | TXA ; X=value of *SPOOL file handle | ||||||||||||||
| 83EE | JSR handle_to_mask_a ; Convert SPOOL handle to bitmask | ||||||||||||||
| 83F1 | CPY fs_spool0 ; Compare SPOOL mask with file mask | ||||||||||||||
| 83F3 | BNE dispatch_fs_error ; Not SPOOL file: dispatch FS error | ||||||||||||||
| 83F5 | LDX #<(sp_dot_string) ; Load '*SP.' command string low | ||||||||||||||
| 83F7 | LDY #>(sp_dot_string) ; Y=&85: high byte of OSCLI string in ROM | ||||||||||||||
| 83F9 | JSR oscli ; Close SPOOL/EXEC via "*SP." or "*E." | ||||||||||||||
| 83FC | .dispatch_fs_error←1← 83F3 BNE | ||||||||||||||
| LDA #&e0 ; Reset CB pointer to error buffer at &0FE0 | |||||||||||||||
| 83FE | STA txcb_start ; Reset reply ptr to error buffer | ||||||||||||||
| 8400 | LDX fs_getb_buf ; Reload reply byte for error dispatch | ||||||||||||||
| fall through ↓ | |||||||||||||||
Handle fileserver error replies (FSERR)The fileserver returns errors as: zero command code + error number + CR-terminated message string. This routine converts the reply buffer in-place to a standard MOS BRK error packet by: 1. Storing the error code at fs_last_error (&0E09) 2. Normalizing error codes below &A8 to &A8 (the standard FS error number), since the MOS error space below &A8 has other meanings 3. Scanning for the CR terminator and replacing it with &00 4. JMPing indirect through (l00c4) to execute the buffer as a BRK instruction — the zero command code serves as the BRK opcode N.B. This relies on the fileserver always returning a zero command code in position 0 of the reply buffer. |
|
| 8403 | .store_fs_error←1← 8395 BNE |
| STX fs_last_error ; Remember raw FS error code | |
| 8406 | LDY #1 ; Y=1: point to error number byte in reply |
| 8408 | CPX #&a8 ; Clamp FS errors below &A8 to standard &A8 |
| 840A | BCS find_cr_terminator ; Error >= &A8: keep original value |
| 840C | LDA #&a8 ; Error < &A8: override with standard &A8 |
| 840E | STA (txcb_start),y ; Write clamped error number to reply buffer |
| 8410 | .find_cr_terminator←2← 840A BCS← 8415 BNE |
| INY ; Advance to next reply buffer byte | |
| 8411 | LDA #&0d ; A=CR: terminator to search for |
| 8413 | EOR (txcb_start),y ; XOR with buffer byte (0 when CR) |
| 8415 | BNE find_cr_terminator ; Not CR: continue scanning |
| 8417 | STA (txcb_start),y ; Store 0 (from XOR) to replace CR |
| 8419 | JMP (txcb_start) ; Execute error via JMP indirect |
| 841C | .update_sequence_return←1← 83DA BEQ |
| STA fs_sequence_nos ; Save updated sequence number | |
| 841F | PLA ; Restore Y from stack |
| 8420 | TAY ; Transfer A to Y for indexing |
| 8421 | PLA ; Restore X from stack |
| 8422 | TAX ; Transfer to X for return |
| 8423 | PLA ; Restore A from stack |
| 8424 | RTS ; Return to caller |
| 8425 | .error_not_listening←1← 8478 BEQ |
| LDA #8 ; Error code 8: "Not listening" error | |
| 8427 | BNE set_listen_offset ; ALWAYS branch to set_listen_offset ALWAYS branch |
| 8429 | .nlistn←1← 868E JMP |
| LDA (net_tx_ptr,x) ; Load TX status byte for error lookup | |
| 842B | .nlisne←2← 8484 BNE← 89B9 JMP |
| AND #7 ; Mask to 3-bit error code (0-7) | |
| 842D | .set_listen_offset←1← 8427 BNE |
| TAX ; X = error code index | |
| 842E | LDY error_offsets,x ; Look up error message offset from table |
| 8431 | LDX #0 ; X=0: start writing at &0101 |
| 8433 | STX l0100 ; Store BRK opcode at &0100 |
| 8436 | .copy_error_message←1← 8440 BNE |
| LDA error_msg_table,y ; Load error message byte | |
| 8439 | STA l0101,x ; Build error message at &0101+ |
| 843C | BEQ execute_brk_error ; Zero byte = end of message; go execute BRK |
| 843E | INX ; Advance output buffer position |
| 843F | INY ; Advance source string pointer |
| 8440 | BNE copy_error_message ; Continue copying message bytes |
| 8442 | .execute_brk_error←1← 843C BEQ |
| JMP l0100 ; Execute constructed BRK error | |
| 8445 | .sp_dot_string |
| EQUS "SP." | |
| 8448 | EQUB &0D |
| 8449 | .send_to_fs_star←3← 8731 JSR← 8FBC JSR← 927E JSR |
| LDA #&2a ; A=&2A: error ptr for FS retry | |
| fall through ↓ | |
Send command to fileserver and handle reply (WAITFS)Performs a complete FS transaction: transmit then wait for reply. Sets bit 7 of rx_status_flags (mark FS transaction in progress), builds a TX frame from the data at (net_tx_ptr), and transmits it. The system RX flag (LFLAG bit 7) is only set when receiving into the page-zero control block — if RXCBP's high byte is non-zero, setting the system flag would interfere with other RX operations. The timeout counter uses the stack (indexed via TSX) rather than memory to avoid bus conflicts with Econet hardware during the tight polling loop. Handles multi-block replies and checks for escape conditions between blocks. |
|
| 844B | .send_to_fs←2← 8387 JSR← 881B JSR |
| PHA ; Save function code on stack | |
| 844C | LDA rx_status_flags ; Load current rx_flags |
| 844F | PHA ; Save rx_flags on stack for restore |
| 8450 | ORA #&80 ; Set bit7: FS transaction in progress |
| 8452 | STA rx_status_flags ; Write back updated rx_flags |
| 8455 | .skip_rx_flag_set |
| LDA #0 ; Push two zero bytes as timeout counters | |
| 8457 | PHA ; First zero for timeout |
| 8458 | PHA ; Second zero for timeout |
| 8459 | TAY ; Y=0: index for flag byte check Y=&00 |
| 845A | TSX ; TSX: index stack-based timeout via X |
| 845B | .incpx←3← 8465 BNE← 846A BNE← 846F BNE |
| LDA (net_tx_ptr),y ; Load TX flag byte from ctrl block | |
| 845D | BMI fs_wait_cleanup ; Bit 7 set: TX complete, clean up |
| 845F | JSR check_escape ; Check for Escape during TX wait |
| 8462 | DEC l0101,x ; Three-stage nested timeout: inner loop |
| 8465 | BNE incpx ; Inner not expired: keep polling |
| 8467 | DEC l0102,x ; Middle timeout loop |
| 846A | BNE incpx ; Middle not expired: keep polling |
| 846C | DEC l0104,x ; Outer timeout loop (slowest) |
| 846F | BNE incpx ; Outer not expired: keep polling |
| 8471 | .fs_wait_cleanup←1← 845D BMI |
| PLA ; Pop first timeout byte | |
| 8472 | PLA ; Pop second timeout byte |
| 8473 | PLA ; Pop saved rx_flags into A |
| 8474 | STA rx_status_flags ; Restore saved rx_flags from stack |
| 8477 | PLA ; Pop saved function code |
| 8478 | BEQ error_not_listening ; A=saved func code; zero would mean no reply |
| 847A | RTS ; Return to caller |
Check and handle escape condition (ESC)Two-level escape gating: the MOS escape flag (&FF bit 7) is ANDed with the software enable flag ESCAP. Both must have bit 7 set for escape to fire. ESCAP is set non-zero during data port operations (LOADOP stores the data port &90, serving double duty as both the port number and the escape-enable flag). ESCAP is disabled via LSR in the ENTER routine, which clears bit 7 — PHP/PLP around the LSR preserves the carry flag since ENTER is called from contexts where carry has semantic meaning (e.g., PUTBYT vs BGET distinction). This architecture allows escape between retransmission attempts but prevents interruption during critical FS transactions. If escape fires: acknowledges via OSBYTE &7E, then checks whether the failing handle is the current SPOOL or EXEC handle (OSBYTE &C6/&C7); if so, issues "*SP." or "*E." via OSCLI to gracefully close the channel before raising the error — preventing the system from continuing to spool output to a broken file handle. |
|
| 847B | .check_escape←2← 845F JSR← 8675 JSR |
| LDA #&7e ; A=&7E: OSBYTE acknowledge escape | |
| fall through ↓ | |
Test MOS escape flag and abort if pendingTests MOS escape flag (&FF bit 7). If escape is pending: acknowledges via OSBYTE &7E, writes &3F (deleted marker) into the control block via (net_tx_ptr),Y, and branches to the NLISTN error path. If no escape, returns immediately. |
|
| 847D | .check_escape_handler |
| BIT escape_flag ; Test escape flag (bit 7) | |
| 847F | BPL return_bget ; Bit 7 clear: no escape, return |
| 8481 | JSR osbyte ; Acknowledge escape via OSBYTE &7E |
| 8484 | BNE nlisne ; Non-zero: report 'Not listening' |
| 8486 | .bgetv_handler |
| SEC ; C=1: flag for BGET mode | |
| 8487 | JSR handle_bput_bget ; Handle BGET via FS command Handle BPUT/BGET file byte I/O |
| 848A | SEC ; SEC: set carry for error check |
| 848B | LDA #&fe ; A=&FE: mask for EOF check |
| 848D | BIT fs_error_flags ; BIT l0fdf: test error flags |
| 8490 | BVS return_bget ; V=1: error, return early |
| 8492 | CLC ; CLC: no error |
| 8493 | BMI tx_flow_control ; Bit 7 set: set EOF hint flag |
| 8495 | LDA fs_spool0 ; Load handle bitmask for flag op |
| 8497 | JSR clear_fs_flag ; Clear EOF hint flag for this handle |
| 849A | BCC tx_error_classify ; Flag cleared: load handle mask |
| 849C | .tx_flow_control←1← 8493 BMI |
| LDA fs_spool0 ; Load handle bitmask for flow control | |
| 849E | JSR set_fs_flag ; Set EOF hint flag for this handle |
| 84A1 | .tx_error_classify←1← 849A BCC |
| LDA fs_handle_mask ; Load handle mask for return value | |
| 84A4 | .return_bget←2← 847F BPL← 8490 BVS |
| RTS ; Return with handle mask in A | |
| 84A5 | .add_5_to_y←1← 8761 JSR |
| INY ; Y += 5 Y += 5 (entry point) | |
| 84A6 | .add_4_to_y←1← 8A4E JSR |
| INY ; Y += 4 | |
| 84A7 | INY ; (continued) |
| 84A8 | INY ; (continued) |
| 84A9 | INY ; (continued) |
| 84AA | RTS ; Return with Y adjusted |
| 84AB | .sub_4_from_y←1← 8750 JSR |
| DEY ; Y -= 4 | |
| 84AC | .sub_3_from_y←2← 885D JSR← 8A56 JSR |
| DEY ; Y -= 3 | |
| 84AD | DEY ; (continued) |
| 84AE | DEY ; (continued) |
| 84AF | .return_4 |
| RTS ; Return with handle mask in A | |
| ; Econet error message table (ERRTAB, 8 entries). | |
| ; Each entry: error number byte followed by NUL-terminated ; string. | |
| ; &A0: "Line Jammed" &A1: "Net Error" | |
| ; &A2: "Not listening" &A3: "No Clock" | |
| ; &A4: "Bad Txcb" &11: "Escape" | |
| ; &CB: "Bad Option" &A5: "No reply" | |
| ; Indexed by the low 3 bits of the TXCB flag byte (AND #&07), | |
| ; which encode the specific Econet failure reason. The NREPLY | |
| ; and NLISTN routines build a MOS BRK error block at &100 on the | |
| ; stack page: NREPLY fires when the fileserver does not respond | |
| ; within the timeout period; NLISTN fires when the destination | |
| ; station actively refused the connection. | |
| ; Indexed via the error dispatch at c8424/c842c. | |
| 84B0 | .error_msg_table←1← 8436 LDA |
| EQUB &A0 | |
| 84B1 | EQUS "Line Jammed." |
| 84BD | EQUB &A1 |
| 84BE | EQUS "Net Error." |
| 84C8 | EQUB &A2 |
| 84C9 | EQUS "Not listening." |
| 84D7 | EQUB &A3 |
| 84D8 | EQUS "No Clock." |
| 84E1 | EQUB &A4 |
| 84E2 | EQUS "Bad Txcb." |
| 84EB | EQUB &11 |
| 84EC | EQUS "Escape." |
| 84F3 | EQUB &CB |
| 84F4 | EQUS "Bad Option." |
| 84FF | EQUB &A5 |
| 8500 | EQUS "No reply." |
Save FSCV/vector argumentsStores A, X, Y into the filing system workspace. Called at the start of every FS vector handler (FILEV, ARGSV, BGETV, BPUTV, GBPBV, FINDV, FSCV). NFS repurposes CFS/RFS workspace locations: &BD (fs_last_byte_flag) = A (function code / command) &BB (fs_options) = X (control block ptr low) &BC (fs_block_offset) = Y (control block ptr high) &BE/&BF (fs_crc_lo/hi) = X/Y (duplicate for indexed access)
|
||||||||
| 8509 | .save_fscv_args←6← 808C JSR← 8695 JSR← 88E2 JSR← 894A JSR← 89EB JSR← 8B93 JSR | |||||||
| STA fs_last_byte_flag ; A = function code / command | ||||||||
| 850B | STX fs_options ; X = control block ptr lo | |||||||
| 850D | STY fs_block_offset ; Y = control block ptr hi | |||||||
| 850F | STX fs_crc_lo ; X dup for indexed access via (fs_crc) | |||||||
| 8511 | STY fs_crc_hi ; Y dup for indexed access | |||||||
| 8513 | RTS ; Return | |||||||
Decode file attributes: FS → BBC format (FSBBC, 6-bit variant)Reads attribute byte at offset &0E from the parameter block, masks to 6 bits, then falls through to the shared bitmask builder. Converts fileserver protection format (5-6 bits) to BBC OSFILE attribute format (8 bits) via the lookup table at &8531. The two formats use different bit layouts for file protection attributes. |
|
| 8514 | .decode_attribs_6bit←2← 886F JSR← 889A JSR |
| LDY #&0e ; Y=&0E: attribute byte offset in param block | |
| 8516 | LDA (fs_options),y ; Load FS attribute byte |
| 8518 | AND #&3f ; Mask to 6 bits (FS → BBC direction) |
| 851A | LDX #4 ; X=4: skip first 4 table entries (BBC→FS half) |
| 851C | BNE attrib_shift_bits ; ALWAYS branch to shared bitmask builder ALWAYS branch |
Decode file attributes: BBC → FS format (BBCFS, 5-bit variant)Masks A to 5 bits and builds an access bitmask via the lookup table at &8531. Each input bit position maps to a different output bit via the table. The conversion is done by iterating through the source bits and OR-ing in the corresponding destination bits from the table, translating between BBC (8-bit) and fileserver (5-bit) protection formats.
|
|||||||||
| 851E | .decode_attribs_5bit←2← 879C JSR← 88B7 JSR | ||||||||
| AND #&1f ; Mask to 5 bits (BBC → FS direction) | |||||||||
| 8520 | LDX #&ff ; X=&FF: INX makes 0; start from table index 0 | ||||||||
| 8522 | .attrib_shift_bits←1← 851C BNE | ||||||||
| STA fs_error_ptr ; Temp storage for source bitmask to shift out | |||||||||
| 8524 | LDA #0 ; A=0: accumulate destination bits here | ||||||||
| 8526 | .map_attrib_bits←1← 852E BNE | ||||||||
| INX ; Next table entry | |||||||||
| 8527 | LSR fs_error_ptr ; Shift out source bits one at a time | ||||||||
| 8529 | BCC skip_set_attrib_bit ; Bit was 0: skip this destination bit | ||||||||
| 852B | ORA access_bit_table,x ; OR in destination bit from lookup table | ||||||||
| 852E | .skip_set_attrib_bit←1← 8529 BCC | ||||||||
| BNE map_attrib_bits ; Loop while source bits remain (A != 0) | |||||||||
| 8530 | RTS ; Return; A = converted attribute bitmask | ||||||||
| 8531 | .access_bit_table←1← 852B ORA | ||||||||
| EQUB &50, &20, &05, &02, &88, &04, &08, &80, &10, &01, &02 | |||||||||
Print inline string, high-bit terminated (VSTRNG)Pops the return address from the stack, prints each byte via OSASCI until a byte with bit 7 set is found, then jumps to that address. The high-bit byte serves as both the string terminator and the opcode of the first instruction after the string. N.B. Cannot be used for BRK error messages -- the stack manipulation means a BRK in the inline data would corrupt the stack rather than invoke the error handler.
|
||||||||
| 853C | .print_inline←13← 81BC JSR← 81E7 JSR← 8206 JSR← 8213 JSR← 8C21 JSR← 8C2B JSR← 8C39 JSR← 8C44 JSR← 8C5E JSR← 8C6F JSR← 8C82 JSR← 8C96 JSR← 8CA3 JSR | |||||||
| PLA ; Pop return address (low) — points to last byte of JSR | ||||||||
| 853D | STA fs_load_addr ; Store return addr low as string ptr | |||||||
| 853F | PLA ; Pop return address (high) | |||||||
| 8540 | STA fs_load_addr_hi ; Store return addr high as string ptr | |||||||
| 8542 | LDY #0 ; Y=0: offset for indirect load | |||||||
| 8544 | .print_inline_char←1← 8551 BNE | |||||||
| INC fs_load_addr ; Advance pointer past return address / to next char | ||||||||
| 8546 | BNE print_next_char ; No page wrap: skip high byte inc | |||||||
| 8548 | INC fs_load_addr_hi ; Handle page crossing in pointer | |||||||
| 854A | .print_next_char←1← 8546 BNE | |||||||
| LDA (fs_load_addr),y ; Load next byte from inline string | ||||||||
| 854C | BMI filev_attrib_code_check ; Bit 7 set? Done — this byte is the next opcode | |||||||
| 854E | JSR osasci ; Write character | |||||||
| 8551 | BNE print_inline_char ; Continue printing loop | |||||||
| 8553 | .filev_attrib_code_check←1← 854C BMI | |||||||
| JMP (fs_load_addr) ; Jump to address of high-bit byte (resumes code after string) | ||||||||
Skip leading spaces in parameter blockAdvances Y past space characters in (fs_options),Y. Returns with the first non-space character in A. Sets carry if the character is >= 'A' (alphabetic). |
|
| 8556 | .skip_spaces←3← 855B BEQ← 8C0D JSR← 8D07 JSR |
| LDA (fs_options),y ; Load character from parameter string | |
| 8558 | INY ; Advance to next character |
| 8559 | CMP #&20 ; Compare against space (ASCII &20) |
| 855B | BEQ skip_spaces ; Space found: keep scanning |
| 855D | DEY ; Back up one (first non-space char) |
| 855E | CMP #&41 ; Compare against 'A' for case flag |
| 8560 | RTS ; Return: A=char, C set if >= 'A' |
Parse decimal number from (fs_options),Y (DECIN)Reads ASCII digits and accumulates in &B2 (fs_load_addr_2). Multiplication by 10 uses the identity: n*10 = n*8 + n*2, computed as ASL &B2 (x2), then A = &B2*4 via two ASLs, then ADC &B2 gives x10. Terminates on "." (pathname separator), control chars, or space. The delimiter handling was revised to support dot-separated path components (e.g. "1.$.PROG", on_entry={"y": "offset into (fs_options) buffer"}, on_exit={"a": "parsed value (accumulated in &B2)", "x": "initial A value (saved by TAX)", "y": "offset past last digit parsed"}) >= &40 (any letter), but the revision allows numbers followed by dots.
|
|||||||||||
| 8561 | .parse_decimal←2← 8D0E JSR← 8D14 JSR | ||||||||||
| TAX ; Save A in X for caller | |||||||||||
| 8562 | LDA #0 ; Zero accumulator | ||||||||||
| 8564 | STA fs_load_addr_2 ; Clear accumulator workspace | ||||||||||
| 8566 | .scan_decimal_digit←1← 8583 BNE | ||||||||||
| LDA (fs_options),y ; Load next char from buffer | |||||||||||
| 8568 | CMP #&40 ; Letter or above? | ||||||||||
| 856A | BCS no_dot_exit ; Yes: not a digit, done | ||||||||||
| 856C | CMP #&2e ; Dot separator? | ||||||||||
| 856E | BEQ parse_decimal_rts ; Yes: exit with C=1 (dot found) | ||||||||||
| 8570 | BMI no_dot_exit ; Control char or space: done | ||||||||||
| 8572 | AND #&0f ; Mask ASCII digit to 0-9 | ||||||||||
| 8574 | STA fs_load_addr_3 ; Save new digit | ||||||||||
| 8576 | ASL fs_load_addr_2 ; Running total * 2 | ||||||||||
| 8578 | LDA fs_load_addr_2 ; A = running total * 2 | ||||||||||
| 857A | ASL ; A = running total * 4 | ||||||||||
| 857B | ASL ; A = running total * 8 | ||||||||||
| 857C | ADC fs_load_addr_2 ; + total*2 = total * 10 | ||||||||||
| 857E | ADC fs_load_addr_3 ; + digit = total*10 + digit | ||||||||||
| 8580 | STA fs_load_addr_2 ; Store new running total | ||||||||||
| 8582 | INY ; Advance to next char | ||||||||||
| 8583 | BNE scan_decimal_digit ; Loop (always: Y won't wrap to 0) | ||||||||||
| 8585 | .no_dot_exit←2← 856A BCS← 8570 BMI | ||||||||||
| CLC ; No dot found: C=0 | |||||||||||
| 8586 | .parse_decimal_rts←1← 856E BEQ | ||||||||||
| LDA fs_load_addr_2 ; Return result in A | |||||||||||
| 8588 | RTS ; Return with result in A | ||||||||||
Convert handle in A to bitmaskTransfers A to Y via TAY, then falls through to handle_to_mask_clc to clear carry and convert.
|
|||||||||||
| 8589 | .handle_to_mask_a←3← 83EE JSR← 8A06 JSR← 8ECD JSR | ||||||||||
| TAY ; Handle number to Y for conversion | |||||||||||
| fall through ↓ | |||||||||||
Convert handle to bitmask (carry cleared)Clears carry to ensure handle_to_mask converts unconditionally. Falls through to handle_to_mask.
|
|||||||||||
| 858A | .handle_to_mask_clc←3← 83AD JSR← 8823 JSR← 88ED JSR | ||||||||||
| CLC ; Force unconditional conversion | |||||||||||
| fall through ↓ | |||||||||||
Convert file handle to bitmask (Y2FS)Converts fileserver handles to single-bit masks segregated inside the BBC. NFS handles occupy the &20-&27 range (base HAND=&20), which cannot collide with local filing system or cassette handles -- the MOS routes OSFIND/OSBGET/OSBPUT to the correct filing system based on the handle value alone. The power-of-two encoding allows the EOF hint byte to track up to 8 files simultaneously with one bit per file, and enables fast set operations (ORA to add, EOR to toggle, AND to test) without loops. Handle 0 passes through unchanged (means "no file", on_entry={"y": "handle number", "c": "0: convert, 1 with Y=0: skip, 1 with Y!=0: convert"}, on_exit={"a": "preserved", "x": "preserved", "y": "bitmask (single bit set) or &FF if handle invalid"}) has a built-in validity check: if the handle is out of range, the repeated ASL shifts all bits out, leaving A=0, which is converted to Y=&FF as a sentinel -- bad handles fail gracefully rather than indexing into garbage. Callers needing to move the handle from A use handle_to_mask_a; callers needing carry cleared use handle_to_mask_clc.
|
|||||||||||||
| 858B | .handle_to_mask←1← 894E JSR | ||||||||||||
| PHA ; Save A (will be restored on exit) | |||||||||||||
| 858C | TXA ; Save X (will be restored on exit) | ||||||||||||
| 858D | PHA ; (second half of X save) | ||||||||||||
| 858E | TYA ; A = handle from Y | ||||||||||||
| 858F | BCC y2fsl5 ; C=0: always convert | ||||||||||||
| 8591 | BEQ handle_mask_exit ; C=1 and Y=0: skip (handle 0 = none) | ||||||||||||
| 8593 | .y2fsl5←1← 858F BCC | ||||||||||||
| SEC ; C=1 and Y!=0: convert | |||||||||||||
| 8594 | SBC #&1f ; A = handle - &1F (1-based bit position) | ||||||||||||
| 8596 | TAX ; X = shift count | ||||||||||||
| 8597 | LDA #1 ; Start with bit 0 set | ||||||||||||
| 8599 | .y2fsl2←1← 859B BNE | ||||||||||||
| ASL ; Shift bit left | |||||||||||||
| 859A | DEX ; Count down | ||||||||||||
| 859B | BNE y2fsl2 ; Loop until correct position | ||||||||||||
| 859D | ROR ; Undo final extra shift | ||||||||||||
| 859E | TAY ; Y = resulting bitmask | ||||||||||||
| 859F | BNE handle_mask_exit ; Non-zero: valid mask, skip to exit | ||||||||||||
| 85A1 | DEY ; Zero: invalid handle, set Y=&FF | ||||||||||||
| 85A2 | .handle_mask_exit←2← 8591 BEQ← 859F BNE | ||||||||||||
| PLA ; Restore X | |||||||||||||
| 85A3 | TAX ; Restore X from stack | ||||||||||||
| 85A4 | PLA ; Restore A | ||||||||||||
| 85A5 | RTS ; Return with mask in X | ||||||||||||
Convert bitmask to handle number (FS2A)Inverse of Y2FS. Converts from the power-of-two FS format back to a sequential handle number by counting right shifts until A=0. Adds &1E to convert the 1-based bit position to a handle number (handles start at &1F+1 = &20). Used when receiving handle values from the fileserver in reply packets.
|
|||||||||||
| 85A6 | .mask_to_handle←2← 8981 JSR← 8EE7 JSR | ||||||||||
| LDX #0 ; X = 0 (bit position counter) | |||||||||||
| 85A8 | .fs2al1←1← 85AA BNE | ||||||||||
| INX ; Count this bit position | |||||||||||
| 85A9 | LSR ; Shift mask right; C=0 when done | ||||||||||
| 85AA | BNE fs2al1 ; Loop until all bits shifted out | ||||||||||
| 85AC | TXA ; A = X = &1F + bit position = handle | ||||||||||
| 85AD | ADC #&1e ; Add &1E+C(=0) = &1E; handle=&1F+pos | ||||||||||
| 85AF | RTS ; Return with A=handle number | ||||||||||
Print byte as 3-digit decimal numberPrints A as a decimal number using repeated subtraction for each digit position (100, 10, 1). Leading zeros are printed (no suppression). Used to display station numbers.
|
|||||||||||
| 85B0 | .print_decimal←2← 81FC JSR← 8C28 JSR | ||||||||||
| TAY ; Y=dividend, A=100: hundreds digit | |||||||||||
| 85B1 | LDA #&64 ; Print hundreds digit | ||||||||||
| 85B3 | JSR print_decimal_digit ; A=10: tens divisor | ||||||||||
| 85B6 | LDA #&0a ; Print tens digit | ||||||||||
| 85B8 | JSR print_decimal_digit ; Transfer remainder to A | ||||||||||
| 85BB | LDA #1 ; Convert to ASCII and print | ||||||||||
| fall through ↓ | |||||||||||
Print one decimal digit by repeated subtractionDivides Y by A using repeated subtraction. Prints the quotient as an ASCII digit ('0'-'9'). Returns with the remainder in Y. X starts at &2F ('0'-1) and increments once per subtraction, giving the ASCII digit directly.
|
|||||||||
| 85BD | .print_decimal_digit←2← 85B3 JSR← 85B8 JSR | ||||||||
| STA fs_error_ptr ; Store divisor in temporary | |||||||||
| 85BF | TYA ; Transfer dividend (Y) to A | ||||||||
| 85C0 | LDX #&2f ; X=&2F: ASCII '0'-1 (loop init) | ||||||||
| 85C2 | SEC ; Set carry for subtraction | ||||||||
| 85C3 | .decimal_divide_loop←1← 85C6 BCS | ||||||||
| INX ; Increment digit (ASCII '0'..'9') | |||||||||
| 85C4 | SBC fs_error_ptr ; Subtract divisor from remainder | ||||||||
| 85C6 | BCS decimal_divide_loop ; Carry set: subtract again | ||||||||
| 85C8 | ADC fs_error_ptr ; Add back divisor (undo last SBC) | ||||||||
| 85CA | TAY ; Remainder to Y for next digit | ||||||||
| 85CB | TXA ; Quotient digit (X) to A for print | ||||||||
| 85CC | .print_via_osasci←1← 85FF BNE | ||||||||
| JMP osasci ; Write character | |||||||||
Compare two 4-byte addressesCompares bytes at &B0-&B3 against &B4-&B7 using EOR. Used by the OSFILE save handler to compare the current transfer address (&C8-&CB, copied to &B0) against the end address (&B4-&B7) during multi-block file data transfers.
|
||||||||
| 85CF | .compare_addresses←2← 8717 JSR← 87CA JSR | |||||||
| LDX #4 ; Compare 4 bytes (index 4,3,2,1) | ||||||||
| 85D1 | .compare_addr_byte←1← 85D8 BNE | |||||||
| LDA addr_work,x ; Load byte from first address | ||||||||
| 85D3 | EOR fs_load_addr_3,x ; XOR with corresponding byte | |||||||
| 85D5 | BNE return_compare ; Mismatch: Z=0, return unequal | |||||||
| 85D7 | DEX ; Next byte | |||||||
| 85D8 | BNE compare_addr_byte ; Continue comparing | |||||||
| 85DA | .return_compare←1← 85D5 BNE | |||||||
| RTS ; Return with Z flag result | ||||||||
| 85DB | .fscv_7_read_handles | |||||||
| LDX #&20 ; X=first handle (&20) | ||||||||
| 85DD | LDY #&27 ; Y=last handle (&27) | |||||||
| 85DF | .return_fscv_handles←1← 8604 BEQ | |||||||
| RTS ; Return (FSCV 7 read handles) | ||||||||
Clear bit(s) in FS flags (&0E07)Inverts A (EOR #&FF), then ANDs into fs_work_0e07 to clear the specified bits. Falls through to set_fs_flag to store.
|
|||||||
| 85E0 | .clear_fs_flag←3← 8497 JSR← 883E JSR← 8A82 JSR | ||||||
| EOR #&ff ; Invert A (NOT mask) | |||||||
| 85E2 | AND fs_eof_flags ; AND inverted mask to clear bits | ||||||
| fall through ↓ | |||||||
Set bit(s) in FS flags (&0E07)ORs A into fs_work_0e07 (EOF hint byte). Each bit represents one of up to 8 open file handles. When clear, the file is definitely NOT at EOF. When set, the fileserver must be queried to confirm EOF status. This negative-cache optimisation avoids expensive network round-trips for the common case. The hint is cleared when the file pointer is updated (since seeking away from EOF invalidates the hint) and set after BGET/OPEN/EOF operations that might have reached the end.
|
|||||||
| 85E5 | .set_fs_flag←5← 849E JSR← 892A JSR← 8976 JSR← 899D JSR← 8A85 JSR | ||||||
| ORA fs_eof_flags ; OR mask into EOF flags | |||||||
| 85E8 | STA fs_eof_flags ; Store updated EOF flags | ||||||
| 85EB | RTS ; Return to caller | ||||||
Print byte as two hex digitsPrints the high nibble first (via 4× LSR), then the low nibble. Each nibble is converted to ASCII '0'-'9' or 'A'-'F' and output via OSASCI.
|
|||||||||
| 85EC | .print_hex←2← 863A JSR← 8C6C JSR | ||||||||
| PHA ; Save full byte on stack | |||||||||
| 85ED | LSR ; Shift high nibble to low position | ||||||||
| 85EE | LSR ; Continue shift (4 LSRs total) | ||||||||
| 85EF | LSR ; Continue shift | ||||||||
| 85F0 | LSR ; High nibble now in bits 0-3 | ||||||||
| 85F1 | JSR print_hex_nibble ; Print high nibble as hex | ||||||||
| 85F4 | PLA ; Restore original byte | ||||||||
| 85F5 | AND #&0f ; Mask to low nibble | ||||||||
| 85F7 | .print_hex_nibble←1← 85F1 JSR | ||||||||
| ORA #&30 ; Convert to ASCII digit ('0'-'9') | |||||||||
| 85F9 | CMP #&3a ; Compare against ':' (past '9'?) | ||||||||
| 85FB | BCC print_hex_digit ; Digit 0-9: skip A-F adjustment | ||||||||
| 85FD | ADC #6 ; Add 7 to get ASCII 'A'-'F' | ||||||||
| 85FF | .print_hex_digit←2← 85FB BCC← 8643 BNE | ||||||||
| BNE print_via_osasci ; ALWAYS branch to print character | |||||||||
Print file catalogue lineDisplays a formatted catalogue entry: filename (padded to 12 chars with spaces), load address (4 hex bytes at offset 5-2), exec address (4 hex bytes at offset 9-6), and file length (3 hex bytes at offset &0C-&0A), followed by a newline. Data is read from (fs_crc_lo) for the filename and from (fs_options) for the numeric fields. Returns immediately if fs_messages_flag is zero (no info available). |
|
| 8601 | .print_file_info←2← 8704 JSR← 8784 JSR |
| LDY fs_messages_flag ; Check if file info available | |
| 8604 | BEQ return_fscv_handles ; No info available: return |
| 8606 | LDY #0 ; Y=0: start of filename string |
| 8608 | .print_filename_loop←1← 8616 BNE |
| LDA (fs_crc_lo),y ; Load filename character | |
| 860A | CMP #&0d ; CR: end of filename |
| 860C | BEQ pad_filename_spaces ; CR: pad rest of filename field |
| 860E | CMP #&20 ; Also end name on space character |
| 8610 | BEQ pad_filename_spaces ; Space: also ends filename |
| 8612 | JSR osasci ; Write character |
| 8615 | INY ; Advance to next filename byte |
| 8616 | BNE print_filename_loop ; Loop until all chars printed |
| 8618 | .pad_filename_spaces←3← 860C BEQ← 8610 BEQ← 861E BCC |
| JSR print_space ; Print padding space | |
| 861B | INY ; Advance past filename position |
| 861C | CPY #&0c ; Pad to 12 chars wide |
| 861E | BCC pad_filename_spaces ; Continue padding if < 12 chars |
| 8620 | LDY #5 ; Y=5: high byte of load address |
| 8622 | JSR print_hex_bytes ; Print load address as 2 hex bytes Print load address as 2 hex bytes |
| 8625 | JSR print_exec_and_len ; Print exec address and length |
| 8628 | JMP osnewl ; Write newline (characters 10 and 13) |
| 862B | .print_exec_and_len←1← 8625 JSR |
| LDY #9 ; Y=9: exec address offset | |
| 862D | JSR print_hex_bytes ; Print exec address bytes |
| 8630 | LDY #&0c ; Y=&0C: file length offset |
| 8632 | LDX #3 ; X=3: print 3 bytes for file length |
| 8634 | BNE num01 ; ALWAYS branch |
| 8636 | .print_hex_bytes←2← 8622 JSR← 862D JSR |
| LDX #4 ; X=4: print 4 bytes for address | |
| 8638 | .num01←2← 8634 BNE← 863F BNE |
| LDA (fs_options),y ; Load address/length byte | |
| 863A | JSR print_hex ; Print as 2 hex digits |
| 863D | DEY ; Move to next lower address byte |
| 863E | DEX ; Decrement byte counter |
| 863F | BNE num01 ; Loop for remaining hex bytes |
| 8641 | .print_space←2← 8618 JSR← 8D5D JSR |
| LDA #&20 ; A=space: separator character | |
| 8643 | BNE print_hex_digit ; ALWAYS branch |
Set up TX pointer to control block at &00C0Points net_tx_ptr to &00C0 where the TX control block has been built by init_tx_ctrl_block. Falls through to tx_poll_ff to initiate transmission with full retry. |
|
| 8645 | .setup_tx_ptr_c0←2← 837D JSR← 880A JSR |
| LDX #&c0 ; X=&C0: TX control block at &00C0 | |
| 8647 | STX net_tx_ptr ; Set TX pointer lo |
| 8649 | LDX #0 ; X=0: page zero |
| 864B | STX net_tx_ptr_hi ; Set TX pointer hi byte to page 0 |
| fall through ↓ | |
Transmit and poll for result (full retry)Sets A=&FF (retry count) and Y=&60 (timeout parameter). Falls through to tx_poll_core. |
|
| 864D | .tx_poll_ff←3← 9002 JSR← 905C JSR← 925C JSR |
| LDA #&ff ; A=&FF: full retry count | |
| 864F | .tx_poll_timeout←1← 8FA5 JSR |
| LDY #&60 ; Y=timeout parameter (&60 = standard) | |
| fall through ↓ | |
Core transmit and poll routine (XMIT)Claims the TX semaphore (tx_ctrl_status) via ASL -- a busy-wait spinlock where carry=0 means the semaphore is held by another operation. Only after claiming the semaphore is the TX pointer copied to nmi_tx_block, ensuring the low-level transmit code sees a consistent pointer. Then calls the ADLC TX setup routine and polls the control byte for completion: bit 7 set = still busy (loop) bit 6 set = error (check escape or report) bit 6 clear = success (clean return) On error, checks for escape condition and handles retries. Two entry points: setup_tx_ptr_c0 (&8645) always uses the standard TXCB; tx_poll_core (&8651) is general-purpose.
|
|||||||||||||
| 8651 | .tx_poll_core←1← 8FE7 JSR | ||||||||||||
| PHA ; Save retry count on stack | |||||||||||||
| 8652 | TYA ; Transfer timeout to A | ||||||||||||
| 8653 | PHA ; Save timeout on stack | ||||||||||||
| 8654 | LDX #0 ; X=0 for (net_tx_ptr,X) indirect | ||||||||||||
| 8656 | LDA (net_tx_ptr,x) ; Load TXCB byte 0 (control/status) | ||||||||||||
| 8658 | .tx_retry←1← 868B BEQ | ||||||||||||
| STA (net_tx_ptr,x) ; Write control byte to start TX | |||||||||||||
| 865A | PHA ; Save control byte for retry | ||||||||||||
| 865B | .tx_semaphore_spin←1← 865E BCC | ||||||||||||
| ASL tx_ctrl_status ; Test TX semaphore (C=1 when free) | |||||||||||||
| 865E | BCC tx_semaphore_spin ; Spin until semaphore released | ||||||||||||
| 8660 | LDA net_tx_ptr ; Copy TX ptr lo to NMI block | ||||||||||||
| 8662 | STA nmi_tx_block ; Store for NMI handler access | ||||||||||||
| 8664 | LDA net_tx_ptr_hi ; Copy TX ptr hi to NMI block | ||||||||||||
| 8666 | STA nmi_tx_block_hi ; Store for NMI handler access | ||||||||||||
| 8668 | JSR trampoline_tx_setup ; Initiate ADLC TX via trampoline | ||||||||||||
| 866B | .poll_txcb_status←1← 866D BMI | ||||||||||||
| LDA (net_tx_ptr,x) ; Poll TXCB byte 0 for completion | |||||||||||||
| 866D | BMI poll_txcb_status ; Bit 7 set: still busy, keep polling | ||||||||||||
| 866F | ASL ; Shift bit 6 into bit 7 (error flag) | ||||||||||||
| 8670 | BPL tx_success ; Bit 6 clear: success, clean return | ||||||||||||
| 8672 | ASL ; Shift bit 5 into carry | ||||||||||||
| 8673 | BEQ tx_not_listening ; Zero: fatal error, no escape | ||||||||||||
| 8675 | JSR check_escape ; Check for user escape condition | ||||||||||||
| 8678 | PLA ; Discard saved control byte | ||||||||||||
| 8679 | TAX ; Save to X for retry delay | ||||||||||||
| 867A | PLA ; Restore timeout parameter | ||||||||||||
| 867B | TAY ; Back to Y | ||||||||||||
| 867C | PLA ; Restore retry count | ||||||||||||
| 867D | BEQ tx_not_listening ; No retries left: report error | ||||||||||||
| 867F | SBC #1 ; Decrement retry count | ||||||||||||
| 8681 | PHA ; Save updated retry count | ||||||||||||
| 8682 | TYA ; Timeout to A for delay | ||||||||||||
| 8683 | PHA ; Save timeout parameter | ||||||||||||
| 8684 | TXA ; Control byte for delay duration | ||||||||||||
| 8685 | .delay_1ms←2← 8686 BNE← 8689 BNE | ||||||||||||
| DEX ; Inner delay loop | |||||||||||||
| 8686 | BNE delay_1ms ; Spin until X=0 | ||||||||||||
| 8688 | DEY ; Outer delay loop | ||||||||||||
| 8689 | BNE delay_1ms ; Continue delay | ||||||||||||
| 868B | BEQ tx_retry ; ALWAYS branch | ||||||||||||
| 868D | .tx_not_listening←2← 8673 BEQ← 867D BEQ | ||||||||||||
| TAX ; Save error code in X | |||||||||||||
| 868E | JMP nlistn ; Report 'Not listening' error | ||||||||||||
| 8691 | .tx_success←1← 8670 BPL | ||||||||||||
| PLA ; Discard saved control byte | |||||||||||||
| 8692 | PLA ; Discard timeout parameter | ||||||||||||
| 8693 | PLA ; Discard retry count | ||||||||||||
| 8694 | RTS ; Return (success) | ||||||||||||
FILEV handler (OSFILE entry point)Saves A/X/Y, copies the filename pointer from the parameter block to os_text_ptr, then uses GSINIT/GSREAD to parse the filename into &0FC5+. Sets fs_crc_lo/hi to point at the parsed filename buffer. Dispatches by function code A: A=&FF: load file (send_fs_examine at &86D1) A=&00: save file (filev_save at &8747) A=&01-&06: attribute operations (filev_attrib_dispatch at &8845) Other: restore_args_return (unsupported, no-op)
|
|||||||||||||||
| 8695 | .filev_handler | ||||||||||||||
| JSR save_fscv_args ; Load FILEV function code from A | |||||||||||||||
| fall through ↓ | |||||||||||||||
Copy filename pointer to os_text_ptr and parseCopies the 2-byte filename pointer from (fs_options),Y into os_text_ptr (&F2/&F3), then falls through to parse_filename_gs to parse the filename via GSINIT/GSREAD into the &0E30 buffer. |
|
| 8698 | .copy_filename_ptr |
| LDY #1 ; Y=1: copy 2 bytes (high then low) | |
| 869A | .file1←1← 86A0 BPL |
| LDA (fs_options),y ; Load filename ptr from control block | |
| 869C | STA os_text_ptr,y ; Store to MOS text pointer (&F2/&F3) |
| 869F | DEY ; Next byte (descending) |
| 86A0 | BPL file1 ; Loop for both bytes |
| 86A2 | INY ; Y=1: offset past filename pointer |
| 86A3 | LDX #&ff ; X=&FF: parse all characters |
| 86A5 | CLC ; C=0: normal string parse entry |
| 86A6 | JSR gsinit ; Init string parsing via GSINIT |
| 86A9 | .quote1←1← 86B2 BCC |
| JSR gsread ; Read next character via GSREAD | |
| 86AC | BCS tx_result_check ; C=1 from GSREAD: end of string reached |
| 86AE | INX ; Advance buffer index |
| 86AF | STA l0fc5,x ; Store parsed character to &0E30+X |
| 86B2 | BCC quote1 ; ALWAYS loop (GSREAD clears C on success) ALWAYS branch |
| 86B4 | .tx_result_check←1← 86AC BCS |
| LDA #&0d ; CR = &0D | |
| 86B6 | STA l0fc6,x ; Store CR terminator at end of string |
| 86B9 | LDA #&c5 ; Point fs_crc_lo/hi at &0E30 parse buffer |
| 86BB | STA fs_crc_lo ; fs_crc_lo = &30 |
| 86BD | LDA #&0f ; fs_crc_hi = &0E → buffer at &0E30 |
| 86BF | STA fs_crc_hi ; Store high byte |
| 86C1 | LDA fs_last_byte_flag ; Recover function code from saved A |
| 86C3 | BPL saveop ; A >= 0: save (&00) or attribs (&01-&06) |
| 86C5 | CMP #&ff ; A=&FF? Only &FF is valid for load |
| 86C7 | BEQ loadop ; A=&FF: branch to load path |
| 86C9 | JMP restore_args_return ; Unknown negative code: no-op return |
| 86CC | .loadop←1← 86C7 BEQ |
| JSR copy_filename ; Copy parsed filename to cmd buffer | |
| 86CF | LDY #2 ; Y=2: FS function code offset |
| fall through ↓ | |
Send FS examine commandSends FS command &03 (FCEXAM: examine file) to the fileserver. Sets &0F02=&03 and error pointer to '*'. Called for OSFILE &FF (load file) with the filename already in the command buffer. The FS reply contains load/exec addresses and file length which are used to set up the data transfer. The header URD field is repurposed to carry the Econet data port number (PLDATA=&92) for the subsequent block data transfer.
|
||||||
| 86D1 | .send_fs_examine←1← 8D91 JSR | |||||
| LDA #&92 ; Port &92 = PLDATA (data transfer port) | ||||||
| 86D3 | STA fs_cmd_urd ; Overwrite URD field with data port number | |||||
| 86D6 | LDA #&2a ; A=&2A: error ptr for retry | |||||
| 86D8 | JSR prepare_cmd_clv ; Build FS header (V=1: CLV path) | |||||
| 86DB | LDY #6 ; Y=6: param block byte 6 | |||||
| 86DD | LDA (fs_options),y ; Byte 6: use file's own load address? | |||||
| 86DF | BNE lodfil ; Non-zero: use FS reply address (lodfil) | |||||
| 86E1 | JSR copy_load_addr_from_params ; Zero: copy caller's load addr first | |||||
| 86E4 | JSR copy_reply_to_params ; Then copy FS reply to param block | |||||
| 86E7 | BCC skip_lodfil ; Carry clear from prepare_cmd_clv: skip lodfil | |||||
| 86E9 | .lodfil←1← 86DF BNE | |||||
| JSR copy_reply_to_params ; Copy FS reply addresses to param block | ||||||
| 86EC | JSR copy_load_addr_from_params ; Then copy load addr from param block | |||||
| 86EF | .skip_lodfil←1← 86E7 BCC | |||||
| LDY #4 ; Compute end address = load + file length | ||||||
| 86F1 | .copy_load_end_addr←1← 86FC BNE | |||||
| LDA fs_load_addr,x ; Load address byte | ||||||
| 86F3 | STA txcb_end,x ; Store as current transfer position | |||||
| 86F5 | ADC fs_file_len,x ; Add file length byte | |||||
| 86F8 | STA fs_work_4,x ; Store as end position | |||||
| 86FA | INX ; Next address byte | |||||
| 86FB | DEY ; Decrement byte counter | |||||
| 86FC | BNE copy_load_end_addr ; Loop for all 4 address bytes | |||||
| 86FE | SEC ; Adjust high byte for 3-byte length overflow | |||||
| 86FF | SBC fs_file_len_3 ; Subtract 4th length byte from end addr | |||||
| 8702 | STA fs_work_7 ; Store adjusted end address high byte | |||||
| 8704 | JSR print_file_info ; Display file info after FS reply | |||||
| 8707 | JSR send_data_blocks ; Transfer file data in &80-byte blocks | |||||
| 870A | LDX #2 ; Copy 3-byte file length to FS reply cmd buffer | |||||
| 870C | .floop←1← 8713 BPL | |||||
| LDA fs_file_len_3,x ; Load file length byte | ||||||
| 870F | STA fs_cmd_data,x ; Store in FS command data buffer | |||||
| 8712 | DEX ; Next byte (count down) | |||||
| 8713 | BPL floop ; Loop for 3 bytes (X=2,1,0) | |||||
| 8715 | BMI set_star_reply_port ; ALWAYS branch | |||||
Send file data in multi-block chunksRepeatedly sends &80-byte (128-byte) blocks of file data to the fileserver using command &7F. Compares current address (&C8-&CB) against end address (&B4-&B7) via compare_addresses, and loops until the entire file has been transferred. Each block is sent via send_to_fs_star. |
|
| 8717 | .send_data_blocks←2← 8707 JSR← 8A71 JSR |
| JSR compare_addresses ; Compare two 4-byte addresses | |
| 871A | BEQ return_lodchk ; Addresses match: transfer complete |
| 871C | LDA #&92 ; Port &92 for data block transfer |
| 871E | STA txcb_port ; Store port to TXCB command byte |
| 8720 | .send_block_loop←1← 873C BNE |
| LDX #3 ; Set up next &80-byte block for transfer | |
| 8722 | .copy_block_addrs←1← 872B BPL |
| LDA txcb_end,x ; Swap: current addr -> source, end -> current | |
| 8724 | STA txcb_start,x ; Source addr = current position |
| 8726 | LDA fs_work_4,x ; Load end address byte |
| 8728 | STA txcb_end,x ; Dest = end address (will be clamped) |
| 872A | DEX ; Next address byte |
| 872B | BPL copy_block_addrs ; Loop for all 4 bytes |
| 872D | LDA #&7f ; Command &7F = data block transfer |
| 872F | STA txcb_ctrl ; Store to TXCB control byte |
| 8731 | JSR send_to_fs_star ; Send this block to the fileserver |
| 8734 | LDY #3 ; Y=3: compare 4 bytes (3..0) |
| 8736 | .lodchk←1← 873F BPL |
| LDA txcb_end,y ; Compare current vs end address (4 bytes) | |
| 8739 | EOR fs_work_4,y ; XOR with end address byte |
| 873C | BNE send_block_loop ; Not equal: more blocks to send |
| 873E | DEY ; Next byte |
| 873F | BPL lodchk ; Loop for all 4 address bytes |
| 8741 | .return_lodchk←1← 871A BEQ |
| RTS ; All equal: transfer complete | |
| 8742 | .saveop←1← 86C3 BPL |
| BEQ filev_save ; A=0: SAVE handler | |
| 8744 | JMP filev_attrib_dispatch ; A!=0: attribute dispatch (A=1-6) |
OSFILE save handler (A=&00)Copies 4-byte load/exec/length addresses from the parameter block to the FS command buffer, along with the filename. Sends FS command &91 with function &14 to initiate the save, then calls print_file_info to display the filename being saved. Handles both host and Tube-based data sources. When receiving the save acknowledgement, the RX low pointer is incremented by 1 to skip the command code (CC) byte, which indicates the FS type and must be preserved. N.B. this assumes the RX buffer does not cross a page boundary. |
|
| 8747 | .filev_save←1← 8742 BEQ |
| LDX #4 ; Process 4 address bytes (load/exec/start/end) | |
| 8749 | LDY #&0e ; Y=&0E: start from end-address in param block |
| 874B | .savsiz←1← 8765 BNE |
| LDA (fs_options),y ; Read end-address byte from param block | |
| 874D | STA port_ws_offset,y ; Save to port workspace for transfer setup |
| 8750 | JSR sub_4_from_y ; Y = Y-4: point to start-address byte |
| 8753 | SBC (fs_options),y ; end - start = transfer length byte |
| 8755 | STA fs_cmd_csd,y ; Store length byte in FS command buffer |
| 8758 | PHA ; Save length byte for param block restore |
| 8759 | LDA (fs_options),y ; Read corresponding start-address byte |
| 875B | STA port_ws_offset,y ; Save to port workspace |
| 875E | PLA ; Restore length byte from stack |
| 875F | STA (fs_options),y ; Replace param block entry with length |
| 8761 | JSR add_5_to_y ; Y = Y+5: advance to next address group |
| 8764 | DEX ; Decrement address byte counter |
| 8765 | BNE savsiz ; Loop for all 4 address bytes |
| 8767 | LDY #9 ; Copy load/exec addresses to FS command buffer |
| 8769 | .copy_save_params←1← 876F BNE |
| LDA (fs_options),y ; Read load/exec address byte from params | |
| 876B | STA fs_cmd_csd,y ; Copy to FS command buffer |
| 876E | DEY ; Next byte (descending) |
| 876F | BNE copy_save_params ; Loop for bytes 9..1 |
| 8771 | LDA #&91 ; Port &91 for save command |
| 8773 | STA fs_cmd_urd ; Overwrite URD field with port number |
| 8776 | STA fs_error_ptr ; Save port &91 for flow control ACK |
| 8778 | LDX #&0b ; Append filename at offset &0B in cmd buffer |
| 877A | JSR copy_string_to_cmd ; Append filename to cmd buffer at offset X |
| 877D | LDY #1 ; Y=1: function code for save |
| 877F | LDA #&14 ; A=&14: FS function code for SAVE |
| 8781 | JSR prepare_cmd_clv ; Build header and send FS save command |
| 8784 | JSR print_file_info ; Display filename being saved |
| 8787 | .save_csd_display |
| LDA fs_cmd_data ; Load CSD from FS reply | |
| 878A | JSR transfer_file_blocks ; Transfer file data blocks to server |
| 878D | .set_star_reply_port←1← 8715 BMI |
| LDA #&2a ; A=&2A: error ptr for FS retry | |
| 878F | STA fs_error_ptr ; Store error ptr for TX poll |
| 8791 | .send_fs_reply |
| JSR send_fs_reply_cmd ; Send FS reply acknowledgement | |
| 8794 | .skip_catalogue_msg |
| STX fs_reply_cmd ; Store reply command for attr decode | |
| 8797 | LDY #&0e ; Y=&0E: access byte offset in param block |
| 8799 | LDA fs_cmd_data ; Load access byte from FS reply |
| 879C | JSR decode_attribs_5bit ; Convert FS access to BBC attribute format |
| 879F | BEQ direct_attr_copy ; Z=1: first byte, use A directly |
| 87A1 | .copy_attr_loop←1← 87A9 BNE |
| LDA fs_reply_data,y ; Load attribute byte from FS reply | |
| 87A4 | .direct_attr_copy←1← 879F BEQ |
| STA (fs_options),y ; Store decoded access in param block | |
| 87A6 | INY ; Next attribute byte |
| 87A7 | CPY #&12 ; Copied all 4 bytes? (Y=&0E..&11) |
| 87A9 | BNE copy_attr_loop ; Loop for 4 attribute bytes |
| 87AB | JMP restore_args_return ; Restore A/X/Y and return to caller |
Copy load address from parameter blockCopies 4 bytes from (fs_options)+2..5 (the load address in the OSFILE parameter block) to &AE-&B3 (local workspace). |
|
| 87AE | .copy_load_addr_from_params←2← 86E1 JSR← 86EC JSR |
| LDY #5 ; Start at offset 5 (top of 4-byte addr) | |
| 87B0 | .lodrl1←1← 87B8 BCS |
| LDA (fs_options),y ; Read from parameter block | |
| 87B2 | STA work_ae,y ; Store to local workspace |
| 87B5 | DEY ; Next byte (descending) |
| 87B6 | CPY #2 ; Copy offsets 5,4,3,2 (4 bytes) |
| 87B8 | BCS lodrl1 ; Loop while Y >= 2 |
| 87BA | RTS ; Return |
Copy FS reply data to parameter blockCopies bytes from the FS command reply buffer (&0F02+) into the parameter block at (fs_options)+2..13. Used to fill in the OSFILE parameter block with information returned by the fileserver.
|
||||
| 87BB | .copy_reply_to_params←2← 86E4 JSR← 86E9 JSR | |||
| LDY #&0d ; Start at offset &0D (top of range) | ||||
| 87BD | TXA ; First store uses X (attrib byte) | |||
| 87BE | .lodrl2←1← 87C6 BCS | |||
| STA (fs_options),y ; Write to parameter block | ||||
| 87C0 | LDA fs_cmd_urd,y ; Read next byte from reply buffer | |||
| 87C3 | DEY ; Next byte (descending) | |||
| 87C4 | CPY #2 ; Copy offsets &0D down to 2 | |||
| 87C6 | BCS lodrl2 ; Loop until offset 2 reached | |||
| 87C8 | RTS ; Return to caller | |||
Multi-block file data transferManages the transfer of file data in chunks between the local machine and the fileserver. Entry conditions: WORK (&B0-&B3) and WORK+4 (&B4-&B7) hold the low and high addresses of the data being sent/received. Sets up source (&C4-&C7) and destination (&C8-&CB) from the FS reply, sends &80-byte (128-byte) blocks with command &91, and continues until all data has been transferred. Handles address overflow and Tube co-processor transfers. For SAVE, WORK+8 holds the port on which to receive byte-level ACKs for each data block (flow control). |
|
| 87C9 | .transfer_file_blocks←2← 878A JSR← 8A6C JSR |
| PHA ; Save FS command byte on stack | |
| 87CA | JSR compare_addresses ; Compare two 4-byte addresses |
| 87CD | BEQ restore_ay_return ; Addresses equal: nothing to transfer |
| 87CF | .next_block←1← 881E BNE |
| LDX #0 ; X=0: clear hi bytes of block size | |
| 87D1 | LDY #4 ; Y=4: process 4 address bytes |
| 87D3 | STX fs_reply_cmd ; Clear block size hi byte 1 |
| 87D6 | STX fs_load_vector ; Clear block size hi byte 2 |
| 87D9 | CLC ; CLC for ADC in loop |
| 87DA | .block_addr_loop←1← 87E7 BNE |
| LDA fs_load_addr,x ; Source = current position | |
| 87DC | STA txcb_start,x ; Store source address byte |
| 87DE | ADC fs_func_code,x ; Add block size to current position |
| 87E1 | STA txcb_end,x ; Store dest address byte |
| 87E3 | STA fs_load_addr,x ; Advance current position |
| 87E5 | INX ; Next address byte |
| 87E6 | DEY ; Decrement byte counter |
| 87E7 | BNE block_addr_loop ; Loop for all 4 bytes |
| 87E9 | BCS clamp_dest_setup ; Carry: address overflowed, clamp |
| 87EB | SEC ; SEC for SBC in overshoot check |
| 87EC | .savchk←1← 87F4 BNE |
| LDA fs_load_addr,y ; Check if new pos overshot end addr | |
| 87EF | SBC fs_work_4,y ; Subtract end address byte |
| 87F2 | INY ; Next byte |
| 87F3 | DEX ; Decrement counter |
| 87F4 | BNE savchk ; Loop for 4-byte comparison |
| 87F6 | BCC send_block ; C=0: no overshoot, proceed |
| 87F8 | .clamp_dest_setup←1← 87E9 BCS |
| LDX #3 ; Overshot: clamp dest to end address | |
| 87FA | .clamp_dest_addr←1← 87FF BPL |
| LDA fs_work_4,x ; Load end address byte | |
| 87FC | STA txcb_end,x ; Replace dest with end address |
| 87FE | DEX ; Next byte |
| 87FF | BPL clamp_dest_addr ; Loop for all 4 bytes |
| 8801 | .send_block←1← 87F6 BCC |
| PLA ; Recover original FS command byte | |
| 8802 | PHA ; Re-push for next iteration |
| 8803 | PHP ; Save processor flags (C from cmp) |
| 8804 | STA txcb_port ; Store command byte in TXCB |
| 8806 | LDA #&80 ; 128-byte block size for data transfer |
| 8808 | STA txcb_ctrl ; Store size in TXCB control byte |
| 880A | JSR setup_tx_ptr_c0 ; Point TX ptr to &00C0; transmit |
| 880D | LDA fs_error_ptr ; ACK port for flow control |
| 880F | JSR init_tx_ctrl_port ; Set reply port for ACK receive |
| 8812 | PLP ; Restore flags (C=overshoot status) |
| 8813 | BCS restore_ay_return ; C=1: all data sent (overshot), done |
| 8815 | LDA #&91 ; Command &91 = data block transfer |
| 8817 | STA txcb_port ; Store command &91 in TXCB |
| 8819 | LDA #&2a ; A=&2A: error ptr for retry |
| 881B | JSR send_to_fs ; Transmit block and wait (BRIANX) |
| 881E | BNE next_block ; More blocks? Loop back |
| fall through ↓ | |
FSCV 1: EOF handlerChecks whether a file handle has reached end-of-file. Converts the handle via handle_to_mask_clc, tests the result against the EOF hint byte (&0E07). If the hint bit is clear, returns X=0 immediately (definitely not at EOF — no network call needed). If the hint bit is set, sends FS command &11 (FCEOF) to query the fileserver for definitive EOF status. Returns X=&FF if at EOF, X=&00 if not. This two-level check avoids an expensive network round-trip when the file is known to not be at EOF.
|
|||||||
| 8820 | .fscv_1_eof | ||||||
| PHA ; Save A (function code) | |||||||
| 8821 | STY fs_block_offset ; Save handle for EOF check | ||||||
| 8823 | JSR handle_to_mask_clc ; Convert handle to bitmask in A | ||||||
| 8826 | TYA ; Y = handle bitmask from conversion | ||||||
| 8827 | AND fs_eof_flags ; Local hint: is EOF possible for this handle? | ||||||
| 882A | TAX ; X = result of AND (0 = not at EOF) | ||||||
| 882B | BEQ restore_ay_return ; Hint clear: definitely not at EOF | ||||||
| 882D | PHA ; Save bitmask for clear_fs_flag | ||||||
| 882E | STY fs_cmd_data ; Handle byte in FS command buffer | ||||||
| 8831 | LDY #&11 ; Y=&11: FS function code FCEOF Y=function code for HDRFN | ||||||
| 8833 | LDX #1 ; X=preserved through header build | ||||||
| 8835 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 8838 | PLA ; Restore bitmask | ||||||
| 8839 | LDX fs_cmd_data ; FS reply: non-zero = at EOF | ||||||
| 883C | BNE restore_ay_return ; At EOF: skip flag clear | ||||||
| 883E | JSR clear_fs_flag ; Not at EOF: clear the hint bit | ||||||
| 8841 | .restore_ay_return←4← 87CD BEQ← 8813 BCS← 882B BEQ← 883C BNE | ||||||
| PLA ; Restore A | |||||||
| 8842 | LDY fs_block_offset ; Restore Y | ||||||
| 8844 | RTS ; Return; X=0 (not EOF) or X=&FF (EOF) | ||||||
FILEV attribute dispatch (A=1-6)Dispatches OSFILE operations by function code: A=1: write catalogue info (load/exec/length/attrs) — FS &14 A=2: write load address only A=3: write exec address only A=4: write file attributes A=5: read catalogue info, returns type in A — FS &12 A=6: delete named object — FS &14 (FCDEL) A>=7: falls through to restore_args_return (no-op) Each handler builds the appropriate FS command, sends it to the fileserver, and copies the reply into the parameter block. The control block layout uses dual-purpose fields: the 'data start' field doubles as 'length' and 'data end' doubles as 'protection' depending on whether reading or writing attrs.
|
|||||||
| 8845 | .filev_attrib_dispatch←1← 8744 JMP | ||||||
| STA fs_cmd_data ; Store function code in FS cmd buffer | |||||||
| 8848 | CMP #6 ; A=6? (delete) | ||||||
| 884A | BEQ cha6 ; Yes: jump to delete handler | ||||||
| 884C | BCS check_attrib_result ; A>=7: unsupported, fall through to return | ||||||
| 884E | CMP #5 ; A=5? (read catalogue info) | ||||||
| 8850 | BEQ cha5 ; Yes: jump to read info handler | ||||||
| 8852 | CMP #4 ; A=4? (write attributes only) | ||||||
| 8854 | BEQ cha4 ; Yes: jump to write attrs handler | ||||||
| 8856 | CMP #1 ; A=1? (write all catalogue info) | ||||||
| 8858 | BEQ get_file_protection ; Yes: jump to write-all handler | ||||||
| 885A | ASL ; A=2 or 3: convert to param block offset | ||||||
| 885B | ASL ; A*4: 2->8, 3->12 | ||||||
| 885C | TAY ; Y = A*4 | ||||||
| 885D | JSR sub_3_from_y ; Y = A*4 - 3 (load addr offset for A=2) | ||||||
| 8860 | LDX #3 ; X=3: copy 4 bytes | ||||||
| 8862 | .chalp1←1← 8869 BPL | ||||||
| LDA (fs_options),y ; Load address byte from param block | |||||||
| 8864 | STA fs_func_code,x ; Store to FS cmd data area | ||||||
| 8867 | DEY ; Next source byte (descending) | ||||||
| 8868 | DEX ; Next dest byte | ||||||
| 8869 | BPL chalp1 ; Loop for 4 bytes | ||||||
| 886B | LDX #5 ; X=5: data extent for filename copy | ||||||
| 886D | BNE copy_filename_to_cmd ; ALWAYS branch | ||||||
| 886F | .get_file_protection←1← 8858 BEQ | ||||||
| JSR decode_attribs_6bit ; A=1: encode protection from param block | |||||||
| 8872 | STA fs_file_attrs ; Store encoded attrs at &0F0E | ||||||
| 8875 | LDY #9 ; Y=9: source offset in param block | ||||||
| 8877 | LDX #8 ; X=8: dest offset in cmd buffer | ||||||
| 8879 | .chalp2←1← 8880 BNE | ||||||
| LDA (fs_options),y ; Load byte from param block | |||||||
| 887B | STA fs_cmd_data,x ; Store to FS cmd buffer | ||||||
| 887E | DEY ; Next source byte (descending) | ||||||
| 887F | DEX ; Next dest byte | ||||||
| 8880 | BNE chalp2 ; Loop until X=0 (8 bytes copied) | ||||||
| 8882 | LDX #&0a ; X=&0A: data extent past attrs+addrs | ||||||
| 8884 | .copy_filename_to_cmd←2← 886D BNE← 88A2 BNE | ||||||
| JSR copy_string_to_cmd ; Append filename to cmd buffer | |||||||
| 8887 | LDY #&13 ; Y=&13: fn code for FCSAVE (write attrs) | ||||||
| 8889 | BNE send_fs_cmd_v1 ; ALWAYS branch to send command ALWAYS branch | ||||||
| 888B | .cha6←1← 884A BEQ | ||||||
| JSR copy_filename ; A=6: copy filename (delete) | |||||||
| 888E | LDY #&14 ; Y=&14: fn code for FCDEL (delete) | ||||||
| 8890 | .send_fs_cmd_v1←1← 8889 BNE | ||||||
| BIT tx_ctrl_upper ; Set V=1 (BIT trick: &B3 has bit 6 set) | |||||||
| 8893 | JSR prepare_fs_cmd_v ; Send via prepare_fs_cmd_v (V=1 path) | ||||||
| 8896 | .check_attrib_result←1← 884C BCS | ||||||
| BCS attrib_error_exit ; C=1: &D6 not-found, skip to return | |||||||
| 8898 | BCC argsv_check_return ; C=0: success, copy reply to param block ALWAYS branch | ||||||
| 889A | .cha4←1← 8854 BEQ | ||||||
| JSR decode_attribs_6bit ; A=4: encode attrs from param block | |||||||
| 889D | STA fs_func_code ; Store encoded attrs at &0F06 | ||||||
| 88A0 | LDX #2 ; X=2: data extent (1 attr byte + fn) | ||||||
| 88A2 | BNE copy_filename_to_cmd ; ALWAYS branch to append filename ALWAYS branch | ||||||
| 88A4 | .cha5←1← 8850 BEQ | ||||||
| LDX #1 ; X=1: filename only, no data extent | |||||||
| 88A6 | JSR copy_string_to_cmd ; Copy filename to cmd buffer | ||||||
| 88A9 | LDY #&12 ; Y=&12: fn code for FCEXAM (read info) Y=function code for HDRFN | ||||||
| 88AB | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 88AE | LDA fs_obj_type ; Save object type from FS reply | ||||||
| 88B1 | STX fs_obj_type ; Clear reply byte (X=0 on success) X=0 on success, &D6 on not-found | ||||||
| 88B4 | STX fs_len_clear ; Clear length high byte in reply | ||||||
| 88B7 | JSR decode_attribs_5bit ; Decode 5-bit access byte from FS reply | ||||||
| 88BA | LDX fs_cmd_data ; Load FS command code from reply | ||||||
| 88BD | BEQ argsv_zero_length ; Zero: no attribute data returned | ||||||
| 88BF | LDY #&0e ; Y=&0E: attrs offset in param block | ||||||
| 88C1 | STA (fs_options),y ; Store decoded attrs at param block +&0E | ||||||
| 88C3 | DEY ; Y=&0D: start copy below attrs Y=&0d | ||||||
| 88C4 | LDX #&0c ; X=&0C: copy from reply offset &0C down | ||||||
| 88C6 | .copy_fs_reply_to_cb←1← 88CD BNE | ||||||
| LDA fs_cmd_data,x ; Load reply byte (load/exec/length) | |||||||
| 88C9 | STA (fs_options),y ; Store to param block | ||||||
| 88CB | DEY ; Next dest byte (descending) | ||||||
| 88CC | DEX ; Next source byte | ||||||
| 88CD | BNE copy_fs_reply_to_cb ; Loop until X=0 (12 bytes copied) | ||||||
| 88CF | INX ; X=0 -> X=2 for length high copy | ||||||
| 88D0 | INX ; INX again: X=2 | ||||||
| 88D1 | LDY #&11 ; Y=&11: length high dest in param block | ||||||
| 88D3 | .cha5lp←1← 88DA BPL | ||||||
| LDA fs_access_level,x ; Load length high byte from reply | |||||||
| 88D6 | STA (fs_options),y ; Store to param block | ||||||
| 88D8 | DEY ; Next dest byte (descending) | ||||||
| 88D9 | DEX ; Next source byte | ||||||
| 88DA | BPL cha5lp ; Loop for 3 length-high bytes | ||||||
| 88DC | LDX fs_cmd_data ; Reload FS command code | ||||||
| 88DF | .argsv_zero_length←1← 88BD BEQ | ||||||
| TXA ; A = command code for exit test | |||||||
| 88E0 | .attrib_error_exit←1← 8896 BCS | ||||||
| BPL restore_xy_return ; A>=0: branch to restore_args_return | |||||||
| fall through ↓ | |||||||
ARGSV handler (OSARGS entry point)A=0, Y=0: return filing system number (10 = network FS) A=0, Y>0: read file pointer via FS command &0A (FCRDSE) A=1, Y>0: write file pointer via FS command &14 (FCWRSE) A>=3 (ensure): silently returns -- NFS has no local write buffer to flush, since all data is sent to the fileserver immediately The handle in Y is converted via handle_to_mask_clc. For writes (A=1), the carry flag from the mask conversion is used to branch to save_args_handle, which records the handle for later use.
|
|||||||||||||
| 88E2 | .argsv_handler | ||||||||||||
| JSR save_fscv_args ; Save A/X/Y registers for later restore | |||||||||||||
| 88E5 | CMP #3 ; Function >= 3? | ||||||||||||
| 88E7 | BCS restore_args_return ; A>=3 (ensure/flush): no-op for NFS | ||||||||||||
| 88E9 | CPY #0 ; Test file handle | ||||||||||||
| 88EB | BEQ argsv_dispatch_a ; Y=0: FS-level query, not per-file | ||||||||||||
| 88ED | JSR handle_to_mask_clc ; Convert handle to bitmask | ||||||||||||
| 88F0 | STY fs_cmd_data ; Store bitmask as first cmd data byte | ||||||||||||
| 88F3 | LSR ; LSR splits A: C=1 means write (A=1) | ||||||||||||
| 88F4 | STA fs_func_code ; Store function code to cmd data byte 2 | ||||||||||||
| 88F7 | BCS save_args_handle ; C=1: write path, copy ptr from caller | ||||||||||||
| 88F9 | LDY #&0c ; Y=&0C: FCRDSE (read sequential pointer) Y=function code for HDRFN | ||||||||||||
| 88FB | LDX #2 ; X=2: 3 data bytes in command X=preserved through header build | ||||||||||||
| 88FD | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 8900 | STA fs_last_byte_flag ; Clear last-byte flag on success A=0 on success (from build_send_fs_cmd) | ||||||||||||
| 8902 | LDX fs_options ; X = saved control block ptr low | ||||||||||||
| 8904 | LDY #2 ; Y=2: copy 3 bytes of file pointer | ||||||||||||
| 8906 | STA zp_work_3,x ; Zero high byte of 3-byte pointer | ||||||||||||
| 8908 | .copy_fileptr_reply←1← 890F BPL | ||||||||||||
| LDA fs_cmd_data,y ; Read reply byte from FS cmd data | |||||||||||||
| 890B | STA zp_work_2,x ; Store to caller's control block | ||||||||||||
| 890D | DEX ; Next byte (descending) | ||||||||||||
| 890E | DEY ; Next source byte | ||||||||||||
| 890F | BPL copy_fileptr_reply ; Loop for all 3 bytes | ||||||||||||
| 8911 | .argsv_check_return←1← 8898 BCC | ||||||||||||
| BCC restore_args_return ; C=0 (read): return to caller | |||||||||||||
| 8913 | .save_args_handle←1← 88F7 BCS | ||||||||||||
| TYA ; Save bitmask for set_fs_flag later | |||||||||||||
| 8914 | PHA ; Push bitmask | ||||||||||||
| 8915 | LDY #3 ; Y=3: copy 4 bytes of file pointer | ||||||||||||
| 8917 | .copy_fileptr_to_cmd←1← 891E BPL | ||||||||||||
| LDA zp_work_3,x ; Read caller's pointer byte | |||||||||||||
| 8919 | STA fs_data_count,y ; Store to FS command data area | ||||||||||||
| 891C | DEX ; Next source byte | ||||||||||||
| 891D | DEY ; Next destination byte | ||||||||||||
| 891E | BPL copy_fileptr_to_cmd ; Loop for all 4 bytes | ||||||||||||
| 8920 | LDY #&0d ; Y=&0D: FCWRSE (write sequential pointer) Y=function code for HDRFN | ||||||||||||
| 8922 | LDX #5 ; X=5: 6 data bytes in command X=preserved through header build | ||||||||||||
| 8924 | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 8927 | STX fs_last_byte_flag ; Save not-found status from X X=0 on success, &D6 on not-found | ||||||||||||
| 8929 | PLA ; Recover bitmask for EOF hint update | ||||||||||||
| 892A | JSR set_fs_flag ; Set EOF hint bit for this handle | ||||||||||||
| fall through ↓ | |||||||||||||
Restore arguments and returnCommon exit point for FS vector handlers. Reloads A from fs_last_byte_flag (&BD), X from fs_options (&BB), and Y from fs_block_offset (&BC) — the values saved at entry by save_fscv_args — and returns to the caller. |
|
| 892D | .restore_args_return←8← 86C9 JMP← 87AB JMP← 88E7 BCS← 8911 BCC← 893C BNE← 89A0 BCC← 89F6 JMP← 8D00 JMP |
| LDA fs_last_byte_flag ; A = saved function code / command | |
| 892F | .restore_xy_return←5← 88E0 BPL← 8939 BNE← 8948 BPL← 8970 BCS← 8984 BNE |
| LDX fs_options ; X = saved control block ptr low | |
| 8931 | LDY fs_block_offset ; Y = saved control block ptr high |
| 8933 | RTS ; Return to MOS with registers restored |
| 8934 | .argsv_dispatch_a←1← 88EB BEQ |
| TAY ; Transfer A to Y for test | |
| 8935 | BNE halve_args_a ; Non-zero: halve A |
| 8937 | LDA #5 ; A=5: default FS number |
| 8939 | BNE restore_xy_return ; ALWAYS branch |
| 893B | .halve_args_a←1← 8935 BNE |
| LSR ; Shared: halve A (A=0 or A=2 paths) | |
| 893C | BNE restore_args_return ; Return with A = FS number or 1 |
| 893E | .osarg1←1← 8944 BPL |
| LDA fs_context_hi,y ; Copy command context to caller's block | |
| 8941 | STA (fs_options),y ; Store to caller's parameter block |
| 8943 | DEY ; Next byte (descending) |
| 8944 | BPL osarg1 ; Loop until all bytes copied |
| fall through ↓ | |
Return with A=0 via register restoreLoads A=0 and branches (always taken) to the common register restore exit at restore_args_return. Used as a shared exit point by ARGSV, FINDV, and GBPBV when an operation is unsupported or should return zero. |
|
| 8946 | .return_a_zero←3← 8956 BNE← 8A96 JMP← 8B33 JMP |
| LDA #0 ; A=operation (0=close, &40=read, &80=write, &C0=R/W) | |
| 8948 | BPL restore_xy_return ; ALWAYS branch |
FINDV handler (OSFIND entry point)A=0: close file -- delegates to close_handle (&8986) A>0: open file -- modes &40=read, &80=write/update, &C0=read/write For open: the mode byte is converted to the fileserver's two-flag format by flipping bit 7 (EOR #&80) and shifting. This produces Flag 1 (read/write direction) and Flag 2 (create/existing), matching the fileserver protocol. After a successful open, the new handle's bit is OR'd into the EOF hint byte (marks it as "might be at EOF, query the server", on_entry={"a": "operation (0=close, &40=read, &80=write, &C0=R/W)", "x": "filename pointer low (open)", "y": "file handle (close) or filename pointer high (open)"}, on_exit={"a": "handle on open, 0 on close-all, restored on close-one", "x": "restored", "y": "restored"}) number tracking byte for the byte-stream protocol.
|
|||||||||||||||
| 894A | .findv_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y and set up pointers | |||||||||||||||
| 894D | SEC ; SEC distinguishes open (A>0) from close | ||||||||||||||
| 894E | JSR handle_to_mask ; Convert file handle to bitmask (Y2FS) | ||||||||||||||
| 8951 | TAX ; A=preserved | ||||||||||||||
| 8952 | BEQ close_handle ; A=0: close file(s) | ||||||||||||||
| 8954 | AND #&3f ; Valid open modes: &40, &80, &C0 only | ||||||||||||||
| 8956 | BNE return_a_zero ; Invalid mode bits: return | ||||||||||||||
| 8958 | TXA ; A = original mode byte | ||||||||||||||
| 8959 | EOR #&80 ; Convert MOS mode to FS protocol flags | ||||||||||||||
| 895B | ASL ; ASL: shift mode bits left | ||||||||||||||
| 895C | STA fs_cmd_data ; Flag 1: read/write direction | ||||||||||||||
| 895F | ROL ; ROL: Flag 2 into bit 0 | ||||||||||||||
| 8960 | STA fs_func_code ; Flag 2: create vs existing file | ||||||||||||||
| 8963 | LDX #2 ; X=2: copy after 2-byte flags | ||||||||||||||
| 8965 | JSR copy_string_to_cmd ; Copy filename to FS command buffer | ||||||||||||||
| 8968 | LDY #6 ; Y=6: FS function code FCOPEN | ||||||||||||||
| 896A | BIT tx_ctrl_upper ; Set V flag from l83b3 bit 6 | ||||||||||||||
| 896D | JSR prepare_fs_cmd_v ; Build and send FS open command | ||||||||||||||
| 8970 | BCS restore_xy_return ; Error: restore and return | ||||||||||||||
| 8972 | LDA fs_cmd_data ; Load reply handle from FS | ||||||||||||||
| 8975 | TAX ; X = new file handle | ||||||||||||||
| 8976 | JSR set_fs_flag ; Set EOF hint + sequence bits | ||||||||||||||
| 8979 | TXA ; A = handle bitmask from set_fs_flag | ||||||||||||||
| 897A | ORA fs_sequence_nos ; Merge handle into sequence tracking | ||||||||||||||
| 897D | STA fs_sequence_nos ; Store updated sequence tracking | ||||||||||||||
| 8980 | TXA ; A=single-bit bitmask | ||||||||||||||
| 8981 | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) | ||||||||||||||
| 8984 | BNE restore_xy_return ; ALWAYS branch to restore and return | ||||||||||||||
Close file handle(s) (CLOSE) Y=0: close all files — first calls OSBYTE &77 (close SPOOL and
EXEC files) to coordinate with the MOS before sending the
close-all command to the fileserver. This ensures locally-
managed file handles are released before the server-side
handles are invalidated, preventing the MOS from writing to
a closed spool file.
Y>0: close single handle — sends FS close command and clears
the handle's bit in both the EOF hint byte and the sequence
number tracking byte.
|
||||
| 8986 | .close_handle←1← 8952 BEQ | |||
| TYA ; A = handle (Y preserved in A) Y=preserved | ||||
| 8987 | BNE close_single_handle ; Y>0: close single file | |||
| 8989 | LDA #osbyte_close_spool_exec ; Close SPOOL/EXEC before FS close-all | |||
| 898B | JSR osbyte ; Close any *SPOOL and *EXEC files | |||
| 898E | LDY #0 ; Y=0: close all handles on server | |||
| 8990 | .close_single_handle←1← 8987 BNE | |||
| STY fs_cmd_data ; Handle byte in FS command buffer | ||||
| 8993 | LDX #1 ; X=preserved through header build | |||
| 8995 | LDY #7 ; Y=function code for HDRFN | |||
| 8997 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||
| 899A | LDA fs_cmd_data ; Reply handle for flag update | |||
| 899D | JSR set_fs_flag ; Update EOF/sequence tracking bits | |||
| 89A0 | .close_opt_return←1← 89C9 BCC | |||
| BCC restore_args_return ; C=0: restore A/X/Y and return | ||||
| fall through ↓ | ||||
FSCV 0: *OPT handler (OPTION)Handles *OPT X,Y to set filing system options: *OPT 1,Y (Y=0/1): set local user option in &0E06 (OPT) *OPT 4,Y (Y=0-3): set boot option via FS command &16 (FCOPT) Other combinations generate error &CB (OPTER: "bad option").
|
||||||
| 89A2 | .fscv_0_opt | |||||
| CPX #4 ; Is it *OPT 4,Y? | ||||||
| 89A4 | BNE gbpbv_func_dispatch ; No: check for *OPT 1 | |||||
| 89A6 | CPY #4 ; Y must be 0-3 for boot option | |||||
| 89A8 | BCC optl1 ; Y < 4: valid boot option | |||||
| 89AA | .gbpbv_func_dispatch←1← 89A4 BNE | |||||
| CPX #1 ; X=1? (*OPT 1: set messaging) | ||||||
| 89AC | BNE opter1 ; Not *OPT 1: bad option error | |||||
| 89AE | CPY #2 ; Y < 2? (valid: 0=off, 1=on) | |||||
| 89B0 | BCS opter1 ; Y >= 2: bad option value error | |||||
| 89B2 | .set_messages_flag | |||||
| STY fs_messages_flag ; Set local messages flag (*OPT 1,Y) | ||||||
| 89B5 | BCC opt_return ; Return via restore_args_return | |||||
| 89B7 | .opter1←2← 89AC BNE← 89B0 BCS | |||||
| LDA #7 ; Error index 7 (Bad option) | ||||||
| 89B9 | JMP nlisne ; Generate BRK error | |||||
| 89BC | .optl1←1← 89A8 BCC | |||||
| STY fs_cmd_data ; Boot option value in FS command | ||||||
| 89BF | LDY #&16 ; Y=&16: FS function code FCOPT Y=function code for HDRFN | |||||
| 89C1 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||||
| 89C4 | LDY fs_block_offset ; Restore Y from saved value | |||||
| 89C6 | STY fs_boot_option ; Cache boot option locally | |||||
| 89C9 | .opt_return←1← 89B5 BCC | |||||
| BCC close_opt_return ; Return via restore_args_return | ||||||
| 89CB | .adjust_addrs_9←1← 8A8A JSR | |||||
| LDY #9 ; Y=9: adjust 9 address bytes | ||||||
| 89CD | JSR adjust_addrs_clc ; Adjust with carry clear | |||||
| 89D0 | .adjust_addrs_1←1← 8B7A JSR | |||||
| LDY #1 ; Y=1: adjust 1 address byte | ||||||
| 89D2 | .adjust_addrs_clc←1← 89CD JSR | |||||
| CLC ; C=0 for address adjustment | ||||||
| fall through ↓ | ||||||
Bidirectional 4-byte address adjustmentAdjusts a 4-byte value in the parameter block at (fs_options)+Y: If fs_load_addr_2 (&B2) is positive: adds fs_lib_handle+X values If fs_load_addr_2 (&B2) is negative: subtracts fs_lib_handle+X Starting offset X=&FC means it reads from &0E06-&0E09 area. Used to convert between absolute and relative file positions.
|
|||||||||||
| 89D3 | .adjust_addrs←2← 8A90 JSR← 8B86 JSR | ||||||||||
| LDX #&fc ; X=&FC: index into &0E06 area (wraps to 0) | |||||||||||
| 89D5 | .adjust_addr_byte←1← 89E8 BNE | ||||||||||
| LDA (fs_options),y ; Load byte from param block | |||||||||||
| 89D7 | BIT fs_load_addr_2 ; Test sign of adjustment direction | ||||||||||
| 89D9 | BMI subtract_adjust ; Negative: subtract instead | ||||||||||
| 89DB | ADC fs_cmd_context,x ; Add adjustment value | ||||||||||
| 89DE | JMP gbpbx ; Skip to store result | ||||||||||
| 89E1 | .subtract_adjust←1← 89D9 BMI | ||||||||||
| SBC fs_cmd_context,x ; Subtract adjustment value | |||||||||||
| 89E4 | .gbpbx←1← 89DE JMP | ||||||||||
| STA (fs_options),y ; Store adjusted byte back | |||||||||||
| 89E6 | INY ; Next param block byte | ||||||||||
| 89E7 | INX ; Next adjustment byte (X wraps &FC->&00) | ||||||||||
| 89E8 | BNE adjust_addr_byte ; Loop 4 times (X=&FC,&FD,&FE,&FF,done) | ||||||||||
| 89EA | RTS ; Return (unsupported function) | ||||||||||
GBPBV handler (OSGBPB entry point)A=1-4: file read/write operations (handle-based) A=5-8: info queries (disc title, current dir, lib, filenames) Calls 1-4 are standard file data transfers via the fileserver. Calls 5-8 were a late addition to the MOS spec and are the only NFS operations requiring Tube data transfer -- described in the original source as "untidy but useful in theory." The data format uses length-prefixed strings (<name length><object name>) rather than the CR-terminated strings used elsewhere in the FS.
|
|||||||||||||||
| 89EB | .gbpbv_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y to FS workspace | |||||||||||||||
| 89EE | TAX ; X = call number for range check | ||||||||||||||
| 89EF | BEQ gbpbx0 ; A=0: invalid, restore and return | ||||||||||||||
| 89F1 | DEX ; Convert to 0-based (A=0..7) | ||||||||||||||
| 89F2 | CPX #8 ; Range check: must be 0-7 | ||||||||||||||
| 89F4 | BCC gbpbx1 ; In range: continue to handler | ||||||||||||||
| 89F6 | .gbpbx0←1← 89EF BEQ | ||||||||||||||
| JMP restore_args_return ; Out of range: restore args and return | |||||||||||||||
| 89F9 | .gbpbx1←1← 89F4 BCC | ||||||||||||||
| TXA ; Recover 0-based function code | |||||||||||||||
| 89FA | LDY #0 ; Y=0: param block byte 0 (file handle) | ||||||||||||||
| 89FC | PHA ; Save function code on stack | ||||||||||||||
| 89FD | CMP #4 ; A>=4: info queries, dispatch separately | ||||||||||||||
| 89FF | BCC gbpbe1 ; A<4: file read/write operations | ||||||||||||||
| 8A01 | JMP osgbpb_info ; Dispatch to OSGBPB 5-8 info handler | ||||||||||||||
| 8A04 | .gbpbe1←1← 89FF BCC | ||||||||||||||
| LDA (fs_options),y ; Get file handle from param block byte 0 | |||||||||||||||
| 8A06 | JSR handle_to_mask_a ; Convert handle to bitmask for EOF flags | ||||||||||||||
| 8A09 | STY fs_cmd_data ; Store handle in FS command data | ||||||||||||||
| 8A0C | LDY #&0b ; Y=&0B: start at param block byte 11 | ||||||||||||||
| 8A0E | LDX #6 ; X=6: copy 6 bytes of transfer params | ||||||||||||||
| 8A10 | .gbpbf1←1← 8A1C BNE | ||||||||||||||
| LDA (fs_options),y ; Load param block byte | |||||||||||||||
| 8A12 | STA fs_func_code,x ; Store to FS command buffer at &0F06+X | ||||||||||||||
| 8A15 | DEY ; Previous param block byte | ||||||||||||||
| 8A16 | CPY #8 ; Skip param block offset 8 (the handle) | ||||||||||||||
| 8A18 | BNE gbpbf2 ; Not at handle offset: continue | ||||||||||||||
| 8A1A | DEY ; Extra DEY to skip handle byte | ||||||||||||||
| 8A1B | .gbpbf2←1← 8A18 BNE | ||||||||||||||
| DEX ; Decrement copy counter | |||||||||||||||
| 8A1C | BNE gbpbf1 ; Loop for all 6 bytes | ||||||||||||||
| 8A1E | PLA ; Recover function code from stack | ||||||||||||||
| 8A1F | LSR ; LSR: odd=read (C=1), even=write (C=0) | ||||||||||||||
| 8A20 | PHA ; Save function code again (need C later) | ||||||||||||||
| 8A21 | BCC gbpbl1 ; Even (write): X stays 0 | ||||||||||||||
| 8A23 | INX ; Odd (read): X=1 | ||||||||||||||
| 8A24 | .gbpbl1←1← 8A21 BCC | ||||||||||||||
| STX fs_func_code ; Store FS direction flag | |||||||||||||||
| 8A27 | LDY #&0b ; Y=&0B: command data extent | ||||||||||||||
| 8A29 | LDX #&91 ; Command &91=put, &92=get | ||||||||||||||
| 8A2B | PLA ; Recover function code | ||||||||||||||
| 8A2C | PHA ; Save again for later direction check | ||||||||||||||
| 8A2D | BEQ gbpb_write_path ; Even (write): keep &91 and Y=&0B | ||||||||||||||
| 8A2F | LDX #&92 ; Odd (read): use &92 (get) instead | ||||||||||||||
| 8A31 | DEY ; Read: one fewer data byte in command Y=&0a | ||||||||||||||
| 8A32 | .gbpb_write_path←1← 8A2D BEQ | ||||||||||||||
| STX fs_cmd_urd ; Store port to FS command URD field | |||||||||||||||
| 8A35 | STX fs_error_ptr ; Save port for error recovery | ||||||||||||||
| 8A37 | LDX #8 ; X=8: command data bytes | ||||||||||||||
| 8A39 | LDA fs_cmd_data ; Load handle from FS command data | ||||||||||||||
| 8A3C | JSR prepare_cmd_with_flag ; Build FS command with handle+flag | ||||||||||||||
| 8A3F | LDA fs_load_addr_3 ; Save seq# for byte-stream flow control | ||||||||||||||
| 8A41 | STA fs_sequence_nos ; Store to FS sequence number workspace | ||||||||||||||
| 8A44 | LDX #4 ; X=4: copy 4 address bytes | ||||||||||||||
| 8A46 | .gbpbl3←1← 8A5A BNE | ||||||||||||||
| LDA (fs_options),y ; Set up source/dest from param block | |||||||||||||||
| 8A48 | STA addr_work,y ; Store as source address | ||||||||||||||
| 8A4B | STA txcb_pos,y ; Store as current transfer position | ||||||||||||||
| 8A4E | JSR add_4_to_y ; Skip 4 bytes to reach transfer length | ||||||||||||||
| 8A51 | ADC (fs_options),y ; Dest = source + length | ||||||||||||||
| 8A53 | STA addr_work,y ; Store as end address | ||||||||||||||
| 8A56 | JSR sub_3_from_y ; Back 3 to align for next iteration | ||||||||||||||
| 8A59 | DEX ; Decrement byte counter | ||||||||||||||
| 8A5A | BNE gbpbl3 ; Loop for all 4 address bytes | ||||||||||||||
| 8A5C | INX ; X=1 after loop | ||||||||||||||
| 8A5D | .gbpbf3←1← 8A64 BPL | ||||||||||||||
| LDA fs_cmd_csd,x ; Copy CSD data to command buffer | |||||||||||||||
| 8A60 | STA fs_func_code,x ; Store at &0F06+X | ||||||||||||||
| 8A63 | DEX ; Decrement counter | ||||||||||||||
| 8A64 | BPL gbpbf3 ; Loop for X=1,0 | ||||||||||||||
| 8A66 | PLA ; Odd (read): send data to FS first | ||||||||||||||
| 8A67 | BNE gbpb_read_path ; Non-zero: skip write path | ||||||||||||||
| 8A69 | LDA fs_cmd_urd ; Load port for transfer setup | ||||||||||||||
| 8A6C | JSR transfer_file_blocks ; Transfer data blocks to fileserver | ||||||||||||||
| 8A6F | BNE findv_eof_check ; Non-zero: branch past error ptr | ||||||||||||||
| 8A71 | .gbpb_read_path←1← 8A67 BNE | ||||||||||||||
| JSR send_data_blocks ; Read path: receive data blocks from FS | |||||||||||||||
| 8A74 | .findv_eof_check←1← 8A6F BNE | ||||||||||||||
| LDA #&2a ; A=&2A: error ptr for FS retry | |||||||||||||||
| 8A76 | STA fs_error_ptr ; Store error ptr for TX poll | ||||||||||||||
| 8A78 | .wait_fs_reply | ||||||||||||||
| JSR send_fs_reply_cmd ; Wait for FS reply command | |||||||||||||||
| 8A7B | LDA (fs_options,x) ; Load handle mask for EOF flag update | ||||||||||||||
| 8A7D | BIT fs_cmd_data ; Check FS reply: bit 7 = not at EOF | ||||||||||||||
| 8A80 | BMI skip_clear_flag ; Bit 7 set: not EOF, skip clear | ||||||||||||||
| 8A82 | JSR clear_fs_flag ; At EOF: clear EOF hint for this handle | ||||||||||||||
| 8A85 | .skip_clear_flag←1← 8A80 BMI | ||||||||||||||
| JSR set_fs_flag ; Set EOF hint flag (may be at EOF) | |||||||||||||||
| 8A88 | STX fs_load_addr_2 ; Direction=0: forward adjustment | ||||||||||||||
| 8A8A | JSR adjust_addrs_9 ; Adjust param block addrs by +9 bytes | ||||||||||||||
| 8A8D | DEC fs_load_addr_2 ; Direction=&FF: reverse adjustment | ||||||||||||||
| 8A8F | SEC ; SEC for reverse subtraction | ||||||||||||||
| 8A90 | JSR adjust_addrs ; Adjust param block addrs (reverse) | ||||||||||||||
| 8A93 | ASL fs_cmd_data ; Shift bit 7 into C for return flag | ||||||||||||||
| 8A96 | JMP return_a_zero ; Return via restore_args path | ||||||||||||||
| 8A99 | .get_disc_title←1← 8AC9 BEQ | ||||||||||||||
| LDY #&15 ; Y=&15: function code for disc title Y=function code for HDRFN | |||||||||||||||
| 8A9B | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||||
| 8A9E | LDA fs_boot_option ; Load boot option from FS workspace | ||||||||||||||
| 8AA1 | STA fs_boot_data ; Store boot option in reply area | ||||||||||||||
| 8AA4 | STX fs_load_addr ; X=0: reply data start offset X=0 on success, &D6 on not-found | ||||||||||||||
| 8AA6 | STX fs_load_addr_hi ; Clear reply buffer high byte | ||||||||||||||
| 8AA8 | LDA #&12 ; A=&12: 18 bytes of reply data | ||||||||||||||
| 8AAA | STA fs_load_addr_2 ; Store as byte count for copy | ||||||||||||||
| 8AAC | BNE copy_reply_to_caller ; ALWAYS branch to copy_reply_to_caller ALWAYS branch | ||||||||||||||
OSGBPB 5-8 info handler (OSINFO)Handles the "messy interface tacked onto OSGBPB" (original source). Checks whether the destination address is in Tube space by comparing the high bytes against TBFLAG. If in Tube space, data must be copied via the Tube FIFO registers (with delays to accommodate the slow 16032 co-processor) instead of direct memory access. |
|
| 8AAE | .osgbpb_info←1← 8A01 JMP |
| LDY #4 ; Y=4: check param block byte 4 | |
| 8AB0 | LDA tx_in_progress ; Check if destination is in Tube space |
| 8AB3 | BEQ store_tube_flag ; No Tube: skip Tube address check |
| 8AB5 | CMP (fs_options),y ; Compare Tube flag with addr byte 4 |
| 8AB7 | BNE store_tube_flag ; Mismatch: not Tube space |
| 8AB9 | DEY ; Y=&03 |
| 8ABA | SBC (fs_options),y ; Y=3: subtract addr byte 3 from flag |
| 8ABC | .store_tube_flag←2← 8AB3 BEQ← 8AB7 BNE |
| STA rom_svc_num ; Non-zero = Tube transfer required | |
| 8ABE | .info2←1← 8AC4 BNE |
| LDA (fs_options),y ; Copy param block bytes 1-4 to workspace | |
| 8AC0 | STA fs_last_byte_flag,y ; Store to &BD+Y workspace area |
| 8AC3 | DEY ; Previous byte |
| 8AC4 | BNE info2 ; Loop for bytes 3,2,1 |
| 8AC6 | PLA ; Sub-function: AND #3 of (original A - 4) |
| 8AC7 | AND #3 ; Mask to 0-3 (OSGBPB 5-8 → 0-3) |
| 8AC9 | BEQ get_disc_title ; A=0 (OSGBPB 5): read disc title |
| 8ACB | LSR ; LSR: A=0 (OSGBPB 6) or A=1 (OSGBPB 7) |
| 8ACC | BEQ gbpb6_read_name ; A=0 (OSGBPB 6): read CSD/LIB name |
| 8ACE | BCS gbpb8_read_dir ; C=1 (OSGBPB 8): read filenames from dir |
| 8AD0 | .gbpb6_read_name←1← 8ACC BEQ |
| TAY ; Y=0 for CSD or carry for fn code select Y=function code | |
| 8AD1 | LDA fs_csd_handle,y ; Get CSD/LIB/URD handles for FS command |
| 8AD4 | STA fs_cmd_csd ; Store CSD handle in command buffer |
| 8AD7 | LDA fs_lib_handle ; Load LIB handle from workspace |
| 8ADA | STA fs_cmd_lib ; Store LIB handle in command buffer |
| 8ADD | LDA fs_urd_handle ; Load URD handle from workspace |
| 8AE0 | STA fs_cmd_urd ; Store URD handle in command buffer |
| 8AE3 | LDX #&12 ; X=&12: buffer extent for command data X=buffer extent (command-specific data bytes) |
| 8AE5 | STX fs_cmd_y_param ; Store X as function code in header |
| 8AE8 | LDA #&0d ; &0D = 13 bytes of reply data expected |
| 8AEA | STA fs_func_code ; Store reply length in command buffer |
| 8AED | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8AEF | LSR ; LSR: &0D >> 1 = 6 A=timeout period for FS reply |
| 8AF0 | STA fs_cmd_data ; Store as command data byte |
| 8AF3 | CLC ; CLC for standard FS path |
| 8AF4 | JSR build_send_fs_cmd ; Build and send FS command (DOFSOP) |
| 8AF7 | STX fs_load_addr_hi ; X=0 on success, &D6 on not-found |
| 8AF9 | INX ; INX: X=1 after build_send_fs_cmd |
| 8AFA | STX fs_load_addr ; Store X as reply start offset |
| 8AFC | .copy_reply_to_caller←2← 8AAC BNE← 8B6F JSR |
| LDA rom_svc_num ; Copy FS reply to caller's buffer | |
| 8AFE | BNE tube_transfer ; Non-zero: use Tube transfer path |
| 8B00 | LDX fs_load_addr ; X = reply start offset |
| 8B02 | LDY fs_load_addr_hi ; Y = reply buffer high byte |
| 8B04 | .copy_reply_bytes←1← 8B0D BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8B07 | STA (fs_crc_lo),y ; Store to caller's buffer |
| 8B09 | INX ; Next source byte |
| 8B0A | INY ; Next destination byte |
| 8B0B | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8B0D | BNE copy_reply_bytes ; Loop until all bytes copied |
| 8B0F | BEQ gbpb_done ; ALWAYS branch to exit ALWAYS branch |
| 8B11 | .tube_transfer←1← 8AFE BNE |
| JSR tube_claim_loop ; Claim Tube transfer channel | |
| 8B14 | LDA #1 ; A=1: Tube claim type 1 (write) |
| 8B16 | LDX fs_options ; X = param block address low |
| 8B18 | LDY fs_block_offset ; Y = param block address high |
| 8B1A | INX ; INX: advance past byte 0 |
| 8B1B | BNE no_page_wrap ; No page wrap: keep Y |
| 8B1D | INY ; Page wrap: increment high byte |
| 8B1E | .no_page_wrap←1← 8B1B BNE |
| JSR tube_addr_claim ; Claim Tube address for transfer | |
| 8B21 | LDX fs_load_addr ; X = reply data start offset |
| 8B23 | .tbcop1←1← 8B2C BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8B26 | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 8B29 | INX ; Next source byte |
| 8B2A | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8B2C | BNE tbcop1 ; Loop until all bytes sent to Tube |
| 8B2E | LDA #&83 ; Release Tube after transfer complete |
| 8B30 | JSR tube_addr_claim ; Release Tube address claim |
| 8B33 | .gbpb_done←2← 8B0F BEQ← 8B89 BEQ |
| JMP return_a_zero ; Return via restore_args path | |
| 8B36 | .gbpb8_read_dir←1← 8ACE BCS |
| LDY #9 ; OSGBPB 8: read filenames from dir | |
| 8B38 | LDA (fs_options),y ; Byte 9: number of entries to read |
| 8B3A | STA fs_func_code ; Store as reply count in command buffer |
| 8B3D | LDY #5 ; Y=5: byte 5 = starting entry number |
| 8B3F | LDA (fs_options),y ; Load starting entry number |
| 8B41 | STA fs_data_count ; Store in command buffer |
| 8B44 | LDX #&0d ; X=&0D: command data extent X=preserved through header build |
| 8B46 | STX fs_reply_cmd ; Store extent in command buffer |
| 8B49 | LDY #2 ; Y=2: function code for dir read |
| 8B4B | STY fs_load_addr ; Store 2 as reply data start offset |
| 8B4D | STY fs_cmd_data ; Store 2 as command data byte |
| 8B50 | INY ; Y=3: function code for header read Y=function code for HDRFN Y=&03 |
| 8B51 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8B54 | STX fs_load_addr_hi ; X=0 after FS command completes X=0 on success, &D6 on not-found |
| 8B56 | LDA fs_func_code ; Load reply entry count |
| 8B59 | STA (fs_options,x) ; Store at param block byte 0 (X=0) |
| 8B5B | LDA fs_cmd_data ; Load entries-read count from reply |
| 8B5E | LDY #9 ; Y=9: param block byte 9 |
| 8B60 | ADC (fs_options),y ; Add to starting entry number |
| 8B62 | STA (fs_options),y ; Update param block with new position |
| 8B64 | LDA txcb_end ; Load total reply length |
| 8B66 | SBC #7 ; Subtract header (7 bytes) from reply len |
| 8B68 | STA fs_func_code ; Store adjusted length in command buffer |
| 8B6B | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8B6D | BEQ skip_copy_reply ; Zero bytes: skip copy |
| 8B6F | JSR copy_reply_to_caller ; Copy reply data to caller's buffer |
| 8B72 | .skip_copy_reply←1← 8B6D BEQ |
| LDX #2 ; X=2: clear 3 bytes | |
| 8B74 | .zero_cmd_bytes←1← 8B78 BPL |
| STA fs_data_count,x ; Zero out &0F07+X area | |
| 8B77 | DEX ; Next byte |
| 8B78 | BPL zero_cmd_bytes ; Loop for X=2,1,0 |
| 8B7A | JSR adjust_addrs_1 ; Adjust pointer by +1 (one filename read) |
| 8B7D | SEC ; SEC for reverse adjustment |
| 8B7E | DEC fs_load_addr_2 ; Reverse adjustment for updated counter |
| 8B80 | LDA fs_cmd_data ; Load entries-read count |
| 8B83 | STA fs_func_code ; Store in command buffer |
| 8B86 | JSR adjust_addrs ; Adjust param block addresses |
| 8B89 | BEQ gbpb_done ; Z=1: all done, exit |
| 8B8B | .tube_claim_loop←3← 8B11 JSR← 8B90 BCC← 8DA1 JSR |
| LDA #&c3 ; A=&C3: Tube claim with retry | |
| 8B8D | JSR tube_addr_claim ; Request Tube address claim |
| 8B90 | BCC tube_claim_loop ; C=0: claim failed, retry |
| 8B92 | RTS ; Tube claimed successfully |
FSCV 2/3/4: unrecognised * command handler (DECODE)CLI parser originally by Sophie Wilson (co-designer of ARM). Matches command text against the table at &8BD7 using case-insensitive comparison with abbreviation support — commands can be shortened with '.' (e.g. "I." for "INFO"). The "I." entry is a special fudge placed first in the table: since "I." could match multiple commands, it jumps to forward_star_cmd to let the fileserver resolve the ambiguity. The matching loop compares input characters against table entries. On mismatch, it skips to the next entry. On match of all table characters, or when '.' abbreviation is found, it dispatches via PHA/PHA/RTS to the entry's handler address. After matching, adjusts fs_crc_lo/fs_crc_hi to point past the matched command text. |
|
| 8B93 | .fscv_3_star_cmd←1← 8243 JMP |
| JSR save_fscv_args ; Save A/X/Y and set up command ptr | |
| 8B96 | LDX #&ff ; X=&FF: table index (pre-incremented) |
| 8B98 | .scan_cmd_table←1← 8BB3 BNE |
| LDY #&ff ; Y=&FF: input index (pre-incremented) | |
| 8B9A | .decfir←1← 8BA5 BEQ |
| INY ; Advance input pointer | |
| 8B9B | INX ; Advance table pointer |
| 8B9C | .decmor←1← 8BB7 BCS |
| LDA fs_cmd_match_table,x ; Load table character | |
| 8B9F | BMI dispatch_cmd ; Bit 7: end of name, dispatch |
| 8BA1 | EOR (fs_crc_lo),y ; XOR input char with table char |
| 8BA3 | AND #&df ; Case-insensitive (clear bit 5) |
| 8BA5 | BEQ decfir ; Match: continue comparing |
| 8BA7 | DEX ; Mismatch: back up table pointer |
| 8BA8 | .decmin←1← 8BAC BPL |
| INX ; Skip to end of table entry | |
| 8BA9 | LDA fs_cmd_match_table,x ; Load table byte |
| 8BAC | BPL decmin ; Loop until bit 7 set (end marker) |
| 8BAE | LDA (fs_crc_lo),y ; Check input for '.' abbreviation |
| 8BB0 | INX ; Skip past handler high byte |
| 8BB1 | CMP #&2e ; Is input '.' (abbreviation)? |
| 8BB3 | BNE scan_cmd_table ; No: try next table entry |
| 8BB5 | INY ; Yes: skip '.' in input |
| 8BB6 | DEX ; Back to handler high byte |
| 8BB7 | BCS decmor ; ALWAYS branch; dispatch via BMI |
| 8BB9 | .dispatch_cmd←1← 8B9F BMI |
| PHA ; Push handler address high byte | |
| 8BBA | LDA cmd_table_entry_1,x ; Load handler address low byte |
| 8BBD | PHA ; Push handler address low byte |
| 8BBE | CLC ; CLC for pointer calculation |
| 8BBF | TYA ; A = chars consumed from input |
| 8BC0 | LDX fs_crc_hi ; X = command text pointer high |
| 8BC2 | ADC fs_crc_lo ; Add chars consumed to pointer low |
| 8BC4 | STA fs_context_hi ; Store adjusted text pointer low |
| 8BC7 | STA fs_cmd_ptr ; Duplicate to second pointer copy |
| 8BCA | BCC cmd_match_retry ; No page overflow: skip INX |
| 8BCC | INX ; Adjust high byte for page crossing |
| 8BCD | .cmd_match_retry←1← 8BCA BCC |
| STX l0e0c ; Store high byte to context ptr 1 | |
| 8BD0 | STX l0e11 ; Store high byte to context ptr 2 |
| 8BD3 | STX fs_work_16 ; Store high byte to context ptr 3 |
| 8BD6 | RTS ; Dispatch via PHA/PHA/RTS |
FS command match table (COMTAB)Format: command letters (bit 7 clear), then dispatch address as two bytes: high|(bit 7 set), low. The PHA/PHA/RTS trick adds 1 to the stored (address-1). Matching is case-insensitive (AND &DF) and supports '.' abbreviation (standard Acorn pattern). Entries: "I." → &8079 (forward_star_cmd) — placed first as a fudge to catch *I. abbreviation before matching *I AM "I AM" → &8D07 (i_am_handler: parse station.net, logon) "EX " → &8BF3 (ex_handler: extended catalogue) "EX"\r → &8BF3 (same, exact match at end of line) "BYE"\r → &834A (bye_handler: logoff) <catch-all> → &8079 (forward anything else to FS) |
|
| 8BD7 | .fs_cmd_match_table←2← 8B9C LDA← 8BA9 LDA |
| EOR #&2e ; XOR with '.' (abbreviation check) | |
| 8BD9 | EQUB &80 |
| 8BDA | EQUS "xI AM" |
| 8BDF | EQUB &8D, &06 |
| 8BE1 | EQUS "EX " |
| 8BE4 | EQUB &8B, &F2, &45, &58, &0D, &8B, &F2 |
| 8BEB | EQUS "BYE" |
| 8BEE | EQUB &0D, &83, &49, &80, &78 |
| fall through ↓ | |
*EX handler (extended catalogue)Sets column width &B6=&50 (80 columns, one file per line with full details) and &B7=&01, then branches into fscv_5_cat at &8C08, bypassing fscv_5_cat's default 20-column setup. |
|
| 8BF3 | .ex_handler |
| DEY ; Pre-decrement Y for parameter | |
| 8BF4 | LDX #1 ; X=1: boot option display field |
| 8BF6 | STX fs_work_7 ; Store to fs_work_7 (&B7) |
| 8BF8 | LDX #&50 ; X=&50: 80-column display width |
| 8BFA | STX l00b6 ; Store column width at &B6 |
| 8BFC | BNE cat_init_display ; ALWAYS branch |
*CAT handler (directory catalogue)Sets column width &B6=&14 (20 columns, four files per 80-column line) and &B7=&03. The catalogue protocol is multi-step: first sends FCUSER (&15: read user environment) to get CSD, disc, and library names, then sends FCREAD (&12: examine) repeatedly to fetch entries in batches until zero are returned (end of dir). The receive buffer abuts the examine request buffer and ends at RXBUFE, allowing seamless data handling across request cycles. The command code byte in the fileserver reply indicates FS type: zero means an old-format FS (client must format data locally), non-zero means new-format (server returns pre-formatted strings). This enables backward compatibility with older Acorn fileservers. Display format: - Station number in parentheses - "Owner" or "Public" access level - Boot option with name (Off/Load/Run/Exec) - Current directory and library paths - Directory entries: CRFLAG (&CF) cycles 0-3 for multi-column layout; at count 0 a newline is printed, others get spaces. *EX sets CRFLAG=&FF to force one entry per line. |
|
| 8BFE | .fscv_5_cat |
| LDX #&14 ; X=&14 (20): column width for display | |
| 8C00 | STX l00b6 ; Store column width for batch calc |
| 8C02 | LDX #3 ; X=3: column count for examine request |
| 8C04 | STX fs_work_7 ; Store column count |
| 8C06 | LDY #0 ; Y=0: initial entry start offset |
| 8C08 | .cat_init_display←1← 8BFC BNE |
| LDA #6 ; A=6: examine format type in command | |
| 8C0A | STA fs_cmd_data ; Store format type at &0F05 |
| 8C0D | JSR skip_spaces ; Skip spaces in dir name argument |
| 8C10 | STY fs_load_addr_3 ; Save parameter offset after spaces |
| 8C12 | LDX #1 ; X=1: copy dir name at cmd offset 1 |
| 8C14 | JSR copy_string_from_offset ; Copy directory name to command buffer |
| 8C17 | LDY #&12 ; Y=function code for HDRFN |
| 8C19 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8C1C | LDX #3 ; X=3: start printing from reply offset 3 |
| 8C1E | JSR print_reply_bytes ; Print directory title (10 chars) |
| 8C21 | JSR print_inline ; Print '(' |
| 8C24 | EQUS "(" |
| 8C25 | LDA fs_reply_stn ; Load station number from FS reply |
| 8C28 | JSR print_decimal ; Print station number as decimal |
| 8C2B | JSR print_inline ; Print ') ' |
| 8C2E | EQUS ")" |
| 8C2F | LDX #5 ; X=5: space padding count |
| 8C31 | JSR print_spaces ; Print 5 spaces for alignment |
| 8C34 | LDX fs_access_level ; Access: 0=Owner, non-zero=Public |
| 8C37 | BNE print_public ; Non-zero: Public access |
| 8C39 | JSR print_inline ; Print 'Owner' + CR |
| 8C3C | EQUS "Owner." |
| 8C42 | BNE cat_access_setup ; Always taken (high-bit term. str) |
| 8C44 | .print_public←1← 8C37 BNE |
| JSR print_inline ; Print 'Public' + CR | |
| 8C47 | EQUS "Public." |
| 8C4E | .cat_access_setup←1← 8C42 BNE |
| LDY #&15 ; Y=function code for HDRFN | |
| 8C50 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8C53 | INX ; X=1: past command code byte |
| 8C54 | LDY #&10 ; Y=&10: print 16 characters |
| 8C56 | JSR print_reply_counted ; Print disc/CSD name from reply |
| 8C59 | LDX #4 ; X=4: space padding count |
| 8C5B | JSR print_spaces ; Print 4 spaces for alignment |
| 8C5E | JSR print_inline ; Print 'Option ' label |
| 8C61 | EQUS "Option " |
| 8C68 | LDA fs_boot_option ; Load boot option from workspace |
| 8C6B | TAX ; X = boot option for name table lookup |
| 8C6C | JSR print_hex ; Print boot option as hex digit |
| 8C6F | JSR print_inline ; Print ' (' |
| 8C72 | EQUS " (" |
| 8C74 | LDY option_name_offsets,x ; Load string offset for option name |
| 8C77 | .print_option_char←1← 8C80 BNE |
| LDA option_name_strings,y ; Load char from option name string | |
| 8C7A | BEQ done_option_name ; Zero terminator: name complete |
| 8C7C | JSR osasci ; Write character |
| 8C7F | INY ; Next character |
| 8C80 | BNE print_option_char ; Continue printing option name |
| 8C82 | .done_option_name←1← 8C7A BEQ |
| JSR print_inline ; Print ')' + CR + 'Dir. ' | |
| 8C85 | EQUS ").Dir. " |
| 8C8C | LDX #&11 ; X=&11: CSD name offset in reply |
| 8C8E | JSR print_reply_bytes ; Print current directory name |
| 8C91 | LDX #5 ; X=5: space padding count |
| 8C93 | JSR print_spaces ; Print 5 spaces for alignment |
| 8C96 | JSR print_inline ; Print 'Lib. ' label |
| 8C99 | EQUS "Lib. " |
| 8C9E | LDX #&1b ; X=&1B: library name offset in reply |
| 8CA0 | JSR print_reply_bytes ; Print library name |
| 8CA3 | JSR print_inline ; Print two CRs (blank line) |
| 8CA6 | EQUS ".." |
| 8CA8 | STY fs_func_code ; Y=0: initial examine start position |
| 8CAB | STY fs_work_4 ; Save start offset in zero page for loop |
| 8CAD | TXA ; A = reply buffer bytes consumed |
| 8CAE | EOR #&ff ; Complement for divide-by-subtraction |
| 8CB0 | .count_columns_loop←1← 8CB4 BCS |
| SEC ; SEC for subtraction | |
| 8CB1 | SBC l00b6 ; Subtract one column width (20) |
| 8CB3 | INY ; Count another entry that fits |
| 8CB4 | BCS count_columns_loop ; Loop while space remains |
| 8CB6 | STY fs_data_count ; Store entries per examine batch |
| 8CB9 | STY fs_work_5 ; Save batch size for loop reset |
| 8CBB | .cat_examine_continue←1← 8CE6 BNE |
| LDY fs_load_addr_3 ; Reload dir name offset for examine | |
| 8CBD | .cat_examine_loop |
| LDX fs_work_7 ; Load column count for display format | |
| 8CBF | STX fs_cmd_data ; Store column count in command data |
| 8CC2 | LDX #3 ; X=3: copy directory name at offset 3 |
| 8CC4 | JSR copy_string_from_offset ; Append directory name to examine command |
| 8CC7 | LDY #3 ; Y=function code for HDRFN |
| 8CC9 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8CCC | LDA fs_cmd_data ; Load entry count from reply |
| 8CCF | BEQ set_handle_return ; Zero entries returned: catalogue done |
| 8CD1 | LDX #2 ; X=2: first entry offset in reply |
| 8CD3 | JSR print_dir_from_offset ; Print/format this directory entry |
| 8CD6 | CLC ; CLC for addition |
| 8CD7 | LDA fs_work_4 ; Load current examine start offset |
| 8CD9 | ADC fs_cmd_data ; Add entries returned this batch |
| 8CDC | STA fs_func_code ; Update next examine start offset |
| 8CDF | STA fs_work_4 ; Save updated start offset |
| 8CE1 | LDA fs_work_5 ; Reload batch size for next request |
| 8CE3 | STA fs_data_count ; Store batch size in command buffer |
| 8CE6 | BNE cat_examine_continue ; Loop for remaining characters |
| 8CE8 | JMP l212e ; Fallthrough (also boot string 'L.!') |
Boot command strings for auto-bootThe four boot options use OSCLI strings at offsets within page &8C: Option 0 (Off): offset &F7 → &8CF7 = bare CR (empty command) Option 1 (Load): offset &E8 → &8CE8 = "L.!BOOT" (dual-purpose: the JMP &212E instruction at &8CE8 has opcode &4C='L' and operand bytes &2E='.' &21='!', forming the string "L.!") Option 2 (Run): offset &EA → boot_cmd_strings-1 = "!BOOT" (bare filename = *RUN) Option 3 (Exec): offset &F0 → &8CF0 = "E.!BOOT" This is a classic BBC ROM space optimisation: the JMP instruction's bytes serve double duty as both executable code and ASCII text. |
|
| 8CEB | .boot_cmd_strings |
| EQUS "BOOT" | |
| 8CEF | EQUB &0D |
| 8CF0 | EQUS "E.!BOOT" |
| 8CF7 | EQUB &0D |
Set library handleStores Y into &0E04 (library directory handle in FS workspace). Falls through to set_handle_return (JMP restore_args_return) if Y is non-zero.
|
||||
| 8CF8 | .fsreply_5_set_lib | |||
| STY fs_lib_handle ; Store Y (library handle) to &0E04 | ||||
| 8CFB | BNE set_handle_return ; Non-zero: continue to set handle | |||
| fall through ↓ | ||||
Set CSD handleStores Y into &0E03 (current selected directory handle). Falls through to set_handle_return (JMP restore_args_return).
|
||||
| 8CFD | .fsreply_3_set_csd | |||
| STY fs_csd_handle ; Store Y (CSD handle) to &0E03 | ||||
| 8D00 | .set_handle_return←3← 8CCF BEQ← 8CFB BNE← 8D2E BCC | |||
| JMP restore_args_return ; Jump to restore_args_return | ||||
Boot option → OSCLI string offset tableFour bytes indexed by the boot option value (0-3). Each byte is the low byte of a pointer into page &8C, where the OSCLI command string for that boot option lives. See boot_cmd_strings. |
|
| 8D03 | .boot_option_offsets←1← 8D33 LDX |
| EQUB &F7 ; Opt 0 (Off): bare CR | |
| 8D04 | EQUB &E8 ; Opt 1 (Load): L.!BOOT |
| 8D05 | EQUB &EA ; Opt 2 (Run): !BOOT |
| 8D06 | EQUB &F0 ; Opt 3 (Exec): E.!BOOT |
"I AM" command handlerDispatched from the command match table when the user types "*I AM <station>" or "*I AM <network>.<station>". Skips leading spaces via skip_spaces, then calls parse_decimal twice if a dot separator is present. The first number becomes the network (&0E01, via TAX pass-through in parse_decimal) and the second becomes the station (&0E00). With a single number, it is stored as the station and the network defaults to 0 (local). Then forwards the command to the fileserver via forward_star_cmd. |
|
| 8D07 | .i_am_handler |
| JSR skip_spaces ; Skip spaces before station number | |
| 8D0A | BCS jmp_restore_args ; C=1: alphabetic, forward to FS |
| 8D0C | LDA #0 ; A=0: default network (local) |
| 8D0E | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 8D11 | BCC fsreply_handle_copy ; C=0: no dot, single number only |
| 8D13 | INY ; Y=offset into (fs_options) buffer |
| 8D14 | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 8D17 | .fsreply_handle_copy←1← 8D11 BCC |
| STA fs_server_stn ; A=parsed value (accumulated in &B2) | |
| 8D1A | STX fs_server_net ; X=initial A value (saved by TAX) |
| 8D1D | .jmp_restore_args←1← 8D0A BCS |
| JMP forward_star_cmd ; Restore A/X/Y and return to caller | |
Copy FS reply handles to workspace and execute boot commandSEC entry (LOGIN): copies 4 bytes from &0F05-&0F08 (FS reply) to &0E02-&0E05 (URD, CSD, LIB handles and boot option), then looks up the boot option in boot_option_offsets to get the OSCLI command string and executes it via JMP oscli. The carry flag distinguishes LOGIN (SEC) from SDISC (CLC) — both share the handle-copying code, but only LOGIN executes the boot command. This use of the carry flag to select behaviour between two callers avoids duplicating the handle-copy loop. |
|
| 8D20 | .fsreply_1_copy_handles_boot |
| SEC ; Set carry: LOGIN path (copy + boot) | |
| fall through ↓ | |
Copy FS reply handles to workspace (no boot)CLC entry (SDISC): copies handles only, then jumps to set_handle_return. Called when the FS reply contains updated handle values but no boot action is needed. |
|
| 8D21 | .fsreply_2_copy_handles |
| LDX #3 ; Copy 4 bytes: boot option + 3 handles | |
| 8D23 | BCC logon3 ; SDISC: skip boot option, copy handles only |
| 8D25 | .logon2←1← 8D2C BPL |
| LDA fs_cmd_data,x ; Load from FS reply (&0F05+X) | |
| 8D28 | STA fs_urd_handle,x ; Store to handle workspace (&0E02+X) |
| 8D2B | .logon3←1← 8D23 BCC |
| DEX ; Next handle (descending) | |
| 8D2C | BPL logon2 ; Loop while X >= 0 |
| 8D2E | BCC set_handle_return ; SDISC: done, restore args and return |
| fall through ↓ | |
Execute boot command via OSCLIReached from fsreply_1_copy_handles_boot when carry is set (LOGIN path). Reads the boot option from fs_boot_option (&0E05), looks up the OSCLI command string offset from boot_option_offsets+1, and executes the boot command via JMP oscli with page &8D. |
|
| 8D30 | .boot_cmd_execute |
| LDY fs_boot_option ; Y = boot option from FS workspace | |
| 8D33 | LDX boot_option_offsets,y ; X = command string offset from table |
| 8D36 | LDY #&8c ; Y = &8D (high byte of command address) |
| 8D38 | JMP oscli ; Execute boot command string via OSCLI |
Option name stringsNull-terminated strings for the four boot option names: "Off", "Load", "Run", "Exec" Used by fscv_5_cat to display the current boot option setting. |
|
| 8D3B | .option_name_strings←1← 8C77 LDA |
| EQUS "Off" | |
| 8D3E | EQUB &00 |
| 8D3F | EQUS "Load" |
| 8D43 | EQUB &00 |
| 8D44 | EQUS "Run" |
| 8D47 | EQUB &00 |
| 8D48 | EQUS "Exec" |
Option name offsetsFour-byte table of offsets into option_name_strings: 0, 4, 9, &0D — one per boot option value (0-3). |
|
| 8D4C | .option_name_offsets←1← 8C74 LDY |
| BRK ; Offset 0 (BRK opcode as zero byte) | |
| 8D4D | EQUB &04, &09, &0D |
Print reply buffer bytesPrints Y characters from the FS reply buffer (&0F05+X) to the screen via OSASCI. X = starting offset, Y = count. Used by fscv_5_cat to display directory and library names. |
|
| 8D50 | .print_reply_bytes←3← 8C1E JSR← 8C8E JSR← 8CA0 JSR |
| LDY #&0a ; Y=10: default character count | |
| 8D52 | .print_reply_counted←2← 8C56 JSR← 8D5A BNE |
| LDA fs_cmd_data,x ; Load character from reply buffer | |
| 8D55 | JSR osasci ; Write character |
| 8D58 | INX ; Advance to next character |
| 8D59 | DEY ; Decrement remaining count |
| 8D5A | BNE print_reply_counted ; Loop until count exhausted |
| 8D5C | RTS ; Return to caller |
Print spacesPrints X space characters via print_space. Used by fscv_5_cat to align columns in the directory listing. |
|
| 8D5D | .print_spaces←4← 8C31 JSR← 8C5B JSR← 8C93 JSR← 8D61 BNE |
| JSR print_space ; Print one space character | |
| 8D60 | DEX ; Decrement space count |
| 8D61 | BNE print_spaces ; Loop until all spaces printed |
| 8D63 | RTS ; Return to caller |
Copy filename to FS command bufferEntry with X=0: copies from (fs_crc_lo),Y to &0F05+X until CR. Used to place a filename into the FS command buffer before sending to the fileserver. Falls through to copy_string_to_cmd. |
|
| 8D64 | .copy_filename←3← 8079 JSR← 86CC JSR← 888B JSR |
| LDX #0 ; X=0: start of output buffer | |
| fall through ↓ | |
Copy string to FS command bufferEntry with X and Y specified: copies bytes from (fs_crc_lo),Y to &0F05+X, stopping when a CR (&0D) is encountered. The CR itself is also copied. Returns with X pointing past the last byte written.
|
|||||||||||
| 8D66 | .copy_string_to_cmd←4← 877A JSR← 8884 JSR← 88A6 JSR← 8965 JSR | ||||||||||
| LDY #0 ; Start copying from offset 0 | |||||||||||
| 8D68 | .copy_string_from_offset←3← 8C14 JSR← 8CC4 JSR← 8D71 BNE | ||||||||||
| LDA (fs_crc_lo),y ; Load next byte from source string | |||||||||||
| 8D6A | STA fs_cmd_data,x ; Store to command buffer | ||||||||||
| 8D6D | INX ; Advance write position | ||||||||||
| 8D6E | INY ; Advance read position | ||||||||||
| 8D6F | EOR #&0d ; XOR with CR: result=0 if byte was CR | ||||||||||
| 8D71 | BNE copy_string_from_offset ; Loop until CR copied | ||||||||||
| 8D73 | .return_copy_string←1← 8D79 BMI | ||||||||||
| RTS ; Return to caller | |||||||||||
Print directory name from reply bufferPrints characters from the FS reply buffer (&0F05+X onwards). Null bytes (&00) are replaced with CR (&0D) for display. Stops when a byte with bit 7 set is encountered (high-bit terminator). Used by fscv_5_cat to display Dir. and Lib. paths. |
|
| 8D74 | .fsreply_0_print_dir |
| LDX #0 ; X=0: start of reply buffer | |
| 8D76 | .print_dir_from_offset←2← 8CD3 JSR← 8D83 BNE |
| LDA fs_cmd_data,x ; Load character from reply | |
| 8D79 | BMI return_copy_string ; Bit 7 set: end of string |
| 8D7B | BNE infol2 ; Non-zero: printable character |
| 8D7D | LDA #&0d ; Replace null with CR |
| 8D7F | .infol2←1← 8D7B BNE |
| JSR osasci ; Write character 13 | |
| 8D82 | INX ; Advance to next character |
| 8D83 | BNE print_dir_from_offset ; Continue printing directory path |
| fall through ↓ | |
Send FS load-as-command and execute responseSets up an FS command with function code &05 (FCCMND: load as command) using send_fs_examine. If a Tube co-processor is present (tx_in_progress != 0), transfers the response data to the Tube via tube_addr_claim. Otherwise jumps via the indirect pointer at (&0F09) to execute at the load address. |
|
| 8D85 | .fsreply_4_notify_exec |
| LDX #&0e ; X=&0E: OSWORD &10 parameter block size | |
| 8D87 | STX fs_block_offset ; Y=0: param block offset |
| 8D89 | LDA #&10 ; A=&10: OSWORD &10 (open RXCB) |
| 8D8B | STA fs_options ; Issue OSWORD &10 to open RXCB |
| 8D8D | LDX #&4a ; Load RXCB number from result |
| 8D8F | LDY #5 ; Zero: no free RXCB, skip |
| 8D91 | JSR send_fs_examine ; Store RXCB number in exec context |
| 8D94 | LDA tx_in_progress ; Y=&70: FS workspace offset Load reply byte from workspace |
| 8D97 | BEQ net_handle_validate ; Store to exec param area |
| 8D99 | ADC fs_load_upper ; Advance to next param byte Compare against boundary |
| 8D9C | ADC fs_addr_check ; Loop for all exec parameters |
| 8D9F | BCS net_handle_validate ; Store final byte |
| 8DA1 | JSR tube_claim_loop ; X=&16: OSBYTE param A=&C7: OSBYTE read/write flag |
| 8DA4 | LDX #9 ; Enable exec notification via OSBYTE |
| 8DA6 | LDY #&0f ; Y=&0F: workspace offset for params |
| 8DA8 | LDA #4 ; A=4: Tube claim type (multi-byte transfer) |
| 8DAA | JMP tube_addr_claim ; Claim Tube for address transfer |
| 8DAD | .net_handle_validate←2← 8D97 BEQ← 8D9F BCS |
| JMP (fs_load_vector) ; Execute at load address | |
*NET1: read file handle from received packetReads a file handle byte from offset &6F in the RX buffer (net_rx_ptr), stores it in &F0, then falls through to the common handle workspace cleanup at clear_svc_return. |
|
| 8DB0 | .net_1_read_handle |
| LDY #&6f ; Y=&6F: handle offset in RX buffer | |
| 8DB2 | LDA (net_rx_ptr),y ; Load handle byte from RX data |
| 8DB4 | STA osword_pb_ptr ; Store handle to &F0 |
| 8DB6 | BCC clear_svc_return ; Branch to cleanup path |
| fall through ↓ | |
Calculate handle workspace offsetConverts a file handle number (in A) to a byte offset (in Y) into the NFS handle workspace. The calculation is A*12: ASL A (A*2), ASL A (A*4), PHA, ASL A (A*8), ADC stack (A*8 + A*4 = A*12). Validates that the offset is < &48 (max 6 handles × 12 bytes per handle entry = 72 bytes). If invalid (>= &48), returns with C set and Y=0, A=0 as an error indicator.
|
|||||||||||
| 8DB8 | .calc_handle_offset←5← 82B7 JSR← 8DCC JSR← 8DE2 JSR← 8F02 JSR← 8F1B JSR | ||||||||||
| ASL ; A = handle * 2 | |||||||||||
| 8DB9 | ASL ; A = handle * 4 | ||||||||||
| 8DBA | PHA ; Push handle*4 onto stack | ||||||||||
| 8DBB | ASL ; A = handle * 8 | ||||||||||
| 8DBC | TSX ; X = stack pointer | ||||||||||
| 8DBD | ADC l0101,x ; A = handle*8 + handle*4 = handle*12 | ||||||||||
| 8DC0 | TAY ; Y = offset into handle workspace | ||||||||||
| 8DC1 | PLA ; Clean up stack (discard handle*4) | ||||||||||
| 8DC2 | CMP #&48 ; Offset >= &48? (6 handles max) | ||||||||||
| 8DC4 | BCC return_calc_handle ; Valid: return with C clear | ||||||||||
| 8DC6 | LDY #0 ; Invalid: Y = 0 | ||||||||||
| 8DC8 | TYA ; A = 0, C set (error) A=&00 | ||||||||||
| 8DC9 | .return_calc_handle←1← 8DC4 BCC | ||||||||||
| RTS ; Return after calculation | |||||||||||
*NET2: read handle entry from workspaceLooks up the handle in &F0 via calc_handle_offset. If the workspace slot contains &3F ('?', meaning unused/closed), returns 0. Otherwise returns the stored handle value. Clears rom_svc_num on exit. |
|
| 8DCA | .net_2_read_handle_entry |
| LDA osword_pb_ptr ; Load handle number from &F0 | |
| 8DCC | JSR calc_handle_offset ; Look up handle &F0 in workspace |
| 8DCF | BCS rxpol2 ; Invalid handle: return 0 |
| 8DD1 | LDA (nfs_workspace),y ; Load stored handle value |
| 8DD3 | CMP #&3f ; &3F = unused/closed slot marker |
| 8DD5 | BNE store_handle_return ; Slot in use: return actual value |
| 8DD7 | .rxpol2←2← 8DCF BCS← 8DE5 BCS |
| LDA #0 ; Return 0 for closed/invalid handle | |
| 8DD9 | .store_handle_return←1← 8DD5 BNE |
| STA osword_pb_ptr ; Store result back to &F0 | |
| 8DDB | .clear_svc_return←3← 8DB6 BCC← 8DF1 BCC← 8DF6 BVC |
| LDA #0 ; A=0: clear service claim | |
| 8DDD | STA rom_svc_num ; Release ROM service number |
| 8DDF | RTS ; Return to caller |
*NET3: close handle (mark as unused)Looks up the handle in &F0 via calc_handle_offset. Writes &3F ('?') to mark the handle slot as closed in the NFS workspace. Preserves the carry flag state across the write using ROL/ROR on rx_status_flags. Clears rom_svc_num on exit. |
|
| 8DE0 | .net_3_close_handle |
| LDA osword_pb_ptr ; Load handle number from &F0 | |
| 8DE2 | JSR calc_handle_offset ; Look up handle &F0 in workspace |
| 8DE5 | BCS rxpol2 ; Invalid handle: return 0 |
| 8DE7 | ROL rx_status_flags ; Preserve carry via ROL |
| 8DEA | LDA #&3f ; A=&3F: handle closed/unused marker |
| 8DEC | STA (nfs_workspace),y ; Mark handle as closed in workspace |
| fall through ↓ | |
Restore RX flags after close handlePerforms ROR on rx_flags to restore the carry flag state that was preserved by the matching ROL in net_3_close_handle. Falls through to osword_12_handler (clearing fs_temp_ce). |
|
| 8DEE | .restore_rx_flags |
| ROR rx_status_flags ; Restore carry via ROR | |
| 8DF1 | BCC clear_svc_return ; C=0: branch to cleanup exit |
| fall through ↓ | |
*NET4: resume after remote operationCalls resume_after_remote (&8146) to re-enable the keyboard and send a completion notification. The BVC always branches to clear_svc_return since resume_after_remote returns with V clear (from CLV in prepare_cmd_clv). |
|
| 8DF3 | .net_4_resume_remote |
| JSR resume_after_remote ; Jump to clear_svc_restore_args | |
| 8DF6 | BVC clear_svc_return ; V always clear: branch to cleanup exit |
| fall through ↓ | |
Filing system OSWORD entrySubtracts &0F from the command code in &EF, giving a 0-4 index for OSWORD calls &0F-&13 (15-19). Falls through to the PHA/PHA/RTS dispatch at &8E02. |
|
| 8DF8 | .svc_8_osword |
| LDA osbyte_a_copy ; Command code from &EF | |
| 8DFA | SBC #&0f ; Subtract &0F: OSWORD &0F-&13 become indices 0-4 |
| 8DFC | BMI return_copy_param ; Outside our OSWORD range, exit |
| 8DFE | CMP #5 ; Only OSWORDs &0F-&13 (index 0-4) |
| 8E00 | BCS return_copy_param ; Index >= 5: not ours, return |
| fall through ↓ | |
| 8E02 | .fs_osword_dispatch |
| TAX ; X = sub-function code for table lookup | |
| 8E03 | LDA fs_osword_tbl_hi,x ; Load handler address high byte from table |
| 8E06 | PHA ; Push high byte for RTS dispatch |
| 8E07 | LDA fs_osword_tbl_lo,x ; Load handler address low byte from table |
| 8E0A | PHA ; Dispatch table: low bytes for OSWORD &0F-&13 handlers |
| 8E0B | LDY #2 ; Y=2: save 3 bytes (&AA-&AC) |
| 8E0D | .save1←1← 8E13 BPL |
| LDA fs_last_byte_flag,y ; Load param block pointer byte | |
| 8E10 | STA (net_rx_ptr),y ; Save to NFS workspace via (net_rx_ptr) |
| 8E12 | DEY ; Next byte (descending) |
| 8E13 | BPL save1 ; Loop for all 3 bytes |
| 8E15 | INY ; Y=0 after BPL exit; INY makes Y=1 |
| 8E16 | LDA (osword_pb_ptr),y ; Read sub-function code from (&F0)+1 |
| 8E18 | RTS ; RTS dispatches to pushed handler |
| 8E19 | .fs_osword_tbl_lo←1← 8E07 LDA |
| EQUB <(osword_0f_handler-1) ; Dispatch table: low bytes for OSWORD &0F-&13 handlers | |
| 8E1A | EQUB <(osword_10_handler-1) |
| 8E1B | EQUB <(osword_11_handler-1) |
| 8E1C | EQUB <(osword_12_handler-1) |
| 8E1D | EQUB <(econet_tx_rx-1) |
| 8E1E | .fs_osword_tbl_hi←1← 8E03 LDA |
| EQUB >(osword_0f_handler-1) ; Dispatch table: high bytes for OSWORD &0F-&13 handlers | |
| 8E1F | EQUB >(osword_10_handler-1) |
| 8E20 | EQUB >(osword_11_handler-1) |
| 8E21 | EQUB >(osword_12_handler-1) |
| 8E22 | EQUB >(econet_tx_rx-1) |
Bidirectional block copy between OSWORD param block and workspace.C=1: copy X+1 bytes from (&F0),Y to (fs_crc_lo),Y (param to workspace) C=0: copy X+1 bytes from (fs_crc_lo),Y to (&F0),Y (workspace to param) |
|
| 8E23 | .copy_param_block←5← 8E31 BPL← 8E47 JSR← 8E63 JSR← 8E97 JSR← 8F32 JSR |
| BCC load_workspace_byte ; C=0: workspace to param direction | |
| 8E25 | LDA (osword_pb_ptr),y ; Load byte from param block |
| 8E27 | STA (fs_crc_lo),y ; Store to workspace |
| 8E29 | BCS copyl3 ; Always taken (C still set) ALWAYS branch |
| 8E2B | .load_workspace_byte←1← 8E23 BCC |
| LDA (fs_crc_lo),y ; Load byte from workspace | |
| 8E2D | STA (osword_pb_ptr),y ; Store to param block (no-op if C=1) |
| 8E2F | .copyl3←1← 8E29 BCS |
| INY ; Advance to next byte | |
| 8E30 | DEX ; Decrement byte counter |
| 8E31 | BPL copy_param_block ; Loop while X >= 0 |
| 8E33 | .return_copy_param←2← 8DFC BMI← 8E00 BCS |
| RTS ; Return after copy | |
OSWORD &0F handler: initiate transmit (CALLTX)Checks the TX semaphore (TXCLR at &0D62) via ASL -- if carry is clear, a TX is already in progress and the call returns an error, preventing user code from corrupting a system transmit. Otherwise copies 16 bytes from the caller's OSWORD parameter block into the user TX control block (UTXCB) in static workspace. The TXCB pointer is copied to LTXCBP only after the semaphore is claimed, ensuring the low-level transmit code (BRIANX) sees a consistent pointer -- if copied before claiming, another transmitter could modify TXCBP between the copy and the claim.
|
|||||||||||||
| 8E34 | .osword_0f_handler | ||||||||||||
| ASL tx_ctrl_status ; ASL: set C if TX in progress | |||||||||||||
| 8E37 | BCC osword_12_subfunc ; C=0: read path | ||||||||||||
| 8E39 | LDA net_rx_ptr_hi ; User TX CB in workspace page (high byte) | ||||||||||||
| 8E3B | STA fs_crc_hi ; Set param block high byte | ||||||||||||
| 8E3D | STA nmi_tx_block_hi ; Set LTXCBP high byte for low-level TX | ||||||||||||
| 8E3F | LDA #&6f ; &6F: offset into workspace for user TXCB | ||||||||||||
| 8E41 | STA fs_crc_lo ; Set param block low byte | ||||||||||||
| 8E43 | STA nmi_tx_block ; Set LTXCBP low byte for low-level TX | ||||||||||||
| 8E45 | LDX #&0f ; X=15: copy 16 bytes (OSWORD param block) | ||||||||||||
| 8E47 | JSR copy_param_block ; Copy param block to user TX control block | ||||||||||||
| 8E4A | JSR trampoline_tx_setup ; Set up and start low-level transmit | ||||||||||||
| 8E4D | JMP clear_svc_restore_args ; Exit: release service claim | ||||||||||||
| 8E50 | .osword_12_subfunc←1← 8E37 BCC | ||||||||||||
| SEC ; SEC: alternate entry for OSWORD &11 | |||||||||||||
| 8E51 | TYA ; A = param block high for branch | ||||||||||||
| 8E52 | BCS readry ; ALWAYS branch | ||||||||||||
OSWORD &11 handler: read JSR arguments (READRA)Copies the JSR (remote procedure call) argument buffer from the static workspace page back to the caller's OSWORD parameter block. Reads the buffer size from workspace offset JSRSIZ, then copies that many bytes. After the copy, clears the old LSTAT byte via CLRJSR to reset the protection status. Also provides READRB as a sub-entry (&8E6B) to return just the buffer size and args size without copying the data. |
|
| 8E54 | .osword_11_handler |
| LDA net_rx_ptr_hi ; Set source high byte from workspace page | |
| 8E56 | STA fs_crc_hi ; Store as copy source high byte in &BF |
| 8E58 | LDY #&7f ; JSRSIZ at workspace offset &7F |
| 8E5A | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8E5C | INY ; Y=&80: start of JSR argument data Y=&80 |
| 8E5D | STY fs_crc_lo ; Store &80 as copy source low byte |
| 8E5F | TAX ; X = buffer size (loop counter) |
| 8E60 | DEX ; X = size-1 (0-based count for copy) |
| 8E61 | LDY #0 ; Y=0: start of destination param block |
| 8E63 | JSR copy_param_block ; Copy X+1 bytes from workspace to param |
| 8E66 | JSR clear_jsr_protection ; Reset JSR protection status |
| 8E69 | BCC set_carry_dispatch ; Branch to set carry and dispatch |
| 8E6B | .read_args_size←1← 8EBE BEQ |
| LDY #&7f ; Y=&7F: JSRSIZ offset (READRB entry) | |
| 8E6D | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8E6F | LDY #1 ; Y=1: param block offset for size byte |
| 8E71 | STA (osword_pb_ptr),y ; Store buffer size to (&F0)+1 |
| 8E73 | INY ; Y=2: param block offset for args size Y=&02 |
| 8E74 | LDA #&80 ; A=&80: argument data starts at offset &80 |
| 8E76 | .readry←1← 8E52 BCS |
| STA (osword_pb_ptr),y ; Store args start offset to (&F0)+2 | |
| 8E78 | BCS carry_exit_or_read ; Always taken (SEC set above) |
| 8E7A | .osword_12_ws_offsets←1← 8E8E LDA |
| EQUB &FF, &01 | |
| fall through ↓ | |
OSWORD &12 handler: read/set state information (RS)Dispatches on the sub-function code (0-9): 0: read FS station (FSLOCN at &0E00) 1: set FS station 2: read printer server station (PSLOCN) 3: set printer server station 4: read protection masks (LSTAT at &D63) 5: set protection masks 6: read context handles (URD/CSD/LIB, converted from internal single-bit form back to handle numbers) 7: set context handles (converted to internal form) 8: read local station number 9: read JSR arguments buffer size Even-numbered sub-functions read; odd-numbered ones write. Uses the bidirectional copy at &8E23 for station read/set. |
|
| 8E7C | .osword_12_handler |
| CMP #6 ; Sub-function >= 6? (handle ops) | |
| 8E7E | BCS rsl1 ; Sub >= 6: handle/station/error |
| 8E80 | CMP #4 ; Sub-function >= 4? (protection) |
| 8E82 | BCS rssl1 ; Sub-function 4 or 5: read/set protection |
| 8E84 | LSR ; LSR: 0->0, 1->0, 2->1, 3->1 |
| 8E85 | LDX #&0d ; X=&0D: default to static workspace page |
| 8E87 | TAY ; Transfer LSR result to Y for indexing |
| 8E88 | BEQ set_workspace_page ; Y=0 (sub 0-1): use page &0D |
| 8E8A | LDX nfs_workspace_hi ; Y=1 (sub 2-3): use dynamic workspace |
| 8E8C | .set_workspace_page←1← 8E88 BEQ |
| STX fs_crc_hi ; Store workspace page in &BF (hi byte) | |
| 8E8E | LDA osword_12_ws_offsets,y ; Load offset: &FF (sub 0-1) or &01 (sub 2-3) |
| 8E91 | STA fs_crc_lo ; Store offset in &BE (lo byte) |
| 8E93 | LDX #1 ; X=1: copy 2 bytes |
| 8E95 | LDY #1 ; Y=1: start at param block offset 1 |
| 8E97 | JSR copy_param_block ; Copy station bytes to/from workspace |
| 8E9A | BNE set_carry_dispatch ; Always taken (Y=2 after copy) |
| 8E9C | .rssl1←1← 8E82 BCS |
| LSR ; LSR A: test bit 0 of sub-function | |
| 8E9D | INY ; Y=1: offset for protection byte |
| 8E9E | LDA (osword_pb_ptr),y ; Load protection byte from param block |
| 8EA0 | BCS rssl2 ; C=1 (odd sub): set protection |
| 8EA2 | LDA prot_status ; C=0 (even sub): read current status |
| 8EA5 | STA (osword_pb_ptr),y ; Return current value to param block |
| 8EA7 | .rssl2←1← 8EA0 BCS |
| STA prot_status ; Update protection status | |
| 8EAA | STA rx_ctrl_copy ; Also save as JSR mask backup |
| 8EAD | .set_carry_dispatch←2← 8E69 BCC← 8E9A BNE |
| SEC ; SEC: set carry for exit | |
| 8EAE | BCS carry_exit_or_read ; ALWAYS branch |
| 8EB0 | .read_local_station←1← 8EBA BEQ |
| LDA tx_clear_flag ; Load local station number | |
| 8EB3 | INY ; Y=1: param block offset for result |
| 8EB4 | STA (osword_pb_ptr),y ; Return station number to caller |
| 8EB6 | BCS carry_exit_or_read ; Always taken (C set above) |
| 8EB8 | .rsl1←1← 8E7E BCS |
| CMP #8 ; Sub-function 8: read FS handle | |
| 8EBA | BEQ read_local_station ; Match: read handle from RX buffer |
| 8EBC | CMP #9 ; Sub-function 9: read args size |
| 8EBE | BEQ read_args_size ; Match: read ARGS buffer info |
| 8EC0 | BPL osword_12_error ; Sub >= 10 (bit 7 clear): read error |
| 8EC2 | LDY #3 ; Y=3: start from handle 3 (descending) |
| 8EC4 | LSR ; LSR: test read/write bit |
| 8EC5 | BCC readc1 ; C=0: read handles from workspace |
| 8EC7 | STY nfs_temp ; Init loop counter at Y=3 |
| 8EC9 | .copy_handles_to_ws←1← 8ED8 BNE |
| LDY nfs_temp ; Reload loop counter | |
| 8ECB | LDA (osword_pb_ptr),y ; Read handle from caller's param block |
| 8ECD | JSR handle_to_mask_a ; Convert handle number to bitmask |
| 8ED0 | TYA ; TYA: get bitmask result |
| 8ED1 | LDY nfs_temp ; Reload loop counter |
| 8ED3 | STA fs_server_net,y ; Store bitmask to FS server table |
| 8ED6 | DEC nfs_temp ; Next handle (descending) |
| 8ED8 | BNE copy_handles_to_ws ; Loop for handles 3,2,1 |
| 8EDA | BEQ clear_svc_restore_args ; ALWAYS branch |
| 8EDC | .osword_12_error←1← 8EC0 BPL |
| INY ; Y=1: param block offset for error | |
| 8EDD | LDA fs_last_error ; Load last FS error number |
| 8EE0 | STA (osword_pb_ptr),y ; Return error code to caller |
| 8EE2 | .carry_exit_or_read←3← 8E78 BCS← 8EAE BCS← 8EB6 BCS |
| BCS clear_svc_restore_args ; Exit with carry set | |
| 8EE4 | .readc1←2← 8EC5 BCC← 8EED BNE |
| LDA fs_server_net,y ; A=single-bit bitmask | |
| 8EE7 | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) |
| 8EEA | STA (osword_pb_ptr),y ; A=handle number (&20-&27) Y=preserved |
| 8EEC | DEY ; Next handle (descending) Y=parameter block address high byte |
| 8EED | BNE readc1 ; Loop for handles 3,2,1 |
| 8EEF | BEQ clear_svc_restore_args ; ALWAYS branch |
OSWORD &10 handler: open/read RX control block (OPENRX)If the first byte of the caller's parameter block is zero, scans for a free RXCB (flag byte = &3F = deleted) starting from RXCB #3 (RXCBs 0-2 are dedicated: printer, remote, FS). Returns the RXCB number in the first byte, or zero if none free. If the first byte is non-zero, reads the specified RXCB's data back into the caller's parameter block (12 bytes) and then deletes the RXCB by setting its flag byte to &3F -- a consume-once semantic so user code reads received data and frees the CB in a single atomic operation, preventing double-reads. The low-level user RX flag (LFLAG) is temporarily disabled via ROR/ROL during the operation to prevent the interrupt-driven receive code from modifying a CB that is being read or opened.
|
|||||||||||||
| 8EF1 | .osword_10_handler | ||||||||||||
| LDX nfs_workspace_hi ; Workspace page high byte | |||||||||||||
| 8EF3 | STX fs_crc_hi ; Set up pointer high byte in &AC | ||||||||||||
| 8EF5 | STY fs_crc_lo ; Save param block high byte in &AB | ||||||||||||
| 8EF7 | ROR rx_status_flags ; Disable user RX during CB operation | ||||||||||||
| 8EFA | LDA (osword_pb_ptr),y ; Read first byte of param block | ||||||||||||
| 8EFC | STA fs_last_byte_flag ; Save: 0=open new, non-zero=read RXCB | ||||||||||||
| 8EFE | .scan_or_read_rxcb | ||||||||||||
| BNE read_rxcb ; Non-zero: read specified RXCB | |||||||||||||
| 8F00 | LDA #3 ; Start scan from RXCB #3 (0-2 reserved) | ||||||||||||
| 8F02 | .scan0←1← 8F14 BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8F05 | BCS openl4 ; Invalid RXCB: return zero | ||||||||||||
| 8F07 | LSR ; LSR twice: byte offset / 4 | ||||||||||||
| 8F08 | LSR ; Yields RXCB number from offset | ||||||||||||
| 8F09 | TAX ; X = RXCB number for iteration | ||||||||||||
| 8F0A | LDA (fs_crc_lo),y ; Read flag byte from RXCB workspace | ||||||||||||
| 8F0C | BEQ openl4 ; Zero = end of CB list | ||||||||||||
| 8F0E | CMP #&3f ; &3F = deleted slot, free for reuse | ||||||||||||
| 8F10 | BEQ scan1 ; Found free slot | ||||||||||||
| 8F12 | INX ; Try next RXCB | ||||||||||||
| 8F13 | TXA ; A = next RXCB number | ||||||||||||
| 8F14 | BNE scan0 ; Continue scan (always branches) | ||||||||||||
| 8F16 | .scan1←1← 8F10 BEQ | ||||||||||||
| TXA ; A = free RXCB number | |||||||||||||
| 8F17 | LDX #0 ; X=0 for indexed indirect store | ||||||||||||
| 8F19 | STA (osword_pb_ptr,x) ; Return RXCB number to caller's byte 0 | ||||||||||||
| 8F1B | .read_rxcb←1← 8EFE BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8F1E | BCS openl4 ; Invalid: write zero to param block | ||||||||||||
| 8F20 | DEY ; Y = offset-1: points to flag byte | ||||||||||||
| 8F21 | STY fs_crc_lo ; Set &AB = workspace ptr low byte | ||||||||||||
| 8F23 | LDA #&c0 ; &C0: test mask for flag byte | ||||||||||||
| 8F25 | LDY #1 ; Y=1: flag byte offset in RXCB | ||||||||||||
| 8F27 | LDX #&0b ; Enable interrupts before transmit | ||||||||||||
| 8F29 | CPY fs_last_byte_flag ; Compare Y(1) with saved byte (open/read) | ||||||||||||
| 8F2B | ADC (fs_crc_lo),y ; ADC flag: test if slot is in use | ||||||||||||
| 8F2D | BEQ openl6 ; Dest station = &FFFF (accept reply from any station) | ||||||||||||
| 8F2F | BMI openl7 ; Negative: slot has received data | ||||||||||||
| 8F31 | .copy_rxcb_to_param←1← 8F41 BNE | ||||||||||||
| CLC ; C=0: workspace-to-param direction | |||||||||||||
| 8F32 | .openl6←1← 8F2D BEQ | ||||||||||||
| JSR copy_param_block ; Copy RXCB data to param block | |||||||||||||
| 8F35 | BCS reenable_rx ; Done: skip deletion on error | ||||||||||||
| 8F37 | LDA #&3f ; Mark CB as consumed (consume-once) | ||||||||||||
| 8F39 | LDY #1 ; Y=1: flag byte offset | ||||||||||||
| 8F3B | STA (fs_crc_lo),y ; Write &3F to mark slot deleted | ||||||||||||
| 8F3D | BNE reenable_rx ; Branch to exit (always taken) ALWAYS branch | ||||||||||||
| 8F3F | .openl7←1← 8F2F BMI | ||||||||||||
| ADC #1 ; Advance through multi-byte field | |||||||||||||
| 8F41 | BNE copy_rxcb_to_param ; Loop until all bytes processed | ||||||||||||
| 8F43 | DEY ; Y=-1 → Y=0 after STA below | ||||||||||||
| 8F44 | .openl4←3← 8F05 BCS← 8F0C BEQ← 8F1E BCS | ||||||||||||
| STA (osword_pb_ptr),y ; Return zero (no free RXCB found) | |||||||||||||
| 8F46 | .reenable_rx←2← 8F35 BCS← 8F3D BNE | ||||||||||||
| ROL rx_status_flags ; Re-enable user RX | |||||||||||||
| fall through ↓ | |||||||||||||
Clear service number and restore OSWORD argsShared exit for OSWORD handlers. Zeros rom_svc_num to release the service claim, then copies 3 bytes from (net_rx_ptr) back to the fs_last_byte_flag area, restoring the OSWORD argument state saved at entry. |
|
| 8F49 | .clear_svc_restore_args←6← 8E4D JMP← 8EDA BEQ← 8EE2 BCS← 8EEF BEQ← 8FBF BNE← 9005 JMP |
| LDY #0 ; Y=0: clear service claim | |
| 8F4B | STY rom_svc_num ; Release ROM service number |
| 8F4D | LDY #2 ; Y=2: copy 3 bytes (indices 2,1,0) |
| 8F4F | .rest1←1← 8F55 BPL |
| LDA (net_rx_ptr),y ; Load saved arg from (net_rx_ptr)+Y | |
| 8F51 | STA fs_last_byte_flag,y ; Restore saved OSWORD argument byte |
| 8F54 | DEY ; Decrement byte counter |
| 8F55 | BPL rest1 ; Loop for bytes 2,1,0 |
| 8F57 | RTS ; Return to caller |
Set up RX buffer pointers in NFS workspaceCalculates the start address of the RX data area (&F0+1) and stores it at workspace offset &28. Also reads the data length from (&F0)+1 and adds it to &F0 to compute the end address at offset &2C.
|
||||
| 8F58 | .setup_rx_buffer_ptrs←1← 8F8B JSR | |||
| LDY #&28 ; Y=&28: RXCB template offset | ||||
| 8F5A | LDA osword_pb_ptr ; A = base address low byte | |||
| 8F5C | ADC #1 ; A = base + 1 (skip length byte) | |||
| 8F5E | JSR store_16bit_at_y ; Receive data blocks until command byte = &00 or &0D | |||
| 8F61 | LDY #1 ; Read data length from (&F0)+1 | |||
| 8F63 | LDA (osword_pb_ptr),y ; A = data length byte | |||
| 8F65 | LDY #&2c ; Workspace offset &20 = RX data end | |||
| 8F67 | ADC osword_pb_ptr ; A = base + length = end address low | |||
| 8F69 | .store_16bit_at_y←1← 8F5E JSR | |||
| STA (nfs_workspace),y ; Store low byte of 16-bit address | ||||
| 8F6B | INY ; Advance to high byte offset | |||
| 8F6C | LDA osword_pb_ptr_hi ; A = high byte of base address | |||
| 8F6E | ADC #0 ; Add carry for 16-bit addition | |||
| 8F70 | STA (nfs_workspace),y ; Store high byte | |||
| 8F72 | RTS ; Return | |||
Econet transmit/receive handlerA=0: Initialise TX control block from ROM template at &8311 (zero entries substituted from NMI workspace &0DDA), transmit it, set up RX control block, and receive reply. A>=1: Handle transmit result (branch to cleanup at &8F49).
|
||||
| 8F73 | .econet_tx_rx | |||
| CMP #1 ; A=0: set up and transmit; A>=1: handle result | ||||
| 8F75 | BCS handle_tx_result ; A >= 1: handle TX result | |||
| 8F77 | LDY #&2f ; Y=&2F: start of template (descending) | |||
| 8F79 | .dofs01←1← 8F86 BNE | |||
| LDA init_tx_ctrl_port,y ; Load from ROM template (zero = use NMI workspace value) | ||||
| 8F7C | BNE store_txcb_byte ; Non-zero = use ROM template byte as-is | |||
| 8F7E | LDA l0dda,y ; Zero = substitute from NMI workspace | |||
| 8F81 | .store_txcb_byte←1← 8F7C BNE | |||
| STA (nfs_workspace),y ; Store to dynamic workspace | ||||
| 8F83 | DEY ; Descend through template | |||
| 8F84 | CPY #&23 ; Stop at offset &17 | |||
| 8F86 | BNE dofs01 ; Loop until all bytes copied | |||
| 8F88 | INY ; Y=&18: TX block starts here | |||
| 8F89 | STY net_tx_ptr ; Point net_tx_ptr at workspace+&18 | |||
| 8F8B | JSR setup_rx_buffer_ptrs ; Set up RX buffer start/end pointers | |||
| 8F8E | LDY #2 ; Y=2: port byte offset in RXCB | |||
| 8F90 | LDA #&90 ; A=&90: FS reply port | |||
| 8F92 | STA (osword_pb_ptr),y ; Store port &90 at (&F0)+2 | |||
| 8F94 | INY ; Y=&03 | |||
| 8F95 | INY ; Y=&04: advance to station address Y=&04 | |||
| 8F96 | .copy_fs_addr←1← 8F9E BNE | |||
| LDA fs_context_base,y ; Copy FS station addr from workspace | ||||
| 8F99 | STA (osword_pb_ptr),y ; Store to RX param block | |||
| 8F9B | INY ; Next byte | |||
| 8F9C | CPY #7 ; Done 3 bytes (Y=4,5,6)? | |||
| 8F9E | BNE copy_fs_addr ; No: continue copying | |||
| 8FA0 | LDA nfs_workspace_hi ; High byte of workspace for TX ptr | |||
| 8FA2 | STA net_tx_ptr_hi ; Store as TX pointer high byte | |||
| 8FA4 | CLI ; Enable interrupts before transmit | |||
| 8FA5 | JSR tx_poll_timeout ; Transmit with full retry | |||
| 8FA8 | LDY #&2c ; Y=&2C: RX end address offset | |||
| 8FAA | LDA #&ff ; Dest station = &FFFF (accept reply from any station) | |||
| 8FAC | STA (nfs_workspace),y ; Store end address low byte (&FF) | |||
| 8FAE | INY ; Y=&2d | |||
| 8FAF | STA (nfs_workspace),y ; Store end address high byte (&FF) | |||
| 8FB1 | LDY #&25 ; Y=&25: port byte in workspace RXCB | |||
| 8FB3 | LDA #&90 ; A=&90: FS reply port | |||
| 8FB5 | STA (nfs_workspace),y ; Store port to workspace RXCB | |||
| 8FB7 | DEY ; Y=&24 | |||
| 8FB8 | LDA #&7f ; A=&7F: flag byte = waiting for reply | |||
| 8FBA | STA (nfs_workspace),y ; Store flag byte to workspace RXCB | |||
| 8FBC | JSR send_to_fs_star ; Initiate receive with timeout | |||
| 8FBF | BNE clear_svc_restore_args ; Non-zero = error/timeout: jump to cleanup | |||
| 8FC1 | .handle_tx_result←1← 8F75 BCS | |||
| PHP ; Save processor flags | ||||
| 8FC2 | LDY #1 ; Y=1: first data byte offset | |||
| 8FC4 | LDA (osword_pb_ptr),y ; Load first data byte from RX buffer | |||
| 8FC6 | TAX ; X = first data byte (command code) | |||
| 8FC7 | INY ; Advance to next data byte Y=&02 | |||
| 8FC8 | LDA (osword_pb_ptr),y ; Load station address high byte | |||
| 8FCA | INY ; Advance past station addr Y=&03 | |||
| 8FCB | STY fs_crc_lo ; Save Y as data index | |||
| 8FCD | LDY #&72 ; Store station addr hi at (net_rx_ptr)+&72 | |||
| 8FCF | STA (net_rx_ptr),y ; Store to workspace | |||
| 8FD1 | DEY ; Y=&71 | |||
| 8FD2 | TXA ; A = command code (from X) | |||
| 8FD3 | STA (net_rx_ptr),y ; Store station addr lo at (net_rx_ptr)+&71 | |||
| 8FD5 | PLP ; Restore flags from earlier PHP | |||
| 8FD6 | BNE dofs2 ; First call: adjust data length | |||
| 8FD8 | .send_data_bytes←1← 8FF2 BNE | |||
| LDY fs_crc_lo ; Receive data blocks until command byte = &00 or &0D | ||||
| 8FDA | INC fs_crc_lo ; Advance data index for next iteration | |||
| 8FDC | LDA (osword_pb_ptr),y ; Load next data byte | |||
| 8FDE | LDY #&7d ; Y=&7D: store byte for TX at offset &7D | |||
| 8FE0 | STA (net_rx_ptr),y ; Store data byte at (net_rx_ptr)+&7D for TX | |||
| 8FE2 | PHA ; Save data byte for &0D check after TX | |||
| 8FE3 | JSR ctrl_block_setup_alt ; Set up TX control block | |||
| 8FE6 | CLI ; Enable interrupts for TX | |||
| 8FE7 | JSR tx_poll_core ; Enable IRQs and transmit Core transmit and poll routine (XMIT) | |||
| 8FEA | .delay_between_tx←1← 8FEB BNE | |||
| DEX ; Short delay loop between TX packets | ||||
| 8FEB | BNE delay_between_tx ; Spin until X reaches 0 | |||
| 8FED | PLA ; Restore data byte for terminator check | |||
| 8FEE | BEQ rx_first_packet ; Z=1: not intercepted, pass through | |||
| 8FF0 | EOR #&0d ; Test for end-of-data marker (&0D) | |||
| 8FF2 | BNE send_data_bytes ; Not &0D: continue with next byte | |||
| 8FF4 | .rx_first_packet←1← 8FEE BEQ | |||
| BEQ jmp_clear_svc_restore ; First packet: exit handler | ||||
| 8FF6 | .dofs2←1← 8FD6 BNE | |||
| JSR ctrl_block_setup_alt ; First-packet: set up control block | ||||
| 8FF9 | LDY #&7b ; Y=&7B: data length offset | |||
| 8FFB | LDA (net_rx_ptr),y ; Load current data length | |||
| 8FFD | ADC #3 ; Adjust data length by 3 for header bytes | |||
| 8FFF | STA (net_rx_ptr),y ; Store adjusted length | |||
| fall through ↓ | ||||
Enable interrupts and transmit via tx_poll_ffCLI to enable interrupts, then JMP tx_poll_ff. A short tail-call wrapper used after building the TX control block. |
|
| 9001 | .enable_irq_and_tx |
| CLI ; Enable interrupts | |
| 9002 | JSR tx_poll_ff ; Poll TX until complete |
| 9005 | .jmp_clear_svc_restore←1← 8FF4 BEQ |
| JMP clear_svc_restore_args ; Transmit via tx_poll_ff | |
NETVEC dispatch handler (ENTRY)Indirected from NETVEC at &0224. Saves all registers and flags, retrieves the reason code from the stacked A, and dispatches to one of 9 handlers (codes 0-8) via the PHA/PHA/RTS trampoline at &9021. Reason codes >= 9 are ignored. Dispatch targets (from NFS09): 0: no-op (RTS) 1-3: PRINT -- chars in printer buffer / Ctrl-B / Ctrl-C 4: NWRCH -- write character to screen (net write char) 5: SELECT -- printer selection changed 6: no-op (net read char -- not implemented) 7: NBYTE -- remote OSBYTE call 8: NWORD -- remote OSWORD call
|
|||||||||||
| 9008 | .osword_dispatch | ||||||||||
| PHP ; Save processor status | |||||||||||
| 9009 | PHA ; Save A (reason code) | ||||||||||
| 900A | TXA ; Save X | ||||||||||
| 900B | PHA ; Push X to stack | ||||||||||
| 900C | TYA ; Save Y | ||||||||||
| 900D | PHA ; Push Y to stack | ||||||||||
| 900E | TSX ; Get stack pointer for indexed access | ||||||||||
| 900F | LDA l0103,x ; Retrieve original A (function code) from stack | ||||||||||
| 9012 | CMP #9 ; Reason codes 0-8 only | ||||||||||
| 9014 | BCS entry1 ; Code >= 9: skip dispatch, restore regs | ||||||||||
| 9016 | TAX ; X = reason code for table lookup | ||||||||||
| 9017 | JSR osword_trampoline ; Dispatch to handler via trampoline | ||||||||||
| 901A | .entry1←1← 9014 BCS | ||||||||||
| PLA ; Restore Y | |||||||||||
| 901B | TAY ; Transfer to Y register | ||||||||||
| 901C | PLA ; Restore X | ||||||||||
| 901D | TAX ; Transfer to X register | ||||||||||
| 901E | PLA ; Restore A | ||||||||||
| 901F | PLP ; Restore processor status flags | ||||||||||
| 9020 | RTS ; Return with all registers preserved | ||||||||||
| 9021 | .osword_trampoline←1← 9017 JSR | ||||||||||
| LDA osword_tbl_hi,x ; PHA/PHA/RTS trampoline: push handler addr-1, RTS jumps to it | |||||||||||
| 9024 | PHA ; Push high byte of handler address | ||||||||||
| 9025 | LDA osword_tbl_lo,x ; Load handler low byte from table | ||||||||||
| 9028 | PHA ; Push low byte of handler address | ||||||||||
| 9029 | LDA osbyte_a_copy ; Load workspace byte &EF for handler | ||||||||||
| 902B | RTS ; RTS dispatches to pushed handler | ||||||||||
| 902C | .osword_tbl_lo←1← 9025 LDA | ||||||||||
| EQUB <(return_2-1) | |||||||||||
| 902D | EQUB <(remote_print_handler-1) | ||||||||||
| 902E | EQUB <(remote_print_handler-1) | ||||||||||
| 902F | EQUB <(remote_print_handler-1) | ||||||||||
| 9030 | EQUB <(net_write_char-1) | ||||||||||
| 9031 | EQUB <(printer_select_handler-1) | ||||||||||
| 9032 | EQUB <(return_2-1) | ||||||||||
| 9033 | EQUB <(remote_cmd_dispatch-1) | ||||||||||
| 9034 | EQUB <(remote_cmd_data-1) | ||||||||||
| 9035 | .osword_tbl_hi←1← 9021 LDA | ||||||||||
| EQUB >(return_2-1) | |||||||||||
| 9036 | EQUB >(remote_print_handler-1) | ||||||||||
| 9037 | EQUB >(remote_print_handler-1) | ||||||||||
| 9038 | EQUB >(remote_print_handler-1) | ||||||||||
| 9039 | EQUB >(net_write_char-1) | ||||||||||
| 903A | EQUB >(printer_select_handler-1) | ||||||||||
| 903B | EQUB >(return_2-1) | ||||||||||
| 903C | EQUB >(remote_cmd_dispatch-1) | ||||||||||
| 903D | EQUB >(remote_cmd_data-1) | ||||||||||
Fn 4: net write character (NWRCH)Writes a character (passed in Y) to the screen via OSWRITCH. Before the write, uses TSX to reach into the stack and zero the carry flag in the caller's saved processor status byte -- ROR followed by ASL on the stacked P byte (&0106,X) shifts carry out and back in as zero. This ensures the calling code's PLP restores carry=0, signalling "character accepted" without needing a separate CLC/PHP sequence. A classic 6502 trick for modifying return flags without touching the actual processor status.
|
|||||||||||
| 903E | .net_write_char | ||||||||||
| TSX ; Get stack pointer for flag access | |||||||||||
| 903F | ROR l0106,x ; ROR/ASL on stacked P: zeros carry to signal success | ||||||||||
| 9042 | ASL l0106,x ; ASL: restore P after ROR zeroed carry | ||||||||||
| 9045 | TYA ; Y = character to write | ||||||||||
| 9046 | LDY #&da ; Store character at workspace offset &DA | ||||||||||
| 9048 | STA (nfs_workspace),y ; Store char at workspace offset &DA | ||||||||||
| 904A | LDA #0 ; A=0: command type for net write char | ||||||||||
| fall through ↓ | |||||||||||
Set up TX control block and sendBuilds a TX control block at (nfs_workspace)+&0C from the current workspace state, then initiates transmission via the ADLC TX path. This is the common send routine used after command data has been prepared. The exact control block layout and field mapping need further analysis.
|
||||
| 904C | .setup_tx_and_send←3← 8159 JSR← 9093 JSR← 90F4 JSR | |||
| LDY #&d9 ; Y=&D9: command type offset | ||||
| 904E | STA (nfs_workspace),y ; Store command type at ws+&D9 | |||
| 9050 | LDA #&80 ; Mark TX control block as active (&80) | |||
| 9052 | LDY #&0c ; Y=&0C: TXCB start offset | |||
| 9054 | STA (nfs_workspace),y ; Set TX active flag at ws+&0C | |||
| 9056 | STY net_tx_ptr ; Redirect net_tx_ptr low to workspace | |||
| 9058 | LDX nfs_workspace_hi ; Load workspace page high byte | |||
| 905A | STX net_tx_ptr_hi ; Complete ptr redirect | |||
| 905C | JSR tx_poll_ff ; Transmit with full retry | |||
| 905F | LDA #&3f ; Mark TXCB as deleted (&3F) after transmit | |||
| 9061 | STA (net_tx_ptr,x) ; Write &3F to TXCB byte 0 | |||
| 9063 | RTS ; Return | |||
Fn 7: remote OSBYTE handler (NBYTE)Full RPC mechanism for OSBYTE calls across the network. When a machine is remoted, OSBYTE/OSWORD calls that affect terminal-side hardware (keyboard scanning, flash rates, etc.) must be indirected across the net. OSBYTE calls are classified into three categories: Y>0 (NCTBPL table): executed on BOTH machines (flash rates etc.) Y<0 (NCTBMI table): executed on terminal only, result sent back Y=0: not recognised, passed through unhandled Results returned via stack manipulation: the saved processor status byte at &0106 has V-flag (bit 6) forced on to tell the MOS the call was claimed (preventing dispatch to other ROMs), and the I-bit (bit 2) forced on to disable interrupts during register restoration, preventing race conditions. The carry flag in the saved P is also manipulated via ROR/ASL to zero it, signaling success to the caller. OSBYTE &81 (INKEY) gets special handling as it must read the terminal's keyboard. |
|
| 9064 | .remote_cmd_dispatch |
| LDY osword_pb_ptr_hi ; Load original Y (OSBYTE secondary param) | |
| 9066 | CMP #&81 ; OSBYTE &81 (INKEY): always forward to terminal |
| 9068 | BEQ dispatch_remote_osbyte ; Forward &81 to terminal for keyboard read |
| 906A | LDY #1 ; Y=1: search NCTBPL table (execute on both) |
| 906C | LDX #7 ; X=7: 8-entry NCTBPL table size |
| 906E | JSR match_osbyte_code ; Search for OSBYTE code in NCTBPL table |
| 9071 | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=1 (both) |
| 9073 | DEY ; Y=-1: search NCTBMI table (terminal only) |
| 9074 | DEY ; Second DEY: Y=&FF (from 1 via 0) |
| 9075 | LDX #&0e ; X=&0E: 15-entry NCTBMI table size |
| 9077 | JSR match_osbyte_code ; Search for OSBYTE code in NCTBMI table |
| 907A | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=&FF (terminal) |
| 907C | INY ; Y=0: OSBYTE not recognised, ignore |
| 907D | .dispatch_remote_osbyte←3← 9068 BEQ← 9071 BEQ← 907A BEQ |
| LDX #2 ; X=2 bytes to copy (default for RBYTE) | |
| 907F | TYA ; A=Y: check table match result |
| 9080 | BEQ return_nbyte ; Y=0: not recognised, return unhandled |
| 9082 | PHP ; Y>0 (NCTBPL): send only, no result expected |
| 9083 | BPL nbyte6 ; Y>0 (NCTBPL): no result expected, skip RX |
| 9085 | INX ; Y<0 (NCTBMI): X=3 bytes (result + P flags) X=&03 |
| 9086 | .nbyte6←1← 9083 BPL |
| LDY #&dc ; Y=&DC: top of 3-byte stack frame region | |
| 9088 | .nbyte1←1← 9090 BPL |
| LDA tube_claimed_id,y ; Copy OSBYTE args from stack frame to workspace | |
| 908B | STA (nfs_workspace),y ; Store to NFS workspace for transmission |
| 908D | DEY ; Next byte (descending) |
| 908E | CPY #&da ; Copied all 3 bytes? (&DC, &DB, &DA) |
| 9090 | BPL nbyte1 ; Loop for remaining bytes |
| 9092 | TXA ; A = byte count for setup_tx_and_send |
| 9093 | JSR setup_tx_and_send ; Build TXCB and transmit to terminal |
| 9096 | PLP ; Restore N flag from table match type |
| 9097 | BPL return_nbyte ; Y was positive (NCTBPL): done, no result |
| 9099 | LDA #&7f ; Set up RX control block to wait for reply |
| 909B | STA (net_tx_ptr,x) ; Write &7F to RXCB (wait for reply) |
| 909D | .poll_rxcb_loop←1← 909F BPL |
| LDA (net_tx_ptr,x) ; Poll RXCB for completion (bit7) | |
| 909F | BPL poll_rxcb_loop ; Bit7 clear: still waiting, poll again |
| 90A1 | TSX ; X = stack pointer for register restoration |
| 90A2 | LDY #&dd ; Y=&DD: saved P byte offset in workspace |
| 90A4 | LDA (nfs_workspace),y ; Load remote processor status from reply |
| 90A6 | ORA #&44 ; Force V=1 (claimed) and I=1 (no IRQ) in saved P |
| 90A8 | BNE nbyte5 ; ALWAYS branch (ORA #&44 never zero) ALWAYS branch |
| 90AA | .nbyte4←1← 90B3 BNE |
| DEY ; Previous workspace offset | |
| 90AB | DEX ; Previous stack register slot |
| 90AC | LDA (nfs_workspace),y ; Load next result byte (X, then Y) |
| 90AE | .nbyte5←1← 90A8 BNE |
| STA l0106,x ; Write result bytes to stacked registers | |
| 90B1 | CPY #&da ; Copied all result bytes? (P at &DA) |
| 90B3 | BNE nbyte4 ; Loop for remaining result bytes |
| 90B5 | .return_nbyte←2← 9080 BEQ← 9097 BPL |
| RTS ; Return to OSBYTE dispatcher | |
Search remote OSBYTE table for match (NCALLP)Searches remote_osbyte_table for OSBYTE code A. X indexes the last entry to check (table is scanned X..0). Returns Z=1 if found. Called twice by remote_cmd_dispatch: X=7 -> first 8 entries (NCTBPL: execute on both machines) X=14 -> all 15 entries (NCTBMI: execute on terminal only) The last 7 entries (&0B, &0C, &0F, &79, &7A, &E3, &E4) are terminal-only because they affect the local keyboard, buffers, or function keys. On entry: A = OSBYTE code, X = table size - 1 On exit: Z=1 if match found, Z=0 if not |
|
| 90B6 | .match_osbyte_code←3← 906E JSR← 9077 JSR← 90BC BPL |
| CMP remote_osbyte_table,x ; Compare OSBYTE code with table entry | |
| 90B9 | BEQ return_match_osbyte ; Match found: return with Z=1 |
| 90BB | DEX ; Next table entry (descending) |
| 90BC | BPL match_osbyte_code ; Loop for remaining entries |
| 90BE | .return_match_osbyte←1← 90B9 BEQ |
| RTS ; Return; Z=1 if match, Z=0 if not | |
| 90BF | .remote_osbyte_table←1← 90B6 CMP |
| EQUB &04 ; OSBYTE &04: cursor key status | |
| 90C0 | EQUB &09 ; OSBYTE &09: flash duration (1st colour) |
| 90C1 | EQUB &0A ; OSBYTE &0A: flash duration (2nd colour) |
| 90C2 | EQUB &14 ; OSBYTE &14: explode soft character RAM |
| 90C3 | EQUB &9A ; OSBYTE &9A: video ULA control register |
| 90C4 | EQUB &9B ; OSBYTE &9B: video ULA palette |
| 90C5 | EQUB &9C ; OSBYTE &9C: ACIA control register |
| 90C6 | EQUB &E2 ; OSBYTE &E2: function key &D0-&DF |
| 90C7 | EQUB &0B ; OSBYTE &0B: auto-repeat delay |
| 90C8 | EQUB &0C ; OSBYTE &0C: auto-repeat rate |
| 90C9 | EQUB &0F ; OSBYTE &0F: flush buffer class |
| 90CA | EQUB &79 ; OSBYTE &79: keyboard scan from X |
| 90CB | EQUB &7A ; OSBYTE &7A: keyboard scan from &10 |
| 90CC | EQUB &E3 ; OSBYTE &E3: function key &E0-&EF |
| 90CD | EQUB &E4 ; OSBYTE &E4: function key &F0-&FF |
Fn 8: remote OSWORD handler (NWORD)Only intercepts OSWORD 7 (make a sound) and OSWORD 8 (define an envelope). Unlike NBYTE which returns results, NWORD is entirely fire-and-forget — no return path is implemented. The developer explicitly noted this was acceptable since sound/envelope commands don't return meaningful results. Copies up to 14 parameter bytes from the RX buffer to workspace, tags the message as RWORD, and transmits. |
|
| 90CE | .remote_cmd_data |
| LDY #&0e ; Y=&0E: 14-byte parameter block | |
| 90D0 | CMP #7 ; OSWORD 7? (make sound) |
| 90D2 | BEQ copy_params_rword ; OSWORD 7 (sound): handle via common path |
| 90D4 | CMP #8 ; OSWORD 8 = define an envelope |
| 90D6 | BNE return_remote_cmd ; Not OSWORD 7 or 8: ignore (BNE exits) |
| 90D8 | .copy_params_rword←1← 90D2 BEQ |
| LDX #&db ; Point workspace to offset &DB for params | |
| 90DA | STX nfs_workspace ; Store workspace ptr offset &DB |
| 90DC | .copy_osword_params←1← 90E1 BPL |
| LDA (osword_pb_ptr),y ; Load param byte from OSWORD param block | |
| 90DE | STA (nfs_workspace),y ; Write param byte to workspace |
| 90E0 | DEY ; Next byte (descending) |
| 90E1 | BPL copy_osword_params ; Loop for all parameter bytes |
| 90E3 | INY ; Y=0 after loop |
| 90E4 | DEC nfs_workspace ; Point workspace to offset &DA |
| 90E6 | LDA osbyte_a_copy ; Load original OSWORD code |
| 90E8 | STA (nfs_workspace),y ; Store OSWORD code at ws+0 |
| 90EA | STY nfs_workspace ; Reset workspace ptr to base |
| 90EC | LDY #&14 ; Y=&14: command type offset |
| 90EE | LDA #&e9 ; Tag as RWORD (port &E9) |
| 90F0 | STA (nfs_workspace),y ; Store port tag at ws+&14 |
| 90F2 | LDA #1 ; A=1: single-byte TX |
| 90F4 | JSR setup_tx_and_send ; Set up TX and send RWORD packet |
| 90F7 | STX nfs_workspace ; Restore workspace ptr |
| 90F9 | JSR ctrl_block_setup_alt ; Set up control block for reply |
| 90FC | .return_remote_cmd←1← 90D6 BNE |
| RTS ; Return from remote command handler | |
Remote boot/execute handlerValidates byte 4 of the RX control block (must be zero), copies the 2-byte execution address from RX offsets &80/&81 into NFS workspace, sets up a control block, disables keyboard (OSBYTE &C9), then falls through to lang_3_execute_at_0100. |
|
| 90FD | .lang_1_remote_boot |
| LDY #4 ; Y=4: RX control block byte 4 | |
| 90FF | LDA (net_rx_ptr),y ; Load first data byte from RX |
| 9101 | BEQ remot1 ; Zero: standard boot, skip code |
| 9103 | .rchex←1← 9149 BNE |
| JMP clear_jsr_protection ; Load language ROM number | |
| 9106 | .remot1←2← 9101 BEQ← 913F BEQ |
| ORA #9 ; OR with 9: set remote boot bits | |
| 9108 | STA (net_rx_ptr),y ; Store modified control byte |
| 910A | LDX #&80 ; X=&80: exec address offset lo |
| 910C | LDY #&80 ; Y=&80: exec address offset hi |
| 910E | LDA (net_rx_ptr),y ; Load exec address low byte |
| 9110 | PHA ; Save boot type on stack |
| 9111 | INY ; Y=&81 |
| 9112 | LDA (net_rx_ptr),y ; Load exec address high byte |
| 9114 | LDY #&0f ; Y=&0F: workspace offset for hi |
| 9116 | STA (nfs_workspace),y ; Store filename offset at ws+&0F |
| 9118 | DEY ; Y=&0e |
| 9119 | PLA ; Restore boot type from stack |
| 911A | STA (nfs_workspace),y ; Copy command to &0100 area |
| 911C | JSR clear_osbyte_ce_cf ; Initialize OSBYTE vectors |
| 911F | JSR ctrl_block_setup ; Set up control block for boot |
| 9122 | LDX #1 ; X=1: enable parameter |
| 9124 | LDY #0 ; Y=0: second argument for OSBYTE |
| 9126 | LDA #osbyte_read_write_econet_keyboard_disable ; A=&C9: disable keyboard for boot |
| 9128 | JSR osbyte ; Disable keyboard (for Econet) |
| fall through ↓ | |
| 912B | .lang_3_execute_at_0100 |
| LDX #2 ; X=2: zero 3 bytes (offsets 2,1,0) | |
| 912D | LDA #0 ; A=0: zero / BRK opcode |
| 912F | .zero_0100_loop←1← 9133 BPL |
| STA l0100,x ; Store zero at &0100+X | |
| 9132 | DEX ; Decrement byte counter |
| 9133 | BPL zero_0100_loop ; Loop until 3 bytes zeroed |
| 9135 | JSR clear_jsr_protection ; Release JSR protection mask |
| 9138 | JMP l0100 ; Execute downloaded code at &0100 |
Remote operation with source validation (REMOT)Validates that the source station/network in the received packet matches the controlling station stored in the remote RXCB. This ensures that only the station that initiated the remote session can send commands — characters from other stations are rejected. Full init sequence: 1) disable keyboard, 2) set workspace ptr, 3) set status busy, 4) set R/W/byte/word masks, 5) set up CB, 6) set MODE 7 (the only mode guaranteed for terminal emulation), 7) set auto repeat rates, 8) enter current language. This is essentially a "thin terminal" setup — the local machine becomes a remote display/keyboard for the controlling station. Bit 0 of the status byte disallows further remote takeover attempts (preventing re-entrant remote control), while bit 3 marks the machine as currently remoted. |
|
| 913B | .lang_4_remote_validated |
| LDY #4 ; Y=4: validation byte offset | |
| 913D | LDA (net_rx_ptr),y ; Load validation byte from RX data |
| 913F | BEQ remot1 ; Zero: validation passed, continue |
| 9141 | LDY #&80 ; Y=&80: source station offset |
| 9143 | LDA (net_rx_ptr),y ; Load source station from RX buffer |
| 9145 | LDY #&0e ; Y=&0E: controlling station offset |
| 9147 | CMP (nfs_workspace),y ; Compare with controlling station |
| 9149 | BNE rchex ; Mismatch: reject remote command |
| fall through ↓ | |
Insert remote keypressReads a character from RX block offset &82 and inserts it into keyboard input buffer 0 via OSBYTE &99. |
|
| 914B | .lang_0_insert_remote_key |
| LDY #&82 ; Y=&82: character offset in RX data | |
| 914D | LDA (net_rx_ptr),y ; Load remote keypress character |
| 914F | TAY ; Transfer character to Y |
| 9150 | LDX #0 ; X=0: keyboard input buffer |
| 9152 | JSR clear_jsr_protection ; Release JSR protection before call |
| 9155 | LDA #osbyte_insert_input_buffer ; A=&99: OSBYTE insert into buffer |
| 9157 | JMP osbyte ; Insert character Y into input buffer X |
Alternate entry into control block setupSets X=&0D, Y=&7C. Tests bit 6 of &833B to choose target: V=0 (bit 6 clear): stores to (nfs_workspace) V=1 (bit 6 set): stores to (net_rx_ptr) |
|
| 915A | .ctrl_block_setup_alt←3← 8FE3 JSR← 8FF6 JSR← 90F9 JSR |
| LDX #&0d ; X=&0D: template offset for alt entry | |
| 915C | LDY #&7c ; Y=&7C: target workspace offset for alt entry |
| 915E | BIT tx_ctrl_upper ; BIT test: V flag = bit 6 of &83B3 |
| 9161 | BVS cbset2 ; V=1: store to (net_rx_ptr) instead |
| fall through ↓ | |
Control block setup — main entrySets X=&1A, Y=&17, clears V (stores to nfs_workspace). Reads the template table at &918F indexed by X, storing each value into the target workspace at offset Y. Both X and Y are decremented on each iteration. Template sentinel values: &FE = stop (end of template for this entry path) &FD = skip (leave existing value unchanged) &FC = use page high byte of target pointer |
|
| 9163 | .ctrl_block_setup←1← 911F JSR |
| LDY #&17 ; Y=&17: workspace target offset (main entry) | |
| 9165 | LDX #&1a ; X=&1A: template table index (main entry) |
| 9167 | .ctrl_block_setup_clv←1← 922C JSR |
| CLV ; V=0: target is (nfs_workspace) | |
| 9168 | .cbset2←2← 9161 BVS← 9189 BPL |
| LDA ctrl_block_template,x ; Load template byte from ctrl_block_template[X] | |
| 916B | CMP #&fe ; &FE = stop sentinel |
| 916D | BEQ cb_template_tail ; End of template: jump to exit |
| 916F | CMP #&fd ; &FD = skip sentinel |
| 9171 | BEQ cb_template_main_start ; Skip: don't store, just decrement Y |
| 9173 | CMP #&fc ; &FC = page byte sentinel |
| 9175 | BNE cbset3 ; Not sentinel: store template value directly |
| 9177 | LDA net_rx_ptr_hi ; V=1: use (net_rx_ptr) page |
| 9179 | BVS rxcb_matched ; V=1: skip to net_rx_ptr page |
| 917B | LDA nfs_workspace_hi ; V=0: use (nfs_workspace) page |
| 917D | .rxcb_matched←1← 9179 BVS |
| STA net_tx_ptr_hi ; PAGE byte → Y=&02 / Y=&74 | |
| 917F | .cbset3←1← 9175 BNE |
| BVS cbset4 ; → Y=&04 / Y=&76 | |
| 9181 | STA (nfs_workspace),y ; PAGE byte → Y=&06 / Y=&78 |
| 9183 | BVC cb_template_main_start ; → Y=&08 / Y=&7A ALWAYS branch |
| 9185 | .cbset4←1← 917F BVS |
| STA (net_rx_ptr),y ; Alt-path only → Y=&70 | |
| 9187 | .cb_template_main_start←2← 9171 BEQ← 9183 BVC |
| DEY ; → Y=&0C (main only) | |
| 9188 | DEX ; → Y=&0D (main only) |
| 9189 | BPL cbset2 ; Loop until all template bytes done |
| 918B | .cb_template_tail←1← 916D BEQ |
| INY ; → Y=&10 (main only) | |
| 918C | STY net_tx_ptr ; Store final offset as net_tx_ptr |
| 918E | RTS ; → Y=&07 / Y=&79 |
Control block initialisation templateRead by the loop at &9168, indexed by X from a starting value down to 0. Values are stored into either (nfs_workspace) or (net_rx_ptr) at offset Y, depending on the V flag. Two entry paths read different slices of this table: ctrl_block_setup: X=&1A (26) down, Y=&17 (23) down, V=0 ctrl_block_setup_alt: X=&0D (13) down, Y=&7C (124) down, V from BIT &833B Sentinel values: &FE = stop processing &FD = skip this offset (decrement Y but don't store) &FC = substitute the page byte (net_rx_ptr_hi or nfs_workspace_hi) |
|
| 918F | .ctrl_block_template←1← 9168 LDA |
| EQUB &85 ; Alt-path only → Y=&6F | |
| 9190 | EQUB &00 ; Alt-path only → Y=&70 |
| 9191 | EQUB &FD ; SKIP |
| 9192 | EQUB &FD ; SKIP |
| 9193 | EQUB &7D ; → Y=&01 / Y=&73 |
| 9194 | EQUB &FC ; PAGE byte → Y=&02 / Y=&74 |
| 9195 | EQUB &FF ; → Y=&03 / Y=&75 |
| 9196 | EQUB &FF ; → Y=&04 / Y=&76 |
| 9197 | EQUB &7E ; → Y=&05 / Y=&77 |
| 9198 | EQUB &FC ; PAGE byte → Y=&06 / Y=&78 |
| 9199 | EQUB &FF ; → Y=&07 / Y=&79 |
| 919A | EQUB &FF ; → Y=&08 / Y=&7A |
| 919B | EQUB &00 ; → Y=&09 / Y=&7B |
| 919C | EQUB &00 ; → Y=&0A / Y=&7C |
| 919D | EQUB &FE ; STOP — main-path boundary |
| 919E | EQUB &80 ; → Y=&0C (main only) |
| 919F | EQUB &93 ; → Y=&0D (main only) |
| 91A0 | EQUB &FD ; SKIP (main only) |
| 91A1 | EQUB &FD ; SKIP (main only) |
| 91A2 | EQUB &D9 ; → Y=&10 (main only) |
| 91A3 | EQUB &FC ; PAGE byte → Y=&11 (main only) |
| 91A4 | EQUB &FF ; → Y=&12 (main only) |
| 91A5 | EQUB &FF ; → Y=&13 (main only) |
| 91A6 | EQUB &DE ; → Y=&14 (main only) |
| 91A7 | EQUB &FC ; PAGE byte → Y=&15 (main only) |
| 91A8 | EQUB &FF ; → Y=&16 (main only) |
| 91A9 | EQUB &FF ; → Y=&17 (main only) |
| 91AA | EQUB &FE, &D1, &FD, &FD, &1F, &FD, &FF, &FF, &FD, &FD, &FF, &FF |
Fn 5: printer selection changed (SELECT)Called when the printer selection changes. Compares the new selection (in PARMX) against the network printer (buffer 4). If it matches, initialises the printer buffer pointer (PBUFFP) and sets the initial flag byte (&41). Otherwise just updates the printer status flags (PFLAGS).
|
||||
| 91B6 | .printer_select_handler | |||
| LDA #0 ; A=0: clear printer buffer state | ||||
| 91B8 | DEX ; X-1: convert 1-based buffer to 0-based | |||
| 91B9 | CPX osword_pb_ptr ; Is this the network printer buffer? | |||
| 91BB | BNE setup1 ; No: skip printer init | |||
| 91BD | LDA #&1f ; &1F = initial buffer pointer offset | |||
| 91BF | STA printer_buf_ptr ; Reset printer buffer write position | |||
| 91C2 | LDA #&43 ; &41 = initial PFLAGS (bit 6 set, bit 0 set) | |||
| 91C4 | .setup1←1← 91BB BNE | |||
| STA l0d60 ; Store initial PFLAGS value | ||||
| 91C7 | .return_printer_select←2← 91CA BNE← 91DE BCS | |||
| RTS ; Return | ||||
Fn 1/2/3: network printer handler (PRINT)Handles network printer output. Reason 1 = chars in buffer (extract from MOS buffer 3 and accumulate), reason 2 = Ctrl-B (start print), reason 3 = Ctrl-C (end print). The printer status byte PFLAGS uses: bit 7 = sequence number (toggles per packet for dup detection) bit 6 = always 1 (validity marker) bit 0 = 0 when print active Print streams reuse the BSXMIT (byte-stream transmit) code with handle=0, which causes the AND SEQNOS to produce zero and sidestep per-file sequence tracking. After transmission, TXCB pointer bytes are filled with &FF to prevent stale values corrupting subsequent BGET/BPUT operations (a historically significant bug fix). N.B. The printer and REMOTE facility share the same dynamically allocated static workspace page via WORKP1 (&9E,&9F) — care must be taken to never leave the pointer corrupted, as corruption would cause one subsystem to overwrite the other's data. Only handles buffer 4 (network printer); others are ignored.
|
||||||
| 91C8 | .remote_print_handler | |||||
| CPY #4 ; Only handle buffer 4 (network printer) | ||||||
| 91CA | BNE return_printer_select ; Not buffer 4: ignore | |||||
| 91CC | TXA ; A = reason code | |||||
| 91CD | DEX ; Reason 1? (DEX: 1->0) | |||||
| 91CE | BNE toggle_print_flag ; Not reason 1: handle Ctrl-B/C | |||||
| 91D0 | TSX ; Get stack pointer for P register | |||||
| 91D1 | ORA l0106,x ; Force I flag in stacked P to block IRQs | |||||
| 91D4 | STA l0106,x ; Write back modified P register | |||||
| 91D7 | .prlp1←2← 91E6 BCC← 91EB BNE | |||||
| LDA #osbyte_read_buffer ; OSBYTE &91: extract char from MOS buffer | ||||||
| 91D9 | LDX #buffer_printer ; X=3: printer buffer number | |||||
| 91DB | JSR osbyte ; Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character) | |||||
| 91DE | BCS return_printer_select ; Buffer empty: return | |||||
| 91E0 | TYA ; Y = extracted character Y is the character extracted from the buffer | |||||
| 91E1 | JSR store_output_byte ; Store char in output buffer | |||||
| 91E4 | CPY #&6e ; Buffer nearly full? (&6E = threshold) | |||||
| 91E6 | BCC prlp1 ; Not full: get next char | |||||
| 91E8 | JSR flush_output_block ; Buffer full: flush to network | |||||
| 91EB | BNE prlp1 ; Flush done: continue loop | |||||
| fall through ↓ | ||||||
Store output byte to network bufferStores byte A at the current output offset in the RX buffer pointed to by (net_rx_ptr). Advances the offset counter and triggers a flush if the buffer is full.
|
|||||||
| 91ED | .store_output_byte←2← 91E1 JSR← 91F7 JSR | ||||||
| LDY printer_buf_ptr ; Load current buffer offset | |||||||
| 91F0 | STA (net_rx_ptr),y ; Store byte at current position | ||||||
| 91F2 | INC printer_buf_ptr ; Advance buffer pointer | ||||||
| 91F5 | RTS ; Return; Y = buffer offset | ||||||
| 91F6 | .toggle_print_flag←1← 91CE BNE | ||||||
| PHA ; Save reason code | |||||||
| 91F7 | JSR store_output_byte ; Decrement transfer count low byte | ||||||
| 91FA | EOR l0d60 ; XOR with transfer count flags | ||||||
| 91FD | ROR ; Shift bit 0 into carry | ||||||
| 91FE | BCC rx_data_phase ; Data phase active: continue | ||||||
| 9200 | LDA l0d60 ; Load transfer count flags | ||||||
| 9203 | ROR ; Shift bit 0 into carry | ||||||
| 9204 | BCC rx_imm_discard ; Bit 0=0 (active): just flush | ||||||
| 9206 | ROL ; Rotate carry back | ||||||
| 9207 | AND #&7f ; Mask off control bits | ||||||
| 9209 | STA l0d60 ; Store updated flags | ||||||
| 920C | .rx_imm_discard←1← 9204 BCC | ||||||
| JSR flush_output_block ; Flush accumulated output to network | |||||||
| 920F | .rx_data_phase←1← 91FE BCC | ||||||
| ROR l0d60 ; Save PFLAGS bit 0 via carry | |||||||
| 9212 | PLA ; Restore original reason code | ||||||
| 9213 | ROR ; Old PFLAGS bit 0 to A bit 7 | ||||||
| 9214 | ROL l0d60 ; Reason bit 0 into PFLAGS bit 0 | ||||||
| 9217 | RTS ; Return | ||||||
Flush output blockSends the accumulated output block over the network, resets the buffer pointer, and prepares for the next block of output data. |
|
| 9218 | .flush_output_block←2← 91E8 JSR← 920C JSR |
| LDY #8 ; Store buffer length at workspace offset &08 | |
| 921A | LDA printer_buf_ptr ; Current buffer fill position |
| 921D | STA (nfs_workspace),y ; Write to workspace offset &08 |
| 921F | LDA net_rx_ptr_hi ; Store page high byte at offset &09 |
| 9221 | INY ; Y=&09 |
| 9222 | STA (nfs_workspace),y ; Write page high byte at offset &09 |
| 9224 | LDY #5 ; Also store at offset &05 |
| 9226 | STA (nfs_workspace),y ; (end address high byte) |
| 9228 | LDY #&0b ; Y=&0B: flag byte offset |
| 922A | LDX #&26 ; X=&26: start from template entry &26 |
| 922C | JSR ctrl_block_setup_clv ; Reuse ctrl_block_setup with CLV entry |
| 922F | DEY ; Y=&0A: sequence flag byte offset |
| 9230 | LDA l0d60 ; Load current PFLAGS |
| 9233 | PHA ; Save current PFLAGS |
| 9234 | ROL ; Carry = current sequence (bit 7) |
| 9235 | PLA ; Restore original PFLAGS |
| 9236 | EOR #&80 ; Toggle sequence number (bit 7 of PFLAGS) |
| 9238 | STA l0d60 ; Store toggled sequence number |
| 923B | ROL ; Old sequence bit into bit 0 |
| 923C | STA (nfs_workspace),y ; Store sequence flag at offset &0A |
| 923E | LDY #&1f ; Y=&1F: buffer start offset |
| 9240 | STY printer_buf_ptr ; Reset printer buffer to start (&1F) |
| 9243 | LDA #0 ; A=0: printer output flag |
| 9245 | TAX ; X=0: workspace low byte X=&00 |
| 9246 | LDY nfs_workspace_hi ; Y = workspace page high byte |
| 9248 | CLI ; Enable interrupts before TX |
| fall through ↓ | |
Transmit with retry loop (XMITFS/XMITFY)Calls the low-level transmit routine (BRIANX) with FSTRY (&FF = 255) retries and FSDELY (&60 = 96) ms delay between attempts. On each iteration, checks the result code: zero means success, non-zero means retry. After all retries exhausted, reports a 'Net error'. Entry point XMITFY allows a custom delay in Y.
|
||||||||
| 9249 | .econet_tx_retry←2← 839C JSR← 83D4 JSR | |||||||
| STX net_tx_ptr ; Set TX control block ptr low byte | ||||||||
| 924B | STY net_tx_ptr_hi ; Set TX control block ptr high byte | |||||||
| 924D | PHA ; Save A (handle bitmask) for later | |||||||
| 924E | AND fs_sequence_nos ; Compute sequence bit from handle | |||||||
| 9251 | BEQ bsxl1 ; Zero: no sequence bit set | |||||||
| 9253 | LDA #1 ; Non-zero: normalise to bit 0 | |||||||
| 9255 | .bsxl1←1← 9251 BEQ | |||||||
| LDY #0 ; Y=0: flag byte offset in TXCB | ||||||||
| 9257 | ORA (net_tx_ptr),y ; Merge sequence into existing flag byte | |||||||
| 9259 | PHA ; Save merged flag byte | |||||||
| 925A | STA (net_tx_ptr),y ; Write flag+sequence to TXCB byte 0 | |||||||
| 925C | JSR tx_poll_ff ; Transmit with full retry | |||||||
| 925F | LDA #&ff ; End address &FFFF = unlimited data length | |||||||
| 9261 | LDY #8 ; Y=8: end address low offset in TXCB | |||||||
| 9263 | STA (net_tx_ptr),y ; Store &FF to end addr low | |||||||
| 9265 | INY ; Y=&09 | |||||||
| 9266 | STA (net_tx_ptr),y ; Store &FF to end addr high (Y=9) | |||||||
| 9268 | PLA ; Recover merged flag byte | |||||||
| 9269 | TAX ; Save in X for sequence compare | |||||||
| 926A | LDY #&d1 ; Y=&D1: printer port number | |||||||
| 926C | PLA ; Recover saved handle bitmask | |||||||
| 926D | PHA ; Re-save for later consumption | |||||||
| 926E | BEQ bspsx ; A=0: port &D1 (print); A!=0: port &90 (FS) | |||||||
| 9270 | LDY #&90 ; Y=&90: FS data port | |||||||
| 9272 | .bspsx←1← 926E BEQ | |||||||
| TYA ; A = selected port number | ||||||||
| 9273 | LDY #1 ; Y=1: port byte offset in TXCB | |||||||
| 9275 | STA (net_tx_ptr),y ; Write port to TXCB byte 1 | |||||||
| 9277 | TXA ; A = saved flag byte (expected sequence) | |||||||
| 9278 | DEY ; Y=&00 | |||||||
| 9279 | PHA ; Push expected sequence for retry loop | |||||||
| 927A | .bsxl0←1← 9286 BCS | |||||||
| LDA #&7f ; Flag byte &7F = waiting for reply | ||||||||
| 927C | STA (net_tx_ptr),y ; Write to TXCB flag byte (Y=0) | |||||||
| 927E | JSR send_to_fs_star ; Transmit and wait for reply (BRIANX) | |||||||
| 9281 | PLA ; Recover expected sequence | |||||||
| 9282 | PHA ; Keep on stack for next iteration | |||||||
| 9283 | EOR (net_tx_ptr),y ; Check if TX result matches expected sequence | |||||||
| 9285 | ROR ; Bit 0 to carry (sequence mismatch?) | |||||||
| 9286 | BCS bsxl0 ; C=1: mismatch, retry transmit | |||||||
| 9288 | PLA ; Clean up: discard expected sequence | |||||||
| 9289 | PLA ; Discard saved handle bitmask | |||||||
| 928A | TAX ; Transfer count to X | |||||||
| 928B | INX ; Test for retry exhaustion | |||||||
| 928C | BEQ return_bspsx ; X wrapped to 0: retries exhausted | |||||||
| 928E | EOR fs_sequence_nos ; Toggle sequence bit on success | |||||||
| 9291 | .return_bspsx←1← 928C BEQ | |||||||
| RTS ; Return | ||||||||
Save palette and VDU state (CVIEW)Part of the VIEW facility (second iteration, started 27/7/82). Uses dynamically allocated buffer store. The WORKP1 pointer (&9E,&9F) serves double duty: non-zero indicates data ready AND provides the buffer address — an efficient use of scarce zero- page space. This code must be user-transparent as the NFS may not be the dominant filing system. Reads all 16 palette entries using OSWORD &0B (read palette) and stores the results. Then reads cursor position (OSBYTE &85), shadow RAM allocation (OSBYTE &C2), and screen start address (OSBYTE &C3) using the 3-entry table at &9305 (osbyte_vdu_table). On completion, restores the JSR buffer protection bits (LSTAT) from OLDJSR to re-enable JSR reception, which was disabled during the screen data capture to prevent interference. |
|
| 9292 | .lang_2_save_palette_vdu |
| LDA fs_load_addr_2 ; Save current table index | |
| 9294 | PHA ; Push for later restore |
| 9295 | LDA #&e9 ; Point workspace to palette save area (&E9) |
| 9297 | STA nfs_workspace ; Set workspace low byte |
| 9299 | LDY #0 ; Y=0: first palette entry |
| 929B | STY fs_load_addr_2 ; Clear table index counter |
| 929D | LDA l0350 ; Save current screen MODE to workspace |
| 92A0 | STA (nfs_workspace),y ; Store MODE at workspace[0] |
| 92A2 | INC nfs_workspace ; Advance workspace pointer past MODE byte |
| 92A4 | LDA l0351 ; Read colour count (from &0351) |
| 92A7 | PHA ; Push for iteration count tracking |
| 92A8 | TYA ; A=0: logical colour number for OSWORD A=&00 |
| 92A9 | .save_palette_entry←1← 92C8 BNE |
| STA (nfs_workspace),y ; Store logical colour at workspace[0] | |
| 92AB | LDX nfs_workspace ; X = workspace ptr low (param block addr) |
| 92AD | LDY nfs_workspace_hi ; Y = workspace ptr high |
| 92AF | LDA #osword_read_palette ; OSWORD &0B: read palette for logical colour |
| 92B1 | JSR osword ; Read palette |
| 92B4 | PLA ; Recover colour count |
| 92B5 | LDY #0 ; Y=0: access workspace[0] |
| 92B7 | STA (nfs_workspace),y ; Write colour count back to workspace[0] |
| 92B9 | INY ; Y=1: access workspace[1] (palette result) Y=&01 |
| 92BA | LDA (nfs_workspace),y ; Read palette value returned by OSWORD |
| 92BC | PHA ; Push palette value for next iteration |
| 92BD | LDX nfs_workspace ; X = current workspace ptr low |
| 92BF | INC nfs_workspace ; Advance workspace pointer |
| 92C1 | INC fs_load_addr_2 ; Increment table index |
| 92C3 | DEY ; Y=0 for next store Y=&00 |
| 92C4 | LDA fs_load_addr_2 ; Load table index as logical colour |
| 92C6 | CPX #&f9 ; Loop until workspace wraps past &F9 |
| 92C8 | BNE save_palette_entry ; Continue for all 16 palette entries |
| 92CA | PLA ; Discard last palette value from stack |
| 92CB | STY fs_load_addr_2 ; Reset table index to 0 |
| 92CD | INC nfs_workspace ; Advance workspace past palette data |
| 92CF | JSR save_vdu_state ; Save cursor pos and OSBYTE state values |
| 92D2 | INC nfs_workspace ; Advance workspace past VDU state data |
| 92D4 | PLA ; Recover saved table index |
| 92D5 | STA fs_load_addr_2 ; Restore table index |
| 92D7 | .clear_jsr_protection←4← 8E66 JSR← 9103 JMP← 9135 JSR← 9152 JSR |
| LDA rx_ctrl_copy ; Restore LSTAT from saved OLDJSR value | |
| 92DA | STA prot_status ; Write to protection status |
| 92DD | RTS ; Return |
Save VDU workspace stateStores the cursor position value from &0355 into NFS workspace, then reads cursor position (OSBYTE &85), shadow RAM (OSBYTE &C2), and screen start (OSBYTE &C3) via read_vdu_osbyte, storing each result into consecutive workspace bytes. |
||
| ROM | Exec | |
|---|---|---|
| 92DE | .save_vdu_state←1← 92CF JSR | |
| LDA l0355 ; Read cursor editing state | ||
| 92E1 | STA (nfs_workspace),y ; Store to workspace[Y] | |
| 92E3 | TAX ; Preserve in X for OSBYTE | |
| 92E4 | JSR read_vdu_osbyte ; OSBYTE &85: read cursor position | |
| 92E7 | INC nfs_workspace ; Advance workspace pointer | |
| 92E9 | TYA ; Y result from OSBYTE &85 | |
| 92EA | STA (nfs_workspace,x) ; Store Y pos to workspace (X=0) | |
| 92EC | JSR read_vdu_osbyte_x0 ; Self-call trick: executes twice | |
| 92EF | .read_vdu_osbyte_x0←1← 92EC JSR | |
| LDX #0 ; X=0 for (zp,X) addressing | ||
| 92F1 | .read_vdu_osbyte←1← 92E4 JSR | |
| LDY fs_load_addr_2 ; Index into OSBYTE number table | ||
| 92F3 | INC fs_load_addr_2 ; Next table entry next time | |
| 92F5 | INC nfs_workspace ; Advance workspace pointer | |
| 92F7 | LDA osbyte_vdu_table,y ; Read OSBYTE number from table | |
| 92FA | LDY #&ff ; Y=&FF: read current value | |
| 92FC | JSR osbyte ; Call OSBYTE | |
| 92FF | TXA ; Result in X to A | |
| 9300 | LDX #0 ; X=0 for indexed indirect store | |
| 9302 | STA (nfs_workspace,x) ; Store result to workspace | |
| 9304 | RTS ; Return after storing result | |
| ; 3-entry OSBYTE table for lang_2_save_palette_vdu (&9292) | ||
| 9305 | .osbyte_vdu_table←1← 92F7 LDA | |
| EQUB &85 ; OSBYTE &85: read cursor position | ||
| 9306 | EQUB &C2 ; OSBYTE &C2: read shadow RAM allocation | |
| 9307 | EQUB &C3 ; OSBYTE &C3: read screen start address | |
| ; Tube BRK handler (BRKV target) — reference: NFS11 NEWBR | ||
| ; Sends error information to the Tube co-processor via R2 ; and R4: | ||
| ; 1. Sends &FF to R4 (WRIFOR) to signal error | ||
| ; 2. Reads R2 data (flush any pending byte) | ||
| ; 3. Sends &00 via R2, then error number from (&FD),0 | ||
| ; 4. Loops sending error string bytes via R2 until zero terminator | ||
| ; 5. Falls through to tube_reset_stack → tube_main_loop | ||
| ; The main loop continuously polls R4 for WRCH requests ; (forwarded | ||
| ; to OSWRITCH &FFCB) and R2 for command bytes (dispatched ; via the | ||
| ; 14-entry table at &0500). The R2 command byte is stored ; at &55 | ||
| ; before dispatch via JMP (&0500). | ||
| 9308 | 0016 | .nmi_workspace_start←1← 8116 STA |
| .tube_brk_handler←1← 8116 STA | ||
| LDA #&ff ; A=&FF: signal error to co-processor via R4 | ||
| 930A | 0018 | JSR tube_send_r4 ; Send &FF error signal to Tube R4 |
| 930D | 001B | LDA tube_data_register_2 ; Flush any pending R2 byte |
| 9310 | 001E | LDA #0 ; A=0: send zero prefix to R2 |
| 9312 | 0020 | .tube_send_zero_r2 |
| JSR tube_send_r2 ; Send zero prefix byte via R2 | ||
| 9315 | 0023 | TAY ; Y=0: start of error block at (&FD) |
| 9316 | 0024 | LDA (brk_ptr),y ; Load error number from (&FD),0 |
| 9318 | 0026 | JSR tube_send_r2 ; Send error number via R2 |
| 931B | 0029 | .tube_brk_send_loop←1← 0030 BNE |
| INY ; Advance to next error string byte | ||
| 931C | 002A | .tube_send_error_byte |
| LDA (brk_ptr),y ; Load next error string byte | ||
| 931E | 002C | JSR tube_send_r2 ; Send error string byte via R2 |
| 9321 | 002F | TAX ; Zero byte = end of error string |
| 9322 | 0030 | BNE tube_brk_send_loop ; Loop until zero terminator sent |
| 9324 | 0032 | .tube_reset_stack |
| LDX #&ff ; Reset stack pointer to top | ||
| 9326 | 0034 | TXS ; TXS: set stack pointer from X |
| 9327 | 0035 | CLI ; Enable interrupts for main loop |
| ; Save registers and enter Tube polling loop | ||
| ; Saves X and Y to zp_temp_11/zp_temp_10, then falls ; through | ||
| ; to tube_main_loop which polls Tube R1 (WRCH) and R2 ; (command) | ||
| ; registers in an infinite loop. Called from ; tube_init_reloc | ||
| ; after ROM relocation and from tube_dispatch_table ; handlers | ||
| ; that need to restart the main loop. | ||
| 9328 | 0036 | .tube_enter_main_loop←2← 04EC JMP← 053A JMP |
| STX zp_temp_11 ; Save X to temporary | ||
| 932A | 0038 | STY zp_temp_10 ; Save Y to temporary |
| 932C | 003A | .tube_main_loop←7← 0048 BPL← 05AE JMP← 05D5 JMP← 0623 JMP← 0638 JMP← 06A0 JMP← 06CD JMP |
| BIT tube_status_register_4_and_cpu_control ; BIT R4 status: check WRCH request | ||
| 932F | 003D | BPL tube_poll_r2 ; R1 not ready: check R2 instead |
| 9331 | 003F | .tube_handle_wrch←1← 004D BMI |
| LDA tube_data_register_4 ; Read character from Tube R4 data | ||
| 9334 | 0042 | JSR nvwrch ; Write character |
| 9337 | 0045 | .tube_poll_r2←1← 003D BPL |
| BIT tube_status_register_2 ; BIT R2 status: check command byte | ||
| 933A | 0048 | BPL tube_main_loop ; R2 not ready: loop back to R1 check |
| 933C | 004A | BIT tube_status_register_4_and_cpu_control ; Re-check R4: WRCH has priority over R2 |
| 933F | 004D | BMI tube_handle_wrch ; R1 ready: handle WRCH first |
| 9341 | 004F | LDX tube_data_register_2 ; Read command byte from Tube R2 data |
| 9344 | 0052 | STX tube_dispatch_ptr_lo ; Self-modify JMP low byte for dispatch |
| 9346 | 0054 | .tube_dispatch_cmd |
| JMP (tube_dispatch_table) ; Dispatch to handler via indirect JMP | ||
| 9349 | 0057 | .tube_transfer_addr←2← 0478 STY← 0493 STA |
| EQUB &00 | ||
| 934A | 0058 | .tube_xfer_page←2← 047C STA← 0498 STA |
| EQUB &80 | ||
| 934B | 0059 | .tube_xfer_addr_2←1← 04A2 STY |
| EQUB &00 | ||
| 934C | 005A | .tube_xfer_addr_3←1← 04A0 STA |
| EQUB &00 | ||
| ; Tube host code page 4 — reference: NFS12 (BEGIN, ADRR, ; SENDW) | ||
| ; Copied from ROM (tube_code_page4) during init. The first ; 28 bytes (&0400-&041B) | ||
| ; overlap with the end of the ZP block (the same ROM bytes ; serve both | ||
| ; the ZP copy at &005B-&0076 and this page at ; &0400-&041B). Contains: | ||
| ; &0400: JMP &0473 (BEGIN — CLI parser / startup entry) | ||
| ; &0403: JMP &06E2 (tube_escape_check) | ||
| ; &0406: tube_addr_claim — Tube address claim protocol (ADRR) | ||
| ; &0414: tube_post_init — called after ROM→RAM copy | ||
| ; &0473: BEGIN — startup/CLI entry, break type check | ||
| ; &04E7: tube_rdch_handler — RDCHV target | ||
| ; &04EF: tube_restore_regs — restore X,Y, dispatch entry 6 | ||
| ; &04F7: tube_read_r2 — poll R2 status, read data byte to A | ||
| 934D | 0400 | .tube_code_page4←1← 80FC STA |
| JMP tube_begin ; JMP to BEGIN startup entry | ||
| 9350 | 0403 | .tube_escape_entry |
| JMP tube_escape_check ; JMP to tube_escape_check (&06A7) | ||
| 9353 | 0406 | .tube_addr_claim←10← 04BC JSR← 04E4 JMP← 8B1E JSR← 8B30 JSR← 8B8D JSR← 8DAA JMP← 99F0 JSR← 9A3D JSR← 9F98 JSR← 9FA0 JSR |
| CMP #&80 ; A>=&80: address claim; A<&80: data transfer | ||
| 9355 | 0408 | BCC setup_data_transfer ; A<&80: data transfer setup (SENDW) |
| 9357 | 040A | CMP #&c0 ; A>=&C0: new address claim from another host |
| 9359 | 040C | BCS addr_claim_external ; C=1: external claim, check ownership |
| 935B | 040E | ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison |
| 935D | 0410 | CMP tube_claimed_id ; Is this for our currently-claimed address? |
| 935F | 0412 | BNE return_tube_init ; Not our address: return |
| 9361 | 0414 | .tube_post_init←1← 810E JSR |
| LDA #&80 ; &80 sentinel: clear address claim | ||
| 9363 | 0416 | STA tube_claim_flag ; Store to claim-in-progress flag |
| 9365 | 0418 | RTS ; Return from tube_post_init |
| 9366 | 0419 | .addr_claim_external←1← 040C BCS |
| ASL tube_claim_flag ; Another host claiming; check if we're owner | ||
| 9368 | 041B | BCS accept_new_claim ; C=1: we have an active claim |
| 936A | 041D | CMP tube_claimed_id ; Compare with our claimed address |
| 936C | 041F | BEQ return_tube_init ; Match: return (we already have it) |
| 936E | 0421 | CLC ; Not ours: CLC = we don't own this address |
| 936F | 0422 | RTS ; Return with C=0 (claim denied) |
| 9370 | 0423 | .accept_new_claim←1← 041B BCS |
| STA tube_claimed_id ; Accept new claim: update our address | ||
| 9372 | 0425 | .return_tube_init←2← 0412 BNE← 041F BEQ |
| RTS ; Return with address updated | ||
| 9373 | 0426 | .setup_data_transfer←1← 0408 BCC |
| STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y) | ||
| 9375 | 0428 | STX tube_data_ptr ; Store address pointer low byte |
| 9377 | 042A | JSR tube_send_r4 ; Send transfer type byte to co-processor |
| 937A | 042D | TAX ; X = transfer type for table lookup |
| 937B | 042E | LDY #3 ; Y=3: send 4 bytes (address + claimed addr) |
| 937D | 0430 | .send_xfer_addr_bytes←1← 0436 BPL |
| LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y) | ||
| 937F | 0432 | JSR tube_send_r4 ; Send address byte to co-processor via R4 |
| 9382 | 0435 | DEY ; Previous byte (big-endian: 3,2,1,0) |
| 9383 | 0436 | BPL send_xfer_addr_bytes ; Loop for all 4 address bytes |
| 9385 | 0438 | LDY #8 ; Y=8: write to Tube control register |
| 9387 | 043A | STY tube_status_1_and_tube_control ; Configure Tube for data transfer |
| 938A | 043D | LDY #&10 ; Y=&10: data transfer control value |
| 938C | 043F | CPX #2 ; Check transfer type (X=2?) |
| 938E | 0441 | BCC tube_ctrl_write_2 ; X<2: skip alternate control |
| 9390 | 0443 | LDY #&90 ; Y=&90: alternate control for X>=2 |
| 9392 | 0445 | .tube_ctrl_write_2←1← 0441 BCC |
| STY tube_status_1_and_tube_control ; Write transfer control to Tube | ||
| 9395 | 0448 | JSR tube_send_r4 ; Send data byte via Tube R4 |
| 9398 | 044B | LDY #&88 ; Y=&88: post-transfer control value |
| 939A | 044D | TXA ; Transfer type to A for comparison |
| 939B | 044E | BEQ flush_r3_nmi_check ; Type 0: go to NMI flush check |
| 939D | 0450 | CMP #2 ; Check if type 2 |
| 939F | 0452 | BEQ flush_r3_nmi_check ; Type 2: go to NMI flush check |
| 93A1 | 0454 | STY tube_status_1_and_tube_control ; Write post-transfer control |
| 93A4 | 0457 | CMP #4 ; Check if type 4 (SENDW) |
| 93A6 | 0459 | BNE return_tube_xfer ; Not SENDW type: skip release path |
| 93A8 | 045B | PLA ; Discard return address (low byte) |
| 93A9 | 045C | PLA ; Discard return address (high byte) |
| 93AA | 045D | .release_claim_restart←1← 04B8 BEQ |
| LDA #&80 ; A=&80: reset claim flag sentinel | ||
| 93AC | 045F | STA tube_claim_flag ; Clear claim-in-progress flag |
| 93AE | 0461 | JMP tube_reply_byte ; Restart Tube main loop |
| 93B1 | 0464 | .flush_r3_nmi_check←3← 044E BEQ← 0452 BEQ← 0467 BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status: wait for transfer ready | ||
| 93B4 | 0467 | BVC flush_r3_nmi_check ; V=0: not ready, poll again |
| 93B6 | 0469 | BIT tube_data_register_3 ; Flush Tube R3 data register |
| 93B9 | 046C | BIT tube_data_register_3 ; Flush Tube R3 again |
| 93BC | 046F | STY tube_status_1_and_tube_control ; Write final control value |
| 93BF | 0472 | .return_tube_xfer←1← 0459 BNE |
| RTS ; Return from Tube data setup | ||
| 93C0 | 0473 | .tube_begin←1← 0400 JMP |
| CLI ; BEGIN: enable interrupts for Tube host code | ||
| 93C1 | 0474 | PHP ; Save processor status |
| 93C2 | 0475 | PHA ; Save A on stack |
| 93C3 | 0476 | LDY #0 ; Y=0: start at beginning of page |
| 93C5 | 0478 | STY tube_transfer_addr ; Store to zero page pointer low byte |
| ; Initialise relocation address for ROM transfer | ||
| ; Sets source page to &8000 and page counter to &80. ; Checks | ||
| ; ROM type bit 5 for a relocation address in the ROM ; header; | ||
| ; if present, extracts the 4-byte address from after the | ||
| ; copyright string. Otherwise uses default &8000 start. | ||
| 93C7 | 047A | .tube_init_reloc |
| LDA #&80 ; Init: start sending from &8000 | ||
| 93C9 | 047C | STA tube_xfer_page ; Store &80 as source page high byte |
| 93CB | 047E | STA zp_ptr_hi ; Store &80 as page counter initial value |
| 93CD | 0480 | LDA #&20 ; A=&20: bit 5 mask for ROM type check |
| 93CF | 0482 | AND rom_type ; ROM type bit 5: reloc address in header? |
| 93D2 | 0485 | BEQ store_xfer_end_addr ; No reloc addr: use defaults |
| 93D4 | 0487 | LDX copyright_offset ; Skip past copyright string to find reloc addr |
| 93D7 | 048A | .scan_copyright_end←1← 048E BNE |
| INX ; Skip past null-terminated copyright string | ||
| 93D8 | 048B | LDA rom_header,x ; Load next byte from ROM header |
| 93DB | 048E | BNE scan_copyright_end ; Loop until null terminator found |
| 93DD | 0490 | LDA lang_entry_lo,x ; Read 4-byte reloc address from ROM header |
| 93E0 | 0493 | STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr |
| 93E2 | 0495 | LDA lang_entry_hi,x ; Load reloc addr byte 2 |
| 93E5 | 0498 | STA tube_xfer_page ; Store as source page start |
| 93E7 | 049A | LDY service_entry,x ; Load reloc addr byte 3 |
| 93EA | 049D | LDA svc_entry_lo,x ; Load reloc addr byte 4 (highest) |
| 93ED | 04A0 | .store_xfer_end_addr←1← 0485 BEQ |
| STA tube_xfer_addr_3 ; Store high byte of end address | ||
| 93EF | 04A2 | STY tube_xfer_addr_2 ; Store byte 3 of end address |
| 93F1 | 04A4 | PLA ; Restore A from stack |
| 93F2 | 04A5 | PLP ; Restore processor status |
| 93F3 | 04A6 | BCS beginr ; Carry set: language entry (claim Tube) |
| 93F5 | 04A8 | TAX ; X = A (preserved from entry) |
| 93F6 | 04A9 | BNE begink ; Non-zero: check break type |
| 93F8 | 04AB | JMP tube_reply_ack ; A=0: acknowledge and return |
| 93FB | 04AE | .begink←1← 04A9 BNE |
| LDX #0 ; X=0 for OSBYTE read | ||
| 93FD | 04B0 | LDY #&ff ; Y=&FF for OSBYTE read |
| 93FF | 04B2 | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read last break type |
| 9401 | 04B4 | JSR osbyte ; Read type of last reset |
| 9404 | 04B7 | TXA ; X=value of type of last reset |
| 9405 | 04B8 | BEQ release_claim_restart ; Soft break (0): skip ROM transfer |
| 9407 | 04BA | .beginr←2← 04A6 BCS← 04BF BCC |
| LDA #&ff ; A=&FF: claim Tube for all operations | ||
| 9409 | 04BC | JSR tube_addr_claim ; Claim Tube address via R4 |
| 940C | 04BF | BCC beginr ; Not claimed: retry until claimed |
| 940E | 04C1 | LDA #1 ; Transfer type 1 (parasite to host) |
| 9410 | 04C3 | JSR tube_setup_transfer ; Set up Tube transfer parameters |
| 9413 | 04C6 | LDY #0 ; Y=0: start at page boundary |
| 9415 | 04C8 | STY zp_ptr_lo ; Source ptr low = 0 |
| 9417 | 04CA | LDX #&40 ; X=&40: 64 pages (16KB) to transfer |
| 9419 | 04CC | .send_rom_byte←2← 04D7 BNE← 04DC BNE |
| LDA (zp_ptr_lo),y ; Read byte from source address | ||
| 941B | 04CE | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 941E | 04D1 | .poll_r3_ready←1← 04D4 BVC |
| BIT tube_status_register_3 ; Check R3 status | ||
| 9421 | 04D4 | BVC poll_r3_ready ; Not ready: wait for Tube |
| 9423 | 04D6 | INY ; Next byte in page |
| 9424 | 04D7 | BNE send_rom_byte ; More bytes in page: continue |
| 9426 | 04D9 | INC zp_ptr_hi ; Next source page |
| 9428 | 04DB | DEX ; Decrement page counter |
| 9429 | 04DC | BNE send_rom_byte ; More pages: continue transfer |
| 942B | 04DE | LDA #4 ; Transfer type 4 (host to parasite burst) |
| 942D | 04E0 | .tube_setup_transfer←1← 04C3 JSR |
| LDY #0 ; Y=0: low byte of param block ptr | ||
| 942F | 04E2 | LDX #&57 ; X=&57: param block at &0057 |
| 9431 | 04E4 | JMP tube_addr_claim ; Claim Tube and start transfer |
| 9434 | 04E7 | .tube_rdch_handler |
| LDA #1 ; R2 command: OSRDCH request | ||
| 9436 | 04E9 | JSR tube_send_r2 ; Send OSRDCH request to host |
| 9439 | 04EC | JMP tube_enter_main_loop ; Jump to RDCH completion handler |
| 943C | 04EF | .tube_restore_regs |
| LDY zp_temp_10 ; Restore saved Y register | ||
| 943E | 04F1 | LDX zp_temp_11 ; Restore X from saved value |
| 9440 | 04F3 | JSR tube_read_r2 ; Read result byte from R2 |
| 9443 | 04F6 | ASL ; Shift carry into C flag |
| 9444 | 04F7 | .tube_read_r2←22← 04F3 JSR← 04FA BPL← 0543 JSR← 0547 JSR← 0550 JSR← 0569 JSR← 0580 JSR← 058C JSR← 0592 JSR← 059B JSR← 05B5 JSR← 05DA JSR← 05EB JSR← 0604 JSR← 060C JSR← 0626 JSR← 062A JSR← 063B JSR← 063F JSR← 0643 JSR← 065D JSR← 06A5 JSR |
| BIT tube_status_register_2 ; Poll R2 status register | ||
| 9447 | 04FA | BPL tube_read_r2 ; Bit 7 clear: R2 not ready, wait |
| 9449 | 04FC | LDA tube_data_register_2 ; Read byte from R2 data register |
| 944C | 04FF | RTS ; Return with pointers initialised |
| ; Tube host code page 5 — reference: NFS13 (TASKS, ; BPUT-FILE) | ||
| ; Copied from ROM (tube_dispatch_table) during init. ; Contains: | ||
| ; &0500: tube_dispatch_table — 14-entry handler address table | ||
| ; &051C: tube_wrch_handler — WRCHV target | ||
| ; &051F: tube_send_and_poll — send byte via R2, poll for reply | ||
| ; &0527: tube_poll_r4_wrch — service R4 WRCH while waiting for R2 | ||
| ; &053D: tube_release_return — restore regs and RTS | ||
| ; &0543: tube_osbput — write byte to file | ||
| ; &0550: tube_osbget — read byte from file | ||
| ; &055B: tube_osrdch — read character | ||
| ; &0569: tube_osfind — open file | ||
| ; &0580: tube_osfind_close — close file (A=0) | ||
| ; &058C: tube_osargs — file argument read/write | ||
| ; &05B1: tube_read_string — read CR-terminated string into &0700 | ||
| ; &05C5: tube_oscli — execute * command | ||
| ; &05CB: tube_reply_ack — send &7F acknowledge | ||
| ; &05CD: tube_reply_byte — send byte and return to main loop | ||
| ; &05D8: tube_osfile — whole file operation | ||
| 944D | 0500 | .tube_dispatch_table←2← 0054 JMP← 8102 STA |
| EQUW tube_osrdch ; cmd 0: OSRDCH | ||
| 944F | 0502 | EQUW tube_oscli ; cmd 1: OSCLI |
| 9451 | 0504 | EQUW tube_osbyte_short ; cmd 2: OSBYTE (2-param) |
| 9453 | 0506 | EQUW tube_osbyte_long ; cmd 3: OSBYTE (3-param) |
| 9455 | 0508 | EQUW tube_osword ; cmd 4: OSWORD |
| 9457 | 050A | EQUW tube_osword_rdln ; cmd 5: OSWORD 0 (read line) |
| 9459 | 050C | EQUW tube_restore_regs ; cmd 6: release/restore regs |
| 945B | 050E | EQUW tube_release_return ; cmd 7: restore regs, RTS |
| 945D | 0510 | EQUW tube_osargs ; cmd 8: OSARGS |
| 945F | 0512 | EQUW tube_osbget ; cmd 9: OSBGET |
| 9461 | 0514 | EQUW tube_osbput ; cmd 10: OSBPUT |
| 9463 | 0516 | EQUW tube_osfind ; cmd 11: OSFIND |
| 9465 | 0518 | EQUW tube_osfile ; cmd 12: OSFILE |
| 9467 | 051A | EQUW tube_osgbpb ; cmd 13: OSGBPB |
| 9469 | 051C | .tube_wrch_handler |
| PHA ; Save character for WRCH echo | ||
| 946A | 051D | LDA #0 ; A=0: send null prefix via R2 |
| 946C | 051F | .tube_send_and_poll |
| JSR tube_send_r2 ; Send prefix byte to co-processor | ||
| 946F | 0522 | .poll_r2_reply←2← 052A BPL← 0532 JMP |
| BIT tube_status_register_2 ; Poll R2 for co-processor reply | ||
| 9472 | 0525 | BVS wrch_echo_reply ; R2 ready: go process reply |
| 9474 | 0527 | .tube_poll_r4_wrch |
| BIT tube_status_register_4_and_cpu_control ; Check R4 for pending WRCH request | ||
| 9477 | 052A | BPL poll_r2_reply ; No R4 data: back to polling R2 |
| 9479 | 052C | LDA tube_data_register_4 ; Read WRCH character from R4 |
| 947C | 052F | JSR nvwrch ; Write character |
| 947F | 0532 | .tube_resume_poll |
| JMP poll_r2_reply ; Resume R2 polling after servicing | ||
| 9482 | 0535 | .wrch_echo_reply←1← 0525 BVS |
| PLA ; Recover original character | ||
| 9483 | 0536 | STA tube_data_register_2 ; Echo character back via R2 |
| 9486 | 0539 | PHA ; Push for dispatch loop re-entry |
| 9487 | 053A | JMP tube_enter_main_loop ; Enter main dispatch loop |
| 948A | 053D | .tube_release_return |
| LDX zp_temp_11 ; Restore saved X | ||
| 948C | 053F | LDY zp_temp_10 ; Restore saved Y from temporary |
| 948E | 0541 | PLA ; Restore saved A |
| 948F | 0542 | RTS ; Return to caller |
| 9490 | 0543 | .tube_osbput |
| JSR tube_read_r2 ; Read file handle from R2 | ||
| 9493 | 0546 | TAY ; Y=channel handle from R2 |
| 9494 | 0547 | JSR tube_read_r2 ; Read data byte from R2 for BPUT |
| 9497 | 054A | .tube_poll_r1_wrch |
| JSR osbput ; Write a single byte A to an open file Y | ||
| 949A | 054D | JMP tube_reply_ack ; BPUT done: send acknowledge, return |
| 949D | 0550 | .tube_osbget |
| JSR tube_read_r2 ; Read file handle from R2 | ||
| 94A0 | 0553 | TAY ; Y=channel handle for OSBGET Y=file handle |
| 94A1 | 0554 | JSR osbget ; Read a single byte from an open file Y |
| 94A4 | 0557 | PHA ; Save byte read from file |
| 94A5 | 0558 | JMP send_reply_ok ; Send carry+byte reply (BGET result) |
| 94A8 | 055B | .tube_osrdch |
| JSR nvrdch ; Read a character from the current input stream | ||
| 94AB | 055E | PHA ; A=character read |
| 94AC | 055F | .send_reply_ok←1← 0558 JMP |
| ORA #&80 ; Set bit 7 (no-error flag) | ||
| 94AE | 0561 | .tube_rdch_reply |
| ROR ; ROR A: encode carry (error flag) into bit 7 | ||
| 94AF | 0562 | JSR tube_send_r2 ; = JSR tube_send_r2 (overlaps &053D entry) |
| 94B2 | 0565 | PLA ; Restore read character/byte |
| 94B3 | 0566 | JMP tube_reply_byte ; Return to Tube main loop |
| 94B6 | 0569 | .tube_osfind |
| JSR tube_read_r2 ; Read open mode from R2 | ||
| 94B9 | 056C | BEQ tube_osfind_close ; A=0: close file, else open with filename |
| 94BB | 056E | PHA ; Save open mode while reading filename |
| 94BC | 056F | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 94BF | 0572 | PLA ; Recover open mode from stack |
| 94C0 | 0573 | JSR osfind ; Open or close file(s) |
| 94C3 | 0576 | PHA ; Save file handle result |
| 94C4 | 0577 | LDA #&ff ; A=&FF: success marker |
| 94C6 | 0579 | JSR tube_send_r2 ; Send success marker via R2 |
| 94C9 | 057C | PLA ; Restore file handle |
| 94CA | 057D | JMP tube_reply_byte ; Send file handle result to co-processor |
| 94CD | 0580 | .tube_osfind_close←1← 056C BEQ |
| JSR tube_read_r2 ; OSFIND close: read handle from R2 | ||
| 94D0 | 0583 | TAY ; Y=handle to close |
| 94D1 | 0584 | LDA #osfind_close ; A=0: close command for OSFIND |
| 94D3 | 0586 | JSR osfind ; Close one or all files |
| 94D6 | 0589 | JMP tube_reply_ack ; Close done: send acknowledge, return |
| 94D9 | 058C | .tube_osargs |
| JSR tube_read_r2 ; Read file handle from R2 | ||
| 94DC | 058F | TAY ; Y=file handle for OSARGS |
| 94DD | 0590 | .tube_read_params |
| LDX #3 ; Read 4-byte arg + reason from R2 into ZP | ||
| 94DF | 0592 | .read_osargs_params←1← 0598 BPL |
| JSR tube_read_r2 ; Read next param byte from R2 | ||
| 94E2 | 0595 | STA zp_ptr_lo,x ; Params stored at &00-&03 (little-endian) |
| 94E4 | 0597 | DEX ; Decrement byte counter |
| 94E5 | 0598 | BPL read_osargs_params ; Loop until all 4 bytes read |
| 94E7 | 059A | INX ; X=0: reset index after loop |
| 94E8 | 059B | JSR tube_read_r2 ; Read OSARGS reason code from R2 |
| 94EB | 059E | JSR osargs ; Read or write a file's attributes |
| 94EE | 05A1 | JSR tube_send_r2 ; Send result A back to co-processor |
| 94F1 | 05A4 | LDX #3 ; Return 4-byte result from ZP &00-&03 |
| 94F3 | 05A6 | .send_osargs_result←1← 05AC BPL |
| LDA zp_ptr_lo,x ; Load result byte from zero page | ||
| 94F5 | 05A8 | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 94F8 | 05AB | DEX ; Previous byte (count down) |
| 94F9 | 05AC | BPL send_osargs_result ; Loop for all 4 bytes |
| 94FB | 05AE | JMP tube_main_loop ; Return to Tube main loop |
| 94FE | 05B1 | .tube_read_string←3← 056F JSR← 05C5 JSR← 05E2 JSR |
| LDX #0 ; X=0: initialise string buffer index | ||
| 9500 | 05B3 | LDY #0 ; Y=0: string buffer offset 0 |
| 9502 | 05B5 | .strnh←1← 05C0 BNE |
| JSR tube_read_r2 ; Read next string byte from R2 | ||
| 9505 | 05B8 | STA l0700,y ; Store byte in string buffer at &0700+Y |
| 9508 | 05BB | INY ; Next buffer position |
| 9509 | 05BC | BEQ string_buf_done ; Y overflow: string too long, truncate |
| 950B | 05BE | CMP #&0d ; Check for CR terminator |
| 950D | 05C0 | BNE strnh ; Not CR: continue reading string |
| 950F | 05C2 | .string_buf_done←1← 05BC BEQ |
| LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND | ||
| 9511 | 05C4 | RTS ; Return with XY pointing to &0700 |
| 9512 | 05C5 | .tube_oscli |
| JSR tube_read_string ; Read command string into &0700 | ||
| 9515 | 05C8 | JSR oscli ; Execute * command via OSCLI |
| 9518 | 05CB | .tube_reply_ack←3← 04AB JMP← 054D JMP← 0589 JMP |
| LDA #&7f ; &7F = success acknowledgement | ||
| 951A | 05CD | .tube_reply_byte←5← 0461 JMP← 0566 JMP← 057D JMP← 05D0 BVC← 06B8 JMP |
| BIT tube_status_register_2 ; Poll R2 status until ready | ||
| 951D | 05D0 | BVC tube_reply_byte ; Bit 6 clear: not ready, loop |
| 951F | 05D2 | STA tube_data_register_2 ; Write byte to R2 data register |
| 9522 | 05D5 | .mj←1← 0600 BEQ |
| JMP tube_main_loop ; Return to Tube main loop | ||
| 9525 | 05D8 | .tube_osfile |
| LDX #&10 ; X=&10: read 16-byte control block | ||
| 9527 | 05DA | .argsw←1← 05E0 BNE |
| JSR tube_read_r2 ; Read next control block byte from R2 | ||
| 952A | 05DD | STA zp_ptr_hi,x ; Store at &01+X (descending) |
| 952C | 05DF | DEX ; Decrement byte counter |
| 952D | 05E0 | BNE argsw ; Loop for all 16 bytes |
| 952F | 05E2 | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 9532 | 05E5 | STX zp_ptr_lo ; XY=&0700: filename pointer for OSFILE |
| 9534 | 05E7 | STY zp_ptr_hi ; Store Y=7 as pointer high byte |
| 9536 | 05E9 | LDY #0 ; Y=0 for OSFILE control block offset |
| 9538 | 05EB | JSR tube_read_r2 ; Read OSFILE reason code from R2 |
| 953B | 05EE | JSR osfile ; Execute OSFILE operation |
| 953E | 05F1 | ORA #&80 ; Set bit 7: mark result as present |
| 9540 | 05F3 | JSR tube_send_r2 ; Send result A (object type) to co-processor |
| 9543 | 05F6 | LDX #&10 ; Return 16-byte control block to co-processor |
| 9545 | 05F8 | .send_osfile_ctrl_blk←1← 05FE BNE |
| LDA zp_ptr_hi,x ; Load control block byte | ||
| 9547 | 05FA | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 954A | 05FD | DEX ; Decrement byte counter |
| 954B | 05FE | BNE send_osfile_ctrl_blk ; Loop for all 16 bytes |
| ; Tube host code page 6 — reference: NFS13 (GBPB-ESCA) | ||
| ; Copied from ROM (tube_code_page6) during init. ; &0600-&0601 is the tail | ||
| ; of tube_osfile (BEQ to tube_reply_byte when done). ; Contains: | ||
| ; &0602: tube_osgbpb — multi-byte file I/O | ||
| ; &0626: tube_osbyte_short — 2-param OSBYTE (returns X) | ||
| ; &063B: tube_osbyte_long — 3-param OSBYTE (returns carry+Y+X) | ||
| ; &065D: tube_osword — variable-length OSWORD (buffer at &0130) | ||
| ; &06A3: tube_osword_rdln — OSWORD 0 (read line, 5-byte params) | ||
| ; &06BB: tube_rdln_send_line — send input line from &0700 | ||
| ; &06D0: tube_send_r2 — poll R2 status, write A to R2 data | ||
| ; &06D9: tube_send_r4 — poll R4 status, write A to R4 data | ||
| ; &06E2: tube_escape_check — check &FF, forward escape to R1 | ||
| ; &06E8: tube_event_handler — EVNTV: forward event (A,X,Y) via R1 | ||
| ; &06F7: tube_send_r1 — poll R1 status, write A to R1 data | ||
| 954D | 0600 | .tube_code_page6←1← 8108 STA |
| BEQ mj ; OSGBPB done: return to main loop | ||
| 954F | 0602 | .tube_osgbpb |
| LDX #&0c ; X=&0C: read 13-byte param block | ||
| 9551 | 0604 | .read_gbpb_params←1← 060A BPL |
| JSR tube_read_r2 ; Read param byte from Tube R2 | ||
| 9554 | 0607 | STA zp_ptr_lo,x ; Store in zero page param block |
| 9556 | 0609 | DEX ; Next byte (descending) |
| 9557 | 060A | BPL read_gbpb_params ; Loop until all 13 bytes read |
| 9559 | 060C | JSR tube_read_r2 ; Read A (OSGBPB function code) |
| 955C | 060F | INX ; X=0 after loop |
| 955D | 0610 | LDY #0 ; Y=0 for OSGBPB call |
| 955F | 0612 | JSR osgbpb ; Read or write multiple bytes to an open file |
| 9562 | 0615 | ROR ; Encode carry into result bit 7 |
| 9563 | 0616 | JSR tube_send_r2 ; Send carry+result byte via R2 |
| 9566 | 0619 | LDX #&0c ; X=12: send 13 updated param bytes |
| 9568 | 061B | .send_gbpb_params←1← 0621 BPL |
| LDA zp_ptr_lo,x ; Load updated param byte | ||
| 956A | 061D | JSR tube_send_r2 ; Send param byte via R2 |
| 956D | 0620 | DEX ; Next byte (descending) |
| 956E | 0621 | BPL send_gbpb_params ; Loop until all 13 bytes sent |
| 9570 | 0623 | JMP tube_main_loop ; Return to main event loop |
| 9573 | 0626 | .tube_osbyte_short |
| JSR tube_read_r2 ; Read X parameter from R2 | ||
| 9576 | 0629 | TAX ; Save in X |
| 9577 | 062A | JSR tube_read_r2 ; Read A (OSBYTE function code) |
| 957A | 062D | JSR osbyte ; Execute OSBYTE A,X |
| 957D | 0630 | .tube_osbyte_send_x←2← 0633 BVC← 065B BVS |
| BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready) | ||
| 9580 | 0633 | BVC tube_osbyte_send_x ; Not ready: keep polling |
| 9582 | 0635 | STX tube_data_register_2 ; Send X result for 2-param OSBYTE |
| 9585 | 0638 | .bytex←1← 064B BEQ |
| JMP tube_main_loop ; Return to main event loop | ||
| 9588 | 063B | .tube_osbyte_long |
| JSR tube_read_r2 ; Read X parameter from R2 | ||
| 958B | 063E | TAX ; Save in X |
| 958C | 063F | JSR tube_read_r2 ; Read Y parameter from co-processor |
| 958F | 0642 | TAY ; Save in Y |
| 9590 | 0643 | JSR tube_read_r2 ; Read A (OSBYTE function code) |
| 9593 | 0646 | JSR osbyte ; Execute OSBYTE A,X,Y |
| 9596 | 0649 | EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT) |
| 9598 | 064B | BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed |
| 959A | 064D | LDA #&40 ; A=&40: high bit will hold carry |
| 959C | 064F | ROR ; Encode carry (error flag) into bit 7 |
| 959D | 0650 | JSR tube_send_r2 ; Send carry+status byte via R2 |
| 95A0 | 0653 | .tube_osbyte_send_y←1← 0656 BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95A3 | 0656 | BVC tube_osbyte_send_y ; Not ready: keep polling |
| 95A5 | 0658 | STY tube_data_register_2 ; Send Y result, then fall through to send X |
| 95A8 | 065B | BVS tube_osbyte_send_x ; ALWAYS branch |
| 95AA | 065D | .tube_osword |
| JSR tube_read_r2 ; Read OSWORD number from R2 | ||
| 95AD | 0660 | TAY ; Save OSWORD number in Y |
| 95AE | 0661 | .tube_osword_read←1← 0664 BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 95B1 | 0664 | BPL tube_osword_read ; Not ready: keep polling |
| 95B3 | 0666 | LDX tube_data_register_2 ; Read param block length from R2 |
| 95B6 | 0669 | DEX ; DEX: length 0 means no params to read |
| 95B7 | 066A | BMI skip_param_read ; No params (length=0): skip read loop |
| 95B9 | 066C | .tube_osword_read_lp←2← 066F BPL← 0678 BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 95BC | 066F | BPL tube_osword_read_lp ; Not ready: keep polling |
| 95BE | 0671 | LDA tube_data_register_2 ; Read param byte from R2 |
| 95C1 | 0674 | STA l0130,x ; Store param bytes into block at &0130 |
| 95C4 | 0677 | DEX ; Next param byte (descending) |
| 95C5 | 0678 | BPL tube_osword_read_lp ; Loop until all params read |
| 95C7 | 067A | TYA ; Restore OSWORD number from Y |
| 95C8 | 067B | .skip_param_read←1← 066A BMI |
| LDX #<(l0130) ; XY=&0130: param block address for OSWORD | ||
| 95CA | 067D | LDY #>(l0130) ; Y=&01: param block at &0130 |
| 95CC | 067F | JSR osword ; Execute OSWORD with XY=&0130 |
| 95CF | 0682 | LDA #&ff ; A=&FF: result marker for co-processor |
| 95D1 | 0684 | JSR tube_send_r2 ; Send result marker via R2 |
| 95D4 | 0687 | .poll_r2_osword_result←1← 068A BPL |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95D7 | 068A | BPL poll_r2_osword_result ; Not ready: keep polling |
| 95D9 | 068C | LDX tube_data_register_2 ; Read result block length from R2 |
| 95DC | 068F | DEX ; Decrement result byte counter |
| 95DD | 0690 | BMI tube_return_main ; No results to send: return to main loop |
| 95DF | 0692 | .tube_osword_write←1← 069E BPL |
| LDY l0130,x ; Send result block bytes from &0128 via R2 | ||
| 95E2 | 0695 | .tube_osword_write_lp←1← 0698 BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95E5 | 0698 | BVC tube_osword_write_lp ; Not ready: keep polling |
| 95E7 | 069A | STY tube_data_register_2 ; Send result byte via R2 |
| 95EA | 069D | DEX ; Next result byte (descending) |
| 95EB | 069E | BPL tube_osword_write ; Loop until all results sent |
| 95ED | 06A0 | .tube_return_main←1← 0690 BMI |
| JMP tube_main_loop ; Return to main event loop | ||
| 95F0 | 06A3 | .tube_osword_rdln |
| LDX #4 ; X=4: read 5-byte RDLN ctrl block | ||
| 95F2 | 06A5 | .read_rdln_ctrl_block←1← 06AB BPL |
| JSR tube_read_r2 ; Read control block byte from R2 | ||
| 95F5 | 06A8 | STA zp_ptr_lo,x ; Store in zero page params |
| 95F7 | 06AA | DEX ; Next byte (descending) |
| 95F8 | 06AB | BPL read_rdln_ctrl_block ; Loop until all 5 bytes read |
| 95FA | 06AD | INX ; X=0 after loop, A=0 for OSWORD 0 (read line) |
| 95FB | 06AE | LDY #0 ; Y=0 for OSWORD 0 |
| 95FD | 06B0 | TXA ; A=0: OSWORD 0 (read line) |
| 95FE | 06B1 | JSR osword ; Read input line from keyboard |
| 9601 | 06B4 | BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed |
| 9603 | 06B6 | LDA #&ff ; &FF = escape/error signal to co-processor |
| 9605 | 06B8 | JMP tube_reply_byte ; Escape: send &FF error to co-processor |
| 9608 | 06BB | .tube_rdln_send_line←1← 06B4 BCC |
| LDX #0 ; X=0: start of input buffer at &0700 | ||
| 960A | 06BD | LDA #&7f ; &7F = line read successfully |
| 960C | 06BF | JSR tube_send_r2 ; Send &7F (success) to co-processor |
| 960F | 06C2 | .tube_rdln_send_loop←1← 06CB BNE |
| LDA l0700,x ; Load char from input buffer | ||
| 9612 | 06C5 | .tube_rdln_send_byte |
| JSR tube_send_r2 ; Send char to co-processor | ||
| 9615 | 06C8 | INX ; Next character |
| 9616 | 06C9 | CMP #&0d ; Check for CR terminator |
| 9618 | 06CB | BNE tube_rdln_send_loop ; Loop until CR terminator sent |
| 961A | 06CD | JMP tube_main_loop ; Return to main event loop |
| 961D | 06D0 | .tube_send_r2←18← 0020 JSR← 0026 JSR← 002C JSR← 04E9 JSR← 051F JSR← 0562 JSR← 0579 JSR← 05A1 JSR← 05A8 JSR← 05F3 JSR← 05FA JSR← 0616 JSR← 061D JSR← 0650 JSR← 0684 JSR← 06BF JSR← 06C5 JSR← 06D3 BVC |
| BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready) | ||
| 9620 | 06D3 | BVC tube_send_r2 ; Not ready: keep polling |
| 9622 | 06D5 | STA tube_data_register_2 ; Write A to Tube R2 data register |
| 9625 | 06D8 | RTS ; Return to caller |
| 9626 | 06D9 | .tube_send_r4←5← 0018 JSR← 042A JSR← 0432 JSR← 0448 JSR← 06DC BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status (bit 6 = ready) | ||
| 9629 | 06DC | BVC tube_send_r4 ; Not ready: keep polling |
| 962B | 06DE | STA tube_data_register_4 ; Write A to Tube R4 data register |
| 962E | 06E1 | RTS ; Return to caller |
| 962F | 06E2 | .tube_escape_check←1← 0403 JMP |
| LDA escape_flag ; Check OS escape flag at &FF | ||
| 9631 | 06E4 | SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7 |
| 9632 | 06E5 | ROR ; ROR: shift escape bit 7 to carry |
| 9633 | 06E6 | BMI tube_send_r1 ; Escape set: forward to co-processor via R1 |
| 9635 | 06E8 | .tube_event_handler |
| PHA ; EVNTV: forward event A, Y, X to co-processor | ||
| 9636 | 06E9 | LDA #0 ; Send &00 prefix (event notification) |
| 9638 | 06EB | JSR tube_send_r1 ; Send zero prefix via R1 |
| 963B | 06EE | TYA ; Y value for event |
| 963C | 06EF | JSR tube_send_r1 ; Send Y via R1 |
| 963F | 06F2 | TXA ; X value for event |
| 9640 | 06F3 | JSR tube_send_r1 ; Send X via R1 |
| 9643 | 06F6 | PLA ; Restore A (event type) |
| 9644 | 06F7 | .tube_send_r1←5← 06E6 BMI← 06EB JSR← 06EF JSR← 06F3 JSR← 06FA BVC |
| BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready) | ||
| 9647 | 06FA | BVC tube_send_r1 ; Not ready: keep polling |
| 9649 | 06FC | STA tube_data_register_1 ; Write A to Tube R1 data register |
| 964C | 06FF | RTS ; Return to caller |
| 964D | EQUB &FF, &42, &FF, &00, &FF, &77, &FF, &FF, &FF, &DF, &FF, &00, &FF, &00, &FF, &00, &FF, &04, &FF | |
| 9660 | .trampoline_tx_setup←2← 8668 JSR← 8E4A JSR | |
| JMP tx_begin ; Trampoline: forward to tx_begin | ||
| 9663 | .trampoline_adlc_init←1← 82CA JSR | |
| JMP adlc_init ; Trampoline: forward to adlc_init | ||
| 9666 | .svc_12_nmi_release | |
| JMP save_econet_state ; Trampoline: forward to NMI release | ||
| 9669 | .svc_11_nmi_claim | |
| JMP restore_econet_state ; Trampoline: forward to NMI claim | ||
| 966C | .svc_5_unknown_irq | |
| JMP check_sr_irq ; Trampoline: forward to IRQ handler | ||
ADLC initialisationReads station ID (INTOFF side effect), performs full ADLC reset, checks for Tube presence (OSBYTE &EA), then falls through to adlc_init_workspace. |
|
| 966F | .adlc_init←1← 9663 JMP |
| BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs | |
| 9672 | JSR adlc_full_reset ; Full ADLC hardware reset |
| 9675 | LDA #osbyte_read_tube_presence ; OSBYTE &EA: check Tube co-processor |
| 9677 | LDX #0 ; X=0 for OSBYTE |
| 9679 | LDY #&ff ; Y=&FF for OSBYTE |
| 967B | JSR osbyte ; Read Tube present flag |
| 967E | STX tx_in_progress ; X=value of Tube present flag |
| fall through ↓ | |
| 9681 | .adlc_init_workspace←1← 96CA JMP |
| JSR install_nmi_shim ; Copy NMI shim from ROM to &0D00 | |
| 9684 | LDA romsel_copy ; Load current ROM bank number |
| 9686 | STA nmi_shim_07 ; Patch ROM bank into NMI shim |
| 9689 | LDA #&80 ; A=&80: TX idle/complete status |
| 968B | STA tx_ctrl_status ; Mark Econet as initialised |
| 968E | LDA station_id_disable_net_nmis ; Read station ID (&FE18 = INTOFF side effect) |
| 9691 | STA tx_src_stn ; Store our station ID in TX scout |
| 9694 | LDA #0 ; A=0: clear source network |
| 9696 | STA tx_src_net ; Clear TX source network byte |
| 9699 | BIT video_ula_control ; INTON: re-enable NMIs (&FE20 read side effect) |
| 969C | RTS ; Return to caller |
Save Econet state to RX control blockStores rx_status_flags, protection_mask, and tx_in_progress to (net_rx_ptr) at offsets 8-10. INTOFF side effect on entry. |
|
| 969D | .save_econet_state←1← 9666 JMP |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs for state save | |
| 96A0 | LDY #8 ; Y=8: RXCB offset for rx_status_flags |
| 96A2 | LDA rx_status_flags ; Load rx_status_flags |
| 96A5 | STA (net_rx_ptr),y ; Store to RXCB offset 8 |
| 96A7 | INY ; Y=&09 |
| 96A8 | LDA prot_status ; Load prot_status |
| 96AB | STA (net_rx_ptr),y ; Store to RXCB offset 9 |
| 96AD | INY ; Y=&0a |
| 96AE | LDA tx_in_progress ; Load TX in-progress flag |
| 96B1 | STA (net_rx_ptr),y ; Store tx_in_progress to offset &0A |
| 96B3 | RTS ; Return to caller |
Restore Econet state from RX control blockLoads rx_status_flags, protection_mask, and tx_in_progress from (net_rx_ptr) at offsets 8-10, then reinitialises via adlc_init_workspace. |
|
| 96B4 | .restore_econet_state←1← 9669 JMP |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs for state restore | |
| 96B7 | LDY #8 ; Y=8: workspace offset for flags |
| 96B9 | LDA (net_rx_ptr),y ; Load saved rx_status_flags |
| 96BB | STA rx_status_flags ; Restore rx_status_flags |
| 96BE | INY ; Y=&09 |
| 96BF | LDA (net_rx_ptr),y ; Load saved protection mask |
| 96C1 | STA prot_status ; Restore prot_status |
| 96C4 | INY ; Y=&0a |
| 96C5 | LDA (net_rx_ptr),y ; Load saved tx_in_progress |
| 96C7 | STA tx_in_progress ; Restore TX state |
| 96CA | JMP adlc_init_workspace ; Reinitialise NMI workspace |
Copy NMI shim from ROM (&9FCA) to RAM (&0D00)Copies 32 bytes. Interrupts are enabled during the copy. |
|
| 96CD | .install_nmi_shim←1← 9681 JSR |
| PHP ; Save interrupt state on stack | |
| 96CE | CLI ; Enable interrupts during copy |
| 96CF | LDY #&20 ; Y=&20: copy 32 bytes |
| 96D1 | .copy_nmi_shim_loop←1← 96D8 BNE |
| LDA nmi_shim_rom_src,y ; Load NMI shim byte from ROM | |
| 96D4 | STA l0cff,y ; Store to NMI area at &0D00+Y |
| 96D7 | DEY ; Decrement byte counter |
| 96D8 | BNE copy_nmi_shim_loop ; Loop until all bytes copied |
| 96DA | PLP ; Restore interrupt state |
| 96DB | RTS ; Return from shim installation |
ADLC full resetAborts all activity and returns to idle RX listen mode. |
|
| 96DC | .adlc_full_reset←3← 9672 JSR← 973E JSR← 9894 JSR |
| LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set) | |
| 96DE | STA econet_control1_or_status1 ; Write CR1: full reset |
| 96E1 | LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding |
| 96E3 | STA econet_data_terminate_frame ; Write CR4 via ADLC reg 3 (AC=1) |
| 96E6 | LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR |
| 96E8 | STA econet_control23_or_status2 ; Write CR3=0: clear loop-back/AEX/DTR |
| fall through ↓ | |
Enter RX listen modeTX held in reset, RX active with interrupts. Clears all status. |
|
| 96EB | .adlc_rx_listen←1← 9A40 JSR |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled) | |
| 96ED | STA econet_control1_or_status1 ; Write CR1: RIE | TX_RESET |
| 96F0 | LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE |
| 96F2 | STA econet_control23_or_status2 ; Write CR2: listen mode config |
| 96F5 | RTS ; Return |
NMI RX scout handler (initial byte)Default NMI handler for incoming scout frames. Checks if the frame is addressed to us or is a broadcast. Installed as the NMI target during idle RX listen mode. Tests SR2 bit0 (AP = Address Present) to detect incoming data. Reads the first byte (destination station) from the RX FIFO and compares against our station ID. Reading &FE18 also disables NMIs (INTOFF side effect). |
|
| 96F6 | .nmi_rx_scout←1← 9FD6 JMP |
| LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present) | |
| 96F8 | BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set |
| 96FB | BEQ scout_error ; AP not set, no incoming data -- check for errors |
| 96FD | LDA econet_data_continue_frame ; Read first RX byte (destination station address) |
| 9700 | CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs) |
| 9703 | BEQ accept_frame ; Match -- accept frame |
| 9705 | CMP #&ff ; Check for broadcast address (&FF) |
| 9707 | BNE scout_reject ; Neither our address nor broadcast -- reject frame |
| 9709 | LDA #&40 ; Flag &40 = broadcast frame |
| 970B | STA tx_flags ; Store broadcast flag in TX flags |
| 970E | .accept_frame←1← 9703 BEQ |
| LDA #&15 ; Install next NMI handler at &9715 (RX scout second byte) | |
| 9710 | LDY #&97 ; High byte of scout net handler |
| 9712 | JMP set_nmi_vector ; Install next handler and RTI |
RX scout second byte handlerReads the second byte of an incoming scout (destination network). Checks for network match: 0 = local network (accept), &FF = broadcast (accept and flag), anything else = reject. Installs the scout data reading loop handler at &9747. |
|
| 9715 | .nmi_rx_scout_net |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 9718 | BPL scout_error ; No RDA -- check errors |
| 971A | LDA econet_data_continue_frame ; Read destination network byte |
| 971D | BEQ accept_local_net ; Network = 0 -- local network, accept |
| 971F | EOR #&ff ; EOR &FF: test if network = &FF (broadcast) |
| 9721 | BEQ accept_scout_net ; Broadcast network -- accept |
| 9723 | .scout_reject←1← 9707 BNE |
| LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE | |
| 9725 | STA econet_control1_or_status1 ; Write CR1 to discontinue RX |
| 9728 | JMP discard_after_reset ; Return to idle scout listening |
| 972B | .accept_local_net←1← 971D BEQ |
| STA tx_flags ; Network = 0 (local): clear tx_flags | |
| 972E | .accept_scout_net←1← 9721 BEQ |
| STA port_buf_len ; Store Y offset for scout data buffer | |
| 9730 | LDA #&47 ; Install scout data reading loop at &9747 |
| 9732 | LDY #&97 ; High byte of scout data handler |
| 9734 | JMP set_nmi_vector ; Install scout data loop and RTI |
Scout error/discard handlerReached when the scout data loop sees no RDA (BPL at &974C) or when scout completion finds unexpected SR2 state. If SR2 & &81 is non-zero (AP or RDA still active), performs full ADLC reset and discards. If zero (clean end), discards via &9A40. This path is a common landing for any unexpected ADLC state during scout reception. |
|
| 9737 | .scout_error←5← 96FB BEQ← 9718 BPL← 974C BPL← 9780 BEQ← 9782 BPL |
| LDA econet_control23_or_status2 ; Read SR2 | |
| 973A | AND #&81 ; Test AP (b0) | RDA (b7) |
| 973C | BEQ scout_discard ; Neither set -- clean end, discard via &9744 |
| 973E | JSR adlc_full_reset ; Unexpected data/status: full ADLC reset |
| 9741 | JMP discard_after_reset ; Discard and return to idle |
| 9744 | .scout_discard←1← 973C BEQ |
| JMP discard_listen ; Gentle discard: RX_DISCONTINUE | |
Scout data reading loopReads the body of a scout frame, two bytes per iteration. Stores bytes at &0D3D+Y (scout buffer: src_stn, src_net, ctrl, port, ...). Between each pair it checks SR2: - At entry (&9749): LDA SR2, BPL tests RDA (bit7) - No RDA (BPL) -> error (&9737) - RDA set (BMI) -> read byte - After first byte (&9755): LDA SR2 - RDA set (BMI) -> read second byte - SR2 non-zero (BNE) -> scout completion (&9771) This is the FV detection point: when FV is set (by inline refill of the last byte during the preceding RX FIFO read), SR2 is non-zero and the branch is taken. - SR2 = 0 -> RTI, wait for next NMI - After second byte (&9769): LDA SR2 - SR2 non-zero (BNE) -> loop back to &974C - SR2 = 0 -> RTI, wait for next NMI The loop ends at Y=&0C (12 bytes max in scout buffer). |
|
| 9747 | .scout_data_loop |
| LDY port_buf_len ; Y = buffer offset | |
| 9749 | LDA econet_control23_or_status2 ; Read SR2 |
| 974C | .scout_loop_rda←1← 976C BNE |
| BPL scout_error ; No RDA -- error handler &9737 | |
| 974E | LDA econet_data_continue_frame ; Read data byte from RX FIFO |
| 9751 | STA rx_src_stn,y ; Store at &0D3D+Y (scout buffer) |
| 9754 | INY ; Advance buffer index |
| 9755 | LDA econet_control23_or_status2 ; Read SR2 again (FV detection point) |
| 9758 | BMI scout_loop_second ; RDA set -- more data, read second byte |
| 975A | BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion |
| 975C | .scout_loop_second←1← 9758 BMI |
| LDA econet_data_continue_frame ; Read second byte of pair | |
| 975F | STA rx_src_stn,y ; Store at &0D3D+Y |
| 9762 | INY ; Advance and check buffer limit |
| 9763 | CPY #&0c ; Copied all 12 scout bytes? |
| 9765 | BEQ scout_complete ; Buffer full (Y=12) -- force completion |
| 9767 | STY port_buf_len ; Save Y for next iteration |
| 9769 | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 976C | BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes |
| 976E | JMP nmi_rti ; SR2 = 0 -- RTI, wait for next NMI |
Scout completion handlerReached from the scout data loop when SR2 is non-zero (FV detected). Disables PSE to allow individual SR2 bit testing: CR1=&00 (clear all enables) CR2=&84 (RDA_SUPPRESS_FV | FC_TDRA) -- no PSE, no CLR bits Then checks FV (bit1) and RDA (bit7): - No FV (BEQ) -> error &9737 (not a valid frame end) - FV set, no RDA (BPL) -> error &9737 (missing last byte) - FV set, RDA set -> read last byte, process scout After reading the last byte, the complete scout buffer (&0D3D-&0D48) contains: src_stn, src_net, ctrl, port [, extra_data...]. The port byte at &0D40 determines further processing: - Port = 0 -> immediate operation (&9A59) - Port non-zero -> check if it matches an open receive block |
|
| 9771 | .scout_complete←2← 975A BNE← 9765 BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 9773 | STA econet_control1_or_status1 ; Write CR1 |
| 9776 | LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV |
| 9778 | STA econet_control23_or_status2 ; Write CR2 |
| 977B | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 977D | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 9780 | BEQ scout_error ; No FV -- not a valid frame end, error |
| 9782 | BPL scout_error ; FV set but no RDA -- missing last byte, error |
| 9784 | LDA econet_data_continue_frame ; Read last byte from RX FIFO |
| 9787 | STA rx_src_stn,y ; Store last byte at &0D3D+Y |
| 978A | LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK) |
| 978C | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 978F | LDA rx_port ; Check port byte: 0 = immediate op, non-zero = data transfer |
| 9792 | BNE scout_match_port ; Port non-zero -- look for matching receive block |
| 9794 | JMP immediate_op ; Port = 0 -- immediate operation handler |
| 9797 | .scout_no_match←3← 97E2 BNE← 97E7 BVC← 9819 JMP |
| JMP rx_error ; Port = 0 -- immediate operation handler | |
| 979A | .scout_match_port←1← 9792 BNE |
| BIT tx_flags ; Check if broadcast (bit6 of tx_flags) | |
| 979D | BVC scan_port_list ; Not broadcast -- skip CR2 setup |
| 979F | LDA #7 ; CR2=&07: broadcast prep |
| 97A1 | STA econet_control23_or_status2 ; Write CR2: broadcast frame prep |
| 97A4 | .scan_port_list←1← 979D BVC |
| BIT rx_status_flags ; Check if RX port list active (bit7) | |
| 97A7 | BPL scout_network_match ; No active ports -- try NFS workspace |
| 97A9 | LDA #&c0 ; Start scanning port list at page &C0 |
| 97AB | .scan_nfs_port_list |
| STA port_ws_offset ; Store page to workspace pointer low | |
| 97AD | LDA #0 ; A=0: no NFS workspace offset yet |
| 97AF | STA rx_buf_offset ; Clear NFS workspace search flag |
| 97B1 | .check_port_slot←1← 97DE BCC |
| LDY #0 ; Y=0: read control byte from start of slot | |
| 97B3 | .scout_ctrl_check←1← 97F1 BEQ |
| LDA (port_ws_offset),y ; Read port control byte from slot | |
| 97B5 | BEQ scout_station_check ; Zero = end of port list, no match |
| 97B7 | CMP #&7f ; &7F = any-port wildcard |
| 97B9 | BNE next_port_slot ; Not wildcard -- check specific port match |
| 97BB | INY ; Y=1: advance to port byte in slot |
| 97BC | LDA (port_ws_offset),y ; Read port number from slot (offset 1) |
| 97BE | BEQ check_station_filter ; Zero port in slot = match any port |
| 97C0 | CMP rx_port ; Check if port matches this slot |
| 97C3 | BNE next_port_slot ; Port mismatch -- try next slot |
| 97C5 | .check_station_filter←1← 97BE BEQ |
| INY ; Y=2: advance to station byte | |
| 97C6 | LDA (port_ws_offset),y ; Read station filter from slot (offset 2) |
| 97C8 | BEQ scout_port_match ; Zero station = match any station, accept |
| 97CA | CMP rx_src_stn ; Check if source station matches |
| 97CD | BNE next_port_slot ; Station mismatch -- try next slot |
| 97CF | .scout_port_match←1← 97C8 BEQ |
| INY ; Y=3: advance to network byte | |
| 97D0 | LDA (port_ws_offset),y ; Read network filter from slot (offset 3) |
| 97D2 | CMP rx_src_net ; Check if source network matches |
| 97D5 | BEQ scout_accept ; Network matches or zero = accept |
| 97D7 | .next_port_slot←3← 97B9 BNE← 97C3 BNE← 97CD BNE |
| LDA port_ws_offset ; Check if NFS workspace search pending | |
| 97D9 | CLC ; CLC for 12-byte slot advance |
| 97DA | ADC #&0c ; Advance to next 12-byte port slot |
| 97DC | STA port_ws_offset ; Update workspace pointer to next slot |
| 97DE | BCC check_port_slot ; Always branches (page &C0 won't overflow) |
| 97E0 | .scout_station_check←1← 97B5 BEQ |
| LDA rx_buf_offset ; Check if NFS workspace already searched | |
| 97E2 | BNE scout_no_match ; Already searched: no match found |
| 97E4 | .scout_network_match←1← 97A7 BPL |
| BIT rx_status_flags ; Try NFS workspace if paged list exhausted | |
| 97E7 | BVC scout_no_match ; No NFS workspace RX (bit6 clear) -- discard |
| 97E9 | LDA nfs_workspace_hi ; Get NFS workspace page number |
| 97EB | STA rx_buf_offset ; Mark NFS workspace as search target |
| 97ED | LDY #0 ; Y=0: start at offset 0 in workspace |
| 97EF | STY port_ws_offset ; Reset slot pointer to start |
| 97F1 | BEQ scout_ctrl_check ; ALWAYS branch |
| 97F3 | .scout_accept←1← 97D5 BEQ |
| BIT tx_flags ; Check broadcast flag (bit 6) | |
| 97F6 | BVC ack_scout_match ; Not broadcast: ACK and set up RX |
| 97F8 | JMP copy_scout_fields ; Broadcast: copy scout fields directly |
| 97FB | .ack_scout_match←2← 97F6 BVC← 9AC5 JMP |
| LDA #3 ; Match found: set scout_status = 3 | |
| 97FD | STA scout_status ; Record match for completion handler |
| 9800 | LDA nmi_tx_block ; Save current TX block ptr (low) |
| 9802 | PHA ; Push TX block low on stack |
| 9803 | LDA nmi_tx_block_hi ; Save current TX block ptr (high) |
| 9805 | PHA ; Push TX block high on stack |
| 9806 | LDA port_ws_offset ; Use port slot as temp RXCB ptr (lo) |
| 9808 | STA nmi_tx_block ; Set RXCB low for tx_calc_transfer |
| 980A | LDA rx_buf_offset ; Use workspace page as temp RXCB (hi) |
| 980C | STA nmi_tx_block_hi ; Set RXCB high for tx_calc_transfer |
| 980E | JSR tx_calc_transfer ; Calculate transfer parameters |
| 9811 | PLA ; Restore original TX block (high) |
| 9812 | STA nmi_tx_block_hi ; Restore TX block ptr (high) |
| 9814 | PLA ; Restore original TX block (low) |
| 9815 | STA nmi_tx_block ; Restore TX block ptr (low) |
| 9817 | BCS send_data_rx_ack ; Transfer OK: send data ACK |
| 9819 | JMP scout_no_match ; Broadcast: different completion path |
| 981C | .send_data_rx_ack←2← 9817 BCS← 9ABA JMP |
| LDA #&44 ; CR1=&44: RX_RESET | TIE | |
| 981E | STA econet_control1_or_status1 ; Write CR1: TX mode for ACK |
| 9821 | LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE |
| 9823 | STA econet_control23_or_status2 ; Write CR2: enable TX with PSE |
| 9826 | LDA #&2d ; Install data_rx_setup at &982D |
| 9828 | LDY #&98 ; High byte of data_rx_setup handler |
| 982A | JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI |
| 982D | .data_rx_setup |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame) | |
| 982F | STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame |
| 9832 | LDA #&39 ; Install nmi_data_rx at &9839 |
| 9834 | LDY #&98 ; High byte of nmi_data_rx handler |
| 9836 | JMP set_nmi_vector ; Install nmi_data_rx and return from NMI |
Data frame RX handler (four-way handshake)Receives the data frame after the scout ACK has been sent. First checks AP (Address Present) for the start of the data frame. Reads and validates the first two address bytes (dest_stn, dest_net) against our station address, then installs continuation handlers to read the remaining data payload into the open port buffer. Handler chain: &9839 (AP+addr check) -> &984F (net=0 check) -> &9865 (skip ctrl+port) -> &989A (bulk data read) -> &98CE (completion) |
|
| 9839 | .nmi_data_rx |
| LDA #1 ; A=&01: mask for AP (Address Present) | |
| 983B | BIT econet_control23_or_status2 ; BIT SR2: test AP bit |
| 983E | BEQ rx_error ; No AP: wrong frame or error |
| 9840 | LDA econet_data_continue_frame ; Read first byte (dest station) |
| 9843 | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF) |
| 9846 | BNE rx_error ; Not for us: error path |
| 9848 | LDA #&4f ; Install net check handler at &984F |
| 984A | LDY #&98 ; High byte of nmi_data_rx_net handler |
| 984C | JMP set_nmi_vector ; Set NMI vector via RAM shim |
| 984F | .nmi_data_rx_net |
| BIT econet_control23_or_status2 ; Validate source network = 0 | |
| 9852 | BPL rx_error ; SR2 bit7 clear: no data ready -- error |
| 9854 | LDA econet_data_continue_frame ; Read dest network byte |
| 9857 | BNE rx_error ; Network != 0: wrong network -- error |
| 9859 | LDA #&65 ; Install skip handler at &9865 |
| 985B | LDY #&98 ; High byte of &9865 handler |
| 985D | BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting |
| 9860 | BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI |
| 9862 | JMP set_nmi_vector ; Install handler and return via RTI |
| 9865 | .nmi_data_rx_skip←1← 9860 BMI |
| BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout) | |
| 9868 | BPL rx_error ; SR2 bit7 clear: error |
| 986A | LDA econet_data_continue_frame ; Discard control byte |
| 986D | LDA econet_data_continue_frame ; Discard port byte |
| fall through ↓ | |
| 9870 | .install_data_rx_handler←1← 9F2F JMP |
| LDA #2 ; A=2: Tube transfer flag mask | |
| 9872 | BIT tx_flags ; Check if Tube transfer active |
| 9875 | BNE install_tube_rx ; Tube active: use Tube RX path |
| 9877 | LDA #&9a ; Install bulk read at &989A |
| 9879 | LDY #&98 ; High byte of &989A handler |
| 987B | BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting? |
| 987E | BMI nmi_data_rx_bulk ; Yes: enter bulk read directly |
| 9880 | JMP set_nmi_vector ; No: install handler and RTI |
| 9883 | .install_tube_rx←1← 9875 BNE |
| LDA #&f7 ; Tube: install Tube RX at &98F7 | |
| 9885 | LDY #&98 ; High byte of &98F7 handler |
| 9887 | JMP set_nmi_vector ; Install Tube handler and RTI |
NMI error handler dispatchCommon error/abort entry used by 12 call sites. Checks tx_flags bit 7: if clear, does a full ADLC reset and returns to idle listen (RX error path); if set, jumps to tx_result_fail (TX not-listening path). |
|
| 988A | .rx_error←12← 9797 JMP← 983E BEQ← 9846 BNE← 9852 BPL← 9857 BNE← 9868 BPL← 98AD BEQ← 98DF BEQ← 98E5 BEQ← 9930 JMP← 99B8 JMP← 9A8C JMP |
| .nmi_error_dispatch←12← 9797 JMP← 983E BEQ← 9846 BNE← 9852 BPL← 9857 BNE← 9868 BPL← 98AD BEQ← 98DF BEQ← 98E5 BEQ← 9930 JMP← 99B8 JMP← 9A8C JMP | |
| LDA tx_flags ; Check tx_flags for error path | |
| 988D | BPL rx_error_reset ; Bit7 clear: RX error path |
| 988F | LDA #&41 ; A=&41: 'not listening' error |
| 9891 | JMP tx_store_result ; Bit7 set: TX result = not listening |
| 9894 | .rx_error_reset←1← 988D BPL |
| JSR adlc_full_reset ; Full ADLC reset on RX error | |
| 9897 | JMP discard_reset_listen ; Discard and return to idle listen |
Data frame bulk read loopReads data payload bytes from the RX FIFO and stores them into the open port buffer at (open_port_buf),Y. Reads bytes in pairs (like the scout data loop), checking SR2 between each pair. SR2 non-zero (FV or other) -> frame completion at &98CE. SR2 = 0 -> RTI, wait for next NMI to continue. |
|
| 989A | .nmi_data_rx_bulk←1← 987E BMI |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 989C | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 989F | .data_rx_loop←1← 98C9 BNE |
| BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV) | |
| 98A1 | LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO |
| 98A4 | STA (open_port_buf),y ; Store byte to buffer |
| 98A6 | INY ; Advance buffer offset |
| 98A7 | BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing |
| 98A9 | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 98AB | DEC port_buf_len_hi ; Decrement remaining page count |
| 98AD | BEQ rx_error ; No pages left: handle as complete |
| 98AF | .read_sr2_between_pairs←1← 98A7 BNE |
| LDA econet_control23_or_status2 ; Read SR2 between byte pairs | |
| 98B2 | BMI read_second_rx_byte ; SR2 bit7 set: more data available |
| 98B4 | BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done |
| 98B6 | .read_second_rx_byte←1← 98B2 BMI |
| LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO | |
| 98B9 | STA (open_port_buf),y ; Store byte to buffer |
| 98BB | INY ; Advance buffer offset |
| 98BC | STY port_buf_len ; Save updated buffer position |
| 98BE | BNE check_sr2_loop_again ; Y != 0: no page boundary crossing |
| 98C0 | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 98C2 | DEC port_buf_len_hi ; Decrement remaining page count |
| 98C4 | BEQ data_rx_complete ; No pages left: frame complete |
| 98C6 | .check_sr2_loop_again←1← 98BE BNE |
| LDA econet_control23_or_status2 ; Read SR2 for next iteration | |
| 98C9 | BNE data_rx_loop ; SR2 non-zero: more data, loop back |
| 98CB | JMP nmi_rti ; SR2=0: no more data yet, wait for NMI |
Data frame completion |
|
| 98CE | .data_rx_complete←3← 989F BPL← 98B4 BNE← 98C4 BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 98D0 | STA econet_control1_or_status1 ; Write CR1 |
| 98D3 | LDA #&84 ; CR2=&84: disable PSE for individual bit testing |
| 98D5 | STA econet_control23_or_status2 ; Write CR2 |
| 98D8 | STY port_buf_len ; Save Y (byte count from data RX loop) |
| 98DA | LDA #2 ; A=&02: FV mask |
| 98DC | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 98DF | BEQ rx_error ; No FV -- error |
| 98E1 | BPL send_ack ; FV set, no RDA -- proceed to ACK |
| 98E3 | LDA port_buf_len_hi ; Check if buffer space remains |
| 98E5 | .read_last_rx_byte |
| BEQ rx_error ; No buffer space: error/discard frame | |
| 98E7 | LDA econet_data_continue_frame ; FV+RDA: read and store last data byte |
| 98EA | LDY port_buf_len ; Y = current buffer write offset |
| 98EC | STA (open_port_buf),y ; Store last byte in port receive buffer |
| 98EE | INC port_buf_len ; Advance buffer write offset |
| 98F0 | BNE send_ack ; No page wrap: proceed to send ACK |
| 98F2 | INC open_port_buf_hi ; Page boundary: advance buffer page |
| 98F4 | .send_ack←2← 98E1 BPL← 98F0 BNE |
| JMP ack_tx ; Send ACK frame to complete handshake | |
| 98F7 | .nmi_data_rx_tube |
| LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path | |
| 98FA | .rx_tube_data←1← 992B BNE |
| BPL data_rx_tube_complete ; RDA clear: no more data, frame complete | |
| 98FC | LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO |
| 98FF | INC port_buf_len ; Advance Tube transfer byte count |
| 9901 | STA tube_data_register_3 ; Send byte to Tube data register 3 |
| 9904 | BNE rx_update_buf ; No overflow: read second byte |
| 9906 | INC port_buf_len_hi ; Carry to transfer count byte 2 |
| 9908 | BNE rx_update_buf ; No overflow: read second byte |
| 990A | INC open_port_buf ; Carry to transfer count byte 3 |
| 990C | BNE rx_update_buf ; No overflow: read second byte |
| 990E | INC open_port_buf_hi ; Carry to transfer count byte 4 |
| 9910 | BEQ data_rx_tube_error ; All bytes zero: overflow error |
| 9912 | .rx_update_buf←3← 9904 BNE← 9908 BNE← 990C BNE |
| LDA econet_data_continue_frame ; Read second data byte (paired transfer) | |
| 9915 | STA tube_data_register_3 ; Send second byte to Tube |
| 9918 | INC port_buf_len ; Advance count after second byte |
| 991A | BNE rx_check_error ; No overflow: check for more data |
| 991C | INC port_buf_len_hi ; Carry to count byte 2 |
| 991E | BNE rx_check_error ; No overflow: check for more data |
| 9920 | INC open_port_buf ; Carry to count byte 3 |
| 9922 | BNE rx_check_error ; No overflow: check for more data |
| 9924 | INC open_port_buf_hi ; Carry to count byte 4 |
| 9926 | BEQ data_rx_tube_complete ; Zero: Tube transfer complete |
| 9928 | .rx_check_error←3← 991A BNE← 991E BNE← 9922 BNE |
| LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair | |
| 992B | BNE rx_tube_data ; More data available: continue loop |
| 992D | JMP nmi_rti ; Return from NMI, wait for data |
| 9930 | .data_rx_tube_error←3← 9910 BEQ← 9942 BEQ← 994E BEQ |
| JMP rx_error ; Unexpected end: return from NMI | |
| 9933 | .data_rx_tube_complete←2← 98FA BPL← 9926 BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 9935 | STA econet_control1_or_status1 ; Write CR1 for individual bit testing |
| 9938 | LDA #&84 ; CR2=&84: disable PSE |
| 993A | STA econet_control23_or_status2 ; Write CR2: same pattern as main path |
| 993D | LDA #2 ; A=&02: FV mask for Tube completion |
| 993F | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 9942 | BEQ data_rx_tube_error ; No FV: incomplete frame, error |
| 9944 | BPL ack_tx ; FV set, no RDA: proceed to ACK |
| 9946 | LDA port_buf_len ; Check if any buffer was allocated |
| 9948 | ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together |
| 994A | ORA open_port_buf ; Check buffer low byte |
| 994C | ORA open_port_buf_hi ; Check buffer high byte |
| 994E | BEQ data_rx_tube_error ; All zero (null buffer): error |
| 9950 | LDA econet_data_continue_frame ; Read extra trailing byte from FIFO |
| 9953 | STA rx_extra_byte ; Save extra byte at &0D5D for later use |
| 9956 | LDA #&20 ; Bit5 = extra data byte available flag |
| 9958 | ORA tx_flags ; Set extra byte flag in tx_flags |
| 995B | STA tx_flags ; Store updated flags |
| fall through ↓ | |
ACK transmissionSends a scout ACK or final ACK frame as part of the four-way handshake. If bit7 of &0D4A is set, this is a final ACK -> completion (&9F39). Otherwise, configures for TX (CR1=&44, CR2=&A7) and sends the ACK frame (dst_stn, dst_net from &0D3D, src_stn from &FE18, src_net=0). The ACK frame has no data payload -- just address bytes. After writing the address bytes to the TX FIFO, installs the next NMI handler from &0D4B/&0D4C (saved by the scout/data RX handler) and sends TX_LAST_DATA (CR2=&3F) to close the frame. |
|
| 995E | .ack_tx←2← 98F4 JMP← 9944 BPL |
| LDA tx_flags ; Load TX flags to check ACK type | |
| 9961 | BPL ack_tx_configure ; Bit7 clear: normal scout ACK |
| 9963 | JMP tx_result_ok ; Jump to TX success result |
| 9966 | .ack_tx_configure←1← 9961 BPL |
| LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode) | |
| 9968 | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 996B | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE |
| 996D | STA econet_control23_or_status2 ; Write CR2: enable TX with status clear |
| 9970 | LDA #&bb ; Install saved next handler (&99BB for scout ACK) |
| 9972 | LDY #&99 ; High byte of post-ACK handler |
| 9974 | .ack_tx_write_dest←2← 982A JMP← 9B0F JMP |
| STA nmi_next_lo ; Store next handler low byte | |
| 9977 | STY nmi_next_hi ; Store next handler high byte |
| 997A | LDA rx_src_stn ; Load dest station from RX scout buffer |
| 997D | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9980 | BVC tdra_error ; TDRA not ready -- error |
| 9982 | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 9985 | LDA rx_src_net ; Write dest network to TX FIFO |
| 9988 | STA econet_data_continue_frame ; Write dest net byte to FIFO |
| 998B | LDA #&92 ; Install handler at &9992 (write src addr) |
| 998D | LDY #&99 ; High byte of nmi_ack_tx_src |
| 998F | JMP set_nmi_vector ; Set NMI vector to ack_tx_src handler |
ACK TX continuationWrites source station and network to TX FIFO, completing the 4-byte ACK frame. Then sends TX_LAST_DATA (CR2=&3F) to close the frame. |
|
| 9992 | .nmi_ack_tx_src |
| LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF) | |
| 9995 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA |
| 9998 | BVC tdra_error ; TDRA not ready -- error |
| 999A | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 999D | LDA #0 ; Write network=0 to TX FIFO |
| 999F | STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO |
| 99A2 | LDA tx_flags ; Check tx_flags for data phase |
| 99A5 | BMI start_data_tx ; bit7 set: start data TX phase |
| 99A7 | LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE |
| 99A9 | STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX |
| 99AC | LDA nmi_next_lo ; Install saved handler from &0D4B/&0D4C |
| 99AF | LDY nmi_next_hi ; Load saved next handler high byte |
| 99B2 | JMP set_nmi_vector ; Install next NMI handler |
| 99B5 | .start_data_tx←1← 99A5 BMI |
| JMP data_tx_begin ; Jump to start data TX phase | |
| 99B8 | .tdra_error←2← 9980 BVC← 9998 BVC |
| JMP rx_error ; TDRA error: jump to error handler | |
Post-ACK scout processingCalled after the scout ACK has been transmitted. Processes the received scout data stored in the buffer at &0D3D-&0D48. Checks the port byte (&0D40) against open receive blocks to find a matching listener. If a match is found, sets up the data RX handler chain for the four-way handshake data phase. If no match, discards the frame. |
|
| 99BB | .post_ack_scout |
| LDA rx_port ; Check port byte from scout | |
| 99BE | BNE advance_rx_buffer_ptr ; Non-zero port: advance RX buffer |
| 99C0 | .dispatch_nmi_error |
| JMP check_imm_op_ctrl ; Jump to error handler | |
Advance RX buffer pointer after transferAdds the transfer count to the RXCB buffer pointer (4-byte addition). If a Tube transfer is active, re-claims the Tube address and sends the extra RX byte via R3, incrementing the Tube pointer by 1. |
|
| 99C3 | .advance_rx_buffer_ptr←1← 99BE BNE |
| LDA #2 ; A=2: test bit1 of tx_flags | |
| 99C5 | BIT tx_flags ; BIT tx_flags: check data transfer bit |
| 99C8 | BEQ add_buf_to_base ; Bit1 clear: no transfer -- return |
| 99CA | CLC ; CLC: init carry for 4-byte add |
| 99CB | PHP ; Save carry on stack for loop |
| 99CC | LDY #8 ; Y=8: RXCB high pointer offset |
| 99CE | .add_rxcb_ptr←1← 99DA BCC |
| LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte) | |
| 99D0 | PLP ; Restore carry from stack |
| 99D1 | ADC net_tx_ptr,y ; Add transfer count byte |
| 99D4 | STA (port_ws_offset),y ; Store updated pointer back to RXCB |
| 99D6 | INY ; Next byte |
| 99D7 | PHP ; Save carry for next iteration |
| 99D8 | CPY #&0c ; Done 4 bytes? (Y reaches &0C) |
| 99DA | BCC add_rxcb_ptr ; No: continue adding |
| 99DC | PLP ; Discard final carry |
| 99DD | LDA #&20 ; A=&20: test bit5 of tx_flags |
| 99DF | BIT tx_flags ; BIT tx_flags: check Tube bit |
| 99E2 | BEQ jmp_store_rxcb ; No Tube: skip Tube update |
| 99E4 | TXA ; Save X on stack |
| 99E5 | PHA ; Push X |
| 99E6 | LDA #8 ; A=8: offset for Tube address |
| 99E8 | CLC ; CLC for address calculation |
| 99E9 | ADC port_ws_offset ; Add workspace base offset |
| 99EB | TAX ; X = address low for Tube claim |
| 99EC | LDY rx_buf_offset ; Y = address high for Tube claim |
| 99EE | LDA #1 ; A=1: Tube claim type (read) |
| 99F0 | JSR tube_addr_claim ; Claim Tube address for transfer |
| 99F3 | LDA rx_extra_byte ; Load extra RX data byte |
| 99F6 | STA tube_data_register_3 ; Send to Tube via R3 |
| 99F9 | PLA ; Restore X from stack |
| 99FA | TAX ; Transfer to X register |
| 99FB | LDY #8 ; Y=8: RXCB buffer ptr offset |
| 99FD | LDA (port_ws_offset),y ; Load current RXCB buffer ptr lo |
| 99FF | SEC ; SEC for ADC #0 = add carry |
| 9A00 | ADC #0 ; Increment by 1 (Tube extra byte) |
| 9A02 | STA (port_ws_offset),y ; Store updated ptr back to RXCB |
| 9A04 | .jmp_store_rxcb←1← 99E2 BEQ |
| JMP store_rxcb_completion ; Other port-0 ops: immediate dispatch | |
| 9A07 | .add_buf_to_base←1← 99C8 BEQ |
| LDA port_buf_len ; Load buffer bytes remaining | |
| 9A09 | CLC ; CLC for address add |
| 9A0A | ADC open_port_buf ; Add to buffer base address |
| 9A0C | BCC store_buf_ptr_lo ; No carry: skip high byte increment |
| 9A0E | .inc_rxcb_buf_hi←1← 9A87 LDA |
| INC open_port_buf_hi ; Carry: increment buffer high byte | |
| 9A10 | .store_buf_ptr_lo←1← 9A0C BCC |
| LDY #8 ; Y=8: store updated buffer position | |
| 9A12 | .store_rxcb_buf_ptr |
| STA (port_ws_offset),y ; Store updated low byte to RXCB | |
| 9A14 | INY ; Y=9: buffer high byte offset |
| 9A15 | .load_rxcb_buf_hi |
| LDA open_port_buf_hi ; Load updated buffer high byte | |
| 9A17 | .store_rxcb_buf_hi |
| STA (port_ws_offset),y ; Store high byte to RXCB | |
| fall through ↓ | |
Store RXCB completion fields from scout bufferWrites source network, source station, port, and control byte from the scout buffer into the active RXCB. Sets bit 7 of the control byte to mark reception complete. |
|
| 9A19 | .store_rxcb_completion←2← 9A04 JMP← 9A56 JMP |
| LDA rx_src_net ; Load source network from scout buffer | |
| 9A1C | LDY #3 ; Y=3: RXCB source network offset |
| 9A1E | STA (port_ws_offset),y ; Store source network to RXCB |
| 9A20 | DEY ; Y=2: source station offset Y=&02 |
| 9A21 | LDA rx_src_stn ; Load source station from scout buffer |
| 9A24 | STA (port_ws_offset),y ; Store source station to RXCB |
| 9A26 | DEY ; Y=1: port byte offset Y=&01 |
| 9A27 | LDA rx_port ; Load port byte |
| 9A2A | STA (port_ws_offset),y ; Store port to RXCB |
| 9A2C | DEY ; Y=0: control/flag byte offset Y=&00 |
| 9A2D | LDA rx_ctrl ; Load control byte from scout |
| 9A30 | ORA #&80 ; Set bit7 = reception complete flag |
| 9A32 | STA (port_ws_offset),y ; Store to RXCB (marks CB as complete) |
| fall through ↓ | |
Discard with full ADLC resetPerforms adlc_full_reset (CR1=&C1, reset both TX and RX sections), then falls through to discard_after_reset. Used when the ADLC is in an unexpected state and needs a hard reset before returning to idle listen mode. 5 references — the main error recovery path. |
|
| 9A34 | .discard_reset_listen←4← 9897 JMP← 9B4F JMP← 9E93 JMP← 9F48 JMP |
| LDA #2 ; Tube flag bit 1 AND tx_flags bit 1 | |
| 9A36 | BIT tx_flags ; Test tx_flags for Tube transfer |
| 9A39 | BEQ discard_listen ; No Tube transfer active -- skip release |
| 9A3B | LDA #&82 ; A=&82: Tube release claim type |
| 9A3D | JSR tube_addr_claim ; Release Tube claim before discarding |
| fall through ↓ | |
Discard frame (gentle)Sends RX_DISCONTINUE (CR1=&A2: RIE|RX_DISCONTINUE) to abort the current frame reception without a full reset, then falls through to discard_after_reset. Used for clean rejection of frames that are correctly formatted but not for us (wrong station/network). |
|
| 9A40 | .discard_listen←2← 9744 JMP← 9A39 BEQ |
| JSR adlc_rx_listen ; Re-enter idle RX listen mode | |
| fall through ↓ | |
Return to idle listen after reset/discardJust calls adlc_rx_listen (CR1=&82, CR2=&67) to re-enter idle RX mode, then RTI. The simplest of the three discard paths — used as the tail of both discard_reset_listen and discard_listen. |
|
| 9A43 | .discard_after_reset←2← 9728 JMP← 9741 JMP |
| .install_rx_scout_handler←2← 9728 JMP← 9741 JMP | |
| LDA #&f6 ; Install nmi_rx_scout (&96F6) as NMI handler | |
| 9A45 | LDY #&96 ; High byte of nmi_rx_scout |
| 9A47 | JMP set_nmi_vector ; Set NMI vector and return |
| 9A4A | .copy_scout_fields←1← 97F8 JMP |
| LDY #4 ; Y=4: start at RX CB offset 4 | |
| 9A4C | .copy_scout_loop←1← 9A54 BNE |
| LDA rx_src_stn,y ; Load scout field (stn/net/ctrl/port) | |
| 9A4F | STA (port_ws_offset),y ; Store to port workspace buffer |
| 9A51 | INY ; Advance buffer pointer |
| 9A52 | CPY #&0c ; All 8 fields copied? |
| 9A54 | BNE copy_scout_loop ; No: continue copy loop |
| 9A56 | JMP store_rxcb_completion ; Jump to completion handler |
Immediate operation handler (port = 0)Handles immediate (non-data-transfer) operations received via scout frames with port byte = 0. The control byte (&0D3F) determines the operation type: &81 = PEEK (read memory) &82 = POKE (write memory) &83 = JSR (remote procedure call) &84 = user procedure &85 = OS procedure &86 = HALT &87 = CONTINUE The protection mask (LSTAT at &D63) controls which operations are permitted — each bit enables or disables an operation type. If the operation is not permitted by the mask, it is silently ignored. LSTAT can be read/set via OSWORD &12 sub-functions 4/5. |
|
| 9A59 | .immediate_op←1← 9794 JMP |
| LDY rx_ctrl ; Control byte &81-&88 range check | |
| 9A5C | CPY #&81 ; Below &81: not an immediate op |
| 9A5E | BCC imm_op_out_of_range ; Out of range low: jump to discard |
| 9A60 | CPY #&89 ; Above &88: not an immediate op |
| 9A62 | BCS imm_op_out_of_range ; Out of range high: jump to discard |
| 9A64 | CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection |
| 9A66 | BCS imm_op_dispatch ; Ctrl >= &87: dispatch without mask check |
| 9A68 | LDA rx_src_stn ; Load source station number |
| 9A6B | CMP #&f0 ; Station >= &F0? (privileged) |
| 9A6D | BCS imm_op_dispatch ; Privileged: skip protection check |
| 9A6F | TYA ; Convert ctrl byte to 0-based index for mask |
| 9A70 | SEC ; SEC for subtract |
| 9A71 | SBC #&81 ; A = ctrl - &81 (0-based operation index) |
| 9A73 | TAY ; Y = index for mask rotation count |
| 9A74 | LDA prot_status ; Load protection mask from LSTAT |
| 9A77 | .rotate_prot_mask←1← 9A79 BPL |
| ROR ; Rotate mask right by control byte index | |
| 9A78 | DEY ; Decrement rotation counter |
| 9A79 | BPL rotate_prot_mask ; Loop until bit aligned |
| 9A7B | BCC imm_op_dispatch ; Carry clear: operation permitted |
| 9A7D | JMP imm_op_discard ; Operation blocked by LSTAT mask |
| 9A80 | .imm_op_dispatch←3← 9A66 BCS← 9A6D BCS← 9A7B BCC |
| LDY rx_ctrl ; Reload ctrl byte for dispatch table | |
| 9A83 | LDA rxcb_buf_hi_operand,y ; Look up handler address high byte |
| 9A86 | PHA ; Push handler address high |
| 9A87 | LDA inc_rxcb_buf_hi,y ; Load handler low byte from jump table |
| 9A8A | PHA ; Push handler address low |
| 9A8B | RTS ; RTS dispatches to handler |
| 9A8C | .imm_op_out_of_range←2← 9A5E BCC← 9A62 BCS |
| JMP rx_error ; Jump to discard handler | |
| 9A8F | EQUB <(rx_imm_peek-1) |
| 9A90 | EQUB <(rx_imm_poke-1) |
| 9A91 | EQUB <(rx_imm_exec-1) |
| 9A92 | EQUB <(rx_imm_exec-1) |
| 9A93 | EQUB <(rx_imm_exec-1) |
| 9A94 | EQUB <(rx_imm_halt_cont-1) |
| 9A95 | EQUB <(rx_imm_halt_cont-1) |
| 9A96 | EQUB <(rx_imm_machine_type-1) |
| 9A97 | EQUB >(rx_imm_peek-1) |
| 9A98 | EQUB >(rx_imm_poke-1) |
| 9A99 | EQUB >(rx_imm_exec-1) |
| 9A9A | EQUB >(rx_imm_exec-1) |
| 9A9B | EQUB >(rx_imm_exec-1) |
| 9A9C | EQUB >(rx_imm_halt_cont-1) |
| 9A9D | EQUB >(rx_imm_halt_cont-1) |
| 9A9E | EQUB >(rx_imm_machine_type-1) |
RX immediate: JSR/UserProc/OSProc setupSets up the port buffer to receive remote procedure data. Copies the 4-byte remote address from rx_remote_addr into the execution address workspace, then jumps to the common receive path. Used for operation types &83-&85. |
|
| 9A9F | .rx_imm_exec |
| LDA #0 ; Buffer start lo = &00 | |
| 9AA1 | STA open_port_buf ; Set port buffer lo |
| 9AA3 | LDA #&82 ; Buffer length lo = &82 |
| 9AA5 | STA port_buf_len ; Set buffer length lo |
| 9AA7 | LDA #1 ; Buffer length hi = 1 |
| 9AA9 | STA port_buf_len_hi ; Set buffer length hi |
| 9AAB | LDA net_rx_ptr_hi ; Load RX page hi for buffer |
| 9AAD | STA open_port_buf_hi ; Set port buffer hi |
| 9AAF | LDY #3 ; Y=3: copy 4 bytes (3 down to 0) |
| 9AB1 | .copy_addr_loop←1← 9AB8 BPL |
| LDA rx_remote_addr,y ; Load remote address byte | |
| 9AB4 | STA l0d58,y ; Store to exec address workspace |
| 9AB7 | DEY ; Next byte (descending) |
| 9AB8 | BPL copy_addr_loop ; Loop until all 4 bytes copied |
| 9ABA | JMP send_data_rx_ack ; Enter common data-receive path |
RX immediate: POKE setupSets up workspace offsets for receiving POKE data, then jumps to the common data-receive path. |
|
| 9ABD | .rx_imm_poke |
| LDA #&3d ; Port workspace offset = &3D | |
| 9ABF | STA port_ws_offset ; Store workspace offset lo |
| 9AC1 | LDA #&0d ; RX buffer page = &0D |
| 9AC3 | STA rx_buf_offset ; Store workspace offset hi |
| 9AC5 | JMP ack_scout_match ; Enter POKE data-receive path |
RX immediate: machine type querySets up a buffer in high memory (length #&01FC) for the machine type query response. Returns system identification data to the remote station. |
|
| 9AC8 | .rx_imm_machine_type |
| LDA #1 ; Buffer length hi = 1 | |
| 9ACA | STA port_buf_len_hi ; Set buffer length hi |
| 9ACC | LDA #&fc ; Buffer length lo = &FC |
| 9ACE | STA port_buf_len ; Set buffer length lo |
| 9AD0 | LDA #&21 ; Buffer start lo = &25 |
| 9AD2 | STA open_port_buf ; Set port buffer lo |
| 9AD4 | LDA #&7f ; Buffer hi = &7F (below screen) |
| 9AD6 | STA open_port_buf_hi ; Set port buffer hi |
| 9AD8 | JMP set_tx_reply_flag ; Enter reply build path |
RX immediate: PEEK setupSaves the current TX block pointer, replaces it with a pointer to the workspace, and prepares to send the PEEK response data back to the requesting station. |
|
| 9ADB | .rx_imm_peek |
| LDA nmi_tx_block ; Save current TX block low byte | |
| 9ADD | PHA ; Push to stack |
| 9ADE | LDA nmi_tx_block_hi ; Save current TX block high byte |
| 9AE0 | PHA ; Push to stack |
| fall through ↓ | |
RX immediate: PEEK setupWrites &0D3D to port_ws_offset/rx_buf_offset, sets scout_status=2, then calls tx_calc_transfer to send the PEEK response data back to the requesting station. Uses workspace offsets (&A6/&A7) for nmi_tx_block. |
|
| 9AE1 | .rx_imm_peek_setup |
| LDA #&3d ; Port workspace offset = &3D | |
| 9AE3 | STA nmi_tx_block ; Store workspace offset lo |
| 9AE5 | LDA #&0d ; RX buffer page = &0D |
| 9AE7 | STA nmi_tx_block_hi ; Store workspace offset hi |
| 9AE9 | LDA #2 ; Scout status = 2 (PEEK response) |
| 9AEB | STA scout_status ; Store scout status |
| 9AEE | JSR tx_calc_transfer ; Calculate transfer size for response |
| 9AF1 | PLA ; Restore saved nmi_tx_block_hi |
| 9AF2 | STA nmi_tx_block_hi ; Restore workspace ptr hi byte |
| 9AF4 | PLA ; Restore saved nmi_tx_block |
| 9AF5 | STA nmi_tx_block ; Restore workspace ptr lo byte |
| 9AF7 | BCC imm_op_discard ; C=0: transfer not set up, discard |
| 9AF9 | .set_tx_reply_flag←1← 9AD8 JMP |
| LDA tx_flags ; Mark TX flags bit 7 (reply pending) | |
| 9AFC | ORA #&80 ; Set reply pending flag |
| 9AFE | STA tx_flags ; Store updated TX flags |
| 9B01 | .rx_imm_halt_cont |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9B03 | STA econet_control1_or_status1 ; Write CR1: enable TX interrupts |
| 9B06 | .tx_cr2_setup |
| LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE | |
| 9B08 | STA econet_control23_or_status2 ; Write CR2 for TX setup |
| 9B0B | .tx_nmi_setup |
| LDA #&2f ; NMI handler lo byte (self-modifying) | |
| 9B0D | .tx_nmi_dispatch_page |
| LDY #&9b ; Y=&9B: dispatch table page | |
| 9B0F | JMP ack_tx_write_dest ; Acknowledge and write TX dest |
Check control byte for immediate operation typeLoads the RX control byte and compares against &82 (immediate HALT). If HALT, discards the frame via imm_op_discard. Otherwise falls through to imm_op_build_reply. |
|
| 9B12 | .check_imm_op_ctrl←1← 99C0 JMP |
| LDY rx_ctrl ; Load RX control byte | |
| 9B15 | CPY #&82 ; Compare against &82 (HALT) |
| 9B17 | BEQ imm_op_discard ; HALT: discard frame |
| fall through ↓ | |
Build immediate operation reply headerStores data length, source station/network, and control byte into the RX buffer header area for port-0 immediate operations. Then disables SR interrupts and configures the VIA shift register for shift-in mode before returning to idle listen. |
|
| 9B19 | .imm_op_build_reply |
| LDA port_buf_len ; Get buffer position for reply header | |
| 9B1B | CLC ; Clear carry for offset addition |
| 9B1C | ADC #&80 ; Data offset = buf_len + &80 (past header) |
| 9B1E | LDY #&7f ; Y=&7F: reply data length slot |
| 9B20 | STA (net_rx_ptr),y ; Store reply data length in RX buffer |
| 9B22 | LDY #&80 ; Y=&80: source station slot |
| 9B24 | LDA rx_src_stn ; Load requesting station number |
| 9B27 | STA (net_rx_ptr),y ; Store source station in reply header |
| 9B29 | INY ; Y=&81 |
| 9B2A | LDA rx_src_net ; Load requesting network number |
| 9B2D | STA (net_rx_ptr),y ; Store source network in reply header |
| 9B2F | LDA rx_ctrl ; Load control byte from received frame |
| 9B32 | STA tx_work_57 ; Save ctrl byte for TX response |
| 9B35 | LDA #&84 ; IER bit 2: disable SR interrupt |
| 9B37 | STA system_via_ier ; Write IER to disable SR |
| 9B3A | LDA system_via_acr ; Read ACR for shift register config |
| 9B3D | AND #&1c ; Isolate shift register mode bits (2-4) |
| 9B3F | STA tx_work_51 ; Save original SR mode for later restore |
| 9B42 | LDA system_via_acr ; Reload ACR for modification |
| 9B45 | AND #&e3 ; Clear SR mode bits (keep other bits) |
| 9B47 | ORA #8 ; SR mode 2: shift in under φ2 |
| 9B49 | STA system_via_acr ; Apply new shift register mode |
| 9B4C | BIT system_via_sr ; Read SR to clear pending interrupt |
| 9B4F | .imm_op_discard←3← 9A7D JMP← 9AF7 BCC← 9B17 BEQ |
| JMP discard_reset_listen ; Return to idle listen mode | |
| 9B52 | .check_sr_irq←1← 966C JMP |
| LDA #4 ; A=&04: IFR bit 2 (SR) mask | |
| 9B54 | BIT system_via_ifr ; Test SR interrupt pending |
| 9B57 | BNE tx_done_error ; SR fired: handle TX completion |
| 9B59 | LDA #5 ; A=5: no SR, return status 5 |
| 9B5B | RTS ; Return (no SR interrupt) |
| 9B5C | .tx_done_error←1← 9B57 BNE |
| TXA ; Save X | |
| 9B5D | PHA ; Push X |
| 9B5E | TYA ; Save Y |
| 9B5F | PHA ; Push Y |
| 9B60 | LDA system_via_acr ; Read ACR for shift register mode |
| 9B63 | AND #&e3 ; Clear SR mode bits (2-4) |
| 9B65 | ORA tx_work_51 ; Restore original SR mode |
| 9B68 | STA system_via_acr ; Write updated ACR |
| 9B6B | LDA system_via_sr ; Read SR to clear pending interrupt |
| 9B6E | LDA #4 ; A=&04: SR bit mask |
| 9B70 | STA system_via_ifr ; Clear SR in IFR |
| 9B73 | STA system_via_ier ; Disable SR in IER |
| 9B76 | LDY tx_work_57 ; Load ctrl byte for dispatch |
| 9B79 | CPY #&86 ; Ctrl >= &86? (HALT/CONTINUE) |
| 9B7B | BCS tx_done_classify ; Yes: skip protection mask save |
| 9B7D | LDA prot_status ; Load current protection mask |
| 9B80 | STA rx_ctrl_copy ; Save mask before JSR modification |
| 9B83 | ORA #&1c ; Enable bits 2-4 (allow JSR ops) |
| 9B85 | STA prot_status ; Store modified protection mask |
| 9B88 | .tx_done_classify←1← 9B7B BCS |
| LDA rx_ctrl_operand,y ; Load handler addr hi from table | |
| 9B8B | PHA ; Push handler hi |
| 9B8C | LDA tx_dispatch_page_operand,y ; Load handler addr lo from table |
| 9B8F | PHA ; Push handler lo |
| 9B90 | RTS ; Dispatch via RTS (addr-1 on stack) |
| 9B91 | EQUB <(tx_done_jsr-1) |
| 9B92 | EQUB <(tx_done_user_proc-1) |
| 9B93 | EQUB <(tx_done_os_proc-1) |
| 9B94 | EQUB <(tx_done_halt-1) |
| 9B95 | EQUB <(tx_done_continue-1) |
| 9B96 | EQUB >(tx_done_jsr-1) |
| 9B97 | EQUB >(tx_done_user_proc-1) |
| 9B98 | EQUB >(tx_done_os_proc-1) |
| 9B99 | EQUB >(tx_done_halt-1) |
| 9B9A | EQUB >(tx_done_continue-1) |
TX done: remote JSR executionPushes a return address on the stack (pointing to tx_done_exit), then does JMP indirect to call the remote JSR target routine. When that routine returns via RTS, control resumes at tx_done_exit. |
|
| 9B9B | .tx_done_jsr |
| LDA #&9b ; Push hi of (tx_done_exit-1) | |
| 9B9D | PHA ; Push hi byte on stack |
| 9B9E | LDA #&dc ; Push lo of (tx_done_exit-1) |
| 9BA0 | PHA ; Push lo byte on stack |
| 9BA1 | JMP (l0d58) ; Call remote JSR; RTS to tx_done_exit |
TX done: UserProc eventGenerates a network event (event 8) via OSEVEN with the remote address. This notifies the user program that a UserProc operation has completed. |
|
| 9BA4 | .tx_done_user_proc |
| LDY #event_network_error ; Y=8: network event type | |
| 9BA6 | LDX l0d58 ; X = remote address lo |
| 9BA9 | LDA l0d59 ; A = remote address hi |
| 9BAC | JSR oseven ; Generate event Y='Network error' |
| 9BAF | JMP tx_done_exit ; Exit TX done handler |
TX done: OSProc callCalls the ROM entry point at &8000 (rom_header) with X/Y from the remote address workspace. This invokes an OS-level procedure on behalf of the remote station. |
|
| 9BB2 | .tx_done_os_proc |
| LDX l0d58 ; X = remote address lo | |
| 9BB5 | LDY l0d59 ; Y = remote address hi |
| 9BB8 | JSR rom_header ; Call ROM entry point at &8000 |
| 9BBB | JMP tx_done_exit ; Exit TX done handler |
TX done: HALTSets bit 2 of rx_flags, enables interrupts, and spin-waits until bit 2 is cleared (by a CONTINUE from the remote station). If bit 2 is already set, skips to exit. |
|
| 9BBE | .tx_done_halt |
| LDA #4 ; A=&04: bit 2 mask for rx_flags | |
| 9BC0 | BIT rx_status_flags ; Test if already halted |
| 9BC3 | BNE tx_done_exit ; Already halted: skip to exit |
| 9BC5 | ORA rx_status_flags ; Set bit 2 in rx_flags |
| 9BC8 | STA rx_status_flags ; Store halt flag |
| 9BCB | LDA #4 ; A=4: re-load halt bit mask |
| 9BCD | CLI ; Enable interrupts during halt wait |
| 9BCE | .halt_spin_loop←1← 9BD1 BNE |
| BIT rx_status_flags ; Test halt flag | |
| 9BD1 | BNE halt_spin_loop ; Still halted: keep spinning |
| 9BD3 | BEQ tx_done_exit ; ALWAYS branch |
TX done: CONTINUEClears bit 2 of rx_flags, releasing any station that is halted and spinning in tx_done_halt. |
|
| 9BD5 | .tx_done_continue |
| LDA rx_status_flags ; Load current RX flags | |
| 9BD8 | AND #&fb ; Clear bit 2: release halted station |
| 9BDA | STA rx_status_flags ; Store updated flags |
| 9BDD | .tx_done_exit←4← 9BAF JMP← 9BBB JMP← 9BC3 BNE← 9BD3 BEQ |
| PLA ; Restore Y from stack | |
| 9BDE | TAY ; Transfer to Y register |
| 9BDF | PLA ; Restore X from stack |
| 9BE0 | TAX ; Transfer to X register |
| 9BE1 | LDA #0 ; A=0: success status |
| 9BE3 | RTS ; Return with A=0 (success) |
Begin TX operationMain TX initiation entry point (called via trampoline at &06CE). Copies dest station/network from the TXCB to the scout buffer, dispatches to immediate op setup (ctrl >= &81) or normal data transfer, calculates transfer sizes, copies extra parameters, then enters the INACTIVE polling loop. |
|
| 9BE4 | .tx_begin←1← 9660 JMP |
| TXA ; Save X on stack | |
| 9BE5 | PHA ; Push X |
| 9BE6 | LDY #2 ; Y=2: TXCB offset for dest station |
| 9BE8 | LDA (nmi_tx_block),y ; Load dest station from TX control block |
| 9BEA | STA tx_dst_stn ; Store to TX scout buffer |
| 9BED | INY ; Y=&03 |
| 9BEE | LDA (nmi_tx_block),y ; Load dest network from TX control block |
| 9BF0 | STA tx_dst_net ; Store to TX scout buffer |
| 9BF3 | LDY #0 ; Y=0: first byte of TX control block |
| 9BF5 | LDA (nmi_tx_block),y ; Load control/flag byte |
| 9BF7 | BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte |
| 9BF9 | JMP tx_active_start ; Bit7 clear: normal data transfer |
| 9BFC | .tx_imm_op_setup←1← 9BF7 BMI |
| STA tx_ctrl_byte ; Store control byte to TX scout buffer | |
| 9BFF | TAX ; X = control byte for range checks |
| 9C00 | INY ; Y=1: port byte offset |
| 9C01 | LDA (nmi_tx_block),y ; Load port byte from TX control block |
| 9C03 | STA tx_port ; Store port byte to TX scout buffer |
| 9C06 | BNE tx_line_idle_check ; Port != 0: skip immediate op setup |
| 9C08 | CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc |
| 9C0A | BCS check_imm_range ; Ctrl >= &83: skip to range check |
| 9C0C | SEC ; SEC: init borrow for 4-byte subtract |
| 9C0D | PHP ; Save carry on stack for loop |
| 9C0E | LDY #8 ; Y=8: high pointer offset in TXCB |
| 9C10 | .calc_peek_poke_size←1← 9C24 BCC |
| LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte) | |
| 9C12 | DEY ; Y -= 4: back to start addr offset |
| 9C13 | DEY ; (continued) |
| 9C14 | DEY ; (continued) |
| 9C15 | DEY ; (continued) |
| 9C16 | PLP ; Restore borrow from stack |
| 9C17 | SBC (nmi_tx_block),y ; end - start = transfer size byte |
| 9C19 | STA tx_data_start,y ; Store result to tx_data_start |
| 9C1C | INY ; Y += 5: advance to next end byte |
| 9C1D | INY ; (continued) |
| 9C1E | INY ; (continued) |
| 9C1F | INY ; (continued) |
| 9C20 | INY ; (continued) |
| 9C21 | PHP ; Save borrow for next byte |
| 9C22 | CPY #&0c ; Done all 4 bytes? (Y reaches &0C) |
| 9C24 | BCC calc_peek_poke_size ; No: next byte pair |
| 9C26 | PLP ; Discard final borrow |
| 9C27 | .check_imm_range←1← 9C0A BCS |
| CPX #&89 ; Ctrl >= &89: out of immediate range | |
| 9C29 | BCS tx_active_start ; Above range: normal data transfer |
| 9C2B | LDY #&0c ; Y=&0C: start of extra data in TXCB |
| 9C2D | .copy_imm_params←1← 9C35 BCC |
| LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB | |
| 9C2F | STA nmi_shim_1a,y ; Copy to NMI shim workspace at &0D1A+Y |
| 9C32 | INY ; Next byte |
| 9C33 | CPY #&10 ; Done 4 bytes? (Y reaches &10) |
| 9C35 | BCC copy_imm_params ; No: continue copying |
| 9C37 | .tx_line_idle_check←1← 9C06 BNE |
| LDA #&20 ; A=&20: mask for SR2 INACTIVE bit | |
| 9C39 | BIT econet_control23_or_status2 ; BIT SR2: test if line is idle |
| 9C3C | BNE tx_no_clock_error ; Line not idle: handle as line jammed |
| 9C3E | LDA #&fd ; A=&FD: high byte of timeout counter |
| 9C40 | PHA ; Push timeout high byte to stack |
| 9C41 | LDA #6 ; Scout frame = 6 address+ctrl bytes |
| 9C43 | STA tx_length ; Store scout frame length |
| 9C46 | LDA #0 ; A=0: init low byte of timeout counter |
| fall through ↓ | |
INACTIVE polling loopPolls SR2 for INACTIVE (bit2) to confirm the network line is idle before attempting transmission. Uses a 3-byte timeout counter on the stack. The timeout (~256^3 iterations) generates "Line Jammed" if INACTIVE never appears. The CTS check at &9C66-&9C6B works because CR2=&67 has RTS=0, so cts_input_ is always true, and SR1_CTS reflects presence of clock hardware. |
|
| 9C48 | .inactive_poll |
| STA tx_index ; Save TX index | |
| 9C4B | PHA ; Push timeout byte 1 on stack |
| 9C4C | PHA ; Push timeout byte 2 on stack |
| 9C4D | LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE| PSE) |
| 9C4F | .test_inactive_retry←3← 9C75 BNE← 9C7A BNE← 9C7F BNE |
| LDA #4 ; A=&04: INACTIVE mask for SR2 bit2 | |
| 9C51 | PHP ; Save interrupt state |
| 9C52 | SEI ; Disable interrupts for ADLC access |
| fall through ↓ | |
Disable NMIs and test INACTIVEMid-instruction label within the INACTIVE polling loop. The operand byte of the LDA before tx_begin is referenced as a constant for self-modifying code. Disables NMIs twice (belt-and-braces) then tests SR2 for INACTIVE before proceeding with TX. |
|
| 9C53 | .intoff_test_inactive←1← 9CCF LDA |
| BIT station_id_disable_net_nmis ; INTOFF -- disable NMIs | |
| 9C56 | BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces) |
| 9C59 | .test_line_idle |
| BIT econet_control23_or_status2 ; BIT SR2: Z = &04 AND SR2 -- tests INACTIVE | |
| 9C5C | BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop |
| 9C5E | LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt) |
| 9C61 | LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE |
| 9C63 | STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX |
| 9C66 | LDA #&10 ; A=&10: CTS mask for SR1 bit4 |
| 9C68 | BIT econet_control1_or_status1 ; BIT SR1: tests CTS present |
| 9C6B | BNE tx_prepare ; CTS set -- clock hardware detected, start TX |
| 9C6D | .inactive_retry←1← 9C5C BEQ |
| BIT video_ula_control ; INTON -- re-enable NMIs (&FE20 read) | |
| 9C70 | PLP ; Restore interrupt state |
| 9C71 | TSX ; 3-byte timeout counter on stack |
| 9C72 | INC l0101,x ; Increment timeout counter byte 1 |
| 9C75 | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C77 | INC l0102,x ; Increment timeout counter byte 2 |
| 9C7A | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C7C | INC l0103,x ; Increment timeout counter byte 3 |
| 9C7F | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C81 | JMP tx_line_jammed ; All 3 bytes overflowed: line jammed |
| ; TX_ACTIVE branch (A=&44 = CR1 value for TX active) | |
| 9C84 | .tx_active_start←2← 9BF9 JMP← 9C29 BCS |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9C86 | BNE store_tx_error ; ALWAYS branch |
TX timeout error handler (Line Jammed)Writes CR2=&07 to abort TX, cleans 3 bytes from stack (the timeout loop's state), then stores error code &40 ("Line Jammed") into the TX control block and signals completion. |
|
| 9C88 | .tx_line_jammed←1← 9C81 JMP |
| LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX) | |
| 9C8A | STA econet_control23_or_status2 ; Write CR2 to abort TX |
| 9C8D | PLA ; Clean 3 bytes of timeout loop state |
| 9C8E | PLA ; Pop saved register |
| 9C8F | PLA ; Pop saved register |
| 9C90 | LDA #&40 ; Error &40 = 'Line Jammed' |
| 9C92 | BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch |
| 9C94 | .tx_no_clock_error←1← 9C3C BNE |
| LDA #&43 ; Error &43 = 'No Clock' | |
| 9C96 | .store_tx_error←2← 9C86 BNE← 9C92 BNE |
| LDY #0 ; Offset 0 = error byte in TX control block | |
| 9C98 | STA (nmi_tx_block),y ; Store error code in TX CB byte 0 |
| 9C9A | LDA #&80 ; &80 = TX complete flag |
| 9C9C | STA tx_ctrl_status ; Signal TX operation complete |
| 9C9F | PLA ; Restore X saved by caller |
| 9CA0 | TAX ; Move to X register |
| 9CA1 | RTS ; Return to TX caller |
TX preparationConfigures ADLC for transmission: asserts RTS via CR2, enables TIE via CR1, installs NMI TX handler at &9D4C, and re-enables NMIs. |
|
| 9CA2 | .tx_prepare←1← 9C6B BNE |
| STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE) | |
| 9CA5 | LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled) |
| 9CA7 | STX econet_control1_or_status1 ; Write to ADLC CR1 |
| 9CAA | LDX #&4c ; Install NMI handler at &9D4C (TX data handler) |
| 9CAC | LDY #&9d ; High byte of NMI handler address |
| 9CAE | STX nmi_jmp_lo ; Write NMI vector low byte directly |
| 9CB1 | STY nmi_jmp_hi ; Write NMI vector high byte directly |
| 9CB4 | BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read) |
| 9CB7 | LDA tx_port ; Load destination port number |
| 9CBA | BNE setup_data_xfer ; Port != 0: standard data transfer |
| 9CBC | LDY tx_ctrl_byte ; Port 0: load control byte for table lookup |
| 9CBF | LDA tube_tx_byte4_operand,y ; Look up tx_flags from table |
| 9CC2 | STA tx_flags ; Store operation flags |
| 9CC5 | LDA tube_tx_byte2_operand,y ; Look up tx_length from table |
| 9CC8 | STA tx_length ; Store expected transfer length |
| 9CCB | LDA sr2_test_operand,y ; Load handler from dispatch table |
| 9CCE | PHA ; Push high byte for PHA/PHA/RTS dispatch |
| 9CCF | LDA intoff_test_inactive,y ; Look up handler address low from table |
| 9CD2 | PHA ; Push low byte for PHA/PHA/RTS dispatch |
| 9CD3 | RTS ; RTS dispatches to control-byte handler |
| 9CD4 | EQUB <(tx_ctrl_peek-1) |
| 9CD5 | EQUB <(tx_ctrl_poke-1) |
| 9CD6 | EQUB <(tx_ctrl_proc-1) |
| 9CD7 | EQUB <(tx_ctrl_proc-1) |
| 9CD8 | EQUB <(tx_ctrl_proc-1) |
| 9CD9 | EQUB <(tx_ctrl_exit-1) |
| 9CDA | EQUB <(tx_ctrl_exit-1) |
| 9CDB | EQUB <(imm_op_status3-1) |
| 9CDC | EQUB >(tx_ctrl_peek-1) |
| 9CDD | EQUB >(tx_ctrl_poke-1) |
| 9CDE | EQUB >(tx_ctrl_proc-1) |
| 9CDF | EQUB >(tx_ctrl_proc-1) |
| 9CE0 | EQUB >(tx_ctrl_proc-1) |
| 9CE1 | EQUB >(tx_ctrl_exit-1) |
| 9CE2 | EQUB >(tx_ctrl_exit-1) |
| 9CE3 | EQUB >(imm_op_status3-1) |
| 9CE4 | .imm_op_status3 |
| LDA #3 ; A=3: scout_status for PEEK | |
| 9CE6 | BNE store_status_calc_xfer ; ALWAYS branch |
TX ctrl: PEEK transfer setupSets scout_status=3, then performs a 4-byte addition of bytes from the TX block into the transfer parameter workspace (with carry propagation). Calls tx_calc_transfer to finalise, then exits via tx_ctrl_exit. |
|
| 9CE8 | .tx_ctrl_peek |
| LDA #3 ; A=3: scout_status for PEEK op | |
| 9CEA | BNE store_status_add4 ; ALWAYS branch |
TX ctrl: POKE transfer setupSets scout_status=2 and shares the 4-byte addition and transfer calculation path with tx_ctrl_peek. |
|
| 9CEC | .tx_ctrl_poke |
| LDA #2 ; Scout status = 2 (POKE transfer) | |
| 9CEE | .store_status_add4←1← 9CEA BNE |
| STA scout_status ; Store scout status | |
| 9CF1 | CLC ; Clear carry for 4-byte addition |
| 9CF2 | PHP ; Save carry on stack |
| 9CF3 | LDY #&0c ; Y=&0C: start at offset 12 |
| 9CF5 | .add_bytes_loop←1← 9D02 BCC |
| LDA l0d1e,y ; Load workspace address byte | |
| 9CF8 | PLP ; Restore carry from previous byte |
| 9CF9 | ADC (nmi_tx_block),y ; Add TXCB address byte |
| 9CFB | STA l0d1e,y ; Store updated address byte |
| 9CFE | INY ; Next byte |
| 9CFF | PHP ; Save carry for next addition |
| fall through ↓ | |
TX ctrl: JSR/UserProc/OSProc setupSets scout_status=2 and calls tx_calc_transfer directly (no 4-byte address addition needed for procedure calls). Shared by operation types &83-&85. |
|
| 9D00 | .tx_ctrl_add_done |
| CPY #&10 ; Compare Y with 16-byte boundary | |
| 9D02 | BCC add_bytes_loop ; Below boundary: continue addition |
| 9D04 | PLP ; Restore processor flags |
| 9D05 | JSR tx_calc_transfer ; Calculate transfer byte count |
| 9D08 | JMP tx_ctrl_exit ; Jump to TX control exit |
TX ctrl: JSR/UserProc/OSProc setupSets scout_status=2 and calls tx_calc_transfer directly (no 4-byte address addition needed for procedure calls). Shared by operation types &83-&85. |
|
| 9D0B | .tx_ctrl_proc |
| LDA #2 ; A=2: scout_status for procedure ops | |
| 9D0D | .store_status_calc_xfer←1← 9CE6 BNE |
| STA scout_status ; Store scout status | |
| 9D10 | JSR tx_calc_transfer ; Calculate transfer parameters |
| 9D13 | JMP tx_ctrl_exit ; Exit TX ctrl setup |
| 9D16 | .setup_data_xfer←1← 9CBA BNE |
| LDA tx_dst_stn ; Load dest station for broadcast check | |
| 9D19 | AND tx_dst_net ; AND with dest network |
| 9D1C | CMP #&ff ; Both &FF = broadcast address? |
| 9D1E | BNE setup_unicast_xfer ; Not broadcast: unicast path |
| 9D20 | LDA #&0e ; Broadcast scout: 14 bytes total |
| 9D22 | STA tx_length ; Store broadcast scout length |
| 9D25 | LDA #&40 ; A=&40: broadcast flag |
| 9D27 | STA tx_flags ; Set broadcast flag in tx_flags |
| 9D2A | LDY #4 ; Y=4: start of address data in TXCB |
| 9D2C | .copy_bcast_addr←1← 9D34 BCC |
| LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer | |
| 9D2E | STA tx_src_stn,y ; Store to TX source/data area |
| 9D31 | INY ; Next byte |
| 9D32 | CPY #&0c ; Done 8 bytes? (Y reaches &0C) |
| 9D34 | BCC copy_bcast_addr ; No: continue copying |
| 9D36 | BCS tx_ctrl_exit ; ALWAYS branch |
| 9D38 | .setup_unicast_xfer←1← 9D1E BNE |
| LDA #0 ; A=0: clear flags for unicast | |
| 9D3A | STA tx_flags ; Clear tx_flags |
| 9D3D | .proc_op_status2 |
| LDA #2 ; scout_status=2: data transfer pending | |
| 9D3F | .store_status_copy_ptr |
| STA scout_status ; Store scout status | |
| 9D42 | JSR tx_calc_transfer ; Calculate transfer size from RXCB |
| 9D45 | .tx_ctrl_exit←3← 9D08 JMP← 9D13 JMP← 9D36 BCS |
| PLP ; Restore processor status from stack | |
| 9D46 | PLA ; Restore stacked registers (4 PLAs) |
| 9D47 | PLA ; Second PLA |
| 9D48 | PLA ; Third PLA |
| 9D49 | PLA ; Fourth PLA |
| 9D4A | TAX ; Restore X from A |
| 9D4B | RTS ; Return to caller |
NMI TX data handlerWrites 2 bytes per NMI invocation to the TX FIFO at &FEA2. Uses the BIT instruction on SR1 to test TDRA (V flag = bit6) and IRQ (N flag = bit7). After writing 2 bytes, checks if the frame is complete. If more data, tests SR1 bit7 (IRQ) via BMI -- if IRQ still asserted, writes 2 more bytes without returning from NMI (tight loop). Otherwise returns via RTI. |
|
| 9D4C | .nmi_tx_data |
| LDY tx_index ; Load TX buffer index | |
| 9D4F | BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ) |
| 9D52 | .tx_fifo_write←1← 9D6D BMI |
| BVC tx_fifo_not_ready ; TDRA not set -- TX error | |
| 9D54 | LDA tx_dst_stn,y ; Load byte from TX buffer |
| 9D57 | STA econet_data_continue_frame ; Write to TX_DATA (continue frame) |
| 9D5A | INY ; Next TX buffer byte |
| 9D5B | LDA tx_dst_stn,y ; Load second byte from TX buffer |
| 9D5E | INY ; Advance TX index past second byte |
| 9D5F | STY tx_index ; Save updated TX buffer index |
| 9D62 | STA econet_data_continue_frame ; Write second byte to TX_DATA |
| 9D65 | CPY tx_length ; Compare index to TX length |
| 9D68 | BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA |
| 9D6A | BIT econet_control1_or_status1 ; Check if we can send another pair |
| 9D6D | BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop) |
| 9D6F | JMP nmi_rti ; RTI -- wait for next NMI |
| ; TX error path | |
| 9D72 | .tx_error←1← 9DB7 BEQ |
| LDA #&42 ; Error &42 | |
| 9D74 | BNE tx_store_error ; ALWAYS branch |
| 9D76 | .tx_fifo_not_ready←1← 9D52 BVC |
| LDA #&67 ; CR2=&67: clear status, return to listen | |
| 9D78 | STA econet_control23_or_status2 ; Write CR2: clear status, idle listen |
| 9D7B | LDA #&41 ; Error &41 (TDRA not ready) |
| 9D7D | .tx_store_error←1← 9D74 BNE |
| LDY station_id_disable_net_nmis ; INTOFF (also loads station ID) | |
| 9D80 | .delay_nmi_disable←1← 9D83 BNE |
| PHA ; PHA/PLA delay loop (256 iterations for NMI disable) | |
| 9D81 | PLA ; PHA/PLA delay (~7 cycles each) |
| 9D82 | INY ; Increment delay counter |
| 9D83 | BNE delay_nmi_disable ; Loop 256 times for NMI disable |
| 9D85 | JMP tx_store_result ; Jump to error handler |
TX_LAST_DATA and frame completionSignals end of TX frame by writing CR2=&3F (TX_LAST_DATA). Then installs the TX completion NMI handler at &9D94 which switches to RX mode. CR2=&3F = 0011_1111: bit5: CLR_RX_ST -- clears fv_stored_ (prepares for RX of reply) bit4: TX_LAST_DATA -- tells ADLC this is the final data byte bit3: FLAG_IDLE -- send flags/idle after frame bit2: FC_TDRA -- force clear TDRA bit1: 2_1_BYTE -- two-byte transfer mode bit0: PSE -- prioritised status enable Note: NO CLR_TX_ST (bit6=0), NO RTS (bit7=0 -- drops RTS after frame) |
|
| 9D88 | .tx_last_data←1← 9D68 BCS |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE | |
| 9D8A | STA econet_control23_or_status2 ; Write to ADLC CR2 |
| 9D8D | LDA #&94 ; Install NMI handler at &9D94 (TX completion) |
| 9D8F | LDY #&9d ; High byte of handler address |
| 9D91 | JMP set_nmi_vector ; Install and return via set_nmi_vector |
TX completion: switch to RX modeCalled via NMI after the frame (including CRC and closing flag) has been fully transmitted. Switches from TX mode to RX mode by writing CR1=&82. CR1=&82 = 1000_0010: TX_RESET | RIE (listen for reply). Checks workspace flags to decide next action: - bit6 set at &0D4A -> completion at &9F39 - bit0 set at &0D4A -> four-way handshake data phase at &9EDD - Otherwise -> install RX reply handler at &9DB2 |
|
| 9D94 | .nmi_tx_complete |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (now in RX mode) | |
| 9D96 | STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX |
| 9D99 | BIT tx_flags ; Test workspace flags |
| 9D9C | BVC check_handshake_bit ; bit6 not set -- check bit0 |
| 9D9E | JMP tx_result_ok ; bit6 set -- TX completion |
| 9DA1 | .check_handshake_bit←1← 9D9C BVC |
| LDA #1 ; A=1: mask for bit0 test | |
| 9DA3 | BIT tx_flags ; Test tx_flags bit0 (handshake) |
| 9DA6 | BEQ install_reply_scout ; bit0 clear: install reply handler |
| 9DA8 | JMP handshake_await_ack ; bit0 set -- four-way handshake data phase |
| 9DAB | .install_reply_scout←1← 9DA6 BEQ |
| LDA #&b2 ; Install RX reply handler at &9DB2 | |
| 9DAD | LDY #&9d ; High byte of nmi_reply_scout addr |
| 9DAF | JMP set_nmi_vector ; Install handler and RTI |
RX reply scout handlerHandles reception of the reply scout frame after transmission. Checks SR2 bit0 (AP) for incoming data, reads the first byte (destination station) and compares to our station ID via &FE18 (which also disables NMIs as a side effect). |
|
| 9DB2 | .nmi_reply_scout |
| LDA #1 ; A=&01: AP mask for SR2 | |
| 9DB4 | BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present) |
| 9DB7 | BEQ tx_error ; No AP -- error |
| 9DB9 | LDA econet_data_continue_frame ; Read first RX byte (destination station) |
| 9DBC | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect) |
| 9DBF | BNE reply_error ; Not our station -- error/reject |
| 9DC1 | LDA #&c8 ; Install next handler at &9DC8 (reply continuation) |
| 9DC3 | LDY #&9d ; High byte of nmi_reply_cont |
| 9DC5 | JMP set_nmi_vector ; Install continuation handler |
RX reply continuation handlerReads the second byte of the reply scout (destination network) and validates it is zero (local network). Installs &9DE3 for the remaining two bytes (source station and network). Optimisation: checks SR1 bit7 (IRQ still asserted) via BMI at &9DD9. If IRQ is still set, falls through directly to &9DE3 without an RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession. |
|
| 9DC8 | .nmi_reply_cont |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 9DCB | BPL reply_error ; No RDA -- error |
| 9DCD | LDA econet_data_continue_frame ; Read destination network byte |
| 9DD0 | BNE reply_error ; Non-zero -- network mismatch, error |
| 9DD2 | LDA #&e3 ; Install next handler at &9DE3 (reply validation) |
| 9DD4 | LDY #&9d ; High byte of nmi_reply_validate |
| 9DD6 | BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready? |
| 9DD9 | BMI nmi_reply_validate ; IRQ set -- fall through to &9D5B without RTI |
| 9DDB | JMP set_nmi_vector ; IRQ not set -- install handler and RTI |
| 9DDE | .reply_error←7← 9DBF BNE← 9DCB BPL← 9DD0 BNE← 9DE6 BPL← 9DEE BNE← 9DF6 BNE← 9DFD BEQ |
| LDA #&41 ; A=&41: 'not listening' error code | |
| 9DE0 | .reject_reply |
| JMP tx_store_result ; Store error and return to idle | |
RX reply validation (Path 2 for FV/PSE interaction)Reads the source station and source network from the reply scout and validates them against the original TX destination (&0D20/&0D21). Sequence: 1. Check SR2 bit7 (RDA) at &9DE3 -- must see data available 2. Read source station at &9DE8, compare to &0D20 (tx_dst_stn) 3. Read source network at &9DF0, compare to &0D21 (tx_dst_net) 4. Check SR2 bit1 (FV) at &9DFA -- must see frame complete If all checks pass, the reply scout is valid and the ROM proceeds to send the scout ACK (CR2=&A7 for RTS, CR1=&44 for TX mode). |
|
| 9DE3 | .nmi_reply_validate←1← 9DD9 BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply. | |
| 9DE6 | BPL reply_error ; No RDA -- error (FV masking RDA via PSE would cause this) |
| 9DE8 | LDA econet_data_continue_frame ; Read source station |
| 9DEB | CMP tx_dst_stn ; Compare to original TX destination station (&0D20) |
| 9DEE | BNE reply_error ; Mismatch -- not the expected reply, error |
| 9DF0 | LDA econet_data_continue_frame ; Read source network |
| 9DF3 | CMP tx_dst_net ; Compare to original TX destination network (&0D21) |
| 9DF6 | BNE reply_error ; Mismatch -- error |
| 9DF8 | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 9DFA | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9DFD | BEQ reply_error ; No FV -- incomplete frame, error |
| 9DFF | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake) |
| 9E01 | STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake |
| 9E04 | LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK) |
| 9E06 | STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt |
| 9E09 | LDA #&dd ; Install next handler at &9EDD (four-way data phase) into &0D4B/&0D4C |
| 9E0B | LDY #&9e ; High byte &9E of next handler address |
| 9E0D | STA nmi_next_lo ; Store low byte to nmi_next_lo |
| 9E10 | STY nmi_next_hi ; Store high byte to nmi_next_hi |
| 9E13 | LDA tx_dst_stn ; Load dest station for scout ACK TX |
| 9E16 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9E19 | BVC data_tx_error ; TDRA not ready -- error |
| 9E1B | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 9E1E | LDA tx_dst_net ; Load dest network for scout ACK TX |
| 9E21 | STA econet_data_continue_frame ; Write dest network to TX FIFO |
| 9E24 | LDA #&2b ; Install handler at &9E2B (write src addr for scout ACK) |
| 9E26 | LDY #&9e ; High byte &9D of handler address |
| 9E28 | JMP set_nmi_vector ; Set NMI vector and return |
TX scout ACK: write source addressWrites our station ID and network=0 to TX FIFO, completing the 4-byte scout ACK frame. Then proceeds to send the data frame. |
|
| 9E2B | .nmi_scout_ack_src |
| LDA station_id_disable_net_nmis ; Read our station ID (also INTOFF) | |
| 9E2E | BIT econet_control1_or_status1 ; BIT SR1: check TDRA before writing |
| 9E31 | BVC data_tx_error ; TDRA not ready: TX error |
| 9E33 | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 9E36 | LDA #0 ; Network = 0 (local network) |
| 9E38 | STA econet_data_continue_frame ; Write network byte to TX FIFO |
| 9E3B | .data_tx_begin←1← 99B5 JMP |
| LDA #2 ; Test bit 1 of tx_flags | |
| 9E3D | BIT tx_flags ; Check if immediate-op or data-transfer |
| 9E40 | BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler |
| 9E42 | LDA #&50 ; Install nmi_data_tx at &9E50 |
| 9E44 | LDY #&9e ; High byte of handler address |
| 9E46 | JMP set_nmi_vector ; Install and return via set_nmi_vector |
| 9E49 | .install_imm_data_nmi←1← 9E40 BNE |
| LDA #&a4 ; Install nmi_data_tx_tube at &9EA4 | |
| 9E4B | LDY #&9e ; High byte of handler address |
| 9E4D | JMP set_nmi_vector ; Install and return via set_nmi_vector |
TX data phase: send payloadSends the data frame payload from (open_port_buf),Y in pairs per NMI. Same pattern as the NMI TX handler at &9D4C but reads from the port buffer instead of the TX workspace. Writes two bytes per iteration, checking SR1 IRQ between pairs for tight looping. |
|
| 9E50 | .nmi_data_tx |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 9E52 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9E55 | .data_tx_check_fifo←1← 9E78 BMI |
| BVC data_tx_error ; TDRA not ready -- error | |
| 9E57 | LDA (open_port_buf),y ; Write data byte to TX FIFO |
| 9E59 | STA econet_data_continue_frame ; Write first byte of pair to FIFO |
| 9E5C | INY ; Advance buffer offset |
| 9E5D | BNE write_second_tx_byte ; No page crossing |
| 9E5F | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9E61 | BEQ data_tx_last ; No pages left: send last data |
| 9E63 | INC open_port_buf_hi ; Increment buffer high byte |
| 9E65 | .write_second_tx_byte←1← 9E5D BNE |
| LDA (open_port_buf),y ; Load second byte of pair | |
| 9E67 | STA econet_data_continue_frame ; Write second byte to FIFO |
| 9E6A | INY ; Advance buffer offset |
| 9E6B | STY port_buf_len ; Save updated buffer position |
| 9E6D | BNE check_irq_loop ; No page crossing |
| 9E6F | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9E71 | BEQ data_tx_last ; No pages left: send last data |
| 9E73 | INC open_port_buf_hi ; Increment buffer high byte |
| 9E75 | .check_irq_loop←1← 9E6D BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop | |
| 9E78 | BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes |
| 9E7A | JMP nmi_rti ; No IRQ: return, wait for next NMI |
| 9E7D | .data_tx_last←4← 9E61 BEQ← 9E71 BEQ← 9EBD BEQ← 9ED3 BEQ |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame) | |
| 9E7F | STA econet_control23_or_status2 ; Write CR2 to close frame |
| 9E82 | LDA tx_flags ; Check tx_flags for next action |
| 9E85 | BPL install_saved_handler ; Bit7 clear: error, install saved handler |
| 9E87 | LDA #&34 ; Install discard_reset_listen at &9A34 |
| 9E89 | LDY #&9a ; High byte of &9A34 handler |
| 9E8B | JMP set_nmi_vector ; Set NMI vector and return |
| 9E8E | .data_tx_error←4← 9E19 BVC← 9E31 BVC← 9E55 BVC← 9EA7 BVC |
| LDA tx_flags ; Load saved next handler low byte | |
| 9E91 | BPL nmi_tx_not_listening ; bit7 clear: error path |
| 9E93 | JMP discard_reset_listen ; ADLC reset and return to idle |
| 9E96 | .nmi_tx_not_listening←1← 9E91 BPL |
| LDA #&41 ; A=&41: 'not listening' error | |
| 9E98 | .jmp_tx_result_fail |
| JMP tx_store_result ; Store result and return to idle | |
| 9E9B | .install_saved_handler←1← 9E85 BPL |
| LDA nmi_next_lo ; Load saved handler low byte | |
| 9E9E | LDY nmi_next_hi ; Load saved next handler high byte |
| 9EA1 | JMP set_nmi_vector ; Install saved handler and return |
| 9EA4 | .nmi_data_tx_tube |
| BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA | |
| 9EA7 | .tube_tx_fifo_write←1← 9ED8 BMI |
| BVC data_tx_error ; TDRA not ready -- error | |
| 9EA9 | LDA tube_data_register_3 ; Read byte from Tube R3 |
| 9EAC | STA econet_data_continue_frame ; Write to TX FIFO |
| 9EAF | INC port_buf_len ; Increment 4-byte buffer counter |
| 9EB1 | BNE write_second_tube_byte ; Low byte didn't wrap |
| 9EB3 | INC port_buf_len_hi ; Carry into second byte |
| 9EB5 | BNE write_second_tube_byte ; No further carry |
| 9EB7 | INC open_port_buf ; Carry into third byte |
| 9EB9 | BNE write_second_tube_byte ; No further carry |
| 9EBB | INC open_port_buf_hi ; Carry into fourth byte |
| 9EBD | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9EBF | .write_second_tube_byte←3← 9EB1 BNE← 9EB5 BNE← 9EB9 BNE |
| LDA tube_data_register_3 ; Read second Tube byte from R3 | |
| 9EC2 | STA econet_data_continue_frame ; Write second byte to TX FIFO |
| 9EC5 | INC port_buf_len ; Increment 4-byte counter (second byte) |
| 9EC7 | BNE check_tube_irq_loop ; Low byte didn't wrap |
| 9EC9 | .tube_tx_inc_byte2 |
| INC port_buf_len_hi ; Carry into second byte | |
| 9ECB | BNE check_tube_irq_loop ; No further carry |
| 9ECD | .tube_tx_inc_byte3 |
| INC open_port_buf ; Carry into third byte | |
| 9ECF | BNE check_tube_irq_loop ; No further carry |
| 9ED1 | .tube_tx_inc_byte4 |
| INC open_port_buf_hi ; Carry into fourth byte | |
| 9ED3 | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9ED5 | .check_tube_irq_loop←3← 9EC7 BNE← 9ECB BNE← 9ECF BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop | |
| 9ED8 | BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes |
| 9EDA | JMP nmi_rti ; No IRQ: return, wait for next NMI |
Four-way handshake: switch to RX for final ACKAfter the data frame TX completes, switches to RX mode (CR1=&82) and installs &9EE9 to receive the final ACK from the remote station. |
|
| 9EDD | .handshake_await_ack←1← 9DA8 JMP |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK) | |
| 9EDF | STA econet_control1_or_status1 ; Write to ADLC CR1 |
| 9EE2 | LDA #&e9 ; Install handler at &9EE9 (RX final ACK) |
| 9EE4 | LDY #&9e ; High byte of handler address |
| 9EE6 | JMP set_nmi_vector ; Install and return via set_nmi_vector |
RX final ACK handlerReceives the final ACK in a four-way handshake. Same validation pattern as the reply scout handler (&9DB2-&9DE3): &9EE9: Check AP, read dest_stn, compare to our station &9EFF: Check RDA, read dest_net, validate = 0 &9F15: Check RDA, read src_stn/net, compare to TX dest &9F32: Check FV for frame completion On success, stores result=0 at &9F39. On any failure, error &41. |
|
| 9EE9 | .nmi_final_ack |
| LDA #1 ; A=&01: AP mask | |
| 9EEB | BIT econet_control23_or_status2 ; BIT SR2: test AP |
| 9EEE | BEQ tx_result_fail ; No AP -- error |
| 9EF0 | LDA econet_data_continue_frame ; Read dest station |
| 9EF3 | CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect) |
| 9EF6 | BNE tx_result_fail ; Not our station -- error |
| 9EF8 | LDA #&ff ; Install handler at &9EFF (final ACK continuation) |
| 9EFA | LDY #&9e ; High byte of handler address |
| 9EFC | JMP set_nmi_vector ; Install continuation handler |
| 9EFF | .nmi_final_ack_net |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9F02 | BPL tx_result_fail ; No RDA -- error |
| 9F04 | LDA econet_data_continue_frame ; Read dest network |
| 9F07 | BNE tx_result_fail ; Non-zero -- network mismatch, error |
| 9F09 | LDA #&15 ; Install handler at &9F15 (final ACK validation) |
| 9F0B | LDY #&9f ; High byte of handler address |
| 9F0D | BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready? |
| 9F10 | BMI nmi_final_ack_validate ; IRQ set -- fall through to &9F15 without RTI |
| 9F12 | JMP set_nmi_vector ; Install handler and RTI |
Final ACK validationReads and validates src_stn and src_net against original TX dest. Then checks FV for frame completion. |
|
| 9F15 | .nmi_final_ack_validate←1← 9F10 BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9F18 | BPL tx_result_fail ; No RDA -- error |
| 9F1A | LDA econet_data_continue_frame ; Read source station |
| 9F1D | CMP tx_dst_stn ; Compare to TX dest station (&0D20) |
| 9F20 | BNE tx_result_fail ; Mismatch -- error |
| 9F22 | LDA econet_data_continue_frame ; Read source network |
| 9F25 | CMP tx_dst_net ; Compare to TX dest network (&0D21) |
| 9F28 | BNE tx_result_fail ; Mismatch -- error |
| 9F2A | LDA tx_flags ; Load TX flags for next action |
| 9F2D | BPL check_fv_final_ack ; bit7 clear: no data phase |
| 9F2F | JMP install_data_rx_handler ; Install data RX handler |
| 9F32 | .check_fv_final_ack←1← 9F2D BPL |
| LDA #2 ; A=&02: FV mask for SR2 bit1 | |
| 9F34 | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9F37 | BEQ tx_result_fail ; No FV -- error |
| fall through ↓ | |
| 9F39 | .tx_result_ok←2← 9963 JMP← 9D9E JMP |
| LDA #0 ; A=0: success result code | |
| 9F3B | BEQ tx_store_result ; BEQ: always taken (A=0) ALWAYS branch |
TX failure: not listeningLoads error code &41 (not listening) and falls through to tx_store_result. The most common TX error path — reached from 11 sites across the final-ACK validation chain when the remote station doesn't respond or the frame is malformed. |
|
| 9F3D | .tx_result_fail←8← 9EEE BEQ← 9EF6 BNE← 9F02 BPL← 9F07 BNE← 9F18 BPL← 9F20 BNE← 9F28 BNE← 9F37 BEQ |
| LDA #&41 ; A=&41: not listening error code | |
| fall through ↓ | |
| 9F3F | .tx_store_result←5← 9891 JMP← 9D85 JMP← 9DE0 JMP← 9E98 JMP← 9F3B BEQ |
| LDY #0 ; Y=0: index into TX control block | |
| 9F41 | STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0 |
| 9F43 | LDA #&80 ; &80: completion flag for &0D3A |
| 9F45 | STA tx_ctrl_status ; Signal TX complete |
| 9F48 | JMP discard_reset_listen ; Full ADLC reset and return to idle listen |
| ; Unreferenced data block (purpose unknown) | |
| 9F4B | EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81 |
Calculate transfer sizeComputes the number of bytes actually transferred during a data frame reception. Subtracts the low pointer (LPTR, offset 4 in the RXCB) from the current buffer position to get the byte count, and stores it back into the RXCB's high pointer field (HPTR, offset 8). This tells the caller how much data was received. |
|
| 9F5B | .tx_calc_transfer←5← 980E JSR← 9AEE JSR← 9D05 JSR← 9D10 JSR← 9D42 JSR |
| LDY #6 ; Load RXCB[6] (buffer addr byte 2) | |
| 9F5D | LDA (nmi_tx_block),y ; Load TX block byte at offset 6 |
| 9F5F | INY ; Y=&07 |
| 9F60 | AND (nmi_tx_block),y ; AND with TX block[7] (byte 3) |
| 9F62 | CMP #&ff ; Both &FF = no buffer? |
| 9F64 | BEQ fallback_calc_transfer ; Yes: fallback path |
| 9F66 | LDA tx_in_progress ; Tube transfer in progress? |
| 9F69 | BEQ fallback_calc_transfer ; No: fallback path |
| 9F6B | LDA tx_flags ; Load TX flags for transfer setup |
| 9F6E | ORA #2 ; Set bit 1 (transfer complete) |
| 9F70 | STA tx_flags ; Store with bit 1 set (Tube xfer) |
| 9F73 | SEC ; Init borrow for 4-byte subtract |
| 9F74 | PHP ; Save carry on stack |
| 9F75 | LDY #4 ; Y=4: start at RXCB offset 4 |
| 9F77 | .calc_transfer_size←1← 9F89 BCC |
| LDA (nmi_tx_block),y ; Load RXCB[Y] (current ptr byte) | |
| 9F79 | INY ; Y += 4: advance to RXCB[Y+4] |
| 9F7A | INY ; (continued) |
| 9F7B | INY ; (continued) |
| 9F7C | INY ; (continued) |
| 9F7D | PLP ; Restore borrow from previous byte |
| 9F7E | SBC (nmi_tx_block),y ; Subtract RXCB[Y+4] (start ptr byte) |
| 9F80 | STA net_tx_ptr,y ; Store result byte |
| 9F83 | DEY ; Y -= 3: next source byte |
| 9F84 | DEY ; (continued) |
| 9F85 | DEY ; (continued) |
| 9F86 | PHP ; Save borrow for next byte |
| 9F87 | CPY #8 ; Done all 4 bytes? |
| 9F89 | BCC calc_transfer_size ; No: next byte pair |
| 9F8B | PLP ; Discard final borrow |
| 9F8C | TXA ; A = saved X |
| 9F8D | PHA ; Save X |
| 9F8E | LDA #4 ; Compute address of RXCB+4 |
| 9F90 | CLC ; CLC for base pointer addition |
| 9F91 | ADC nmi_tx_block ; Add RXCB base to get RXCB+4 addr |
| 9F93 | TAX ; X = low byte of RXCB+4 |
| 9F94 | LDY nmi_tx_block_hi ; Y = high byte of RXCB ptr |
| 9F96 | LDA #&c2 ; Tube claim type &C2 |
| 9F98 | JSR tube_addr_claim ; Claim Tube transfer address |
| 9F9B | BCC restore_x_and_return ; No Tube: skip reclaim |
| 9F9D | LDA scout_status ; Tube: reclaim with scout status |
| 9FA0 | JSR tube_addr_claim ; Reclaim with scout status type |
| 9FA3 | SEC ; C=1: Tube address claimed |
| 9FA4 | .restore_x_and_return←1← 9F9B BCC |
| PLA ; Restore X | |
| 9FA5 | TAX ; Restore X from stack |
| 9FA6 | RTS ; Return with C = transfer status |
| 9FA7 | .fallback_calc_transfer←2← 9F64 BEQ← 9F69 BEQ |
| LDY #4 ; Y=4: RXCB current pointer offset | |
| 9FA9 | LDA (nmi_tx_block),y ; Load RXCB[4] (current ptr lo) |
| 9FAB | LDY #8 ; Y=8: RXCB start address offset |
| 9FAD | SEC ; Set carry for subtraction |
| 9FAE | SBC (nmi_tx_block),y ; Subtract RXCB[8] (start ptr lo) |
| 9FB0 | STA port_buf_len ; Store transfer size lo |
| 9FB2 | LDY #5 ; Y=5: current ptr hi offset |
| 9FB4 | LDA (nmi_tx_block),y ; Load RXCB[5] (current ptr hi) |
| 9FB6 | SBC #0 ; Propagate borrow from lo subtraction |
| 9FB8 | STA open_port_buf_hi ; Temp store adjusted current ptr hi |
| 9FBA | LDY #8 ; Y=8: start address lo offset |
| 9FBC | LDA (nmi_tx_block),y ; Copy RXCB[8] to open port buffer lo |
| 9FBE | STA open_port_buf ; Store to scratch (side effect) |
| 9FC0 | LDY #9 ; Y=9: start address hi offset |
| 9FC2 | LDA (nmi_tx_block),y ; Load RXCB[9] (start ptr hi) |
| 9FC4 | SEC ; Set carry for subtraction |
| 9FC5 | SBC open_port_buf_hi ; start_hi - adjusted current_hi |
| 9FC7 | STA port_buf_len_hi ; Store transfer size hi |
| 9FC9 | SEC ; Return with C=1 |
| 9FCA | .nmi_shim_rom_src←1← 96D1 LDA |
| RTS ; Return with C=1 (success) | |
Bootstrap NMI entry point (in ROM)An alternate NMI handler that lives in the ROM itself rather than in the RAM workspace at &0D00. Unlike the RAM shim (which uses a self-modifying JMP to dispatch to different handlers), this one hardcodes JMP nmi_rx_scout (&96F6). Used as the initial NMI handler before the workspace has been properly set up during initialisation. Same sequence as the RAM shim: BIT &FE18 (INTOFF), PHA, TYA, PHA, LDA romsel, STA &FE30, JMP &96F6. |
|
| 9FCB | .nmi_bootstrap_entry |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM | |
| 9FCE | PHA ; Save A |
| 9FCF | TYA ; Transfer Y to A |
| 9FD0 | PHA ; Save Y (via A) |
| 9FD1 | LDA #0 ; ROM bank 0 (patched during init for actual bank) |
| 9FD3 | STA romsel ; Select Econet ROM bank via ROMSEL |
| 9FD6 | JMP nmi_rx_scout ; Jump to scout handler in ROM |
ROM copy of set_nmi_vector + nmi_rti |
|
| 9FD9 | .rom_set_nmi_vector |
| STY nmi_jmp_hi ; Store handler high byte at &0D0D | |
| 9FDC | STA nmi_jmp_lo ; Store handler low byte at &0D0C |
| 9FDF | LDA romsel_copy ; Restore NFS ROM bank |
| 9FE1 | STA romsel ; Page in via hardware latch |
| 9FE4 | PLA ; Restore Y from stack |
| 9FE5 | TAY ; Transfer ROM bank to Y |
| 9FE6 | PLA ; Restore A from stack |
| 9FE7 | BIT video_ula_control ; INTON: re-enable NMIs |
| 9FEA | RTI ; Return from interrupt |
| 9FEB | .rom_nmi_tail |
| LDA tx_flags ; Load current TX flags | |
| 9FEE | ORA #2 ; Set bit 1 (transfer mode flag) |
| 9FF0 | STA tx_flags ; Store updated TX flags |
| 9FF3 | SEC ; SEC for subtraction |
| 9FF4 | PHP ; Save carry on stack |
| 9FF5 | LDY #4 ; Y=4: TXCB data start low offset |
| 9FF7 | LDA (nmi_tx_block),y ; Load data start low byte |
| 9FF9 | INY ; Y += 4: advance to data end low offset Y=&05 |
| 9FFA | INY ; (continued) Y=&06 |
| 9FFB | INY ; (continued) Y=&07 |
| 9FFC | INY ; (continued) Y=&08 |
| 9FFD | PLP ; Restore carry for subtraction |
| 9FFE | SBC (nmi_tx_block),y ; Subtract buffer end from start |
| fall through ↓ | |
