Created
October 21, 2019 23:07
-
-
Save tschak909/d5c3393bcd662bcdf967e5e491190852 to your computer and use it in GitHub Desktop.
XMODEM CRC-16 implementation that can compile with ASM.COM
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
;====================================================== | |
; CP/M XMODEM 1.01 by Martin Eberhard | |
;====================================================== | |
; Compact XMODEM file transfer program for CP/M 2.2 | |
; | |
; Supports both regular XMODEM checksums and XMODEM-CRC | |
; | |
; See help the message near the end of this code for | |
; program usage | |
; | |
; Assemble with Digital Research's ASM Assembler | |
; | |
; Designed to run on a 2 MHz 8080 or a 4 MHz Z80, | |
; communicating via CP/M's RDR: and PUN: drivers. | |
; | |
; CPM's BIOS RDR: driver must be modified to return | |
; with Z set if no character is available, and return | |
; with the received byte in A and Z cleared otherwise. | |
; (A standard CP/M RDR: driver will hang forever | |
; waiting for input. You can select a standard RDR: | |
; driver by setting CSTRDR to FALSE.) | |
; | |
; Additionally (and regardless of CSTRDR), the RDR: and | |
; PUN: devices must work with 8-bit data, although the | |
; CP/M documentation specifies that the RDR: function | |
; should strip the high (parity) bit. | |
; | |
; The CON:, RDR: and PUN: BIOS routines must not trash | |
; registers other than the accumulator. | |
; | |
; CUSTOMIZATION: You can easily replace the RDR: and | |
; PUN: calls with routines that access your I/O port | |
; directly - see comments in the TXBYTE and RXBYTE | |
; subroutines, and also in the INIT subroutine. | |
; (search for 'CUSTOMIZATION') | |
; | |
; Timimg loops are used for the various XMODEM time- | |
; outs. The firmware distinguishes between the 8080 | |
; and Z80 instruction sets by looking at the parity bit | |
; in the corner-case where the two processors behave | |
; differently. If it's an 8080, then 2MHz timing is | |
; assumed. If it's a Z80, then 4 MHz timing is assumed. | |
; | |
; Note: All of the code that is only used during | |
; initialization is at the end, and gets overwritten by | |
; the sector buffer. This allows this program to run in | |
; as little as 8K of user memory, despite a large 6K | |
; buffer and reasonably robust options and messages. | |
; | |
; Based on XMODEM for CDOS version 1.03 by M. Eberhard | |
; | |
; Thanks to Ward Christensen for inventing XMODEM and | |
; keeping it simple. | |
; Thanks to John Byrns for the XMODEM-CRC extension, | |
; which was adopted in Windows Hyperterm. | |
; Thanks to Keith Petersen, W8SDZ, for a few ideas I | |
; borrowed from his XMODEM.ASM V3.2 | |
; | |
; Revision History: | |
; Rev Date Author Comments | |
; 1.00 06APR2013 M.Eberhard ported | |
; 1.01 09APR2013 M.Eberhard Squeezed to fit in 2k file | |
; fix access to other drives | |
; | |
; To Do (maybe): | |
; use all available RAM for buffers | |
; Retries on disk errors | |
; command line option to turn pacifiers off | |
;====================================================== | |
FALSE equ 0 | |
TRUE equ -1 | |
;************************* | |
; Program Option Switches | |
;************************* | |
CSTRDR equ TRUE ;FALSE: unmodified BIOS RDR | |
;driver. XMODEM will hang with- | |
;out timeout if no input from | |
;RDR. TRUE:modified RDR driver. | |
;XMODEM will timeout neatly if | |
;no input from RDR. | |
ERRLIM equ 10 ;Max # of error-retries 10 | |
;is standard. | |
;Timeout values in seconds. Values in parenthesis are | |
;XMODEM standard values. | |
SOHTO equ 10 ;(10)sender to send SOH | |
NAKTO equ 90 ;(90)receiver to send init NAK | |
ACKTO equ 60 ;(60)receiver to ACK (or NAK) | |
;(time to write to disk) | |
SBUFSZ equ 48 ;blocks per disk read/write | |
;buffer size = SBUFSIZ * 128 bytes | |
;multiples of 16 work well with CP/M | |
BLKSIZ equ 128 ;bytes per XMODEM block | |
;DO NOT CHANGE. BLKSIZ should be 128! | |
PACIFY equ TRUE ;true to print pacifiers | |
;Progress pacifiers printed on the console | |
PACACK equ '+' ;Received an ACK | |
PACNAK equ '-' ;Received a NAK | |
PACBLK equ '+' ;Received a good block | |
PACRSD equ '-' ;Requested a resend | |
;************** | |
; CP/M Equates | |
;************** | |
;------------------------------------------ | |
;BDOS Entry Points and low-memory locations | |
;------------------------------------------ | |
WBOOT equ 0000H ;JUMP TO BIOS WARM BOOT | |
WBOOTA equ WBOOT+1 ;ADDRESS OF WARM BOOT | |
IOBYTE equ WBOOT+3 | |
;CDISK equ WBOOT+4 ;LOGIN DRIVE | |
BDOS equ WBOOT+5 ;BDOS Entry Point | |
FCB equ WBOOT+5CH ;CP/M file control blk | |
FCBDR equ FCB ;Drive Descriptor | |
FCBFN equ FCB+1 ;File name (8 chrs) | |
FCBFT equ FCB+9 ;File Type (3 chrs) | |
FCBEXT equ FCB+12 ;File extent within FCB | |
FCBCLR equ 24 ;# of bytes to clear, | |
;starting at FCBEXT | |
COMBUF equ WBOOT+80H ;disk & cmd line buffer | |
USAREA equ WBOOT+100H ;User program area | |
BLKBUF equ COMBUF ;We reuse 128-byte CP/M cmd/disk | |
;..buffer to buffer XMODEM blocks | |
;------------------------------------------- | |
; BDOS Function Codes, passed in register C | |
; Note: CON:, RDR:, and PUN: I/O is done via | |
; direct BIOS calls, not BDOS calls. | |
;------------------------------------------- | |
;BRESET equ 0 ;System Reset | |
;BCONIN equ 1 ;Read Console Chr | |
;BCONOT equ 2 ;Type Chr on Console | |
;BRDRIN equ 3 ;Read Reader Chr | |
;BPUNOT equ 4 ;Write Punch Chr | |
BPRINT equ 9 ;Print $-terminated String | |
;BRDCON equ 10 ;Get Line from Console | |
;BCONST equ 11 ;Console Status (<>0 IF CHR) | |
;BDRST equ 13 ;Reset Disk | |
BSDISK equ 14 ;select disk | |
BOPEN equ 15 ;Disk File Open | |
BCLOSE equ 16 ;Close disk file, FCB at de | |
BSERCH equ 17 ;Search dir for file, FCB at de | |
BDELET equ 19 ;delete file, FCB at (de) | |
BREAD equ 20 ;Read from Disk, 0=OK, <>0=EOF | |
BWRITE equ 21 ;Write next record, 0=OK, <>0=ERR | |
BMAKE equ 22 ;Make new file, 0FFH=BAD | |
BCDISK equ 25 ;get current disk | |
BSTDMA equ 26 ;Set disk buffer to (de) | |
;--------------------------------------------------------- | |
; BIOS Entry Points, relative to the base address in WBOOT | |
;--------------------------------------------------------- | |
CONST equ 06h ;Console Status | |
CONIN equ 09h ;Console Input | |
CONOUT equ 0Ch ;Console output | |
PUNCH equ 12h ;punch output | |
READER equ 15h ;reader input | |
;****************** | |
; ASCII Characters | |
;****************** | |
SOH equ 1 ;Start of XMODEM block | |
CTRLC equ 3 ;Control-C for user-abort | |
EOT equ 4 ;End XMODEM session | |
ACK equ 6 ;XMODEM block acknowledge | |
NAK equ 15H ;XMODEM block negative ACK | |
LF equ 0AH ;Linefeed | |
CR equ 0DH ;Carriage return | |
SELCRC equ 'C' ;selects CRC mode at initiation | |
;********************* | |
;* Beginning of Code * | |
;********************* | |
ORG USAREA ;normal place for CP/M programs | |
;-------------------------------- | |
;Save CP/M SP, create local stack | |
;-------------------------------- | |
lxi h,0 | |
dad sp ;hl=CP/M stack pointer | |
shld stack ;CP/M SP on local stack | |
lxi SP,STACK ;SP=local stack | |
;---------------------------------------------------- | |
;Initialize, using code that gets wiped out by SECBUF | |
;ret with a=1 for receive, 2 for send | |
;---------------------------------------------------- | |
call INIT | |
;-------------------------------------------------- | |
; Send or receive, based on XMODE, set by /S or /C | |
;-------------------------------------------------- | |
dcr a ;so 0 means receive | |
jz RXFILE | |
; fall into TXFILE | |
;*****Function***************************************** | |
; Send a CP/M file in XMODEM format | |
; On Entry: | |
; FCB is valid | |
;****************************************************** | |
TXFILE: call FOPEN ;Open file specified in FCB | |
;& print message on console | |
call GTMODE ;wait for NAK or SELCRC to | |
;..determine cksum or CRC mode | |
;--------------------------------------------- | |
;Transmit all sectors of the file: | |
;Read a sector from the disk, and test for EOF | |
;--------------------------------------------- | |
TXLOOP: call RDSECT ;Read sector from disk | |
jc TXEOF ;C set means EOF | |
lhld CURBLK ;inc 16-bit block count | |
inx h | |
shld CURBLK | |
xra a ;Clear error count | |
sta ERRCNT | |
;--------------------------------- | |
;Send block header: SOH, Block | |
;number, Complimented block number | |
;--------------------------------- | |
TXRPT: mvi a,SOH ;SOH first | |
call TXBYTE | |
lda CURBLK ;8-bit block number | |
call TXBYTE ;(preserves A) | |
cma ;complimented block no | |
call TXBYTE | |
;------------------------------------ | |
;Send a BLKSIZ-byte block from BLKBUF | |
; On Entry: | |
; BLKBUF has a sector full of data | |
; On Exit: | |
; Data checksum is in C | |
; 16-bit data CRC is in CRC16 | |
;------------------------------------ | |
lxi h,0 ;clr CRC for new block | |
shld CRC16 | |
lxi b,BLKSIZ*256+0 ;b=bytes/block, | |
;...clear cksum in C | |
lxi h,BLKBUF ;Point to block | |
TXBLUP: mov a,m ;Get a data byte | |
call TXBYTE ;Send it, do cksum | |
call CALCRC ;combine a into the CRC | |
inx h ;Next byte | |
dcr b | |
jnz TXBLUP ;loop through block | |
;-------------------------------------------- | |
;Send checksum or 16-bit CRC, based on CRCFLG | |
; c= 8-bit checksum | |
; de= 16-bit CRC | |
; CRCFLG <>0 if CRC mode enabled | |
;-------------------------------------------- | |
lda CRCFLG ;Checksum or CRC? | |
ora a ;clear Z if CRCFLG | |
jz TXBDON ;jump to send checksum | |
lhld CRC16 ;get calculated CRC | |
mov a,h | |
call TXBYTE ;send byte in a | |
mov c,l ;now the 2nd CRC byte | |
TXBDON: mov a,c ;a=cksum or CRC 2nd byte | |
call TXBYTE ;send byte in a | |
;------------------------------------------- | |
;Wait for the ACK. If none arrives by the | |
;end of the timeout, or if a NAK is received | |
;instead of an ACK, then resend block. | |
;------------------------------------------- | |
call GETACK ;Wait for the ACK | |
;Z flag set if ACK | |
jnz TXRPT ;NZ: timeout or NAK | |
;--------------------------------------------------- | |
;Ack received. Print pacifier, and go for next block | |
;--------------------------------------------------- | |
IF PACIFY | |
mvi c,PACACK ;pacifier on console | |
call TYPEC | |
ENDIF | |
jmp TXLOOP | |
;--------------------------------------------------- | |
;File send completed. Send EOT'S intil we get an ACK | |
;--------------------------------------------------- | |
TXEOF: mvi a,EOT ;Send an EOT | |
call TXBYTE | |
call GETACK ;Wait for an ACK | |
jnz TXEOF ;Loop until an ACK | |
;------------------------------ | |
;Send happy termination message | |
;------------------------------ | |
call ILPRT | |
db TXSLEN | |
TXSMSG: db CR,LF,'Transmit done' | |
TXSLEN equ $-TXSMSG | |
;---------------------------------------------- | |
;Report succesfull block count & return to CP/M | |
;---------------------------------------------- | |
TXCNT: call ILPRT | |
db TXCLEN | |
TXCMSG: db CR,LF,'Sent ' | |
TXCLEN equ $-TXCMSG | |
jmp REPCNT ;print block count, goto CP/M | |
;*****Function***************************************** | |
; Receive XMODEM file & save it to disk | |
; On Entry: | |
; FCB is valid | |
;****************************************************** | |
RXFILE: call CREATE ;create & open file on disk | |
;---------------------------------------------------- | |
;Receive & validate a block, and see if we got an EOT | |
;---------------------------------------------------- | |
RXLOOP: call GETBLK ;Receive an XMODEM block | |
jc RXEOT ;C set means EOT received | |
;--------------------------------------------------- | |
;Save the received block, bump current block counter | |
;--------------------------------------------------- | |
call WBLOCK ;Write block to SECBUF | |
lhld CURBLK ;inc 16-bit block count | |
inx h | |
shld CURBLK | |
;------------------------------------------------ | |
;Good block received. Print pacifier on console, | |
;then send ACK and loop back to get another block | |
;------------------------------------------------ | |
IF PACIFY | |
mvi c,PACBLK ;pacifier on console | |
call TYPEC | |
ENDIF | |
call TXACK ;Send XMODEM ACK | |
jmp RXLOOP ;LOOP until EOF | |
;---------------------------------- | |
;Received EOT. Flush SECBUF and end | |
;---------------------------------- | |
RXEOT: call WFLUSH ;Write all blocks in SECBUF | |
call TXACK ;ACK this sector | |
call FCLOSE ;Close CP/M file | |
;------------------------------------------------- | |
;Send happy termination message and return to CP/M | |
;------------------------------------------------- | |
call ILPRT | |
db RXCLEN | |
RXCMSG: db CR,LF,'Receive done' | |
RXCLEN equ $-RXCMSG | |
; fall into RRXCNT | |
;*****Exit********************************************* | |
;Exit: Report the number of blocks succesfully | |
;received, and then return to CP/M | |
;****************************************************** | |
RRXCNT: call ILPRT | |
db SRLEN | |
SRMSG: db CR,LF,'Received ' | |
SRLEN equ $-SRMSG | |
; fall into REPCNT | |
;*****Exit********************************************* | |
; Report 16-bit block count, and then return to CP/M | |
;****************************************************** | |
REPCNT: lhld CURBLK | |
call PDEC16 ;print hl in decimal | |
call MSGXIT | |
db ' blocks$' | |
;*****Subroutine*************************************** | |
; Get an XMODEM block | |
; On Entry: | |
; On Exit: | |
; Carry set if EOT received | |
; Trashes all registers | |
;****************************************************** | |
GETBLK: | |
;----------------------------------------- | |
;Wait for SOH from sender to start | |
;reception, checking for EOT while we wait | |
;----------------------------------------- | |
xra a | |
sta ERRCNT ;Clear error count | |
RXRPT: mvi e,SOHTO*2 ;Timeout for SOH | |
call RXBYTE | |
jc RXSERR ;Carry means timeout | |
cpi SOH ;Did we get an SOH? | |
jz RXSOH ;If so, get the block | |
;----------------------------------------------- | |
;Earlier versions of XMODEM sent some nulls here | |
;We could just ignore them. (Not in this code) | |
;----------------------------------------------- | |
IF FALSE | |
ora a ;Null? | |
jz RXRPT ;Yes: ignore it | |
ENDIF | |
;------------------------------------- | |
;Set carry and return if we get an EOT | |
;------------------------------------- | |
cpi EOT | |
stc | |
rz | |
;------------------------------------------- | |
;No SOH or EOT - this is an invalid header. | |
;Eat all received chrs until a 1-sec timeout | |
;------------------------------------------- | |
PURGE: call RXBYT1 ;Receive chr w/ timeout | |
jnc PURGE ;Carry means timeout | |
; fall into RXSERR | |
;-------------------------------------------------- | |
;Send a NAK to indicate receive error. If we are | |
;waiting to start (and we are in CRC mode (NAKCHR | |
;=SELCRC), then send SELCRC instead of NAK | |
;-------------------------------------------------- | |
RXSERR: call CCTRLC ;user abort? | |
IF PACIFY | |
mvi c,PACRSD ;pacifier on console | |
call TYPEC | |
ENDIF | |
lda NAKCHR ;current NAK chr | |
call TXBYTE | |
;---------------------------------------------- | |
;Bump error count, and abort if too many errors | |
;---------------------------------------------- | |
lxi h,ERRCNT ;Clear error count | |
inr m ;bump error count | |
mov a,m ;Too many errors? | |
cpi ERRLIM | |
jc RXRPT ;No: try again | |
;----------------------------------- | |
;Too many errors: abort with message | |
;----------------------------------- | |
lxi d,ELEMSG ;error limit exceeded | |
jmp ABORT | |
;-------------------------------------------------- | |
;Got an SOH, at beginning of block. Now get header: | |
;Block number, Complemented block number | |
;-------------------------------------------------- | |
RXSOH: mvi a,NAK ;we have received | |
sta NAKCHR ;..at least one SOH | |
call RXBYT1 ;Get block number | |
jc RXSERR ;Carry means timeout | |
mov d,a ;Save block number | |
call RXBYT1 ;compl'd block number | |
jc RXSERR ;Carry means timeout | |
cma ;compliment to compare | |
cmp d | |
jnz PURGE ;No match: error | |
sta RXBLK ;Save block number | |
;-------------------------------------------------- | |
;Loop to receive BLKSIZ bytes and store them in | |
;BLKBUF, computing the checksum & CRC along the way | |
;-------------------------------------------------- | |
lxi h,0 ;clear CRC | |
shld CRC16 | |
lxi b,BLKSIZ*256+0 ;b=bytes, c=0 cksum | |
lxi h,BLKBUF ;Point to buffer | |
RXCHR: call RXBYT1 ;Get one byte of data | |
jc RXSERR ;Carry means timeout | |
mov m,a ;Store byte in buffer | |
add c ;compute checksum | |
mov c,a | |
mov a,m ;recover received byte | |
call CALCRC ;calculate CRC too | |
inx h ;next byte | |
dcr b | |
jnz RXCHR | |
;------------------------------------------------------ | |
;Verify checksum in C, or CRC in CRC16, based on CRCFLG | |
; de = CRC | |
;------------------------------------------------------ | |
lda CRCFLG ;CRC mode? | |
ora a ;0 means cksum | |
jz CKCKSM | |
call RXBYT1 ;Get 1st byte of CRC | |
jc RXSERR ;Carry means timeout | |
lhld CRC16 | |
cmp h | |
jnz PURGE ;no: try again, but 1st | |
;purge rest of CRC | |
mov c,l ;put 2nd CRC byte in C | |
CKCKSM: call RXBYT1 ;2nd CRC byte or cksum | |
jc RXSERR ;Carry means timeout | |
cmp c ;Does it match? | |
jnz RXSERR ;No: error | |
;----------------------------------------------- | |
;Got a good block. See if we've already received | |
;this block. (It might be a retransmission.) If | |
;it's the most recently received block, then try | |
;again - otherwise it's an error. | |
;----------------------------------------------- | |
lda CURBLK ;8-bit block number | |
mov b,a ;b=recent Rx block | |
lda RXBLK ;a=this block's number | |
sub b ;calc the difference | |
cz TXACK ;same as last block: | |
jz GETBLK ;..send ACK & try again | |
dcr a ;should be next block | |
ora a ;must clear carry too! | |
rz ;return: correct block | |
;-------------------------- | |
;Error: Blocks out of order | |
;-------------------------- | |
lxi d,SEMSG ;sync error | |
jmp ABORT | |
;*****Subroutine*************************************** | |
; Get an ACK from the receiver. If we get a NAK, print | |
; the NAK pacifier on the console. | |
; On Exit: | |
; Z set and Carry clear if ACK received | |
; Z clear and Carry clear if NAK received | |
; Z clear, Carry set and ERRCNT bumped if timeout | |
; or too many bogus chrs received | |
; If too mant errors, abort | |
; Trashes a,b,c, hl | |
;****************************************************** | |
GETACK: | |
;------------------------------- | |
;Get a received byte, or timeout | |
;------------------------------- | |
mvi e,ACKTO*2 ;ACK-wait timeout value | |
call RXBYTE ;go get a character | |
jc ACKERR ;Carry means timeout | |
;------------------------------------------------ | |
;Return form subroutine with Z set if it's an ACK | |
;------------------------------------------------ | |
cpi ACK ;Did we get an ACK? | |
rz ;Yes: return w/ carry cleared | |
;--------------------------------------- | |
;If NAK, print pacifier, and return with | |
;C & Z cleared unless the user aborts us | |
;--------------------------------------- | |
mov e,a ;save received chr | |
call CCTRLC ;user abort? | |
mov a,e ;recover received chr | |
cpi NAK ;NAK? | |
jnz ACKERR ;NZ: bad byte received | |
IF PACIFY | |
mvi c,PACNAK ;NAK pacifier | |
call TYPEC | |
ENDIF | |
ora a ;NAK: Clear Z & C | |
ret | |
;------------------------------------------------ | |
;Timeout or bogus chr while waiting for ACK/NAK | |
;Bump error count & check limit. Set C for return | |
;------------------------------------------------ | |
ACKERR: call CCTRLC ;user abort? | |
lxi h,ERRCNT ;bump error count | |
inr m | |
mov a,m ;too many errors? | |
cpi ERRLIM | |
rc ;No: Return w/ Carry set | |
;and Z cleared for timeout | |
;-------------------------------------- | |
;Abort waiting for ACK: Too many errors | |
;-------------------------------------- | |
lxi d,TAEMSG ;too many ack errors | |
jmp ABORT | |
;*****Subroutine*************************************** | |
; Close CP/M disk file | |
; This is required after writing to a file! | |
; On Exit: | |
; de = FCB | |
; Trashes A | |
;****************************************************** | |
FCLOSE: lxi d,FCB ;FCB describes the file | |
mvi c,BCLOSE ;CP/M CLOSE FILE funct. | |
call GOBDOS | |
inr a ;-1 means close error | |
rnz | |
;-------------------------------------- | |
;Error closing file: abort with message | |
;-------------------------------------- | |
call CMSGXT | |
db 'ERROR CLOSING FILE!' | |
db CR,LF,'File is probably corrupt.$' | |
;*****Subroutine*************************************** | |
; Get sector from SECBUF into BLKBUF | |
; | |
;---> ENTRY IS AT RDSECT <---- | |
; | |
; If SECBUF is empty, then read up to BLKSIZ more | |
; sectors from the disk. | |
; | |
; For speed, this routine buffers up to SBUFSZ blocks | |
; (sectors) into SECBUF each time it reads from disk. | |
; On Entry: | |
; SECCNT = number of remaining sectors in SECBUF | |
; SECPTR = address of next sector to send | |
; On Exit: | |
; Carry and EOFLG set if EOF | |
; Trashes all registers | |
;****************************************************** | |
;--------------------------------------- | |
;SECBUF is empty: read up to SBUFSZ more | |
;sectors from the disk into SECBUF | |
;--------------------------------------- | |
RDBLOC: lda EOFLG ;Have we seen the EOF? | |
ora a | |
stc ;Return w/ Carry if so | |
rnz | |
mvi b,SBUFSZ ;B=free buffs in SECBUF | |
lxi d,SECBUF ;de=address in SECBUF | |
RSECLP: mvi c,BSTDMA ;Set CP/M DMA address | |
call GOBDOS ;trashes no registers | |
xchg ;pointer to hl, free de | |
lxi d,FCB ;Disk sect. into SECBUF | |
mvi c,BREAD | |
call GOBDOS ;trashes no registers | |
ora a ;Read ok? | |
jnz RSEOF ;No: no more data | |
lxi d,BLKSIZ ;next block | |
dad d | |
xchg ;Result goes in de | |
dcr b | |
jnz RSECLP ;go until all space used | |
jmp RBFULL ;No: done reading | |
;---------------------------------------------- | |
;Encountered the EOF. Set EOFLG so we will stop | |
;---------------------------------------------- | |
RSEOF: sta EOFLG ;SET EOF FLAG | |
; fall into RBFULL | |
;--------------------------------------- | |
;Receive buffers are all full, or we got | |
;an EOF from CP/M. compute & save block | |
;count, point SECPTR to the 1st block | |
; On Entry: | |
; b = remaining space in SECBUF | |
;--------------------------------------- | |
RBFULL: mvi a,SBUFSZ ;compute # of secs read | |
sub b ;B=remaining space | |
sta SECCNT ;Store sector count | |
lxi h,SECBUF ;Point SECPTR to start | |
shld SECPTR ;..of SECBUF | |
; Fall into RDSECT | |
;****************************** | |
;Subroutine RDSECT: Entry point | |
;****************************** | |
RDSECT: lxi h,SECCNT ;decrement SECCNT | |
dcr m | |
jm RDBLOC ;Empty? go read disk | |
lhld SECPTR ;(hl) = data in SECBUF | |
lxi d,BLKBUF ;(de) = data in BLKBUF | |
lxi b,BLKSIZ ;bytes per block | |
RSLP: mov a,m | |
stax d | |
inx h | |
inx d | |
dcx b | |
mov a,b | |
ora c | |
jnz RSLP | |
shld SECPTR ;Update data pointer | |
ret | |
;*****Subroutine*************************************** | |
; Write a received BLKSIZ-byte block from BLKBUF into | |
; SECBUF. If SECBUF is full, flush it to disk. | |
; On Entry: | |
; SECPTR = address of next available block in SECBUF | |
; SECCNT = count of blocks currently in SECBUF | |
; On Exit: | |
; Trashes all registers | |
;****************************************************** | |
WBLOCK: lhld SECPTR | |
xchg ;de=SECBUF block data | |
lxi h,BLKBUF ;(hl)=BLKBUF | |
lxi b,BLKSIZ ;bytes per block | |
WBLP: mov a,m | |
stax d | |
inx d | |
inx h | |
dcx b | |
mov a,b | |
ora c | |
jnz WBLP | |
xchg ;Update SECPTR | |
shld SECPTR | |
lxi h,SECCNT ;increment SECCNT | |
inr m | |
mov a,m | |
cpi SBUFSZ ;Is SECBUF full? | |
rnz ;No: return for more | |
; fall into WFLUSH | |
;*****Subroutine*************************************** | |
; Write all data in SECBUF to disk | |
; On Entry: | |
; SECCNT has count of blocks currently in SECBUF | |
; On Exit: | |
; Trashes all registers | |
;****************************************************** | |
WFLUSH: lda SECCNT ;# of sectors in SECBUF | |
ora a ;End of file already? | |
rz ;Return w/ Z set if so | |
mov b,a ;Sector count in b | |
lxi d,SECBUF ;de=start of sect data | |
WFLOOP: mvi c,BSTDMA ;CP/M SET DMA function | |
call GOBDOS ;de = DMA address | |
xchg ;pointer to hl, free de | |
lxi d,FCB ;Write from buf to disk | |
mvi c,BWRITE | |
call GOBDOS | |
lxi d,EWFMSG | |
ora a ;return 0 if okay | |
jnz ABORT ;OOPS, error | |
;hl = address in SECBUF | |
lxi d,BLKSIZ ;de=block size | |
dad d ;(hl)=next sector data | |
xchg ;addr to de for BSTDMA | |
dcr b | |
jnz WFLOOP ;until all sectors sent | |
;------------------------------- | |
;Reset pointers for empty SECBUF | |
;------------------------------- | |
xra a | |
sta SECCNT ;SECCNT = 0 | |
lxi h,SECBUF ;reset SECPTR | |
shld SECPTR | |
ret | |
;*****Subroutine*************************************** | |
; Update the 16-bit CRC with one more byte | |
; speed matters here. | |
; On Entry: | |
; a has the new byte | |
; CRC16 is current except this byte | |
; On Exit: | |
; CRC16 has been updated | |
; Trashes a,de | |
;****************************************************** | |
CALCRC: push b | |
push h | |
lhld CRC16 ;get CRC so far | |
xra h ;XOR into CRC top byte | |
mov h,a | |
lxi d,1021h | |
mvi b,8 ;prepare to rot 8 bits | |
CROTLP: dad h ;16-bit shift | |
jnc CCLR ;skip if bit 15 was 0 | |
mov a,h ;CRC=CRC xor 1021H | |
xra d | |
mov h,a | |
mov a,l | |
xra e | |
mov l,a | |
CCLR: | |
dcr b | |
jnz CROTLP ;rotate 8 times | |
shld CRC16 ;save CRC so far | |
pop h | |
pop b | |
ret | |
;*****Subroutine*************************************** | |
; Receive a byte, with 1-sec timeout | |
; On Entry: | |
; On Exit: | |
; Carry set for timeout error | |
; Character in a | |
; trashes a,e | |
;****************************************************** | |
RXBYT1: mvi e,2 | |
; fall into RXBYTE | |
;*****Subroutine*************************************** | |
; Receive a byte from the reader device | |
; Note: CTSRDR option selects a custom reader port: | |
; rather than hanging and waiting for a byte, the | |
; driver will return with Z set if no byte is | |
; available. Otherwise, a=received byte | |
; | |
; On Entry: | |
; e = timeout value in half-seconds | |
; On Exit: | |
; Carry set for timeout error | |
; a = received byte if no timeout | |
; trashes e | |
;****************************************************** | |
;* CUSTOMIZATION * | |
;***************** | |
; You can replace this call to GOBIOS with a call to | |
; your subroutine that reads your hardware directly, | |
; like this: | |
; call MYRDR | |
; | |
; ... | |
; | |
; MYRDR: in <my status port> | |
; ani <my rx buffer-full bit mask> | |
; rz | |
; | |
; in <my data port> | |
; ret | |
; | |
; ...and then adjust the timer value loaded into hl so | |
; that the loop is 1/2 second long. (Note that the code | |
; detects the difference between an 8080 and a Z80, | |
; and assumes an 8080 at 2 MHz, or a Z80 at 4 MHz.) | |
;****************************************************** | |
RXBYTE: push h | |
; set HL for 1/2 second timeout for either a 2 MHz | |
; 8080 or a 4MHz Z80 | |
MS500: lxi h,4274 ;1/2-sec count down | |
sub a ;test for 8080 or Z80 | |
jpe IS8080 | |
dad h ;Z80 is twice as fast | |
IS8080: | |
RXWAIT: mvi a,READER ;(7)BIOS call | |
call GOBIOS ;(172+17=189) | |
stc ;(4) | |
if CSTRDR | |
jnz RXDONE ;(10) | |
dcx h ;(5)timeout timer | |
mov a,h ;(5)Test for 16-bit 0 | |
ora l ;(4) | |
jnz RXWAIT ;(10) | |
;inner loop:234 cycles=117 uS for 8080 | |
;0.5 sec / 117 uS = 4273.5 cycles | |
dcr e | |
jnz MS500 ;spin for time | |
;timeout waiting for chr. Does the user want to abort? | |
call CCTRLC ;user abort? | |
ora a ;clear carry for exit | |
endif | |
;------------------------------------------- | |
;Set carry for error, clear for ok & return | |
;------------------------------------------- | |
RXDONE: pop h | |
cmc ;Carry if timeout | |
ret | |
;*****Subroutine*************************************** | |
; Send ACK | |
; On Exit: | |
; a trashed | |
; All flags and all other registers preserved | |
;****************************************************** | |
TXACK: mvi a,ACK | |
; fall into TXBYTE | |
;*****Subroutine*************************************** | |
; Send a to the punch device | |
; | |
; On Entry: | |
; a = byte to send | |
; On Exit: | |
; Checksum in c has been updated | |
; All registers preserved | |
;****************************************************** | |
;* CUSTOMIZATION * | |
;***************** | |
; You can replace this call to GOBIOS with a direct | |
; write to your hardware, like this: | |
; | |
; TXWAIT: in <my status port> | |
; ani <my tx ready mask> | |
; jz TXWAIT | |
; | |
; mov a,c | |
; out <my data port> | |
;****************************************************** | |
TXBYTE: push psw | |
push b | |
mov c,a ;data to c for BIOS | |
mvi a,PUNCH ;BIOS send c to punch | |
call GOBIOS | |
mov a,c ;original byte | |
pop b | |
add c ;update checksum | |
mov c,a | |
pop psw | |
ret | |
;*****Subroutine*************************************** | |
; Print hl in decimal on the console with leading | |
; zeros suppressed | |
; Trashes all registers | |
;****************************************************** | |
PDEC16: mvi d,0 ;Suppress leading 0's | |
lxi b,-10000 | |
call DECDIG | |
lxi b,-1000 | |
call DECDIG | |
lxi b,-100 | |
call DECDIG | |
lxi b,-10 | |
call DECDIG | |
mov a,l ;last digit is simple | |
jmp DECDG0 ;with leading 0's | |
;-----Local Subroutine---------------------------- | |
; Divide HL by power of 10 in bc and print result, | |
; unless it's a leading 0. | |
; On Entry: | |
; hl=Dividend | |
; bc=divisor (a negative power of 10) | |
; d=0 if all prior digits were 0 | |
; On Exit: | |
; Quotent is printed, unless it's a leading 0 | |
; hl=remainder | |
; d=0 iff this and all prior digits are 0 | |
;------------------------------------------------- | |
DECDIG: mvi a,0FFh ;will go 1 too many times | |
push d ;leading zero state | |
DIGLP: mov d,h ;de gets prev value | |
mov e,l | |
inr a | |
dad b ;subtract power of 10 | |
jc DIGLP | |
xchg ;hl has remainder | |
pop d ;leading 0 state | |
mov e,a ;e has digit to print | |
ora d ;leading 0 to suppress? | |
rz ;yes: digit is done | |
mov d,a ;don't suppress next digit | |
mov a,e | |
DECDG0: adi '0' ;make digit ASCII | |
mov c,a ;result to c for TYPEC | |
; fall into TYPEC | |
;*****Subroutine*************************************** | |
; Print character in c on console | |
; trashes a | |
;****************************************************** | |
TYPEC: mvi a,CONOUT ;BIOS WR Console func | |
; fall into GOBIOS | |
;*****Subroutine*************************************** | |
; Go call a BIOS driver directly | |
; On Entry: | |
; c=value for BIOS routine, if any | |
; a = BIOS call address offset | |
; On Return: | |
; all other regs and flags as the BIOS code left them | |
; READER will take 172 8080 cycles if no chr ready | |
;****************************************************** | |
GOBIOS: push h ;(11) | |
lhld WBOOTA ;(16)get BIOS base address | |
mov l,a ;(5)a has jump vector | |
xthl ;(18) | |
ret ;(10)go to BIOS routine | |
;Assume BIOS takes 112 cycles, when no READER chr is ready: | |
; jmp <routine> ;(10) BIOS jump vector | |
; call <status> ;(17) reader status routine | |
; lda IOBYTE ;(13) which reader port? | |
; ani MASK ;(7) | |
; jz <not taken> ;(10) not RDR=TTY | |
; cpi <val> ;(7) | |
; jc <not taken> ;(10) not RDR=HSR | |
; jz <taken> ;(10) RDR=UR1 | |
; in <port> ;(10) get reader stat | |
; ani <mask> ;(7) test, set Z | |
; rz ;(11) return from BIOS | |
;*****Subroutine*************************************** | |
;Print $-terminated string at de | |
;trashes a,c | |
;****************************************************** | |
PRINTF: mvi c,BPRINT | |
; fall into GOBDOS | |
;*****Subroutine*************************************** | |
;Call BDOS while preserving all regs except a | |
;****************************************************** | |
GOBDOS: push h | |
push d | |
push b | |
call BDOS | |
pop b | |
pop d | |
pop h | |
ret | |
;*****Subroutine*************************************** | |
; Print In-line Message | |
; The call to ILPRT is followed by a message string. | |
; The first chr is the byte count of the message. | |
; Trashes bc | |
;****************************************************** | |
ILPRT: xthl ;Save hl, get msg addr | |
mov b,m ;Byte count | |
IPLOOP: inx h ;Next byte | |
mov c,m | |
call TYPEC ;print byte | |
dcr b | |
jnz IPLOOP ;Do all bytes of msg | |
inx h ;point past message | |
xthl ;Restore hl, | |
;..get return address | |
ret | |
;*****Subroutine*************************************** | |
;Check for Control-C on the console, and quit if so | |
; trashes a | |
;****************************************************** | |
CCTRLC: mvi a,CONST ;anything on console? | |
call GOBIOS ;(about 200 cycles) | |
ora a ;0 means no chr waiting | |
rz | |
; chr waiting: Take a look | |
mvi a,CONIN ;read the typed chr | |
call GOBIOS | |
cpi CTRLC | |
rnz ;ignore everything else | |
lxi d,CCMSG ;control C | |
; fall into ABORT to close file and report | |
;*****Exit********************************************* | |
;Abort Rx - close file, delete it if no blocks received | |
; ON Entry: | |
; DE = abort message to print | |
; XMODE = 0 for receiving, <>0 for sending | |
;****************************************************** | |
ABORT: call ILPRT | |
db ABLEN | |
ABMSG: db CR,LF,'ABORT: ' | |
ABLEN equ $-ABMSG | |
call PRINTF ;print string at de | |
lda XMODE ;need to close the file? | |
ora a ;0 means receiving | |
jnz EXIT | |
call FCLOSE ;Close file neatly | |
lhld CURBLK ;any disk blks written? | |
mov a,h | |
ora l ;check 16-bit blk count | |
jnz RRXCNT ;y: report blks written | |
mvi c,BDELET ;n: delete 0-byte file | |
lxi d,FCB | |
call GOBDOS | |
inr a ;successful delete? | |
jz EXIT ;no: done | |
call CMSGXT ;Exit w/ this message | |
db '0-byte file deleted$' | |
;*************************** | |
;$-terminated abort messages | |
;*************************** | |
CCMSG: db '^C$' | |
ELEMSG: db 'too many errors$' | |
SEMSG: db 'lost blocks$' | |
TAEMSG: db 'too many ACK errors$' | |
EWFMSG: db 'disk write error$' | |
;*****Exit********************************************* | |
; Print CRLF, then $-terminated string following the | |
; call. Fix everything for CP/M, and return to CP/M | |
;****************************************************** | |
CMSGXT: call ILPRT | |
db CRLEN | |
CRMSG: db CR,LF | |
CRLEN equ $-CRMSG | |
; fall into MSGXIT | |
;*****Exit********************************************* | |
; Print $-terminated string following the call, fix | |
; everything for CP/M, and return to CP/M | |
;****************************************************** | |
MSGXIT: pop d ;Get message address | |
call PRINTF | |
; fall into EXIT | |
;*****Exit********************************************* | |
; Restore CP/M's default disk, disk DMA buffer, and | |
; stack, and then return to CP/M | |
;****************************************************** | |
EXIT: lda CURDSK ;original default disk | |
mov e,a | |
mvi c,BSDISK | |
call GOBDOS | |
lhld STACK ;restore CP/M stack | |
sphl | |
mvi d,COMBUF ;Reset CP/M DMA Address | |
mvi c,BSTDMA | |
jmp BDOS ;returns to CP/M | |
;****************************************************** | |
;RAM Variables and Storage, all initialized during load | |
;****************************************************** | |
;------------------------------ | |
;XMODEM file transfer variables | |
;------------------------------ | |
RXBLK: db 0 ;Received block number | |
CURBLK: dw 0 ;16-bit Current block number | |
ERRCNT: db 0 ;Error count | |
CRC16: dw 0 ;calculated CRC so far | |
NAKCHR: db NAK ;current NAK chr | |
CURDSK db 0 ;current disk at entry | |
;------------------------ | |
;Disk buffering variables | |
;------------------------ | |
SECPTR: DW SECBUF ;Points to next sect in SECBUF | |
SECCNT: db 0 ;Count of sectors in SECBUF | |
EOFLG: db 0 ;EOF flag (<>0 means true) | |
;---------------------- | |
;Command line variables | |
;---------------------- | |
XMODE: db 0FFH ;1 for send, 0 for receive | |
CRCFLG: db SELCRC ;0 for checksum, SELCRC for CRC | |
;------------------------------------------- | |
;Local stack, initialized for easy debugging | |
;------------------------------------------- | |
dw 0,0,0,0,0,0,0,0 | |
dw 0,0,0,0,0,0,0,0 | |
STACK: dw 0 ;top of stack | |
;****************************************************** | |
;Buffer for SBUFSZ disk sectors (same as XMODEM blocks) | |
; This buffer over-writes the following initialization | |
; code. | |
;****************************************************** | |
SECBUF: equ $ ;Sector buffer | |
;****************************************************** | |
; The following subroutines are used only during the * | |
; initial command line processing, and get wiped out * | |
; by the SECBUF, once we start transfering data. * | |
;****************************************************** | |
;*****Subroutine*************************************** | |
;Open CP/M disk file (for reading), | |
; and reports succss or failure to console. | |
; On Entry: | |
; FCB has file name | |
; On successful Exit: | |
; File is open | |
; File-open message has been rinted on the console | |
; On failure: | |
; Relevent error msg has been printed on the console | |
; jump to CP/M | |
;****************************************************** | |
FOPEN: lxi d,FCB ;FCB describes the file to open | |
mvi c,BOPEN ;CP/M FILE OPEN function | |
call GOBDOS | |
inr a ;-1 means open failure | |
jz FOFAIL | |
call ILPRT | |
db FELEN | |
FEMSG: db CR,LF,'File open' | |
db CR,LF,'Sending' | |
FELEN equ $-FEMSG | |
ret | |
;-------------------------------------- | |
;Error opening file: Abort with message | |
;-------------------------------------- | |
FOFAIL: call CMSGXT ;Exit w/ this message | |
db 'ERROR: File not found$' | |
;*****Subroutine*************************************** | |
;Get the error-checking mode | |
; Wait for initial NAK or a SELCRC from receiver to get | |
; going. (NAK means we use checksums, SELCRC means use | |
; CRC-16.) Ignore all other characters, w/ long timeout | |
; Abort if user types Control-C | |
; On Entry: | |
; CRCFLG = 0, defaulting to checksum mode | |
; On Succesful Exit: | |
; CRCFLG = 0 if NAK received | |
; CRCFLG = SELCRC if SELCRC received | |
; Message printed if CRC mode | |
; Trashes a,bc,de,hl | |
;****************************************************** | |
GTMODE: mvi b,NAKTO ;Long timeout | |
lxi h,CRCFLG ;assume cksum for now | |
mvi m,0 | |
WAITNK: lxi d,NAMSG | |
dcr b ;Timeout? | |
jz ABORT ;yes: abort | |
call RXBYT1 ;trashes e | |
cpi NAK ;NAK for checksum? | |
rz ;yes:done | |
cpi SELCRC ;'C' for CRC? | |
jnz WAITNK ;No: Keep looking | |
mov m,a ;remember CRC mode | |
; fall into PCRC | |
;*****Subroutine*************************************** | |
; Print ' with CRC' | |
;****************************************************** | |
PCRC: call ILPRT | |
db SCLEN ;Message length | |
SCMSG: db ' with CRC' | |
SCLEN equ $-SCMSG | |
ret | |
;abort message | |
NAMSG: db 'no init from receiver$' | |
;*****Subroutine*************************************** | |
;Create file on disk and report | |
; On Entry: | |
; FCB has file name | |
; On successful Exit: | |
; File is created and open | |
; File-created message has been rinted on the console | |
; Initial NAK or C (cksum or CRC mode) has been sent | |
; On failure: | |
; Relevent error msg has been printed on the console | |
; jump to CP/M | |
;****************************************************** | |
CREATE: | |
;------------------------------------------- | |
;See if file already exists, and abort if so | |
;------------------------------------------- | |
lxi d,FCB | |
mvi c,BSERCH ;Search directory for file | |
call GOBDOS | |
inr a ;-1 means not there | |
jnz FILEX ;error if so | |
;------------------------- | |
;Create file on CP/M disk | |
; DE still points to FCB | |
;------------------------- | |
mvi c,BMAKE ;CP/M CREATE FILE func | |
call GOBDOS | |
inr a ;-1 means create error | |
jz FCERR | |
;--------------------------- | |
;Tell user that we are ready | |
;--------------------------- | |
call ILPRT ;Print this message | |
db RTRLEN ;Message length | |
RTRMSG: db CR,LF,'File created' | |
db CR,LF,'Receiving' | |
RTRLEN equ $-RTRMSG | |
;---------------------------------------- | |
;Send initial NAK or SELCRC to get things | |
;going, & report if CRC mode is selected | |
;---------------------------------------- | |
lxi h,NAKCHR ;init'ed to NAK | |
lda CRCFLG ;CRC or checksum? | |
ora a ;0 means checksum | |
;SELCRC means CRC | |
jz RXCSM | |
mov m,a ;set CRC initial ACK | |
call PCRC ;print ' with CRC' | |
RXCSM: mov a,m | |
jmp TXBYTE ;send the initial ACK | |
;return via TXBYTE | |
;---------------------------------------- | |
;Error: Attempt to write to existing file | |
;---------------------------------------- | |
FILEX: call CMSGXT | |
db 'ERROR: File already exists$' | |
;------------------------- | |
;Error: File create failed | |
;------------------------- | |
FCERR: call CMSGXT | |
db 'File create error',CR,LF | |
db 'Write prot? Dir full?$' | |
;*****Subroutine*************************************** | |
;Initialization: parse command line, set up FCB | |
; return with a=1 for receive, 2 for send | |
;****************************************************** | |
INIT: call ILPRT ;print this message | |
db SOLEN ;message length | |
SOMSG: | |
db CR,LF | |
db '======================================' | |
db CR,LF | |
db '= CP/M XMODEM 1.01 By M. Eberhard =' | |
db CR,LF | |
db '======================================' | |
db CR,LF | |
SOLEN equ $-SOMSG | |
;------------------------------------------------ | |
;Initialize File Control Block for disk transfers | |
;------------------------------------------------ | |
xra a | |
lxi h,FCBEXT | |
mvi b,FCBCLR | |
FCBLUP: mov m,a | |
inx h | |
dcr b | |
jnz FCBLUP | |
;----------------------------------------------------- | |
;Some versions of CP/M misbehave when the specified | |
;drive is not the default drive. So make the specified | |
;drive the default, and put the default back when done | |
;----------------------------------------------------- | |
mvi c,BCDISK ;remember default drive | |
call GOBDOS | |
sta CURDSK | |
;make the requested disk the default disk | |
lda FCBDR ;default=requested drive | |
ora a ;use default? | |
jz DEFDRV ;yes:its ok | |
dcr a ;so 0 is drive a | |
mov e,a | |
mvi c,BSDISK | |
call GOBDOS | |
DEFDRV: | |
;----------------------------- | |
;Point to command line options | |
;----------------------------- | |
lxi h,COMBUF ;CP/M put cmd line here | |
mov b,m ;1st byte is byte count | |
inx h ;point to the string | |
call SSKIP ;skip initial spaces | |
jz HLPEXT ;no parameters? | |
;------------------------------------------- | |
;Skip past the file name, which CP/M already | |
;put in the FCB for us | |
; b = bytes remaining to see in COMBUF | |
; hl points to next chr in COMBUF | |
;------------------------------------------- | |
SKPFIL: call CMDCHR | |
jz NODIR ;no /R or /S | |
cpi ' ' ;hunt for a space | |
jnz SKPFIL | |
;------------------------------------------------ | |
;Parse all command line options and set variables | |
;accordingly. Each option must be preceeded by a | |
;'/' Options may be preceeded by any reasonable | |
;number of spaces. | |
;------------------------------------------------ | |
OPTLUP: call SSKIP ;skip spaces | |
jz OPTDON ;end of input line? | |
cpi '/' ;all start with / | |
jnz BADINP ;error:no slash | |
call CMDCHR ;Get an option chr | |
jz BADINP ;Error: nothing after / | |
sta PAR1 ;put it in error msg | |
;------------------------------------------------ | |
;Got a command line option in a. Loop through | |
;table of options, looking for a match. Update | |
;the appropriate option variable with the table | |
;value. Error exit if not in table. | |
; TRASH a,c,de | |
;------------------------------------------------ | |
push h ;Save COMBUF pointer | |
lxi h,OPTTAB | |
CHKLUP: cmp m ;Match? (alpha order) | |
inx h ;get var value | |
mov c,m | |
inx h ;get var address | |
mov e,m | |
inx h | |
mov d,m | |
inx h | |
jz OPMTCH ;matched an option | |
jnc CHKLUP ;No match: keep looking | |
;------------------------------------------------ | |
;Illegal option. Print message and return to CP/M | |
;------------------------------------------------ | |
call CMSGXT ;Exit with this message | |
db 'ERROR: /' | |
PAR1: db '&' ;parameter goes here | |
db '$' | |
;------------------------------------------- | |
;Option match. Save value, and look for more | |
;------------------------------------------- | |
OPMTCH: mov a,c ;value from table | |
stax d ;save value | |
pop h | |
jmp OPTLUP ;look for more | |
;-------------------------- | |
;Done parsing command line. | |
;-------------------------- | |
OPTDON: | |
;------------------------------------------------ | |
;* CUSTOMIZATION * | |
;----------------- | |
; If you replace the calls to RDR: and PUN: with | |
; direct access to your I/O ports, here is a good | |
; place to initialize your I/O port hardware. | |
;------------------------------------------------ | |
;------------------------------------ | |
; Did we get a direction? return with | |
; a=(XMODE) + 1 if so, error if not | |
;------------------------------------ | |
lda XMODE ;did /R or /S get set? | |
inr a ;-1 means uninit'ed | |
rnz ;ret w/ XMODE+1 | |
; fall into NODIR - no options with this command | |
;----------------------------- | |
;ABORT: No direction specified | |
;----------------------------- | |
NODIR: call CMSGXT ;Exit with this message | |
db 'ERROR: Must specify /S or /R$' | |
;--------------------------------------------------- | |
;Input error exits. Print message and return to CP/M | |
;--------------------------------------------------- | |
BADINP: call ILPRT ;print this message | |
db IPELEN ;message length | |
IPEMSG: db CR,LF,'Huh?',CR,LF | |
IPELEN equ $-IPEMSG | |
; Fall into HLPEXT | |
;*****Exit********************************************* | |
; Print help screen and exit | |
;****************************************************** | |
HLPEXT: call CMSGXT ;Exit w/ this message | |
db 'Usage:',CR,LF | |
db ' XMODEM <filename.ext> {/R or /S} [/C]' | |
db CR,LF | |
db ' /R to receive, /S to send, one must be specified' | |
db CR,LF | |
db ' /C to receive with checksums, otherwise CRC' | |
db ' error checking',CR,LF | |
db ' (Transmit error-check mode is set by receiver)' | |
db CR,LF | |
db ' Input from RDR:, output to PUN:' | |
db CR,LF | |
if CSTRDR | |
db ' Special BIOS RDR: returns with Z set if not' | |
db ' ready' | |
endif | |
db CR,LF | |
db ' Each + means good block, each - means block' | |
db ' retry' | |
db '$' | |
;*****Subroutine*************************************** | |
;Skip over spaces in command line buffer until a non- | |
;space character is found | |
; On Entry: | |
; b has remaining COMBUF byte count | |
; hl points to the next chr in COMBUF | |
; On Exit: | |
; a = chr from COMBUF | |
; b has been decremented | |
; hl has been advanced | |
; Z=1 means end of buffer (and a is not valid) | |
;****************************************************** | |
SSKIP: call CMDCHR | |
rz ;Z set for nothing left | |
cpi ' ' ;white space? | |
jz SSKIP | |
ret ;chr in a, Z clear | |
;*****Subroutine*************************************** | |
;Get next chr from command line buffer | |
; On Entry: | |
; b has remaining COMBUF byte count | |
; hl points to the next chr in COMBUF | |
; On Exit: | |
; a = chr from COMBUF, parity stripped | |
; b has been decremented | |
; hl has been advanced | |
; Z=1 means end of buffer, and a=0 | |
; Carry = 0 | |
;****************************************************** | |
CMDCHR: mov a,b ;End of buffer already? | |
ora a ;also clears carry | |
rz ;and clears a | |
mov a,m ;get buffer chr | |
inx h ;bump buffer pointers | |
dcr b | |
ani 7FH ;Strip parity, clear Z | |
ret ;with Z cleared | |
;****************************************************** | |
;Command Line Options Table | |
; Table entries must be in alphabetical order, and | |
; terminated with 0FFh | |
; | |
; Each entry is 4 bytes long: | |
; byte1 = uppercase legal option letter | |
; byte2 = value for variable | |
; bytes3-4 = variable address | |
;****************************************************** | |
OPTTAB: | |
;Error check mode: 0 means checksum, 1 means CRC | |
db 'C',0 | |
dw CRCFLG | |
;Select receive mode: puts 0 in XMODE | |
db 'R',0 | |
dw XMODE | |
;Select sendmode: puts 1 in XMODE | |
db 'S',1 | |
dw XMODE | |
;end of table marker | |
db 0FFh | |
END | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment