aboutsummaryrefslogtreecommitdiff
path: root/src/cpsutl.asm
diff options
context:
space:
mode:
authorJeff Armstrong <jeff@approximatrix.com>2026-10-25 17:42:06 -0400
committerJeff Armstrong <jeff@approximatrix.com>2026-10-25 17:42:06 -0400
commitebb2ff2af569d5822b70ee555a32d27cf7494816 (patch)
tree7ac5e7b3fa73f7a8d996129086932ecce66374ad /src/cpsutl.asm
downloadkermit-80-ebb2ff2af569d5822b70ee555a32d27cf7494816.tar.gz
kermit-80-ebb2ff2af569d5822b70ee555a32d27cf7494816.zip
Initial import of 4.11HEADmain
Diffstat (limited to 'src/cpsutl.asm')
-rw-r--r--src/cpsutl.asm1227
1 files changed, 1227 insertions, 0 deletions
diff --git a/src/cpsutl.asm b/src/cpsutl.asm
new file mode 100644
index 0000000..3081232
--- /dev/null
+++ b/src/cpsutl.asm
@@ -0,0 +1,1227 @@
+; CPSUTL.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,1985
+; 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 31, 21-Mar-1991 by MF. Implement edit 30 without checking takflg after
+; "r1tch1" as we are **always** TAKEing from a file if we get to that
+; point in the code. Makes for simplicity.
+;edit 30, 27-Feb-1991 by MF. When TAKEing characters from a TAKE-file,
+; view semicolons as normal characters (not command separators).
+; This will allow such commands as REMOTE DELETE *.*;* to work
+; properly from TAKE-files without having to revert to old code in
+; cpsrem.asm at "remcl0" to decode Remote command arguments. TAKE-files
+; ought not (in my opinion) to have multiple commands per line anyway.
+;edit 29, 30-Jan-1991 by MF. Fix bug in IN1CHR which decremented "chrcnt"
+; once too often after a call to INBUF (which predecrements it
+; already). This, along with a fix in CPSTT.ASM, fixes a bug in the
+; TRANSMIT command wherein certain characters in the file were not
+; being transmitted. This bug was reported to me by Lance Tagliapietra
+; of the University of Wisconsin at Platteville, WI (Email:
+; <TAGLANCE@ucs.UWPLATT.EDU>).
+; (he suggested not predecrementing "chrcnt" in INBUF" but this
+; breaks code in routine "GTCHR" from CPSPK1.ASM so it's better
+; to modify IN1CHR in this module and XMIT in CPSTT.ASM).
+;edit 28, 30-Nov-1990 by MF. Modify routine "p20ln" to use "pausit" routine
+; rather than explicitly checking for Console input to eliminate
+; redundant code. Also fix spelling in "p20ln"'s comments.
+;edit 27, 9-Sep-90 by MF. Put RET in routine PAUSIT per CPKERM.BWR.
+; edit 26, September, 1987. Added pause-it routine to wait for a user keysroke.
+;
+; edit 25, August 19, 1987 by OBSchou. Fixed a few bugs here and there.
+;
+; edit 24, April 8, 1987 by OBSchou. Added routine to return one character
+; from a section of several sectors worth of file. This routine needed
+; for TRANSMIT.
+;
+; edit 23, March 11, 1986 by OBSchou for Richard Russell
+; Bug in the TAKE code, such that a new sector was read in after 256
+; bytes, and not the CPM value. A jnz is now jp in the test to see if
+; the file buffer has bben exhausted. Many thanks for finding this
+; bug. I have never used TAKE files more than 128 bytes long.
+;
+; edit 22, January 28, 1986 by OBSchou.
+; split off the data areas from CPSUTL to CPSDAT.ASM (All in line
+; with keeping individual files relatively small)
+;
+; edit 21 August, 1986 by OBSchou. Sorted a few more bugs in printer buffer
+; etc. Have yet to try this with a real printer The code, apart from
+; actually printing works OK.
+;
+; edit 26 20 August, 1986 by OBSchou for Godfrey Nix:
+; edit 8-Aug-86 by Godfrey N. Nix [gnn] Nottingham University
+; Added two extra bytes for storage of the send and receive
+; start-of-packet characters. Used by CP4PKT, and altered
+; by SET option in CP4MIT. Also added message strings for
+; use by show routines. Added remote filename buffer and length byte.
+;
+; edit 11: June 20, 1986 by OBSchou. Added multi-fcbs for the DIR command
+; together with some bug clearing and new routines. Had to move
+; the overlay to 5000h as we ran out of space...
+;
+; edit 10: June 16, 1986 OBSchou. Added a pseudo clock and check for printer
+; ready whenever one enters BDOS... This may slow things down a little
+; but adds in (hopefully) pseudo background printing...
+;
+; edit 9 30-May-86 OBSchou. Added XON/XOFF routines here for the world
+; at large to use. Also added two new entries in the overlay tables.
+; One to give the address of the family of machines using the overlay,
+; the other to the routine for giving printer status.
+;
+; edit 8: 27-May-86 OBSchou. Added code to check BDOS calls for info from
+; the console. If so, and the take flag (takflg) is set then we
+; substitute our own characters. Simple, a little tatty...
+; Also added bits for SET CASE-SENSITIVE and SET FLOW=CONTROL, and
+; removed the XMIT rubbish. This is a prelude to better TRANSMIT
+;
+; edit 7: 22 April, 1986 by OBSchou Lohghborough University
+; Prlude to more changee, this time make overlay address to 4000h
+; May revert back to ($+0ffh) AND 0ff00h as address for overlay.
+; This gives us space to make quite a few modifications to the system
+; dependent part without much fear of having to change this overlay
+; address. Should also fix the Osborne problem of having to have io
+; routines ii memory above 16k. I know I should not be introducing
+; such system dependent rot here, but it wont be too difficult to fill
+; memory to 4000h.
+;
+; edit 6: February 6, 1985
+; Added a storage location for the port value (PORT, just below
+; SPEED) which is used by the port status routine, and moved the
+; printer copy flag (PRNFLG:) into the communications area so
+; that the machine dependant overlay can toggle it. [Hal Hostetler]
+; Added ffussy flag for filename checking. Generate the version
+; string from 'verno', which is set in CP4KER, because CP4KER has the
+; list of modules and their edit numbers. [Charles Carvalho]
+;
+; edit 5: 13-Jan-85 by Vanya J.Cooper Pima Commun. College Tel: 602-884-6809
+;
+;pcc002 28-Dec-84 modules:cp4tt,cp4utl
+; Add connect mode <esc>P command to toggle printer on
+; and off. Conflicts with "official" recommended commands
+; in protocol manual, but I don't think CP/M will ever get
+; a PUSH command.
+;
+;pcc003-pcc005 2-Jan-85 vjc modules:cp4mit,cp4tt,cp4utl
+; These edits must all be installed together and change the way
+; logging is handled. The log file spec is moved to a separate
+; fcb, and not opened until an actual CONNECT command is given.
+; This takes care of a NASTY bug that if you used any other file
+; command between the LOG and CONNECT, the log file would get
+; written over the last file used. This also allows logging to
+; be "permanently" enabled until an CLOSE (new command) for all
+; CONNECT sessions, like most other kermits do. If a log file
+; already exists, it will be appended to. Also add two new
+; CONNECT mode commands <esc>Q to suspend logging and <esc>R to
+; resume. <esc>R means something else during TRANSMIT, but
+; logging is never on then, so there shouldn't be any conflict.
+; I also changed the write code, so that it can handle one more
+; character after the XOFF is send to stop the host. This allows
+; a little "slop" for systems that don't stop immediately (such
+; as TOPS10), but it didn't help much.
+;
+;pcc006 2-jan-85 VJC modules:cp4cmd,cp4utl
+; Problems with "?" in filespecs. On reparse, may cause action
+; flag to be reset at wrong point, requiring multiple <CR>'s
+; to terminate the line or other weird stuff. Also need to
+; check flag and complain if wild-cards illegal.
+;
+;pcc008 2-Jan-85 vjc modules:cp4def,cp4tt,cp4utl
+; Keyboard input during CONNECT mode can get locked out if
+; there is enough input from the modem port to keep prtchr
+; busy. This can happen for example, if the printer is running
+; at the same speed as the modem line, leaving you helpless to
+; turn it off or abort the host. Add a fairness count, so that
+; at least every prfair characters we look at console input.
+;
+;pcc012 4-Jan-85 vjc modules:cp4mit,cp4tt,cp4utl
+; Use the big buffer for the log file. Move the log file back
+; into the common fcb and only save the drive, name, and
+; extension between connects. Add new routines to cp4utl to
+; create or append to an existing file, and to conditionally
+; advance buffers only if in memory. Remove edit pcc003 that
+; allows one more character after the xoff, since it didn't
+; really work very well and does not fit in well with the way
+; the buffer advancing routines are set up. If someone still
+; thinks this would be useful, it could be put back in with a
+; little more work.
+;
+; While testing this edit, I also noticed another bug that
+; the command parsing routines do not limit or check the
+; length of command lines or file specs, trashing what ever
+; comes after them. Currently because of where the fcb and
+; command buffer are located, this does not usually cause a
+; problem, but could if an extremely long line was typed in,
+; or in the future multiple fcbs defined elsewhere in memory
+; were used. Maybe this should be put on the bug list
+; somewhere.
+;
+;pcc013 8-Jan-85 vjc modules:cp4mit,cp4utl,cp4typ
+; Replace CLOSE command to cancel session logging to SET
+; LOGGING ON/OFF. This seems to fit in with the command
+; structure better. Default the log file to KERMIT.LOG
+; incase no previous LOG command. Logging is also enabled
+; by LOG command, as before.
+;
+; 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 'CPSUTL.ASM (31) 21-Mar-1991$'
+
+; 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 spacep
+
+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
+
+spacep: ani 7FH ;Turn off the parity bit.
+ jmp parret
+
+parret: pop b
+ pop h ;Restore HL.
+ ret
+;
+; Print the escape char.
+; called by: stat01, telnet, intchr
+
+escpr: lda escchr ;Get the escape char.
+escpr1: cpi ' ' ;Is it a control char?
+ jp escpr2
+ push psw ; save the character
+ lxi d,inms10 ;Output "Control-".
+ call prtstr
+ pop psw ; restore the character
+ 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, 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, user
+
+nout: mvi a,'0' ; fill tempx with zeros
+ call filltmp
+
+ 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
+ call shiftmp ; cycle temp registers
+ sta temp1 ; and save digit
+ mov e,a ;Output the digit.
+ lda nquiet ; are we to be quiet?
+ ana a
+ rnz ; yup, so return here rather than frm bdos
+ mvi c,conout
+ jmp bdos
+
+; prcrlf - print a CR/LF. (Saves no registers.) [Toad Hall]
+; prtstr - print string pointed to by DE (now in overlay section.. see prtstx)
+; called by: lots of places.
+prcrlf: lxi d,crlf ;Point to the CR/LF
+ jmp prtstr ; Use the one in the overlay
+
+; prtstx is funtionally the same as prtstr in the overaly but is here as
+; we may need a print a string routine in case the overlay is
+; either incorrect version or simply not present.
+prtstx: ; PRTSTR moved to overlay, but we do need the
+ ; same function for writing the sign-on
+ ; message and error message if the overlay
+ ;is not present. Thence prtstx
+ 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
+
+; Pause-it routine. Informs the user to press any key to continue
+; and then waits for a key input. Called by the any routine
+; with more than, say, 20 lines of output.
+
+pausit: lxi d,anymes ; ask user to press any key to continue
+ call prtstr
+pausi1: call ckcon ; see if a key typed
+ ana a
+ jz pausi1 ; loop until a key has been pressed.
+
+ 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
+
+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, get1xc (from xmt/transmit)
+
+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 rstdma
+ 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 rstdma ;[pcc012]
+ lda seccnt ; Get sector count again.
+ jmp inbuf2 ; Return a sector.
+
+; IN1CHR - get a single character from the file. Taken code from old
+; TRANSMIT routine.
+in1chr:
+ lda eoflag ;EOF encountered?
+ ora a
+ rnz ; Yes, finish.
+ lxi d,cmdbuf ; Use comnd buffer as line buffer.
+ lhld bufpnt ; Get current buffer pointer.
+ lda chrcnt ; Get current byte count
+ mov b,a ; in B
+in1ch1: dcr b ; Assume there's a character there
+ jp in1ch2 ; If there was, proceed.
+ call inbuf ; There wasn't. Try for another buffer.
+ jmp in1che ; End of file.
+ lhld bufpnt ; Got another buffer. Get new pointer in HL
+ lda chrcnt ; and new byte count
+ mov b,a ; in B
+;[MF]The modification below was made 30-Jan-1991 per report from
+;[MF]Lance Tagliapietra from the University of Wisconsin at Platteville
+;[MF]The following instruction should not be executed as the character counter
+;[MF]has already been decremented by INBUF.
+; dcr b ; we are reading in a character, so less one
+in1ch2: mov a,b ; save new count
+ sta chrcnt
+ mov a,m ; Get a character from disk buffer.
+ inx h
+ shld bufpnt ; save new pointer
+ ani 7FH ; Mask 7 bits.
+ jz in1ch1 ; Skip nulls.
+ ret ; character in a
+
+
+in1che: mvi a,0ffh ; dubious about this one...
+ sta eoflag
+ ret ; set end of file flag...?????
+
+; ;[pcc012]
+; appfil - Create or append to an existing file. File name is in FCB.
+; Non-skip return if could not be done. Skip return with file
+; open and bufpnt pointing to end of file.
+; called by logopn
+appfil: xra a ;[pcc012] zero out stuff for open
+ sta fcb+0CH ;[pcc012] extent
+ sta fcb+0EH ;[pcc012] Must be zero for MAKEF or OPENF.
+ sta fcb+20H ;[pcc012] Zero the current record.
+ mvi c,openf ;[pcc012] Try to open the file
+ lxi d,fcb ;[pcc012]
+ call bdos ;[pcc012]
+ cpi 0FFH ;[pcc012] Did we find it?
+ jz makfi1 ;[pcc012] If not, go create it
+ mvi c,cflsz ;[pcc012] Compute the file size
+ lxi d,fcb ;[pcc012]
+ call bdos ;[pcc012]
+ lhld fcb+21H ;[pcc012] random record pointer
+ mov a,h ;[pcc012] See if zero length file
+ ora l ;[pcc012]
+ jz makfi2 ;[pcc012] set up pointers if null file
+ dcx h ;[pcc012] backup to last record written
+ shld fcb+21H ;[pcc012] store rec ptr back
+ lhld bufadr ;[pcc012] get buffer address
+ xchg ;[pcc012] to DE
+ mvi c,setdma ;[pcc012] set dma address
+ call bdos ;[pcc012] for read
+ mvi c,rrand ;[pcc012] read the last block
+ lxi d,fcb ;[pcc012]
+ call bdos ;[pcc012]
+ ora a ;[pcc012] check results
+ jnz rstdma ;[pcc012] reset dma and return if error
+ lhld bufadr ;[pcc012] get address again
+ lxi d,bufsiz ;[pcc012] and and size
+ mvi a,'Z'-40H ;[pcc012] control-Z for comparison
+appcz: cmp m ;[pcc012] Is this the EOF?
+ jz appxit ;[pcc012] Jump if yes
+ inx h ;[pcc012] no, bump
+ dcr e ;[pcc012] and grind
+ jnz appcz ;[pcc012] until find or buffer empty
+appxit: shld bufpnt ;[pcc012] store buffer pointer
+ dad d ;[pcc012] compute next buffer adr
+ shld nxtbuf ;[pcc012] and store
+ mov a,e ;[pcc012] updated chr count
+ sta chrcnt ;[pcc012]
+ xra a ;[pcc012] reset sector count
+ sta seccnt ;[pcc012]
+ call rstdma ;[pcc012] reset normal dma
+ jmp rskp ;[pcc012] and give good return
+
+; 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.
+;[pcc012] here from appfil above if file does not exist
+makfi1: mvi c,makef ;[pcc012] 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.
+;[pcc012] also here from appfil if found zero length file
+makfi2: lhld bufadr ;[pcc012] 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.
+
+;[pcc012]
+; outadv - conditionally advance output buffer if disk write not needed.
+; preserves BC
+; skip return with with next output buffer set up
+; non-skip return if memory buffer full and must write to disk.
+; called by:logit
+
+outadv: push b ;[pcc012] save BC as advertised
+ lxi h,seccnt ;[pcc012] point to sectors buffered
+ inr m ;[pcc012] count this one
+ lda bufsec ;[pcc012] how many we can hold
+ cmp m ;[pcc012] check if full
+ jnz outbf2 ;[pcc012] continue if not
+ dcr m ;[pcc012] full, un-advance sector count
+ pop b ;[pcc012] restore bc
+ ret ;[pcc012] and give non-skip 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,logwrt
+
+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.
+;[pcc012] also here from outadv to advance buffer
+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)
+ jnz rstdma ;[pcc012] reset dma and take nonskip return if so
+ lxi h,seccnt
+ dcr m ; count down buffered sectors
+ jnz outmb2 ; loop if more saved
+ call rstdma ;[pcc012] restore normal dma
+ 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.
+
+
+; Reset DMA address to the default buffer
+; called from inbuf,appfil,outmbf
+rstdma: lxi d,buff ;[pcc012]
+ mvi c,setdma ;[pcc012]
+ jmp bdos ;[pcc012]
+
+; [8] Intercept BDOS calls to check for console input
+; This leads to simple trapping for input from disk rather than from the
+; keyboard, alowing commands to be stored in a TAKE file.
+; Printer is tested for readiness, and the second fuzzy clock is updated.
+bdos: ;call print ; print a character to the printer if needed
+ ;call clock ; update the clock
+ push psw ; we will need this register
+ lda takflg ; are we taking from a
+ ana a ; file or from command line
+ jz notake ; no, so do usual BDOS stuff
+ mov a,c ; get bdos function
+ cpi conin ; is it console in?
+ jz bd1in ; get a single character
+ cpi dconio ; direct console in or out?
+ jz bd1io ; test further for inpu or output
+ cpi rdstr ; read the console buffer?
+ jz bdcbuf ; then do it
+ cpi consta ; get the console status?
+ jz bdcst ; anything left in buffer?
+notake: pop psw ; else we have a kosher BDOS call
+ jmp 0005h ; Absolute address = BDOS entry point
+;
+bd1in: ; get a single character from take file
+ pop psw ; restore stack
+ call r1tchr ; read a single take character
+ ret ; and return. We dont expand tabs,
+ ; check for xon/off or backspaces.
+ ;Make sure the take file is error free?
+;
+bd1io: ;get or put a single character from/to console
+ mov a,e ; get e. If 0ffh then input else output
+ inr a ; if 0 then input
+ jnz notake ; its for output, so let notake restore stack
+ pop psw ; otherwise we do it
+ call r1tchr ; read a single take character
+ ret ; and return from out BDOS
+;
+bdcbuf: ; read a line of edited (?) input from the console
+ pop psw ; restore stack
+ inx d ; point to nc
+bdosc1: call r1tchr ; get a character
+ cpi cr ; if a cr then return
+ jz nomore
+ cpi lf ; ignore line feeds
+ jz bdosc1 ; so get another character
+ push psw ; we will want it later
+ mvi h,0
+ ldax d ; get nc, the no of characters in buffer
+ mov l,a ; now use as index from de+2
+ mvi h,0
+ inx d ; de is now de + 2
+ dad d
+ pop psw ; Told you we will want this
+ mov m,a
+ dcx d ; point again to nc
+ xchg ; make hl point to memory
+ inr m ; update pointer nc
+ xchg ; restore it
+ jmp bdosc1
+nomore: dcx d ; restore de to point to buffer
+ ret
+
+bdcst: ; get the console status. Returns a 00 if at eof
+ pop psw
+ push h ; now save de, hl for return
+ push d ;
+ lxi d,takdma ; make a point to next byte...
+ lhld takptr ; pointer from dma address. There will always
+ dad d ; be at least one byte, as the buffer is
+ mov a,m ; ...
+ pop d ; filled only if a read empties it.
+ pop h ; restore hl, de
+ cpi cntlz ; end of file?
+ mvi a,0ffh ; say there is
+ rnz ; if it is not a cntl z
+ jmp closet ; otherwise, close take file etc
+
+r1tchr: ; read a single character from the take file or command line
+ push h
+ push d ; save in case of return
+ lda takflg ; see if character is to come from file or line
+ ani 1 ; if bit zero set, from take file
+ jz r1lchr ; get character from the command line
+ lxi d,takdma
+ lhld takptr ; get next data byte
+ dad d
+ mov a,m
+ lhld takptr ; cos it's destroyed with dad
+ inx h
+ shld takptr ; update pointer
+ call p1tchr ; print it (so the user sees it)
+ push psw ; save the read data for a while
+ mov a,l ; if l = 0 then read another sector
+ ana a
+ jp r1tch1 ;[23] was jnz. jp => 128 byte sectors
+ call rnsect ;read next sector
+r1tch1: pop psw ; now, is this a cntl-z.. in whic case
+ pop d ; also these...
+ pop h
+ cpi lf ; skip if a line feed
+ jz r1tchr ;
+;[MF][31][30]Following lines commented out so semicolons are not considered
+;[MF][31][30]command separators and thus are considered part of the command
+;[MF][31][30]so commands like REMOTE DELETE *.*;* work correctly.
+; cpi semico ; see if its a semicolon
+; jnz r1tch2 ; no, ignore it
+; mvi a,cr ; else say its a cr (in case of command lines)
+r1tch2: cpi cntlz ; end of file??
+ rnz
+
+
+c1tchr: call closet ; close file etc, then
+ mvi a,cr ; fake a carriage return chr
+ ; ( => clears kermit comnd line)
+c1tch1: ret ; and hope that editing etc not required.
+
+r1lchr: ; read a single character from the command line
+ lxi d,cbuff ; point to buffer
+ lda cbptr ; get pointer for next character
+ mov l,a
+ lda cbuff ; get total number of characters there
+ cmp l ; ... less current character
+ jp r1lch1 ; if positive, we have more characters
+ lda takflg ; no more, so reset command line bit (bit 4)
+ ani 0efh
+ sta takflg
+ mvi a,cntlz ; fudge an end of file
+ push psw ; save for common exit (r1tch1)
+ jmp r1tch1
+;
+r1lch1: mov a,l ; get count back again
+ mvi h,0
+ dad d ; get offset to character
+ inr a
+ sta cbptr
+ mov a,m ; get next character
+ cpi semico ; if a semicolon, make it a carriage return
+ jnz r1lch2
+ mvi a,cr
+r1lch2: call p1tchr ; send a copy to the console
+ push psw ; save it for r1tch1
+ jmp r1tch1 ; common exit
+
+
+; rnsect - read the next take sector from disk to the take dma address
+; if there is no more then close the file too
+rnsect: push b
+ push d
+ push h ; save in case we need these later
+ mvi c,setdma
+ lxi d,takdma ; set a next read from disk
+ call bdos ; recursive...
+ mvi c,readf
+ lxi d,takfcb
+ call bdos
+ ana a
+ cnz closet ; if returned value not zero, assume eof
+ lxi h,0
+ shld takptr ; pointer restored
+ call rstdma ; reset the dma address for fussy routines (this one)
+ pop h
+ pop d
+ pop b
+ ret
+
+; closet - close the take file and set the take flag to 0 (ie no takes)
+;
+closet: lda takflg ; reset the take file bit (bit 0)
+ ani 0feh
+ sta takflg ; close the take file, and restore the flag
+ mvi c,closf
+ lxi d,takfcb
+ call bdos
+ call rstdma ; in case we did not do it above, reset dma
+ ret
+
+;
+; clock - is a 32 bit counter incremented every BDOS call. It serves as a
+; timer of sorts and allows a background clock to tick away..
+clock: push psw ; we need flags and hl
+ push h
+ lhld clkbit ; get the counter
+ inx h ;
+ shld clkbit
+ mov a,h
+ ora l ; do we need to update the next lot of clock bits?
+ jnz clockx
+ lhld clkbit+2 ; if carry up the top 16 bits
+ inx h
+ shld clkbit+2
+clockx: pop h
+ pop psw
+ ret
+
+; p1tchr - print a character in accumulator directly to the console
+; bypassing the bdos trap above.
+
+p1tchr: cpi lf ; if a lf ignore it
+ jz p1tchx
+ cpi cr ; ditto carriage returns
+ jz p1tchx
+ cpi cntlz ; control z
+ jz p1tchx ; then dont write it out
+ push psw ; we do not want to loose it, do we?
+ push b
+ push d
+ push h ; 'cos you never know what bdos does...
+ mov e,a
+ mvi c,conout ; direct console io
+ call 5 ; absolute address as we skip the trap
+ pop h
+ pop d
+ pop b ; ... and we need some of these regs.
+ pop psw ;
+p1tchx: ret ; and return
+
+
+
+; outprn - This routine sends charactes to the printer if the latter is ready,
+; or to a buffer if the printer is not ready. If the buffer is nearly
+; full, an XOFF is sent to the host, asking it to be quiet. The buffer
+; is emptied by a series of calls in the connect state only.
+; If the buffer is made nearly empty, then an XON is sent to the host.
+;
+outprn: mov a,e ; get the character to send back to a
+ sta prntmp ; we need all registers.
+ jmp outprx ; -testing-testing-testing- avoid buffer
+;
+outp0: call tstfree ; see how many spaces free
+ cpi 2 ; (free spaces in a on return)
+ jp outp1 ; enough free spaces, so keep going
+ call print ; else see if we can print summat
+ jmp outp0 ; and try again
+;
+outp1: cpi 4 ; common test - if three or less then send xoff
+ cm sndxoff
+outpr2: mov a,b ; inc ptr and check for wrap around
+ call wrapt
+ mov b,a ; input pointer to b
+ sta prnbuf+1 ; save the new pointer away
+ lxi h,prnbuf+2 ; point to first real data entry in buffer
+ call inchl ; add offset in a to hl
+ lda prntmp ; get th character to save away
+ mov m,a ; save the data away
+ ret
+
+; outprx - send character in a to the printer. (We have checked to see if
+; the printer is ready)
+; called by outprn, print
+outprx: mov e,a ; character has to be in e register
+ call outlpt ; send it to printer
+ ret ; assume we print it
+
+; TSTFREE - see how many free spacse there are in the buffer
+; - returns with free space in a, ip pointer in b, op pointer in c
+szecyc equ 127 ; 128 bytes in buffer (less for debugging)
+;
+tstfree:
+ lda prnbuf ; get output pointer
+ mov c,a ;.. to c
+ lda prnbuf+1 ; and input pointer ...
+ mov b,a ; ... to b
+;
+;
+; Now comes the tricky bit. We must establish whether there is less than
+; three characters left in the buffer. There are two conditions to test for
+; 1) the input pointer is a higher value than the output pointer
+; 2) the input pointer has been wrapped round and is less than the output pointer
+; ie
+; |-------|-------|---------------------------------------------|
+; Buffer |o/p ptr|i/p ptr| Buffer proper filling ---> |
+; |-------|-------|------|-------------|-----------|------------|
+; i/p2 o/p i/p1
+;
+; If ip = ip1 then if
+; (size of buffer - ip ptr + op ptr) < 3 send xoff
+; If ip = ip2 then if
+; (op ptr - input ptr ) < 3 send xoff
+;
+; First decide whice one applies
+
+ mov a,b ; get ip ptr
+ sub c ; see if op ptr > ip ptr (case 2)
+ jm outp2 ; yup, so do case two
+ mvi a,szecyc ; else do buffer - ip + op
+ sub b
+ add c
+ jmp outpx ; do common test
+outp2: mov a,c ; get op pointer
+ sub b ; less input pointer
+outpx: ret ; with free space in a
+;
+;
+;
+; print - get a character from the buffer and print it if the printer
+; is ready for it. If the buffer clears more than 3 spare characters
+; and an xoff has been sent, then send an xon again.
+print: push h ; save for rainy days
+ push d
+ push b
+ push psw ; .. as we may need flags etc....
+ lda initflg ; First check if the system has initialised
+ ana a
+ jz printx ; If system not set up then skip
+; nop
+; nop
+; nop ; debugging only...
+ call ckprtr ; check to see if printer is ready...
+ ana a ; not zero => ok
+ jz printx ; else skipit.
+; nop
+; nop
+; nop ; skip the jump for debugging
+ lxi h,prnbuf
+ mov a,m ; get input pointer
+ inx h ; test against output pointer
+ cmp m ; if = then buffer empty
+ jz printx ; so quit
+ dcx h ;pointer to output pointer
+ call wrapt ; check for wrap around
+ sta prnbuf ; save new pointer
+ inx h
+ inx h ;
+ call inchl ; add output pointer to hl
+ mov c,m ; get byte
+ lda hosths ; have we told host to be quiet?
+ cpi xoff ; if = xoff then we have
+ jnz print1 ; nope, so just print it..
+ push b ; save the character to print
+ call tstfree ; see how many free bytes in buffer
+ pop b
+ cpi 4 ; 3 characters left?
+ jz printx
+ push b
+ call sndxon ; send an xon to host and wake it up.
+ pop b
+print1: mov a,c ; we are gonna print a character, so ...
+ call outprx ; get it to a (as required by outprx) and print it
+printx:
+ pop psw
+ pop b
+ pop d
+ pop h ; restore regs.
+ ret
+
+;
+; Utilities for the cyclical buffer. Returns a 0ffh if printer ready,
+; else 0h. Called by outprn, print
+
+ckprtr:
+ call lptstat ; no registers saved
+; mvi a,0 ; FOR DEBUGGING PURPOSES
+; nop
+ ret
+;
+
+inchl: push psw ; we do maths through this register
+ add l
+ mov l,a
+ mvi a,0
+ adc h
+ mov h,a
+ pop psw ; hl = a + hl
+ ret
+
+; wrapt - checks the offset in a with the limits of the buffer.
+; returns next address or if wrap around then 0 (start of buffer)
+wrapt: push b
+ inr a
+ mov b,a ; save new a into b for now
+ mvi a,szecyc ; test for size of buffer
+ sub b
+ mov a,b
+ pop b ; restore bc regs again
+ rnz
+ xra a ; if wrap around, then reset pointer
+ ret ; return with next address pointer to in a
+
+; sndxoff - send an xoff to the host and save the xoff character in hosths
+; saves all regs. is called by logwrt, outprn
+sndxoff:
+ push psw
+ push b
+ push d
+ push h ; some calling routines may be sensitive...
+ lda floctl ; are we doing flow control?
+ ana a
+ jz sndxf ; no, so dont bother.
+ mvi a,xoff ;^S to stop the host while we write the buffer.
+ sta hosths ; save it so we know we have sent it
+ call setpar ; set correct parity...
+ mov e,a
+ call outmdm ; output it.
+ lxi d,ofsnt ; say we have sent an xoff
+ call prtstr
+sndxf: pop h
+ pop d
+ pop b
+ pop psw ; some routines touchy
+ ret
+ofsnt: db cr,lf,'[XOFF sent to host]',cr,lf,'$'
+
+; sndxon - send an xon to the host and clear the hosths flag. saves everything
+; called by logwrt, print
+sndxon: push psw
+ push b
+ push d
+ push h
+ lda floctl ; are we doing flow control?
+ ana a
+ jz sndxn
+ xra a
+ sta hosths ; no xoff to hos any more
+ mvi a,xon ;^Q to restart the host
+ call setpar ; set appropriate parity
+ mov e,a
+ call outmdm ; send it.
+ lxi d,onsnt
+ call prtstr ; say xon sent to host...
+sndxn: pop h
+ pop d
+ pop b
+ pop psw
+ ret ; shame we dont do a pushall/popall subroutine...
+onsnt: db cr,lf,'[XON sent to host]',cr,lf,'$'
+;
+; Routines to clear (or rather fill) TEMPnnn space with the data in A
+; and to shift it all along one (filltmp an shiftmp respectively)
+filltmp:
+ push b
+ push d
+ push h ; save all
+ lxi h,temp1
+ mvi b,10 ; ten locations to fill
+fillp: mov m,a
+ inx h
+ dcr b ; loop til all done
+ jnz fillp
+ pop h
+ pop d
+ pop b ; restore all
+ ret
+
+shiftmp:
+ push psw ; save all again
+ push b
+ push d
+ push h
+ lxi d,temp9
+ lxi h,temp10
+ mvi b,9 ; shift nine times
+shiftl: ldax d ; from tempx
+ mov m,a ; to tempx+1
+ dcx d
+ dcx h ; mover does not work as that increments
+ dcr b
+ jnz shiftl
+ pop h
+ pop d
+ pop b
+ pop psw
+ ret ; else all done
+
+; getun - get the user number to temp1 (lsd) and temp2 (msd)
+;
+getun: mvi a,0ffh ; tell nout to be quiet
+ sta nquiet
+ mvi c,usrcod
+ mvi e,0ffh ; get current user from bdos
+ call bdos
+ mov l,a ; put into hl
+ mvi h,0
+ call nout ; decimalise it (decimalise???)
+ xra a
+ sta nquiet ; let nout print again
+ ret
+
+; ckcon - Do a direct console IO (read) to see if there is any input
+; returns with a=0 (no input) or character (input received)
+; Assume that all regs may be destroy.
+ckcon: mvi e,0ffh ; direct console input
+ mvi c,dconio
+ call bdos
+ ret ; and return with wahtever returned in a
+
+
+
+;
+; subbc - Subtract the unsigned number in bc from the unsigned number
+; in HL with the answer in HL. Flags altered, all
+; other registers left intact.
+subbc: sta temp1 ; hl = hl - bc.. we need the accumulator
+ mov a,l
+ sub c
+ mov l,a
+ mov a,h
+ sbb b
+ mov h,a
+ lda temp1 ; restore loop counter but not flags
+ ret
+
+; P20LN - Routine to print a string at (DE) and count the number of
+; line feeds. Pause after 20 lines printed.
+p20ln: xra a ; clear the line counter
+ sta lincnt
+p20ln1: ldax d ; get character to print
+ inx d
+ cpi '$' ; if a dollar we have done
+ rz
+ push d
+ push psw ; save pointer and character to print
+ mov e,a
+ call outcon ; send character
+ pop psw
+ pop d ; restore pointers etc
+ cpi lf ; was that last character a line feed?
+ jnz p20ln1 ; no, so carry on
+ lda lincnt ; yup, so update counter
+ inr a
+ sta lincnt
+ cpi 20 ; 20 lines printed?
+ jnz p20ln1 ; not yet
+ push d ; we need DE
+; lxi d,anymes ; pause a while [MF]removed
+; call prtstr ; write the message [MF]
+;p20ln2: call ckcon ; wait for any input [MF]
+; ana a ;[MF]
+; jz p20ln2 ;[MF]
+ call pausit ;[MF] pause a while
+ pop d
+ jmp p20ln ; and continue
+
+
+; Little code to allow some expansion of code without changing
+; every futher address, only up to the end of this file.
+; TO BE REMOVED FOR RELEASE!
+
+; org ($+100h) AND 0FF00H
+
+; link to the data area (was part of CPSUTL.ASM)
+
+IF lasm
+ LINK CPSDAT
+ENDIF ;lasm