Created
October 10, 2020 16:25
-
-
Save ppe/2aa660410a966032b48194348feb5fd4 to your computer and use it in GitHub Desktop.
Sinclair QL BlackBox xmodem receiver core
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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