Acorn NFS 3.62
Updated 31 Mar 2026
← All Acorn NFS and Acorn ANFS versions
- Acorn NFS 3.62 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.60
- Found a mistake or a comment that could be clearer? Report an issue.
| ; Sideways ROM header | |
| ; NFS ROM 3.62 disassembly (Acorn Econet filing system) | |
| ; ==================================================== | |
| 8000 | .rom_header←2← 04E6 LDA← 9B42 JSR |
| .language_entry←2← 04E6 LDA← 9B42 JSR | |
| .pydis_start←2← 04E6 LDA← 9B42 JSR | |
| JMP language_handler ; JMP language_handler | |
| 8003 | .service_entry←1← 04F5 LDY |
| JMP service_handler ; JMP service_handler | |
| 8006 | .rom_type←1← 04DA AND |
| EQUB &82 ; ROM type: service + language | |
| 8007 | .copyright_offset←1← 04E2 LDX |
| EQUB copyright - rom_header ; Copyright string offset from &8000 | |
| 8008 | .binary_version←2← 836E CMP← 8377 LDA |
| EQUB &83 ; Binary version number | |
| 8009 | .title |
| EQUS " NET" ; ROM title string " NET" | |
| 8010 | .copyright |
| EQUB &00 ; Null terminator before copyright | |
| ; The 'ROFF' suffix (copyright_string+3) is reused by | |
| ; the *ROFF command matcher (svc_star_command) — a | |
| ; space-saving trick that shares ROM bytes between the | |
| ; copyright string and the star command table. | |
| 8011 | .copyright_string |
| EQUS "(C)ROFF" ; Copyright string "(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. | |
| 8018 | .error_offsets←1← 8515 LDY |
| EQUB &00 ; "Line Jammed" | |
| 8019 | EQUB &0D ; "Net Error" |
| 801A | EQUB &18 ; "Not listening" |
| 801B | EQUB &27 ; "No Clock" |
| 801C | EQUB &31 ; "Escape" |
| 801D | EQUB &31 ; "Escape" |
| 801E | EQUB &31 ; "Escape" |
| 801F | EQUB &39 ; "Bad Option" |
| 8020 | EQUB &45 ; "No reply" |
| ; Four bytes with unknown purpose. | |
| 8021 | EQUB &01 ; Purpose unknown |
| 8022 | EQUB &00 ; Purpose unknown |
| 8023 | EQUB &62 ; Purpose unknown |
| 8024 | 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 &80E7. | |
| ; See dispatch_0_hi (&804A) for the corresponding high bytes. | |
| ; Five callers share this table via different Y base offsets: | |
| ; Y=&00 Service calls 0-12 (indices 0-13) | |
| ; Y=&0E Language entry reasons (indices 14-18) | |
| ; Y=&13 FSCV codes 0-7 (indices 19-26) | |
| ; Y=&17 FS reply handlers (indices 27-32) | |
| ; Y=&21 *NET1-4 sub-commands (indices 33-36) | |
| ; Lo bytes for the last 6 entries (indices 31-36) occupy | |
| ; &8044-&8049, immediately before the hi bytes. Their hi | |
| ; bytes are at &8069-&806E, after dispatch_0_hi. | |
| 8025 | .dispatch_0_lo |
| EQUB <(return_1-1) ; lo - Svc 0: already claimed (no-op) | |
| 8026 | EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace |
| 8027 | EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace |
| 8028 | EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot |
| 8029 | EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command |
| 802A | EQUB <(svc5_irq_check-1) ; lo - Svc 5: unrecognised interrupt |
| 802B | EQUB <(return_1-1) ; lo - Svc 6: BRK (no-op) |
| 802C | EQUB <(dispatch_net_cmd-1) ; lo - Svc 7: unrecognised OSBYTE |
| 802D | EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD |
| 802E | EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP |
| 802F | EQUB <(return_1-1) ; lo - Svc 10: static workspace (no-op) |
| 8030 | EQUB <(econet_restore-1) ; lo - Svc 11: NMI release (reclaim NMIs) |
| 8031 | EQUB <(econet_save-1) ; lo - Svc 12: NMI claim (save NMI state) |
| 8032 | EQUB <(svc_13_select_nfs-1) ; lo - Svc 13: select NFS (intercepted before dispatch) |
| 8033 | EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube |
| 8034 | EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup |
| 8035 | EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron) |
| 8036 | EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron) |
| 8037 | EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated |
| 8038 | EQUB <(fscv_0_opt_entry-1) ; lo - FSCV 0: *OPT |
| 8039 | EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check |
| 803A | EQUB <(fscv_2_star_run-1) ; lo - FSCV 2: */ (run) |
| 803B | EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command |
| 803C | EQUB <(fscv_2_star_run-1) ; lo - FSCV 4: *RUN |
| 803D | EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT |
| 803E | EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown |
| 803F | EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range |
| 8040 | EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name |
| 8041 | EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot |
| 8042 | EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles |
| 8043 | EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle |
| 8044 | EQUB <(fsreply_4_notify_exec-1) ; lo - FS reply: notify + execute |
| 8045 | EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle |
| 8046 | EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet |
| 8047 | EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace |
| 8048 | EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle |
| 8049 | EQUB <(net_4_resume_remote-1) ; lo - *NET4: resume remote |
| ; Dispatch table: high bytes of (handler_address - 1) | |
| ; Paired with dispatch_0_lo (&8025). Together they form a table | |
| ; of 37 handler addresses, used via the PHA/PHA/RTS trick at | |
| ; &80E7. | |
| 804A | .dispatch_0_hi |
| EQUB >(return_1-1) ; hi - Svc 0: already claimed (no-op) | |
| 804B | EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace |
| 804C | EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace |
| 804D | EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot |
| 804E | EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command |
| 804F | EQUB >(svc5_irq_check-1) ; hi - Svc 5: unrecognised interrupt |
| 8050 | EQUB >(return_1-1) ; hi - Svc 6: BRK (no-op) |
| 8051 | EQUB >(dispatch_net_cmd-1) ; hi - Svc 7: unrecognised OSBYTE |
| 8052 | EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD |
| 8053 | EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP |
| 8054 | EQUB >(return_1-1) ; hi - Svc 10: static workspace (no-op) |
| 8055 | EQUB >(econet_restore-1) ; hi - Svc 11: NMI release (reclaim NMIs) |
| 8056 | EQUB >(econet_save-1) ; hi - Svc 12: NMI claim (save NMI state) |
| 8057 | EQUB >(svc_13_select_nfs-1) ; hi - Svc 13: select NFS (intercepted before dispatch) |
| 8058 | EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube |
| 8059 | EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup |
| 805A | EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron) |
| 805B | EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron) |
| 805C | EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated |
| 805D | EQUB >(fscv_0_opt_entry-1) ; hi - FSCV 0: *OPT |
| 805E | EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check |
| 805F | EQUB >(fscv_2_star_run-1) ; hi - FSCV 2: */ (run) |
| 8060 | EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command |
| 8061 | EQUB >(fscv_2_star_run-1) ; hi - FSCV 4: *RUN |
| 8062 | EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT |
| 8063 | EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown |
| 8064 | EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range |
| 8065 | EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name |
| 8066 | EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot |
| 8067 | EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles |
| 8068 | EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle |
| 8069 | EQUB >(fsreply_4_notify_exec-1) ; hi - FS reply: notify + execute |
| 806A | EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle |
| 806B | EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet |
| 806C | EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace |
| 806D | EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle |
| 806E | 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=&21, and dispatches via &80E7. 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 (&8E67): read file handle from received packet (net_1_read_handle) *NET2 (&8E6D): read handle entry from workspace (net_2_read_handle_entry) *NET3 (&8E7D): close handle / mark as unused (net_3_close_handle) *NET4 (&81BC): resume after remote operation (net_4_resume_remote) |
|
| 806F | .dispatch_net_cmd |
| LDA osbyte_a_copy ; Read command character following *NET | |
| 8071 | SBC #&31 ; Subtract ASCII '1' to get 0-based command index |
| 8073 | CMP #4 ; Command index >= 4: invalid *NET sub-command |
| 8075 | BCS svc_dispatch_range ; Out of range: return via c80e3/RTS |
| 8077 | TAX ; X = command index (0-3) |
| 8078 | LDA #0 ; Clear &A9 (used by dispatch) |
| 807A | STA svc_state ; Store zero to &A9 |
| 807C | TYA ; Preserve A before dispatch |
| 807D | LDY #&21 ; Y=&21: base offset for *NET commands (index 33+) |
| 807F | BNE dispatch ; ALWAYS branch to dispatch ALWAYS branch |
| 8081 | .skip_cmd_spaces←1← 8086 BEQ |
| INY ; Advance past matched command text | |
| fall through ↓ | |
"I AM" command handlerDispatched from the command match table when the user types "*I AM <station>" or "*I AM <network>.<station>". Also used as the station number parser for "*NET <network>.<station>". Skips leading spaces, then calls parse_decimal for the first number. If a dot separator was found (carry set), it stores the result directly as the network (&0E01) and calls parse_decimal again for the station (&0E00). With a single number, it is stored as the station and the network defaults to 0 (local). If a colon follows, reads interactive input via OSRDCH and appends it to the command buffer. Finally jumps to forward_star_cmd. |
|
| 8082 | .i_am_handler |
| LDA (fs_options),y ; Load next char from command line | |
| 8084 | CMP #&20 ; Skip spaces |
| 8086 | BEQ skip_cmd_spaces ; Loop back to skip leading spaces |
| 8088 | CMP #&3a ; Colon = interactive remote command prefix |
| 808A | BCS skip_stn_parse ; Char >= ':': skip number parsing |
| 808C | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 808F | BCC got_station_num ; C=1: dot found, first number was network |
| 8091 | STA fs_server_net ; Store network number (n.s = network.station) A=parsed value (accumulated in &B2) |
| 8094 | INY ; Y=offset into (fs_options) buffer |
| 8095 | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 8098 | .got_station_num←1← 808F BCC |
| BEQ skip_stn_parse ; Z=1: no station parsed (empty or non-numeric) | |
| 809A | STA fs_server_stn ; A=parsed value (accumulated in &B2) |
| 809D | .skip_stn_parse←2← 808A BCS← 8098 BEQ |
| JSR infol2 ; Copy command text to FS buffer | |
| 80A0 | .scan_for_colon←2← 80A8 BNE← 80BF BNE |
| DEY ; Scan backward for ':' (interactive prefix) | |
| 80A1 | BEQ prepare_cmd_dispatch ; Y=0: no colon found, send command |
| 80A3 | LDA fs_cmd_data,y ; Read char from FS command buffer |
| 80A6 | CMP #&3a ; Test for colon separator |
| 80A8 | BNE scan_for_colon ; Not colon: keep scanning backward |
| 80AA | JSR oswrch ; Echo colon, then read user input from keyboard Write character |
| 80AD | .read_remote_cmd_line←1← 80BA BNE |
| JSR check_escape ; Check for escape condition | |
| 80B0 | JSR osrdch ; Test escape flag before FS reply Read a character from the current input stream |
| 80B3 | STA fs_cmd_data,y ; Append typed character to command buffer A=character read |
| 80B6 | INY ; Advance write pointer |
| 80B7 | INX ; Increment character count |
| 80B8 | CMP #&0d ; Test for CR (end of line) |
| 80BA | BNE read_remote_cmd_line ; Not CR: continue reading input |
| 80BC | JSR osnewl ; Write newline (characters 10 and 13) |
| 80BF | BNE scan_for_colon ; After OSNEWL: loop back to scan for colon |
| fall through ↓ | |
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 &8C4B, and from FSCV 2/3/4 indirectly. If CSD handle is zero (not logged in), returns without sending. |
|
| 80C1 | .forward_star_cmd←1← 9FBE JMP |
| JSR infol2 ; Copy command text to FS buffer | |
| 80C4 | TAY ; Y=function code for HDRFN |
| 80C5 | .prepare_cmd_dispatch←1← 80A1 BEQ |
| JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |
| 80C8 | LDX fs_cmd_csd ; X=depends on function |
| 80CB | BEQ return_1 ; CSD handle zero: not logged in |
| 80CD | LDA fs_cmd_data ; A=function code (0-7) |
| 80D0 | LDY #&17 ; Y=depends on function |
| 80D2 | 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 &8024 with base offset Y=&13 (table indices 20-27). Function codes: 0=*OPT, 1=EOF, 2=*/, 3=unrecognised *, 4=*RUN, 5=*CAT, 6=shutdown, 7=read handles.
|
|||||||||||||||
| 80D4 | .fscv_handler | ||||||||||||||
| JSR save_fscv_args_with_ptrs ; Store A/X/Y in FS workspace | |||||||||||||||
| 80D7 | CMP #8 ; FSCV function >= 8? | ||||||||||||||
| 80D9 | BCS return_1 ; Function code >= 8? Return (unsupported) | ||||||||||||||
| 80DB | TAX ; X = function code for dispatch | ||||||||||||||
| 80DC | TYA ; Save Y (command text ptr hi) | ||||||||||||||
| 80DD | LDY #&13 ; Y=&13: base offset for FSCV dispatch (indices 20+) | ||||||||||||||
| 80DF | 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 15-19 (base offset Y=&0E). |
|
| 80E1 | .language_handler←1← 8000 JMP |
| .lang_entry_dispatch←1← 8000 JMP | |
| CPX #5 ; X >= 5: invalid reason code, return | |
| 80E3 | .svc_dispatch_range←1← 8075 BCS |
| BCS return_1 ; Out of range: return via RTS | |
| 80E5 | LDY #&0e ; Y=&0E: base offset for language handlers (index 15+) |
| fall through ↓ | |
PHA/PHA/RTS computed dispatchX = command index within caller's group (e.g. service number) Y = base offset into dispatch table (0, &0E, &13, &21, 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. |
|
| 80E7 | .dispatch←5← 807F BNE← 80D2 BNE← 80DF BNE← 80E9 BPL← 81A1 JSR |
| INX ; Add base offset Y to index X (loop: X += Y+1) | |
| 80E8 | DEY ; Decrement base offset counter |
| 80E9 | BPL dispatch ; Loop until Y exhausted |
| 80EB | TAY ; Y=&FF (no further use) |
| 80EC | LDA dispatch_0_hi-1,x ; Load high byte of (handler - 1) from table |
| 80EF | PHA ; Push high byte onto stack |
| 80F0 | LDA dispatch_0_lo-1,x ; Load low byte of (handler - 1) from table |
| 80F3 | PHA ; Push low byte onto stack |
| 80F4 | LDX fs_options ; Restore X (fileserver options) for use by handler |
| 80F6 | .return_1←3← 80CB BEQ← 80D9 BCS← 80E3 BCS |
| RTS ; RTS pops address, adds 1, jumps to handler | |
| 80F7 | .service_handler←1← 8003 JMP |
| NOP ; 9 NOPs: bus settling time for ADLC probe | |
| 80F8 | NOP ; (bus settling continued) |
| 80F9 | NOP ; (bus settling continued) |
| 80FA | NOP ; (bus settling continued) |
| 80FB | NOP ; (bus settling continued) |
| 80FC | NOP ; (bus settling continued) |
| 80FD | NOP ; (bus settling continued) |
| 80FE | NOP ; (bus settling continued) |
| 80FF | NOP ; (bus settling continued) |
| 8100 | PHA ; Save service call number |
| 8101 | CMP #1 ; Only probe ADLC on service 1 (workspace claim) |
| 8103 | BNE check_disable_flag ; Not service 1: skip probe |
| 8105 | LDA econet_control1_or_status1 ; Probe ADLC SR1: non-zero = absent (bus noise) |
| 8108 | AND #&ed ; Mask SR1 status bits (ignore bits 4,1) |
| 810A | BNE set_adlc_disable ; Non-zero: ADLC absent, set disable flag |
| 810C | LDA econet_control23_or_status2 ; Probe ADLC SR2 if SR1 was all zeros |
| 810F | AND #&db ; Mask SR2 status bits (ignore bits 5,2) |
| 8111 | BEQ check_disable_flag ; Both zero: ADLC present, skip |
| 8113 | .set_adlc_disable←1← 810A BNE |
| ROL rom_ws_table,x ; Set bit 7 of per-ROM workspace = disable flag | |
| 8116 | SEC ; SEC for ROR to set bit 7 |
| 8117 | ROR rom_ws_table,x ; Rotate carry into bit 7 of workspace |
| 811A | .check_disable_flag←2← 8103 BNE← 8111 BEQ |
| LDA rom_ws_table,x ; Read back flag; ASL puts bit 7 into carry | |
| 811D | ASL ; C into bit 7 of A |
| 811E | PLA ; Restore service call number |
| 811F | BMI check_svc_high ; Service >= &80: always handle (Tube/init) |
| 8121 | BCS svc_unhandled_return ; C=1 (no ADLC): disable ROM, skip |
| fall through ↓ | |
Service handler entryPreamble at &80F7 (9 NOPs + ADLC probe): on service 1 only, probes ADLC status registers &FEA0/&FEA1 to detect whether Econet hardware is present. Non-zero reads indicate bus noise from absent hardware; sets bit 7 of per-ROM workspace as a disable flag. For services < &80, the flag causes an early return (disabling this ROM). Services >= &80 (&FE, &FF) are always handled regardless of flag. Intercepts three service calls before normal dispatch: &FE: Tube init — explode character definitions &FF: Full init — vector setup, copy code to RAM, select NFS &12 (Y=5): Select NFS as active filing system All other service calls < &0D dispatch via c8146. Probes ADLC status registers SR1 (&FEA0) and SR2 (&FEA1) to detect whether Econet hardware is present. Sets bit 7 of per-ROM workspace as a disable flag if not found. The 9 NOPs at &80F7 provide bus settling time after register access. |
|
| 8123 | .check_svc_high←1← 811F BMI |
| .service_handler_entry←1← 811F BMI | |
| CMP #&fe ; Service >= &FE? | |
| 8125 | BCC check_svc_12 ; Service < &FE: skip to &12/dispatch check |
| 8127 | BNE init_vectors_and_copy ; Service &FF: full init (vectors + RAM copy) |
| 8129 | CPY #0 ; Service &FE: Y=0? |
| 812B | BEQ check_svc_12 ; Y=0: no Tube data, skip to &12 check |
| 812D | LDX #6 ; X=6 extra pages for char definitions |
| 812F | LDA #osbyte_explode_chars ; OSBYTE &14: explode character RAM |
| 8131 | JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6) |
| 8134 | .poll_tube_ready←2← 8137 BPL← 8141 JMP |
| BIT tube_status_1_and_tube_control ; Poll Tube status register 1 | |
| 8137 | BPL poll_tube_ready ; Loop until Tube ready (bit 7 set) |
| 8139 | LDA tube_data_register_1 ; Read byte from Tube data register 1 |
| 813C | BEQ tube_chars_done ; Zero byte: Tube transfer complete |
| 813E | JSR oswrch ; Send Tube char to screen via OSWRCH Write character |
| 8141 | JMP poll_tube_ready ; Loop for next Tube byte |
| 8144 | .init_vectors_and_copy←1← 8127 BNE |
| LDA #&ad ; EVNTV low = &AD (event handler address) | |
| 8146 | STA evntv ; Set EVNTV low byte at &0220 |
| 8149 | LDA #6 ; EVNTV high = &06 (page 6) |
| 814B | STA evntv+1 ; Set EVNTV high byte at &0221 |
| 814E | LDA #&16 ; BRKV low = &16 (NMI workspace) |
| 8150 | STA brkv ; Set BRKV low byte at &0202 |
| 8153 | LDA #0 ; BRKV high = &00 (zero page) |
| 8155 | STA brkv+1 ; Set BRKV high byte at &0203 |
| 8158 | LDA #&8e ; Tube control register init value &8E |
| 815A | STA tube_status_1_and_tube_control ; Write to Tube control register |
| 815D | LDY #0 ; Y=0: copy 256 bytes per page |
| ; Copy NMI handler code from ROM to RAM pages &04-&06 | |
| 815F | .cloop←1← 8172 BNE |
| LDA reloc_p4_src,y ; Load ROM byte from page &93 | |
| 8162 | STA tube_code_page4,y ; Store to page &04 (Tube code) |
| 8165 | LDA reloc_p5_src,y ; Load ROM byte from page &94 |
| 8168 | STA tube_dispatch_table,y ; Store to page &05 (dispatch table) |
| 816B | LDA reloc_p6_src,y ; Load ROM byte from page &95 |
| 816E | STA tube_page6_start,y ; Store to page &06 |
| 8171 | DEY ; DEY wraps 0 -> &FF on first iteration |
| 8172 | BNE cloop ; Loop until 256 bytes copied per page |
| 8174 | JSR tube_post_init ; Run post-init routine in copied code |
| 8177 | LDX #&60 ; X=&60: copy 97 bytes (&60..&00) |
| ; Copy NMI workspace initialiser from ROM to &0016-&0076 | |
| 8179 | .copy_nmi_workspace←1← 817F BPL |
| LDA reloc_zp_src,x ; Load NMI workspace init byte from ROM | |
| 817C | STA nmi_workspace_start,x ; Store to zero page &16+X |
| 817E | DEX ; Next byte |
| 817F | BPL copy_nmi_workspace ; Loop until all workspace bytes copied |
| 8181 | .tube_chars_done←1← 813C BEQ |
| LDA #0 ; A=0: fall through to service &12 check | |
| 8183 | .check_svc_12←2← 8125 BCC← 812B BEQ |
| CMP #&12 ; Is this service &12 (select FS)? | |
| 8185 | BNE not_svc_12_nfs ; No: check if service < &0D |
| 8187 | CPY #5 ; Service &12: Y=5 (NFS)? |
| 8189 | BNE not_svc_12_nfs ; Not NFS: check if service < &0D |
| 818B | LDA #&0d ; A=&0D: dispatch index for svc_13_select_nfs |
| 818D | BNE do_svc_dispatch ; ALWAYS branch to dispatch ALWAYS branch |
| 818F | .not_svc_12_nfs←2← 8185 BNE← 8189 BNE |
| CMP #&0d ; Service >= &0D? | |
| 8191 | .svc_unhandled_return←1← 8121 BCS |
| BCS return_2 ; Service >= &0D: not handled, return | |
| 8193 | .do_svc_dispatch←1← 818D BNE |
| TAX ; X = service number (dispatch index) | |
| 8194 | LDA svc_state ; Save &A9 (current service state) |
| 8196 | PHA ; Push saved &A9 |
| 8197 | LDA ws_page ; Save &A8 (workspace page number) |
| 8199 | PHA ; Push saved &A8 |
| 819A | STX svc_state ; Store service number to &A9 |
| 819C | STY ws_page ; Store Y (page number) to &A8 |
| 819E | TYA ; A = Y for dispatch table offset |
| 819F | LDY #0 ; Y=0: base offset for service dispatch |
| 81A1 | JSR dispatch ; Dispatch to service handler |
| 81A4 | LDX svc_state ; Recover service claim status from &A9 |
| 81A6 | PLA ; Restore saved &A8 from stack |
| 81A7 | STA ws_page ; Write back &A8 |
| fall through ↓ | |
Service 4: unrecognised * commandThe first 5 bytes (&81A9-&81AF) are the service handler epilogue: PLA/STA restores &A9, TXA/LDX retrieves romsel_copy, then RTS. This is the common return path reached after any dispatched service handler completes. The service 4 handler entry at &81B5 (after 5 NOPs of padding) makes two match_rom_string calls against the ROM header, reusing header bytes as command strings: X=&0C: matches "ROFF" at copyright_string+3 — the
suffix of "(C)ROFF" — *ROFF (Remote Off, end
remote session) — falls through to net_4_resume_remote
X=5: matches "NET" at the ROM title suffix
— *NET (select NFS) — falls through to svc_13_select_nfs
If neither matches, returns with the service call unclaimed. |
|
| 81A9 | .svc_star_command |
| PLA ; Restore saved A from service dispatch | |
| 81AA | STA svc_state ; Save to workspace &A9 |
| 81AC | TXA ; Return ROM number in A |
| 81AD | LDX romsel_copy ; Restore X from MOS ROM select copy |
| 81AF | .return_2←1← 8191 BCS |
| RTS ; Return to MOS service handler | |
| 81B0 | NOP ; Padding: dispatch targets &81B5 |
| 81B1 | NOP ; NOP padding for command table |
| 81B2 | NOP ; NOP padding |
| 81B3 | NOP ; NOP padding |
| 81B4 | NOP ; NOP padding |
| 81B5 | .svc_4_star_command |
| LDX #&0c ; ROM offset for "ROFF" (copyright suffix) | |
| 81B7 | JSR match_rom_string ; Try matching *ROFF command |
| 81BA | BNE match_net_cmd ; No match: try *NET |
| fall through ↓ | |
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. |
|
| 81BC | .net_4_resume_remote |
| LDY #4 ; Y=4: offset of keyboard disable flag | |
| 81BE | LDA (net_rx_ptr),y ; Read flag from RX buffer |
| 81C0 | BEQ skip_kbd_reenable ; Zero: keyboard not disabled, skip |
| 81C2 | LDA #0 ; A=0: value to clear flag and re-enable |
| 81C4 | TAX ; X=&00 |
| 81C5 | STA (net_rx_ptr),y ; Clear keyboard disable flag in buffer |
| 81C7 | TAY ; Y=&00 |
| 81C8 | LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable |
| 81CA | JSR osbyte ; Re-enable keyboard (X=0, Y=0) Enable keyboard (for Econet) |
| 81CD | LDA #&0a ; Function &0A: remote operation complete |
| 81CF | JSR setup_tx_and_send ; Send notification to controlling station |
| 81D2 | .clear_osbyte_ce_cf←1← 84CE JSR |
| STX nfs_workspace ; Save X (return value from TX) | |
| 81D4 | LDA #&ce ; OSBYTE &CE: first system mask to reset |
| 81D6 | .clear_osbyte_masks←1← 81E1 BEQ |
| LDX nfs_workspace ; Restore X for OSBYTE call | |
| 81D8 | LDY #&7f ; Y=&7F: AND mask (clear bit 7) |
| 81DA | JSR osbyte ; Reset system mask byte |
| 81DD | ADC #1 ; Advance to next OSBYTE (&CE -> &CF) |
| 81DF | CMP #&d0 ; Reached &D0? (past &CF) |
| 81E1 | .cmd_name_matched |
| BEQ clear_osbyte_masks ; No: reset &CF too | |
| 81E3 | .skip_kbd_reenable←1← 81C0 BEQ |
| LDA #0 ; A=0: clear remote state | |
| 81E5 | STA svc_state ; Clear &A9 (service dispatch state) |
| 81E7 | .skpspi |
| STA nfs_workspace ; Clear workspace byte | |
| 81E9 | RTS ; Return |
| 81EA | .match_net_cmd←1← 81BA BNE |
| LDX #5 ; X=5: ROM offset for "NET" match | |
| 81EC | JSR match_rom_string ; Try matching *NET command |
| 81EF | BNE restore_ws_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. |
|
| 81F1 | .svc_13_select_nfs |
| JSR call_fscv_shutdown ; Notify current FS of shutdown (FSCV A=6) | |
| 81F4 | SEC ; C=1 for ROR |
| 81F5 | ROR ws_page ; Set bit 7 of l00a8 (inhibit auto-boot) |
| 81F7 | JSR issue_vectors_claimed ; Claim OS vectors, issue service &0F |
| 81FA | LDY #&1d ; Y=&1D: top of FS state range |
| 81FC | .initl←1← 8204 BNE |
| LDA (net_rx_ptr),y ; Copy FS state from RX buffer... | |
| 81FE | STA fs_state_deb,y ; ...to workspace (offsets &15-&1D) |
| 8201 | DEY ; Next byte (descending) |
| 8202 | CPY #&14 ; Loop until offset &14 done |
| 8204 | BNE initl ; Continue loop |
| 8206 | BEQ init_fs_vectors ; ALWAYS branch to init_fs_vectors ALWAYS branch |
Service 9: *HELPPrints the ROM identification string using print_inline.
|
||||
| 8208 | .svc_9_help | |||
| JSR print_inline ; Print ROM identification string | ||||
| 820B | EQUS ".NFS 3.62." ; Notify current FS of shutdown | |||
| 8215 | .restore_ws_return←2← 81EF BNE← 822A BNE | |||
| LDY ws_page ; Restore Y (workspace page number) | ||||
| 8217 | 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). |
|
| 8218 | .call_fscv_shutdown←2← 81F1 JSR← 821D JSR |
| LDA #6 ; FSCV reason 6 = FS shutdown | |
| 821A | 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 no key is pressed, auto-boot proceeds directly via print_station_info. If a key is pressed, falls through to check_boot_key: the 'N' key (matrix address &55) proceeds with auto-boot, any other key causes the auto-boot to be declined. |
|
| 821D | .svc_3_autoboot |
| JSR call_fscv_shutdown ; Notify current FS of shutdown | |
| 8220 | LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard |
| 8222 | JSR osbyte ; Keyboard scan starting from key 16 |
| 8225 | TXA ; X is key number if key is pressed, or &ff otherwise |
| 8226 | 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. |
|
| 8228 | .check_boot_key |
| EOR #&55 ; XOR with &55: result=0 if key is 'N' | |
| 822A | BNE restore_ws_return ; Not 'N': return without claiming |
| 822C | TAY ; Y=key |
| 822D | LDA #osbyte_write_keys_pressed ; OSBYTE &78: clear key-pressed state |
| 822F | 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. |
|
| 8232 | .print_station_info←1← 8226 BMI |
| JSR print_inline ; Print 'Econet Station ' banner | |
| 8235 | EQUS "Econet Station " ; Inline string "Econet Station " |
| 8244 | LDY #&14 ; Y=&14: station number offset in RX buf |
| 8246 | LDA (net_rx_ptr),y ; Load station number |
| 8248 | JSR print_decimal ; Print as 3-digit decimal |
| 824B | LDA #&20 ; BIT trick: bit 5 of SR2 = clock present |
| 824D | BIT econet_control23_or_status2 ; Test DCD: clock present if bit 5 clear |
| 8250 | .dofsl1 |
| BEQ skip_no_clock_msg ; Clock present: skip warning | |
| 8252 | JSR print_inline ; Print ' No Clock' warning |
| 8255 | EQUS " No Clock" ; Inline string " No Clock" |
| 825E | NOP ; NOP (padding after inline string) |
| 825F | .skip_no_clock_msg←1← 8250 BEQ |
| JSR print_inline ; Print two CRs (blank line) | |
| 8262 | EQUS ".." ; 7 FS vectors to install |
| fall through ↓ | |
Initialise filing system vectorsCopies 14 bytes from l829a (&829A) 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 svc_13_select_nfs, bypassing the station display. Falls through to issue_vectors_claimed. |
|
| 8264 | .init_fs_vectors←1← 8206 BEQ |
| LDY #&0d ; Copy 14 bytes: FS vector addresses to FILEV-FSCV | |
| 8266 | .copy_fs_vectors←1← 826D BPL |
| LDA fs_dispatch_addrs,y ; Load extended vector dispatch address | |
| 8269 | STA filev,y ; Write to FILEV-FSCV vector table |
| 826C | DEY ; Next byte (descending) |
| 826D | BPL copy_fs_vectors ; Loop until all 14 bytes copied |
| 826F | JSR setup_rom_ptrs_netv ; Read ROM ptr table addr, install NETV |
| 8272 | LDY #&1b ; Install 7 handler entries in ROM ptr table |
| 8274 | LDX #7 ; 7 FS vectors to install |
| 8276 | JSR store_rom_ptr_pair ; Install each 3-byte vector entry |
| 8279 | STX svc_state ; 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 l00a8 is zero (soft break — RXCBs already initialised), sets up the command string "I .BOOT" at &828E and jumps to the FSCV 3 unrecognised-command handler (which matches against the command table at &8C4B). The "I." prefix triggers the catch-all entry which forwards the command to the fileserver. Falls through to run_fscv_cmd. |
|
| 827B | .issue_vectors_claimed←1← 81F7 JSR |
| LDA #osbyte_issue_service_request ; A=&8F: issue service request | |
| 827D | LDX #&0f ; X=&0F: 'vectors claimed' service |
| 827F | JSR osbyte ; Issue paged ROM service call, Reason X=15 - Vectors claimed |
| 8282 | LDX #&0a ; X=&0A: service &0A |
| 8284 | JSR osbyte ; Issue service &0A |
| 8287 | LDX ws_page ; Non-zero after hard reset: skip auto-boot |
| 8289 | BNE return_3 ; Non-zero: skip auto-boot |
| 828B | LDX #&92 ; X = lo byte of auto-boot string (run_fscv_cmd+5) |
| 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. |
|
| 828D | .run_fscv_cmd←2← 8339 LDA← 833F LDA |
| EQUB &A0 ; Y=&82: ROM page high byte | |
| ; 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. | |
| 828E | EQUS ".L..I .B" ; Execute command string at (X, Y) |
| 8296 | EQUB &4F, &4F ; 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 not read at runtime (store_rom_ptr_pair writes the current ROM bank number without reading). The last entry (FSCV) has no padding byte. |
|
| 8298 | .fs_vector_addrs |
| EQUB &54, &0D ; Auto-boot string tail / NETV handler data | |
| 829A | .fs_dispatch_addrs←1← 8266 LDA |
| EQUW &FF1B ; FILEV dispatch (&FF1B) | |
| 829C | EQUW &FF1E ; ARGSV dispatch (&FF1E) ARGSV dispatch lo |
| 829E | EQUW &FF21 ; BGETV dispatch (&FF21) BGETV dispatch hi |
| 82A0 | EQUW &FF24 ; BPUTV dispatch (&FF24) BPUTV dispatch lo |
| 82A2 | EQUW &FF27 ; GBPBV dispatch (&FF27) GBPBV dispatch lo GBPBV dispatch hi |
| 82A4 | EQUW &FF2A ; FINDV dispatch (&FF2A) FINDV dispatch lo FINDV dispatch hi |
| 82A6 | EQUW &FF2D ; FSCV dispatch (&FF2D) FSCV dispatch lo |
| 82A8 | EQUW &870C ; FILEV handler (&870C) |
| 82AA | EQUB &4A ; (ROM bank — not read) |
| 82AB | EQUW &8968 ; ARGSV handler (&8968) |
| 82AD | EQUB &44 ; (ROM bank — not read) |
| 82AE | EQUW &8563 ; BGETV handler (&8563) |
| 82B0 | EQUB &57 ; (ROM bank — not read) |
| 82B1 | EQUW &8413 ; BPUTV handler (&8413) |
| 82B3 | EQUB &42 ; (ROM bank — not read) |
| 82B4 | EQUW &8A72 ; GBPBV handler (&8A72) |
| 82B6 | EQUB &41 ; (ROM bank — not read) |
| 82B7 | EQUW &89D8 ; FINDV handler (&89D8) |
| 82B9 | EQUB &52 ; (ROM bank — not read) |
| 82BA | EQUW &80D4 ; FSCV handler (&80D4) 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.
|
|||||||
| 82BC | .svc_1_abs_workspace | ||||||
| CPY #&10 ; Already at page &10 or above? | |||||||
| 82BE | BCS return_3 ; Yes: nothing to claim | ||||||
| 82C0 | LDY #&10 ; Claim pages &0D-&0F (3 pages) | ||||||
| 82C2 | .return_3←2← 8289 BNE← 82BE BCS | ||||||
| RTS ; Return (workspace claim done) | |||||||
| 82C3 | EQUB &80, &90 ; FS page hi:lo for workspace pointer | ||||||
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 (no server selected) - Sets printer server station to &FE (no server selected) - 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).
|
|||||||
| 82C5 | .svc_2_private_workspace | ||||||
| STY net_rx_ptr_hi ; RX buffer page = first claimed page | |||||||
| 82C7 | INY ; Advance to next page | ||||||
| 82C8 | STY nfs_workspace_hi ; Workspace page = second claimed page | ||||||
| 82CA | LDA #0 ; A=0 for clearing workspace | ||||||
| 82CC | LDY #4 ; Y=4: remote status offset | ||||||
| 82CE | STA (net_rx_ptr),y ; Clear status byte in net receive buffer | ||||||
| 82D0 | LDY #&ff ; Y=&FF: used for later iteration | ||||||
| 82D2 | STA net_rx_ptr ; Clear RX ptr low byte | ||||||
| 82D4 | STA nfs_workspace ; Clear workspace ptr low byte | ||||||
| 82D6 | STA ws_page ; Clear RXCB iteration counter | ||||||
| 82D8 | STA tx_clear_flag ; Clear TX semaphore (no TX in progress) | ||||||
| 82DB | TAX ; X=0 for OSBYTE X=&00 | ||||||
| 82DC | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read type of last reset | ||||||
| 82DE | JSR osbyte ; Read type of last reset | ||||||
| 82E1 | TXA ; X = break type from OSBYTE result X=value of type of last reset | ||||||
| 82E2 | BEQ read_station_id ; Soft break (X=0): skip FS init | ||||||
| 82E4 | LDY #&15 ; Y=&15: printer station offset in RX buffer | ||||||
| 82E6 | LDA #&fe ; &FE = no server selected | ||||||
| 82E8 | STA fs_server_stn ; Station &FE = no server selected | ||||||
| 82EB | STA (net_rx_ptr),y ; Store &FE at printer station offset | ||||||
| 82ED | LDA #0 ; A=0 for clearing workspace fields | ||||||
| 82EF | STA fs_server_net ; Clear network number | ||||||
| 82F2 | STA prot_status ; Clear protection status | ||||||
| 82F5 | STA fs_messages_flag ; Clear message flag | ||||||
| 82F8 | STA fs_boot_option ; Clear boot option | ||||||
| 82FB | INY ; Y=&16 | ||||||
| 82FC | STA (net_rx_ptr),y ; Clear net number at RX buffer offset &16 | ||||||
| 82FE | LDY #3 ; Init printer server: station &FE, net 0 | ||||||
| 8300 | STA (nfs_workspace),y ; Store net 0 at workspace offset 3 | ||||||
| 8302 | DEY ; Y=2: printer station offset Y=&02 | ||||||
| 8303 | LDA #&fe ; &FE = no printer server | ||||||
| 8305 | STA (nfs_workspace),y ; Store &FE at printer station in workspace | ||||||
| 8307 | .init_rxcb_entries←1← 8314 BNE | ||||||
| LDA ws_page ; Load RXCB counter | |||||||
| 8309 | JSR calc_handle_offset ; Convert to workspace byte offset | ||||||
| 830C | BCS read_station_id ; C=1: past max handles, done | ||||||
| 830E | LDA #&3f ; Mark RXCB as available | ||||||
| 8310 | STA (nfs_workspace),y ; Write &3F flag to workspace | ||||||
| 8312 | INC ws_page ; Next RXCB number | ||||||
| 8314 | BNE init_rxcb_entries ; Loop for all RXCBs | ||||||
| 8316 | .read_station_id←2← 82E2 BEQ← 830C BCS | ||||||
| LDA station_id_disable_net_nmis ; Read station ID (also INTOFF) | |||||||
| 8319 | LDY #&14 ; Y=&14: station ID offset in RX buffer | ||||||
| 831B | STA (net_rx_ptr),y ; Store our station number | ||||||
| 831D | JSR init_adlc_hw ; Initialise ADLC hardware | ||||||
| 8320 | LDA #&40 ; Enable user-level RX (LFLAG=&40) | ||||||
| 8322 | STA rx_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=&9080, rom=current) into the ROM pointer table at offset &36, installing osword_dispatch as the NETV handler. |
|
| 8325 | .setup_rom_ptrs_netv←1← 826F JSR |
| LDA #osbyte_read_rom_ptr_table_low ; OSBYTE &A8: read ROM pointer table address | |
| 8327 | LDX #0 ; X=0: read low byte |
| 8329 | LDY #&ff ; Y=&FF: read high byte |
| 832B | JSR osbyte ; Returns table address in X (lo) Y (hi) Read address of ROM pointer table |
| 832E | STX osrdsc_ptr ; Store table base address low byte X=value of address of ROM pointer table (low byte) |
| 8330 | STY osrdsc_ptr_hi ; Store table base address high byte Y=value of address of ROM pointer table (high byte) |
| 8332 | LDY #&36 ; NETV extended vector offset in ROM ptr table |
| 8334 | STY netv ; Set NETV low byte = &36 (vector dispatch) |
| 8337 | LDX #1 ; Install 1 entry (NETV) in ROM ptr table |
| 8339 | .store_rom_ptr_pair←2← 8276 JSR← 834B BNE |
| LDA run_fscv_cmd,y ; Load handler address low byte from table | |
| 833C | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 833E | INY ; Next byte |
| 833F | LDA run_fscv_cmd,y ; Load handler address high byte from table |
| 8342 | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 8344 | INY ; Next byte |
| 8345 | LDA romsel_copy ; Write current ROM bank number |
| 8347 | STA (osrdsc_ptr),y ; Store ROM number to ROM pointer table |
| 8349 | INY ; Advance to next entry position |
| 834A | DEX ; Count down entries |
| 834B | BNE store_rom_ptr_pair ; Loop until all entries installed |
| 834D | LDY nfs_workspace_hi ; Y = workspace high byte + 1 = next free page |
| 834F | INY ; Advance past workspace page |
| 8350 | 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 &77 (close SPOOL/EXEC files) to release the Econet network printer on FS switch. |
|
| 8351 | .fscv_6_shutdown |
| LDY #&1d ; Copy 10 bytes: FS state to workspace backup | |
| 8353 | .fsdiel←1← 835B BNE |
| LDA fs_state_deb,y ; Load FS state byte at offset Y | |
| 8356 | STA (net_rx_ptr),y ; Store to workspace backup area |
| 8358 | DEY ; Next byte down |
| 8359 | CPY #&14 ; Offsets &15-&1D: server, handles, OPT, etc. |
| 835B | BNE fsdiel ; Loop for offsets &1D..&15 |
| 835D | LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec |
| 835F | JMP osbyte ; Close any *SPOOL and *EXEC files |
| 8362 | .match_rom_string←2← 81B7 JSR← 81EC JSR |
| LDY ws_page ; Y = saved text pointer offset | |
| 8364 | .match_cmd_chars←1← 8375 BNE |
| LDA (os_text_ptr),y ; Load next input character | |
| 8366 | CMP #&2e ; Is it a '.' (abbreviation)? |
| 8368 | BEQ skip_space_next ; Yes: skip to space skipper (match) |
| 836A | AND #&df ; Force uppercase (clear bit 5) |
| 836C | BEQ check_rom_end ; Input char is NUL/space: check ROM byte |
| 836E | CMP binary_version,x ; Compare with ROM string byte |
| 8371 | BNE check_rom_end ; Mismatch: check if ROM string ended |
| 8373 | INY ; Advance input pointer |
| 8374 | INX ; Advance ROM string pointer |
| 8375 | BNE match_cmd_chars ; Continue matching (always taken) |
| 8377 | .check_rom_end←2← 836C BEQ← 8371 BNE |
| LDA binary_version,x ; Load ROM string byte at match point | |
| 837A | BEQ skip_spaces ; Zero = end of ROM string = full match |
| 837C | RTS ; Non-zero = partial/no match; Z=0 |
| 837D | .skip_space_next←2← 8368 BEQ← 8382 BEQ |
| INY ; Skip this space | |
| fall through ↓ | |
Skip spaces and test for end of lineAdvances Y past leading spaces in the text at (os_text_ptr),Y. Returns Z=1 if the next non-space character is CR (end of line), Z=0 otherwise with A holding the character.
|
|||||||||||
| 837E | .skip_spaces←2← 837A BEQ← 8DED JSR | ||||||||||
| LDA (os_text_ptr),y ; Load next input character | |||||||||||
| 8380 | CMP #&20 ; Is it a space? | ||||||||||
| 8382 | BEQ skip_space_next ; Yes: keep skipping | ||||||||||
| 8384 | EOR #&0d ; XOR with CR: Z=1 if end of line | ||||||||||
| 8386 | RTS ; Return with Z flag result | ||||||||||
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. |
|
| 8387 | .init_tx_reply_port←1← 83F4 JSR |
| LDA #&90 ; A=&90: FS reply port (PREPLY) | |
| 8389 | .init_tx_ctrl_port←1← 889C JSR |
| JSR init_tx_ctrl_block ; Init TXCB from template | |
| 838C | STA txcb_port ; Store port number in TXCB |
| 838E | LDA #3 ; Control byte: 3 = transmit |
| 8390 | STA txcb_start ; Store control byte in TXCB |
| 8392 | DEC txcb_ctrl ; Decrement TXCB flag to arm TX |
| 8394 | RTS ; Return after port setup |
Initialise TX control block at &00C0 from templateCopies 12 bytes from tx_ctrl_template (&83AD) to &00C0. For the first 2 bytes (Y=0,1), also copies the fileserver station/network from &0E00/&0E01 to &00C2/&00C3. The template sets up: control=&80, port=&99 (FS command port), command data length=&0F, plus padding bytes.
|
||||||
| 8395 | .init_tx_ctrl_block←4← 8389 JSR← 83E3 JSR← 842F JSR← 8FF6 LDA | |||||
| PHA ; Preserve A across call | ||||||
| 8396 | LDY #&0b ; Copy 12 bytes (Y=11..0) | |||||
| 8398 | .fstxl1←1← 83A9 BPL | |||||
| LDA tx_ctrl_template,y ; Load template byte | ||||||
| 839B | STA txcb_ctrl,y ; Store to TX control block at &00C0 | |||||
| 839E | CPY #2 ; Y < 2: also copy FS server station/network | |||||
| 83A0 | BPL fstxl2 ; Skip station/network copy for Y >= 2 | |||||
| 83A2 | LDA fs_server_stn,y ; Load FS server station (Y=0) or network (Y=1) | |||||
| 83A5 | STA txcb_dest,y ; Store to dest station/network at &00C2 | |||||
| 83A8 | .fstxl2←1← 83A0 BPL | |||||
| DEY ; Next byte (descending) | ||||||
| 83A9 | BPL fstxl1 ; Loop until all 12 bytes copied | |||||
| 83AB | PLA ; Restore A | |||||
| 83AC | RTS ; Return | |||||
| 83AD | .tx_ctrl_template←1← 8398 LDA |
| EQUB &80, &99, ; Control flag Port (FS command = &99) &00, &00, ; Buffer start low Buffer start high (page &00, &0F ; &0F) | |
| 83B3 | .tx_ctrl_upper←3← 891C BIT← 89FB BIT← 9183 BIT |
| EQUB &FF, &FF, ; Buffer start pad (4-byte Econet addr) &FF, &0F, ; Buffer start pad Buffer end low Buffer end &FF, &FF ; high (page &0F) Buffer end pad 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.
|
||||||
| 83B9 | .prepare_cmd_with_flag←1← 8AC3 JSR | |||||
| PHA ; Save flag byte for command | ||||||
| 83BA | SEC ; C=1: include flag in FS command | |||||
| 83BB | BCS store_fs_hdr_fn ; ALWAYS branch to prepare_fs_cmd ALWAYS branch | |||||
| 83BD | .prepare_cmd_clv←2← 8729 JSR← 87CF JSR | |||||
| CLV ; V=0: command has no flag byte | ||||||
| 83BE | 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 &8C4B for "BYE". |
|
| 83C0 | .bye_handler |
| LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec | |
| 83C2 | JSR osbyte ; Close any *SPOOL and *EXEC files |
| 83C5 | 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.
|
|||||||||||||
| 83C7 | .prepare_fs_cmd←12← 80C5 JSR← 88C1 JSR← 8937 JSR← 8983 JSR← 89AA JSR← 8A21 JSR← 8A48 JSR← 8B1E JSR← 8BD9 JSR← 8C85 JSR← 8CBC JSR← 8D27 JSR | ||||||||||||
| CLV ; V=0: standard FS command path | |||||||||||||
| 83C8 | .init_tx_ctrl_data←2← 891F JSR← 89FE JSR | ||||||||||||
| .prepare_fs_cmd_v←2← 891F JSR← 89FE JSR | |||||||||||||
| LDA fs_urd_handle ; Copy URD handle from workspace to buffer | |||||||||||||
| 83CB | STA fs_cmd_urd ; Store URD at &0F02 | ||||||||||||
| 83CE | .store_fs_hdr_clc←1← 83BE BVC | ||||||||||||
| CLC ; CLC: no byte-stream path | |||||||||||||
| 83CF | .store_fs_hdr_fn←1← 83BB BCS | ||||||||||||
| STY fs_cmd_y_param ; Store function code at &0F01 | |||||||||||||
| 83D2 | LDY #1 ; Y=1: copy CSD (offset 1) then LIB (offset 0) | ||||||||||||
| 83D4 | .copy_dir_handles←1← 83DB BPL | ||||||||||||
| LDA fs_csd_handle,y ; Copy CSD and LIB handles to command buffer | |||||||||||||
| 83D7 | STA fs_cmd_csd,y ; Store at &0F03 (CSD) and &0F04 (LIB) | ||||||||||||
| 83DA | DEY ; Y=function code | ||||||||||||
| 83DB | 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") 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.
|
|||||||||||||||
| 83DD | .build_send_fs_cmd←1← 8B77 JSR | ||||||||||||||
| PHP ; Save carry (FS path vs byte-stream) | |||||||||||||||
| 83DE | LDA #&90 ; Reply port &90 (PREPLY) | ||||||||||||||
| 83E0 | STA fs_cmd_type ; Store at &0F00 (HDRREP) | ||||||||||||||
| 83E3 | JSR init_tx_ctrl_block ; Copy TX template to &00C0 | ||||||||||||||
| 83E6 | TXA ; A = X (buffer extent) | ||||||||||||||
| 83E7 | ADC #5 ; HPTR = header (5) + data (X) bytes to send | ||||||||||||||
| 83E9 | STA txcb_end ; Store to TXCB end-pointer low | ||||||||||||||
| 83EB | PLP ; Restore carry flag | ||||||||||||||
| 83EC | BCS dofsl5 ; C=1: byte-stream path (BSXMIT) | ||||||||||||||
| 83EE | PHP ; Save flags for send_fs_reply_cmd | ||||||||||||||
| 83EF | JSR setup_tx_ptr_c0 ; Point net_tx_ptr to &00C0; transmit | ||||||||||||||
| 83F2 | PLP ; Restore flags | ||||||||||||||
| 83F3 | .send_fs_reply_cmd←2← 87DC JSR← 8AFB JSR | ||||||||||||||
| PHP ; Save flags (V flag state) | |||||||||||||||
| 83F4 | JSR init_tx_reply_port ; Set up RX wait for FS reply | ||||||||||||||
| 83F7 | JSR waitfs ; Transmit and wait (BRIANX) | ||||||||||||||
| 83FA | PLP ; Restore flags | ||||||||||||||
| 83FB | .dofsl7←1← 8411 BCC | ||||||||||||||
| INY ; Y=1: skip past command code byte | |||||||||||||||
| 83FC | LDA (txcb_start),y ; Load return code from FS reply | ||||||||||||||
| 83FE | TAX ; X = return code | ||||||||||||||
| 83FF | BEQ return_dofsl7 ; Zero: success, return | ||||||||||||||
| 8401 | BVC check_fs_error ; V=0: standard path, error is fatal | ||||||||||||||
| 8403 | ADC #&2a ; ADC #&2A: test for &D6 (not found) | ||||||||||||||
| 8405 | .check_fs_error←1← 8401 BVC | ||||||||||||||
| BNE store_fs_error ; Non-zero: hard error, go to FSERR | |||||||||||||||
| 8407 | .return_dofsl7←1← 83FF BEQ | ||||||||||||||
| RTS ; Return (success or soft &D6 error) | |||||||||||||||
| 8408 | .dofsl5←1← 83EC BCS | ||||||||||||||
| PLA ; Discard saved flags from stack | |||||||||||||||
| 8409 | LDX #&c0 ; X=&C0: TXCB address for byte-stream TX | ||||||||||||||
| 840B | INY ; Y++ past command code | ||||||||||||||
| 840C | JSR econet_tx_retry ; Byte-stream transmit with retry | ||||||||||||||
| 840F | STA fs_load_addr_3 ; Store result to &B3 | ||||||||||||||
| 8411 | BCC dofsl7 ; C=0: success, check reply code | ||||||||||||||
| 8413 | .bputv_handler | ||||||||||||||
| CLC ; CLC for address addition | |||||||||||||||
| fall through ↓ | |||||||||||||||
BGETV entry pointClears the escapable flag via clear_escapable, then falls through to handle_bput_bget with carry set (SEC by caller) to indicate a BGET operation.
|
|||||||||||
| 8414 | .bgetv_entry←1← 8564 JSR | ||||||||||
| JSR clear_escapable ; Clear escapable flag before BGET | |||||||||||
| fall through ↓ | |||||||||||
Handle BPUT/BGET file byte I/OBPUTV enters at &8413 (CLC; fall through) and BGETV enters at &8563 (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.
|
|||||||||||||||
| 8417 | .handle_bput_bget | ||||||||||||||
| PHA ; Save A (BPUT byte) on stack | |||||||||||||||
| 8418 | STA fs_error_flags ; Also save byte at &0FDF for BSXMIT | ||||||||||||||
| 841B | TXA ; Transfer X for stack save | ||||||||||||||
| 841C | PHA ; Save X on stack | ||||||||||||||
| 841D | TYA ; Transfer Y (handle) for stack save | ||||||||||||||
| 841E | PHA ; Save Y (handle) on stack | ||||||||||||||
| 841F | PHP ; Save P (C = BPUT/BGET selector) on stack | ||||||||||||||
| 8420 | STY fs_spool_handle ; Save handle for SPOOL/EXEC comparison later | ||||||||||||||
| 8422 | JSR handle_to_mask_clc ; Convert handle Y to single-bit mask | ||||||||||||||
| 8425 | STY fs_handle_mask ; Store handle bitmask at &0FDE | ||||||||||||||
| 8428 | STY fs_spool0 ; Store handle bitmask for sequence tracking | ||||||||||||||
| 842A | LDY #&90 ; &90 = data port (PREPLY) | ||||||||||||||
| 842C | STY fs_putb_buf ; Store reply port in command buffer | ||||||||||||||
| 842F | JSR init_tx_ctrl_block ; Set up 12-byte TXCB from template | ||||||||||||||
| 8432 | LDA #&dc ; CB reply buffer at &0FDC | ||||||||||||||
| 8434 | STA txcb_start ; Store reply buffer ptr low in TXCB | ||||||||||||||
| 8436 | LDA #&e0 ; Error buffer at &0FE0 | ||||||||||||||
| 8438 | STA txcb_end ; Store error buffer ptr low in TXCB | ||||||||||||||
| 843A | INY ; Y=1 (from init_tx_ctrl_block exit) | ||||||||||||||
| 843B | LDX #9 ; X=9: BPUT function code | ||||||||||||||
| 843D | PLP ; Restore C: selects BPUT (0) vs BGET (1) | ||||||||||||||
| 843E | BCC store_retry_count ; C=0 (BPUT): keep X=9 | ||||||||||||||
| 8440 | DEX ; X=&08 | ||||||||||||||
| 8441 | .store_retry_count←1← 843E BCC | ||||||||||||||
| STX fs_getb_buf ; Store function code at &0FDD | |||||||||||||||
| 8444 | LDA fs_spool0 ; Load handle bitmask for BSXMIT | ||||||||||||||
| 8446 | LDX #&c0 ; X=&C0: TXCB address for econet_tx_retry | ||||||||||||||
| 8448 | JSR econet_tx_retry ; Transmit via byte-stream protocol | ||||||||||||||
| 844B | LDX fs_getb_buf ; Load reply byte from buffer | ||||||||||||||
| 844E | BEQ update_sequence_return ; Zero reply = success, skip error handling | ||||||||||||||
| 8450 | LDY #&1f ; Copy 32-byte reply to error buffer at &0FE0 | ||||||||||||||
| 8452 | .error1←1← 8459 BPL | ||||||||||||||
| LDA fs_putb_buf,y ; Load reply byte at offset Y | |||||||||||||||
| 8455 | STA fs_error_buf,y ; Store to error buffer at &0FE0+Y | ||||||||||||||
| 8458 | DEY ; Next byte (descending) | ||||||||||||||
| 8459 | BPL error1 ; Loop until all 32 bytes copied | ||||||||||||||
| 845B | TAX ; X=File handle | ||||||||||||||
| 845C | LDA #osbyte_read_write_exec_file_handle ; A=&C6: read *EXEC file handle | ||||||||||||||
| 845E | JSR osbyte ; Read/Write *EXEC file handle | ||||||||||||||
| 8461 | LDA #&29 ; ')': offset into "SP." string at &8529 | ||||||||||||||
| 8463 | CPY fs_spool_handle ; Y=value of *SPOOL file handle | ||||||||||||||
| 8465 | BEQ close_spool_exec ; Handle matches SPOOL -- close it | ||||||||||||||
| 8467 | LDA #&2d ; '-': offset into "E." close-exec string | ||||||||||||||
| 8469 | CPX fs_spool_handle ; X=value of *EXEC file handle | ||||||||||||||
| 846B | BNE dispatch_fs_error ; No EXEC match -- skip close | ||||||||||||||
| 846D | .close_spool_exec←1← 8465 BEQ | ||||||||||||||
| TAX ; X = string offset for OSCLI close | |||||||||||||||
| 846E | LDY #&85 ; Y=&85: high byte of OSCLI string in ROM | ||||||||||||||
| 8470 | JSR oscli ; Close SPOOL/EXEC via "*SP." or "*E." | ||||||||||||||
| 8473 | .dispatch_fs_error←1← 846B BNE | ||||||||||||||
| LDA #&e0 ; Reset CB pointer to error buffer at &0FE0 | |||||||||||||||
| 8475 | STA txcb_start ; Reset reply ptr to error buffer | ||||||||||||||
| 8477 | 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. |
|
| 847A | .store_fs_error←1← 8405 BNE |
| STX fs_last_error ; Remember raw FS error code | |
| 847D | LDY #1 ; Y=1: point to error number byte in reply |
| 847F | CPX #&a8 ; Clamp FS errors below &A8 to standard &A8 |
| 8481 | BCS error_code_clamped ; Error >= &A8: keep original value |
| 8483 | LDA #&a8 ; Error < &A8: override with standard &A8 |
| 8485 | STA (txcb_start),y ; Write clamped error number to reply buffer |
| 8487 | .error_code_clamped←1← 8481 BCS |
| LDY #&ff ; Start scanning from offset &FF (will INY to 0) | |
| 8489 | .copy_error_to_brk←1← 8491 BNE |
| INY ; Next byte in reply buffer | |
| 848A | LDA (txcb_start),y ; Copy reply buffer to &0100 for BRK execution |
| 848C | STA error_block,y ; Build BRK error block at &0100 |
| 848F | EOR #&0d ; Scan for CR terminator (&0D) |
| 8491 | BNE copy_error_to_brk ; Continue until CR found |
| 8493 | STA error_block,y ; Replace CR with zero = BRK error block end |
| 8496 | BEQ execute_downloaded ; Execute as BRK error block at &0100; ALWAYS ALWAYS branch |
| 8498 | .update_sequence_return←1← 844E BEQ |
| STA fs_sequence_nos ; Save updated sequence number | |
| 849B | PLA ; Restore Y from stack |
| 849C | TAY ; Transfer A to Y for indexing |
| 849D | PLA ; Restore X from stack |
| 849E | TAX ; Transfer to X for return |
| 849F | PLA ; Restore A from stack |
| 84A0 | .return_remote_cmd←1← 84A5 BPL |
| RTS ; Return to caller | |
Check for pending escape conditionTests bit 7 of the MOS escape flag (&FF) ANDed with the escapable flag. If no escape is pending, returns immediately. If escape is active, acknowledges it via OSBYTE &7E and jumps to the escape error handler.
|
||||
| 84A1 | .check_escape←2← 80AD JSR← 8627 JSR | |||
| LDA escape_flag ; Read escape flag from MOS workspace | ||||
| 84A3 | AND escapable ; Mask with escapable: bit 7 set if active | |||
| 84A5 | BPL return_remote_cmd ; No escape pending: return | |||
| 84A7 | LDA #osbyte_acknowledge_escape ; OSBYTE &7E: acknowledge escape condition | |||
| 84A9 | JSR osbyte ; Clear escape condition and perform escape effects | |||
| 84AC | JMP nlisne ; Report escape error via error message table | |||
Remote boot/execute handlerChecks byte 4 of the RX control block (remote status flag). If zero (not currently remoted), falls through to remot1 to set up a new remote session. If non-zero (already remoted), jumps to clear_jsr_protection and returns. |
|
| 84AF | .lang_1_remote_boot |
| LDY #4 ; Y=4: remote status flag offset | |
| 84B1 | LDA (net_rx_ptr),y ; Read remote status from RX CB |
| 84B3 | BEQ remot1 ; Zero: not remoted, set up session |
| 84B5 | .rchex←1← 84FB BNE |
| JMP clear_jsr_protection ; Already remoted: clear and return | |
| 84B8 | .remot1←2← 84B3 BEQ← 84F1 BEQ |
| ORA #9 ; Set remote status: bits 0+3 (ORA #9) | |
| 84BA | STA (net_rx_ptr),y ; Store updated remote status |
| 84BC | LDX #&80 ; X=&80: RX data area offset |
| 84BE | LDY #&80 ; Y=&80: read source station low |
| 84C0 | LDA (net_rx_ptr),y ; Read source station lo from RX data at &80 |
| 84C2 | PHA ; Save source station low byte |
| 84C3 | INY ; Y=&81 |
| 84C4 | LDA (net_rx_ptr),y ; Read source station hi from RX data at &81 |
| 84C6 | LDY #&0f ; Save controlling station to workspace &0E/&0F |
| 84C8 | STA (nfs_workspace),y ; Store station high to ws+&0F |
| 84CA | DEY ; Y=&0E Y=&0e |
| 84CB | PLA ; Restore source station low |
| 84CC | STA (nfs_workspace),y ; Store station low to ws+&0E |
| 84CE | JSR clear_osbyte_ce_cf ; Clear OSBYTE &CE/&CF flags |
| 84D1 | JSR ctrl_block_setup ; Set up TX control block |
| 84D4 | LDX #1 ; X=1: disable keyboard |
| 84D6 | LDY #0 ; Y=0 for OSBYTE |
| 84D8 | LDA #osbyte_read_write_econet_keyboard_disable ; Disable keyboard for remote session |
| 84DA | JSR osbyte ; Disable keyboard (for Econet) |
| fall through ↓ | |
| 84DD | .lang_3_execute_at_0100 |
| JSR clear_jsr_protection ; Allow JSR to page 1 (stack page) | |
| 84E0 | LDX #2 ; Zero bytes &0100-&0102 |
| 84E2 | LDA #0 ; A=0: zero execution header bytes |
| 84E4 | .zero_exec_header←1← 84E8 BPL |
| STA error_block,x ; BRK at &0100 as safe default | |
| 84E7 | DEX ; Next byte |
| 84E8 | BPL zero_exec_header ; Loop until all zeroed |
| 84EA | .execute_downloaded←2← 8496 BEQ← 8523 BEQ |
| JMP error_block ; Execute downloaded code | |
Remote operation with source validationValidates that the source station in the received packet matches the controlling station stored in the NFS workspace. If byte 4 of the RX control block is zero (not currently remoted), allows the new remote session via remot1. If non-zero, compares the source station at RX offset &80 against workspace offset &0E -- rejects mismatched stations via clear_jsr_protection, accepts matching stations by falling through to lang_0_insert_remote_key. |
|
| 84ED | .lang_4_remote_validated |
| LDY #4 ; Y=4: RX control block byte 4 (remote status) | |
| 84EF | LDA (net_rx_ptr),y ; Read remote status flag |
| 84F1 | BEQ remot1 ; Zero = not remoted; allow new session |
| 84F3 | LDY #&80 ; Read source station from RX data at &80 |
| 84F5 | LDA (net_rx_ptr),y ; A = source station number |
| 84F7 | LDY #&0e ; Compare against controlling station at &0E |
| 84F9 | CMP (nfs_workspace),y ; Check if source matches controller |
| 84FB | BNE rchex ; Reject: source != controlling station |
| fall through ↓ | |
Insert remote keypressReads a character from RX block offset &82 and inserts it into keyboard input buffer 0 via OSBYTE &99. |
|
| 84FD | .lang_0_insert_remote_key |
| LDY #&82 ; Read keypress from RX data at &82 | |
| 84FF | LDA (net_rx_ptr),y ; Load character byte |
| 8501 | TAY ; Y = character to insert |
| 8502 | LDX #0 ; X = buffer 0 (keyboard input) |
| 8504 | JSR clear_jsr_protection ; Release JSR protection before inserting key |
| 8507 | LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert char into input buffer |
| 8509 | JMP osbyte ; Tail call: insert character Y into buffer X Insert character Y into input buffer X |
| 850C | .error_not_listening←1← 8560 BEQ |
| LDA #8 ; Error code 8: "Not listening" error | |
| 850E | BNE set_listen_offset ; ALWAYS branch to set_listen_offset ALWAYS branch |
| 8510 | .nlistn←1← 8640 JMP |
| LDA (net_tx_ptr,x) ; Load TX status byte for error lookup | |
| 8512 | .nlisne←2← 84AC JMP← 8A40 JMP |
| AND #7 ; Mask to 3-bit error code (0-7) | |
| 8514 | .set_listen_offset←1← 850E BNE |
| TAX ; X = error code index | |
| 8515 | LDY error_offsets,x ; Look up error message offset from table |
| 8518 | LDX #0 ; X=0: start writing at &0101 |
| 851A | STX error_block ; Store BRK opcode at &0100 |
| 851D | .copy_error_message←1← 8527 BNE |
| LDA error_msg_table,y ; Load error message byte | |
| 8520 | STA error_text,x ; Build error message at &0101+ |
| 8523 | BEQ execute_downloaded ; Zero byte = end of message; go execute BRK |
| 8525 | INY ; Next source byte |
| 8526 | INX ; Next dest byte |
| 8527 | BNE copy_error_message ; Continue copying message |
| 8529 | EQUS "SP." ; Set bit7: FS transaction in progress |
| 852C | EQUB &0D, &45, &2E, &0D ; CR + E. + CR: *EXEC boot command |
| fall through ↓ | |
Load '*' prefix and send FS command (WAITFS)Loads A with &2A ('*') as the FS command prefix byte, then falls through to send_to_fs to perform a full fileserver transaction: transmit and wait for reply.
|
||||
| 8530 | .waitfs←5← 83F7 JSR← 877F JSR← 88A8 JSR← 903B JMP← 929B JSR | |||
| LDA #&2a ; A = '*' for FS command prefix | ||||
| 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_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.
|
|||||||
| 8532 | .send_to_fs | ||||||
| PHA ; Save function code on stack | |||||||
| 8533 | .send_to_fs_star | ||||||
| LDA rx_flags ; Load current rx_flags | |||||||
| 8536 | PHA ; Save rx_flags on stack for restore | ||||||
| 8537 | LDX net_tx_ptr_hi ; Only flag rx_flags if using page-zero CB | ||||||
| 8539 | BNE skip_rx_flag_set ; High byte != 0: skip flag set | ||||||
| 853B | ORA #&80 ; Set bit7: FS transaction in progress | ||||||
| 853D | STA rx_flags ; Write back updated rx_flags | ||||||
| 8540 | .skip_rx_flag_set←1← 8539 BNE | ||||||
| LDA #0 ; Push two zero bytes as timeout counters | |||||||
| 8542 | PHA ; First zero for timeout | ||||||
| 8543 | PHA ; Second zero for timeout | ||||||
| 8544 | TAY ; Y=0: index for flag byte check Y=&00 | ||||||
| 8545 | TSX ; TSX: index stack-based timeout via X | ||||||
| 8546 | .incpx←3← 854D BNE← 8552 BNE← 8557 BNE | ||||||
| LDA (net_tx_ptr),y ; Read flag byte from TX control block | |||||||
| 8548 | BMI fs_wait_cleanup ; Bit 7 set = reply received | ||||||
| 854A | DEC error_text,x ; Three-stage nested timeout: inner loop | ||||||
| 854D | BNE incpx ; Inner not expired: keep polling | ||||||
| 854F | DEC stk_timeout_mid,x ; Middle timeout loop | ||||||
| 8552 | BNE incpx ; Middle not expired: keep polling | ||||||
| 8554 | DEC stk_timeout_hi,x ; Outer timeout loop (slowest) | ||||||
| 8557 | BNE incpx ; Outer not expired: keep polling | ||||||
| 8559 | .fs_wait_cleanup←1← 8548 BMI | ||||||
| PLA ; Pop first timeout byte | |||||||
| 855A | PLA ; Pop second timeout byte | ||||||
| 855B | PLA ; Pop saved rx_flags into A | ||||||
| 855C | STA rx_flags ; Restore saved rx_flags from stack | ||||||
| 855F | PLA ; Pop saved function code | ||||||
| 8560 | BEQ error_not_listening ; A=saved func code; zero would mean no reply | ||||||
| 8562 | RTS ; Return to caller | ||||||
| 8563 | .bgetv_handler | ||||||
| SEC ; C=1: flag for BGET mode | |||||||
| 8564 | JSR bgetv_entry ; Handle BGET via FS command | ||||||
| 8567 | SEC ; SEC: set carry for error check | ||||||
| 8568 | LDA #&fe ; A=&FE: mask for EOF check | ||||||
| 856A | BIT fs_error_flags ; BIT l0fdf: test error flags | ||||||
| 856D | BVS return_4 ; V=1: error, return early | ||||||
| 856F | CLC ; CLC: no error | ||||||
| 8570 | PHP ; Save flags for EOF check | ||||||
| 8571 | LDA fs_spool0 ; Load BGET result byte | ||||||
| 8573 | PLP ; Restore flags | ||||||
| 8574 | BMI bgetv_shared_jsr ; Bit7 set: skip FS flag clear | ||||||
| 8576 | JSR clear_fs_flag ; Clear FS flag for handle | ||||||
| 8579 | .bgetv_shared_jsr←1← 8574 BMI | ||||||
| EQUB &20 ; Set EOF flag for this handle | |||||||
| ; Econet error message table (ERRTAB, 7 entries). | |||||||
| ; Each entry: error number byte followed by NUL-terminated ; string. | |||||||
| ; &A0: "Line Jammed" &A1: "Net Error" | |||||||
| ; &A2: "Not listening" &A3: "No Clock" | |||||||
| ; &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 c850c/nlistn/nlisne at &850C-&8514. | |||||||
| 857A | .error_table_base | ||||||
| EQUS "P.-^.` Line Jammed." ; Load handle bitmask for caller Return with handle mask in A | |||||||
| 858D | EQUB &A1 ; Error &A1: Net Error | ||||||
| 858E | EQUS "Net Error." ; Error string "Net Error" | ||||||
| 8598 | EQUB &A2 ; Error &A2: Not listening | ||||||
| 8599 | EQUS "Not listening." ; Error string "Not listening" | ||||||
| 85A7 | EQUB &A3 ; Error &A3: No Clock | ||||||
| 85A8 | EQUS "No Clock." ; Error string "No Clock" | ||||||
| 85B1 | EQUB &11 ; Error &11: Escape | ||||||
| 85B2 | EQUS "Escape." ; Error string "Escape" | ||||||
| 85B9 | EQUB &CB ; Error &CB: Bad Option | ||||||
| 85BA | EQUS "Bad Option." ; Error string "Bad Option" | ||||||
| 85C5 | EQUB &A5 ; Error &A5: No reply | ||||||
| 85C6 | EQUS "No reply." ; Error string "No reply" | ||||||
| fall through ↓ | |||||||
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 &85EC. The two formats use different bit layouts for file protection attributes.
|
||||||||
| 85CF | .decode_attribs_6bit←2← 88FB JSR← 8926 JSR | |||||||
| LDY #&0e ; Y=&0E: attribute byte offset in param block | ||||||||
| 85D1 | LDA (fs_options),y ; Load FS attribute byte | |||||||
| 85D3 | AND #&3f ; Mask to 6 bits (FS → BBC direction) | |||||||
| 85D5 | LDX #4 ; X=4: skip first 4 table entries (BBC→FS half) | |||||||
| 85D7 | 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 &85EC. 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.
|
|||||||||
| 85D9 | .decode_attribs_5bit←2← 881F JSR← 8943 JSR | ||||||||
| AND #&1f ; Mask to 5 bits (BBC → FS direction) | |||||||||
| 85DB | LDX #&ff ; X=&FF: INX makes 0; start from table index 0 | ||||||||
| 85DD | .attrib_shift_bits←1← 85D7 BNE | ||||||||
| STA fs_error_ptr ; Temp storage for source bitmask to shift out | |||||||||
| 85DF | LDA #0 ; A=0: accumulate destination bits here | ||||||||
| 85E1 | .map_attrib_bits←1← 85E9 BNE | ||||||||
| INX ; Next table entry | |||||||||
| 85E2 | LSR fs_error_ptr ; Shift out source bits one at a time | ||||||||
| 85E4 | BCC skip_set_attrib_bit ; Bit was 0: skip this destination bit | ||||||||
| 85E6 | ORA access_bit_table,x ; OR in destination bit from lookup table | ||||||||
| 85E9 | .skip_set_attrib_bit←1← 85E4 BCC | ||||||||
| BNE map_attrib_bits ; Loop while source bits remain (A != 0) | |||||||||
| 85EB | RTS ; Return; A = converted attribute bitmask | ||||||||
| 85EC | .access_bit_table←1← 85E6 ORA | ||||||||
| EQUB &50, &20, &05, &02, &88, &04, ; Attribute bit mapping &08, &80, &10, &01, &02 ; table (11 entries) | |||||||||
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.
|
||||||||
| 85F7 | .setup_tx_ptr_c0←2← 83EF JSR← 8897 JSR | |||||||
| LDX #&c0 ; TX control block low byte | ||||||||
| 85F9 | STX net_tx_ptr ; Set net_tx_ptr = &00C0 | |||||||
| 85FB | LDX #0 ; TX control block high byte | |||||||
| 85FD | STX net_tx_ptr_hi ; Set net_tx_ptr+1 = &00 | |||||||
| 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.
|
||||||||
| 85FF | .tx_poll_ff←4← 9024 JSR← 907D JMP← 90DA JSR← 9279 JSR | |||||||
| LDA #&ff ; A=&FF: full retry count | ||||||||
| 8601 | .tx_poll_timeout | |||||||
| LDY #&60 ; Y=timeout parameter (&60 = standard) | ||||||||
| fall through ↓ | ||||||||
Core transmit and poll routine (XMIT)Claims the TX semaphore (tx_clear_flag) 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 (&85F7) always uses the standard TXCB; tx_poll_core (&8603) is general-purpose.
|
|||||||||||||
| 8603 | .tx_poll_core | ||||||||||||
| PHA ; Save retry count and timeout on stack | |||||||||||||
| 8604 | TYA ; Transfer timeout to A | ||||||||||||
| 8605 | PHA ; Push timeout parameter | ||||||||||||
| 8606 | LDX #0 ; X=0 for (zp,X) indirect addressing | ||||||||||||
| 8608 | LDA (net_tx_ptr,x) ; Read control byte from TX block | ||||||||||||
| 860A | .rearm_tx_attempt←1← 863D BEQ | ||||||||||||
| STA (net_tx_ptr,x) ; Write back control byte (re-arm for TX) | |||||||||||||
| 860C | PHA ; Save control byte for error recovery | ||||||||||||
| 860D | .poll_tx_semaphore←1← 8610 BCC | ||||||||||||
| ASL tx_clear_flag ; Spin until TX semaphore is free (C=1) | |||||||||||||
| 8610 | BCC poll_tx_semaphore ; C=0: still held, keep spinning | ||||||||||||
| 8612 | LDA net_tx_ptr ; Copy TX pointer to NMI block while locked | ||||||||||||
| 8614 | STA nmi_tx_block ; Store low byte to NMI TX block | ||||||||||||
| 8616 | LDA net_tx_ptr_hi ; Load TX pointer high byte | ||||||||||||
| 8618 | STA nmi_tx_block_hi ; Store high byte to NMI TX block | ||||||||||||
| 861A | JSR start_adlc_tx ; Initiate ADLC transmission | ||||||||||||
| 861D | .poll_tx_complete←1← 861F BMI | ||||||||||||
| LDA (net_tx_ptr,x) ; Poll: wait for bit 7 to clear (TX done) | |||||||||||||
| 861F | BMI poll_tx_complete ; Bit 7 set: still busy, keep polling | ||||||||||||
| 8621 | ASL ; Bit 6 into sign: 0=success, 1=error | ||||||||||||
| 8622 | BPL tx_success_exit ; Success: clean up stack and exit | ||||||||||||
| 8624 | ASL ; Bit 5: escape condition? | ||||||||||||
| 8625 | BEQ tx_abort ; Yes (Z=1): abort via nlistn | ||||||||||||
| 8627 | JSR check_escape ; Check for escape key pressed | ||||||||||||
| 862A | PLA ; Recover saved control byte | ||||||||||||
| 862B | TAX ; Move to X for retry | ||||||||||||
| 862C | PLA ; Recover timeout parameter | ||||||||||||
| 862D | TAY ; Move to Y for delay loop | ||||||||||||
| 862E | PLA ; Recover retry count | ||||||||||||
| 862F | BEQ tx_abort ; Retries exhausted: abort via nlistn | ||||||||||||
| 8631 | SBC #1 ; Decrement retry count (C=1 from CMP) | ||||||||||||
| 8633 | PHA ; Re-push retry count and timeout for retry | ||||||||||||
| 8634 | TYA ; Transfer timeout to A | ||||||||||||
| 8635 | PHA ; Push timeout for next attempt | ||||||||||||
| 8636 | TXA ; Restore control byte for retry | ||||||||||||
| 8637 | .tx_retry_delay←2← 8638 BNE← 863B BNE | ||||||||||||
| DEX ; Delay loop: X*Y iterations before retry | |||||||||||||
| 8638 | BNE tx_retry_delay ; Inner loop: decrement X | ||||||||||||
| 863A | DEY ; Outer loop: decrement Y | ||||||||||||
| 863B | BNE tx_retry_delay ; Continue delay until Y=0 | ||||||||||||
| 863D | BEQ rearm_tx_attempt ; ALWAYS branch | ||||||||||||
| 863F | .tx_abort←2← 8625 BEQ← 862F BEQ | ||||||||||||
| TAX ; A = error code for nlistn | |||||||||||||
| 8640 | JMP nlistn ; Report net error via nlistn | ||||||||||||
| 8643 | .tx_success_exit←1← 8622 BPL | ||||||||||||
| PLA ; Success: discard 3 saved bytes from stack | |||||||||||||
| 8644 | PLA ; Discard timeout | ||||||||||||
| 8645 | PLA ; Discard retry count | ||||||||||||
| 8646 | JMP clear_escapable ; Jump to clear escapable flag and return | ||||||||||||
Save FSCV arguments with text pointersExtended entry used by FSCV, FINDV, and fscv_3_star_cmd. Copies X/Y into os_text_ptr/&F3 and fs_cmd_ptr/&0E11, then falls through to save_fscv_args to store A/X/Y in the FS workspace.
|
||||||||
| 8649 | .save_fscv_args_with_ptrs←3← 80D4 JSR← 89D8 JSR← 8C1B JSR | |||||||
| STX os_text_ptr ; Set os_text_ptr low = X | ||||||||
| 864B | STY os_text_ptr_hi ; Set os_text_ptr high = Y | |||||||
| fall through ↓ | ||||||||
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)
|
||||||||
| 864D | .save_fscv_args←3← 870C JSR← 8968 JSR← 8A72 JSR | |||||||
| STA fs_last_byte_flag ; Save A = function code / command | ||||||||
| 864F | STX fs_options ; Save X = control block ptr low | |||||||
| 8651 | STY fs_block_offset ; Save Y = control block ptr high | |||||||
| 8653 | STX fs_crc_lo ; Duplicate X for indirect indexed access | |||||||
| 8655 | STY fs_crc_hi ; Duplicate Y for indirect indexed access | |||||||
| fall through ↓ | ||||||||
Clear escapable flag preserving processor statusPHP/LSR escapable/PLP: clears bit 7 of the escapable flag while preserving the processor status register. Used at the start of FS vector operations to mark them as not yet escapable. |
|
| 8657 | .clear_escapable←2← 8414 JSR← 8646 JMP |
| PHP ; Clear escapable flag, preserving processor flags | |
| 8658 | LSR escapable ; Reset: this operation is not escapable yet |
| 865A | PLP ; Restore flags (caller may need N/Z/C) |
| 865B | RTS ; Return |
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.
|
||||||||
| 865C | .print_inline←13← 8208 JSR← 8232 JSR← 8252 JSR← 825F JSR← 8C8D JSR← 8C97 JSR← 8CA5 JSR← 8CB0 JSR← 8CC5 JSR← 8CDA JSR← 8CED JSR← 8CFC JSR← 8DAB JSR | |||||||
| PLA ; Pop return address (low) — points to last byte of JSR | ||||||||
| 865D | STA fs_load_addr ; Store return addr low as string ptr | |||||||
| 865F | PLA ; Pop return address (high) | |||||||
| 8660 | STA fs_load_addr_hi ; Store return addr high as string ptr | |||||||
| 8662 | LDY #0 ; Y=0: offset for indirect load | |||||||
| 8664 | .print_inline_char←1← 8671 JMP | |||||||
| INC fs_load_addr ; Advance pointer past return address / to next char | ||||||||
| 8666 | BNE print_next_char ; No page wrap: skip high byte inc | |||||||
| 8668 | INC fs_load_addr_hi ; Handle page crossing in pointer | |||||||
| 866A | .print_next_char←1← 8666 BNE | |||||||
| LDA (fs_load_addr),y ; Load next byte from inline string | ||||||||
| 866C | BMI jump_via_addr ; Bit 7 set? Done — this byte is the next opcode | |||||||
| 866E | JSR osasci ; Write character | |||||||
| 8671 | JMP print_inline_char ; Continue printing next character | |||||||
| 8674 | .jump_via_addr←1← 866C BMI | |||||||
| JMP (fs_load_addr) ; Jump to address of high-bit byte (resumes code after string) | ||||||||
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") -- originally stopped on any char >= &40 (any letter), but the revision allows numbers followed by dots.
|
|||||||||||
| 8677 | .parse_decimal←2← 808C JSR← 8095 JSR | ||||||||||
| LDA #0 ; Zero accumulator | |||||||||||
| 8679 | STA fs_load_addr_2 ; Initialise accumulator to zero | ||||||||||
| 867B | .scan_decimal_digit←1← 8694 BNE | ||||||||||
| LDA (fs_options),y ; Load next char from buffer | |||||||||||
| 867D | CMP #&2e ; Dot separator? | ||||||||||
| 867F | BEQ parse_decimal_rts ; Yes: exit with C=1 (dot found) | ||||||||||
| 8681 | BCC no_dot_exit ; Control char or space: done | ||||||||||
| 8683 | AND #&0f ; Mask ASCII digit to 0-9 | ||||||||||
| 8685 | STA fs_load_addr_3 ; Save new digit | ||||||||||
| 8687 | ASL fs_load_addr_2 ; Running total * 2 | ||||||||||
| 8689 | LDA fs_load_addr_2 ; A = running total * 2 | ||||||||||
| 868B | ASL ; A = running total * 4 | ||||||||||
| 868C | ASL ; A = running total * 8 | ||||||||||
| 868D | ADC fs_load_addr_2 ; + total*2 = total * 10 | ||||||||||
| 868F | ADC fs_load_addr_3 ; + digit = total*10 + digit | ||||||||||
| 8691 | STA fs_load_addr_2 ; Store new running total | ||||||||||
| 8693 | INY ; Advance to next char | ||||||||||
| 8694 | BNE scan_decimal_digit ; Loop (always: Y won't wrap to 0) | ||||||||||
| 8696 | .no_dot_exit←1← 8681 BCC | ||||||||||
| CLC ; No dot found: C=0 | |||||||||||
| 8697 | .parse_decimal_rts←1← 867F BEQ | ||||||||||
| LDA fs_load_addr_2 ; Return result in A | |||||||||||
| 8699 | 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.
|
|||||||||||
| 869A | .handle_to_mask_a←3← 88AF JSR← 8A8D JSR← 8F5B 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.
|
|||||||||||
| 869B | .handle_to_mask_clc←2← 8422 JSR← 8973 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"). The bit-shift conversion loop 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.
|
|||||||||||||
| 869C | .handle_to_mask←1← 89DC JSR | ||||||||||||
| PHA ; Save A (will be restored on exit) | |||||||||||||
| 869D | TXA ; Save X (will be restored on exit) | ||||||||||||
| 869E | PHA ; (second half of X save) | ||||||||||||
| 869F | TYA ; A = handle from Y | ||||||||||||
| 86A0 | BCC y2fsl5 ; C=0: always convert | ||||||||||||
| 86A2 | BEQ handle_mask_exit ; C=1 and Y=0: skip (handle 0 = none) | ||||||||||||
| 86A4 | .y2fsl5←1← 86A0 BCC | ||||||||||||
| SEC ; C=1 and Y!=0: convert | |||||||||||||
| 86A5 | SBC #&1f ; A = handle - &1F (1-based bit position) | ||||||||||||
| 86A7 | TAX ; X = shift count | ||||||||||||
| 86A8 | LDA #1 ; Start with bit 0 set | ||||||||||||
| 86AA | .y2fsl2←1← 86AC BNE | ||||||||||||
| ASL ; Shift bit left | |||||||||||||
| 86AB | DEX ; Count down | ||||||||||||
| 86AC | BNE y2fsl2 ; Loop until correct position | ||||||||||||
| 86AE | ROR ; Undo final extra shift | ||||||||||||
| 86AF | TAY ; Y = resulting bitmask | ||||||||||||
| 86B0 | BNE handle_mask_exit ; Non-zero: valid mask, skip to exit | ||||||||||||
| 86B2 | DEY ; Zero: invalid handle, set Y=&FF | ||||||||||||
| 86B3 | .handle_mask_exit←2← 86A2 BEQ← 86B0 BNE | ||||||||||||
| PLA ; Restore X | |||||||||||||
| 86B4 | TAX ; Restore X from stack | ||||||||||||
| 86B5 | PLA ; Restore A | ||||||||||||
| 86B6 | 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.
|
|||||||||||
| 86B7 | .mask_to_handle←2← 8A0B JSR← 8F73 JSR | ||||||||||
| LDX #&1f ; X = &1F (handle base - 1) | |||||||||||
| 86B9 | .fs2al1←1← 86BB BNE | ||||||||||
| INX ; Count this bit position | |||||||||||
| 86BA | LSR ; Shift mask right; C=0 when done | ||||||||||
| 86BB | BNE fs2al1 ; Loop until all bits shifted out | ||||||||||
| 86BD | TXA ; A = X = &1F + bit position = handle | ||||||||||
| 86BE | RTS ; Return with handle in A | ||||||||||
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.
|
||||||||
| 86BF | .compare_addresses←2← 8765 JSR← 8854 JSR | |||||||
| LDX #4 ; Compare 4 bytes (index 4,3,2,1) | ||||||||
| 86C1 | .compare_addr_byte←1← 86C8 BNE | |||||||
| LDA addr_work,x ; Load byte from first address | ||||||||
| 86C3 | EOR fs_load_addr_3,x ; XOR with corresponding byte | |||||||
| 86C5 | BNE return_compare ; Mismatch: Z=0, return unequal | |||||||
| 86C7 | DEX ; Next byte | |||||||
| 86C8 | BNE compare_addr_byte ; Continue comparing | |||||||
| 86CA | .return_compare←1← 86C5 BNE | |||||||
| RTS ; Return with Z flag result | ||||||||
| 86CB | .fscv_7_read_handles | |||||||
| LDX #&20 ; X=first handle (&20) | ||||||||
| 86CD | LDY #&27 ; Y=last handle (&27) | |||||||
| 86CF | .return_fscv_handles | |||||||
| RTS ; Return (FSCV 7 read handles) | ||||||||
Set bit(s) in EOF hint flags (&0E07)ORs A into fs_eof_flags then stores the result via store_fs_flag. 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.
|
|||||||
| 86D0 | .set_fs_flag←4← 89B0 JSR← 8A07 JSR← 8A27 JSR← 8B08 JSR | ||||||
| ORA fs_eof_flags ; Merge new bits into flags | |||||||
| 86D3 | BNE store_fs_flag ; Store updated flags (always taken) | ||||||
| fall through ↓ | |||||||
Clear bit(s) in FS flags (&0E07)Inverts A (EOR #&FF), then ANDs the result into fs_eof_flags to clear the specified bits.
|
|||||||
| 86D5 | .clear_fs_flag←3← 8576 JSR← 88CA JSR← 8B05 JSR | ||||||
| EOR #&ff ; Invert mask: set bits become clear bits | |||||||
| 86D7 | AND fs_eof_flags ; Clear specified bits in flags | ||||||
| 86DA | .store_fs_flag←1← 86D3 BNE | ||||||
| STA fs_eof_flags ; Write back updated flags | |||||||
| 86DD | RTS ; Return | ||||||
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.
|
||||||
| 86DE | .copy_filename_ptr←1← 870F JSR | |||||
| LDY #1 ; Y=1: copy 2 bytes (high then low) | ||||||
| 86E0 | .file1←1← 86E6 BPL | |||||
| LDA (fs_options),y ; Load filename ptr from control block | ||||||
| 86E2 | STA os_text_ptr,y ; Store to MOS text pointer (&F2/&F3) | |||||
| 86E5 | DEY ; Next byte (descending) | |||||
| 86E6 | BPL file1 ; Loop for both bytes | |||||
| fall through ↓ | ||||||
Parse filename using GSINIT/GSREAD into &0E30Uses the MOS GSINIT/GSREAD API to parse a filename string from (os_text_ptr),Y, handling quoted strings and |-escaped characters. Stores the parsed result CR-terminated at &0E30 and sets up fs_crc_lo/hi to point to that buffer. Sub-entry at &86EA allows a non-zero starting Y offset.
|
|||||||||
| 86E8 | .parse_filename_gs←2← 89F1 JSR← 8DDC JSR | ||||||||
| LDY #0 ; Start from beginning of string | |||||||||
| fall through ↓ | |||||||||
Parse filename via GSINIT/GSREAD from offset YSub-entry of parse_filename_gs that accepts a non-zero Y offset into the (os_text_ptr) string. Initialises GSINIT, reads chars via GSREAD into &0E30, CR-terminates the result, and sets up fs_crc_lo/hi to point at the buffer.
|
|||||||||
| 86EA | .parse_filename_gs_y←1← 8C7B JSR | ||||||||
| LDX #&ff ; X=&FF: next INX wraps to first char index | |||||||||
| 86EC | CLC ; C=0 for GSINIT: parse from current position | ||||||||
| 86ED | JSR gsinit ; Initialise GS string parser | ||||||||
| 86F0 | BEQ terminate_filename ; Empty string: skip to CR terminator | ||||||||
| 86F2 | .quote1←1← 86FB BCC | ||||||||
| JSR gsread ; Read next character via GSREAD | |||||||||
| 86F5 | BCS terminate_filename ; C=1 from GSREAD: end of string reached | ||||||||
| 86F7 | INX ; Advance buffer index | ||||||||
| 86F8 | STA fs_filename_buf,x ; Store parsed character to &0E30+X | ||||||||
| 86FB | BCC quote1 ; ALWAYS loop (GSREAD clears C on success) ALWAYS branch | ||||||||
| 86FD | .terminate_filename←2← 86F0 BEQ← 86F5 BCS | ||||||||
| INX ; Terminate parsed string with CR | |||||||||
| 86FE | LDA #&0d ; CR = &0D | ||||||||
| 8700 | STA fs_filename_buf,x ; Store CR terminator at end of string | ||||||||
| 8703 | LDA #&30 ; Point fs_crc_lo/hi at &0E30 parse buffer | ||||||||
| 8705 | STA fs_crc_lo ; fs_crc_lo = &30 | ||||||||
| 8707 | LDA #&0e ; fs_crc_hi = &0E → buffer at &0E30 | ||||||||
| 8709 | STA fs_crc_hi ; Store high byte | ||||||||
| 870B | RTS ; Return; X = string length | ||||||||
FILEV handler (OSFILE entry point)Calls save_fscv_args (&864D) to preserve A/X/Y, then JSR &86DE to copy the 2-byte filename pointer from the parameter block to os_text_ptr and fall through to parse_filename_gs (&86E8) which parses the filename into &0E30+. 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 &8722) A=&00: save file (filev_save at &8795) A=&01-&06: attribute operations (filev_attrib_dispatch at &88D1) Other: restore_args_return (unsupported, no-op)
|
|||||||||||||||
| 870C | .filev_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y in FS workspace | |||||||||||||||
| 870F | JSR copy_filename_ptr ; Copy filename ptr from param block to os_text_ptr | ||||||||||||||
| 8712 | LDA fs_last_byte_flag ; Recover function code from saved A | ||||||||||||||
| 8714 | BPL saveop ; A >= 0: save (&00) or attribs (&01-&06) | ||||||||||||||
| 8716 | CMP #&ff ; A=&FF? Only &FF is valid for load | ||||||||||||||
| 8718 | BEQ loadop ; A=&FF: branch to load path | ||||||||||||||
| 871A | JMP restore_args_return ; Unknown negative code: no-op return | ||||||||||||||
| 871D | .loadop←1← 8718 BEQ | ||||||||||||||
| JSR infol2 ; Copy parsed filename to cmd buffer | |||||||||||||||
| 8720 | LDY #2 ; Y=2: FS function code offset | ||||||||||||||
| fall through ↓ | |||||||||||||||
Send FS examine commandSends an FS examine/load command to the fileserver. The function code in Y is set by the caller (Y=2 for load, Y=5 for examine). Overwrites fs_cmd_urd (&0F02) with &92 (PLDATA port number) to repurpose the URD header field for the data transfer port. Sets escapable to &92 so escape checking is active during the transfer. Calls prepare_cmd_clv to build the FS header (which skips the normal URD copy, preserving &92). The FS reply contains load/exec addresses and file length used to set up the data transfer. Byte 6 of the parameter block selects load address handling: non-zero uses the address from the FS reply (load to file's own address); zero uses the caller-supplied address.
|
||||||
| 8722 | .send_fs_examine←1← 8E0D JSR | |||||
| LDA #&92 ; Port &92 = PLDATA (data transfer port) | ||||||
| 8724 | STA escapable ; Mark transfer as escapable | |||||
| 8726 | STA fs_cmd_urd ; Overwrite URD field with data port number | |||||
| 8729 | JSR prepare_cmd_clv ; Build FS header (V=1: CLV path) | |||||
| 872C | LDY #6 ; Y=6: param block byte 6 | |||||
| 872E | LDA (fs_options),y ; Byte 6: use file's own load address? | |||||
| 8730 | BNE lodfil ; Non-zero: use FS reply address (lodfil) | |||||
| 8732 | JSR copy_load_addr_from_params ; Zero: copy caller's load addr first | |||||
| 8735 | JSR copy_reply_to_params ; Then copy FS reply to param block | |||||
| 8738 | BCC skip_lodfil ; Carry clear from prepare_cmd_clv: skip lodfil | |||||
| 873A | .lodfil←1← 8730 BNE | |||||
| JSR copy_reply_to_params ; Copy FS reply addresses to param block | ||||||
| 873D | JSR copy_load_addr_from_params ; Then copy load addr from param block | |||||
| 8740 | .skip_lodfil←1← 8738 BCC | |||||
| LDY #4 ; Compute end address = load + file length | ||||||
| 8742 | .copy_load_end_addr←1← 874D BNE | |||||
| LDA fs_load_addr,x ; Load address byte | ||||||
| 8744 | STA txcb_end,x ; Store as current transfer position | |||||
| 8746 | ADC fs_file_len,x ; Add file length byte | |||||
| 8749 | STA fs_work_4,x ; Store as end position | |||||
| 874B | INX ; Next address byte | |||||
| 874C | DEY ; Decrement byte counter | |||||
| 874D | BNE copy_load_end_addr ; Loop for all 4 address bytes | |||||
| 874F | SEC ; Adjust high byte for 3-byte length overflow | |||||
| 8750 | SBC fs_file_len_3 ; Subtract 4th length byte from end addr | |||||
| 8753 | STA fs_work_7 ; Store adjusted end address high byte | |||||
| 8755 | JSR send_data_blocks ; Transfer file data in &80-byte blocks | |||||
| 8758 | LDX #2 ; Copy 3-byte file length to FS reply cmd buffer | |||||
| 875A | .floop←1← 8761 BPL | |||||
| LDA fs_file_len_3,x ; Load file length byte | ||||||
| 875D | STA fs_cmd_data,x ; Store in FS command data buffer | |||||
| 8760 | DEX ; Next byte (count down) | |||||
| 8761 | BPL floop ; Loop for 3 bytes (X=2,1,0) | |||||
| 8763 | BMI save_csd_display ; 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. |
|
| 8765 | .send_data_blocks←2← 8755 JSR← 8AF8 JSR |
| JSR compare_addresses ; Compare two 4-byte addresses | |
| 8768 | BEQ return_lodchk ; Addresses match: transfer complete |
| 876A | LDA #&92 ; Port &92 for data block transfer |
| 876C | STA txcb_port ; Store port to TXCB command byte |
| 876E | .send_block_loop←1← 878A BNE |
| LDX #3 ; Set up next &80-byte block for transfer | |
| 8770 | .copy_block_addrs←1← 8779 BPL |
| LDA txcb_end,x ; Swap: current addr -> source, end -> current | |
| 8772 | STA txcb_start,x ; Source addr = current position |
| 8774 | LDA fs_work_4,x ; Load end address byte |
| 8776 | STA txcb_end,x ; Dest = end address (will be clamped) |
| 8778 | DEX ; Next address byte |
| 8779 | BPL copy_block_addrs ; Loop for all 4 bytes |
| 877B | LDA #&7f ; Command &7F = data block transfer |
| 877D | STA txcb_ctrl ; Store to TXCB control byte |
| 877F | JSR waitfs ; Send this block to the fileserver |
| 8782 | LDY #3 ; Y=3: compare 4 bytes (3..0) |
| 8784 | .lodchk←1← 878D BPL |
| LDA txcb_end,y ; Compare current vs end address (4 bytes) | |
| 8787 | EOR fs_work_4,y ; XOR with end address byte |
| 878A | BNE send_block_loop ; Not equal: more blocks to send |
| 878C | DEY ; Next byte |
| 878D | BPL lodchk ; Loop for all 4 address bytes |
| 878F | .return_lodchk←1← 8768 BEQ |
| RTS ; All equal: transfer complete | |
| 8790 | .saveop←1← 8714 BPL |
| BEQ filev_save ; A=0: SAVE handler | |
| 8792 | 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. The savsiz loop computes data-end minus data-start for each address byte to derive the transfer length, saving both the original address and the difference. Sends FS command with port &91, function code Y=1, and the filename via copy_string_to_cmd. After transfer_file_blocks sends the data, calls send_fs_reply_cmd for the final handshake. If fs_messages_flag is set, prints the catalogue line inline: filename (padded to 12 chars), load address, exec address, and file length. Finally decodes the FS attributes and copies the reply data back into the caller's parameter block. |
|
| 8795 | .filev_save←1← 8790 BEQ |
| LDX #4 ; Process 4 address bytes (load/exec/start/end) | |
| 8797 | LDY #&0e ; Y=&0E: start from end-address in param block |
| 8799 | .savsiz←1← 87B3 BNE |
| LDA (fs_options),y ; Read end-address byte from param block | |
| 879B | STA port_ws_offset,y ; Save to port workspace for transfer setup |
| 879E | JSR sub_4_from_y ; Y = Y-4: point to start-address byte |
| 87A1 | SBC (fs_options),y ; end - start = transfer length byte |
| 87A3 | STA fs_cmd_csd,y ; Store length byte in FS command buffer |
| 87A6 | PHA ; Save length byte for param block restore |
| 87A7 | LDA (fs_options),y ; Read corresponding start-address byte |
| 87A9 | STA port_ws_offset,y ; Save to port workspace |
| 87AC | PLA ; Restore length byte from stack |
| 87AD | STA (fs_options),y ; Replace param block entry with length |
| 87AF | JSR add_5_to_y ; Y = Y+5: advance to next address group |
| 87B2 | DEX ; Decrement address byte counter |
| 87B3 | BNE savsiz ; Loop for all 4 address bytes |
| 87B5 | LDY #9 ; Copy load/exec addresses to FS command buffer |
| 87B7 | .copy_save_params←1← 87BD BNE |
| LDA (fs_options),y ; Read load/exec address byte from params | |
| 87B9 | STA fs_cmd_csd,y ; Copy to FS command buffer |
| 87BC | DEY ; Next byte (descending) |
| 87BD | BNE copy_save_params ; Loop for bytes 9..1 |
| 87BF | LDA #&91 ; Port &91 for save command |
| 87C1 | STA escapable ; Mark as escapable during save |
| 87C3 | STA fs_cmd_urd ; Overwrite URD field with port number |
| 87C6 | STA fs_error_ptr ; Save port &91 for flow control ACK |
| 87C8 | LDX #&0b ; Append filename at offset &0B in cmd buffer |
| 87CA | JSR copy_string_to_cmd ; Append filename to cmd buffer at offset X |
| 87CD | LDY #1 ; Y=1: function code for save |
| 87CF | JSR prepare_cmd_clv ; Build header and send FS save command |
| 87D2 | LDA fs_cmd_data ; Read FS reply command code for transfer type |
| 87D5 | JSR transfer_file_blocks ; Send file data blocks to server |
| 87D8 | .save_csd_display←1← 8763 BMI |
| LDA fs_cmd_csd ; Save CSD from reply for catalogue display | |
| 87DB | PHA ; Save CSD byte from reply for display |
| 87DC | JSR send_fs_reply_cmd ; Send final reply acknowledgement |
| 87DF | PLA ; Restore CSD byte after reply command |
| 87E0 | LDY fs_messages_flag ; Check if file info messages enabled |
| 87E3 | BEQ skip_catalogue_msg ; Messages off: skip catalogue display |
| 87E5 | LDY #0 ; Y=0: start of filename in reply |
| 87E7 | TAX ; A = CSD; test for directory prefix |
| 87E8 | BEQ print_filename_char ; CSD=0: no directory prefix |
| 87EA | JSR print_dir_from_offset ; Print directory prefix from reply |
| 87ED | BMI print_addresses ; Dir printed: skip to address display |
| 87EF | .print_filename_char←2← 87E8 BEQ← 87F9 BNE |
| LDA (fs_crc_lo),y ; Load filename character from reply | |
| 87F1 | CMP #&21 ; Check for control character or space |
| 87F3 | BCC pad_filename_space ; Below &21: pad with spaces to column 12 |
| 87F5 | JSR osasci ; Write character |
| 87F8 | INY ; Next character in filename |
| 87F9 | BNE print_filename_char ; Loop for more filename characters |
| 87FB | .pad_filename_space←2← 87F3 BCC← 8801 BCC |
| JSR print_space ; Print space to pad filename to 12 chars | |
| 87FE | INY ; Advance column counter |
| 87FF | CPY #&0c ; Reached column 12? |
| 8801 | BCC pad_filename_space ; No: keep padding with spaces |
| 8803 | .print_addresses←1← 87ED BMI |
| LDY #5 ; Y=5: load address offset in reply | |
| 8805 | JSR print_hex_bytes ; Print 4-byte load address in hex |
| 8808 | LDY #9 ; Y=9: exec address offset in reply |
| 880A | JSR print_hex_bytes ; Print 4-byte exec address in hex |
| 880D | LDY #&0c ; Y=&0C: file length offset in reply |
| 880F | LDX #3 ; X=3: print 3 bytes of length |
| 8811 | JSR num01 ; Print file length in hex |
| 8814 | .send_fs_reply |
| JSR osnewl ; Send FS reply acknowledgement Write newline (characters 10 and 13) | |
| 8817 | .skip_catalogue_msg←1← 87E3 BEQ |
| STX fs_reply_cmd ; Store reply command for attr decode | |
| 881A | LDY #&0e ; Y=&0E: access byte offset in param block |
| 881C | LDA fs_cmd_data ; Load access byte from FS reply |
| 881F | JSR decode_attribs_5bit ; Convert FS access to BBC attribute format |
| 8822 | .copy_attribs_reply←1← 882A BNE |
| STA (fs_options),y ; Store decoded access in param block | |
| 8824 | INY ; Next attribute byte |
| 8825 | LDA fs_reply_data,y ; Load remaining reply data for param block |
| 8828 | CPY #&12 ; Copied all 4 bytes? (Y=&0E..&11) |
| 882A | BNE copy_attribs_reply ; Loop for 4 attribute bytes |
| 882C | 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).
|
||||||
| 882F | .copy_load_addr_from_params←2← 8732 JSR← 873D JSR | |||||
| LDY #5 ; Start at offset 5 (top of 4-byte addr) | ||||||
| 8831 | .lodrl1←1← 8839 BCS | |||||
| LDA (fs_options),y ; Read from parameter block | ||||||
| 8833 | STA work_ae,y ; Store to local workspace | |||||
| 8836 | DEY ; Next byte (descending) | |||||
| 8837 | CPY #2 ; Copy offsets 5,4,3,2 (4 bytes) | |||||
| 8839 | BCS lodrl1 ; Loop while Y >= 2 | |||||
| 883B | .add_5_to_y←1← 87AF JSR | |||||
| INY ; Y += 5 | ||||||
| 883C | .add_4_to_y←1← 8AD5 JSR | |||||
| INY ; Y += 4 | ||||||
| 883D | INY ; (continued) | |||||
| 883E | INY ; (continued) | |||||
| 883F | INY ; (continued) | |||||
| 8840 | 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.
|
||||
| 8841 | .copy_reply_to_params←2← 8735 JSR← 873A JSR | |||
| LDY #&0d ; Start at offset &0D (top of range) | ||||
| 8843 | TXA ; First store uses X (attrib byte) | |||
| 8844 | .lodrl2←1← 884C BCS | |||
| STA (fs_options),y ; Write to parameter block | ||||
| 8846 | LDA fs_cmd_urd,y ; Read next byte from reply buffer | |||
| 8849 | DEY ; Next byte (descending) | |||
| 884A | CPY #2 ; Copy offsets &0D down to 2 | |||
| 884C | BCS lodrl2 ; Loop until offset 2 reached | |||
| 884E | .sub_4_from_y←1← 879E JSR | |||
| DEY ; Y -= 4 | ||||
| 884F | .sub_3_from_y←2← 88E9 JSR← 8ADD JSR | |||
| DEY ; Y -= 3 | ||||
| 8850 | DEY ; (continued) | |||
| 8851 | DEY ; (continued) | |||
| 8852 | 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). |
|
| 8853 | .transfer_file_blocks←2← 87D5 JSR← 8AF3 JSR |
| PHA ; Save FS command byte on stack | |
| 8854 | JSR compare_addresses ; Compare two 4-byte addresses |
| 8857 | BEQ restore_ay_return ; Addresses equal: nothing to transfer |
| 8859 | .transfer_loop_top←1← 88AB BNE |
| LDA #0 ; A=0: high bytes of block size | |
| 885B | PHA ; Push 4-byte block size: 0, 0, hi, lo |
| 885C | PHA ; Push second zero byte |
| 885D | TAX ; X=&00 |
| 885E | LDA fs_data_count ; Load block size high byte from &0F07 |
| 8861 | PHA ; Push block size high |
| 8862 | LDA fs_func_code ; Load block size low byte from &0F06 |
| 8865 | PHA ; Push block size low |
| 8866 | LDY #4 ; Y=4: process 4 address bytes |
| 8868 | CLC ; CLC for ADC in loop |
| 8869 | .setup_block_addrs←1← 8876 BNE |
| LDA fs_load_addr,x ; Source = current position | |
| 886B | STA txcb_start,x ; Store source address byte |
| 886D | PLA ; Pop block size byte from stack |
| 886E | ADC fs_load_addr,x ; Dest = current pos + block size |
| 8870 | STA txcb_end,x ; Store dest address byte |
| 8872 | STA fs_load_addr,x ; Advance current position |
| 8874 | INX ; Next address byte |
| 8875 | DEY ; Decrement byte counter |
| 8876 | BNE setup_block_addrs ; Loop for all 4 bytes |
| 8878 | SEC ; SEC for SBC in overshoot check |
| 8879 | .savchk←1← 8881 BNE |
| LDA fs_load_addr,y ; Check if new pos overshot end addr | |
| 887C | SBC fs_work_4,y ; Subtract end address byte |
| 887F | INY ; Next byte |
| 8880 | DEX ; Decrement counter |
| 8881 | BNE savchk ; Loop for 4-byte comparison |
| 8883 | BCC send_block ; C=0: no overshoot, proceed |
| 8885 | .clamp_dest_setup |
| LDX #3 ; Overshot: clamp dest to end address | |
| 8887 | .clamp_dest_addr←1← 888C BPL |
| LDA fs_work_4,x ; Load end address byte | |
| 8889 | STA txcb_end,x ; Replace dest with end address |
| 888B | DEX ; Next byte |
| 888C | BPL clamp_dest_addr ; Loop for all 4 bytes |
| 888E | .send_block←1← 8883 BCC |
| PLA ; Recover original FS command byte | |
| 888F | PHA ; Re-push for next iteration |
| 8890 | PHP ; Save processor flags (C from cmp) |
| 8891 | STA txcb_port ; Store command byte in TXCB |
| 8893 | LDA #&80 ; 128-byte block size for data transfer |
| 8895 | STA txcb_ctrl ; Store size in TXCB control byte |
| 8897 | JSR setup_tx_ptr_c0 ; Point TX ptr to &00C0; transmit |
| 889A | LDA fs_error_ptr ; ACK port for flow control |
| 889C | JSR init_tx_ctrl_port ; Set reply port for ACK receive |
| 889F | PLP ; Restore flags (C=overshoot status) |
| 88A0 | BCS restore_ay_return ; C=1: all data sent (overshot), done |
| 88A2 | LDA #&91 ; Command &91 = data block transfer |
| 88A4 | STA txcb_port ; Store command &91 in TXCB |
| 88A6 | INC txcb_start ; Skip command code byte in TX buffer |
| 88A8 | JSR waitfs ; Transmit block and wait (BRIANX) |
| 88AB | BNE transfer_loop_top ; 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.
|
|||||||
| 88AD | .fscv_1_eof | ||||||
| PHA ; Save A (function code) | |||||||
| 88AE | TXA ; X = file handle to check | ||||||
| 88AF | JSR handle_to_mask_a ; Convert handle to bitmask in A | ||||||
| 88B2 | TYA ; Y = handle bitmask from conversion | ||||||
| 88B3 | AND fs_eof_flags ; Local hint: is EOF possible for this handle? | ||||||
| 88B6 | TAX ; X = result of AND (0 = not at EOF) | ||||||
| 88B7 | BEQ restore_ay_return ; Hint clear: definitely not at EOF | ||||||
| 88B9 | PHA ; Save bitmask for clear_fs_flag | ||||||
| 88BA | STY fs_cmd_data ; Handle byte in FS command buffer | ||||||
| 88BD | LDY #&11 ; Y=&11: FS function code FCEOF Y=function code for HDRFN | ||||||
| 88BF | LDX #1 ; X=preserved through header build | ||||||
| 88C1 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 88C4 | PLA ; Restore bitmask | ||||||
| 88C5 | LDX fs_cmd_data ; FS reply: non-zero = at EOF | ||||||
| 88C8 | BNE restore_ay_return ; At EOF: skip flag clear | ||||||
| 88CA | JSR clear_fs_flag ; Not at EOF: clear the hint bit | ||||||
| 88CD | .restore_ay_return←4← 8857 BEQ← 88A0 BCS← 88B7 BEQ← 88C8 BNE | ||||||
| PLA ; Restore A | |||||||
| 88CE | LDY fs_block_offset ; Restore Y | ||||||
| 88D0 | 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.
|
|||||||
| 88D1 | .filev_attrib_dispatch←1← 8792 JMP | ||||||
| STA fs_cmd_data ; Store function code in FS cmd buffer | |||||||
| 88D4 | CMP #6 ; A=6? (delete) | ||||||
| 88D6 | BEQ cha6 ; Yes: jump to delete handler | ||||||
| 88D8 | BCS check_attrib_result ; A>=7: unsupported, fall through to return | ||||||
| 88DA | CMP #5 ; A=5? (read catalogue info) | ||||||
| 88DC | BEQ cha5 ; Yes: jump to read info handler | ||||||
| 88DE | CMP #4 ; A=4? (write attributes only) | ||||||
| 88E0 | BEQ cha4 ; Yes: jump to write attrs handler | ||||||
| 88E2 | CMP #1 ; A=1? (write all catalogue info) | ||||||
| 88E4 | BEQ get_file_protection ; Yes: jump to write-all handler | ||||||
| 88E6 | ASL ; A=2 or 3: convert to param block offset | ||||||
| 88E7 | ASL ; A*4: 2->8, 3->12 | ||||||
| 88E8 | TAY ; Y = A*4 | ||||||
| 88E9 | JSR sub_3_from_y ; Y = A*4 - 3 (load addr offset for A=2) | ||||||
| 88EC | LDX #3 ; X=3: copy 4 bytes | ||||||
| 88EE | .chalp1←1← 88F5 BPL | ||||||
| LDA (fs_options),y ; Load address byte from param block | |||||||
| 88F0 | STA fs_func_code,x ; Store to FS cmd data area | ||||||
| 88F3 | DEY ; Next source byte (descending) | ||||||
| 88F4 | DEX ; Next dest byte | ||||||
| 88F5 | BPL chalp1 ; Loop for 4 bytes | ||||||
| 88F7 | LDX #5 ; X=5: data extent for filename copy | ||||||
| 88F9 | BNE copy_filename_to_cmd ; ALWAYS branch | ||||||
| 88FB | .get_file_protection←1← 88E4 BEQ | ||||||
| JSR decode_attribs_6bit ; A=1: encode protection from param block | |||||||
| 88FE | STA fs_file_attrs ; Store encoded attrs at &0F0E | ||||||
| 8901 | LDY #9 ; Y=9: source offset in param block | ||||||
| 8903 | LDX #8 ; X=8: dest offset in cmd buffer | ||||||
| 8905 | .chalp2←1← 890C BNE | ||||||
| LDA (fs_options),y ; Load byte from param block | |||||||
| 8907 | STA fs_cmd_data,x ; Store to FS cmd buffer | ||||||
| 890A | DEY ; Next source byte (descending) | ||||||
| 890B | DEX ; Next dest byte | ||||||
| 890C | BNE chalp2 ; Loop until X=0 (8 bytes copied) | ||||||
| 890E | LDX #&0a ; X=&0A: data extent past attrs+addrs | ||||||
| 8910 | .copy_filename_to_cmd←2← 88F9 BNE← 892E BNE | ||||||
| JSR copy_string_to_cmd ; Append filename to cmd buffer | |||||||
| 8913 | LDY #&13 ; Y=&13: fn code for FCSAVE (write attrs) | ||||||
| 8915 | BNE send_fs_cmd_v1 ; ALWAYS branch to send command ALWAYS branch | ||||||
| 8917 | .cha6←1← 88D6 BEQ | ||||||
| JSR infol2 ; A=6: copy filename (delete) | |||||||
| 891A | LDY #&14 ; Y=&14: fn code for FCDEL (delete) | ||||||
| 891C | .send_fs_cmd_v1←1← 8915 BNE | ||||||
| BIT tx_ctrl_upper ; Set V=1 (BIT trick: &B3 has bit 6 set) | |||||||
| 891F | JSR init_tx_ctrl_data ; Send via prepare_fs_cmd_v (V=1 path) | ||||||
| 8922 | .check_attrib_result←1← 88D8 BCS | ||||||
| BCS attrib_error_exit ; C=1: &D6 not-found, skip to return | |||||||
| 8924 | BCC argsv_check_return ; C=0: success, copy reply to param block ALWAYS branch | ||||||
| 8926 | .cha4←1← 88E0 BEQ | ||||||
| JSR decode_attribs_6bit ; A=4: encode attrs from param block | |||||||
| 8929 | STA fs_func_code ; Store encoded attrs at &0F06 | ||||||
| 892C | LDX #2 ; X=2: data extent (1 attr byte + fn) | ||||||
| 892E | BNE copy_filename_to_cmd ; ALWAYS branch to append filename ALWAYS branch | ||||||
| 8930 | .cha5←1← 88DC BEQ | ||||||
| LDX #1 ; X=1: filename only, no data extent | |||||||
| 8932 | JSR copy_string_to_cmd ; Copy filename to cmd buffer | ||||||
| 8935 | LDY #&12 ; Y=&12: fn code for FCEXAM (read info) Y=function code for HDRFN | ||||||
| 8937 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 893A | LDA fs_obj_type ; Save object type from FS reply | ||||||
| 893D | STX fs_obj_type ; Clear reply byte (X=0 on success) X=0 on success, &D6 on not-found | ||||||
| 8940 | STX fs_len_clear ; Clear length high byte in reply | ||||||
| 8943 | JSR decode_attribs_5bit ; Decode 5-bit access byte from FS reply | ||||||
| 8946 | LDY #&0e ; Y=&0E: attrs offset in param block | ||||||
| 8948 | STA (fs_options),y ; Store decoded attrs at param block +&0E | ||||||
| 894A | DEY ; Y=&0D: start copy below attrs Y=&0d | ||||||
| 894B | LDX #&0c ; X=&0C: copy from reply offset &0C down | ||||||
| 894D | .copy_fs_reply_to_cb←1← 8954 BNE | ||||||
| LDA fs_cmd_data,x ; Load reply byte (load/exec/length) | |||||||
| 8950 | STA (fs_options),y ; Store to param block | ||||||
| 8952 | DEY ; Next dest byte (descending) | ||||||
| 8953 | DEX ; Next source byte | ||||||
| 8954 | BNE copy_fs_reply_to_cb ; Loop until X=0 (12 bytes copied) | ||||||
| 8956 | INX ; X=0 -> X=2 for length high copy | ||||||
| 8957 | INX ; INX again: X=2 | ||||||
| 8958 | LDY #&11 ; Y=&11: length high dest in param block | ||||||
| 895A | .cha5lp←1← 8961 BPL | ||||||
| LDA fs_access_level,x ; Load length high byte from reply | |||||||
| 895D | STA (fs_options),y ; Store to param block | ||||||
| 895F | DEY ; Next dest byte (descending) | ||||||
| 8960 | DEX ; Next source byte | ||||||
| 8961 | BPL cha5lp ; Loop for 3 length-high bytes | ||||||
| 8963 | LDA fs_cmd_data ; Return object type in A | ||||||
| 8966 | .attrib_error_exit←1← 8922 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 (5 = network FS) A=0, Y>0: read file pointer via FS command &0C (FCRDSE) A=1, Y>0: write file pointer via FS command &0D (FCWRSE) A=2, Y=0: return &01 (command-line tail supported) 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.
|
|||||||||||||
| 8968 | .argsv_handler | ||||||||||||
| JSR save_fscv_args ; Save A/X/Y registers for later restore | |||||||||||||
| 896B | CMP #3 ; Function >= 3? | ||||||||||||
| 896D | BCS restore_args_return ; A>=3 (ensure/flush): no-op for NFS | ||||||||||||
| 896F | CPY #0 ; Test file handle | ||||||||||||
| 8971 | BEQ argsv_fs_query ; Y=0: FS-level query, not per-file | ||||||||||||
| 8973 | JSR handle_to_mask_clc ; Convert handle to bitmask | ||||||||||||
| 8976 | STY fs_cmd_data ; Store bitmask as first cmd data byte | ||||||||||||
| 8979 | LSR ; LSR splits A: C=1 means write (A=1) | ||||||||||||
| 897A | STA fs_func_code ; Store function code to cmd data byte 2 | ||||||||||||
| 897D | BCS save_args_handle ; C=1: write path, copy ptr from caller | ||||||||||||
| 897F | LDY #&0c ; Y=&0C: FCRDSE (read sequential pointer) Y=function code for HDRFN | ||||||||||||
| 8981 | LDX #2 ; X=2: 3 data bytes in command X=preserved through header build | ||||||||||||
| 8983 | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 8986 | STA fs_last_byte_flag ; Clear last-byte flag on success A=0 on success (from build_send_fs_cmd) | ||||||||||||
| 8988 | LDX fs_options ; X = saved control block ptr low | ||||||||||||
| 898A | LDY #2 ; Y=2: copy 3 bytes of file pointer | ||||||||||||
| 898C | STA zp_work_3,x ; Zero high byte of 3-byte pointer | ||||||||||||
| 898E | .copy_fileptr_reply←1← 8995 BPL | ||||||||||||
| LDA fs_cmd_data,y ; Read reply byte from FS cmd data | |||||||||||||
| 8991 | STA zp_work_2,x ; Store to caller's control block | ||||||||||||
| 8993 | DEX ; Next byte (descending) | ||||||||||||
| 8994 | DEY ; Next source byte | ||||||||||||
| 8995 | BPL copy_fileptr_reply ; Loop for all 3 bytes | ||||||||||||
| 8997 | .argsv_check_return←1← 8924 BCC | ||||||||||||
| BCC restore_args_return ; C=0 (read): return to caller | |||||||||||||
| 8999 | .save_args_handle←1← 897D BCS | ||||||||||||
| TYA ; Save bitmask for set_fs_flag later | |||||||||||||
| 899A | PHA ; Push bitmask | ||||||||||||
| 899B | LDY #3 ; Y=3: copy 4 bytes of file pointer | ||||||||||||
| 899D | .copy_fileptr_to_cmd←1← 89A4 BPL | ||||||||||||
| LDA zp_work_3,x ; Read caller's pointer byte | |||||||||||||
| 899F | STA fs_data_count,y ; Store to FS command data area | ||||||||||||
| 89A2 | DEX ; Next source byte | ||||||||||||
| 89A3 | DEY ; Next destination byte | ||||||||||||
| 89A4 | BPL copy_fileptr_to_cmd ; Loop for all 4 bytes | ||||||||||||
| 89A6 | LDY #&0d ; Y=&0D: FCWRSE (write sequential pointer) Y=function code for HDRFN | ||||||||||||
| 89A8 | LDX #5 ; X=5: 6 data bytes in command X=preserved through header build | ||||||||||||
| 89AA | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 89AD | STX fs_last_byte_flag ; Save not-found status from X X=0 on success, &D6 on not-found | ||||||||||||
| 89AF | PLA ; Recover bitmask for EOF hint update | ||||||||||||
| 89B0 | 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.
|
||||||||
| 89B3 | .restore_args_return←7← 871A JMP← 882C JMP← 896D BCS← 8997 BCC← 8A2A BCC← 8A7D JMP← 8E35 JMP | |||||||
| LDA fs_last_byte_flag ; A = saved function code / command | ||||||||
| 89B5 | .restore_xy_return←5← 8966 BPL← 89C6 BNE← 89D6 BPL← 8A01 BCS← 8A0E BNE | |||||||
| LDX fs_options ; X = saved control block ptr low | ||||||||
| 89B7 | LDY fs_block_offset ; Y = saved control block ptr high | |||||||
| 89B9 | RTS ; Return to MOS with registers restored | |||||||
| 89BA | .argsv_fs_query←1← 8971 BEQ | |||||||
| CMP #2 ; Y=0: FS-level queries (no file handle) | ||||||||
| 89BC | BEQ halve_args_a ; A=2: FS-level ensure (write extent) | |||||||
| 89BE | BCS return_a_zero ; A>=3: FS command (ARGSV write) | |||||||
| 89C0 | TAY ; Y = A = byte count for copy loop | |||||||
| 89C1 | BNE osarg1 ; A!=0: copy command context block | |||||||
| 89C3 | LDA #&0a ; FS number 5 (loaded as &0A, LSR'd) | |||||||
| 89C5 | .halve_args_a←1← 89BC BEQ | |||||||
| LSR ; Shared: halve A (A=0 or A=2 paths) | ||||||||
| 89C6 | BNE restore_xy_return ; Return with A = FS number or 1 | |||||||
| 89C8 | .osarg1←2← 89C1 BNE← 89CE BPL | |||||||
| LDA fs_cmd_context,y ; Copy command context to caller's block | ||||||||
| 89CB | STA (fs_options),y ; Store to caller's parameter block | |||||||
| 89CD | DEY ; Next byte (descending) | |||||||
| 89CE | BPL osarg1 ; Loop until all bytes copied | |||||||
| 89D0 | STY zp_work_2,x ; Y=&FF after loop; fill high bytes | |||||||
| 89D2 | STY zp_work_3,x ; Set 32-bit result bytes 2-3 to &FF | |||||||
| 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.
|
||||||||
| 89D4 | .return_a_zero←4← 89BE BCS← 89E4 BNE← 8B19 JMP← 8BBB JMP | |||||||
| LDA #0 ; A=operation (0=close, &40=read, &80=write, &C0=R/W) | ||||||||
| 89D6 | BPL restore_xy_return ; ALWAYS branch | |||||||
FINDV handler (OSFIND entry point)A=0: close file -- delegates to close_handle (&8A10) 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"), and into the sequence number tracking byte for the byte-stream protocol.
|
|||||||||||||||
| 89D8 | .findv_handler | ||||||||||||||
| JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up pointers | |||||||||||||||
| 89DB | SEC ; SEC distinguishes open (A>0) from close | ||||||||||||||
| 89DC | JSR handle_to_mask ; Convert file handle to bitmask (Y2FS) | ||||||||||||||
| 89DF | TAX ; A=preserved | ||||||||||||||
| 89E0 | BEQ close_handle ; A=0: close file(s) | ||||||||||||||
| 89E2 | AND #&3f ; Valid open modes: &40, &80, &C0 only | ||||||||||||||
| 89E4 | BNE return_a_zero ; Invalid mode bits: return | ||||||||||||||
| 89E6 | TXA ; A = original mode byte | ||||||||||||||
| 89E7 | EOR #&80 ; Convert MOS mode to FS protocol flags | ||||||||||||||
| 89E9 | ASL ; ASL: shift mode bits left | ||||||||||||||
| 89EA | STA fs_cmd_data ; Flag 1: read/write direction | ||||||||||||||
| 89ED | ROL ; ROL: Flag 2 into bit 0 | ||||||||||||||
| 89EE | STA fs_func_code ; Flag 2: create vs existing file | ||||||||||||||
| 89F1 | JSR parse_filename_gs ; Parse filename from command line | ||||||||||||||
| 89F4 | LDX #2 ; X=2: copy after 2-byte flags | ||||||||||||||
| 89F6 | JSR copy_string_to_cmd ; Copy filename to FS command buffer | ||||||||||||||
| 89F9 | LDY #6 ; Y=6: FS function code FCOPEN | ||||||||||||||
| 89FB | BIT tx_ctrl_upper ; Set V flag from l83b3 bit 6 | ||||||||||||||
| 89FE | JSR init_tx_ctrl_data ; Build and send FS open command | ||||||||||||||
| 8A01 | BCS restore_xy_return ; Error: restore and return | ||||||||||||||
| 8A03 | LDA fs_cmd_data ; Load reply handle from FS | ||||||||||||||
| 8A06 | TAX ; X = new file handle | ||||||||||||||
| 8A07 | JSR set_fs_flag ; Set EOF hint + sequence bits | ||||||||||||||
| ; OR handle bit into fs_sequence_nos | |||||||||||||||
| ; (&0E08). Without this, a newly opened file could | |||||||||||||||
| ; inherit a stale sequence number from a previous | |||||||||||||||
| ; file using the same handle, causing byte-stream | |||||||||||||||
| ; protocol errors. | |||||||||||||||
| 8A0A | TXA ; A=handle bitmask for new file A=single-bit bitmask | ||||||||||||||
| 8A0B | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) | ||||||||||||||
| 8A0E | 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.
|
||||
| 8A10 | .close_handle←1← 89E0 BEQ | |||
| TYA ; A = handle (Y preserved in A) Y=preserved | ||||
| 8A11 | BNE close_single_handle ; Y>0: close single file | |||
| 8A13 | LDA #osbyte_close_spool_exec ; Close SPOOL/EXEC before FS close-all | |||
| 8A15 | JSR osbyte ; Close any *SPOOL and *EXEC files | |||
| 8A18 | LDY #0 ; Y=0: close all handles on server | |||
| 8A1A | .close_single_handle←1← 8A11 BNE | |||
| STY fs_cmd_data ; Handle byte in FS command buffer | ||||
| 8A1D | LDX #1 ; X=preserved through header build | |||
| 8A1F | LDY #7 ; Y=function code for HDRFN | |||
| 8A21 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||
| 8A24 | LDA fs_cmd_data ; Reply handle for flag update | |||
| 8A27 | JSR set_fs_flag ; Update EOF/sequence tracking bits | |||
| 8A2A | .close_opt_return←1← 8A50 BCC | |||
| BCC restore_args_return ; C=0: restore A/X/Y and return | ||||
| 8A2C | .fscv_0_opt_entry | |||
| BEQ set_messages_flag ; Entry from fscv_0_opt (close-all path) | ||||
| 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").
|
||||||
| 8A2E | .fscv_0_opt | |||||
| CPX #4 ; Is it *OPT 4,Y? | ||||||
| 8A30 | BNE check_opt1 ; No: check for *OPT 1 | |||||
| 8A32 | CPY #4 ; Y must be 0-3 for boot option | |||||
| 8A34 | BCC optl1 ; Y < 4: valid boot option | |||||
| 8A36 | .check_opt1←1← 8A30 BNE | |||||
| DEX ; Not *OPT 4: check for *OPT 1 | ||||||
| 8A37 | BNE opter1 ; Not *OPT 1 either: bad option | |||||
| 8A39 | .set_messages_flag←1← 8A2C BEQ | |||||
| STY fs_messages_flag ; Set local messages flag (*OPT 1,Y) | ||||||
| 8A3C | BCC opt_return ; Return via restore_args_return | |||||
| 8A3E | .opter1←1← 8A37 BNE | |||||
| LDA #7 ; Error index 7 (Bad option) | ||||||
| 8A40 | JMP nlisne ; Generate BRK error | |||||
| 8A43 | .optl1←1← 8A34 BCC | |||||
| STY fs_cmd_data ; Boot option value in FS command | ||||||
| 8A46 | LDY #&16 ; Y=&16: FS function code FCOPT Y=function code for HDRFN | |||||
| 8A48 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||||
| 8A4B | LDY fs_block_offset ; Restore Y from saved value | |||||
| 8A4D | STY fs_boot_option ; Cache boot option locally | |||||
| 8A50 | .opt_return←1← 8A3C BCC | |||||
| BCC close_opt_return ; Return via restore_args_return | ||||||
| 8A52 | .adjust_addrs_9←1← 8B0D JSR | |||||
| LDY #9 ; Y=9: adjust 9 address bytes | ||||||
| 8A54 | JSR adjust_addrs_clc ; Adjust with carry clear | |||||
| 8A57 | .adjust_addrs_1←1← 8C02 JSR | |||||
| LDY #1 ; Y=1: adjust 1 address byte | ||||||
| 8A59 | .adjust_addrs_clc←1← 8A54 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.
|
|||||||||||
| 8A5A | .adjust_addrs←2← 8B13 JSR← 8C0E JSR | ||||||||||
| LDX #&fc ; X=&FC: index into &0E06 area (wraps to 0) | |||||||||||
| 8A5C | .adjust_addr_byte←1← 8A6F BNE | ||||||||||
| LDA (fs_options),y ; Load byte from param block | |||||||||||
| 8A5E | BIT fs_load_addr_2 ; Test sign of adjustment direction | ||||||||||
| 8A60 | BMI subtract_adjust ; Negative: subtract instead | ||||||||||
| 8A62 | ADC fs_cmd_context,x ; Add adjustment value | ||||||||||
| 8A65 | JMP gbpbx ; Skip to store result | ||||||||||
| 8A68 | .subtract_adjust←1← 8A60 BMI | ||||||||||
| SBC fs_cmd_context,x ; Subtract adjustment value | |||||||||||
| 8A6B | .gbpbx←1← 8A65 JMP | ||||||||||
| STA (fs_options),y ; Store adjusted byte back | |||||||||||
| 8A6D | INY ; Next param block byte | ||||||||||
| 8A6E | INX ; Next adjustment byte (X wraps &FC->&00) | ||||||||||
| 8A6F | BNE adjust_addr_byte ; Loop 4 times (X=&FC,&FD,&FE,&FF,done) | ||||||||||
| 8A71 | 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.
|
|||||||||||||||
| 8A72 | .gbpbv_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y to FS workspace | |||||||||||||||
| 8A75 | TAX ; X = call number for range check | ||||||||||||||
| 8A76 | BEQ gbpb_invalid_exit ; A=0: invalid, restore and return | ||||||||||||||
| 8A78 | DEX ; Convert to 0-based (A=0..7) | ||||||||||||||
| 8A79 | CPX #8 ; Range check: must be 0-7 | ||||||||||||||
| 8A7B | BCC gbpbx1 ; In range: continue to handler | ||||||||||||||
| 8A7D | .gbpb_invalid_exit←1← 8A76 BEQ | ||||||||||||||
| JMP restore_args_return ; Out of range: restore args and return | |||||||||||||||
| 8A80 | .gbpbx1←1← 8A7B BCC | ||||||||||||||
| TXA ; Recover 0-based function code | |||||||||||||||
| 8A81 | LDY #0 ; Y=0: param block byte 0 (file handle) | ||||||||||||||
| 8A83 | PHA ; Save function code on stack | ||||||||||||||
| 8A84 | CMP #4 ; A>=4: info queries, dispatch separately | ||||||||||||||
| 8A86 | BCC gbpbe1 ; A<4: file read/write operations | ||||||||||||||
| 8A88 | JMP osgbpb_info ; Dispatch to OSGBPB 5-8 info handler | ||||||||||||||
| 8A8B | .gbpbe1←1← 8A86 BCC | ||||||||||||||
| LDA (fs_options),y ; Get file handle from param block byte 0 | |||||||||||||||
| 8A8D | JSR handle_to_mask_a ; Convert handle to bitmask for EOF flags | ||||||||||||||
| 8A90 | STY fs_cmd_data ; Store handle in FS command data | ||||||||||||||
| 8A93 | LDY #&0b ; Y=&0B: start at param block byte 11 | ||||||||||||||
| 8A95 | LDX #6 ; X=6: copy 6 bytes of transfer params | ||||||||||||||
| 8A97 | .gbpbf1←1← 8AA3 BNE | ||||||||||||||
| LDA (fs_options),y ; Load param block byte | |||||||||||||||
| 8A99 | STA fs_func_code,x ; Store to FS command buffer at &0F06+X | ||||||||||||||
| 8A9C | DEY ; Previous param block byte | ||||||||||||||
| 8A9D | CPY #8 ; Skip param block offset 8 (the handle) | ||||||||||||||
| 8A9F | BNE gbpbx0 ; Not at handle offset: continue | ||||||||||||||
| 8AA1 | DEY ; Extra DEY to skip handle byte | ||||||||||||||
| 8AA2 | .gbpbx0←1← 8A9F BNE | ||||||||||||||
| .gbpbf2←1← 8A9F BNE | |||||||||||||||
| DEX ; Decrement copy counter | |||||||||||||||
| 8AA3 | BNE gbpbf1 ; Loop for all 6 bytes | ||||||||||||||
| 8AA5 | PLA ; Recover function code from stack | ||||||||||||||
| 8AA6 | LSR ; LSR: odd=read (C=1), even=write (C=0) | ||||||||||||||
| 8AA7 | PHA ; Save function code again (need C later) | ||||||||||||||
| 8AA8 | BCC gbpbl1 ; Even (write): X stays 0 | ||||||||||||||
| 8AAA | INX ; Odd (read): X=1 | ||||||||||||||
| 8AAB | .gbpbl1←1← 8AA8 BCC | ||||||||||||||
| STX fs_func_code ; Store FS direction flag | |||||||||||||||
| 8AAE | LDY #&0b ; Y=&0B: command data extent | ||||||||||||||
| 8AB0 | LDX #&91 ; Command &91=put, &92=get | ||||||||||||||
| 8AB2 | PLA ; Recover function code | ||||||||||||||
| 8AB3 | PHA ; Save again for later direction check | ||||||||||||||
| 8AB4 | BEQ gbpb_write_path ; Even (write): keep &91 and Y=&0B | ||||||||||||||
| 8AB6 | LDX #&92 ; Odd (read): use &92 (get) instead | ||||||||||||||
| 8AB8 | DEY ; Read: one fewer data byte in command Y=&0a | ||||||||||||||
| 8AB9 | .gbpb_write_path←1← 8AB4 BEQ | ||||||||||||||
| STX fs_cmd_urd ; Store port to FS command URD field | |||||||||||||||
| 8ABC | STX fs_error_ptr ; Save port for error recovery | ||||||||||||||
| 8ABE | LDX #8 ; X=8: command data bytes | ||||||||||||||
| 8AC0 | LDA fs_cmd_data ; Load handle from FS command data | ||||||||||||||
| 8AC3 | JSR prepare_cmd_with_flag ; Build FS command with handle+flag | ||||||||||||||
| 8AC6 | LDA fs_load_addr_3 ; Save seq# for byte-stream flow control | ||||||||||||||
| 8AC8 | STA fs_sequence_nos ; Store to FS sequence number workspace | ||||||||||||||
| 8ACB | LDX #4 ; X=4: copy 4 address bytes | ||||||||||||||
| 8ACD | .gbpbl3←1← 8AE1 BNE | ||||||||||||||
| LDA (fs_options),y ; Set up source/dest from param block | |||||||||||||||
| 8ACF | STA addr_work,y ; Store as source address | ||||||||||||||
| 8AD2 | STA txcb_pos,y ; Store as current transfer position | ||||||||||||||
| 8AD5 | JSR add_4_to_y ; Skip 4 bytes to reach transfer length | ||||||||||||||
| 8AD8 | ADC (fs_options),y ; Dest = source + length | ||||||||||||||
| 8ADA | STA addr_work,y ; Store as end address | ||||||||||||||
| 8ADD | JSR sub_3_from_y ; Back 3 to align for next iteration | ||||||||||||||
| 8AE0 | DEX ; Decrement byte counter | ||||||||||||||
| 8AE1 | BNE gbpbl3 ; Loop for all 4 address bytes | ||||||||||||||
| 8AE3 | INX ; X=1 after loop | ||||||||||||||
| 8AE4 | .gbpbf3←1← 8AEB BPL | ||||||||||||||
| LDA fs_cmd_csd,x ; Copy CSD data to command buffer | |||||||||||||||
| 8AE7 | STA fs_func_code,x ; Store at &0F06+X | ||||||||||||||
| 8AEA | DEX ; Decrement counter | ||||||||||||||
| 8AEB | BPL gbpbf3 ; Loop for X=1,0 | ||||||||||||||
| 8AED | PLA ; Odd (read): send data to FS first | ||||||||||||||
| 8AEE | BNE gbpb_read_path ; Non-zero: skip write path | ||||||||||||||
| 8AF0 | LDA fs_cmd_urd ; Load port for transfer setup | ||||||||||||||
| 8AF3 | JSR transfer_file_blocks ; Transfer data blocks to fileserver | ||||||||||||||
| 8AF6 | BCS wait_fs_reply ; Carry set: transfer error | ||||||||||||||
| 8AF8 | .gbpb_read_path←1← 8AEE BNE | ||||||||||||||
| JSR send_data_blocks ; Read path: receive data blocks from FS | |||||||||||||||
| 8AFB | .wait_fs_reply←1← 8AF6 BCS | ||||||||||||||
| JSR send_fs_reply_cmd ; Wait for FS reply command | |||||||||||||||
| 8AFE | LDA (fs_options,x) ; Load handle mask for EOF flag update | ||||||||||||||
| 8B00 | BIT fs_cmd_data ; Check FS reply: bit 7 = not at EOF | ||||||||||||||
| 8B03 | BMI skip_clear_flag ; Bit 7 set: not EOF, skip clear | ||||||||||||||
| 8B05 | JSR clear_fs_flag ; At EOF: clear EOF hint for this handle | ||||||||||||||
| 8B08 | .skip_clear_flag←1← 8B03 BMI | ||||||||||||||
| JSR set_fs_flag ; Set EOF hint flag (may be at EOF) | |||||||||||||||
| 8B0B | STX fs_load_addr_2 ; Direction=0: forward adjustment | ||||||||||||||
| 8B0D | JSR adjust_addrs_9 ; Adjust param block addrs by +9 bytes | ||||||||||||||
| 8B10 | DEC fs_load_addr_2 ; Direction=&FF: reverse adjustment | ||||||||||||||
| 8B12 | SEC ; SEC for reverse subtraction | ||||||||||||||
| 8B13 | JSR adjust_addrs ; Adjust param block addrs (reverse) | ||||||||||||||
| 8B16 | ASL fs_cmd_data ; Shift bit 7 into C for return flag | ||||||||||||||
| 8B19 | JMP return_a_zero ; Return via restore_args path | ||||||||||||||
| 8B1C | .get_disc_title←1← 8B4C BEQ | ||||||||||||||
| LDY #&15 ; Y=&15: function code for disc title Y=function code for HDRFN | |||||||||||||||
| 8B1E | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||||
| 8B21 | LDA fs_boot_option ; Load boot option from FS workspace | ||||||||||||||
| 8B24 | STA fs_boot_data ; Store boot option in reply area | ||||||||||||||
| 8B27 | STX fs_load_addr ; X=0: reply data start offset X=0 on success, &D6 on not-found | ||||||||||||||
| 8B29 | STX fs_load_addr_hi ; Clear reply buffer high byte | ||||||||||||||
| 8B2B | LDA #&12 ; A=&12: 18 bytes of reply data | ||||||||||||||
| 8B2D | STA fs_load_addr_2 ; Store as byte count for copy | ||||||||||||||
| 8B2F | 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. |
|
| 8B31 | .osgbpb_info←1← 8A88 JMP |
| LDY #4 ; Y=4: check param block byte 4 | |
| 8B33 | LDA tube_flag ; Check if destination is in Tube space |
| 8B36 | BEQ store_tube_flag ; No Tube: skip Tube address check |
| 8B38 | CMP (fs_options),y ; Compare Tube flag with addr byte 4 |
| 8B3A | BNE store_tube_flag ; Mismatch: not Tube space |
| 8B3C | DEY ; Y=&03 |
| 8B3D | SBC (fs_options),y ; Y=3: subtract addr byte 3 from flag |
| 8B3F | .store_tube_flag←2← 8B36 BEQ← 8B3A BNE |
| STA svc_state ; Non-zero = Tube transfer required | |
| 8B41 | .info2←1← 8B47 BNE |
| LDA (fs_options),y ; Copy param block bytes 1-4 to workspace | |
| 8B43 | STA fs_last_byte_flag,y ; Store to &BD+Y workspace area |
| 8B46 | DEY ; Previous byte |
| 8B47 | BNE info2 ; Loop for bytes 3,2,1 |
| 8B49 | PLA ; Sub-function: AND #3 of (original A - 4) |
| 8B4A | AND #3 ; Mask to 0-3 (OSGBPB 5-8 → 0-3) |
| 8B4C | BEQ get_disc_title ; A=0 (OSGBPB 5): read disc title |
| 8B4E | LSR ; LSR: A=0 (OSGBPB 6) or A=1 (OSGBPB 7) |
| 8B4F | BEQ gbpb6_read_name ; A=0 (OSGBPB 6): read CSD/LIB name |
| 8B51 | BCS gbpb8_read_dir ; C=1 (OSGBPB 8): read filenames from dir |
| 8B53 | .gbpb6_read_name←1← 8B4F BEQ |
| TAY ; Y=0 for CSD or carry for fn code select Y=function code | |
| 8B54 | LDA fs_csd_handle,y ; Get CSD/LIB/URD handles for FS command |
| 8B57 | STA fs_cmd_csd ; Store CSD handle in command buffer |
| 8B5A | LDA fs_lib_handle ; Load LIB handle from workspace |
| 8B5D | STA fs_cmd_lib ; Store LIB handle in command buffer |
| 8B60 | LDA fs_urd_handle ; Load URD handle from workspace |
| 8B63 | STA fs_cmd_urd ; Store URD handle in command buffer |
| 8B66 | LDX #&12 ; X=&12: buffer extent for command data X=buffer extent (command-specific data bytes) |
| 8B68 | STX fs_cmd_y_param ; Store X as function code in header |
| 8B6B | LDA #&0d ; &0D = 13 bytes of reply data expected |
| 8B6D | STA fs_func_code ; Store reply length in command buffer |
| 8B70 | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8B72 | LSR ; LSR: &0D >> 1 = 6 |
| 8B73 | STA fs_cmd_data ; Store as command data byte |
| 8B76 | CLC ; CLC for standard FS path |
| 8B77 | JSR build_send_fs_cmd ; Build and send FS command (DOFSOP) |
| 8B7A | STX fs_load_addr_hi ; X=0 on success, &D6 on not-found |
| 8B7C | INX ; INX: X=1 after build_send_fs_cmd |
| 8B7D | STX fs_load_addr ; Store X as reply start offset |
| 8B7F | .copy_reply_to_caller←2← 8B2F BNE← 8BF7 JSR |
| LDA svc_state ; Copy FS reply to caller's buffer | |
| 8B81 | BNE tube_transfer ; Non-zero: use Tube transfer path |
| 8B83 | LDX fs_load_addr ; X = reply start offset |
| 8B85 | LDY fs_load_addr_hi ; Y = reply buffer high byte |
| 8B87 | .copy_reply_bytes←1← 8B90 BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8B8A | STA (fs_crc_lo),y ; Store to caller's buffer |
| 8B8C | INX ; Next source byte |
| 8B8D | INY ; Next destination byte |
| 8B8E | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8B90 | BNE copy_reply_bytes ; Loop until all bytes copied |
| 8B92 | BEQ gbpb_done ; ALWAYS branch to exit ALWAYS branch |
| 8B94 | .tube_transfer←1← 8B81 BNE |
| JSR tube_claim_loop ; Claim Tube transfer channel | |
| 8B97 | LDA #1 ; A=1: Tube claim type 1 (write) |
| 8B99 | LDX fs_options ; X = param block address low |
| 8B9B | LDY fs_block_offset ; Y = param block address high |
| 8B9D | INX ; INX: advance past byte 0 |
| 8B9E | BNE no_page_wrap ; No page wrap: keep Y |
| 8BA0 | INY ; Page wrap: increment high byte |
| 8BA1 | .no_page_wrap←1← 8B9E BNE |
| JSR tube_addr_claim ; Claim Tube address for transfer | |
| 8BA4 | LDX fs_load_addr ; X = reply data start offset |
| 8BA6 | .tbcop1←1← 8BB4 BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8BA9 | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 8BAC | INX ; Next source byte |
| 8BAD | LDY #6 ; Delay loop for slow Tube co-processor |
| 8BAF | .wait_tube_delay←1← 8BB0 BNE |
| DEY ; Decrement delay counter | |
| 8BB0 | BNE wait_tube_delay ; Loop until delay complete |
| 8BB2 | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8BB4 | BNE tbcop1 ; Loop until all bytes sent to Tube |
| 8BB6 | LDA #&83 ; Release Tube after transfer complete |
| 8BB8 | JSR tube_addr_claim ; Release Tube address claim |
| 8BBB | .gbpb_done←2← 8B92 BEQ← 8C11 BEQ |
| JMP return_a_zero ; Return via restore_args path | |
| 8BBE | .gbpb8_read_dir←1← 8B51 BCS |
| LDY #9 ; OSGBPB 8: read filenames from dir | |
| 8BC0 | LDA (fs_options),y ; Byte 9: number of entries to read |
| 8BC2 | STA fs_func_code ; Store as reply count in command buffer |
| 8BC5 | LDY #5 ; Y=5: byte 5 = starting entry number |
| 8BC7 | LDA (fs_options),y ; Load starting entry number |
| 8BC9 | STA fs_data_count ; Store in command buffer |
| 8BCC | LDX #&0d ; X=&0D: command data extent X=preserved through header build |
| 8BCE | STX fs_reply_cmd ; Store extent in command buffer |
| 8BD1 | LDY #2 ; Y=2: function code for dir read |
| 8BD3 | STY fs_load_addr ; Store 2 as reply data start offset |
| 8BD5 | STY fs_cmd_data ; Store 2 as command data byte |
| 8BD8 | INY ; Y=3: function code for header read Y=function code for HDRFN Y=&03 |
| 8BD9 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8BDC | STX fs_load_addr_hi ; X=0 after FS command completes X=0 on success, &D6 on not-found |
| 8BDE | LDA fs_func_code ; Load reply entry count |
| 8BE1 | STA (fs_options,x) ; Store at param block byte 0 (X=0) |
| 8BE3 | LDA fs_cmd_data ; Load entries-read count from reply |
| 8BE6 | LDY #9 ; Y=9: param block byte 9 |
| 8BE8 | ADC (fs_options),y ; Add to starting entry number |
| 8BEA | STA (fs_options),y ; Update param block with new position |
| 8BEC | LDA txcb_end ; Load total reply length |
| 8BEE | SBC #7 ; Subtract header (7 bytes) from reply len |
| 8BF0 | STA fs_func_code ; Store adjusted length in command buffer |
| 8BF3 | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8BF5 | BEQ skip_copy_reply ; Zero bytes: skip copy |
| 8BF7 | JSR copy_reply_to_caller ; Copy reply data to caller's buffer |
| 8BFA | .skip_copy_reply←1← 8BF5 BEQ |
| LDX #2 ; X=2: clear 3 bytes | |
| 8BFC | .zero_cmd_bytes←1← 8C00 BPL |
| STA fs_data_count,x ; Zero out &0F07+X area | |
| 8BFF | DEX ; Next byte |
| 8C00 | BPL zero_cmd_bytes ; Loop for X=2,1,0 |
| 8C02 | JSR adjust_addrs_1 ; Adjust pointer by +1 (one filename read) |
| 8C05 | SEC ; SEC for reverse adjustment |
| 8C06 | DEC fs_load_addr_2 ; Reverse adjustment for updated counter |
| 8C08 | LDA fs_cmd_data ; Load entries-read count |
| 8C0B | STA fs_func_code ; Store in command buffer |
| 8C0E | JSR adjust_addrs ; Adjust param block addresses |
| 8C11 | BEQ gbpb_done ; Z=1: all done, exit |
| 8C13 | .tube_claim_loop←3← 8B94 JSR← 8C18 BCC← 8E1D JSR |
| LDA #&c3 ; A=&C3: Tube claim with retry | |
| 8C15 | JSR tube_addr_claim ; Request Tube address claim |
| 8C18 | BCC tube_claim_loop ; C=0: claim failed, retry |
| 8C1A | 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 &8C4B 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. |
|
| 8C1B | .fscv_3_star_cmd |
| JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up command ptr | |
| 8C1E | LDX #&ff ; X=&FF: table index (pre-incremented) |
| 8C20 | STX fs_crflag ; Disable column formatting |
| 8C22 | STX escapable ; Enable escape checking |
| 8C24 | .scan_cmd_table←1← 8C3F BNE |
| LDY #&ff ; Y=&FF: input index (pre-incremented) | |
| 8C26 | .decfir←1← 8C31 BEQ |
| INY ; Advance input pointer | |
| 8C27 | INX ; Advance table pointer |
| 8C28 | .decmor←1← 8C43 BCS |
| LDA fs_cmd_match_table,x ; Load table character | |
| 8C2B | BMI dispatch_cmd ; Bit 7: end of name, dispatch |
| 8C2D | EOR (fs_crc_lo),y ; XOR input char with table char |
| 8C2F | AND #&df ; Case-insensitive (clear bit 5) |
| 8C31 | BEQ decfir ; Match: continue comparing |
| 8C33 | DEX ; Mismatch: back up table pointer |
| 8C34 | .decmin←1← 8C38 BPL |
| INX ; Skip to end of table entry | |
| 8C35 | LDA fs_cmd_match_table,x ; Load table byte |
| 8C38 | BPL decmin ; Loop until bit 7 set (end marker) |
| 8C3A | LDA (fs_crc_lo),y ; Check input for '.' abbreviation |
| 8C3C | INX ; Skip past handler high byte |
| 8C3D | CMP #&2e ; Is input '.' (abbreviation)? |
| 8C3F | BNE scan_cmd_table ; No: try next table entry |
| 8C41 | INY ; Yes: skip '.' in input |
| 8C42 | DEX ; Back to handler high byte |
| 8C43 | BCS decmor ; ALWAYS branch; dispatch via BMI |
| 8C45 | .dispatch_cmd←1← 8C2B BMI |
| PHA ; Push handler address high byte | |
| 8C46 | LDA cmd_match_data,x ; Load handler address low byte |
| 8C49 | PHA ; Push handler address low byte |
| 8C4A | RTS ; Dispatch via RTS (addr-1 on stack) |
FS command match table (COMTAB)Format: command letters (bit 7 clear), then dispatch address as two big-endian bytes: high|(bit 7 set), low. The bit 7 set on the high byte marks the end of the command string. The PHA/PHA/RTS trick adds 1 to the stored (address-1). Matching is case-insensitive (AND &DF) and supports '.' abbreviation. Entries: "I." → &80C1 (forward_star_cmd) — placed first as a fudge to catch *I. abbreviation before matching *I AM "I AM" → &8082 (i_am_handler: parse station.net, logon) "EX" → &9FB5 (ex_trampoline: checks next char, dispatches to ex_handler or forward_star_cmd for *EXEC etc.) "BYE"\r → &83C0 (bye_handler: logoff) <catch-all> → &80C1 (forward anything else to FS) |
|
| 8C4B | .fs_cmd_match_table←2← 8C28 LDA← 8C35 LDA |
| EOR #&2e ; Match last char against '.' for *I. abbreviation | |
| 8C4D | EQUB &80 ; I. handler hi → &80C1 (forward_star_cmd) |
| 8C4E | EQUB &C0 ; I. handler lo |
| 8C4F | EQUS "I AM" ; "I AM" command string |
| 8C53 | EQUB &80 ; I AM handler hi → &8082 (i_am_handler) |
| 8C54 | EQUB &81 ; I AM handler lo |
| 8C55 | EQUS "EX" ; "EX" command string |
| 8C57 | EQUB &9F ; EX handler hi → &9FB5 (ex_trampoline) |
| 8C58 | EQUB &B4 ; EX handler lo |
| 8C59 | EQUS "BYE" ; "BYE" command string |
| 8C5C | EQUB &0D ; CR terminator for BYE |
| 8C5D | EQUB &83 ; BYE handler hi → &83C0 (bye_handler) |
| 8C5E | EQUB &BF ; BYE handler lo |
| 8C5F | EQUB &80 ; Catch-all hi → &80C1 (forward_star_cmd) |
| 8C60 | EQUB &C0 ; Catch-all lo |
| 8C61 | .ex_handler←1← 9FBB JMP |
| LDX #1 ; X=1: force one entry per line for *EX | |
| 8C63 | LDA #3 ; A=3: examine format code |
| 8C65 | BNE init_cat_params ; ALWAYS branch |
*CAT handler (directory catalogue)Initialises &B5=&0B (examine arg count) and &B7=&03 (column count). The catalogue protocol is multi-step: first sends FCREAD (&12: examine) to get the directory header, then sends FCUSER (&15: read user environment) to get CSD, disc, and library names, then sends FC &03 (examine entries) 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 (&B9) 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. |
|
| 8C67 | .fscv_5_cat |
| LDX #3 ; X=3: column count for multi-column layout | |
| 8C69 | STX fs_crflag ; CRFLAG=3: first entry will trigger newline |
| 8C6B | LDY #&ff ; Y=&FF: mark as escapable |
| 8C6D | STY escapable ; Store escapable flag for Escape checking |
| 8C6F | INY ; Y=&00 |
| 8C70 | LDA #&0b ; A=&0B: examine argument count |
| 8C72 | .init_cat_params←1← 8C65 BNE |
| STA fs_work_5 ; Store examine argument count | |
| 8C74 | STX fs_work_7 ; Store column count |
| 8C76 | LDA #6 ; A=6: examine format type in command |
| 8C78 | STA fs_cmd_data ; Store format type at &0F05 |
| 8C7B | JSR parse_filename_gs_y ; Set up command parameter pointers |
| 8C7E | LDX #1 ; X=1: copy dir name at cmd offset 1 |
| 8C80 | JSR copy_string_to_cmd ; Copy directory name to command buffer |
| 8C83 | LDY #&12 ; Y=function code for HDRFN |
| 8C85 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8C88 | LDX #3 ; X=3: start printing from reply offset 3 |
| 8C8A | JSR print_reply_bytes ; Print directory title (10 chars) |
| 8C8D | JSR print_inline ; Print '(' |
| 8C90 | EQUS "(" ; Inline string "(" |
| 8C91 | LDA fs_reply_stn ; Load station number from FS reply |
| 8C94 | JSR print_decimal ; Print station number as decimal |
| 8C97 | JSR print_inline ; Print ') ' |
| 8C9A | EQUS ") " ; Inline string ") " |
| 8CA0 | LDY fs_access_level ; Access level byte: 0=Owner, non-zero=Public |
| 8CA3 | BNE print_public ; Non-zero: Public access |
| 8CA5 | JSR print_inline ; Print 'Owner' + CR |
| 8CA8 | EQUS "Owner." ; Inline string "Owner" + CR |
| 8CAE | BNE print_user_env ; Always branches (print_inline sets N=1) |
| 8CB0 | .print_public←1← 8CA3 BNE |
| JSR print_inline ; Print 'Public' + CR | |
| 8CB3 | EQUS "Public." ; Print ' Option ' |
| 8CBA | .print_user_env←1← 8CAE BNE |
| LDY #&15 ; Y=function code for HDRFN | |
| 8CBC | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8CBF | INX ; X=1: past command code byte |
| 8CC0 | LDY #&10 ; Y=&10: print 16 characters |
| 8CC2 | JSR print_reply_counted ; Print disc/CSD name from reply |
| 8CC5 | JSR print_inline ; Print ' Option ' |
| 8CC8 | EQUS " Option " ; Print ' (' |
| 8CD3 | LDA fs_boot_option ; Load boot option from workspace |
| 8CD6 | TAX ; X = boot option for name table lookup |
| 8CD7 | JSR print_hex ; Print boot option as hex digit |
| 8CDA | JSR print_inline ; Print ' (' |
| 8CDD | EQUS " (" ; Inline string " (" |
| 8CDF | LDY option_name_offsets,x ; Load string offset for option name |
| 8CE2 | .print_option_name←1← 8CEB BNE |
| LDA option_name_offsets,y ; Load character from option name string | |
| 8CE5 | BMI done_option_name ; Bit7 set: string terminated, done |
| 8CE7 | JSR osasci ; Write character |
| 8CEA | INY ; Next character |
| 8CEB | BNE print_option_name ; Continue printing option name |
| 8CED | .done_option_name←1← 8CE5 BMI |
| JSR print_inline ; Print ')' + CR + 'Dir. ' | |
| 8CF0 | EQUS ").Dir. " ; Inline string ")" + CR + "Dir. " |
| 8CF7 | LDX #&11 ; X=&11: directory name offset in reply |
| 8CF9 | JSR print_reply_bytes ; Print current directory name |
| 8CFC | JSR print_inline ; Print ' Lib. ' |
| 8CFF | EQUS " Lib. " ; Store entry start offset for request |
| 8D09 | LDX #&1b ; X=&1B: library name offset in reply |
| 8D0B | JSR print_reply_bytes ; Print library name |
| 8D0E | JSR osnewl ; Print two CRs (blank line) Write newline (characters 10 and 13) |
| 8D11 | .fetch_dir_batch←1← 8D45 BNE |
| STY fs_func_code ; Store entry start offset for request | |
| 8D14 | STY fs_work_4 ; Save start offset in zero page for loop |
| 8D16 | LDX fs_work_5 ; Load examine arg count for batch size |
| 8D18 | STX fs_data_count ; Store as request count at &0F07 |
| 8D1B | .cat_examine_loop |
| LDX fs_work_7 ; Load column count for display format | |
| 8D1D | STX fs_cmd_data ; Store column count in command data |
| 8D20 | LDX #3 ; X=3: copy directory name at offset 3 |
| 8D22 | JSR copy_string_to_cmd ; Append directory name to examine command |
| 8D25 | LDY #3 ; Y=function code for HDRFN |
| 8D27 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8D2A | INX ; X past command code byte in reply |
| 8D2B | LDA fs_cmd_data ; Load entry count from reply |
| 8D2E | BNE process_entries ; Zero entries returned = end of directory |
| 8D30 | JMP osnewl ; Write newline (characters 10 and 13) |
| 8D33 | .process_entries←1← 8D2E BNE |
| PHA ; Save entry count for batch processing | |
| 8D34 | .scan_entry_terminator←1← 8D38 BPL |
| INY ; Advance Y past entry data bytes | |
| 8D35 | LDA fs_cmd_data,y ; Read entry byte from reply buffer |
| 8D38 | BPL scan_entry_terminator ; Loop until high-bit terminator found |
| 8D3A | STA fs_cmd_lib,y ; Store terminator as print boundary |
| 8D3D | JSR cat_column_separator ; Print/format this directory entry |
| 8D40 | PLA ; Restore entry count from stack |
| 8D41 | CLC ; CLC for addition |
| 8D42 | ADC fs_work_4 ; Advance start offset by entry count |
| 8D44 | TAY ; Y = new entry start offset |
| 8D45 | BNE fetch_dir_batch ; More entries: fetch next batch |
| 8D47 | .print_reply_bytes←3← 8C8A JSR← 8CF9 JSR← 8D0B JSR |
| LDY #&0a ; Y=&0A: default print 10 characters | |
| 8D49 | .print_reply_counted←2← 8CC2 JSR← 8D51 BNE |
| LDA fs_cmd_data,x ; Load reply byte at offset X | |
| 8D4C | JSR osasci ; Write character |
| 8D4F | INX ; Next reply byte |
| 8D50 | DEY ; Decrement character count |
| 8D51 | BNE print_reply_counted ; Loop for remaining characters |
| ; Option name encoding: the boot option names ("Off", "Load", | |
| ; "Run", "Exec") are scattered through the code rather than | |
| ; stored as a contiguous table. They are addressed via ; base+offset | |
| ; from l8d54 (&8D54), whose four bytes are offsets into page ; &8D: | |
| ; &2B→&8D7F "Off", &3E→&8D92 "Load", | |
| ; &66→&8DBA "Run", &18→&8D6C "Exec" | |
| ; Each string is terminated by the next instruction's opcode | |
| ; having bit 7 set (e.g. LDA #imm = &A9, RTS = &60). | |
| 8D53 | .return_9 |
| RTS ; Return from column separator | |
| 8D54 | .option_name_offsets←2← 8CDF LDY← 8CE2 LDA |
| EQUS "+>f" ; Option name offsets (4 entries) | |
| 8D57 | EQUB &18 ; Opt 3 (Exec) name offset: &18 -> &8D6C |
| 8D58 | EQUS "L.!" ; Opt 1 (Load): "L.!" command prefix |
Boot command strings for auto-bootThe four boot options use OSCLI strings at offsets within page &8D. The offset table at boot_option_offsets+1 (&8D68) is indexed by the boot option value (0-3); each byte is the low byte of the string address, with the page high byte &8D loaded separately: Option 0 (Off): offset &67 → &8D67 = bare CR (empty command) Option 1 (Load): offset &58 → &8D58 = "L.!BOOT" (the bytes &4C='L', &2E='.', &21='!' precede "BOOT" + CR at &8D5F) Option 2 (Run): offset &5A → boot_cmd_strings-1 = "!BOOT" (*RUN) Option 3 (Exec): offset &60 → &8D60 = "E.!BOOT" This is a classic BBC ROM space optimisation: the string data overlaps with other byte sequences to save space. The &0D byte at &8D67 terminates "E.!BOOT" AND doubles as the bare-CR command for boot option 0. |
|
| 8D5B | .boot_cmd_strings |
| EQUS "BOOT" ; "BOOT" string for boot option 2 | |
| 8D5F | EQUB &0D ; CR terminator for BOOT string |
| 8D60 | EQUS "E.!BOOT" ; Opt 3 (Exec): "E.!BOOT" command string |
Boot option → OSCLI string offset tableFive bytes: the first byte (&0D) is the bare-CR target for boot option 0; bytes 1-4 are the offset table indexed by boot option (0-3). Each offset is the low byte of a pointer into page &8D. The code reads from boot_option_offsets+1 (&8D68) via LDX l8d68,Y with Y=boot_option, then LDY #&8D, JMP oscli. See boot_cmd_strings for the target strings. |
|
| 8D67 | .boot_option_offsets |
| EQUB &0D ; CR terminator / Opt 0 bare CR command | |
| 8D68 | .boot_string_offsets←1← 8E4B LDX |
| EQUB &67 ; Opt 0 (Off): bare CR at &8D67 | |
| 8D69 | EQUB &58 ; Opt 1 (Load): L.!BOOT at &8D58 |
| 8D6A | EQUB &5A ; Opt 2 (Run): !BOOT at &8D5A |
| 8D6B | EQUB &60 ; Opt 3 (Exec): E.!BOOT at &8D60 |
| 8D6C | EQUS "Exec" ; "Exec" option name string |
| 8D70 | .print_hex_bytes←2← 8805 JSR← 880A JSR |
| LDX #4 ; X=4: print 4 hex bytes | |
| 8D72 | .num01←2← 8811 JSR← 8D79 BNE |
| LDA (fs_options),y ; Load byte from parameter block | |
| 8D74 | JSR print_hex ; Print as two hex digits |
| 8D77 | DEY ; Next byte (descending) |
| 8D78 | DEX ; Count down |
| 8D79 | BNE num01 ; Loop until 4 bytes printed |
| 8D7B | .print_space←1← 87FB JSR |
| LDA #&20 ; A=space character | |
| 8D7D | BNE print_digit ; ALWAYS branch |
| 8D7F | EQUS "Off" ; Return; X = next free position in buffer |
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.
|
||||||||
| 8D82 | .infol2←5← 809D JSR← 80C1 JSR← 871D JSR← 8917 JSR← 8DDF JSR | |||||||
| .copy_filename←5← 809D JSR← 80C1 JSR← 871D JSR← 8917 JSR← 8DDF JSR | ||||||||
| LDX #0 ; Start writing at &0F05 (after cmd header) | ||||||||
| 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.
|
|||||||||||
| 8D84 | .copy_string_to_cmd←6← 87CA JSR← 8910 JSR← 8932 JSR← 89F6 JSR← 8C80 JSR← 8D22 JSR | ||||||||||
| LDY #0 ; Start copying from offset 0 | |||||||||||
| 8D86 | .copy_string_from_offset←1← 8D8F BNE | ||||||||||
| LDA (fs_crc_lo),y ; Load next byte from source string | |||||||||||
| 8D88 | STA fs_cmd_data,x ; Store to FS command buffer (&0F05+X) | ||||||||||
| 8D8B | INX ; Advance write position | ||||||||||
| 8D8C | INY ; Advance source pointer | ||||||||||
| 8D8D | EOR #&0d ; XOR with CR: result=0 if byte was CR | ||||||||||
| 8D8F | BNE copy_string_from_offset ; Loop until CR copied | ||||||||||
| 8D91 | .return_5←1← 8D9B BMI | ||||||||||
| RTS ; Return; X = next free position in buffer | |||||||||||
| 8D92 | EQUS "Load" ; Transfer to A for modulo | ||||||||||
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. |
|
| 8D96 | .fsreply_0_print_dir |
| LDX #0 ; X=0: start from first reply byte | |
| 8D98 | .print_dir_from_offset←2← 87EA JSR← 8DB8 BNE |
| LDA fs_cmd_data,x ; Load byte from FS reply buffer | |
| 8D9B | BMI return_5 ; Bit 7 set: end of string, return |
| 8D9D | BNE print_newline ; Non-zero: print character |
| fall through ↓ | |
Print catalogue column separator or newlineHandles column formatting for *CAT display. On a null byte separator, advances the column counter modulo 4: prints a 2-space separator between columns, or a CR at column 0. Called from fsreply_0_print_dir. |
|
| 8D9F | .cat_column_separator←1← 8D3D JSR |
| LDY fs_crflag ; Null byte: check column counter | |
| 8DA1 | BMI print_cr ; Negative: print CR (no columns) |
| 8DA3 | INY ; Advance column counter |
| 8DA4 | TYA ; Transfer to A for modulo |
| 8DA5 | AND #3 ; Modulo 4 columns |
| 8DA7 | STA fs_crflag ; Update column counter |
| 8DA9 | BEQ print_cr ; Column 0: start new line |
| 8DAB | JSR print_inline ; Print 2-space column separator |
| 8DAE | EQUS " " ; Print hundreds digit |
| 8DB0 | BNE next_dir_entry ; ALWAYS branch to next byte |
| 8DB2 | .print_cr←2← 8DA1 BMI← 8DA9 BEQ |
| LDA #&0d ; CR = carriage return | |
| 8DB4 | .print_newline←1← 8D9D BNE |
| JSR osasci ; Write character 13 | |
| 8DB7 | .next_dir_entry←1← 8DB0 BNE |
| INX ; Next byte in reply buffer | |
| 8DB8 | BNE print_dir_from_offset ; Loop until end of buffer |
| 8DBA | EQUS "Run" ; A = dividend (from Y) |
| fall through ↓ | |
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.
|
|||||||||||
| 8DBD | .print_decimal←2← 8248 JSR← 8C94 JSR | ||||||||||
| TAY ; Y = value to print | |||||||||||
| 8DBE | LDA #&64 ; Divisor = 100 (hundreds digit) | ||||||||||
| 8DC0 | JSR print_decimal_digit ; Print hundreds digit | ||||||||||
| 8DC3 | LDA #&0a ; Divisor = 10 (tens digit) | ||||||||||
| 8DC5 | JSR print_decimal_digit ; Print tens digit | ||||||||||
| 8DC8 | LDA #1 ; Divisor = 1; fall through to units | ||||||||||
| fall through ↓ | |||||||||||
Print one decimal digit by repeated subtractionDivides Y by A using repeated subtraction. Prints the quotient as an ASCII digit ('0'-'9') via OSASCI. Returns with the remainder in Y. X starts at &2F ('0'-1) and increments once per subtraction, giving the ASCII digit directly.
|
|||||||||
| 8DCA | .print_decimal_digit←2← 8DC0 JSR← 8DC5 JSR | ||||||||
| STA fs_error_ptr ; Save divisor to workspace | |||||||||
| 8DCC | TYA ; A = dividend (from Y) | ||||||||
| 8DCD | LDX #&2f ; X = &2F = ASCII '0' - 1 | ||||||||
| 8DCF | SEC ; Prepare for subtraction | ||||||||
| 8DD0 | .divide_subtract←1← 8DD3 BCS | ||||||||
| INX ; Count one subtraction (next digit value) | |||||||||
| 8DD1 | SBC fs_error_ptr ; A = A - divisor | ||||||||
| 8DD3 | BCS divide_subtract ; Loop while A >= 0 (borrow clear) | ||||||||
| 8DD5 | ADC fs_error_ptr ; Undo last subtraction: A = remainder | ||||||||
| 8DD7 | TAY ; Y = remainder for caller | ||||||||
| 8DD8 | TXA ; A = X = ASCII digit character | ||||||||
| 8DD9 | .print_digit←1← 8D7D BNE | ||||||||
| JMP osasci ; Write character | |||||||||
FSCV 2/4: */ (run) and *RUN handlerParses the filename via parse_filename_gs and calls infol2, then falls through to fsreply_4_notify_exec to set up and send the FS load-as-command request. |
|
| 8DDC | .fscv_2_star_run |
| JSR parse_filename_gs ; Parse filename from command line | |
| 8DDF | JSR infol2 ; Copy filename to FS command buffer |
| fall through ↓ | |
FS reply 4: send FS load-as-command and execute responseInitialises a GS reader to skip past the filename and calculate the command context address, then sets up an FS command with function code &05 (FCCMND: load as command) using send_fs_examine. If a Tube co-processor is present (tube_flag != 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. |
|
| 8DE2 | .fsreply_4_notify_exec |
| LDY #0 ; Y=0: start of text for GSINIT | |
| 8DE4 | CLC ; CLC before GSINIT call |
| 8DE5 | JSR gsinit ; GSINIT/GSREAD: skip past the filename |
| 8DE8 | .skip_gs_filename←1← 8DEB BCC |
| JSR gsread ; Read next filename character | |
| 8DEB | BCC skip_gs_filename ; C=0: more characters, keep reading |
| 8DED | JSR skip_spaces ; Skip spaces after filename |
| 8DF0 | CLC ; Calculate context addr = text ptr + Y |
| 8DF1 | TYA ; Y = offset past filename end |
| 8DF2 | ADC os_text_ptr ; Add text pointer low byte |
| 8DF4 | STA fs_cmd_context ; Store context address low byte |
| 8DF7 | LDA os_text_ptr_hi ; Load text pointer high byte |
| 8DF9 | ADC #0 ; Add carry from low byte addition |
| 8DFB | STA fs_context_hi ; Store context address high byte |
| 8DFE | LDX #&0e ; X=&0E: FS command buffer offset |
| 8E00 | STX fs_block_offset ; Store block offset for FS command |
| 8E02 | LDA #&10 ; A=&10: 16 bytes of command data |
| 8E04 | STA fs_options ; Store options byte |
| 8E06 | STA fs_work_16 ; Store to FS workspace |
| 8E09 | LDX #&4a ; X=&4A: TXCB size for load command |
| 8E0B | LDY #5 ; Y=5: FCCMND (load as command) |
| 8E0D | JSR send_fs_examine ; Send FS examine/load command |
| 8E10 | LDA tube_flag ; Check for Tube co-processor |
| 8E13 | BEQ exec_local ; No Tube: execute locally |
| 8E15 | ADC fs_load_upper ; Check load address upper bytes |
| 8E18 | ADC fs_addr_check ; Continue address range check |
| 8E1B | BCS exec_local ; Carry set: not Tube space, exec locally |
| 8E1D | JSR tube_claim_loop ; Claim Tube transfer channel |
| 8E20 | LDX #9 ; X=9: source offset in FS reply |
| 8E22 | LDY #&0f ; Y=&0F: page &0F (FS command buffer) |
| 8E24 | LDA #4 ; A=4: Tube transfer type 4 (256-byte) |
| 8E26 | JMP tube_addr_claim ; Transfer data to Tube co-processor |
| 8E29 | .exec_local←2← 8E13 BEQ← 8E1B BCS |
| ROL ; ROL: restore A (undo ADC carry) | |
| 8E2A | .exec_at_load_addr |
| JMP (fs_load_vector) ; Execute at load address via indirect JMP | |
Set library handleStores Y into &0E04 (library directory handle in FS workspace). Falls through to JMP restore_args_return if Y is non-zero.
|
||||
| 8E2D | .fsreply_5_set_lib | |||
| STY fs_lib_handle ; Save library handle from FS reply | ||||
| 8E30 | BCC jmp_restore_args ; SDISC path: skip CSD, jump to return | |||
| fall through ↓ | ||||
Set CSD handleStores Y into &0E03 (current selected directory handle). Falls through to JMP restore_args_return.
|
||||
| 8E32 | .fsreply_3_set_csd | |||
| STY fs_csd_handle ; Store CSD handle from FS reply | ||||
| 8E35 | .jmp_restore_args←2← 8E30 BCC← 8E46 BCC | |||
| JMP restore_args_return ; 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. |
|
| 8E38 | .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 restore_args_return via jmp_restore_args. Called when the FS reply contains updated handle values but no boot action is needed. |
|
| 8E39 | .fsreply_2_copy_handles |
| LDX #3 ; Copy 4 bytes: boot option + 3 handles | |
| 8E3B | BCC copy_handles_loop ; SDISC: skip boot option, copy handles only |
| 8E3D | .logon2←1← 8E44 BPL |
| LDA fs_cmd_data,x ; Load from FS reply (&0F05+X) | |
| 8E40 | STA fs_urd_handle,x ; Store to handle workspace (&0E02+X) |
| 8E43 | .copy_handles_loop←1← 8E3B BCC |
| DEX ; Next handle (descending) | |
| 8E44 | BPL logon2 ; Loop while X >= 0 |
| 8E46 | BCC jmp_restore_args ; 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. |
|
| 8E48 | .boot_cmd_execute |
| LDY fs_boot_option ; Y = boot option from FS workspace | |
| 8E4B | LDX boot_string_offsets,y ; X = command string offset from table |
| 8E4E | LDY #&8d ; Y = &8D (high byte of command address) |
| 8E50 | JMP oscli ; Execute boot command string via OSCLI |
Load handle from &F0 and calculate workspace offsetLoads the file handle byte from &F0, then falls through to calc_handle_offset which converts handle * 12 to a workspace byte offset. Validates offset < &48.
|
||||||||
| 8E53 | .load_handle_calc_offset←2← 8E6D JSR← 8E7D JSR | |||||||
| LDA osword_pb_ptr ; Load handle from &F0 | ||||||||
| 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.
|
|||||||||||
| 8E55 | .calc_handle_offset←3← 8309 JSR← 8F8D JSR← 8FA6 JSR | ||||||||||
| ASL ; A = handle * 2 | |||||||||||
| 8E56 | ASL ; A = handle * 4 | ||||||||||
| 8E57 | PHA ; Push handle*4 onto stack | ||||||||||
| 8E58 | ASL ; A = handle * 8 | ||||||||||
| 8E59 | TSX ; X = stack pointer | ||||||||||
| 8E5A | ADC error_text,x ; A = handle*8 + handle*4 = handle*12 | ||||||||||
| 8E5D | TAY ; Y = offset into handle workspace | ||||||||||
| 8E5E | PLA ; Clean up stack (discard handle*4) | ||||||||||
| 8E5F | CMP #&48 ; Offset >= &48? (6 handles max) | ||||||||||
| 8E61 | BCC return_6 ; Valid: return with C clear | ||||||||||
| 8E63 | LDY #0 ; Invalid: Y = 0 | ||||||||||
| 8E65 | TYA ; A = 0, C set (error) A=&00 | ||||||||||
| 8E66 | .return_6←1← 8E61 BCC | ||||||||||
| .return_calc_handle←1← 8E61 BCC | |||||||||||
| RTS ; Return after calculation | |||||||||||
| ; *NET1: read file handle from received packet. | |||||||||||
| ; Reads a byte from offset &6F of the RX buffer (net_rx_ptr) | |||||||||||
| ; and falls through to net_2_read_handle_entry's common path. | |||||||||||
| 8E67 | .net_1_read_handle | ||||||||||
| LDY #&6f ; Y=&6F: RX buffer handle offset | |||||||||||
| 8E69 | LDA (net_rx_ptr),y ; Read handle from RX packet | ||||||||||
| 8E6B | BCC store_handle_return ; Valid handle: store and return | ||||||||||
| fall through ↓ | |||||||||||
*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.
|
||||
| 8E6D | .net_2_read_handle_entry | |||
| JSR load_handle_calc_offset ; Look up handle &F0 in workspace | ||||
| 8E70 | BCS rxpol2 ; Invalid handle: return 0 | |||
| 8E72 | LDA (nfs_workspace),y ; Load stored handle value | |||
| 8E74 | CMP #&3f ; &3F = unused/closed slot marker | |||
| 8E76 | BNE store_handle_return ; Slot in use: return actual value | |||
| 8E78 | .rxpol2←2← 8E70 BCS← 8E80 BCS | |||
| LDA #0 ; Return 0 for closed/invalid handle | ||||
| 8E7A | .store_handle_return←2← 8E6B BCC← 8E76 BNE | |||
| STA osword_pb_ptr ; Store result back to &F0 | ||||
| 8E7C | RTS ; Return | |||
*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. Returns via RTS (earlier versions preserved the carry flag across the write using ROL/ROR on rx_flags, but 3.60 simplified this).
|
||||
| 8E7D | .net_3_close_handle | |||
| JSR load_handle_calc_offset ; Look up handle &F0 in workspace | ||||
| 8E80 | BCS rxpol2 ; Invalid handle: return 0 | |||
| 8E82 | LDA #&3f ; &3F = '?' marks slot as unused | |||
| 8E84 | STA (nfs_workspace),y ; Write close marker to workspace slot | |||
| 8E86 | RTS ; Return | |||
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 range check and dispatch at osword_12_handler (&8E8D). |
|
| 8E87 | .svc_8_osword |
| LDA osbyte_a_copy ; Command code from &EF | |
| 8E89 | SBC #&0f ; Subtract &0F: OSWORD &0F-&13 become indices 0-4 |
| 8E8B | BMI return_7 ; Outside our OSWORD range, exit |
| fall through ↓ | |
OSWORD range check, dispatch, and register restoreReached by fall-through from svc_8_osword with A = OSWORD number minus &0F. Rejects indices >= 5 (only OSWORDs &0F-&13 are handled). Dispatches to the appropriate handler via fs_osword_dispatch, then on return copies 3 bytes from (net_rx_ptr)+0..2 back to &AA-&AC (restoring the param block pointer that was saved by fs_osword_dispatch before dispatch). The actual OSWORD &12 sub-function dispatch (read/set station, protection, handles etc.) lives in sub_c8f01. |
|
| 8E8D | .osword_12_handler |
| CMP #5 ; Only OSWORDs &0F-&13 (index 0-4) | |
| 8E8F | BCS return_7 ; Index >= 5: not ours, return |
| 8E91 | JSR fs_osword_dispatch ; Dispatch via PHA/PHA/RTS table |
| 8E94 | LDY #2 ; Y=2: restore 3 bytes (&AA-&AC) |
| 8E96 | .copy_param_ptr←1← 8E9C BPL |
| LDA (net_rx_ptr),y ; Load saved param block byte | |
| 8E98 | STA osword_flag,y ; Restore to &AA-&AC |
| 8E9B | DEY ; Next byte (descending) |
| 8E9C | BPL copy_param_ptr ; Loop for all 3 bytes |
| 8E9E | RTS ; Return to service handler |
PHA/PHA/RTS dispatch for filing system OSWORDs |
|
| 8E9F | .fs_osword_dispatch←1← 8E91 JSR |
| TAX ; X = sub-function code for table lookup | |
| 8EA0 | LDA fs_osword_tbl_hi,x ; Load handler address high byte from table |
| 8EA3 | PHA ; Push high byte for RTS dispatch |
| 8EA4 | LDA osword_handler_lo,x ; Load handler address low byte from table |
| 8EA7 | .fs_osword_tbl_lo |
| PHA ; Push low byte for RTS dispatch | |
| 8EA8 | LDY #2 ; Y=2: save 3 bytes (&AA-&AC) |
| 8EAA | .save1←1← 8EB0 BPL |
| LDA osword_flag,y ; Load param block pointer byte | |
| 8EAD | STA (net_rx_ptr),y ; Save to NFS workspace via (net_rx_ptr) |
| 8EAF | DEY ; Next byte (descending) |
| 8EB0 | BPL save1 ; Loop for all 3 bytes |
| 8EB2 | INY ; Y=0 after BPL exit; INY makes Y=1 |
| 8EB3 | LDA (osword_pb_ptr),y ; Read sub-function code from (&F0)+1 |
| 8EB5 | STY svc_state ; Store Y=1 to &A9 |
| 8EB7 | .return_7←2← 8E8B BMI← 8E8F BCS |
| RTS ; RTS dispatches to pushed handler address | |
| 8EB8 | .osword_handler_lo←1← 8EA4 LDA |
| EQUB <(osword_0f_handler-1) ; lo(osword_0f_handler-1): OSWORD &0F | |
| 8EB9 | EQUB <(osword_10_handler-1) ; lo(osword_10_handler-1): OSWORD &10 |
| 8EBA | EQUB <(osword_11_handler-1) ; lo(osword_11_handler-1): OSWORD &11 |
| 8EBB | EQUB <(osword_12_dispatch-1) ; lo(osword_12_dispatch-1): OSWORD &12 |
| 8EBC | EQUB <(econet_tx_rx-1) ; lo(econet_tx_rx-1): OSWORD &13 |
| 8EBD | .fs_osword_tbl_hi←1← 8EA0 LDA |
| EQUB >(osword_0f_handler-1) ; hi(osword_0f_handler-1): OSWORD &0F | |
| 8EBE | EQUB >(osword_10_handler-1) ; hi(osword_10_handler-1): OSWORD &10 |
| 8EBF | EQUB >(osword_11_handler-1) ; hi(osword_11_handler-1): OSWORD &11 |
| 8EC0 | EQUB >(osword_12_dispatch-1) ; hi(osword_12_dispatch-1): OSWORD &12 |
| 8EC1 | EQUB >(econet_tx_rx-1) ; hi(econet_tx_rx-1): OSWORD &13 |
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.
|
|||||||||||||
| 8EC2 | .osword_0f_handler | ||||||||||||
| ASL tx_clear_flag ; ASL TXCLR: C=1 means TX free to claim | |||||||||||||
| 8EC5 | TYA ; Save Y (param block high) for later | ||||||||||||
| 8EC6 | BCC readry ; C=0: TX busy, return error status | ||||||||||||
| 8EC8 | LDA net_rx_ptr_hi ; User TX CB in workspace page (high byte) | ||||||||||||
| 8ECA | STA ws_ptr_hi ; Set param block high byte | ||||||||||||
| 8ECC | STA nmi_tx_block_hi ; Set LTXCBP high byte for low-level TX | ||||||||||||
| 8ECE | LDA #&6f ; &6F: offset into workspace for user TXCB | ||||||||||||
| 8ED0 | STA ws_ptr_lo ; Set param block low byte | ||||||||||||
| 8ED2 | STA nmi_tx_block ; Set LTXCBP low byte for low-level TX | ||||||||||||
| 8ED4 | LDX #&0f ; X=15: copy 16 bytes (OSWORD param block) | ||||||||||||
| 8ED6 | JSR copy_param_workspace ; Copy param block to user TX control block | ||||||||||||
| 8ED9 | JMP start_adlc_tx ; Start user transmit via BRIANX | ||||||||||||
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 (&8EE7) to return just the buffer size and args size without copying the data. |
|
| 8EDC | .osword_11_handler |
| LDA net_rx_ptr_hi ; Set source high byte from workspace page | |
| 8EDE | STA ws_ptr_hi ; Store as copy source high byte in &AC |
| 8EE0 | LDY #&7f ; JSRSIZ at workspace offset &7F |
| 8EE2 | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8EE4 | INY ; Y=&80: start of JSR argument data Y=&80 |
| 8EE5 | STY ws_ptr_lo ; Store &80 as copy source low byte |
| 8EE7 | TAX ; X = buffer size (loop counter) |
| 8EE8 | DEX ; X = size-1 (0-based count for copy) |
| 8EE9 | LDY #0 ; Y=0: start of destination param block |
| 8EEB | JSR copy_param_workspace ; Copy X+1 bytes from workspace to param |
| 8EEE | JMP clear_jsr_protection ; Clear JSR protection status (CLRJSR) |
| 8EF1 | .read_args_size←1← 8F4C BEQ |
| LDY #&7f ; Y=&7F: JSRSIZ offset (READRB entry) | |
| 8EF3 | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8EF5 | LDY #1 ; Y=1: param block offset for size byte |
| 8EF7 | STA (osword_pb_ptr),y ; Store buffer size to (&F0)+1 |
| 8EF9 | INY ; Y=2: param block offset for args size Y=&02 |
| 8EFA | LDA #&80 ; A=&80: argument data starts at offset &80 |
| 8EFC | .readry←1← 8EC6 BCC |
| STA (osword_pb_ptr),y ; Store args start offset to (&F0)+2 | |
| 8EFE | RTS ; Return |
| 8EFF | .osword_12_offsets←1← 8F13 LDA |
| EQUB &FF, &01 ; Store args start offset to (&F0)+2 | |
OSWORD &12 handler: dispatch sub-functions 0-5Range-checks the sub-function code from the param block. Sub-functions 4-5 go to read/set station number. Sub-functions 0-3 select the appropriate workspace page (static &0D or dynamic) and offset, then fall through to the bidirectional param block copy loop. |
|
| 8F01 | .osword_12_dispatch |
| CMP #6 ; OSWORD &12: range check sub-function | |
| 8F03 | BCS rsl1 ; Sub-function >= 6: not supported |
| 8F05 | CMP #4 ; Check for sub-functions 4-5 |
| 8F07 | BCS rssl1 ; Sub-function 4 or 5: read/set protection |
| 8F09 | LSR ; LSR: 0->0, 1->0, 2->1, 3->1 |
| 8F0A | LDX #&0d ; X=&0D: default to static workspace page |
| 8F0C | TAY ; Transfer LSR result to Y for indexing |
| 8F0D | BEQ set_workspace_page ; Y=0 (sub 0-1): use page &0D |
| 8F0F | LDX nfs_workspace_hi ; Y=1 (sub 2-3): use dynamic workspace |
| 8F11 | .set_workspace_page←1← 8F0D BEQ |
| STX ws_ptr_hi ; Store workspace page in &AC (hi byte) | |
| 8F13 | LDA osword_12_offsets,y ; Load offset: &FF (sub 0-1) or &01 (sub 2-3) |
| 8F16 | STA ws_ptr_lo ; Store offset in &AB (lo byte) |
| 8F18 | LDX #1 ; X=1: copy 2 bytes |
| 8F1A | LDY #1 ; Y=1: start at param block offset 1 |
| fall through ↓ | |
Bidirectional copy loop between param block and workspaceIf C=1, copies from OSWORD param block (&F0),Y to workspace (&AB),Y. In either case, loads from workspace and stores to param block. Loops for X+1 bytes. Used by OSWORD &0F, &10, &11, and &12 handlers.
|
|||||||||||||||
| 8F1C | .copy_param_workspace←4← 8ED6 JSR← 8EEB JSR← 8F28 BPL← 8FBD JSR | ||||||||||||||
| BCC skip_param_write ; C=0: skip param-to-workspace copy | |||||||||||||||
| 8F1E | LDA (osword_pb_ptr),y ; C=1: copy from param to workspace | ||||||||||||||
| 8F20 | STA (ws_ptr_lo),y ; Store param byte to workspace | ||||||||||||||
| 8F22 | .skip_param_write←1← 8F1C BCC | ||||||||||||||
| LDA (ws_ptr_lo),y ; Load byte from workspace | |||||||||||||||
| fall through ↓ | |||||||||||||||
Bidirectional block copy between OSWORD param block and workspace.C=1: copy X+1 bytes from (&F0),Y to (&AB),Y (param to workspace) C=0: copy X+1 bytes from (&AB),Y to (&F0),Y (workspace to param)
|
|||||||||||||||
| 8F24 | .copy_param_block | ||||||||||||||
| STA (osword_pb_ptr),y ; Store to param block (no-op if C=1) | |||||||||||||||
| 8F26 | INY ; Advance to next byte | ||||||||||||||
| 8F27 | DEX ; Decrement remaining count | ||||||||||||||
| 8F28 | BPL copy_param_workspace ; Loop while bytes remain | ||||||||||||||
| 8F2A | .logon3 | ||||||||||||||
| .return_copy_param | |||||||||||||||
| RTS ; Return | |||||||||||||||
| 8F2B | .rssl1←1← 8F07 BCS | ||||||||||||||
| LSR ; LSR A: test bit 0 of sub-function | |||||||||||||||
| 8F2C | INY ; Y=1: offset for protection byte | ||||||||||||||
| 8F2D | LDA (osword_pb_ptr),y ; Load protection byte from param block | ||||||||||||||
| 8F2F | BCS rssl2 ; C=1 (odd sub): set protection | ||||||||||||||
| 8F31 | LDA prot_status ; C=0 (even sub): read current status | ||||||||||||||
| 8F34 | STA (osword_pb_ptr),y ; Return current value to param block | ||||||||||||||
| 8F36 | .rssl2←1← 8F2F BCS | ||||||||||||||
| STA prot_status ; Update protection status | |||||||||||||||
| 8F39 | STA saved_jsr_mask ; Also save as JSR mask backup | ||||||||||||||
| 8F3C | RTS ; Return | ||||||||||||||
| 8F3D | .read_fs_handle←1← 8F48 BEQ | ||||||||||||||
| LDY #&14 ; Y=&14: RX buffer offset for FS handle | |||||||||||||||
| 8F3F | LDA (net_rx_ptr),y ; Read FS reply handle from RX data | ||||||||||||||
| 8F41 | LDY #1 ; Y=1: param block byte 1 | ||||||||||||||
| 8F43 | STA (osword_pb_ptr),y ; Return handle to caller's param block | ||||||||||||||
| 8F45 | RTS ; Return | ||||||||||||||
| 8F46 | .rsl1←1← 8F03 BCS | ||||||||||||||
| CMP #8 ; Sub-function 8: read FS handle | |||||||||||||||
| 8F48 | BEQ read_fs_handle ; Match: read handle from RX buffer | ||||||||||||||
| 8F4A | CMP #9 ; Sub-function 9: read args size | ||||||||||||||
| 8F4C | BEQ read_args_size ; Match: read ARGS buffer info | ||||||||||||||
| 8F4E | BPL return_last_error ; Sub >= 10 (bit 7 clear): read error | ||||||||||||||
| 8F50 | LDY #3 ; Y=3: start from handle 3 (descending) | ||||||||||||||
| 8F52 | LSR ; LSR: test read/write bit | ||||||||||||||
| 8F53 | BCC readc1 ; C=0: read handles from workspace | ||||||||||||||
| 8F55 | STY ws_page ; Init loop counter at Y=3 | ||||||||||||||
| 8F57 | .copy_handles_to_ws←1← 8F66 BNE | ||||||||||||||
| LDY ws_page ; Reload loop counter | |||||||||||||||
| 8F59 | LDA (osword_pb_ptr),y ; Read handle from caller's param block | ||||||||||||||
| 8F5B | JSR handle_to_mask_a ; Convert handle number to bitmask | ||||||||||||||
| 8F5E | TYA ; TYA: get bitmask result | ||||||||||||||
| 8F5F | LDY ws_page ; Reload loop counter | ||||||||||||||
| 8F61 | STA fs_server_net,y ; Store bitmask to FS server table | ||||||||||||||
| 8F64 | DEC ws_page ; Next handle (descending) | ||||||||||||||
| 8F66 | BNE copy_handles_to_ws ; Loop for handles 3,2,1 | ||||||||||||||
| 8F68 | RTS ; Return | ||||||||||||||
| 8F69 | .return_last_error←1← 8F4E BPL | ||||||||||||||
| INY ; Y=1 (post-INY): param block byte 1 | |||||||||||||||
| 8F6A | LDA fs_last_error ; Read last FS error code | ||||||||||||||
| 8F6D | STA (osword_pb_ptr),y ; Return error to caller's param block | ||||||||||||||
| 8F6F | RTS ; Return | ||||||||||||||
| 8F70 | .readc1←2← 8F53 BCC← 8F79 BNE | ||||||||||||||
| LDA fs_server_net,y ; A=single-bit bitmask | |||||||||||||||
| 8F73 | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) | ||||||||||||||
| 8F76 | STA (osword_pb_ptr),y ; A=handle number (&20-&27) Y=preserved | ||||||||||||||
| 8F78 | DEY ; Next handle (descending) | ||||||||||||||
| 8F79 | BNE readc1 ; Loop for handles 3,2,1 | ||||||||||||||
| 8F7B | RTS ; Return | ||||||||||||||
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.
|
|||||||||||||
| 8F7C | .osword_10_handler | ||||||||||||
| LDX nfs_workspace_hi ; Workspace page high byte | |||||||||||||
| 8F7E | STX ws_ptr_hi ; Set up pointer high byte in &AC | ||||||||||||
| 8F80 | STY ws_ptr_lo ; Save param block high byte in &AB | ||||||||||||
| 8F82 | ROR rx_flags ; Disable user RX during CB operation | ||||||||||||
| 8F85 | LDA (osword_pb_ptr),y ; Read first byte of param block | ||||||||||||
| 8F87 | STA osword_flag ; Save: 0=open new, non-zero=read RXCB | ||||||||||||
| 8F89 | BNE read_rxcb ; Non-zero: read specified RXCB | ||||||||||||
| 8F8B | LDA #3 ; Start scan from RXCB #3 (0-2 reserved) | ||||||||||||
| 8F8D | .scan0←1← 8F9F BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8F90 | BCS openl4 ; Invalid RXCB: return zero | ||||||||||||
| 8F92 | LSR ; LSR twice: byte offset / 4 | ||||||||||||
| 8F93 | LSR ; Yields RXCB number from offset | ||||||||||||
| 8F94 | TAX ; X = RXCB number for iteration | ||||||||||||
| 8F95 | LDA (ws_ptr_lo),y ; Read flag byte from RXCB workspace | ||||||||||||
| 8F97 | BEQ openl4 ; Zero = end of CB list | ||||||||||||
| 8F99 | CMP #&3f ; &3F = deleted slot, free for reuse | ||||||||||||
| 8F9B | BEQ scan1 ; Found free slot | ||||||||||||
| 8F9D | INX ; Try next RXCB | ||||||||||||
| 8F9E | TXA ; A = next RXCB number | ||||||||||||
| 8F9F | BNE scan0 ; Continue scan (always branches) | ||||||||||||
| 8FA1 | .scan1←1← 8F9B BEQ | ||||||||||||
| TXA ; A = free RXCB number | |||||||||||||
| 8FA2 | LDX #0 ; X=0 for indexed indirect store | ||||||||||||
| 8FA4 | STA (osword_pb_ptr,x) ; Return RXCB number to caller's byte 0 | ||||||||||||
| 8FA6 | .read_rxcb←1← 8F89 BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8FA9 | BCS openl4 ; Invalid: write zero to param block | ||||||||||||
| 8FAB | DEY ; Y = offset-1: points to flag byte | ||||||||||||
| 8FAC | STY ws_ptr_lo ; Set &AB = workspace ptr low byte | ||||||||||||
| 8FAE | LDA #&c0 ; &C0: test mask for flag byte | ||||||||||||
| 8FB0 | LDY #1 ; Y=1: flag byte offset in RXCB | ||||||||||||
| 8FB2 | LDX #&0b ; Enable interrupts before transmit | ||||||||||||
| 8FB4 | CPY osword_flag ; Compare Y(1) with saved byte (open/read) | ||||||||||||
| 8FB6 | ADC (ws_ptr_lo),y ; ADC flag: test if slot is in use | ||||||||||||
| 8FB8 | BEQ openl6 ; Dest station = &FFFF (accept reply from any station) | ||||||||||||
| 8FBA | BMI openl7 ; Negative: slot has received data | ||||||||||||
| 8FBC | .copy_rxcb_to_param←1← 8FCC BNE | ||||||||||||
| CLC ; C=0: workspace-to-param direction | |||||||||||||
| 8FBD | .openl6←1← 8FB8 BEQ | ||||||||||||
| JSR copy_param_workspace ; Copy RXCB data to param block | |||||||||||||
| 8FC0 | BCS reenable_rx ; Done: skip deletion on error | ||||||||||||
| 8FC2 | LDA #&3f ; Mark CB as consumed (consume-once) | ||||||||||||
| 8FC4 | LDY #1 ; Y=1: flag byte offset | ||||||||||||
| 8FC6 | STA (ws_ptr_lo),y ; Write &3F to mark slot deleted | ||||||||||||
| 8FC8 | BNE reenable_rx ; Branch to exit (always taken) ALWAYS branch | ||||||||||||
| 8FCA | .openl7←1← 8FBA BMI | ||||||||||||
| ADC #1 ; Advance through multi-byte field | |||||||||||||
| 8FCC | BNE copy_rxcb_to_param ; Loop until all bytes processed | ||||||||||||
| 8FCE | DEY ; Y=-1 → Y=0 after STA below | ||||||||||||
| 8FCF | .openl4←3← 8F90 BCS← 8F97 BEQ← 8FA9 BCS | ||||||||||||
| STA (osword_pb_ptr),y ; Return zero (no free RXCB found) | |||||||||||||
| 8FD1 | .reenable_rx←2← 8FC0 BCS← 8FC8 BNE | ||||||||||||
| ROL rx_flags ; Re-enable user RX | |||||||||||||
| 8FD4 | RTS ; Return | ||||||||||||
Set up RX buffer pointers in NFS workspaceCalculates the start address of the RX data area (&F0+1) and stores it at workspace offset &1C. Also reads the data length from (&F0)+1 and adds it to &F0 to compute the end address at offset &20.
|
||||
| 8FD5 | .setup_rx_buffer_ptrs←1← 9008 JSR | |||
| LDY #&1c ; Y=&1C: workspace offset for RX data start | ||||
| 8FD7 | LDA osword_pb_ptr ; A = base address low byte | |||
| 8FD9 | ADC #1 ; A = base + 1 (skip length byte) | |||
| 8FDB | JSR store_16bit_at_y ; Receive data blocks until command byte = &00 or &0D | |||
| 8FDE | LDY #1 ; Read data length from (&F0)+1 | |||
| 8FE0 | LDA (osword_pb_ptr),y ; A = data length byte | |||
| 8FE2 | LDY #&20 ; Workspace offset &20 = RX data end | |||
| 8FE4 | ADC osword_pb_ptr ; A = base + length = end address low | |||
| 8FE6 | .store_16bit_at_y←1← 8FDB JSR | |||
| STA (nfs_workspace),y ; Store low byte of 16-bit address | ||||
| 8FE8 | INY ; Advance to high byte offset | |||
| 8FE9 | LDA osword_pb_ptr_hi ; A = high byte of base address | |||
| 8FEB | ADC #0 ; Add carry for 16-bit addition | |||
| 8FED | STA (nfs_workspace),y ; Store high byte | |||
| 8FEF | RTS ; Return | |||
Econet transmit/receive handlerA=0: Initialise TX control block from ROM template at &8395 (init_tx_ctrl_block+Y, zero entries substituted from NMI workspace &0DE6), transmit it, set up RX control block, and receive reply. A>=1: Handle transmit result (branch to cleanup at &903E).
|
||||
| 8FF0 | .econet_tx_rx | |||
| CMP #1 ; A=0: set up and transmit; A>=1: handle result | ||||
| 8FF2 | BCS handle_tx_result ; A >= 1: handle TX result | |||
| 8FF4 | LDY #&23 ; Y=&23: start of template (descending) | |||
| 8FF6 | .dofs01←1← 9003 BNE | |||
| LDA init_tx_ctrl_block,y ; Load ROM template byte | ||||
| 8FF9 | BNE store_txcb_byte ; Non-zero = use ROM template byte as-is | |||
| 8FFB | LDA nmi_sub_table,y ; Zero = substitute from NMI workspace | |||
| 8FFE | .store_txcb_byte←1← 8FF9 BNE | |||
| STA (nfs_workspace),y ; Store to dynamic workspace | ||||
| 9000 | DEY ; Descend through template | |||
| 9001 | CPY #&17 ; Stop at offset &17 | |||
| 9003 | BNE dofs01 ; Loop until all bytes copied | |||
| 9005 | INY ; Y=&18: TX block starts here | |||
| 9006 | STY net_tx_ptr ; Point net_tx_ptr at workspace+&18 | |||
| 9008 | JSR setup_rx_buffer_ptrs ; Set up RX buffer start/end pointers | |||
| 900B | LDY #2 ; Y=2: port byte offset in RXCB | |||
| 900D | LDA #&90 ; A=&90: FS reply port | |||
| 900F | STA escapable ; Mark as escapable operation | |||
| 9011 | STA (osword_pb_ptr),y ; Store port &90 at (&F0)+2 | |||
| 9013 | INY ; Y=&03 | |||
| 9014 | INY ; Y=&04: advance to station address Y=&04 | |||
| 9015 | .copy_fs_addr←1← 901D BNE | |||
| LDA fs_context_base,y ; Copy FS station addr from workspace | ||||
| 9018 | STA (osword_pb_ptr),y ; Store to RX param block | |||
| 901A | INY ; Next byte | |||
| 901B | CPY #7 ; Done 3 bytes (Y=4,5,6)? | |||
| 901D | BNE copy_fs_addr ; No: continue copying | |||
| 901F | LDA nfs_workspace_hi ; High byte of workspace for TX ptr | |||
| 9021 | STA net_tx_ptr_hi ; Store as TX pointer high byte | |||
| 9023 | CLI ; Enable interrupts before transmit | |||
| 9024 | JSR tx_poll_ff ; Transmit with full retry | |||
| 9027 | LDY #&20 ; Y=&20: RX end address offset | |||
| 9029 | LDA #&ff ; Set RX end address to &FFFF (accept any length) | |||
| 902B | STA (nfs_workspace),y ; Store end address low byte (&FF) | |||
| 902D | INY ; Y=&21 | |||
| 902E | STA (nfs_workspace),y ; Store end address high byte (&FF) | |||
| 9030 | LDY #&19 ; Y=&19: port byte in workspace RXCB | |||
| 9032 | LDA #&90 ; A=&90: FS reply port | |||
| 9034 | STA (nfs_workspace),y ; Store port to workspace RXCB | |||
| 9036 | DEY ; Y=&18 | |||
| 9037 | LDA #&7f ; A=&7F: flag byte = waiting for reply | |||
| 9039 | STA (nfs_workspace),y ; Store flag byte to workspace RXCB | |||
| 903B | JMP waitfs ; Jump to RX poll (BRIANX) | |||
| 903E | .handle_tx_result←1← 8FF2 BCS | |||
| PHP ; Save processor flags | ||||
| 903F | LDY #1 ; Y=1: first data byte offset | |||
| 9041 | LDA (osword_pb_ptr),y ; Load first data byte from RX buffer | |||
| fall through ↓ | ||||
FS response data relay (DOFS)Entered from the econet_tx_rx response handler at &903E after loading the first data byte from the RX buffer. Saves the command byte and station address from the received packet into (net_rx_ptr)+&71/&72, then iterates through remaining data bytes. Each byte is stored at (net_rx_ptr)+&7D, the control block is set up via ctrl_block_setup_alt, and the packet is transmitted. Loops until a &0D terminator or &00 null is found. The branch at &9053 (BNE dofs2) handles the first-packet case where the data length field at (net_rx_ptr)+&7B is adjusted. |
|
| 9043 | .net_write_char |
| TAX ; X = first data byte (command code) | |
| 9044 | INY ; Advance to next data byte |
| 9045 | LDA (osword_pb_ptr),y ; Load station address high byte |
| 9047 | INY ; Advance past station addr |
| 9048 | STY ws_ptr_lo ; Save Y as data index |
| 904A | LDY #&72 ; Store station addr hi at (net_rx_ptr)+&72 |
| 904C | STA (net_rx_ptr),y ; Store to workspace |
| 904E | DEY ; Y=&71 |
| 904F | TXA ; A = command code (from X) |
| 9050 | STA (net_rx_ptr),y ; Store station addr lo at (net_rx_ptr)+&71 |
| 9052 | PLP ; Restore flags from earlier PHP |
| 9053 | BNE dofs2 ; First call: adjust data length |
| 9055 | .send_data_bytes←1← 906E BNE |
| LDY ws_ptr_lo ; Reload data index | |
| 9057 | INC ws_ptr_lo ; Advance data index for next iteration |
| 9059 | LDA (osword_pb_ptr),y ; Load next data byte |
| 905B | BEQ return_8 ; Zero byte: end of data, return |
| 905D | LDY #&7d ; Y=&7D: store byte for TX at offset &7D |
| 905F | STA (net_rx_ptr),y ; Store data byte at (net_rx_ptr)+&7D for TX |
| 9061 | PHA ; Save data byte for &0D check after TX |
| 9062 | JSR ctrl_block_setup_alt ; Set up TX control block |
| 9065 | JSR enable_irq_and_tx ; Enable IRQs and transmit |
| 9068 | .delay_between_tx←1← 9069 BNE |
| DEX ; Short delay loop between TX packets | |
| 9069 | BNE delay_between_tx ; Spin until X reaches 0 |
| 906B | PLA ; Restore data byte for terminator check |
| 906C | EOR #&0d ; Test for end-of-data marker (&0D) |
| 906E | BNE send_data_bytes ; Not &0D: continue with next byte |
| 9070 | .return_8←1← 905B BEQ |
| RTS ; Return (data complete) | |
| 9071 | .dofs2←1← 9053 BNE |
| JSR ctrl_block_setup_alt ; First-packet: set up control block | |
| 9074 | LDY #&7b ; Y=&7B: data length offset |
| 9076 | LDA (net_rx_ptr),y ; Load current data length |
| 9078 | ADC #3 ; Adjust data length by 3 for header bytes |
| 907A | 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. |
|
| 907C | .enable_irq_and_tx←1← 9065 JSR |
| CLI ; Enable interrupts | |
| 907D | JMP tx_poll_ff ; 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 &9099. 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
|
|||||||||||
| 9080 | .osword_dispatch | ||||||||||
| PHP ; Save processor status | |||||||||||
| 9081 | PHA ; Save A (reason code) | ||||||||||
| 9082 | TXA ; Save X | ||||||||||
| 9083 | PHA ; Push X to stack | ||||||||||
| 9084 | TYA ; Save Y | ||||||||||
| 9085 | PHA ; Push Y to stack | ||||||||||
| 9086 | TSX ; Get stack pointer for indexed access | ||||||||||
| 9087 | LDA stk_frame_3,x ; Retrieve original A (reason code) from stack | ||||||||||
| 908A | CMP #9 ; Reason codes 0-8 only | ||||||||||
| 908C | BCS entry1 ; Code >= 9: skip dispatch, restore regs | ||||||||||
| 908E | TAX ; X = reason code for table lookup | ||||||||||
| 908F | JSR osword_trampoline ; Dispatch to handler via trampoline | ||||||||||
| 9092 | .entry1←1← 908C BCS | ||||||||||
| PLA ; Restore Y | |||||||||||
| 9093 | TAY ; Transfer to Y register | ||||||||||
| 9094 | PLA ; Restore X | ||||||||||
| 9095 | TAX ; Transfer to X register | ||||||||||
| 9096 | PLA ; Restore A | ||||||||||
| 9097 | PLP ; Restore processor status flags | ||||||||||
| 9098 | RTS ; Return with all registers preserved | ||||||||||
| 9099 | .osword_trampoline←1← 908F JSR | ||||||||||
| LDA osword_tbl_hi,x ; PHA/PHA/RTS trampoline: push handler addr-1, RTS jumps to it | |||||||||||
| 909C | PHA ; Push high byte of handler address | ||||||||||
| 909D | LDA osword_tbl_lo,x ; Load handler low byte from table | ||||||||||
| 90A0 | PHA ; Push low byte of handler address | ||||||||||
| 90A1 | LDA osbyte_a_copy ; Load workspace byte &EF for handler | ||||||||||
| 90A3 | RTS ; RTS dispatches to pushed handler | ||||||||||
| 90A4 | .osword_tbl_lo←1← 909D LDA | ||||||||||
| EQUB <(return_1-1) ; Load workspace byte &EF for handler | |||||||||||
| 90A5 | EQUB <(remote_print_handler-1) ; Push low byte of handler address | ||||||||||
| 90A6 | EQUB <(remote_print_handler-1) ; RTS dispatches to pushed handler | ||||||||||
| 90A7 | EQUB <(remote_print_handler-1) ; lo(return_1-1): fn 0 (null handler) | ||||||||||
| 90A8 | EQUB <(net_write_char_handler-1) ; lo(remote_print_handler- 1): fn 1 | ||||||||||
| 90A9 | EQUB <(printer_select_handler-1) ; lo(remote_print_handler- 1): fn 2 | ||||||||||
| 90AA | EQUB <(return_1-1) ; lo(remote_print_handler- 1): fn 3 | ||||||||||
| 90AB | EQUB <(remote_cmd_dispatch-1) ; lo(net_write_char_ handler-1): fn 4 | ||||||||||
| 90AC | EQUB <(remote_osword_handler-1) ; lo(printer_select_ handler-1): fn 5 | ||||||||||
| 90AD | .osword_tbl_hi←1← 9099 LDA | ||||||||||
| EQUB >(return_1-1) ; lo(return_1-1): fn 6 (null handler) | |||||||||||
| 90AE | EQUB >(remote_print_handler-1) ; lo(remote_cmd_dispatch- 1): fn 7 | ||||||||||
| 90AF | EQUB >(remote_print_handler-1) ; lo(remote_osword_ handler-1): fn 8 | ||||||||||
| 90B0 | EQUB >(remote_print_handler-1) ; hi(return_1-1): fn 0 (null handler) | ||||||||||
| 90B1 | EQUB >(net_write_char_handler-1) ; hi(remote_print_handler- 1): fn 1 | ||||||||||
| 90B2 | EQUB >(printer_select_handler-1) ; hi(remote_print_handler- 1): fn 2 | ||||||||||
| 90B3 | EQUB >(return_1-1) ; hi(remote_print_handler- 1): fn 3 | ||||||||||
| 90B4 | EQUB >(remote_cmd_dispatch-1) ; hi(net_write_char_ handler-1): fn 4 | ||||||||||
| 90B5 | EQUB >(remote_osword_handler-1) ; hi(printer_select_ handler-1): fn 5 | ||||||||||
NETVEC fn 4: handle net write character (NWRCH)Zeros the carry flag in the stacked processor status to signal success, stores the character from Y into workspace offset &DA, loads A=0 as the command type, and falls through to setup_tx_and_send.
|
||||
| 90B6 | .net_write_char_handler | |||
| TSX ; Get stack pointer for P register access | ||||
| 90B7 | ROR stk_frame_p,x ; ROR/ASL on stacked P: zeros carry to signal success | |||
| 90BA | ASL stk_frame_p,x ; ASL: restore P after ROR zeroed carry | |||
| 90BD | TYA ; Y = character to write | |||
| 90BE | LDY #&da ; Store character at workspace offset &DA | |||
| 90C0 | STA (nfs_workspace),y ; Store char at workspace offset &DA | |||
| 90C2 | LDA #0 ; A=0: command type for net write char | |||
| fall through ↓ | ||||
Set up TX control block and sendStores A at workspace offset &D9 (command type), then sets byte &0C to &80 (TX active flag). Saves the current net_tx_ptr, temporarily redirects it to (nfs_workspace)+&0C so tx_poll_ff transmits from the workspace TX control block. After transmission completes, writes &3F (TX deleted) at (net_tx_ptr)+&00 to mark the control block as free, then restores net_tx_ptr to its original value.
|
||||
| 90C4 | .setup_tx_and_send←3← 81CF JSR← 9117 JSR← 917A JSR | |||
| LDY #&d9 ; Y=&D9: command type offset | ||||
| 90C6 | STA (nfs_workspace),y ; Store command type at ws+&D9 | |||
| 90C8 | LDA #&80 ; Mark TX control block as active (&80) | |||
| 90CA | LDY #&0c ; Y=&0C: TXCB start offset | |||
| 90CC | STA (nfs_workspace),y ; Set TX active flag at ws+&0C | |||
| 90CE | LDA net_tx_ptr ; Save net_tx_ptr; redirect to workspace TXCB | |||
| 90D0 | PHA ; Save net_tx_ptr low | |||
| 90D1 | LDA net_tx_ptr_hi ; Load net_tx_ptr high | |||
| 90D3 | PHA ; Save net_tx_ptr high | |||
| 90D4 | STY net_tx_ptr ; Redirect net_tx_ptr low to workspace | |||
| 90D6 | LDX nfs_workspace_hi ; Load workspace page high byte | |||
| 90D8 | STX net_tx_ptr_hi ; Complete ptr redirect | |||
| 90DA | JSR tx_poll_ff ; Transmit with full retry | |||
| 90DD | LDA #&3f ; Mark TXCB as deleted (&3F) after transmit | |||
| 90DF | STA (net_tx_ptr,x) ; Write &3F to TXCB byte 0 | |||
| 90E1 | PLA ; Restore net_tx_ptr high | |||
| 90E2 | STA net_tx_ptr_hi ; Write back | |||
| 90E4 | PLA ; Restore net_tx_ptr low | |||
| 90E5 | STA net_tx_ptr ; Write back | |||
| 90E7 | 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. |
|
| 90E8 | .remote_cmd_dispatch |
| LDY osword_pb_ptr_hi ; Load original Y (OSBYTE secondary param) | |
| 90EA | CMP #&81 ; OSBYTE &81 (INKEY): always forward to terminal |
| 90EC | BEQ dispatch_remote_osbyte ; Forward &81 to terminal for keyboard read |
| 90EE | LDY #1 ; Y=1: search NCTBPL table (execute on both) |
| 90F0 | LDX #9 ; X=9: 10-entry NCTBPL table size |
| 90F2 | JSR match_osbyte_code ; Search for OSBYTE code in NCTBPL table |
| 90F5 | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=1 (both) |
| 90F7 | DEY ; Y=-1: search NCTBMI table (terminal only) |
| 90F8 | DEY ; Second DEY: Y=&FF (from 1 via 0) |
| 90F9 | LDX #&0e ; X=&0E: 15-entry NCTBMI table size |
| 90FB | JSR match_osbyte_code ; Search for OSBYTE code in NCTBMI table |
| 90FE | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=&FF (terminal) |
| 9100 | INY ; Y=0: OSBYTE not recognised, ignore |
| 9101 | .dispatch_remote_osbyte←3← 90EC BEQ← 90F5 BEQ← 90FE BEQ |
| LDX #2 ; X=2 bytes to copy (default for RBYTE) | |
| 9103 | TYA ; A=Y: check table match result |
| 9104 | BEQ return_nbyte ; Y=0: not recognised, return unhandled |
| 9106 | PHP ; Y>0 (NCTBPL): send only, no result expected |
| 9107 | BPL nbyte6 ; Y>0 (NCTBPL): no result expected, skip RX |
| 9109 | INX ; Y<0 (NCTBMI): X=3 bytes (result + P flags) X=&03 |
| 910A | .nbyte6←1← 9107 BPL |
| LDY #&dc ; Y=&DC: top of 3-byte stack frame region | |
| 910C | .nbyte1←1← 9114 BPL |
| LDA tube_claimed_id,y ; Copy OSBYTE args from stack frame to workspace | |
| 910F | STA (nfs_workspace),y ; Store to NFS workspace for transmission |
| 9111 | DEY ; Next byte (descending) |
| 9112 | CPY #&da ; Copied all 3 bytes? (&DC, &DB, &DA) |
| 9114 | BPL nbyte1 ; Loop for remaining bytes |
| 9116 | TXA ; A = byte count for setup_tx_and_send |
| 9117 | JSR setup_tx_and_send ; Build TXCB and transmit to terminal |
| 911A | PLP ; Restore N flag from table match type |
| 911B | BPL return_nbyte ; Y was positive (NCTBPL): done, no result |
| 911D | LDA #&7f ; Set up RX control block to wait for reply |
| 911F | LDY #&0c ; Y=&0C: RX control block offset in workspace |
| 9121 | STA (nfs_workspace),y ; Write &7F (waiting) to RXCB flag byte |
| 9123 | .poll_rxcb_flag←1← 9125 BPL |
| LDA (nfs_workspace),y ; Poll for TX completion (wait for bit 7 set) | |
| 9125 | BPL poll_rxcb_flag ; Bit7 clear: still waiting, poll again |
| 9127 | TSX ; X = stack pointer for register restoration |
| 9128 | LDY #&dd ; Y=&DD: saved P byte offset in workspace |
| 912A | LDA (nfs_workspace),y ; Load remote processor status from reply |
| 912C | ORA #&44 ; Force V=1 (claimed) and I=1 (no IRQ) in saved P |
| 912E | BNE nbyte5 ; ALWAYS branch (ORA #&44 never zero) ALWAYS branch |
| 9130 | .nbyte4←1← 9139 BNE |
| DEY ; Previous workspace offset | |
| 9131 | DEX ; Previous stack register slot |
| 9132 | LDA (nfs_workspace),y ; Load next result byte (X, then Y) |
| 9134 | .nbyte5←1← 912E BNE |
| STA stk_frame_p,x ; Write result bytes to stacked registers | |
| 9137 | CPY #&da ; Copied all result bytes? (P at &DA) |
| 9139 | BNE nbyte4 ; Loop for remaining result bytes |
| 913B | .return_nbyte←2← 9104 BEQ← 911B 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=9 → first 10 entries (NCTBPL: execute on both machines) X=14 → all 15 entries (NCTBMI: execute on terminal only) The last 5 entries (&0B, &0C, &0F, &79, &7A) are terminal-only because they affect the local keyboard or buffers. On entry: A = OSBYTE code, X = table size - 1 On exit: Z=1 if match found, Z=0 if not |
|
| 913C | .match_osbyte_code←3← 90F2 JSR← 90FB JSR← 9142 BPL |
| CMP remote_osbyte_table,x ; Compare OSBYTE code with table entry | |
| 913F | BEQ return_match_osbyte ; Match found: return with Z=1 |
| 9141 | DEX ; Next table entry (descending) |
| 9142 | BPL match_osbyte_code ; Loop for remaining entries |
| 9144 | .return_match_osbyte←2← 913F BEQ← 915C BNE |
| RTS ; Return; Z=1 if match, Z=0 if not | |
| 9145 | .remote_osbyte_table←1← 913C CMP |
| EQUB &04 ; OSBYTE &04: cursor key status | |
| 9146 | EQUB &09 ; OSBYTE &09: flash duration (1st colour) |
| 9147 | EQUB &0A ; OSBYTE &0A: flash duration (2nd colour) |
| 9148 | EQUB &15 ; OSBYTE &15: flush specific buffer |
| 9149 | EQUB &9A ; OSBYTE &9A: video ULA control register |
| 914A | EQUB &9B ; OSBYTE &9B: video ULA palette |
| 914B | EQUB &E1 ; OSBYTE &E1: function key &C0-&CF |
| 914C | EQUB &E2 ; OSBYTE &E2: function key &D0-&DF |
| 914D | EQUB &E3 ; OSBYTE &E3: function key &E0-&EF |
| 914E | EQUB &E4 ; OSBYTE &E4: function key &F0-&FF |
| 914F | EQUB &0B ; OSBYTE &0B: auto-repeat delay |
| 9150 | EQUB &0C ; OSBYTE &0C: auto-repeat rate |
| 9151 | EQUB &0F ; OSBYTE &0F: flush buffer class |
| 9152 | EQUB &79 ; OSBYTE &79: keyboard scan from X |
| 9153 | EQUB &7A ; OSBYTE &7A: keyboard scan from 16 |
NETVEC fn 8: remote OSWORD dispatch (NWORD)Only accepts OSWORD 7 (make a sound) and OSWORD 8 (define an envelope), rejecting all others. Sets Y=14 as the maximum parameter byte count, then falls through to remote_cmd_data. |
|
| 9154 | .remote_osword_handler |
| LDY #&0e ; Y=&0E: max 14 parameter bytes for OSWORD | |
| 9156 | CMP #7 ; OSWORD 7 = make a sound |
| 9158 | BEQ copy_params_rword ; OSWORD 7 (sound): handle via common path |
| 915A | CMP #8 ; OSWORD 8 = define an envelope |
| fall through ↓ | |
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. |
|
| 915C | .remote_cmd_data |
| BNE return_match_osbyte ; Not OSWORD 7 or 8: ignore (BNE exits) | |
| 915E | .copy_params_rword←1← 9158 BEQ |
| LDX #&db ; Point workspace to offset &DB for params | |
| 9160 | STX nfs_workspace ; Store workspace ptr offset &DB |
| 9162 | .copy_osword_params←1← 9167 BPL |
| LDA (osword_pb_ptr),y ; Load param byte from OSWORD param block | |
| 9164 | STA (nfs_workspace),y ; Write param byte to workspace |
| 9166 | DEY ; Next byte (descending) |
| 9167 | BPL copy_osword_params ; Loop for all parameter bytes |
| 9169 | INY ; Y=0 after loop |
| 916A | DEC nfs_workspace ; Point workspace to offset &DA |
| 916C | LDA osbyte_a_copy ; Load original OSWORD code |
| 916E | STA (nfs_workspace),y ; Store OSWORD code at ws+0 |
| 9170 | STY nfs_workspace ; Reset workspace ptr to base |
| 9172 | LDY #&14 ; Y=&14: command type offset |
| 9174 | LDA #&e9 ; Tag as RWORD (port &E9) |
| 9176 | STA (nfs_workspace),y ; Store port tag at ws+&14 |
| 9178 | LDA #1 ; A=1: single-byte TX |
| 917A | JSR setup_tx_and_send ; Load template byte from ctrl_block_template[X] |
| 917D | STX nfs_workspace ; Restore workspace ptr |
| fall through ↓ | |
Alternate entry into control block setupSets X=&0D, Y=&7C. Tests bit 6 of &83B3 to choose target: V=0 (bit 6 clear): stores to (nfs_workspace) V=1 (bit 6 set): stores to (net_rx_ptr) |
|
| 917F | .ctrl_block_setup_alt←2← 9062 JSR← 9071 JSR |
| LDX #&0d ; X=&0D: template offset for alt entry | |
| 9181 | LDY #&7c ; Y=&7C: target workspace offset for alt entry |
| 9183 | BIT tx_ctrl_upper ; BIT test: V flag = bit 6 of &83B3 |
| 9186 | 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 &91B4 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 |
|
| 9188 | .ctrl_block_setup←1← 84D1 JSR |
| LDY #&17 ; Y=&17: workspace target offset (main entry) | |
| 918A | LDX #&1a ; X=&1A: template table index (main entry) |
| 918C | .ctrl_block_setup_clv←1← 924B JSR |
| CLV ; V=0: target is (nfs_workspace) | |
| 918D | .cbset2←2← 9186 BVS← 91AE BPL |
| LDA ctrl_block_template,x ; Load template byte from ctrl_block_template[X] | |
| 9190 | CMP #&fe ; &FE = stop sentinel |
| 9192 | BEQ cb_template_tail ; End of template: jump to exit |
| 9194 | CMP #&fd ; &FD = skip sentinel |
| 9196 | BEQ cb_template_main_start ; Skip: don't store, just decrement Y |
| 9198 | CMP #&fc ; &FC = page byte sentinel |
| 919A | BNE cbset3 ; Not sentinel: store template value directly |
| 919C | LDA net_rx_ptr_hi ; V=1: use (net_rx_ptr) page |
| 919E | BVS rxcb_matched ; V=1: skip to net_rx_ptr page |
| 91A0 | LDA nfs_workspace_hi ; V=0: use (nfs_workspace) page |
| 91A2 | .rxcb_matched←1← 919E BVS |
| STA net_tx_ptr_hi ; PAGE byte → Y=&02 / Y=&74 | |
| 91A4 | .cbset3←1← 919A BNE |
| BVS cbset4 ; → Y=&04 / Y=&76 | |
| 91A6 | STA (nfs_workspace),y ; PAGE byte → Y=&06 / Y=&78 |
| 91A8 | BVC cb_template_main_start ; → Y=&08 / Y=&7A ALWAYS branch |
| 91AA | .cbset4←1← 91A4 BVS |
| STA (net_rx_ptr),y ; Alt-path only → Y=&70 | |
| 91AC | .cb_template_main_start←2← 9196 BEQ← 91A8 BVC |
| DEY ; → Y=&0C (main only) | |
| 91AD | DEX ; → Y=&0D (main only) |
| 91AE | BPL cbset2 ; Loop until all template bytes done |
| 91B0 | .cb_template_tail←1← 9192 BEQ |
| INY ; → Y=&10 (main only) | |
| 91B1 | STY net_tx_ptr ; Store final offset as net_tx_ptr |
| 91B3 | RTS ; → Y=&07 / Y=&79 |
Control block initialisation templateRead by the loop at &918D, 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 &83B3 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) |
|
| 91B4 | .ctrl_block_template←1← 918D LDA |
| STA zp_ptr_lo ; Alt-path only → Y=&6F | |
| 91B6 | SBC l7dfd,x |
| 91B9 | EQUB &FC, &FF, &FF, &7E, ; → Y=&0D (main only) → Y=&03 / &FC, &FF, &FF, &00, ; Y=&75 SKIP (main only) → Y=&10 &00, &FE, &80, &93, ; (main only) → Y=&08 / Y=&7A → &FD, &FD, &D9, &FC, ; Y=&09 / Y=&7B PAGE byte → Y=&15 &FF, &FF, &DE, &FC, ; (main only) → Y=&16 (main only) &FF, &FF, &FE, &D1, ; SKIP (main only) PAGE byte → &FD, &FD, &1F, &FD, ; Y=&11 (main only) → Y=&12 (main &FF, &FF, &FD, &FD, ; only) → Y=&13 (main only) → &FF, &FF ; Y=&14 (main only) → Y=&17 (main ; only) |
| fall through ↓ | |
Fn 5: printer selection changed (SELECT)Called when the printer selection changes. Compares X against the network printer buffer number (&F0). If it matches, initialises the printer buffer pointer (&0D61 = &1F) and sets the initial flag byte (&0D60 = &41). Otherwise falls through to return.
|
||||
| 91DB | .printer_select_handler | |||
| DEX ; X-1: convert 1-based buffer to 0-based | ||||
| 91DC | CPX osword_pb_ptr ; Is this the network printer buffer? | |||
| 91DE | BNE setup1 ; No: skip printer init | |||
| 91E0 | LDA #&1f ; &1F = initial buffer pointer offset | |||
| 91E2 | STA printer_buf_ptr ; Reset printer buffer write position | |||
| 91E5 | LDA #&41 ; &41 = initial PFLAGS (bit 6 set, bit 0 set) | |||
| 91E7 | .setup1←1← 91DE BNE | |||
| STA prot_flags ; Store A to printer status byte | ||||
| 91E9 | .return_printer_select←2← 91EC BNE← 9200 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.
|
||||||
| 91EA | .remote_print_handler | |||||
| CPY #4 ; Only handle buffer 4 (network printer) | ||||||
| 91EC | BNE return_printer_select ; Not buffer 4: ignore | |||||
| 91EE | TXA ; A = reason code | |||||
| 91EF | DEX ; Reason 1? (DEX: 1->0) | |||||
| 91F0 | BNE toggle_print_flag ; Not reason 1: handle Ctrl-B/C | |||||
| 91F2 | TSX ; Get stack pointer for P register | |||||
| 91F3 | ORA stk_frame_p,x ; Force I flag in stacked P to block IRQs | |||||
| 91F6 | STA stk_frame_p,x ; Write back modified P register | |||||
| 91F9 | .prlp1←2← 9208 BCC← 920D BCC | |||||
| LDA #osbyte_read_buffer ; OSBYTE &91: extract char from MOS buffer | ||||||
| 91FB | LDX #buffer_printer ; X=3: printer buffer number | |||||
| 91FD | JSR osbyte ; Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character) | |||||
| 9200 | BCS return_printer_select ; Buffer empty: return | |||||
| 9202 | TYA ; Y = extracted character Y is the character extracted from the buffer | |||||
| 9203 | JSR store_output_byte ; Store char in output buffer | |||||
| 9206 | CPY #&6e ; Buffer nearly full? (&6E = threshold) | |||||
| 9208 | BCC prlp1 ; Not full: get next char | |||||
| 920A | JSR flush_output_block ; Buffer full: flush to network | |||||
| 920D | BCC prlp1 ; Continue after flush | |||||
| 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.
|
|||||||
| 920F | .store_output_byte←2← 9203 JSR← 921C JSR | ||||||
| LDY printer_buf_ptr ; Load current buffer offset | |||||||
| 9212 | STA (net_rx_ptr),y ; Store byte at current position | ||||||
| 9214 | INC printer_buf_ptr ; Advance buffer pointer | ||||||
| 9217 | RTS ; Return; Y = buffer offset | ||||||
| 9218 | .toggle_print_flag←1← 91F0 BNE | ||||||
| PHA ; Save reason code | |||||||
| 9219 | TXA ; A = reason code | ||||||
| 921A | EOR #1 ; EOR #1: toggle print-active flag (bit 0) | ||||||
| 921C | JSR store_output_byte ; Store toggled flag as output byte | ||||||
| 921F | EOR prot_flags ; XOR with PFLAGS | ||||||
| 9221 | ROR ; Test if sequence changed (bit 7 mismatch) | ||||||
| 9222 | BCC skip_flush ; Sequence unchanged: skip flush | ||||||
| 9224 | ROL ; Undo ROR | ||||||
| 9225 | STA prot_flags ; Update PFLAGS | ||||||
| 9227 | JSR flush_output_block ; Flush current output block | ||||||
| 922A | .skip_flush←1← 9222 BCC | ||||||
| LDA prot_flags ; Load PFLAGS | |||||||
| 922C | AND #&f0 ; Extract upper nibble of PFLAGS | ||||||
| 922E | ROR ; Shift for bit extraction | ||||||
| 922F | TAX ; Save in X | ||||||
| 9230 | PLA ; Restore original reason code | ||||||
| 9231 | ROR ; Merge print-active bit from original A | ||||||
| 9232 | TXA ; Retrieve shifted PFLAGS | ||||||
| 9233 | ROL ; Recombine into new PFLAGS value | ||||||
| 9234 | STA prot_flags ; Update PFLAGS | ||||||
| 9236 | 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. |
|
| 9237 | .flush_output_block←2← 920A JSR← 9227 JSR |
| LDY #8 ; Store buffer length at workspace offset &08 | |
| 9239 | LDA printer_buf_ptr ; Current buffer fill position |
| 923C | STA (nfs_workspace),y ; Write to workspace offset &08 |
| 923E | LDA net_rx_ptr_hi ; Store page high byte at offset &09 |
| 9240 | INY ; Y=&09 |
| 9241 | STA (nfs_workspace),y ; Write page high byte at offset &09 |
| 9243 | LDY #5 ; Also store at offset &05 |
| 9245 | STA (nfs_workspace),y ; (end address high byte) |
| 9247 | LDY #&0b ; Y=&0B: flag byte offset |
| 9249 | LDX #&26 ; X=&26: start from template entry &26 |
| 924B | JSR ctrl_block_setup_clv ; Reuse ctrl_block_setup with CLV entry |
| 924E | DEY ; Y=&0A: sequence flag byte offset |
| 924F | LDA prot_flags ; Load protocol flags (PFLAGS) |
| 9251 | PHA ; Save current PFLAGS |
| 9252 | ROL ; Carry = current sequence (bit 7) |
| 9253 | PLA ; Restore original PFLAGS |
| 9254 | EOR #&80 ; Toggle sequence number (bit 7 of PFLAGS) |
| 9256 | STA prot_flags ; Save toggled PFLAGS |
| 9258 | ROL ; Old sequence bit into bit 0 |
| 9259 | STA (nfs_workspace),y ; Store sequence flag at offset &0A |
| 925B | LDY #&1f ; Y=&1F: buffer start offset |
| 925D | STY printer_buf_ptr ; Reset printer buffer to start (&1F) |
| 9260 | LDA #0 ; A=0: printer output flag |
| 9262 | TAX ; X=0: workspace low byte X=&00 |
| 9263 | LDY nfs_workspace_hi ; Y = workspace page high byte |
| 9265 | CLI ; Enable interrupts before TX |
| fall through ↓ | |
Byte-stream transmit (BSXMIT/BSPSX)Transmits a data packet over econet with sequence number tracking. Sets up the TX control block pointer from X/Y, computes the sequence bit from A AND fs_sequence_nos (handle-based tracking), merges it into the flag byte at (net_tx_ptr)+0, then initiates transmit via tx_poll_ff. Sets end addresses (offsets 8/9) to &FF to allow unlimited data. Selects port byte &D1 (print) or &90 (FS) based on the original A value. Polls the TX result in a loop via BRIANX (c8530), retrying while the result bit differs from the expected sequence. On success, toggles the sequence tracking bit in fs_sequence_nos.
|
||||||||
| 9266 | .econet_tx_retry←2← 840C JSR← 8448 JSR | |||||||
| STX net_tx_ptr ; Set TX control block ptr low byte | ||||||||
| 9268 | STY net_tx_ptr_hi ; Set TX control block ptr high byte | |||||||
| 926A | PHA ; Save A (handle bitmask) for later | |||||||
| 926B | AND fs_sequence_nos ; Compute sequence bit from handle | |||||||
| 926E | BEQ bsxl1 ; Zero: no sequence bit set | |||||||
| 9270 | LDA #1 ; Non-zero: normalise to bit 0 | |||||||
| 9272 | .bsxl1←1← 926E BEQ | |||||||
| LDY #0 ; Y=0: flag byte offset in TXCB | ||||||||
| 9274 | ORA (net_tx_ptr),y ; Merge sequence into existing flag byte | |||||||
| 9276 | PHA ; Save merged flag byte | |||||||
| 9277 | STA (net_tx_ptr),y ; Write flag+sequence to TXCB byte 0 | |||||||
| 9279 | JSR tx_poll_ff ; Transmit with full retry | |||||||
| 927C | LDA #&ff ; End address &FFFF = unlimited data length | |||||||
| 927E | LDY #8 ; Y=8: end address low offset in TXCB | |||||||
| 9280 | STA (net_tx_ptr),y ; Store &FF to end addr low | |||||||
| 9282 | INY ; Y=&09 | |||||||
| 9283 | STA (net_tx_ptr),y ; Store &FF to end addr high (Y=9) | |||||||
| 9285 | PLA ; Recover merged flag byte | |||||||
| 9286 | TAX ; Save in X for sequence compare | |||||||
| 9287 | LDY #&d1 ; Y=&D1: printer port number | |||||||
| 9289 | PLA ; Recover saved handle bitmask | |||||||
| 928A | PHA ; Re-save for later consumption | |||||||
| 928B | BEQ bspsx ; A=0: port &D1 (print); A!=0: port &90 (FS) | |||||||
| 928D | LDY #&90 ; Y=&90: FS data port | |||||||
| 928F | .bspsx←1← 928B BEQ | |||||||
| TYA ; A = selected port number | ||||||||
| 9290 | LDY #1 ; Y=1: port byte offset in TXCB | |||||||
| 9292 | STA (net_tx_ptr),y ; Write port to TXCB byte 1 | |||||||
| 9294 | TXA ; A = saved flag byte (expected sequence) | |||||||
| 9295 | DEY ; Y=&00 | |||||||
| 9296 | PHA ; Push expected sequence for retry loop | |||||||
| 9297 | .bsxl0←1← 92A3 BCS | |||||||
| LDA #&7f ; Flag byte &7F = waiting for reply | ||||||||
| 9299 | STA (net_tx_ptr),y ; Write to TXCB flag byte (Y=0) | |||||||
| 929B | JSR waitfs ; Transmit and wait for reply (BRIANX) | |||||||
| 929E | PLA ; Recover expected sequence | |||||||
| 929F | PHA ; Keep on stack for next iteration | |||||||
| 92A0 | EOR (net_tx_ptr),y ; Check if TX result matches expected sequence | |||||||
| 92A2 | ROR ; Bit 0 to carry (sequence mismatch?) | |||||||
| 92A3 | BCS bsxl0 ; C=1: mismatch, retry transmit | |||||||
| 92A5 | PLA ; Clean up: discard expected sequence | |||||||
| 92A6 | PLA ; Discard saved handle bitmask | |||||||
| 92A7 | EOR fs_sequence_nos ; Toggle sequence bit on success | |||||||
| 92AA | .return_bspsx | |||||||
| 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 &931E. 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. |
|
| 92AB | .lang_2_save_palette_vdu |
| LDA table_idx ; Save current table index | |
| 92AD | PHA ; Push for later restore |
| 92AE | LDA #&e9 ; Point workspace to palette save area (&E9) |
| 92B0 | STA nfs_workspace ; Set workspace low byte |
| 92B2 | LDY #0 ; Y=0: first palette entry |
| 92B4 | STY table_idx ; Clear table index counter |
| 92B6 | LDA vdu_screen_mode ; Save current screen MODE to workspace |
| 92B9 | STA (nfs_workspace),y ; Store MODE at workspace[0] |
| 92BB | INC nfs_workspace ; Advance workspace pointer past MODE byte |
| 92BD | LDA vdu_colours ; Read colour count (from &0351) |
| 92C0 | PHA ; Push for iteration count tracking |
| 92C1 | TYA ; A=0: logical colour number for OSWORD A=&00 |
| 92C2 | .save_palette_entry←1← 92E1 BNE |
| STA (nfs_workspace),y ; Store logical colour at workspace[0] | |
| 92C4 | LDX nfs_workspace ; X = workspace ptr low (param block addr) |
| 92C6 | LDY nfs_workspace_hi ; Y = workspace ptr high |
| 92C8 | LDA #osword_read_palette ; OSWORD &0B: read palette for logical colour |
| 92CA | JSR osword ; Read palette |
| 92CD | PLA ; Recover colour count |
| 92CE | LDY #0 ; Y=0: access workspace[0] |
| 92D0 | STA (nfs_workspace),y ; Write colour count back to workspace[0] |
| 92D2 | INY ; Y=1: access workspace[1] (palette result) Y=&01 |
| 92D3 | LDA (nfs_workspace),y ; Read palette value returned by OSWORD |
| 92D5 | PHA ; Push palette value for next iteration |
| 92D6 | LDX nfs_workspace ; X = current workspace ptr low |
| 92D8 | INC nfs_workspace ; Advance workspace pointer |
| 92DA | INC table_idx ; Increment table index |
| 92DC | DEY ; Y=0 for next store Y=&00 |
| 92DD | LDA table_idx ; Load table index as logical colour |
| 92DF | CPX #&f9 ; Loop until workspace wraps past &F9 |
| 92E1 | BNE save_palette_entry ; Continue for all 16 palette entries |
| 92E3 | PLA ; Discard last palette value from stack |
| 92E4 | STY table_idx ; Reset table index to 0 |
| 92E6 | INC nfs_workspace ; Advance workspace past palette data |
| 92E8 | JSR save_vdu_state ; Save cursor pos and OSBYTE state values |
| 92EB | INC nfs_workspace ; Advance workspace past VDU state data |
| 92ED | PLA ; Recover saved table index |
| 92EE | STA table_idx ; Restore table index |
| 92F0 | .clear_jsr_protection←4← 84B5 JMP← 84DD JSR← 8504 JSR← 8EEE JMP |
| LDA saved_jsr_mask ; Restore LSTAT from saved OLDJSR value | |
| 92F3 | STA prot_status ; Write to protection status |
| 92F6 | 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. The JSR to read_vdu_osbyte_x0 is a self-calling trick: it executes read_vdu_osbyte twice (once for &C2, once for &C3) because the RTS returns to the instruction at read_vdu_osbyte_x0 itself. |
||
| ROM | Exec | |
|---|---|---|
| 92F7 | .save_vdu_state←1← 92E8 JSR | |
| LDA vdu_cursor_edit ; Read cursor editing state | ||
| 92FA | STA (nfs_workspace),y ; Store to workspace[Y] | |
| 92FC | TAX ; Preserve in X for OSBYTE | |
| 92FD | JSR read_vdu_osbyte ; OSBYTE &85: read cursor position | |
| 9300 | INC nfs_workspace ; Advance workspace pointer | |
| 9302 | TYA ; Y result from OSBYTE &85 | |
| 9303 | STA (nfs_workspace,x) ; Store Y pos to workspace (X=0) | |
| 9305 | JSR read_vdu_osbyte_x0 ; Self-call trick: executes twice | |
| 9308 | .read_vdu_osbyte_x0←1← 9305 JSR | |
| LDX #0 ; X=0 for (zp,X) addressing | ||
| 930A | .read_vdu_osbyte←1← 92FD JSR | |
| LDY table_idx ; Index into OSBYTE number table | ||
| 930C | INC table_idx ; Next table entry next time | |
| 930E | INC nfs_workspace ; Advance workspace pointer | |
| 9310 | LDA vdu_osbyte_table,y ; Read OSBYTE number from table | |
| 9313 | LDY #&ff ; Y=&FF: read current value | |
| 9315 | JSR osbyte ; Call OSBYTE | |
| 9318 | TXA ; Result in X to A | |
| 9319 | LDX #0 ; X=0 for indexed indirect store | |
| 931B | STA (nfs_workspace,x) ; Store result to workspace | |
| 931D | RTS ; Return after storing result | |
| 931E | .vdu_osbyte_table←1← 9310 LDA | |
| EQUB &85, ; OSBYTE &85: read cursor position OSBYTE &C2, ; &C3: read screen start address &C3 | ||
| ; 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 R1 for WRCH requests ; (forwarded | ||
| ; to OSWRITCH &FFCB) and R2 for command bytes (dispatched ; via the | ||
| ; 12-entry table at &0500). The R2 command byte is stored ; at &51 | ||
| ; (self-modifying the JMP indirect low byte) before ; dispatch. | ||
| 9321 | 0016 | .nmi_workspace_start←1← 817C STA |
| .tube_brk_handler←1← 817C STA | ||
| LDA #&ff ; A=&FF: signal error to co-processor via R4 | ||
| 9323 | 0018 | JSR tube_send_r4 ; Send &FF error signal to Tube R4 |
| 9326 | 001B | LDA tube_data_register_2 ; Flush any pending R2 byte |
| 9329 | 001E | LDA #0 ; A=0: send zero prefix to R2 |
| 932B | 0020 | .tube_send_zero_r2 |
| JSR tube_send_r2 ; Send zero prefix byte via R2 | ||
| 932E | 0023 | TAY ; Y=0: start of error block at (&FD) |
| 932F | 0024 | LDA (brk_ptr),y ; Load error number from (&FD),0 |
| 9331 | 0026 | JSR tube_send_r2 ; Send error number via R2 |
| 9334 | 0029 | .tube_brk_send_loop←1← 0030 BNE |
| INY ; Advance to next error string byte | ||
| 9335 | 002A | .tube_send_error_byte |
| LDA (brk_ptr),y ; Load next error string byte | ||
| 9337 | 002C | JSR tube_send_r2 ; Send error string byte via R2 |
| 933A | 002F | TAX ; Zero byte = end of error string |
| 933B | 0030 | BNE tube_brk_send_loop ; Loop until zero terminator sent |
| 933D | 0032 | .tube_reset_stack←1← 0477 JMP |
| LDX #&ff ; Reset stack pointer to top | ||
| 933F | 0034 | TXS ; TXS: set stack pointer from X |
| 9340 | 0035 | CLI ; Enable interrupts for main loop |
| 9341 | 0036 | .tube_main_loop←6← 0044 BPL← 057F JMP← 05A6 JMP← 0604 JMP← 0665 JMP← 0692 JMP |
| BIT tube_status_1_and_tube_control ; BIT R1 status: check WRCH request | ||
| 9344 | 0039 | BPL tube_poll_r2 ; R1 not ready: check R2 instead |
| 9346 | 003B | .tube_handle_wrch←1← 0049 BMI |
| LDA tube_data_register_1 ; Read character from Tube R1 data | ||
| 9349 | 003E | JSR oswrch ; Write character |
| 934C | 0041 | .tube_poll_r2←1← 0039 BPL |
| BIT tube_status_register_2 ; BIT R2 status: check command byte | ||
| 934F | 0044 | BPL tube_main_loop ; R2 not ready: loop back to R1 check |
| 9351 | 0046 | BIT tube_status_1_and_tube_control ; Re-check R1: WRCH has priority over R2 |
| 9354 | 0049 | BMI tube_handle_wrch ; R1 ready: handle WRCH first |
| 9356 | 004B | LDX tube_data_register_2 ; Read command byte from Tube R2 data |
| 9359 | 004E | STX tube_cmd_lo ; Self-modify JMP low byte for dispatch |
| 935B | 0050 | .tube_dispatch_cmd |
| JMP (tube_dispatch_table) ; Dispatch to handler via indirect JMP | ||
| 935E | 0053 | .tube_transfer_addr←2← 04DE STY← 04EE STA |
| EQUB &00 ; Tube transfer address low byte | ||
| 935F | 0054 | .tube_xfer_page←3← 04B6 INC← 04D4 STA← 04F3 STA |
| EQUB &80 ; Tube transfer page (default &80) | ||
| 9360 | 0055 | .tube_xfer_addr_2←2← 04BA INC← 04FD STY |
| EQUB &00 ; Tube transfer address byte 2 | ||
| 9361 | 0056 | .tube_xfer_addr_3←2← 04BE INC← 04FB STA |
| EQUB &00 ; Tube transfer address byte 3 | ||
| ; Tube host code page 4 — reference: NFS12 (BEGIN, ADRR, ; SENDW) | ||
| ; Copied from ROM at reloc_p4_src 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 &0484 (BEGIN — startup/CLI entry, break type check) | ||
| ; &0403: JMP &06E2 (tube_escape_check) | ||
| ; &0406: tube_addr_claim — Tube address claim (ADRR protocol) | ||
| ; &0414: tube_release_claim — release address claim via R4 cmd 5 | ||
| ; &0421: tube_post_init — reset claimed-address state to &80 | ||
| ; &0435: data transfer setup (SENDW protocol) — &0435-&0483 | ||
| ; &0484: BEGIN — startup entry, sends ROM contents to Tube | ||
| ; &04CB: tube_claim_default — claim default transfer address | ||
| ; &04D2: tube_init_reloc — extract relocation address from ROM | ||
| 9362 | 0400 | .tube_code_page4←1← 8162 STA |
| JMP tube_begin ; JMP to BEGIN startup entry | ||
| 9365 | 0403 | .tube_escape_entry |
| JMP tube_escape_check ; JMP to tube_escape_check (&06A7) | ||
| 9368 | 0406 | .tube_addr_claim←10← 049A JSR← 04CF JMP← 8BA1 JSR← 8BB8 JSR← 8C15 JSR← 8E26 JMP← 997B JSR← 9A31 JSR← 9F07 JSR← 9F0F JSR |
| CMP #&80 ; A>=&80: address claim; A<&80: data transfer | ||
| 936A | 0408 | BCC tube_transfer_setup ; A<&80: data transfer setup (SENDW) |
| 936C | 040A | CMP #&c0 ; A>=&C0: new address claim from another host |
| 936E | 040C | BCS addr_claim_external ; C=1: external claim, check ownership |
| 9370 | 040E | ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison |
| 9372 | 0410 | CMP tube_claimed_id ; Is this for our currently-claimed address? |
| 9374 | 0412 | BNE return_tube_init ; Not our address: return |
| ; Release Tube address claim via R4 command 5 | ||
| ; Saves interrupt state (PHP/SEI), sends R4 command 5 ; (release) | ||
| ; followed by the currently-claimed address, then restores | ||
| ; interrupts. Falls through to tube_post_init to reset the | ||
| ; claimed-address state to &80. | ||
| 9376 | 0414 | .tube_release_claim←1← 0471 JSR |
| PHP ; PHP: save interrupt state for release | ||
| 9377 | 0415 | SEI ; SEI: disable interrupts during R4 protocol |
| 9378 | 0416 | LDA #5 ; R4 cmd 5: release our address claim |
| 937A | 0418 | JSR tube_send_r4 ; Send release command to co-processor |
| 937D | 041B | LDA tube_claimed_id ; Load our currently-claimed address |
| 937F | 041D | JSR tube_send_r4 ; Send our address as release parameter |
| 9382 | 0420 | PLP ; Restore interrupt state |
| 9383 | 0421 | .tube_post_init←1← 8174 JSR |
| LDA #&80 ; &80 sentinel: clear address claim | ||
| 9385 | 0423 | STA tube_claimed_id ; &80 sentinel = no address currently claimed |
| 9387 | 0425 | STA tube_claim_flag ; Store to claim-in-progress flag |
| 9389 | 0427 | RTS ; Return from tube_post_init |
| 938A | 0428 | .addr_claim_external←1← 040C BCS |
| ASL tube_claim_flag ; Another host claiming; check if we're owner | ||
| 938C | 042A | BCS accept_new_claim ; C=1: we have an active claim |
| 938E | 042C | CMP tube_claimed_id ; Compare with our claimed address |
| 9390 | 042E | BEQ return_tube_init ; Match: return (we already have it) |
| 9392 | 0430 | CLC ; Not ours: CLC = we don't own this address |
| 9393 | 0431 | RTS ; Return with C=0 (claim denied) |
| 9394 | 0432 | .accept_new_claim←1← 042A BCS |
| STA tube_claimed_id ; Accept new claim: update our address | ||
| 9396 | 0434 | .return_tube_init←2← 0412 BNE← 042E BEQ |
| RTS ; Return with address updated | ||
| 9397 | 0435 | .tube_transfer_setup←1← 0408 BCC |
| PHP ; PHP: save interrupt state | ||
| 9398 | 0436 | SEI ; SEI: disable interrupts for R4 protocol |
| 9399 | 0437 | .setup_data_transfer |
| STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y) | ||
| 939B | 0439 | STX tube_data_ptr ; Store address pointer low byte |
| 939D | 043B | JSR tube_send_r4 ; Send transfer type byte to co-processor |
| 93A0 | 043E | TAX ; X = transfer type for table lookup |
| 93A1 | 043F | LDY #3 ; Y=3: send 4 bytes (address + claimed addr) |
| 93A3 | 0441 | LDA tube_claimed_id ; Send our claimed address + 4-byte xfer addr |
| 93A5 | 0443 | JSR tube_send_r4 ; Send transfer address byte |
| 93A8 | 0446 | .send_xfer_addr_bytes←1← 044C BPL |
| LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y) | ||
| 93AA | 0448 | JSR tube_send_r4 ; Send address byte to co-processor via R4 |
| 93AD | 044B | DEY ; Previous byte (big-endian: 3,2,1,0) |
| 93AE | 044C | BPL send_xfer_addr_bytes ; Loop for all 4 address bytes |
| 93B0 | 044E | LDY #&18 ; Y=&18: enable Tube control register |
| 93B2 | 0450 | STY tube_status_1_and_tube_control ; Enable Tube interrupt generation |
| 93B5 | 0453 | LDA tube_ctrl_values,x ; Look up Tube control bits for this xfer type |
| 93B8 | 0456 | STA tube_status_1_and_tube_control ; Apply transfer- specific control bits |
| 93BB | 0459 | LSR ; LSR: check bit 2 (2-byte flush needed?) |
| 93BC | 045A | LSR ; LSR: shift bit 2 to carry |
| 93BD | 045B | BCC skip_r3_flush ; C=0: no flush needed, skip R3 reads |
| 93BF | 045D | BIT tube_data_register_3 ; Dummy R3 reads: flush for 2-byte transfers |
| 93C2 | 0460 | BIT tube_data_register_3 ; Second dummy read to flush R3 FIFO |
| 93C5 | 0463 | .skip_r3_flush←1← 045B BCC |
| JSR tube_send_r4 ; Trigger co-processor ack via R4 | ||
| 93C8 | 0466 | .poll_r4_copro_ack←1← 0469 BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status for co- processo r response | ||
| 93CB | 0469 | BVC poll_r4_copro_ack ; Bit 6 clear: not ready, keep polling |
| 93CD | 046B | BCS copro_ack_nmi_check ; R4 bit 7: co-processor acknowledged transfer |
| 93CF | 046D | CPX #4 ; Type 4 = SENDW (host-to-parasite word xfer) |
| 93D1 | 046F | BNE skip_nmi_release ; Not SENDW type: skip release path |
| 93D3 | 0471 | .tube_sendw_complete←1← 0496 BEQ |
| JSR tube_release_claim ; SENDW complete: release, sync, restart | ||
| 93D6 | 0474 | JSR tube_send_r2 ; Sync via R2 send |
| 93D9 | 0477 | JMP tube_reset_stack ; Restart Tube main loop |
| 93DC | 047A | .copro_ack_nmi_check←1← 046B BCS |
| LSR ; LSR: check bit 0 (NMI used?) | ||
| 93DD | 047B | BCC skip_nmi_release ; C=0: NMI not used, skip NMI release |
| 93DF | 047D | LDY #&88 ; Release Tube NMI (transfer used interrupts) |
| 93E1 | 047F | STY tube_status_1_and_tube_control ; Write &88 to Tube control to release NMI |
| 93E4 | 0482 | .skip_nmi_release←2← 046F BNE← 047B BCC |
| PLP ; Restore interrupt state | ||
| 93E5 | 0483 | .return_tube_xfer |
| RTS ; Return from transfer setup | ||
| ; Tube host startup entry (BEGIN) | ||
| ; Entry point via JMP from &0400. Enables interrupts, ; checks | ||
| ; break type via OSBYTE &FD: soft break re-initialises ; Tube and | ||
| ; restarts, hard break claims address &FF. Sends ROM ; contents | ||
| ; to co-processor page by page via SENDW, then claims the ; final | ||
| ; transfer address. | ||
| 93E6 | 0484 | .tube_begin←1← 0400 JMP |
| CLI ; BEGIN: enable interrupts for Tube host code | ||
| 93E7 | 0485 | BCS claim_addr_ff ; C=1: hard break, claim addr &FF |
| 93E9 | 0487 | BNE check_break_type ; C=0, A!=0: re-init path |
| 93EB | 0489 | JMP tube_reply_ack ; Z=1 from C=0 path: just acknowledge |
| 93EE | 048C | .check_break_type←1← 0487 BNE |
| LDX #0 ; X=0 for OSBYTE | ||
| 93F0 | 048E | LDY #&ff ; Y=&FF for OSBYTE |
| 93F2 | 0490 | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: what type of reset was this? |
| 93F4 | 0492 | JSR osbyte ; Read type of last reset |
| 93F7 | 0495 | TXA ; X=value of type of last reset |
| 93F8 | 0496 | BEQ tube_sendw_complete ; Soft break (X=0): re-init Tube and restart |
| 93FA | 0498 | .claim_addr_ff←2← 0485 BCS← 049D BCC |
| LDA #&ff ; Claim address &FF (startup = highest prio) | ||
| 93FC | 049A | JSR tube_addr_claim ; Request address claim from Tube system |
| 93FF | 049D | BCC claim_addr_ff ; C=0: claim failed, retry |
| 9401 | 049F | JSR tube_init_reloc ; Init reloc pointers from ROM header |
| 9404 | 04A2 | .next_rom_page←1← 04C4 BVC |
| LDA #7 ; R4 cmd 7: SENDW to send ROM to parasite | ||
| 9406 | 04A4 | JSR tube_claim_default ; Set up Tube for SENDW transfer |
| 9409 | 04A7 | LDY #0 ; Y=0: start at beginning of page |
| 940B | 04A9 | STY zp_ptr_lo ; Store to zero page pointer low byte |
| 940D | 04AB | .send_rom_page_bytes←1← 04B4 BNE |
| LDA (zp_ptr_lo),y ; Send 256-byte page via R3, byte at a time | ||
| 940F | 04AD | STA tube_data_register_3 ; Write byte to Tube R3 data register |
| 9412 | 04B0 | NOP ; Timing delay: Tube data register needs NOPs |
| 9413 | 04B1 | NOP ; NOP delay (2) |
| 9414 | 04B2 | NOP ; NOP delay (3) |
| 9415 | 04B3 | INY ; Next byte in page |
| 9416 | 04B4 | BNE send_rom_page_bytes ; Loop for all 256 bytes |
| 9418 | 04B6 | INC tube_xfer_page ; Increment 24-bit destination addr |
| 941A | 04B8 | BNE skip_addr_carry ; No carry: skip higher bytes |
| 941C | 04BA | INC tube_xfer_addr_2 ; Carry into second byte |
| 941E | 04BC | BNE skip_addr_carry ; No carry: skip third byte |
| 9420 | 04BE | INC tube_xfer_addr_3 ; Carry into third byte |
| 9422 | 04C0 | .skip_addr_carry←2← 04B8 BNE← 04BC BNE |
| INC zp_ptr_hi ; Increment page counter | ||
| 9424 | 04C2 | BIT zp_ptr_hi ; Bit 6 set = all pages transferred |
| 9426 | 04C4 | BVC next_rom_page ; More pages: loop back to SENDW |
| 9428 | 04C6 | JSR tube_init_reloc ; Re-init reloc pointers for final claim |
| 942B | 04C9 | LDA #4 ; A=4: transfer type for final address claim |
| ; Claim default Tube transfer address | ||
| ; Sets Y=0, X=&53 (address &0053), then JMP ; tube_addr_claim | ||
| ; to initiate a Tube address claim for the default ; transfer | ||
| ; address. Called from the BEGIN startup path and after ; the | ||
| ; page transfer loop completes. | ||
| 942D | 04CB | .tube_claim_default←1← 04A4 JSR |
| LDY #0 ; Y=0: transfer address low byte | ||
| 942F | 04CD | LDX #&53 ; X=&53: transfer address high byte (&0053) |
| 9431 | 04CF | JMP tube_addr_claim ; Claim Tube address for transfer |
| ; 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. | ||
| 9434 | 04D2 | .tube_init_reloc←2← 049F JSR← 04C6 JSR |
| LDA #&80 ; Init: start sending from &8000 | ||
| 9436 | 04D4 | STA tube_xfer_page ; Store &80 as source page high byte |
| 9438 | 04D6 | STA zp_ptr_hi ; Store &80 as page counter initial value |
| 943A | 04D8 | LDA #&20 ; A=&20: bit 5 mask for ROM type check |
| 943C | 04DA | AND rom_type ; ROM type bit 5: reloc address in header? |
| 943F | 04DD | TAY ; Y = 0 or &20 (reloc flag) |
| 9440 | 04DE | STY tube_transfer_addr ; Store as transfer address selector |
| 9442 | 04E0 | BEQ store_xfer_end_addr ; No reloc addr: use defaults |
| 9444 | 04E2 | LDX copyright_offset ; Skip past copyright string to find reloc addr |
| 9447 | 04E5 | .scan_copyright_end←1← 04E9 BNE |
| INX ; Skip past null-terminated copyright string | ||
| 9448 | 04E6 | LDA rom_header,x ; Load next byte from ROM header |
| 944B | 04E9 | BNE scan_copyright_end ; Loop until null terminator found |
| 944D | 04EB | LDA language_handler_lo,x ; Read 4-byte reloc address from ROM header |
| 9450 | 04EE | STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr |
| 9452 | 04F0 | LDA language_handler_hi,x ; Load reloc addr byte 2 |
| 9455 | 04F3 | STA tube_xfer_page ; Store as source page start |
| 9457 | 04F5 | LDY service_entry,x ; Load reloc addr byte 3 |
| 945A | 04F8 | LDA service_handler_lo,x ; Load reloc addr byte 4 (highest) |
| 945D | 04FB | .store_xfer_end_addr←1← 04E0 BEQ |
| STA tube_xfer_addr_3 ; Store high byte of end address | ||
| 945F | 04FD | STY tube_xfer_addr_2 ; Store byte 3 of end address |
| 9461 | 04FF | RTS ; Return with pointers initialised |
| ; Tube host code page 5 — reference: NFS13 (TASKS, ; BPUT-FILE) | ||
| ; Copied from ROM at reloc_p5_src during init. Contains: | ||
| ; &0500: 12-entry dispatch table (&0500-&0517) | ||
| ; &0518: 8-byte Tube control register value table | ||
| ; &0520: tube_osbput — write byte to file | ||
| ; &052D: tube_osbget — read byte from file | ||
| ; &0537: tube_osrdch — read character | ||
| ; &053A: tube_rdch_reply — send carry+data as reply | ||
| ; &0542: tube_osfind — open/close file | ||
| ; &055E: tube_osargs — file argument read/write | ||
| ; &0582: tube_read_string — read string from R2 into &0700 | ||
| ; &0596: tube_oscli — execute * command | ||
| ; &059C: tube_reply_ack — send &7F acknowledge | ||
| ; &05A9: tube_osfile — whole file operation | ||
| ; &05D1: tube_osgbpb — multi-byte file read/write | ||
| ; Code continues seamlessly into page 6 (tube_osbyte_short ; at &05F2 | ||
| ; straddles the page boundary with a BVC at &05FF/&0600). | ||
| 9462 | 0500 | .tube_dispatch_table←2← 0050 JMP← 8168 STA |
| EQUW tube_osrdch ; 12-entry Tube R2 command dispatch table R2 cmd 0: OSRDCH | ||
| 9464 | 0502 | EQUW tube_oscli ; R2 cmd 1: OSCLI |
| 9466 | 0504 | EQUW tube_osbyte_2param ; R2 cmd 2: OSBYTE (2-param) |
| 9468 | 0506 | EQUW tube_osbyte_long ; R2 cmd 3: OSBYTE (3-param) |
| 946A | 0508 | EQUW tube_osword ; R2 cmd 4: OSWORD |
| 946C | 050A | EQUW tube_osword_rdln ; R2 cmd 5: OSWORD 0 (read line) |
| 946E | 050C | EQUW tube_osargs ; R2 cmd 6: OSARGS |
| 9470 | 050E | EQUW tube_osbget ; R2 cmd 7: OSBGET |
| 9472 | 0510 | EQUW tube_osbput ; R2 cmd 8: OSBPUT |
| 9474 | 0512 | EQUW tube_osfind ; R2 cmd 9: OSFIND |
| 9476 | 0514 | EQUW tube_osfile ; R2 cmd 10: OSFILE |
| 9478 | 0516 | EQUW tube_osgbpb ; R2 cmd 11: OSGBPB |
| ; Tube ULA control register values, indexed by transfer | ||
| ; type (0-7). Written to &FEE0 after clearing V+M with | ||
| ; &18. Bit layout: S=set/clear, T=reset regs, P=PRST, | ||
| ; V=2-byte R3, M=PNMI(R3), J=PIRQ(R4), I=PIRQ(R1), | ||
| ; Q=HIRQ(R4). Bits 1-7 select flags; bit 0 (S) is the | ||
| ; value to set or clear. | ||
| 947A | 0518 | .tube_ctrl_values←1← 0453 LDA |
| EQUB &86 ; Type 0: set I+J (1-byte R3, parasite to host) | ||
| 947B | 0519 | EQUB &88 ; Type 1: set M (1-byte R3, host to parasite) |
| 947C | 051A | EQUB &96 ; Type 2: set V+I+J (2-byte R3, parasite to host) |
| 947D | 051B | EQUB &98 ; Type 3: set V+M (2-byte R3, host to parasite) |
| 947E | 051C | EQUB &18 ; Type 4: clear V+M (execute code at address) |
| 947F | 051D | EQUB &18 ; Type 5: clear V+M (release address claim) |
| 9480 | 051E | EQUB &82 ; Type 6: set I (define event handler) |
| 9481 | 051F | EQUB &18 ; Type 7: clear V+M (transfer and release) |
| ; Tube OSBPUT handler (R2 cmd 8) | ||
| ; Reads file handle and data byte from R2, then | ||
| ; calls OSBPUT (&FFD4) to write the byte. Falls through | ||
| ; to tube_reply_ack to send &7F acknowledgement. | ||
| 9482 | 0520 | .tube_osbput |
| JSR tube_read_r2 ; Read channel handle from R2 for BPUT | ||
| 9485 | 0523 | TAY ; Y=channel handle from R2 |
| 9486 | 0524 | JSR tube_read_r2 ; Read data byte from R2 for BPUT |
| 9489 | 0527 | .tube_poll_r1_wrch |
| JSR osbput ; Write a single byte A to an open file Y | ||
| 948C | 052A | JMP tube_reply_ack ; BPUT done: send acknowledge, return |
| ; Tube OSBGET handler (R2 cmd 7) | ||
| ; Reads file handle from R2, calls OSBGET (&FFD7) | ||
| ; to read a byte, then falls through to tube_rdch_reply | ||
| ; which encodes the carry flag (error) into bit 7 and | ||
| ; sends the result byte via R2. | ||
| 948F | 052D | .tube_osbget |
| JSR tube_read_r2 ; Read channel handle from R2 for BGET | ||
| 9492 | 0530 | TAY ; Y=channel handle for OSBGET Y=file handle |
| 9493 | 0531 | JSR osbget ; Read a single byte from an open file Y |
| 9496 | 0534 | JMP tube_rdch_reply ; Send carry+byte reply (BGET result) |
| ; Tube OSRDCH handler (R2 cmd 0) | ||
| ; Calls OSRDCH (&FFE0) to read a character from | ||
| ; the current input stream, then falls through to | ||
| ; tube_rdch_reply which encodes the carry flag (error) | ||
| ; into bit 7 and sends the result byte via R2. | ||
| 9499 | 0537 | .tube_osrdch |
| JSR osrdch ; Read a character from the current input stream | ||
| 949C | 053A | .tube_rdch_reply←2← 0534 JMP← 05EF JMP |
| ROR ; ROR A: encode carry (error flag) into bit 7 | ||
| 949D | 053B | JSR tube_send_r2 ; Send carry+data byte to Tube R2 |
| 94A0 | 053E | ROL ; ROL A: restore carry flag |
| 94A1 | 053F | JMP tube_reply_byte ; Return via tube_reply_byte |
| ; Tube OSFIND handler (R2 cmd 9) | ||
| ; Reads open mode from R2. If zero, reads a file | ||
| ; handle and closes that file. Otherwise saves the mode, | ||
| ; reads a filename string into &0700 via tube_read_string, | ||
| ; then calls OSFIND (&FFCE) to open the file. Sends the | ||
| ; resulting file handle (or &00) via tube_reply_byte. | ||
| 94A4 | 0542 | .tube_osfind |
| JSR tube_read_r2 ; Read open mode from R2 for OSFIND | ||
| 94A7 | 0545 | BEQ tube_osfind_close ; A=0: close file, else open with filename |
| 94A9 | 0547 | PHA ; Save open mode while reading filename |
| 94AA | 0548 | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 94AD | 054B | PLA ; Recover open mode from stack |
| 94AE | 054C | JSR osfind ; Open or close file(s) |
| 94B1 | 054F | JMP tube_reply_byte ; Send file handle result to co-processor |
| 94B4 | 0552 | .tube_osfind_close←1← 0545 BEQ |
| JSR tube_read_r2 ; OSFIND close: read handle from R2 | ||
| 94B7 | 0555 | TAY ; Y=handle to close |
| 94B8 | 0556 | LDA #osfind_close ; A=0: close command for OSFIND |
| 94BA | 0558 | JSR osfind ; Close one or all files |
| 94BD | 055B | JMP tube_reply_ack ; Close done: send acknowledge, return |
| ; Tube OSARGS handler (R2 cmd 6) | ||
| ; Reads file handle from R2 into Y, then reads | ||
| ; a 4-byte argument and reason code into zero page. | ||
| ; Calls OSARGS (&FFDA), sends the result A and 4-byte | ||
| ; return value via R2, then returns to the main loop. | ||
| 94C0 | 055E | .tube_osargs |
| JSR tube_read_r2 ; Read file handle from R2 for OSARGS | ||
| 94C3 | 0561 | TAY ; Y=file handle for OSARGS |
| 94C4 | 0562 | .tube_read_params |
| LDX #4 ; Read 4-byte arg + reason from R2 into ZP | ||
| 94C6 | 0564 | .read_osargs_params←1← 056A BNE |
| JSR tube_read_r2 ; Read next param byte from R2 | ||
| 94C9 | 0567 | STA escape_flag,x ; Params stored at &00-&03 (little-endian) |
| 94CB | 0569 | DEX ; Decrement byte counter |
| 94CC | 056A | BNE read_osargs_params ; Loop for 4 bytes |
| 94CE | 056C | JSR tube_read_r2 ; Read OSARGS reason code from R2 |
| 94D1 | 056F | JSR osargs ; Read or write a file's attributes |
| 94D4 | 0572 | JSR tube_send_r2 ; Send result A back to co-processor |
| 94D7 | 0575 | LDX #3 ; Return 4-byte result from ZP &00-&03 |
| 94D9 | 0577 | .send_osargs_result←1← 057D BPL |
| LDA zp_ptr_lo,x ; Load result byte from zero page | ||
| 94DB | 0579 | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 94DE | 057C | DEX ; Previous byte (count down) |
| 94DF | 057D | BPL send_osargs_result ; Loop for all 4 bytes |
| 94E1 | 057F | JMP tube_main_loop ; Return to Tube main loop |
| ; Read string from Tube R2 into buffer | ||
| ; Loops reading bytes from tube_read_r2 into the | ||
| ; string buffer at &0700, storing at string_buf+Y. | ||
| ; Terminates on CR (&0D) or when Y wraps to zero | ||
| ; (256-byte overflow). Returns with X=0, Y=7 so that | ||
| ; XY = &0700, ready for OSCLI or OSFIND dispatch. | ||
| ; Called by the Tube OSCLI and OSFIND handlers. | ||
| 94E4 | 0582 | .tube_read_string←3← 0548 JSR← 0596 JSR← 05B3 JSR |
| LDX #0 ; X=0: initialise string buffer index | ||
| 94E6 | 0584 | LDY #0 ; Y=0: string buffer offset 0 |
| 94E8 | 0586 | .strnh←1← 0591 BNE |
| JSR tube_read_r2 ; Read next string byte from R2 | ||
| 94EB | 0589 | STA string_buf,y ; Store byte in string buffer at &0700+Y |
| 94EE | 058C | INY ; Next buffer position |
| 94EF | 058D | BEQ string_buf_done ; Y overflow: string too long, truncate |
| 94F1 | 058F | CMP #&0d ; Check for CR terminator |
| 94F3 | 0591 | BNE strnh ; Not CR: continue reading string |
| 94F5 | 0593 | .string_buf_done←1← 058D BEQ |
| LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND | ||
| 94F7 | 0595 | RTS ; Return with XY pointing to &0700 |
| ; Tube OSCLI handler (R2 cmd 1) | ||
| ; Reads a command string from R2 into &0700 via | ||
| ; tube_read_string, then calls OSCLI (&FFF7) to execute | ||
| ; it. Falls through to tube_reply_ack to send &7F | ||
| ; acknowledgement. | ||
| 94F8 | 0596 | .tube_oscli |
| JSR tube_read_string ; Read command string from R2 into &0700 | ||
| 94FB | 0599 | JSR oscli ; Execute * command via OSCLI |
| 94FE | 059C | .tube_reply_ack←3← 0489 JMP← 052A JMP← 055B JMP |
| LDA #&7f ; &7F = success acknowledgement | ||
| 9500 | 059E | .tube_reply_byte←4← 053F JMP← 054F JMP← 05A1 BVC← 067D JMP |
| BIT tube_status_register_2 ; Poll R2 status until ready | ||
| 9503 | 05A1 | BVC tube_reply_byte ; Bit 6 clear: not ready, loop |
| 9505 | 05A3 | STA tube_data_register_2 ; Write byte to R2 data register |
| 9508 | 05A6 | .mj←1← 05CF BEQ |
| JMP tube_main_loop ; Return to Tube main loop | ||
| ; Tube OSFILE handler (R2 cmd 10) | ||
| ; Reads a 16-byte control block into zero page, | ||
| ; a filename string into &0700 via tube_read_string, | ||
| ; and a reason code from R2. Calls OSFILE (&FFDD), | ||
| ; then sends the result A and updated 16-byte control | ||
| ; block back via R2. Returns to the main loop via mj. | ||
| 950B | 05A9 | .tube_osfile |
| LDX #&10 ; Read 16-byte OSFILE control block from R2 | ||
| 950D | 05AB | .argsw←1← 05B1 BNE |
| JSR tube_read_r2 ; Read next control block byte from R2 | ||
| 9510 | 05AE | STA zp_ptr_hi,x ; Store at &01+X (descending) |
| 9512 | 05B0 | DEX ; Decrement byte counter |
| 9513 | 05B1 | BNE argsw ; Loop for all 16 bytes |
| 9515 | 05B3 | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 9518 | 05B6 | STX zp_ptr_lo ; XY=&0700: filename pointer for OSFILE |
| 951A | 05B8 | STY zp_ptr_hi ; Store Y=7 as pointer high byte |
| 951C | 05BA | LDY #0 ; Y=0 for OSFILE control block offset |
| 951E | 05BC | JSR tube_read_r2 ; Read OSFILE reason code from R2 |
| 9521 | 05BF | JSR osfile ; Execute OSFILE operation |
| 9524 | 05C2 | JSR tube_send_r2 ; Send result A (object type) to co-processor |
| 9527 | 05C5 | LDX #&10 ; Return 16-byte control block to co-processor |
| 9529 | 05C7 | .send_osfile_ctrl_blk←1← 05CD BNE |
| LDA zp_ptr_hi,x ; Load control block byte | ||
| 952B | 05C9 | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 952E | 05CC | DEX ; Decrement byte counter |
| 952F | 05CD | BNE send_osfile_ctrl_blk ; Loop for all 16 bytes |
| 9531 | 05CF | BEQ mj ; ALWAYS branch to main loop ALWAYS branch |
| ; Tube OSGBPB handler (R2 cmd 11) | ||
| ; Reads a 13-byte control block and reason code | ||
| ; from R2 into zero page. Calls OSGBPB (&FFD1), then | ||
| ; sends 12 result bytes and the carry+result byte | ||
| ; (via tube_rdch_reply) back via R2. | ||
| 9533 | 05D1 | .tube_osgbpb |
| LDX #&0d ; Read 13-byte OSGBPB control block from R2 | ||
| 9535 | 05D3 | .read_osgbpb_ctrl_blk←1← 05D9 BNE |
| JSR tube_read_r2 ; Read next control block byte from R2 | ||
| 9538 | 05D6 | STA escape_flag,x ; Store at &FF+X (descending into &00-&0C) |
| 953A | 05D8 | DEX ; Decrement byte counter |
| 953B | 05D9 | BNE read_osgbpb_ctrl_blk ; Loop for all 13 bytes |
| 953D | 05DB | JSR tube_read_r2 ; Read OSGBPB reason code from R2 |
| 9540 | 05DE | LDY #0 ; Y=0 for OSGBPB control block |
| 9542 | 05E0 | JSR osgbpb ; Read or write multiple bytes to an open file |
| 9545 | 05E3 | PHA ; Save A (completion status) for later |
| 9546 | 05E4 | LDX #&0c ; Return 13-byte result block to co-processor |
| 9548 | 05E6 | .send_osgbpb_result←1← 05EC BPL |
| LDA zp_ptr_lo,x ; Load result byte from zero page | ||
| 954A | 05E8 | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 954D | 05EB | DEX ; Decrement byte counter |
| 954E | 05EC | BPL send_osgbpb_result ; Loop for 13 bytes (X=12..0) |
| 9550 | 05EE | PLA ; Recover completion status from stack |
| 9551 | 05EF | JMP tube_rdch_reply ; Send carry+status as RDCH-style reply |
| ; Tube OSBYTE 2-param handler (R2 cmd 2) | ||
| ; Reads X and A from R2, calls OSBYTE (&FFF4) | ||
| ; with Y=0, then sends the result X via | ||
| ; tube_reply_byte. Used for OSBYTE calls that take | ||
| ; only A and X parameters. | ||
| 9554 | 05F2 | .tube_osbyte_2param |
| JSR tube_read_r2 ; Read X param from R2 for 2-param OSBYTE | ||
| 9557 | 05F5 | TAX ; X = first parameter |
| 9558 | 05F6 | JSR tube_read_r2 ; Read A (OSBYTE number) from R2 |
| 955B | 05F9 | JSR osbyte ; Execute OSBYTE call |
| 955E | 05FC | .tube_poll_r2_result←2← 05FF ref← 0625 BVS |
| BIT tube_status_register_2 ; Poll R2 status for result send | ||
| 9561 | 05FF | EQUB &50 ; BVC: page 5/6 boundary straddle |
| 9562 | 0600 | .tube_page6_start←1← 816E STA |
| EQUB &FB ; Send carry+status to co-processor via R2 | ||
| 9563 | 0601 | STX tube_data_register_2 ; Send X result for 2-param OSBYTE |
| 9566 | 0604 | .bytex←1← 0617 BEQ |
| JMP tube_main_loop ; Return to main event loop | ||
| ; Tube OSBYTE 3-param handler (R2 cmd 3) | ||
| ; Reads X, Y, and A from R2, calls OSBYTE | ||
| ; (&FFF4), then sends carry+Y and X as result bytes | ||
| ; via R2. Used for OSBYTE calls needing all three | ||
| ; parameters and returning both X and Y results. | ||
| 9569 | 0607 | .tube_osbyte_long |
| JSR tube_read_r2 ; Read X, Y, A from R2 for 3-param OSBYTE | ||
| 956C | 060A | TAX ; Save in X |
| 956D | 060B | JSR tube_read_r2 ; Read Y parameter from co-processor |
| 9570 | 060E | TAY ; Save in Y |
| 9571 | 060F | JSR tube_read_r2 ; Read A (OSBYTE function code) |
| 9574 | 0612 | JSR osbyte ; Execute OSBYTE A,X,Y |
| 9577 | 0615 | EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT) |
| 9579 | 0617 | BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed |
| 957B | 0619 | ROR ; Encode carry (error flag) into bit 7 |
| 957C | 061A | JSR tube_send_r2 ; Send carry+status byte via R2 |
| 957F | 061D | .tube_osbyte_send_y←1← 0620 BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 9582 | 0620 | BVC tube_osbyte_send_y ; Not ready: keep polling |
| 9584 | 0622 | STY tube_data_register_2 ; Send Y result, then fall through to send X |
| 9587 | 0625 | .tube_osbyte_short |
| BVS tube_poll_r2_result ; BVS &05FC: overlapping code — loops back to page 5 R2 poll to send X after Y | ||
| ; Tube OSWORD handler (R2 cmd 4) | ||
| ; Reads OSWORD number A and in-length from R2, | ||
| ; then reads the parameter block into &0128. Calls | ||
| ; OSWORD (&FFF1), then sends the out-length result | ||
| ; bytes from the parameter block back via R2. | ||
| ; Returns to the main loop via tube_return_main. | ||
| 9589 | 0627 | .tube_osword |
| JSR tube_read_r2 ; Overlapping entry: &20 = JSR c06c5 (OSWORD) | ||
| 958C | 062A | TAY ; Save OSWORD number in Y |
| 958D | 062B | .tube_osword_read←1← 062E BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 9590 | 062E | BPL tube_osword_read ; Not ready: keep polling |
| 9592 | 0630 | .tube_osbyte_send_x |
| LDX tube_data_register_2 ; Read param block length from R2 | ||
| 9595 | 0633 | DEX ; DEX: length 0 means no params to read |
| 9596 | 0634 | BMI skip_param_read ; No params (length=0): skip read loop |
| 9598 | 0636 | .tube_osword_read_lp←2← 0639 BPL← 0642 BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 959B | 0639 | BPL tube_osword_read_lp ; Not ready: keep polling |
| 959D | 063B | LDA tube_data_register_2 ; Read param byte from R2 |
| 95A0 | 063E | STA tube_osword_pb,x ; Store param bytes into block at &0128 |
| 95A3 | 0641 | DEX ; Next param byte (descending) |
| 95A4 | 0642 | BPL tube_osword_read_lp ; Loop until all params read |
| 95A6 | 0644 | TYA ; Restore OSWORD number from Y |
| 95A7 | 0645 | .skip_param_read←1← 0634 BMI |
| LDX #<(tube_osword_pb) ; XY=&0128: param block address for OSWORD | ||
| 95A9 | 0647 | LDY #>(tube_osword_pb) ; Y=&01: param block at &0128 |
| 95AB | 0649 | JSR osword ; Execute OSWORD with XY=&0128 |
| 95AE | 064C | .poll_r2_osword_result←1← 064F BPL |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95B1 | 064F | BPL poll_r2_osword_result ; Not ready: keep polling |
| 95B3 | 0651 | LDX tube_data_register_2 ; Read result block length from R2 |
| 95B6 | 0654 | DEX ; Decrement result byte counter |
| 95B7 | 0655 | BMI tube_return_main ; No results to send: return to main loop |
| 95B9 | 0657 | .tube_osword_write←1← 0663 BPL |
| LDY tube_osword_pb,x ; Send result block bytes from &0128 via R2 | ||
| 95BC | 065A | .tube_osword_write_lp←1← 065D BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95BF | 065D | BVC tube_osword_write_lp ; Not ready: keep polling |
| 95C1 | 065F | STY tube_data_register_2 ; Send result byte via R2 |
| 95C4 | 0662 | DEX ; Next result byte (descending) |
| 95C5 | 0663 | BPL tube_osword_write ; Loop until all results sent |
| 95C7 | 0665 | .tube_return_main←1← 0655 BMI |
| JMP tube_main_loop ; Return to main event loop | ||
| ; Tube OSWORD 0 handler (R2 cmd 5) | ||
| ; Handles OSWORD 0 (read line) specially. Reads | ||
| ; 4 parameter bytes from R2 into &0128 (max length, | ||
| ; min char, max char, flags). Calls OSWORD 0 (&FFF1) | ||
| ; to read a line, then sends &7F+CR or the input line | ||
| ; byte-by-byte via R2, followed by &80 (error/escape) | ||
| ; or &7F (success). | ||
| 95CA | 0668 | .tube_osword_rdln |
| LDX #4 ; Read 5-byte OSWORD 0 control block from R2 | ||
| 95CC | 066A | .read_rdln_ctrl_block←1← 0670 BPL |
| JSR tube_read_r2 ; Read control block byte from R2 | ||
| 95CF | 066D | STA zp_ptr_lo,x ; Store in zero page params |
| 95D1 | 066F | DEX ; Next byte (descending) |
| 95D2 | 0670 | BPL read_rdln_ctrl_block ; Loop until all 5 bytes read |
| 95D4 | 0672 | INX ; X=0 after loop, A=0 for OSWORD 0 (read line) |
| 95D5 | 0673 | LDY #0 ; Y=0 for OSWORD 0 |
| 95D7 | 0675 | TXA ; A=0: OSWORD 0 (read line) |
| 95D8 | 0676 | JSR osword ; Read input line from keyboard |
| 95DB | 0679 | BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed |
| 95DD | 067B | LDA #&ff ; &FF = escape/error signal to co-processor |
| 95DF | 067D | JMP tube_reply_byte ; Escape: send &FF error to co-processor |
| 95E2 | 0680 | .tube_rdln_send_line←1← 0679 BCC |
| LDX #0 ; X=0: start of input buffer at &0700 | ||
| 95E4 | 0682 | LDA #&7f ; &7F = line read successfully |
| 95E6 | 0684 | JSR tube_send_r2 ; Send &7F (success) to co-processor |
| 95E9 | 0687 | .tube_rdln_send_loop←1← 0690 BNE |
| LDA string_buf,x ; Load char from input buffer | ||
| 95EC | 068A | .tube_rdln_send_byte |
| JSR tube_send_r2 ; Send char to co-processor | ||
| 95EF | 068D | INX ; Next character |
| 95F0 | 068E | CMP #&0d ; Check for CR terminator |
| 95F2 | 0690 | BNE tube_rdln_send_loop ; Loop until CR terminator sent |
| 95F4 | 0692 | JMP tube_main_loop ; Return to main event loop |
| ; Send byte to Tube data register R2 | ||
| ; Polls Tube status register 2 until bit 6 (TDRA) | ||
| ; is set, then writes A to the data register. Uses a | ||
| ; tight BIT/BVC polling loop. Called by 12 sites | ||
| ; across the Tube host code for all R2 data | ||
| ; transmission: command responses, file data, OSBYTE | ||
| ; results, and control block bytes. | ||
| 95F7 | 0695 | .tube_send_r2←14← 0020 JSR← 0026 JSR← 002C JSR← 0474 JSR← 053B JSR← 0572 JSR← 0579 JSR← 05C2 JSR← 05C9 JSR← 05E8 JSR← 061A JSR← 0684 JSR← 068A JSR← 0698 BVC |
| BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready) | ||
| 95FA | 0698 | BVC tube_send_r2 ; Not ready: keep polling |
| 95FC | 069A | STA tube_data_register_2 ; Write A to Tube R2 data register |
| 95FF | 069D | RTS ; Return to caller |
| ; Send byte to Tube data register R4 | ||
| ; Polls Tube status register 4 until bit 6 is set, | ||
| ; then writes A to the data register. Uses a tight | ||
| ; BIT/BVC polling loop. R4 is the command/control | ||
| ; channel used for address claims (ADRR), data transfer | ||
| ; setup (SENDW), and release commands. Called by 7 | ||
| ; sites, primarily during tube_release_claim and | ||
| ; tube_transfer_setup sequences. | ||
| 9600 | 069E | .tube_send_r4←8← 0018 JSR← 0418 JSR← 041D JSR← 043B JSR← 0443 JSR← 0448 JSR← 0463 JSR← 06A1 BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status (bit 6 = ready) | ||
| 9603 | 06A1 | BVC tube_send_r4 ; Not ready: keep polling |
| 9605 | 06A3 | STA tube_data_register_4 ; Write A to Tube R4 data register |
| 9608 | 06A6 | RTS ; Return to caller |
| 9609 | 06A7 | .tube_escape_check←1← 0403 JMP |
| LDA escape_flag ; Check OS escape flag at &FF | ||
| 960B | 06A9 | SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7 |
| 960C | 06AA | ROR ; ROR: shift escape bit 7 to carry |
| 960D | 06AB | BMI tube_send_r1 ; Escape set: forward to co-processor via R1 |
| 960F | 06AD | .tube_event_handler |
| PHA ; EVNTV: forward event A, Y, X to co-processor | ||
| 9610 | 06AE | LDA #0 ; Send &00 prefix (event notification) |
| 9612 | 06B0 | JSR tube_send_r1 ; Send zero prefix via R1 |
| 9615 | 06B3 | TYA ; Y value for event |
| 9616 | 06B4 | JSR tube_send_r1 ; Send Y via R1 |
| 9619 | 06B7 | TXA ; X value for event |
| 961A | 06B8 | JSR tube_send_r1 ; Send X via R1 |
| 961D | 06BB | PLA ; Restore A (event type) |
| ; Send byte to Tube data register R1 | ||
| ; Polls Tube status register 1 until bit 6 is set, | ||
| ; then writes A to the data register. Uses a tight | ||
| ; BIT/BVC polling loop. R1 is used for asynchronous | ||
| ; event and escape notification to the co-processor. | ||
| ; Called by tube_event_handler to forward event type, | ||
| ; Y, and X parameters, and reached via BMI from | ||
| ; tube_escape_check when the escape flag is set. | ||
| 961E | 06BC | .tube_send_r1←5← 06AB BMI← 06B0 JSR← 06B4 JSR← 06B8 JSR← 06BF BVC |
| BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready) | ||
| 9621 | 06BF | BVC tube_send_r1 ; Not ready: keep polling |
| 9623 | 06C1 | STA tube_data_register_1 ; Write A to Tube R1 data register |
| 9626 | 06C4 | RTS ; Return to caller |
| ; Read a byte from Tube data register R2 | ||
| ; Polls Tube status register 2 until data is available | ||
| ; (bit 7 set), then loads A from Tube data register 2. | ||
| ; Called by all Tube dispatch handlers that receive data | ||
| ; or parameters from the co-processor. | ||
| 9627 | 06C5 | .tube_read_r2←21← 0520 JSR← 0524 JSR← 052D JSR← 0542 JSR← 0552 JSR← 055E JSR← 0564 JSR← 056C JSR← 0586 JSR← 05AB JSR← 05BC JSR← 05D3 JSR← 05DB JSR← 05F2 JSR← 05F6 JSR← 0607 JSR← 060B JSR← 060F JSR← 0627 JSR← 066A JSR← 06C8 BPL |
| BIT tube_status_register_2 ; Poll R2 status (bit 7 = ready) | ||
| 962A | 06C8 | BPL tube_read_r2 ; Not ready: keep polling |
| 962C | 06CA | LDA tube_data_register_2 ; Read data byte from R2 |
| 962F | 06CD | RTS ; Return with byte in A |
| 9630 | 06CE | .trampoline_tx_setup |
| JMP tx_begin ; Trampoline: begin TX operation | ||
| 9633 | 06D1 | .trampoline_adlc_init |
| JMP adlc_init ; Trampoline: full ADLC init | ||
| 9636 | 06D4 | .svc_12_nmi_release |
| JMP wait_idle_and_reset ; Trampoline: wait idle and reset | ||
| 9639 | 06D7 | .svc_11_nmi_claim |
| JMP init_nmi_workspace ; Trampoline: init NMI workspace | ||
| 963C | 06DA | LDA #4 ; A=4: SR interrupt bit mask |
| 963E | 06DC | BIT system_via_ifr ; Test SR flag in VIA IFR |
| 9641 | 06DF | BNE handle_sr_intr ; SR active: handle interrupt |
| 9643 | 06E1 | LDA #5 ; A=5: NMI not for us |
| 9645 | 06E3 | RTS ; Return (NMI not claimed) |
| 9646 | 06E4 | .handle_sr_intr←1← 06DF BNE |
| TXA ; Save X on stack | ||
| 9647 | 06E5 | PHA ; Push X for later restore |
| 9648 | 06E6 | TYA ; Save Y on stack |
| 9649 | 06E7 | PHA ; Push Y for later restore |
| 964A | 06E8 | LDA system_via_acr ; Read VIA auxiliary control reg |
| 964D | 06EB | AND #&e3 ; Mask shift register bits |
| 964F | 06ED | ORA tx_work_51 ; OR in TX shift register mode |
| 9652 | 06F0 | STA system_via_acr ; Write back ACR with SR mode |
| 9655 | 06F3 | LDA system_via_sr ; Read SR to clear shift complete |
| 9658 | 06F6 | LDA #4 ; A=4: SR interrupt bit |
| 965A | 06F8 | STA system_via_ifr ; Clear SR interrupt flag |
| 965D | 06FB | STA system_via_ier ; Disable SR interrupt |
| 9660 | 06FE | EQUB &AC, &57 ; A=5: NMI not for us |
| 9662 | EQUB &0D ; CR terminator for VDU state data | |
| 9663 | CPY #&86 ; Y >= &86: above dispatch range | |
| 9665 | BCS dispatch_svc5 ; Out of range: skip protection | |
| 9667 | LDA prot_status ; Save current JSR protection mask | |
| 966A | STA saved_jsr_mask ; Backup to saved_jsr_mask | |
| 966D | ORA #&1c ; Set protection bits 2-4 | |
| 966F | STA prot_status ; Apply protection during dispatch | |
| 9672 | .dispatch_svc5←1← 9665 BCS | |
| LDA #&9b ; Push return addr high (&9B) | ||
| 9674 | PHA ; High byte on stack for RTS | |
| 9675 | LDA svc5_dispatch_lo,y ; Load dispatch target low byte | |
| 9678 | PHA ; Low byte on stack for RTS | |
| 9679 | .svc_5_unknown_irq | |
| RTS ; RTS = dispatch to PHA'd address | ||
ADLC initialisationReads station ID (INTOFF side effect), performs full ADLC reset, checks for Tube presence (OSBYTE &EA), then falls through to adlc_init_workspace. |
|
| 967A | .adlc_init←1← 06D1 JMP |
| BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs | |
| 967D | JSR adlc_full_reset ; Full ADLC hardware reset |
| 9680 | LDA #osbyte_read_tube_presence ; OSBYTE &EA: check Tube co-processor |
| 9682 | LDX #0 ; X=0 for OSBYTE |
| 9684 | STX econet_init_flag ; Clear Econet init flag before setup |
| 9687 | LDY #&ff ; Y=&FF for OSBYTE |
| 9689 | JSR osbyte ; Read Tube present flag |
| 968C | STX tube_flag ; X=value of Tube present flag |
| 968F | LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request |
| 9691 | LDX #&0c ; X=&0C: NMI claim service |
| 9693 | LDY #&ff ; Y=&FF: pass to adlc_init_workspace |
| fall through ↓ | |
Initialise NMI workspaceIssues OSBYTE &8F with X=&0C (NMI claim service request) before copying the NMI shim. Sub-entry at &9698 skips the service request for quick re-init. Then copies 32 bytes of NMI shim from ROM (&9F7D) to RAM (&0D00), patches the current ROM bank number into the shim's self-modifying code at &0D07, sets TX clear flag and econet_init_flag to &80, reads station ID from &FE18 (INTOFF side effect), stores it in the TX scout buffer, and re-enables NMIs by reading &FE20 (INTON side effect). |
|
| 9695 | .adlc_init_workspace |
| JSR osbyte ; Issue paged ROM service call, Reason X=12 - NMI claim | |
| fall through ↓ | |
Initialise NMI workspace (skip service request)Sub-entry of adlc_init_workspace that skips the OSBYTE &8F service request. Copies 32 bytes of NMI shim from ROM to &0D00, patches the ROM bank number, sets init flags, reads station ID, and re-enables NMIs. |
|
| 9698 | .init_nmi_workspace←1← 06D7 JMP |
| LDY #&20 ; Copy 32 bytes of NMI shim from ROM to &0D00 | |
| 969A | .copy_nmi_shim←1← 96A1 BNE |
| LDA listen_jmp_hi,y ; Read byte from NMI shim ROM source | |
| 969D | STA nmi_code_base,y ; Write to NMI shim RAM at &0D00 |
| 96A0 | DEY ; Next byte (descending) |
| 96A1 | BNE copy_nmi_shim ; Loop until all 32 bytes copied |
| 96A3 | LDA romsel_copy ; Patch current ROM bank into NMI shim |
| 96A5 | STA nmi_shim_07 ; Self-modifying code: ROM bank at &0D07 |
| 96A8 | LDA #&80 ; &80 = Econet initialised |
| 96AA | STA tx_clear_flag ; Mark TX as complete (ready) |
| 96AD | STA econet_init_flag ; Mark Econet as initialised |
| 96B0 | LDA station_id_disable_net_nmis ; Read station ID (&FE18 = INTOFF side effect) |
| 96B3 | STA tx_src_stn ; Store our station ID in TX scout |
| 96B6 | STY tx_src_net ; Y=0 after copy loop: net = local |
| 96B9 | STY need_release_tube ; Clear Tube release flag |
| 96BB | BIT video_ula_control ; INTON: re-enable NMIs (&FE20 read side effect) |
| 96BE | 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). |
|
| 96BF | .nmi_rx_scout←1← 9F88 JMP |
| LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present) | |
| 96C1 | BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set |
| 96C4 | BEQ scout_error ; AP not set, no incoming data -- check for errors |
| 96C6 | LDA econet_data_continue_frame ; Read first RX byte (destination station address) |
| 96C9 | CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs) |
| 96CC | BEQ accept_frame ; Match -- accept frame |
| 96CE | CMP #&ff ; Check for broadcast address (&FF) |
| 96D0 | BNE scout_reject ; Neither our address nor broadcast -- reject frame |
| 96D2 | LDA #&40 ; Flag &40 = broadcast frame |
| 96D4 | STA tx_flags ; Store broadcast flag in TX flags |
| 96D7 | .accept_frame←1← 96CC BEQ |
| LDA #&dc ; Install next NMI handler at &96DC (RX scout net byte) | |
| 96D9 | JMP install_nmi_handler ; 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 &970E. |
|
| 96DC | .nmi_rx_scout_net |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 96DF | BPL scout_error ; No RDA -- check errors |
| 96E1 | LDA econet_data_continue_frame ; Read destination network byte |
| 96E4 | BEQ accept_local_net ; Network = 0 -- local network, accept |
| 96E6 | EOR #&ff ; EOR &FF: test if network = &FF (broadcast) |
| 96E8 | BEQ accept_scout_net ; Broadcast network -- accept |
| 96EA | .scout_reject←1← 96D0 BNE |
| LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE | |
| 96EC | STA econet_control1_or_status1 ; Write CR1 to discontinue RX |
| 96EF | JMP install_rx_scout_handler ; Return to idle scout listening |
| 96F2 | .accept_local_net←1← 96E4 BEQ |
| STA tx_flags ; Network = 0 (local): clear tx_flags | |
| 96F5 | .accept_scout_net←1← 96E8 BEQ |
| STA port_buf_len ; Store Y offset for scout data buffer | |
| 96F7 | LDA #&0e ; Install scout data reading loop at &970E |
| 96F9 | LDY #&97 ; High byte of scout data handler |
| 96FB | JMP set_nmi_vector ; Install scout data loop and RTI |
Scout error/discard handlerReached when the scout data loop sees no RDA (BPL at &9713) 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 &99E8. This path is a common landing for any unexpected ADLC state during scout reception. |
|
| 96FE | .scout_error←5← 96C4 BEQ← 96DF BPL← 9713 BPL← 9747 BEQ← 9749 BPL |
| LDA econet_control23_or_status2 ; Read SR2 | |
| 9701 | AND #&81 ; Test AP (b0) | RDA (b7) |
| 9703 | BEQ scout_discard ; Neither set -- clean end, discard via &970B |
| 9705 | JSR adlc_full_reset ; Unexpected data/status: full ADLC reset |
| 9708 | JMP install_rx_scout_handler ; Discard and return to idle |
| 970B | .scout_discard←1← 9703 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 (&9710): SR2 read, BPL tests RDA (bit7) - No RDA (BPL) -> error (&96FE) - RDA set (BMI) -> read byte - After first byte (&971C): full SR2 tested - SR2 non-zero (BNE) -> scout completion (&9738) 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 -> read second byte and loop - After second byte (&9730): re-test full SR2 - SR2 non-zero (BNE) -> loop back to &9713 - SR2 = 0 -> RTI, wait for next NMI The loop ends at Y=&0C (12 bytes max in scout buffer). |
|
| 970E | .scout_data_loop |
| LDY port_buf_len ; Y = buffer offset | |
| 9710 | LDA econet_control23_or_status2 ; Read SR2 |
| 9713 | .scout_loop_rda←1← 9733 BNE |
| BPL scout_error ; No RDA -- error handler &96FE | |
| 9715 | LDA econet_data_continue_frame ; Read data byte from RX FIFO |
| 9718 | STA rx_src_stn,y ; Store at &0D3D+Y (scout buffer) |
| 971B | INY ; Advance buffer index |
| 971C | LDA econet_control23_or_status2 ; Read SR2 again (FV detection point) |
| 971F | BMI scout_loop_second ; RDA set -- more data, read second byte |
| 9721 | BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion |
| 9723 | .scout_loop_second←1← 971F BMI |
| LDA econet_data_continue_frame ; Read second byte of pair | |
| 9726 | STA rx_src_stn,y ; Store at &0D3D+Y |
| 9729 | INY ; Advance and check buffer limit |
| 972A | CPY #&0c ; Copied all 12 scout bytes? |
| 972C | BEQ scout_complete ; Buffer full (Y=12) -- force completion |
| 972E | STY port_buf_len ; Save final buffer offset |
| 9730 | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 9733 | BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes |
| 9735 | 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 &96FE (not a valid frame end) - FV set, no RDA (BPL) -> error &96FE (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 (&9A46) - Port non-zero -> check if it matches an open receive block |
|
| 9738 | .scout_complete←2← 9721 BNE← 972C BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 973A | STA econet_control1_or_status1 ; Write CR1 |
| 973D | LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV |
| 973F | STA econet_control23_or_status2 ; Write CR2 |
| 9742 | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 9744 | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 9747 | BEQ scout_error ; No FV -- not a valid frame end, error |
| 9749 | BPL scout_error ; FV set but no RDA -- missing last byte, error |
| 974B | LDA econet_data_continue_frame ; Read last byte from RX FIFO |
| 974E | STA rx_src_stn,y ; Store last byte at &0D3D+Y |
| 9751 | LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK) |
| 9753 | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 9756 | SEC ; Set bit7 of need_release_tube flag |
| 9757 | ROR need_release_tube ; Rotate C=1 into bit7: mark Tube release needed |
| 9759 | LDA rx_port ; Check port byte: 0 = immediate op, non-zero = data transfer |
| 975C | BNE scout_match_port ; Port non-zero -- look for matching receive block |
| 975E | .scout_no_match |
| JMP immediate_op ; Port = 0 -- immediate operation handler | |
| 9761 | .scout_match_port←1← 975C BNE |
| BIT tx_flags ; Check if broadcast (bit6 of tx_flags) | |
| 9764 | BVC scan_port_list ; Not broadcast -- skip CR2 setup |
| 9766 | LDA #7 ; CR2=&07: broadcast prep |
| 9768 | STA econet_control23_or_status2 ; Write CR2: broadcast frame prep |
| 976B | .scan_port_list←1← 9764 BVC |
| BIT rx_flags ; Check if RX port list active (bit7) | |
| 976E | BPL try_nfs_port_list ; No active ports -- try NFS workspace |
| 9770 | LDA #&c0 ; Start scanning port list at page &C0 |
| 9772 | LDY #0 ; Y=0: start offset within each port slot |
| 9774 | .scan_nfs_port_list←1← 97B7 BNE |
| STA port_ws_offset ; Store page to workspace pointer low | |
| 9776 | STY rx_buf_offset ; Store page high byte for slot scanning |
| 9778 | .check_port_slot←1← 97A9 BCC |
| LDY #0 ; Y=0: read control byte from start of slot | |
| 977A | .scout_ctrl_check |
| LDA (port_ws_offset),y ; Read port control byte from slot | |
| 977C | BEQ discard_no_match ; Zero = end of port list, no match |
| 977E | CMP #&7f ; &7F = any-port wildcard |
| 9780 | BNE next_port_slot ; Not wildcard -- check specific port match |
| 9782 | INY ; Y=1: advance to port byte in slot |
| 9783 | LDA (port_ws_offset),y ; Read port number from slot (offset 1) |
| 9785 | BEQ check_station_filter ; Zero port in slot = match any port |
| 9787 | CMP rx_port ; Check if port matches this slot |
| 978A | BNE next_port_slot ; Port mismatch -- try next slot |
| 978C | .check_station_filter←1← 9785 BEQ |
| INY ; Y=2: advance to station byte | |
| 978D | LDA (port_ws_offset),y ; Read station filter from slot (offset 2) |
| 978F | BEQ port_match_found ; Zero station = match any station, accept |
| 9791 | CMP rx_src_stn ; Check if source station matches |
| 9794 | BNE next_port_slot ; Station mismatch -- try next slot |
| 9796 | .scout_port_match |
| INY ; Y=3: advance to network byte | |
| 9797 | LDA (port_ws_offset),y ; Read network filter from slot (offset 3) |
| 9799 | CMP rx_src_net ; Check if source network matches |
| 979C | BEQ port_match_found ; Network matches or zero = accept |
| 979E | .next_port_slot←3← 9780 BNE← 978A BNE← 9794 BNE |
| LDA rx_buf_offset ; Check if NFS workspace search pending | |
| 97A0 | BEQ try_nfs_port_list ; No NFS workspace -- try fallback path |
| 97A2 | LDA port_ws_offset ; Load current slot base address |
| 97A4 | CLC ; CLC for 12-byte slot advance |
| 97A5 | ADC #&0c ; Advance to next 12-byte port slot |
| 97A7 | STA port_ws_offset ; Update workspace pointer to next slot |
| 97A9 | BCC check_port_slot ; Always branches (page &C0 won't overflow) |
| 97AB | .discard_no_match←2← 977C BEQ← 97B1 BVC |
| JMP nmi_error_dispatch ; No match found -- discard frame | |
| 97AE | .try_nfs_port_list←2← 976E BPL← 97A0 BEQ |
| BIT rx_flags ; Try NFS workspace if paged list exhausted | |
| 97B1 | BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard |
| 97B3 | LDA #0 ; NFS workspace starts at offset 0 in page |
| 97B5 | LDY nfs_workspace_hi ; NFS workspace high byte for port list |
| 97B7 | BNE scan_nfs_port_list ; Scan NFS workspace port list |
| 97B9 | .port_match_found←3← 978F BEQ← 979C BEQ← 9AA7 JMP |
| LDA #3 ; Match found: set scout_status = 3 | |
| 97BB | STA scout_status ; Record match for completion handler |
| 97BE | JSR tx_calc_transfer ; Calculate transfer parameters |
| 97C1 | BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard |
| 97C3 | BIT tx_flags ; Check broadcast flag for ACK path |
| 97C6 | BVC send_data_rx_ack ; Not broadcast -- normal ACK path |
| 97C8 | JMP copy_scout_to_buffer ; Broadcast: different completion path |
| 97CB | .send_data_rx_ack←2← 97C6 BVC← 9A9C JMP |
| LDA #&44 ; CR1=&44: RX_RESET | TIE | |
| 97CD | STA econet_control1_or_status1 ; Write CR1: TX mode for ACK |
| 97D0 | LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE |
| 97D2 | STA econet_control23_or_status2 ; Write CR2: enable TX with PSE |
| 97D5 | LDA #&dc ; Install data_rx_setup at &97DC |
| 97D7 | LDY #&97 ; High byte of data_rx_setup handler |
| 97D9 | JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI |
| 97DC | .data_rx_setup |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame) | |
| 97DE | STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame |
| 97E1 | LDA #&e6 ; Install nmi_data_rx at &97E6 |
| 97E3 | JMP install_nmi_handler ; 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: &97E6 (AP+addr check) -> &97FA (net=0 check) -> &9810 (skip ctrl+port) -> &9843 (bulk data read) -> &9877 (completion) |
|
| 97E6 | .nmi_data_rx |
| LDA #1 ; A=&01: mask for AP (Address Present) | |
| 97E8 | BIT econet_control23_or_status2 ; BIT SR2: test AP bit |
| 97EB | BEQ nmi_error_dispatch ; No AP: wrong frame or error |
| 97ED | LDA econet_data_continue_frame ; Read first byte (dest station) |
| 97F0 | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF) |
| 97F3 | BNE nmi_error_dispatch ; Not for us: error path |
| 97F5 | LDA #&fa ; Install net check handler at &97FA |
| 97F7 | JMP install_nmi_handler ; Set NMI vector via RAM shim |
| 97FA | .nmi_data_rx_net |
| BIT econet_control23_or_status2 ; Validate source network = 0 | |
| 97FD | BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error |
| 97FF | LDA econet_data_continue_frame ; Read dest network byte |
| 9802 | BNE nmi_error_dispatch ; Network != 0: wrong network -- error |
| 9804 | LDA #&10 ; Install skip handler at &9810 |
| 9806 | LDY #&98 ; High byte of &9810 handler |
| 9808 | BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting |
| 980B | BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI |
| 980D | JMP set_nmi_vector ; Install handler and return via RTI |
| 9810 | .nmi_data_rx_skip←1← 980B BMI |
| BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout) | |
| 9813 | BPL nmi_error_dispatch ; SR2 bit7 clear: error |
| 9815 | LDA econet_data_continue_frame ; Discard control byte |
| 9818 | LDA econet_data_continue_frame ; Discard port byte |
| fall through ↓ | |
| 981B | .install_data_rx_handler←1← 9E9E JMP |
| LDA #2 ; A=2: Tube transfer flag mask | |
| 981D | BIT tx_flags ; Check if Tube transfer active |
| 9820 | BNE install_tube_rx ; Tube active: use Tube RX path |
| 9822 | LDA #&43 ; Install bulk read at &9843 |
| 9824 | LDY #&98 ; High byte of &9843 handler |
| 9826 | BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting? |
| 9829 | BMI nmi_data_rx_bulk ; Yes: enter bulk read directly |
| 982B | JMP set_nmi_vector ; No: install handler and RTI |
| 982E | .install_tube_rx←1← 9820 BNE |
| LDA #&a0 ; Tube: install Tube RX at &98A0 | |
| 9830 | LDY #&98 ; High byte of &98A0 handler |
| 9832 | 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). |
|
| 9835 | .nmi_error_dispatch←12← 97AB JMP← 97C1 BCC← 97EB BEQ← 97F3 BNE← 97FD BPL← 9802 BNE← 9813 BPL← 9856 BEQ← 9888 BEQ← 988E BEQ← 994B JMP← 9A76 JMP |
| LDA tx_flags ; Check tx_flags for error path | |
| 9838 | BPL rx_error ; Bit7 clear: RX error path |
| 983A | JMP tx_result_fail ; Bit7 set: TX result = not listening |
| 983D | .rx_error←1← 9838 BPL |
| .rx_error_reset←1← 9838 BPL | |
| JSR adlc_full_reset ; Full ADLC reset on RX error | |
| 9840 | 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 &9877. SR2 = 0 -> RTI, wait for next NMI to continue. |
|
| 9843 | .nmi_data_rx_bulk←1← 9829 BMI |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 9845 | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 9848 | .data_rx_loop←1← 9872 BNE |
| BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV) | |
| 984A | LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO |
| 984D | STA (open_port_buf),y ; Store byte to buffer |
| 984F | INY ; Advance buffer offset |
| 9850 | BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing |
| 9852 | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 9854 | DEC port_buf_len_hi ; Decrement remaining page count |
| 9856 | BEQ nmi_error_dispatch ; No pages left: handle as complete |
| 9858 | .read_sr2_between_pairs←1← 9850 BNE |
| LDA econet_control23_or_status2 ; Read SR2 between byte pairs | |
| 985B | BMI read_second_rx_byte ; SR2 bit7 set: more data available |
| 985D | BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done |
| 985F | .read_second_rx_byte←1← 985B BMI |
| LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO | |
| 9862 | STA (open_port_buf),y ; Store byte to buffer |
| 9864 | INY ; Advance buffer offset |
| 9865 | STY port_buf_len ; Save updated buffer position |
| 9867 | BNE check_sr2_loop_again ; Y != 0: no page boundary crossing |
| 9869 | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 986B | DEC port_buf_len_hi ; Decrement remaining page count |
| 986D | BEQ data_rx_complete ; No pages left: frame complete |
| 986F | .check_sr2_loop_again←1← 9867 BNE |
| LDA econet_control23_or_status2 ; Read SR2 for next iteration | |
| 9872 | BNE data_rx_loop ; SR2 non-zero: more data, loop back |
| 9874 | JMP nmi_rti ; SR2=0: no more data yet, wait for NMI |
Data frame completion |
|
| 9877 | .data_rx_complete←3← 9848 BPL← 985D BNE← 986D BEQ |
| LDA #&84 ; CR2=&84: disable PSE for bit testing | |
| 9879 | STA econet_control23_or_status2 ; Write CR2 |
| 987C | LDA #0 ; CR1=&00: disable all interrupts |
| 987E | STA econet_control1_or_status1 ; Write CR1 |
| 9881 | STY port_buf_len ; Save Y (byte count from data RX loop) |
| 9883 | LDA #2 ; A=&02: FV mask |
| 9885 | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 9888 | BEQ nmi_error_dispatch ; No FV -- error |
| 988A | BPL send_ack ; FV set, no RDA -- proceed to ACK |
| 988C | LDA port_buf_len_hi ; Check if buffer space remains |
| 988E | .read_last_rx_byte←3← 98AB BEQ← 98D2 BEQ← 98DE BEQ |
| BEQ nmi_error_dispatch ; No buffer space: error/discard frame | |
| 9890 | LDA econet_data_continue_frame ; FV+RDA: read and store last data byte |
| 9893 | LDY port_buf_len ; Y = current buffer write offset |
| 9895 | STA (open_port_buf),y ; Store last byte in port receive buffer |
| 9897 | INC port_buf_len ; Advance buffer write offset |
| 9899 | BNE send_ack ; No page wrap: proceed to send ACK |
| 989B | INC open_port_buf_hi ; Page boundary: advance buffer page |
| 989D | .send_ack←2← 988A BPL← 9899 BNE |
| JMP ack_tx ; Send ACK frame to complete handshake | |
| 98A0 | .nmi_data_rx_tube |
| LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path | |
| 98A3 | .rx_tube_data←1← 98BE BNE |
| BPL data_rx_tube_complete ; RDA clear: no more data, frame complete | |
| 98A5 | LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO |
| 98A8 | JSR inc_buf_counter_32 ; Check buffer limits and transfer size |
| 98AB | BEQ read_last_rx_byte ; Zero: buffer full, handle as error |
| 98AD | STA tube_data_register_3 ; Send byte to Tube data register 3 |
| 98B0 | LDA econet_data_continue_frame ; Read second data byte (paired transfer) |
| 98B3 | STA tube_data_register_3 ; Send second byte to Tube |
| 98B6 | JSR inc_buf_counter_32 ; Check limits after byte pair |
| 98B9 | BEQ data_rx_tube_complete ; Zero: Tube transfer complete |
| 98BB | LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair |
| 98BE | BNE rx_tube_data ; More data available: continue loop |
| 98C0 | .data_rx_tube_error |
| JMP nmi_rti ; Unexpected end: return from NMI | |
| 98C3 | .data_rx_tube_complete←2← 98A3 BPL← 98B9 BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 98C5 | STA econet_control1_or_status1 ; Write CR1 for individual bit testing |
| 98C8 | LDA #&84 ; CR2=&84: disable PSE |
| 98CA | STA econet_control23_or_status2 ; Write CR2: same pattern as main path |
| 98CD | LDA #2 ; A=&02: FV mask for Tube completion |
| 98CF | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 98D2 | BEQ read_last_rx_byte ; No FV: incomplete frame, error |
| 98D4 | BPL ack_tx ; FV set, no RDA: proceed to ACK |
| 98D6 | LDA port_buf_len ; Check if any buffer was allocated |
| 98D8 | ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together |
| 98DA | ORA open_port_buf ; Check buffer low byte |
| 98DC | ORA open_port_buf_hi ; Check buffer high byte |
| 98DE | BEQ read_last_rx_byte ; All zero (null buffer): error |
| 98E0 | LDA econet_data_continue_frame ; Read extra trailing byte from FIFO |
| 98E3 | STA rx_extra_byte ; Save extra byte at &0D5D for later use |
| 98E6 | LDA #&20 ; Bit5 = extra data byte available flag |
| 98E8 | ORA tx_flags ; Set extra byte flag in tx_flags |
| 98EB | 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 (&9EA8). 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. |
|
| 98EE | .ack_tx←2← 989D JMP← 98D4 BPL |
| LDA tx_flags ; Load TX flags to check ACK type | |
| 98F1 | BPL ack_tx_configure ; Bit7 clear: normal scout ACK |
| 98F3 | JSR advance_rx_buffer_ptr ; Final ACK: call completion handler |
| 98F6 | JMP tx_result_ok ; Jump to TX success result |
| 98F9 | .ack_tx_configure←1← 98F1 BPL |
| LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode) | |
| 98FB | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 98FE | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE |
| 9900 | STA econet_control23_or_status2 ; Write CR2: enable TX with status clear |
| 9903 | LDA #&95 ; Install saved next handler (&99BB for scout ACK) |
| 9905 | LDY #&99 ; High byte of post-ACK handler |
| 9907 | .ack_tx_write_dest←2← 97D9 JMP← 9AE4 JMP |
| STA nmi_next_lo ; Store next handler low byte | |
| 990A | STY nmi_next_hi ; Store next handler high byte |
| 990D | LDA rx_src_stn ; Load dest station from RX scout buffer |
| 9910 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9913 | BVC dispatch_nmi_error ; TDRA not ready -- error |
| 9915 | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 9918 | LDA rx_src_net ; Write dest network to TX FIFO |
| 991B | STA econet_data_continue_frame ; Write dest net byte to FIFO |
| 991E | LDA #&25 ; Install nmi_ack_tx_src at &9925 |
| 9920 | LDY #&99 ; High byte of nmi_ack_tx_src |
| 9922 | 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. |
|
| 9925 | .nmi_ack_tx_src |
| LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF) | |
| 9928 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA |
| 992B | BVC dispatch_nmi_error ; TDRA not ready -- error |
| 992D | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 9930 | LDA #0 ; Write network=0 to TX FIFO |
| 9932 | STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO |
| 9935 | LDA tx_flags ; Check tx_flags for data phase |
| 9938 | BMI start_data_tx ; bit7 set: start data TX phase |
| 993A | LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE |
| fall through ↓ | |
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. |
|
| 993C | .post_ack_scout |
| STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX | |
| 993F | LDA nmi_next_lo ; Install saved handler from &0D4B/&0D4C |
| 9942 | LDY nmi_next_hi ; Load saved next handler high byte |
| 9945 | JMP set_nmi_vector ; Install next NMI handler |
| 9948 | .start_data_tx←1← 9938 BMI |
| JMP data_tx_begin ; Jump to start data TX phase | |
| 994B | .dispatch_nmi_error←2← 9913 BVC← 992B BVC |
| JMP nmi_error_dispatch ; 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. |
|
| 994E | .advance_rx_buffer_ptr←2← 98F3 JSR← 99A4 JSR |
| LDA #2 ; A=2: test bit1 of tx_flags | |
| 9950 | BIT tx_flags ; BIT tx_flags: check data transfer bit |
| 9953 | BEQ return_10 ; Bit1 clear: no transfer -- return |
| 9955 | CLC ; CLC: init carry for 4-byte add |
| 9956 | PHP ; Save carry on stack for loop |
| 9957 | LDY #8 ; Y=8: RXCB high pointer offset |
| 9959 | .add_rxcb_ptr←1← 9965 BCC |
| LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte) | |
| 995B | PLP ; Restore carry from stack |
| 995C | ADC net_tx_ptr,y ; Add transfer count byte |
| 995F | STA (port_ws_offset),y ; Store updated pointer back to RXCB |
| 9961 | INY ; Next byte |
| 9962 | PHP ; Save carry for next iteration |
| 9963 | CPY #&0c ; Done 4 bytes? (Y reaches &0C) |
| 9965 | BCC add_rxcb_ptr ; No: continue adding |
| 9967 | PLP ; Discard final carry |
| 9968 | LDA #&20 ; A=&20: test bit5 of tx_flags |
| 996A | BIT tx_flags ; BIT tx_flags: check Tube bit |
| 996D | BEQ skip_tube_update ; No Tube: skip Tube update |
| 996F | TXA ; Save X on stack |
| 9970 | PHA ; Push X |
| 9971 | LDA #8 ; A=8: offset for Tube address |
| 9973 | CLC ; CLC for address calculation |
| 9974 | ADC port_ws_offset ; Add workspace base offset |
| 9976 | TAX ; X = address low for Tube claim |
| 9977 | LDY rx_buf_offset ; Y = address high for Tube claim |
| 9979 | LDA #1 ; A=1: Tube claim type (read) |
| 997B | JSR tube_addr_claim ; Claim Tube address for transfer |
| 997E | LDA rx_extra_byte ; Load extra RX data byte |
| 9981 | STA tube_data_register_3 ; Send to Tube via R3 |
| 9984 | SEC ; SEC: init carry for increment |
| 9985 | LDY #8 ; Y=8: start at high pointer |
| 9987 | .inc_rxcb_ptr←1← 998E BCS |
| LDA #0 ; A=0: add carry only (increment) | |
| 9989 | ADC (port_ws_offset),y ; Add carry to pointer byte |
| 998B | STA (port_ws_offset),y ; Store back to RXCB |
| 998D | INY ; Next byte |
| 998E | BCS inc_rxcb_ptr ; Keep going while carry propagates |
| 9990 | PLA ; Restore X from stack |
| 9991 | TAX ; Transfer to X register |
| 9992 | .skip_tube_update←1← 996D BEQ |
| LDA #&ff ; A=&FF: return value (transfer done) | |
| 9994 | .return_10←1← 9953 BEQ |
| RTS ; Return | |
| 9995 | LDA rx_port ; Load received port byte |
| 9998 | BNE rx_complete_update_rxcb ; Port != 0: data transfer frame |
| 999A | LDY rx_ctrl ; Port=0: load control byte |
| 999D | CPY #&82 ; Ctrl = &82 (POKE)? |
| 999F | BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer |
| 99A1 | JMP imm_op_build_reply ; Other port-0 ops: immediate dispatch |
Complete RX and update RXCBPost-scout completion for data transfer frames (port != 0) and POKE (ctrl=&82). Calls advance_rx_buffer_ptr, updates the open port buffer address, then writes source station/ network, port, and control byte into the RXCB. |
|
| 99A4 | .rx_complete_update_rxcb←3← 9998 BNE← 999F BEQ← 9A16 JMP |
| JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube | |
| 99A7 | BNE skip_buf_ptr_update ; Transfer not done: skip buffer update |
| 99A9 | .add_buf_to_base |
| LDA port_buf_len ; Load buffer bytes remaining | |
| 99AB | CLC ; CLC for address add |
| 99AC | ADC open_port_buf ; Add to buffer base address |
| 99AE | BCC store_buf_ptr_lo ; No carry: skip high byte increment |
| 99B0 | .inc_rxcb_buf_hi |
| INC open_port_buf_hi ; Carry: increment buffer high byte | |
| 99B2 | .store_buf_ptr_lo←1← 99AE BCC |
| LDY #8 ; Y=8: store updated buffer position | |
| 99B4 | .store_rxcb_buf_ptr |
| STA (port_ws_offset),y ; Store updated low byte to RXCB | |
| 99B6 | INY ; Y=9: buffer high byte offset |
| 99B7 | LDA open_port_buf_hi ; Load updated buffer high byte |
| 99B9 | .store_rxcb_buf_hi |
| STA (port_ws_offset),y ; Store high byte to RXCB | |
| 99BB | .skip_buf_ptr_update←1← 99A7 BNE |
| LDA rx_port ; Check port byte again | |
| 99BE | BEQ discard_reset_listen ; Port=0: immediate op, discard+listen |
| 99C0 | LDA rx_src_net ; Load source network from scout buffer |
| 99C3 | LDY #3 ; Y=3: RXCB source network offset |
| 99C5 | STA (port_ws_offset),y ; Store source network to RXCB |
| 99C7 | DEY ; Y=2: source station offset Y=&02 |
| 99C8 | LDA rx_src_stn ; Load source station from scout buffer |
| 99CB | STA (port_ws_offset),y ; Store source station to RXCB |
| 99CD | DEY ; Y=1: port byte offset Y=&01 |
| 99CE | LDA rx_port ; Load port byte |
| 99D1 | STA (port_ws_offset),y ; Store port to RXCB |
| 99D3 | DEY ; Y=0: control/flag byte offset Y=&00 |
| 99D4 | LDA rx_ctrl ; Load control byte from scout |
| 99D7 | ORA #&80 ; Set bit7 = reception complete flag |
| 99D9 | STA (port_ws_offset),y ; Store to RXCB (marks CB as complete) |
| fall through ↓ | |
Discard with Tube releaseConditionally releases the Tube co-processor before discarding. If tx_flags bit 1 is set (Tube transfer was active), calls sub_c9a2b to release the Tube claim, then falls through to discard_listen. The main teardown path for RX operations that used the Tube. |
|
| 99DB | .discard_reset_listen←4← 9840 JMP← 99BE BEQ← 9E4D JMP← 9EB7 JMP |
| LDA #2 ; Tube flag bit 1 AND tx_flags bit 1 | |
| 99DD | AND tube_flag ; Check if Tube transfer active |
| 99E0 | BIT tx_flags ; Test tx_flags for Tube transfer |
| 99E3 | BEQ discard_listen ; No Tube transfer active -- skip release |
| 99E5 | JSR release_tube ; Release Tube claim before discarding |
| fall through ↓ | |
Discard frame and return to idle listenCalls adlc_rx_listen to re-enter idle RX mode (CR1=&82, CR2=&67), then installs nmi_rx_scout (&96BF) as the NMI handler via set_nmi_vector. Returns to the caller's NMI context. Used as the common discard tail for both gentle rejection (wrong station/network) and error recovery paths. |
|
| 99E8 | .discard_listen←4← 970B JMP← 99E3 BEQ← 9A61 BCS← 9B1D JMP |
| JSR adlc_rx_listen ; Re-enter idle RX listen mode | |
| fall through ↓ | |
Install RX scout NMI handlerInstalls nmi_rx_scout (&96BF) as the NMI handler via set_nmi_vector, without first calling adlc_rx_listen. Used when the ADLC is already in the correct RX mode. |
|
| 99EB | .install_rx_scout_handler←2← 96EF JMP← 9708 JMP |
| LDA #&bf ; Install nmi_rx_scout (&96BF) as NMI handler | |
| 99ED | LDY #&96 ; High byte of nmi_rx_scout |
| 99EF | JMP set_nmi_vector ; Set NMI vector and return |
Copy scout data to port bufferCopies scout data bytes (offsets 4-11) from the RX scout buffer into the open port buffer, handling both direct memory and Tube R3 write paths. |
|
| 99F2 | .copy_scout_to_buffer←1← 97C8 JMP |
| TXA ; Save X on stack | |
| 99F3 | PHA ; Push X |
| 99F4 | LDX #4 ; X=4: start at scout byte offset 4 |
| 99F6 | LDA #2 ; A=2: Tube transfer check mask |
| 99F8 | BIT tx_flags ; BIT tx_flags: check Tube bit |
| 99FB | BNE copy_scout_via_tube ; Tube active: use R3 write path |
| 99FD | LDY port_buf_len ; Y = current buffer position |
| 99FF | .copy_scout_bytes←1← 9A12 BNE |
| LDA rx_src_stn,x ; Load scout data byte | |
| 9A02 | STA (open_port_buf),y ; Store to port buffer |
| 9A04 | INY ; Advance buffer pointer |
| 9A05 | BNE next_scout_byte ; No page crossing |
| 9A07 | INC open_port_buf_hi ; Page crossing: inc buffer high byte |
| 9A09 | DEC port_buf_len_hi ; Decrement remaining page count |
| 9A0B | BEQ scout_page_overflow ; No pages left: overflow |
| 9A0D | .next_scout_byte←1← 9A05 BNE |
| INX ; Next scout data byte | |
| 9A0E | STY port_buf_len ; Save updated buffer position |
| 9A10 | CPX #&0c ; Done all scout data? (X reaches &0C) |
| 9A12 | BNE copy_scout_bytes ; No: continue copying |
| 9A14 | .scout_copy_done←2← 9A29 BEQ← 9A72 BEQ |
| PLA ; Restore X from stack | |
| 9A15 | TAX ; Transfer to X register |
| 9A16 | JMP rx_complete_update_rxcb ; Jump to completion handler |
| 9A19 | .copy_scout_via_tube←2← 99FB BNE← 9A27 BNE |
| LDA rx_src_stn,x ; Tube path: load scout data byte | |
| 9A1C | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 9A1F | JSR inc_buf_counter_32 ; Increment buffer position counters |
| 9A22 | BEQ check_scout_done ; Counter overflow: handle end of buffer |
| 9A24 | INX ; Next scout data byte |
| 9A25 | CPX #&0c ; Done all scout data? |
| 9A27 | BNE copy_scout_via_tube ; No: continue Tube writes |
| 9A29 | BEQ scout_copy_done ; ALWAYS branch |
Release Tube co-processor claimIf need_release_tube bit 7 is clear (Tube is claimed), calls tube_addr_claim with A=&82 to release it, then clears the release flag via LSR. |
|
| 9A2B | .release_tube←2← 99E5 JSR← 9F12 JSR |
| BIT need_release_tube ; Check if Tube needs releasing | |
| 9A2D | BMI clear_release_flag ; Bit7 set: already released |
| 9A2F | LDA #&82 ; A=&82: Tube release claim type |
| 9A31 | JSR tube_addr_claim ; Release Tube address claim |
| 9A34 | .clear_release_flag←1← 9A2D BMI |
| LSR need_release_tube ; Clear release flag (LSR clears bit7) | |
| 9A36 | RTS ; Return |
Increment 32-bit buffer counterIncrements a 4-byte counter across port_buf_len / port_buf_len_hi / open_port_buf / open_port_buf_hi with carry propagation. Returns Z=1 if the counter wraps to zero. |
|
| 9A37 | .inc_buf_counter_32←3← 98A8 JSR← 98B6 JSR← 9A1F JSR |
| INC port_buf_len ; Increment buffer position (4-byte) | |
| 9A39 | BNE return_inc_port_buf ; Low byte didn't wrap: done |
| 9A3B | INC port_buf_len_hi ; Carry into second byte |
| 9A3D | BNE return_inc_port_buf ; No further carry: done |
| 9A3F | INC open_port_buf ; Carry into third byte |
| 9A41 | BNE return_inc_port_buf ; No further carry: done |
| 9A43 | INC open_port_buf_hi ; Carry into fourth byte |
| 9A45 | .return_inc_port_buf←3← 9A39 BNE← 9A3D BNE← 9A41 BNE |
| RTS ; Return | |
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. |
|
| 9A46 | .immediate_op←1← 975E JMP |
| LDY rx_ctrl ; Control byte &81-&88 range check | |
| 9A49 | CPY #&81 ; Below &81: not an immediate op |
| 9A4B | BCC imm_op_out_of_range ; Out of range low: jump to discard |
| 9A4D | CPY #&89 ; Above &88: not an immediate op |
| 9A4F | BCS imm_op_out_of_range ; Out of range high: jump to discard |
| 9A51 | CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection |
| 9A53 | BCS dispatch_imm_op ; Ctrl >= &87: dispatch without mask check |
| 9A55 | TYA ; Convert ctrl byte to 0-based index for mask |
| 9A56 | SEC ; SEC for subtract |
| 9A57 | SBC #&81 ; A = ctrl - &81 (0-based operation index) |
| 9A59 | TAY ; Y = index for mask rotation count |
| 9A5A | LDA prot_status ; Load protection mask from LSTAT |
| 9A5D | .rotate_prot_mask←1← 9A5F BPL |
| ROR ; Rotate mask right by control byte index | |
| 9A5E | DEY ; Decrement rotation counter |
| 9A5F | BPL rotate_prot_mask ; Loop until bit aligned |
| 9A61 | BCS discard_listen ; Bit set = operation disabled, discard |
| 9A63 | .dispatch_imm_op←1← 9A53 BCS |
| LDY rx_ctrl ; Reload ctrl byte for dispatch table | |
| 9A66 | LDA #&9a ; Hi byte: all handlers are in page &9A |
| 9A68 | PHA ; Push hi byte for PHA/PHA/RTS dispatch |
| 9A69 | LDA imm_op_dispatch_lo-&81,y ; Load handler low byte from jump table |
| 9A6C | PHA ; Push handler low byte |
| 9A6D | RTS ; RTS dispatches to handler |
| 9A6E | .scout_page_overflow←1← 9A0B BEQ |
| INC port_buf_len ; Increment port buffer length | |
| 9A70 | .check_scout_done←1← 9A22 BEQ |
| CPX #&0b ; Check if scout data index reached 11 | |
| 9A72 | BEQ scout_copy_done ; Yes: loop back to continue reading |
| 9A74 | PLA ; Restore A from stack |
| 9A75 | TAX ; Transfer to X |
| 9A76 | .imm_op_out_of_range←2← 9A4B BCC← 9A4F BCS |
| JMP nmi_error_dispatch ; Jump to discard handler | |
| 9A79 | .imm_op_dispatch_lo |
| EQUB <(rx_imm_peek-1) ; Ctrl &81: PEEK | |
| 9A7A | EQUB <(rx_imm_poke-1) ; Ctrl &82: POKE |
| 9A7B | EQUB <(rx_imm_exec-1) ; Ctrl &83: JSR |
| 9A7C | EQUB <(rx_imm_exec-1) ; Ctrl &84: UserProc |
| 9A7D | EQUB <(rx_imm_exec-1) ; Ctrl &85: OSProc |
| 9A7E | EQUB <(rx_imm_halt_cont-1) ; Ctrl &86: HALT |
| 9A7F | EQUB <(rx_imm_halt_cont-1) ; Ctrl &87: CONTINUE |
| 9A80 | EQUB <(rx_imm_machine_type-1) ; Ctrl &88: machine type query |
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 at &0D58, then jumps to the common receive path at c9826. Used for operation types &83 (JSR), &84 (UserProc), and &85 (OSProc). |
|
| 9A81 | .rx_imm_exec |
| LDA #0 ; A=0: port buffer lo at page boundary | |
| 9A83 | STA open_port_buf ; Set port buffer lo |
| 9A85 | LDA #&82 ; Buffer length lo = &82 |
| 9A87 | STA port_buf_len ; Set buffer length lo |
| 9A89 | LDA #1 ; Buffer length hi = 1 |
| 9A8B | STA port_buf_len_hi ; Set buffer length hi |
| 9A8D | LDA net_rx_ptr_hi ; Load RX page hi for buffer |
| 9A8F | STA open_port_buf_hi ; Set port buffer hi |
| 9A91 | LDY #3 ; Y=3: copy 4 bytes (3 down to 0) |
| 9A93 | .copy_addr_loop←1← 9A9A BPL |
| LDA rx_remote_addr,y ; Load remote address byte | |
| 9A96 | STA l0d58,y ; Store to exec address workspace |
| 9A99 | DEY ; Next byte (descending) |
| 9A9A | BPL copy_addr_loop ; Loop until all 4 bytes copied |
| 9A9C | .sub_c9a9c |
| JMP send_data_rx_ack ; Enter common data-receive path Immediate op dispatch lo bytes | |
RX immediate: POKE setupSets up workspace offsets for receiving POKE data. port_ws_offset=&3D, rx_buf_offset=&0D, then jumps to the common data-receive path at port_match_found. |
|
| 9A9F | .rx_imm_poke |
| LDA #&3d ; Port workspace offset = &3D | |
| 9AA1 | STA port_ws_offset ; Store workspace offset lo |
| 9AA3 | LDA #&0d ; RX buffer page = &0D |
| 9AA5 | STA rx_buf_offset ; Store workspace offset hi |
| 9AA7 | JMP port_match_found ; Enter POKE data-receive path |
RX immediate: machine type querySets up a response buffer (start &25, page &7F, length #&01FC) for the machine type query, then jumps to the query handler at set_tx_reply_flag. Returns system identification data to the remote station. |
|
| 9AAA | .rx_imm_machine_type |
| LDA #1 ; Buffer length hi = 1 | |
| 9AAC | STA port_buf_len_hi ; Set buffer length hi |
| 9AAE | LDA #&fc ; Buffer length lo = &FC |
| 9AB0 | STA port_buf_len ; Set buffer length lo |
| 9AB2 | LDA #&25 ; Buffer start lo = &25 |
| 9AB4 | STA open_port_buf ; Set port buffer lo |
| 9AB6 | LDA #&7f ; Buffer hi = &7F (below screen) |
| 9AB8 | STA open_port_buf_hi ; Set port buffer hi |
| 9ABA | BNE set_tx_reply_flag ; ALWAYS branch |
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. |
|
| 9ABC | .rx_imm_peek |
| LDA #&3d ; Port workspace offset = &3D | |
| 9ABE | STA port_ws_offset ; Store workspace offset lo |
| 9AC0 | LDA #&0d ; RX buffer page = &0D |
| 9AC2 | STA rx_buf_offset ; Store workspace offset hi |
| 9AC4 | LDA #2 ; Scout status = 2 (PEEK response) |
| 9AC6 | STA scout_status ; Store scout status |
| 9AC9 | JSR tx_calc_transfer ; Calculate transfer size for response |
| 9ACC | BCC imm_op_discard ; C=0: transfer not set up, discard |
| 9ACE | .set_tx_reply_flag←1← 9ABA BNE |
| LDA tx_flags ; Mark TX flags bit 7 (reply pending) | |
| 9AD1 | ORA #&80 ; Set reply pending flag |
| 9AD3 | STA tx_flags ; Store updated TX flags |
| 9AD6 | .rx_imm_halt_cont |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9AD8 | STA econet_control1_or_status1 ; Write CR1: enable TX interrupts |
| 9ADB | .tx_cr2_setup |
| LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE | |
| 9ADD | STA econet_control23_or_status2 ; Write CR2 for TX setup |
| 9AE0 | .tx_nmi_setup |
| LDA #&fd ; NMI handler lo byte (self-modifying) | |
| 9AE2 | LDY #&9a ; Y=&9A: dispatch table page |
| 9AE4 | JMP ack_tx_write_dest ; Acknowledge and write TX dest |
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. |
|
| 9AE7 | .imm_op_build_reply←1← 99A1 JMP |
| LDA port_buf_len ; Get buffer position for reply header | |
| 9AE9 | CLC ; Clear carry for offset addition |
| 9AEA | ADC #&80 ; Data offset = buf_len + &80 (past header) |
| 9AEC | LDY #&7f ; Y=&7F: reply data length slot |
| 9AEE | STA (net_rx_ptr),y ; Store reply data length in RX buffer |
| 9AF0 | LDY #&80 ; Y=&80: source station slot |
| 9AF2 | LDA rx_src_stn ; Load requesting station number |
| 9AF5 | STA (net_rx_ptr),y ; Store source station in reply header |
| 9AF7 | INY ; Y=&81 |
| 9AF8 | LDA rx_src_net ; Load requesting network number |
| 9AFB | STA (net_rx_ptr),y ; Store source network in reply header |
| 9AFD | LDA rx_ctrl ; Load control byte from received frame |
| 9B00 | STA tx_work_57 ; Save ctrl byte for TX response |
| 9B03 | LDA #&84 ; IER bit 2: disable SR interrupt |
| 9B05 | STA system_via_ier ; Write IER to disable SR |
| 9B08 | LDA system_via_acr ; Read ACR for shift register config |
| 9B0B | AND #&1c ; Isolate shift register mode bits (2-4) |
| 9B0D | STA tx_work_51 ; Save original SR mode for later restore |
| 9B10 | LDA system_via_acr ; Reload ACR for modification |
| 9B13 | AND #&e3 ; Clear SR mode bits (keep other bits) |
| 9B15 | ORA #8 ; SR mode 2: shift in under φ2 |
| 9B17 | STA system_via_acr ; Apply new shift register mode |
| 9B1A | BIT system_via_sr ; Read SR to clear pending interrupt |
| 9B1D | .imm_op_discard←1← 9ACC BCC |
| JMP discard_listen ; Return to idle listen mode | |
| 9B20 | EQUS "$-;G^" ; Immediate op port number table |
TX done: remote JSR executionPushes tx_done_exit-1 on the stack (so RTS returns to tx_done_exit), then does JMP (l0d58) to call the remote JSR target routine. When that routine returns via RTS, control resumes at tx_done_exit. |
|
| 9B25 | .tx_done_jsr |
| LDA #&9b ; Hi byte of tx_done_exit-1 | |
| 9B27 | PHA ; Push hi byte on stack |
| 9B28 | LDA #&66 ; Push lo of (tx_done_exit-1) |
| 9B2A | PHA ; Push lo byte on stack |
| 9B2B | JMP (l0d58) ; Call remote JSR; RTS to tx_done_exit |
TX done: UserProc eventGenerates a network event (event 8) via OSEVEN with X=l0d58, A=l0d59 (the remote address). This notifies the user program that a UserProc operation has completed. |
|
| 9B2E | .tx_done_user_proc |
| LDY #event_network_error ; Y=8: network event type | |
| 9B30 | LDX l0d58 ; X = remote address lo |
| 9B33 | LDA l0d59 ; A = remote address hi |
| 9B36 | JSR oseven ; Generate event Y='Network error' |
| 9B39 | JMP tx_done_exit ; Exit TX done handler |
TX done: OSProc callCalls the ROM entry point at &8000 (rom_header) with X=l0d58, Y=l0d59. This invokes an OS-level procedure on behalf of the remote station. |
|
| 9B3C | .tx_done_os_proc |
| LDX l0d58 ; X = remote address lo | |
| 9B3F | LDY l0d59 ; Y = remote address hi |
| 9B42 | JSR rom_header ; Call ROM entry point at &8000 |
| 9B45 | JMP tx_done_exit ; Exit TX done handler |
TX done: HALTSets bit 2 of rx_flags (&0D64), 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. |
|
| 9B48 | .tx_done_halt |
| LDA #4 ; A=&04: bit 2 mask for rx_flags | |
| 9B4A | BIT rx_flags ; Test if already halted |
| 9B4D | BNE tx_done_exit ; Already halted: skip to exit |
| 9B4F | ORA rx_flags ; Set bit 2 in rx_flags |
| 9B52 | STA rx_flags ; Store halt flag |
| 9B55 | LDA #4 ; A=4: re-load halt bit mask |
| 9B57 | CLI ; Enable interrupts during halt wait |
| 9B58 | .halt_spin_loop←1← 9B5B BNE |
| BIT rx_flags ; Test halt flag | |
| 9B5B | BNE halt_spin_loop ; Still halted: keep spinning |
| 9B5D | BEQ tx_done_exit ; ALWAYS branch |
TX done: CONTINUEClears bit 2 of rx_flags (&0D64), releasing any station that is halted and spinning in tx_done_halt. |
|
| 9B5F | .tx_done_continue |
| LDA rx_flags ; Load current RX flags | |
| 9B62 | AND #&fb ; Clear bit 2: release halted station |
| 9B64 | STA rx_flags ; Store updated flags |
| 9B67 | .tx_done_exit←4← 9B39 JMP← 9B45 JMP← 9B4D BNE← 9B5D BEQ |
| PLA ; Restore Y from stack | |
| 9B68 | TAY ; Transfer to Y register |
| 9B69 | PLA ; Restore X from stack |
| 9B6A | TAX ; Transfer to X register |
| 9B6B | LDA #0 ; A=0: success status |
| 9B6D | 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. |
|
| 9B6E | .tx_begin←1← 06CE JMP |
| TXA ; Save X on stack | |
| 9B6F | PHA ; Push X |
| 9B70 | LDY #2 ; Y=2: TXCB offset for dest station |
| 9B72 | LDA (nmi_tx_block),y ; Load dest station from TX control block |
| 9B74 | STA tx_dst_stn ; Store to TX scout buffer |
| 9B77 | INY ; Y=&03 |
| 9B78 | LDA (nmi_tx_block),y ; Load dest network from TX control block |
| 9B7A | STA tx_dst_net ; Store to TX scout buffer |
| 9B7D | LDY #0 ; Y=0: first byte of TX control block |
| 9B7F | LDA (nmi_tx_block),y ; Load control/flag byte |
| 9B81 | BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte |
| 9B83 | JMP tx_active_start ; Bit7 clear: normal data transfer |
| 9B86 | .tx_imm_op_setup←1← 9B81 BMI |
| STA tx_ctrl_byte ; Store control byte to TX scout buffer | |
| 9B89 | TAX ; X = control byte for range checks |
| 9B8A | INY ; Y=1: port byte offset |
| 9B8B | LDA (nmi_tx_block),y ; Load port byte from TX control block |
| 9B8D | STA tx_port ; Store port byte to TX scout buffer |
| 9B90 | BNE tx_line_idle_check ; Port != 0: skip immediate op setup |
| 9B92 | CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc |
| 9B94 | BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check |
| 9B96 | SEC ; SEC: init borrow for 4-byte subtract |
| 9B97 | PHP ; Save carry on stack for loop |
| 9B98 | LDY #8 ; Y=8: high pointer offset in TXCB |
| 9B9A | .calc_peek_poke_size←1← 9BAE BCC |
| LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte) | |
| 9B9C | DEY ; Y -= 4: back to start addr offset |
| 9B9D | DEY ; (continued) |
| 9B9E | DEY ; (continued) |
| 9B9F | DEY ; (continued) |
| 9BA0 | PLP ; Restore borrow from stack |
| 9BA1 | SBC (nmi_tx_block),y ; end - start = transfer size byte |
| 9BA3 | STA tx_data_start,y ; Store result to tx_data_start |
| 9BA6 | INY ; Y += 5: advance to next end byte |
| 9BA7 | INY ; (continued) |
| 9BA8 | INY ; (continued) |
| 9BA9 | INY ; (continued) |
| 9BAA | INY ; (continued) |
| 9BAB | PHP ; Save borrow for next byte |
| 9BAC | CPY #&0c ; Done all 4 bytes? (Y reaches &0C) |
| 9BAE | BCC calc_peek_poke_size ; No: next byte pair |
| 9BB0 | PLP ; Discard final borrow |
| 9BB1 | .tx_ctrl_range_check←1← 9B94 BCS |
| CPX #&81 ; Ctrl < &81: not an immediate op | |
| 9BB3 | BCC tx_active_start ; Below range: normal data transfer |
| 9BB5 | .check_imm_range |
| CPX #&89 ; Ctrl >= &89: out of immediate range | |
| 9BB7 | BCS tx_active_start ; Above range: normal data transfer |
| 9BB9 | LDY #&0c ; Y=&0C: start of extra data in TXCB |
| 9BBB | .copy_imm_params←1← 9BC3 BCC |
| LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB | |
| 9BBD | STA nmi_shim_1a,y ; Copy to NMI shim workspace at &0D1A+Y |
| 9BC0 | INY ; Next byte |
| 9BC1 | CPY #&10 ; Done 4 bytes? (Y reaches &10) |
| 9BC3 | BCC copy_imm_params ; No: continue copying |
| 9BC5 | .tx_line_idle_check←1← 9B90 BNE |
| LDA #&20 ; A=&20: mask for SR2 INACTIVE bit | |
| 9BC7 | BIT econet_control23_or_status2 ; BIT SR2: test if line is idle |
| 9BCA | BNE tx_no_clock_error ; Line not idle: handle as line jammed |
| 9BCC | LDA #&fd ; A=&FD: high byte of timeout counter |
| 9BCE | PHA ; Push timeout high byte to stack |
| 9BCF | LDA #6 ; Scout frame = 6 address+ctrl bytes |
| 9BD1 | STA tx_length ; Store scout frame length |
| 9BD4 | 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 &9BF4-&9BF9 works because CR2=&67 has RTS=0, so cts_input_ is always true, and SR1_CTS reflects presence of clock hardware. |
|
| 9BD6 | .inactive_poll |
| STA tx_index ; Save TX index | |
| 9BD9 | PHA ; Push timeout byte 1 on stack |
| 9BDA | PHA ; Push timeout byte 2 on stack |
| 9BDB | LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE| PSE) |
| 9BDD | .test_inactive_retry←3← 9C03 BNE← 9C08 BNE← 9C0D BNE |
| LDA #4 ; A=&04: INACTIVE mask for SR2 bit2 | |
| 9BDF | PHP ; Save interrupt state |
| 9BE0 | SEI ; Disable interrupts for ADLC access |
| fall through ↓ | |
Disable NMIs and test INACTIVEMid-instruction label within the INACTIVE polling loop. The intoff_operand address (intoff_test_inactive+1) is referenced as a constant for self-modifying code. Disables NMIs twice (belt-and-braces) then tests SR2 for INACTIVE before proceeding with TX. |
|
| 9BE1 | .intoff_test_inactive |
| BIT station_id_disable_net_nmis ; INTOFF -- disable NMIs | |
| 9BE4 | BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces) |
| 9BE7 | .test_line_idle |
| BIT econet_control23_or_status2 ; BIT SR2: Z = &04 AND SR2 -- tests INACTIVE | |
| 9BEA | BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop |
| 9BEC | LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt) |
| 9BEF | LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE |
| 9BF1 | STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX |
| 9BF4 | LDA #&10 ; A=&10: CTS mask for SR1 bit4 |
| 9BF6 | BIT econet_control1_or_status1 ; BIT SR1: tests CTS present |
| 9BF9 | BNE tx_prepare ; CTS set -- clock hardware detected, start TX |
| 9BFB | .inactive_retry←1← 9BEA BEQ |
| BIT video_ula_control ; INTON -- re-enable NMIs (&FE20 read) | |
| 9BFE | PLP ; Restore interrupt state |
| 9BFF | TSX ; 3-byte timeout counter on stack |
| 9C00 | INC error_text,x ; Increment timeout counter byte 1 |
| 9C03 | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C05 | INC stk_timeout_mid,x ; Increment timeout counter byte 2 |
| 9C08 | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C0A | INC stk_frame_3,x ; Increment timeout counter byte 3 |
| 9C0D | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C0F | BEQ tx_line_jammed ; ALWAYS branch |
| ; TX_ACTIVE branch (A=&44 = CR1 value for TX active) | |
| 9C11 | .tx_active_start←3← 9B83 JMP← 9BB3 BCC← 9BB7 BCS |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9C13 | 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. |
|
| 9C15 | .tx_line_jammed←1← 9C0F BEQ |
| LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX) | |
| 9C17 | STA econet_control23_or_status2 ; Write CR2 to abort TX |
| 9C1A | PLA ; Clean 3 bytes of timeout loop state |
| 9C1B | PLA ; Pop saved register |
| 9C1C | PLA ; Pop saved register |
| 9C1D | LDA #&40 ; Error &40 = 'Line Jammed' |
| 9C1F | BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch |
| 9C21 | .tx_no_clock_error←1← 9BCA BNE |
| LDA #&43 ; Error &43 = 'No Clock' | |
| 9C23 | .store_tx_error←2← 9C13 BNE← 9C1F BNE |
| LDY #0 ; Offset 0 = error byte in TX control block | |
| 9C25 | STA (nmi_tx_block),y ; Store error code in TX CB byte 0 |
| 9C27 | LDA #&80 ; &80 = TX complete flag |
| 9C29 | STA tx_clear_flag ; Signal TX operation complete |
| 9C2C | PLA ; Restore X saved by caller |
| 9C2D | TAX ; Move to X register |
| 9C2E | RTS ; Return to TX caller |
TX preparationConfigures ADLC for transmission: asserts RTS via CR2, enables TIE via CR1, installs NMI TX handler at &9CCC (nmi_tx_data), and re-enables NMIs. For port-0 (immediate) operations, dispatches via a lookup table indexed by control byte to set tx_flags, tx_length, and a per-operation handler. For port non-zero, branches to c9c8e for standard data transfer setup. |
|
| 9C2F | .tx_prepare←1← 9BF9 BNE |
| STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE) | |
| 9C32 | LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled) |
| 9C34 | STX econet_control1_or_status1 ; Write to ADLC CR1 |
| 9C37 | LDX #&cc ; Install NMI handler at &9CCC (nmi_tx_data) |
| 9C39 | LDY #&9c ; High byte of NMI handler address |
| 9C3B | STX nmi_jmp_lo ; Write NMI vector low byte directly |
| 9C3E | STY nmi_jmp_hi ; Write NMI vector high byte directly |
| 9C41 | SEC ; Set need_release_tube flag (SEC/ROR = bit7) |
| 9C42 | ROR need_release_tube ; Rotate carry into bit 7 of flag |
| 9C44 | BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read) |
| 9C47 | LDA tx_port ; Load destination port number |
| 9C4A | BNE setup_data_xfer ; Port != 0: standard data transfer |
| 9C4C | LDY tx_ctrl_byte ; Port 0: load control byte for table lookup |
| 9C4F | LDA tube_tx_sr1_operand,y ; Look up tx_flags from table |
| 9C52 | STA tx_flags ; Store operation flags |
| 9C55 | LDA tube_tx_inc_operand,y ; Look up tx_length from table |
| 9C58 | STA tx_length ; Store expected transfer length |
| 9C5B | LDA #&9c ; Push high byte of return address (&9C) |
| 9C5D | PHA ; Push high byte for PHA/PHA/RTS dispatch |
| 9C5E | LDA intoff_operand,y ; Look up handler address low from table |
| 9C61 | PHA ; Push low byte for PHA/PHA/RTS dispatch |
| 9C62 | RTS ; RTS dispatches to control-byte handler |
| 9C63 | EQUB &6E, &72, &B4, &B4, &B4, ; TX timeout/retry parameter &C4, &C4, &6A ; table |
| 9C6B | .imm_op_status3 |
| EQUB &A9, &03, &D0, &48 ; A=3: scout_status for PEEK | |
| 9C6F | .tx_ctrl_peek |
| LDA #3 ; A=3: scout_status for PEEK op | |
| 9C71 | 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. |
|
| 9C73 | .tx_ctrl_poke |
| LDA #2 ; Scout status = 2 (POKE transfer) | |
| 9C75 | .store_status_add4←1← 9C71 BNE |
| STA scout_status ; Store scout status | |
| 9C78 | CLC ; Clear carry for 4-byte addition |
| 9C79 | PHP ; Save carry on stack |
| 9C7A | LDY #&0c ; Y=&0C: start at offset 12 |
| 9C7C | .add_bytes_loop←1← 9C89 BCC |
| LDA l0d1e,y ; Load workspace address byte | |
| 9C7F | PLP ; Restore carry from previous byte |
| 9C80 | ADC (nmi_tx_block),y ; Add TXCB address byte |
| 9C82 | STA l0d1e,y ; Store updated address byte |
| 9C85 | INY ; Next byte |
| 9C86 | 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. |
|
| 9C87 | .tx_ctrl_proc |
| CPY #&10 ; Compare Y with 16-byte boundary | |
| 9C89 | BCC add_bytes_loop ; Below boundary: continue addition |
| 9C8B | PLP ; Restore processor flags |
| 9C8C | BNE skip_buf_setup ; Skip buffer setup if transfer size is zero |
| 9C8E | .setup_data_xfer←1← 9C4A BNE |
| LDA tx_dst_stn ; Load dest station for broadcast check | |
| 9C91 | AND tx_dst_net ; AND with dest network |
| 9C94 | CMP #&ff ; Both &FF = broadcast address? |
| 9C96 | BNE setup_unicast_xfer ; Not broadcast: unicast path |
| 9C98 | LDA #&0e ; Broadcast scout: 14 bytes total |
| 9C9A | STA tx_length ; Store broadcast scout length |
| 9C9D | LDA #&40 ; A=&40: broadcast flag |
| 9C9F | STA tx_flags ; Set broadcast flag in tx_flags |
| 9CA2 | LDY #4 ; Y=4: start of address data in TXCB |
| 9CA4 | .copy_bcast_addr←1← 9CAC BCC |
| LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer | |
| 9CA6 | STA tx_src_stn,y ; Store to TX source/data area |
| 9CA9 | INY ; Next byte |
| 9CAA | CPY #&0c ; Done 8 bytes? (Y reaches &0C) |
| 9CAC | BCC copy_bcast_addr ; No: continue copying |
| 9CAE | BCS tx_ctrl_exit ; ALWAYS branch |
| 9CB0 | .setup_unicast_xfer←1← 9C96 BNE |
| LDA #0 ; A=0: clear flags for unicast | |
| 9CB2 | STA tx_flags ; Clear tx_flags |
| 9CB5 | .proc_op_status2 |
| LDA #2 ; scout_status=2: data transfer pending | |
| 9CB7 | .store_status_copy_ptr |
| STA scout_status ; Store scout status | |
| 9CBA | .skip_buf_setup←1← 9C8C BNE |
| LDA nmi_tx_block ; Copy TX block pointer to workspace ptr | |
| 9CBC | STA port_ws_offset ; Store low byte |
| 9CBE | LDA nmi_tx_block_hi ; Copy TX block pointer high byte |
| 9CC0 | STA rx_buf_offset ; Store high byte |
| 9CC2 | JSR tx_calc_transfer ; Calculate transfer size from RXCB |
| 9CC5 | .tx_ctrl_exit←1← 9CAE BCS |
| PLP ; Restore processor status from stack | |
| 9CC6 | PLA ; Restore stacked registers (4 PLAs) |
| 9CC7 | PLA ; Second PLA |
| 9CC8 | PLA ; Third PLA |
| 9CC9 | PLA ; Fourth PLA |
| 9CCA | TAX ; Restore X from A |
| 9CCB | 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. |
|
| 9CCC | .nmi_tx_data |
| LDY tx_index ; Load TX buffer index | |
| 9CCF | BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ) |
| 9CD2 | .tx_fifo_write←1← 9CED BMI |
| BVC tx_fifo_not_ready ; TDRA not set -- TX error | |
| 9CD4 | LDA tx_dst_stn,y ; Load byte from TX buffer |
| 9CD7 | STA econet_data_continue_frame ; Write to TX_DATA (continue frame) |
| 9CDA | INY ; Next TX buffer byte |
| 9CDB | LDA tx_dst_stn,y ; Load second byte from TX buffer |
| 9CDE | INY ; Advance TX index past second byte |
| 9CDF | STY tx_index ; Save updated TX buffer index |
| 9CE2 | STA econet_data_continue_frame ; Write second byte to TX_DATA |
| 9CE5 | CPY tx_length ; Compare index to TX length |
| 9CE8 | BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA |
| 9CEA | BIT econet_control1_or_status1 ; Check if we can send another pair |
| 9CED | BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop) |
| 9CEF | JMP nmi_rti ; RTI -- wait for next NMI |
| ; TX error path | |
| 9CF2 | .tx_error←1← 9D35 BEQ |
| LDA #&42 ; Error &42 | |
| 9CF4 | BNE tx_store_error ; ALWAYS branch |
| 9CF6 | .tx_fifo_not_ready←1← 9CD2 BVC |
| LDA #&67 ; CR2=&67: clear status, return to listen | |
| 9CF8 | STA econet_control23_or_status2 ; Write CR2: clear status, idle listen |
| 9CFB | LDA #&41 ; Error &41 (TDRA not ready) |
| 9CFD | .tx_store_error←1← 9CF4 BNE |
| LDY station_id_disable_net_nmis ; INTOFF (also loads station ID) | |
| 9D00 | .delay_nmi_disable←1← 9D03 BNE |
| PHA ; PHA/PLA delay loop (256 iterations for NMI disable) | |
| 9D01 | PLA ; PHA/PLA delay (~7 cycles each) |
| 9D02 | INY ; Increment delay counter |
| 9D03 | BNE delay_nmi_disable ; Loop 256 times for NMI disable |
| 9D05 | JMP tx_store_result ; Store error and return to idle |
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 &9D14 (nmi_tx_complete). 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) |
|
| 9D08 | .tx_last_data←1← 9CE8 BCS |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE | |
| 9D0A | STA econet_control23_or_status2 ; Write to ADLC CR2 |
| 9D0D | LDA #&14 ; Install NMI handler at &9D14 (nmi_tx_complete) |
| 9D0F | LDY #&9d ; High byte of handler address |
| 9D11 | 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 -> tx_result_ok at &9EA8 - bit0 set at &0D4A -> handshake_await_ack at &9E50 - Otherwise -> install nmi_reply_scout at &9D30 |
|
| 9D14 | .nmi_tx_complete |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (now in RX mode) | |
| 9D16 | STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX |
| 9D19 | BIT tx_flags ; Test workspace flags |
| 9D1C | BVC check_handshake_bit ; bit6 not set -- check bit0 |
| 9D1E | JMP tx_result_ok ; bit6 set -- TX completion |
| 9D21 | .check_handshake_bit←1← 9D1C BVC |
| LDA #1 ; A=1: mask for bit0 test | |
| 9D23 | BIT tx_flags ; Test tx_flags bit0 (handshake) |
| 9D26 | BEQ install_reply_scout ; bit0 clear: install reply handler |
| 9D28 | JMP handshake_await_ack ; bit0 set -- four-way handshake data phase |
| 9D2B | .install_reply_scout←1← 9D26 BEQ |
| LDA #&30 ; Install nmi_reply_scout at &9D30 | |
| 9D2D | JMP install_nmi_handler ; 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). |
|
| 9D30 | .nmi_reply_scout |
| LDA #1 ; A=&01: AP mask for SR2 | |
| 9D32 | BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present) |
| 9D35 | BEQ tx_error ; No AP -- error |
| 9D37 | LDA econet_data_continue_frame ; Read first RX byte (destination station) |
| 9D3A | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect) |
| 9D3D | BNE reject_reply ; Not our station -- error/reject |
| 9D3F | LDA #&44 ; Install nmi_reply_cont at &9D44 |
| 9D41 | JMP install_nmi_handler ; Install continuation handler |
RX reply continuation handlerReads the second byte of the reply scout (destination network) and validates it is zero (local network). Installs nmi_reply_validate (&9D5B) for the remaining two bytes (source station and network). Optimisation: checks SR1 bit7 (IRQ still asserted) via BMI at &9D53. If IRQ is still set, falls through directly to &9D5B without an RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession. |
|
| 9D44 | .nmi_reply_cont |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 9D47 | BPL reject_reply ; No RDA -- error |
| 9D49 | LDA econet_data_continue_frame ; Read destination network byte |
| 9D4C | BNE reject_reply ; Non-zero -- network mismatch, error |
| 9D4E | LDA #&5b ; Install nmi_reply_validate at &9D5B |
| 9D50 | BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready? |
| 9D53 | BMI nmi_reply_validate ; IRQ set: validate reply immediately |
| 9D55 | JMP install_nmi_handler ; IRQ not set -- install handler and RTI |
| 9D58 | .reject_reply←7← 9D3D BNE← 9D47 BPL← 9D4C BNE← 9D5E BPL← 9D66 BNE← 9D6E BNE← 9D75 BEQ |
| JMP tx_result_fail ; 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 &9D5B -- must see data available 2. Read source station at &9D60, compare to &0D20 (tx_dst_stn) 3. Read source network at &9D68, compare to &0D21 (tx_dst_net) 4. Check SR2 bit1 (FV) at &9D72 -- 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). |
|
| 9D5B | .nmi_reply_validate←1← 9D53 BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply. | |
| 9D5E | BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this) |
| 9D60 | LDA econet_data_continue_frame ; Read source station |
| 9D63 | CMP tx_dst_stn ; Compare to original TX destination station (&0D20) |
| 9D66 | BNE reject_reply ; Mismatch -- not the expected reply, error |
| 9D68 | LDA econet_data_continue_frame ; Read source network |
| 9D6B | CMP tx_dst_net ; Compare to original TX destination network (&0D21) |
| 9D6E | BNE reject_reply ; Mismatch -- error |
| 9D70 | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 9D72 | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9D75 | BEQ reject_reply ; No FV -- incomplete frame, error |
| 9D77 | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake) |
| 9D79 | STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake |
| 9D7C | LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK) |
| 9D7E | STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt |
| 9D81 | LDA #&50 ; Install next handler at &9E50 into &0D4B/&0D4C |
| 9D83 | LDY #&9e ; High byte &9E of next handler address |
| 9D85 | STA nmi_next_lo ; Store low byte to nmi_next_lo |
| 9D88 | STY nmi_next_hi ; Store high byte to nmi_next_hi |
| 9D8B | LDA tx_dst_stn ; Load dest station for scout ACK TX |
| 9D8E | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9D91 | BVC data_tx_check_fifo ; TDRA not ready -- error |
| 9D93 | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 9D96 | LDA tx_dst_net ; Load dest network for scout ACK TX |
| 9D99 | STA econet_data_continue_frame ; Write dest network to TX FIFO |
| 9D9C | LDA #&a3 ; Install nmi_scout_ack_src at &9DA3 |
| 9D9E | LDY #&9d ; High byte &9D of handler address |
| 9DA0 | 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. |
|
| 9DA3 | .nmi_scout_ack_src |
| LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF) | |
| 9DA6 | BIT econet_control1_or_status1 ; BIT SR1: check TDRA before writing |
| 9DA9 | BVC data_tx_check_fifo ; TDRA not ready: TX error |
| 9DAB | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 9DAE | LDA #0 ; Network = 0 (local network) |
| 9DB0 | STA econet_data_continue_frame ; Write network byte to TX FIFO |
| 9DB3 | .data_tx_begin←1← 9948 JMP |
| LDA #2 ; Test bit 1 of tx_flags | |
| 9DB5 | BIT tx_flags ; Check if immediate-op or data-transfer |
| 9DB8 | BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler |
| 9DBA | LDA #&c8 ; Install nmi_data_tx at &9DC8 |
| 9DBC | LDY #&9d ; High byte of handler address |
| 9DBE | JMP set_nmi_vector ; Install and return via set_nmi_vector |
| 9DC1 | .install_imm_data_nmi←1← 9DB8 BNE |
| LDA #&0f ; Install nmi_imm_data at &9E0F | |
| 9DC3 | LDY #&9e ; High byte of handler address |
| 9DC5 | 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 &9CCC but reads from the port buffer instead of the TX workspace. Writes two bytes per iteration, checking SR1 IRQ between pairs for tight looping. |
|
| 9DC8 | .nmi_data_tx |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 9DCA | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9DCD | .data_tx_check_fifo←3← 9D91 BVC← 9DA9 BVC← 9DF0 BMI |
| BVC tx_tdra_error ; TDRA not ready -- error | |
| 9DCF | LDA (open_port_buf),y ; Write data byte to TX FIFO |
| 9DD1 | STA econet_data_continue_frame ; Write first byte of pair to FIFO |
| 9DD4 | INY ; Advance buffer offset |
| 9DD5 | BNE write_second_tx_byte ; No page crossing |
| 9DD7 | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9DD9 | BEQ data_tx_last ; No pages left: send last data |
| 9DDB | INC open_port_buf_hi ; Increment buffer high byte |
| 9DDD | .write_second_tx_byte←1← 9DD5 BNE |
| LDA (open_port_buf),y ; Load second byte of pair | |
| 9DDF | STA econet_data_continue_frame ; Write second byte to FIFO |
| 9DE2 | INY ; Advance buffer offset |
| 9DE3 | STY port_buf_len ; Save updated buffer position |
| 9DE5 | BNE check_irq_loop ; No page crossing |
| 9DE7 | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9DE9 | BEQ data_tx_last ; No pages left: send last data |
| 9DEB | INC open_port_buf_hi ; Increment buffer high byte |
| 9DED | .check_irq_loop←1← 9DE5 BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop | |
| 9DF0 | BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes |
| 9DF2 | JMP nmi_rti ; No IRQ: return, wait for next NMI |
| 9DF5 | .data_tx_last←4← 9DD9 BEQ← 9DE9 BEQ← 9E28 BEQ← 9E3E BEQ |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame) | |
| 9DF7 | STA econet_control23_or_status2 ; Write CR2 to close frame |
| 9DFA | LDA tx_flags ; Check tx_flags for next action |
| 9DFD | BPL data_tx_error ; Bit7 clear: error, install saved handler |
| 9DFF | LDA #&db ; Install discard_reset_listen at &99DB |
| 9E01 | LDY #&99 ; High byte of &99DB handler |
| 9E03 | JMP set_nmi_vector ; Set NMI vector and return |
| 9E06 | .data_tx_error←1← 9DFD BPL |
| .install_saved_handler←1← 9DFD BPL | |
| LDA nmi_next_lo ; Load saved next handler low byte | |
| 9E09 | LDY nmi_next_hi ; Load saved next handler high byte |
| 9E0C | JMP set_nmi_vector ; Install saved handler and return |
| 9E0F | .nmi_data_tx_tube |
| BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA | |
| 9E12 | .tube_tx_fifo_write←1← 9E43 BMI |
| BVC tx_tdra_error ; TDRA not ready -- error | |
| 9E14 | LDA tube_data_register_3 ; Read byte from Tube R3 |
| 9E17 | STA econet_data_continue_frame ; Write to TX FIFO |
| 9E1A | INC port_buf_len ; Increment 4-byte buffer counter |
| 9E1C | BNE write_second_tube_byte ; Low byte didn't wrap |
| 9E1E | INC port_buf_len_hi ; Carry into second byte |
| 9E20 | BNE write_second_tube_byte ; No further carry |
| 9E22 | INC open_port_buf ; Carry into third byte |
| 9E24 | BNE write_second_tube_byte ; No further carry |
| 9E26 | INC open_port_buf_hi ; Carry into fourth byte |
| 9E28 | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9E2A | .write_second_tube_byte←3← 9E1C BNE← 9E20 BNE← 9E24 BNE |
| LDA tube_data_register_3 ; Read second Tube byte from R3 | |
| 9E2D | STA econet_data_continue_frame ; Write second byte to TX FIFO |
| 9E30 | INC port_buf_len ; Increment 4-byte counter (second byte) |
| 9E32 | BNE check_tube_irq_loop ; Low byte didn't wrap |
| 9E34 | .tube_tx_inc_byte2 |
| INC port_buf_len_hi ; Carry into second byte | |
| 9E36 | BNE check_tube_irq_loop ; No further carry |
| 9E38 | .tube_tx_inc_byte3 |
| INC open_port_buf ; Carry into third byte | |
| 9E3A | BNE check_tube_irq_loop ; No further carry |
| 9E3C | .tube_tx_inc_byte4 |
| INC open_port_buf_hi ; Carry into fourth byte | |
| 9E3E | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9E40 | .check_tube_irq_loop←3← 9E32 BNE← 9E36 BNE← 9E3A BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop | |
| 9E43 | BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes |
| 9E45 | JMP nmi_rti ; No IRQ: return, wait for next NMI |
| 9E48 | .tx_tdra_error←2← 9DCD BVC← 9E12 BVC |
| LDA tx_flags ; TX error: check flags for path | |
| 9E4B | BPL tx_result_fail ; Bit7 clear: TX result = not listening |
| 9E4D | JMP discard_reset_listen ; Bit7 set: discard and return to listen |
Four-way handshake: switch to RX for final ACKAfter the data frame TX completes, switches to RX mode (CR1=&82) and installs &9EF8 to receive the final ACK from the remote station. |
|
| 9E50 | .handshake_await_ack←1← 9D28 JMP |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK) | |
| 9E52 | STA econet_control1_or_status1 ; Write to ADLC CR1 |
| 9E55 | LDA #&5c ; Install nmi_final_ack at &9E5C |
| 9E57 | LDY #&9e ; High byte of handler address |
| 9E59 | 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 (&9D30-&9D5B): &9E5C: Check AP, read dest_stn, compare to our station &9E70: Check RDA, read dest_net, validate = 0 &9E84: Check RDA, read src_stn/net, compare to TX dest &9EA3: Check FV for frame completion On success, stores result=0 at tx_result_ok. On failure, error &41. |
|
| 9E5C | .nmi_final_ack |
| LDA #1 ; A=&01: AP mask | |
| 9E5E | BIT econet_control23_or_status2 ; BIT SR2: test AP |
| 9E61 | BEQ tx_result_fail ; No AP -- error |
| 9E63 | LDA econet_data_continue_frame ; Read dest station |
| 9E66 | CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect) |
| 9E69 | BNE tx_result_fail ; Not our station -- error |
| 9E6B | LDA #&70 ; Install nmi_final_ack_net at &9E70 |
| 9E6D | JMP install_nmi_handler ; Install continuation handler |
| 9E70 | .nmi_final_ack_net |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9E73 | BPL tx_result_fail ; No RDA -- error |
| 9E75 | LDA econet_data_continue_frame ; Read dest network |
| 9E78 | BNE tx_result_fail ; Non-zero -- network mismatch, error |
| 9E7A | LDA #&84 ; Install nmi_final_ack_validate at &9E84 |
| 9E7C | BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready? |
| 9E7F | BMI nmi_final_ack_validate ; IRQ set: validate final ACK immediately |
| 9E81 | JMP install_nmi_handler ; Install handler and RTI |
Final ACK validationReads and validates src_stn and src_net against original TX dest. Then checks FV for frame completion. |
|
| 9E84 | .nmi_final_ack_validate←1← 9E7F BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9E87 | BPL tx_result_fail ; No RDA -- error |
| 9E89 | LDA econet_data_continue_frame ; Read source station |
| 9E8C | CMP tx_dst_stn ; Compare to TX dest station (&0D20) |
| 9E8F | BNE tx_result_fail ; Mismatch -- error |
| 9E91 | LDA econet_data_continue_frame ; Read source network |
| 9E94 | CMP tx_dst_net ; Compare to TX dest network (&0D21) |
| 9E97 | BNE tx_result_fail ; Mismatch -- error |
| 9E99 | LDA tx_flags ; Load TX flags for next action |
| 9E9C | BPL check_fv_final_ack ; bit7 clear: no data phase |
| 9E9E | JMP install_data_rx_handler ; Install data RX handler |
| 9EA1 | .check_fv_final_ack←1← 9E9C BPL |
| LDA #2 ; A=&02: FV mask for SR2 bit1 | |
| 9EA3 | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9EA6 | BEQ tx_result_fail ; No FV -- error |
| fall through ↓ | |
TX completion handlerStores result code 0 (success) into the first byte of the TX control block (nmi_tx_block),Y=0. Then sets &0D3A bit7 to signal completion and calls discard_reset_listen to return to idle. |
|
| 9EA8 | .tx_result_ok←2← 98F6 JMP← 9D1E JMP |
| LDA #0 ; A=0: success result code | |
| 9EAA | 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. |
|
| 9EAC | .tx_result_fail←11← 983A JMP← 9D58 JMP← 9E4B BPL← 9E61 BEQ← 9E69 BNE← 9E73 BPL← 9E78 BNE← 9E87 BPL← 9E8F BNE← 9E97 BNE← 9EA6 BEQ |
| LDA #&41 ; A=&41: not listening error code | |
| fall through ↓ | |
TX result store and completionStores result code (A) into the TX control block at (nmi_tx_block),0 and sets bit 7 of &0D3A to signal completion. Returns to idle via discard_reset_listen. Reached from tx_result_ok (A=0, success), tx_result_fail (A=&41, not listening), and directly with other codes (A=&40 line jammed, A=&42 net error). |
|
| 9EAE | .tx_store_result←2← 9D05 JMP← 9EAA BEQ |
| LDY #0 ; Y=0: index into TX control block | |
| 9EB0 | STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0 |
| 9EB2 | LDA #&80 ; &80: completion flag for &0D3A |
| 9EB4 | STA tx_clear_flag ; Signal TX complete |
| 9EB7 | JMP discard_reset_listen ; Full ADLC reset and return to idle listen |
| ; Unreferenced data block (purpose unknown) | |
| 9EBA | EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81 ; Unreferenced data |
Calculate transfer sizeComputes the number of bytes actually transferred during a data frame reception by subtracting RXCB[8..11] (start address) from RXCB[4..7] (current pointer), giving the byte count. Two paths: the main path performs a 4-byte subtraction for Tube transfers, storing results to port_buf_len..open_port_buf_hi (&A2-&A5). The fallback path (no Tube or buffer addr = &FFFF) does a 2-byte subtraction using open_port_buf/open_port_buf_hi (&A4/&A5) as scratch. Both paths clobber &A4/&A5 as a side effect of the result area overlapping open_port_buf.
|
||||||
| 9ECA | .tx_calc_transfer←3← 97BE JSR← 9AC9 JSR← 9CC2 JSR | |||||
| LDY #6 ; Load RXCB[6] (buffer addr byte 2) | ||||||
| 9ECC | LDA (port_ws_offset),y ; Load workspace byte at offset Y | |||||
| 9ECE | INY ; Y=&07 | |||||
| 9ECF | AND (port_ws_offset),y ; AND with TX block[7] (byte 3) | |||||
| 9ED1 | CMP #&ff ; Both &FF = no buffer? | |||||
| 9ED3 | BEQ fallback_calc_transfer ; Yes: fallback path | |||||
| 9ED5 | LDA tube_flag ; Tube transfer in progress? | |||||
| 9ED8 | BEQ fallback_calc_transfer ; No: fallback path | |||||
| 9EDA | LDA tx_flags ; Load TX flags for transfer setup | |||||
| 9EDD | ORA #2 ; Set bit 1 (transfer complete) | |||||
| 9EDF | STA tx_flags ; Store with bit 1 set (Tube xfer) | |||||
| 9EE2 | SEC ; Init borrow for 4-byte subtract | |||||
| 9EE3 | PHP ; Save carry on stack | |||||
| 9EE4 | LDY #4 ; Y=4: start at RXCB offset 4 | |||||
| 9EE6 | .calc_transfer_size←1← 9EF8 BCC | |||||
| LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte) | ||||||
| 9EE8 | INY ; Y += 4: advance to RXCB[Y+4] | |||||
| 9EE9 | INY ; (continued) | |||||
| 9EEA | INY ; (continued) | |||||
| 9EEB | INY ; (continued) | |||||
| 9EEC | PLP ; Restore borrow from previous byte | |||||
| 9EED | SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte) | |||||
| 9EEF | STA net_tx_ptr,y ; Store result byte | |||||
| 9EF2 | DEY ; Y -= 3: next source byte | |||||
| 9EF3 | DEY ; (continued) | |||||
| 9EF4 | DEY ; (continued) | |||||
| 9EF5 | PHP ; Save borrow for next byte | |||||
| 9EF6 | CPY #8 ; Done all 4 bytes? | |||||
| 9EF8 | BCC calc_transfer_size ; No: next byte pair | |||||
| 9EFA | PLP ; Discard final borrow | |||||
| 9EFB | TXA ; A = saved X | |||||
| 9EFC | PHA ; Save X | |||||
| 9EFD | LDA #4 ; Compute address of RXCB+4 | |||||
| 9EFF | CLC ; CLC for base pointer addition | |||||
| 9F00 | ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr | |||||
| 9F02 | TAX ; X = low byte of RXCB+4 | |||||
| 9F03 | LDY rx_buf_offset ; Y = high byte of RXCB ptr | |||||
| 9F05 | LDA #&c2 ; Tube claim type &C2 | |||||
| 9F07 | JSR tube_addr_claim ; Claim Tube transfer address | |||||
| 9F0A | BCC restore_x_and_return ; No Tube: skip reclaim | |||||
| 9F0C | LDA scout_status ; Tube: reclaim with scout status | |||||
| 9F0F | JSR tube_addr_claim ; Reclaim with scout status type | |||||
| 9F12 | JSR release_tube ; Release Tube claim after reclaim | |||||
| 9F15 | SEC ; C=1: Tube address claimed | |||||
| 9F16 | .restore_x_and_return←1← 9F0A BCC | |||||
| PLA ; Restore X | ||||||
| 9F17 | TAX ; Restore X from stack | |||||
| 9F18 | RTS ; Return with C = transfer status | |||||
| 9F19 | .fallback_calc_transfer←2← 9ED3 BEQ← 9ED8 BEQ | |||||
| LDY #4 ; Y=4: RXCB current pointer offset | ||||||
| 9F1B | LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo) | |||||
| 9F1D | LDY #8 ; Y=8: RXCB start address offset | |||||
| 9F1F | SEC ; Set carry for subtraction | |||||
| 9F20 | SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo) | |||||
| 9F22 | STA port_buf_len ; Store transfer size lo | |||||
| 9F24 | LDY #5 ; Y=5: current ptr hi offset | |||||
| 9F26 | LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi) | |||||
| 9F28 | SBC #0 ; Propagate borrow from lo subtraction | |||||
| 9F2A | STA open_port_buf_hi ; Temp store adjusted current ptr hi | |||||
| 9F2C | LDY #8 ; Y=8: start address lo offset | |||||
| 9F2E | LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo | |||||
| 9F30 | STA open_port_buf ; Store to scratch (side effect) | |||||
| 9F32 | LDY #9 ; Y=9: start address hi offset | |||||
| 9F34 | LDA (port_ws_offset),y ; Load RXCB[9] (start ptr hi) | |||||
| 9F36 | SEC ; Set carry for subtraction | |||||
| 9F37 | SBC open_port_buf_hi ; start_hi - adjusted current_hi | |||||
| 9F39 | STA port_buf_len_hi ; Store transfer size hi | |||||
| 9F3B | SEC ; Return with C=1 | |||||
| 9F3C | .nmi_shim_rom_src | |||||
| RTS ; Return with C=1 (success) | ||||||
ADLC full resetAborts all activity and returns to idle RX listen mode.
|
||||
| 9F3D | .adlc_full_reset←3← 967D JSR← 9705 JSR← 983D JSR | |||
| LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set) | ||||
| 9F3F | STA econet_control1_or_status1 ; Write CR1 to ADLC register 0 | |||
| 9F42 | LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding | |||
| 9F44 | STA econet_data_terminate_frame ; Write CR4 to ADLC register 3 | |||
| 9F47 | LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR | |||
| 9F49 | STA econet_control23_or_status2 ; Write CR3 to ADLC register 1 | |||
| fall through ↓ | ||||
Enter RX listen modeTX held in reset, RX active with interrupts. Clears all status.
|
||||
| 9F4C | .adlc_rx_listen←2← 99E8 JSR← 9F7A JMP | |||
| LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled) | ||||
| 9F4E | STA econet_control1_or_status1 ; Write to ADLC CR1 | |||
| 9F51 | LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE | |||
| 9F53 | STA econet_control23_or_status2 ; Write to ADLC CR2 | |||
| 9F56 | RTS ; Return; ADLC now in RX listen mode | |||
Wait for idle NMI state and reset EconetCalled via svc_12_nmi_release (&06D4). Checks if Econet has been initialised; if not, skips to adlc_rx_listen. If initialised, spins until the NMI handler is idle (pointing at nmi_rx_scout), then falls through to save_econet_state to clear flags and re-enter RX listen mode. |
|
| 9F57 | .wait_idle_and_reset←1← 06D4 JMP |
| BIT econet_init_flag ; Econet not initialised -- skip to adlc_rx_listen | |
| 9F5A | BPL reset_enter_listen ; Not initialised: skip to RX listen |
| 9F5C | .poll_nmi_idle←2← 9F61 BNE← 9F68 BNE |
| LDA nmi_jmp_lo ; Spin until NMI handler = &96BF (nmi_rx_scout) | |
| 9F5F | CMP #&bf ; Expected: &BF (nmi_rx_scout low) |
| 9F61 | BNE poll_nmi_idle ; Not idle: spin and wait |
| 9F63 | LDA nmi_jmp_hi ; Read current NMI handler high byte |
| 9F66 | CMP #&96 ; Expected: &96 (nmi_rx_scout high) |
| 9F68 | BNE poll_nmi_idle ; Not idle: spin and wait |
| 9F6A | BIT station_id_disable_net_nmis ; INTOFF before clearing state |
| fall through ↓ | |
Reset Econet flags and enter RX listenDisables NMIs via INTOFF (BIT &FE18), clears tx_clear_flag and econet_init_flag to zero, then falls through to adlc_rx_listen with Y=5. |
|
| 9F6D | .save_econet_state |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs | |
| 9F70 | LDA #0 ; Clear both flags |
| 9F72 | STA tx_clear_flag ; TX not in progress |
| 9F75 | STA econet_init_flag ; Econet not initialised |
| 9F78 | LDY #5 ; Y=5: service call workspace page |
| 9F7A | .reset_enter_listen←1← 9F5A BPL |
| JMP adlc_rx_listen ; Set ADLC to RX listen mode | |
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 (&96BF). 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 &96BF. |
|
| 9F7D | .nmi_bootstrap_entry |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM | |
| 9F80 | PHA ; Save A |
| 9F81 | TYA ; Transfer Y to A |
| 9F82 | PHA ; Save Y (via A) |
| 9F83 | LDA #0 ; ROM bank 0 (patched during init for actual bank) |
| 9F85 | STA romsel ; Select Econet ROM bank via ROMSEL |
| 9F88 | JMP nmi_rx_scout ; Jump to scout handler in ROM |
ROM copy of set_nmi_vector + nmi_rti |
|
| 9F8B | .rom_set_nmi_vector |
| STY nmi_jmp_hi ; Store handler high byte at &0D0D | |
| 9F8E | STA nmi_jmp_lo ; Store handler low byte at &0D0C |
| 9F91 | LDA romsel_copy ; Restore NFS ROM bank |
| 9F93 | STA romsel ; Page in via hardware latch |
| 9F96 | PLA ; Restore Y from stack |
| 9F97 | TAY ; Transfer ROM bank to Y |
| 9F98 | PLA ; Restore A from stack |
| 9F99 | BIT video_ula_control ; INTON: re-enable NMIs |
| 9F9C | RTI ; Return from interrupt |
Print byte as two hex digitsPrints the high nibble first (via 4x LSR), then the low nibble. Each nibble is converted to ASCII '0'-'9' or 'A'-'F' and output via OSASCI. Returns with carry set.
|
|||||||||||
| 9F9D | .print_hex←2← 8CD7 JSR← 8D74 JSR | ||||||||||
| PHA ; Save original byte for low nibble | |||||||||||
| 9F9E | LSR ; Shift high nibble right (4x LSR) | ||||||||||
| 9F9F | LSR ; Shift high nibble to low | ||||||||||
| 9FA0 | LSR ; Shift high nibble to low | ||||||||||
| 9FA1 | LSR ; Shift high nibble to low | ||||||||||
| 9FA2 | JSR print_hex_nibble ; Print high nibble as hex | ||||||||||
| 9FA5 | PLA ; Restore byte; fall through for low nibble | ||||||||||
| fall through ↓ | |||||||||||
Print single hex nibbleConverts the low nibble of A to ASCII hex ('0'-'9' or 'A'-'F') and prints via OSASCI. Returns with carry set.
|
|||||||||||
| 9FA6 | .print_hex_nibble←1← 9FA2 JSR | ||||||||||
| AND #&0f ; Mask to low nibble (0-F) | |||||||||||
| 9FA8 | CMP #&0a ; Digit A-F? | ||||||||||
| 9FAA | BCC add_ascii_base ; No: skip letter offset | ||||||||||
| 9FAC | ADC #6 ; A-F: ADC #6 + ADC #&30 + C = &41-&46 | ||||||||||
| 9FAE | .add_ascii_base←1← 9FAA BCC | ||||||||||
| ADC #&30 ; Add ASCII '0' base (with carry) | |||||||||||
| 9FB0 | JSR osasci ; Write character | ||||||||||
| 9FB3 | SEC ; C=1: callers use SEC as sentinel | ||||||||||
| 9FB4 | RTS ; Return | ||||||||||
*EX vs *EXEC disambiguation trampolineAdded in 3.62 to fix *EX matching *EXEC. The "EX" command table entry dispatches here instead of directly to ex_handler. Checks the next character in the command line: if < &21 (space/CR/ctrl) the command is *EX so jumps to ex_handler; if >= &21 (printable) the command continues (e.g. *EXEC) so forwards to the FS. |
|
| 9FB5 | .ex_trampoline |
| .ex_exec_trampoline | |
| LDA (fs_crc_lo),y ; Load next char from command line | |
| 9FB7 | CMP #&21 ; Printable non-space character? |
| 9FB9 | BCS forward_ex_to_fs ; Yes: not *EX, forward to FS |
| 9FBB | JMP ex_handler ; No: handle *EX locally |
| 9FBE | .forward_ex_to_fs←1← 9FB9 BCS |
| JMP forward_star_cmd ; Forward *EXEC etc. to fileserver | |
| 9FC1 | EQUB &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; & &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; FF &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; pa &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; dd &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; in &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; g &FF, &FF, &FF ; ( ; en ; d ; of ; RO ; M) |
