xmodem.s
1 ; CCGMS Terminal 2 ; 3 ; Copyright (c) 2016,2020, Craig Smith, alwyz, Michael Steil. All rights reserved. 4 ; This project is licensed under the BSD 3-Clause License. 5 ; 6 ; XMODEM, XMODEM-CRC and XMODEM-1K Send and Receive 7 ; 8 9 MAX_RETRIES = 10 10 PAYLOAD_SIZE_128 = 128 11 PAYLOAD_SIZE_1K = 1024 12 13 ; KERNAL 14 STATUS = $90 ; channel I/O error/EOF indicator 15 RIDBE = $029b 16 RIDBS = $029c 17 RODBS = $029d 18 RODBE = $029e 19 20 ; protocol constants 21 SOH = $01 ; Start of Heading 22 STX_ = $02 ; Start of Heading (1K blocks) 23 EOT = $04 ; End of Transmission 24 ACK = $06 ; Acknowledge 25 NAK = $15 ; Negative Acknowledge 26 CAN = $18 ; Cancel 27 CRC = 'C' ; sent by receiver as first char instead of NAK to indicate CRC instead of checksum 28 CPMEOF = $1a ; CP/M EOF character 29 30 ; final status of the transfer 31 STAT_OK = 0 ; transfer OK 32 STAT_CANCELLED = 1 ; peer sent CAN or didn't respond in time 33 STAT_NO_EOT_ACK = 2 ; during send, receiver did not ack the EOT signal 34 STAT_MAX_RETRIES = 3 ; too many errors retrying receiving a block 35 STAT_SYNC_LOST = 4 ; sender sent the wrong block 36 STAT_USER_ABORTED = 5 ; the user aborted the transfer 37 38 ; memory 39 xmobuf = $fd ; zero page pointer to access the buffer 40 41 ; uses the following KERNAL calls: 42 ; chkin 43 ; chkout 44 ; close 45 ; clrchn 46 ; getin 47 48 ; calls from outside code: 49 ; xmodem_download download, will jump back to main 50 ; xmodem_upload upload, will jump back to main 51 ; xmmrtc increment counter, generic code 52 ; symbols used from outside code 53 ; xmodel receive timeout, reused var 54 ; rtca0 counter, see xmmrtc 55 ; rtca2 " 56 ; rtca1 " 57 58 ; uses the following CCGMSTERM symbols: 59 ; ui_abort jumped to after a transfer has failed or was user-aborted 60 ; gong play sound when printing error 61 ; xfrdun jumped to after a transfer has succeeded 62 ; outstr print error message 63 ; clear232 clear buffer 64 ; modget receive byte 65 ; goobad show transmission status "key" to user 66 ; crclo pre-computed crc table 67 ; crchi pre-computed crc table 68 ; enablexfer enable serial driver 69 ; disablexfer disable serial driver 70 ; reset same as "enablexfer" (no punter dep) 71 ; protoc protocol (XMODEM, -CRC, -1K) 72 ; buffer contains 3 XMODEM buffers 73 74 _enablexfer = reset 75 76 xmstat .byte 0 ; final error code 77 xmoblk .byte 0 ; current block index 78 xmochk .byte 0 ; checksum 79 xmobad .byte 0 ; error counter 80 xmodel .byte 0 ; receive timeout 81 xmoend .byte 0 ; send: EOT flag (and EOT send counter), receive: protocol error counter 82 xmostk .byte $ff ; stack pointer 83 84 85 ;---------------------------------------------------------------------- 86 ; SEND 87 ; * The sender picks the block size - we use the "protoc" setting. 88 ; * The receiver picks checksum vs. CRC, we support both. 89 ;---------------------------------------------------------------------- 90 ; 91 ; | Receiver: checksum | Receiver: CRC | 92 ;---------------------|--------------------|----------------| 93 ; Setting: XMODEM | 128 checksum | 128 CRC16 |\sa 94 ; Setting: XMODEM-CRC | 128 checksum | 128 CRC16 |/me 95 ; Setting: XMODEM-1K | 1K checksum | 1K CRC16 | 96 ; 97 ; If the receiver does not support 1K, we *could* fall back to 128B, but 98 ; this would be tricky: 99 ; * A receiver that understands the "STX" code for 1K doesn't ACK it; it 100 ; just keeps receiving the block and ACKs at the end. 101 ; * A receiver that does not understand the "STX" code just ignores it, 102 ; receiving more bytes and hoping for a SOH, EOT or CAN. 103 ; Therefore, it's tricky to detect whether a receiver supports 1K blocks. 104 ; The common way of doing a fallback is to do a full retry with a 128B 105 ; after sending the 1K block, but: 106 ; * this is slow 107 ; * would require lots of extra logic to send the 1 KB worth of buffer 108 ; contents as eight 128B blocks, since we can't rewind the source file. 109 ;---------------------------------------------------------------------- 110 xmodem_send: 111 ; save stack pointer 112 tsx 113 stx xmostk 114 115 jsr init_transfer 116 jsr _enablexfer 117 118 lda protoc 119 cmp #PROTOCOL_XMODEM_1K 120 bne @b128 121 lda #0 122 ldx #>PAYLOAD_SIZE_1K 123 bne @contsz 124 @b128: lda #PAYLOAD_SIZE_128 125 ldx #1 126 @contsz: 127 sta firstpagebytes 128 stx pagectr 129 130 ; expect NAK (chksum) or 'C' (CRC) 131 @loop: 132 lda #6 ; 60 secs 133 jsr modem_get 134 beq :+ 135 @abort: jmp xmabrt ; timeout -> cancelled 136 : cmp #CAN 137 beq @abort ; CAN -> cancelled 138 cmp #NAK 139 beq @nakok 140 cmp #CRC 141 bne @loop 142 @nakok: sta xprotoc 143 144 @block_loop: 145 jsr setup_buffer 146 ldy #0 147 sty xmoend ; reset EOT flag 148 sty xmobad ; init error counter 149 150 jsr disablexfer 151 152 ; read block into buffer 153 ldx #LFN_FILE 154 jsr chkin 155 156 lda pagectr 157 sta tmppagectr 158 jsr crcinit 159 160 ldy #0 161 @snd2: jsr getin ; read from file 162 ldx STATUS 163 stx xmoend ; set EOT flag if end of file (or error) 164 @snd3: jsr store_byte 165 bne :+ 166 inc xmobuf+1 167 dec tmppagectr 168 beq @snd5 169 : ldx xmoend ; end of file? 170 beq @snd2 ; no, next byte 171 172 lda #CPMEOF ; EOF (or error) 173 bne @snd3 ; -> fill with end of file code 174 175 @snd5: 176 jsr clrchn 177 178 @send_again: 179 jsr clear_buffers 180 jsr enablexfer 181 182 ; send block header 183 lda #SOH 184 ldx protoc 185 cpx #PROTOCOL_XMODEM_1K 186 bne :+ 187 lda #STX_ 188 : jsr modput ; 0: SOH/STX (128/1K) 189 lda xmoblk 190 jsr modput ; 1: block index 191 eor #$ff 192 jsr modput ; 2: block index ^ $FF 193 194 jsr setup_buffer 195 196 ldx pagectr 197 ldy #0 198 : lda (xmobuf),y 199 jsr modput 200 iny 201 cpy firstpagebytes 202 bne :- 203 inc xmobuf+1 204 dex 205 bne :- 206 207 lda xprotoc 208 cmp #CRC 209 bne @ncrc 210 211 ; send CRC 212 lda crcz+1 213 jsr modput 214 lda crcz 215 jmp @send_crc_cont 216 @ncrc: 217 lda xmochk 218 @send_crc_cont: 219 jsr modput 220 221 jsr clrchn 222 jsr clear_input_buffer 223 224 ; expect CAN 225 lda #3 ; timeout 226 jsr modem_get 227 bne @snbd ; error 228 cmp #CAN 229 bne @snd8 230 jmp xmabrt ; CAN -> cancelled 231 232 @snd8: cmp #NAK 233 bne @snd9 234 235 @snbd: 236 jsr chrout ; ??? send to screen? 237 jmp @send_again ; NAK -> send again 238 239 @snd9: cmp #ACK 240 bne @snbd ; error 241 242 ; receiver ACK'ed the block 243 lda #'-' 244 jsr goobad 245 246 ldx xmoend ; was the end of the file reached? 247 bne :+ ; yes 248 249 inc xmoblk ; next block index 250 jmp @block_loop 251 252 : lda #0 253 sta xmoend ; reset EOT flag 254 255 ; send EOT 256 @sne1: 257 jsr enablexfer 258 lda #EOT 259 jsr modput ; send EOT 260 lda #3 ; timeout 261 jsr modem_get 262 bne :+ 263 cmp #ACK ; ACK received! 264 bne :+ 265 jmp xmfnok ; set status: OK 266 267 : inc xmoend 268 lda xmoend 269 cmp #MAX_RETRIES 270 bcc @sne1 ; retries... 271 jmp xmneot ; set status: no EOT ack 272 273 274 ;---------------------------------------------------------------------- 275 crcinit: 276 ; init checksum 277 lda #0 278 sta xmochk 279 ; init crc 280 sta crcz 281 sta crcz+1 282 rts 283 284 ;---------------------------------------------------------------------- 285 init_effect: 286 ; init screen effect 287 lda #<$0400 288 sta scrptr 289 lda #>$0400 290 sta scrptr+1 291 rts 292 293 ; store and checksum/CRC 294 store_byte: 295 ; store 296 sta (xmobuf),y 297 ; screen effect 298 scrptr=*+1 299 sta $ffff 300 ; calc checksum 301 pha 302 clc 303 adc xmochk 304 sta xmochk 305 pla 306 ; quick crc computation with lookup tables 307 eor crcz+1 308 tax 309 lda crcz 310 eor crchi,x 311 sta crcz+1 312 lda crclo,x 313 sta crcz 314 ; update screen effect 315 inc scrptr 316 bne :+ 317 inc scrptr+1 318 : lda scrptr 319 cmp #<($0400+14*40) 320 bne :+ 321 lda scrptr+1 322 cmp #>($0400+14*40) 323 bne :+ 324 jsr init_effect 325 : ; increment and compare 326 iny 327 firstpagebytes=*+1 328 cpy #$00 329 rts 330 331 ;---------------------------------------------------------------------- 332 init_transfer: 333 jsr init_effect 334 lda #1 335 sta xmoblk ; start block index 336 lda #0 337 sta xmobad 338 ;---------------------------------------------------------------------- 339 setup_buffer: 340 lda #<xmodem_buffer 341 sta xmobuf 342 lda #>xmodem_buffer 343 sta xmobuf+1 344 rts 345 346 ;---------------------------------------------------------------------- 347 clear_buffers: 348 lda RODBS ; clear rs232 output buffer 349 sta RODBE ; [XXX no driver has one, SwiftLink uses RODBS for something else!] 350 ;---------------------------------------------------------------------- 351 clear_input_buffer: 352 lda rtail ; clear rs232 input buffer 353 sta rhead 354 rts 355 356 ;---------------------------------------------------------------------- 357 modem_get_1: 358 lda #1 359 ; get modem byte with timeout 360 modem_get: 361 sta xmodel 362 lda #0 363 sta rtca1 364 sta rtca2 365 sta rtca0 366 367 @1: jsr modget ; receive byte 368 bcs :+ 369 ldx #0 ; ok 370 rts 371 : jsr xchkcm ; check for keyboard abort 372 jsr xmmrtc 373 lda rtca0 374 cmp xmodel 375 bcc @1 376 377 ; timeout 378 jsr clrchn 379 and #0 380 ldx #1 381 rts 382 383 ;---------------------------------------------------------------------- 384 ; timeout counter 385 RTCMOD = 72271 ; ??? 386 xmmrtc 387 ldx #0 388 inc rtca2 389 bne @1 390 inc rtca1 391 bne @1 392 inc rtca0 393 @1: sec 394 lda rtca2 395 sbc #RTCMOD >> 16 396 lda rtca1 397 sbc #>RTCMOD 398 lda rtca0 399 sbc #<RTCMOD 400 bcc @2 401 stx rtca0 402 stx rtca1 403 stx rtca2 404 @2: rts 405 406 rtca1 .byte 0 407 rtca2 .byte 0 408 rtca0 .byte 0 409 410 ;---------------------------------------------------------------------- 411 ; increment number of retries, and abort if maximum reached 412 count_bad: 413 lda #':' ; BAD 414 jsr goobad 415 inc xmobad 416 lda xmobad 417 cmp #MAX_RETRIES 418 bcs xmtrys ; max retries reached 419 rts 420 421 ;---------------------------------------------------------------------- 422 ; test for CBM key pressed (abort transfer) 423 xchkcm 424 ldx SHFLAG 425 cpx #SHFLAG_CBM 426 beq xmcmab 427 rts 428 429 ;---------------------------------------------------------------------- 430 ; set final status and clean up 431 xmfnok lda #'*' ; GOOD 432 jsr goobad 433 lda #STAT_OK 434 .byte $2c 435 xmabrt lda #STAT_CANCELLED 436 .byte $2c 437 xmneot lda #STAT_NO_EOT_ACK 438 .byte $2c 439 xmtrys lda #STAT_MAX_RETRIES 440 .byte $2c 441 xmsync lda #STAT_SYNC_LOST 442 .byte $2c 443 xmcmab lda #STAT_USER_ABORTED 444 sta xmstat 445 446 ; clear garbage off stack 447 ldx xmostk 448 txs 449 450 jsr clear_buffers 451 452 lda xmstat 453 cmp #STAT_SYNC_LOST 454 bcc @ex4 455 456 ; send CAN if STAT_SYNC_LOST and STAT_USER_ABORTED 457 jsr _enablexfer 458 459 ldy #8 460 lda #CAN 461 : jsr modput 462 dey 463 bpl :- ; 9x CAN 464 465 @ex4: jsr clrchn 466 jsr disablexfer 467 lda #LFN_FILE 468 jmp close ; close file on disk 469 470 471 ;---------------------------------------------------------------------- 472 ; RECEIVE 473 ;---------------------------------------------------------------------- 474 ; * The sender picks the block size, we support 128 and 1K. 475 ; * The receiver picks checksum vs. CRC. 476 ; 477 ; | Sender: 128 | Sender: 1K | 478 ;---------------------|--------------|-------------| 479 ; Setting: XMODEM | 128 checksum | 1K checksum | 480 ; Setting: XMODEM-CRC | 128 CRC16 | 1K CRC16 |\sa 481 ; Setting: XMODEM-1K | 128 CRC16 | 1K CRC16 |/me 482 ; 483 ;---------------------------------------------------------------------- 484 xmodem_receive: 485 tsx 486 stx xmostk 487 488 ; set block size & checksum based on protocol 489 lda #NAK ; XMODEM: send NAK before first block 490 ldx protoc 491 cpx #PROTOCOL_XMODEM 492 beq :+ 493 lda #CRC ; XMODEM-CRC and -1K: send 'C' before first block 494 : sta @receive_nak_code 495 496 jsr _enablexfer 497 jsr init_transfer 498 jmp :+ 499 @retry1: ; receive error 500 jsr count_bad 501 : lda #0 502 sta xmoend ; reset EOT counter 503 504 @retry2: 505 jsr clear232 506 jsr enablexfer 507 508 @receive_nak_code = *+1 509 lda #NAK 510 jsr modput 511 jsr clrchn 512 513 ; block loop 514 @bloop: jsr modem_get_1 515 beq @3 ; ok 516 517 @error: inc xmoend ; count protocol errors 518 lda xmoend 519 cmp #MAX_RETRIES 520 bcc @retry2 ; loop 521 @abort: jmp xmabrt ; cancelled 522 523 @3: cmp #CAN ; cancel? 524 beq @abort 525 cmp #EOT ; end of transmission 526 bne @neot 527 528 lda #1 529 sta xmoend ; EOT! 530 jmp @ackblk ; send ACK, end 531 532 @neot: cmp #SOH 533 bne @nsoh 534 535 lda #PAYLOAD_SIZE_128 536 ldx #1 537 bne @contsz 538 539 @nsoh: cmp #STX_ 540 bne @error ; no -> protocol error 541 542 lda #0 543 ldx #>PAYLOAD_SIZE_1K 544 545 @contsz: 546 sta firstpagebytes 547 stx pagectr 548 stx tmppagectr 549 550 ; get block index and duplicate 551 jsr modem_get_1 552 bne @retry1 553 sta block_index 554 jsr modem_get_1 555 bne @retry1 556 sta nblock_index 557 558 jsr crcinit 559 jsr setup_buffer 560 ldy #0 561 sty xmoend ; reset EOT flag 562 563 @rloop: jsr modem_get_1 564 jne @retry1 ; error 565 jsr store_byte 566 bne @rloop 567 568 inc xmobuf+1 569 dec tmppagectr 570 bne @rloop 571 572 ; validate block index from duplicate 573 lda block_index 574 eor nblock_index; block index ^ $FF 575 cmp #$ff 576 jne @retry1 ; incorrect 577 578 jsr modem_get_1 ; checksum byte or first CRC byte 579 jne @retry1 580 581 ldx protoc 582 cpx #PROTOCOL_XMODEM 583 beq @old_chksum 584 585 ; CRC check for XMODEM-CRC or -1K receive 586 pha 587 jsr modem_get_1 ; second CRC byte 588 jne @retry1 589 590 cmp crcz 591 bne @bad 592 pla 593 cmp crcz+1 594 beq @chksum_cont 595 @bad: jmp @retry1 596 597 ; compare old XMODEM checksum 598 @old_chksum: 599 cmp xmochk ; compare with transmitted checksum 600 jne @retry1 ; incorrect 601 602 ; check whether it's the expected block, a re-send, or the wrong block 603 @chksum_cont: 604 jsr disablexfer 605 606 lda block_index 607 cmp xmoblk ; expected block? 608 beq @okblk ; yes 609 610 ldx xmoblk 611 dex 612 txa 613 cmp block_index ; is it the preceding block again? 614 bne @xmorsa ; no, sync error 615 616 ; sender misunderstood our ACK, did a re-send; 617 ; we can just ignore the block 618 lda #'/' ; duplicate block 619 jsr goobad 620 jmp @next 621 622 @xmorsa jmp xmsync ; sync error 623 624 ; we just received the next block intact 625 @okblk: jsr clrchn 626 jsr disablexfer 627 628 ; write payload to disk 629 jsr setup_buffer 630 ldx #LFN_FILE 631 jsr chkout 632 ldx pagectr 633 ldy #0 634 : lda (xmobuf),y 635 jsr chrout 636 iny 637 cpy firstpagebytes 638 bne :- 639 inc xmobuf+1 640 dex 641 bne :- 642 643 @next: lda #0 644 sta xmoend ; reset error counter 645 inc xmoblk ; expect next block 646 jsr clrchn 647 lda #'-' ; good block 648 jsr goobad 649 650 @ackblk: 651 jsr clear232 652 653 ; send ACK 654 jsr enablexfer 655 lda #ACK 656 jsr modput 657 jsr clrchn 658 659 lda #0 660 sta xmobad ; reset bad block counter 661 lda xmoend 662 bne :+ ; EOT! 663 jmp @bloop ; next block 664 665 : jmp xmfnok ; end of file, send * key 666 667 ; does not belong to XMODEM source 668 .include "filetype.s" 669 670 ;---------------------------------------------------------------------- 671 ; error messages 672 SET_PETSCII 673 msg_cancelled: 674 .byte CR,"Transfer Cancelled.",0 675 msg_no_eto_ack: 676 .byte CR,"EOT Not Acknowleged.",0 677 msg_max_retries: 678 .byte CR,"Too Many Bad Blocks!",0 679 msg_sync_lost: 680 .byte CR,"Sync Lost!",0 681 SET_ASCII 682 683 ;---------------------------------------------------------------------- 684 ; *** upload: send, handle status 685 xmodem_upload 686 jsr xmodem_send ; send 687 jmp xmodon 688 689 ;---------------------------------------------------------------------- 690 ; *** download: receive, handle status 691 xmodem_download: 692 jsr xmodem_receive ; receive 693 xmodon 694 lda #CR 695 jsr chrout 696 lda xmstat 697 bne :+ ; success 698 jmp xfrdun 699 700 : cmp #STAT_USER_ABORTED 701 beq xmodna ; error-return with no message 702 cmp #STAT_CANCELLED 703 bne xmodn3 704 lda #<msg_cancelled 705 ldy #>msg_cancelled 706 bne xmodnp 707 xmodn3 708 cmp #STAT_NO_EOT_ACK 709 bne xmodn4 710 lda #<msg_no_eto_ack 711 ldy #>msg_no_eto_ack 712 bne xmodnp 713 xmodn4 cmp #STAT_MAX_RETRIES 714 bne xmodn5 715 lda #<msg_max_retries 716 ldy #>msg_max_retries 717 bne xmodnp 718 xmodn5 719 lda #<msg_sync_lost 720 ldy #>msg_sync_lost 721 xmodnp 722 jsr outstr 723 jsr gong 724 lda #CR 725 jsr chrout 726 xmodna 727 jmp ui_abort 728 729 xprotoc: 730 .res 1 731 pagectr: 732 .res 1 733 tmppagectr: 734 .res 1 735 block_index: 736 .res 1 737 nblock_index: 738 .res 1 739 ; used for temprarily storing the two CRC bytes 740 crcz: 741 .res 2