; CP4UTL.ASM ; KERMIT - (Celtic for "FREE") ; ; This is the CP/M-80 implementation of the Columbia University ; KERMIT file transfer protocol. ; ; Version 4.0 ; ; Copyright June 1981,1982,1983,1984 ; Columbia University ; ; Originally written by Bill Catchings of the Columbia University Center for ; Computing Activities, 612 W. 115th St., New York, NY 10025. ; ; Contributions by Frank da Cruz, Daphne Tzoar, Bernie Eiben, ; Bruce Tanner, Nick Bush, Greg Small, Kimmo Laaksonen, Jeff Damens, and many ; others. ; ; Utility routines, pure and impure data. ; ; revision history: ; edit 4: September 9, 1984 ; Move command tables and associated help text to CP4MIT. Add ; makfil/clofil routines and modify outbuf to write files in big ; chunks. Update Kermit's version to 4.03. ; ; edit 3: August 21, 1984 ; Make inbuf read files in big chunks to minimize disk start/stop ; delays. Buffer size and address is specified by system-dependent ; overlay. ; ; edit 2: August 3, 1984 ; move "mover" to CP4SYS to allow use of Z80 block move instruction. ; ; edit 1: July 27, 1984 ; extracted from CP4MIT.M80 edit 2, as part of LASM support. This is ; the last file linked for the system-independent code. ; utlver: db 'CP4UTL.ASM (4) 9-Sep-84$' ; Set the parity for a character in A. ; called by: spack, rexmit, logit, vt52, conchr, intchr setpar: push h ;Save HL. push b lxi h,parity mov c,m ;Get the parity routine. mvi b,0 lxi h,parjmp ;Get the first address. dad b pchl parjmp: jmp even jmp mark jmp none jmp odd jmp space none: jmp parret ;Don't touch the parity bit. even: ani 7FH ;Strip parity. jpe parret ;Already even, leave it. ori 80H ;Make it even parity. jmp parret mark: ori 80H ;Turn on the parity bit. jmp parret odd: ani 7FH ;Strip parity. jpo parret ;Already odd, leave it. ori 80H ;Make it odd parity. jmp parret space: ani 7FH ;Turn off the parity bit. jmp parret parret: pop b pop h ;Restore HL. ret ; ; Print the escape char. ; called by: stat01, xmit, telnet, intchr escpr: lda escchr ;Get the escape char. cpi ' ' ;Is it a control char? jp escpr2 lxi d,inms10 ;Output "Control-". call prtstr lda escchr ori 100O ;De-controlify. escpr2: mvi c,conout ;Output the char mov e,a call bdos ret ; fetch keyword; if unsuccessful, return to command level. ; called by: kermit, setcom keycmd: mvi a,cmkey call comnd jmp keycm2 ;no match ret keycm2: lxi d,ermes1 ;"Unrecognized Command" call prtstr jmp kermit ;Do it again. ; request confirmation; if unsuccessful, return to command level ; called by: bye, exit, help, log, setcom, show, status, send, ; finish, logout, xmit, telnet cfmcmd: mvi a,cmcfm call comnd jmp kermt3 ;"Not confirmed" ret ; ; This routine prints the number in HL on the screen in decimal. ; Uses all ACs. ; called by: cp4sys, read, send, updrtr, dir nout: lxi b,-10 ;Get some useful constants. nout1: lxi d,-1 nout2: dad b ;Subtract as many 10s as possible. inx d ;Count them. jc nout2 ;If some left keep going. push h ;save remainder - 10 xchg ;Swap the remainder and the quotient. mov a,h ;Get the number of 10s found. ora l ;check for quotient non zero cnz nout1 ;If non zero, recurse. pop h ;Get the remainder - 10 mov a,l ;in a adi '0'+10 ;Make the number printable and add the 10 back mov e,a ;Output the digit. mvi c,conout jmp bdos ; prcrlf - print a CR/LF. (Saves no registers.) [Toad Hall] ; prtstr - print string pointed to by DE ; called by: lots of places. prcrlf: lxi d,crlf ;Point to the CR/LF prtstr: mvi c,prstr ; output string jmp bdos ;a CALL followed by a RET becomes a JMP ; Jumping to this location is like retskp. It assumes the instruction ; after the call is a jmp addr. ; here from: many places. rskp: pop h ;Get the return address. inx h ;Increment by three. inx h inx h pchl ; Jumping here is the same as a ret. 'jmp r' is used after routines ; that have skip returns, where the non-skip instruction must be 3 bytes. ; here from: many places. r: ret ; ; Open a file for reading (with inbuf). The filename is already ; in fcb; upon return, the end of file flag is cleared and chrcnt ; is set to zero, so inbuf will be called to get a buffer when we ; next attempt to get a character. ; called by: sinit, seof, xmit getfil: xra a sta chrcnt ;Buffer is empty. sta seccnt ;No sectors buffered. sta eoflag ;Not the end of file. sta endsts ;No EOF/error pending. sta fcb+0CH ;Zero the extent. sta fcb+0EH ;Must be zero for MAKEF or OPENF. sta fcb+20H ;Zero the current record. mvi c,openf ;Open the file. lxi d,fcb call bdos ret ; Get next sector. If necessary, read some more from disk. ; preserves bc, de, hl ; returns nonskip if EOF or error; ; returns skip with chrcnt and bufpnt updated if success. ; called by: gtchr, xnext inbuf: lda eoflag ;Have we reached the end? ora a rnz ;Return if so. push b push d push h inbuf1: lda seccnt ; Do we have any sectors left? ora a jz inbuf3 ; If not, go get some more. inbuf2: lhld nxtbuf ; Yes. Get address of next sector shld bufpnt ; Update current buffer pointer lxi b,bufsiz ; Get number of bytes in sector dad b ; Update HL to point to next sector shld nxtbuf ; Save for next time dcr a ; Decrement count of buffered sectors sta seccnt ; Put it back mvi a,bufsiz-1 ; Number of bytes in buffer (pre-decremented) sta chrcnt ; Store for our caller pop h pop d pop b jmp rskp ; Return success ; We don't have any sectors buffered. If we've already hit an error or ; EOF, return that status to the user. inbuf3: lda endsts ; Check status from previous read ora a jz inbuf4 ; It was OK. Get some more sectors. sta eoflag ; End of file or error. Set the flag. xra a sta chrcnt ; Say no characters in buffer. pop h pop d pop b ret ; Return failure ; Read sectors until we fill the buffer or get an error or EOF, then return ; the first buffer to the user. (seccnt is zero) inbuf4: lhld bufadr ; Get address of big buffer shld nxtbuf ; Store as next buffer address to give user inbuf5: shld bufpnt ; Store as next buffer address to read into xchg ; Move buffer address to DE mvi c,setdma ; Tell CP/M where to put the data call bdos ; ... mvi c,readf ; Read a record. lxi d,fcb call bdos sta endsts ; Save ending status ora a ; 00H => read O.K jnz inbuf6 ; EOF/error: stop reading. lxi h,seccnt ; Success. Get addr of sector count inr m ; Bump sector count by one lda bufsec ; Get max number of sectors in buffer cmp m ; Are we there yet? jz inbuf7 ; Yes, can't read any more. lhld bufpnt ; No, try for another. Get buffer address, lxi d,bufsiz ; and size of sector, dad d ; giving next buffer address in HL jmp inbuf5 ; Go read another sector. ; We hit EOF or got an error. Put the DMA address back where it belongs, ; then go see if we have any sectors (before the one that got the error) ; to return to the caller. Nxtbuf points to the first good sector, if ; any; seccnt contains the count of good sectors. inbuf6: call inbuf8 jmp inbuf1 ; Go see if we have some data to return ; We've filled the big buffer. Reset the DMA address, then go return a ; sector to the caller. nxtbuf points to the beginning of the buffer; ; seccnt contains the number of sectors successfully read (except that ; if we've read 256 sectors, seccnt contains zero, so we can't just go ; to inbuf1). inbuf7: call inbuf8 lda seccnt ; Get sector count again. jmp inbuf2 ; Return a sector. ; Reset DMA address to the default buffer inbuf8: lxi d,buff mvi c,setdma jmp bdos ; ; Create a file, deleting any previous version. The filename is in ; fcb. ; Returns nonskip if file could not be created. ; If successful, takes skip return with bufpnt and chrcnt initialized ; for output; buffers should be output via outbuf. ; called by: gofil makfil: mvi c,delf ; delete the file if it exists. lxi d,fcb call bdos xra a sta fcb+0CH ; zero the extent. sta fcb+0EH ; must be zero for MAKEF or OPENF. sta fcb+20H ; zero the current record. mvi c,makef ; now create it. lxi d,fcb call bdos cpi 0FFH ; is the disk full? rz ; take error return if so. ; success. set up pointers and counters for multisector buffering. lhld bufadr ; find beginning of buffer space. shld bufpnt ; make it current buffer. lxi d,bufsiz ; get sector size. dad d ; find beginning of next buffer. shld nxtbuf ; store for later. mov a,e ; store buffer size sta chrcnt ; for caller. xra a sta seccnt ; no sectors stored yet. jmp rskp ; take success return. ; get a fresh output buffer, flushing big buffer if necessary. ; returns nonskip if disk full. ; if successful, returns skip with bufpnt and chrcnt updated. Note ; that chrcnt holds one less than the buffer size. ; preserves BC. ; called by: ptchr outbuf: push b lxi h,seccnt ; count another buffered sector inr m ; ... lda bufsec ; get number of sectors we can hold cmp m ; full? jnz outbf2 ; if not, set up pointers and return call outmbf ; flush the big buffer jmp outbf9 ; disk error. outbf2: lhld nxtbuf ; get pointer to fresh buffer shld bufpnt ; store for caller lxi d,bufsiz ; advance our pointer to next buffer dad d shld nxtbuf mvi a,bufsiz-1 ; get buffer size (pre-decremented) sta chrcnt ; store for caller pop b jmp rskp ; return success. outbf9: pop b ; clean up stack ret ; and take error return. ; flush incore output buffers. ; returns nonskip if disk full. ; if successful, returns skip with nxtbuf reset to start of buffer and ; seccnt zero. ; destroys all ac's. ; called by: outbuf, clofil. outmbf: lhld bufadr ; get start of buffer shld nxtbuf ; store for next fill cycle shld bufpnt ; store for empty loop outmb2: lhld bufpnt ; get address of current sector xchg ; into DE lxi h,bufsiz ; advance HL to next sector dad d ; ... shld bufpnt ; and store for later mvi c,setdma call bdos ; point CP/M at current sector lxi d,fcb mvi c,writef call bdos ; output the sector ora a ; test for error (A non-zero) rnz ; take nonskip return if so lxi h,seccnt dcr m ; count down buffered sectors jnz outmb2 ; loop if more saved lxi d,buff ; done. restore standard DMA address mvi c,setdma call bdos jmp rskp ; return success. ; output current buffer, flush incore buffers, and close output file. ; returns nonskip if disk full; skip if successful. ; called by: rdata clofil: lda chrcnt ; get the number of chars left in the buffer. cpi bufsiz ; Virgin buffer? jz clofl3 ; yes, don't output it. lhld bufpnt ; get the buffer pointer. clofl1: dcr a ; lower the count. jm clofl2 ; if full then stop. mvi m,'Z'-100O ; put in a ^Z for EOF. inx h ; point to the next space. jmp clofl1 clofl2: call outbuf ; output the last buffer. jmp r ; give up if the disk is full. clofl3: lda seccnt ; any sectors buffered in memory? ora a jz clofl4 ; if not, don't try to flush. call outmbf ; flush buffers jmp r ; disk full. clofl4: mvi c,closf ; close up the file. lxi d,fcb call bdos jmp rskp ; return success. ; version:db 'Kermit-80 v4.03 $' kerm: db 'Kermit-80 ' kerm1: db 'x:>$' ;'x' filled in at startup with DRIVE name crlf: db cr,lf,'$' ermes1: db cr,lf,'?Unrecognized command$' ermes3: db cr,lf,'?Not confirmed$' ermes4: db '?Unable to receive initiate',cr,lf,'$' ermes5: db '?Unable to receive file name',cr,lf,'$' ermes6: db '?Unable to receive end of file',cr,lf,'$' erms10: db '?Unable to receive data',cr,lf,'$' erms11: db '?Disk full',cr,lf,'$' erms14: db '?Unable to receive an acknowledgement from the host',cr,lf,'$' erms15: db cr,lf,'?Unable to find file',cr,lf,'$' erms16: db '?Unable to rename file$' erms17: db cr,lf,'?Disk full$' erms18: db cr,lf,'?Unable to tell host that the session is finished$' erms19: db cr,lf,'?Unable to tell host to logout$' erms20: db cr,lf,'?Kermit has not been configured for a target system$' erms21: db cr,lf,'?Consistency check on configuration failed$' infms3: db bell,'Completed$' infms4: db bell,'Failed$' infms5: db '%Renaming file to $' infms6: db cr,lf,'[Closing the log file]$' infms7: db cr,lf,'[Connected to remote host. Type $' infms8: db 'C to return;',cr,lf,' type $' inms8a: db '? for command list]',cr,lf,'$' infms9: db cr,lf,'[Connection closed, back at micro]$' inms10: db 'Control-$' inms12: db ' (Not implemented)',cr,lf,'$' inms13: db bell,'Interrupted$' inms14: db TAB,TAB,' Directory for drive ' dnam14: db 'x:',cr,lf,'$' ;filled in by dir routine. inms15: DB CR,LF,TAB,TAB,'Drive $' inms16: DB ' has $';filled in by summary code with drive letter inms17: DB 'K bytes free',CR,LF,'$' inms18: DB CR,LF,'File(s) erased$',CR,LF inms19: db cr,lf,'[Transmitting file to host:' db cr,lf,' 1. Type any character to send a line.' db cr,lf,' 2. Type RETURN to terminate the line ' db 'and to get the next line (go back to 1.)' db cr,lf,' (You may send other characters ' db 'before RETURN.),' db cr,lf,' or type $' inms20: db 'R to send the same line again,' db cr,lf,' or type $' inms21: db 'C to abort transmission.]',cr,lf,'$' inms22: db cr,lf,'[Transmission done. Connected normally ' db 'to remote host,' db cr,lf,' type $' inms23: db 'Sending...$' inms24: db 'Receiving...$' inms25: db bell,'Warning: eighth bit cannot be sent$' inms26: db cr,lf,'For help, type ? at any point in a command$' escmes: db cr,lf,'Type the new escape character: $' tacmes: db cr,lf,'Type the new TAC intercept character: $' xmthlp: db cr,lf,'R Send the same line again$' inthlp: db cr,lf,'? This message' db cr,lf,'C Close the connection' db cr,lf,'0 (zero) Transmit a NULL' db cr,lf,'S Status of the connection$' inhlp1: db cr,lf,'Typing another $' inhlp2: db ' will send it to the host' db cr,lf,cr,lf,'Command>$' xmtst: db cr,lf,'Transmitting a file$' locst: db cr,lf,'Local echo$' onstr: db ' on$' offstr: db ' off$' vtemst: db cr,lf,'VT52 emulation$' cpmst: db cr,lf,'File Mode$' defstr: db ' default$' ascstr: db ' ASCII$' binstr: db ' binary$' ibmst: db cr,lf,'IBM flag$' filst: db cr,lf,'File warning$' prst: db cr,lf,'Printer copy$' escst: db cr,lf,'Escape char: $' bckst: db cr,lf,'Block check type: $' bckst1: db '-character$' parst: db cr,lf,'Parity: $' pnonst: db 'none$' pmrkst: db 'mark$' pspcst: db 'space$' poddst: db 'odd$' pevnst: db 'even$' spdst: db cr,lf,'Speed $' spdust: db 'is indeterminate (not SET)$' timmsg: db 'Timer$' tacst: db cr,lf,'Current TACTrap Status/Intercept Character: $' cmer00: db cr,lf,'?Program error: Invalid COMND call$' cmer01: db cr,lf,'?Ambiguous$' cmer02: db cr,lf,'?Illegal CP/M file specification$' cmer03: db cr,lf,'?"*" not allowed in file specification$' cmin00: db ' Confirm with carriage return$' ; ;Impure data ;COMND storage cmstat: ds 1 ;What is presently being parsed. cmaflg: ds 1 ;Non-zero when an action char has been found. cmccnt: ds 1 ;Non-zero if a significant char is found. cmsflg: ds 1 ;Non-zero when the last char was a space. cmostp: ds 2 ;Old stack pointer for reparse. cmrprs: ds 2 ;Address to go to on reparse. cmprmp: ds 2 ;Address of prompt. cmptab: ds 2 ;Address of present keyword table. cmhlp: ds 2 ;Address of present help. cmdbuf: ds 80H ;Buffer for command parsing. cmfcb: ds 2 ;Pointer to FCB. cmfcb2: ds 2 ;Pointer to position in FCB. cmfwld: ds 1 ;Wildcard flag cmcptr: ds 2 ;Pointer for next char input. cmdptr: ds 2 ;Pointer into the command buffer. cmkptr: ds 2 ;Pointer to keyword. cmsptr: ds 2 ;Place to save a pointer. ; oldsp: ds 2 ;Room for old system stack. ds 40H ;Room for 32 levels of calls. stack: ds 2 eoflag: ds 1 ;EOF flag;non-zero on EOF. curdsk: db 0 ;holds "logged" disk prnflg: db 0 ;Flag copy to printer timflg: db 0 ;[jd] timer flag: 0 -> no timer timval: dw 0 ;[jd] timer value wrn8: db 0 ;[jd] non-zero if 8-bit-lost warning sent qbchr: db '&' ;[jd] binary quote character. quot8: db 0 ;[jd] non-zero if doing 8-bit quoting logflg: db 0 ;Flag for a log file. escflg: db 0 ;Escape flag (start off). fileio: db 0 ;Line-by-line from file (default off). xofflg: db 0 ;X-OFF (=^S) received from COMM-line ; ;X-ON (=^Q) received resets this vtyval: ds 1 ; holds row number for VT52 cursor positioning chrcnt: ds 1 ;Number of chars in the file buffer. filcnt: ds 1 ;Number of chars left to fill. outpnt: ds 2 ;Position in packet. bufpnt: ds 2 ;Position in file buffer. fcbptr: ds 2 ;Position in FCB. datptr: ds 2 ;Position in packet data buffer. cbfptr: ds 2 ;Position in character buffer. pktptr: ds 2 ;Position in receive packet. size: ds 1 ;Size of data from gtchr. curchk: ds 1 ;Current checksum type inichk: ds 1 ;Agreed upon checksum type czseen: ds 1 ;Flag that control-Z was typed pktnum: ds 1 ;Packet number. numpkt: ds 2 ;Total number of packets sent. numrtr: ds 2 ;Total number of retries. numtry: ds 1 ;Number of tries on this packet. oldtry: ds 1 ;Number of tries on previous packet. state: ds 1 ;Present state of the automaton. packet: ds 4 ;Packet (data is part of it). data: ds 5AH ;Data and checksum field of packet. recpkt: ds 65H ;Receive packet storage (use the following). recpkx: db cr,'$' ;= = = buffer limit filbuf: ds 65H ;Character buffer. fnbuf: ds 20h ;[jd] file name buffer ;** Temp 1 & 2 must be in order temp1: ds 1 ;Temporary storage. lstchr EQU temp1 ;Last console input character. temp2: ds 1 temp3: ds 1 temp4: ds 1 argblk: ds 20H ;Used for subroutine arguments maxfil EQU 2 ; currently, only two names used. fcbblk: ds maxfil*10H ;Used for a list of FCB's ; Bookkeeping storage for multiple-sector buffering. The actual buffer ; is somewhere in the system-dependent overlay. (at the end, I hope). nxtbuf: ds 2 ; Pointer to next sector seccnt: ds 1 ; Number of sectors buffered endsts: ds 1 ; Status for last read into buffer patch: ds 100 ; Guarantee some patch space ORG ($ + 0ffH) AND 0ff00H ; move to start of next page ; ; hooks for system-dependent routines: ; This area is overwritten by the system-dependent overlay. ; lnkflg: dw 0 ; linkage information for consistency check. lnkent: dw 0 ; more of the same. ovlver: dw 0 ; pointer to overlay's version string ; ; Input/output routines. Note that outmdm and outcon may actually be the ; same routine if selmdm and selcon do anything. (the same is true ; of inpmdm and inpcon). ; selmdm: jmp $-$ ; select modem for I/O outmdm: jmp $-$ ; output character in E to modem inpmdm: jmp $-$ ; read character from modem. return character or 0 in A. flsmdm: jmp $-$ ; flush pending input from modem selcon: jmp $-$ ; select console for I/O outcon: jmp $-$ ; output character in E to console inpcon: jmp $-$ ; read char from console. return character or 0 in A outlpt: jmp $-$ ; output character in E to printer ; ; screen formatting routines clrlin: jmp $-$ ; erase current line clrspc: jmp $-$ ; erase current position (after backspace) delchr: jmp $-$ ; make delete look like backspace clrtop: jmp $-$ ; erase screen and go home ; ; these routines are called to display a field on the screen. scrend: jmp $-$ ; move to prompt field screrr: jmp $-$ ; move to error message field scrfln: jmp $-$ ; move to filename field scrnp: jmp $-$ ; move to packet count field scrnrt: jmp $-$ ; move to retry count field scrst: jmp $-$ ; move to status field rppos: jmp $-$ ; move to receive packet field (debug) sppos: jmp $-$ ; move to send packet field (debug) ; sysinit: jmp $-$ ; program initialization sysexit: jmp $-$ ; program termination syscon: jmp $-$ ; remote session initialization syscls: jmp $-$ ; return to local command level sysinh: jmp $-$ ; help text for interrupt (escape) extensions sysint: jmp $-$ ; interrupt (escape) extensions, including break sysflt: jmp $-$ ; filter for incoming characters. ; called with character in E. sysbye: jmp $-$ ; terminate remote session sysspd: jmp $-$ ; baud rate change routine. ; called with value from table in DE sysprt: jmp $-$ ; port change routine. ; called with value from table in DE sysscr: jmp $-$ ; screen setup for file transfer ; called with Kermit's version string in DE csrpos: jmp $-$ ; move cursor to row B, column C sysspc: jmp $-$ ; calculate free space for current disk mover: jmp $-$ ; block move ; ; Data initialized by system-dependent overlay: ; pttab: ds 2 ; points to local equivalents to VT52 escape sequences spdtab: ds 2 ; address of baud rate command table, or zero spdhlp: ds 2 ; address of baud rate help table, or zero prttab: ds 2 ; address of port command table, or zero prthlp: ds 2 ; address of port help table, or zero timout: ds 2 ; Initial value for fuzzy timeout vtflg: ds 1 ; VT52 emulation flag escchr: ds 1 ; Storage for the escape character. speed: ds 2 ; storage for the baud rate dbgflg: ds 1 ; debugging flag ecoflg: ds 1 ; Local echo flag (default off). flwflg: ds 1 ; File warning flag (default on). ibmflg: ds 1 ; IBM flag (default off). cpmflg: ds 1 ; File mode flag (ascii/binary/default) parity: ds 1 ; Current parity. spsiz: ds 1 ; Send packet size. rpsiz: ds 1 ; Receive packet size. stime: ds 1 ; Send time out. rtime: ds 1 ; Receive time out. spad: ds 1 ; Send padding. rpad: ds 1 ; Receive padding. spadch: ds 1 ; Send padding char. rpadch: ds 1 ; Receive padding char. seol: ds 1 ; Send EOL char. reol: ds 1 ; Receive EOL char. squote: ds 1 ; Send quote char. rquote: ds 1 ; Receive quote char. chktyp: ds 1 ; Checksum type desired tacflg: ds 1 ; TACTrap flag (zero=off, nonzero=on; when non-zero, ; contains current TAC intercept character) tacchr: ds 1 ; TAC intercept character bufadr: ds 2 ; Pointer to big buffer for multiple-sector I/O bufsec: ds 1 ; Number of sectors big buffer can hold (0 means 256) ; space used by directory command; here because space calculation is ; (operating) system-dependent bmax: ds 2 ; highest block number on drive bmask: ds 1 ; (records/block)-1 bshiftf: ds 1 ; number of shifts to multiply by rec/block nnams: ds 1 ; counter for filenames per line lnksiz equ $-lnkflg ; length of linkage section, for consistency check. END START