Acorn NFS 3.35D
Updated 31 Mar 2026
← All Acorn NFS and Acorn ANFS versions
- Acorn NFS 3.35D 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.34B
- Found a mistake or a comment that could be clearer? Report an issue.
| ; Sideways ROM header | |
| ; NFS ROM 3.35D disassembly (Acorn Econet filing system) | |
| ; ===================================================== | |
| 8000 | .rom_header←2← 048B LDA← 9BC7 JSR |
| .language_entry←2← 048B LDA← 9BC7 JSR | |
| .pydis_start←2← 048B LDA← 9BC7 JSR | |
| JMP language_handler ; JMP language_handler | |
| 8003 | .service_entry←1← 049A LDY |
| JMP service_handler ; JMP service_handler | |
| 8006 | .rom_type←1← 0482 AND |
| EQUB &82 | |
| 8007 | .copyright_offset←1← 0487 LDX |
| EQUB copyright - rom_header | |
| 8008 | .binary_version←2← 81DE CMP← 81E7 LDA |
| EQUB &03 | |
| 8009 | .title |
| EQUS "NET" | |
| 800C | .copyright |
| EQUB &00 | |
| ; The 'ROFF' suffix at copyright_string+3 is reused by | |
| ; the *ROFF command matcher (svc_star_command) — a | |
| ; space-saving trick that shares ROM bytes between the | |
| ; copyright string and the star command table. | |
| 800D | .copyright_string |
| EQUS "(C)ROFF" | |
| ; Error message offset table (9 entries). | |
| ; Each byte is a Y offset into error_msg_table. | |
| ; Entry 0 (Y=0, "Line Jammed") doubles as the | |
| ; copyright string null terminator. | |
| ; Indexed by TXCB status (AND #7), or hardcoded 8. | |
| 8014 | .error_offsets←1← 84DD LDY |
| EQUB &00 ; "Line Jammed" | |
| 8015 | EQUB &0D ; "Net Error" |
| 8016 | EQUB &18 ; "Not listening" |
| 8017 | EQUB &27 ; "No Clock" |
| 8018 | EQUB &31 ; "Escape" |
| 8019 | EQUB &31 ; "Escape" |
| 801A | EQUB &31 ; "Escape" |
| 801B | EQUB &39 ; "Bad Option" |
| 801C | EQUB &45 ; "No reply" |
| ; Four bytes with unknown purpose. | |
| 801D | EQUB &01 ; Purpose unknown |
| 801E | EQUB &00 ; Purpose unknown |
| 801F | EQUB &35 ; Purpose unknown |
| 8020 | EQUB &03 ; Purpose unknown |
| ; Dispatch table: low bytes of (handler_address - 1) | |
| ; Each entry stores the low byte of a handler address minus 1, | |
| ; for use with the PHA/PHA/RTS dispatch trick at &80DA. | |
| ; See dispatch_0_hi (&8045) for the corresponding high bytes. | |
| ; Five callers share this table via different Y base offsets: | |
| ; Y=&00 Service calls 0-12 (indices 1-13) | |
| ; Y=&0D Language entry reasons (indices 14-18) | |
| ; Y=&12 FSCV codes 0-7 (indices 19-26) | |
| ; Y=&16 FS reply handlers (indices 27-32) | |
| ; Y=&20 *NET1-4 sub-commands (indices 33-36) | |
| 8021 | .dispatch_0_lo |
| EQUB <(return_2-1) ; lo - Svc 0: already claimed (no-op) | |
| 8022 | EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace |
| 8023 | EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace |
| 8024 | EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot |
| 8025 | EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command |
| 8026 | EQUB <(svc_5_unknown_irq-1) ; lo - Svc 5: unrecognised interrupt |
| 8027 | EQUB <(return_2-1) ; lo - Svc 6: BRK (no-op) |
| 8028 | EQUB <(dispatch_net_cmd-1) ; lo - Svc 7: unrecognised OSBYTE |
| 8029 | EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD |
| 802A | EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP |
| 802B | EQUB <(return_2-1) ; lo - Svc 10: static workspace (no-op) |
| 802C | EQUB <(svc_11_nmi_claim-1) ; lo - Svc 11: NMI release (reclaim NMIs) |
| 802D | EQUB <(svc_12_nmi_release-1) ; lo - Svc 12: NMI claim (save NMI state) |
| 802E | EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube |
| 802F | EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup |
| 8030 | EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron) |
| 8031 | EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron) |
| 8032 | EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated |
| 8033 | EQUB <(fscv_0_opt-1) ; lo - FSCV 0: *OPT |
| 8034 | EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check |
| 8035 | EQUB <(fscv_2_star_run-1) ; lo - FSCV 2: */ (run) |
| 8036 | EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command |
| 8037 | EQUB <(fscv_2_star_run-1) ; lo - FSCV 4: *RUN |
| 8038 | EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT |
| 8039 | EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown |
| 803A | EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range |
| 803B | EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name |
| 803C | EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot |
| 803D | EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles |
| 803E | EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle |
| 803F | EQUB <(fsreply_4_notify_exec-1) ; lo - FS reply: notify + execute |
| 8040 | EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle |
| 8041 | EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet |
| 8042 | EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace |
| 8043 | EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle |
| 8044 | EQUB <(net_4_resume_remote-1) ; lo - *NET4: resume remote |
| ; Dispatch table: high bytes of (handler_address - 1) | |
| ; Paired with dispatch_0_lo (&8021). Together they form a table | |
| ; of 37 handler addresses, used via the PHA/PHA/RTS trick at | |
| ; &80DA. | |
| 8045 | .dispatch_0_hi |
| EQUB >(return_2-1) ; hi - Svc 0: already claimed (no-op) | |
| 8046 | EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace |
| 8047 | EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace |
| 8048 | EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot |
| 8049 | EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command |
| 804A | EQUB >(svc_5_unknown_irq-1) ; hi - Svc 5: unrecognised interrupt |
| 804B | EQUB >(return_2-1) ; hi - Svc 6: BRK (no-op) |
| 804C | EQUB >(dispatch_net_cmd-1) ; hi - Svc 7: unrecognised OSBYTE |
| 804D | EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD |
| 804E | EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP |
| 804F | EQUB >(return_2-1) ; hi - Svc 10: static workspace (no-op) |
| 8050 | EQUB >(svc_11_nmi_claim-1) ; hi - Svc 11: NMI release (reclaim NMIs) |
| 8051 | EQUB >(svc_12_nmi_release-1) ; hi - Svc 12: NMI claim (save NMI state) |
| 8052 | EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube |
| 8053 | EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup |
| 8054 | EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron) |
| 8055 | EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron) |
| 8056 | EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated |
| 8057 | EQUB >(fscv_0_opt-1) ; hi - FSCV 0: *OPT |
| 8058 | EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check |
| 8059 | EQUB >(fscv_2_star_run-1) ; hi - FSCV 2: */ (run) |
| 805A | EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command |
| 805B | EQUB >(fscv_2_star_run-1) ; hi - FSCV 4: *RUN |
| 805C | EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT |
| 805D | EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown |
| 805E | EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range |
| 805F | EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name |
| 8060 | EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot |
| 8061 | EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles |
| 8062 | EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle |
| 8063 | EQUB >(fsreply_4_notify_exec-1) ; hi - FS reply: notify + execute |
| 8064 | EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle |
| 8065 | EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet |
| 8066 | EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace |
| 8067 | EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle |
| 8068 | EQUB >(net_4_resume_remote-1) ; hi - *NET4: resume remote |
*NET command dispatcherParses the character after *NET as '1'-'4', maps to table indices 33-36 via base offset Y=&20, and dispatches via &80DA. 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 (&8E43): read file handle from received packet (net_1_read_handle) *NET2 (&8E5E): read handle entry from workspace (net_2_read_handle_entry) *NET3 (&8E6E): close handle / mark as unused (net_3_close_handle) *NET4 (&818A): resume after remote operation (net_4_resume_remote) |
|
| 8069 | .dispatch_net_cmd |
| LDA osbyte_a_copy ; Read command character following *NET | |
| 806B | SBC #&31 ; Subtract ASCII '1' to get 0-based command index |
| 806D | BMI return_1 ; Negative: not a net command, exit |
| 806F | CMP #4 ; Command index >= 4: invalid *NET sub-command |
| 8071 | BCS return_1 ; Out of range: return via c80e3/RTS |
| 8073 | TAX ; X = command index (0-3) |
| 8074 | LDA #0 ; Clear &A9 (used by dispatch) |
| 8076 | STA rom_svc_num ; Store zero to &A9 |
| 8078 | TYA ; Preserve A before dispatch |
| 8079 | LDY #&20 ; Y=&20: base offset for *NET commands (index 33+) |
| 807B | BNE dispatch ; ALWAYS branch to dispatch ALWAYS branch |
| 807D | .skip_iam_spaces←1← 8082 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 twice if a dot separator is present. The first number becomes the network (&0E01, via TAX pass-through in parse_decimal) and the second becomes the station (&0E00). With a single number, it is stored as the station and the network defaults to 0 (local). If a colon follows, reads interactive input via OSRDCH and appends it to the command buffer. Finally jumps to forward_star_cmd. |
|
| 807E | .i_am_handler |
| LDA (fs_options),y ; Load next char from command line | |
| 8080 | CMP #&20 ; Skip spaces |
| 8082 | BEQ skip_iam_spaces ; Loop back to skip leading spaces |
| 8084 | CMP #&41 ; Colon = interactive remote command prefix |
| 8086 | BCS scan_for_colon ; Char >= ':': skip number parsing |
| 8088 | LDA #0 ; A=0: default network number |
| 808A | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 808D | BCC store_station_net ; C=1: dot found, first number was network |
| 808F | INY ; Y=offset into (fs_options) buffer |
| 8090 | JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN) |
| 8093 | .store_station_net←1← 808D BCC |
| STA fs_server_stn ; A=parsed value (accumulated in &B2) | |
| 8096 | STX fs_server_net ; X=initial A value (saved by TAX) |
| 8099 | .scan_for_colon←2← 8086 BCS← 80A2 BNE |
| INY ; Skip past current character | |
| 809A | LDA (fs_options),y ; Load next character from cmd line |
| 809C | CMP #&0d ; CR: end of command string? |
| 809E | BEQ forward_star_cmd ; Y=0: no colon found, send command |
| 80A0 | CMP #&3a ; Test for colon separator |
| 80A2 | BNE scan_for_colon ; Not colon: keep scanning backward |
| 80A4 | JSR oswrch ; Echo colon, then read user input from keyboard Write character |
| 80A7 | .read_remote_cmd_line←1← 80AF BNE |
| JSR osrdch ; Check for escape condition Read a character from the current input stream | |
| 80AA | STA (fs_options),y ; A=character read |
| 80AC | INY ; Advance write pointer |
| 80AD | CMP #&0d ; Test for CR (end of line) |
| 80AF | BNE read_remote_cmd_line ; Not CR: continue reading input |
| 80B1 | JSR osnewl ; Write newline (characters 10 and 13) |
| 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 &8BE2, and from FSCV 2/3/4 indirectly. If CSD handle is zero (not logged in), returns without sending. |
|
| 80B4 | .forward_star_cmd←1← 809E BEQ |
| JSR copy_filename ; Copy command text to FS buffer | |
| 80B7 | TAY ; Y=function code for HDRFN |
| 80B8 | .prepare_cmd_dispatch |
| JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |
| 80BB | LDX fs_cmd_csd ; X=depends on function |
| 80BE | BEQ return_1 ; CSD handle zero: not logged in |
| 80C0 | LDA fs_cmd_data ; A=function code (0-7) |
| 80C3 | LDY #&16 ; Y=depends on function |
| 80C5 | BNE dispatch ; ALWAYS branch |
FSCV dispatch entryEntered via the extended vector table when the MOS calls FSCV. Stores A/X/Y via save_fscv_args, compares A (function code) against 8, and dispatches codes 0-7 via the shared dispatch table at &8020 with base offset Y=&12 (table indices 19-26). Function codes: 0=*OPT, 1=EOF, 2=*/, 3=unrecognised *, 4=*RUN, 5=*CAT, 6=shutdown, 7=read handles.
|
|||||||||||||||
| 80C7 | .fscv_handler | ||||||||||||||
| JSR save_fscv_args_with_ptrs ; Store A/X/Y in FS workspace | |||||||||||||||
| 80CA | CMP #8 ; Function code >= 8? Return (unsupported) | ||||||||||||||
| 80CC | BCS return_1 ; Function code >= 8? Return (unsupported) | ||||||||||||||
| 80CE | TAX ; X = function code for dispatch | ||||||||||||||
| 80CF | TYA ; Save Y (command text ptr hi) | ||||||||||||||
| 80D0 | LDY #&12 ; Y=&12: base offset for FSCV dispatch (indices 19+) | ||||||||||||||
| 80D2 | BNE dispatch ; ALWAYS branch | ||||||||||||||
Language entry dispatcherCalled when the NFS ROM is entered as a language. Although rom_type (&82) does not set the language bit, the MOS enters this point after NFS claims service &FE (Tube post-init). X = reason code (0-4). Dispatches via table indices 14-18 (base offset Y=&0D). |
|
| 80D4 | .language_handler←1← 8000 JMP |
| .lang_entry_dispatch←1← 8000 JMP | |
| CPX #5 ; X >= 5: invalid reason code, return | |
| 80D6 | .svc_dispatch_range |
| BCS return_1 ; Out of range: return via RTS | |
| 80D8 | LDY #&0d ; Y=&0D: base offset for language handlers (index 14+) |
| fall through ↓ | |
PHA/PHA/RTS computed dispatchX = command index within caller's group (e.g. service number) Y = base offset into dispatch table (0, &0D, &20, etc.) The loop adds Y+1 to X, so final X = command index + base + 1. Then high and low bytes of (handler-1) are pushed onto the stack, and RTS pops them and jumps to handler_address. This is a standard 6502 trick: RTS increments the popped address by 1 before jumping, so the table stores (address - 1) to compensate. Multiple callers share one table via different Y base offsets. |
|
| 80DA | .dispatch←5← 807B BNE← 80C5 BNE← 80D2 BNE← 80DC BPL← 816A JSR |
| INX ; Add base offset Y to index X (loop: X += Y+1) | |
| 80DB | DEY ; Decrement base offset counter |
| 80DC | BPL dispatch ; Loop until Y exhausted |
| 80DE | TAY ; Y=&FF (no further use) |
| 80DF | LDA dispatch_0_hi-1,x ; Load high byte of (handler - 1) from table |
| 80E2 | PHA ; Push high byte onto stack |
| 80E3 | LDA dispatch_0_lo-1,x ; Load low byte of (handler - 1) from table |
| 80E6 | PHA ; Push low byte onto stack |
| 80E7 | LDX fs_options ; Restore X (fileserver options) for use by handler |
| 80E9 | .return_1←7← 806D BMI← 8071 BCS← 80BE BEQ← 80CC BCS← 80D6 BCS← 80F2 BCS← 80FC BEQ |
| RTS ; RTS pops address, adds 1, jumps to handler | |
Service handler entryChecks per-ROM disable flag at &0DF0+X (new in 3.35D). If bit 7 is set, returns immediately; service calls &FE/&FF bypass this check. Intercepts three service calls: &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 c8150. |
|
| 80EA | .service_handler←1← 8003 JMP |
| .service_handler_entry←1← 8003 JMP | |
| PHA ; Save A (service number) on stack | |
| 80EB | LDA rom_ws_table,x ; Load per-ROM workspace flag |
| 80EE | ASL ; Test bit 7 (ROM disabled flag) |
| 80EF | PLA ; Restore A (service number) |
| 80F0 | BMI check_svc_high ; Service >= &80: always handle |
| 80F2 | BCS return_1 ; C=1 (disabled): skip this ROM |
| fall through ↓ | |
Service handler: high service dispatchHandles service calls >= &80. Compares A against &FE: &FF: Full init — jump to init_vectors_and_copy &FE: Tube init — explode character definitions (if Y != 0) < &FE: Fall through to check for service &12 and dispatch. |
|
| 80F4 | .check_svc_high←1← 80F0 BMI |
| CMP #&fe ; Service >= &FE? | |
| 80F6 | BCC check_svc_12 ; Service < &FE: skip to &12/dispatch check |
| 80F8 | BNE init_vectors_and_copy ; Service &FF: full init (vectors + RAM copy) |
| 80FA | CPY #0 ; Service &FE: Y=0? |
| 80FC | BEQ return_1 ; Y=0: no Tube data, skip to &12 check |
| 80FE | STX zp_temp_11 ; Save ROM number across OSBYTE |
| 8100 | STY zp_temp_10 ; Save Tube address across OSBYTE |
| 8102 | LDX #6 ; X=6 extra pages for char definitions |
| 8104 | LDA #osbyte_explode_chars ; OSBYTE &14: explode character RAM |
| 8106 | JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6) |
| 8109 | LDX zp_temp_11 ; Restore ROM number |
| 810B | BNE restore_y_check_svc ; Continue to vector setup |
| fall through ↓ | |
NFS initialisation (service &FF: full reset)New in 3.35D: table-driven vector initialisation replaces the hardcoded LDA/STA pairs of 3.34B. Reads 4 triplets from the data table at &8177 (low byte, high byte, vector offset) and stores each 16-bit value at &0200+offset: EVNTV (&0220) = &06E5 BRKV (&0202) = &0016 RDCHV (&0210) = &04E7 WRCHV (&020E) = &051C Then writes &8E to Tube control register (&FEE0) and copies 3 pages of Tube host code from ROM (reloc_p4_src / reloc_p5_src / reloc_p6_src) to RAM (&0400/&0500/&0600), calls tube_post_init (&0414), and copies 97 bytes of workspace init from ROM (reloc_zp_src) to &0016-&0076. |
|
| 810D | .init_vectors_and_copy←1← 80F8 BNE |
| STY zp_temp_10 ; Save Y (ROM number) for later | |
| 810F | LDY #&0c ; Y=12: 4 triplets x 3 bytes each |
| 8111 | .init_vector_loop←1← 8123 BNE |
| LDX return_2,y ; Load vector offset from table | |
| 8114 | DEY ; Previous table byte |
| 8115 | LDA return_2,y ; Load vector high byte from table |
| 8118 | STA userv+1,x ; Store high byte at &0201+X |
| 811B | DEY ; Previous table byte |
| 811C | LDA return_2,y ; Load vector low byte from table |
| 811F | STA userv,x ; Store low byte at &0200+X |
| 8122 | DEY ; Previous table byte |
| 8123 | BNE init_vector_loop ; Loop for all 4 vector pairs |
| 8125 | .init_tube_and_workspace |
| LDA #&8e ; A=&8E: Tube control register init value | |
| 8127 | STA tube_status_1_and_tube_control ; Write to Tube control register |
| ; Copy NMI handler code from ROM to RAM pages &04-&06 | |
| 812A | .cloop←1← 813D BNE |
| LDA reloc_p4_src,y ; Load ROM byte from page &93 | |
| 812D | STA tube_code_page4,y ; Store to page &04 (Tube code) |
| 8130 | LDA reloc_p5_src,y ; Load ROM byte from page &94 |
| 8133 | STA tube_dispatch_table,y ; Store to page &05 (dispatch table) |
| 8136 | LDA reloc_p6_src,y ; Load ROM byte from page &95 |
| 8139 | STA tube_code_page6,y ; Store to page &06 |
| 813C | DEY ; DEY wraps 0 -> &FF on first iteration |
| 813D | BNE cloop ; Loop until 256 bytes copied per page |
| 813F | JSR tube_post_init ; Run post-init routine in copied code |
| 8142 | LDX #&60 ; X=&60: copy 97 bytes (&60..&00) |
| ; Copy NMI workspace initialiser from ROM to &0016-&0076 | |
| 8144 | .copy_nmi_workspace←1← 814A BPL |
| LDA reloc_zp_src,x ; Load NMI workspace init byte from ROM | |
| 8147 | STA nmi_workspace_start,x ; Store to zero page &16+X |
| 8149 | DEX ; Next byte |
| 814A | BPL copy_nmi_workspace ; Loop until all workspace bytes copied |
| 814C | .restore_y_check_svc←1← 810B BNE |
| LDY zp_temp_10 ; Restore Y (ROM number) | |
| 814E | .tube_chars_done |
| LDA #0 ; A=0: fall through to service &12 check | |
| 8150 | .check_svc_12←1← 80F6 BCC |
| CMP #&12 ; Is this service &12 (select FS)? | |
| 8152 | BNE not_svc_12_nfs ; No: check if service < &0D |
| 8154 | CPY #5 ; Service &12: Y=5 (NFS)? |
| 8156 | BEQ select_nfs ; Y=5: select NFS |
| fall through ↓ | |
Service call dispatcherDispatches MOS service calls 0-12 via the shared dispatch table. Uses base offset Y=0, so table index = service number + 1. Service numbers >= 13 are ignored (branch to return). |
|
| 8158 | .not_svc_12_nfs←1← 8152 BNE |
| .dispatch_service←1← 8152 BNE | |
| CMP #&0d ; Service >= &0D? | |
| 815A | .svc_unhandled_return |
| BCS return_2 ; Service >= &0D: not handled, return | |
| 815C | .do_svc_dispatch |
| TAX ; X = service number (dispatch index) | |
| 815D | LDA rom_svc_num ; Save &A9 (current service state) |
| 815F | PHA ; Push saved &A9 |
| 8160 | LDA nfs_temp ; Save &A8 (workspace page number) |
| 8162 | PHA ; Push saved &A8 |
| 8163 | STX rom_svc_num ; Store service number to &A9 |
| 8165 | STY nfs_temp ; Store Y (page number) to &A8 |
| 8167 | TYA ; A = Y for dispatch table offset |
| 8168 | LDY #0 ; Y=0: base offset for service dispatch |
| 816A | JSR dispatch ; JSR to dispatcher (returns here after handler completes) |
| 816D | LDX rom_svc_num ; Recover service claim status from &A9 |
| 816F | PLA ; Restore saved &A8 from stack |
| 8170 | STA nfs_temp ; Write back &A8 |
| fall through ↓ | |
Service dispatch epilogueCommon return path for all dispatched service handlers. Restores rom_svc_num from the stack (pushed by dispatch_service), transfers X (ROM number) to A, then returns via RTS. |
|
| 8172 | .svc_dispatch_epilogue |
| PLA ; Restore saved A from service dispatch | |
| 8173 | STA rom_svc_num ; Save to workspace &A9 |
| 8175 | TXA ; Return ROM number in A |
| 8176 | .return_2←4← 8111 LDX← 8115 LDA← 811C LDA← 815A BCS |
| RTS ; Return (not our command) | |
| 8177 | EQUB &1C, &05, &0E, &E7, &04, &10, &16, &00, &02, &E5, &06, &20 |
| 8183 | .svc_4_star_command |
| LDX #8 ; X=8: compare 8 chars for *ROFF | |
| 8185 | JSR match_rom_string ; Try matching *ROFF command |
| 8188 | 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. |
|
| 818A | .net_4_resume_remote |
| LDY #4 ; Y=4: offset of keyboard disable flag | |
| 818C | LDA (net_rx_ptr),y ; Read flag from RX buffer |
| 818E | BEQ skip_kbd_reenable ; Zero: keyboard not disabled, skip |
| 8190 | LDA #0 ; A=0: value to clear flag and re-enable |
| 8192 | TAX ; X=&00 |
| 8193 | STA (net_rx_ptr),y ; Clear keyboard disable flag in buffer |
| 8195 | TAY ; Y=&00 |
| 8196 | LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable |
| 8198 | JSR osbyte ; Re-enable keyboard (X=0, Y=0) Enable keyboard (for Econet) |
| 819B | LDA #&0a ; Function &0A: remote operation complete |
| 819D | JSR setup_tx_and_send ; Send notification to controlling station |
| 81A0 | .clear_osbyte_ce_cf←1← 8496 JSR |
| STX nfs_workspace ; Save X (return value from TX) | |
| 81A2 | LDA #&ce ; OSBYTE &CE: first system mask to reset |
| 81A4 | .clear_osbyte_masks←1← 81AF BEQ |
| LDX nfs_workspace ; Restore X for OSBYTE call | |
| 81A6 | LDY #&7f ; Y=&7F: AND mask (clear bit 7) |
| 81A8 | JSR osbyte ; Reset system mask byte |
| 81AB | ADC #1 ; Advance to next OSBYTE (&CE -> &CF) |
| 81AD | CMP #&d0 ; Reached &D0? (past &CF) |
| 81AF | BEQ clear_osbyte_masks ; No: reset &CF too |
| 81B1 | .skip_kbd_reenable←1← 818E BEQ |
| LDA #0 ; A=0: clear remote state | |
| 81B3 | STA rom_svc_num ; Clear &A9 (service dispatch state) |
| 81B5 | STA nfs_workspace ; Clear workspace byte |
| 81B7 | RTS ; Return |
| 81B8 | .match_net_cmd←1← 8188 BNE |
| LDX #1 ; X=1: ROM offset for "NET" match | |
| 81BA | JSR match_rom_string ; Try matching *NET command |
| 81BD | BNE restore_y_return ; No match: return unclaimed |
| fall through ↓ | |
Select NFS as active filing system (INIT)Reached from service &12 (select FS) with Y=5, or when *NET command selects NFS. Notifies the current FS of shutdown via FSCV A=6 — this triggers the outgoing FS to save its context back to its workspace page, allowing restoration if re-selected later (the FSDIE handoff mechanism). Then sets up the standard OS vector indirections (FILEV through FSCV) to NFS entry points, claims the extended vector table entries, and issues service &0F (vectors claimed) to notify other ROMs. If fs_temp_cd is zero (auto-boot not inhibited), injects the synthetic command "I .BOOT" through the command decoder to trigger auto-boot login. |
|
| 81BF | .select_nfs←1← 8156 BEQ |
| JSR call_fscv_shutdown ; Notify current FS of shutdown (FSCV A=6) | |
| 81C2 | SEC ; C=1 for ROR |
| 81C3 | ROR nfs_temp ; Set bit 7 of l00a8 (inhibit auto-boot) |
| 81C5 | JSR issue_vectors_claimed ; Claim OS vectors, issue service &0F |
| 81C8 | LDY #&1d ; Y=&1D: top of FS state range |
| 81CA | .initl←1← 81D2 BNE |
| LDA (net_rx_ptr),y ; Copy FS state from RX buffer... | |
| 81CC | STA fs_state_deb,y ; ...to workspace (offsets &15-&1D) |
| 81CF | DEY ; Next byte (descending) |
| 81D0 | CPY #&14 ; Loop until offset &14 done |
| 81D2 | BNE initl ; Continue loop |
| 81D4 | BEQ init_fs_vectors ; ALWAYS branch to init_fs_vectors ALWAYS branch |
Match command text against ROM string tableCompares characters from (os_text_ptr)+Y against bytes starting at binary_version+X (&8008+X). Input is uppercased via AND &DF. Returns with Z=1 if the ROM string's NUL terminator was reached (match), or Z=0 if a mismatch was found. On match, Y points past the matched text; on return, skips trailing spaces. |
|
| 81D6 | .match_rom_string←2← 8185 JSR← 81BA JSR |
| LDY nfs_temp ; Y = saved text pointer offset | |
| 81D8 | .match_next_char←1← 81E5 BNE |
| LDA (os_text_ptr),y ; Load next input character | |
| 81DA | AND #&df ; Force uppercase (clear bit 5) |
| 81DC | BEQ cmd_name_matched ; Input char is NUL/space: check ROM byte |
| 81DE | CMP binary_version,x ; Compare with ROM string byte |
| 81E1 | BNE cmd_name_matched ; Mismatch: check if ROM string ended |
| 81E3 | INY ; Advance input pointer |
| 81E4 | INX ; Advance ROM string pointer |
| 81E5 | BNE match_next_char ; Continue matching (always taken) |
| 81E7 | .cmd_name_matched←2← 81DC BEQ← 81E1 BNE |
| LDA binary_version,x ; Load ROM string byte at match point | |
| 81EA | BEQ skip_cmd_spaces ; Zero = end of ROM string = full match |
| 81EC | RTS ; Non-zero = partial/no match; Z=0 |
| 81ED | .skpspi←1← 81F2 BEQ |
| INY ; Skip this space | |
| 81EE | .skip_cmd_spaces←1← 81EA BEQ |
| LDA (os_text_ptr),y ; Load next input character | |
| 81F0 | CMP #&20 ; Is it a space? |
| 81F2 | BEQ skpspi ; Yes: keep skipping |
| 81F4 | EOR #&0d ; XOR with CR: Z=1 if end of line |
| 81F6 | RTS ; Return (not our service call) |
Service 9: *HELPPrints the ROM identification string using print_inline. |
|
| 81F7 | .svc_9_help |
| JSR print_inline ; Print inline ROM identification string | |
| 81FA | EQUS ".NFS 3.35d." |
| 8205 | .restore_y_return←2← 81BD BNE← 821A BNE |
| LDY nfs_temp ; Reload saved character counter | |
| 8207 | 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). |
|
| 8208 | .call_fscv_shutdown←2← 81BF JSR← 820D JSR |
| LDA #6 ; FSCV reason 6 = FS shutdown | |
| 820A | 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. |
|
| 820D | .svc_3_autoboot |
| JSR call_fscv_shutdown ; Notify current FS of shutdown | |
| 8210 | LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard |
| 8212 | JSR osbyte ; Keyboard scan starting from key 16 |
| 8215 | TXA ; X is key number if key is pressed, or &ff otherwise |
| 8216 | 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. |
|
| 8218 | .check_boot_key |
| EOR #&55 ; XOR with &55: result=0 if key is 'N' | |
| 821A | BNE restore_y_return ; Not 'N': return without claiming |
| 821C | TAY ; Y=key |
| 821D | LDA #osbyte_write_keys_pressed ; OSBYTE &78: clear key-pressed state |
| 821F | 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. |
|
| 8222 | .print_station_info←1← 8216 BMI |
| JSR print_inline ; Print 'Econet Station ' banner | |
| 8225 | EQUS "Econet Station " |
| 8234 | LDY #&14 ; Y=&14: offset for station number |
| 8236 | LDA (net_rx_ptr),y ; Load station number |
| 8238 | JSR print_decimal ; Print as 3-digit decimal |
| 823B | LDA #&20 ; BIT trick: bit 5 of SR2 = clock present |
| 823D | BIT econet_control23_or_status2 ; Test DCD: clock present if bit 5 clear |
| 8240 | .dofsl1 |
| BEQ skip_no_clock_msg ; Clock present: skip warning | |
| 8242 | JSR print_inline ; Print ' No Clock' warning |
| 8245 | EQUS " No Clock" |
| 824E | NOP ; NOP (padding after inline string) |
| 824F | .skip_no_clock_msg←1← 8240 BEQ |
| JSR print_inline ; Print two CRs (blank line) | |
| 8252 | EQUS ".." |
| fall through ↓ | |
Initialise filing system vectorsCopies 14 bytes from fs_vector_addrs (&828A) into FILEV-FSCV (&0212), setting all 7 filing system vectors to the extended vector dispatch addresses (&FF1B-&FF2D). Calls setup_rom_ptrs_netv to install the ROM pointer table entries with the actual NFS handler addresses. Also reached directly from select_nfs, bypassing the station display. Falls through to issue_vectors_claimed. |
|
| 8254 | .init_fs_vectors←1← 81D4 BEQ |
| LDY #&0d ; Copy 14 bytes: FS vector addresses → FILEV-FSCV | |
| 8256 | .init_vector_copy_loop←1← 825D BPL |
| LDA fs_vector_addrs,y ; Load vector address from table | |
| 8259 | STA filev,y ; Write to FILEV-FSCV vector table |
| 825C | DEY ; Next byte (descending) |
| 825D | BPL init_vector_copy_loop ; Loop until all 14 bytes copied |
| 825F | JSR setup_rom_ptrs_netv ; Read ROM ptr table addr, install NETV |
| 8262 | LDY #&1b ; Install 7 handler entries in ROM ptr table |
| 8264 | LDX #7 ; 7 FS vectors to install |
| 8266 | JSR store_rom_ptr_pair ; Install each 3-byte vector entry |
| 8269 | STX rom_svc_num ; X=0 after loop; store as workspace offset |
| fall through ↓ | |
Issue 'vectors claimed' service and optionally auto-bootIssues service &0F (vectors claimed) via OSBYTE &8F, then service &0A. If fs_temp_cd is zero (auto-boot not inhibited), sets up the command string "I .BOOT" at &8282 and jumps to the FSCV 3 unrecognised-command handler (which matches against the command table at &8BE2). The "I." prefix triggers the catch-all entry which forwards the command to the fileserver. Falls through to run_fscv_cmd. |
|
| 826B | .issue_vectors_claimed←1← 81C5 JSR |
| LDA #osbyte_issue_service_request ; A=&8F: issue service request | |
| 826D | LDX #&0f ; X=&0F: 'vectors claimed' service |
| 826F | JSR osbyte ; Issue paged ROM service call, Reason X=15 - Vectors claimed |
| 8272 | LDX #&0a ; X=&0A: service &0A |
| 8274 | JSR osbyte ; Issue service &0A |
| 8277 | LDX nfs_temp ; Non-zero after hard reset: skip auto-boot |
| 8279 | BNE return_3 ; Non-zero: skip auto-boot |
| 827B | LDX #&82 ; X = lo byte of auto-boot string at &8292 |
| fall through ↓ | |
Run FSCV command from ROMSets Y to the ROM page high byte (&82) and jumps to fscv_3_star_cmd to execute the command string at (X, Y). X is pre-loaded by the caller with the low byte of the string address. Also used as a data base address by store_rom_ptr_pair for Y-indexed access to the handler address table. |
|
| 827D | .run_fscv_cmd←2← 8329 LDA← 832F LDA |
| LDY #&82 ; Y=&82: ROM page high byte | |
| 827F | JMP fscv_3_star_cmd ; Execute command string at (X, Y) |
| ; Synthetic auto-boot command string. "I " does not match any | |
| ; entry in NFS's local command table — "I." requires a dot, and | |
| ; "I AM" requires 'A' after the space — so fscv_3_star_cmd | |
| ; forwards the entire string to the fileserver, which executes | |
| ; the .BOOT file. | |
| 8282 | EQUS "I .BOOT." ; Auto-boot string tail / NETV handler data |
FS vector dispatch and handler addresses (34 bytes)Bytes 0-13: extended vector dispatch addresses, copied to FILEV-FSCV (&0212) by init_fs_vectors. Each 2-byte pair is a dispatch address (&FF1B-&FF2D) that the MOS uses to look up the handler in the ROM pointer table. Bytes 14-33: handler address pairs read by store_rom_ptr_pair. Each entry has addr_lo, addr_hi, then a padding byte that is not read at runtime (store_rom_ptr_pair writes the current ROM bank number without reading). The last entry (FSCV) has no padding byte. |
|
| 828A | .fs_vector_addrs←1← 8256 LDA |
| EQUB &1B ; FILEV dispatch lo | |
| 828B | EQUB &FF ; FILEV dispatch hi |
| 828C | EQUB &1E ; ARGSV dispatch lo |
| 828D | EQUB &FF ; ARGSV dispatch hi |
| 828E | EQUB &21 ; BGETV dispatch lo |
| 828F | EQUB &FF ; BGETV dispatch hi |
| 8290 | EQUB &24 ; BPUTV dispatch lo |
| 8291 | EQUB &FF ; BPUTV dispatch hi |
| 8292 | EQUB &27 ; GBPBV dispatch lo |
| 8293 | EQUB &FF ; GBPBV dispatch hi |
| 8294 | EQUB &2A ; FINDV dispatch lo |
| 8295 | EQUB &FF ; FINDV dispatch hi |
| 8296 | EQUB &2D ; FSCV dispatch lo |
| 8297 | EQUB &FF ; FSCV dispatch hi |
| 8298 | EQUB &E7 ; FILEV handler lo (&86E7) |
| 8299 | EQUB &86 ; FILEV handler hi |
| 829A | EQUB &4A ; (ROM bank — not read) |
| 829B | EQUB &0C ; ARGSV handler lo (&890C) |
| 829C | EQUB &89 ; ARGSV handler hi |
| 829D | EQUB &44 ; (ROM bank — not read) |
| 829E | EQUB &39 ; BGETV handler lo (&8539) |
| 829F | EQUB &85 ; BGETV handler hi |
| 82A0 | EQUB &57 ; (ROM bank — not read) |
| 82A1 | EQUB &EC ; BPUTV handler lo (&83EC) |
| 82A2 | EQUB &83 ; BPUTV handler hi |
| 82A3 | EQUB &42 ; (ROM bank — not read) |
| 82A4 | EQUB &10 ; GBPBV handler lo (&8A10) |
| 82A5 | EQUB &8A ; GBPBV handler hi |
| 82A6 | EQUB &41 ; (ROM bank — not read) |
| 82A7 | EQUB &78 ; FINDV handler lo (&8978) |
| 82A8 | EQUB &89 ; FINDV handler hi |
| 82A9 | EQUB &52 ; (ROM bank — not read) |
| 82AA | EQUB &C7 ; FSCV handler lo (&80C7) |
| 82AB | EQUB &80 ; FSCV handler hi |
Service 1: claim absolute workspaceClaims pages up to &10 for NMI workspace (&0D), FS state (&0E), and FS command buffer (&0F). If Y >= &10, workspace already allocated — returns unchanged.
|
|||||||
| 82AC | .svc_1_abs_workspace | ||||||
| CPY #&10 ; Already at page &10 or above? | |||||||
| 82AE | BCS return_3 ; Yes: nothing to claim | ||||||
| 82B0 | LDY #&10 ; Claim pages &0D-&0F (3 pages) | ||||||
| 82B2 | .return_3←2← 8279 BNE← 82AE BCS | ||||||
| RTS ; Return (workspace claim done) | |||||||
| 82B3 | EQUB &7C, &90 | ||||||
Service 2: claim private workspace and initialise NFSY = next available workspace page on entry. Sets up net_rx_ptr (Y) and nfs_workspace (Y+1) page pointers. On soft break (OSBYTE &FD returns 0): skips FS state init, preserving existing login state, file server selection, and control block configuration — this is why pressing BREAK keeps the user logged in. On power-up/CTRL-BREAK (result non-zero): - Sets FS server station to &FE (FS, the default; no server) - Sets printer server to &EB (PS, the default) - Clears FS handles, OPT byte, message flag, SEQNOS - Initialises all RXCBs with &3F flag (available) In both cases: reads station ID from &FE18 (only valid during reset), calls adlc_init, enables user-level RX (LFLAG=&40).
|
|||||||
| 82B5 | .svc_2_private_workspace | ||||||
| STY net_rx_ptr_hi ; Store page as RX buffer high byte | |||||||
| 82B7 | INY ; Next page for NFS workspace | ||||||
| 82B8 | STY nfs_workspace_hi ; Store page as NFS workspace high | ||||||
| 82BA | LDA #0 ; A=0 for clearing workspace | ||||||
| 82BC | LDY #4 ; Y=4: remote status offset | ||||||
| 82BE | STA (net_rx_ptr),y ; Clear status byte in net receive buffer | ||||||
| 82C0 | LDY #&ff ; Y=&FF: used for later iteration | ||||||
| 82C2 | STA net_rx_ptr ; Clear RX ptr low byte | ||||||
| 82C4 | STA nfs_workspace ; Clear workspace ptr low byte | ||||||
| 82C6 | STA nfs_temp ; Clear RXCB iteration counter | ||||||
| 82C8 | STA tx_clear_flag ; Clear TX semaphore (no TX in progress) | ||||||
| 82CB | TAX ; X=0 for OSBYTE X=&00 | ||||||
| 82CC | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read type of last reset | ||||||
| 82CE | JSR osbyte ; Read type of last reset | ||||||
| 82D1 | TXA ; X = break type from OSBYTE result X=value of type of last reset | ||||||
| 82D2 | BEQ read_station_id ; Soft break (X=0): skip FS init | ||||||
| 82D4 | LDY #&15 ; Y=&15: printer station offset in RX buffer | ||||||
| 82D6 | LDA #&fe ; &FE = no server selected | ||||||
| 82D8 | STA fs_server_stn ; Station &FE = no server selected | ||||||
| 82DB | STA (net_rx_ptr),y ; Store &FE at printer station offset | ||||||
| 82DD | LDA #0 ; A=0 for clearing workspace fields | ||||||
| 82DF | STA fs_server_net ; Clear network number | ||||||
| 82E2 | STA prot_status ; Clear protection status | ||||||
| 82E5 | STA fs_messages_flag ; Clear message flag | ||||||
| 82E8 | STA fs_boot_option ; Clear boot option | ||||||
| 82EB | INY ; Y=&16 | ||||||
| 82EC | STA (net_rx_ptr),y ; Clear net number at RX buffer offset &16 | ||||||
| 82EE | LDY #3 ; Init printer server: station &FE, net 0 | ||||||
| 82F0 | STA (nfs_workspace),y ; Store net 0 at workspace offset 3 | ||||||
| 82F2 | DEY ; Y=2: printer station offset Y=&02 | ||||||
| 82F3 | LDA #&eb ; &FE = no printer server | ||||||
| 82F5 | STA (nfs_workspace),y ; Store &FE at printer station in workspace | ||||||
| 82F7 | .init_rxcb_entries←1← 8304 BNE | ||||||
| LDA nfs_temp ; Load RXCB counter | |||||||
| 82F9 | JSR calc_handle_offset ; Convert to workspace byte offset | ||||||
| 82FC | BCS read_station_id ; C=1: past max handles, done | ||||||
| 82FE | LDA #&3f ; Mark RXCB as available | ||||||
| 8300 | STA (nfs_workspace),y ; Write &3F flag to workspace | ||||||
| 8302 | INC nfs_temp ; Next RXCB number | ||||||
| 8304 | BNE init_rxcb_entries ; Loop for all RXCBs | ||||||
| 8306 | .read_station_id←2← 82D2 BEQ← 82FC BCS | ||||||
| LDA station_id_disable_net_nmis ; Read station ID (also INTOFF) | |||||||
| 8309 | LDY #&14 ; Y=&14: station ID offset in RX buffer | ||||||
| 830B | STA (net_rx_ptr),y ; Store our station number | ||||||
| 830D | JSR trampoline_adlc_init ; Initialise ADLC hardware | ||||||
| 8310 | LDA #&40 ; Enable user-level RX (LFLAG=&40) | ||||||
| 8312 | 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=&9008, rom=current) into the ROM pointer table at offset &36, installing osword_dispatch as the NETV handler. |
|
| 8315 | .setup_rom_ptrs_netv←1← 825F JSR |
| LDA #osbyte_read_rom_ptr_table_low ; OSBYTE &A8: read ROM pointer table address | |
| 8317 | LDX #0 ; X=0: read low byte |
| 8319 | LDY #&ff ; Y=&FF: read high byte |
| 831B | JSR osbyte ; Returns table address in X (lo) Y (hi) Read address of ROM pointer table |
| 831E | STX osrdsc_ptr ; Store table base address low byte X=value of address of ROM pointer table (low byte) |
| 8320 | STY osrdsc_ptr_hi ; Store table base address high byte Y=value of address of ROM pointer table (high byte) |
| 8322 | LDY #&36 ; NETV extended vector offset in ROM ptr table |
| 8324 | STY netv ; Set NETV low byte = &36 (vector dispatch) |
| 8327 | LDX #1 ; Install 1 entry (NETV) in ROM ptr table |
| 8329 | .store_rom_ptr_pair←2← 8266 JSR← 833B BNE |
| LDA run_fscv_cmd,y ; Load handler address low byte from table | |
| 832C | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 832E | INY ; Next byte |
| 832F | LDA run_fscv_cmd,y ; Load handler address high byte from table |
| 8332 | STA (osrdsc_ptr),y ; Store to ROM pointer table |
| 8334 | INY ; Next byte |
| 8335 | LDA romsel_copy ; Write current ROM bank number |
| 8337 | STA (osrdsc_ptr),y ; Store ROM number to ROM pointer table |
| 8339 | INY ; Advance to next entry position |
| 833A | DEX ; Count down entries |
| 833B | BNE store_rom_ptr_pair ; Loop until all entries installed |
| 833D | LDY nfs_workspace_hi ; Y = workspace high byte + 1 = next free page |
| 833F | INY ; Advance past workspace page |
| 8340 | RTS ; Return; Y = page after NFS workspace |
FSCV 6: Filing system shutdown / save state (FSDIE)Called when another filing system (e.g. DFS) is selected. Saves the current NFS context (FSLOCN station number, URD/CSD/LIB handles, OPT byte, etc.) from page &0E into the dynamic workspace backup area. This allows the state to be restored when *NET is re-issued later, without losing the login session. Finally calls OSBYTE &7B (printer driver going dormant) to release the Econet network printer on FS switch. |
|
| 8341 | .fscv_6_shutdown |
| LDY #&1d ; Copy 10 bytes: FS state to workspace backup | |
| 8343 | .fsdiel←1← 834B BNE |
| LDA fs_state_deb,y ; Load FS state byte at offset Y | |
| 8346 | STA (net_rx_ptr),y ; Store to workspace backup area |
| 8348 | DEY ; Next byte down |
| 8349 | CPY #&14 ; Offsets &15-&1D: server, handles, OPT, etc. |
| 834B | BNE fsdiel ; Loop for offsets &1D..&15 |
| 834D | LDA #osbyte_printer_driver_going_dormant ; A=&7B: printer driver going dormant |
| 834F | JMP osbyte ; Printer driver going dormant |
Initialise TX control block for FS reply on port &90Loads port &90 (PREPLY) into A, calls init_tx_ctrl_block to set up the TX control block, stores the port and control bytes, then decrements the control flag. Used by send_fs_reply_cmd to prepare for receiving the fileserver's reply. |
|
| 8352 | .init_tx_reply_port←1← 83CB JSR |
| LDA #&90 ; A=&90: FS reply port (PREPLY) | |
| 8354 | .init_tx_ctrl_port←1← 8840 JSR |
| JSR init_tx_ctrl_block ; Init TXCB from template | |
| 8357 | STA txcb_port ; Store port number in TXCB |
| 8359 | LDA #3 ; Control byte: 3 = transmit |
| 835B | STA txcb_start ; Store control byte in TXCB |
| 835D | DEC txcb_ctrl ; Decrement TXCB flag to arm TX |
| 835F | RTS ; Return after port setup |
Initialise TX control block at &00C0 from template |
|
| 8360 | .init_tx_ctrl_block←4← 8354 JSR← 83B4 JSR← 8405 JSR← 8FF3 LDA |
| PHA ; Preserve A across call | |
| 8361 | LDY #&0b ; Copy 12 bytes (Y=11..0) |
| 8363 | .fstxl1←1← 8374 BPL |
| LDA tx_ctrl_template,y ; Load template byte | |
| 8366 | STA txcb_ctrl,y ; Store to TX control block at &00C0 |
| 8369 | CPY #2 ; Y < 2: also copy FS server station/network |
| 836B | BPL fstxl2 ; Skip station/network copy for Y >= 2 |
| 836D | LDA fs_server_stn,y ; Load FS server station (Y=0) or network (Y=1) |
| 8370 | STA txcb_dest,y ; Store to dest station/network at &00C2 |
| 8373 | .fstxl2←1← 836B BPL |
| DEY ; Next byte (descending) | |
| 8374 | BPL fstxl1 ; Loop until all 12 bytes copied |
| 8376 | PLA ; Restore A |
| 8377 | RTS ; Return |
| 8378 | .tx_ctrl_template←1← 8363 LDA |
| EQUB &80 ; Control flag | |
| 8379 | EQUB &99 ; Port (FS command = &99) |
| 837A | EQUB &00 ; Station (filled at runtime) |
| 837B | EQUB &00 ; Network (filled at runtime) |
| 837C | EQUB &00 ; Buffer start low |
| 837D | EQUB &0F ; Buffer start high (page &0F) |
| 837E | .tx_ctrl_upper←3← 88C0 BIT← 899B BIT← 9171 BIT |
| EQUB &FF ; Buffer start pad (4-byte Econet addr) | |
| 837F | EQUB &FF ; Buffer start pad |
| 8380 | EQUB &FF ; Buffer end low |
| 8381 | EQUB &0F ; Buffer end high (page &0F) |
| 8382 | EQUB &FF ; Buffer end pad |
| 8383 | EQUB &FF ; Buffer end pad |
Prepare FS command with carry setAlternate entry to prepare_fs_cmd that pushes A, loads &2A into fs_error_ptr, and enters with carry set (SEC). The carry flag is later tested by build_send_fs_cmd to select the byte-stream (BSXMIT) transmission path.
|
||||||
| 8384 | .prepare_cmd_with_flag←1← 8A61 JSR | |||||
| PHA ; Save flag byte for command | ||||||
| 8385 | LDA #&2a ; A=&2A: error ptr for retry | |||||
| 8387 | SEC ; C=1: include flag in FS command | |||||
| 8388 | BCS store_fs_hdr_fn ; ALWAYS branch to prepare_fs_cmd ALWAYS branch | |||||
| 838A | .prepare_cmd_clv←2← 8704 JSR← 87AD JSR | |||||
| CLV ; V=0: command has no flag byte | ||||||
| 838B | 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 &8BE2 for "BYE". |
|
| 838D | .bye_handler |
| LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec | |
| 838F | JSR osbyte ; Close any *SPOOL and *EXEC files |
| 8392 | 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.
|
|||||||||||||
| 8394 | .prepare_fs_cmd←12← 80B8 JSR← 8865 JSR← 88DB JSR← 8927 JSR← 894E JSR← 89C1 JSR← 89E6 JSR← 8ABC JSR← 8B72 JSR← 8C1B JSR← 8C52 JSR← 8CBF JSR | ||||||||||||
| CLV ; V=0: standard FS command path | |||||||||||||
| 8395 | .init_tx_ctrl_data←2← 88C3 JSR← 899E JSR | ||||||||||||
| .prepare_fs_cmd_v←2← 88C3 JSR← 899E JSR | |||||||||||||
| LDA fs_urd_handle ; Copy URD handle from workspace to buffer | |||||||||||||
| 8398 | STA fs_cmd_urd ; Store URD at &0F02 | ||||||||||||
| 839B | LDA #&2a ; A=&2A: error ptr for retry | ||||||||||||
| 839D | .store_fs_hdr_clc←1← 838B BVC | ||||||||||||
| CLC ; CLC: no byte-stream path | |||||||||||||
| 839E | .store_fs_hdr_fn←1← 8388 BCS | ||||||||||||
| STY fs_cmd_y_param ; Store function code at &0F01 | |||||||||||||
| 83A1 | STA fs_error_ptr ; Store error ptr for TX poll | ||||||||||||
| 83A3 | LDY #1 ; Y=1: copy CSD (offset 1) then LIB (offset 0) | ||||||||||||
| 83A5 | .copy_dir_handles←1← 83AC BPL | ||||||||||||
| LDA fs_csd_handle,y ; Copy CSD and LIB handles to command buffer A=timeout period for FS reply | |||||||||||||
| 83A8 | STA fs_cmd_csd,y ; Store at &0F03 (CSD) and &0F04 (LIB) | ||||||||||||
| 83AB | DEY ; Y=function code | ||||||||||||
| 83AC | BPL copy_dir_handles ; Loop for both handles | ||||||||||||
| fall through ↓ | |||||||||||||
Build and send FS command (DOFSOP)Sets reply port to &90 (PREPLY) at &0F00, initialises the TX control block, then adjusts TXCB's high pointer (HPTR) to X+5 -- the 5-byte FS header (reply port, function code, URD, CSD, LIB) plus the command data -- so only meaningful bytes are transmitted, conserving Econet bandwidth. If carry is set on entry (DOFSBX byte-stream path), takes the alternate path through econet_tx_retry for direct BSXMIT transmission. Otherwise sets up the TX pointer via setup_tx_ptr_c0 and falls through to send_fs_reply_cmd for reply handling. The carry flag is the sole discriminator between byte-stream and standard FS protocol paths -- set by SEC at the BPUTV/BGETV entry points. On return from WAITFS/BSXMIT, Y=0; INY advances past the command code to read the return code. Error &D6 ("not found", on_entry={"x": "buffer extent (command-specific data bytes)", "y": "function code", "a": "timeout period for FS reply", "c": "0 for standard FS path, 1 for byte-stream (BSXMIT)"}, on_exit={"a": "0 on success", "x": "0 on success, &D6 on not-found", "y": "1 (offset past command code in reply)"}) is detected via ADC #(&100-&D6) with C=0 -- if the return code was exactly &D6, the result wraps to zero (Z=1). This is a branchless comparison returning C=1, A=0 as a soft error that callers can handle, vs hard errors which go through FSERR.
|
|||||||||||||||||
| 83AE | .build_send_fs_cmd←1← 8B15 JSR | ||||||||||||||||
| PHP ; Save carry (FS path vs byte-stream) | |||||||||||||||||
| 83AF | LDA #&90 ; Reply port &90 (PREPLY) | ||||||||||||||||
| 83B1 | STA fs_cmd_type ; Store at &0F00 (HDRREP) | ||||||||||||||||
| 83B4 | JSR init_tx_ctrl_block ; Copy TX template to &00C0 | ||||||||||||||||
| 83B7 | TXA ; A = X (buffer extent) | ||||||||||||||||
| 83B8 | ADC #5 ; HPTR = header (5) + data (X) bytes to send | ||||||||||||||||
| 83BA | STA txcb_end ; Store to TXCB end-pointer low | ||||||||||||||||
| 83BC | PLP ; Restore carry flag | ||||||||||||||||
| 83BD | BCS dofsl5 ; C=1: byte-stream path (BSXMIT) | ||||||||||||||||
| 83BF | PHP ; Save flags for send_fs_reply_cmd | ||||||||||||||||
| 83C0 | JSR setup_tx_ptr_c0 ; Point net_tx_ptr to &00C0; transmit | ||||||||||||||||
| 83C3 | PLP ; Restore flags | ||||||||||||||||
| 83C4 | BCC send_fs_reply_cmd ; C=0: standard FS command path | ||||||||||||||||
| fall through ↓ | |||||||||||||||||
Send FS command with standard timeoutWrapper for send_fs_reply_cmd that sets the timeout counter (fs_error_ptr at &B8) to &2A before falling through. The &2A value becomes the outer loop count in send_to_fs's 3-level polling loop (~42 x 65536 iterations). Called after file transfer operations to send the completion command to the fileserver. Eliminated in 3.35K where call sites inline the LDA #&2A / STA fs_error_ptr sequence directly. |
|
| 83C6 | .send_fs_reply_timed←2← 87B9 JSR← 8A99 JSR |
| LDA #&2a ; A=&2A: error ptr for retry | |
| 83C8 | STA fs_error_ptr ; Store error ptr for TX poll |
| 83CA | .send_fs_reply_cmd←1← 83C4 BCC |
| PHP ; Save flags (V flag state) | |
| 83CB | JSR init_tx_reply_port ; Set up RX wait for FS reply |
| 83CE | LDA fs_error_ptr ; Load error ptr for TX retry |
| 83D0 | JSR send_to_fs ; Transmit and wait (BRIANX) |
| 83D3 | PLP ; Restore flags |
| 83D4 | .dofsl7←1← 83EA BCC |
| INY ; Y=1: skip past command code byte | |
| 83D5 | LDA (txcb_start),y ; Load return code from FS reply |
| 83D7 | TAX ; X = return code |
| 83D8 | BEQ return_dofsl7 ; Zero: success, return |
| 83DA | BVC check_fs_error ; V=0: standard path, error is fatal |
| 83DC | ADC #&2a ; ADC #&2A: test for &D6 (not found) |
| 83DE | .check_fs_error←1← 83DA BVC |
| BNE store_fs_error ; Non-zero: hard error, go to FSERR | |
| 83E0 | .return_dofsl7←1← 83D8 BEQ |
| RTS ; Return (success or soft &D6 error) | |
| 83E1 | .dofsl5←1← 83BD BCS |
| PLA ; Discard saved flags from stack | |
| 83E2 | LDX #&c0 ; X=&C0: TXCB address for byte-stream TX |
| 83E4 | INY ; Y++ past command code |
| 83E5 | JSR econet_tx_retry ; Byte-stream transmit with retry |
| 83E8 | STA fs_load_addr_3 ; Store result to &B3 |
| 83EA | BCC dofsl7 ; C=0: success, check reply code |
| 83EC | .bputv_handler |
| CLC ; CLC for address addition | |
| fall through ↓ | |
Handle BPUT/BGET file byte I/OBPUTV enters at &83A3 (CLC; fall through) and BGETV enters at &8486 (SEC; JSR here). The carry flag is preserved via PHP/PLP through the call chain and tested later (BCS) to select byte-stream transmission (BSXMIT) vs normal FS transmission (FSXMIT) -- a control-flow encoding using processor flags to avoid an extra flag variable. BSXMIT uses handle=0 for print stream transactions (which sidestep the SEQNOS sequence number manipulation) and non-zero handles for file operations. After transmission, the high pointer bytes of the CB are reset to &FF -- "The BGET/PUT byte fix" which prevents stale buffer pointers corrupting subsequent byte-level operations.
|
|||||||||||||||
| 83ED | .handle_bput_bget←1← 853A JSR | ||||||||||||||
| PHA ; Save A (BPUT byte) on stack | |||||||||||||||
| 83EE | STA fs_error_flags ; Also save byte at &0FDF for BSXMIT | ||||||||||||||
| 83F1 | TXA ; Transfer X for stack save | ||||||||||||||
| 83F2 | PHA ; Save X on stack | ||||||||||||||
| 83F3 | TYA ; Transfer Y (handle) for stack save | ||||||||||||||
| 83F4 | PHA ; Save Y (handle) on stack | ||||||||||||||
| 83F5 | PHP ; Save P (C = BPUT/BGET selector) on stack | ||||||||||||||
| 83F6 | STY fs_spool_handle ; Save handle for SPOOL/EXEC comparison later | ||||||||||||||
| 83F8 | JSR handle_to_mask_clc ; Convert handle Y to single-bit mask | ||||||||||||||
| 83FB | STY fs_handle_mask ; Store handle bitmask at &0FDE | ||||||||||||||
| 83FE | STY fs_spool0 ; Store handle bitmask for sequence tracking | ||||||||||||||
| 8400 | LDY #&90 ; &90 = data port (PREPLY) | ||||||||||||||
| 8402 | STY fs_putb_buf ; Store reply port in command buffer | ||||||||||||||
| 8405 | JSR init_tx_ctrl_block ; Set up 12-byte TXCB from template | ||||||||||||||
| 8408 | LDA #&dc ; CB reply buffer at &0FDC | ||||||||||||||
| 840A | STA txcb_start ; Store reply buffer ptr low in TXCB | ||||||||||||||
| 840C | LDA #&e0 ; Error buffer at &0FE0 | ||||||||||||||
| 840E | STA txcb_end ; Store error buffer ptr low in TXCB | ||||||||||||||
| 8410 | INY ; Y=1 (from init_tx_ctrl_block exit) | ||||||||||||||
| 8411 | LDX #9 ; X=9: BPUT function code | ||||||||||||||
| 8413 | PLP ; Restore C: selects BPUT (0) vs BGET (1) | ||||||||||||||
| 8414 | BCC store_retry_count ; C=0 (BPUT): keep X=9 | ||||||||||||||
| 8416 | DEX ; X=&08 | ||||||||||||||
| 8417 | .store_retry_count←1← 8414 BCC | ||||||||||||||
| STX fs_getb_buf ; Store function code at &0FDD | |||||||||||||||
| 841A | LDA fs_spool0 ; Load handle bitmask for BSXMIT | ||||||||||||||
| 841C | LDX #&c0 ; X=&C0: TXCB address for econet_tx_retry | ||||||||||||||
| 841E | JSR econet_tx_retry ; Transmit via byte-stream protocol | ||||||||||||||
| 8421 | LDX fs_getb_buf ; Load reply byte from buffer | ||||||||||||||
| 8424 | BEQ update_sequence_return ; Zero reply = success, skip error handling | ||||||||||||||
| 8426 | LDY #&1f ; Copy 32-byte reply to error buffer at &0FE0 | ||||||||||||||
| 8428 | .error1←1← 842F BPL | ||||||||||||||
| LDA fs_putb_buf,y ; Load reply byte at offset Y | |||||||||||||||
| 842B | STA fs_error_buf,y ; Store to error buffer at &0FE0+Y | ||||||||||||||
| 842E | DEY ; Next byte (descending) | ||||||||||||||
| 842F | BPL error1 ; Loop until all 32 bytes copied | ||||||||||||||
| 8431 | TAX ; X=File handle | ||||||||||||||
| 8432 | LDA #osbyte_read_write_exec_file_handle ; A=&C6: read *EXEC file handle | ||||||||||||||
| 8434 | JSR osbyte ; Read/Write *EXEC file handle | ||||||||||||||
| 8437 | LDA #&f1 ; A=&F1: OSCLI "SP." string at &84F1 | ||||||||||||||
| 8439 | CPY fs_spool_handle ; Y=value of *SPOOL file handle | ||||||||||||||
| 843B | BEQ close_spool_exec ; Match: close SPOOL file | ||||||||||||||
| 843D | LDA #&f5 ; A=&F5: offset for 'E.' string | ||||||||||||||
| 843F | CPX fs_spool_handle ; X=value of *EXEC file handle | ||||||||||||||
| 8441 | BNE dispatch_fs_error ; No match: dispatch FS error | ||||||||||||||
| 8443 | .close_spool_exec←1← 843B BEQ | ||||||||||||||
| TAX ; X = string offset for OSCLI close | |||||||||||||||
| 8444 | LDY #&84 ; Y=&84: high byte of OSCLI string in ROM | ||||||||||||||
| 8446 | JSR oscli ; Close SPOOL/EXEC via "*SP." or "*E." | ||||||||||||||
| 8449 | .dispatch_fs_error←1← 8441 BNE | ||||||||||||||
| LDA #&e0 ; Reset CB pointer to error buffer at &0FE0 | |||||||||||||||
| 844B | STA txcb_start ; Reset reply ptr to error buffer | ||||||||||||||
| 844D | 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. |
|
| 8450 | .store_fs_error←1← 83DE BNE |
| STX fs_last_error ; Remember raw FS error code | |
| 8453 | LDY #1 ; Y=1: point to error number byte in reply |
| 8455 | CPX #&a8 ; Clamp FS errors below &A8 to standard &A8 |
| 8457 | BCS error_code_clamped ; Error >= &A8: keep original value |
| 8459 | LDA #&a8 ; Error < &A8: override with standard &A8 |
| 845B | STA (txcb_start),y ; Write clamped error number to reply buffer |
| 845D | .error_code_clamped←1← 8457 BCS |
| LDY #&ff ; Start scanning from offset &FF (will INY to 0) | |
| 845F | .copy_error_to_brk←1← 8467 BNE |
| INY ; Next byte in reply buffer | |
| 8460 | LDA (txcb_start),y ; Copy reply buffer to &0100 for BRK execution |
| 8462 | STA l0100,y ; Build BRK error block at &0100 |
| 8465 | EOR #&0d ; Scan for CR terminator (&0D) |
| 8467 | BNE copy_error_to_brk ; Continue until CR found |
| 8469 | STA l0100,y ; Replace CR with zero = BRK error block end |
| 846C | BEQ execute_downloaded ; Execute as BRK error block at &0100; ALWAYS ALWAYS branch |
| 846E | .update_sequence_return←1← 8424 BEQ |
| STA fs_sequence_nos ; Save updated sequence number | |
| 8471 | PLA ; Restore Y from stack |
| 8472 | TAY ; Transfer A to Y for indexing |
| 8473 | PLA ; Restore X from stack |
| 8474 | TAX ; Transfer to X for return |
| 8475 | PLA ; Restore A from stack |
| 8476 | .return_remote_cmd |
| RTS ; Return to caller | |
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. |
|
| 8477 | .lang_1_remote_boot |
| LDY #4 ; Y=4: remote status flag offset | |
| 8479 | LDA (net_rx_ptr),y ; Read remote status from RX CB |
| 847B | BEQ remot1 ; Zero: not remoted, set up session |
| 847D | .rchex←1← 84C3 BNE |
| JMP clear_jsr_protection ; Already remoted: clear and return | |
| 8480 | .remot1←2← 847B BEQ← 84B9 BEQ |
| ORA #9 ; Set remote status: bits 0+3 (ORA #9) | |
| 8482 | STA (net_rx_ptr),y ; Store updated remote status |
| 8484 | LDX #&80 ; X=&80: RX data area offset |
| 8486 | LDY #&80 ; Y=&80: read source station low |
| 8488 | LDA (net_rx_ptr),y ; Read source station lo from RX data at &80 |
| 848A | PHA ; Save source station low byte |
| 848B | INY ; Y=&81 |
| 848C | LDA (net_rx_ptr),y ; Read source station hi from RX data at &81 |
| 848E | LDY #&0f ; Save controlling station to workspace &0E/&0F |
| 8490 | STA (nfs_workspace),y ; Store station high to ws+&0F |
| 8492 | DEY ; Y=&0E Y=&0e |
| 8493 | PLA ; Restore source station low |
| 8494 | STA (nfs_workspace),y ; Store station low to ws+&0E |
| 8496 | JSR clear_osbyte_ce_cf ; Clear OSBYTE &CE/&CF flags |
| 8499 | JSR ctrl_block_setup ; Set up TX control block |
| 849C | LDX #1 ; X=1: disable keyboard |
| 849E | LDY #0 ; Y=0 for OSBYTE |
| 84A0 | LDA #osbyte_read_write_econet_keyboard_disable ; Disable keyboard for remote session |
| 84A2 | JSR osbyte ; Disable keyboard (for Econet) |
| fall through ↓ | |
| 84A5 | .lang_3_execute_at_0100 |
| JSR clear_jsr_protection ; Allow JSR to page 1 (stack page) | |
| 84A8 | LDX #2 ; Zero bytes &0100-&0102 |
| 84AA | LDA #0 ; A=0: zero execution header bytes |
| 84AC | .zero_exec_header←1← 84B0 BPL |
| STA l0100,x ; BRK at &0100 as safe default | |
| 84AF | DEX ; Next byte |
| 84B0 | BPL zero_exec_header ; Loop until all zeroed |
| 84B2 | .execute_downloaded←2← 846C BEQ← 84EB BEQ |
| JMP l0100 ; 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. |
|
| 84B5 | .lang_4_remote_validated |
| LDY #4 ; Y=4: RX control block byte 4 (remote status) | |
| 84B7 | LDA (net_rx_ptr),y ; Read remote status flag |
| 84B9 | BEQ remot1 ; Zero = not remoted; allow new session |
| 84BB | LDY #&80 ; Read source station from RX data at &80 |
| 84BD | LDA (net_rx_ptr),y ; A = source station number |
| 84BF | LDY #&0e ; Compare against controlling station at &0E |
| 84C1 | CMP (nfs_workspace),y ; Check if source matches controller |
| 84C3 | 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. |
|
| 84C5 | .lang_0_insert_remote_key |
| LDY #&82 ; Read keypress from RX data at &82 | |
| 84C7 | LDA (net_rx_ptr),y ; Load character byte |
| 84C9 | TAY ; Y = character to insert |
| 84CA | LDX #0 ; X = buffer 0 (keyboard input) |
| 84CC | JSR clear_jsr_protection ; Release JSR protection before inserting key |
| 84CF | LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert char into input buffer |
| 84D1 | JMP osbyte ; Tail call: insert character Y into buffer X Insert character Y into input buffer X |
| 84D4 | .error_not_listening←1← 8527 BEQ |
| LDA #8 ; Error code 8: "Not listening" error | |
| 84D6 | BNE set_listen_offset ; ALWAYS branch to set_listen_offset ALWAYS branch |
| 84D8 | .nlistn←1← 86B2 JMP |
| LDA (net_tx_ptr,x) ; Load TX status byte for error lookup | |
| 84DA | .nlisne←2← 8537 BNE← 89DE JMP |
| AND #7 ; Mask to 3-bit error code (0-7) | |
| 84DC | .set_listen_offset←1← 84D6 BNE |
| TAX ; X = error code index | |
| 84DD | LDY error_offsets,x ; Look up error message offset from table |
| 84E0 | LDX #0 ; X=0: start writing at &0101 |
| 84E2 | STX l0100 ; Store BRK opcode at &0100 |
| 84E5 | .copy_error_message←1← 84EF BNE |
| LDA error_msg_table,y ; Load error message byte | |
| 84E8 | STA l0101,x ; Build error message at &0101+ |
| 84EB | BEQ execute_downloaded ; Zero byte = end of message; go execute BRK |
| 84ED | INY ; Next error message source byte |
| 84EE | INX ; Next error buffer position |
| 84EF | BNE copy_error_message ; Continue copying message |
| 84F1 | EQUS "SP." |
| 84F4 | EQUB &0D, &45, &2E, &0D |
| 84F8 | .send_to_fs_star←3← 875D JSR← 9036 JMP← 9290 JSR |
| LDA #&2a ; A=&2A: error ptr for FS send | |
| fall through ↓ | |
Send command to fileserver and handle reply (WAITFS)Performs a complete FS transaction: transmit then wait for reply. Sets bit 7 of rx_status_flags (mark FS transaction in progress), builds a TX frame from the data at (net_tx_ptr), and transmits it. The system RX flag (LFLAG bit 7) is only set when receiving into the page-zero control block — if RXCBP's high byte is non-zero, setting the system flag would interfere with other RX operations. The timeout counter uses the stack (indexed via TSX) rather than memory to avoid bus conflicts with Econet hardware during the tight polling loop. Handles multi-block replies and checks for escape conditions between blocks. |
|
| 84FA | .send_to_fs←2← 83D0 JSR← 884C JSR |
| PHA ; Save function code on stack | |
| 84FB | LDA rx_flags ; Load current rx_flags |
| 84FE | PHA ; Save rx_flags on stack for restore |
| 84FF | ORA #&80 ; Set bit7: FS transaction in progress |
| 8501 | STA rx_flags ; Write back updated rx_flags |
| 8504 | .skip_rx_flag_set |
| LDA #0 ; Push two zero bytes as timeout counters | |
| 8506 | PHA ; First zero for timeout |
| 8507 | PHA ; Second zero for timeout |
| 8508 | TAY ; Y=0: index for flag byte check Y=&00 |
| 8509 | TSX ; TSX: index stack-based timeout via X |
| 850A | .fs_reply_poll←3← 8514 BNE← 8519 BNE← 851E BNE |
| JSR check_escape ; Check for user escape condition | |
| 850D | .incpx |
| LDA (net_tx_ptr),y ; Read flag byte from TX control block | |
| 850F | BMI fs_wait_cleanup ; Bit 7 set = reply received |
| 8511 | DEC l0101,x ; Three-stage nested timeout: inner loop |
| 8514 | BNE fs_reply_poll ; Inner not expired: keep polling |
| 8516 | DEC l0102,x ; Middle timeout loop |
| 8519 | BNE fs_reply_poll ; Middle not expired: keep polling |
| 851B | DEC l0104,x ; Outer timeout loop (slowest) |
| 851E | BNE fs_reply_poll ; Outer not expired: keep polling |
| 8520 | .fs_wait_cleanup←1← 850F BMI |
| PLA ; Pop first timeout byte | |
| 8521 | PLA ; Pop second timeout byte |
| 8522 | PLA ; Pop saved rx_flags into A |
| 8523 | STA rx_flags ; Restore saved rx_flags from stack |
| 8526 | PLA ; Pop saved function code |
| 8527 | BEQ error_not_listening ; A=saved func code; zero would mean no reply |
| 8529 | RTS ; Return to caller |
Check and handle escape condition (ESC)Two-level escape gating: the MOS escape flag (&FF bit 7) is ANDed with the software enable flag ESCAP. Both must have bit 7 set for escape to fire. ESCAP is set non-zero during data port operations (LOADOP stores the data port &90, serving double duty as both the port number and the escape-enable flag). ESCAP is disabled via LSR in the ENTER routine, which clears bit 7 — PHP/PLP around the LSR preserves the carry flag since ENTER is called from contexts where carry has semantic meaning (e.g., PUTBYT vs BGET distinction). This architecture allows escape between retransmission attempts but prevents interruption during critical FS transactions. If escape fires: acknowledges via OSBYTE &7E, then checks whether the failing handle is the current SPOOL or EXEC handle (OSBYTE &C6/&C7); if so, issues "*SP." or "*E." via OSCLI to gracefully close the channel before raising the error — preventing the system from continuing to spool output to a broken file handle. |
|
| 852A | .check_escape←2← 850A JSR← 8699 JSR |
| LDA #&7e ; A=&7E: OSBYTE acknowledge escape | |
| fall through ↓ | |
Test MOS escape flag and abort if pendingTests MOS escape flag (&FF bit 7). If escape is pending: acknowledges via OSBYTE &7E, writes &3F (deleted marker) into the control block via (net_tx_ptr),Y, and branches to the NLISTN error path. If no escape, returns immediately. |
|
| 852C | .check_escape_handler |
| BIT escape_flag ; Test escape flag (bit 7) | |
| 852E | BPL return_4 ; Bit 7 clear: no escape, return |
| 8530 | JSR osbyte ; Acknowledge escape via OSBYTE &7E |
| 8533 | LSR ; LSR: get escape result bit |
| 8534 | STA (net_tx_ptr),y ; Store escape result to TXCB |
| 8536 | ASL ; Restore A |
| 8537 | BNE nlisne ; Non-zero: report 'Not listening' |
| 8539 | .bgetv_handler |
| SEC ; C=1: flag for BGET mode | |
| 853A | JSR handle_bput_bget ; Handle BGET via FS command Handle BPUT/BGET file byte I/O |
| 853D | SEC ; SEC: set carry for error check |
| 853E | LDA #&fe ; A=&FE: mask for EOF check |
| 8540 | BIT fs_error_flags ; BIT l0fdf: test error flags |
| 8543 | BVS return_4 ; V=1: error, return early |
| 8545 | CLC ; CLC: no error |
| 8546 | PHP ; Save P (C=0 from CLC above) |
| 8547 | LDA fs_spool0 ; Load handle bitmask |
| 8549 | PLP ; Restore C flag for branch test |
| 854A | BMI bgetv_shared_jsr ; Bit 7 set: skip clear, just set |
| 854C | JSR clear_fs_flag ; Clear EOF hint for this handle |
| 854F | .bgetv_shared_jsr←1← 854A BMI |
| JSR set_fs_flag ; Set EOF flag for this handle | |
| 8552 | .load_handle_mask |
| LDA fs_handle_mask ; Load handle bitmask for caller | |
| 8555 | .return_4←2← 852E BPL← 8543 BVS |
| RTS ; Return with handle mask in A | |
| ; 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 the error dispatch at c8424/c842c. | |
| 8556 | .error_msg_table←1← 84E5 LDA |
| EQUB &A0 | |
| 8557 | EQUS "Line Jammed." |
| 8563 | EQUB &A1 |
| 8564 | EQUS "Net Error." |
| 856E | EQUB &A2 |
| 856F | EQUS "Not listening." |
| 857D | EQUB &A3 |
| 857E | EQUS "No Clock." |
| 8587 | EQUB &11 |
| 8588 | EQUS "Escape." |
| 858F | EQUB &CB |
| 8590 | EQUS "Bad Option." |
| 859B | EQUB &A5 |
| 859C | EQUS "No reply." |
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. |
|
| 85A5 | .save_fscv_args_with_ptrs←3← 80C7 JSR← 8978 JSR← 8BB4 JSR |
| STX os_text_ptr ; X to os_text_ptr (text ptr lo) | |
| 85A7 | STY os_text_ptr_hi ; Y to os_text_ptr hi |
| 85A9 | STX fs_cmd_ptr ; X to FS command ptr lo |
| 85AC | STY l0e11 ; Y to FS command ptr hi |
| 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)
|
||||||||
| 85AF | .save_fscv_args←3← 86E7 JSR← 890C JSR← 8A10 JSR | |||||||
| STA fs_last_byte_flag ; A = function code / command | ||||||||
| 85B1 | STX fs_options ; X = control block ptr lo | |||||||
| 85B3 | STY fs_block_offset ; Y = control block ptr hi | |||||||
| 85B5 | STX fs_crc_lo ; X dup for indexed access via (fs_crc) | |||||||
| 85B7 | STY fs_crc_hi ; Y dup for indexed access | |||||||
| 85B9 | RTS ; Return | |||||||
Decode file attributes: FS → BBC format (FSBBC, 6-bit variant)Reads attribute byte at offset &0E from the parameter block, masks to 6 bits, then falls through to the shared bitmask builder. Converts fileserver protection format (5-6 bits) to BBC OSFILE attribute format (8 bits) via the lookup table at &85D7. The two formats use different bit layouts for file protection attributes. |
|
| 85BA | .decode_attribs_6bit←2← 889F JSR← 88CA JSR |
| LDY #&0e ; Y=&0E: attribute byte offset in param block | |
| 85BC | LDA (fs_options),y ; Load FS attribute byte |
| 85BE | AND #&3f ; Mask to 6 bits (FS → BBC direction) |
| 85C0 | LDX #4 ; X=4: skip first 4 table entries (BBC→FS half) |
| 85C2 | 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 &85D7. 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.
|
|||||||||
| 85C4 | .decode_attribs_5bit←2← 87C4 JSR← 88E7 JSR | ||||||||
| AND #&1f ; Mask to 5 bits (BBC → FS direction) | |||||||||
| 85C6 | LDX #&ff ; X=&FF: INX makes 0; start from table index 0 | ||||||||
| 85C8 | .attrib_shift_bits←1← 85C2 BNE | ||||||||
| STA fs_error_ptr ; Temp storage for source bitmask to shift out | |||||||||
| 85CA | LDA #0 ; A=0: accumulate destination bits here | ||||||||
| 85CC | .map_attrib_bits←1← 85D4 BNE | ||||||||
| INX ; Next table entry | |||||||||
| 85CD | LSR fs_error_ptr ; Shift out source bits one at a time | ||||||||
| 85CF | BCC skip_set_attrib_bit ; Bit was 0: skip this destination bit | ||||||||
| 85D1 | ORA access_bit_table,x ; OR in destination bit from lookup table | ||||||||
| 85D4 | .skip_set_attrib_bit←1← 85CF BCC | ||||||||
| BNE map_attrib_bits ; Loop while source bits remain (A != 0) | |||||||||
| 85D6 | RTS ; Return; A = converted attribute bitmask | ||||||||
| 85D7 | .access_bit_table←1← 85D1 ORA | ||||||||
| EQUB &50, &20, &05, &02, &88, &04, &08, &80, &10, &01, &02 | |||||||||
Print inline string, high-bit terminated (VSTRNG)Pops the return address from the stack, prints each byte via OSASCI until a byte with bit 7 set is found, then jumps to that address. The high-bit byte serves as both the string terminator and the opcode of the first instruction after the string. N.B. Cannot be used for BRK error messages -- the stack manipulation means a BRK in the inline data would corrupt the stack rather than invoke the error handler.
|
||||||||
| 85E2 | .print_inline←14← 81F7 JSR← 8222 JSR← 8242 JSR← 824F JSR← 8C23 JSR← 8C2D JSR← 8C3B JSR← 8C46 JSR← 8C5B JSR← 8C70 JSR← 8C83 JSR← 8C92 JSR← 8CA4 JSR← 8D74 JSR | |||||||
| PLA ; Pop return address (low) — points to last byte of JSR | ||||||||
| 85E3 | STA fs_load_addr ; Store return addr low as string ptr | |||||||
| 85E5 | PLA ; Pop return address (high) | |||||||
| 85E6 | STA fs_load_addr_hi ; Store return addr high as string ptr | |||||||
| 85E8 | LDY #0 ; Y=0: offset for indirect load | |||||||
| 85EA | .print_inline_char←1← 85F7 JMP | |||||||
| INC fs_load_addr ; Advance pointer past return address / to next char | ||||||||
| 85EC | BNE print_next_char ; No page wrap: skip high byte inc | |||||||
| 85EE | INC fs_load_addr_hi ; Handle page crossing in pointer | |||||||
| 85F0 | .print_next_char←1← 85EC BNE | |||||||
| LDA (fs_load_addr),y ; Load next byte from inline string | ||||||||
| 85F2 | BMI jump_via_addr ; Bit 7 set? Done — this byte is the next opcode | |||||||
| 85F4 | JSR osasci ; Write character | |||||||
| 85F7 | JMP print_inline_char ; Continue printing next character | |||||||
| 85FA | .jump_via_addr←1← 85F2 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", on_entry={"y": "offset into (fs_options) buffer"}, on_exit={"a": "parsed value (accumulated in &B2)", "x": "initial A value (saved by TAX)", "y": "offset past last digit parsed"}) >= &40 (any letter), but the revision allows numbers followed by dots.
|
|||||||||||
| 85FD | .parse_decimal←2← 808A JSR← 8090 JSR | ||||||||||
| TAX ; Save A in X for caller | |||||||||||
| 85FE | LDA #0 ; Zero accumulator | ||||||||||
| 8600 | STA fs_load_addr_2 ; Clear accumulator workspace | ||||||||||
| 8602 | .scan_decimal_digit←1← 861F BNE | ||||||||||
| LDA (fs_options),y ; Load next char from buffer | |||||||||||
| 8604 | CMP #&40 ; Letter or above? | ||||||||||
| 8606 | BCS no_dot_exit ; Yes: not a digit, done | ||||||||||
| 8608 | CMP #&2e ; Dot separator? | ||||||||||
| 860A | BEQ parse_decimal_rts ; Yes: exit with C=1 (dot found) | ||||||||||
| 860C | BMI no_dot_exit ; Control char or space: done | ||||||||||
| 860E | AND #&0f ; Mask ASCII digit to 0-9 | ||||||||||
| 8610 | STA fs_load_addr_3 ; Save new digit | ||||||||||
| 8612 | ASL fs_load_addr_2 ; Running total * 2 | ||||||||||
| 8614 | LDA fs_load_addr_2 ; A = running total * 2 | ||||||||||
| 8616 | ASL ; A = running total * 4 | ||||||||||
| 8617 | ASL ; A = running total * 8 | ||||||||||
| 8618 | ADC fs_load_addr_2 ; + total*2 = total * 10 | ||||||||||
| 861A | ADC fs_load_addr_3 ; + digit = total*10 + digit | ||||||||||
| 861C | STA fs_load_addr_2 ; Store new running total | ||||||||||
| 861E | INY ; Advance to next char | ||||||||||
| 861F | BNE scan_decimal_digit ; Loop (always: Y won't wrap to 0) | ||||||||||
| 8621 | .no_dot_exit←2← 8606 BCS← 860C BMI | ||||||||||
| CLC ; No dot found: C=0 | |||||||||||
| 8622 | .parse_decimal_rts←1← 860A BEQ | ||||||||||
| LDA fs_load_addr_2 ; Return result in A | |||||||||||
| 8624 | 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.
|
|||||||||||
| 8625 | .handle_to_mask_a←3← 8853 JSR← 8A2B JSR← 8F4D 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.
|
|||||||||||
| 8626 | .handle_to_mask_clc←2← 83F8 JSR← 8917 JSR | ||||||||||
| CLC ; Force unconditional conversion | |||||||||||
| fall through ↓ | |||||||||||
Convert file handle to bitmask (Y2FS)Converts fileserver handles to single-bit masks segregated inside the BBC. NFS handles occupy the &20-&27 range (base HAND=&20), which cannot collide with local filing system or cassette handles -- the MOS routes OSFIND/OSBGET/OSBPUT to the correct filing system based on the handle value alone. The power-of-two encoding allows the EOF hint byte to track up to 8 files simultaneously with one bit per file, and enables fast set operations (ORA to add, EOR to toggle, AND to test) without loops. Handle 0 passes through unchanged (means "no file", on_entry={"y": "handle number", "c": "0: convert, 1 with Y=0: skip, 1 with Y!=0: convert"}, on_exit={"a": "preserved", "x": "preserved", "y": "bitmask (single bit set) or &FF if handle invalid"}) has a built-in validity check: if the handle is out of range, the repeated ASL shifts all bits out, leaving A=0, which is converted to Y=&FF as a sentinel -- bad handles fail gracefully rather than indexing into garbage. Callers needing to move the handle from A use handle_to_mask_a; callers needing carry cleared use handle_to_mask_clc.
|
|||||||||||||
| 8627 | .handle_to_mask←1← 897C JSR | ||||||||||||
| PHA ; Save A (will be restored on exit) | |||||||||||||
| 8628 | TXA ; Save X (will be restored on exit) | ||||||||||||
| 8629 | PHA ; (second half of X save) | ||||||||||||
| 862A | TYA ; A = handle from Y | ||||||||||||
| 862B | BCC y2fsl5 ; C=0: always convert | ||||||||||||
| 862D | BEQ handle_mask_exit ; C=1 and Y=0: skip (handle 0 = none) | ||||||||||||
| 862F | .y2fsl5←1← 862B BCC | ||||||||||||
| SEC ; C=1 and Y!=0: convert | |||||||||||||
| 8630 | SBC #&1f ; A = handle - &1F (1-based bit position) | ||||||||||||
| 8632 | TAX ; X = shift count | ||||||||||||
| 8633 | LDA #1 ; Start with bit 0 set | ||||||||||||
| 8635 | .y2fsl2←1← 8637 BNE | ||||||||||||
| ASL ; Shift bit left | |||||||||||||
| 8636 | DEX ; Count down | ||||||||||||
| 8637 | BNE y2fsl2 ; Loop until correct position | ||||||||||||
| 8639 | ROR ; Undo final extra shift | ||||||||||||
| 863A | TAY ; Y = resulting bitmask | ||||||||||||
| 863B | BNE handle_mask_exit ; Non-zero: valid mask, skip to exit | ||||||||||||
| 863D | DEY ; Zero: invalid handle, set Y=&FF | ||||||||||||
| 863E | .handle_mask_exit←2← 862D BEQ← 863B BNE | ||||||||||||
| PLA ; Restore X | |||||||||||||
| 863F | TAX ; Restore X from stack | ||||||||||||
| 8640 | PLA ; Restore A | ||||||||||||
| 8641 | 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.
|
|||||||||||
| 8642 | .mask_to_handle←2← 89AB JSR← 8F65 JSR | ||||||||||
| LDX #&1f ; X = &1F (handle base - 1) | |||||||||||
| 8644 | .fs2al1←1← 8646 BNE | ||||||||||
| INX ; Count this bit position | |||||||||||
| 8645 | LSR ; Shift mask right; C=0 when done | ||||||||||
| 8646 | BNE fs2al1 ; Loop until all bits shifted out | ||||||||||
| 8648 | TXA ; A = X = &1F + bit position = handle | ||||||||||
| 8649 | 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.
|
||||||||
| 864A | .compare_addresses←2← 8743 JSR← 87FB JSR | |||||||
| LDX #4 ; Compare 4 bytes (index 4,3,2,1) | ||||||||
| 864C | .compare_addr_byte←1← 8653 BNE | |||||||
| LDA addr_work,x ; Load byte from first address | ||||||||
| 864E | EOR fs_load_addr_3,x ; XOR with corresponding byte | |||||||
| 8650 | BNE return_compare ; Mismatch: Z=0, return unequal | |||||||
| 8652 | DEX ; Next byte | |||||||
| 8653 | BNE compare_addr_byte ; Continue comparing | |||||||
| 8655 | .return_compare←1← 8650 BNE | |||||||
| RTS ; Return with Z flag result | ||||||||
| 8656 | .fscv_7_read_handles | |||||||
| LDX #&20 ; X=first handle (&20) | ||||||||
| 8658 | LDY #&27 ; Y=last handle (&27) | |||||||
| 865A | .return_fscv_handles | |||||||
| RTS ; Return (FSCV 7 read handles) | ||||||||
Set bit(s) in FS flags (&0E07)ORs A into fs_work_0e07 (EOF hint byte). Each bit represents one of up to 8 open file handles. When clear, the file is definitely NOT at EOF. When set, the fileserver must be queried to confirm EOF status. This negative-cache optimisation avoids expensive network round-trips for the common case. The hint is cleared when the file pointer is updated (since seeking away from EOF invalidates the hint) and set after BGET/OPEN/EOF operations that might have reached the end.
|
|||||||
| 865B | .set_fs_flag←5← 854F JSR← 8954 JSR← 89A7 JSR← 89C7 JSR← 8AA6 JSR | ||||||
| ORA fs_eof_flags ; Set EOF hint bit(s) for handle mask | |||||||
| 865E | BNE store_fs_flag ; ALWAYS branch to store updated flags | ||||||
Clear bit(s) in FS flags (&0E07)Inverts A (EOR #&FF), then ANDs into fs_work_0e07 to clear the specified bits. Falls through to store the result.
|
|||||||
| 8660 | .clear_fs_flag←3← 854C JSR← 886E JSR← 8AA3 JSR | ||||||
| EOR #&ff ; Invert mask for AND clear | |||||||
| 8662 | AND fs_eof_flags ; Clear specified bits in flags | ||||||
| 8665 | .store_fs_flag←1← 865E BNE | ||||||
| STA fs_eof_flags ; Write back updated flags | |||||||
| 8668 | RTS ; Return | ||||||
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. |
|
| 8669 | .setup_tx_ptr_c0←2← 83C0 JSR← 883B JSR |
| LDX #&c0 ; X=&C0: TX control block at &00C0 | |
| 866B | STX net_tx_ptr ; Set TX pointer lo |
| 866D | LDX #0 ; X=0: page zero |
| 866F | STX net_tx_ptr_hi ; Set TX pointer high = 0 (page zero) |
| 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. |
|
| 8671 | .tx_poll_ff←4← 901F JSR← 9079 JMP← 90D0 JSR← 926E JSR |
| LDA #&ff ; A=&FF: full retry count | |
| 8673 | .tx_poll_timeout |
| LDY #&60 ; Y=timeout parameter (&60 = standard) | |
| fall through ↓ | |
Core transmit and poll routine (XMIT)Claims the TX semaphore (tx_ctrl_status) via ASL -- a busy-wait spinlock where carry=0 means the semaphore is held by another operation. Only after claiming the semaphore is the TX pointer copied to nmi_tx_block, ensuring the low-level transmit code sees a consistent pointer. Then calls the ADLC TX setup routine and polls the control byte for completion: bit 7 set = still busy (loop) bit 6 set = error (check escape or report) bit 6 clear = success (clean return) On error, checks for escape condition and handles retries. Two entry points: setup_tx_ptr_c0 (&8669) always uses the standard TXCB; tx_poll_core (&8675) is general-purpose.
|
|||||||||||||
| 8675 | .tx_poll_core←1← 905F JSR | ||||||||||||
| PHA ; Save retry count on stack | |||||||||||||
| 8676 | TYA ; Transfer timeout to A | ||||||||||||
| 8677 | PHA ; Save timeout on stack | ||||||||||||
| 8678 | LDX #0 ; X=0 for (net_tx_ptr,X) indirect | ||||||||||||
| 867A | LDA (net_tx_ptr,x) ; Load TXCB byte 0 (control/status) | ||||||||||||
| 867C | .tx_retry←1← 86AF BEQ | ||||||||||||
| STA (net_tx_ptr,x) ; Write control byte to start TX | |||||||||||||
| 867E | PHA ; Save control byte for retry | ||||||||||||
| 867F | .tx_semaphore_spin←1← 8682 BCC | ||||||||||||
| ASL tx_clear_flag ; Test TX semaphore (C=1 when free) | |||||||||||||
| 8682 | BCC tx_semaphore_spin ; Spin until semaphore released | ||||||||||||
| 8684 | LDA net_tx_ptr ; Copy TX ptr lo to NMI block | ||||||||||||
| 8686 | STA nmi_tx_block ; Store for NMI handler access | ||||||||||||
| 8688 | LDA net_tx_ptr_hi ; Copy TX ptr hi to NMI block | ||||||||||||
| 868A | STA nmi_tx_block_hi ; Store for NMI handler access | ||||||||||||
| 868C | JSR trampoline_tx_setup ; Initiate ADLC TX via trampoline | ||||||||||||
| 868F | .poll_txcb_status←1← 8691 BMI | ||||||||||||
| LDA (net_tx_ptr,x) ; Poll TXCB byte 0 for completion | |||||||||||||
| 8691 | BMI poll_txcb_status ; Bit 7 set: still busy, keep polling | ||||||||||||
| 8693 | ASL ; Shift bit 6 into bit 7 (error flag) | ||||||||||||
| 8694 | BPL tx_success ; Bit 6 clear: success, clean return | ||||||||||||
| 8696 | ASL ; Shift bit 5 into carry | ||||||||||||
| 8697 | BEQ tx_not_listening ; Zero: fatal error, no escape | ||||||||||||
| 8699 | JSR check_escape ; Check for user escape condition | ||||||||||||
| 869C | PLA ; Discard saved control byte | ||||||||||||
| 869D | TAX ; Save to X for retry delay | ||||||||||||
| 869E | PLA ; Restore timeout parameter | ||||||||||||
| 869F | TAY ; Back to Y | ||||||||||||
| 86A0 | PLA ; Restore retry count | ||||||||||||
| 86A1 | BEQ tx_not_listening ; No retries left: report error | ||||||||||||
| 86A3 | SBC #1 ; Decrement retry count | ||||||||||||
| 86A5 | PHA ; Save updated retry count | ||||||||||||
| 86A6 | TYA ; Timeout to A for delay | ||||||||||||
| 86A7 | PHA ; Save timeout parameter | ||||||||||||
| 86A8 | TXA ; Control byte for delay duration | ||||||||||||
| 86A9 | .msdely←2← 86AA BNE← 86AD BNE | ||||||||||||
| DEX ; Inner delay loop | |||||||||||||
| 86AA | BNE msdely ; Spin until X=0 | ||||||||||||
| 86AC | DEY ; Outer delay loop | ||||||||||||
| 86AD | BNE msdely ; Continue delay | ||||||||||||
| 86AF | BEQ tx_retry ; ALWAYS branch | ||||||||||||
| 86B1 | .tx_not_listening←2← 8697 BEQ← 86A1 BEQ | ||||||||||||
| TAX ; Save error code in X | |||||||||||||
| 86B2 | JMP nlistn ; Report 'Not listening' error | ||||||||||||
| 86B5 | .tx_success←1← 8694 BPL | ||||||||||||
| PLA ; Discard saved control byte | |||||||||||||
| 86B6 | PLA ; Discard timeout parameter | ||||||||||||
| 86B7 | PLA ; Discard retry count | ||||||||||||
| 86B8 | RTS ; Return (success) | ||||||||||||
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. |
|
| 86B9 | .copy_filename_ptr←1← 86EA JSR |
| LDY #1 ; Y=1: copy 2 bytes (high then low) | |
| 86BB | .file1←1← 86C1 BPL |
| LDA (fs_options),y ; Load filename ptr from control block | |
| 86BD | STA os_text_ptr,y ; Store to MOS text pointer (&F2/&F3) |
| 86C0 | DEY ; Next byte (descending) |
| 86C1 | 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 &86C5 allows a non-zero starting Y offset.
|
|||||||||
| 86C3 | .parse_filename_gs←2← 8991 JSR← 8DC7 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. |
|
| 86C5 | .parse_filename_gs_y←1← 8C11 JSR |
| LDX #&ff ; X=&FF: next INX wraps to first char index | |
| 86C7 | CLC ; C=0 for GSINIT: parse from current position |
| 86C8 | JSR gsinit ; Initialise GS string parser |
| 86CB | BEQ terminate_filename ; Empty string: skip to CR terminator |
| 86CD | .quote1←1← 86D6 BCC |
| JSR gsread ; Read next character via GSREAD | |
| 86D0 | BCS terminate_filename ; C=1 from GSREAD: end of string reached |
| 86D2 | INX ; Advance buffer index |
| 86D3 | STA fs_filename_buf,x ; Store parsed character to &0E30+X |
| 86D6 | BCC quote1 ; ALWAYS loop (GSREAD clears C on success) ALWAYS branch |
| 86D8 | .terminate_filename←2← 86CB BEQ← 86D0 BCS |
| INX ; Terminate parsed string with CR | |
| 86D9 | .tx_result_check |
| LDA #&0d ; CR = &0D | |
| 86DB | STA fs_filename_buf,x ; Store CR terminator at end of string |
| 86DE | LDA #&30 ; Point fs_crc_lo/hi at &0E30 parse buffer |
| 86E0 | STA fs_crc_lo ; fs_crc_lo = &30 |
| 86E2 | LDA #&0e ; fs_crc_hi = &0E → buffer at &0E30 |
| 86E4 | STA fs_crc_hi ; Store high byte |
| 86E6 | RTS ; Return; X = string length |
FILEV handler (OSFILE entry point)Calls save_fscv_args (&85AF) to preserve A/X/Y, then JSR &86B9 to copy the 2-byte filename pointer from the parameter block to os_text_ptr and fall through to parse_filename_gs (&86C3) 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 &86FD) A=&00: save file (filev_save at &8773) A=&01-&06: attribute operations (filev_attrib_dispatch at &8875) Other: restore_args_return (unsupported, no-op)
|
|||||||||||||||
| 86E7 | .filev_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y in FS workspace | |||||||||||||||
| 86EA | JSR copy_filename_ptr ; Copy filename ptr from param block to os_text_ptr | ||||||||||||||
| 86ED | LDA fs_last_byte_flag ; Recover function code from saved A | ||||||||||||||
| 86EF | BPL saveop ; A >= 0: save (&00) or attribs (&01-&06) | ||||||||||||||
| 86F1 | CMP #&ff ; A=&FF? Only &FF is valid for load | ||||||||||||||
| 86F3 | BEQ loadop ; A=&FF: branch to load path | ||||||||||||||
| 86F5 | JMP restore_args_return ; Unknown negative code: no-op return | ||||||||||||||
| 86F8 | .loadop←1← 86F3 BEQ | ||||||||||||||
| JSR copy_filename ; Copy parsed filename to cmd buffer | |||||||||||||||
| 86FB | LDY #2 ; Y=2: FS function code offset | ||||||||||||||
| fall through ↓ | |||||||||||||||
Send FS examine commandSends FS command &03 (FCEXAM: examine file) to the fileserver. Sets &0F02=&03 and error pointer to '*'. Called for OSFILE &FF (load file) with the filename already in the command buffer. The FS reply contains load/exec addresses and file length which are used to set up the data transfer. The header URD field is repurposed to carry the Econet data port number (PLDATA=&92) for the subsequent block data transfer.
|
||||||
| 86FD | .send_fs_examine←1← 8DDC JSR | |||||
| LDA #&92 ; Port &92 = PLDATA (data transfer port) | ||||||
| 86FF | STA fs_cmd_urd ; Overwrite URD field with data port number | |||||
| 8702 | LDA #&2a ; A=&2A: error ptr for retry | |||||
| 8704 | JSR prepare_cmd_clv ; Build FS header (V=1: CLV path) | |||||
| 8707 | LDY #6 ; Y=6: param block byte 6 | |||||
| 8709 | LDA (fs_options),y ; Byte 6: use file's own load address? | |||||
| 870B | BNE lodfil ; Non-zero: use FS reply address (lodfil) | |||||
| 870D | JSR copy_load_addr_from_params ; Zero: copy caller's load addr first | |||||
| 8710 | JSR copy_reply_to_params ; Then copy FS reply to param block | |||||
| 8713 | BCC skip_lodfil ; Carry clear from prepare_cmd_clv: skip lodfil | |||||
| 8715 | .lodfil←1← 870B BNE | |||||
| JSR copy_reply_to_params ; Copy FS reply addresses to param block | ||||||
| 8718 | JSR copy_load_addr_from_params ; Then copy load addr from param block | |||||
| 871B | .skip_lodfil←1← 8713 BCC | |||||
| LDY #4 ; Compute end address = load + file length | ||||||
| 871D | .copy_load_end_addr←1← 8728 BNE | |||||
| LDA fs_load_addr,x ; Load address byte | ||||||
| 871F | STA txcb_end,x ; Store as current transfer position | |||||
| 8721 | ADC fs_file_len,x ; Add file length byte | |||||
| 8724 | STA fs_work_4,x ; Store as end position | |||||
| 8726 | INX ; Next address byte | |||||
| 8727 | DEY ; Decrement byte counter | |||||
| 8728 | BNE copy_load_end_addr ; Loop for all 4 address bytes | |||||
| 872A | SEC ; Adjust high byte for 3-byte length overflow | |||||
| 872B | SBC fs_file_len_3 ; Subtract 4th length byte from end addr | |||||
| 872E | STA fs_work_7 ; Store adjusted end address high byte | |||||
| 8730 | JSR print_file_info ; Display file info after FS reply | |||||
| 8733 | JSR send_data_blocks ; Transfer file data in &80-byte blocks | |||||
| 8736 | LDX #2 ; Copy 3-byte file length to FS reply cmd buffer | |||||
| 8738 | .floop←1← 873F BPL | |||||
| LDA fs_file_len_3,x ; Load file length byte | ||||||
| 873B | STA fs_cmd_data,x ; Store in FS command data buffer | |||||
| 873E | DEX ; Next byte (count down) | |||||
| 873F | BPL floop ; Loop for 3 bytes (X=2,1,0) | |||||
| 8741 | BMI send_fs_reply ; 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. |
|
| 8743 | .send_data_blocks←2← 8733 JSR← 8A96 JSR |
| JSR compare_addresses ; Compare two 4-byte addresses | |
| 8746 | BEQ return_lodchk ; Addresses match: transfer complete |
| 8748 | LDA #&92 ; Port &92 for data block transfer |
| 874A | STA txcb_port ; Store port to TXCB command byte |
| 874C | .send_block_loop←1← 8768 BNE |
| LDX #3 ; Set up next &80-byte block for transfer | |
| 874E | .copy_block_addrs←1← 8757 BPL |
| LDA txcb_end,x ; Swap: current addr -> source, end -> current | |
| 8750 | STA txcb_start,x ; Source addr = current position |
| 8752 | LDA fs_work_4,x ; Load end address byte |
| 8754 | STA txcb_end,x ; Dest = end address (will be clamped) |
| 8756 | DEX ; Next address byte |
| 8757 | BPL copy_block_addrs ; Loop for all 4 bytes |
| 8759 | LDA #&7f ; Command &7F = data block transfer |
| 875B | STA txcb_ctrl ; Store to TXCB control byte |
| 875D | JSR send_to_fs_star ; Send this block to the fileserver |
| 8760 | LDY #3 ; Y=3: compare 4 bytes (3..0) |
| 8762 | .lodchk←1← 876B BPL |
| LDA txcb_end,y ; Compare current vs end address (4 bytes) | |
| 8765 | EOR fs_work_4,y ; XOR with end address byte |
| 8768 | BNE send_block_loop ; Not equal: more blocks to send |
| 876A | DEY ; Next byte |
| 876B | BPL lodchk ; Loop for all 4 address bytes |
| 876D | .return_lodchk←1← 8746 BEQ |
| RTS ; All equal: transfer complete | |
| 876E | .saveop←1← 86EF BPL |
| BEQ filev_save ; A=0: SAVE handler | |
| 8770 | JMP filev_attrib_dispatch ; A!=0: attribute dispatch (A=1-6) |
OSFILE save handler (A=&00)Copies 4-byte load/exec/length addresses from the parameter block to the FS command buffer, along with the filename. Sends FS command &91 with function &14 to initiate the save, then calls print_file_info to display the filename being saved. Handles both host and Tube-based data sources. When receiving the save acknowledgement, the RX low pointer is incremented by 1 to skip the command code (CC) byte, which indicates the FS type and must be preserved. N.B. this assumes the RX buffer does not cross a page boundary. |
|
| 8773 | .filev_save←1← 876E BEQ |
| LDX #4 ; Process 4 address bytes (load/exec/start/end) | |
| 8775 | LDY #&0e ; Y=&0E: start from end-address in param block |
| 8777 | .savsiz←1← 8791 BNE |
| LDA (fs_options),y ; Read end-address byte from param block | |
| 8779 | STA port_ws_offset,y ; Save to port workspace for transfer setup |
| 877C | JSR sub_4_from_y ; Y = Y-4: point to start-address byte |
| 877F | SBC (fs_options),y ; end - start = transfer length byte |
| 8781 | STA fs_cmd_csd,y ; Store length byte in FS command buffer |
| 8784 | PHA ; Save length byte for param block restore |
| 8785 | LDA (fs_options),y ; Read corresponding start-address byte |
| 8787 | STA port_ws_offset,y ; Save to port workspace |
| 878A | PLA ; Restore length byte from stack |
| 878B | STA (fs_options),y ; Replace param block entry with length |
| 878D | JSR add_5_to_y ; Y = Y+5: advance to next address group |
| 8790 | DEX ; Decrement address byte counter |
| 8791 | BNE savsiz ; Loop for all 4 address bytes |
| 8793 | LDY #9 ; Copy load/exec addresses to FS command buffer |
| 8795 | .copy_save_params←1← 879B BNE |
| LDA (fs_options),y ; Read load/exec address byte from params | |
| 8797 | STA fs_cmd_csd,y ; Copy to FS command buffer |
| 879A | DEY ; Next byte (descending) |
| 879B | BNE copy_save_params ; Loop for bytes 9..1 |
| 879D | LDA #&91 ; Port &91 for save command |
| 879F | STA fs_cmd_urd ; Overwrite URD field with port number |
| 87A2 | STA fs_error_ptr ; Save port &91 for flow control ACK |
| 87A4 | LDX #&0b ; Append filename at offset &0B in cmd buffer |
| 87A6 | JSR copy_string_to_cmd ; Append filename to cmd buffer at offset X |
| 87A9 | LDY #1 ; Y=1: function code for save |
| 87AB | LDA #&14 ; A=&14: FS function code for SAVE |
| 87AD | JSR prepare_cmd_clv ; Build header and send FS save command |
| 87B0 | JSR print_file_info ; Display filename being saved |
| 87B3 | .save_csd_display |
| LDA fs_cmd_data ; Load CSD from FS reply | |
| 87B6 | JSR transfer_file_blocks ; Transfer file data blocks to server |
| 87B9 | .send_fs_reply←1← 8741 BMI |
| JSR send_fs_reply_timed ; Send FS reply acknowledgement | |
| 87BC | .skip_catalogue_msg |
| STX fs_reply_cmd ; Store reply command for attr decode | |
| 87BF | LDY #&0e ; Y=&0E: access byte offset in param block |
| 87C1 | LDA fs_cmd_data ; Load access byte from FS reply |
| 87C4 | JSR decode_attribs_5bit ; Convert FS access to BBC attribute format |
| 87C7 | BEQ direct_attr_copy ; Z=1: first byte, use A directly |
| 87C9 | .copy_attr_loop←1← 87D1 BNE |
| LDA fs_reply_data,y ; Load attribute byte from FS reply | |
| 87CC | .direct_attr_copy←1← 87C7 BEQ |
| STA (fs_options),y ; Store decoded access in param block | |
| 87CE | INY ; Next attribute byte |
| 87CF | CPY #&12 ; Copied all 4 bytes? (Y=&0E..&11) |
| 87D1 | BNE copy_attr_loop ; Loop for 4 attribute bytes |
| 87D3 | 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). |
|
| 87D6 | .copy_load_addr_from_params←2← 870D JSR← 8718 JSR |
| LDY #5 ; Start at offset 5 (top of 4-byte addr) | |
| 87D8 | .lodrl1←1← 87E0 BCS |
| LDA (fs_options),y ; Read from parameter block | |
| 87DA | STA work_ae,y ; Store to local workspace |
| 87DD | DEY ; Next byte (descending) |
| 87DE | CPY #2 ; Copy offsets 5,4,3,2 (4 bytes) |
| 87E0 | BCS lodrl1 ; Loop while Y >= 2 |
| 87E2 | .add_5_to_y←1← 878D JSR |
| INY ; Y += 5 | |
| 87E3 | .add_4_to_y←1← 8A73 JSR |
| INY ; Y += 4 | |
| 87E4 | INY ; (continued) |
| 87E5 | INY ; (continued) |
| 87E6 | INY ; (continued) |
| 87E7 | 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.
|
||||
| 87E8 | .copy_reply_to_params←2← 8710 JSR← 8715 JSR | |||
| LDY #&0d ; Start at offset &0D (top of range) | ||||
| 87EA | TXA ; First store uses X (attrib byte) | |||
| 87EB | .lodrl2←1← 87F3 BCS | |||
| STA (fs_options),y ; Write to parameter block | ||||
| 87ED | LDA fs_cmd_urd,y ; Read next byte from reply buffer | |||
| 87F0 | DEY ; Next byte (descending) | |||
| 87F1 | CPY #2 ; Copy offsets &0D down to 2 | |||
| 87F3 | BCS lodrl2 ; Loop until offset 2 reached | |||
| 87F5 | .sub_4_from_y←1← 877C JSR | |||
| DEY ; Y -= 4 | ||||
| 87F6 | .sub_3_from_y←2← 888D JSR← 8A7B JSR | |||
| DEY ; Y -= 3 | ||||
| 87F7 | DEY ; (continued) | |||
| 87F8 | DEY ; (continued) | |||
| 87F9 | 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). |
|
| 87FA | .transfer_file_blocks←2← 87B6 JSR← 8A91 JSR |
| PHA ; Save FS command byte on stack | |
| 87FB | JSR compare_addresses ; Compare two 4-byte addresses |
| 87FE | BEQ restore_ay_return ; Addresses equal: nothing to transfer |
| 8800 | .next_block←1← 884F BNE |
| LDX #0 ; X=0: clear hi bytes of block size | |
| 8802 | LDY #4 ; Y=4: process 4 address bytes |
| 8804 | STX fs_reply_cmd ; Clear block size hi byte 1 |
| 8807 | STX fs_load_vector ; Clear block size hi byte 2 |
| 880A | CLC ; CLC for ADC in loop |
| 880B | .block_addr_loop←1← 8818 BNE |
| LDA fs_load_addr,x ; Source = current position | |
| 880D | STA txcb_start,x ; Store source address byte |
| 880F | ADC fs_func_code,x ; Add block size to current position |
| 8812 | STA txcb_end,x ; Store dest address byte |
| 8814 | STA fs_load_addr,x ; Advance current position |
| 8816 | INX ; Next address byte |
| 8817 | DEY ; Decrement byte counter |
| 8818 | BNE block_addr_loop ; Loop for all 4 bytes |
| 881A | BCS clamp_dest_setup ; Carry: address overflowed, clamp |
| 881C | SEC ; SEC for SBC in overshoot check |
| 881D | .savchk←1← 8825 BNE |
| LDA fs_load_addr,y ; Check if new pos overshot end addr | |
| 8820 | SBC fs_work_4,y ; Subtract end address byte |
| 8823 | INY ; Next byte |
| 8824 | DEX ; Decrement counter |
| 8825 | BNE savchk ; Loop for 4-byte comparison |
| 8827 | BCC send_block ; C=0: no overshoot, proceed |
| 8829 | .clamp_dest_setup←1← 881A BCS |
| LDX #3 ; Overshot: clamp dest to end address | |
| 882B | .clamp_dest_addr←1← 8830 BPL |
| LDA fs_work_4,x ; Load end address byte | |
| 882D | STA txcb_end,x ; Replace dest with end address |
| 882F | DEX ; Next byte |
| 8830 | BPL clamp_dest_addr ; Loop for all 4 bytes |
| 8832 | .send_block←1← 8827 BCC |
| PLA ; Recover original FS command byte | |
| 8833 | PHA ; Re-push for next iteration |
| 8834 | PHP ; Save processor flags (C from cmp) |
| 8835 | STA txcb_port ; Store command byte in TXCB |
| 8837 | LDA #&80 ; 128-byte block size for data transfer |
| 8839 | STA txcb_ctrl ; Store size in TXCB control byte |
| 883B | JSR setup_tx_ptr_c0 ; Point TX ptr to &00C0; transmit |
| 883E | LDA fs_error_ptr ; ACK port for flow control |
| 8840 | JSR init_tx_ctrl_port ; Set reply port for ACK receive |
| 8843 | PLP ; Restore flags (C=overshoot status) |
| 8844 | BCS restore_ay_return ; C=1: all data sent (overshot), done |
| 8846 | LDA #&91 ; Command &91 = data block transfer |
| 8848 | STA txcb_port ; Store command &91 in TXCB |
| 884A | LDA #&2a ; A=&2A: error ptr for retry |
| 884C | JSR send_to_fs ; Transmit block and wait (BRIANX) |
| 884F | BNE next_block ; More blocks? Loop back |
| fall through ↓ | |
FSCV 1: EOF handlerChecks whether a file handle has reached end-of-file. Converts the handle via handle_to_mask_clc, tests the result against the EOF hint byte (&0E07). If the hint bit is clear, returns X=0 immediately (definitely not at EOF — no network call needed). If the hint bit is set, sends FS command &11 (FCEOF) to query the fileserver for definitive EOF status. Returns X=&FF if at EOF, X=&00 if not. This two-level check avoids an expensive network round-trip when the file is known to not be at EOF.
|
|||||||
| 8851 | .fscv_1_eof | ||||||
| PHA ; Save A (function code) | |||||||
| 8852 | TXA ; X = file handle to check | ||||||
| 8853 | JSR handle_to_mask_a ; Convert handle to bitmask in A | ||||||
| 8856 | TYA ; Y = handle bitmask from conversion | ||||||
| 8857 | AND fs_eof_flags ; Local hint: is EOF possible for this handle? | ||||||
| 885A | TAX ; X = result of AND (0 = not at EOF) | ||||||
| 885B | BEQ restore_ay_return ; Hint clear: definitely not at EOF | ||||||
| 885D | PHA ; Save bitmask for clear_fs_flag | ||||||
| 885E | STY fs_cmd_data ; Handle byte in FS command buffer | ||||||
| 8861 | LDY #&11 ; Y=&11: FS function code FCEOF Y=function code for HDRFN | ||||||
| 8863 | LDX #1 ; X=preserved through header build | ||||||
| 8865 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 8868 | PLA ; Restore bitmask | ||||||
| 8869 | LDX fs_cmd_data ; FS reply: non-zero = at EOF | ||||||
| 886C | BNE restore_ay_return ; At EOF: skip flag clear | ||||||
| 886E | JSR clear_fs_flag ; Not at EOF: clear the hint bit | ||||||
| 8871 | .restore_ay_return←4← 87FE BEQ← 8844 BCS← 885B BEQ← 886C BNE | ||||||
| PLA ; Restore A | |||||||
| 8872 | LDY fs_block_offset ; Restore Y | ||||||
| 8874 | 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.
|
|||||||
| 8875 | .filev_attrib_dispatch←1← 8770 JMP | ||||||
| STA fs_cmd_data ; Store function code in FS cmd buffer | |||||||
| 8878 | CMP #6 ; A=6? (delete) | ||||||
| 887A | BEQ cha6 ; Yes: jump to delete handler | ||||||
| 887C | BCS check_attrib_result ; A>=7: unsupported, fall through to return | ||||||
| 887E | CMP #5 ; A=5? (read catalogue info) | ||||||
| 8880 | BEQ cha5 ; Yes: jump to read info handler | ||||||
| 8882 | CMP #4 ; A=4? (write attributes only) | ||||||
| 8884 | BEQ cha4 ; Yes: jump to write attrs handler | ||||||
| 8886 | CMP #1 ; A=1? (write all catalogue info) | ||||||
| 8888 | BEQ get_file_protection ; Yes: jump to write-all handler | ||||||
| 888A | ASL ; A=2 or 3: convert to param block offset | ||||||
| 888B | ASL ; A*4: 2->8, 3->12 | ||||||
| 888C | TAY ; Y = A*4 | ||||||
| 888D | JSR sub_3_from_y ; Y = A*4 - 3 (load addr offset for A=2) | ||||||
| 8890 | LDX #3 ; X=3: copy 4 bytes | ||||||
| 8892 | .chalp1←1← 8899 BPL | ||||||
| LDA (fs_options),y ; Load address byte from param block | |||||||
| 8894 | STA fs_func_code,x ; Store to FS cmd data area | ||||||
| 8897 | DEY ; Next source byte (descending) | ||||||
| 8898 | DEX ; Next dest byte | ||||||
| 8899 | BPL chalp1 ; Loop for 4 bytes | ||||||
| 889B | LDX #5 ; X=5: data extent for filename copy | ||||||
| 889D | BNE copy_filename_to_cmd ; ALWAYS branch | ||||||
| 889F | .get_file_protection←1← 8888 BEQ | ||||||
| JSR decode_attribs_6bit ; A=1: encode protection from param block | |||||||
| 88A2 | STA fs_file_attrs ; Store encoded attrs at &0F0E | ||||||
| 88A5 | LDY #9 ; Y=9: source offset in param block | ||||||
| 88A7 | LDX #8 ; X=8: dest offset in cmd buffer | ||||||
| 88A9 | .chalp2←1← 88B0 BNE | ||||||
| LDA (fs_options),y ; Load byte from param block | |||||||
| 88AB | STA fs_cmd_data,x ; Store to FS cmd buffer | ||||||
| 88AE | DEY ; Next source byte (descending) | ||||||
| 88AF | DEX ; Next dest byte | ||||||
| 88B0 | BNE chalp2 ; Loop until X=0 (8 bytes copied) | ||||||
| 88B2 | LDX #&0a ; X=&0A: data extent past attrs+addrs | ||||||
| 88B4 | .copy_filename_to_cmd←2← 889D BNE← 88D2 BNE | ||||||
| JSR copy_string_to_cmd ; Append filename to cmd buffer | |||||||
| 88B7 | LDY #&13 ; Y=&13: fn code for FCSAVE (write attrs) | ||||||
| 88B9 | BNE send_fs_cmd_v1 ; ALWAYS branch to send command ALWAYS branch | ||||||
| 88BB | .cha6←1← 887A BEQ | ||||||
| JSR copy_filename ; A=6: copy filename (delete) | |||||||
| 88BE | LDY #&14 ; Y=&14: fn code for FCDEL (delete) | ||||||
| 88C0 | .send_fs_cmd_v1←1← 88B9 BNE | ||||||
| BIT tx_ctrl_upper ; Set V=1 (BIT trick: &B3 has bit 6 set) | |||||||
| 88C3 | JSR init_tx_ctrl_data ; Send via prepare_fs_cmd_v (V=1 path) | ||||||
| 88C6 | .check_attrib_result←1← 887C BCS | ||||||
| BCS attrib_error_exit ; C=1: &D6 not-found, skip to return | |||||||
| 88C8 | BCC argsv_check_return ; C=0: success, copy reply to param block ALWAYS branch | ||||||
| 88CA | .cha4←1← 8884 BEQ | ||||||
| JSR decode_attribs_6bit ; A=4: encode attrs from param block | |||||||
| 88CD | STA fs_func_code ; Store encoded attrs at &0F06 | ||||||
| 88D0 | LDX #2 ; X=2: data extent (1 attr byte + fn) | ||||||
| 88D2 | BNE copy_filename_to_cmd ; ALWAYS branch to append filename ALWAYS branch | ||||||
| 88D4 | .cha5←1← 8880 BEQ | ||||||
| LDX #1 ; X=1: filename only, no data extent | |||||||
| 88D6 | JSR copy_string_to_cmd ; Copy filename to cmd buffer | ||||||
| 88D9 | LDY #&12 ; Y=&12: fn code for FCEXAM (read info) Y=function code for HDRFN | ||||||
| 88DB | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | ||||||
| 88DE | LDA fs_obj_type ; Save object type from FS reply | ||||||
| 88E1 | STX fs_obj_type ; Clear reply byte (X=0 on success) X=0 on success, &D6 on not-found | ||||||
| 88E4 | STX fs_len_clear ; Clear length high byte in reply | ||||||
| 88E7 | JSR decode_attribs_5bit ; Decode 5-bit access byte from FS reply | ||||||
| 88EA | LDY #&0e ; Y=&0E: attrs offset in param block | ||||||
| 88EC | STA (fs_options),y ; Store decoded attrs at param block +&0E | ||||||
| 88EE | DEY ; Y=&0D: start copy below attrs Y=&0d | ||||||
| 88EF | LDX #&0c ; X=&0C: copy from reply offset &0C down | ||||||
| 88F1 | .copy_fs_reply_to_cb←1← 88F8 BNE | ||||||
| LDA fs_cmd_data,x ; Load reply byte (load/exec/length) | |||||||
| 88F4 | STA (fs_options),y ; Store to param block | ||||||
| 88F6 | DEY ; Next dest byte (descending) | ||||||
| 88F7 | DEX ; Next source byte | ||||||
| 88F8 | BNE copy_fs_reply_to_cb ; Loop until X=0 (12 bytes copied) | ||||||
| 88FA | INX ; X=0 -> X=2 for length high copy | ||||||
| 88FB | INX ; INX again: X=2 | ||||||
| 88FC | LDY #&11 ; Y=&11: length high dest in param block | ||||||
| 88FE | .cha5lp←1← 8905 BPL | ||||||
| LDA fs_access_level,x ; Load length high byte from reply | |||||||
| 8901 | STA (fs_options),y ; Store to param block | ||||||
| 8903 | DEY ; Next dest byte (descending) | ||||||
| 8904 | DEX ; Next source byte | ||||||
| 8905 | BPL cha5lp ; Loop for 3 length-high bytes | ||||||
| 8907 | LDA fs_cmd_data ; Return object type in A | ||||||
| 890A | .attrib_error_exit←1← 88C6 BCS | ||||||
| BPL restore_xy_return ; A>=0: branch to restore_args_return | |||||||
| fall through ↓ | |||||||
ARGSV handler (OSARGS entry point)A=0, Y=0: return filing system number (10 = network FS) A=0, Y>0: read file pointer via FS command &0A (FCRDSE) A=1, Y>0: write file pointer via FS command &14 (FCWRSE) A>=3 (ensure): silently returns -- NFS has no local write buffer to flush, since all data is sent to the fileserver immediately The handle in Y is converted via handle_to_mask_clc. For writes (A=1), the carry flag from the mask conversion is used to branch to save_args_handle, which records the handle for later use.
|
|||||||||||||
| 890C | .argsv_handler | ||||||||||||
| JSR save_fscv_args ; Save A/X/Y registers for later restore | |||||||||||||
| 890F | CMP #3 ; Function >= 3? | ||||||||||||
| 8911 | BCS restore_args_return ; A>=3 (ensure/flush): no-op for NFS | ||||||||||||
| 8913 | CPY #0 ; Test file handle | ||||||||||||
| 8915 | BEQ argsv_dispatch_a ; Y=0: FS-level query, not per-file | ||||||||||||
| 8917 | JSR handle_to_mask_clc ; Convert handle to bitmask | ||||||||||||
| 891A | STY fs_cmd_data ; Store bitmask as first cmd data byte | ||||||||||||
| 891D | LSR ; LSR splits A: C=1 means write (A=1) | ||||||||||||
| 891E | STA fs_func_code ; Store function code to cmd data byte 2 | ||||||||||||
| 8921 | BCS save_args_handle ; C=1: write path, copy ptr from caller | ||||||||||||
| 8923 | LDY #&0c ; Y=&0C: FCRDSE (read sequential pointer) Y=function code for HDRFN | ||||||||||||
| 8925 | LDX #2 ; X=2: 3 data bytes in command X=preserved through header build | ||||||||||||
| 8927 | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 892A | STA fs_last_byte_flag ; Clear last-byte flag on success A=0 on success (from build_send_fs_cmd) | ||||||||||||
| 892C | LDX fs_options ; X = saved control block ptr low | ||||||||||||
| 892E | LDY #2 ; Y=2: copy 3 bytes of file pointer | ||||||||||||
| 8930 | STA zp_work_3,x ; Zero high byte of 3-byte pointer | ||||||||||||
| 8932 | .copy_fileptr_reply←1← 8939 BPL | ||||||||||||
| LDA fs_cmd_data,y ; Read reply byte from FS cmd data | |||||||||||||
| 8935 | STA zp_work_2,x ; Store to caller's control block | ||||||||||||
| 8937 | DEX ; Next byte (descending) | ||||||||||||
| 8938 | DEY ; Next source byte | ||||||||||||
| 8939 | BPL copy_fileptr_reply ; Loop for all 3 bytes | ||||||||||||
| 893B | .argsv_check_return←1← 88C8 BCC | ||||||||||||
| BCC restore_args_return ; C=0 (read): return to caller | |||||||||||||
| 893D | .save_args_handle←1← 8921 BCS | ||||||||||||
| TYA ; Save bitmask for set_fs_flag later | |||||||||||||
| 893E | PHA ; Push bitmask | ||||||||||||
| 893F | LDY #3 ; Y=3: copy 4 bytes of file pointer | ||||||||||||
| 8941 | .copy_fileptr_to_cmd←1← 8948 BPL | ||||||||||||
| LDA zp_work_3,x ; Read caller's pointer byte | |||||||||||||
| 8943 | STA fs_data_count,y ; Store to FS command data area | ||||||||||||
| 8946 | DEX ; Next source byte | ||||||||||||
| 8947 | DEY ; Next destination byte | ||||||||||||
| 8948 | BPL copy_fileptr_to_cmd ; Loop for all 4 bytes | ||||||||||||
| 894A | LDY #&0d ; Y=&0D: FCWRSE (write sequential pointer) Y=function code for HDRFN | ||||||||||||
| 894C | LDX #5 ; X=5: 6 data bytes in command X=preserved through header build | ||||||||||||
| 894E | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||
| 8951 | STX fs_last_byte_flag ; Save not-found status from X X=0 on success, &D6 on not-found | ||||||||||||
| 8953 | PLA ; Recover bitmask for EOF hint update | ||||||||||||
| 8954 | 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. |
|
| 8957 | .restore_args_return←7← 86F5 JMP← 87D3 JMP← 8911 BCS← 893B BCC← 89CA BCC← 8A1B JMP← 8E25 JMP |
| LDA fs_last_byte_flag ; A = saved function code / command | |
| 8959 | .restore_xy_return←5← 890A BPL← 896A BNE← 8976 BPL← 89A1 BCS← 89AE BNE |
| LDX fs_options ; X = saved control block ptr low | |
| 895B | LDY fs_block_offset ; Y = saved control block ptr high |
| 895D | RTS ; Return to MOS with registers restored |
| 895E | .argsv_dispatch_a←1← 8915 BEQ |
| CMP #2 ; OSARGS A=2: read filing system | |
| 8960 | BEQ halve_args_a ; A=2: halve to get FS number |
| 8962 | BCS return_a_zero ; A>2: unsupported, return zero |
| 8964 | TAY ; Y=A for block copy index |
| 8965 | BNE osarg1 ; A=1: read command context block |
| 8967 | LDA #&0a ; A=&0A: NFS filing system number |
| 8969 | .halve_args_a←1← 8960 BEQ |
| LSR ; Shared: halve A (A=0 or A=2 paths) | |
| 896A | BNE restore_xy_return ; Return with A = FS number or 1 |
| 896C | .osarg1←2← 8965 BNE← 8972 BPL |
| LDA fs_cmd_context,y ; Copy command context to caller's block | |
| 896F | STA (fs_options),y ; Store to caller's parameter block |
| 8971 | DEY ; Next byte (descending) |
| 8972 | BPL osarg1 ; Loop until all bytes copied |
| fall through ↓ | |
Return with A=0 via register restoreLoads A=0 and branches (always taken) to the common register restore exit at restore_args_return. Used as a shared exit point by ARGSV, FINDV, and GBPBV when an operation is unsupported or should return zero. |
|
| 8974 | .return_a_zero←4← 8962 BCS← 8984 BNE← 8AB7 JMP← 8B54 JMP |
| LDA #0 ; A=operation (0=close, &40=read, &80=write, &C0=R/W) | |
| 8976 | BPL restore_xy_return ; ALWAYS branch |
FINDV handler (OSFIND entry point)A=0: close file -- delegates to close_handle (&8986) A>0: open file -- modes &40=read, &80=write/update, &C0=read/write For open: the mode byte is converted to the fileserver's two-flag format by flipping bit 7 (EOR #&80) and shifting. This produces Flag 1 (read/write direction) and Flag 2 (create/existing), matching the fileserver protocol. After a successful open, the new handle's bit is OR'd into the EOF hint byte (marks it as "might be at EOF, query the server", on_entry={"a": "operation (0=close, &40=read, &80=write, &C0=R/W)", "x": "filename pointer low (open)", "y": "file handle (close) or filename pointer high (open)"}, on_exit={"a": "handle on open, 0 on close-all, restored on close-one", "x": "restored", "y": "restored"}) number tracking byte for the byte-stream protocol.
|
|||||||||||||||
| 8978 | .findv_handler | ||||||||||||||
| JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up pointers | |||||||||||||||
| 897B | SEC ; SEC distinguishes open (A>0) from close | ||||||||||||||
| 897C | JSR handle_to_mask ; Convert file handle to bitmask (Y2FS) | ||||||||||||||
| 897F | TAX ; A=preserved | ||||||||||||||
| 8980 | BEQ close_handle ; A=0: close file(s) | ||||||||||||||
| 8982 | AND #&3f ; Valid open modes: &40, &80, &C0 only | ||||||||||||||
| 8984 | BNE return_a_zero ; Invalid mode bits: return | ||||||||||||||
| 8986 | TXA ; A = original mode byte | ||||||||||||||
| 8987 | EOR #&80 ; Convert MOS mode to FS protocol flags | ||||||||||||||
| 8989 | ASL ; ASL: shift mode bits left | ||||||||||||||
| 898A | STA fs_cmd_data ; Flag 1: read/write direction | ||||||||||||||
| 898D | ROL ; ROL: Flag 2 into bit 0 | ||||||||||||||
| 898E | STA fs_func_code ; Flag 2: create vs existing file | ||||||||||||||
| 8991 | JSR parse_filename_gs ; Parse filename from command line | ||||||||||||||
| 8994 | LDX #2 ; X=2: copy after 2-byte flags | ||||||||||||||
| 8996 | JSR copy_string_to_cmd ; Copy filename to FS command buffer | ||||||||||||||
| 8999 | LDY #6 ; Y=6: FS function code FCOPEN | ||||||||||||||
| 899B | BIT tx_ctrl_upper ; Set V flag from l83b3 bit 6 | ||||||||||||||
| 899E | JSR init_tx_ctrl_data ; Build and send FS open command | ||||||||||||||
| 89A1 | BCS restore_xy_return ; Error: restore and return | ||||||||||||||
| 89A3 | LDA fs_cmd_data ; Load reply handle from FS | ||||||||||||||
| 89A6 | TAX ; X = new file handle | ||||||||||||||
| 89A7 | JSR set_fs_flag ; Set EOF hint + sequence bits | ||||||||||||||
| 89AA | TXA ; A=single-bit bitmask | ||||||||||||||
| 89AB | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) | ||||||||||||||
| 89AE | 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.
|
||||
| 89B0 | .close_handle←1← 8980 BEQ | |||
| TYA ; A = handle (Y preserved in A) Y=preserved | ||||
| 89B1 | BNE close_single_handle ; Y>0: close single file | |||
| 89B3 | LDA #osbyte_close_spool_exec ; Close SPOOL/EXEC before FS close-all | |||
| 89B5 | JSR osbyte ; Close any *SPOOL and *EXEC files | |||
| 89B8 | LDY #0 ; Y=0: close all handles on server | |||
| 89BA | .close_single_handle←1← 89B1 BNE | |||
| STY fs_cmd_data ; Handle byte in FS command buffer | ||||
| 89BD | LDX #1 ; X=preserved through header build | |||
| 89BF | LDY #7 ; Y=function code for HDRFN | |||
| 89C1 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||
| 89C4 | LDA fs_cmd_data ; Reply handle for flag update | |||
| 89C7 | JSR set_fs_flag ; Update EOF/sequence tracking bits | |||
| 89CA | .close_opt_return←1← 89EE BCC | |||
| BCC restore_args_return ; C=0: restore A/X/Y and return | ||||
| fall through ↓ | ||||
FSCV 0: *OPT handler (OPTION)Handles *OPT X,Y to set filing system options: *OPT 1,Y (Y=0/1): set local user option in &0E06 (OPT) *OPT 4,Y (Y=0-3): set boot option via FS command &16 (FCOPT) Other combinations generate error &CB (OPTER: "bad option").
|
||||||
| 89CC | .fscv_0_opt | |||||
| CPX #4 ; Is it *OPT 4,Y? | ||||||
| 89CE | BNE check_opt1 ; No: check for *OPT 1 | |||||
| 89D0 | CPY #4 ; Y must be 0-3 for boot option | |||||
| 89D2 | BCC optl1 ; Y < 4: valid boot option | |||||
| 89D4 | .check_opt1←1← 89CE BNE | |||||
| DEX ; Not *OPT 4: check for *OPT 1 | ||||||
| 89D5 | BNE opter1 ; Not *OPT 1 either: bad option | |||||
| 89D7 | .set_messages_flag | |||||
| STY fs_messages_flag ; Set local messages flag (*OPT 1,Y) | ||||||
| 89DA | BCC opt_return ; Return via restore_args_return | |||||
| 89DC | .opter1←1← 89D5 BNE | |||||
| LDA #7 ; Error index 7 (Bad option) | ||||||
| 89DE | JMP nlisne ; Generate BRK error | |||||
| 89E1 | .optl1←1← 89D2 BCC | |||||
| STY fs_cmd_data ; Boot option value in FS command | ||||||
| 89E4 | LDY #&16 ; Y=&16: FS function code FCOPT Y=function code for HDRFN | |||||
| 89E6 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) | |||||
| 89E9 | LDY fs_block_offset ; Restore Y from saved value | |||||
| 89EB | STY fs_boot_option ; Cache boot option locally | |||||
| 89EE | .opt_return←1← 89DA BCC | |||||
| BCC close_opt_return ; Return via restore_args_return | ||||||
| 89F0 | .adjust_addrs_9←1← 8AAB JSR | |||||
| LDY #9 ; Y=9: adjust 9 address bytes | ||||||
| 89F2 | JSR adjust_addrs_clc ; Adjust with carry clear | |||||
| 89F5 | .adjust_addrs_1←1← 8B9B JSR | |||||
| LDY #1 ; Y=1: adjust 1 address byte | ||||||
| 89F7 | .adjust_addrs_clc←1← 89F2 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.
|
|||||||||||
| 89F8 | .adjust_addrs←2← 8AB1 JSR← 8BA7 JSR | ||||||||||
| LDX #&fc ; X=&FC: index into &0E06 area (wraps to 0) | |||||||||||
| 89FA | .adjust_addr_byte←1← 8A0D BNE | ||||||||||
| LDA (fs_options),y ; Load byte from param block | |||||||||||
| 89FC | BIT fs_load_addr_2 ; Test sign of adjustment direction | ||||||||||
| 89FE | BMI subtract_adjust ; Negative: subtract instead | ||||||||||
| 8A00 | ADC fs_cmd_context,x ; Add adjustment value | ||||||||||
| 8A03 | JMP gbpbx ; Skip to store result | ||||||||||
| 8A06 | .subtract_adjust←1← 89FE BMI | ||||||||||
| SBC fs_cmd_context,x ; Subtract adjustment value | |||||||||||
| 8A09 | .gbpbx←1← 8A03 JMP | ||||||||||
| STA (fs_options),y ; Store adjusted byte back | |||||||||||
| 8A0B | INY ; Next param block byte | ||||||||||
| 8A0C | INX ; Next adjustment byte (X wraps &FC->&00) | ||||||||||
| 8A0D | BNE adjust_addr_byte ; Loop 4 times (X=&FC,&FD,&FE,&FF,done) | ||||||||||
| 8A0F | 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.
|
|||||||||||||||
| 8A10 | .gbpbv_handler | ||||||||||||||
| JSR save_fscv_args ; Save A/X/Y to FS workspace | |||||||||||||||
| 8A13 | TAX ; X = call number for range check | ||||||||||||||
| 8A14 | BEQ gbpb_invalid_exit ; A=0: invalid, restore and return | ||||||||||||||
| 8A16 | DEX ; Convert to 0-based (A=0..7) | ||||||||||||||
| 8A17 | CPX #8 ; Range check: must be 0-7 | ||||||||||||||
| 8A19 | BCC gbpbx1 ; In range: continue to handler | ||||||||||||||
| 8A1B | .gbpb_invalid_exit←1← 8A14 BEQ | ||||||||||||||
| JMP restore_args_return ; Out of range: restore args and return | |||||||||||||||
| 8A1E | .gbpbx1←1← 8A19 BCC | ||||||||||||||
| TXA ; Recover 0-based function code | |||||||||||||||
| 8A1F | LDY #0 ; Y=0: param block byte 0 (file handle) | ||||||||||||||
| 8A21 | PHA ; Save function code on stack | ||||||||||||||
| 8A22 | CMP #4 ; A>=4: info queries, dispatch separately | ||||||||||||||
| 8A24 | BCC gbpbe1 ; A<4: file read/write operations | ||||||||||||||
| 8A26 | JMP osgbpb_info ; Dispatch to OSGBPB 5-8 info handler | ||||||||||||||
| 8A29 | .gbpbe1←1← 8A24 BCC | ||||||||||||||
| LDA (fs_options),y ; Get file handle from param block byte 0 | |||||||||||||||
| 8A2B | JSR handle_to_mask_a ; Convert handle to bitmask for EOF flags | ||||||||||||||
| 8A2E | STY fs_cmd_data ; Store handle in FS command data | ||||||||||||||
| 8A31 | LDY #&0b ; Y=&0B: start at param block byte 11 | ||||||||||||||
| 8A33 | LDX #6 ; X=6: copy 6 bytes of transfer params | ||||||||||||||
| 8A35 | .gbpbf1←1← 8A41 BNE | ||||||||||||||
| LDA (fs_options),y ; Load param block byte | |||||||||||||||
| 8A37 | STA fs_func_code,x ; Store to FS command buffer at &0F06+X | ||||||||||||||
| 8A3A | DEY ; Previous param block byte | ||||||||||||||
| 8A3B | CPY #8 ; Skip param block offset 8 (the handle) | ||||||||||||||
| 8A3D | BNE gbpbx0 ; Not at handle offset: continue | ||||||||||||||
| 8A3F | DEY ; Extra DEY to skip handle byte | ||||||||||||||
| 8A40 | .gbpbx0←1← 8A3D BNE | ||||||||||||||
| .gbpbf2←1← 8A3D BNE | |||||||||||||||
| DEX ; Decrement copy counter | |||||||||||||||
| 8A41 | BNE gbpbf1 ; Loop for all 6 bytes | ||||||||||||||
| 8A43 | PLA ; Recover function code from stack | ||||||||||||||
| 8A44 | LSR ; LSR: odd=read (C=1), even=write (C=0) | ||||||||||||||
| 8A45 | PHA ; Save function code again (need C later) | ||||||||||||||
| 8A46 | BCC gbpbl1 ; Even (write): X stays 0 | ||||||||||||||
| 8A48 | INX ; Odd (read): X=1 | ||||||||||||||
| 8A49 | .gbpbl1←1← 8A46 BCC | ||||||||||||||
| STX fs_func_code ; Store FS direction flag | |||||||||||||||
| 8A4C | LDY #&0b ; Y=&0B: command data extent | ||||||||||||||
| 8A4E | LDX #&91 ; Command &91=put, &92=get | ||||||||||||||
| 8A50 | PLA ; Recover function code | ||||||||||||||
| 8A51 | PHA ; Save again for later direction check | ||||||||||||||
| 8A52 | BEQ gbpb_write_path ; Even (write): keep &91 and Y=&0B | ||||||||||||||
| 8A54 | LDX #&92 ; Odd (read): use &92 (get) instead | ||||||||||||||
| 8A56 | DEY ; Read: one fewer data byte in command Y=&0a | ||||||||||||||
| 8A57 | .gbpb_write_path←1← 8A52 BEQ | ||||||||||||||
| STX fs_cmd_urd ; Store port to FS command URD field | |||||||||||||||
| 8A5A | STX fs_error_ptr ; Save port for error recovery | ||||||||||||||
| 8A5C | LDX #8 ; X=8: command data bytes | ||||||||||||||
| 8A5E | LDA fs_cmd_data ; Load handle from FS command data | ||||||||||||||
| 8A61 | JSR prepare_cmd_with_flag ; Build FS command with handle+flag | ||||||||||||||
| 8A64 | LDA fs_load_addr_3 ; Save seq# for byte-stream flow control | ||||||||||||||
| 8A66 | STA fs_sequence_nos ; Store to FS sequence number workspace | ||||||||||||||
| 8A69 | LDX #4 ; X=4: copy 4 address bytes | ||||||||||||||
| 8A6B | .gbpbl3←1← 8A7F BNE | ||||||||||||||
| LDA (fs_options),y ; Set up source/dest from param block | |||||||||||||||
| 8A6D | STA addr_work,y ; Store as source address | ||||||||||||||
| 8A70 | STA txcb_pos,y ; Store as current transfer position | ||||||||||||||
| 8A73 | JSR add_4_to_y ; Skip 4 bytes to reach transfer length | ||||||||||||||
| 8A76 | ADC (fs_options),y ; Dest = source + length | ||||||||||||||
| 8A78 | STA addr_work,y ; Store as end address | ||||||||||||||
| 8A7B | JSR sub_3_from_y ; Back 3 to align for next iteration | ||||||||||||||
| 8A7E | DEX ; Decrement byte counter | ||||||||||||||
| 8A7F | BNE gbpbl3 ; Loop for all 4 address bytes | ||||||||||||||
| 8A81 | INX ; X=1 after loop | ||||||||||||||
| 8A82 | .gbpbf3←1← 8A89 BPL | ||||||||||||||
| LDA fs_cmd_csd,x ; Copy CSD data to command buffer | |||||||||||||||
| 8A85 | STA fs_func_code,x ; Store at &0F06+X | ||||||||||||||
| 8A88 | DEX ; Decrement counter | ||||||||||||||
| 8A89 | BPL gbpbf3 ; Loop for X=1,0 | ||||||||||||||
| 8A8B | PLA ; Odd (read): send data to FS first | ||||||||||||||
| 8A8C | BNE gbpb_read_path ; Non-zero: skip write path | ||||||||||||||
| 8A8E | LDA fs_cmd_urd ; Load port for transfer setup | ||||||||||||||
| 8A91 | JSR transfer_file_blocks ; Transfer data blocks to fileserver | ||||||||||||||
| 8A94 | BNE wait_fs_reply ; Non-zero: branch past error ptr | ||||||||||||||
| 8A96 | .gbpb_read_path←1← 8A8C BNE | ||||||||||||||
| JSR send_data_blocks ; Read path: receive data blocks from FS | |||||||||||||||
| 8A99 | .wait_fs_reply←1← 8A94 BNE | ||||||||||||||
| JSR send_fs_reply_timed ; Wait for FS reply command | |||||||||||||||
| 8A9C | LDA (fs_options,x) ; Load handle mask for EOF flag update | ||||||||||||||
| 8A9E | BIT fs_cmd_data ; Check FS reply: bit 7 = not at EOF | ||||||||||||||
| 8AA1 | BMI skip_clear_flag ; Bit 7 set: not EOF, skip clear | ||||||||||||||
| 8AA3 | JSR clear_fs_flag ; At EOF: clear EOF hint for this handle | ||||||||||||||
| 8AA6 | .skip_clear_flag←1← 8AA1 BMI | ||||||||||||||
| JSR set_fs_flag ; Set EOF hint flag (may be at EOF) | |||||||||||||||
| 8AA9 | STX fs_load_addr_2 ; Direction=0: forward adjustment | ||||||||||||||
| 8AAB | JSR adjust_addrs_9 ; Adjust param block addrs by +9 bytes | ||||||||||||||
| 8AAE | DEC fs_load_addr_2 ; Direction=&FF: reverse adjustment | ||||||||||||||
| 8AB0 | SEC ; SEC for reverse subtraction | ||||||||||||||
| 8AB1 | JSR adjust_addrs ; Adjust param block addrs (reverse) | ||||||||||||||
| 8AB4 | ASL fs_cmd_data ; Shift bit 7 into C for return flag | ||||||||||||||
| 8AB7 | JMP return_a_zero ; Return via restore_args path | ||||||||||||||
| 8ABA | .get_disc_title←1← 8AEA BEQ | ||||||||||||||
| LDY #&15 ; Y=&15: function code for disc title Y=function code for HDRFN | |||||||||||||||
| 8ABC | JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references) | ||||||||||||||
| 8ABF | LDA fs_boot_option ; Load boot option from FS workspace | ||||||||||||||
| 8AC2 | STA fs_boot_data ; Store boot option in reply area | ||||||||||||||
| 8AC5 | STX fs_load_addr ; X=0: reply data start offset X=0 on success, &D6 on not-found | ||||||||||||||
| 8AC7 | STX fs_load_addr_hi ; Clear reply buffer high byte | ||||||||||||||
| 8AC9 | LDA #&12 ; A=&12: 18 bytes of reply data | ||||||||||||||
| 8ACB | STA fs_load_addr_2 ; Store as byte count for copy | ||||||||||||||
| 8ACD | 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. |
|
| 8ACF | .osgbpb_info←1← 8A26 JMP |
| LDY #4 ; Y=4: check param block byte 4 | |
| 8AD1 | LDA tx_in_progress ; Check if destination is in Tube space |
| 8AD4 | BEQ store_tube_flag ; No Tube: skip Tube address check |
| 8AD6 | CMP (fs_options),y ; Compare Tube flag with addr byte 4 |
| 8AD8 | BNE store_tube_flag ; Mismatch: not Tube space |
| 8ADA | DEY ; Y=&03 |
| 8ADB | SBC (fs_options),y ; Y=3: subtract addr byte 3 from flag |
| 8ADD | .store_tube_flag←2← 8AD4 BEQ← 8AD8 BNE |
| STA rom_svc_num ; Non-zero = Tube transfer required | |
| 8ADF | .info2←1← 8AE5 BNE |
| LDA (fs_options),y ; Copy param block bytes 1-4 to workspace | |
| 8AE1 | STA fs_last_byte_flag,y ; Store to &BD+Y workspace area |
| 8AE4 | DEY ; Previous byte |
| 8AE5 | BNE info2 ; Loop for bytes 3,2,1 |
| 8AE7 | PLA ; Sub-function: AND #3 of (original A - 4) |
| 8AE8 | AND #3 ; Mask to 0-3 (OSGBPB 5-8 → 0-3) |
| 8AEA | BEQ get_disc_title ; A=0 (OSGBPB 5): read disc title |
| 8AEC | LSR ; LSR: A=0 (OSGBPB 6) or A=1 (OSGBPB 7) |
| 8AED | BEQ gbpb6_read_name ; A=0 (OSGBPB 6): read CSD/LIB name |
| 8AEF | BCS gbpb8_read_dir ; C=1 (OSGBPB 8): read filenames from dir |
| 8AF1 | .gbpb6_read_name←1← 8AED BEQ |
| TAY ; Y=0 for CSD or carry for fn code select Y=function code | |
| 8AF2 | LDA fs_csd_handle,y ; Get CSD/LIB/URD handles for FS command |
| 8AF5 | STA fs_cmd_csd ; Store CSD handle in command buffer |
| 8AF8 | LDA fs_lib_handle ; Load LIB handle from workspace |
| 8AFB | STA fs_cmd_lib ; Store LIB handle in command buffer |
| 8AFE | LDA fs_urd_handle ; Load URD handle from workspace |
| 8B01 | STA fs_cmd_urd ; Store URD handle in command buffer |
| 8B04 | LDX #&12 ; X=&12: buffer extent for command data X=buffer extent (command-specific data bytes) |
| 8B06 | STX fs_cmd_y_param ; Store X as function code in header |
| 8B09 | LDA #&0d ; &0D = 13 bytes of reply data expected |
| 8B0B | STA fs_func_code ; Store reply length in command buffer |
| 8B0E | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8B10 | LSR ; LSR: &0D >> 1 = 6 A=timeout period for FS reply |
| 8B11 | STA fs_cmd_data ; Store as command data byte |
| 8B14 | CLC ; CLC for standard FS path |
| 8B15 | JSR build_send_fs_cmd ; Build and send FS command (DOFSOP) |
| 8B18 | STX fs_load_addr_hi ; X=0 on success, &D6 on not-found |
| 8B1A | INX ; INX: X=1 after build_send_fs_cmd |
| 8B1B | STX fs_load_addr ; Store X as reply start offset |
| 8B1D | .copy_reply_to_caller←2← 8ACD BNE← 8B90 JSR |
| LDA rom_svc_num ; Copy FS reply to caller's buffer | |
| 8B1F | BNE tube_transfer ; Non-zero: use Tube transfer path |
| 8B21 | LDX fs_load_addr ; X = reply start offset |
| 8B23 | LDY fs_load_addr_hi ; Y = reply buffer high byte |
| 8B25 | .copy_reply_bytes←1← 8B2E BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8B28 | STA (fs_crc_lo),y ; Store to caller's buffer |
| 8B2A | INX ; Next source byte |
| 8B2B | INY ; Next destination byte |
| 8B2C | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8B2E | BNE copy_reply_bytes ; Loop until all bytes copied |
| 8B30 | BEQ gbpb_done ; ALWAYS branch to exit ALWAYS branch |
| 8B32 | .tube_transfer←1← 8B1F BNE |
| JSR tube_claim_loop ; Claim Tube transfer channel | |
| 8B35 | LDA #1 ; A=1: Tube claim type 1 (write) |
| 8B37 | LDX fs_options ; X = param block address low |
| 8B39 | LDY fs_block_offset ; Y = param block address high |
| 8B3B | INX ; INX: advance past byte 0 |
| 8B3C | BNE no_page_wrap ; No page wrap: keep Y |
| 8B3E | INY ; Page wrap: increment high byte |
| 8B3F | .no_page_wrap←1← 8B3C BNE |
| JSR tube_addr_claim ; Claim Tube address for transfer | |
| 8B42 | LDX fs_load_addr ; X = reply data start offset |
| 8B44 | .tbcop1←1← 8B4D BNE |
| LDA fs_cmd_data,x ; Load reply data byte | |
| 8B47 | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 8B4A | INX ; Next source byte |
| 8B4B | DEC fs_load_addr_2 ; Decrement remaining bytes |
| 8B4D | BNE tbcop1 ; Loop until all bytes sent to Tube |
| 8B4F | LDA #&83 ; Release Tube after transfer complete |
| 8B51 | JSR tube_addr_claim ; Release Tube address claim |
| 8B54 | .gbpb_done←2← 8B30 BEQ← 8BAA BEQ |
| JMP return_a_zero ; Return via restore_args path | |
| 8B57 | .gbpb8_read_dir←1← 8AEF BCS |
| LDY #9 ; OSGBPB 8: read filenames from dir | |
| 8B59 | LDA (fs_options),y ; Byte 9: number of entries to read |
| 8B5B | STA fs_func_code ; Store as reply count in command buffer |
| 8B5E | LDY #5 ; Y=5: byte 5 = starting entry number |
| 8B60 | LDA (fs_options),y ; Load starting entry number |
| 8B62 | STA fs_data_count ; Store in command buffer |
| 8B65 | LDX #&0d ; X=&0D: command data extent X=preserved through header build |
| 8B67 | STX fs_reply_cmd ; Store extent in command buffer |
| 8B6A | LDY #2 ; Y=2: function code for dir read |
| 8B6C | STY fs_load_addr ; Store 2 as reply data start offset |
| 8B6E | STY fs_cmd_data ; Store 2 as command data byte |
| 8B71 | INY ; Y=3: function code for header read Y=function code for HDRFN Y=&03 |
| 8B72 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8B75 | STX fs_load_addr_hi ; X=0 after FS command completes X=0 on success, &D6 on not-found |
| 8B77 | LDA fs_func_code ; Load reply entry count |
| 8B7A | STA (fs_options,x) ; Store at param block byte 0 (X=0) |
| 8B7C | LDA fs_cmd_data ; Load entries-read count from reply |
| 8B7F | LDY #9 ; Y=9: param block byte 9 |
| 8B81 | ADC (fs_options),y ; Add to starting entry number |
| 8B83 | STA (fs_options),y ; Update param block with new position |
| 8B85 | LDA txcb_end ; Load total reply length |
| 8B87 | SBC #7 ; Subtract header (7 bytes) from reply len |
| 8B89 | STA fs_func_code ; Store adjusted length in command buffer |
| 8B8C | STA fs_load_addr_2 ; Store as byte count for copy loop |
| 8B8E | BEQ skip_copy_reply ; Zero bytes: skip copy |
| 8B90 | JSR copy_reply_to_caller ; Copy reply data to caller's buffer |
| 8B93 | .skip_copy_reply←1← 8B8E BEQ |
| LDX #2 ; X=2: clear 3 bytes | |
| 8B95 | .zero_cmd_bytes←1← 8B99 BPL |
| STA fs_data_count,x ; Zero out &0F07+X area | |
| 8B98 | DEX ; Next byte |
| 8B99 | BPL zero_cmd_bytes ; Loop for X=2,1,0 |
| 8B9B | JSR adjust_addrs_1 ; Adjust pointer by +1 (one filename read) |
| 8B9E | SEC ; SEC for reverse adjustment |
| 8B9F | DEC fs_load_addr_2 ; Reverse adjustment for updated counter |
| 8BA1 | LDA fs_cmd_data ; Load entries-read count |
| 8BA4 | STA fs_func_code ; Store in command buffer |
| 8BA7 | JSR adjust_addrs ; Adjust param block addresses |
| 8BAA | BEQ gbpb_done ; Z=1: all done, exit |
| 8BAC | .tube_claim_loop←3← 8B32 JSR← 8BB1 BCC← 8E0E JSR |
| LDA #&c3 ; A=&C3: Tube claim with retry | |
| 8BAE | JSR tube_addr_claim ; Request Tube address claim |
| 8BB1 | BCC tube_claim_loop ; C=0: claim failed, retry |
| 8BB3 | 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 &8BE2 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. |
|
| 8BB4 | .fscv_3_star_cmd←1← 827F JMP |
| JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up command ptr | |
| 8BB7 | LDX #&ff ; X=&FF: table index (pre-incremented) |
| 8BB9 | STX fs_crflag ; Disable column formatting |
| 8BBB | .scan_cmd_table←1← 8BD6 BNE |
| LDY #&ff ; Y=&FF: input index (pre-incremented) | |
| 8BBD | .decfir←1← 8BC8 BEQ |
| INY ; Advance input pointer | |
| 8BBE | INX ; Advance table pointer |
| 8BBF | .decmor←1← 8BDA BCS |
| LDA fs_cmd_match_table,x ; Load table character | |
| 8BC2 | BMI dispatch_cmd ; Bit 7: end of name, dispatch |
| 8BC4 | EOR (fs_crc_lo),y ; XOR input char with table char |
| 8BC6 | AND #&df ; Case-insensitive (clear bit 5) |
| 8BC8 | BEQ decfir ; Match: continue comparing |
| 8BCA | DEX ; Mismatch: back up table pointer |
| 8BCB | .decmin←1← 8BCF BPL |
| INX ; Skip to end of table entry | |
| 8BCC | LDA fs_cmd_match_table,x ; Load table byte |
| 8BCF | BPL decmin ; Loop until bit 7 set (end marker) |
| 8BD1 | LDA (fs_crc_lo),y ; Check input for '.' abbreviation |
| 8BD3 | INX ; Skip past handler high byte |
| 8BD4 | CMP #&2e ; Is input '.' (abbreviation)? |
| 8BD6 | BNE scan_cmd_table ; No: try next table entry |
| 8BD8 | INY ; Yes: skip '.' in input |
| 8BD9 | DEX ; Back to handler high byte |
| 8BDA | BCS decmor ; ALWAYS branch; dispatch via BMI |
| 8BDC | .dispatch_cmd←1← 8BC2 BMI |
| PHA ; Push handler address high byte | |
| 8BDD | LDA cmd_table_entry_1,x ; Load handler address low byte |
| 8BE0 | PHA ; Push handler address low byte |
| 8BE1 | RTS ; Dispatch via RTS (addr-1 on stack) |
FS command match table (COMTAB)Format: command letters (bit 7 clear), then dispatch address as two bytes: high|(bit 7 set), low. The PHA/PHA/RTS trick adds 1 to the stored (address-1). Matching is case-insensitive (AND &DF) and supports '.' abbreviation (standard Acorn pattern). Entries: "I." → &80B4 (forward_star_cmd) — placed first as a fudge to catch *I. abbreviation before matching *I AM "I AM" → &807E (i_am_handler: parse station.net, logon) "EX" → &8BF8 (ex_handler: extended catalogue) "BYE"\r → &838D (bye_handler: logoff) <catch-all> → &80B4 (forward anything else to FS) |
|
| 8BE2 | .fs_cmd_match_table←2← 8BBF LDA← 8BCC LDA |
| EOR #&2e ; Match last char against '.' for *I. abbreviation | |
| 8BE4 | EQUB &80, &B3 |
| 8BE6 | EQUS "I AM" |
| 8BEA | EQUB &80 |
| 8BEB | EQUS "}EX" |
| 8BEE | EQUB &8B, &F7 |
| 8BF0 | EQUS "BYE" |
| 8BF3 | EQUB &0D, &83, &8C, &80, &B3 |
| fall through ↓ | |
*EX handler (extended catalogue)Sets &B7=&01 and &B5=&03, then branches into fscv_5_cat at &8C0A, bypassing fscv_5_cat's default column setup. &B7=1 gives one entry per line with full details (vs &B7=3 for *CAT which gives multiple files per line). |
|
| 8BF8 | .ex_handler |
| LDX #1 ; X=1: single-entry-per-line mode | |
| 8BFA | STX fs_work_7 ; Store column format selector |
| 8BFC | LDA #3 ; A=3: FS function code for EX |
| 8BFE | BNE init_cat_params ; ALWAYS branch |
*CAT handler (directory catalogue)Sets column width &B6=&14 (20 columns, four files per 80-column line) and &B7=&03. The catalogue protocol is multi-step: first sends FCUSER (&15: read user environment) to get CSD, disc, and library names, then sends FCREAD (&12: examine) repeatedly to fetch entries in batches until zero are returned (end of dir). The receive buffer abuts the examine request buffer and ends at RXBUFE, allowing seamless data handling across request cycles. The command code byte in the fileserver reply indicates FS type: zero means an old-format FS (client must format data locally), non-zero means new-format (server returns pre-formatted strings). This enables backward compatibility with older Acorn fileservers. Display format: - Station number in parentheses - "Owner" or "Public" access level - Boot option with name (Off/Load/Run/Exec) - Current directory and library paths - Directory entries: CRFLAG (&CF) cycles 0-3 for multi-column layout; at count 0 a newline is printed, others get spaces. *EX sets CRFLAG=&FF to force one entry per line. |
|
| 8C00 | .fscv_5_cat |
| LDX #3 ; X=3: column count for multi-column layout | |
| 8C02 | STX fs_work_7 ; CRFLAG=3: first entry will trigger newline |
| 8C04 | LDY #0 ; Y=0: initialise column counter |
| 8C06 | STY fs_crflag ; Clear CRFLAG column counter |
| 8C08 | LDA #&0b ; A=&0B: examine argument count |
| 8C0A | .init_cat_params←1← 8BFE BNE |
| STA fs_work_5 ; Store examine argument count | |
| 8C0C | LDA #6 ; A=6: examine format type in command |
| 8C0E | STA fs_cmd_data ; Store format type at &0F05 |
| 8C11 | JSR parse_filename_gs_y ; Set up command parameter pointers |
| 8C14 | LDX #1 ; X=1: copy dir name at cmd offset 1 |
| 8C16 | JSR copy_string_to_cmd ; Copy directory name to command buffer |
| 8C19 | LDY #&12 ; Y=function code for HDRFN |
| 8C1B | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8C1E | LDX #3 ; X=3: start printing from reply offset 3 |
| 8C20 | JSR print_reply_bytes ; Print directory title (10 chars) |
| 8C23 | JSR print_inline ; Print '(' |
| 8C26 | EQUS "(" |
| 8C27 | LDA fs_reply_stn ; Load station number from FS reply |
| 8C2A | JSR print_decimal ; Print station number as decimal |
| 8C2D | JSR print_inline ; Print ') ' |
| 8C30 | EQUS ") " |
| 8C36 | LDX fs_access_level ; Load access level from reply |
| 8C39 | BNE print_public ; Non-zero: Public access |
| 8C3B | JSR print_inline ; Print 'Owner' + CR |
| 8C3E | EQUS "Owner." |
| 8C44 | BNE cat_print_header ; ALWAYS branch past 'Owner' |
| 8C46 | .print_public←1← 8C39 BNE |
| JSR print_inline ; Print 'Public' + CR | |
| 8C49 | EQUS "Public." |
| 8C50 | .cat_print_header←1← 8C44 BNE |
| LDY #&15 ; Y=function code for HDRFN | |
| 8C52 | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8C55 | INX ; X=1: past command code byte |
| 8C56 | LDY #&10 ; Y=&10: print 16 characters |
| 8C58 | JSR print_reply_counted ; Print disc/CSD name from reply |
| 8C5B | JSR print_inline ; Print ' Option ' |
| 8C5E | EQUS " Option " |
| 8C69 | LDA fs_boot_option ; Load boot option from reply |
| 8C6C | TAX ; X = boot option for name table lookup |
| 8C6D | JSR print_hex ; Print boot option as hex digit |
| 8C70 | JSR print_inline ; Print ' (' |
| 8C73 | EQUS " (" |
| 8C75 | LDY boot_option_strings,x ; Y=string offset for this option |
| 8C78 | .print_option_char←1← 8C81 BNE |
| LDA boot_option_strings,y ; Load next char of option name | |
| 8C7B | BMI done_option_name ; Bit 7 set: end of option name |
| 8C7D | JSR osasci ; Write character |
| 8C80 | INY ; Next character |
| 8C81 | BNE print_option_char ; Continue printing option name |
| 8C83 | .done_option_name←1← 8C7B BMI |
| JSR print_inline ; Print ')' + CR + 'Dir. ' | |
| 8C86 | EQUS ").Dir. " |
| 8C8D | LDX #&11 ; X=&11: Dir. name offset in reply |
| 8C8F | JSR print_reply_bytes ; Print directory name (10 chars) |
| 8C92 | JSR print_inline ; Print ' Lib. ' header |
| 8C95 | EQUS " Lib. " |
| 8C9F | LDX #&1b ; X=&1B: Lib. name offset in reply |
| 8CA1 | JSR print_reply_bytes ; Print library name |
| 8CA4 | JSR print_inline ; Print two CRs (blank line) |
| 8CA7 | EQUS ".." |
| 8CA9 | STY fs_func_code ; Store next examine start offset |
| 8CAC | STY fs_work_4 ; Save start offset in zero page for loop |
| 8CAE | LDX fs_work_5 ; Load examine arg count for batch size |
| 8CB0 | STX fs_data_count ; Store as request count at &0F07 |
| 8CB3 | .cat_examine_loop←1← 8CDC BNE |
| LDX fs_work_7 ; Load column count for display format | |
| 8CB5 | STX fs_cmd_data ; Store column count in command data |
| 8CB8 | LDX #3 ; X=3: copy directory name at offset 3 |
| 8CBA | JSR copy_string_to_cmd ; Append directory name to examine command |
| 8CBD | LDY #3 ; Y=function code for HDRFN |
| 8CBF | JSR prepare_fs_cmd ; Prepare FS command buffer (12 references) |
| 8CC2 | LDA fs_cmd_data ; Load entry count from reply |
| 8CC5 | BEQ print_newline ; Zero entries returned: catalogue done |
| 8CC7 | LDX #2 ; X=2: first entry offset in reply |
| 8CC9 | JSR print_dir_from_offset ; Print/format this directory entry |
| 8CCC | CLC ; CLC for addition |
| 8CCD | LDA fs_work_4 ; Load current examine start offset |
| 8CCF | ADC fs_cmd_data ; Add entries returned this batch |
| 8CD2 | STA fs_func_code ; Update next examine start offset |
| 8CD5 | STA fs_work_4 ; Save updated start offset |
| 8CD7 | LDA fs_work_5 ; Reload batch size for next request |
| 8CD9 | STA fs_data_count ; Store batch size in command buffer |
| 8CDC | BNE cat_examine_loop ; Loop for remaining characters |
| ; Option name encoding: in 3.35, 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 c8cde (&8CDE), whose first four bytes | |
| ; (starting with the ROR A opcode &6A) double as the offset ; table: | |
| ; &6A→&8D48 "Off", &7D→&8D5B "Load", | |
| ; &A5→&8D83 "Run", &18→&8CF6 "Exec" | |
| ; Each string is terminated by the next instruction's opcode | |
| ; having bit 7 set (e.g. LDA #imm = &A9, RTS = &60). | |
| 8CDE | .boot_option_strings←2← 8C75 LDY← 8C78 LDA |
| ROR ; Boot option string offset table | |
| 8CDF | ADC l18a5,x ; Dual-purpose: offset data + code |
| 8CE2 | JMP l212e ; Fallthrough (also boot string 'L.!') |
Boot command strings for auto-bootThe four boot options use OSCLI strings at offsets within page &8C: Option 0 (Off): offset &F1 → &8CF1 = bare CR (empty command) Option 1 (Load): offset &E2 → &8CE2 = "L.!BOOT" (dual-purpose: the JMP &212E instruction at &8CE2 has opcode &4C='L' and operand bytes &2E='.' &21='!', forming the string "L.!") Option 2 (Run): offset &E4 → boot_cmd_strings-1 = "!BOOT" (*RUN) Option 3 (Exec): offset &EA → &8CEA = "E.!BOOT" This is a classic BBC ROM space optimisation: the JMP instruction's bytes serve double duty as both executable code and ASCII text. |
|
| 8CE5 | .boot_cmd_strings |
| EQUS "BOOT" | |
| 8CE9 | EQUB &0D |
| 8CEA | EQUS "E.!BOOT" |
| 8CF1 | EQUB &0D |
Boot option → OSCLI string offset tableFour bytes indexed by the boot option value (0-3). Each byte is the low byte of a pointer into page &8C, where the OSCLI command string for that boot option lives. See boot_cmd_strings. |
|
| 8CF2 | .boot_option_offsets←1← 8E3B LDX |
| EQUB &F1 ; Opt 0 (Off): bare CR | |
| 8CF3 | EQUB &E2 ; Opt 1 (Load): L.!BOOT |
| 8CF4 | EQUB &E4 ; Opt 2 (Run): !BOOT |
| 8CF5 | EQUB &EA ; Opt 3 (Exec): E.!BOOT |
| 8CF6 | EQUS "Exec" ; Data bytes: boot_cmd_strings 'ec' |
Print file catalogue lineDisplays a formatted catalogue entry: filename (padded to 12 chars with spaces), load address (4 hex bytes at offset 5-2), exec address (4 hex bytes at offset 9-6), and file length (3 hex bytes at offset &0C-&0A), followed by a newline. Data is read from (fs_crc_lo) for the filename and from (fs_options) for the numeric fields. Returns immediately if fs_messages_flag is zero (no info available). |
|
| 8CFA | .print_file_info←2← 8730 JSR← 87B0 JSR |
| LDY fs_messages_flag ; Check if messages enabled | |
| 8CFD | BEQ return_5 ; Zero: no info to display, return |
| 8CFF | LDY #0 ; Y=0: start of filename |
| 8D01 | LDX fs_cmd_csd ; Load CSD handle from reply |
| 8D04 | BEQ next_filename_char ; Zero: no dir prefix needed |
| 8D06 | JSR print_dir_from_offset ; Print directory prefix |
| 8D09 | BMI print_hex_fields ; ALWAYS: skip to hex field output |
| 8D0B | .next_filename_char←2← 8D04 BEQ← 8D19 BNE |
| LDA (fs_crc_lo),y ; Load next filename character | |
| 8D0D | CMP #&0d ; CR: end of filename |
| 8D0F | BEQ pad_filename_spaces ; CR found: pad remaining with spaces |
| 8D11 | CMP #&20 ; Space: end of name field |
| 8D13 | BEQ pad_filename_spaces ; Space found: pad with spaces |
| 8D15 | JSR osasci ; Write character |
| 8D18 | INY ; Advance to next character |
| 8D19 | BNE next_filename_char ; Continue printing filename |
| 8D1B | .pad_filename_spaces←3← 8D0F BEQ← 8D13 BEQ← 8D21 BCC |
| JSR print_space ; Print space for padding | |
| 8D1E | INY ; Advance column counter |
| 8D1F | CPY #&0c ; Reached 12 columns? |
| 8D21 | BCC pad_filename_spaces ; No: continue padding |
| 8D23 | .print_hex_fields←1← 8D09 BMI |
| LDY #5 ; Y=5: load address offset (4 bytes) | |
| 8D25 | JSR print_hex_bytes ; Print load address |
| 8D28 | JSR print_exec_and_len ; Print exec address and file length |
| 8D2B | .print_newline←1← 8CC5 BEQ |
| JMP osnewl ; Write newline (characters 10 and 13) | |
| 8D2E | .print_exec_and_len←1← 8D28 JSR |
| LDY #9 ; Y=9: exec address offset (4 bytes) | |
| 8D30 | JSR print_hex_bytes ; Print exec address |
| 8D33 | LDY #&0c ; Y=&0C: file length offset |
| 8D35 | LDX #3 ; X=3: print 3 bytes (24-bit length) |
| 8D37 | BNE num01 ; ALWAYS branch |
| 8D39 | .print_hex_bytes←2← 8D25 JSR← 8D30 JSR |
| LDX #4 ; X=4: print 4 hex bytes | |
| 8D3B | .num01←2← 8D37 BNE← 8D42 BNE |
| LDA (fs_options),y ; Load byte from parameter block | |
| 8D3D | JSR print_hex ; Print as two hex digits |
| 8D40 | DEY ; Next byte (descending) |
| 8D41 | DEX ; Count down |
| 8D42 | BNE num01 ; Loop until 4 bytes printed |
| 8D44 | .print_space←1← 8D1B JSR |
| LDA #&20 ; A=space character | |
| 8D46 | BNE print_char_always ; ALWAYS branch |
| 8D48 | EQUS "Off" |
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. |
|
| 8D4B | .copy_filename←4← 80B4 JSR← 86F8 JSR← 88BB JSR← 8DCA 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.
|
|||||||||||
| 8D4D | .copy_string_to_cmd←6← 87A6 JSR← 88B4 JSR← 88D6 JSR← 8996 JSR← 8C16 JSR← 8CBA JSR | ||||||||||
| LDY #0 ; Start copying from offset 0 | |||||||||||
| 8D4F | .copy_string_from_offset←1← 8D58 BNE | ||||||||||
| LDA (fs_crc_lo),y ; Load next byte from source string | |||||||||||
| 8D51 | STA fs_cmd_data,x ; Store to FS command buffer (&0F05+X) | ||||||||||
| 8D54 | INX ; Advance write position | ||||||||||
| 8D55 | INY ; Advance source pointer | ||||||||||
| 8D56 | EOR #&0d ; XOR with CR: result=0 if byte was CR | ||||||||||
| 8D58 | BNE copy_string_from_offset ; Loop until CR copied | ||||||||||
| 8D5A | .return_5←2← 8CFD BEQ← 8D64 BMI | ||||||||||
| RTS ; Return; X = next free position in buffer | |||||||||||
| 8D5B | EQUS "Load" | ||||||||||
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. |
|
| 8D5F | .fsreply_0_print_dir |
| LDX #0 ; X=0: start from first reply byte | |
| 8D61 | .print_dir_from_offset←3← 8CC9 JSR← 8D06 JSR← 8D81 BNE |
| LDA fs_cmd_data,x ; Load byte from FS reply buffer | |
| 8D64 | BMI return_5 ; Bit 7 set: end of string, return |
| 8D66 | BNE infol2 ; 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. |
|
| 8D68 | .cat_column_separator |
| LDY fs_crflag ; Null byte: check column counter | |
| 8D6A | BMI print_cr_separator ; Negative: print CR (no columns) |
| 8D6C | INY ; Advance column counter |
| 8D6D | TYA ; Transfer to A for modulo |
| 8D6E | AND #3 ; Modulo 4 columns |
| 8D70 | STA fs_crflag ; Update column counter |
| 8D72 | BEQ print_cr_separator ; Column 0: start new line |
| 8D74 | JSR print_inline ; Print 2-space column separator |
| 8D77 | EQUS " " |
| 8D79 | BNE next_dir_entry ; More entries: skip final newline |
| 8D7B | .print_cr_separator←2← 8D6A BMI← 8D72 BEQ |
| LDA #&0d ; A=CR: print newline separator | |
| 8D7D | .infol2←1← 8D66 BNE |
| JSR osasci ; Write character 13 | |
| 8D80 | .next_dir_entry←1← 8D79 BNE |
| INX ; Next byte in reply buffer | |
| 8D81 | BNE print_dir_from_offset ; Loop until end of buffer |
| 8D83 | EQUS "Run" |
| 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.
|
|||||||||||
| 8D86 | .print_decimal←2← 8238 JSR← 8C2A JSR | ||||||||||
| TAY ; Y = value to print | |||||||||||
| 8D87 | LDA #&64 ; Divisor = 100 (hundreds digit) | ||||||||||
| 8D89 | JSR print_decimal_digit ; Print hundreds digit | ||||||||||
| 8D8C | LDA #&0a ; Divisor = 10 (tens digit) | ||||||||||
| 8D8E | JSR print_decimal_digit ; Print tens digit | ||||||||||
| 8D91 | 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.
|
|||||||||
| 8D93 | .print_decimal_digit←2← 8D89 JSR← 8D8E JSR | ||||||||
| STA fs_error_ptr ; Save divisor to workspace | |||||||||
| 8D95 | TYA ; A = dividend (from Y) | ||||||||
| 8D96 | LDX #&2f ; X = &2F = ASCII '0' - 1 | ||||||||
| 8D98 | SEC ; Prepare for subtraction | ||||||||
| 8D99 | .divide_subtract←1← 8D9C BCS | ||||||||
| INX ; Count one subtraction (next digit value) | |||||||||
| 8D9A | SBC fs_error_ptr ; A = A - divisor | ||||||||
| 8D9C | BCS divide_subtract ; Loop while A >= 0 (borrow clear) | ||||||||
| 8D9E | ADC fs_error_ptr ; Undo last subtraction: A = remainder | ||||||||
| 8DA0 | TAY ; Y = remainder for caller | ||||||||
| 8DA1 | TXA ; A = X = ASCII digit character | ||||||||
| 8DA2 | .print_digit←1← 8DB8 BNE | ||||||||
| JMP osasci ; Write character | |||||||||
Print byte as two hex digitsPrints the high nibble first (via 4× LSR), then the low nibble. Each nibble is converted to ASCII '0'-'9' or 'A'-'F' and output via OSASCI.
|
|||||||||
| 8DA5 | .print_hex←2← 8C6D JSR← 8D3D JSR | ||||||||
| PHA ; Save byte for low nibble | |||||||||
| 8DA6 | LSR ; Shift high nibble to low position | ||||||||
| 8DA7 | LSR ; Shift high nibble to low position | ||||||||
| 8DA8 | LSR ; Shift high nibble to low position | ||||||||
| 8DA9 | LSR ; Shift high nibble to low position | ||||||||
| 8DAA | JSR print_hex_nibble ; Print high nibble as hex digit | ||||||||
| 8DAD | PLA ; Restore original byte | ||||||||
| 8DAE | AND #&0f ; Mask to low nibble | ||||||||
| 8DB0 | .print_hex_nibble←1← 8DAA JSR | ||||||||
| ORA #&30 ; Convert to ASCII '0' base | |||||||||
| 8DB2 | CMP #&3a ; Above '9'? | ||||||||
| 8DB4 | BCC print_char_always ; No: digit 0-9, skip adjustment | ||||||||
| 8DB6 | ADC #6 ; Add 7 (6+C) for 'A'-'F' | ||||||||
| 8DB8 | .print_char_always←2← 8D46 BNE← 8DB4 BCC | ||||||||
| BNE print_digit ; ALWAYS branch to print character | |||||||||
Print reply buffer bytesPrints Y characters from the FS reply buffer (&0F05+X) to the screen via OSASCI. X = starting offset, Y = count. Used by fscv_5_cat to display directory and library names. |
|
| 8DBA | .print_reply_bytes←3← 8C20 JSR← 8C8F JSR← 8CA1 JSR |
| LDY #&0a ; Y=10: character count | |
| 8DBC | .print_reply_counted←2← 8C58 JSR← 8DC4 BNE |
| LDA fs_cmd_data,x ; Load byte from FS reply buffer | |
| 8DBF | JSR osasci ; Write character |
| 8DC2 | INX ; Next buffer offset |
| 8DC3 | DEY ; Decrement character counter |
| 8DC4 | BNE print_reply_counted ; Loop until all chars printed |
| 8DC6 | RTS ; Return to caller |
FSCV 2/4: */ (run) and *RUN handlerParses the filename via parse_filename_gs and copies it into the command buffer, then falls through to fsreply_4_notify_exec to send the FS load-as-command request. |
|
| 8DC7 | .fscv_2_star_run |
| JSR parse_filename_gs ; Parse filename from command line | |
| 8DCA | JSR copy_filename ; Copy filename to FS command buffer |
| fall through ↓ | |
Send FS load-as-command and execute responseSets up an FS command with function code &05 (FCCMND: load as command) using send_fs_examine. If a Tube co-processor is present (tx_in_progress != 0), transfers the response data to the Tube via tube_addr_claim. Otherwise jumps via the indirect pointer at (&0F09) to execute at the load address. |
|
| 8DCD | .fsreply_4_notify_exec |
| LDX #&0e ; X=&0E: FS command buffer offset | |
| 8DCF | STX fs_block_offset ; Store block offset for FS command |
| 8DD1 | LDA #&10 ; A=&10: 16 bytes of command data |
| 8DD3 | STA fs_options ; Store options byte |
| 8DD5 | STA fs_work_16 ; Store to FS workspace |
| 8DD8 | LDX #&4a ; X=&4A: TXCB size for load command |
| 8DDA | LDY #5 ; Y=5: FCCMND (load as command) |
| 8DDC | JSR send_fs_examine ; Send FS examine/load command |
| 8DDF | LDY #0 ; Y=0: init GSINIT string offset |
| 8DE1 | CLC ; CLC: no flags for GSINIT |
| 8DE2 | JSR gsinit ; Init string scanning state |
| 8DE5 | .gsread_scan_loop←1← 8DE8 BCC |
| JSR gsread ; Read next char via GSREAD | |
| 8DE8 | BCC gsread_scan_loop ; More chars: continue scanning |
| 8DEA | DEY ; Back up Y to last valid char |
| 8DEB | .skip_filename_spaces←1← 8DF0 BEQ |
| INY ; Advance past current position | |
| 8DEC | LDA (os_text_ptr),y ; Read char from command line |
| 8DEE | CMP #&20 ; Is it a space? |
| 8DF0 | BEQ skip_filename_spaces ; Skip leading spaces in filename |
| 8DF2 | CLC ; CLC for pointer addition |
| 8DF3 | TYA ; A = Y (offset past spaces) |
| 8DF4 | ADC os_text_ptr ; Add base pointer to get abs addr |
| 8DF6 | STA fs_cmd_context ; Store filename pointer (low) |
| 8DF9 | LDA os_text_ptr_hi ; Load text pointer high byte |
| 8DFB | ADC #0 ; Add carry from low byte addition |
| 8DFD | STA fs_context_hi ; Store filename pointer (high) |
| 8E00 | SEC ; SEC for address range test |
| 8E01 | LDA tx_in_progress ; Check for Tube co-processor |
| 8E04 | BEQ exec_at_load_addr ; No Tube: execute locally |
| 8E06 | ADC fs_load_upper ; Check load address upper bytes |
| 8E09 | ADC fs_addr_check ; Continue address range check |
| 8E0C | BCS exec_at_load_addr ; Carry set: not Tube space, exec locally |
| 8E0E | JSR tube_claim_loop ; Claim Tube transfer channel |
| 8E11 | LDX #9 ; X=9: source offset in FS reply |
| 8E13 | LDY #&0f ; Y=&0F: page &0F (FS command buffer) |
| 8E15 | LDA #4 ; A=4: Tube transfer type 4 (256-byte) |
| 8E17 | JMP tube_addr_claim ; Transfer data to Tube co-processor |
| 8E1A | .exec_at_load_addr←2← 8E04 BEQ← 8E0C BCS |
| 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.
|
||||
| 8E1D | .fsreply_5_set_lib | |||
| STY fs_lib_handle ; Save library handle from FS reply | ||||
| 8E20 | BNE jmp_restore_args ; Non-zero: jump to restore exit | |||
| fall through ↓ | ||||
Set CSD handleStores Y into &0E03 (current selected directory handle). Falls through to JMP restore_args_return.
|
||||
| 8E22 | .fsreply_3_set_csd | |||
| STY fs_csd_handle ; Store CSD handle from FS reply | ||||
| 8E25 | .jmp_restore_args←2← 8E20 BNE← 8E36 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. |
|
| 8E28 | .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 jmp_restore_args. Called when the FS reply contains updated handle values but no boot action is needed. |
|
| 8E29 | .fsreply_2_copy_handles |
| LDX #3 ; Copy 4 bytes: boot option + 3 handles | |
| 8E2B | BCC copy_handles_loop ; SDISC: skip boot option, copy handles only |
| 8E2D | .logon2←1← 8E34 BPL |
| LDA fs_cmd_data,x ; Load from FS reply (&0F05+X) | |
| 8E30 | STA fs_urd_handle,x ; Store to handle workspace (&0E02+X) |
| 8E33 | .copy_handles_loop←1← 8E2B BCC |
| DEX ; Next handle (descending) | |
| 8E34 | BPL logon2 ; Loop while X >= 0 |
| 8E36 | 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. |
|
| 8E38 | .boot_cmd_execute |
| LDY fs_boot_option ; Y = boot option from FS workspace | |
| 8E3B | LDX boot_option_offsets,y ; X = command string offset from table |
| 8E3E | LDY #&8c ; Y = &8D (high byte of command address) |
| 8E40 | JMP oscli ; Execute boot command string via OSCLI |
*NET1: read file handle from received packetReads a file handle byte from offset &6F in the RX buffer (net_rx_ptr), stores it in &F0, then returns. |
|
| 8E43 | .net_1_read_handle |
| LDY #&6f ; Y=&6F: offset to handle byte in RX buf | |
| 8E45 | LDA (net_rx_ptr),y ; Load file handle from RX buffer |
| 8E47 | STA osword_pb_ptr ; Store in parameter block pointer (&F0) |
| 8E49 | RTS ; Return to caller |
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. |
|
| 8E4A | .load_handle_calc_offset←2← 8E5E JSR← 8E6E 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.
|
|||||||||||
| 8E4C | .calc_handle_offset←3← 82F9 JSR← 8F7F JSR← 8F98 JSR | ||||||||||
| ASL ; A = handle * 2 | |||||||||||
| 8E4D | ASL ; A = handle * 4 | ||||||||||
| 8E4E | PHA ; Push handle*4 onto stack | ||||||||||
| 8E4F | ASL ; A = handle * 8 | ||||||||||
| 8E50 | TSX ; X = stack pointer | ||||||||||
| 8E51 | ADC l0101,x ; A = handle*8 + handle*4 = handle*12 | ||||||||||
| 8E54 | TAY ; Y = offset into handle workspace | ||||||||||
| 8E55 | PLA ; Clean up stack (discard handle*4) | ||||||||||
| 8E56 | CMP #&48 ; Offset >= &48? (6 handles max) | ||||||||||
| 8E58 | BCC return_6 ; Valid: return with C clear | ||||||||||
| 8E5A | LDY #0 ; Invalid: Y = 0 | ||||||||||
| 8E5C | TYA ; A = 0, C set (error) A=&00 | ||||||||||
| 8E5D | .return_6←1← 8E58 BCC | ||||||||||
| .return_calc_handle←1← 8E58 BCC | |||||||||||
| RTS ; Return after calculation | |||||||||||
*NET2: read handle entry from workspaceLooks up the handle in &F0 via calc_handle_offset. If the workspace slot contains &3F ('?', meaning unused/closed), returns 0. Otherwise returns the stored handle value. Clears fs_temp_ce on exit. |
|
| 8E5E | .net_2_read_handle_entry |
| JSR load_handle_calc_offset ; Look up handle &F0 in workspace | |
| 8E61 | BCS rxpol2 ; Invalid handle: return 0 |
| 8E63 | LDA (nfs_workspace),y ; Load stored handle value |
| 8E65 | CMP #&3f ; &3F = unused/closed slot marker |
| 8E67 | BNE store_handle_return ; Slot in use: return actual value |
| 8E69 | .rxpol2←2← 8E61 BCS← 8E71 BCS |
| LDA #0 ; Return 0 for closed/invalid handle | |
| 8E6B | .store_handle_return←1← 8E67 BNE |
| STA osword_pb_ptr ; Store result back to &F0 | |
| 8E6D | 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. Preserves the carry flag state across the write using ROL/ROR on rx_status_flags. Clears fs_temp_ce on exit. |
|
| 8E6E | .net_3_close_handle |
| JSR load_handle_calc_offset ; Look up handle &F0 in workspace | |
| 8E71 | BCS rxpol2 ; Invalid handle: return 0 |
| 8E73 | ROL rx_flags ; Preserve carry via ROL |
| 8E76 | LDA #&3f ; A=&3F: handle closed/unused marker |
| 8E78 | STA (nfs_workspace),y ; Mark handle as closed in workspace |
| fall through ↓ | |
Restore RX flags after close handlePerforms ROR on rx_flags to restore the carry flag state that was preserved by the matching ROL in net_3_close_handle. Falls through to osword_12_handler (clearing fs_temp_ce). |
|
| ; OSWORD &12 handler: read/set state information (RS) | |
| ; Dispatches on the sub-function code (0-9): | |
| ; 0: read FS station (FSLOCN at &0E00) | |
| ; 1: set FS station | |
| ; 2: read printer server station (PSLOCN) | |
| ; 3: set printer server station | |
| ; 4: read protection masks (LSTAT at &D63) | |
| ; 5: set protection masks | |
| ; 6: read context handles (URD/CSD/LIB, converted from | |
| ; internal single-bit form back to handle numbers) | |
| ; 7: set context handles (converted to internal form) | |
| ; 8: read local station number | |
| ; 9: read JSR arguments buffer size | |
| ; Even-numbered sub-functions read; odd-numbered ones write. | |
| ; Uses the bidirectional copy at &8EB1 for station read/set. | |
| 8E7A | .restore_rx_flags |
| ROR rx_flags ; Restore carry via ROR | |
| 8E7D | RTS ; Return |
| fall through ↓ | |
Filing system OSWORD entrySubtracts &0F from the command code in &EF, giving a 0-4 index for OSWORD calls &0F-&13 (15-19). Falls through to the PHA/PHA/RTS dispatch at &8E88. |
|
| 8E7E | .svc_8_osword |
| LDA osbyte_a_copy ; Command code from &EF | |
| 8E80 | SBC #&0f ; Subtract &0F: OSWORD &0F-&13 become indices 0-4 |
| 8E82 | BMI return_7 ; Outside our OSWORD range, exit |
| 8E84 | CMP #5 ; Only OSWORDs &0F-&13 (index 0-4) |
| 8E86 | BCS return_7 ; Index >= 5: not ours, return |
| fall through ↓ | |
| 8E88 | .fs_osword_dispatch |
| TAX ; X = sub-function code for table lookup | |
| 8E89 | LDA #&8f ; Push return addr high (restore_args) |
| 8E8B | PHA ; Push return addr high byte |
| 8E8C | LDA #&c6 ; Push return addr low (restore_args) |
| 8E8E | PHA ; Push return addr low byte |
| 8E8F | LDA osword_0f_tbl_hi,x ; Load handler address high byte from table |
| 8E92 | PHA ; Push high byte for RTS dispatch |
| 8E93 | LDA osword_0f_tbl_lo,x ; Load handler address low byte from table |
| 8E96 | PHA ; Dispatch table: low bytes for OSWORD &0F-&13 handlers |
| 8E97 | LDY #2 ; Y=2: save 3 bytes (&AA-&AC) |
| 8E99 | .save1←1← 8E9F BPL |
| LDA osword_flag,y ; Load param block pointer byte | |
| 8E9C | STA (net_rx_ptr),y ; Save to NFS workspace via (net_rx_ptr) |
| 8E9E | DEY ; Next byte (descending) |
| 8E9F | BPL save1 ; Loop for all 3 bytes |
| 8EA1 | INY ; Y=0 after BPL exit; INY makes Y=1 |
| 8EA2 | LDA (osword_pb_ptr),y ; Read sub-function code from (&F0)+1 |
| 8EA4 | STY rom_svc_num ; Store Y=1 to &A9 |
| 8EA6 | RTS ; RTS dispatches to pushed handler address |
| 8EA7 | .osword_0f_tbl_lo←1← 8E93 LDA |
| EQUB <(osword_0f_handler-1) ; Dispatch table: low bytes for OSWORD &0F-&13 handlers | |
| 8EA8 | EQUB <(osword_10_handler-1) |
| 8EA9 | .fs_osword_tbl_lo |
| EQUB <(osword_11_handler-1) | |
| 8EAA | EQUB <(osword_12_dispatch-1) |
| 8EAB | EQUB <(econet_tx_rx-1) |
| 8EAC | .osword_0f_tbl_hi←1← 8E8F LDA |
| EQUB >(osword_0f_handler-1) ; Dispatch table: high bytes for OSWORD &0F-&13 handlers | |
| 8EAD | EQUB >(osword_10_handler-1) |
| 8EAE | .fs_osword_tbl_hi |
| EQUB >(osword_11_handler-1) | |
| 8EAF | EQUB >(osword_12_dispatch-1) |
| 8EB0 | EQUB >(econet_tx_rx-1) |
Bidirectional block copy between OSWORD param block and workspace.C=1: copy X+1 bytes from (&F0),Y to (fs_crc_lo),Y (param to workspace) C=0: copy X+1 bytes from (fs_crc_lo),Y to (&F0),Y (workspace to param) |
|
| 8EB1 | .copy_param_block←5← 8EBD BPL← 8ED4 JSR← 8EE9 JSR← 8F1A JMP← 8FAF JSR |
| BCC load_workspace_byte ; C=0: skip param-to-workspace copy | |
| 8EB3 | LDA (osword_pb_ptr),y ; Load byte from param block |
| 8EB5 | STA (ws_ptr_lo),y ; Store to workspace |
| 8EB7 | .load_workspace_byte←1← 8EB1 BCC |
| LDA (ws_ptr_lo),y ; Load byte from workspace | |
| 8EB9 | STA (osword_pb_ptr),y ; Store to param block (no-op if C=1) |
| 8EBB | .copyl3 |
| INY ; Advance to next byte | |
| 8EBC | DEX ; Decrement byte counter |
| 8EBD | BPL copy_param_block ; Loop while X >= 0 |
| 8EBF | .return_7←2← 8E82 BMI← 8E86 BCS |
| .logon3←2← 8E82 BMI← 8E86 BCS | |
| .return_copy_param←2← 8E82 BMI← 8E86 BCS | |
| RTS ; Return after copy | |
OSWORD &0F handler: initiate transmit (CALLTX)Checks the TX semaphore (TXCLR at &0D62) via ASL -- if carry is clear, a TX is already in progress and the call returns an error, preventing user code from corrupting a system transmit. Otherwise copies 16 bytes from the caller's OSWORD parameter block into the user TX control block (UTXCB) in static workspace. The TXCB pointer is copied to LTXCBP only after the semaphore is claimed, ensuring the low-level transmit code (BRIANX) sees a consistent pointer -- if copied before claiming, another transmitter could modify TXCBP between the copy and the claim.
|
|||||||||||||
| 8EC0 | .osword_0f_handler | ||||||||||||
| ASL tx_clear_flag ; ASL: set C if TX in progress | |||||||||||||
| 8EC3 | TYA ; Save param block high byte to A | ||||||||||||
| 8EC4 | BCC readry ; C=0: read path | ||||||||||||
| 8EC6 | LDA net_rx_ptr_hi ; User TX CB in workspace page (high byte) | ||||||||||||
| 8EC8 | STA ws_ptr_hi ; Set param block high byte | ||||||||||||
| 8ECA | STA nmi_tx_block_hi ; Set LTXCBP high byte for low-level TX | ||||||||||||
| 8ECC | LDA #&6f ; &6F: offset into workspace for user TXCB | ||||||||||||
| 8ECE | STA ws_ptr_lo ; Set param block low byte | ||||||||||||
| 8ED0 | STA nmi_tx_block ; Set LTXCBP low byte for low-level TX | ||||||||||||
| 8ED2 | LDX #&0f ; X=15: copy 16 bytes (OSWORD param block) | ||||||||||||
| 8ED4 | JSR copy_param_block ; Copy param block to user TX control block | ||||||||||||
| 8ED7 | JMP trampoline_tx_setup ; 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 (&8E6B) to return just the buffer size and args size without copying the data. |
|
| 8EDA | .osword_11_handler |
| LDA net_rx_ptr_hi ; Set source high byte from workspace page | |
| 8EDC | STA ws_ptr_hi ; Store as copy source high byte in &AC |
| 8EDE | LDY #&7f ; JSRSIZ at workspace offset &7F |
| 8EE0 | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8EE2 | INY ; Y=&80: start of JSR argument data Y=&80 |
| 8EE3 | STY ws_ptr_lo ; Store &80 as copy source low byte |
| 8EE5 | TAX ; X = buffer size (loop counter) |
| 8EE6 | DEX ; X = size-1 (0-based count for copy) |
| 8EE7 | LDY #0 ; Y=0: start of destination param block |
| 8EE9 | JSR copy_param_block ; Copy X+1 bytes from workspace to param |
| 8EEC | JMP clear_jsr_protection ; Clear JSR protection status (CLRJSR) |
| 8EEF | .read_args_size←1← 8F3E BEQ |
| LDY #&7f ; Y=&7F: JSRSIZ offset (READRB entry) | |
| 8EF1 | LDA (net_rx_ptr),y ; Load buffer size from workspace |
| 8EF3 | LDY #1 ; Y=1: param block offset for size byte |
| 8EF5 | STA (osword_pb_ptr),y ; Store buffer size to (&F0)+1 |
| 8EF7 | INY ; Y=2: param block offset for args size Y=&02 |
| 8EF8 | LDA #&80 ; A=&80: argument data starts at offset &80 |
| 8EFA | .readry←1← 8EC4 BCC |
| STA (osword_pb_ptr),y ; Store args start offset to (&F0)+2 | |
| 8EFC | RTS ; Return |
| 8EFD | .osword_12_offsets←1← 8F11 LDA |
| EQUB &FF, &01 | |
OSWORD &12 sub-function dispatcherDispatches sub-functions 0-9 for OSWORD &12 (read/set FS state). Functions 0-3 read/set station and printer server addresses; 4-5 read/set protection masks; 6-7 read/set context handles (URD/CSD/LIB); 8 reads local station; 9 reads JSR argument buffer size. |
|
| 8EFF | .osword_12_dispatch |
| CMP #6 ; Sub-function >= 6? | |
| 8F01 | BCS rsl1 ; Yes: jump to sub 6-9 handler |
| 8F03 | CMP #4 ; Sub-function >= 4? |
| 8F05 | BCS rssl1 ; Sub-function 4 or 5: read/set protection |
| 8F07 | LSR ; LSR: 0->0, 1->0, 2->1, 3->1 |
| 8F08 | LDX #&0d ; X=&0D: default to static workspace page |
| 8F0A | TAY ; Transfer LSR result to Y for indexing |
| 8F0B | BEQ set_workspace_page ; Y=0 (sub 0-1): use page &0D |
| 8F0D | LDX nfs_workspace_hi ; Y=1 (sub 2-3): use dynamic workspace |
| 8F0F | .set_workspace_page←1← 8F0B BEQ |
| STX ws_ptr_hi ; Store workspace page in &AC (hi byte) | |
| 8F11 | LDA osword_12_offsets,y ; Load offset: &FF (sub 0-1) or &01 (sub 2-3) |
| 8F14 | STA ws_ptr_lo ; Store offset in &AB (lo byte) |
| 8F16 | LDX #1 ; X=1: copy 2 bytes |
| 8F18 | LDY #1 ; Y=1: start at param block offset 1 |
| 8F1A | JMP copy_param_block ; Jump to read/write workspace path |
| 8F1D | .rssl1←1← 8F05 BCS |
| LSR ; LSR A: test bit 0 of sub-function | |
| 8F1E | INY ; Y=1: offset for protection byte |
| 8F1F | LDA (osword_pb_ptr),y ; Load protection byte from param block |
| 8F21 | BCS rssl2 ; C=1 (odd sub): set protection |
| 8F23 | LDA prot_status ; C=0 (even sub): read current status |
| 8F26 | STA (osword_pb_ptr),y ; Return current value to param block |
| 8F28 | .rssl2←1← 8F21 BCS |
| STA prot_status ; Update protection status | |
| 8F2B | STA saved_jsr_mask ; Also save as JSR mask backup |
| 8F2E | RTS ; Return |
| 8F2F | .read_fs_handle←1← 8F3A BEQ |
| LDY #&14 ; Y=&14: RX buffer offset for FS handle | |
| 8F31 | LDA (net_rx_ptr),y ; Read FS reply handle from RX data |
| 8F33 | LDY #1 ; Y=1: param block byte 1 |
| 8F35 | STA (osword_pb_ptr),y ; Return handle to caller's param block |
| 8F37 | RTS ; Return |
| 8F38 | .rsl1←1← 8F01 BCS |
| CMP #8 ; Sub-function 8: read FS handle | |
| 8F3A | BEQ read_fs_handle ; Match: read handle from RX buffer |
| 8F3C | CMP #9 ; Sub-function 9: read args size |
| 8F3E | BEQ read_args_size ; Match: read ARGS buffer info |
| 8F40 | BPL return_last_error ; Sub >= 10 (bit 7 clear): read error |
| 8F42 | LDY #3 ; Y=3: start from handle 3 (descending) |
| 8F44 | LSR ; LSR: test read/write bit |
| 8F45 | BCC readc1 ; C=0: read handles from workspace |
| 8F47 | STY nfs_temp ; Init loop counter at Y=3 |
| 8F49 | .copy_handles_to_ws←1← 8F58 BNE |
| LDY nfs_temp ; Reload loop counter | |
| 8F4B | LDA (osword_pb_ptr),y ; Read handle from caller's param block |
| 8F4D | JSR handle_to_mask_a ; Convert handle number to bitmask |
| 8F50 | TYA ; TYA: get bitmask result |
| 8F51 | LDY nfs_temp ; Reload loop counter |
| 8F53 | STA fs_server_net,y ; Store bitmask to FS server table |
| 8F56 | DEC nfs_temp ; Next handle (descending) |
| 8F58 | BNE copy_handles_to_ws ; Loop for handles 3,2,1 |
| 8F5A | RTS ; Return |
| 8F5B | .return_last_error←1← 8F40 BPL |
| INY ; Y=1 (post-INY): param block byte 1 | |
| 8F5C | LDA fs_last_error ; Read last FS error code |
| 8F5F | STA (osword_pb_ptr),y ; Return error to caller's param block |
| 8F61 | RTS ; Return |
| 8F62 | .readc1←2← 8F45 BCC← 8F6B BNE |
| LDA fs_server_net,y ; A=single-bit bitmask | |
| 8F65 | JSR mask_to_handle ; Convert bitmask to handle number (FS2A) |
| 8F68 | STA (osword_pb_ptr),y ; A=handle number (&20-&27) Y=preserved |
| 8F6A | DEY ; Next handle (descending) |
| 8F6B | BNE readc1 ; Loop for handles 3,2,1 |
| 8F6D | 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.
|
|||||||||||||
| 8F6E | .osword_10_handler | ||||||||||||
| LDX nfs_workspace_hi ; Workspace page high byte | |||||||||||||
| 8F70 | STX ws_ptr_hi ; Set up pointer high byte in &AC | ||||||||||||
| 8F72 | STY ws_ptr_lo ; Save param block high byte in &AB | ||||||||||||
| 8F74 | ROR rx_flags ; Disable user RX during CB operation | ||||||||||||
| 8F77 | LDA (osword_pb_ptr),y ; Read first byte of param block | ||||||||||||
| 8F79 | STA osword_flag ; Save: 0=open new, non-zero=read RXCB | ||||||||||||
| 8F7B | .scan_or_read_rxcb | ||||||||||||
| BNE read_rxcb ; Non-zero: read specified RXCB | |||||||||||||
| 8F7D | LDA #3 ; Start scan from RXCB #3 (0-2 reserved) | ||||||||||||
| 8F7F | .scan0←1← 8F91 BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8F82 | BCS openl4 ; Invalid RXCB: return zero | ||||||||||||
| 8F84 | LSR ; LSR twice: byte offset / 4 | ||||||||||||
| 8F85 | LSR ; Yields RXCB number from offset | ||||||||||||
| 8F86 | TAX ; X = RXCB number for iteration | ||||||||||||
| 8F87 | LDA (ws_ptr_lo),y ; Read flag byte from RXCB workspace | ||||||||||||
| 8F89 | BEQ openl4 ; Zero = end of CB list | ||||||||||||
| 8F8B | CMP #&3f ; &3F = deleted slot, free for reuse | ||||||||||||
| 8F8D | BEQ scan1 ; Found free slot | ||||||||||||
| 8F8F | INX ; Try next RXCB | ||||||||||||
| 8F90 | TXA ; A = next RXCB number | ||||||||||||
| 8F91 | BNE scan0 ; Continue scan (always branches) | ||||||||||||
| 8F93 | .scan1←1← 8F8D BEQ | ||||||||||||
| TXA ; A = free RXCB number | |||||||||||||
| 8F94 | LDX #0 ; X=0 for indexed indirect store | ||||||||||||
| 8F96 | STA (osword_pb_ptr,x) ; Return RXCB number to caller's byte 0 | ||||||||||||
| 8F98 | .read_rxcb←1← 8F7B BNE | ||||||||||||
| JSR calc_handle_offset ; Convert RXCB number to workspace offset | |||||||||||||
| 8F9B | BCS openl4 ; Invalid: write zero to param block | ||||||||||||
| 8F9D | DEY ; Y = offset-1: points to flag byte | ||||||||||||
| 8F9E | STY ws_ptr_lo ; Set &AB = workspace ptr low byte | ||||||||||||
| 8FA0 | LDA #&c0 ; &C0: test mask for flag byte | ||||||||||||
| 8FA2 | LDY #1 ; Y=1: flag byte offset in RXCB | ||||||||||||
| 8FA4 | LDX #&0b ; Enable interrupts before transmit | ||||||||||||
| 8FA6 | CPY osword_flag ; Compare Y(1) with saved byte (open/read) | ||||||||||||
| 8FA8 | ADC (ws_ptr_lo),y ; ADC flag: test if slot is in use | ||||||||||||
| 8FAA | BEQ openl6 ; Dest station = &FFFF (accept reply from any station) | ||||||||||||
| 8FAC | BMI openl7 ; Negative: slot has received data | ||||||||||||
| 8FAE | .copy_rxcb_to_param←1← 8FBE BNE | ||||||||||||
| CLC ; C=0: workspace-to-param direction | |||||||||||||
| 8FAF | .openl6←1← 8FAA BEQ | ||||||||||||
| JSR copy_param_block ; Copy RXCB data to param block | |||||||||||||
| 8FB2 | BCS reenable_rx ; Done: skip deletion on error | ||||||||||||
| 8FB4 | LDA #&3f ; Mark CB as consumed (consume-once) | ||||||||||||
| 8FB6 | LDY #1 ; Y=1: flag byte offset | ||||||||||||
| 8FB8 | STA (ws_ptr_lo),y ; Write &3F to mark slot deleted | ||||||||||||
| 8FBA | BNE reenable_rx ; Branch to exit (always taken) ALWAYS branch | ||||||||||||
| 8FBC | .openl7←1← 8FAC BMI | ||||||||||||
| ADC #1 ; Advance through multi-byte field | |||||||||||||
| 8FBE | BNE copy_rxcb_to_param ; Loop until all bytes processed | ||||||||||||
| 8FC0 | DEY ; Y=-1 → Y=0 after STA below | ||||||||||||
| 8FC1 | .openl4←3← 8F82 BCS← 8F89 BEQ← 8F9B BCS | ||||||||||||
| STA (osword_pb_ptr),y ; Return zero (no free RXCB found) | |||||||||||||
| 8FC3 | .reenable_rx←2← 8FB2 BCS← 8FBA BNE | ||||||||||||
| ROL rx_flags ; Re-enable user RX | |||||||||||||
| 8FC6 | RTS ; Return | ||||||||||||
| 8FC7 | EQUB &A0, &02 ; Y=2: copy 3 bytes (indices 2,1,0) | ||||||||||||
| 8FC9 | .rest1 | ||||||||||||
| EQUB &B1, &9C, &99, ; Load saved arg from (net_rx_ptr)+Y &AA, &00, &88, ; Restore saved OSWORD argument byte &10, &F8, &60 ; Decrement byte counter Loop for bytes ; 2,1,0 Return to caller | |||||||||||||
Set up RX buffer pointers in NFS workspaceCalculates the start address of the RX data area (&F0+1) and stores it at workspace offset &28. Also reads the data length from (&F0)+1 and adds it to &F0 to compute the end address at offset &2C.
|
||||
| 8FD2 | .setup_rx_buffer_ptrs←1← 9005 JSR | |||
| LDY #&1c ; Y=&1C: RXCB template offset | ||||
| 8FD4 | LDA osword_pb_ptr ; A = base address low byte | |||
| 8FD6 | ADC #1 ; A = base + 1 (skip length byte) | |||
| 8FD8 | JSR store_16bit_at_y ; Receive data blocks until command byte = &00 or &0D | |||
| 8FDB | LDY #1 ; Read data length from (&F0)+1 | |||
| 8FDD | LDA (osword_pb_ptr),y ; A = data length byte | |||
| 8FDF | LDY #&20 ; Workspace offset &20 = RX data end | |||
| 8FE1 | ADC osword_pb_ptr ; A = base + length = end address low | |||
| 8FE3 | .store_16bit_at_y←1← 8FD8 JSR | |||
| STA (nfs_workspace),y ; Store low byte of 16-bit address | ||||
| 8FE5 | INY ; Advance to high byte offset | |||
| 8FE6 | LDA osword_pb_ptr_hi ; A = high byte of base address | |||
| 8FE8 | ADC #0 ; Add carry for 16-bit addition | |||
| 8FEA | STA (nfs_workspace),y ; Store high byte | |||
| 8FEC | RTS ; Return | |||
Econet transmit/receive handlerA=0: Initialise TX control block from ROM template at &8360 (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 &9039).
|
||||
| 8FED | .econet_tx_rx | |||
| CMP #1 ; A=0: set up and transmit; A>=1: handle result | ||||
| 8FEF | BCS handle_tx_result ; A >= 1: handle TX result | |||
| 8FF1 | LDY #&23 ; Y=&23: start of template (descending) | |||
| 8FF3 | .dofs01←1← 9000 BNE | |||
| LDA init_tx_ctrl_block,y ; Load from ROM template (zero = use NMI workspace value) | ||||
| 8FF6 | BNE store_txcb_byte ; Non-zero = use ROM template byte as-is | |||
| 8FF8 | LDA nmi_sub_table,y ; Zero = substitute from NMI workspace | |||
| 8FFB | .store_txcb_byte←1← 8FF6 BNE | |||
| STA (nfs_workspace),y ; Store to dynamic workspace | ||||
| 8FFD | DEY ; Descend through template | |||
| 8FFE | CPY #&17 ; Stop at offset &17 | |||
| 9000 | BNE dofs01 ; Loop until all bytes copied | |||
| 9002 | INY ; Y=&18: TX block starts here | |||
| 9003 | STY net_tx_ptr ; Point net_tx_ptr at workspace+&18 | |||
| 9005 | JSR setup_rx_buffer_ptrs ; Set up RX buffer start/end pointers | |||
| 9008 | LDY #2 ; Y=2: port byte offset in RXCB | |||
| 900A | LDA #&90 ; A=&90: FS reply port | |||
| 900C | STA (osword_pb_ptr),y ; Store port &90 at (&F0)+2 | |||
| 900E | INY ; Y=&03 | |||
| 900F | INY ; Y=&04: advance to station address Y=&04 | |||
| 9010 | .copy_fs_addr←1← 9018 BNE | |||
| LDA fs_context_base,y ; Copy FS station addr from workspace | ||||
| 9013 | STA (osword_pb_ptr),y ; Store to RX param block | |||
| 9015 | INY ; Next byte | |||
| 9016 | CPY #7 ; Done 3 bytes (Y=4,5,6)? | |||
| 9018 | BNE copy_fs_addr ; No: continue copying | |||
| 901A | LDA nfs_workspace_hi ; High byte of workspace for TX ptr | |||
| 901C | STA net_tx_ptr_hi ; Store as TX pointer high byte | |||
| 901E | CLI ; Enable interrupts before transmit | |||
| 901F | JSR tx_poll_ff ; Transmit with full retry | |||
| 9022 | LDY #&20 ; Y=&20: RX end address offset | |||
| 9024 | LDA #&ff ; Dest station = &FFFF (accept reply from any station) | |||
| 9026 | STA (nfs_workspace),y ; Store end address low byte (&FF) | |||
| 9028 | INY ; Y=&21 | |||
| 9029 | STA (nfs_workspace),y ; Store end address high byte (&FF) | |||
| 902B | LDY #&19 ; Y=&19: port byte in workspace RXCB | |||
| 902D | LDA #&90 ; A=&90: FS reply port | |||
| 902F | STA (nfs_workspace),y ; Store port to workspace RXCB | |||
| 9031 | DEY ; Y=&18 | |||
| 9032 | LDA #&7f ; A=&7F: flag byte = waiting for reply | |||
| 9034 | STA (nfs_workspace),y ; Store flag byte to workspace RXCB | |||
| 9036 | JMP send_to_fs_star ; Jump to RX poll (BRIANX) | |||
| 9039 | .handle_tx_result←1← 8FEF BCS | |||
| PHP ; Save processor flags | ||||
| 903A | LDY #1 ; Y=1: first data byte offset Y=character to write | |||
| 903C | LDA (osword_pb_ptr),y ; Load first data byte from RX buffer | |||
| fall through ↓ | ||||
Fn 4: net write character (NWRCH)Writes a character (passed in Y) to the screen via OSWRITCH. Before the write, uses TSX to reach into the stack and zero the carry flag in the caller's saved processor status byte -- ROR followed by ASL on the stacked P byte (&0106,X) shifts carry out and back in as zero. This ensures the calling code's PLP restores carry=0, signalling "character accepted" without needing a separate CLC/PHP sequence. A classic 6502 trick for modifying return flags without touching the actual processor status.
|
|||||||||||
| 903E | .net_write_char | ||||||||||
| TAX ; X = first data byte (command code) | |||||||||||
| 903F | INY ; Advance to next data byte | ||||||||||
| 9040 | LDA (osword_pb_ptr),y ; Load station address high byte | ||||||||||
| 9042 | INY ; Advance past station addr | ||||||||||
| 9043 | STY ws_ptr_lo ; Save Y as data index | ||||||||||
| 9045 | LDY #&72 ; Store station addr hi at (net_rx_ptr)+&72 | ||||||||||
| 9047 | STA (net_rx_ptr),y ; Store to workspace | ||||||||||
| 9049 | DEY ; Y=&71 | ||||||||||
| 904A | TXA ; A = command code (from X) | ||||||||||
| 904B | STA (net_rx_ptr),y ; Store station addr lo at (net_rx_ptr)+&71 | ||||||||||
| 904D | PLP ; Restore flags from earlier PHP | ||||||||||
| 904E | BNE dofs2 ; First call: adjust data length | ||||||||||
| 9050 | .send_data_bytes←1← 906A BNE | ||||||||||
| LDY ws_ptr_lo ; Receive data blocks until command byte = &00 or &0D | |||||||||||
| 9052 | INC ws_ptr_lo ; Advance data index for next iteration | ||||||||||
| 9054 | LDA (osword_pb_ptr),y ; Load next data byte | ||||||||||
| 9056 | LDY #&7d ; Y=&7D: store byte for TX at offset &7D | ||||||||||
| 9058 | STA (net_rx_ptr),y ; Store data byte at (net_rx_ptr)+&7D for TX | ||||||||||
| 905A | PHA ; Save data byte for &0D check after TX | ||||||||||
| 905B | JSR ctrl_block_setup_alt ; Set up TX control block | ||||||||||
| 905E | CLI ; Enable interrupts for TX | ||||||||||
| 905F | JSR tx_poll_core ; Enable IRQs and transmit Core transmit and poll routine (XMIT) | ||||||||||
| 9062 | .delay_between_tx←1← 9063 BNE | ||||||||||
| DEX ; Short delay loop between TX packets | |||||||||||
| 9063 | BNE delay_between_tx ; Spin until X reaches 0 | ||||||||||
| 9065 | PLA ; Restore data byte for terminator check | ||||||||||
| 9066 | BEQ return_8 ; Z=1: not intercepted, pass through | ||||||||||
| 9068 | EOR #&0d ; Test for end-of-data marker (&0D) | ||||||||||
| 906A | BNE send_data_bytes ; Not &0D: continue with next byte | ||||||||||
| 906C | .return_8←1← 9066 BEQ | ||||||||||
| RTS ; Return (data complete) | |||||||||||
| 906D | .dofs2←1← 904E BNE | ||||||||||
| JSR ctrl_block_setup_alt ; First-packet: set up control block | |||||||||||
| 9070 | LDY #&7b ; Y=&7B: data length offset | ||||||||||
| 9072 | LDA (net_rx_ptr),y ; Load current data length | ||||||||||
| 9074 | ADC #3 ; Adjust data length by 3 for header bytes | ||||||||||
| 9076 | 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. |
|
| 9078 | .enable_irq_and_tx |
| CLI ; Enable interrupts | |
| 9079 | .jmp_clear_svc_restore |
| 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 &9095. 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
|
|||||||||||
| 907C | .osword_dispatch | ||||||||||
| PHP ; Save processor status | |||||||||||
| 907D | PHA ; Save A (reason code) | ||||||||||
| 907E | TXA ; Save X | ||||||||||
| 907F | PHA ; Push X to stack | ||||||||||
| 9080 | TYA ; Save Y | ||||||||||
| 9081 | PHA ; Push Y to stack | ||||||||||
| 9082 | TSX ; Get stack pointer for indexed access | ||||||||||
| 9083 | LDA l0103,x ; Retrieve original A (function code) from stack | ||||||||||
| 9086 | CMP #9 ; Reason codes 0-8 only | ||||||||||
| 9088 | BCS entry1 ; Code >= 9: skip dispatch, restore regs | ||||||||||
| 908A | TAX ; X = reason code for table lookup | ||||||||||
| 908B | JSR osword_trampoline ; Dispatch to handler via trampoline | ||||||||||
| 908E | .entry1←1← 9088 BCS | ||||||||||
| PLA ; Restore Y | |||||||||||
| 908F | TAY ; Transfer to Y register | ||||||||||
| 9090 | PLA ; Restore X | ||||||||||
| 9091 | TAX ; Transfer to X register | ||||||||||
| 9092 | PLA ; Restore A | ||||||||||
| 9093 | PLP ; Restore processor status flags | ||||||||||
| 9094 | RTS ; Return with all registers preserved | ||||||||||
| 9095 | .osword_trampoline←1← 908B JSR | ||||||||||
| LDA osword_tbl_hi,x ; PHA/PHA/RTS trampoline: push handler addr-1, RTS jumps to it | |||||||||||
| 9098 | PHA ; Push high byte of handler address | ||||||||||
| 9099 | LDA osword_tbl_lo,x ; Load handler low byte from table | ||||||||||
| 909C | PHA ; Push low byte of handler address | ||||||||||
| 909D | LDA osbyte_a_copy ; Load workspace byte &EF for handler | ||||||||||
| 909F | RTS ; RTS dispatches to pushed handler | ||||||||||
| 90A0 | .osword_tbl_lo←1← 9099 LDA | ||||||||||
| EQUB <(return_2-1) | |||||||||||
| 90A1 | EQUB <(remote_print_handler-1) | ||||||||||
| 90A2 | EQUB <(remote_print_handler-1) | ||||||||||
| 90A3 | EQUB <(remote_print_handler-1) | ||||||||||
| 90A4 | EQUB <(nwrch_handler-1) | ||||||||||
| 90A5 | EQUB <(printer_select_handler-1) | ||||||||||
| 90A6 | EQUB <(return_2-1) | ||||||||||
| 90A7 | EQUB <(remote_cmd_dispatch-1) | ||||||||||
| 90A8 | EQUB <(remote_cmd_data-1) | ||||||||||
| 90A9 | .osword_tbl_hi←1← 9095 LDA | ||||||||||
| EQUB >(return_2-1) | |||||||||||
| 90AA | EQUB >(remote_print_handler-1) | ||||||||||
| 90AB | EQUB >(remote_print_handler-1) | ||||||||||
| 90AC | EQUB >(remote_print_handler-1) | ||||||||||
| 90AD | EQUB >(nwrch_handler-1) | ||||||||||
| 90AE | EQUB >(printer_select_handler-1) | ||||||||||
| 90AF | EQUB >(return_2-1) | ||||||||||
| 90B0 | EQUB >(remote_cmd_dispatch-1) | ||||||||||
| 90B1 | EQUB >(remote_cmd_data-1) | ||||||||||
NETVEC reason 4: write character to network (NWRCH)Handles remote character output over the network. Clears carry in the stacked processor status (via ROR/ASL on the stack frame) to signal success to the MOS dispatcher. Stores the character from Y into workspace offset &DA, then falls through to setup_tx_and_send with A=0 to transmit the character to the remote terminal. |
|
| 90B2 | .nwrch_handler |
| TSX ; Get stack pointer for P register | |
| 90B3 | ROR l0106,x ; ROR/ASL on stacked P: zeros carry to signal success |
| 90B6 | ASL l0106,x ; ASL: restore P after ROR zeroed carry |
| 90B9 | TYA ; Y = character to write |
| 90BA | LDY #&da ; Store character at workspace offset &DA |
| 90BC | STA (nfs_workspace),y ; Store char at workspace offset &DA |
| 90BE | LDA #0 ; A=0: command type for net write char |
| fall through ↓ | |
Set up TX control block and sendBuilds a TX control block at (nfs_workspace)+&0C from the current workspace state, then initiates transmission via the ADLC TX path. This is the common send routine used after command data has been prepared. The exact control block layout and field mapping need further analysis.
|
||||
| 90C0 | .setup_tx_and_send←3← 819D JSR← 9107 JSR← 9168 JSR | |||
| LDY #&d9 ; Y=&D9: command type offset | ||||
| 90C2 | STA (nfs_workspace),y ; Store command type at ws+&D9 | |||
| 90C4 | LDA #&80 ; Mark TX control block as active (&80) | |||
| 90C6 | LDY #&0c ; Y=&0C: TXCB start offset | |||
| 90C8 | STA (nfs_workspace),y ; Set TX active flag at ws+&0C | |||
| 90CA | STY net_tx_ptr ; Redirect net_tx_ptr low to workspace | |||
| 90CC | LDX nfs_workspace_hi ; Load workspace page high byte | |||
| 90CE | STX net_tx_ptr_hi ; Complete ptr redirect | |||
| 90D0 | JSR tx_poll_ff ; Transmit with full retry | |||
| 90D3 | LDA #&3f ; Mark TXCB as deleted (&3F) after transmit | |||
| 90D5 | STA (net_tx_ptr,x) ; Write &3F to TXCB byte 0 | |||
| 90D7 | 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. |
|
| 90D8 | .remote_cmd_dispatch |
| LDY osword_pb_ptr_hi ; Load original Y (OSBYTE secondary param) | |
| 90DA | CMP #&81 ; OSBYTE &81 (INKEY): always forward to terminal |
| 90DC | BEQ dispatch_remote_osbyte ; Forward &81 to terminal for keyboard read |
| 90DE | LDY #1 ; Y=1: search NCTBPL table (execute on both) |
| 90E0 | LDX #7 ; X=7: 8-entry NCTBPL table size |
| 90E2 | JSR match_osbyte_code ; Search for OSBYTE code in NCTBPL table |
| 90E5 | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=1 (both) |
| 90E7 | DEY ; Y=-1: search NCTBMI table (terminal only) |
| 90E8 | DEY ; Second DEY: Y=&FF (from 1 via 0) |
| 90E9 | LDX #&0e ; X=&0E: 15-entry NCTBMI table size |
| 90EB | JSR match_osbyte_code ; Search for OSBYTE code in NCTBMI table |
| 90EE | BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=&FF (terminal) |
| 90F0 | INY ; Y=0: OSBYTE not recognised, ignore |
| 90F1 | .dispatch_remote_osbyte←3← 90DC BEQ← 90E5 BEQ← 90EE BEQ |
| LDX #2 ; X=2 bytes to copy (default for RBYTE) | |
| 90F3 | TYA ; A=Y: check table match result |
| 90F4 | BEQ return_nbyte ; Y=0: not recognised, return unhandled |
| 90F6 | PHP ; Y>0 (NCTBPL): send only, no result expected |
| 90F7 | BPL nbyte6 ; Y>0 (NCTBPL): no result expected, skip RX |
| 90F9 | INX ; Y<0 (NCTBMI): X=3 bytes (result + P flags) X=&03 |
| 90FA | .nbyte6←1← 90F7 BPL |
| LDY #&dc ; Y=&DC: top of 3-byte stack frame region | |
| 90FC | .nbyte1←1← 9104 BPL |
| LDA tube_claimed_id,y ; Copy OSBYTE args from stack frame to workspace | |
| 90FF | STA (nfs_workspace),y ; Store to NFS workspace for transmission |
| 9101 | DEY ; Next byte (descending) |
| 9102 | CPY #&da ; Copied all 3 bytes? (&DC, &DB, &DA) |
| 9104 | BPL nbyte1 ; Loop for remaining bytes |
| 9106 | TXA ; A = byte count for setup_tx_and_send |
| 9107 | JSR setup_tx_and_send ; Build TXCB and transmit to terminal |
| 910A | PLP ; Restore N flag from table match type |
| 910B | BPL return_nbyte ; Y was positive (NCTBPL): done, no result |
| 910D | LDA #&7f ; Set up RX control block to wait for reply |
| 910F | STA (net_tx_ptr,x) ; Write &7F to RXCB (wait for reply) |
| 9111 | .poll_rxcb_loop←1← 9113 BPL |
| LDA (net_tx_ptr,x) ; Poll RXCB for completion (bit7) | |
| 9113 | BPL poll_rxcb_loop ; Bit7 clear: still waiting, poll again |
| 9115 | TSX ; X = stack pointer for register restoration |
| 9116 | LDY #&dd ; Y=&DD: saved P byte offset in workspace |
| 9118 | LDA (nfs_workspace),y ; Load remote processor status from reply |
| 911A | ORA #&44 ; Force V=1 (claimed) and I=1 (no IRQ) in saved P |
| 911C | BNE nbyte5 ; ALWAYS branch (ORA #&44 never zero) ALWAYS branch |
| 911E | .nbyte4←1← 9127 BNE |
| DEY ; Previous workspace offset | |
| 911F | DEX ; Previous stack register slot |
| 9120 | LDA (nfs_workspace),y ; Load next result byte (X, then Y) |
| 9122 | .nbyte5←1← 911C BNE |
| STA l0106,x ; Write result bytes to stacked registers | |
| 9125 | CPY #&da ; Copied all result bytes? (P at &DA) |
| 9127 | BNE nbyte4 ; Loop for remaining result bytes |
| 9129 | .return_nbyte←2← 90F4 BEQ← 910B BPL |
| RTS ; Return to OSBYTE dispatcher | |
Search remote OSBYTE table for match (NCALLP)Searches remote_osbyte_table for OSBYTE code A. X indexes the last entry to check (table is scanned X..0). Returns Z=1 if found. Called twice by remote_cmd_dispatch: X=7 -> first 8 entries (NCTBPL: execute on both machines) X=14 -> all 15 entries (NCTBMI: execute on terminal only) The last 7 entries (&0B, &0C, &0F, &79, &7A, &E3, &E4) are terminal-only because they affect the local keyboard, buffers, or function keys. On entry: A = OSBYTE code, X = table size - 1 On exit: Z=1 if match found, Z=0 if not |
|
| 912A | .match_osbyte_code←3← 90E2 JSR← 90EB JSR← 9130 BPL |
| CMP remote_osbyte_table,x ; Compare OSBYTE code with table entry | |
| 912D | BEQ return_match_osbyte ; Match found: return with Z=1 |
| 912F | DEX ; Next table entry (descending) |
| 9130 | BPL match_osbyte_code ; Loop for remaining entries |
| 9132 | .return_match_osbyte←2← 912D BEQ← 914A BNE |
| RTS ; Return; Z=1 if match, Z=0 if not | |
| 9133 | .remote_osbyte_table←1← 912A CMP |
| EQUB &04 ; OSBYTE &04: cursor key status | |
| 9134 | EQUB &09 ; OSBYTE &09: flash duration (1st colour) |
| 9135 | EQUB &0A ; OSBYTE &0A: flash duration (2nd colour) |
| 9136 | EQUB &14 ; OSBYTE &14: explode soft character RAM |
| 9137 | EQUB &9A ; OSBYTE &9A: video ULA control register |
| 9138 | EQUB &9B ; OSBYTE &9B: video ULA palette |
| 9139 | EQUB &9C ; OSBYTE &9C: ACIA control register |
| 913A | EQUB &E2 ; OSBYTE &E2: function key &D0-&DF |
| 913B | EQUB &0B ; OSBYTE &0B: auto-repeat delay |
| 913C | EQUB &0C ; OSBYTE &0C: auto-repeat rate |
| 913D | EQUB &0F ; OSBYTE &0F: flush buffer class |
| 913E | EQUB &79 ; OSBYTE &79: keyboard scan from X |
| 913F | EQUB &7A ; OSBYTE &7A: keyboard scan from &10 |
| 9140 | EQUB &E3 ; OSBYTE &E3: function key &E0-&EF |
| 9141 | EQUB &E4 ; OSBYTE &E4: function key &F0-&FF |
Fn 8: remote OSWORD handler (NWORD)Only intercepts OSWORD 7 (make a sound) and OSWORD 8 (define an envelope). Unlike NBYTE which returns results, NWORD is entirely fire-and-forget -- no return path is implemented. The developer explicitly noted this was acceptable since sound/envelope commands don't return meaningful results. Copies up to 14 parameter bytes from the RX buffer to workspace, tags the message as RWORD, and transmits. |
|
| 9142 | .remote_cmd_data |
| LDY #&0e ; Y=14: max OSWORD parameter bytes | |
| 9144 | CMP #7 ; OSWORD 7 = make a sound |
| 9146 | BEQ copy_params_rword ; OSWORD 7 (sound): handle via common path |
| 9148 | CMP #8 ; OSWORD 8 = define an envelope |
| 914A | BNE return_match_osbyte ; Not OSWORD 7 or 8: ignore (BNE exits) |
| 914C | .copy_params_rword←1← 9146 BEQ |
| LDX #&db ; Point workspace to offset &DB for params | |
| 914E | STX nfs_workspace ; Store workspace ptr offset &DB |
| 9150 | .copy_osword_params←1← 9155 BPL |
| LDA (osword_pb_ptr),y ; Load param byte from OSWORD param block | |
| 9152 | STA (nfs_workspace),y ; Write param byte to workspace |
| 9154 | DEY ; Next byte (descending) |
| 9155 | BPL copy_osword_params ; Loop for all parameter bytes |
| 9157 | INY ; Y=0 after loop |
| 9158 | DEC nfs_workspace ; Point workspace to offset &DA |
| 915A | LDA osbyte_a_copy ; Load original OSWORD code |
| 915C | STA (nfs_workspace),y ; Store OSWORD code at ws+0 |
| 915E | STY nfs_workspace ; Reset workspace ptr to base |
| 9160 | LDY #&14 ; Y=&14: command type offset |
| 9162 | LDA #&e9 ; Tag as RWORD (port &E9) |
| 9164 | STA (nfs_workspace),y ; Store port tag at ws+&14 |
| 9166 | LDA #1 ; A=1: single-byte TX |
| 9168 | JSR setup_tx_and_send ; Load template byte from ctrl_block_template[X] |
| 916B | STX nfs_workspace ; Restore workspace ptr |
| fall through ↓ | |
Alternate entry into control block setupSets X=&0D, Y=&7C. Tests bit 6 of &837E to choose target: V=0 (bit 6 clear): stores to (nfs_workspace) V=1 (bit 6 set): stores to (net_rx_ptr) |
|
| 916D | .ctrl_block_setup_alt←2← 905B JSR← 906D JSR |
| LDX #&0d ; X=&0D: template offset for alt entry | |
| 916F | LDY #&7c ; Y=&7C: target workspace offset for alt entry |
| 9171 | BIT tx_ctrl_upper ; BIT test: V flag = bit 6 of &837E |
| 9174 | 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 &91A2 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 |
|
| 9176 | .ctrl_block_setup←1← 8499 JSR |
| LDY #&17 ; Y=&17: workspace target offset (main entry) | |
| 9178 | LDX #&1a ; X=&1A: template table index (main entry) |
| 917A | .ctrl_block_setup_clv←1← 923E JSR |
| CLV ; V=0: target is (nfs_workspace) | |
| 917B | .cbset2←2← 9174 BVS← 919C BPL |
| LDA ctrl_block_template,x ; Set up TX and send RWORD packet | |
| 917E | CMP #&fe ; &FE = stop sentinel |
| 9180 | BEQ cb_template_tail ; End of template: jump to exit |
| 9182 | CMP #&fd ; &FD = skip sentinel |
| 9184 | BEQ cb_template_main_start ; Skip: don't store, just decrement Y |
| 9186 | CMP #&fc ; &FC = page byte sentinel |
| 9188 | BNE cbset3 ; Not sentinel: store template value directly |
| 918A | LDA net_rx_ptr_hi ; V=1: use (net_rx_ptr) page |
| 918C | BVS rxcb_matched ; V=1: skip to net_rx_ptr page |
| 918E | LDA nfs_workspace_hi ; V=0: use (nfs_workspace) page |
| 9190 | .rxcb_matched←1← 918C BVS |
| STA net_tx_ptr_hi ; PAGE byte → Y=&02 / Y=&74 | |
| 9192 | .cbset3←1← 9188 BNE |
| BVS cbset4 ; → Y=&04 / Y=&76 | |
| 9194 | STA (nfs_workspace),y ; PAGE byte → Y=&06 / Y=&78 |
| 9196 | BVC cb_template_main_start ; → Y=&08 / Y=&7A ALWAYS branch |
| 9198 | .cbset4←1← 9192 BVS |
| STA (net_rx_ptr),y ; Alt-path only → Y=&70 | |
| 919A | .cb_template_main_start←2← 9184 BEQ← 9196 BVC |
| DEY ; → Y=&0C (main only) | |
| 919B | DEX ; → Y=&0D (main only) |
| 919C | BPL cbset2 ; Loop until all template bytes done |
| 919E | .cb_template_tail←1← 9180 BEQ |
| INY ; → Y=&10 (main only) | |
| 919F | STY net_tx_ptr ; Store final offset as net_tx_ptr |
| 91A1 | RTS ; → Y=&07 / Y=&79 |
Control block initialisation templateRead by the loop at &9168, indexed by X from a starting value down to 0. Values are stored into either (nfs_workspace) or (net_rx_ptr) at offset Y, depending on the V flag. Two entry paths read different slices of this table: ctrl_block_setup: X=&1A (26) down, Y=&17 (23) down, V=0 ctrl_block_setup_alt: X=&0D (13) down, Y=&7C (124) down, V from BIT &837E 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) |
|
| 91A2 | .ctrl_block_template←1← 917B LDA |
| EQUB &85 ; Alt-path only → Y=&6F | |
| 91A3 | EQUB &00 ; Alt-path only → Y=&70 |
| 91A4 | EQUB &FD ; SKIP |
| 91A5 | EQUB &FD ; SKIP |
| 91A6 | EQUB &7D ; → Y=&01 / Y=&73 |
| 91A7 | EQUB &FC ; PAGE byte → Y=&02 / Y=&74 |
| 91A8 | EQUB &FF ; → Y=&03 / Y=&75 |
| 91A9 | EQUB &FF ; → Y=&04 / Y=&76 |
| 91AA | EQUB &7E ; → Y=&05 / Y=&77 |
| 91AB | EQUB &FC ; PAGE byte → Y=&06 / Y=&78 |
| 91AC | EQUB &FF ; → Y=&07 / Y=&79 |
| 91AD | EQUB &FF ; → Y=&08 / Y=&7A |
| 91AE | EQUB &00 ; → Y=&09 / Y=&7B |
| 91AF | EQUB &00 ; → Y=&0A / Y=&7C |
| 91B0 | EQUB &FE ; STOP — main-path boundary |
| 91B1 | EQUB &80 ; → Y=&0C (main only) |
| 91B2 | EQUB &93 ; → Y=&0D (main only) |
| 91B3 | EQUB &FD ; SKIP (main only) |
| 91B4 | EQUB &FD ; SKIP (main only) |
| 91B5 | EQUB &D9 ; → Y=&10 (main only) |
| 91B6 | EQUB &FC ; PAGE byte → Y=&11 (main only) |
| 91B7 | EQUB &FF ; → Y=&12 (main only) |
| 91B8 | EQUB &FF ; → Y=&13 (main only) |
| 91B9 | EQUB &DE ; → Y=&14 (main only) |
| 91BA | EQUB &FC ; PAGE byte → Y=&15 (main only) |
| 91BB | EQUB &FF ; → Y=&16 (main only) |
| 91BC | EQUB &FF ; → Y=&17 (main only) |
| 91BD | EQUB &FE, &D1, &FD, &FD, &1F, &FD, &FF, &FF, &FD, &FD, &FF, &FF |
Fn 5: printer selection changed (SELECT)Called when the printer selection changes. Compares 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.
|
||||
| 91C9 | .printer_select_handler | |||
| DEX ; X-1: convert 1-based buffer to 0-based | ||||
| 91CA | CPX osword_pb_ptr ; Is this the network printer buffer? | |||
| 91CC | BNE setup1 ; No: skip printer init | |||
| 91CE | LDA #&1f ; &1F = initial buffer pointer offset | |||
| 91D0 | STA printer_buf_ptr ; Reset printer buffer write position | |||
| 91D3 | LDA #&41 ; &41 = initial PFLAGS (bit 6 set, bit 0 set) | |||
| 91D5 | .setup1←1← 91CC BNE | |||
| STA l0d60 ; Store initial PFLAGS value | ||||
| 91D8 | .return_printer_select←2← 91DB BNE← 91EF 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.
|
||||||
| 91D9 | .remote_print_handler | |||||
| CPY #4 ; Only handle buffer 4 (network printer) | ||||||
| 91DB | BNE return_printer_select ; Not buffer 4: ignore | |||||
| 91DD | TXA ; A = reason code | |||||
| 91DE | DEX ; Reason 1? (DEX: 1->0) | |||||
| 91DF | BNE toggle_print_flag ; Not reason 1: handle Ctrl-B/C | |||||
| 91E1 | TSX ; Get stack pointer for P register | |||||
| 91E2 | ORA l0106,x ; Force I flag in stacked P to block IRQs | |||||
| 91E5 | STA l0106,x ; Write back modified P register | |||||
| 91E8 | .prlp1←2← 91F7 BCC← 91FC BCC | |||||
| LDA #osbyte_read_buffer ; OSBYTE &91: extract char from MOS buffer | ||||||
| 91EA | LDX #buffer_printer ; X=3: printer buffer number | |||||
| 91EC | JSR osbyte ; Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character) | |||||
| 91EF | BCS return_printer_select ; Buffer empty: return | |||||
| 91F1 | TYA ; Y = extracted character Y is the character extracted from the buffer | |||||
| 91F2 | JSR store_output_byte ; Store char in output buffer | |||||
| 91F5 | CPY #&6e ; Buffer nearly full? (&6E = threshold) | |||||
| 91F7 | BCC prlp1 ; Not full: get next char | |||||
| 91F9 | JSR flush_output_block ; Buffer full: flush to network | |||||
| 91FC | 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.
|
|||||||
| 91FE | .store_output_byte←2← 91F2 JSR← 920B JSR | ||||||
| LDY printer_buf_ptr ; Y = current printer buffer offset | |||||||
| 9201 | STA (net_rx_ptr),y ; Store byte at current position | ||||||
| 9203 | INC printer_buf_ptr ; Advance buffer pointer | ||||||
| 9206 | RTS ; Return; Y = buffer offset | ||||||
| 9207 | .toggle_print_flag←1← 91DF BNE | ||||||
| PHA ; Save reason code | |||||||
| 9208 | TXA ; A = reason code | ||||||
| 9209 | EOR #1 ; EOR #1: toggle print-active flag (bit 0) | ||||||
| 920B | JSR store_output_byte ; Store toggled flag as output byte | ||||||
| 920E | EOR l0d60 ; XOR with current PFLAGS | ||||||
| 9211 | ROR ; Test if sequence changed (bit 7 mismatch) | ||||||
| 9212 | BCC pril1 ; Sequence unchanged: skip flush | ||||||
| 9214 | ROL ; Undo ROR | ||||||
| 9215 | STA l0d60 ; Store toggled PFLAGS | ||||||
| 9218 | JSR flush_output_block ; Flush current output block | ||||||
| 921B | .pril1←1← 9212 BCC | ||||||
| LDA l0d60 ; Reload current PFLAGS | |||||||
| 921E | AND #&f0 ; Extract upper nibble of PFLAGS | ||||||
| 9220 | ROR ; Shift for bit extraction | ||||||
| 9221 | TAX ; Save in X | ||||||
| 9222 | PLA ; Restore original reason code | ||||||
| 9223 | ROR ; Merge print-active bit from original A | ||||||
| 9224 | TXA ; Retrieve shifted PFLAGS | ||||||
| 9225 | ROL ; Recombine into new PFLAGS value | ||||||
| 9226 | STA l0d60 ; Store recombined PFLAGS value | ||||||
| 9229 | 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. |
|
| 922A | .flush_output_block←2← 91F9 JSR← 9218 JSR |
| LDY #8 ; Store buffer length at workspace offset &08 | |
| 922C | LDA printer_buf_ptr ; Current buffer fill position |
| 922F | STA (nfs_workspace),y ; Write to workspace offset &08 |
| 9231 | LDA net_rx_ptr_hi ; Store page high byte at offset &09 |
| 9233 | INY ; Y=&09 |
| 9234 | STA (nfs_workspace),y ; Write page high byte at offset &09 |
| 9236 | LDY #5 ; Also store at offset &05 |
| 9238 | STA (nfs_workspace),y ; (end address high byte) |
| 923A | LDY #&0b ; Y=&0B: flag byte offset |
| 923C | LDX #&26 ; X=&26: start from template entry &26 |
| 923E | JSR ctrl_block_setup_clv ; Reuse ctrl_block_setup with CLV entry |
| 9241 | DEY ; Y=&0A: sequence flag byte offset |
| 9242 | LDA l0d60 ; Load current PFLAGS |
| 9245 | PHA ; Save current PFLAGS |
| 9246 | ROL ; Carry = current sequence (bit 7) |
| 9247 | PLA ; Restore original PFLAGS |
| 9248 | EOR #&80 ; Toggle sequence number (bit 7 of PFLAGS) |
| 924A | STA l0d60 ; Store toggled sequence number |
| 924D | ROL ; Old sequence bit into bit 0 |
| 924E | STA (nfs_workspace),y ; Store sequence flag at offset &0A |
| 9250 | LDY #&1f ; Y=&1F: buffer start offset |
| 9252 | STY printer_buf_ptr ; Reset printer buffer to start (&1F) |
| 9255 | LDA #0 ; A=0: printer output flag |
| 9257 | TAX ; X=0: workspace low byte X=&00 |
| 9258 | LDY nfs_workspace_hi ; Y = workspace page high byte |
| 925A | CLI ; Enable interrupts before TX |
| fall through ↓ | |
Transmit with retry loop (XMITFS/XMITFY)Calls the low-level transmit routine (BRIANX) with FSTRY (&FF = 255) retries and FSDELY (&60 = 96) ms delay between attempts. On each iteration, checks the result code: zero means success, non-zero means retry. After all retries exhausted, reports a 'Net error'. Entry point XMITFY allows a custom delay in Y.
|
||||||||
| 925B | .econet_tx_retry←2← 83E5 JSR← 841E JSR | |||||||
| STX net_tx_ptr ; Set TX control block ptr low byte | ||||||||
| 925D | STY net_tx_ptr_hi ; Set TX control block ptr high byte | |||||||
| 925F | PHA ; Save A (handle bitmask) for later | |||||||
| 9260 | AND fs_sequence_nos ; Compute sequence bit from handle | |||||||
| 9263 | BEQ bsxl1 ; Zero: no sequence bit set | |||||||
| 9265 | LDA #1 ; Non-zero: normalise to bit 0 | |||||||
| 9267 | .bsxl1←1← 9263 BEQ | |||||||
| LDY #0 ; Y=0: flag byte offset in TXCB | ||||||||
| 9269 | ORA (net_tx_ptr),y ; Merge sequence into existing flag byte | |||||||
| 926B | PHA ; Save merged flag byte | |||||||
| 926C | STA (net_tx_ptr),y ; Write flag+sequence to TXCB byte 0 | |||||||
| 926E | JSR tx_poll_ff ; Transmit with full retry | |||||||
| 9271 | LDA #&ff ; End address &FFFF = unlimited data length | |||||||
| 9273 | LDY #8 ; Y=8: end address low offset in TXCB | |||||||
| 9275 | STA (net_tx_ptr),y ; Store &FF to end addr low | |||||||
| 9277 | INY ; Y=&09 | |||||||
| 9278 | STA (net_tx_ptr),y ; Store &FF to end addr high (Y=9) | |||||||
| 927A | PLA ; Recover merged flag byte | |||||||
| 927B | TAX ; Save in X for sequence compare | |||||||
| 927C | LDY #&d1 ; Y=&D1: printer port number | |||||||
| 927E | PLA ; Recover saved handle bitmask | |||||||
| 927F | PHA ; Re-save for later consumption | |||||||
| 9280 | BEQ bspsx ; A=0: port &D1 (print); A!=0: port &90 (FS) | |||||||
| 9282 | LDY #&90 ; Y=&90: FS data port | |||||||
| 9284 | .bspsx←1← 9280 BEQ | |||||||
| TYA ; A = selected port number | ||||||||
| 9285 | LDY #1 ; Y=1: port byte offset in TXCB | |||||||
| 9287 | STA (net_tx_ptr),y ; Write port to TXCB byte 1 | |||||||
| 9289 | TXA ; A = saved flag byte (expected sequence) | |||||||
| 928A | DEY ; Y=&00 | |||||||
| 928B | PHA ; Push expected sequence for retry loop | |||||||
| 928C | .bsxl0←1← 9298 BCS | |||||||
| LDA #&7f ; Flag byte &7F = waiting for reply | ||||||||
| 928E | STA (net_tx_ptr),y ; Write to TXCB flag byte (Y=0) | |||||||
| 9290 | JSR send_to_fs_star ; Transmit and wait for reply (BRIANX) | |||||||
| 9293 | PLA ; Recover expected sequence | |||||||
| 9294 | PHA ; Keep on stack for next iteration | |||||||
| 9295 | EOR (net_tx_ptr),y ; Check if TX result matches expected sequence | |||||||
| 9297 | ROR ; Bit 0 to carry (sequence mismatch?) | |||||||
| 9298 | BCS bsxl0 ; C=1: mismatch, retry transmit | |||||||
| 929A | PLA ; Clean up: discard expected sequence | |||||||
| 929B | PLA ; Discard saved handle bitmask | |||||||
| 929C | TAX ; Transfer count to X | |||||||
| 929D | INX ; Test for retry exhaustion | |||||||
| 929E | BEQ return_bspsx ; X wrapped to 0: retries exhausted | |||||||
| 92A0 | EOR fs_sequence_nos ; Toggle sequence bit on success | |||||||
| 92A3 | .return_bspsx←1← 929E BEQ | |||||||
| RTS ; Return | ||||||||
Save palette and VDU state (CVIEW)Part of the VIEW facility (second iteration, started 27/7/82). Uses dynamically allocated buffer store. The WORKP1 pointer (&9E,&9F) serves double duty: non-zero indicates data ready AND provides the buffer address — an efficient use of scarce zero- page space. This code must be user-transparent as the NFS may not be the dominant filing system. Reads all 16 palette entries using OSWORD &0B (read palette) and stores the results. Then reads cursor position (OSBYTE &85), shadow RAM allocation (OSBYTE &C2), and screen start address (OSBYTE &C3) using the 3-entry table at &9305 (osbyte_vdu_table). On completion, restores the JSR buffer protection bits (LSTAT) from OLDJSR to re-enable JSR reception, which was disabled during the screen data capture to prevent interference. |
|
| 92A4 | .lang_2_save_palette_vdu |
| LDA table_idx ; Save current table index | |
| 92A6 | PHA ; Push for later restore |
| 92A7 | LDA #&e9 ; Point workspace to palette save area (&E9) |
| 92A9 | STA nfs_workspace ; Set workspace low byte |
| 92AB | LDY #0 ; Y=0: first palette entry |
| 92AD | STY table_idx ; Clear table index counter |
| 92AF | LDA l0350 ; Save current screen MODE to workspace |
| 92B2 | STA (nfs_workspace),y ; Store MODE at workspace[0] |
| 92B4 | INC nfs_workspace ; Advance workspace pointer past MODE byte |
| 92B6 | LDA l0351 ; Read colour count (from &0351) |
| 92B9 | PHA ; Push for iteration count tracking |
| 92BA | TYA ; A=0: logical colour number for OSWORD A=&00 |
| 92BB | .save_palette_entry←1← 92DA BNE |
| STA (nfs_workspace),y ; Store logical colour at workspace[0] | |
| 92BD | LDX nfs_workspace ; X = workspace ptr low (param block addr) |
| 92BF | LDY nfs_workspace_hi ; Y = workspace ptr high |
| 92C1 | LDA #osword_read_palette ; OSWORD &0B: read palette for logical colour |
| 92C3 | JSR osword ; Read palette |
| 92C6 | PLA ; Recover colour count |
| 92C7 | LDY #0 ; Y=0: access workspace[0] |
| 92C9 | STA (nfs_workspace),y ; Write colour count back to workspace[0] |
| 92CB | INY ; Y=1: access workspace[1] (palette result) Y=&01 |
| 92CC | LDA (nfs_workspace),y ; Read palette value returned by OSWORD |
| 92CE | PHA ; Push palette value for next iteration |
| 92CF | LDX nfs_workspace ; X = current workspace ptr low |
| 92D1 | INC nfs_workspace ; Advance workspace pointer |
| 92D3 | INC table_idx ; Increment table index |
| 92D5 | DEY ; Y=0 for next store Y=&00 |
| 92D6 | LDA table_idx ; Load table index as logical colour |
| 92D8 | CPX #&f9 ; Loop until workspace wraps past &F9 |
| 92DA | BNE save_palette_entry ; Continue for all 16 palette entries |
| 92DC | PLA ; Discard last palette value from stack |
| 92DD | STY table_idx ; Reset table index to 0 |
| 92DF | INC nfs_workspace ; Advance workspace past palette data |
| 92E1 | JSR save_vdu_state ; Save cursor pos and OSBYTE state values |
| 92E4 | INC nfs_workspace ; Advance workspace past VDU state data |
| 92E6 | PLA ; Recover saved table index |
| 92E7 | STA table_idx ; Restore table index |
| 92E9 | .clear_jsr_protection←4← 847D JMP← 84A5 JSR← 84CC JSR← 8EEC JMP |
| LDA saved_jsr_mask ; Restore LSTAT from saved OLDJSR value | |
| 92EC | STA prot_status ; Write to protection status |
| 92EF | RTS ; Return |
Save VDU workspace stateStores the cursor position value from &0355 into NFS workspace, then reads cursor position (OSBYTE &85), shadow RAM (OSBYTE &C2), and screen start (OSBYTE &C3) via read_vdu_osbyte, storing each result into consecutive workspace bytes. |
||
| ROM | Exec | |
|---|---|---|
| 92F0 | .save_vdu_state←1← 92E1 JSR | |
| LDA l0355 ; Read cursor editing state | ||
| 92F3 | STA (nfs_workspace),y ; Store to workspace[Y] | |
| 92F5 | TAX ; Preserve in X for OSBYTE | |
| 92F6 | JSR read_vdu_osbyte ; OSBYTE &85: read cursor position | |
| 92F9 | INC nfs_workspace ; Advance workspace pointer | |
| 92FB | TYA ; Y result from OSBYTE &85 | |
| 92FC | STA (nfs_workspace,x) ; Store Y pos to workspace (X=0) | |
| 92FE | JSR read_vdu_osbyte_x0 ; Self-call trick: executes twice | |
| 9301 | .read_vdu_osbyte_x0←1← 92FE JSR | |
| LDX #0 ; X=0 for (zp,X) addressing | ||
| 9303 | .read_vdu_osbyte←1← 92F6 JSR | |
| LDY table_idx ; Index into OSBYTE number table | ||
| 9305 | INC table_idx ; Next table entry next time | |
| 9307 | INC nfs_workspace ; Advance workspace pointer | |
| 9309 | LDA osbyte_vdu_table,y ; Read OSBYTE number from table | |
| 930C | LDY #&ff ; Y=&FF: read current value | |
| 930E | JSR osbyte ; Call OSBYTE | |
| 9311 | TXA ; Result in X to A | |
| 9312 | LDX #0 ; X=0 for indexed indirect store | |
| 9314 | STA (nfs_workspace,x) ; Store result to workspace | |
| 9316 | RTS ; Return after storing result | |
| ; 3-entry OSBYTE table for lang_2_save_palette_vdu (&92A4) | ||
| 9317 | .osbyte_vdu_table←1← 9309 LDA | |
| EQUB &85 ; OSBYTE &85: read cursor position | ||
| 9318 | EQUB &C2 ; OSBYTE &C2: read shadow RAM allocation | |
| 9319 | EQUB &C3 ; OSBYTE &C3: read screen start address | |
| ; Tube BRK handler (BRKV target) — reference: NFS11 NEWBR | ||
| ; Sends error information to the Tube co-processor via R2 ; and R4: | ||
| ; 1. Sends &FF to R4 (WRIFOR) to signal error | ||
| ; 2. Reads R2 data (flush any pending byte) | ||
| ; 3. Sends &00 via R2, then error number from (&FD),0 | ||
| ; 4. Loops sending error string bytes via R2 until zero terminator | ||
| ; 5. Falls through to tube_reset_stack → tube_main_loop | ||
| ; The main loop continuously polls R1 for WRCH requests ; (forwarded | ||
| ; to OSWRITCH &FFCB) and R2 for command bytes (dispatched ; via the | ||
| ; 14-entry table at &0500). The R2 command byte is stored ; at &55 | ||
| ; before dispatch via JMP (&0500). | ||
| 931A | 0016 | .nmi_workspace_start←1← 8147 STA |
| .tube_brk_handler←1← 8147 STA | ||
| LDA #&ff ; A=&FF: signal error to co-processor via R4 | ||
| 931C | 0018 | JSR tube_send_r4 ; Send &FF error signal to Tube R4 |
| 931F | 001B | LDA tube_data_register_2 ; Flush any pending R2 byte |
| 9322 | 001E | LDA #0 ; A=0: send zero prefix to R2 |
| 9324 | 0020 | .tube_send_zero_r2 |
| JSR tube_send_r2 ; Send zero prefix byte via R2 | ||
| 9327 | 0023 | TAY ; Y=0: start of error block at (&FD) |
| 9328 | 0024 | LDA (brk_ptr),y ; Load error number from (&FD),0 |
| 932A | 0026 | JSR tube_send_r2 ; Send error number via R2 |
| 932D | 0029 | .tube_brk_send_loop←1← 0030 BNE |
| INY ; Advance to next error string byte | ||
| 932E | 002A | .tube_send_error_byte |
| LDA (brk_ptr),y ; Load next error string byte | ||
| 9330 | 002C | JSR tube_send_r2 ; Send error string byte via R2 |
| 9333 | 002F | TAX ; Zero byte = end of error string |
| 9334 | 0030 | BNE tube_brk_send_loop ; Loop until zero terminator sent |
| 9336 | 0032 | .tube_reset_stack |
| LDX #&ff ; Reset stack pointer to top | ||
| 9338 | 0034 | TXS ; TXS: set stack pointer from X |
| 9339 | 0035 | CLI ; Enable interrupts for main loop |
| ; Save registers and enter Tube polling loop | ||
| ; Saves X and Y to zp_temp_11/zp_temp_10, then falls ; through | ||
| ; to tube_main_loop which polls Tube R1 (WRCH) and R2 ; (command) | ||
| ; registers in an infinite loop. Called from ; tube_init_reloc | ||
| ; after ROM relocation and from tube_dispatch_table ; handlers | ||
| ; that need to restart the main loop. | ||
| 933A | 0036 | .tube_enter_main_loop←2← 04EC JMP← 053A JMP |
| STX zp_temp_11 ; Save X to temporary | ||
| 933C | 0038 | STY zp_temp_10 ; Save Y to temporary |
| 933E | 003A | .tube_main_loop←6← 0048 BPL← 05AE JMP← 05D5 JMP← 0635 JMP← 069D JMP← 06CA JMP |
| BIT tube_status_1_and_tube_control ; BIT R1 status: check WRCH request | ||
| 9341 | 003D | BPL tube_poll_r2 ; R1 not ready: check R2 instead |
| 9343 | 003F | .tube_handle_wrch←1← 004D BMI |
| LDA tube_data_register_1 ; Read character from Tube R1 data | ||
| 9346 | 0042 | JSR nvwrch ; Write character |
| 9349 | 0045 | .tube_poll_r2←1← 003D BPL |
| BIT tube_status_register_2 ; BIT R2 status: check command byte | ||
| 934C | 0048 | BPL tube_main_loop ; R2 not ready: loop back to R1 check |
| 934E | 004A | BIT tube_status_1_and_tube_control ; Re-check R1: WRCH has priority over R2 |
| 9351 | 004D | BMI tube_handle_wrch ; R1 ready: handle WRCH first |
| 9353 | 004F | LDX tube_data_register_2 ; Read command byte from Tube R2 data |
| 9356 | 0052 | STX tube_dispatch_ptr_lo ; Self-modify JMP low byte for dispatch |
| 9358 | 0054 | .tube_dispatch_cmd |
| JMP (tube_dispatch_table) ; Dispatch to handler via indirect JMP | ||
| 935B | 0057 | .tube_transfer_addr←2← 0478 STY← 0493 STA |
| EQUB &00 | ||
| 935C | 0058 | .tube_xfer_page←2← 047C STA← 0498 STA |
| EQUB &80 | ||
| 935D | 0059 | .tube_xfer_addr_2←1← 04A2 STY |
| EQUB &00 | ||
| 935E | 005A | .tube_xfer_addr_3←1← 04A0 STA |
| EQUB &00 | ||
| ; Tube host code page 4 — reference: NFS12 (BEGIN, ADRR, ; SENDW) | ||
| ; Copied from ROM during init (reloc_p4_src-18). The first | ||
| ; 28 bytes (&0400-&041B) overlap with the end of the ZP ; block | ||
| ; (the same ROM bytes serve both the ZP copy at ; &005B-&0076 | ||
| ; and this page at &0400-&041B). Contains: | ||
| ; &0400: JMP &0473 (BEGIN — CLI parser / startup entry) | ||
| ; &0403: JMP &06E2 (tube_escape_check) | ||
| ; &0406: tube_addr_claim — Tube address claim protocol (ADRR) | ||
| ; &0414: tube_post_init — called after ROM→RAM copy | ||
| ; &0473: BEGIN — startup/CLI entry, break type check | ||
| ; &04E7: tube_rdch_handler — RDCHV target | ||
| ; &04EF: tube_restore_regs — restore X,Y, dispatch entry 6 | ||
| ; &04F7: tube_read_r2 — poll R2 status, read data byte to A | ||
| 935F | 0400 | .tube_code_page4←1← 812D STA |
| JMP tube_begin ; JMP to BEGIN startup entry | ||
| 9362 | 0403 | .tube_escape_entry |
| JMP tube_escape_check ; JMP to tube_escape_check (&06A7) | ||
| 9365 | 0406 | .tube_addr_claim←10← 04BC JSR← 04E4 JMP← 8B3F JSR← 8B51 JSR← 8BAE JSR← 8E17 JMP← 9A01 JSR← 9A53 JSR← 9FA7 JSR← 9FAF JSR |
| CMP #&80 ; A>=&80: address claim; A<&80: data transfer | ||
| 9367 | 0408 | BCC setup_data_transfer ; A<&80: data transfer setup (SENDW) |
| 9369 | 040A | CMP #&c0 ; A>=&C0: new address claim from another host |
| 936B | 040C | BCS addr_claim_external ; C=1: external claim, check ownership |
| 936D | 040E | ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison |
| 936F | 0410 | CMP tube_claimed_id ; Is this for our currently-claimed address? |
| 9371 | 0412 | BNE return_tube_init ; Not our address: return |
| 9373 | 0414 | .tube_post_init←1← 813F JSR |
| LDA #&80 ; &80 sentinel: clear address claim | ||
| 9375 | 0416 | STA tube_claim_flag ; Store to claim-in-progress flag |
| 9377 | 0418 | RTS ; Return from tube_post_init |
| 9378 | 0419 | .addr_claim_external←1← 040C BCS |
| ASL tube_claim_flag ; Another host claiming; check if we're owner | ||
| 937A | 041B | BCS accept_new_claim ; C=1: we have an active claim |
| 937C | 041D | CMP tube_claimed_id ; Compare with our claimed address |
| 937E | 041F | BEQ return_tube_init ; Match: return (we already have it) |
| 9380 | 0421 | CLC ; Not ours: CLC = we don't own this address |
| 9381 | 0422 | RTS ; Return with C=0 (claim denied) |
| 9382 | 0423 | .accept_new_claim←1← 041B BCS |
| STA tube_claimed_id ; Accept new claim: update our address | ||
| 9384 | 0425 | .return_tube_init←2← 0412 BNE← 041F BEQ |
| RTS ; Return with address updated | ||
| 9385 | 0426 | .setup_data_transfer←1← 0408 BCC |
| STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y) | ||
| 9387 | 0428 | STX tube_data_ptr ; Store address pointer low byte |
| 9389 | 042A | JSR tube_send_r4 ; Send transfer type byte to co-processor |
| 938C | 042D | TAX ; X = transfer type for table lookup |
| 938D | 042E | LDY #3 ; Y=3: send 4 bytes (address + claimed addr) |
| 938F | 0430 | .send_xfer_addr_bytes←1← 0436 BPL |
| LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y) | ||
| 9391 | 0432 | JSR tube_send_r4 ; Send address byte to co-processor via R4 |
| 9394 | 0435 | DEY ; Previous byte (big-endian: 3,2,1,0) |
| 9395 | 0436 | BPL send_xfer_addr_bytes ; Loop for all 4 address bytes |
| 9397 | 0438 | JSR tube_send_r4 ; Send claimed address via R4 |
| 939A | 043B | LDY #&18 ; Y=&18: enable Tube control register |
| 939C | 043D | STY tube_status_1_and_tube_control ; Enable Tube interrupt generation |
| 939F | 0440 | LDA tube_xfer_ctrl_bits,x ; Look up Tube control bits for this xfer type |
| 93A2 | 0443 | STA tube_status_1_and_tube_control ; Apply transfer- specific control bits |
| 93A5 | 0446 | LSR ; LSR: check bit 2 (2-byte flush needed?) |
| 93A6 | 0447 | LSR ; LSR: shift bit 2 to carry |
| 93A7 | 0448 | .poll_r4_copro_ack←1← 044B BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status for co- processo r response | ||
| 93AA | 044B | BVC poll_r4_copro_ack ; Bit 6 clear: not ready, keep polling |
| 93AC | 044D | BCS flush_r3_nmi_check ; R4 bit 7: co-processor acknowledged transfer |
| 93AE | 044F | CPX #4 ; Type 4 = SENDW (host-to-parasite word xfer) |
| 93B0 | 0451 | BNE return_tube_xfer ; Not SENDW type: skip release path |
| 93B2 | 0453 | PLA ; Discard return address (low byte) |
| 93B3 | 0454 | PLA ; Discard return address (high byte) |
| 93B4 | 0455 | .release_claim_restart←1← 04B8 BEQ |
| LDA #&80 ; A=&80: reset claim flag sentinel | ||
| 93B6 | 0457 | STA tube_claim_flag ; Clear claim-in-progress flag |
| 93B8 | 0459 | JMP tube_reply_byte ; Restart Tube main loop |
| 93BB | 045C | .flush_r3_nmi_check←1← 044D BCS |
| BIT tube_data_register_3 ; Flush R3 data (first byte) | ||
| 93BE | 045F | BIT tube_data_register_3 ; Flush R3 data (second byte) |
| 93C1 | 0462 | .copro_ack_nmi_check |
| LSR ; LSR: check bit 0 (NMI used?) | ||
| 93C2 | 0463 | BCC return_tube_xfer ; C=0: NMI not used, skip NMI release |
| 93C4 | 0465 | LDY #&88 ; Release Tube NMI (transfer used interrupts) |
| 93C6 | 0467 | STY tube_status_1_and_tube_control ; Write &88 to Tube control to release NMI |
| 93C9 | 046A | .return_tube_xfer←2← 0451 BNE← 0463 BCC |
| RTS ; Return from transfer setup | ||
| 93CA | 046B | .tube_xfer_ctrl_bits←1← 0440 LDA |
| EQUB &86, &88, &96, &98, &18, &18, &82, &18 | ||
| 93D2 | 0473 | .tube_begin←1← 0400 JMP |
| CLI ; BEGIN: enable interrupts for Tube host code | ||
| 93D3 | 0474 | PHP ; Save processor status |
| 93D4 | 0475 | PHA ; Save A on stack |
| 93D5 | 0476 | LDY #0 ; Y=0: start at beginning of page |
| 93D7 | 0478 | STY tube_transfer_addr ; Store to zero page pointer low byte |
| ; Initialise relocation address for ROM transfer | ||
| ; Sets source page to &8000 and page counter to &80. ; Checks | ||
| ; ROM type bit 5 for a relocation address in the ROM ; header; | ||
| ; if present, extracts the 4-byte address from after the | ||
| ; copyright string. Otherwise uses default &8000 start. | ||
| 93D9 | 047A | .tube_init_reloc |
| LDA #&80 ; Init: start sending from &8000 | ||
| 93DB | 047C | STA tube_xfer_page ; Store &80 as source page high byte |
| 93DD | 047E | STA zp_ptr_hi ; Store &80 as page counter initial value |
| 93DF | 0480 | LDA #&20 ; A=&20: bit 5 mask for ROM type check |
| 93E1 | 0482 | AND rom_type ; ROM type bit 5: reloc address in header? |
| 93E4 | 0485 | BEQ store_xfer_end_addr ; No reloc addr: use defaults |
| 93E6 | 0487 | LDX copyright_offset ; Skip past copyright string to find reloc addr |
| 93E9 | 048A | .scan_copyright_end←1← 048E BNE |
| INX ; Skip past null-terminated copyright string | ||
| 93EA | 048B | LDA rom_header,x ; Load next byte from ROM header |
| 93ED | 048E | BNE scan_copyright_end ; Loop until null terminator found |
| 93EF | 0490 | LDA lang_entry_lo,x ; Read 4-byte reloc address from ROM header |
| 93F2 | 0493 | STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr |
| 93F4 | 0495 | LDA lang_entry_hi,x ; Load reloc addr byte 2 |
| 93F7 | 0498 | STA tube_xfer_page ; Store as source page start |
| 93F9 | 049A | LDY service_entry,x ; Load reloc addr byte 3 |
| 93FC | 049D | LDA svc_entry_lo,x ; Load reloc addr byte 4 (highest) |
| 93FF | 04A0 | .store_xfer_end_addr←1← 0485 BEQ |
| STA tube_xfer_addr_3 ; Store high byte of end address | ||
| 9401 | 04A2 | STY tube_xfer_addr_2 ; Store byte 3 of end address |
| 9403 | 04A4 | PLA ; Restore A from stack |
| 9404 | 04A5 | PLP ; Restore processor status |
| 9405 | 04A6 | BCS beginr ; Carry set: language entry (claim Tube) |
| 9407 | 04A8 | TAX ; X = A (preserved from entry) |
| 9408 | 04A9 | BNE begink ; Non-zero: check break type |
| 940A | 04AB | JMP tube_reply_ack ; A=0: acknowledge and return |
| 940D | 04AE | .begink←1← 04A9 BNE |
| LDX #0 ; X=0 for OSBYTE read | ||
| 940F | 04B0 | LDY #&ff ; Y=&FF for OSBYTE read |
| 9411 | 04B2 | LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read last break type |
| 9413 | 04B4 | JSR osbyte ; Read type of last reset |
| 9416 | 04B7 | TXA ; X=value of type of last reset |
| 9417 | 04B8 | BEQ release_claim_restart ; Soft break (0): skip ROM transfer |
| 9419 | 04BA | .beginr←2← 04A6 BCS← 04BF BCC |
| LDA #&ff ; A=&FF: claim Tube for all operations | ||
| 941B | 04BC | JSR tube_addr_claim ; Claim Tube address via R4 |
| 941E | 04BF | BCC beginr ; Not claimed: retry until claimed |
| 9420 | 04C1 | LDA #1 ; Transfer type 1 (parasite to host) |
| 9422 | 04C3 | JSR tube_setup_transfer ; Set up Tube transfer parameters |
| 9425 | 04C6 | LDY #0 ; Y=0: start at page boundary |
| 9427 | 04C8 | STY zp_ptr_lo ; Source ptr low = 0 |
| 9429 | 04CA | LDX #&40 ; X=&40: 64 pages (16KB) to transfer |
| 942B | 04CC | .send_rom_byte←2← 04D7 BNE← 04DC BNE |
| LDA (zp_ptr_lo),y ; Read byte from source address | ||
| 942D | 04CE | STA tube_data_register_3 ; Send byte to Tube via R3 |
| 9430 | 04D1 | .poll_r3_ready←1← 04D4 BVC |
| BIT tube_status_register_3 ; Check R3 status | ||
| 9433 | 04D4 | BVC poll_r3_ready ; Not ready: wait for Tube |
| 9435 | 04D6 | INY ; Next byte in page |
| 9436 | 04D7 | BNE send_rom_byte ; More bytes in page: continue |
| 9438 | 04D9 | INC zp_ptr_hi ; Next source page |
| 943A | 04DB | DEX ; Decrement page counter |
| 943B | 04DC | BNE send_rom_byte ; More pages: continue transfer |
| 943D | 04DE | LDA #4 ; Transfer type 4 (host to parasite burst) |
| 943F | 04E0 | .tube_setup_transfer←1← 04C3 JSR |
| LDY #0 ; Y=0: low byte of param block ptr | ||
| 9441 | 04E2 | LDX #&57 ; X=&57: param block at &0057 |
| 9443 | 04E4 | JMP tube_addr_claim ; Claim Tube and start transfer |
| 9446 | 04E7 | .tube_rdch_handler |
| LDA #1 ; R2 command: OSRDCH request | ||
| 9448 | 04E9 | JSR tube_send_r2 ; Send OSRDCH request to host |
| 944B | 04EC | JMP tube_enter_main_loop ; Jump to RDCH completion handler |
| 944E | 04EF | .tube_restore_regs |
| LDY zp_temp_10 ; Restore saved Y register | ||
| 9450 | 04F1 | LDX zp_temp_11 ; Restore X from saved value |
| 9452 | 04F3 | JSR tube_read_r2 ; Read result byte from R2 |
| 9455 | 04F6 | ASL ; Shift carry into C flag |
| 9456 | 04F7 | .tube_read_r2←22← 04F3 JSR← 04FA BPL← 0543 JSR← 0547 JSR← 0550 JSR← 0569 JSR← 0580 JSR← 058C JSR← 0592 JSR← 059B JSR← 05B5 JSR← 05DA JSR← 05EB JSR← 0604 JSR← 060C JSR← 0623 JSR← 0627 JSR← 0638 JSR← 063C JSR← 0640 JSR← 065A JSR← 06A2 JSR |
| BIT tube_status_register_2 ; Poll R2 status register | ||
| 9459 | 04FA | BPL tube_read_r2 ; Bit 7 clear: R2 not ready, wait |
| 945B | 04FC | LDA tube_data_register_2 ; Read byte from R2 data register |
| 945E | 04FF | RTS ; Return with pointers initialised |
| ; Tube host code page 5 — reference: NFS13 (TASKS, ; BPUT-FILE) | ||
| ; Copied from ROM during init (reloc_p5_src-18). Contains: | ||
| ; &0500: tube_dispatch_table — 14-entry handler address table | ||
| ; &051C: tube_wrch_handler — WRCHV target | ||
| ; &051F: tube_send_and_poll — send byte via R2, poll for reply | ||
| ; &0527: tube_poll_r1_wrch — service R1 WRCH while waiting for R2 | ||
| ; &053D: tube_release_return — restore regs and RTS | ||
| ; &0543: tube_osbput — write byte to file | ||
| ; &0550: tube_osbget — read byte from file | ||
| ; &055B: tube_osrdch — read character | ||
| ; &0569: tube_osfind — open file | ||
| ; &0580: tube_osfind_close — close file (A=0) | ||
| ; &058C: tube_osargs — file argument read/write | ||
| ; &05B1: tube_read_string — read CR-terminated string into &0700 | ||
| ; &05C5: tube_oscli — execute * command | ||
| ; &05CB: tube_reply_ack — send &7F acknowledge | ||
| ; &05CD: tube_reply_byte — send byte and return to main loop | ||
| ; &05D8: tube_osfile — whole file operation | ||
| 945F | 0500 | .tube_dispatch_table←2← 0054 JMP← 8133 STA |
| EQUW tube_osrdch ; cmd 0: OSRDCH | ||
| 9461 | 0502 | EQUW tube_oscli ; cmd 1: OSCLI |
| 9463 | 0504 | EQUW tube_osbyte_short ; cmd 2: OSBYTE (2-param) |
| 9465 | 0506 | EQUW tube_osbyte_long ; cmd 3: OSBYTE (3-param) |
| 9467 | 0508 | EQUW tube_osword ; cmd 4: OSWORD |
| 9469 | 050A | EQUW tube_osword_rdln ; cmd 5: OSWORD 0 (read line) |
| 946B | 050C | EQUW tube_restore_regs ; cmd 6: release/restore regs |
| 946D | 050E | EQUW tube_release_return ; cmd 7: restore regs, RTS |
| 946F | 0510 | EQUW tube_osargs ; cmd 8: OSARGS |
| 9471 | 0512 | EQUW tube_osbget ; cmd 9: OSBGET |
| 9473 | 0514 | EQUW tube_osbput ; cmd 10: OSBPUT |
| 9475 | 0516 | EQUW tube_osfind ; cmd 11: OSFIND |
| 9477 | 0518 | EQUW tube_osfile ; cmd 12: OSFILE |
| 9479 | 051A | EQUW tube_osgbpb ; cmd 13: OSGBPB |
| 947B | 051C | .tube_wrch_handler |
| PHA ; Save A for WRCHV echo | ||
| 947C | 051D | LDA #0 ; A=0: send null prefix via R2 |
| 947E | 051F | .tube_send_and_poll |
| JSR tube_send_r2 ; Send prefix byte to co-processor | ||
| 9481 | 0522 | .poll_r2_reply←2← 052A BPL← 0532 JMP |
| BIT tube_status_register_2 ; Poll R2 for co-processor reply | ||
| 9484 | 0525 | BVS wrch_echo_reply ; R2 ready: go process reply |
| 9486 | 0527 | .tube_poll_r1_wrch |
| BIT tube_status_1_and_tube_control ; Check R1 for pending WRCH request | ||
| 9489 | 052A | BPL poll_r2_reply ; No R1 data: back to polling R2 |
| 948B | 052C | LDA tube_data_register_1 ; Read WRCH character from R1 |
| 948E | 052F | JSR nvwrch ; Write character |
| 9491 | 0532 | .tube_resume_poll |
| JMP poll_r2_reply ; Resume R2 polling after servicing | ||
| 9494 | 0535 | .wrch_echo_reply←1← 0525 BVS |
| PLA ; Recover original character | ||
| 9495 | 0536 | STA tube_data_register_2 ; Echo character back via R2 |
| 9498 | 0539 | PHA ; Push for dispatch loop re-entry |
| 9499 | 053A | JMP tube_enter_main_loop ; Enter main dispatch loop |
| 949C | 053D | .tube_release_return |
| LDX zp_temp_11 ; Restore saved X from temporary | ||
| 949E | 053F | LDY zp_temp_10 ; Restore saved Y from temporary |
| 94A0 | 0541 | PLA ; Restore saved A |
| 94A1 | 0542 | RTS ; Return to caller |
| 94A2 | 0543 | .tube_osbput |
| JSR tube_read_r2 ; Read channel handle from R2 | ||
| 94A5 | 0546 | TAY ; Y=channel handle from R2 |
| 94A6 | 0547 | JSR tube_read_r2 ; Read data byte from R2 for BPUT |
| 94A9 | 054A | JSR osbput ; Write a single byte A to an open file Y |
| 94AC | 054D | JMP tube_reply_ack ; BPUT done: send acknowledge, return |
| 94AF | 0550 | .tube_osbget |
| JSR tube_read_r2 ; Read channel handle from R2 | ||
| 94B2 | 0553 | TAY ; Y=channel handle for OSBGET Y=file handle |
| 94B3 | 0554 | JSR osbget ; Read a single byte from an open file Y |
| 94B6 | 0557 | PHA ; Save byte read from file |
| 94B7 | 0558 | JMP send_reply_ok ; Send carry+byte reply (BGET result) |
| 94BA | 055B | .tube_osrdch |
| JSR nvrdch ; Read a character from the current input stream | ||
| 94BD | 055E | PHA ; A=character read |
| 94BE | 055F | .send_reply_ok←2← 0558 JMP← 0620 JMP |
| ORA #&80 ; Set bit 7 (no-error flag) | ||
| 94C0 | 0561 | .tube_rdch_reply |
| ROR ; ROR A: encode carry (error flag) into bit 7 | ||
| 94C1 | 0562 | JSR tube_send_r2 ; = JSR tube_send_r2 (overlaps &053D entry) |
| 94C4 | 0565 | PLA ; Restore read character/byte |
| 94C5 | 0566 | JMP tube_reply_byte ; Return to Tube main loop |
| 94C8 | 0569 | .tube_osfind |
| JSR tube_read_r2 ; Read open mode from R2 | ||
| 94CB | 056C | BEQ tube_osfind_close ; A=0: close file, else open with filename |
| 94CD | 056E | PHA ; Save open mode while reading filename |
| 94CE | 056F | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 94D1 | 0572 | PLA ; Recover open mode from stack |
| 94D2 | 0573 | JSR osfind ; Open or close file(s) |
| 94D5 | 0576 | PHA ; Save file handle result |
| 94D6 | 0577 | LDA #&ff ; A=&FF: success marker |
| 94D8 | 0579 | JSR tube_send_r2 ; Send success marker via R2 |
| 94DB | 057C | PLA ; Restore file handle |
| 94DC | 057D | JMP tube_reply_byte ; Send file handle result to co-processor |
| 94DF | 0580 | .tube_osfind_close←1← 056C BEQ |
| JSR tube_read_r2 ; OSFIND close: read handle from R2 | ||
| 94E2 | 0583 | TAY ; Y=handle to close |
| 94E3 | 0584 | LDA #osfind_close ; A=0: close command for OSFIND |
| 94E5 | 0586 | JSR osfind ; Close one or all files |
| 94E8 | 0589 | JMP tube_reply_ack ; Close done: send acknowledge, return |
| 94EB | 058C | .tube_osargs |
| JSR tube_read_r2 ; Read file handle from R2 | ||
| 94EE | 058F | TAY ; Y=file handle for OSARGS |
| 94EF | 0590 | .tube_read_params |
| LDX #3 ; Read 4-byte arg + reason from R2 into ZP | ||
| 94F1 | 0592 | .read_osargs_params←1← 0598 BPL |
| JSR tube_read_r2 ; Read next param byte from R2 | ||
| 94F4 | 0595 | STA zp_ptr_lo,x ; Params stored at &00-&03 (little-endian) |
| 94F6 | 0597 | DEX ; Decrement byte counter |
| 94F7 | 0598 | BPL read_osargs_params ; Loop until all 4 bytes read |
| 94F9 | 059A | INX ; X=0: reset index after loop |
| 94FA | 059B | JSR tube_read_r2 ; Read OSARGS reason code from R2 |
| 94FD | 059E | JSR osargs ; Read or write a file's attributes |
| 9500 | 05A1 | JSR tube_send_r2 ; Send result A back to co-processor |
| 9503 | 05A4 | LDX #3 ; Return 4-byte result from ZP &00-&03 |
| 9505 | 05A6 | .send_osargs_result←1← 05AC BPL |
| LDA zp_ptr_lo,x ; Load result byte from zero page | ||
| 9507 | 05A8 | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 950A | 05AB | DEX ; Previous byte (count down) |
| 950B | 05AC | BPL send_osargs_result ; Loop for all 4 bytes |
| 950D | 05AE | JMP tube_main_loop ; Return to Tube main loop |
| 9510 | 05B1 | .tube_read_string←3← 056F JSR← 05C5 JSR← 05E2 JSR |
| LDX #0 ; X=0: initialise string buffer index | ||
| 9512 | 05B3 | LDY #0 ; Y=0: string buffer offset 0 |
| 9514 | 05B5 | .strnh←1← 05C0 BNE |
| JSR tube_read_r2 ; Read next string byte from R2 | ||
| 9517 | 05B8 | STA l0700,y ; Store byte in string buffer at &0700+Y |
| 951A | 05BB | INY ; Next buffer position |
| 951B | 05BC | BEQ string_buf_done ; Y overflow: string too long, truncate |
| 951D | 05BE | CMP #&0d ; Check for CR terminator |
| 951F | 05C0 | BNE strnh ; Not CR: continue reading string |
| 9521 | 05C2 | .string_buf_done←1← 05BC BEQ |
| LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND | ||
| 9523 | 05C4 | RTS ; Return with XY pointing to &0700 |
| 9524 | 05C5 | .tube_oscli |
| JSR tube_read_string ; Read command string from R2 | ||
| 9527 | 05C8 | JSR oscli ; Execute * command via OSCLI |
| 952A | 05CB | .tube_reply_ack←3← 04AB JMP← 054D JMP← 0589 JMP |
| LDA #&7f ; &7F = success acknowledgement | ||
| 952C | 05CD | .tube_reply_byte←5← 0459 JMP← 0566 JMP← 057D JMP← 05D0 BVC← 06B5 JMP |
| BIT tube_status_register_2 ; Poll R2 status until ready | ||
| 952F | 05D0 | BVC tube_reply_byte ; Bit 6 clear: not ready, loop |
| 9531 | 05D2 | STA tube_data_register_2 ; Write byte to R2 data register |
| 9534 | 05D5 | .mj←1← 0600 BEQ |
| JMP tube_main_loop ; Return to Tube main loop | ||
| 9537 | 05D8 | .tube_osfile |
| LDX #&10 ; X=&10: 16 bytes for OSFILE CB | ||
| 9539 | 05DA | .argsw←1← 05E0 BNE |
| JSR tube_read_r2 ; Read next control block byte from R2 | ||
| 953C | 05DD | STA zp_ptr_hi,x ; Store at &01+X (descending) |
| 953E | 05DF | DEX ; Decrement byte counter |
| 953F | 05E0 | BNE argsw ; Loop for all 16 bytes |
| 9541 | 05E2 | JSR tube_read_string ; Read filename string from R2 into &0700 |
| 9544 | 05E5 | STX zp_ptr_lo ; XY=&0700: filename pointer for OSFILE |
| 9546 | 05E7 | STY zp_ptr_hi ; Store Y=7 as pointer high byte |
| 9548 | 05E9 | LDY #0 ; Y=0 for OSFILE control block offset |
| 954A | 05EB | JSR tube_read_r2 ; Read OSFILE reason code from R2 |
| 954D | 05EE | JSR osfile ; Execute OSFILE operation |
| 9550 | 05F1 | ORA #&80 ; Set bit 7: mark result as present |
| 9552 | 05F3 | JSR tube_send_r2 ; Send result A (object type) to co-processor |
| 9555 | 05F6 | LDX #&10 ; Return 16-byte control block to co-processor |
| 9557 | 05F8 | .send_osfile_ctrl_blk←1← 05FE BNE |
| LDA zp_ptr_hi,x ; Load control block byte | ||
| 9559 | 05FA | JSR tube_send_r2 ; Send byte to co-processor via R2 |
| 955C | 05FD | DEX ; Decrement byte counter |
| 955D | 05FE | BNE send_osfile_ctrl_blk ; Loop for all 16 bytes |
| ; Tube host code page 6 — reference: NFS13 (GBPB-ESCA) | ||
| ; Copied from ROM during init (reloc_p6_src-18). ; &0600-&0601 is the tail | ||
| ; of tube_osfile (BEQ to tube_reply_byte when done). ; Contains: | ||
| ; &0602: tube_osgbpb — multi-byte file I/O | ||
| ; &0626: tube_osbyte_short — 2-param OSBYTE (returns X) | ||
| ; &063B: tube_osbyte_long — 3-param OSBYTE (returns carry+Y+X) | ||
| ; &065D: tube_osword — variable-length OSWORD (buffer at &0130) | ||
| ; &06A3: tube_osword_rdln — OSWORD 0 (read line, 5-byte params) | ||
| ; &06BB: tube_rdln_send_line — send input line from &0700 | ||
| ; &06D0: tube_send_r2 — poll R2 status, write A to R2 data | ||
| ; &06D9: tube_send_r4 — poll R4 status, write A to R4 data | ||
| ; &06E2: tube_escape_check — check &FF, forward escape to R1 | ||
| ; &06E8: tube_event_handler — EVNTV: forward event (A,X,Y) via R1 | ||
| ; &06F7: tube_send_r1 — poll R1 status, write A to R1 data | ||
| 955F | 0600 | .tube_code_page6←1← 8139 STA |
| BEQ mj ; OSGBPB done: return to main loop | ||
| 9561 | 0602 | .tube_osgbpb |
| LDX #&0c ; X=&0C: 13 bytes for OSGBPB CB | ||
| 9563 | 0604 | .read_gbpb_params←1← 060A BPL |
| JSR tube_read_r2 ; Read param byte from Tube R2 | ||
| 9566 | 0607 | STA zp_ptr_lo,x ; Store in zero page param block |
| 9568 | 0609 | DEX ; Next byte (descending) |
| 9569 | 060A | BPL read_gbpb_params ; Loop until all 13 bytes read |
| 956B | 060C | JSR tube_read_r2 ; Read A (OSGBPB function code) |
| 956E | 060F | INX ; X=0 after loop |
| 956F | 0610 | LDY #0 ; Y=0 for OSGBPB call |
| 9571 | 0612 | JSR osgbpb ; Read or write multiple bytes to an open file |
| 9574 | 0615 | PHA ; Save OSGBPB result on stack |
| 9575 | 0616 | LDX #&0c ; X=12: send 13 updated param bytes |
| 9577 | 0618 | .send_gbpb_params←1← 061E BPL |
| LDA zp_ptr_lo,x ; Load updated param byte | ||
| 9579 | 061A | JSR tube_send_r2 ; Send param byte via R2 |
| 957C | 061D | DEX ; Next byte (descending) |
| 957D | 061E | BPL send_gbpb_params ; Loop until all 13 bytes sent |
| 957F | 0620 | JMP send_reply_ok ; Return to main event loop |
| 9582 | 0623 | .tube_osbyte_short |
| JSR tube_read_r2 ; Read X parameter from R2 | ||
| 9585 | 0626 | TAX ; Save in X |
| 9586 | 0627 | JSR tube_read_r2 ; Read A (OSBYTE function code) |
| 9589 | 062A | JSR osbyte ; Execute OSBYTE A,X |
| 958C | 062D | .tube_osbyte_send_x←2← 0630 BVC← 0658 BVS |
| BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready) | ||
| 958F | 0630 | BVC tube_osbyte_send_x ; Not ready: keep polling |
| 9591 | 0632 | STX tube_data_register_2 ; Send X result for 2-param OSBYTE |
| 9594 | 0635 | .bytex←1← 0648 BEQ |
| JMP tube_main_loop ; Return to main event loop | ||
| 9597 | 0638 | .tube_osbyte_long |
| JSR tube_read_r2 ; Read X parameter from R2 | ||
| 959A | 063B | TAX ; Save in X |
| 959B | 063C | JSR tube_read_r2 ; Read Y parameter from co-processor |
| 959E | 063F | TAY ; Save in Y |
| 959F | 0640 | JSR tube_read_r2 ; Read A (OSBYTE function code) |
| 95A2 | 0643 | JSR osbyte ; Execute OSBYTE A,X,Y |
| 95A5 | 0646 | EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT) |
| 95A7 | 0648 | BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed |
| 95A9 | 064A | LDA #&40 ; A=&40: high bit will hold carry |
| 95AB | 064C | ROR ; Encode carry (error flag) into bit 7 |
| 95AC | 064D | JSR tube_send_r2 ; Send carry+status byte via R2 |
| 95AF | 0650 | .tube_osbyte_send_y←1← 0653 BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95B2 | 0653 | BVC tube_osbyte_send_y ; Not ready: keep polling |
| 95B4 | 0655 | STY tube_data_register_2 ; Send Y result, then fall through to send X |
| 95B7 | 0658 | BVS tube_osbyte_send_x ; ALWAYS branch |
| 95B9 | 065A | .tube_osword |
| JSR tube_read_r2 ; Read OSWORD number from R2 | ||
| 95BC | 065D | TAY ; Save OSWORD number in Y |
| 95BD | 065E | .tube_osword_read←1← 0661 BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 95C0 | 0661 | BPL tube_osword_read ; Not ready: keep polling |
| 95C2 | 0663 | LDX tube_data_register_2 ; Read param block length from R2 |
| 95C5 | 0666 | DEX ; DEX: length 0 means no params to read |
| 95C6 | 0667 | BMI skip_param_read ; No params (length=0): skip read loop |
| 95C8 | 0669 | .tube_osword_read_lp←2← 066C BPL← 0675 BPL |
| BIT tube_status_register_2 ; Poll R2 status for data ready | ||
| 95CB | 066C | BPL tube_osword_read_lp ; Not ready: keep polling |
| 95CD | 066E | LDA tube_data_register_2 ; Read param byte from R2 |
| 95D0 | 0671 | STA l0128,x ; Store param bytes into block at &0128 |
| 95D3 | 0674 | DEX ; Next param byte (descending) |
| 95D4 | 0675 | BPL tube_osword_read_lp ; Loop until all params read |
| 95D6 | 0677 | TYA ; Restore OSWORD number from Y |
| 95D7 | 0678 | .skip_param_read←1← 0667 BMI |
| LDX #<(l0128) ; XY=&0128: param block address for OSWORD | ||
| 95D9 | 067A | LDY #>(l0128) ; Y=&01: param block at &0128 |
| 95DB | 067C | JSR osword ; Execute OSWORD with XY=&0128 |
| 95DE | 067F | LDA #&ff ; A=&FF: result marker for co-processor |
| 95E0 | 0681 | JSR tube_send_r2 ; Send result marker via R2 |
| 95E3 | 0684 | .poll_r2_osword_result←1← 0687 BPL |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95E6 | 0687 | BPL poll_r2_osword_result ; Not ready: keep polling |
| 95E8 | 0689 | LDX tube_data_register_2 ; Read result block length from R2 |
| 95EB | 068C | DEX ; Decrement result byte counter |
| 95EC | 068D | BMI tube_return_main ; No results to send: return to main loop |
| 95EE | 068F | .tube_osword_write←1← 069B BPL |
| LDY l0128,x ; Send result block bytes from &0128 via R2 | ||
| 95F1 | 0692 | .tube_osword_write_lp←1← 0695 BVC |
| BIT tube_status_register_2 ; Poll R2 status for ready | ||
| 95F4 | 0695 | BVC tube_osword_write_lp ; Not ready: keep polling |
| 95F6 | 0697 | STY tube_data_register_2 ; Send result byte via R2 |
| 95F9 | 069A | DEX ; Next result byte (descending) |
| 95FA | 069B | BPL tube_osword_write ; Loop until all results sent |
| 95FC | 069D | .tube_return_main←1← 068D BMI |
| JMP tube_main_loop ; Return to main event loop | ||
| 95FF | 06A0 | .tube_osword_rdln |
| LDX #4 ; X=4: read 5-byte RDLN ctrl block | ||
| 9601 | 06A2 | .read_rdln_ctrl_block←1← 06A8 BPL |
| JSR tube_read_r2 ; Read control block byte from R2 | ||
| 9604 | 06A5 | STA zp_ptr_lo,x ; Store in zero page params |
| 9606 | 06A7 | DEX ; Next byte (descending) |
| 9607 | 06A8 | BPL read_rdln_ctrl_block ; Loop until all 5 bytes read |
| 9609 | 06AA | INX ; X=0 after loop, A=0 for OSWORD 0 (read line) |
| 960A | 06AB | LDY #0 ; Y=0 for OSWORD 0 |
| 960C | 06AD | TXA ; A=0: OSWORD 0 (read line) |
| 960D | 06AE | JSR osword ; Read input line from keyboard |
| 9610 | 06B1 | BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed |
| 9612 | 06B3 | LDA #&ff ; &FF = escape/error signal to co-processor |
| 9614 | 06B5 | JMP tube_reply_byte ; Escape: send &FF error to co-processor |
| 9617 | 06B8 | .tube_rdln_send_line←1← 06B1 BCC |
| LDX #0 ; X=0: start of input buffer at &0700 | ||
| 9619 | 06BA | LDA #&7f ; &7F = line read successfully |
| 961B | 06BC | JSR tube_send_r2 ; Send &7F (success) to co-processor |
| 961E | 06BF | .tube_rdln_send_loop←1← 06C8 BNE |
| LDA l0700,x ; Load char from input buffer | ||
| 9621 | 06C2 | .tube_rdln_send_byte |
| JSR tube_send_r2 ; Send char to co-processor | ||
| 9624 | 06C5 | INX ; Next character |
| 9625 | 06C6 | CMP #&0d ; Check for CR terminator |
| 9627 | 06C8 | BNE tube_rdln_send_loop ; Loop until CR terminator sent |
| 9629 | 06CA | JMP tube_main_loop ; Return to main event loop |
| 962C | 06CD | .tube_send_r2←17← 0020 JSR← 0026 JSR← 002C JSR← 04E9 JSR← 051F JSR← 0562 JSR← 0579 JSR← 05A1 JSR← 05A8 JSR← 05F3 JSR← 05FA JSR← 061A JSR← 064D JSR← 0681 JSR← 06BC JSR← 06C2 JSR← 06D0 BVC |
| BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready) | ||
| 962F | 06D0 | BVC tube_send_r2 ; Not ready: keep polling |
| 9631 | 06D2 | STA tube_data_register_2 ; Write A to Tube R2 data register |
| 9634 | 06D5 | RTS ; Return to caller |
| 9635 | 06D6 | .tube_send_r4←5← 0018 JSR← 042A JSR← 0432 JSR← 0438 JSR← 06D9 BVC |
| BIT tube_status_register_4_and_cpu_control ; Poll R4 status (bit 6 = ready) | ||
| 9638 | 06D9 | BVC tube_send_r4 ; Not ready: keep polling |
| 963A | 06DB | STA tube_data_register_4 ; Write A to Tube R4 data register |
| 963D | 06DE | RTS ; Return to caller |
| 963E | 06DF | .tube_escape_check←1← 0403 JMP |
| LDA escape_flag ; Check OS escape flag at &FF | ||
| 9640 | 06E1 | SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7 |
| 9641 | 06E2 | ROR ; ROR: shift escape bit 7 to carry |
| 9642 | 06E3 | BMI tube_send_r1 ; Escape set: forward to co-processor via R1 |
| 9644 | 06E5 | .tube_event_handler |
| PHA ; EVNTV: forward event A, Y, X to co-processor | ||
| 9645 | 06E6 | LDA #0 ; Send &00 prefix (event notification) |
| 9647 | 06E8 | JSR tube_send_r1 ; Send zero prefix via R1 |
| 964A | 06EB | TYA ; Y value for event |
| 964B | 06EC | JSR tube_send_r1 ; Send Y via R1 |
| 964E | 06EF | TXA ; X value for event |
| 964F | 06F0 | JSR tube_send_r1 ; Send X via R1 |
| 9652 | 06F3 | PLA ; Restore A (event type) |
| 9653 | 06F4 | .tube_send_r1←5← 06E3 BMI← 06E8 JSR← 06EC JSR← 06F0 JSR← 06F7 BVC |
| BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready) | ||
| 9656 | 06F7 | BVC tube_send_r1 ; Not ready: keep polling |
| 9658 | 06F9 | STA tube_data_register_1 ; Write A to Tube R1 data register |
| 965B | 06FC | RTS ; Return to caller |
| 965C | 06FD | EQUS " NE" |
| 965F | EQUB &00 | |
| 9660 | .trampoline_tx_setup←2← 868C JSR← 8ED7 JMP | |
| JMP tx_begin ; Trampoline: forward to tx_begin | ||
| 9663 | .trampoline_adlc_init←1← 830D JSR | |
| JMP adlc_init ; Trampoline: forward to adlc_init | ||
| 9666 | .svc_12_nmi_release | |
| JMP wait_nmi_ready ; Trampoline: forward to NMI release | ||
| 9669 | .svc_11_nmi_claim | |
| JMP restore_econet_state ; Trampoline: forward to NMI claim | ||
| 966C | .svc_5_unknown_irq | |
| JMP check_sr_irq ; Trampoline: forward to IRQ handler | ||
ADLC initialisationReads station ID (INTOFF side effect), performs full ADLC reset, checks for Tube presence (OSBYTE &EA), then falls through to adlc_init_workspace. |
|
| 966F | .adlc_init←1← 9663 JMP |
| BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs | |
| 9672 | JSR adlc_full_reset ; Full ADLC hardware reset |
| 9675 | LDA #osbyte_read_tube_presence ; OSBYTE &EA: check Tube co-processor |
| 9677 | LDX #0 ; X=0 for OSBYTE |
| 9679 | LDY #&ff ; Y=&FF for OSBYTE |
| 967B | JSR osbyte ; Read Tube present flag |
| 967E | STX tx_in_progress ; X=value of Tube present flag |
| 9681 | LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request |
| 9683 | LDX #&0c ; X=&0C: NMI claim service |
| 9685 | LDY #&ff ; Y=&FF: pass to adlc_init_workspace |
| fall through ↓ | |
Initialise NMI workspaceNew in 3.35D: issues OSBYTE &8F with X=&0C (NMI claim service request) before copying the NMI shim. Sub-entry at &968A skips the service request for quick re-init. Then copies 32 bytes of NMI shim from ROM (&9FD9) 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). |
|
| 9687 | .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. |
|
| 968A | .init_nmi_workspace←1← 96E3 JMP |
| LDY #&20 ; Copy 32 bytes of NMI shim from ROM to &0D00 | |
| 968C | .copy_nmi_shim←1← 9693 BNE |
| LDA nmi_shim_rom_src,y ; Read byte from NMI shim ROM source | |
| 968F | STA l0cff,y ; Write to NMI shim RAM at &0D00 |
| 9692 | DEY ; Next byte (descending) |
| 9693 | BNE copy_nmi_shim ; Loop until all 32 bytes copied |
| 9695 | LDA romsel_copy ; Patch current ROM bank into NMI shim |
| 9697 | STA nmi_shim_07 ; Self-modifying code: ROM bank at &0D07 |
| 969A | LDA #&80 ; &80 = Econet initialised |
| 969C | STA tx_clear_flag ; Mark TX as complete (ready) |
| 969F | STA econet_init_flag ; Mark Econet as initialised |
| 96A2 | LDA station_id_disable_net_nmis ; Read station ID (&FE18 = INTOFF side effect) |
| 96A5 | STA tx_src_stn ; Store our station ID in TX scout |
| 96A8 | LDA #0 ; A=0: clear source network |
| 96AA | STA tx_src_net ; Clear TX source network byte |
| 96AD | BIT video_ula_control ; INTON: re-enable NMIs (&FE20 read side effect) |
| 96B0 | RTS ; Return to caller |
Wait for NMI subsystem ready and save Econet stateClears the TX complete flag, then polls econet_init_flag until the NMI subsystem reports initialised. Validates the NMI jump vector points to the expected handler (&9700) by polling nmi_jmp_lo/hi in a tight loop. Once validated, disables NMIs via INTOFF and falls through to save_econet_state. Called from save_vdu_state during VDU state preservation. |
|
| 96B1 | .wait_nmi_ready←1← 9666 JMP |
| LDA #0 ; A=0: clear TX complete flag | |
| 96B3 | STA tx_clear_flag ; Clear TX complete flag |
| 96B6 | .poll_nmi_ready |
| BIT econet_init_flag ; Poll Econet init status | |
| 96B9 | BPL jmp_rx_listen ; Not initialised: skip to RX listen |
| 96BB | STA econet_init_flag ; Clear Econet init flag |
| 96BE | .nmi_vec_lo_match←2← 96C3 BNE← 96CA BNE |
| LDA nmi_jmp_lo ; Load NMI vector low byte | |
| 96C1 | CMP #0 ; Check if low byte is expected value |
| 96C3 | BNE nmi_vec_lo_match ; Mismatch: keep polling |
| 96C5 | LDA nmi_jmp_hi ; Load NMI vector high byte |
| 96C8 | CMP #&97 ; Check if high byte is &97 |
| 96CA | BNE nmi_vec_lo_match ; Mismatch: keep polling |
| 96CC | BIT station_id_disable_net_nmis ; BIT INTOFF: disable NMIs |
| fall through ↓ | |
Save Econet state to RX control blockStores rx_status_flags, protection_mask, and tx_in_progress to (net_rx_ptr) at offsets 8-10. INTOFF side effect on entry. |
|
| 96CF | .save_econet_state |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs | |
| 96D2 | LDY #8 ; Y=8: RXCB offset for TX status |
| 96D4 | LDA tx_in_progress ; Load current TX status flag |
| 96D7 | STA (net_rx_ptr),y ; Save TX status in RXCB |
| 96D9 | .jmp_rx_listen←1← 96B9 BPL |
| JMP adlc_rx_listen ; Enter RX listen mode | |
Restore Econet TX state and re-enter RX listenLoads the saved tx_in_progress flag from RXCB offset 8 (previously stored by save_econet_state) and restores it. Then jumps to init_nmi_workspace to re-initialise the NMI handler and return to idle RX listen mode. Called from save_vdu_state during VDU state restoration. |
|
| 96DC | .restore_econet_state←1← 9669 JMP |
| LDY #8 ; Y=8: RXCB offset for TX status | |
| 96DE | LDA (net_rx_ptr),y ; Load saved TX status from RXCB |
| 96E0 | STA tx_in_progress ; Restore TX status flag |
| 96E3 | .enter_rx_listen |
| JMP init_nmi_workspace ; Re-enter idle RX listen mode | |
ADLC full resetAborts all activity and returns to idle RX listen mode. |
|
| 96E6 | .adlc_full_reset←3← 9672 JSR← 9748 JSR← 989E JSR |
| LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set) | |
| 96E8 | STA econet_control1_or_status1 ; Write CR1: full reset |
| 96EB | LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding |
| 96ED | STA econet_data_terminate_frame ; Write CR4 via ADLC reg 3 (AC=1) |
| 96F0 | LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR |
| 96F2 | STA econet_control23_or_status2 ; Write CR3=0: clear loop-back/AEX/DTR |
| fall through ↓ | |
Enter RX listen modeTX held in reset, RX active with interrupts. Clears all status. |
|
| 96F5 | .adlc_rx_listen←2← 96D9 JMP← 9A56 JSR |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled) | |
| 96F7 | STA econet_control1_or_status1 ; Write CR1: RIE | TX_RESET |
| 96FA | LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE |
| 96FC | STA econet_control23_or_status2 ; Write CR2: listen mode config |
| 96FF | 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). |
|
| 9700 | .nmi_rx_scout←1← 9FE5 JMP |
| LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present) | |
| 9702 | BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set |
| 9705 | BEQ scout_error ; AP not set, no incoming data -- check for errors |
| 9707 | LDA econet_data_continue_frame ; Read first RX byte (destination station address) |
| 970A | CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs) |
| 970D | BEQ accept_frame ; Match -- accept frame |
| 970F | CMP #&ff ; Check for broadcast address (&FF) |
| 9711 | BNE scout_reject ; Neither our address nor broadcast -- reject frame |
| 9713 | LDA #&40 ; Flag &40 = broadcast frame |
| 9715 | STA tx_flags ; Store broadcast flag in TX flags |
| 9718 | .accept_frame←1← 970D BEQ |
| LDA #&1f ; Install next NMI handler at &971F (RX scout net byte) | |
| 971A | LDY #&97 ; High byte of scout net handler |
| 971C | JMP set_nmi_vector ; Install next handler and RTI |
RX scout second byte handlerReads the second byte of an incoming scout (destination network). Checks for network match: 0 = local network (accept), &FF = broadcast (accept and flag), anything else = reject. Installs the scout data reading loop handler at &9751. |
|
| 971F | .nmi_rx_scout_net |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 9722 | BPL scout_error ; No RDA -- check errors |
| 9724 | LDA econet_data_continue_frame ; Read destination network byte |
| 9727 | BEQ accept_local_net ; Network = 0 -- local network, accept |
| 9729 | EOR #&ff ; EOR &FF: test if network = &FF (broadcast) |
| 972B | BEQ accept_scout_net ; Broadcast network -- accept |
| 972D | .scout_reject←1← 9711 BNE |
| LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE | |
| 972F | STA econet_control1_or_status1 ; Write CR1 to discontinue RX |
| 9732 | JMP install_rx_scout_handler ; Return to idle scout listening |
| 9735 | .accept_local_net←1← 9727 BEQ |
| STA tx_flags ; Network = 0 (local): clear tx_flags | |
| 9738 | .accept_scout_net←1← 972B BEQ |
| STA port_buf_len ; Store Y offset for scout data buffer | |
| 973A | LDA #&51 ; Install scout data reading loop at &9751 |
| 973C | LDY #&97 ; High byte of scout data handler |
| 973E | JMP set_nmi_vector ; Install scout data loop and RTI |
Scout error/discard handlerReached when the scout data loop sees no RDA (BPL at &9756) or when scout completion finds unexpected SR2 state. Reads SR2 and tests AP|RDA bits. If non-zero, performs full ADLC reset and discards. If zero (clean end), discards via scout_discard. This path is a common landing for any unexpected ADLC state during scout reception. |
|
| 9741 | .scout_error←5← 9705 BEQ← 9722 BPL← 9756 BPL← 978A BEQ← 978C BPL |
| LDA econet_control23_or_status2 ; Read SR2 | |
| 9744 | AND #&81 ; Test AP (b0) | RDA (b7) |
| 9746 | BEQ scout_discard ; Neither set -- clean end, discard via &974E |
| 9748 | JSR adlc_full_reset ; Unexpected data/status: full ADLC reset |
| 974B | JMP install_rx_scout_handler ; Discard and return to idle |
| 974E | .scout_discard←1← 9746 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: - SR2 read at entry (&9753) - No RDA (BPL) -> error (&9741) - RDA set (BMI) -> read byte - After first byte (&975F): full SR2 tested - SR2 non-zero (BNE) -> scout completion (&977B) 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 (&9773): re-test SR2 for next pair - RDA set (BMI) -> loop back to &9758 - Neither set -> RTI, wait for next NMI The loop ends at Y=&0C (12 bytes max in scout buffer). |
|
| 9751 | .scout_data_loop |
| LDY port_buf_len ; Y = buffer offset | |
| 9753 | LDA econet_control23_or_status2 ; Read SR2 |
| 9756 | .scout_loop_rda←1← 9776 BNE |
| BPL scout_error ; No RDA -- error handler &9741 | |
| 9758 | LDA econet_data_continue_frame ; Read data byte from RX FIFO |
| 975B | STA rx_src_stn,y ; Store at &0D3D+Y (scout buffer) |
| 975E | INY ; Advance buffer index |
| 975F | LDA econet_control23_or_status2 ; Read SR2 again (FV detection point) |
| 9762 | BMI scout_loop_second ; RDA set -- more data, read second byte |
| 9764 | BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion |
| 9766 | .scout_loop_second←1← 9762 BMI |
| LDA econet_data_continue_frame ; Read second byte of pair | |
| 9769 | STA rx_src_stn,y ; Store at &0D3D+Y |
| 976C | INY ; Advance and check buffer limit |
| 976D | CPY #&0c ; Copied all 12 scout bytes? |
| 976F | BEQ scout_complete ; Buffer full (Y=12) -- force completion |
| 9771 | STY port_buf_len ; Save final buffer offset |
| 9773 | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 9776 | BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes |
| 9778 | 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 &9741 (not a valid frame end) - FV set, no RDA (BPL) -> error &9741 (missing last byte) - FV set, RDA set -> read last byte, process scout After reading the last byte, the complete scout buffer (&0D3D-&0D48) contains: src_stn, src_net, ctrl, port [, extra_data...]. The port byte at &0D40 determines further processing: - Port = 0 -> immediate operation (&9A59) - Port non-zero -> check if it matches an open receive block |
|
| 977B | .scout_complete←2← 9764 BNE← 976F BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 977D | STA econet_control1_or_status1 ; Write CR1 |
| 9780 | LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV |
| 9782 | STA econet_control23_or_status2 ; Write CR2 |
| 9785 | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 9787 | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 978A | BEQ scout_error ; No FV -- not a valid frame end, error |
| 978C | BPL scout_error ; FV set but no RDA -- missing last byte, error |
| 978E | LDA econet_data_continue_frame ; Read last byte from RX FIFO |
| 9791 | STA rx_src_stn,y ; Store last byte at &0D3D+Y |
| 9794 | LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK) |
| 9796 | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 9799 | LDA rx_port ; Check port byte: 0 = immediate op, non-zero = data transfer |
| 979C | BNE scout_match_port ; Port non-zero -- look for matching receive block |
| 979E | JMP immediate_op ; Port = 0 -- immediate operation handler |
| 97A1 | .scout_no_match←3← 97EC BNE← 97F1 BVC← 9823 JMP |
| JMP nmi_error_dispatch ; Port = 0 -- immediate operation handler | |
| 97A4 | .scout_match_port←1← 979C BNE |
| BIT tx_flags ; Check if broadcast (bit6 of tx_flags) | |
| 97A7 | BVC scan_port_list ; Not broadcast -- skip CR2 setup |
| 97A9 | LDA #7 ; CR2=&07: broadcast prep |
| 97AB | STA econet_control23_or_status2 ; Write CR2: broadcast frame prep |
| 97AE | .scan_port_list←1← 97A7 BVC |
| BIT rx_flags ; Check if RX port list active (bit7) | |
| 97B1 | BPL scout_network_match ; No active ports -- try NFS workspace |
| 97B3 | LDA #&c0 ; Start scanning port list at page &C0 |
| 97B5 | .scan_nfs_port_list |
| STA port_ws_offset ; Store page to workspace pointer low | |
| 97B7 | LDA #0 ; A=0: no NFS workspace offset yet |
| 97B9 | STA rx_buf_offset ; Clear NFS workspace search flag |
| 97BB | .check_port_slot←1← 97E8 BCC |
| LDY #0 ; Y=0: read control byte from start of slot | |
| 97BD | .scout_ctrl_check←1← 97FB BEQ |
| LDA (port_ws_offset),y ; Read port control byte from slot | |
| 97BF | BEQ scout_station_check ; Zero = end of port list, no match |
| 97C1 | CMP #&7f ; &7F = any-port wildcard |
| 97C3 | BNE next_port_slot ; Not wildcard -- check specific port match |
| 97C5 | INY ; Y=1: advance to port byte in slot |
| 97C6 | LDA (port_ws_offset),y ; Read port number from slot (offset 1) |
| 97C8 | BEQ check_station_filter ; Zero port in slot = match any port |
| 97CA | CMP rx_port ; Check if port matches this slot |
| 97CD | BNE next_port_slot ; Port mismatch -- try next slot |
| 97CF | .check_station_filter←1← 97C8 BEQ |
| INY ; Y=2: advance to station byte | |
| 97D0 | LDA (port_ws_offset),y ; Read station filter from slot (offset 2) |
| 97D2 | BEQ scout_port_match ; Zero station = match any station, accept |
| 97D4 | CMP rx_src_stn ; Check if source station matches |
| 97D7 | BNE next_port_slot ; Station mismatch -- try next slot |
| 97D9 | .scout_port_match←1← 97D2 BEQ |
| INY ; Y=3: advance to network byte | |
| 97DA | LDA (port_ws_offset),y ; Read network filter from slot (offset 3) |
| 97DC | CMP rx_src_net ; Check if source network matches |
| 97DF | BEQ scout_accept ; Network matches or zero = accept |
| 97E1 | .next_port_slot←3← 97C3 BNE← 97CD BNE← 97D7 BNE |
| LDA port_ws_offset ; Check if NFS workspace search pending | |
| 97E3 | CLC ; CLC for 12-byte slot advance |
| 97E4 | ADC #&0c ; Advance to next 12-byte port slot |
| 97E6 | STA port_ws_offset ; Update workspace pointer to next slot |
| 97E8 | BCC check_port_slot ; Always branches (page &C0 won't overflow) |
| 97EA | .scout_station_check←1← 97BF BEQ |
| LDA rx_buf_offset ; Check if NFS workspace already searched | |
| 97EC | BNE scout_no_match ; Already searched: no match found |
| 97EE | .scout_network_match←1← 97B1 BPL |
| BIT rx_flags ; Try NFS workspace if paged list exhausted | |
| 97F1 | BVC scout_no_match ; No NFS workspace RX (bit6 clear) -- discard |
| 97F3 | LDA nfs_workspace_hi ; Get NFS workspace page number |
| 97F5 | STA rx_buf_offset ; Mark NFS workspace as search target |
| 97F7 | LDY #0 ; Y=0: start at offset 0 in workspace |
| 97F9 | STY port_ws_offset ; Reset slot pointer to start |
| 97FB | BEQ scout_ctrl_check ; ALWAYS branch |
| 97FD | .scout_accept←1← 97DF BEQ |
| BIT tx_flags ; Check broadcast flag (bit 6) | |
| 9800 | BVC ack_scout_match ; Not broadcast: ACK and set up RX |
| 9802 | JMP copy_scout_fields ; Broadcast: copy scout fields directly |
| 9805 | .ack_scout_match←2← 9800 BVC← 9ADB JMP |
| LDA #3 ; Match found: set scout_status = 3 | |
| 9807 | STA scout_status ; Record match for completion handler |
| 980A | LDA nmi_tx_block ; Save current TX block ptr (low) |
| 980C | PHA ; Push TX block low on stack |
| 980D | LDA nmi_tx_block_hi ; Save current TX block ptr (high) |
| 980F | PHA ; Push TX block high on stack |
| 9810 | LDA port_ws_offset ; Use port slot as temp RXCB ptr (lo) |
| 9812 | STA nmi_tx_block ; Set RXCB low for tx_calc_transfer |
| 9814 | LDA rx_buf_offset ; Use workspace page as temp RXCB (hi) |
| 9816 | STA nmi_tx_block_hi ; Set RXCB high for tx_calc_transfer |
| 9818 | JSR tx_calc_transfer ; Calculate transfer parameters |
| 981B | PLA ; Restore original TX block (high) |
| 981C | STA nmi_tx_block_hi ; Restore TX block ptr (high) |
| 981E | PLA ; Restore original TX block (low) |
| 981F | STA nmi_tx_block ; Restore TX block ptr (low) |
| 9821 | BCS send_data_rx_ack ; Transfer OK: send data ACK |
| 9823 | JMP scout_no_match ; Broadcast: different completion path |
| 9826 | .send_data_rx_ack←2← 9821 BCS← 9AD0 JMP |
| LDA #&44 ; CR1=&44: RX_RESET | TIE | |
| 9828 | STA econet_control1_or_status1 ; Write CR1: TX mode for ACK |
| 982B | LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE |
| 982D | STA econet_control23_or_status2 ; Write CR2: enable TX with PSE |
| 9830 | LDA #&37 ; Install data_rx_setup at &97DC |
| 9832 | LDY #&98 ; High byte of data_rx_setup handler |
| 9834 | JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI |
| 9837 | .data_rx_setup |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame) | |
| 9839 | STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame |
| 983C | LDA #&43 ; Install nmi_data_rx at &9843 |
| 983E | LDY #&98 ; High byte of nmi_data_rx handler |
| 9840 | JMP set_nmi_vector ; Install nmi_data_rx and return from NMI |
Data frame RX handler (four-way handshake)Receives the data frame after the scout ACK has been sent. First checks AP (Address Present) for the start of the data frame. Reads and validates the first two address bytes (dest_stn, dest_net) against our station address, then installs continuation handlers to read the remaining data payload into the open port buffer. Handler chain: &9843 (AP+addr check) -> &9859 (net=0 check) -> &986F (skip ctrl+port) -> &98A4 (bulk data read) -> &98D8 (completion) |
|
| 9843 | .nmi_data_rx |
| LDA #1 ; A=&01: mask for AP (Address Present) | |
| 9845 | BIT econet_control23_or_status2 ; BIT SR2: test AP bit |
| 9848 | BEQ nmi_error_dispatch ; No AP: wrong frame or error |
| 984A | LDA econet_data_continue_frame ; Read first byte (dest station) |
| 984D | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF) |
| 9850 | BNE nmi_error_dispatch ; Not for us: error path |
| 9852 | LDA #&59 ; Install net check handler at &9859 |
| 9854 | LDY #&98 ; High byte of nmi_data_rx handler |
| 9856 | JMP set_nmi_vector ; Set NMI vector via RAM shim |
| 9859 | .nmi_data_rx_net |
| BIT econet_control23_or_status2 ; Validate source network = 0 | |
| 985C | BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error |
| 985E | LDA econet_data_continue_frame ; Read dest network byte |
| 9861 | BNE nmi_error_dispatch ; Network != 0: wrong network -- error |
| 9863 | LDA #&6f ; Install skip handler at &986F |
| 9865 | LDY #&98 ; High byte of &986F handler |
| 9867 | BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting |
| 986A | BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI |
| 986C | JMP set_nmi_vector ; Install handler and return via RTI |
| 986F | .nmi_data_rx_skip←1← 986A BMI |
| BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout) | |
| 9872 | BPL nmi_error_dispatch ; SR2 bit7 clear: error |
| 9874 | LDA econet_data_continue_frame ; Discard control byte |
| 9877 | LDA econet_data_continue_frame ; Discard port byte |
| fall through ↓ | |
| 987A | .install_data_rx_handler←1← 9F3E JMP |
| LDA #2 ; A=2: Tube transfer flag mask | |
| 987C | BIT tx_flags ; Check if Tube transfer active |
| 987F | BNE install_tube_rx ; Tube active: use Tube RX path |
| 9881 | LDA #&a4 ; Install bulk read at &9843 |
| 9883 | LDY #&98 ; High byte of &9843 handler |
| 9885 | BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting? |
| 9888 | BMI nmi_data_rx_bulk ; Yes: enter bulk read directly |
| 988A | JMP set_nmi_vector ; No: install handler and RTI |
| 988D | .install_tube_rx←1← 987F BNE |
| LDA #1 ; Tube: install Tube RX at &9901 | |
| 988F | LDY #&99 ; High byte of &9901 handler |
| 9891 | 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). |
|
| 9894 | .nmi_error_dispatch←12← 97A1 JMP← 9848 BEQ← 9850 BNE← 985C BPL← 9861 BNE← 9872 BPL← 98B7 BEQ← 98E9 BEQ← 98EF BEQ← 993A JMP← 99C2 JMP← 9AA2 JMP |
| LDA tx_flags ; Check tx_flags for error path | |
| 9897 | BPL rx_error ; Bit7 clear: RX error path |
| 9899 | LDA #&41 ; A=&41: 'not listening' error |
| 989B | JMP tx_store_result ; Bit7 set: TX result = not listening |
| 989E | .rx_error←1← 9897 BPL |
| .rx_error_reset←1← 9897 BPL | |
| JSR adlc_full_reset ; Full ADLC reset on RX error | |
| 98A1 | 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 &98D8. SR2 = 0 -> RTI, wait for next NMI to continue. |
|
| 98A4 | .nmi_data_rx_bulk←1← 9888 BMI |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 98A6 | LDA econet_control23_or_status2 ; Read SR2 for next pair |
| 98A9 | .data_rx_loop←1← 98D3 BNE |
| BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV) | |
| 98AB | LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO |
| 98AE | STA (open_port_buf),y ; Store byte to buffer |
| 98B0 | INY ; Advance buffer offset |
| 98B1 | BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing |
| 98B3 | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 98B5 | DEC port_buf_len_hi ; Decrement remaining page count |
| 98B7 | BEQ nmi_error_dispatch ; No pages left: handle as complete |
| 98B9 | .read_sr2_between_pairs←1← 98B1 BNE |
| LDA econet_control23_or_status2 ; Read SR2 between byte pairs | |
| 98BC | BMI read_second_rx_byte ; SR2 bit7 set: more data available |
| 98BE | BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done |
| 98C0 | .read_second_rx_byte←1← 98BC BMI |
| LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO | |
| 98C3 | STA (open_port_buf),y ; Store byte to buffer |
| 98C5 | INY ; Advance buffer offset |
| 98C6 | STY port_buf_len ; Save updated buffer position |
| 98C8 | BNE check_sr2_loop_again ; Y != 0: no page boundary crossing |
| 98CA | INC open_port_buf_hi ; Crossed page: increment buffer high byte |
| 98CC | DEC port_buf_len_hi ; Decrement remaining page count |
| 98CE | BEQ data_rx_complete ; No pages left: frame complete |
| 98D0 | .check_sr2_loop_again←1← 98C8 BNE |
| LDA econet_control23_or_status2 ; Read SR2 for next iteration | |
| 98D3 | BNE data_rx_loop ; SR2 non-zero: more data, loop back |
| 98D5 | JMP nmi_rti ; SR2=0: no more data yet, wait for NMI |
Data frame completion |
|
| 98D8 | .data_rx_complete←3← 98A9 BPL← 98BE BNE← 98CE BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 98DA | STA econet_control1_or_status1 ; Write CR1 |
| 98DD | LDA #&84 ; CR2=&84: disable PSE for individual bit testing |
| 98DF | STA econet_control23_or_status2 ; Write CR2 |
| 98E2 | STY port_buf_len ; Save Y (byte count from data RX loop) |
| 98E4 | LDA #2 ; A=&02: FV mask |
| 98E6 | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 98E9 | BEQ nmi_error_dispatch ; No FV -- error |
| 98EB | BPL send_ack ; FV set, no RDA -- proceed to ACK |
| 98ED | LDA port_buf_len_hi ; Check if buffer space remains |
| 98EF | .read_last_rx_byte |
| BEQ nmi_error_dispatch ; No buffer space: error/discard frame | |
| 98F1 | LDA econet_data_continue_frame ; FV+RDA: read and store last data byte |
| 98F4 | LDY port_buf_len ; Y = current buffer write offset |
| 98F6 | STA (open_port_buf),y ; Store last byte in port receive buffer |
| 98F8 | INC port_buf_len ; Advance buffer write offset |
| 98FA | BNE send_ack ; No page wrap: proceed to send ACK |
| 98FC | INC open_port_buf_hi ; Page boundary: advance buffer page |
| 98FE | .send_ack←2← 98EB BPL← 98FA BNE |
| JMP ack_tx ; Send ACK frame to complete handshake | |
| 9901 | .nmi_data_rx_tube |
| LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path | |
| 9904 | .rx_tube_data←1← 9935 BNE |
| BPL data_rx_tube_complete ; RDA clear: no more data, frame complete | |
| 9906 | LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO |
| 9909 | INC port_buf_len ; Advance Tube transfer byte count |
| 990B | STA tube_data_register_3 ; Send byte to Tube data register 3 |
| 990E | BNE rx_update_buf ; No overflow: read second byte |
| 9910 | INC port_buf_len_hi ; Carry to transfer count byte 2 |
| 9912 | BNE rx_update_buf ; No overflow: read second byte |
| 9914 | INC open_port_buf ; Carry to transfer count byte 3 |
| 9916 | BNE rx_update_buf ; No overflow: read second byte |
| 9918 | INC open_port_buf_hi ; Carry to transfer count byte 4 |
| 991A | BEQ data_rx_tube_error ; All bytes zero: overflow error |
| 991C | .rx_update_buf←3← 990E BNE← 9912 BNE← 9916 BNE |
| LDA econet_data_continue_frame ; Read second data byte (paired transfer) | |
| 991F | STA tube_data_register_3 ; Send second byte to Tube |
| 9922 | INC port_buf_len ; Advance count after second byte |
| 9924 | BNE rx_check_error ; No overflow: check for more data |
| 9926 | INC port_buf_len_hi ; Carry to count byte 2 |
| 9928 | BNE rx_check_error ; No overflow: check for more data |
| 992A | INC open_port_buf ; Carry to count byte 3 |
| 992C | BNE rx_check_error ; No overflow: check for more data |
| 992E | INC open_port_buf_hi ; Carry to count byte 4 |
| 9930 | BEQ data_rx_tube_complete ; Zero: Tube transfer complete |
| 9932 | .rx_check_error←3← 9924 BNE← 9928 BNE← 992C BNE |
| LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair | |
| 9935 | BNE rx_tube_data ; More data available: continue loop |
| 9937 | JMP nmi_rti ; Return from NMI, wait for data |
| 993A | .data_rx_tube_error←3← 991A BEQ← 994C BEQ← 9958 BEQ |
| JMP nmi_error_dispatch ; Unexpected end: return from NMI | |
| 993D | .data_rx_tube_complete←2← 9904 BPL← 9930 BEQ |
| LDA #0 ; CR1=&00: disable all interrupts | |
| 993F | STA econet_control1_or_status1 ; Write CR1 for individual bit testing |
| 9942 | LDA #&84 ; CR2=&84: disable PSE |
| 9944 | STA econet_control23_or_status2 ; Write CR2: same pattern as main path |
| 9947 | LDA #2 ; A=&02: FV mask for Tube completion |
| 9949 | BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N) |
| 994C | BEQ data_rx_tube_error ; No FV: incomplete frame, error |
| 994E | BPL ack_tx ; FV set, no RDA: proceed to ACK |
| 9950 | LDA port_buf_len ; Check if any buffer was allocated |
| 9952 | ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together |
| 9954 | ORA open_port_buf ; Check buffer low byte |
| 9956 | ORA open_port_buf_hi ; Check buffer high byte |
| 9958 | BEQ data_rx_tube_error ; All zero (null buffer): error |
| 995A | LDA econet_data_continue_frame ; Read extra trailing byte from FIFO |
| 995D | STA rx_extra_byte ; Save extra byte at &0D5D for later use |
| 9960 | LDA #&20 ; Bit5 = extra data byte available flag |
| 9962 | ORA tx_flags ; Set extra byte flag in tx_flags |
| 9965 | 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 (&9F48). 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. |
|
| 9968 | .ack_tx←2← 98FE JMP← 994E BPL |
| LDA tx_flags ; Load TX flags to check ACK type | |
| 996B | BPL ack_tx_configure ; Bit7 clear: normal scout ACK |
| 996D | JMP tx_result_ok ; Jump to TX success result |
| 9970 | .ack_tx_configure←1← 996B BPL |
| LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode) | |
| 9972 | STA econet_control1_or_status1 ; Write CR1: switch to TX mode |
| 9975 | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE |
| 9977 | STA econet_control23_or_status2 ; Write CR2: enable TX with status clear |
| 997A | LDA #&c5 ; Install saved next handler (&99C5 for scout ACK) |
| 997C | LDY #&99 ; High byte of post-ACK handler |
| 997E | .ack_tx_write_dest←2← 9834 JMP← 9B25 JMP |
| STA nmi_next_lo ; Store next handler low byte | |
| 9981 | STY nmi_next_hi ; Store next handler high byte |
| 9984 | LDA rx_src_stn ; Load dest station from RX scout buffer |
| 9987 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 998A | BVC tdra_error ; TDRA not ready -- error |
| 998C | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 998F | LDA rx_src_net ; Write dest network to TX FIFO |
| 9992 | STA econet_data_continue_frame ; Write dest net byte to FIFO |
| 9995 | LDA #&9c ; Install nmi_ack_tx_src at &999C |
| 9997 | LDY #&99 ; High byte of nmi_ack_tx_src |
| 9999 | 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. |
|
| 999C | .nmi_ack_tx_src |
| LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF) | |
| 999F | BIT econet_control1_or_status1 ; BIT SR1: test TDRA |
| 99A2 | BVC tdra_error ; TDRA not ready -- error |
| 99A4 | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 99A7 | LDA #0 ; Write network=0 to TX FIFO |
| 99A9 | STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO |
| 99AC | LDA tx_flags ; Check tx_flags for data phase |
| 99AF | BMI start_data_tx ; bit7 set: start data TX phase |
| 99B1 | LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE |
| 99B3 | STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX |
| 99B6 | LDA nmi_next_lo ; Install saved handler from &0D4B/&0D4C |
| 99B9 | LDY nmi_next_hi ; Load saved next handler high byte |
| 99BC | JMP set_nmi_vector ; Install next NMI handler |
| 99BF | .start_data_tx←1← 99AF BMI |
| JMP data_tx_begin ; Jump to start data TX phase | |
| 99C2 | .tdra_error←2← 998A BVC← 99A2 BVC |
| JMP nmi_error_dispatch ; TDRA error: jump to error handler | |
Post-ACK scout processingCalled after the scout ACK has been transmitted. Processes the received scout data stored in the buffer at &0D3D-&0D48. Checks the port byte (&0D40) against open receive blocks to find a matching listener. If a match is found, sets up the data RX handler chain for the four-way handshake data phase. If no match, discards the frame. |
|
| 99C5 | .post_ack_scout |
| LDA rx_port ; Check port byte from scout | |
| 99C8 | BNE advance_rx_buffer_ptr ; Non-zero port: advance RX buffer |
| 99CA | LDY rx_ctrl ; Load control byte from scout |
| 99CD | CPY #&82 ; Is ctrl &82 (immediate peek)? |
| 99CF | BEQ advance_rx_buffer_ptr ; Yes: advance RX buffer for peek |
| 99D1 | .dispatch_nmi_error |
| JMP imm_op_build_reply ; 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. |
|
| 99D4 | .advance_rx_buffer_ptr←2← 99C8 BNE← 99CF BEQ |
| LDA #2 ; A=2: test bit1 of tx_flags | |
| 99D6 | BIT tx_flags ; BIT tx_flags: check data transfer bit |
| 99D9 | BEQ add_buf_to_base ; Bit1 clear: no transfer -- return |
| 99DB | CLC ; CLC: init carry for 4-byte add |
| 99DC | PHP ; Save carry on stack for loop |
| 99DD | LDY #8 ; Y=8: RXCB high pointer offset |
| 99DF | .add_rxcb_ptr←1← 99EB BCC |
| LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte) | |
| 99E1 | PLP ; Restore carry from stack |
| 99E2 | ADC net_tx_ptr,y ; Add transfer count byte |
| 99E5 | STA (port_ws_offset),y ; Store updated pointer back to RXCB |
| 99E7 | INY ; Next byte |
| 99E8 | PHP ; Save carry for next iteration |
| 99E9 | CPY #&0c ; Done 4 bytes? (Y reaches &0C) |
| 99EB | BCC add_rxcb_ptr ; No: continue adding |
| 99ED | PLP ; Discard final carry |
| 99EE | LDA #&20 ; A=&20: test bit5 of tx_flags |
| 99F0 | BIT tx_flags ; BIT tx_flags: check Tube bit |
| 99F3 | BEQ skip_buf_ptr_update ; No Tube: skip Tube update |
| 99F5 | TXA ; Save X on stack |
| 99F6 | PHA ; Push X |
| 99F7 | LDA #8 ; A=8: offset for Tube address |
| 99F9 | CLC ; CLC for address calculation |
| 99FA | ADC port_ws_offset ; Add workspace base offset |
| 99FC | TAX ; X = address low for Tube claim |
| 99FD | LDY rx_buf_offset ; Y = address high for Tube claim |
| 99FF | LDA #1 ; A=1: Tube claim type (read) |
| 9A01 | JSR tube_addr_claim ; Claim Tube address for transfer |
| 9A04 | LDA rx_extra_byte ; Load extra RX data byte |
| 9A07 | STA tube_data_register_3 ; Send to Tube via R3 |
| 9A0A | PLA ; Restore X from stack |
| 9A0B | TAX ; Transfer to X register |
| 9A0C | LDY #8 ; Y=8: RXCB buffer ptr offset |
| 9A0E | LDA (port_ws_offset),y ; Load current RXCB buffer ptr lo |
| 9A10 | SEC ; SEC for ADC #0 = add carry |
| 9A11 | ADC #0 ; Increment by 1 (Tube extra byte) |
| 9A13 | STA (port_ws_offset),y ; Store updated ptr back to RXCB |
| 9A15 | .jmp_store_rxcb |
| JMP skip_buf_ptr_update ; Other port-0 ops: immediate dispatch | |
| 9A18 | .add_buf_to_base←1← 99D9 BEQ |
| LDA port_buf_len ; Load buffer bytes remaining | |
| 9A1A | CLC ; CLC for address add |
| 9A1B | ADC open_port_buf ; Add to buffer base address |
| 9A1D | BCC store_buf_ptr_lo ; No carry: skip high byte increment |
| 9A1F | .inc_rxcb_buf_hi |
| INC open_port_buf_hi ; Carry: increment buffer high byte | |
| 9A21 | .store_buf_ptr_lo←1← 9A1D BCC |
| LDY #8 ; Y=8: store updated buffer position | |
| 9A23 | .store_rxcb_buf_ptr |
| STA (port_ws_offset),y ; Store updated low byte to RXCB | |
| 9A25 | INY ; Y=9: buffer high byte offset |
| 9A26 | .load_rxcb_buf_hi |
| LDA open_port_buf_hi ; Load updated buffer high byte | |
| 9A28 | .store_rxcb_buf_hi |
| STA (port_ws_offset),y ; Store high byte to RXCB | |
| 9A2A | .skip_buf_ptr_update←3← 99F3 BEQ← 9A15 JMP← 9A6C JMP |
| LDA rx_port ; Check port byte again | |
| 9A2D | BEQ discard_reset_listen ; Port=0: immediate op, discard+listen |
| fall through ↓ | |
Store RXCB completion fields from scout bufferWrites source network, source station, port, and control byte from the scout buffer into the active RXCB. Sets bit 7 of the control byte to mark reception complete. |
|
| 9A2F | .store_rxcb_completion |
| LDA rx_src_net ; Load source network from scout buffer | |
| 9A32 | LDY #3 ; Y=3: RXCB source network offset |
| 9A34 | STA (port_ws_offset),y ; Store source network to RXCB |
| 9A36 | DEY ; Y=2: source station offset Y=&02 |
| 9A37 | LDA rx_src_stn ; Load source station from scout buffer |
| 9A3A | STA (port_ws_offset),y ; Store source station to RXCB |
| 9A3C | DEY ; Y=1: port byte offset Y=&01 |
| 9A3D | LDA rx_port ; Load port byte |
| 9A40 | STA (port_ws_offset),y ; Store port to RXCB |
| 9A42 | DEY ; Y=0: control/flag byte offset Y=&00 |
| 9A43 | LDA rx_ctrl ; Load control byte from scout |
| 9A46 | ORA #&80 ; Set bit7 = reception complete flag |
| 9A48 | STA (port_ws_offset),y ; Store to RXCB (marks CB as complete) |
| fall through ↓ | |
Discard with full ADLC resetPerforms adlc_full_reset (CR1=&C1, reset both TX and RX sections), then falls through to install_rx_scout_handler. Used when the ADLC is in an unexpected state and needs a hard reset before returning to idle listen mode. 5 references — the main error recovery path. |
|
| 9A4A | .discard_reset_listen←4← 98A1 JMP← 9A2D BEQ← 9EA2 JMP← 9F57 JMP |
| LDA #2 ; Tube flag bit 1 AND tx_flags bit 1 | |
| 9A4C | BIT tx_flags ; Test tx_flags for Tube transfer |
| 9A4F | BEQ discard_listen ; No Tube transfer active -- skip release |
| 9A51 | LDA #&82 ; A=&82: Tube release claim type |
| 9A53 | JSR tube_addr_claim ; Release Tube claim before discarding |
| fall through ↓ | |
Discard frame (gentle)Sends RX_DISCONTINUE (CR1=&A2: RIE|RX_DISCONTINUE) to abort the current frame reception without a full reset, then falls through to install_rx_scout_handler. Used for clean rejection of frames that are correctly formatted but not for us (wrong station/network). |
|
| 9A56 | .discard_listen←3← 974E JMP← 9A4F BEQ← 9B5E JMP |
| JSR adlc_rx_listen ; Re-enter idle RX listen mode | |
| fall through ↓ | |
Install RX scout NMI handlerInstalls nmi_rx_scout (&9700) 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. |
|
| 9A59 | .install_rx_scout_handler←2← 9732 JMP← 974B JMP |
| LDA #0 ; Install nmi_rx_scout (&9700) as NMI handler | |
| 9A5B | LDY #&97 ; High byte of nmi_rx_scout |
| 9A5D | JMP set_nmi_vector ; Set NMI vector and return |
| 9A60 | .copy_scout_fields←1← 9802 JMP |
| LDY #4 ; Y=4: start at RX CB offset 4 | |
| 9A62 | .copy_scout_loop←1← 9A6A BNE |
| LDA rx_src_stn,y ; Load scout field (stn/net/ctrl/port) | |
| 9A65 | STA (port_ws_offset),y ; Store to port workspace buffer |
| 9A67 | INY ; Advance buffer pointer |
| 9A68 | CPY #&0c ; All 8 fields copied? |
| 9A6A | BNE copy_scout_loop ; No: continue copy loop |
| 9A6C | JMP skip_buf_ptr_update ; Jump to completion handler |
Immediate operation handler (port = 0)Handles immediate (non-data-transfer) operations received via scout frames with port byte = 0. The control byte (&0D3F) determines the operation type: &81 = PEEK (read memory) &82 = POKE (write memory) &83 = JSR (remote procedure call) &84 = user procedure &85 = OS procedure &86 = HALT &87 = CONTINUE The protection mask (LSTAT at &D63) controls which operations are permitted — each bit enables or disables an operation type. If the operation is not permitted by the mask, it is silently ignored. LSTAT can be read/set via OSWORD &12 sub-functions 4/5. |
|
| 9A6F | .immediate_op←1← 979E JMP |
| LDY rx_ctrl ; Control byte &81-&88 range check | |
| 9A72 | CPY #&81 ; Below &81: not an immediate op |
| 9A74 | BCC imm_op_out_of_range ; Out of range low: jump to discard |
| 9A76 | CPY #&89 ; Above &88: not an immediate op |
| 9A78 | BCS imm_op_out_of_range ; Out of range high: jump to discard |
| 9A7A | CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection |
| 9A7C | BCS imm_op_dispatch ; Ctrl >= &87: dispatch without mask check |
| 9A7E | LDA rx_src_stn ; Load source station number |
| 9A81 | CMP #&f0 ; Station >= &F0? (privileged) |
| 9A83 | BCS imm_op_dispatch ; Privileged: skip protection check |
| 9A85 | TYA ; Convert ctrl byte to 0-based index for mask |
| 9A86 | SEC ; SEC for subtract |
| 9A87 | SBC #&81 ; A = ctrl - &81 (0-based operation index) |
| 9A89 | TAY ; Y = index for mask rotation count |
| 9A8A | LDA prot_status ; Load protection mask from LSTAT |
| 9A8D | .rotate_prot_mask←1← 9A8F BPL |
| ROR ; Rotate mask right by control byte index | |
| 9A8E | DEY ; Decrement rotation counter |
| 9A8F | BPL rotate_prot_mask ; Loop until bit aligned |
| 9A91 | BCC imm_op_dispatch ; Carry clear: operation permitted |
| 9A93 | JMP imm_op_discard ; Operation blocked by LSTAT mask |
| 9A96 | .imm_op_dispatch←3← 9A7C BCS← 9A83 BCS← 9A91 BCC |
| LDY rx_ctrl ; Reload ctrl byte for dispatch table | |
| 9A99 | LDA rx_port_operand,y ; Look up handler address high byte |
| 9A9C | PHA ; Push handler address high |
| 9A9D | LDA store_rxcb_byte,y ; Load handler low byte from jump table |
| 9AA0 | PHA ; Push handler address low |
| 9AA1 | RTS ; RTS dispatches to handler |
| 9AA2 | .imm_op_out_of_range←2← 9A74 BCC← 9A78 BCS |
| JMP nmi_error_dispatch ; Jump to discard handler | |
| 9AA5 | EQUB <(rx_imm_peek-1) |
| 9AA6 | EQUB <(rx_imm_poke-1) |
| 9AA7 | EQUB <(rx_imm_exec-1) |
| 9AA8 | EQUB <(rx_imm_exec-1) |
| 9AA9 | EQUB <(rx_imm_exec-1) |
| 9AAA | EQUB <(rx_imm_halt_cont-1) |
| 9AAB | EQUB <(rx_imm_halt_cont-1) |
| 9AAC | EQUB <(rx_imm_machine_type-1) |
| 9AAD | EQUB >(rx_imm_peek-1) |
| 9AAE | EQUB >(rx_imm_poke-1) |
| 9AAF | EQUB >(rx_imm_exec-1) |
| 9AB0 | EQUB >(rx_imm_exec-1) |
| 9AB1 | EQUB >(rx_imm_exec-1) |
| 9AB2 | EQUB >(rx_imm_halt_cont-1) |
| 9AB3 | EQUB >(rx_imm_halt_cont-1) |
| 9AB4 | EQUB >(rx_imm_machine_type-1) |
RX immediate: JSR/UserProc/OSProc setupSets up the port buffer to receive remote procedure data. Copies the 4-byte remote address from rx_remote_addr into the execution address workspace at &0D58, then jumps to the common receive path at c9826. Used for operation types &83 (JSR), &84 (UserProc), and &85 (OSProc). |
|
| 9AB5 | .rx_imm_exec |
| LDA #0 ; Buffer start lo = &00 | |
| 9AB7 | STA open_port_buf ; Set port buffer lo |
| 9AB9 | LDA #&82 ; Buffer length lo = &82 |
| 9ABB | STA port_buf_len ; Set buffer length lo |
| 9ABD | LDA #1 ; Buffer length hi = 1 |
| 9ABF | STA port_buf_len_hi ; Set buffer length hi |
| 9AC1 | LDA net_rx_ptr_hi ; Load RX page hi for buffer |
| 9AC3 | STA open_port_buf_hi ; Set port buffer hi |
| 9AC5 | LDY #3 ; Y=3: copy 4 bytes (3 down to 0) |
| 9AC7 | .copy_addr_loop←1← 9ACE BPL |
| LDA rx_remote_addr,y ; Load remote address byte | |
| 9ACA | STA l0d58,y ; Store to exec address workspace |
| 9ACD | DEY ; Next byte (descending) |
| 9ACE | BPL copy_addr_loop ; Loop until all 4 bytes copied |
| 9AD0 | JMP send_data_rx_ack ; Enter common data-receive path |
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 c9805. |
|
| 9AD3 | .rx_imm_poke |
| LDA #&3d ; Port workspace offset = &3D | |
| 9AD5 | STA port_ws_offset ; Store workspace offset lo |
| 9AD7 | LDA #&0d ; RX buffer page = &0D |
| 9AD9 | STA rx_buf_offset ; Store workspace offset hi |
| 9ADB | JMP ack_scout_match ; Enter POKE data-receive path |
RX immediate: machine type querySets up a receive buffer (length #&01FC) below the screen for the machine type query response, then jumps to the query handler at set_tx_reply_flag. Returns system identification data to the remote station. |
|
| 9ADE | .rx_imm_machine_type |
| LDA #1 ; Buffer length hi = 1 | |
| 9AE0 | STA port_buf_len_hi ; Set buffer length hi |
| 9AE2 | LDA #&fc ; Buffer length lo = &FC |
| 9AE4 | STA port_buf_len ; Set buffer length lo |
| 9AE6 | LDA #&21 ; Buffer start lo = &21 |
| 9AE8 | STA open_port_buf ; Set port buffer lo |
| 9AEA | LDA #&7f ; Buffer hi = &7F (below screen) |
| 9AEC | STA open_port_buf_hi ; Set port buffer hi |
| 9AEE | JMP set_tx_reply_flag ; Enter reply build path |
RX immediate: PEEK setupSaves the current TX block pointer, replaces it with a pointer to &0D3D, and prepares to send the PEEK response data back to the requesting station. |
|
| 9AF1 | .rx_imm_peek |
| LDA nmi_tx_block ; Save current TX block low byte | |
| 9AF3 | PHA ; Push to stack |
| 9AF4 | LDA nmi_tx_block_hi ; Save current TX block high byte |
| 9AF6 | PHA ; Push to stack |
| fall through ↓ | |
RX immediate: PEEK setupWrites &0D3D to port_ws_offset/rx_buf_offset, sets scout_status=2, then calls tx_calc_transfer to send the PEEK response data back to the requesting station. Uses workspace offsets (&A6/&A7) for nmi_tx_block. |
|
| 9AF7 | .rx_imm_peek_setup |
| LDA #&3d ; Port workspace offset = &3D | |
| 9AF9 | STA nmi_tx_block ; Store workspace offset lo |
| 9AFB | LDA #&0d ; RX buffer page = &0D |
| 9AFD | STA nmi_tx_block_hi ; Store workspace offset hi |
| 9AFF | LDA #2 ; Scout status = 2 (PEEK response) |
| 9B01 | STA scout_status ; Store scout status |
| 9B04 | JSR tx_calc_transfer ; Calculate transfer size for response |
| 9B07 | PLA ; Restore saved nmi_tx_block_hi |
| 9B08 | STA nmi_tx_block_hi ; Restore workspace ptr hi byte |
| 9B0A | PLA ; Restore saved nmi_tx_block |
| 9B0B | STA nmi_tx_block ; Restore workspace ptr lo byte |
| 9B0D | BCC imm_op_discard ; C=0: transfer not set up, discard |
| 9B0F | .set_tx_reply_flag←1← 9AEE JMP |
| LDA tx_flags ; Mark TX flags bit 7 (reply pending) | |
| 9B12 | ORA #&80 ; Set reply pending flag |
| 9B14 | STA tx_flags ; Store updated TX flags |
| 9B17 | .rx_imm_halt_cont |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9B19 | STA econet_control1_or_status1 ; Write CR1: enable TX interrupts |
| 9B1C | .tx_cr2_setup |
| LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE | |
| 9B1E | STA econet_control23_or_status2 ; Write CR2 for TX setup |
| 9B21 | .tx_nmi_setup |
| LDA #&3e ; NMI handler lo byte (self-modifying) | |
| 9B23 | .tx_nmi_dispatch_page |
| LDY #&9b ; Y=&9B: dispatch table page | |
| 9B25 | 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. |
|
| 9B28 | .imm_op_build_reply←1← 99D1 JMP |
| LDA port_buf_len ; Get buffer position for reply header | |
| 9B2A | CLC ; Clear carry for offset addition |
| 9B2B | ADC #&80 ; Data offset = buf_len + &80 (past header) |
| 9B2D | LDY #&7f ; Y=&7F: reply data length slot |
| 9B2F | STA (net_rx_ptr),y ; Store reply data length in RX buffer |
| 9B31 | LDY #&80 ; Y=&80: source station slot |
| 9B33 | LDA rx_src_stn ; Load requesting station number |
| 9B36 | STA (net_rx_ptr),y ; Store source station in reply header |
| 9B38 | INY ; Y=&81 |
| 9B39 | LDA rx_src_net ; Load requesting network number |
| 9B3C | STA (net_rx_ptr),y ; Store source network in reply header |
| 9B3E | LDA rx_ctrl ; Load control byte from received frame |
| 9B41 | STA tx_work_57 ; Save ctrl byte for TX response |
| 9B44 | LDA #&84 ; IER bit 2: disable SR interrupt |
| 9B46 | STA system_via_ier ; Write IER to disable SR |
| 9B49 | LDA system_via_acr ; Read ACR for shift register config |
| 9B4C | AND #&1c ; Isolate shift register mode bits (2-4) |
| 9B4E | STA tx_work_51 ; Save original SR mode for later restore |
| 9B51 | LDA system_via_acr ; Reload ACR for modification |
| 9B54 | AND #&e3 ; Clear SR mode bits (keep other bits) |
| 9B56 | ORA #8 ; SR mode 2: shift in under φ2 |
| 9B58 | STA system_via_acr ; Apply new shift register mode |
| 9B5B | BIT system_via_sr ; Read SR to clear pending interrupt |
| 9B5E | .imm_op_discard←2← 9A93 JMP← 9B0D BCC |
| JMP discard_listen ; Return to idle listen mode | |
| 9B61 | .check_sr_irq←1← 966C JMP |
| LDA #4 ; A=&04: IFR bit 2 (SR) mask | |
| 9B63 | BIT system_via_ifr ; Test SR interrupt pending |
| 9B66 | BNE tx_done_error ; SR fired: handle TX completion |
| 9B68 | LDA #5 ; A=5: no SR, return status 5 |
| 9B6A | RTS ; Return (no SR interrupt) |
| 9B6B | .tx_done_error←1← 9B66 BNE |
| TXA ; Save X | |
| 9B6C | PHA ; Push X |
| 9B6D | TYA ; Save Y |
| 9B6E | PHA ; Push Y |
| 9B6F | LDA system_via_acr ; Read ACR for shift register mode |
| 9B72 | AND #&e3 ; Clear SR mode bits (2-4) |
| 9B74 | ORA tx_work_51 ; Restore original SR mode |
| 9B77 | STA system_via_acr ; Write updated ACR |
| 9B7A | LDA system_via_sr ; Read SR to clear pending interrupt |
| 9B7D | LDA #4 ; A=&04: SR bit mask |
| 9B7F | STA system_via_ifr ; Clear SR in IFR |
| 9B82 | STA system_via_ier ; Disable SR in IER |
| 9B85 | LDY tx_work_57 ; Load ctrl byte for dispatch |
| 9B88 | CPY #&86 ; Ctrl >= &86? (HALT/CONTINUE) |
| 9B8A | BCS tx_done_classify ; Yes: skip protection mask save |
| 9B8C | LDA prot_status ; Load current protection mask |
| 9B8F | STA saved_jsr_mask ; Save mask before JSR modification |
| 9B92 | ORA #&1c ; Enable bits 2-4 (allow JSR ops) |
| 9B94 | STA prot_status ; Store modified protection mask |
| 9B97 | .tx_done_classify←1← 9B8A BCS |
| LDA tx_nmi_lo_operand,y ; Load handler addr hi from table | |
| 9B9A | PHA ; Push handler hi |
| 9B9B | LDA tx_cr2_operand,y ; Load handler addr lo from table |
| 9B9E | PHA ; Push handler lo |
| 9B9F | RTS ; Dispatch via RTS (addr-1 on stack) |
| 9BA0 | EQUB <(tx_done_jsr-1) |
| 9BA1 | EQUB <(tx_done_user_proc-1) |
| 9BA2 | EQUB <(tx_done_os_proc-1) |
| 9BA3 | EQUB <(tx_done_halt-1) |
| 9BA4 | EQUB <(tx_done_continue-1) |
| 9BA5 | EQUB >(tx_done_jsr-1) |
| 9BA6 | EQUB >(tx_done_user_proc-1) |
| 9BA7 | EQUB >(tx_done_os_proc-1) |
| 9BA8 | EQUB >(tx_done_halt-1) |
| 9BA9 | EQUB >(tx_done_continue-1) |
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. |
|
| 9BAA | .tx_done_jsr |
| LDA #&9b ; Push hi of (tx_done_exit-1) | |
| 9BAC | PHA ; Push hi byte on stack |
| 9BAD | LDA #&eb ; Push lo of (tx_done_exit-1) |
| 9BAF | PHA ; Push lo byte on stack |
| 9BB0 | 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. |
|
| 9BB3 | .tx_done_user_proc |
| LDY #event_network_error ; Y=8: network event type | |
| 9BB5 | LDX l0d58 ; X = remote address lo |
| 9BB8 | LDA l0d59 ; A = remote address hi |
| 9BBB | JSR oseven ; Generate event Y='Network error' |
| 9BBE | 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. |
|
| 9BC1 | .tx_done_os_proc |
| LDX l0d58 ; X = remote address lo | |
| 9BC4 | LDY l0d59 ; Y = remote address hi |
| 9BC7 | JSR rom_header ; Call ROM entry point at &8000 |
| 9BCA | 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. |
|
| 9BCD | .tx_done_halt |
| LDA #4 ; A=&04: bit 2 mask for rx_flags | |
| 9BCF | BIT rx_flags ; Test if already halted |
| 9BD2 | BNE tx_done_exit ; Already halted: skip to exit |
| 9BD4 | ORA rx_flags ; Set bit 2 in rx_flags |
| 9BD7 | STA rx_flags ; Store halt flag |
| 9BDA | LDA #4 ; A=4: re-load halt bit mask |
| 9BDC | CLI ; Enable interrupts during halt wait |
| 9BDD | .halt_spin_loop←1← 9BE0 BNE |
| BIT rx_flags ; Test halt flag | |
| 9BE0 | BNE halt_spin_loop ; Still halted: keep spinning |
| 9BE2 | 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. |
|
| 9BE4 | .tx_done_continue |
| LDA rx_flags ; Load current RX flags | |
| 9BE7 | AND #&fb ; Clear bit 2: release halted station |
| 9BE9 | STA rx_flags ; Store updated flags |
| 9BEC | .tx_done_exit←4← 9BBE JMP← 9BCA JMP← 9BD2 BNE← 9BE2 BEQ |
| PLA ; Restore Y from stack | |
| 9BED | TAY ; Transfer to Y register |
| 9BEE | PLA ; Restore X from stack |
| 9BEF | TAX ; Transfer to X register |
| 9BF0 | LDA #0 ; A=0: success status |
| 9BF2 | 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. |
|
| 9BF3 | .tx_begin←1← 9660 JMP |
| TXA ; Save X on stack | |
| 9BF4 | PHA ; Push X |
| 9BF5 | LDY #2 ; Y=2: TXCB offset for dest station |
| 9BF7 | LDA (nmi_tx_block),y ; Load dest station from TX control block |
| 9BF9 | STA tx_dst_stn ; Store to TX scout buffer |
| 9BFC | INY ; Y=&03 |
| 9BFD | LDA (nmi_tx_block),y ; Load dest network from TX control block |
| 9BFF | STA tx_dst_net ; Store to TX scout buffer |
| 9C02 | LDY #0 ; Y=0: first byte of TX control block |
| 9C04 | LDA (nmi_tx_block),y ; Load control/flag byte |
| 9C06 | BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte |
| 9C08 | JMP tx_active_start ; Bit7 clear: normal data transfer |
| 9C0B | .tx_imm_op_setup←1← 9C06 BMI |
| STA tx_ctrl_byte ; Store control byte to TX scout buffer | |
| 9C0E | TAX ; X = control byte for range checks |
| 9C0F | INY ; Y=1: port byte offset |
| 9C10 | LDA (nmi_tx_block),y ; Load port byte from TX control block |
| 9C12 | STA tx_port ; Store port byte to TX scout buffer |
| 9C15 | BNE tx_line_idle_check ; Port != 0: skip immediate op setup |
| 9C17 | CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc |
| 9C19 | BCS check_imm_range ; Ctrl >= &83: skip to range check |
| 9C1B | SEC ; SEC: init borrow for 4-byte subtract |
| 9C1C | PHP ; Save carry on stack for loop |
| 9C1D | LDY #8 ; Y=8: high pointer offset in TXCB |
| 9C1F | .calc_peek_poke_size←1← 9C33 BCC |
| LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte) | |
| 9C21 | DEY ; Y -= 4: back to start addr offset |
| 9C22 | DEY ; (continued) |
| 9C23 | DEY ; (continued) |
| 9C24 | DEY ; (continued) |
| 9C25 | PLP ; Restore borrow from stack |
| 9C26 | SBC (nmi_tx_block),y ; end - start = transfer size byte |
| 9C28 | STA tx_data_start,y ; Store result to tx_data_start |
| 9C2B | INY ; Y += 5: advance to next end byte |
| 9C2C | INY ; (continued) |
| 9C2D | INY ; (continued) |
| 9C2E | INY ; (continued) |
| 9C2F | INY ; (continued) |
| 9C30 | PHP ; Save borrow for next byte |
| 9C31 | CPY #&0c ; Done all 4 bytes? (Y reaches &0C) |
| 9C33 | BCC calc_peek_poke_size ; No: next byte pair |
| 9C35 | PLP ; Discard final borrow |
| 9C36 | .check_imm_range←1← 9C19 BCS |
| CPX #&89 ; Ctrl >= &89: out of immediate range | |
| 9C38 | BCS tx_active_start ; Above range: normal data transfer |
| 9C3A | LDY #&0c ; Y=&0C: start of extra data in TXCB |
| 9C3C | .copy_imm_params←1← 9C44 BCC |
| LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB | |
| 9C3E | STA nmi_shim_1a,y ; Copy to NMI shim workspace at &0D1A+Y |
| 9C41 | INY ; Next byte |
| 9C42 | CPY #&10 ; Done 4 bytes? (Y reaches &10) |
| 9C44 | BCC copy_imm_params ; No: continue copying |
| 9C46 | .tx_line_idle_check←1← 9C15 BNE |
| LDA #&20 ; A=&20: mask for SR2 INACTIVE bit | |
| 9C48 | BIT econet_control23_or_status2 ; BIT SR2: test if line is idle |
| 9C4B | BNE tx_no_clock_error ; Line not idle: handle as line jammed |
| 9C4D | LDA #&fd ; A=&FD: high byte of timeout counter |
| 9C4F | PHA ; Push timeout high byte to stack |
| 9C50 | LDA #6 ; Scout frame = 6 address+ctrl bytes |
| 9C52 | STA tx_length ; Store scout frame length |
| 9C55 | 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 &9C75-&9C7A works because CR2=&67 has RTS=0, so cts_input_ is always true, and SR1_CTS reflects presence of clock hardware. |
|
| 9C57 | .inactive_poll |
| STA tx_index ; Save TX index | |
| 9C5A | PHA ; Push timeout byte 1 on stack |
| 9C5B | PHA ; Push timeout byte 2 on stack |
| 9C5C | LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE| PSE) |
| 9C5E | .test_inactive_retry←3← 9C84 BNE← 9C89 BNE← 9C8E BNE |
| LDA #4 ; A=&04: INACTIVE mask for SR2 bit2 | |
| 9C60 | PHP ; Save interrupt state |
| 9C61 | SEI ; Disable interrupts for ADLC access |
| fall through ↓ | |
Disable NMIs and test INACTIVEMid-instruction label within the INACTIVE polling loop. The address &9BE2 is referenced as a constant for self-modifying code. Disables NMIs twice (belt-and-braces) then tests SR2 for INACTIVE before proceeding with TX. |
|
| 9C62 | .intoff_test_inactive←1← 9CDE LDA |
| BIT station_id_disable_net_nmis ; INTOFF -- disable NMIs | |
| 9C65 | BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces) |
| 9C68 | .test_line_idle |
| BIT econet_control23_or_status2 ; BIT SR2: Z = &04 AND SR2 -- tests INACTIVE | |
| 9C6B | BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop |
| 9C6D | LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt) |
| 9C70 | LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE |
| 9C72 | STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX |
| 9C75 | LDA #&10 ; A=&10: CTS mask for SR1 bit4 |
| 9C77 | BIT econet_control1_or_status1 ; BIT SR1: tests CTS present |
| 9C7A | BNE tx_prepare ; CTS set -- clock hardware detected, start TX |
| 9C7C | .inactive_retry←1← 9C6B BEQ |
| BIT video_ula_control ; INTON -- re-enable NMIs (&FE20 read) | |
| 9C7F | PLP ; Restore interrupt state |
| 9C80 | TSX ; 3-byte timeout counter on stack |
| 9C81 | INC l0101,x ; Increment timeout counter byte 1 |
| 9C84 | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C86 | INC l0102,x ; Increment timeout counter byte 2 |
| 9C89 | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C8B | INC l0103,x ; Increment timeout counter byte 3 |
| 9C8E | BNE test_inactive_retry ; Not overflowed: retry INACTIVE test |
| 9C90 | JMP tx_line_jammed ; All 3 bytes overflowed: line jammed |
| ; TX_ACTIVE branch (A=&44 = CR1 value for TX active) | |
| 9C93 | .tx_active_start←2← 9C08 JMP← 9C38 BCS |
| LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA | |
| 9C95 | 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. |
|
| 9C97 | .tx_line_jammed←1← 9C90 JMP |
| LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX) | |
| 9C99 | STA econet_control23_or_status2 ; Write CR2 to abort TX |
| 9C9C | PLA ; Clean 3 bytes of timeout loop state |
| 9C9D | PLA ; Pop saved register |
| 9C9E | PLA ; Pop saved register |
| 9C9F | LDA #&40 ; Error &40 = 'Line Jammed' |
| 9CA1 | BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch |
| 9CA3 | .tx_no_clock_error←1← 9C4B BNE |
| LDA #&43 ; Error &43 = 'No Clock' | |
| 9CA5 | .store_tx_error←2← 9C95 BNE← 9CA1 BNE |
| LDY #0 ; Offset 0 = error byte in TX control block | |
| 9CA7 | STA (nmi_tx_block),y ; Store error code in TX CB byte 0 |
| 9CA9 | LDA #&80 ; &80 = TX complete flag |
| 9CAB | STA tx_clear_flag ; Signal TX operation complete |
| 9CAE | PLA ; Restore X saved by caller |
| 9CAF | TAX ; Move to X register |
| 9CB0 | RTS ; Return to TX caller |
TX preparationConfigures ADLC for transmission: asserts RTS via CR2, enables TIE via CR1, installs NMI TX handler at &9D4C, and re-enables NMIs. |
|
| 9CB1 | .tx_prepare←1← 9C7A BNE |
| STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE) | |
| 9CB4 | LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled) |
| 9CB6 | STX econet_control1_or_status1 ; Write to ADLC CR1 |
| 9CB9 | LDX #&5b ; Install NMI handler at &9D5B (nmi_tx_data) |
| 9CBB | LDY #&9d ; High byte of NMI handler address |
| 9CBD | STX nmi_jmp_lo ; Write NMI vector low byte directly |
| 9CC0 | STY nmi_jmp_hi ; Write NMI vector high byte directly |
| 9CC3 | BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read) |
| 9CC6 | LDA tx_port ; Load destination port number |
| 9CC9 | BNE setup_data_xfer ; Port != 0: standard data transfer |
| 9CCB | LDY tx_ctrl_byte ; Port 0: load control byte for table lookup |
| 9CCE | LDA tube_tx_byte4_operand,y ; Look up tx_flags from table |
| 9CD1 | STA tx_flags ; Store operation flags |
| 9CD4 | LDA tube_tx_byte2_operand,y ; Look up tx_length from table |
| 9CD7 | STA tx_length ; Store expected transfer length |
| 9CDA | LDA sr2_test_operand,y ; Load handler from dispatch table |
| 9CDD | PHA ; Push high byte for PHA/PHA/RTS dispatch |
| 9CDE | LDA intoff_test_inactive,y ; Look up handler address low from table |
| 9CE1 | PHA ; Push low byte for PHA/PHA/RTS dispatch |
| 9CE2 | RTS ; RTS dispatches to control-byte handler |
| 9CE3 | EQUB <(tx_ctrl_peek-1) |
| 9CE4 | EQUB <(tx_ctrl_poke-1) |
| 9CE5 | EQUB <(tx_ctrl_proc-1) |
| 9CE6 | EQUB <(tx_ctrl_proc-1) |
| 9CE7 | EQUB <(tx_ctrl_proc-1) |
| 9CE8 | EQUB <(tx_ctrl_exit-1) |
| 9CE9 | EQUB <(tx_ctrl_exit-1) |
| 9CEA | EQUB <(imm_op_status3-1) |
| 9CEB | EQUB >(tx_ctrl_peek-1) |
| 9CEC | EQUB >(tx_ctrl_poke-1) |
| 9CED | EQUB >(tx_ctrl_proc-1) |
| 9CEE | EQUB >(tx_ctrl_proc-1) |
| 9CEF | EQUB >(tx_ctrl_proc-1) |
| 9CF0 | EQUB >(tx_ctrl_exit-1) |
| 9CF1 | EQUB >(tx_ctrl_exit-1) |
| 9CF2 | EQUB >(imm_op_status3-1) |
| 9CF3 | .imm_op_status3 |
| LDA #3 ; A=3: scout_status for PEEK | |
| 9CF5 | BNE store_status_calc_xfer ; ALWAYS branch |
| 9CF7 | .tx_ctrl_peek |
| LDA #3 ; A=3: scout_status for PEEK op | |
| 9CF9 | 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. |
|
| 9CFB | .tx_ctrl_poke |
| LDA #2 ; Scout status = 2 (POKE transfer) | |
| 9CFD | .store_status_add4←1← 9CF9 BNE |
| STA scout_status ; Store scout status | |
| 9D00 | CLC ; Clear carry for 4-byte addition |
| 9D01 | PHP ; Save carry on stack |
| 9D02 | LDY #&0c ; Y=&0C: start at offset 12 |
| 9D04 | .add_bytes_loop←1← 9D11 BCC |
| LDA l0d1e,y ; Load workspace address byte | |
| 9D07 | PLP ; Restore carry from previous byte |
| 9D08 | ADC (nmi_tx_block),y ; Add TXCB address byte |
| 9D0A | STA l0d1e,y ; Store updated address byte |
| 9D0D | INY ; Next byte |
| 9D0E | 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. |
|
| 9D0F | .tx_ctrl_add_done |
| CPY #&10 ; Compare Y with 16-byte boundary | |
| 9D11 | BCC add_bytes_loop ; Below boundary: continue addition |
| 9D13 | PLP ; Restore processor flags |
| 9D14 | JSR tx_calc_transfer ; Calculate transfer byte count |
| 9D17 | JMP tx_ctrl_exit ; Jump to TX control exit |
TX ctrl: JSR/UserProc/OSProc setupSets scout_status=2 and calls tx_calc_transfer directly (no 4-byte address addition needed for procedure calls). Shared by operation types &83-&85. |
|
| 9D1A | .tx_ctrl_proc |
| LDA #2 ; A=2: scout_status for procedure ops | |
| 9D1C | .store_status_calc_xfer←1← 9CF5 BNE |
| STA scout_status ; Store scout status | |
| 9D1F | JSR tx_calc_transfer ; Calculate transfer parameters |
| 9D22 | JMP tx_ctrl_exit ; Exit TX ctrl setup |
| 9D25 | .setup_data_xfer←1← 9CC9 BNE |
| LDA tx_dst_stn ; Load dest station for broadcast check | |
| 9D28 | AND tx_dst_net ; AND with dest network |
| 9D2B | CMP #&ff ; Both &FF = broadcast address? |
| 9D2D | BNE setup_unicast_xfer ; Not broadcast: unicast path |
| 9D2F | LDA #&0e ; Broadcast scout: 14 bytes total |
| 9D31 | STA tx_length ; Store broadcast scout length |
| 9D34 | LDA #&40 ; A=&40: broadcast flag |
| 9D36 | STA tx_flags ; Set broadcast flag in tx_flags |
| 9D39 | LDY #4 ; Y=4: start of address data in TXCB |
| 9D3B | .copy_bcast_addr←1← 9D43 BCC |
| LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer | |
| 9D3D | STA tx_src_stn,y ; Store to TX source/data area |
| 9D40 | INY ; Next byte |
| 9D41 | CPY #&0c ; Done 8 bytes? (Y reaches &0C) |
| 9D43 | BCC copy_bcast_addr ; No: continue copying |
| 9D45 | BCS tx_ctrl_exit ; ALWAYS branch |
| 9D47 | .setup_unicast_xfer←1← 9D2D BNE |
| LDA #0 ; A=0: clear flags for unicast | |
| 9D49 | STA tx_flags ; Clear tx_flags |
| 9D4C | .proc_op_status2 |
| LDA #2 ; scout_status=2: data transfer pending | |
| 9D4E | .store_status_copy_ptr |
| STA scout_status ; Store scout status | |
| 9D51 | JSR tx_calc_transfer ; Calculate transfer size from RXCB |
| 9D54 | .tx_ctrl_exit←3← 9D17 JMP← 9D22 JMP← 9D45 BCS |
| PLP ; Restore processor status from stack | |
| 9D55 | PLA ; Restore stacked registers (4 PLAs) |
| 9D56 | PLA ; Second PLA |
| 9D57 | PLA ; Third PLA |
| 9D58 | PLA ; Fourth PLA |
| 9D59 | TAX ; Restore X from A |
| 9D5A | 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. |
|
| 9D5B | .nmi_tx_data |
| LDY tx_index ; Load TX buffer index | |
| 9D5E | BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ) |
| 9D61 | .tx_fifo_write←1← 9D7C BMI |
| BVC tx_fifo_not_ready ; TDRA not set -- TX error | |
| 9D63 | LDA tx_dst_stn,y ; Load byte from TX buffer |
| 9D66 | STA econet_data_continue_frame ; Write to TX_DATA (continue frame) |
| 9D69 | INY ; Next TX buffer byte |
| 9D6A | LDA tx_dst_stn,y ; Load second byte from TX buffer |
| 9D6D | INY ; Advance TX index past second byte |
| 9D6E | STY tx_index ; Save updated TX buffer index |
| 9D71 | STA econet_data_continue_frame ; Write second byte to TX_DATA |
| 9D74 | CPY tx_length ; Compare index to TX length |
| 9D77 | BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA |
| 9D79 | BIT econet_control1_or_status1 ; Check if we can send another pair |
| 9D7C | BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop) |
| 9D7E | JMP nmi_rti ; RTI -- wait for next NMI |
| ; TX error path | |
| 9D81 | .tx_error←1← 9DC6 BEQ |
| LDA #&42 ; Error &42 | |
| 9D83 | BNE tx_store_error ; ALWAYS branch |
| 9D85 | .tx_fifo_not_ready←1← 9D61 BVC |
| LDA #&67 ; CR2=&67: clear status, return to listen | |
| 9D87 | STA econet_control23_or_status2 ; Write CR2: clear status, idle listen |
| 9D8A | LDA #&41 ; Error &41 (TDRA not ready) |
| 9D8C | .tx_store_error←1← 9D83 BNE |
| LDY station_id_disable_net_nmis ; INTOFF (also loads station ID) | |
| 9D8F | .delay_nmi_disable←1← 9D92 BNE |
| PHA ; PHA/PLA delay loop (256 iterations for NMI disable) | |
| 9D90 | PLA ; PHA/PLA delay (~7 cycles each) |
| 9D91 | INY ; Increment delay counter |
| 9D92 | BNE delay_nmi_disable ; Loop 256 times for NMI disable |
| 9D94 | 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 &9D94 which switches to RX mode. CR2=&3F = 0011_1111: bit5: CLR_RX_ST -- clears fv_stored_ (prepares for RX of reply) bit4: TX_LAST_DATA -- tells ADLC this is the final data byte bit3: FLAG_IDLE -- send flags/idle after frame bit2: FC_TDRA -- force clear TDRA bit1: 2_1_BYTE -- two-byte transfer mode bit0: PSE -- prioritised status enable Note: NO CLR_TX_ST (bit6=0), NO RTS (bit7=0 -- drops RTS after frame) |
|
| 9D97 | .tx_last_data←1← 9D77 BCS |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE | |
| 9D99 | STA econet_control23_or_status2 ; Write to ADLC CR2 |
| 9D9C | LDA #&a3 ; Install NMI handler at &9DA3 (nmi_tx_complete) |
| 9D9E | LDY #&9d ; High byte of handler address |
| 9DA0 | JMP set_nmi_vector ; Install and return via set_nmi_vector |
TX completion: switch to RX modeCalled via NMI after the frame (including CRC and closing flag) has been fully transmitted. Switches from TX mode to RX mode by writing CR1=&82. CR1=&82 = 1000_0010: TX_RESET | RIE (listen for reply). Checks workspace flags to decide next action: - bit6 set at &0D4A -> completion at &9F48 - bit0 set at &0D4A -> four-way handshake data phase at &9EEC - Otherwise -> install RX reply handler at &9DB2 |
|
| 9DA3 | .nmi_tx_complete |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (now in RX mode) | |
| 9DA5 | STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX |
| 9DA8 | BIT tx_flags ; Test workspace flags |
| 9DAB | BVC check_handshake_bit ; bit6 not set -- check bit0 |
| 9DAD | JMP tx_result_ok ; bit6 set -- TX completion |
| 9DB0 | .check_handshake_bit←1← 9DAB BVC |
| LDA #1 ; A=1: mask for bit0 test | |
| 9DB2 | BIT tx_flags ; Test tx_flags bit0 (handshake) |
| 9DB5 | BEQ install_reply_scout ; bit0 clear: install reply handler |
| 9DB7 | JMP handshake_await_ack ; bit0 set -- four-way handshake data phase |
| 9DBA | .install_reply_scout←1← 9DB5 BEQ |
| LDA #&c1 ; Install nmi_reply_scout at &9DC1 | |
| 9DBC | LDY #&9d ; High byte of nmi_reply_scout addr |
| 9DBE | JMP set_nmi_vector ; Install handler and RTI |
RX reply scout handlerHandles reception of the reply scout frame after transmission. Checks SR2 bit0 (AP) for incoming data, reads the first byte (destination station) and compares to our station ID via &FE18 (which also disables NMIs as a side effect). |
|
| 9DC1 | .nmi_reply_scout |
| LDA #1 ; A=&01: AP mask for SR2 | |
| 9DC3 | BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present) |
| 9DC6 | BEQ tx_error ; No AP -- error |
| 9DC8 | LDA econet_data_continue_frame ; Read first RX byte (destination station) |
| 9DCB | CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect) |
| 9DCE | BNE reply_error ; Not our station -- error/reject |
| 9DD0 | LDA #&d7 ; Install nmi_reply_cont at &9DD7 |
| 9DD2 | LDY #&9d ; High byte of nmi_reply_cont |
| 9DD4 | JMP set_nmi_vector ; Install continuation handler |
RX reply continuation handlerReads the second byte of the reply scout (destination network) and validates it is zero (local network). Installs &9DF2 for the remaining two bytes (source station and network). Optimisation: checks SR1 bit7 (IRQ still asserted) via BMI at &9DE8. If IRQ is still set, falls through directly to &9DF2 without an RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession. |
|
| 9DD7 | .nmi_reply_cont |
| BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available) | |
| 9DDA | BPL reply_error ; No RDA -- error |
| 9DDC | LDA econet_data_continue_frame ; Read destination network byte |
| 9DDF | BNE reply_error ; Non-zero -- network mismatch, error |
| 9DE1 | LDA #&f2 ; Install nmi_reply_validate at &9D5B |
| 9DE3 | LDY #&9d ; High byte of nmi_reply_validate |
| 9DE5 | BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready? |
| 9DE8 | BMI nmi_reply_validate ; IRQ set -- fall through to &9D5B without RTI |
| 9DEA | JMP set_nmi_vector ; IRQ not set -- install handler and RTI |
| 9DED | .reply_error←7← 9DCE BNE← 9DDA BPL← 9DDF BNE← 9DF5 BPL← 9DFD BNE← 9E05 BNE← 9E0C BEQ |
| LDA #&41 ; A=&41: 'not listening' error code | |
| 9DEF | .reject_reply |
| JMP tx_store_result ; Store error and return to idle | |
RX reply validation (Path 2 for FV/PSE interaction)Reads the source station and source network from the reply scout and validates them against the original TX destination (&0D20/&0D21). Sequence: 1. Check SR2 bit7 (RDA) at &9DF2 -- must see data available 2. Read source station at &9DF7, compare to &0D20 (tx_dst_stn) 3. Read source network at &9DFF, compare to &0D21 (tx_dst_net) 4. Check SR2 bit1 (FV) at &9E09 -- 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). |
|
| 9DF2 | .nmi_reply_validate←1← 9DE8 BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply. | |
| 9DF5 | BPL reply_error ; No RDA -- error (FV masking RDA via PSE would cause this) |
| 9DF7 | LDA econet_data_continue_frame ; Read source station |
| 9DFA | CMP tx_dst_stn ; Compare to original TX destination station (&0D20) |
| 9DFD | BNE reply_error ; Mismatch -- not the expected reply, error |
| 9DFF | LDA econet_data_continue_frame ; Read source network |
| 9E02 | CMP tx_dst_net ; Compare to original TX destination network (&0D21) |
| 9E05 | BNE reply_error ; Mismatch -- error |
| 9E07 | LDA #2 ; A=&02: FV mask for SR2 bit1 |
| 9E09 | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9E0C | BEQ reply_error ; No FV -- incomplete frame, error |
| 9E0E | LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake) |
| 9E10 | STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake |
| 9E13 | LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK) |
| 9E15 | STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt |
| 9E18 | LDA #&ec ; Install next handler at &9EEC into &0D4B/&0D4C |
| 9E1A | LDY #&9e ; High byte &9E of next handler address |
| 9E1C | STA nmi_next_lo ; Store low byte to nmi_next_lo |
| 9E1F | STY nmi_next_hi ; Store high byte to nmi_next_hi |
| 9E22 | LDA tx_dst_stn ; Load dest station for scout ACK TX |
| 9E25 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9E28 | BVC data_tx_error ; TDRA not ready -- error |
| 9E2A | STA econet_data_continue_frame ; Write dest station to TX FIFO |
| 9E2D | LDA tx_dst_net ; Load dest network for scout ACK TX |
| 9E30 | STA econet_data_continue_frame ; Write dest network to TX FIFO |
| 9E33 | LDA #&3a ; Install nmi_scout_ack_src at &9DA3 |
| 9E35 | LDY #&9e ; High byte &9D of handler address |
| 9E37 | 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. |
|
| 9E3A | .nmi_scout_ack_src |
| LDA station_id_disable_net_nmis ; Read our station ID (also INTOFF) | |
| 9E3D | BIT econet_control1_or_status1 ; BIT SR1: check TDRA before writing |
| 9E40 | BVC data_tx_error ; TDRA not ready: TX error |
| 9E42 | STA econet_data_continue_frame ; Write our station to TX FIFO |
| 9E45 | LDA #0 ; Network = 0 (local network) |
| 9E47 | STA econet_data_continue_frame ; Write network byte to TX FIFO |
| 9E4A | .data_tx_begin←1← 99BF JMP |
| LDA #2 ; Test bit 1 of tx_flags | |
| 9E4C | BIT tx_flags ; Check if immediate-op or data-transfer |
| 9E4F | BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler |
| 9E51 | LDA #&5f ; Install nmi_data_tx at &9E5F |
| 9E53 | LDY #&9e ; High byte of handler address |
| 9E55 | JMP set_nmi_vector ; Install and return via set_nmi_vector |
| 9E58 | .install_imm_data_nmi←1← 9E4F BNE |
| LDA #&b3 ; Install nmi_data_tx_tube at &9EB3 | |
| 9E5A | LDY #&9e ; High byte of handler address |
| 9E5C | JMP set_nmi_vector ; Install and return via set_nmi_vector |
TX data phase: send payloadSends the data frame payload from (open_port_buf),Y in pairs per NMI. Same pattern as the NMI TX handler at &9D4C but reads from the port buffer instead of the TX workspace. Writes two bytes per iteration, checking SR1 IRQ between pairs for tight looping. |
|
| 9E5F | .nmi_data_tx |
| LDY port_buf_len ; Y = buffer offset, resume from last position | |
| 9E61 | BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6) |
| 9E64 | .data_tx_check_fifo←1← 9E87 BMI |
| BVC data_tx_error ; TDRA not ready -- error | |
| 9E66 | LDA (open_port_buf),y ; Write data byte to TX FIFO |
| 9E68 | STA econet_data_continue_frame ; Write first byte of pair to FIFO |
| 9E6B | INY ; Advance buffer offset |
| 9E6C | BNE write_second_tx_byte ; No page crossing |
| 9E6E | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9E70 | BEQ data_tx_last ; No pages left: send last data |
| 9E72 | INC open_port_buf_hi ; Increment buffer high byte |
| 9E74 | .write_second_tx_byte←1← 9E6C BNE |
| LDA (open_port_buf),y ; Load second byte of pair | |
| 9E76 | STA econet_data_continue_frame ; Write second byte to FIFO |
| 9E79 | INY ; Advance buffer offset |
| 9E7A | STY port_buf_len ; Save updated buffer position |
| 9E7C | BNE check_irq_loop ; No page crossing |
| 9E7E | DEC port_buf_len_hi ; Page crossing: decrement page count |
| 9E80 | BEQ data_tx_last ; No pages left: send last data |
| 9E82 | INC open_port_buf_hi ; Increment buffer high byte |
| 9E84 | .check_irq_loop←1← 9E7C BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop | |
| 9E87 | BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes |
| 9E89 | JMP nmi_rti ; No IRQ: return, wait for next NMI |
| 9E8C | .data_tx_last←4← 9E70 BEQ← 9E80 BEQ← 9ECC BEQ← 9EE2 BEQ |
| LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame) | |
| 9E8E | STA econet_control23_or_status2 ; Write CR2 to close frame |
| 9E91 | LDA tx_flags ; Check tx_flags for next action |
| 9E94 | BPL install_saved_handler ; Bit7 clear: error, install saved handler |
| 9E96 | LDA #&4a ; Install discard_reset_listen at &9A4A |
| 9E98 | LDY #&9a ; High byte of &9A4A handler |
| 9E9A | JMP set_nmi_vector ; Set NMI vector and return |
| 9E9D | .data_tx_error←4← 9E28 BVC← 9E40 BVC← 9E64 BVC← 9EB6 BVC |
| LDA tx_flags ; Load saved next handler low byte | |
| 9EA0 | BPL nmi_tx_not_listening ; bit7 clear: error path |
| 9EA2 | JMP discard_reset_listen ; ADLC reset and return to idle |
| 9EA5 | .nmi_tx_not_listening←1← 9EA0 BPL |
| LDA #&41 ; A=&41: 'not listening' error | |
| 9EA7 | .jmp_tx_result_fail |
| JMP tx_store_result ; Store result and return to idle | |
| 9EAA | .install_saved_handler←1← 9E94 BPL |
| LDA nmi_next_lo ; Load saved handler low byte | |
| 9EAD | LDY nmi_next_hi ; Load saved next handler high byte |
| 9EB0 | JMP set_nmi_vector ; Install saved handler and return |
| 9EB3 | .nmi_data_tx_tube |
| BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA | |
| 9EB6 | .tube_tx_fifo_write←1← 9EE7 BMI |
| BVC data_tx_error ; TDRA not ready -- error | |
| 9EB8 | LDA tube_data_register_3 ; Read byte from Tube R3 |
| 9EBB | STA econet_data_continue_frame ; Write to TX FIFO |
| 9EBE | INC port_buf_len ; Increment 4-byte buffer counter |
| 9EC0 | BNE write_second_tube_byte ; Low byte didn't wrap |
| 9EC2 | INC port_buf_len_hi ; Carry into second byte |
| 9EC4 | BNE write_second_tube_byte ; No further carry |
| 9EC6 | INC open_port_buf ; Carry into third byte |
| 9EC8 | BNE write_second_tube_byte ; No further carry |
| 9ECA | INC open_port_buf_hi ; Carry into fourth byte |
| 9ECC | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9ECE | .write_second_tube_byte←3← 9EC0 BNE← 9EC4 BNE← 9EC8 BNE |
| LDA tube_data_register_3 ; Read second Tube byte from R3 | |
| 9ED1 | STA econet_data_continue_frame ; Write second byte to TX FIFO |
| 9ED4 | INC port_buf_len ; Increment 4-byte counter (second byte) |
| 9ED6 | BNE check_tube_irq_loop ; Low byte didn't wrap |
| 9ED8 | .tube_tx_inc_byte2 |
| INC port_buf_len_hi ; Carry into second byte | |
| 9EDA | BNE check_tube_irq_loop ; No further carry |
| 9EDC | .tube_tx_inc_byte3 |
| INC open_port_buf ; Carry into third byte | |
| 9EDE | BNE check_tube_irq_loop ; No further carry |
| 9EE0 | .tube_tx_inc_byte4 |
| INC open_port_buf_hi ; Carry into fourth byte | |
| 9EE2 | BEQ data_tx_last ; Counter wrapped to zero: last data |
| 9EE4 | .check_tube_irq_loop←3← 9ED6 BNE← 9EDA BNE← 9EDE BNE |
| BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop | |
| 9EE7 | BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes |
| 9EE9 | JMP nmi_rti ; No IRQ: return, wait for next NMI |
Four-way handshake: switch to RX for final ACKAfter the data frame TX completes, switches to RX mode (CR1=&82) and installs &9EE9 to receive the final ACK from the remote station. |
|
| 9EEC | .handshake_await_ack←1← 9DB7 JMP |
| LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK) | |
| 9EEE | STA econet_control1_or_status1 ; Write to ADLC CR1 |
| 9EF1 | LDA #&f8 ; Install nmi_final_ack at &9E5C |
| 9EF3 | LDY #&9e ; High byte of handler address |
| 9EF5 | 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 (&9DC1-&9DF2): &9EF8: Check AP, read dest_stn, compare to our station &9F0E: Check RDA, read dest_net, validate = 0 &9F24: Check RDA, read src_stn/net, compare to TX dest &9F41: Check FV for frame completion On success, stores result=0 at &9F48. On any failure, error &41. |
|
| 9EF8 | .nmi_final_ack |
| LDA #1 ; A=&01: AP mask | |
| 9EFA | BIT econet_control23_or_status2 ; BIT SR2: test AP |
| 9EFD | BEQ tx_result_fail ; No AP -- error |
| 9EFF | LDA econet_data_continue_frame ; Read dest station |
| 9F02 | CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect) |
| 9F05 | BNE tx_result_fail ; Not our station -- error |
| 9F07 | LDA #&0e ; Install nmi_final_ack_net at &9E70 |
| 9F09 | LDY #&9f ; High byte of handler address |
| 9F0B | JMP set_nmi_vector ; Install continuation handler |
| 9F0E | .nmi_final_ack_net |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9F11 | BPL tx_result_fail ; No RDA -- error |
| 9F13 | LDA econet_data_continue_frame ; Read dest network |
| 9F16 | BNE tx_result_fail ; Non-zero -- network mismatch, error |
| 9F18 | LDA #&24 ; Install nmi_final_ack_validate at &9E84 |
| 9F1A | LDY #&9f ; High byte of handler address |
| 9F1C | BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready? |
| 9F1F | BMI nmi_final_ack_validate ; IRQ set -- fall through to &9E84 without RTI |
| 9F21 | JMP set_nmi_vector ; Install handler and RTI |
Final ACK validationReads and validates src_stn and src_net against original TX dest. Then checks FV for frame completion. |
|
| 9F24 | .nmi_final_ack_validate←1← 9F1F BMI |
| BIT econet_control23_or_status2 ; BIT SR2: test RDA | |
| 9F27 | BPL tx_result_fail ; No RDA -- error |
| 9F29 | LDA econet_data_continue_frame ; Read source station |
| 9F2C | CMP tx_dst_stn ; Compare to TX dest station (&0D20) |
| 9F2F | BNE tx_result_fail ; Mismatch -- error |
| 9F31 | LDA econet_data_continue_frame ; Read source network |
| 9F34 | CMP tx_dst_net ; Compare to TX dest network (&0D21) |
| 9F37 | BNE tx_result_fail ; Mismatch -- error |
| 9F39 | LDA tx_flags ; Load TX flags for next action |
| 9F3C | BPL check_fv_final_ack ; bit7 clear: no data phase |
| 9F3E | JMP install_data_rx_handler ; Install data RX handler |
| 9F41 | .check_fv_final_ack←1← 9F3C BPL |
| LDA #2 ; A=&02: FV mask for SR2 bit1 | |
| 9F43 | BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete |
| 9F46 | BEQ tx_result_fail ; No FV -- error |
| fall through ↓ | |
| 9F48 | .tx_result_ok←2← 996D JMP← 9DAD JMP |
| LDA #0 ; A=0: success result code | |
| 9F4A | 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. |
|
| 9F4C | .tx_result_fail←8← 9EFD BEQ← 9F05 BNE← 9F11 BPL← 9F16 BNE← 9F27 BPL← 9F2F BNE← 9F37 BNE← 9F46 BEQ |
| LDA #&41 ; A=&41: not listening error code | |
| fall through ↓ | |
| 9F4E | .tx_store_result←5← 989B JMP← 9D94 JMP← 9DEF JMP← 9EA7 JMP← 9F4A BEQ |
| LDY #0 ; Y=0: index into TX control block | |
| 9F50 | STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0 |
| 9F52 | LDA #&80 ; &80: completion flag for &0D3A |
| 9F54 | STA tx_clear_flag ; Signal TX complete |
| 9F57 | JMP discard_reset_listen ; Full ADLC reset and return to idle listen |
| ; Unreferenced data block (purpose unknown) | |
| 9F5A | EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81 |
Calculate transfer sizeComputes the number of bytes actually transferred during a data frame reception. Subtracts the low pointer (LPTR, offset 4 in the RXCB) from the current buffer position to get the byte count, and stores it back into the RXCB's high pointer field (HPTR, offset 8). This tells the caller how much data was received. |
|
| 9F6A | .tx_calc_transfer←5← 9818 JSR← 9B04 JSR← 9D14 JSR← 9D1F JSR← 9D51 JSR |
| LDY #6 ; Load RXCB[6] (buffer addr byte 2) | |
| 9F6C | LDA (nmi_tx_block),y ; Load TX block byte at offset 6 |
| 9F6E | INY ; Y=&07 |
| 9F6F | AND (nmi_tx_block),y ; AND with TX block[7] (byte 3) |
| 9F71 | CMP #&ff ; Both &FF = no buffer? |
| 9F73 | BEQ fallback_calc_transfer ; Yes: fallback path |
| 9F75 | LDA tx_in_progress ; Tube transfer in progress? |
| 9F78 | BEQ fallback_calc_transfer ; No: fallback path |
| 9F7A | LDA tx_flags ; Load TX flags for transfer setup |
| 9F7D | ORA #2 ; Set bit 1 (transfer complete) |
| 9F7F | STA tx_flags ; Store with bit 1 set (Tube xfer) |
| 9F82 | SEC ; Init borrow for 4-byte subtract |
| 9F83 | PHP ; Save carry on stack |
| 9F84 | LDY #4 ; Y=4: start at RXCB offset 4 |
| 9F86 | .calc_transfer_size←1← 9F98 BCC |
| LDA (nmi_tx_block),y ; Load RXCB[Y] (current ptr byte) | |
| 9F88 | INY ; Y += 4: advance to RXCB[Y+4] |
| 9F89 | INY ; (continued) |
| 9F8A | INY ; (continued) |
| 9F8B | INY ; (continued) |
| 9F8C | PLP ; Restore borrow from previous byte |
| 9F8D | SBC (nmi_tx_block),y ; Subtract RXCB[Y+4] (start ptr byte) |
| 9F8F | STA net_tx_ptr,y ; Store result byte |
| 9F92 | DEY ; Y -= 3: next source byte |
| 9F93 | DEY ; (continued) |
| 9F94 | DEY ; (continued) |
| 9F95 | PHP ; Save borrow for next byte |
| 9F96 | CPY #8 ; Done all 4 bytes? |
| 9F98 | BCC calc_transfer_size ; No: next byte pair |
| 9F9A | PLP ; Discard final borrow |
| 9F9B | TXA ; A = saved X |
| 9F9C | PHA ; Save X |
| 9F9D | LDA #4 ; Compute address of RXCB+4 |
| 9F9F | CLC ; CLC for base pointer addition |
| 9FA0 | ADC nmi_tx_block ; Add RXCB base to get RXCB+4 addr |
| 9FA2 | TAX ; X = low byte of RXCB+4 |
| 9FA3 | LDY nmi_tx_block_hi ; Y = high byte of RXCB ptr |
| 9FA5 | LDA #&c2 ; Tube claim type &C2 |
| 9FA7 | JSR tube_addr_claim ; Claim Tube transfer address |
| 9FAA | BCC restore_x_and_return ; No Tube: skip reclaim |
| 9FAC | LDA scout_status ; Tube: reclaim with scout status |
| 9FAF | JSR tube_addr_claim ; Reclaim with scout status type |
| 9FB2 | SEC ; C=1: Tube address claimed |
| 9FB3 | .restore_x_and_return←1← 9FAA BCC |
| PLA ; Restore X | |
| 9FB4 | TAX ; Restore X from stack |
| 9FB5 | RTS ; Return with C = transfer status |
| 9FB6 | .fallback_calc_transfer←2← 9F73 BEQ← 9F78 BEQ |
| LDY #4 ; Y=4: RXCB current pointer offset | |
| 9FB8 | LDA (nmi_tx_block),y ; Load RXCB[4] (current ptr lo) |
| 9FBA | LDY #8 ; Y=8: RXCB start address offset |
| 9FBC | SEC ; Set carry for subtraction |
| 9FBD | SBC (nmi_tx_block),y ; Subtract RXCB[8] (start ptr lo) |
| 9FBF | STA port_buf_len ; Store transfer size lo |
| 9FC1 | LDY #5 ; Y=5: current ptr hi offset |
| 9FC3 | LDA (nmi_tx_block),y ; Load RXCB[5] (current ptr hi) |
| 9FC5 | SBC #0 ; Propagate borrow from lo subtraction |
| 9FC7 | STA open_port_buf_hi ; Temp store adjusted current ptr hi |
| 9FC9 | LDY #8 ; Y=8: start address lo offset |
| 9FCB | LDA (nmi_tx_block),y ; Copy RXCB[8] to open port buffer lo |
| 9FCD | STA open_port_buf ; Store to scratch (side effect) |
| 9FCF | LDY #9 ; Y=9: start address hi offset |
| 9FD1 | LDA (nmi_tx_block),y ; Load RXCB[9] (start ptr hi) |
| 9FD3 | SEC ; Set carry for subtraction |
| 9FD4 | SBC open_port_buf_hi ; start_hi - adjusted current_hi |
| 9FD6 | STA port_buf_len_hi ; Store transfer size hi |
| 9FD8 | SEC ; Return with C=1 |
| 9FD9 | .nmi_shim_rom_src←1← 968C LDA |
| RTS ; Return with C=1 (success) | |
Bootstrap NMI entry point (in ROM)An alternate NMI handler that lives in the ROM itself rather than in the RAM workspace at &0D00. Unlike the RAM shim (which uses a self-modifying JMP to dispatch to different handlers), this one hardcodes JMP nmi_rx_scout (&9700). 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 &9700. |
|
| 9FDA | .nmi_bootstrap_entry |
| BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM | |
| 9FDD | PHA ; Save A |
| 9FDE | TYA ; Transfer Y to A |
| 9FDF | PHA ; Save Y (via A) |
| 9FE0 | LDA #0 ; ROM bank 0 (patched during init for actual bank) |
| 9FE2 | STA romsel ; Select Econet ROM bank via ROMSEL |
| 9FE5 | JMP nmi_rx_scout ; Jump to scout handler in ROM |
ROM copy of set_nmi_vector + nmi_rti |
|
| 9FE8 | .rom_set_nmi_vector |
| STY nmi_jmp_hi ; Store handler high byte at &0D0D | |
| 9FEB | STA nmi_jmp_lo ; Store handler low byte at &0D0C |
| 9FEE | LDA romsel_copy ; Restore NFS ROM bank |
| 9FF0 | STA romsel ; Page in via hardware latch |
| 9FF3 | PLA ; Restore Y from stack |
| 9FF4 | TAY ; Transfer ROM bank to Y |
| 9FF5 | PLA ; Restore A from stack |
| 9FF6 | BIT video_ula_control ; INTON: re-enable NMIs |
| 9FF9 | RTI ; Return from interrupt |
| 9FFA | .rom_nmi_tail |
| EQUB &AD, &4A, ; Load current TX flags Set bit 1 (transfer &0D, &09, ; mode flag) Store updated TX flags &02, &8D | |
