; Converted from Intel 8080 to Zilog Z80 mnemonics via XIZ ; so this now assembles using Microsoft M80 or ZSM4 macro ; assemblers. ; ; This version has the Z80-MBC2 modifications included ; (these were done by J4F when he released the original ; Z80-MBC2 version of XMODEM - based on version 2.7). To ; enable these changes you need to define the MBC2 symbol ; on the assembler command line. ; ; Assemble using ; ; ZSM4 =XM29MBC2/dMBC2 ; ; to enable them, and link with the Digital Research linker ; ; LINK XMODEM=XM29MBC2 ; ; To use on the Z80-MBC2 - be sure to add the new /K20 switch ; to the XMODEM.CFG configuration file - or use it on the ; the command line. This limits the amount of file buffering ; to 20KB (and reduces the risk that file writes to the microSD ; card disk on the Z80-MBC2 will not cause the protocol to ; time-out. ; ; by Tony Nicholson 17-Nov-2021 ; .z80 aseg ;============================================================== ; CP/M Xmodem by Martin Eberhard ;============================================================== ;A command line and configuration file-driven program for ;transferring files to and from a CP/M system using the ;Xmodem protocol, supporting both the original checksum error ;checking, and also the newer Xmodem-CRC error checking ; ;This program has been tested at 115.2 K Baud, running on a ;4 MHz Z80 with 0-wait state memory, and performing direct I/O ;via a CCS 2719A serial board (which is based on the Z80-DART). ;Calculations show that a 2 MHz 8080 ought to be able to send ;and receive up to 76.8K baud (with for example a Cromemco ;TU-ART), so long as there are not too many wait states. ; ; To use Xmodem, type: ; XMODEM <filename> {option list} ; ; A file name is madatory, and can be any legal CP/M file ; name. If you are receiving with Xmodem and the file already ; exists, then you will be asked if it should be overwritten. ; If you are sending, then the file must exist, obviously. ; ; Xmodem first looks for a file called XMODEM.CFG on CP/M's ; default drive. If found, then this file is parsed for ; options, the same as the command line. XMODEM.CFG is parsed ; first, so that options that are set on the command line will ; override those set in XMODEM.CFG. ; ; XMODEM.CFG and the command line both support the following ; options (though some are less useful on the command line.) ; ; /R Specifies Receive mode ; ; /S Specifies Send mode ; ; If neither /R nor /S is specified then you will be asked. ; ; /C Selects checksum error checking when receiving; ; otherwise receiving uses CRC error checking. When ; sending, the error-checking mode is set by the receiver. ; ; /E Specifies an enhanced RDR routine that returns with the ; Z flag set if no character is waiting. Note that this ; option does not actually select the RDR device as the ; transfer port. (/X2 does.) ; ; /In h0 h1 h2... (max h11) Defines assembly code for the ; custom I/O port (used by the /X3 option), using Intel ; 8080 machine language. ; ; n = 0 specifies initialization code, to be run when ; command line and config file parsing are done. ; All registers may be trashed. This is useful ; for setting the baud rate, etc. ; ; n = 1 installs a transmit byte routine. ; on entry to this routine, the character to send ; is in c. do not trash de or hl. Sample custom ; transmit routine (for SOLOS): ; 48 mov b,c ;SOLOS wants chr in b ; 3e 01 mvi a,1 ;serial pseudoport ; cd 1c c0 call AOUT ;output character ; Encode as follows: ; /I1 48 3E 01 CD 1C C0 ; ; n = 2 installs a receive status subroutine, which ; should end with the Z flag set if no character ; is waiting, and optionally the chr in a. Do not ; trash any registers except psw. Sample routine: ; 3e 01 mvi a,1 ;serial pseudoport ; cd 22 c0 call AINP ;input character, ; ;Z set if none ; Encode as follows: ; /I2 3E 01 CD 22 C0 ; ; n = 3 installs a receive character routine, assuming a ; character is waiting. Ends with the character in ; a. Trashes no registers except psw. If the status ; rutine returns the character, (e.g. for SOLOS), ; then no /I3 option is required. ; ; /Knn sets the maximum buffer size in k-bytes. If no /K ; option, or if the specified size is larger than the ; free RAM, then all free RAM will be used. ; ; /M Print message on console. This lets you tell the user ; e.g. what port is set up for direct I/O in XMODEM.CFG ; ; /O Specifies an output sequence for an I/O port, intended to ; initialize the direct I/O port. The form of this ; option is: ; /O pp h1 h2 ... hn ; where pp is the port address, and all the subsequent ; bytes are sent to that port. You can have more than ; one /O option, allowing you e.g. to initialize the ; UART and also to set the baud rate. ; ; /P Defines the direct I/O transfer port (used by the /X2 ; option). The form of this command is: ; /P ss dd qq rr tt ; where: ; ss is the status port (for Rx and Tx) ; dd is the data port (for both Rx and tx) ; qq is 00 if the ready bits are true when low ; and 01 if the ready bits are true when high ; rr is the receive-ready bit mask ; tt is the transmit-ready bit mask ; ; Xmodem assumes that the receive port works like this: ; RXWAIT: in <status port> ; ani <Rx Mask> ; jz/jnz RXWAIT ; nop ; in <data port> ; ; ..and the transmit port works like this: ; mov c,a ; TXWAIT: in <status port> ; ani <Tx Mask> ; jz/jnz TXWAIT ; mov a,c ; out <data port> ; ; Any port that can work with these templates will work ; with Xmodem's /X2 option. ; ; All variables for the /O and /P commands are in hexidecimal, ; and must be exactly two characters long. Legal characters ; are: {0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F} (No lowercase.) ; ; /Q Specifies quiet mode, preventing messages and pacifiers ; from being printed on the console during transfer. This ; is particularly useful if the port you are using is also ; the port used for CP/M's console. Otherwise, a '+' will ; be printed on CON for every succesful block, and a '-' ; will be printed for every block retry. ; ; /Xn Specifies the transfer port: ; n=0 uses CP/M's CON device. Contrary to CP/M specs, ; the CON input port must not strip parity. ; ; n=1 uses CP/M'd RDR and PUN devices. Contrary to CP/M ; specs, the RDR input port must not strip parity. ; Also use /E if the RDR driver has been enhanced ; to return with the Z flag set when no character is ; waiting. (Otherwise, no timeout is possible when ; waiting for a RDR character.) ; ; n=2 uses direct I/O, which can be set up using the /P ; option. If no /P option is entered, then /X2 will ; select a MITS 88-SIO port. ; ; n=3 uses custom-patched I/O routines, set with the ; /I option. If no /I option is entered then ; transfers with /X3 will just cause errors. ; ; /Zn Specifies CPU speed in MHz - n is between 1 and 6. ; The CPU speed used for timeouts while sending or ; receiving. The default is 2 MHz for an 8080, and 4 MHz ; for a Z80. (Xmodem can tell the difference.) ; ; A semicolon begins a comment on a new line. All characters ; from the ';' until the end of the line will be ignored. ; ; Here is a sample XMODEM.CFG file: ; ; /MDirect I/O is configured for 88-2SIO port B ; /P 12 13 01 01 02 ;set up for an 88-2SIO Port B ; /O 12 03 15 ;8 bits, 1 stop, no parity ; /X2 ;use direct I/O ; ; You can usually abort a transfer with ^C on the console. ; ;============================================================== ;PROGRAM NOTES ; ;Reducing the buffer size with the /K option can also reduce ;the amount of time consumed by disk accesses. ; ;The /P option modifies code in the direct I/O transmit and ;receive routines. This is because the 8080 has no command to ;IN or OUT with a port that is specified by a register value - ;so self-modifying code is the only way. ; ;Code that is only used during initialization is at the end, ;and gets overwritten by the block buffer. Xmodem uses all ;available RAM (past its 2K runtime code, up through CP/M's ;CCP) for buffering received and transmitted data, to speed up ;transfers by minimizing disk accesses. This allows Xmodem to ;run comfortably in (for example) a 16K-byte CP/M system ;(with 10K of user memory), and still have a decent 8K data ;buffer and reasonably robust options and messages. ; ;The XModem spec calls for a sender to time out after 10 ;seconds if it does not receive the ACK after sending a ;complete block. XMODEM.COM holds off the sender while it ;writes to disk by waiting to send the ACK for the most recent ;block. Depending on the BIOS, CP/M can be pretty slow when ;writing to double-density floppy disks (due for example to ;its bloxking/deblocking algorithm, or verified writes). To ;prevent the sender from timing out while Xmodem writes to ;disk, XMODEM.COM receives an occasional Xmodem block while it ;is performing disk writes. To stay within the 10-second ;timeout spec with a slow deblocking algorithm in the BIOS, ;WBPERX should be no arger than about 40. (Hyperterminal is ;happy with a value even higher than 64.) CP/M is usually much ;faster at reading from disk so an equivalent feature is not ;implemented for sending files. ; ;The position of the CRC calculation table (CRCTAB) is forced ;to be on a 256-byte page boundary. Subroutines have been ;shoved around to minimize the number of wasted bytes before ;this table. ; ;This code implicitly assumes that Xmodem blocks are the same ;size as CP/M disk records - 128 bytes each. ; ;This program's help screens will display correctly on displays ;that are 16X64 characters or larger. ; ; Assemble with Digital Research's ASM Assembler ; ;============================================================== ; 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: ; ; 2.9-mbc2 17 November 2021 Tony Nicholson ; Converted to Zilog mnemonics (via XIZ). ; Incorporated the original Just4Fun (J4F) modifications ; for the Z80-MBC2 which are enabled via conditional assembly ; when the MBC2 symbol is defined. ; ; 2.9 14 November 2021 M. Eberhard ; Eat received characters until the line is idle for 1 second, ; before receiving a file. Squeeze code a bit to keep it under ; 4 Kbytes. ; ; 2.8 9 April 2019 M. Eberhard ; Receive blocks occasionally while flushing the buffer to ; disk, to prevent sender timeout. Recognize CAN chr. Send CAN ; when aborting reception. Send EOT when aborting during ; sending. Add /K option. Better abort algorithm. Some ; improved messages. Squeeze code to keep it under 4K-bytes. ; Increase the number of bytes for the /I routines from 8 to ; 12 bytes. Eliminate seperate stack for use during INIT. ; When receiving, chuck the first received chr if it's bogus. ; ; 2.7 9 October 2017 M. Eberhard ; Fix stack bug when switching stacks ; ; 2.6 10 September 2017 ; Significant speed up by using table-driven CRC algorithm. ; Make the initialization code modify the jump addresses in ; RXBYTE and TXBYTE based on the type of transfer port ; selected, eliminated some subroutine calls, and a few other ; speed optimizations. Ask user for direction if no /R or /S ; was specified. Use all RAM (even more than 32K) for the ; buffer. perform initial buffer fill on Sends (before ; determining error checking mode), while the disk is still ; spinning. Flush a byte from the receiver port at the ; beginning, if possible. Squeeze a bit to keep disk file ; smaller than 4K-bytes. Tidy up comments and labels. ; ; 2.5 9 May 2017 M. Eberhard ; Clean up file name in FCB if a command option was crammed ; against the file name (no spaces) on the command line. ; ; 2.4 4 August 2016 M. Eberhard ; Fix bug in TXLOOP that would cause sending with checksums ; to fail. Fix bug causing a spurious block after the 1st ; buffer-full of received data. (Thanks to Bob Bell for ; helping find and fix these bugs.) Add /I cmd, and add /X3 ; option for custom port routine patching. (This makes it ; possible to call external I/O routines, such as SOLOS or ; POLEX.) Also cleaned up comments. ; ; 2.3 9 Oct 2014 M. Eberhard ; Eliminate intermediate data buffer. Support CP/M 1.4 ; ; 2.2 7 Oct 2014 M. Eberhard ; fix error-reporting bug, increase stack size for BDOS ; ; 2.1 3 Oct 2014 M. Eberhard ; Fix bug in reporting the source of an error ; Speed up RDR/PUN ; require CR after "Y" on overwrite question ; ; 2.0 1 OCT 2014 M. Eberhard ; New major release: ; + Supports a configuration file (XMODEM.CFG), with same ; options as on the command line ; + combine features of all 1.0x assembly options ; + Define direct I/O port in XMODEM.CFG (or on command line) ; + User-set CPU speed (/Z), overrides 8080/Z80 defaults ; + Option to delete file on /R, if it already exists ; + Include which port we are using in announcement prior to ; Xmodem file transfer ; + A few new timeouts, to prevent hanging in odd situations ; + Several other minor enhancements ; ; 1.0x 06 APR 2013 through 27 SEP 2014 M. Eberhard ; Command-line driven versions Based on Xmodem for CDOS ; (Z-80), version 1.03 by M. Eberhard ;============================================================== ;--------------------------- ;This code's revision number ;--------------------------- version equ 0209h ; High byte = major revision number ; Low byte = minor version false equ 0 true equ not false errlim equ 10 ; Max error-retries. 10 is standard. dbsiz equ 63 ; Default buffer size (kbytes) ; (Uses the smaller of this ; And "all available RAM") crstal equ 10 ; Delay (in seconds) before ; ..receiving when the console ; ..is the transfer port ;The following parameter exists to prevent the sender on ;the other end of the serial port from timing out while ;we do disk writes. See PROGRAM NOTES above. wbperx equ 40 ; # of 128-byte disk records to ; Write before requesting ; Another Xmodem block (max=256) ;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) blksiz equ 128 ; Bytes per Xmodem block ; DO NOT CHANGE. BLKSIZ must be 128 secsiz equ 128 ; CP/M logical-sector size must be 128 ;Progress pacifiers printed on the console pacack equ '+' ; Sent/Received a good block pacnak equ '-' ; Sent/Received a NAK paclin equ 60 ; Pacifiers per line ;The following cycle values are used in the timing loops for ;timeouts when transferring via the CON or the RDR and PUN. ;It is ok if they are imperfect - the Xmodem protocol is quite ;tolerant of timing variations. The example BIOS Code below was ;used to estimate these cycle counts for CSTIME and CRTIME. cstime equ 85 ; Number of CPU cycles that BIOS uses to ; ..return CON status crtime equ 95 ; Number of cpu cycles that BIOS uses to ; ..return with no chr ready for custom ; ..RDR driver extime equ 135 ; Number of cycles an external receive ; Routine (e.g. SOLOS) will use for testing ; Status, when a chr is not ready. ;===Example BIOS Code========================================== ;Timing est. for getting reader status via custom RDR driver. ;Assume the IOBYTE is implemented, and assume RDR=UR1 ;(the desired RDR port) ;This takes 95 8080 cycles. ; jmp RDRIN ;(10) BIOS jump vector ; ... ;RDRIN: lda IOBYTE ;(13) which reader port? ; ani 0Ch ;(7) ; jz <not taken> ;(10) not RDR=TTY ; cpi 8 ;(7) ; jc <not taken> ;(10) not RDR=HSR ; jz UR1ST ;(10) RDR=UR1 ; ... ;UR1ST: in <port> ;(10) get reader stat ; ani <mask> ;(7) test, set Z ; rz ;(11) return from BIOS ;===Example BIOS Code========================================== ;Timing estimate for getting console status. ;Assume the IOBYTE is implemented, and assume CON=CRT ;This takes 85 8080 cycles. ; jmp CONST ;(10) BIOS jump vector ; ... ;RDRIN: lda IOBYTE ;(13) which CON port? ; ani 03h ;(7) ; jz <not taken> ;(10) not CON=TTY ; cpi 2 ;(7) ; jc CRTST ;(10) CON=CRT ; ... ;CRTST: in <port> ;(10) get reader stat ; ani <mask> ;(7) test, set Z ; rz ;(11) return from BIOS ;============================================================== ;************** ; 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 bdosa equ wboot+6 ; First address of BDOS ; (can overlay up to here) 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) fcbnl equ 11 ; File name length 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 ;------------------------------------------ ;BDOS Function Codes, passed in register C ;Note: CON, RDR, and PUN I/O are 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. BIOS calls are used ;(instead of BDOS calls) where speed matters. ;-------------------------------------------- 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 ifdef mbc2 constmd equ 5ah ;Z80-MBC2 set CONST mode to 8-bit endif ;----------------------------------- ;Altair 88-SIO Registers and Equates ;(Used as a default direct port.) ;----------------------------------- siosta equ 00h ; Status port siodat equ 01h ; Data port siordf equ 00000001b ; -RX Data register full siotde equ 10000000b ; -TX Data register empty ;---------------- ;ASCII Characters ;---------------- soh equ 1 ; Start of 128-byte block ;STX equ 2 ;Start of 1K-byte block ctrlc equ 3 ; Control-C for user-abort eot equ 4 ; End Xmodem session ack equ 6 ; Xmodem block acknowledge tab equ 9 ; Horizontal tab lf equ 0ah ; Linefeed cr equ 0dh ; Carriage return nak equ 15h ; Xmodem block negative ACK can equ 18h ; Xmodem Cancel eof equ 1ah ; ^Z end of XMODEM.CFG file quote equ 2ch ; Single quote selcrc equ 'C' ; Selects CRC mode at initiation ;********************* ;* Beginning of Code * ;********************* org usarea ; Normal place for CP/M programs ;------------------------------------------------------- ;Set the top address of the buffer, in case we don't get ;a /K option. This is actually INIT code, and is placed ;here so that the stack can overwrite it harmlessly. ;------------------------------------------------------- ld a,(bdosa+1) ; Msb of start of BDOS dec a ; Last allowed page ld (bufmax),a ;-------------------------------------------------------- ;Initialize, using code that gets wiped out by the BUFFER ;-------------------------------------------------------- jp init ;******************************************************** ;The local stack overwrites the above code, and also the ;top of the COMBUF (which is just below USAREA). ;******************************************************** lstack equ $ ;***Function********************************* ;Send a CP/M file in Xmodem format ;On Entry: ; Disk file is open for reading ; Initial NAK or SELRC has already been sent ; FCB is valid ; BUFCNT = 0 ; XBLOCK = 0 ; BUFMAX = Max address in BUFFER/256 ; PACCNT = 0FFh if quiet mode, 0 otherwise ; RTORET is set to WAITNK ;******************************************** txfile: ;------------------------------------------------------- ;Do initial buffer fill while the disk is still spinning ;------------------------------------------------------- call filbuf ;------------------------------------------------------ ;Get the transfer error checking mode from the receiver ;------------------------------------------------------ call gtmode ; Wait for NAK or SELCRC to ; ..determine cksum or CRC mode ld hl,ackerr ; Timeout return address ld (rtoret),hl ; ..for RXBYTE ;--------------------------------------------------------- ;Transmit the entire file in 128-byte blocks. Whenever the ;buffer is empty, refill it from disk and test for EOF. ;--------------------------------------------------------- txloop: ld hl,(bufcnt) ; Block count in buffer ld a,h ; 16-bit test for 0 or l call z,filbuf ; Empty? go read disk ; Returns BLKPTR=BUFFER ; ..and hl=BUFCNT=blocks in buf ; Exit directly if no more data dec hl ; One fewer block in ld (bufcnt),hl ; ..the buffer ld hl,(xblock) ; Inc 16-bit Xmodem block # inc hl ld (xblock),hl ;--------------------------------------- ;Send the block header: SOH, 8-bit Block ;number, Complimented 8-bit block number ;(Block send retry re-entry point) ;On Entry: ; XBLOCK=16-bit Xmodem block number ;--------------------------------------- txrtry: ld a,soh ; SOH first call txbyte ld a,(xblock) ; 8-bit block number call txbyte ; (preserves a) cpl ; Complimented block call txbyte ;------------------------------------------- ;Send the next BLKSIZ-byte block from BUFFER ;On Entry: ; BLKPTR=DMA address ;On Exit: ; Data checksum is in c ; 16-bit data CRC is in de ; hl=BLKPTR+128 ;------------------------------------------- ld bc,blksiz*256+0 ; B=bytes/block, ; ...clear checksum in c ld d,c ; Clear CRC in de ld e,c ld hl,(blkptr) ; (hl) = data in BUFFER txblup: ld a,(hl) ; Get a data byte call txbyte ; Send it ;------------------------------------------------------ ;(Inline for speed) ;Update the 16-bit CRC and 8-bit checksum with one more ;data byte. Speed matters here. For speed, this code ;assumes that the CRC table is on a page boundary, and ;that the table is split, with the high bytes in the ;first half and the low bytes in the second half. ; a has the byte just transmitted ; c has checksum so far ; de has the CRC so far ;------------------------------------------------------ push hl ; (11) ld h,crctab/256 ; (7)CRC table addr high byte xor d ; (4)compute lookup address ld l,a ; (5)low byte of lookup xor d ; (4)recover original byte add a,c ; (4)update checksum too ld c,a ; (5) ld a,(hl) ; (7)compute new CRC high byte xor e ; (4)using the table ld d,a ; (5) inc h ; (5)low bytes are in the ld e,(hl) ; (5)..other half of the table pop hl ; (10) ;----------------------------------------------------------- ;CRC is done. Next byte, unless we have transmitted them all ;----------------------------------------------------------- inc hl ; Next byte dec b jp nz,txblup ; Loop through block ;-------------------------------------------- ;Send checksum or 16-bit CRC, based on CRCFLG ; c= 8-bit checksum ; de = CRC16 ; CRCFLG <>0 if CRC mode enabled ; hl=BLKPTR+128 ;-------------------------------------------- ld a,(crcflg) ; Checksum or CRC? or a ; Clear Z if CRCFLG jp z,txcksm ; Jump to send checksum ld a,d call txbyte ; Send byte in a ld c,e ; Now the 2nd CRC byte txcksm: ld 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 the block. ; hl=BLKPTR+128 ;------------------------------------------- call getack ; Wait for the ACK ; A=0, Z set if ACK jp nz,txrtry ; NZ: timeout or NAK ld (errcnt),a ; A=0 ld (blkptr),hl ; Next block in the buffer ;---------------------------------------------------- ;Ack received. Print pacifier, and go send next block ;---------------------------------------------------- call pacok jp txloop ;***Function******************************* ;Receive Xmodem file & save it to disk ;-->Entry is at RXFILE<-- ;On Entry: ; Carry is clear ; a=(NAKCHR) (which is NAK or SELCRC), ; to start the transfer ; FCB is valid ; Disk file has been created and is open ; XBLOCK=0 ; EOFLAG = 00h ; FCB is valid ; BUFPTR = BUFFER ; BUFMAX = Max address in BUFFER/256 ; XBLOCK = 0 ; PACCNT = 0FFh if quiet mode, 0 otherwise ; RTORET is set to RXSERR ;****************************************** ;------------------------------------------------------- ;Loop to receive Xmodem blocks until we get an EOT. ;Flush the buffer whenever it becomes full. ; XBLOCK=16-bit block number ;EOFLAG = 00h if no EOT yet ; = FFh if EOT received and it has been ACKed ;------------------------------------------------------- rxloop: ld a,(eoflag) ; Msb means EOT's been ACKed add a,a ; Carry means EOT's been ACKed ld a,ack ;===================================== ;Routine Entry ;(a=NAKCHR and Carry cleared on entry) ;===================================== rxfile: push af ; Remember carry call nc,txbyte ; Ack a block pop af ; Carry means EOT's been ACKed call nc,rxblk ; Go receive 1 block, ; Unless we got the EOT ; On return, carry means BUFFER ; ..is full or EOT or CAN call c,wflush ; Flush if needed ; Carry set if none flushed jp nc,rxloop ;WFLUSH returned with carry set: success call cilprt ; Success message defb 'O','K'+80h ;Fall into RXEND to close the file and report ;***Exit***************************************** ;Close the file. If no blocks were written, then ;erase the empty file; otherwise, print the ;number of blocks received. Then return to CP/M. ;On Entry: ; XBLOCK = block count ;************************************************ rxend: ld c,bclose ; CP/M CLOSE FILE function call fcbdos ; Sets de=FCB jp m,fclerr ; -1 meant close error ld a,(xmode) ; Did we ever write? add a,a ; Msb means yes jp c,rrxcnt call ferase ; N: erase empty file jp m,efexit ; BDOS returns FFh if failed rrxcnt: call cilprt defb 'Received',' '+80h ;Fall into REPCNT ;***Exit******************************************* ;Report 16-bit block count, and then return to CP/M ;On Entry: ; XBLOCK = block count ; either 'sent ' or 'received ' already printed ;************************************************** repcnt: ld hl,(xblock) call pdec16 ; Print block count in decimal call msgxit ; Print & return to CP/M defb ' block','s'+80h ;***Subroutine***************************************** ;Get an ACK from the receiver. If we get a NAK, then ;print the NAK pacifier on the console. Opportunity for ;user to abourt (with ^C) if timeout waiting for ACK. ;On Entry: ; RTORET = ACKERR (RXBYTE eror return adress) ;On Exit: ; Z set and a=0 if ACK received ; Abort if CAN received ; Z clear if NAK received, bogus, or timeout ; If too many errors, abort ;Good ack: all other registers preserved ;Bad ack: trashes hl ;****************************************************** ;----------------------------------------- ;Get a received byte, or timeout. Return ;with Z set and carry clear if it's an ACK ;----------------------------------------- getack: ld a,ackto*2 ; ACK-wait timeout value call rxbyte ; Go get a character ; Timeout will "return" ; ..to ACKERR xor ack ; Did we get an ACK? ret z ; Y: Z set, a=0 ;--------------------------------------------- ;Is the receiver trying to cancel the session? ;--------------------------------------------- xor can xor ack ; Xmodem Cancel chr jp z,rxcan ; Cancel with a=0 ;Fall into ACKERR ;***Subroutine*********************************** ;If anything else (including a NAK, or an RXBYTE ;timeout, which jumps to ACKERR), print an error- ;pacifier and return with Z cleared, unless the ;user aborts. If too many errors, then abort. ;On Exit: ; Z clear ;Trashes a,bc,hl ;************************************************ ackerr: call pacerr ; Opportunity to abort, ; Pacifier if allowed ld hl,errcnt ; Bump error count inc (hl) ld a,(hl) ; Too many errors? cp errlim ret c ; N: Z cleared for fail ;----Exit------------------------------ ;Abort waiting for ACK: Too many errors ;-------------------------------------- call canabt ; Cancel and abort defb (errlim/10)+'0' ; Too many bad ACKs defb (errlim-((errlim/10)*10))+'0' defb ' ACK error','s'+80h ;***Subroutine*********************************** ;Print Message at hl ;Follow all CR's with LF's ;On Entry: ; hl points to the message ; The last message chr has its msb set. ;On Exit: ; hl points to the next address past the string ; Z cleared ;Trashes psw,c ;************************************************ hlprnt: ld a,(hl) ; Get a character and 7fh ; Strip end marker hlprt1: call printa ; Returns chr in c ld a,cr ; Just print a CR? cp c ; C has printed chr ld a,lf jp z,hlprt1 ; Y: follow with LF ld a,(hl) ; Was that the last chr? or a ; Msb was set if so inc hl ; Next byte jp p,hlprnt ; Do all bytes of msg ret ;***Table******************************* ;CRC Calculation Table ;strategically placed on a page boundary ;*************************************** tpager equ $ ; Force page alignment org (tpager+255) and 0ff00h crctab: ;high bytes defb 000h,010h,020h,030h,040h,050h,060h,070h defb 081h,091h,0a1h,0b1h,0c1h,0d1h,0e1h,0f1h defb 012h,002h,032h,022h,052h,042h,072h,062h defb 093h,083h,0b3h,0a3h,0d3h,0c3h,0f3h,0e3h defb 024h,034h,004h,014h,064h,074h,044h,054h defb 0a5h,0b5h,085h,095h,0e5h,0f5h,0c5h,0d5h defb 036h,026h,016h,006h,076h,066h,056h,046h defb 0b7h,0a7h,097h,087h,0f7h,0e7h,0d7h,0c7h defb 048h,058h,068h,078h,008h,018h,028h,038h defb 0c9h,0d9h,0e9h,0f9h,089h,099h,0a9h,0b9h defb 05ah,04ah,07ah,06ah,01ah,00ah,03ah,02ah defb 0dbh,0cbh,0fbh,0ebh,09bh,08bh,0bbh,0abh defb 06ch,07ch,04ch,05ch,02ch,03ch,00ch,01ch defb 0edh,0fdh,0cdh,0ddh,0adh,0bdh,08dh,09dh defb 07eh,06eh,05eh,04eh,03eh,02eh,01eh,00eh defb 0ffh,0efh,0dfh,0cfh,0bfh,0afh,09fh,08fh defb 091h,081h,0b1h,0a1h,0d1h,0c1h,0f1h,0e1h defb 010h,000h,030h,020h,050h,040h,070h,060h defb 083h,093h,0a3h,0b3h,0c3h,0d3h,0e3h,0f3h defb 002h,012h,022h,032h,042h,052h,062h,072h defb 0b5h,0a5h,095h,085h,0f5h,0e5h,0d5h,0c5h defb 034h,024h,014h,004h,074h,064h,054h,044h defb 0a7h,0b7h,087h,097h,0e7h,0f7h,0c7h,0d7h defb 026h,036h,006h,016h,066h,076h,046h,056h defb 0d9h,0c9h,0f9h,0e9h,099h,089h,0b9h,0a9h defb 058h,048h,078h,068h,018h,008h,038h,028h defb 0cbh,0dbh,0ebh,0fbh,08bh,09bh,0abh,0bbh defb 04ah,05ah,06ah,07ah,00ah,01ah,02ah,03ah defb 0fdh,0edh,0ddh,0cdh,0bdh,0adh,09dh,08dh defb 07ch,06ch,05ch,04ch,03ch,02ch,01ch,00ch defb 0efh,0ffh,0cfh,0dfh,0afh,0bfh,08fh,09fh defb 06eh,07eh,04eh,05eh,02eh,03eh,00eh,01eh ;Low Bytes defb 000h,021h,042h,063h,084h,0a5h,0c6h,0e7h defb 008h,029h,04ah,06bh,08ch,0adh,0ceh,0efh defb 031h,010h,073h,052h,0b5h,094h,0f7h,0d6h defb 039h,018h,07bh,05ah,0bdh,09ch,0ffh,0deh defb 062h,043h,020h,001h,0e6h,0c7h,0a4h,085h defb 06ah,04bh,028h,009h,0eeh,0cfh,0ach,08dh defb 053h,072h,011h,030h,0d7h,0f6h,095h,0b4h defb 05bh,07ah,019h,038h,0dfh,0feh,09dh,0bch defb 0c4h,0e5h,086h,0a7h,040h,061h,002h,023h defb 0cch,0edh,08eh,0afh,048h,069h,00ah,02bh defb 0f5h,0d4h,0b7h,096h,071h,050h,033h,012h defb 0fdh,0dch,0bfh,09eh,079h,058h,03bh,01ah defb 0a6h,087h,0e4h,0c5h,022h,003h,060h,041h defb 0aeh,08fh,0ech,0cdh,02ah,00bh,068h,049h defb 097h,0b6h,0d5h,0f4h,013h,032h,051h,070h defb 09fh,0beh,0ddh,0fch,01bh,03ah,059h,078h defb 088h,0a9h,0cah,0ebh,00ch,02dh,04eh,06fh defb 080h,0a1h,0c2h,0e3h,004h,025h,046h,067h defb 0b9h,098h,0fbh,0dah,03dh,01ch,07fh,05eh defb 0b1h,090h,0f3h,0d2h,035h,014h,077h,056h defb 0eah,0cbh,0a8h,089h,06eh,04fh,02ch,00dh defb 0e2h,0c3h,0a0h,081h,066h,047h,024h,005h defb 0dbh,0fah,099h,0b8h,05fh,07eh,01dh,03ch defb 0d3h,0f2h,091h,0b0h,057h,076h,015h,034h defb 04ch,06dh,00eh,02fh,0c8h,0e9h,08ah,0abh defb 044h,065h,006h,027h,0c0h,0e1h,082h,0a3h defb 07dh,05ch,03fh,01eh,0f9h,0d8h,0bbh,09ah defb 075h,054h,037h,016h,0f1h,0d0h,0b3h,092h defb 02eh,00fh,06ch,04dh,0aah,08bh,0e8h,0c9h defb 026h,007h,064h,045h,0a2h,083h,0e0h,0c1h defb 01fh,03eh,05dh,07ch,09bh,0bah,0d9h,0f8h defb 017h,036h,055h,074h,093h,0b2h,0d1h,0f0h ;***Subroutine****************************************** ;Receive & validate a block, and see if we got an EOT ; XBLOCK=16-bit block number of the last block received ;On Entry: ; BLKPTR = address for next block ; BUFCNT = number of blocks in the buffer ; XMODE<1> = 1 iff called from within FLUSH ; (meaning CCTRLC should not try to flush) ; BUFMAX = Max address in BUFFER/256 ;On Exit (valid block): ; Carry set if buffer full ; BLKPTR points to next space ; BUFCNT incremented ;On Exit, EOF received ; Carry set ; EOFLAG = FFh ; ACK has NOT been sent ;Trashes all registers ;******************************************************* rxblk: xor a ld (errcnt),a ; Clear error count ;--------------------------------------- ;Wait for SOH from sender to start ;reception, go investigate anything else ;--------------------------------------- ;Bad block retry re-entry point rxrtry: ld a,sohto*2 ; Timeout for SOH call rxbyte cp soh ; Did we get an SOH? jp nz,notsoh ; If not, see what we got ;------------------------------------------------ ;Got an SOH at beginning of the block. Now get ;the rest of the block header: 8-bit Block number ;followed by the complemented 8-bit block number ;------------------------------------------------ ld a,nak ; We have received ld (nakchr),a ; ..at least one SOH ld (rx1st),a ; We've received now call rxbyt1 ; Get block number ld d,a ; Save block number ld a,(xblock) ; 8-bit previous block number ld e,a ; ..for later call rxbyt1 ; Complimented block number cpl ; (4)compliment to compare cp d ; (4) jp nz,purge ; (10)No match: error ;--------------------------------------------------- ;Calculate and remember the difference between this ;block number and the previous block's block number. ;(We calculate this here because we have the time.) ;--------------------------------------------------- sub e ; (4)calc the difference ; 0 means same block ; 1 means next block ld (rxbdif),a ; (7)Save block number diff ;----------------------------------------------------- ;Loop to receive BLKSIZ bytes and store them in the ;next slot in the buffer, computing both the checksum ;and the CRC along the way. Throughout the RXCHR loop: ; b is the byte counter ; c accumulates the checksum ; de accumulates the CRC ; hl is the buffer memory pointer ;---------------------------------------------------- ld bc,blksiz*256+0 ; (10)b=bytes, c=0 checksum ld d,c ; (5)Clear CRC too ld e,c ; (5) ld hl,(blkptr) ; (16)next block in the buffer rxchr: call rxbyt1 ; (17+118)Get one byte of data ld (hl),a ; (7)Store byte in buffer ;------------------------------------------------------------- ;(Inline for speed: this is the critical path when receiving.) ;Update the 16-bit CRC and 8-bit checksum with one more data ;byte. For speed, this code assumes that the CRC table is on ;a page boundary, and that the table is split, with the high ;bytes in the first half and the low bytes in the second half. ; a has the newly received byte ; c has checksum so far ; de has the CRC so far ;(This loop uses 238 8080 cycles IN-to-IN for direct I/O. with ;a 4 MHz Z80, this will require about 60 uS per byte. 115.2K ;baud sends a byte every 86.8 uS, so we have enough headroom.) ;------------------------------------------------------------- push hl ; (11) ld h,crctab/256 ; (7)CRC table addr high byte xor d ; (4)compute lookup address ld l,a ; (5)low byte of lookup xor d ; (4)recover original byte add a,c ; (4)update checksum too ld c,a ; (5) ld a,(hl) ; (7)compute new CRC high byte xor e ; (4)..using the table ld d,a ; (5) inc h ; (5)low bytes are in the ld e,(hl) ; (5)..other half of the table pop hl ; (10) ;-------------------------------------------------------------- ;Next byte, unless we have received all the data for this block ;-------------------------------------------------------------- inc hl ; (5)next byte dec b ; (5) jp nz,rxchr ; (10) ;------------------------------------------------------- ;We've received all the block's data bytes. Now verify ;either the checksum in c or CRC in de, based on CRCFLG. ; hl=next buffer address ;------------------------------------------------------- ld a,(crcflg) ; CRC mode? or a ; 0 means cksum jp z,rxcksm call rxbyt1 ; Get 1st byte of CRC cp d ; Test the 1st CRC byte jp nz,rxcerr ; Fail: try again, but ; ..first, purge 2nd CRC ld c,e ; Put 2nd CRC byte in c rxcksm: call rxbyt1 ; 2nd CRC byte or cksum cp c ; Does it match? jp nz,rxserr ; No: error ;------------------------------------------------------ ;Got an error-free block. See if it has the block ;number we expected, based on the prior block's number. ; hl=next buffer address ; RXBDIF = this block's block number minus the ; previous block's block number. ; Carry is clear here. ;------------------------------------------------------ ld a,(rxbdif) ; Difference between this ; Block's number & the ; Prior block's number dec a jp nz,blkord ; Nonsequential? (Carry clear) ;----------------------------------------- ;Correct block received. Bump pointers and ;counters, and see if the buffer is full. ; hl=next buffer address ;----------------------------------------- ld (blkptr),hl ; Next slot in BUFFER ld d,h ; Remember for full-test ld hl,(bufcnt) ; Bump buffer block count inc hl ld (bufcnt),hl ld hl,(xblock) ; Inc 16-bit Xmodem block # inc hl ld (xblock),hl ;Print good-block pacifier on the console, if enabled call pacok ;Test for enough room in the buffer for another ;block, and return with carry set if not. ;d = (BLKPTR)/256, the page address for the next block ld a,(bufmax) cp d ; Carry: not room for another ret ;------------------------------------------------------- ;Non-sequential block received without a checksum or CRC ;error. a=FFh if this block has the same block number as ;the previous block (and should be ignored). Otherwise ;abort because blocks hves been irretrievably lost. ;Carry is clear on entry ;------------------------------------------------------- blkord: inc a ; Was it FFh? ret z ; Y: ignore repeated block ; ..return with carry clear ;---Exit---------------- ;Blocks are out of order ;----------------------- call canabt ; Cancel and abort defb 'Lost block','s'+80h ; Out of sequence ;----------------------------------------------------------- ;Received something besides an SOH. If it was an EOT or a ;CAN then send an ACK, set EOFLAG=FFh, and return with carry ;set. If it was a CAN then say so and abort. Ignore a bogus ;chr on the very 1st byte ever received, in case the UART ;receiver had a garbage chr when we started. ;----------------------------------------------------------- notsoh: ld hl,rx1st ; First byte received? inc (hl) dec (hl) jp z,purge ; Y: just ignore bogus chrs cp can ; Cancel? jp nz,chkeot call cilprt defb 'Sender cancele','d'+80h ld a,eot ; Act like we got an EOT ; ..so we will FLUSH chkeot: cp eot ; End of Xmodem file? jp nz,purge ; N: bogus call txack ; Returns a=ACK sub ack+1 ; A=FF, carry set ld (eoflag),a ; Remember EOT ret ;***Subroutine**************************************** ;Eat incoming bytes (up to 256 received bytes) until ;the line is quiet for 1 second. (RXBYT1 timeout will ;jump to the address in RTORET, which is set up during ;initialization.) ;Trashes b ;***************************************************** purge: ld b,0 ; Allow 256 babbling chrs prglup: call rxbyt1 ; Receive w/ 1-sec timeout dec b jp nz,prglup ; RXBYT1 times out to RXSERR ;---Exit------------------------------------------- ;The transmitter is babbling, unable to synchronize ;with the incoming data stream. Abort with message. ;-------------------------------------------------- call canabt ; Cancel and abort defb 'Can',quote,',t syn','c'+80h ; Can't find SOH ;-------------------------------- ;Error on 1st CRC byte. Flush 2nd ;CRC byte, and indicate an error. ;-------------------------------- rxcerr: call rxbyt1 ; Get and chuck 2nd CRC byte ;--------------------------------------------------------- ;Send a NAK to indicate receive error. NAKCHR (below) gets ;modified. If we are waiting to start and we are in CRC ;mode (NAKCHR=SELCRC), then send SELCRC instead of NAK. ;--------------------------------------------------------- rxserr: call pacerr ; Opportunity to abort, ; Pacifier if allowed ; Trashes bc,hl ld a,nak ; This NAK gets modified nakchr equ $-1 ; ..if CRC's call txbyte ;Bump error count, and abort if too many errors. ;otherwise, retry the block. ld hl,errcnt ; Clear error count inc (hl) ; Bump error count ld a,(hl) ; Too many errors? cp errlim jp c,rxrtry ; No: try again ;---Exit------------------------ ;Too many errors. Abort cleanly. ;------------------------------- call canabt ; Cancel and abort defb (errlim/10)+'0' ; Too many block retries defb (errlim-((errlim/10)*10))+'0' defb ' bad block','s'+80h ;***Subroutine********************************************** ;Write all data in BUFFER to disk ;Every WBPERX disk writes, stop to receive one Xmodem block, ;often enough that the sender does not time out. Put out. ;(Stop doing this once we receive an Xmodem EOT.) the new ;blocks in the beginning of the BUFFER, where it's already ;been cleared it out by writing to disk. This code assumes ;that disk records are the same size as Xmodem blocks, 12 ;bytes each. ;On Entry: ; BUFCNT = count of blocks currently in BUFFER ; FCB describes the open file ; XMODE = 01 if no disk writes yet ; = 81 if disk writes have ever occured ;On Exit: ; Carry set if no blocks written ; BUFCNT=number of new blocks in buffer ; BLKPTR points to next free space in the buffer ; XMODE = 81h if disk writes have occured ;Trashes all registers ;*********************************************************** wflush: ld hl,(bufcnt) ld a,h or l scf ret z ld a,82h ; Remember we are flushing ld (xmode),a ; And that we wrote ex de,hl ; De has block count ld hl,0 ; Reset block count ld (bufcnt),hl ld h,buffer/256 ; Reset BLKPTR ld (blkptr),hl ; (BUFFER is page-aligned) ;----------------------------------------------------- ;Loop to write all blocks in the BUFFER to disk. ; b = WBPERX count-down of received xmodem blocks ; (to prevent timeout) ; de = number of BUFFER records to write to disk ; hl = pointer for data to write to disk ; BLKPTR = DMA address for new received Xmodem blocks ; BUFCNT counts new Xmodem received blocks ; XMODE = 82h (we will write, currently flushing) ;----------------------------------------------------- wflp0: ld b,wbperx and 0ffh ; Interleave counter ;Check for ^C, with XMODE=82, indicating that the ^C ;occured during flush (meaning that the abort routine ;should not attempt to flush the buffer). call cctrlc ; User abort? trashes c wfloop: ld a,d ; De=write record cnt or e ; Any more to send? jp nz,wflp1 ; Y:keep flushing ld a,81h ; No longer flushing ld (xmode),a ret ; Done, carry clear wflp1: ;Write a disk record at hl to disk push de ; Record count ld d,h ; De gets DMA address ld e,l ld c,bstdma ; CP/M SET DMA function call gobdos ; De = DMA address ld c,bwrite ; Write from buf to disk call fcbdos ; Returns Z for success ld de,blksiz ; De=block size add hl,de ; (hl)=next block data pop de ; Record count jp nz,wfail ; Oops, write error dec de ; Next block ;---------------------------------------------------- ;See if we need to do a block read. Read a block ;if b has counted down to 0. ;Within RXBLK ; BUFPTR = DMA address for new received Xmodem blocks ; BUFCNT counts new received Xmodem blocks ; EOFLAG <> 0 if we've received the Xmodem EOT ; XMODE = 82h (writes occured, currently flushing) ;---------------------------------------------------- dec b jp nz,wfloop ld a,(eoflag) ; Has sender finished? or a jp nz,wflp0 call txack ; ACK prvious block push hl push de push bc call rxblk ; Get 1 more block ; (will never be full) pop bc pop de pop hl jp wflp0 ; Until all blocks sent ;***Subroutine************************************** ;Read more blocks from the disk and put them in the ;buffer until it is full or there are no more blocks ;On Entry: ; BUFCNT = 0 ; BUFMAX = Max address in BUFFER/256 ; Buffer start address = BUFFER ;On Exit: ; BLKPTR = buffer start address ; BUFCNT=number of 128-byte blocks in the buffer ; hl=(BUFCNT) ; EOFLAG set if EOF encountered ; direct exit to TXEOF if no more data ;Trashes all registers ;*************************************************** ;-------------------------------------------------------- ;BUFFER is empty: read 128-byte logical disk sectors into ;the buffer until EOF or the buffer is full (up to BDOS) ;-------------------------------------------------------- filbuf: ld a,(eoflag) ; Already seen the EOF? or a jp nz,txeof ; Y: no more data. ld hl,buffer ld (blkptr),hl ; Reset pointer for exit ex de,hl ; De=address in BUFFER fbloop: ld c,bstdma ; De=CP/M DMA address call gobdos ; Trashes no registers ex de,hl ; Pointer to hl, free de ld c,bread ; Logical sector into BUFFER call fcbdos ; Sets de=FCB, Z for success jp nz,fbeof ; EOF from CP/M?: no more data ld de,secsiz ; Logical sector size add hl,de ; Next logical sector's address ex de,hl ; ..into de ld hl,(bufcnt) ; Count blocks in buffer. Note: inc hl ; This assumes blocks are the ld (bufcnt),hl ; Same size as logical sectors. ;Test to see if there's enough room in the buffer for ;another logical disk sector, and return if not. ld a,(bufmax) ; Page of last allowed address cp d ; Carry set if not room ; ..for another block jp nc,fbloop ; Go until all space used ret ; With hl=BUFCNT ;------------------------------------------- ;We got an EOF from CP/M. If we read 0 ;logical sectors, then send the EOF and end. ;On Entry: ; a<>0 ; count-down on stack ; BUFCNT = # of blocks read from disk ;On Exit: ; hl=(BUFCNT) ; EOFLAG <> 0 ;------------------------------------------- fbeof: ld (eoflag),a ; Set EOF flag ld hl,(bufcnt) ; Zero blocks? ld a,h or l ret nz ; N: ret with hl=BUFCNT ;Fall into TXEOF to end transmission ;***Exit************************************************ ;File send completed. Send EOT'S until we get an ACK ;Then print happy message, report block count anbd exit. ;On Entry: ; XBLOCK=16-bit block number of the last block sent ;******************************************************* txeof: ld a,eot ; Send an EOT call txbyte call getack ; Wait for an ACK jp nz,txeof ; Loop until we get an ACK call cilprt ; Report success defb 'OK',cr defb 'Sent',' '+80h jp repcnt ; Print block count, goto CP/M ;***Subroutine******************************** ;Send ACK ; returns a=ACK ; flags trashed ; All other registers preserved ;********************************************* txack: ld a,ack ;Fall into TXBYTE ;***Subroutine******************************** ;Send a to the transfer port, based on XPORT ;and the assembly options. ;This routine gets modified during INIT to ;connect the correct transmit routine. ;On Entry: ; a = byte to send ; TXROUT has been modified by initialization, ; based on the default or a /X option ;On Exit: ; All registers except flags preserved ;********************************************* txbyte: push bc ld c,a ; Chr to c txrout: jp exit ; This gets modified with ; ..the routine address ;***Subroutine************************ ;Receive a byte, with 1-second timeout ;On Entry: ; RTORET = error return address ;On Exit: ; exit to RTORET if timeout ; a = received byte if no timeout ;************************************* rxbyt1: ld a,2 ; 1-second timeout ;Fall into RXBYTE ;***Subroutine********************************************* ;Receive a byte from the transfer port - from the CON or ;RDR device or a direct I/O port.The RDR port may or may ;not be enhanced to return with Z set if no chr is waiting. ; ;On Entry: ; RXROUT has been patched by the initialization code, ; subject to the default port or as set by the /X option ; a = timeout value in half-seconds ; RTORET = error return address ;On Exit: ; exit to RTORET if timeout ; a = received byte if no timeout ;(118 8080 cycles for direct I/O) ;********************************************************** rxbyte: ld (timrh),a ; (13)Timer high byte push hl ; (11) ld hl,(timrld) ; (16)start timeout timer rxrout: jp exit ; (10)This gets modified with ; ..the routine address ;***Subroutine********************* ;Print hl in decimal on the console ;with leading zeros suppressed ;Trashes all registers ;********************************** pdec16: ld de,00ffh ; D: Suppress leading 0's ; E: a handy -1 ld bc,-10000 call decdig ld bc,-1000 call decdig ld bc,-100 ; Sets b to FF call decdig ; LD C,(-10) AND 0FFH ; B=FF already ld c,-10 call decdig ld c,e ; Bc=-1 ld d,e ; Always print final 0 ;Fall into DECDIG to print the 1's digit ;---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, 0FFh otherwise ; e=-1 ;On Exit: ; Quotent is printed, unless it's a leading 0 ; hl=remainder ; d=0 iff this and all prior digits are 0 ; d=-1 if this digit was not 0 ;Trashes psw,c ;------------------------------------------------- decdig: ld a,e ; Will go 1 too many times push de ; Leading 0 state & -1 diglp: ld d,h ; De gets prev value ld e,l inc a add hl,bc ; Subtract power of 10 jp c,diglp ex de,hl ; Hl has remainder pop de ; Leading 0 state & -1 cp d ; Leading 0 to suppress? ret z ; Y: done ld d,e ; FF: no more leading 0's add a,'0' ; Make digit ASCII ;Fall into PRINTA ;(trashes c) ;***Subroutine****************** ;Print character in a on console ;On Exit: ; c = printed character ;Trashes psw ;******************************* printa: ld c,a ; Value to c for PRINTC ;Fall into PRINTC ;***Subroutine****************** ;Print character in c on console ;Trashes psw ;******************************* printc: ld a,conout jp gobios ;***Subroutine******************************** ;Print error pacifier on the console unless ;disabled, giving the user a chance to abort ;On Entry: ; PACCNT =FFh to disable pacifier printing ; otherwise, PACCNT = column count-down ;On Exit: ; PACCNT decremented, and reset to PACCNT, ; unless it was FFh ;Trashes psw,bc,hl ;********************************************* pacerr: ld b,pacnak defb 21h ; 'LXI H' opcode skips 2 bytes ;Hop into PACIFY ;***Subroutine************************************ ;Print success pacifier on the console unless ;disabled, giving the user a chance to abort ;On Entry: ; PACCNT =FFh to disable pacifier printing ; otherwise, PACCNT = column count-down ;On Exit: ; PACCNT decremented, and reset to PACCNT, ; unless it was FFh ;Trashes psw,bc,h ;************************************************* pacok: ld b,pacack ;Fall into PACIFY ;***Subroutine************************************ ;Print pacifier on the console unless disabled, ;giving the user a chance to abort ;Print a CR/LF at the end of every PACLIN columns. ;On Entry: ; PACCNT =FFh to disable pacifier printing ; otherwise, PACCNT = column count-down ; b=pacify character ;On Exit: ; PACCNT decremented, and reset to PACCNT, ; unless it was FFh ;Trashes psw,c,hl ;************************************************* pacify: ld hl,paccnt ld a,(hl) ; FF means quiet mode inc a ret z dec (hl) jp p,pcfy1 call ilprnt ; New line, trashes c defb cr+80h ; ILPRNT adds the LF ld (hl),paclin pcfy1: ld c,b ; Recover pacifier call printc ; ..and print it ;Fall into CCTRLC for a user-abort opportunity ;***Subroutine************************************* ;Check for Control-C on the console, and quit if so ;On Exit: ; Z set if no chr was waiting ; Z clear if anything but ^C was waiting ; XMODE<1>=1 iff called from within FLUSH ; (meaning CANABT should not flush) ;Trashes a ;************************************************** cctrlc: ld a,const ; Anything on console? call gobios ; (about 200 cycles) or a ; Z means no chr waiting ret z ;Chr waiting: fall into GETCON to take a look ;***Subroutine********************************* ;Get console character, abort if it's control-C ;On Exit: ; chr in a ; Z cleared ; XMODE<1>=1 iff called from within FLUSH ; (meaning CANABT should not flush) ;Trashes a ;********************************************** getcon: ld a,conin ; Read the typed chr call gobios cp ctrlc ret nz ; Ignore everything else ;---Exit--- ;User abort ;---------- call canabt defb '^','C'+80h ; User typed ^C ;***Subroutine******************************* ;Erase file at FCB ;On Exit: ; de=FCB ; Flags set according to how BDOS returned a ; (M flag set if error) ;Trashes psw,c ;******************************************** ferase: ld c,bdelet ;Fall into FCBDOS ;***Subroutine******************************* ;Call BDOS with de=FCB ;On Exit: ; de=FCB ; Flags set according to how BDOS returned a ;******************************************** fcbdos: ld de,fcb ;Fall into GOBDOS ;***Subroutine********************************* ;Call BDOS while preserving all regs except psw ;Flags set according to how BDOS returned a ;Generally, the result is negative if there ;was an error. Often, Z set means OK too. ;********************************************** gobdos: push hl push de push bc call bdos pop bc pop de pop hl or a ; Test response ret ;***Subroutine********************************************* ;Get the error-checking mode: Wait for the initial NAK or ;SELCRC from the receiver. (NAK means we use checksums, and ;SELCRC means we use CRC-16.) Ignore all other characters, ;with a long timeout. Abort if user types Control-C. ; ;This subroutine is not in INIT because it gets called ;after FILBUF has filled the BUFFER and wiped out INIT. ; ;On Entry: ; RTORET is set to WAITNK ; PACCNT=FFh if quiet mode or using console for transfers ; (so don't print messages on console) ;On Succesful Exit: ; CRCFLG = 0 if NAK received ; CRCFLG <> 0 if SELCRC received ; Message printed if not quiet mode ;Trashes psw,bc ;********************************************************** gtmode: ld b,nakto ; Long timeout ld a,(paccnt) ; For quiet mode test ld c,a waitnk: dec b ; Timeout? jp z,ntoerr ; Y: abort call rxbyt1 ; 1-second timeout xor nak ; NAK for checksum? ld (crcflg),a ; 0 for cksum, NZ otherwise jp z,pcsnt ; Yes:message, done xor selcrc xor nak ; 'C' for CRC? jp nz,waitnk ; No: Keep looking inc c ; Quiet mode? ret z ; Y: no message ;Fall into PCRC ;***Subroutine****************** ;Print 'with CRCs' ;On Entry: ; a = initial value for NAKCHR ; (only used when receiving) ;On Exit: ; Z flag cleared ;Trashes a,c ;******************************* pcrc: ld (nakchr),a ; Set CRC initial ACK ; Used only by RXFILE call ilprnt defb ' with CRC','s'+80h ret ;***Subroutine********************** ;Print 'with checksums' unless quiet ;On Entry: ; c= FFh if quiet mode ;Trashes a,c ;*********************************** pcsnt: inc c ; Quiet mode? ret z ; Y: no message ;Fall into PCKSUM ;***Subroutine********* ;Print 'with checksums' ;On Exit: ; Z flag cleared ;Trashes a,c ;********************** pcksum: call ilprnt defb ' with checksum','s'+80h ret ;***Subroutine************************************** ;Print CR, LF, then In-line Message ;The call to ILPRNT is followed by a message string. ;Follow CR's with LF's ;The last message chr has its msb set. ;Trashes psw,c ;*************************************************** cilprt: call ilprnt ; Trashes c defb cr+80h ; ILPRNT adds the LF ;Fall into ILPRNT ;***Subroutine************************************** ;Print In-line Message ;The call to ILPRNT is followed by a message string. ;Follow CR's with LF's ;The last message chr has its msb set. ;On Exit: ; Z cleared ;Trashes psw,c ;*************************************************** ilprnt: ex (sp),hl ; Save hl, get msg addr call hlprnt ex (sp),hl ; Restore hl, ; ..get return address ret ;***Exit**************** ;Timeout waiting for NAK ;*********************** ntoerr: call canabt defb 'Init timeou','t'+80h ;***Exit************* ;Write fail from CP/M ;******************** wfail: call canabt ; Cancel and abort defb 'Disk write fai','l'+80h ; CP/M error ;***Exit************************ ;Transmitter timeout: the UART's ;CTS signal is probably not true. ;On Entry: ; a=0 ;******************************** txbto: ld b,a ; B=0: don't close file call abort defb 'UART Tx fai','l'+80h ;***Exit************************************** ;Receiver sent us a CAN, so abort with message ;a=0 ;********************************************* rxcan: ld b,a ; 0 for no flush call abort defb 'by receive','r'+80h ;***Exit**************************************** ;Cancel and Abort - if receiving, send a CAN. ;If sending, send an EOT. Then tidy up and quit. ;On Entry: ; top-of-stack = address of MSB-terminated ; XMODE = 00h if sending ; = X1h if receiving and not flushing ; = X2h if flushing when ^C came ; = 8Xh if we have ever written to disk ;*********************************************** canabt: ld a,(xmode) ; Sending or receiving? ld b,a ; Remember for ABORT or a ; 0 means sending ld a,can jp nz,cab1 ld a,eot cab1: call txbyte ;Fall into ABORT to close file and report ;***Exit***************************************** ;Abort - close file if writing, erase it if no ;records were written to disk ;On Entry: ; b = XMODE (NZ means we need to close the file) ; top-of-stack = address of MSB-terminated ; XMODE = 0 for sending, <>0 for receiving ;************************************************ abort: call cilprt defb 'ABORT:',' '+80h pop hl ; Message to print call hlprnt ; Print string at hl ld a,b ; Need to close the file? or a ; 0 means sending jp z,exit ;We were receiving an Xmodem file. Flush the ;BUFFER unless we were already flushing when ;we got the ^C from the user ; a = XMODE value <> 0 ld (eoflag),a ; NZ to prevent interleaved Rx ; XMODE msb set if we were ; ..writing when ^C came and 02h ; Were we already flushing? call z,wflush ; N: then flush the buffer jp rxend ;***Exit*********** ;Error closing file ;****************** fclerr: call cmsgxt defb 'FILE CLOSE FAIL! May be corrup','t'+80h ;***Exit************************ ;Report that the empty file has ;been erased, and return to CP/M ;******************************* efexit: call cmsgxt ; Succesful erase defb 'Empty file erase','d'+80h ;***Exit******************************************* ;Print CRLF, then $-terminated string following the ;call. Fix everything for CP/M, and return to CP/M ;************************************************** cmsgxt: call ilprnt ; Trashes c defb cr+80h ; ILPRNT adds the LF ;Fall into MSGXIT ;***Exit****************************************** ;Print $-terminated string following the call, fix ;everything for CP/M, and return to CP/M ;************************************************* msgxit: pop hl ; Get message address call hlprnt ;Fall into EXIT ;***Exit****************************** ;Jump to CP/M's WBOOT at address 0000. ; All exits go through here. ;************************************* exit: rst 8*0 ; Go to CP/M ;---RX Byte Routine-------------------------- ;Receive a transfer byte from CON ;On Entry: ; hl = timer low word ; TIMRH = timer high byte ; prior hl is on the stack ;RXBCON loop: 179+CRTIME cycles, and round up ;-->Entry is at RXBCON <--- ;-------------------------------------------- conto equ 50000/((195+crtime+9)/10) rxclup: dec hl ; (5) ld a,l ; (5) or h ; (4) call z,rxtimr ; (11) Timeout? ;----- ;Entry ;----- rxbcon: ld a,const ; (7)get console status call gobios ; (116+17+CRTIME) or a ; (4)nz means chr ready jp z,rxclup ; (10)Go get the chr ld a,conin ; Get console chr jp crdone ;---RX Byte Routine-------------- ;Receive a transfer byte from RDR ;On Entry: ; hl = timer low word ; TIMRH = timer high byte ; prior hl is on the stack ;-------------------------------- rxrdr: ld a,reader ; BIOS routine offset ;Fall into CRDONE ;---------------------------------- ;Get character from BIOS and return ;On Entry: ; a = BIOS routine offset ; prior hl is on the stack ;---------------------------------- crdone: pop hl ; Chuck timer ;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: ; psw as BIOS left it ; all other regs preserved ;(116 cycles + BIOS time) ;********************************** gobios: push hl ; (11) push de ; (11) push bc ; (11) call dobios ; (17+26+BIOS time) pop bc ; (10) pop de ; (10) pop hl ; (10) ret ; (10) ;---Local Subroutine--------------- ;Go call a BIOS driver directly ;On Entry: ; c=value for BIOS routine, if any ; a = BIOS call address offset ;On Return: ; all regs as BIOS left them ;(26 cycles + BIOS time) ;---------------------------------- dobios: ld hl,(wboota) ; (16)get BIOS base address ld l,a ; (5)a has jump vector jp (hl) ; (5) 'call' BIOS routine ;---RX Byte Routine---------------------------- ;Receiver from enhanced BIOS RDR routine, which ;returns with Z set if no character is waiting, ;which allow us to have a timeout here. ;On Entry: ; hl = timer low word ; TIMRH = timer high byte ; prior hl is on the stack ;RXERDR loop: 175+CRTIME cycles, and round up ;--> Entry is at RXERDR <-- ;---------------------------------------------- rdrto equ 50000/((175+crtime+9)/10) rxerlp: dec hl ; (5) ld a,l ; (5) or h ; (4) call z,rxtimr ; (11) Timeout? rxerdr: ld a,reader ; (7)BIOS routine offset call gobios ; (116+17+BIOS time) jp z,rxerlp ; (10)nz means chr ready pop hl ret ;---RX Byte Routine------------------------------- ;Generic direct transfer port Input Routine - gets ;modified by INIT based on selected transfer port ;On Entry: ; hl = timer low word ; TIMRH = timer high byte ; prior hl is on the stack ;--> Entry is it RXDRCT <-- ; WAITRX loop = 53 cycles. 0.5S / 53 uS = 9434 ;------------------------------------------------- dirto equ 9434 waitrx: dec hl ; (5) ld a,l ; (5) or h ; (4) call z,rxtimr ; (11) Timeout? rxdrct: ;The relative position of the following instructions must not ;change because MODIO assumes the positions of the bytes it ;modifies. imodfy: in a,(siosta) ; (10+1)status port (modified) and siordf ; (7)test ready (clear carry) (modified) jp nz,waitrx ; (10)low when chr ready (modified) pop hl ; (10)here to match OMODFY in a,(siodat) ; (10)data port (modified) ret ; (10) ;---TX Byte Routine---------------------- ;Transmit via direct I/O, with timeout ;the timeout value doesn't really matter: ;we just shouldn't hang forever here ;---------------------------------------- txdrct: push hl ld hl,0 ; About 1.7 second timeout ; ..at 2 MHz txwait: dec hl ; (5)timeout? ld a,h ; (5) or l ; (4) jp z,txbto ; (10)y: abort ;The relative position of the following instructions must not ;change because MODIO assumes the positions of the bytes it ;modifies. omodfy: in a,(siosta) ; (10+1)status port (modified) and siotde ; (7)mask (modified) jp nz,txwait ; (10)may become jnz (modified) ;52 cycles = 26 uS per pass at 2 MHz ld a,c ; Recover chr out (siodat),a ; Data port (modified) pop hl pop bc ret ;---TX Byte Routine------- ;Transmit via CP/M CON ;------------------------- txcon: ld a,conout ; BIOS send c to console jp txcp ;---TX Byte Routine------- ;Transmit via CP/M PUN ;------------------------- txpun: ld a,punch ; 1:BIOS send c to punch ;Fall into TXCP ;---------------------------- ;Transmit via CP/M CON or PUN ;---------------------------- txcp: call gobios ; Chr in c, routine in a ld a,c ; Restore character pop bc ret ;---Local Subroutine------------------------------- ;Bump timer, test for abort every 1/2 sec ;On Entry: ; TIMRH = remaining timeout in 0.5 sec units ; RTORET = (modified) error return address ; top-of-stack = our return address ; next-on-stack = hl save value ; next-on-stack = RXBYTE return address ;On Exit: ; hl reloaded ;On Timeout: ; repair stack for call to RXBYTE ; jump to address in RTORET (which gets modified) ;Trashes psw ;-------------------------------------------------- rxtimr: call cctrlc ; User abort? ld hl,timrh dec (hl) ; Bump timer high byte ld hl,(timrld) ; Reload timer ret nz ; Return unless timeout ;Timeout: fix stack, "return" from RXBYTE to address in RTORET pop hl ; Chuck RXTIMR return address pop hl ; Chuck original hl pop hl ; Chuck RXBYTE return address jp pptimo ; This address gets modified, and is rtoret equ $-2 ; ..initialized for UART flush in INIT ;**************************************************** ;Custom I/O Routines ;These all get modified in INT by the /I options. The ;Modifying code overwrites the error messages that ;are here as defaults. These routines are located ;just after CTABLE.The last one (CINIT, which is in ;the INIT code) must be within 256 bytes of CTABLE. ;**************************************************** ;---Table---------------------- ;Offsets to custom I/O routines ;Used only when INIT modifies ;the following routines. ;------------------------------ ctable: defb cinit-ctable defb cwdat-(ctable+1) defb crstat-(ctable+2) defb crdat-(ctable+3) ;---TX Byte Routine------------------------------ ;Custom Output Subroutine ;On Entry: ; c=chr to send ;User routine should not trash c ;(Up to 12 bytes will be written at CWDAT by the ;/I1 option, overwriting the error message here.) ;------------------------------------------------ txcust: cwdat: ld b,0 ; Don't flush call abort defb 'No /I','1'+80h nop equ 00h ;AGN define NOP opcode defb nop ld a,c ; Restore registers pop bc ret ;---RX Byte Routine-------------------------------- ;Custom Receive Subroutine with timeout ;On Entry: ; hl = timer low word ; TIMRH = timer high byte ; prior hl is on the stack ;--> Entry is at RXCUST <--- ;Assume WATCRX loop time is 80 cycles, and round up ;-------------------------------------------------- custo equ 50000/((80+extime+9)/10) watcrx: dec hl ; (5) ld a,l ; (5) or h ; (4) call z,rxtimr ; (11) Timeout? rxcust: ;Wait for data to be ready ;(Up to 12 bytes will be written at CRSTAT by the ;/I2 option, overwriting the error message here.) crstat: ld b,0 ; Don't flush call abort defb 'No /I','2'+80h defb nop jp z,watcrx pop hl ;Get the received data byte ;(Up to 12 bytes will be written at CRDAT by /I3 here) crdat: defb nop defb nop defb nop defb nop defb nop defb nop defb nop defb nop defb nop defb nop defb nop defb nop ret ;****************************************************** ;RAM Variables and Storage, all initialized during load ;****************************************************** ;------------------------------ ;Xmodem file transfer variables ;------------------------------ rxbdif: defb 0 ; Received block number minus ; ..previous block's block number xblock: defw 0 ; 16-bit Current block number errcnt: defb 0 ; Error count rx1st: defb 0 ; 0 means 1st chr ever received timrld: defw 0001h ; Receive timeout value ; Initialized for receiver flush in INIT timrh: defb 0 ; High byte of timer ;------------------------ ;Disk buffering variables ;------------------------ blkptr: defw buffer ; Points to next block in BUFFER bufcnt: defw 0 ; Count of 128-byte blocks in BUFFER bufmax: defb 0 ; Max address in BUFFER/256 eoflag: defb 0 ; EOF flag (0 means no EOF yet) ;--------------------------- ;Other initialized variables ;--------------------------- xmode: defb 0ffh ; 00: send ; X1: Rx, not currently flushing ; X2: Rx, currently flushing ; 01 or 02: no disk writes yet ; 8x: disk writes have occured ; FFh: uninitialized crcflg: defb selcrc ; 0 for checksum, NZ for CRC ; Init to SELCRC for receiving paccnt: defb 0 ; Count-down for pacifiers. Init to ; Start new line ; FFh disables pacifiers. ;***************************************************** ;Buffer for Xmodem blocks. This buffer overwrites the ;following initialization code, as well as CP/M's CCP. ;***************************************************** ;Force the BUFFER to be page-aligned for faster compares bpager equ $ buffer equ (bpager+255) and 0ff00h ;=========================================================== ;= The following subroutines and variables are used only = ;= during the initial command line processing, and get = ;= wiped out by the BUFFER, once we start transfering data.= ;=========================================================== ;----------------------------------------------------- ;Defaulted variables needed only during initialization ;----------------------------------------------------- opmode: defb 0 ; 0 means reading from command line ; 01h means reading from XMODEM.CFG ; 80h means end of file forXMODEM.CFG ; ..0 means command line bytcnt: defb 0 ; Command buffer bytes cpumhz: defb 2 ; CPU speed in MHz (for timeouts) xport: defb 1 ; Transfer port defaults to RDR/PUN enhrdr: defb 0 ; 01 for RDR that returns with ; ..Z set if chr not ready ;***INIT-Only Routine************************ ;Initialization: parse XMODEM.CFG and command ;line, set up for transmit or receive ;******************************************** init: ld sp,lstack ; Use local stack ifdef mbc2 ; Set the CONST mode to 8-bit mode on the Z80-MBC2 ld a,constmd call gobios endif ;------------------------------------------------------ ;Set default CPU speed to 4MHZ and fix the help message ;if a Z80 is detected. Othwerwise, leave it at 2MHz. ;(The user can later change this with /Z option) ;------------------------------------------------------ sub a ; Test for 8080 or Z80 jp pe,is8080 ld a,4 ; Assume Z80s run 4 MHz ld (cpumhz),a add a,'0' ; Fix default in help ld (dmhz),a ; ..message too is8080: ;---------------------------------------------------- ;Initialize CP/M's default File Control Block for ;disk transfers: Check for an option crammed against ;the filename in the FCB, and clean it up (by padding ;it with spaces) if necessary. Fill the FCB Extent ;bytes with zeros, as required. Test for options, ;but no filename provided. ;---------------------------------------------------- ld de,fcbnl*256+fcbclr ; 2 counters ld hl,fcbfn ; File name in FCB ld a,'/' ; Option marker cp (hl) ; Options without filename? jp z,nofner ; Y: error exit ffcb1: cp (hl) jp nz,ffcb2 ld a,' ' ld (hl),a inc hl ; Force all future tests ld (hl),a ; ..at FFCB1 to pass dec hl ffcb2: inc hl dec d jp nz,ffcb1 ;Clear the FCB Extent bytes to 0's as required by CP/M ; d=0 ; e=FCBCLR ; hl=FCBEXT ffcb3: ld (hl),d inc hl dec e jp nz,ffcb3 ;---------------------------------------------------- ;Skip over the filename in the command line tail to ;find the beginning of command line options. If there ;is no filename, then just print the help screens. ;(The filename can only come from the command line) ; OPMODE=0 ;---------------------------------------------------- ld de,combuf ; CP/M cmd line tail ld a,(de) ; 1st byte is the byte count ld (bytcnt),a cp 100 ; Max input line length jp nc,badinp ; ..else it clobbers the stack inc de ; Point to 1st chr call wskip ; Skip initial whitespace jp c,hlpext ; Just XMODEM: help ;Scan past the file name, which ;either ends with a space or a '/' skpfil: call cmdcei ; Get input, find end of item jp nz,skpfil ;------------------------------------------------------- ;look for a configuration file and parse it for options, ;if it exists. Set the DMA address first, since since ;CP/M 1.4 will clobber the DMA address in BOPEN. ; de=address of 1st option on the command line ; hl=BYTCNT ;------------------------------------------------------- xor a ld b,(hl) ld (hl),a ; BYTCNT=0 push hl ; BYTCNT push de ; Command line pointer push bc ; BYTCNT value inc a ; A=1 ld (opmode),a ; Read XMODEM.CFG ld de,cfgbuf ld c,bstdma ; Set CP/M DMA address call gobdos ld de,cfgfcb ; FCB describes file to open ld c,bopen ; CP/M FILE OPEN function call gobdos ; M set if a's msb is set ; -1 means open failure ; ..meaning no XMODEM.CFG call p,parse ; CMDCHR will initialize de pop bc ; BYTCNT value pop de ; Command line pointer pop hl ; BYTCNT ld (hl),b ; Restore BYTCNT ;-------------------------- ;Parse command line options ;-------------------------- xor a ; Parse command line next ld (opmode),a call parse ; Finally, find options ;----------------------------------------------------- ;Run any user-defined initialization code ;(Up to 12 bytes get filled in here by the /I0 option) ;----------------------------------------------------- cinit: nop nop nop nop nop nop nop nop nop nop nop nop ;-------------------------------------------------------- ;Patch RXBYTE and TXBYTE for the transfer port specified ;by the /X option, or by the default transfer port. ;Also set TIMRLD to the correct value for 1/2 second ;receive timouts, based on XPORT and CPU speed. Flush the ;receiver unless it's RDR that's not modified to return ;with Z set when no chr is waiting. ;On Entry ; XPORT ENHRDR Port ; 0 x Console ; 1 0 PUN/Standard RDR ; 1 1 PUN/Enhanced RDR ; 2 x Direct I/O ; 3 x Custom I/O ;------------------------------------------------------- ld hl,portab ld a,(xport) add a,a ; *2 ld e,a add a,a ; *4 add a,e ; *6 ld e,a ld d,0 add hl,de ld c,(hl) inc hl ld b,(hl) ; Bc=timeout value inc hl ld e,(hl) inc hl ld d,(hl) ; De=RX byte routine addr ex de,hl ;Check for enhanced RDR if RDR is selected, and ;flush the reader unless it's the unmodified RDR port ;(which would hang forever waiting for input) cp 6 ; RDR port? jp nz,pp1 ; N: hl has correct address ld a,(enhrdr) ; Enhanced RDR? or a jp z,pp1 ; N: hl has correct address ld hl,rxerdr ; Y: update patch pp1: ld (rxrout+1),hl call nz,rxbyt1 ; Flush receiver unless it's ; ..unmodified RDR pptimo: ; (timeout goes to PPTIMO) ex de,hl inc hl ld e,(hl) inc hl ld d,(hl) ; De=TX byte routine addr ex de,hl ld (txrout+1),hl ;Set TIMRLD based on CPU speed and port cycles in bc ; ; bc = CPU cycles for 0.5 sec loop assuming 1 MHz CPU ; CPUMHZ = CPU speed, in MHz ld a,(cpumhz) ld hl,0 adjmhz: add hl,bc dec a jp nz,adjmhz ld (timrld),hl ; Timer reload value ;-------------------------------------- ;If neither /R nor /S then ask the user ;-------------------------------------- ld a,(xmode) ; Did /R or /S get set? or a ; -1 meant uninitialized jp p,gotdir ; With XMODEnin a askrs: call cilprt defb 'Send or receive (S/R)?',' '+80h call getans sub 'R' ; 'R' and 'S' are adjacent sub 2 ; 'R' or 'S'? jp nc,askrs ; N: try again cpl ; 0 for send, 1 for receive ld (xmode),a gotdir: ;---------------------------------------------- ;Branch to do transmit- or receive-specific ;initialization, based on XMODE, which is in a ;---------------------------------------------- call cilprt ; Begin message defb 'File',' '+80h ld a,(xmode) or a ; Z means receive jp nz,gorx ;========================================== ;Set up for TXFILE ; FCB has the file name ; 'File ' has already been printed ; CP/M's DMA address is still CFGBUF ; FCB is set up with the transfer file name ;========================================== ld hl,waitnk ; Timeout return address ld (rtoret),hl ; ..for RXBYTE ld c,bopen ; CP/M FILE OPEN function call fcbdos ; Set de=FCB, -1 means fail jp m,fofail ;Continue announcing call ilprnt defb 'open',cr defb 'Sen','d'+80h call annctp ; Announce transfer port ;-------------------------------------------------- ;Setup is done - go transmit the file ; FCB is valid ; File is open ; File-open message has been printed on the console ;-------------------------------------------------- jp txfile ;==================================== ;Set up for RXFILE ; FCB has the file name ; 'File ' has already been printed ; CP/M's DMA address is set to CFGBUF ;==================================== gorx: ;-------------------------------------------- ;If the file already exists then ask to ;overwrite it. (BSERCH will write a directory ;block at the current DMA address = CFGBUF) ;-------------------------------------------- ld c,bserch ; Search directory for file call fcbdos ; Sets de=FCB jp m,filnex ; -1 means not there (ok) call ilprnt ; Continue message defb 'exists. Overwrite (Y/N)','?'+80h call getans ; Get 1-chr response cp 'Y' jp nz,exit call ferase ; Erase existing file ; Returns de=FCB call cilprt defb 'File',' '+80h filnex: ;------------------------------------------------- ;Create file on disk (for writing) and report. ; FCB has file name ; 'File ' has already been printed on the console ;------------------------------------------------- call ilprnt ; Either 'File created' ; Or 'File create error' defb 'creat','e'+80h ld c,bmake ; CP/M CREATE FILE func call fcbdos ; Sets de=FCB jp m,fcerr ; -1 means create error ;-------------------------------------- ;Tell user that we are ready ;'File create' has already been printed ;-------------------------------------- call ilprnt ; Finish message defb 'd' ; End of 'File created' defb cr,'Recei','v'+80h call annctp ; Announce port setup ;--------------------------------------------- ;Eat up to 256 characters until the line is ;idle for 1 second. Error exit (in PURGE) if ;no timeout before 256 characters are received. ;---------------------------------------------- ld hl,rprgdn ; Timeout return address ld (rtoret),hl ; ..for RXBYT1 and RXBYTE call purge ; Go eat chrs rprgdn: ; Timeout "returns" here ;------------------------------------------- ;Install timeout return address for transfer ;------------------------------------------- ld hl,rxserr ; Timeout return address ld (rtoret),hl ; ..for RXBYT1 and RXBYTE ;-------------------------------------------- ;Set initial character to NAK or SELCRC, and ;report error checking mode (checksum or CRC) ;-------------------------------------------- ld a,(crcflg) ; CRC or checksum? or a ; 0 means checksum ; NZ means CRC ; A=SELCRC here call nz,pcrc ; Saves a at NAKCHR ; ..prints ' with CRCs' ; ..returns with Z cleared call z,pcksum ; Print ' with checksums' ;----------------------------------------------- ;If the console port is the transfer port then ;stall for a few seconds to give the user time ;to start the Xmodem send on the other end. ;----------------------------------------------- ld a,(xport) ; Console? or a ; Clears carry jp nz,rxind ld a,(cpumhz) ; Adjust for CPU speed ld b,a ridel: ld hl,8929*crstal ; Delay loop time ridel1: call cctrlc ; (about 200 cycles) dec hl ; (5)timeout timer? ld a,l ; (5)Test for 16-bit 0 or h ; (4)clears carry jp nz,ridel1 ; (10) ;224 cycles/pass ;1 second = 1,000,000 uS = 2,000,000 cycles ;2,000,000/224 = 8929 dec b jp nz,ridel rxind: ;----------------------------------------- ;Prepare to send the initial NAK or SELCRC ;to initiate transfer, and go receive. ; Carry is clear ;----------------------------------------- ld a,(nakchr) ; Initial ACK jp rxfile ;***INIT-Only Subroutine************** ;Announce transfer port. Disable ;pacifiers if transfer port is CON ;On Entry: ; XPORT is valid ; 'send' or 'receiv' has been printed ;Trashes psw,c ;************************************* annctp: call ilprnt defb 'ing via',' '+80h ld a,(xport) dec a jp m,tvc dec a jp m,tvr jp z,tvd call ilprnt defb 'custom cod','e'+80h ret tvd: call ilprnt defb 'direct I/','O'+80h ret tvr: call ilprnt defb 'RDR/PU','N'+80h ret ;CON: turn off pacifiers tvc: ld (paccnt),a ; A=FFh call ilprnt defb 'CO','N'+80h ret ;***INIT-Only Subroutine************************** ;Get a 1-character response (with editing, CR, and ;potential control-C) from the user ;On Exit: ; a=uppercase response, if 1 chr typed ; a=ff for no characters typed ; a=1-9 for 2-10 characters typed (definitely not ; 'Y','N','R', or 'S') ;Trashes c,de ;************************************************* getans: ld de,combuf ld a,brdcon ; Max chrs (10) ld (de),a ; In place for BDOS ld c,a ; BRDCON call gobdos ; Returns chr count inc de ; COMBUF+1 ld a,(de) ; ..has the byte count dec a ; Just 1 chr? ret nz ; N: error exit inc de ; 1st and only chr ld a,(de) ; AND ('a'-'A')XOR 0FFH ; Uppercase and 0ffh xor ('a'-'A') ;AGN ret ;***INIT-Only Subroutine********************************* ;Parse command line or CFG file options ;On Entry: ; OPMODE = 00h if reading the tail of the command line ; 01h if reading from XMODEM.CFG ; 80h for end-of-file for XMODEM.CFG ; BYTCNT has the number of bytes in the buffer or line ; the command line or from XMODEM.CFG ; de points to the next input chr, either in the command ; line or in the current block from XMODEM.CFG. de ; may be uninitialized if OPMODE<>0 and BYTCNT=0. ;******************************************************** parse: ;----------------------------------------------------------- ;Parse all command line options & set variables accordingly. ;Each option must be preceeded by a '/' Options may be ;preceeded by any reasonable number of spaces, tabs, ;carriage returns and/or line feeds. ;----------------------------------------------------------- optlup: call wskip ; Skip whitespace ret c ; End of input input? ld bc,optlup ; Create return address push bc cp ';' ; Comment? jp z,coment ; Y: ignore until CR or LF cp '/' ; All options start with / jp nz,badinp ; Error: no / call cmdchr ; Get an option chr jp z,badinp ; Error: nothing after / ;----------------------------------------------------- ;Got an option chr in a. Loop through table of options ;looking for a match. Error exit if not in table. ; a = option character ;Trashes c,hl ;----------------------------------------------------- ld (par1),a ; Put option in error msgs ld (par2),a ; ..in case of errors ld hl,opttab chklup: cp (hl) ; Match? (alpha order) inc hl ld c,(hl) ; Get routine address offset inc hl ; Options are in ASCII order jp c,opterr ; Illegal option jp nz,chklup ; No match: keep looking ;----------------------------------------- ;Option match. Go execute option routine ;On Entry: ; c = option routine address offset ; de points to next cmd byte ; hl points to next option table entry ; top-of-stack = return address to OPTLUP ;Command routines preserve/advance de ;----------------------------------------- xor a ; A=b=0 for options ld b,a ; High byte for dad too add hl,bc ; Hl=address of routine jp (hl) ; Go to routine ;***INIT-Only Table********************************** ;Command Line Options Table ;Table entries must be in alphabetical order, and the ;table is terminated with 0FFh. This table must be ;located before the 1st option routine, and within ;256 bytes of the start of the last option routine. ; ;2 bytes per entry: ; Byte 0 = Uppercase legal option letter ; Byte 1 = offset to address of parse routine ;**************************************************** opttab: defb 'C',ccksum-(opttab+2) ; Select checksum mode defb 'E',cmodr-(opttab+4) ; Enhanced RDR port defb 'I',ccio-(opttab+6) ; Custom I/O definition defb 'K',bufkb-(opttab+8) ; Max buffer size defb 'M',cmessg-(opttab+10) ; Console message defb 'O',coutp-(opttab+12) ; Output to port defb 'P',cport-(opttab+14) ; Define transfer port defb 'Q',cquiet-(opttab+16) ; Quiet mode defb 'R',csetr-(opttab+18) ; Select receive mode defb 'S',csets-(opttab+20) ; Select receive mode defb 'X',csetx-(opttab+22) ; Select transfer port defb 'Z',cmhz-(opttab+24) ; Specify CPU MHz defb 0ffh ; End of table ;**************** ; Option Routines ;**************** ;******---------------------- ;* /C * Set Rx Checksum Mode ;****** ;On Entry: ; a=b=0 ; (de)=next command line chr ;On Exit: ; CRCFLG = 0 ;---------------------------- ccksum: ld (crcflg),a ret ;******---------------------------- ;* /E * Specify Enhanced Reader ;****** (RDR returns Z when no chr) ;On Entry: ; a=b=0 ; (de)=next command line chr ;On Exit: ; ENHRDR = 1 ;---------------------------------- cmodr: inc a ld (enhrdr),a ret ;*****------------------------------------------------- ; /I * Patch Custom I/O Routine ;***** ; a=b=0 ; /I0 hh hh hh... defines init code ; /I1 hh hh hh... defines transmit port routine ; /I2 hh hh hh... defines receive status routine ; /I3 hh hh hh... defines receive data routine ;Max 12 hh digits. (The original intention is to use ;these patches to call some ROM I/O routines, perhaps ;with a couple of registers set up prior to the calls.) ;On Entry: ; b=0 ; (de)=next command line chr ;On Exit: ; A Custom Transfer port routine has been written ; de incremented past /I data ;------------------------------------------------------ ccio: call cmdchr ; Get next command line chr sub '0' ; Un-ASCII the chr cp 4 ; Valid code at all? jp nc,badval ;Get the address of the routine to define, based on a. The ;4-byte table is also the reference for address offsets, ;and is located just before the first modified routine. ld hl,ctable ld c,a ; 0<c<4, b=0 already ; Bc is table offset add hl,bc ; Hl points to table entry ld c,(hl) ; Lookup offset in table add hl,bc ; Get final address ;Get & install all routine bytes, padding with nops at the end. ;The number of bytes here must match the available space in ;the custom code routines above (which expects 12 bytes). ; hl=address of beginning of routine to be modified. cioget: ld c,12 ; Max bytes for a routine ciog0: call gethex jp nc,giog1 ; Any character? xor a ; N: install nop giog1: ld (hl),a inc hl dec c jp nz,ciog0 ret ; Note: any more hex values ; Will cause an error in PARSE ;******------------------------------------- ;* /M * print message on console ;****** ;On Entry: ; de points to a string that is terminated ; by either a CR/LF or the end of the input ; subsequent bytes are init sequence ;On Exit: ; de incremented past the end of the line ;------------------------------------------- cmessg: call ilprnt ; Trashes c defb cr+80h ; ILPRNT adds the LF cmsglp: call cmdchr ; Get next chr ret z ; End of message string? call printa ; To console jp cmsglp ;******----------------------------------------- ;* /O * Output to Port ;****** ;On Entry: ; (de)=next command line chr ; subsequent bytes are init sequence ;On Exit: ; Data sequence has been sent to specified port ; de incremented past /O data ;----------------------------------------------- coutp: call gthexm ; Get port number ld (iport+1),a ciloop: call gethex ; Get an init value ret c ; Done? iport: out (0),a ; Port address gets modified jp ciloop ;******------------------------------------- ;* /P * Define Transfer Port ;****** ;On Entry: ; (de)=next command line chr ;On Exit: ; Transfer port routines have been modified ; de incremented past /P data ;------------------------------------------- cport: ld b,4 ; Shift 4 bytes in cploop: ld l,h ; L gets status port ld h,c ; H gets data port ld c,a ; C gets jz/jnz flag call gthexm ; A gets Rx Ready mask dec b jp nz,cploop ld b,a ; B=Rx ready mask call gthexm ; Get a=Tx ready mask push de ; Command pointer ex de,hl ; Port addrsses to de ld hl,omodfy+1 call modio ; Modify input routine ld a,b ; Rx ready mask ld hl,imodfy+1 call modio ; Modify output routine pop de ; Command pointer ret ;******----------------------------------- ;* /Q * Enables quiet mode ;****** (no pacifiers etc. on the console) ;On Entry: ; a=b=0 ; (de)=next command line chr ;On Exit: ; PACCNT=FFh ;----------------------------------------- cquiet: dec a ; A=FFh ld (paccnt),a ret ;******---------------------- ;* /R * Select receive mode ;****** ;On Entry: ; a=b=0 ; (de)=next command line chr ;On Exit: ; XMODE = 1 ;---------------------------- csetr: inc a ; A=1 ;Fall into CSETS to save XMODE ;******---------------------- ;* /S * Select send mode ;****** ;On Entry: ; a=b=0 ; (de)=next command line chr ;On Exit: ; XMODE = 0 ;---------------------------- csets: ld (xmode),a ret ;******---------------------- ;* /X * Select transfer port ;****** ;On Entry: ; (de)=next command line chr ;On Exit: ; XPORT set as specified ;---------------------------- csetx: call cmdchr sub '0' ; Un-ASCII cp 4 ; 0-3 allowed jp nc,badval ld (xport),a ret ;******-------------------------------- ;* /Z * Specify CPU speed, in MHz (1-6) ;****** ;On Entry: ; (de)=next command line chr ;On Exit: ; CPUMHZ updated ;------------------------------------- cmhz: call cmdchr sub '1' ; Un-ASCII cp 6 ; 1-6 allowed jp nc,badval inc a ; Make it 1-6 ld (cpumhz),a ret ;**********------------------------------------ ;* /Kn[n] * Set max buffer k-bytes ;********** ;Use this value to set the upper limit for the ;buffer. If the given value results in an upper ;limit above the beginning of BDOS, then use ;the beginning of BDOS as the limit instead. ;1K is the minimum. ;On Entry: ; (de)=next command line chr ;On Exit: ; de advanced past /K value ; BYTCNT decremented accordingly ;trashes psw,b,hl ;---------------------------------------------- bufkb: call cmdchr call d2bdig ; Error-exit if not ASCII digit ;We don't yet know whether this is the 1's digit or ;the 10's digit, so assume it could be either. ld b,a ; Temp save 1st digit add a,a ; *2 add a,a ; *4 add a,b ; *5 add a,a ; *10 in case of another digit ld c,a ; Temp save 10x digit ; Look for 2nd digit call cmdcei ; Z means whitespace, / or EOF jp z,bfbk1 ; No more digits? ;Convert and incorporate the new low digit call d2bdig ; 1's digit to binary add a,c ; Combine with high digit ld b,a ; Both decimal digits ;b=given k-bytes in binary. Check for a reasonable value bfbk1: ld a,b dec a ; Min 1K buffer cp 64 ; Max value jp nc,badval ;compute number of pages from number of k-bytes high byte ld a,b add a,a ; *512 add a,a ; *1024 ;compute last allowed page's address add a,buffer/256 ld b,a ; Result in b ;b=potential end page address ld a,(bdosa+1) ; Page beginning of BDOS ;Choose the smaller of b and a cp b jp c,bfk4 ; Carry means b is bigger ld a,b bfk4: dec a ; Last allowed page ld (bufmax),a ret ;---Local Subroutine-------------- ;Convert digit from ASCII and test ;--------------------------------- d2bdig: sub '0' ; Convert from ASCII cp 9+1 ret c ; Valid decimal? jp badval ; Valid? ;***INIT-Only Subroutine*************** ;Ignore a comment, terminated either ;by the end of the XMODEM.CFG line ;or the end of file/end of command line ;On Entry: ; de=next command line chr ;************************************** coment: call cmdchr jp nz,coment ; Z means EOF, CR or LF ret ;***INIT-Only Subroutine******************************* ;Get next character from the command line or XMODEM.CFG ;-->Entry is at CMDCHR<-- ; ;On Entry: ; OPMODE = 00h if reading the tail of the command line ; 01h if reading from XMODEM.CFG ; 80h for end-of-file for XMODEM.CFG ; BYTCNT has remaining buffer byte count, <=128 ; de points to the next chr of the input ;On Exit, not end of input stream: ; Carry clear ; Z set iff CR or LF found ; a = chr from COMBUF or XMODEM.CFG, parity stripped ; de has been advanced and BYTCNT decremented ; unless at end ; hl points to BYTCNT ;On Exit, end of command line: ; (End of line for the command line is when BYTCNT=0.) ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;On exit, end of file found in XMODEM.CFG ; (End of file for XMODEM.CFG is either an EOF chr ; or the end-of-file response from the BDOS.) ; OPMODE=80h ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;****************************************************** rdcmd: inc (hl) ; BYTCNT=0 ld a,(opmode) ; Reading command line? add a,a ; 00 and 80 will both become 0 scf ret z ; Y: done, Z and Carry set ;Try to read another block of XMODEM.CFG data push bc ld de,cfgbuf push de ld c,bstdma ; Set CP/M DMA address call gobdos ld de,cfgfcb ld c,bread ; Read another sector call gobdos ; A=0, Z set if not file end pop de ; CFGBUF pop bc jp nz,ccdone ; End: go set Z and C ; No more blocks? ld (hl),blksiz ; Another XMODEM.CFG block ;================ ;Subroutine Entry ;================ cmdchr: ld hl,bytcnt dec (hl) ; Dec BYTCNT (max was 128) jp m,rdcmd ; Empty: try to get more ld a,(de) ; Get buffer chr inc de ; Bump buffer pointer and 7fh ; Strip parity cp cr ret z cp lf ret z cp eof ; File end chr? scf ccf ; N:clear carry ret nz ;Exit CMDCHR subroutine: end of file found in XMODEM.CFG ccdone: ld a,80h ; Remember EOF ld (opmode),a add a,a ; Clear a, Set Z and C ret ;***INIT-Only Subroutine************************** ;Skip over whitespace (spaces, tabs, CRs, and LFs) ;in the command line buffer or XMODEM.CFG file ;On Entry: ; BYTCNT has remaining byte count ; de points to the next chr in buffer ;On Exit: ; a = chr from buffer ; BYTCNT has been decremented ; de has been advanced ; hl points to BYTCNT ; Carry means end of input (and a is not valid) ;************************************************* wskip: call cmdtws ret c ; End of input? jp z,wskip ; Whitespace? ret ;***INIT-Only Subroutine************************************* ;Get next command line or XMODEM.CFG chr, and test it for end ;of item. End of item is end of input, whitespace, or '/'. ;Whitespace is space, tab, CR, or LF. End of input for ;XMODEM.CFG is either an EOF chr or the EOF response from ;BDOS. End of input for the command line is when BYTCNT=0. ;On Entry: ; BYTCNT has remaining byte count ; de points to the next chr in buffer ;On Exit, not end of input stream: ; Carry clear ; Z set if EOF, space, tab, CR, LF, or / ; Z clear: a has valid chr ; a = chr from buffer unless whitespace or EOF ; BYTCNT has been decremented unless / ; de has been advanced unless / ; hl points to BYTCNT ;On Exit, end of command line: ; (End of line for the command line is when BYTCNT=0.) ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;On exit, end of file found in XMODEM.CFG ; (End of file for XMODEM.CFG is either an EOF chr ; or the end-of-file response from the BDOS.) ; OPMODE=80h ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;************************************************************ cmdcei: call cmdtws ret z ; End or whitespace? cp '/' ; Option crammed? scf ccf ; Clear carry ret nz ;slash (meaning crammed option), so ;back up, set Z for end of item dec de ; Y: back up inc (hl) ; Hl=BYTCNT from CMDCHR xor a ; Clear carry, set Z ret ;***INIT-Only Subroutine****************************** ;Get next command line or XMODEM.CFG chr and test it ;for end of input or whitespace. Whitespace is space, ;tab, CR, or LF. End of input for XMODEM.CFG is either ;an EOF chr or the end of file response from the BDOS. ;End of input for the command line is when BYTCNT=0. ;On Entry: ; BYTCNT has remaining byte count ; de points to the next chr in buffer ;On Exit: ; a = chr from buffer ; BYTCNT has been decremented ; de has been advanced ; hl points to BYTCNT ; Carry set means EOF (and a is 0) ; Z set if EOF, space, tab, CR, or LF ; Z clear: a has valid chr ;On Exit, not end of input stream: ; Carry clear ; Z set if EOF, space, tab, CR, or LF ; Z clear: a has valid chr ; a = chr from buffer unless whitespace or EOF ; BYTCNT has been decremented ; de has been advanced ; hl points to BYTCNT ;On Exit, end of command line: ; (End of line for the command line is when BYTCNT=0.) ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;On exit, end of file found in XMODEM.CFG ; (End of file for XMODEM.CFG is either an EOF chr ; or the end-of-file response from the BDOS.) ; OPMODE=80h ; Carry set ; Z set ; a=0 ; de invalid ; BYTCNT is 0 ;***************************************************** cmdtws: call cmdchr ; EOF, CR, LF? ret z ; Y: done, carry for EOF cp ' ' ret z cp tab scf ; Clear carry ccf ; Painfully ret ; Z set if TAB ;***INIT-Only Subroutine************************ ;Modify either the transfer input port routine ;or output port routine. This assumes that both ;routines look like this: ; ; WAIT: ... ; IMODFY or OMODFY: ; in <status port> ; ani <port ready mask> ; jnz WAIT (may get converted to jz) ; ; pop psw (or other 1-byte cmd) ; in/out <data port> ; ... ; ret ; ;On Entry: ; a = port-ready mask byte ; c <> 0 if jz needs to be installed ; d = data port address ; e = status port address ; hl = IMODFY+1 or OMODFY+1 ;Trashes a,hl ;********************************************* modio: ld (hl),e ; Install status port adr inc hl ; Point to mask location inc hl ld (hl),a ; Install status mask inc hl ; Point to jnz location ld a,c or a ; Code already has a JNZ jp z,modio1 ; Need a jz instead? jz equ 0cah ;AGN - JP Z opcode ld (hl),jz ; Y: install jz opcode modio1: inc hl ; Point to data port loc inc hl inc hl inc hl inc hl ld (hl),d ; Install data port adr ret ;***INIT-Only Subroutine*************************** ;Get an exactly 2-digit hex value from LINBUF ;On Entry: ; de points to first hex digit ;On Exit: ; carry set if no value found, either due to ; end of input or non-hex chr found on 1st digit ; a=value ; de advanced past hex if hex found ; de pointing to non-hex chr if found on 1st digit ; BYTCNT decremented accordingly ;Rude jump to BADVAL if bogus hex found on 2nd digit ;*************************************************** gethex: push hl call wskip ; Skip whitespace, get a chr ; Also sets hl=BYTCNT ; EOF will be bogus hex call hex2bn ; Convert a=1st digit jp nc,ghback ; Bogus digit? add a,a ; Shift high digit add a,a add a,a add a,a ; ..into high nibble push bc ld b,a ; Save high digit call cmdchr ; Get low digit ; A=0 if end, will fail call hex2bn ; Convert to binary jp nc,badval ; No digit found or bogus? add a,b ; Combine w/ high digit ; (clears carry) pop bc pop hl ret ; Carry is clear for ret ;non-hex 1st chr found, so backup, ;and return with carry set ghback: dec de ; Back up inc (hl) ; BYTCNT pop hl scf ret ; With carry set ;***INIT-Only Subroutine************** ;convert a to binary ;On Entry: ; a=ASCII hex digit ;On Exit: ; a=chr ; Carry set if OK, clear if bogus chr ;************************************* hex2bn: sub '0' ; Remove ASCII bias cp 10 ret c ; If 0-9 then we're done sub 9+('A'-'9') ; Should be 0-5 now cp 6 ; Gap chr or too high? ret nc ; Error: return W/O carry sub 0f6h ; Add 0Ah, set Carry ret ;***INIT-Only Subroutine**************************** ;Get a mandatory 2-digit hex value from LINBUF ;On Entry: ; de points to first hex digit ;On Exit: ; a=value ; de advanced 2 ; Rude exit via BADVAL if no chr or bogus hex found ;*************************************************** gthexm: call gethex ret nc ;Fall into BADVAL ;***INIT-Only Exit********************************* ;Bad Value, bad hex character ;Fix everything for CP/M, and return to CP/M ;The character following the / gets pasted in here. ;************************************************** badval: call cilprt defb '/' par1: defb '&' ; Parameter goes here defb ' bad valu','e'+80h jp errsrc ; Command line or .CFG file ;***INIT-Only Exit********************************* ;Illegal option. Print message and return to CP/M ;The character following the / gets pasted in here. ;************************************************** opterr: call cilprt ; Exit with this message defb '/' par2: defb '&' ; Parameter goes here defb ' unknow','n'+80h jp errsrc ; Command line or .CFG file ;***INIT-Only Exit********************************** ;Input error exits. Print message and return to CP/M ;*************************************************** badinp: call cilprt defb 'Jun','k'+80h ;Fall into ERRSRC ;***INIT-Only Exit**************** ;Bad input of some sort. Print the ;source of error and quit to CP/M ;On Entry: ; OPMODE <>0 if reading .CFG file ; =0 if command line ;********************************* errsrc: call ilprnt defb ' in',' '+80h ld a,(opmode) ; Command line or XMODEM.CFG? or a jp z,badcln call msgxit defb 'XMODEM.CF','G'+80h badcln: call msgxit defb 'command lin','e'+80h ;***INIT-Only Exit********************* ;Error opening file: Abort with message ; 'File ' has already been printed ;************************************** fofail: call msgxit ; Exit w/ this message defb 'not foun','d'+80h ;***INIT-Only Exit********************** ;Error: File create failed ; 'File create' has already been printed ;*************************************** fcerr: call msgxit defb ' fail. Write protect? Dir full','?'+80h ;***INIT-Only Exit**** ;No file name provided ;********************* nofner: call cmsgxt defb 'No filenam','e'+80h ;***INIT-Only Exit********************************* ;Print help screen, and then exit. Break up the ;help screen so that it even fits on a 16x64 screen ;************************************************** hlpext: call cilprt ; Print this message ; 123456789012345678901234567890123456789012345678901234567890123 defb '========================',cr defb 'Xmodem ' defb ((version and 0f00h)/256)+'0','.',(version and 0fh) +'0' ifdef mbc2 defb ' for Z80-MBC2.',cr else defb ' By M.Eberhard',cr endif defb '========================',cr defb 'Usage: XMODEM <file> <option list>',cr defb '^C aborts',cr,lf defb 'Command line and XMODEM.CFG options:',cr defb ' /R Receive, /S Send',cr defb ' /C Receive with checksums, else CRCs',cr defb ' (Receiver always sets error check mode)',cr defb ' /E if CP/M RDR returns with Z set when not ready',cr defb ' /Knn sets buffer max k-bytes (default: all free RAM)',cr defb ' nn is decimal, 0<nn<64',cr,lf defb '--More-','-'+80h call getcon ; Wait for user input call cilprt defb ' /In 8080 code patches for /X3 I/O routines:',cr defb ' /I0 h0 h1 ...h11: initialize',cr defb ' /I1 h0 h1 ...h11: Tx data, chr in c',cr defb ' /I2 h0 h1 ...h11: Rx status, Z set if no chr',cr defb ' /I3 h0 h1 ...h11: Rx data, chr in a',cr defb ' /M Console message',cr defb ' /O pp h0 h1...hn sends hex h1-hn to port pp',cr defb ' /P ss dd qq rr tt defines direct I/O port:',cr defb ' ss: status port',cr defb ' dd: data port',cr defb ' qq: 00/01 for active low/high ready',cr defb ' rr: Rx ready bit mask',cr defb ' tt: Tx ready bit mask',cr defb '/I, /O and /P values are 2-digit hex',cr,lf defb '--More-','-'+80h call getcon ; Wait for user input call cmsgxt ; Print message and exit to CP/M defb ' /Q for Quiet; else + means ok block, - means retry',CR defb ' /X sets the transfer port:',cr defb ' /X0 CP/M CON *',cr defb ' /X1 CP/M RDR/PUN * (default)',cr defb ' /X2 Direct I/O, defined by /P option',cr defb ' /X3 Custom code from /I patches',cr defb ' * Must not strip parity',cr defb ' /Zm for m MHz CPU. 0<m<7, default m=' ;This '2' gets changed to '4' if a Z80 is detected. dmhz: defb '2',cr+80h ;***INIT-Onl* Table************************ ;Port configuration table ;Three words per entry: ; Word 0 = timer constant ; Word 1 = address of receive byte routine ; Word 3 = address of transmit byte routine ;****************************************** portab: defw conto,rxbcon,txcon ; X0 CP/M console defw rdrto,rxrdr,txpun ; X1 CP/M RDR/PUN defw dirto,rxdrct,txdrct ; X2 Direct, setup with /P defw custo,rxcust,txcust ; X3 Custom, setup with /I ;***INIT-Only Table************** ;Configuration File Control Block ;******************************** cfgfcb: defb 0 ; (dr) use default drive defb 'XMODEM ' ; (f1-f8) defb 'CFG' ; (t1-t3) defb 0,0,0,0 ; (ex,s1,s2,rc) defw 0,0,0,0,0,0,0,0 ; (d0-d15) defb 0,0,0,0 ; (cr,r0,r1,r2) ;***INIT-Only Buffer****** ;Configuration file buffer ;************************* cfgbuf: defs blksiz end