Acorn 6502 Tube Client 1.10 (external 65C02 3 MHz)
Updated 31 Mar 2026
← All Acorn 6502 Tube Client versions
- Disassembly source on GitHub
- Acorn 6502 Tube Client 1.10 in The BBC Micro ROM Library
- Discuss this disassembly on the Stardot Forums thread: Annotated disassembly of Acorn 6502 Tube Client ROM
- Found a mistake or a comment that could be clearer? Report an issue.
Power-on resetInitialise 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 initialisePrint 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 loopThe 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 addressCheck 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 codeEnter 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' errorSet 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' errorSet 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 handlerDefault 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 blockParameter 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 implementationSend character in A to the host via Tube R1.
|
|||||||
| 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 implementationRead a character from the host via the Tube. Sends command &00 to the host, then waits for a carry byte and the character.
|
||||||
| 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 byteWait 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.
|
||||||
| 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 R2Poll Tube R2 status until data is available, then read and return the byte.
|
||||
| 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 returnAn 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 stringIncrement Y then fall through to skip_spaces. Called when the current character has been consumed and any following spaces should be skipped.
|
|||||||||
| F97E | .skip_spaces_step←2← F983 BEQ← FA44 JSR | ||||||||
| INY ; Advance past current character | |||||||||
| fall through ↓ | |||||||||
Skip spaces in command stringAdvance past space characters in the string at (string_ptr),Y.
|
|||||||||
| 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 numberRead a hexadecimal number from the string at (string_ptr),Y into the hex accumulator at &F0/F1.
|
|||||||||||||||
| 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 R2Send a CR-terminated string to the host via Tube R2.
|
|||||||||
| 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 implementationExecute 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.
|
||||||
| 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 commandPrint 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 hostForward 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 acknowledgementWait 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 commandParse *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 stateSave 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 implementationHandle 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.
|
|||||||||||||||||
| 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 memoryReturn the current top of user memory from &F2/F3. Falls through to the RTS at osbyte_himem_rts.
|
||||||
| 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 memoryReturn the bottom of user memory, fixed at &0800. Shares its RTS with osbyte_read_high_word.
|
||||||
| 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 addressReturn &0000 as the high word of the address space. This indicates the 6502 has a 16-bit address space with no bank switching.
|
||||||
| 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 implementationHandle 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.
|
||||||
| 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
|
||||||
| 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 implementationRead or write information about an open file. Sends command &0C with handle, 4-byte data word, and function code. Receives result and updated data.
|
|||||||||||||
| 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 implementationOpen or close a file. For close (A=0): sends command &12, function, handle. For open (A<>0): sends command &12, function, filename.
|
|||||||||
| 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 implementationRead a byte from an open file. Sends command &0E with handle, waits for carry and byte.
|
|||||||||
| 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 implementationWrite a byte to an open file. Sends command &10 with handle and byte.
|
|||||||||
| 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 R2Wait for Tube R2 to be free, then send byte.
|
|||||||
| 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 implementationOperate 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.
|
||||||
| 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 implementationMultiple byte read and write. Sends command &16 with 13-byte control block and function. Receives updated control block, carry, and result.
|
||||||
| 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 callGenerate 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 tableIndexed 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 tableIndexed 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 entryHardware 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 handlerFirst-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 dispatchExtract 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 dataCalled 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 interruptProcess 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 NMIConfigure 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 interruptCommon 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 R3Read 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 TubeTransfer 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 TubeTransfer 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 TubeTransfer 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 TubeTransfer 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 R1Wait 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.
|
||||
| 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 textPrint 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.
|
||||
| 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 acknowledgeAcknowledge 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 window39 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 | |
| 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 | |
| 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 |
| 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 |
| 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: OSFINDOpen or close a file. Dispatches via FINDV (&021C).
|
|||||||||
| FFCE | .osfind_entry | ||||||||
| JMP (findv) ; Dispatch via FINDV | |||||||||
MOS entry: OSGBPBMultiple-byte get or put. Dispatches via GBPBV (&021A).
|
||||||
| FFD1 | .osgbpb_entry | |||||
| JMP (gbpbv) ; Dispatch via GBPBV | ||||||
MOS entry: OSBPUTWrite a byte to an open file. Dispatches via BPUTV (&0218).
|
||||||
| FFD4 | .osbput_entry | |||||
| JMP (bputv) ; Dispatch via BPUTV | ||||||
MOS entry: OSBGETRead a byte from an open file. Dispatches via BGETV (&0216).
|
|||||||||
| FFD7 | .osbget_entry | ||||||||
| JMP (bgetv) ; Dispatch via BGETV | |||||||||
MOS entry: OSARGSRead or write open file arguments. Dispatches via ARGSV (&0214).
|
||||||||
| FFDA | .osargs_entry | |||||||
| JMP (argsv) ; Dispatch via ARGSV | ||||||||
MOS entry: OSFILEWhole-file operations. Dispatches via FILEV (&0212).
|
||||||
| FFDD | .osfile_entry | |||||
| JMP (filev) ; Dispatch via FILEV | ||||||
MOS entry: OSRDCHRead a character from the input stream. Dispatches via RDCHV (&0210).
|
||||||
| FFE0 | .osrdch_entry | |||||
| JMP (rdchv) ; Dispatch via RDCHV | ||||||
MOS entry: OSASCIWrite 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.
|
||||
| FFE3 | .osasci_entry | |||
| CMP #&0d ; Is it carriage return? | ||||
| FFE5 | BNE oswrch_entry ; No: skip newline, go to OSWRCH | |||
| fall through ↓ | ||||
MOS entry: OSNEWLSend 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: OSWRCRSend 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: OSWRCHWrite a character to the output stream. Dispatches via WRCHV (&020E).
|
|||||||
| FFEE | .oswrch_entry←5← F88F JSR← F951 JSR← FEAA JSR← FFE5 BNE← FFE9 JSR | ||||||
| JMP (wrchv) ; Dispatch via WRCHV | |||||||
MOS entry: OSWORDPerform a word-based MOS operation. Dispatches via WORDV (&020C).
|
||||||
| FFF1 | .osword_entry←1← F898 JSR | |||||
| JMP (wordv) ; Dispatch via WORDV | ||||||
MOS entry: OSBYTEPerform a byte-based MOS operation. Dispatches via BYTEV (&020A).
|
||||||||
| FFF4 | .osbyte_entry←1← F8A9 JSR | |||||||
| JMP (bytev) ; Dispatch via BYTEV | ||||||||
MOS entry: OSCLIExecute a star command. Dispatches via CLIV (&0208).
|
||||
| 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 | ||||
