Skip to content

Instantly share code, notes, and snippets.

@ppe
Created October 10, 2020 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ppe/2aa660410a966032b48194348feb5fd4 to your computer and use it in GitHub Desktop.
Save ppe/2aa660410a966032b48194348feb5fd4 to your computer and use it in GitHub Desktop.
Sinclair QL BlackBox xmodem receiver core
SOH equ 1
EOT equ 4
ACK equ 6
NAK equ 21
SUB equ 26
block_size equ 128
max_retries equ 20
status_eot equ -1
status_read_block_fail equ -2
res_ok equ 0
res_max_retries equ -1
err_nc equ -1
err_no equ -6 ; Channel not open
bv_chbas equ $30 ; Offset to channel table
bv_chp equ $34 ; Offset to channel table end
print_chan equ $10001 ; (Default) Channel ID of channel #1
; Call with an opened channel number in D1, i.e. do:
; "OPEN #3,ser1r" followed by "CALL address,3"
; The code will store received data starting at the end of this code
; So do a big enough ALCHP/RESPR for the code to have a large enough buffer
; for the received file, e.g.
; a=RESPR(32000):LBYTES xmodem_bin,a
; Will leave approx 31k buffer for the received file
; After receiving the file, inspect the memory locations below
; To get a status code, address of buffer and total number of
; bytes received and SBYTES, SEXEC the received binary
bra start
; Result code of the receive process, 0=OK
result_code
ds.w 1
; Start address of the receive buffer
start_address
ds.l 1
; Total number of bytes received
bytes_received
ds.l 1
;
; Static register allocation:
; A0 = channel id of serial channel
; D7 = response status to send to server
start
move.l d1,d0
bsr channel_id ; Get channel ID of channel # in D0 into A0
tst.l d0
bne exit
lea serchan_id,a1
move.l a0,(a1) ; Save the channel ID
lea retries,a1
move.w #0,(a1)
lea file_buffer,a1
lea start_address,a2
move.l a1,(a2)+
move.l #0,(a2) ; Total bytes received
lea data_pointer,a2
move.l a1,(a2)
lea result_code,a1
move.w #res_ok,(a1)
lea expected_block,a1
move.w #1,(a1)
moveq #NAK,d7
recv_loop
lea expected_block,a1
move.w (a1),d4
lea msg_recv_blk,a1
bsr print_msg_num
cmp.b #NAK,d7
bne send_response
bsr flush_input
send_response
move.b d7,d1 ; d1 = byte to be sent
moveq #5,d0 ; IO.SBYTE trap code
moveq #50,d3 ; Timeout of 1s
trap #3 ; Corrupts d1,a1
tst.b d0
bmi exit
moveq #NAK,d7
bsr read_block
cmp.w #status_eot,d0
beq end_of_transmission
tst.w d0
bpl check_block_no
; Block read error if negative
check_max_retries
lea retries,a1
move.w (a1),d1
addq.w #1,d1
cmp.w #max_retries,d1
bgt retries_exceeded
move.w d1,(a1)
lea msg_retry,a1
move.w d1,d4
bsr print_msg_num
bra recv_loop
check_block_no
lea expected_block,a1
move.w (a1),d1
cmp.b d1,d0
beq right_block_received
lea msg_out_of_sequence,a1
move.w d0,d4
bsr print_msg_num
bra check_max_retries
right_block_received
addq.w #1,d1
move.w d1,(a1)
lea retries,a1
move.w #0,(a1)
moveq #ACK,d7
bra recv_loop
; End recv_loop
read_block
bsr read_byte ; Read SOH or EOT, anything else is garbage
tst.l d0
bne read_block_fail
cmp.b #EOT,d1
beq read_block_eot
cmp.b #SOH,d1
bne read_block_fail
bsr read_byte ; Read block #
tst.l d0
bne read_block_fail
move.b d1,d6 ; Save block # to d6
bsr read_byte ; Read 255-block#
tst.l d0
bne read_block_fail
moveq #-1,d5
sub.b d1,d5 ; Inv block # = 255-block#
cmp.b d5,d6 ; Block # and inv block # should match
bne read_block_fail
lea data_pointer,a2
move.l (a2),a1
movea.l a1,a4
move.w #128,d2 ; Number of bytes to fetch
move.w #200,d3 ; Timeout in 1/50s ticks
moveq #3,d0 ; Trap code for IO_FSTRG
trap #3
tst.l d0
bne read_data_fail
cmp.w #128,d1 ; Check that 128 bytes were received
bne read_data_fail
move.l a1,(a2) ; Save updated data pointer
lea msg_bytes_fetched,a1
move.w d1,d4
bsr print_msg_num
bsr read_byte ; Checksum
tst.l d0
bne read_block_fail
move.w d1,d5
movea.l a4,a1
bsr calc_cksum
cmp.w d1,d5
bne cksum_fail ; Received and calculated checksums should match
moveq #0,d0
move.b d6,d0
rts
read_block_eot
moveq #status_eot,d0
rts
read_block_fail
moveq #status_read_block_fail,d0
rts
cksum_fail
lea msg_cksum_fail,a1
move.w d1,d4
bsr print_msg_num
moveq #status_read_block_fail,d0
rts
read_data_fail
lea msg_read_data_fail,a1
move.w d0,d4
bsr print_msg_num
moveq #status_read_block_fail,d0
rts
end_of_transmission
; Send final ACK
move.b #ACK,d1 ; d1 = byte to be sent
moveq #5,d0 ; IO.SBYTE trap code
moveq #50,d3 ; Timeout of 1s
trap #3 ; Corrupts d1,a1
; Ignore trap result
;
; Trim padding SUB characters from end
lea data_pointer,a2
movea.l (a2),a2
trim_loop
move.b -(a2),d1
cmp.b #SUB,d1
beq trim_loop
addq.l #1,a2
move.l a2,d0
; Calculate number of bytes read and store the result
lea file_buffer,a3
sub.l a3,d0
lea bytes_received,a1
move.l d0,(a1)
; Save final result code
lea result_code,a1
move.w #res_ok,(a1)
moveq #0,d0
rts
retries_exceeded
lea result_code,a1
move.w #res_max_retries,(a1)
moveq #0,d0
rts
; A0 = chanid
read_byte
moveq #50,d3
moveq #1,d0
trap #3
rts
calc_cksum
move.w #127,d0
moveq #0,d1
cksum_loop
add.b (a1)+,d1
dbra d0,cksum_loop
exit
rts
; This routine is from the excellent Qdosmsq wiki
; at http://www.qdosmsq.dunbar-it.co.uk/doku.php?id=qdosmsq:sbinternal:chantab
channel_id
mulu #$28,d0 ; Offset into channel table
add.l bv_chbas(a6),d0 ; Add table start address
cmp.l bv_chp(a6),d0 ; Valid?
bge.s ch_bad ; No, channel # off end of table
move.l 0(a6,d0.l),d0 ; Channel id
bmi.s ch_bad ; Channel closed
move.l d0,a0 ; We need id in A0
moveq #0,d0 ; No errors
rts ; Finished
ch_bad
moveq #err_no,d0 ; Channel not open (-6)
rts ; Bail out
; Parameter: A0 = channel ID
flush_input
moveq #50,d3 ; 1s timeout
flush_loop
moveq #1,d0 ; Trap code for IO_FBYTE
trap #3
tst.l d0
bmi flush_done
bra flush_loop
rts
flush_done
moveq #0,d0
rts
; Print a message followed by an integer to channel 1
; a1 = pointer to preamble text
; d4.w = integer to print
print_msg_num
move.l a0,-(a7)
movea.l #print_chan,a0
movea.w UT_MTEXT,a2
jsr (a2)
move.w d4,d1
movea.w UT_MINT,a2
jsr (a2)
lea msg_eol,a1a
movea.w UT_MTEXT,a2
jsr (a2)
movea.l (a7)+,a0
rts
cksum
ds.w 1
expected_block
ds.w 1
serchan_id
ds.l 1
retries
ds.w 1
data_pointer
ds.l 1
data_end_pointer
ds.l 1
msg_recv_blk
dc.w 8
dc.b 'Rcv blk '
msg_bytes_fetched
dc.w 12
dc.b 'Read bytes: '
msg_out_of_sequence
dc.w 15
dc.b 'Out of seq blk '
msg_retry
dc.w 6
dc.b 'Retry '
msg_read_data_fail
dc.w 18
dc.b 'Data read failed: '
msg_cksum
dc.w 7
dc.b 'Cksum: '
msg_calc_cksum
dc.w 12
dc.b 'Calc cksum: '
msg_cksum_fail
dc.w 12
dc.b 'Cksum fail: '
msg_eol
dc.w 1
dc.b 10
buffer
ds.l 4
header
ds.l 1
file_buffer
ds.w 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment