emailler/apps/telnet65.s
Oliver Schmidt 4577c2ab19 Removed Ethernet driver I/O base.
So far the base address of the Ethernet chip was a general property of all Ethernet drivers. It served two purposes:
1. Allowing to use a single Ethernet driver for a certain Ethernet chip, no matter what machine was connected to the chip.
2. Allowing use an Ethernet card in all Apple II slots.

However, we now use customized Ethernet drivers for the individual machines so 1.) isn't relevant anymore. In fact one wants to omit the overhead of a runtime-adjustable base address where it isn't needed.

So only the Apple II slots are left. But this should rather be a driver-internal approach then. We should just hand the driver the slot number the user wants to use and have the driver do its thing.

Independently from the aspect if the driver parameter is a base address or a slot number the parameter handling was changed too. For asm programs there was so far a specific init function to be called prior to the main init function if it was desired to chnage the parameter default. This was done to keep the main init function backward compatible. But now that the parameter (now the slot number) is only used on the Apple II anyhow it seems reasonable to drop the specific init function again and just provide the parameter to the main init function. All C64-only user code can stay as-is. Only Apple II user code needs to by adjusted. Please note that this change only affects asm programs, C programs always used a single init function with the Apple II slot number as parameter.
2019-05-02 14:44:24 +02:00

608 lines
14 KiB
ArmAsm

; minimal telnet implementation (dumb terminal emulation only)
.include "../inc/common.inc"
.include "../inc/commonprint.inc"
.include "../inc/net.inc"
.include "../inc/error.inc"
.export start
.import abort_key
.importzp abort_key_default
.importzp abort_key_disable
.importzp eth_init_default
.import get_filtered_input
.import get_key
.import get_key_if_available
.import exit_to_basic
.import filter_dns
.import filter_number
.import dns_hostname_is_dotted_quad
.import dns_ip
.import dns_resolve
.import dns_set_hostname
.import ip65_process
.import native_to_ascii
.import parse_integer
.import print_cr
.import tcp_callback
.import tcp_close
.import tcp_connect
.import tcp_connect_ip
.import tcp_inbound_data_ptr
.import tcp_inbound_data_length
.import tcp_send
.import tcp_send_data_len
.import tcp_send_keep_alive
.import timer_read
.import vt100_init_terminal
.import vt100_exit_terminal
.import vt100_process_inbound_char
.import vt100_process_outbound_char
.importzp vt100_screen_cols
.importzp vt100_screen_rows
.export telnet_close
.export telnet_send_char
.export telnet_send_string
buffer_ptr = sreg
; keep LD65 happy
.segment "INIT"
.segment "ONCE"
.segment "STARTUP"
jmp start
eth_init_value:
.byte eth_init_default
.code
start:
jsr vt100_init_terminal
; initialize stack
ldax #welcome_1
jsr print_vt100
ldax #welcome_2
jsr print_vt100
ldax #initializing
jsr print_ascii_as_native
lda eth_init_value
jsr ip65_init
bcc :+
ldax #device_not_found
jsr print_ascii_as_native
jmp error_exit
: ldax #eth_name
jsr print_ascii_as_native
jsr print_cr
; get IP addr
ldax #obtaining
jsr print_ascii_as_native
jsr dhcp_init
bcc :+
jsr print_error
jmp error_exit
: ldax #cfg_ip
jsr print_dotted_quad
jsr print_cr
telnet_main_entry:
; enter host name
ldax #remote_host
jsr print_ascii_as_native
ldy #40 ; max chars
ldax #filter_dns
jsr get_filtered_input
bcc :+
jmp exit
; set host name
: stax buffer_ptr
ldy #$ff
: iny
lda (buffer_ptr),y
jsr native_to_ascii
cmp #'a'
bcs :+
cmp #'A'
bcc :+
clc
adc #'a'-'A'
: sta (buffer_ptr),y
tax ; set Z flag
bne :--
ldax buffer_ptr
jsr dns_set_hostname
bcc :+
jsr print_error
jmp telnet_main_entry
; resolve host name
: lda dns_hostname_is_dotted_quad
bne :++
ldax #resolving
jsr print_ascii_as_native
jsr dns_resolve
bcc :+
jsr print_error
jmp telnet_main_entry
: ldax #dns_ip
jsr print_dotted_quad
; enter port
: ldax #remote_port
jsr print_ascii_as_native
ldy #5 ; max chars
ldax #filter_number
jsr get_filtered_input
bcs :+ ; empty -> default
jsr parse_integer
bcc :++
jmp telnet_main_entry
: ldax #23 ; default
: stax telnet_port
; connect
ldax #connecting
jsr print_ascii_as_native
ldax #dns_ip
jsr print_dotted_quad
ldax #blank
jsr print_ascii_as_native
ldax #telnet_callback
stax tcp_callback
ldx #3
: lda dns_ip,x
sta tcp_connect_ip,x
dex
bpl :-
ldax telnet_port
jsr tcp_connect
bcc :+
jsr print_error
jmp telnet_main_entry
; connected
: ldax #ok
jsr print_ascii_as_native
lda #0
sta connection_close_requested
sta connection_closed
sta data_received
sta iac_response_buffer_length
lda #abort_key_disable
sta abort_key
ldax #on_connect
jsr print_vt100
@main_polling_loop:
jsr timer_read
txa ; 1/1000 * 256 = ~ 1/4 seconds
adc #$20 ; 32 x 1/4 = ~ 8 seconds
sta telnet_timeout
@check_timeout:
lda data_received
bne :+
jsr timer_read
cpx telnet_timeout
bne :+
jsr tcp_send_keep_alive
jmp @main_polling_loop
: lda #0
sta data_received
jsr ip65_process
lda connection_close_requested
beq :+
jsr tcp_close
jmp :++
: lda connection_closed
beq :++
: lda #abort_key_default
sta abort_key
ldax #on_disconnect
jsr print_vt100
ldax #disconnected
jsr print_ascii_as_native
jmp telnet_main_entry
: lda iac_response_buffer_length
beq :+
ldx #0
stax tcp_send_data_len
stx iac_response_buffer_length
ldax #iac_response_buffer
jsr tcp_send
: jsr get_key_if_available
bcc @check_timeout
ldx #0
stx tcp_send_data_len
stx tcp_send_data_len+1
tay
jsr vt100_process_outbound_char
jmp @main_polling_loop
print_vt100:
stax buffer_ptr
lda #0
sta buffer_offset
: ldy buffer_offset
lda (buffer_ptr),y
bne :+
rts
: tay
jsr vt100_process_inbound_char
inc buffer_offset
jmp :--
print_error:
lda ip65_error
cmp #IP65_ERROR_ABORTED_BY_USER
bne :+
ldax #abort
jmp print_ascii_as_native
: cmp #IP65_ERROR_TIMEOUT_ON_RECEIVE
bne :+
ldax #timeout
jmp print_ascii_as_native
: ldax #error_prefix
jsr print_ascii_as_native
lda ip65_error
jsr print_hex
jmp print_cr
error_exit:
ldax #press_a_key_to_continue
jsr print_ascii_as_native
jsr get_key
exit:
jsr vt100_exit_terminal
jmp exit_to_basic
; vt100 callback - will be executed when the user requests to close the connection
telnet_close:
lda #1
sta connection_close_requested
rts
; vt100 callback - will be executed when sending a char string
telnet_send_string:
stx buffer_ptr
sty buffer_ptr+1
ldy #0
: lda (buffer_ptr),y
beq send_char
sta scratch_buffer,y
inc tcp_send_data_len
iny
bne :-
jmp send_char
; vt100 callback - will be executed when sending a single char
telnet_send_char:
ldy tcp_send_data_len
sta scratch_buffer,y
inc tcp_send_data_len
send_char:
ldax #scratch_buffer
jsr tcp_send
bcs :+
rts
: lda ip65_error
cmp #IP65_ERROR_CONNECTION_CLOSED
bne :+
lda #1
sta connection_closed
rts
: ldax #send_error
jsr print_ascii_as_native
jmp print_error
; tcp callback - will be executed whenever data arrives on the TCP connection
telnet_callback:
lda #1
ldx tcp_inbound_data_length+1
cpx #$ff
bne :+
sta connection_closed
rts
: sta data_received
lda tcp_inbound_data_length
stax buffer_length
ldax tcp_inbound_data_ptr
stax buffer_ptr
@next_byte:
ldy #0
lda (buffer_ptr),y
tax
; if we get here, we are in ASCII 'char at a time' mode,
; so look for (and process) Telnet style IAC bytes
lda telnet_state
cmp #telnet_state_got_command
bne :+
jmp @waiting_for_option
: cmp #telnet_state_got_iac
beq @waiting_for_command
cmp #telnet_state_got_suboption
beq @waiting_for_suboption_end
; we must be in 'normal' mode
txa
cmp #255
beq :+
jmp @not_iac
: lda #telnet_state_got_iac
sta telnet_state
jmp @byte_processed
@waiting_for_suboption_end:
txa
ldx iac_suboption_buffer_length
sta iac_suboption_buffer,x
inc iac_suboption_buffer_length
cmp #$f0 ; SE - suboption end
bne @exit_suboption
lda #telnet_state_normal
sta telnet_state
lda iac_suboption_buffer
cmp #$18
bne @not_terminal_type
ldx #0
: lda terminal_type_response,x
ldy iac_response_buffer_length
inc iac_response_buffer_length
sta iac_response_buffer,y
inx
txa
cmp #terminal_type_response_length
bne :-
@not_terminal_type:
@exit_suboption:
jmp @byte_processed
@waiting_for_command:
txa
sta telnet_command
cmp #$fa ; SB - suboption begin
beq @suboption
cmp #$fb ; WILL
beq @option
cmp #$fc ; WONT
beq @option
cmp #$fd ; DO
beq @option
cmp #$fe ; DONT
beq @option
; we got a command we don't understand - just ignore it
lda #telnet_state_normal
sta telnet_state
jmp @byte_processed
@suboption:
lda #telnet_state_got_suboption
sta telnet_state
lda #0
sta iac_suboption_buffer_length
jmp @byte_processed
@option:
lda #telnet_state_got_command
sta telnet_state
jmp @byte_processed
@waiting_for_option:
; we have now got IAC, <command>, <option>
txa
sta telnet_option
lda telnet_command
cmp #$fb
beq @iac_will
cmp #$fc
beq @iac_wont
cmp #$fe
beq @iac_dont
; if we get here, then it's a "do"
lda telnet_option
cmp #$18 ; terminal type
beq @do_terminaltype
cmp #$1f
beq @do_naws
; if we get here, then it's a "do" command we don't honour
@iac_dont:
lda #$fc ; WONT
@add_iac_response:
ldx iac_response_buffer_length
sta iac_response_buffer+1,x
lda #$ff
sta iac_response_buffer,x
lda telnet_option
sta iac_response_buffer+2,x
inc iac_response_buffer_length
inc iac_response_buffer_length
inc iac_response_buffer_length
@after_set_iac_response:
lda #telnet_state_normal
sta telnet_state
jmp @byte_processed
@iac_will:
lda telnet_option
cmp #$01 ; ECHO
beq @will_echo
cmp #$03 ; DO SUPPRESS GA
beq @will_suppress_ga
@iac_wont:
lda #$fe ; DONT
jmp @add_iac_response
@will_echo:
lda #$fd ; DO
jmp @add_iac_response
@will_suppress_ga:
lda #$fd ; DO
jmp @add_iac_response
@do_naws:
ldx #0
: lda naws_response,x
ldy iac_response_buffer_length
inc iac_response_buffer_length
sta iac_response_buffer,y
inx
txa
cmp #naws_response_length
bne :-
jmp @after_set_iac_response
@do_terminaltype:
lda #$fb ; WILL
jmp @add_iac_response
@not_iac:
txa
tay
jsr vt100_process_inbound_char
@byte_processed:
inc buffer_ptr
bne :+
inc buffer_ptr+1
: lda buffer_length+1
beq :++
lda buffer_length
bne :+
dec buffer_length+1
: dec buffer_length
jmp @next_byte
: dec buffer_length
beq :+
jmp @next_byte
: rts
.rodata
blank: .byte " ",0
initializing: .byte 10,"Initializing ",0
obtaining: .byte "Obtaining IP address ",0
resolving: .byte 10,"Resolving to address ",0
connecting: .byte 10,"Connecting to ",0
ok: .byte "Ok",10,10,0
device_not_found: .byte "- Device not found",10,0
abort: .byte "- User abort",10,0
timeout: .byte "- Timeout",10,0
error_prefix: .byte "- Error $",0
remote_host: .byte 10,"Hostname (leave blank to quit)",10,"? ",0
remote_port: .byte 10,10,"Port Num (leave blank for default)",10,"? ",0
disconnected: .byte 10,"Disconnected",10,0
send_error: .byte "Sending ",0
welcome_1: .byte 27,")0"
.byte 14,"lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10
.byte 14,"x",15," ",27,"[1m","Telnet65 v1.2",27,"[0m"," based on: ",14,"x"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10,0
welcome_2: .byte 14,"x",15," - IP65 (github.com/cc65/ip65) ",14,"x"
.byte 15,13,10
.byte 14,"x",15," - CaTer (www.opppf.de/Cater) ",14,"x"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10
.byte 14,"mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"
.byte 15,13,10
.byte 27,")A"
.byte 27,"[?25l",0
on_connect: .byte 27,"[?25h",0
on_disconnect: .byte 27,"[?25l",27,"[0m",27,"(A",15,0
; initial_telnet_options:
; .byte $ff,$fb,$1F ; IAC WILL NAWS
; .byte $ff,$fb,$18 ; IAC WILL TERMINAL TYPE
; initial_telnet_options_length = *-initial_telnet_options
terminal_type_response:
.byte $ff ; IAC
.byte $fa ; SB
.byte $18 ; TERMINAL TYPE
.byte $0 ; IS
.byte "vt100" ; what we pretend to be
.byte $ff ; IAC
.byte $f0 ; SE
terminal_type_response_length = *-terminal_type_response
naws_response:
.byte $ff,$fb,$1f ; IAC WILL NAWS
.byte $ff ; IAC
.byte $fa ; SB
.byte $1f ; NAWS
.byte $00 ; width (high byte)
.byte vt100_screen_cols ; width (low byte)
.byte $00 ; height (high byte)
.byte vt100_screen_rows ; height (low byte)
.byte $ff ; IAC
.byte $f0 ; SE
naws_response_length = *-naws_response
.bss
; variables
telnet_port: .res 2 ; port number to connect to
telnet_timeout: .res 1
connection_close_requested: .res 1
connection_closed: .res 1
data_received: .res 1
buffer_offset: .res 1
telnet_command: .res 1
telnet_option: .res 1
telnet_state_normal = 0
telnet_state_got_iac = 1
telnet_state_got_command = 2
telnet_state_got_suboption = 3
buffer_length: .res 2
telnet_state: .res 1
iac_response_buffer: .res 64
iac_response_buffer_length: .res 1
scratch_buffer : .res 40
iac_suboption_buffer: .res 64
iac_suboption_buffer_length: .res 1
; -- LICENSE FOR telnet.s --
; The contents of this file are subject to the Mozilla Public License
; Version 1.1 (the "License"); you may not use this file except in
; compliance with the License. You may obtain a copy of the License at
; http://www.mozilla.org/MPL/
;
; Software distributed under the License is distributed on an "AS IS"
; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
; License for the specific language governing rights and limitations
; under the License.
;
; The Original Code is ip65.
;
; The Initial Developer of the Original Code is Jonno Downes,
; jonno@jamtronix.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Jonno Downes. All Rights Reserved.
; -- LICENSE END --