Acorn 6502 Tube Client 1.10 (external 65C02 3 MHz)

Updated 31 Mar 2026

← All Acorn 6502 Tube Client versions

Power-on reset

Initialise the 65C02 parasite processor.

Copies the ROM contents to RAM, sets up the default MOS vectors, clears the escape flag, and jumps via low memory to page out the ROM and start the operating system.

ROMExec
F800 .reset
.pydis_start
LDX #0 ; Start copy index at 0
F802 .copy_page_ff_loop←1← F809 BNE
LDA unused_fill_page_ff,x ; Read byte from page &FF of ROM
F805 STA unused_fill_page_ff,x ; Copy to RAM (vectors + MOS entries)
F808 DEX ; Next byte
F809 BNE copy_page_ff_loop ; Loop until all 256 bytes copied
F80B LDX #&36 ; 54 bytes = 27 default vector entries
F80D .copy_vectors_loop←1← F814 BPL
LDA default_vector_table,x ; Read default vector value
F810 STA userv,x ; Write to MOS vector table
F813 DEX ; Next entry
F814 BPL copy_vectors_loop ; Loop until all vectors set
F816 TXS ; Clear the stack (X=&FF from loop)
F817 LDX #&f0 ; X=&F0: copy &FE00-&FEEF to RAM
F819 .copy_io_page_loop←1← F820 BNE
LDA io_page_base,x ; Read ROM byte below Tube I/O window
F81C STA io_page_base,x ; Copy to RAM
F81F DEX ; Next byte
F820 BNE copy_io_page_loop ; Loop until &FE00-&FEEF copied
F822 LDY #0 ; Y=0 for page offset
F824 STY string_ptr ; Point string_ptr low to &00
F826 LDA #&f8 ; High byte = &F8 (start of ROM)
F828 STA string_ptr_hi ; Set string_ptr to &F800
F82A .copy_rom_page←2← F82F BNE← F837 BNE
LDA (string_ptr),y ; Read byte from ROM page
F82C STA (string_ptr),y ; Write to RAM (self-copy)
F82E INY ; Next byte in page
F82F BNE copy_rom_page ; Loop until 256 bytes copied
F831 INC string_ptr_hi ; Move to next page
F833 LDA string_ptr_hi ; Get current page number
F835 CMP #&fe ; Reached Tube I/O window at &FE00?
F837 BNE copy_rom_page ; No, copy next page
F839 LDX #&10 ; 17 bytes of startup code to copy
F83B .copy_startup_code_loop←1← F842 BPL
LDA reloc_low_memory_src,x ; Read startup code byte
F83E STA low_memory_code,x ; Write to low memory at &0100
F841 DEX ; Next byte
F842 BPL copy_startup_code_loop ; Loop until all startup code copied
F844 LDA current_program ; Get current program low byte
F846 STA data_transfer_addr ; Set as transfer address low
F848 LDA current_program_hi ; Get current program high byte
F84A STA data_transfer_addr_hi ; Set as transfer address high
F84C LDA #0 ; A=0 for clearing
F84E STA escape_flag ; Clear Escape flag
F850 STA memory_top ; Set memory top low byte to 0
F852 LDA #&f8 ; High byte = &F8 (start of ROM)
F854 STA memory_top_hi ; Set memory top to &F800
F856 JMP low_memory_code ; Jump to low memory to page ROM out
; Low memory startup code (relocated from ROM at ; reloc_low_memory_src)
; Copied from ROM to &0100 by the reset routine.
; Reads Tube R1 status to page out the ROM, enables
; interrupts, then jumps to display the startup banner.
; On subsequent soft resets, the JMP target at &F85E is
; patched to skip the banner and enter the command prompt
; directly.
F859 0100 .low_memory_code←2← F83E STA← F856 JMP
.low_memory_startup_code←2← F83E STA← F856 JMP
LDA tube_r1_status ; Read Tube R1 status to page ROM out
F85C 0103 .irq_return_addr_lo←1← FD00 LDA
CLI ; Enable interrupts for data transfers
F85D 0104 .irq_return_addr_hi←1← FD09 LDA
JMP startup_banner ; Patched after first boot to skip banner

Display startup banner and initialise

Print the startup banner, patch the soft reset entry to skip the banner on future resets, then wait for the host's acknowledge byte.

If the acknowledge has bit 7 set, the host is requesting code execution; otherwise enters the command prompt.

F860 .startup_banner←1← 0104 JMP
JSR print_embedded_text ; Print inline startup banner string
F863 EQUS ".Acorn TUBE 6502 64K...."
F87B NOP ; NOP (&EA) terminates string and is executed
F87C LDA #&8d ; Low byte of command_prompt address
F87E STA soft_reset_jmp_lo ; Patch JMP target low byte
F881 LDA #&f8 ; High byte of command_prompt address
F883 STA soft_reset_jmp_hi ; Patch JMP target high byte
F886 JSR wait_for_tube_r2_byte ; Wait for host acknowledge byte
F889 CMP #&80 ; Is it &80 (enter code)?
F88B BEQ enter_code ; Yes, enter transferred code
fall through ↓

Command prompt loop

The main supervisor command prompt.

Prints a '*' prompt, reads a line of input using OSWORD 0, and passes it to OSCLI for execution. Handles Escape by acknowledging it and reporting the error.

F88D .command_prompt←2← F8A4 JMP← F95A JMP
LDA #&2a ; Print '*' prompt character
F88F JSR oswrch_entry ; Send '*' via OSWRCH
F892 LDX #&5d ; Low byte of rdline control block
F894 LDY #&f9 ; High byte of rdline control block
F896 LDA #0 ; OSWORD 0: read line of input
F898 JSR osword_entry ; Call OSWORD
F89B BCS command_prompt_escape ; Carry set: Escape was pressed
F89D LDX #&36 ; Low byte of input buffer &0236
F89F LDY #2 ; High byte of input buffer
F8A1 JSR oscli_entry ; Execute command via OSCLI
F8A4 JMP command_prompt ; Loop back for next command
F8A7 .command_prompt_escape←1← F89B BCS
LDA #&7e ; OSBYTE &7E: acknowledge Escape
F8A9 JSR osbyte_entry ; Call OSBYTE
F8AC BRK ; Generate error 17: 'Escape'
F8AD EQUB &11
F8AE EQUS "Escape"
F8B4 EQUB &00

Enter code at transfer address

Check whether the code at the data transfer address has a valid ROM header with a (C) string, and if so verify it is a 6502 language ROM.

Sets the current program and memory top to the transfer address, then enters the code with A=1. If the header is missing or invalid, enters with A=1 anyway (raw code entry). Generates an error if the ROM type indicates it is not a language or not 6502 code.

Note: the v1.10 ROM always enters with A=1 regardless of whether it is a RESET or OSCLI entry, and does not pass the carry flag. J.G. Harston identifies this as a bug.

F8B5 .enter_code←2← F88B BEQ← FA62 JSR
LDA data_transfer_addr ; Get transfer address low byte
F8B7 STA current_program ; Set as current program low
F8B9 STA memory_top ; Also set memory top low
F8BB LDA data_transfer_addr_hi ; Get transfer address high byte
F8BD STA current_program_hi ; Set as current program high
F8BF STA memory_top_hi ; Also set memory top high
F8C1 LDY #7 ; Offset 7 = copyright string offset
F8C3 LDA (current_program),y ; Read copyright offset from header
F8C5 CLD ; Clear decimal mode
F8C6 CLC ; Clear carry for addition
F8C7 ADC current_program ; Add base address to offset
F8C9 STA last_error ; Store copyright pointer low
F8CB LDA #0 ; A=0 for high byte add
F8CD ADC current_program_hi ; Add carry to high byte
F8CF STA last_error_hi ; Store copyright pointer high
F8D1 LDY #0 ; Y=0 to check first byte
F8D3 LDA (last_error),y ; Read byte at copyright pointer
F8D5 BNE enter_raw_code ; Not zero: no valid header, enter raw
F8D7 INY ; Y=1 to check '(' Y=&01
F8D8 LDA (last_error),y ; Read next byte
F8DA CMP #&28 ; Is it '('?
F8DC BNE enter_raw_code ; No: enter as raw code
F8DE INY ; Y=2 to check 'C' Y=&02
F8DF LDA (last_error),y ; Read next byte
F8E1 CMP #&43 ; Is it 'C'?
F8E3 BNE enter_raw_code ; No: enter as raw code
F8E5 INY ; Y=3 to check ')' Y=&03
F8E6 LDA (last_error),y ; Read next byte
F8E8 CMP #&29 ; Is it ')'?
F8EA BNE enter_raw_code ; No: enter as raw code
F8EC LDY #6 ; Offset 6 = ROM type byte
F8EE LDA (current_program),y ; Read ROM type
F8F0 AND #&4f ; Mask language and CPU type bits
F8F2 CMP #&40 ; Bit 6 clear: not a language ROM
F8F4 BCC error_not_a_language ; Generate 'not a language' error
F8F6 AND #&0d ; Mask CPU type: 0 or 2 = 6502
F8F8 BNE error_not_6502_code ; Non-zero: not 6502 code
fall through ↓

Enter raw code

Enter code at the transfer address without a valid ROM header. Loads A=1 and jumps via the memory top pointer (which has been set to the transfer address).

Note: J.G. Harston identifies a bug where raw code should be entered with A=0, and the carry should indicate whether the entry is from RESET or OSCLI.

F8FA .enter_raw_code←4← F8D5 BNE← F8DC BNE← F8E3 BNE← F8EA BNE
LDA #1 ; Enter code with A=1
F8FC JMP (memory_top) ; Jump to code via memory top pointer

Generate 'not a language' error

Set up the BRK vector to point to the default error handler, then generate error 0: 'This is not a language'. The error handler must be re-established here because the previous language's handler will have been overwritten.

F8FF .error_not_a_language←1← F8F4 BCC
LDA #&45 ; Set BRKV low to error handler
F901 STA brkv ; Store BRKV low byte
F904 LDA #&f9 ; Set BRKV high to error handler
F906 STA brkv_hi ; Store BRKV high byte
F909 BRK ; Generate 'not a language' error
F90A EQUB &00
F90B EQUS "This is not a language"
F921 EQUB &00

Generate 'not 6502 code' error

Set up the BRK vector to point to the default error handler, then generate error 0: 'I cannot run this code'. Called when the ROM type indicates the code is not suitable for a 6502 processor.

F922 .error_not_6502_code←1← F8F8 BNE
LDA #&45 ; Set BRKV low to error handler
F924 STA brkv ; Store BRKV low byte
F927 LDA #&f9 ; Set BRKV high to error handler
F929 STA brkv_hi ; Store BRKV high byte
F92C BRK ; Generate 'I cannot run this code'
F92D EQUB &00
F92E EQUS "I cannot run this code"
F944 EQUB &00

Error handler

Default BRK handler. Clears the stack, prints the error message from the BRK instruction, and returns to the command prompt.

F945 .error_handler
LDX #&ff ; Reset stack pointer to &FF
F947 TXS ; Clear the stack
F948 JSR osnewl_entry ; Print newline before error message
F94B LDY #1 ; Start at offset 1 (skip error number)
F94D .print_error_loop←1← F955 BNE
LDA (last_error),y ; Get next error message character
F94F BEQ print_error_done ; Zero: end of error message
F951 JSR oswrch_entry ; Print character via OSWRCH
F954 INY ; Next character
F955 BNE print_error_loop ; Loop until all characters printed
F957 .print_error_done←1← F94F BEQ
JSR osnewl_entry ; Print trailing newline
F95A JMP command_prompt ; Return to command prompt

OSWORD 0 control block

Parameter block passed to OSWORD 0 (read line) when reading input at the supervisor command prompt. Specifies where to store the input text, the buffer size, and the range of acceptable character codes.

F95D .rdline_control_block
EQUB &36 ; Buffer address low (&0236)
F95E EQUB &02 ; Buffer address high
F95F EQUB &CA ; Buffer length (&CA = 202 bytes)
F960 EQUB &20 ; Minimum ASCII value (&20 = space)
F961 EQUB &FF ; Maximum ASCII value (&FF = all)

OSWRCH implementation

Send character in A to the host via Tube R1.

On EntryAcharacter to send
On ExitApreserved
F962 .oswrch_impl←2← F966 BVC← FFCB JMP
BIT tube_r1_status ; Check Tube R1 status
F965 NOP ; NOP for timing
F966 BVC oswrch_impl ; Wait until R1 ready to accept data
F968 STA tube_r1_data ; Send character to Tube R1
F96B RTS ; Return with A preserved

OSRDCH implementation

Read a character from the host via the Tube.

Sends command &00 to the host, then waits for a carry byte and the character.

On ExitAcharacter received
CEscape flag
F96C .osrdch_impl←1← FFC8 JMP
LDA #0 ; Command &00: request character
F96E JSR send_command ; Send command to host via Tube R2
fall through ↓

Wait for carry byte and data byte

Wait for two bytes from Tube R2: first a carry indicator, then the data byte.

The carry byte is shifted left so bit 7 moves into the carry flag, then falls through to read the actual data byte.

On ExitAdata byte from Tube R2
Ccarry indicator from host
F971 .wait_carry_and_byte←2← FC33 JMP← FCB4 JMP
JSR wait_for_tube_r2_byte ; Wait for carry byte from host
F974 ASL ; Shift carry flag into C
fall through ↓

Wait for byte from Tube R2

Poll Tube R2 status until data is available, then read and return the byte.

On ExitAbyte read from Tube R2
F975 .wait_for_tube_r2_byte←18← F886 JSR← F971 JSR← F978 BPL← FA35 JSR← FBA3 JSR← FBF2 JSR← FBF6 JSR← FBFB JSR← FC00 JSR← FC05 JSR← FC1F JSR← FC27 JMP← FC45 JSR← FC78 JSR← FC7E JSR← FCA8 JSR← FD53 JSR← FD5A JSR
BIT tube_r2_status ; Poll Tube R2 status
F978 BPL wait_for_tube_r2_byte ; Loop until data available
F97A LDA tube_r2_data ; Read data byte from Tube R2
fall through ↓

Null return

An RTS used as a no-op handler for vectors that have no action (EVNTV, IND1V-IND3V in the default table).

F97D .null_return
RTS ; Return

Advance and skip spaces in command string

Increment Y then fall through to skip_spaces. Called when the current character has been consumed and any following spaces should be skipped.

On EntryYoffset of the character just consumed
On ExitAfirst non-space character after skipped spaces
Yoffset of that character
F97E .skip_spaces_step←2← F983 BEQ← FA44 JSR
INY ; Advance past current character
fall through ↓

Skip spaces in command string

Advance past space characters in the string at (string_ptr),Y.

On EntryYcurrent offset into string
On ExitAfirst non-space character
Yoffset of that character
F97F .skip_spaces←2← F9D1 JSR← FA4A JSR
LDA (string_ptr),y ; Get character from string
F981 CMP #&20 ; Is it a space?
F983 BEQ skip_spaces_step ; Yes: skip and check next
F985 RTS ; Return with non-space char in A

Parse hexadecimal number

Read a hexadecimal number from the string at (string_ptr),Y into the hex accumulator at &F0/F1.

On EntryYoffset into string
On ExitHEX_ACCUMULATORparsed value (low byte)
HEX_ACCUMULATOR_HIparsed value (high byte)
Xnon-zero if any digits were parsed
Yoffset past last hex digit
Afirst non-hex character
F986 .scan_hex←1← FA47 JSR
LDX #0 ; X=0: no digits parsed yet
F988 STX hex_accumulator ; Clear hex accumulator low
F98A STX hex_accumulator_hi ; Clear hex accumulator high
F98C .scan_hex_next_char←1← F9AF BNE
LDA (string_ptr),y ; Get current character
F98E CMP #&30 ; Below '0': not a hex digit
F990 BCC scan_hex_return ; Exit if not a digit
F992 CMP #&3a ; Below ':': it is a decimal digit
F994 BCC scan_hex_got_digit ; Process digit 0-9
F996 AND #&df ; Force uppercase
F998 SBC #7 ; Adjust for hex letter offset
F99A BCC scan_hex_return ; Below 'A': not a hex digit
F99C CMP #&40 ; Above 'F': not a hex digit
F99E BCS scan_hex_return ; Exit if out of range
F9A0 .scan_hex_got_digit←1← F994 BCC
ASL ; Shift digit into upper nybble
F9A1 ASL ; Second shift
F9A2 ASL ; Third shift
F9A3 ASL ; Fourth shift (digit now in bits 7-4)
F9A4 LDX #3 ; 4 bits to rotate in
F9A6 .scan_hex_shift_loop←1← F9AC BPL
ASL ; Shift bit out of digit
F9A7 ROL hex_accumulator ; Rotate into accumulator low
F9A9 ROL hex_accumulator_hi ; Rotate into accumulator high
F9AB DEX ; Next bit
F9AC BPL scan_hex_shift_loop ; Loop for 4 bits
F9AE INY ; Move to next input character
F9AF BNE scan_hex_next_char ; Loop for more hex digits
F9B1 .scan_hex_return←3← F990 BCC← F99A BCC← F99E BCS
RTS ; Return

Send string to Tube R2

Send a CR-terminated string to the host via Tube R2.

On EntryXstring address low byte
Ystring address high byte
On ExitYrestored from string_ptr_hi
F9B2 .send_string←2← FC24 JSR← FC71 JSR
STX string_ptr ; Set string pointer low byte
F9B4 STY string_ptr_hi ; Set string pointer high byte
F9B6 .send_string_via_ptr←1← FA32 JSR
LDY #0 ; Start at offset 0
F9B8 .send_string_loop←2← F9BB BVC← F9C5 BNE
BIT tube_r2_status ; Poll Tube R2 status
F9BB BVC send_string_loop ; Wait until R2 ready
F9BD LDA (string_ptr),y ; Get character from string
F9BF STA tube_r2_data ; Send to Tube R2
F9C2 INY ; Next character
F9C3 CMP #&0d ; Was it carriage return?
F9C5 BNE send_string_loop ; No: send next character
F9C7 LDY string_ptr_hi ; Restore Y from string_ptr_hi
F9C9 RTS ; Return

OSCLI implementation

Execute a * command. Parses the command to check for *GO and *HELP which are handled locally; all other commands are forwarded to the host via the Tube.

On EntryXcommand string address low byte
Ycommand string address high byte
F9CA .oscli_impl
PHA ; Save A on stack
F9CB STX string_ptr ; Store command string low byte
F9CD STY string_ptr_hi ; Store command string high byte
F9CF LDY #0 ; Start at offset 0
F9D1 .oscli_skip_stars_loop←1← F9D7 BEQ
JSR skip_spaces ; Skip leading spaces
F9D4 INY ; Advance past character
F9D5 CMP #&2a ; Is it a '*'?
F9D7 BEQ oscli_skip_stars_loop ; Yes: skip leading stars
F9D9 AND #&df ; Force uppercase for matching
F9DB TAX ; Save first command letter in X
F9DC LDA (string_ptr),y ; Peek at next character
F9DE CPX #&47 ; Is first letter 'G'?
F9E0 BEQ command_go ; Yes: check for *GO
F9E2 CPX #&48 ; Is first letter 'H'?
F9E4 BNE oscli_send_to_host ; No: pass command to host
F9E6 CMP #&2e ; Is next char '.' (abbreviated)?
F9E8 BEQ command_help ; H. matches *HELP
F9EA AND #&df ; Force uppercase
F9EC CMP #&45 ; Is it 'E'?
F9EE BNE oscli_send_to_host ; No: pass to host
F9F0 INY ; Advance past 'E'
F9F1 LDA (string_ptr),y ; Get next character
F9F3 CMP #&2e ; Is it '.' (abbreviated)?
F9F5 BEQ command_help ; HE. matches *HELP
F9F7 AND #&df ; Force uppercase
F9F9 CMP #&4c ; Is it 'L'?
F9FB BNE oscli_send_to_host ; No: pass to host
F9FD INY ; Advance past 'L'
F9FE LDA (string_ptr),y ; Get next character
FA00 CMP #&2e ; Is it '.' (abbreviated)?
FA02 BEQ command_help ; HEL. matches *HELP
FA04 AND #&df ; Force uppercase
FA06 CMP #&50 ; Is it 'P'?
FA08 BNE oscli_send_to_host ; No: pass to host
FA0A INY ; Advance past 'P'
FA0B LDA (string_ptr),y ; Get next character
FA0D AND #&df ; Force uppercase
FA0F CMP #&41 ; Below 'A': end of command, it is HELP
FA11 BCC command_help ; Non-letter terminates: do *HELP
FA13 CMP #&5b ; Below '[': followed by letter
FA15 BCC oscli_send_to_host ; Letter follows: pass to host
fall through ↓

Handle *HELP command

Print local help text showing the Tube Client version, then fall through to forward the *HELP command to the host.

FA17 .command_help←4← F9E8 BEQ← F9F5 BEQ← FA02 BEQ← FA11 BCC
JSR print_embedded_text ; Print inline version string
FA1A EQUS "..6502 TUBE 1.10.."
FA2C NOP ; NOP (&EA) terminates string and is executed
fall through ↓

Send OSCLI command to host

Forward the command string at (string_ptr) to the host via Tube R2 with command code &02.

Tube protocol: &02 string &0D -- &7F or &80

If the response has bit 7 set, code needs to be entered (a language was selected).

FA2D .oscli_send_to_host←7← F9E4 BNE← F9EE BNE← F9FB BNE← FA08 BNE← FA15 BCC← FA42 BNE← FA4F BNE
LDA #2 ; Command &02: OSCLI
FA2F JSR send_command ; Send command code to host
FA32 JSR send_string_via_ptr ; Send command string via string_ptr
fall through ↓

Wait for OSCLI acknowledgement

Wait for the host's response byte after sending an OSCLI command. If the response has bit 7 set (&80), the host has selected a language and code needs to be entered at the transfer address. Otherwise restore A and return to the caller.

Also used by OSBYTE &8E (select language) via the check at osbyte_check_ack.

FA35 .oscli_wait_ack←1← FA71 BEQ
JSR wait_for_tube_r2_byte ; Wait for host acknowledgement
FA38 CMP #&80 ; &80: host wants code entered
FA3A BEQ execute_code ; Enter transferred code
FA3C PLA ; Restore saved A
FA3D RTS ; Return to caller

Handle *GO command

Parse *GO [address]. If an address is given, set the transfer address to it. If no address given, use the current transfer address. Falls through to execute the code.

Note: does not check for a separator after 'GO', so commands like *GOAD would be incorrectly matched. J.G. Harston identifies this as a bug.

FA3E .command_go←1← F9E0 BEQ
AND #&df ; Force uppercase
FA40 CMP #&4f ; Is it 'O' (completing GO)?
FA42 BNE oscli_send_to_host ; No: pass to host
FA44 JSR skip_spaces_step ; Skip past 'O' and spaces
FA47 JSR scan_hex ; Parse optional hex address
FA4A JSR skip_spaces ; Skip trailing spaces
FA4D CMP #&0d ; End of line (CR)?
FA4F BNE oscli_send_to_host ; No: extra params, pass to host
FA51 TXA ; X=0 means no address was given
FA52 BEQ execute_code ; Use current transfer address
FA54 LDA hex_accumulator ; Get parsed address low byte
FA56 STA data_transfer_addr ; Set transfer address low
FA58 LDA hex_accumulator_hi ; Get parsed address high byte
FA5A STA data_transfer_addr_hi ; Set transfer address high
fall through ↓

Execute code and restore state

Save the current program pointer, call enter_code, then restore the current program and memory top on return.

Note: in v1.10, the carry flag is not explicitly set before calling enter_code, so entered code cannot reliably distinguish RESET from OSCLI entry. J.G. Harston identifies this as a bug.

FA5C .execute_code←2← FA3A BEQ← FA52 BEQ
LDA current_program_hi ; Get current program high byte
FA5E PHA ; Save on stack
FA5F LDA current_program ; Get current program low byte
FA61 PHA ; Save on stack
FA62 JSR enter_code ; Enter the code at transfer address
FA65 PLA ; Restore current program low
FA66 STA current_program ; Set current program low
FA68 STA memory_top ; Also restore memory top low
FA6A PLA ; Restore current program high
FA6B STA current_program_hi ; Set current program high
FA6D STA memory_top_hi ; Also restore memory top high
FA6F PLA ; Restore saved A
FA70 RTS ; Return to caller

Check OSCLI acknowledgement (OSBYTE &8E path)

Entry point used by OSBYTE &8E (select language). If the function matched &8E, branches to wait for the OSCLI acknowledgement byte from the host, which may trigger code entry.

FA71 .check_oscli_ack←1← FACE BEQ
.osbyte_check_ack←1← FACE BEQ
BEQ oscli_wait_ack ; If &8E matched, wait for OSCLI ack
fall through ↓

OSBYTE implementation

Handle OSBYTE calls. Functions &82-&84 are handled locally (memory high word, bottom/top of memory). Low functions (A < &80) send command &04 with X and A. High functions send command &06 with X, Y, and A.

Special handling for OSBYTE &8E (select language) which checks for code to enter, and &9D (fast BPUT) which returns immediately without waiting for a response.

On EntryAfunction
Xparameter 1
Yparameter 2
On ExitApreserved
Xreturned value (for A >= &80)
Yreturned value (for A >= &80)
Creturned value (for A >= &80)
FA73 .osbyte_impl
CMP #&80 ; Function >= &80?
FA75 BCS osbyte_high ; Yes: handle high OSBYTE
FA77 PHA ; Save function on stack
FA78 LDA #4 ; Command &04: OSBYTE low
FA7A .osbyte_low_wait_r2_1←1← FA7D BVC
BIT tube_r2_status ; Poll Tube R2 status
FA7D BVC osbyte_low_wait_r2_1 ; Wait until R2 ready
FA7F STA tube_r2_data ; Send command &04
FA82 .osbyte_low_wait_r2_2←1← FA85 BVC
BIT tube_r2_status ; Poll Tube R2 status
FA85 BVC osbyte_low_wait_r2_2 ; Wait until R2 ready
FA87 STX tube_r2_data ; Send X parameter
FA8A PLA ; Restore function code
FA8B .osbyte_low_wait_r2_3←1← FA8E BVC
BIT tube_r2_status ; Poll Tube R2 status
FA8E BVC osbyte_low_wait_r2_3 ; Wait until R2 ready
FA90 STA tube_r2_data ; Send function code
FA93 .osbyte_low_wait_result←1← FA96 BPL
BIT tube_r2_status ; Poll Tube R2 for response
FA96 BPL osbyte_low_wait_result ; Wait until data available
FA98 LDX tube_r2_data ; Read return value into X
FA9B RTS ; Return
FA9C .osbyte_high←1← FA75 BCS
CMP #&82 ; Is it OSBYTE &82 (read high word)?
FA9E BEQ osbyte_read_high_word ; Yes: return &0000
FAA0 CMP #&83 ; Is it OSBYTE &83 (read LOMEM)?
FAA2 BEQ osbyte_read_lomem ; Yes: return &0800
FAA4 CMP #&84 ; Is it OSBYTE &84 (read HIMEM)?
FAA6 BEQ osbyte_read_himem ; Yes: return memory_top
FAA8 PHA ; Save function on stack
FAA9 LDA #6 ; Command &06: OSBYTE high
FAAB .osbyte_high_wait_r2_1←1← FAAE BVC
BIT tube_r2_status ; Poll Tube R2 status
FAAE BVC osbyte_high_wait_r2_1 ; Wait until R2 ready
FAB0 STA tube_r2_data ; Send command &06
FAB3 .osbyte_high_wait_r2_2←1← FAB6 BVC
BIT tube_r2_status ; Poll Tube R2 status
FAB6 BVC osbyte_high_wait_r2_2 ; Wait until R2 ready
FAB8 STX tube_r2_data ; Send X parameter
FABB .osbyte_high_wait_r2_3←1← FABE BVC
BIT tube_r2_status ; Poll Tube R2 status
FABE BVC osbyte_high_wait_r2_3 ; Wait until R2 ready
FAC0 STY tube_r2_data ; Send Y parameter
FAC3 PLA ; Restore function code
FAC4 .osbyte_high_wait_r2_4←1← FAC7 BVC
BIT tube_r2_status ; Poll Tube R2 status
FAC7 BVC osbyte_high_wait_r2_4 ; Wait until R2 ready
FAC9 STA tube_r2_data ; Send function code
FACC CMP #&8e ; Is it &8E (select language)?
FACE BEQ check_oscli_ack ; Yes: check for code to enter
FAD0 CMP #&9d ; Is it &9D (fast BPUT)?
FAD2 BEQ osbyte_high_return ; Yes: return without response
FAD4 PHA ; Save function for later restore
FAD5 .osbyte_high_wait_carry←1← FAD8 BPL
BIT tube_r2_status ; Poll Tube R2 for carry byte
FAD8 BPL osbyte_high_wait_carry ; Wait until data available
FADA LDA tube_r2_data ; Read carry byte
FADD ASL ; Shift carry into C flag
FADE PLA ; Restore saved function
FADF .osbyte_high_wait_y←1← FAE2 BPL
BIT tube_r2_status ; Poll Tube R2 for Y return value
FAE2 BPL osbyte_high_wait_y ; Wait until data available
FAE4 LDY tube_r2_data ; Read Y return value
FAE7 .osbyte_high_wait_x←1← FAEA BPL
BIT tube_r2_status ; Poll Tube R2 for X return value
FAEA BPL osbyte_high_wait_x ; Wait until data available
FAEC LDX tube_r2_data ; Read X return value
FAEF .osbyte_high_return←1← FAD2 BEQ
RTS ; Return

OSBYTE &84: read top of memory

Return the current top of user memory from &F2/F3. Falls through to the RTS at osbyte_himem_rts.

On ExitXmemory_top low byte
Ymemory_top high byte
FAF0 .osbyte_read_himem←1← FAA6 BEQ
LDX memory_top ; X = memory top low byte
FAF2 LDY memory_top_hi ; Y = memory top high byte
FAF4 .osbyte_himem_rts
RTS ; Return (OSBYTE &84)

OSBYTE &83: read bottom of memory

Return the bottom of user memory, fixed at &0800. Shares its RTS with osbyte_read_high_word.

On ExitX&00
Y&08
FAF5 .osbyte_read_lomem←1← FAA2 BEQ
LDX #0 ; X = &00 (bottom of user memory)
FAF7 LDY #8 ; Y = &08 (bottom of memory high byte)
FAF9 .osbyte_lomem_rts
RTS ; Return (OSBYTE &83)

OSBYTE &82: read machine high order address

Return &0000 as the high word of the address space. This indicates the 6502 has a 16-bit address space with no bank switching.

On ExitX&00
Y&00
FAFA .osbyte_read_high_word←1← FA9E BEQ
LDX #0 ; X = &00 (high word low)
FAFC LDY #0 ; Y = &00 (high word high)
FAFE RTS ; Return (OSBYTE &82)

OSWORD implementation

Handle OSWORD calls. OSWORD 0 (read line) is handled specially via rdline. All other functions send the control block to the host and receive the response, with block sizes determined by lookup tables.

On EntryAfunction
XYcontrol block address
FAFF .osword_impl
STX string_ptr ; Store control block address low
FB01 STY string_ptr_hi ; Store control block address high
FB03 TAY ; A=0: OSWORD 0?
FB04 BEQ rdline ; Yes: handle read line specially
FB06 PHA ; Save function on stack
FB07 LDY #8 ; Command &08: OSWORD
FB09 .osword_wait_r2_cmd←1← FB0C BVC
BIT tube_r2_status ; Poll Tube R2 status
FB0C BVC osword_wait_r2_cmd ; Wait until R2 ready
FB0E STY tube_r2_data ; Send command &08
FB11 .osword_wait_r2_func←1← FB14 BVC
BIT tube_r2_status ; Poll Tube R2 status
FB14 BVC osword_wait_r2_func ; Wait until R2 ready
FB16 STA tube_r2_data ; Send function number
FB19 TAX ; Copy function to X
FB1A BPL osword_send_low_lookup ; Function >= &80?
FB1C LDY #0 ; Y=0 for control block read
FB1E LDA (string_ptr),y ; Get send length from control block
FB20 TAY ; Transfer to Y
FB21 JMP osword_send_block ; Jump to send block
FB24 .osword_send_low_lookup←1← FB1A BPL
LDY osword_send_lengths,x ; Get send length from table
FB27 CPX #&15 ; Function < &15?
FB29 BCC osword_send_block ; Yes: use table length
FB2B LDY #&10 ; Default: send 16 bytes
FB2D .osword_send_block←3← FB21 JMP← FB29 BCC← FB30 BVC
BIT tube_r2_status ; Poll Tube R2 status
FB30 BVC osword_send_block ; Wait until R2 ready
FB32 STY tube_r2_data ; Send block length to host
FB35 DEY ; Decrement index
FB36 BMI osword_recv_get_length ; Negative: nothing to send
FB38 .osword_send_bytes_loop←2← FB3B BVC← FB43 BPL
BIT tube_r2_status ; Poll Tube R2 status
FB3B BVC osword_send_bytes_loop ; Wait until R2 ready
FB3D LDA (string_ptr),y ; Read byte from control block
FB3F STA tube_r2_data ; Send to Tube R2
FB42 DEY ; Next byte (reverse order)
FB43 BPL osword_send_bytes_loop ; Loop until all bytes sent
FB45 .osword_recv_get_length←1← FB36 BMI
TXA ; Get function back in A
FB46 BPL osword_recv_low_lookup ; Function >= &80?
FB48 LDY #1 ; Y=1 for control block read
FB4A LDA (string_ptr),y ; Get receive length from block
FB4C TAY ; Transfer to Y
FB4D JMP osword_recv_block ; Jump to receive block
FB50 .osword_recv_low_lookup←1← FB46 BPL
LDY osword_recv_lengths,x ; Get receive length from table
FB53 CPX #&15 ; Function < &15?
FB55 BCC osword_recv_block ; Yes: use table length
FB57 LDY #&10 ; Default: receive 16 bytes
FB59 .osword_recv_block←3← FB4D JMP← FB55 BCC← FB5C BVC
BIT tube_r2_status ; Poll Tube R2 status
FB5C BVC osword_recv_block ; Wait until R2 ready
FB5E STY tube_r2_data ; Send receive length to host
FB61 DEY ; Decrement index
FB62 BMI osword_restore_regs ; Negative: nothing to receive
FB64 .osword_recv_bytes_loop←2← FB67 BPL← FB6F BPL
BIT tube_r2_status ; Poll Tube R2 for data
FB67 BPL osword_recv_bytes_loop ; Wait until data available
FB69 LDA tube_r2_data ; Read response byte
FB6C STA (string_ptr),y ; Store in control block
FB6E DEY ; Next byte (reverse order)
FB6F BPL osword_recv_bytes_loop ; Loop until all received
FB71 .osword_restore_regs←1← FB62 BMI
LDY string_ptr_hi ; Restore Y from string_ptr_hi
FB73 LDX string_ptr ; Restore X from string_ptr
FB75 PLA ; Restore function code
FB76 RTS ; Return

Read line of input (OSWORD 0)

Read a line of text from the host.

Sends command &0A with the control block parameters, then receives the input string character by character.

Tube protocol: &0A block -- &FF or &7F string &0D

On ExitYlength of string (excluding CR)
C0 if OK, 1 if Escape
FB77 .rdline←1← FB04 BEQ
LDA #&0a ; Command &0A: read line
FB79 JSR send_command ; Send command to host
FB7C LDY #4 ; Start at control block byte 4
FB7E .rdline_send_block_loop←2← FB81 BVC← FB8B BNE
BIT tube_r2_status ; Poll Tube R2 status
FB81 BVC rdline_send_block_loop ; Wait until R2 ready
FB83 LDA (string_ptr),y ; Get control block byte
FB85 STA tube_r2_data ; Send to host
FB88 DEY ; Decrement index
FB89 CPY #1 ; Reached byte 1?
FB8B BNE rdline_send_block_loop ; No: send next (bytes 4, 3, 2)
FB8D LDA #7 ; &07 as high byte of buffer address
FB8F JSR send_command ; Send buffer address high byte
FB92 LDA (string_ptr),y ; Get actual buffer high byte
FB94 PHA ; Save on stack for later
FB95 DEY ; Decrement to byte 0
FB96 .rdline_send_addr_low←1← FB99 BVC
BIT tube_r2_status ; Poll Tube R2 status
FB99 BVC rdline_send_addr_low ; Wait until R2 ready
FB9B STY tube_r2_data ; Send &00 as buffer address low byte
FB9E LDA (string_ptr),y ; Get actual buffer low byte
FBA0 PHA ; Save on stack for later
FBA1 LDX #&ff ; X=&FF as Escape indicator
FBA3 JSR wait_for_tube_r2_byte ; Wait for host response
FBA6 CMP #&80 ; Is response >= &80 (Escape)?
FBA8 BCS rdline_escape ; Yes: handle Escape
FBAA PLA ; Recover buffer low byte
FBAB STA string_ptr ; Set string_ptr low
FBAD PLA ; Recover buffer high byte
FBAE STA string_ptr_hi ; Set string_ptr high
FBB0 LDY #0 ; Start at offset 0
FBB2 .rdline_recv_loop←2← FBB5 BPL← FBBF BNE
BIT tube_r2_status ; Poll Tube R2 for data
FBB5 BPL rdline_recv_loop ; Wait until data available
FBB7 LDA tube_r2_data ; Read character from host
FBBA STA (string_ptr),y ; Store in buffer
FBBC INY ; Advance buffer position
FBBD CMP #&0d ; Is it carriage return?
FBBF BNE rdline_recv_loop ; No: receive next character
FBC1 LDA #0 ; A=0 on success
FBC3 DEY ; Y=length (exclude CR)
FBC4 CLC ; Clear carry: no Escape
FBC5 INX ; X=0 on success
FBC6 RTS ; Return: A=0, Y=len, C=0
FBC7 .rdline_escape←1← FBA8 BCS
PLA ; Discard saved buffer low byte
FBC8 PLA ; Discard saved buffer high byte
FBC9 LDA #0 ; A=0, carry set from CMP &80
FBCB RTS ; Return: Escape, A=0, C=1

OSARGS implementation

Read or write information about an open file.

Sends command &0C with handle, 4-byte data word, and function code. Receives result and updated data.

On EntryAfunction
Xzero-page data word address
Yhandle
On ExitAresult
DATA WORD AT Xupdated
FBCC .osargs_impl
PHA ; Save function on stack
FBCD LDA #&0c ; Command &0C: OSARGS
FBCF JSR send_command ; Send command to host
FBD2 .osargs_wait_r2_handle←1← FBD5 BVC
BIT tube_r2_status ; Poll Tube R2 status
FBD5 BVC osargs_wait_r2_handle ; Wait until R2 ready
FBD7 STY tube_r2_data ; Send file handle
FBDA LDA zp_data_base_3,x ; Get data word byte 3
FBDC JSR send_command ; Send data byte 3
FBDF LDA zp_data_base_2,x ; Get data word byte 2
FBE1 JSR send_command ; Send data byte 2
FBE4 LDA zp_data_base_1,x ; Get data word byte 1
FBE6 JSR send_command ; Send data byte 1
FBE9 LDA zp_data_base,x ; Get data word byte 0
FBEB JSR send_command ; Send data byte 0
FBEE PLA ; Restore function code
FBEF JSR send_command ; Send function code
FBF2 JSR wait_for_tube_r2_byte ; Wait for result byte
FBF5 PHA ; Save result on stack
FBF6 JSR wait_for_tube_r2_byte ; Wait for data byte 3
FBF9 STA zp_data_base_3,x ; Store in data word
FBFB JSR wait_for_tube_r2_byte ; Wait for data byte 2
FBFE STA zp_data_base_2,x ; Store in data word
FC00 JSR wait_for_tube_r2_byte ; Wait for data byte 1
FC03 STA zp_data_base_1,x ; Store in data word
FC05 JSR wait_for_tube_r2_byte ; Wait for data byte 0
FC08 STA zp_data_base,x ; Store in data word
FC0A PLA ; Restore result to A
FC0B RTS ; Return with result in A

OSFIND implementation

Open or close a file.

For close (A=0): sends command &12, function, handle. For open (A<>0): sends command &12, function, filename.

On EntryAfunction
XYfilename address (open) or Y = handle (close)
On ExitAhandle (open) or preserved (close)
FC0C .osfind_impl
PHA ; Save function on stack
FC0D LDA #&12 ; Command &12: OSFIND
FC0F JSR send_command ; Send command to host
FC12 PLA ; Restore function code
FC13 JSR send_command ; Send function code
FC16 CMP #0 ; Is it close (A=0)?
FC18 BNE osfind_open ; No: handle open
FC1A PHA ; Save A=0
FC1B TYA ; Transfer handle from Y to A
FC1C JSR send_command ; Send handle
FC1F JSR wait_for_tube_r2_byte ; Wait for acknowledge
FC22 PLA ; Restore A=0
FC23 RTS ; Return
FC24 .osfind_open←1← FC18 BNE
JSR send_string ; Send filename string
FC27 JMP wait_for_tube_r2_byte ; Wait for and return file handle

OSBGET implementation

Read a byte from an open file.

Sends command &0E with handle, waits for carry and byte.

On EntryYhandle
On ExitAbyte read
Cset if EOF
FC2A .osbget_impl
LDA #&0e ; Command &0E: OSBGET
FC2C JSR send_command ; Send command to host
FC2F TYA ; Transfer handle from Y to A
FC30 JSR send_command ; Send handle
FC33 JMP wait_carry_and_byte ; Wait for carry and data byte

OSBPUT implementation

Write a byte to an open file.

Sends command &10 with handle and byte.

On EntryAbyte
Yhandle
On ExitApreserved
FC36 .osbput_impl
PHA ; Save byte to write
FC37 LDA #&10 ; Command &10: OSBPUT
FC39 JSR send_command ; Send command to host
FC3C TYA ; Transfer handle from Y to A
FC3D JSR send_command ; Send handle
FC40 PLA ; Restore byte to write
FC41 JSR send_command ; Send data byte
FC44 PHA ; Save A for restore after ack
FC45 JSR wait_for_tube_r2_byte ; Wait for acknowledge
FC48 PLA ; Restore A (preserved)
FC49 RTS ; Return

Send byte to Tube R2

Wait for Tube R2 to be free, then send byte.

On EntryAbyte to send
On ExitApreserved
FC4A .send_command←25← F96E JSR← FA2F JSR← FB79 JSR← FB8F JSR← FBCF JSR← FBDC JSR← FBE1 JSR← FBE6 JSR← FBEB JSR← FBEF JSR← FC0F JSR← FC13 JSR← FC1C JSR← FC2C JSR← FC30 JSR← FC39 JSR← FC3D JSR← FC41 JSR← FC4D BVC← FC5A JSR← FC61 JSR← FC75 JSR← FC95 JSR← FC9C JSR← FCA3 JSR
.send_byte_to_tube_r2←25← F96E JSR← FA2F JSR← FB79 JSR← FB8F JSR← FBCF JSR← FBDC JSR← FBE1 JSR← FBE6 JSR← FBEB JSR← FBEF JSR← FC0F JSR← FC13 JSR← FC1C JSR← FC2C JSR← FC30 JSR← FC39 JSR← FC3D JSR← FC41 JSR← FC4D BVC← FC5A JSR← FC61 JSR← FC75 JSR← FC95 JSR← FC9C JSR← FCA3 JSR
BIT tube_r2_status ; Poll Tube R2 status
FC4D BVC send_command ; Wait until R2 ready
FC4F STA tube_r2_data ; Write byte to Tube R2 data
FC52 RTS ; Return with A preserved

OSFILE implementation

Operate on whole files (load, save, read/write attributes).

Sends command &14 with 16-byte control block, filename, and function code. Receives result and updated control block.

On EntryAfunction
XYcontrol block address
FC53 .osfile_impl
STY control_block_ptr_hi ; Store control block high byte
FC55 STX control_block_ptr ; Store control block low byte
FC57 PHA ; Save function on stack
FC58 LDA #&14 ; Command &14: OSFILE
FC5A JSR send_command ; Send command to host
FC5D LDY #&11 ; Start at control block byte &11
FC5F .osfile_send_block_loop←1← FC67 BNE
LDA (control_block_ptr),y ; Get control block byte
FC61 JSR send_command ; Send to host
FC64 DEY ; Decrement index
FC65 CPY #1 ; Reached byte 1?
FC67 BNE osfile_send_block_loop ; No: send next byte
FC69 DEY ; Decrement to byte 0
FC6A LDA (control_block_ptr),y ; Get filename pointer low
FC6C TAX ; Transfer to X
FC6D INY ; Move to byte 1
FC6E LDA (control_block_ptr),y ; Get filename pointer high
FC70 TAY ; Transfer to Y
FC71 JSR send_string ; Send filename string
FC74 PLA ; Restore function code
FC75 JSR send_command ; Send function code
FC78 JSR wait_for_tube_r2_byte ; Wait for result byte
FC7B PHA ; Save result on stack
FC7C LDY #&11 ; Start at control block byte &11
FC7E .osfile_recv_block_loop←1← FC86 BNE
JSR wait_for_tube_r2_byte ; Wait for response byte
FC81 STA (control_block_ptr),y ; Store in control block
FC83 DEY ; Decrement index
FC84 CPY #1 ; Reached byte 1?
FC86 BNE osfile_recv_block_loop ; No: receive next byte
FC88 LDY control_block_ptr_hi ; Restore Y from control block ptr
FC8A LDX control_block_ptr ; Restore X from control block ptr
FC8C PLA ; Restore result to A
FC8D RTS ; Return with result in A

OSGBPB implementation

Multiple byte read and write.

Sends command &16 with 13-byte control block and function. Receives updated control block, carry, and result.

On EntryAfunction
XYcontrol block address
FC8E .osgbpb_impl
STY control_block_ptr_hi ; Store control block high byte
FC90 STX control_block_ptr ; Store control block low byte
FC92 PHA ; Save function on stack
FC93 LDA #&16 ; Command &16: OSGBPB
FC95 JSR send_command ; Send command to host
FC98 LDY #&0c ; Start at control block byte &0C
FC9A .osgbpb_send_block_loop←1← FCA0 BPL
LDA (control_block_ptr),y ; Get control block byte
FC9C JSR send_command ; Send to host
FC9F DEY ; Decrement index
FCA0 BPL osgbpb_send_block_loop ; Loop for bytes &0C..&00
FCA2 PLA ; Restore function code
FCA3 JSR send_command ; Send function code
FCA6 LDY #&0c ; Start at control block byte &0C
FCA8 .osgbpb_recv_block_loop←1← FCAE BPL
JSR wait_for_tube_r2_byte ; Wait for response byte
FCAB STA (control_block_ptr),y ; Store in control block
FCAD DEY ; Decrement index
FCAE BPL osgbpb_recv_block_loop ; Loop for bytes &0C..&00
FCB0 LDY control_block_ptr_hi ; Restore Y from control block ptr
FCB2 LDX control_block_ptr ; Restore X from control block ptr
FCB4 JMP wait_carry_and_byte ; Get carry and result byte

Unsupported MOS call

Generate a 'Bad' error for unsupported MOS calls. Used as the default handler for USERV, IRQ2V, FSCV, and several other vectors that have no parasite-side implementation.

FCB7 .unsupported←5← FFB9 JMP← FFBC JMP← FFBF JMP← FFC2 JMP← FFC5 JMP
BRK ; Generate error 255: 'Bad'
FCB8 EQUB &FF
FCB9 EQUS "Bad"

OSWORD send block length table

Indexed by OSWORD number via LDY table,X where X is the OSWORD function. Entry 0 is never used because OSWORD 0 (RDLINE) is handled separately. Entries 1-20 give the number of bytes to send from the control block to the host for each OSWORD function. Functions above &14 (20) default to sending 16 bytes.

For functions >= &80, the send length is taken from byte 0 of the control block instead of this table.

FCBC .osword_send_lengths←1← FB24 LDY
EQUB &00 ; (unused: OSWORD 0 handled separately)
FCBD EQUB &00 ; &01 Read system clock
FCBE EQUB &05 ; &02 Write system clock
FCBF EQUB &00 ; &03 Read interval timer
FCC0 EQUB &05 ; &04 Write interval timer
FCC1 EQUB &02 ; &05 Read I/O memory (2-byte addr)
FCC2 EQUB &05 ; &06 Read real-time clock
FCC3 EQUB &08 ; &07 Write real-time clock / sound
FCC4 EQUB &0E ; &08 Define envelope
FCC5 EQUB &04 ; &09 Read pixel colour
FCC6 EQUB &01 ; &0A Read character definition
FCC7 EQUB &01 ; &0B Read palette
FCC8 EQUB &05 ; &0C Write palette
FCC9 EQUB &00 ; &0D Read last two graphics cursor posns
FCCA EQUB &01 ; &0E Read clock as string
FCCB EQUB &20 ; &0F Write clock as string
FCCC EQUB &10 ; &10 Net transmit
FCCD EQUB &0D ; &11 Net receive
FCCE EQUB &00 ; &12 Net read arguments
FCCF EQUB &04 ; &13 Net FS operation

OSWORD receive block length table

Indexed by OSWORD number via LDY table,X. Gives the number of bytes to receive back from the host into the control block. Entries 1-20 correspond to OSWORD functions &01-&14. Functions above &14 default to receiving 16 bytes.

The first byte (&80) is shared: it serves as both the OSWORD &14 send length (meaning the length is in the control block) and the unused recv slot 0.

For functions >= &80, the receive length is taken from byte 1 of the control block instead of this table.

FCD0 .osword_recv_lengths←1← FB50 LDY
EQUB &80 ; &14 send / (recv slot 0 unused)
FCD1 EQUB &05 ; &01 Read system clock
FCD2 EQUB &00 ; &02 Write system clock
FCD3 EQUB &05 ; &03 Read interval timer
FCD4 EQUB &00 ; &04 Write interval timer
FCD5 EQUB &05 ; &05 Read I/O memory
FCD6 EQUB &00 ; &06 Read real-time clock
FCD7 EQUB &00 ; &07 Write real-time clock / sound
FCD8 EQUB &00 ; &08 Define envelope
FCD9 EQUB &05 ; &09 Read pixel colour
FCDA EQUB &09 ; &0A Read character definition
FCDB EQUB &05 ; &0B Read palette
FCDC EQUB &00 ; &0C Write palette
FCDD EQUB &08 ; &0D Read last two graphics cursor posns
FCDE EQUB &18 ; &0E Read clock as string
FCDF EQUB &00 ; &0F Write clock as string
FCE0 EQUB &01 ; &10 Net transmit
FCE1 EQUB &0D ; &11 Net receive
FCE2 EQUB &80 ; &12 Net read arguments
FCE3 EQUB &04 ; &13 Net FS operation
FCE4 EQUB &80 ; &14 Net FS operation

Interrupt handler entry

Hardware interrupt entry point. Saves A, checks the break flag in the stacked processor status to distinguish BRK from IRQ, and dispatches accordingly.

FCE5 .interrupt_handler
STA irq_a_store ; Save A in irq_a_store
FCE7 PLA ; Pull stacked processor status
FCE8 PHA ; Push it back (non-destructive read)
FCE9 AND #&10 ; Isolate BRK flag (bit 4)
FCEB BNE brk_handler_entry ; BRK flag set: handle BRK
FCED JMP (irq1v) ; Dispatch via IRQ1V

IRQ1 handler

First-level IRQ handler. Checks Tube R4 for data transfer requests, then Tube R1 for escape/event notifications. Falls through to IRQ2V if neither.

FCF0 .irq1_handler
BIT tube_r4_status ; Check Tube R4 for data
FCF3 BMI tube_r4_interrupt ; Data present: handle transfer/error
FCF5 BIT tube_r1_status ; Check Tube R1 for data
FCF8 BMI tube_r1_interrupt ; Data present: handle escape/event
FCFA JMP (irq2v) ; Neither: dispatch via IRQ2V

BRK handler dispatch

Extract the return address from the stack, subtract 1 to point to the byte after the BRK opcode (the error block), store the pointer in last_error (&FD/FE), then re-enable interrupts and dispatch via BRKV.

FCFD .brk_handler_entry←1← FCEB BNE
TXA ; Save X on stack
FCFE PHA ; Push X
FCFF TSX ; Get stack pointer
FD00 LDA irq_return_addr_lo,x ; Get return address low from stack
FD03 CLD ; Clear decimal mode
FD04 SEC ; Set carry for subtract
FD05 SBC #1 ; Subtract 1 to point at error block
FD07 STA last_error ; Store as last_error low
FD09 LDA irq_return_addr_hi,x ; Get return address high from stack
FD0C SBC #0 ; Subtract borrow
FD0E STA last_error_hi ; Store as last_error high
FD10 PLA ; Restore X from stack
FD11 TAX ; Transfer back to X
FD12 LDA irq_a_store ; Get saved A from irq_a_store
FD14 CLI ; Re-enable interrupts
FD15 JMP (brkv) ; Dispatch via BRKV

Handle Tube R1 interrupt (escape and events)

Process data received via Tube R1. If bit 7 is set, it is an Escape state change (stored in escape_flag). Otherwise, it is an event notification: reads the event parameters (Y, X, event number) from R1 and dispatches via EVNTV.

FD18 .tube_r1_interrupt←1← FCF8 BMI
LDA tube_r1_data ; Read data from Tube R1
FD1B BMI set_escape_flag ; Bit 7 set: Escape state change
FD1D TYA ; Save Y
FD1E PHA ; Push Y
FD1F TXA ; Save X
FD20 PHA ; Push X
FD21 JSR wait_for_tube_r1_byte ; Read event Y parameter via R1
FD24 TAY ; Store in Y
FD25 JSR wait_for_tube_r1_byte ; Read event X parameter via R1
FD28 TAX ; Store in X
FD29 JSR wait_for_tube_r1_byte ; Read event number via R1
FD2C JSR dispatch_event ; Dispatch event via EVNTV
FD2F PLA ; Restore X from stack
FD30 TAX ; Transfer to X
FD31 PLA ; Restore Y from stack
FD32 TAY ; Transfer to Y
FD33 LDA irq_a_store ; Get saved A from irq_a_store
FD35 RTI ; Return from interrupt
FD36 .dispatch_event←1← FD2C JSR
JMP (evntv) ; Dispatch event via EVNTV

Set escape flag from Tube R1 data

Called when Tube R1 data has bit 7 set, indicating an Escape state change. Shifts bit 6 of the received byte into bit 7 via ASL and stores the result in escape_flag (&FF). Bit 7 set = Escape active.

FD39 .set_escape_flag←1← FD1B BMI
ASL ; Shift bit 6 into bit 7 for Escape
FD3A STA escape_flag ; Store as Escape flag
FD3C LDA irq_a_store ; Get saved A from irq_a_store
FD3E RTI ; Return from interrupt

Handle Tube R4 interrupt

Process data received via Tube R4. If bit 7 is set, it is an error from the host: reads the error number and message via R2 into the error buffer, then executes the error via a JMP to the buffer (which starts with a BRK opcode).

If bit 7 is clear, it is a data transfer request: falls through to data_transfer_setup.

FD3F .tube_r4_interrupt←1← FCF3 BMI
LDA tube_r4_data ; Read data from Tube R4
FD42 BPL data_transfer_setup ; Bit 7 clear: data transfer request
FD44 CLI ; Re-enable IRQs for error reception
FD45 .tube_r4_wait_error←1← FD48 BPL
BIT tube_r2_status ; Poll Tube R2 for error data
FD48 BPL tube_r4_wait_error ; Wait until data available
FD4A LDA tube_r2_data ; Read and discard R2 sync byte
FD4D LDA #0 ; A=0 (BRK opcode)
FD4F STA error_buffer ; Store BRK at start of error buffer
FD52 TAY ; Y=0 for buffer index Y=&00
FD53 JSR wait_for_tube_r2_byte ; Wait for error number byte
FD56 STA error_buffer_errnum ; Store error number in buffer
FD59 .tube_r4_read_error_loop←1← FD60 BNE
INY ; Advance buffer index
FD5A JSR wait_for_tube_r2_byte ; Wait for next error string byte
FD5D STA error_buffer_errnum,y ; Store in error buffer
FD60 BNE tube_r4_read_error_loop ; Loop until NUL terminator
FD62 JMP error_buffer ; Execute BRK in error buffer

Set up data transfer via NMI

Configure the NMI handler for a data transfer.

The transfer type (0-7) from R4 selects the NMI routine and the address pointer. Types 0-3 are single/double byte transfers. Types 4-5 are release. Types 6-7 are 256-byte block transfers.

Reads the 4-byte transfer address from R4 (only the low 2 bytes are used), configures the NMI vector and transfer address, then reads the sync byte from R4.

FD65 .data_transfer_setup←1← FD42 BPL
STA nmi_vector ; Save transfer type in NMI vector
FD68 TYA ; Save Y on stack
FD69 PHA ; Push Y
FD6A LDY nmi_vector ; Get transfer type back
FD6D LDA nmi_routine_addr_table,y ; Look up NMI routine address low
FD70 STA nmi_vector ; Set NMI vector low byte
FD73 LDA nmi_routine_addr_hi_table,y ; Look up NMI routine address high
FD76 STA nmi_vector_hi ; Set NMI vector high byte
FD79 LDA transfer_addr_ptr_table,y ; Look up address pointer low
FD7C STA transfer_addr_ptr ; Set transfer_addr_ptr low
FD7E LDA transfer_addr_ptr_hi_table,y ; Look up address pointer high
FD81 STA transfer_addr_ptr_hi ; Set transfer_addr_ptr high
FD83 .transfer_wait_id←1← FD86 BPL
BIT tube_r4_status ; Poll Tube R4 for called ID byte
FD86 BPL transfer_wait_id ; Wait until data available
FD88 LDA tube_r4_data ; Read called ID byte
FD8B CPY #5 ; Type 5: release, no transfer needed
FD8D BEQ restore_regs_and_rti ; Yes: exit immediately
FD8F TYA ; Save transfer type
FD90 PHA ; Push transfer type
FD91 LDY #1 ; Y=1 for address byte index
FD93 .transfer_read_addr4←1← FD96 BPL
BIT tube_r4_status ; Poll Tube R4 for address byte 4
FD96 BPL transfer_read_addr4 ; Wait until data available
FD98 LDA tube_r4_data ; Read and discard byte 4 (bits 31-24)
FD9B .transfer_read_addr3←1← FD9E BPL
BIT tube_r4_status ; Poll Tube R4 for address byte 3
FD9E BPL transfer_read_addr3 ; Wait until data available
FDA0 LDA tube_r4_data ; Read and discard byte 3 (bits 23-16)
FDA3 .transfer_read_addr2←1← FDA6 BPL
BIT tube_r4_status ; Poll Tube R4 for address byte 2
FDA6 BPL transfer_read_addr2 ; Wait until data available
FDA8 LDA tube_r4_data ; Read address byte 2 (high)
FDAB STA (transfer_addr_ptr),y ; Store via transfer address pointer
FDAD DEY ; Decrement to byte 0
FDAE .transfer_read_addr1←1← FDB1 BPL
BIT tube_r4_status ; Poll Tube R4 for address byte 1
FDB1 BPL transfer_read_addr1 ; Wait until data available
FDB3 LDA tube_r4_data ; Read address byte 1 (low)
FDB6 STA (transfer_addr_ptr),y ; Store via transfer address pointer
FDB8 BIT tube_r3_data ; Dummy read of Tube R3 to sync
FDBB BIT tube_r3_data ; Second dummy read of Tube R3
FDBE .transfer_wait_sync←1← FDC1 BPL
BIT tube_r4_status ; Poll Tube R4 for sync byte
FDC1 BPL transfer_wait_sync ; Wait until data available
FDC3 LDA tube_r4_data ; Read sync byte
FDC6 PLA ; Restore transfer type
FDC7 CMP #6 ; Is it type 6 or above?
FDC9 BCC restore_regs_and_rti ; Below 6: exit (single/double/release)
FDCB BNE transfer_256_bytes_from_tube ; Not 6: must be type 7 (read block)
FDCD LDY #0 ; Y=0 for 256-byte counter
FDCF .transfer_write_loop←2← FDD4 BPL← FDDD BNE
LDA tube_r3_status ; Read Tube R3 status
FDD2 AND #&80 ; Isolate ready bit
FDD4 BPL transfer_write_loop ; Wait until R3 ready
FDD6 .transfer_write_read_byte
LDA irq_vector_hi,y ; Read byte (address patched)
FDD9 STA tube_r3_data ; Send to Tube R3
FDDC INY ; Next byte
FDDD BNE transfer_write_loop ; Loop for 256 bytes
FDDF .transfer_write_sync←1← FDE2 BPL
BIT tube_r3_status ; Poll Tube R3 status
FDE2 BPL transfer_write_sync ; Wait until R3 ready
FDE4 STA tube_r3_data ; Send final sync byte to R3
fall through ↓

Restore registers and return from interrupt

Common exit path for data transfer setup. Restores Y from the stack, retrieves saved A from irq_a_store, and executes RTI.

FDE7 .restore_regs_and_rti←3← FD8D BEQ← FDC9 BCC← FDFE BEQ
PLA ; Restore Y from stack
FDE8 TAY ; Transfer to Y
FDE9 LDA irq_a_store ; Get saved A from irq_a_store
FDEB RTI ; Return from interrupt

Transfer 256 bytes from Tube via R3

Read 256 bytes from Tube R3 into memory at the address patched into the STA instruction. Used for transfer type 7 (256-byte read from host).

The target address is set up by data_transfer_setup via the transfer address pointer table.

FDEC .transfer_256_bytes_from_tube←1← FDCB BNE
LDY #0 ; Y=0 for 256-byte counter
FDEE .transfer_read_loop←2← FDF3 BPL← FDFC BNE
LDA tube_r3_status ; Read Tube R3 status
FDF1 AND #&80 ; Isolate data ready bit
FDF3 BPL transfer_read_loop ; Wait until R3 has data
FDF5 LDA tube_r3_data ; Read byte from Tube R3
FDF8 .transfer_read_store_byte
STA irq_vector_hi,y ; Store byte (address patched)
FDFB INY ; Next byte
FDFC BNE transfer_read_loop ; Loop for 256 bytes
FDFE .transfer_read_done
BEQ restore_regs_and_rti ; Always branch to exit
fall through ↓

NMI: single byte to Tube

Transfer type 0. Sends one byte from the transfer address to Tube R3, then increments the address.

FE00 .nmi_single_byte_to_tube
PHA ; Save A
FE01 .nmi0_read_byte
LDA irq_vector_hi ; Read byte (address patched by setup)
FE04 STA tube_r3_data ; Send byte to Tube R3
FE07 INC nmi0_transfer_addr ; Increment address low byte
FE0A BNE nmi0_done ; No carry: skip high byte increment
FE0C INC lfe03 ; Increment address high byte
FE0F .nmi0_done←1← FE0A BNE
PLA ; Restore A
FE10 RTI ; Return from NMI

NMI: single byte from Tube

Transfer type 1. Reads one byte from Tube R3 and stores it at the transfer address, then increments the address.

FE11 .nmi_single_byte_from_tube
PHA ; Save A
FE12 LDA tube_r3_data ; Read byte from Tube R3
FE15 .sub_cfe15
STA irq_vector_hi ; Store byte (address patched by setup)
FE18 INC nmi1_transfer_addr ; Increment address low byte
FE1B BNE nmi1_done ; No carry: skip high byte increment
FE1D INC lfe17 ; Increment address high byte
FE20 .nmi1_done←1← FE1B BNE
PLA ; Restore A
FE21 RTI ; Return from NMI

NMI: two bytes to Tube

Transfer type 2. Sends two consecutive bytes from (data_transfer_addr) to Tube R3, incrementing the pointer after each byte.

FE22 .nmi_two_bytes_to_tube
PHA ; Save A
FE23 TYA ; Save Y
FE24 PHA ; Push Y
FE25 LDY #0 ; Y=0 for indirect indexed access
FE27 LDA (data_transfer_addr),y ; Read first byte via pointer
FE29 STA tube_r3_data ; Send to Tube R3
FE2C INC data_transfer_addr ; Increment transfer address low
FE2E BNE nmi2_second_byte ; No carry: skip high increment
FE30 INC data_transfer_addr_hi ; Increment transfer address high
FE32 .nmi2_second_byte←1← FE2E BNE
LDA (data_transfer_addr),y ; Read second byte via pointer
FE34 STA tube_r3_data ; Send to Tube R3
FE37 INC data_transfer_addr ; Increment transfer address low
FE39 BNE nmi2_done ; No carry: skip high increment
FE3B INC data_transfer_addr_hi ; Increment transfer address high
FE3D .nmi2_done←1← FE39 BNE
PLA ; Restore Y from stack
FE3E TAY ; Transfer to Y
FE3F PLA ; Restore A
FE40 RTI ; Return from NMI

NMI: two bytes from Tube

Transfer type 3. Reads two bytes from Tube R3 and stores them at (data_transfer_addr), incrementing the pointer after each byte.

FE41 .nmi_two_bytes_from_tube
PHA ; Save A
FE42 TYA ; Save Y
FE43 PHA ; Push Y
FE44 LDY #0 ; Y=0 for indirect indexed access
FE46 LDA tube_r3_data ; Read first byte from Tube R3
FE49 STA (data_transfer_addr),y ; Store via transfer address pointer
FE4B INC data_transfer_addr ; Increment transfer address low
FE4D BNE nmi3_second_byte ; No carry: skip high increment
FE4F INC data_transfer_addr_hi ; Increment transfer address high
FE51 .nmi3_second_byte←1← FE4D BNE
LDA tube_r3_data ; Read second byte from Tube R3
FE54 STA (data_transfer_addr),y ; Store via transfer address pointer
FE56 INC data_transfer_addr ; Increment transfer address low
FE58 BNE nmi3_done ; No carry: skip high increment
FE5A INC data_transfer_addr_hi ; Increment transfer address high
FE5C .nmi3_done←1← FE58 BNE
PLA ; Restore Y from stack
FE5D TAY ; Transfer to Y
FE5E PLA ; Restore A
FE5F RTI ; Return from NMI

Transfer address pointer table (low bytes)

Eight entries indexed by transfer type (0-7). Each entry is the low byte of the address of the two-byte field that holds the current transfer address for that type. Types 0-1 and 6-7 point to self-modifying code address operands; types 2-5 point to the data_transfer_addr zero page location (&F6).

FE60 .transfer_addr_ptr_table←1← FD79 LDA
EQUB <(nmi0_transfer_addr) ; Low bytes of transfer addr pointers
FE61 EQUB <(nmi1_transfer_addr)
FE62 EQUB <(data_transfer_addr)
FE63 EQUB <(data_transfer_addr)
FE64 EQUB <(data_transfer_addr)
FE65 EQUB <(data_transfer_addr)
FE66 EQUB <(nmi6_transfer_addr)
FE67 EQUB <(nmi7_transfer_addr)

Transfer address pointer table (high bytes)

High bytes corresponding to the low byte table at &FE60. Together they form 8 pointers to the address fields used by each transfer type.

FE68 .transfer_addr_ptr_hi_table←1← FD7E LDA
EQUB >(nmi0_transfer_addr) ; High bytes of transfer addr pointers
FE69 EQUB >(nmi1_transfer_addr)
FE6A EQUB >(data_transfer_addr)
FE6B EQUB >(data_transfer_addr)
FE6C EQUB >(data_transfer_addr)
FE6D EQUB >(data_transfer_addr)
FE6E EQUB >(nmi6_transfer_addr)
FE6F EQUB >(nmi7_transfer_addr)

NMI routine address table (low bytes)

Eight entries indexed by transfer type (0-7). Each entry is the low byte of the NMI handler routine for that type. Types 0-3 have dedicated handlers; types 4-7 all use the NMI acknowledge routine.

FE70 .nmi_routine_addr_table←1← FD6D LDA
EQUB <(nmi_single_byte_to_tube) ; Low bytes of NMI handler addresses
FE71 EQUB <(nmi_single_byte_from_tube)
FE72 EQUB <(nmi_two_bytes_to_tube)
FE73 EQUB <(nmi_two_bytes_from_tube)
FE74 EQUB <(nmi_acknowledge)
FE75 EQUB <(nmi_acknowledge)
FE76 EQUB <(nmi_acknowledge)
FE77 EQUB <(nmi_acknowledge)

NMI routine address table (high bytes)

High bytes corresponding to the low byte table at &FE70. Together they form 8 NMI handler addresses.

FE78 .nmi_routine_addr_hi_table←1← FD73 LDA
EQUB >(nmi_single_byte_to_tube) ; High bytes of NMI handler addresses
FE79 EQUB >(nmi_single_byte_from_tube)
FE7A EQUB >(nmi_two_bytes_to_tube)
FE7B EQUB >(nmi_two_bytes_from_tube)
FE7C EQUB >(nmi_acknowledge)
FE7D EQUB >(nmi_acknowledge)
FE7E EQUB >(nmi_acknowledge)
FE7F EQUB >(nmi_acknowledge)

Wait for byte in Tube R1

Wait for data in Tube R1, allowing Tube R4 transfer requests to be serviced via IRQ while waiting.

Polls R1 status; if R4 has data instead, briefly enables interrupts to let the R4 handler run, then resumes polling R1.

On ExitAbyte from Tube R1
FE80 .wait_for_tube_r1_byte←5← FD21 JSR← FD25 JSR← FD29 JSR← FE88 BPL← FE91 JMP
BIT tube_r1_status ; Check Tube R1 for data
FE83 BMI tube_r1_read_byte ; Data available: go read it
FE85 BIT tube_r4_status ; Check Tube R4 for pending transfers
FE88 BPL wait_for_tube_r1_byte ; Nothing pending: keep polling R1
FE8A LDA irq_a_store ; Save irq_a_store before IRQ
FE8C PHP ; Save processor status
FE8D CLI ; Allow one IRQ to service R4
FE8E PLP ; Restore processor status
FE8F STA irq_a_store ; Restore irq_a_store after IRQ
FE91 JMP wait_for_tube_r1_byte ; Continue polling R1
FE94 .tube_r1_read_byte←1← FE83 BMI
LDA tube_r1_data ; Read byte from Tube R1
FE97 RTS ; Return with byte in A

Print inline text

Print the text string embedded immediately after the JSR to this routine. Characters are sent to OSWRCH until a byte with bit 7 set is encountered, which terminates the string.

Execution resumes by jumping directly to the address of the terminator byte, so the terminator is executed as a 6502 instruction. This is why NOP (&EA) is used: bit 7 is set (ending the string loop), it is a single- byte opcode (no operand to skip), and it has no effect when executed. This saves the bytes and cycles that would otherwise be needed to increment the pointer past the terminator before returning.

On ExitAterminator byte (bit 7 set)
FE98 .print_embedded_text←2← F860 JSR← FA17 JSR
PLA ; Pull return address low
FE99 STA control_block_ptr ; Store in control_block_ptr low
FE9B PLA ; Pull return address high
FE9C STA control_block_ptr_hi ; Store in control_block_ptr high
FE9E LDY #0 ; Y=0 for indirect access
FEA0 .print_text_loop←1← FEAD JMP
INC control_block_ptr ; Increment string pointer low
FEA2 BNE print_text_get_char ; No carry: skip high byte
FEA4 .print_text_inc_high
INC control_block_ptr_hi ; Increment string pointer high
FEA6 .print_text_get_char←1← FEA2 BNE
LDA (control_block_ptr),y ; Read character from inline string
FEA8 BMI print_text_resume ; Bit 7 set: end of string
FEAA JSR oswrch_entry ; Print character via OSWRCH
FEAD JMP print_text_loop ; Loop for next character
FEB0 .print_text_resume←1← FEA8 BMI
JMP (control_block_ptr) ; Jump to terminator byte and execute it

NMI acknowledge

Acknowledge an NMI by writing A to Tube R3, then return from interrupt. Used as the NMI handler for transfer types 4-7 (release and 256-byte block transfers, which do not use NMI for individual bytes). Also the initial value of the NMI vector at reset.

FEB3 .nmi_acknowledge
STA tube_r3_data ; Write to Tube R3 to acknowledge NMI
FEB6 RTI

Unused fill between code and I/O window

39 bytes of &FF fill between the end of the NMI acknowledge routine and the start of the Tube ULA I/O register window at &FEF0.

FEB7 .unused_fill_pre_io
EQUB &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF

Tube ULA I/O window

Hardware registers overlay these ROM addresses. The ROM bytes here are never read by the CPU. The registers at &FEF0-&FEF7 mirror &FEF8-&FEFF but are not used by the Tube Client software.

FEF0 .tube_ula_io_window
EQUB &FF ; Tube R1 status (mirror, not used)
FEF1 EQUB &FF ; Tube R1 data (mirror, not used)
FEF2 EQUB &FF ; Tube R2 status (mirror, not used)
FEF3 EQUB &FF ; Tube R2 data (mirror, not used)
FEF4 EQUB &FF ; Tube R3 status (mirror, not used)
FEF5 EQUB &FF ; Tube R3 data (mirror, not used)
FEF6 EQUB &FF ; Tube R4 status (mirror, not used)
FEF7 EQUB &FF ; Tube R4 data (mirror, not used)
FEF8 .tube_r1_status←4← 0100 LDA← F962 BIT← FCF5 BIT← FE80 BIT
EQUB &FF ; Tube register 1 status
FEF9 .tube_r1_data←3← F968 STA← FD18 LDA← FE94 LDA
EQUB &FF ; Tube register 1 data
FEFA .tube_r2_status←25← F975 BIT← F9B8 BIT← FA7A BIT← FA82 BIT← FA8B BIT← FA93 BIT← FAAB BIT← FAB3 BIT← FABB BIT← FAC4 BIT← FAD5 BIT← FADF BIT← FAE7 BIT← FB09 BIT← FB11 BIT← FB2D BIT← FB38 BIT← FB59 BIT← FB64 BIT← FB7E BIT← FB96 BIT← FBB2 BIT← FBD2 BIT← FC4A BIT← FD45 BIT
EQUB &FF ; Tube register 2 status
FEFB .tube_r2_data←25← F97A LDA← F9BF STA← FA7F STA← FA87 STX← FA90 STA← FA98 LDX← FAB0 STA← FAB8 STX← FAC0 STY← FAC9 STA← FADA LDA← FAE4 LDY← FAEC LDX← FB0E STY← FB16 STA← FB32 STY← FB3F STA← FB5E STY← FB69 LDA← FB85 STA← FB9B STY← FBB7 LDA← FBD7 STY← FC4F STA← FD4A LDA
EQUB &FF ; Tube register 2 data
FEFC .tube_r3_status←3← FDCF LDA← FDDF BIT← FDEE LDA
EQUB &FF ; Tube register 3 status
FEFD .tube_r3_data←12← FDB8 BIT← FDBB BIT← FDD9 STA← FDE4 STA← FDF5 LDA← FE04 STA← FE12 LDA← FE29 STA← FE34 STA← FE46 LDA← FE51 LDA← FEB3 STA
EQUB &FF ; Tube register 3 data
FEFE .tube_r4_status←8← FCF0 BIT← FD83 BIT← FD93 BIT← FD9B BIT← FDA3 BIT← FDAE BIT← FDBE BIT← FE85 BIT
EQUB &FF ; Tube register 4 status
FEFF .tube_r4_data←7← FD3F LDA← FD88 LDA← FD98 LDA← FDA0 LDA← FDA8 LDA← FDB3 LDA← FDC3 LDA
EQUB &FF ; Tube register 4 data

Unused fill in lower page &FF

The reset code copies all of page &FF to RAM with LDA/STA &FF00,X but only &FF80 onwards contains the default vector table and MOS entry points.

FF00 .unused_fill_page_ff←2← F802 LDA← F805 STA
EQUB &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF

Default MOS vector table

27 two-byte entries (54 bytes) copied to &0200-&0235 at reset. Each entry is the initial value for the corresponding MOS vector. Vectors for unimplemented functions point to the 'unsupported' error handler; unused event and indirect vectors point to null_return (RTS).

FF80 .default_vector_table←1← F80D LDA
EQUW unsupported ; USERV - User vector
FF82 EQUW error_handler ; BRKV - BRK vector
FF84 EQUW irq1_handler ; IRQ1V - Primary IRQ handler
FF86 EQUW unsupported ; IRQ2V - Secondary IRQ handler
FF88 EQUW oscli_impl ; CLIV - OSCLI vector
FF8A EQUW osbyte_impl ; BYTEV - OSBYTE vector
FF8C EQUW osword_impl ; WORDV - OSWORD vector
FF8E EQUW oswrch_impl ; WRCHV - OSWRCH vector
FF90 EQUW osrdch_impl ; RDCHV - OSRDCH vector
FF92 EQUW osfile_impl ; FILEV - OSFILE vector
FF94 EQUW osargs_impl ; ARGSV - OSARGS vector
FF96 EQUW osbget_impl ; BGetV - OSBGET vector
FF98 EQUW osbput_impl ; BPutV - OSBPUT vector
FF9A EQUW osgbpb_impl ; GBPBV - OSGBPB vector
FF9C EQUW osfind_impl ; FINDV - OSFIND vector
FF9E EQUW unsupported ; FSCV - FS control vector
FFA0 EQUW null_return ; EVNTV - Event vector
FFA2 EQUW unsupported ; UPTV - User print vector
FFA4 EQUW unsupported ; NETV - Network vector
FFA6 EQUW unsupported ; VduV - Unrecognised VDU vector
FFA8 EQUW unsupported ; KEYV - Keyboard vector
FFAA EQUW unsupported ; INSV - Insert buffer vector
FFAC EQUW unsupported ; RemV - Remove buffer vector
FFAE EQUW unsupported ; CNPV - Count/purge buffer vector
FFB0 EQUW null_return ; IND1V - Spare indirect vector 1
FFB2 EQUW null_return ; IND2V - Spare indirect vector 2
FFB4 EQUW null_return ; IND3V - Spare indirect vector 3

Vector table descriptor

Three-byte block at &FFB6 used by OSBYTE &FD (read vector table info). Byte 0 = length of the vector table in bytes (&36 = 54 = 27 vectors). Bytes 1-2 = address of the default vector table (&FF80).

FFB6 .vector_table_info
EQUB &36 ; Vector table: length &36 at &FF80 Vector count in bytes (&36 = 27 words)
FFB7 EQUW default_vector_table ; Address of default vector table

MOS entry: unsupported (5 stubs at &FFB9-&FFC7)

Five consecutive JMP unsupported stubs at &FFB9, &FFBC, &FFBF, &FFC2, &FFC5. In the v1.10 external ROM, all five generate a 'Bad' error. These addresses are reserved for MOS compatibility but have no function in this Tube Client.

FFB9 .mos_stub_unsupported_1
JMP unsupported ; Unsupported: generates 'Bad' error
FFBC .mos_stub_unsupported_2
JMP unsupported
FFBF .mos_stub_unsupported_3
JMP unsupported
FFC2 .mos_stub_unsupported_4
JMP unsupported
FFC5 .mos_stub_unsupported_5
JMP unsupported

MOS entry: NVRDCH (non-vectored RDCH)

Jump directly to the OSRDCH implementation, bypassing RDCHV. Used when the caller needs to ensure the real RDCH handler runs regardless of any vector interception.

FFC8 .nvrdch
JMP osrdch_impl ; Non-vectored RDCH

MOS entry: NVWRCH (non-vectored WRCH)

Jump directly to the OSWRCH implementation, bypassing WRCHV. Used when the caller needs to ensure the real WRCH handler runs regardless of any vector interception.

FFCB .nvwrch
JMP oswrch_impl ; Non-vectored WRCH

MOS entry: OSFIND

Open or close a file. Dispatches via FINDV (&021C).

On EntryA0 to close, non-zero to open
Yhandle (close) or XY => filename (open)
On ExitAfile handle (open) or preserved (close)
FFCE .osfind_entry
JMP (findv) ; Dispatch via FINDV

MOS entry: OSGBPB

Multiple-byte get or put. Dispatches via GBPBV (&021A).

On EntryAfunction
XY13-byte control block address
FFD1 .osgbpb_entry
JMP (gbpbv) ; Dispatch via GBPBV

MOS entry: OSBPUT

Write a byte to an open file. Dispatches via BPUTV (&0218).

On EntryAbyte to write
Yfile handle
FFD4 .osbput_entry
JMP (bputv) ; Dispatch via BPUTV

MOS entry: OSBGET

Read a byte from an open file. Dispatches via BGETV (&0216).

On EntryYfile handle
On ExitAbyte read
Cset if EOF
FFD7 .osbget_entry
JMP (bgetv) ; Dispatch via BGETV

MOS entry: OSARGS

Read or write open file arguments. Dispatches via ARGSV (&0214).

On EntryAfunction
Xzero-page data word address
Yhandle
FFDA .osargs_entry
JMP (argsv) ; Dispatch via ARGSV

MOS entry: OSFILE

Whole-file operations. Dispatches via FILEV (&0212).

On EntryAfunction
XY18-byte control block address
FFDD .osfile_entry
JMP (filev) ; Dispatch via FILEV

MOS entry: OSRDCH

Read a character from the input stream. Dispatches via RDCHV (&0210).

On ExitAcharacter
Cset if Escape
FFE0 .osrdch_entry
JMP (rdchv) ; Dispatch via RDCHV

MOS entry: OSASCI

Write a character, converting CR to CR+LF. If the character is &0D, falls through to OSNEWL to send LF then CR. Otherwise jumps directly to OSWRCH.

On EntryAcharacter to write
FFE3 .osasci_entry
CMP #&0d ; Is it carriage return?
FFE5 BNE oswrch_entry ; No: skip newline, go to OSWRCH
fall through ↓

MOS entry: OSNEWL

Send a newline sequence (LF followed by CR) via OSWRCH. Loads A=&0A, calls OSWRCH, then falls through to OSWRCR which loads A=&0D and calls OSWRCH.

FFE7 .osnewl_entry←2← F948 JSR← F957 JSR
LDA #&0a ; Send linefeed first
FFE9 JSR oswrch_entry ; Send linefeed via OSWRCH
fall through ↓

MOS entry: OSWRCR

Send a carriage return via OSWRCH. Loads A=&0D and falls through to OSWRCH.

FFEC .oswrcr_entry
LDA #&0d ; Load carriage return
fall through ↓

MOS entry: OSWRCH

Write a character to the output stream. Dispatches via WRCHV (&020E).

On EntryAcharacter to write
On ExitApreserved
FFEE .oswrch_entry←5← F88F JSR← F951 JSR← FEAA JSR← FFE5 BNE← FFE9 JSR
JMP (wrchv) ; Dispatch via WRCHV

MOS entry: OSWORD

Perform a word-based MOS operation. Dispatches via WORDV (&020C).

On EntryAfunction
XYcontrol block address
FFF1 .osword_entry←1← F898 JSR
JMP (wordv) ; Dispatch via WORDV

MOS entry: OSBYTE

Perform a byte-based MOS operation. Dispatches via BYTEV (&020A).

On EntryAfunction
Xparameter 1
Yparameter 2
FFF4 .osbyte_entry←1← F8A9 JSR
JMP (bytev) ; Dispatch via BYTEV

MOS entry: OSCLI

Execute a star command. Dispatches via CLIV (&0208).

On EntryXYcommand string address (CR-terminated)
FFF7 .oscli_entry←1← F8A1 JSR
JMP (cliv) ; Dispatch via CLIV
FFFA .nmi_vector←3← FD65 STA← FD6A LDY← FD70 STA
EQUW nmi_acknowledge ; NMI vector (patched at runtime)
FFFC .reset_vector
EQUW reset ; RESET vector
FFFE .irq_vector
EQUW interrupt_handler ; IRQ/BRK vector