IF NOT lasm
.printx * CPXSB.ASM *
ENDIF	;NOT lasm
;       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.
;
;	This file created 16 July, 1987 by OBSchou from code submitted
;	by William Rose for the Micromint SB180 systems (as featured in
;	BYTE magazine, 1986).  This file has been modified to fit in with
;	Kermit-80 V4.08 etc.  His file KERSYS.ASM is a stripped down version
;	of CPXSYS.ASM (formerly CP4SYS.ASM).
;
; revision history:
; KERSYS.ASM - version 0.8 dated 13 Jul 87.
;
; Cutdown CP4SYS.ASM for SB-180/Ampro 230.
;
;
; While this is a single CPU version (to ease editing) the assembler
; conditionals have been kept to identify machine specific code.
;
; Note that the baud setting routine also sets parity, but this does not
; change the parity given by Kermit's 'stat' command.  I assume that the
; main body of the program does its own parity check.
;
; Revision history (last entry first)
;
; edit 2, 22 July by OBSchou to massage file to fit with CPXCOM.ASM.
;
; edit 1, 15 July, 1987 by OBSchou for William Rose who submitted
;	the code for Kermit-80 V 4.05.  Modified code as appropriate 
;	for 4.08 compatability.
;

delfac	EQU	150	; Delay factor in SB-180 input loop - a fudge

;
; Keep module name, edit number, and last revision date in memory.
;
;sysedt:	db	'KERSYS.ASM (03) 12-FEB-87 $'	; last SB-180 revision
;sysedt:	db	'KERSYS.ASM (04) 12-APR-87 $'	; Telecom Merlin added
;sysedt:	db	'KERSYS.ASM (5)  9-May-87 $'	; Minor tidying
;sysedt:	db	'KERSYS.ASM (6A) 17-Jun-87 $'	; BT Merlin M2215 only
;sysedt:	db	'KERSYS.ASM (7)  19-Jun-87 $'	; SB-180 only
;sysedt:	db	'KERSYS.ASM (8)  13-Jul-87 $'	; 6/9 MHz version
family:	db	'CPXSB.ASM (2)  22-Jul-87$'	; First entry for V4.08/9

;
; Assembly time message to let me know I'm building the right version.
;

IF sb180
.printx * Assembling Kermit-80 for Micromint SB-180 *
ENDIF


IF sb180
mnctrla	EQU	000H	;Modem control port - CNTLA0
mnctrlb	EQU	002H	;Modem control port - CNTLB0
mnstat	EQU	004H	;Modem status port - STAT0
mntxdat	EQU	006H	;Modem output port - TDR0
mnrddat	EQU	008H	;Modem input port - RDR0
output	EQU	002H	;Transmit data register empty mask - TDRE
input	EQU	080H	;Receive data register full mask - RDRF
z80	EQU	TRUE	;This one's an HD64180, but Z80 will do 
ENDIF


sysxin:		; continuation of system initialisation from sysinit

IF sb180
	lxi	h, porbuf	; park the original settings
	db	0EDh, 038h, mnctrla	; HD64180 code IN0 g,(m)
	mov	m, a
	inx	h
	db	0EDh, 038h, mnctrlb
	mov	m, a
	inx	h
	db	0EDh, 038h, mnstat
	mov	m, a
ENDIF
				; re-initialise for KERMIT
IF sb6
	mvi	h, 08h		; 0000$1001 - 9600 baud, (even) parity
	mvi	l, 08h		; 'speed' is two bytes
ENDIF

IF sb9
	mvi	h, 21h		; 0010$0001 - 9600 baud, (even) parity
	mvi	l, 21h		; 'speed' is two bytes
ENDIF

IF sb180
	shld	speed
	lxi	h, parind
	mvi	m, 8		; index for 8 bits, no parity, 2 stop
	call	setpor
ENDIF

	ret

porbuf:	ds	3		; original port settings
   
;
;	system-dependent KERMIT termination processing
;	If we've changed anything, this is our last chance to put it back.
;
sysexit:

IF sb180
	lxi	h, porbuf
	mov	a, m		; output parity
	db	0EDh, 039h, mnctrla	; HD64180 code OUT0 (m),g
	inx	h
	mov	a, m		; output baud rate
	db	0EDh, 039h, mnctrlb
	inx	h
	mov	a, m		; output to clear error flags 
	db	0EDh, 039h, mnstat
				; read twice to reset DCD0 ?
	db	0EDh, 038h, mnstat
	db	0EDh, 038h, mnstat
ENDIF

	ret

;
;	system-dependent processing for start of CONNECT command
;
syscon:
	ret

conmsg:		; Messages printed when entering transparent (CONNECT) mode:

	db	'$'

;
;	syscls - system-dependent close routine
;	called when exiting transparent session.
;
syscls:
	ret

;
;	sysinh - help for system-dependent special functions.
;	called in response to <escape>?, after listing all the
;	system-independent escape sequences.
;
sysinh:

IF sb180 
	lxi	d, inhlps
	call	prtstr
ENDIF

	ret

; Additional, system-dependent help for transparent mode
; (two-character escape sequences)
inhlps:

IF sb180
	db	cr, lf, 'V  Cycle port parameters'
ENDIF

	db	'$'			; string terminator

;
;	sysint - system dependent special functions
;	called when transparent escape character has been typed;
;	the second character of the sequence is in A (and in B).
;	returns:
;	non-skip: sequence has been processed
;	skip:	seqence was not recognized
;
sysint:
;	ani	137O		; convert lower case to upper, for testing...
				; does this work?
IF sb180
	cpi	'V'		; cycle port ?
	jz pcycl
	cpi	'v'
	jz pcycl
ENDIF

	jmp	rskp		; take skip return - command not recognised

; Actual commands

IF sb180
pcycl:
	lxi	h, parind	; increment parval, modulo 12
	mov	a, m
	adi	1
	cpi	13
	jnz	pcy1
	mvi	a, 1
pcy1:	mov	m, a		; update the storage
				; get index of name in parstr
	ora	a		; clear flags
	dcr	a
	rlc
	rlc
	mov	c, a
	mvi	b, 0
	lxi	h, parstr
	inx	h
	dad	b
	push	h
	lxi	d, cgmsg1
	call	prtstr
	pop	d
	call	prtstr
	lxi	d, cgmsg2
	call	prtstr
	call	setpor		; reset the port

	ret

cgmsg1:	db	'<$'
cgmsg2: db	'>$'
ENDIF

	ret

;
;	Delay routine.  Called with time (hundredths of seconds) in A.
;	The inner loop delays 1001 T-states, assuming no wait states are
;	inserted; this is repeated CPUSPD times, for a total delay of just
;	over 0.01 second. (CPUSPD should be set to the system clock rate,
;	in units of 100KHz: for an unmodified Kaypro II, that's 25 for
;	2.5 MHz.  Some enterprising soul could determine whether or not the
;	Kaypro actually inserts a wait state on instruction fetch (a common
;	practice); if so, the magic number at delay2 needs to be decreased.
;	(We also neglect to consider time spent at interrupt level).
;
;	called by: sendbr
;	destroys BC
;
;delay:	mvi	c,cpuspd	; Number of times to wait 1000 T-states to
;				;  make .01 second delay
;delay2:	mvi	b,70		; Number of times to execute inner loop to
;				;  make 1000 T-state delay
;delay3:	dcr	b		; 4 T-states (* 70 * cpuspd)
;	jnz	delay3		; 10 T-states (* 70 * cpuspd)
;	dcr	c		; 4 T-states (* cpuspd)
;	jnz	delay2		; 10 T-states (* cpuspd)
;				; total delay: ((14 * 70) + 14) * cpuspd
;				;  = 1001 * cpuspd
;	dcr	a		; 4 T-states
;	jnz	delay		; 10 T-states
;
;	ret			; grand total: ((1001 * cpuspd) + 14) * a

;
;	sysflt - system-dependent filter
;	called with character in E.
;	if this character should not be printed, return with A = zero.
;	preserves bc, de, hl.
;	note: <xon>,<xoff>,<del>, and <nul> are always discarded.
;
sysflt:
	mov	a,e		; get character for testing
	ret

;
;	mdmflt - modem filter
;	called with character to be sent to printer in E
;	with parity set as appropriate.
;	return with accumulator = 0 do do nothing,
;				<> 0 to send char in E.
mdmflt:
	mov	a,e		; get character to test
	ret

;
;	prtflt - printer filter
;	called with character to be sent to printer in E
;	returns with a = 0 to do nothing
;		     a <> 0 to print it.
;
;	this routine for those printer that automatically insert
;	a lf on cr, or cr for lf.  Should this be shifted to 
;	the system indep. stuff, in say 4.06?
;
prtflt:
	mov	a,e		; get character to test

IF FALSE			; strip out lf from printer stream
	ani	7fh		; make sure it is parity less
	cpi	lf		; is it a line feed?
	rnz			; no, print it
;	xra	a		; yes, don't.
	
ENDIF

	ret

;
; system-dependent processing for BYE command.
;
sysbye:
	ret

;
;	This is the system-dependent command to change the baud rate.
;	DE contains the two-byte value from the baud rate table; both
;	bytes of this value are also stored in 'speed'.
;
sysspd:

IF sb180
	lxi	d, prtmsg	; ask for variables
	call	prtstr

	lxi	d, tbuf		; get suitable string
	mvi	c, 10
	call	bdos

	lxi	h, tbuf1
	mov	a, m
	ora	a
	jz	setpor		; leave unchanged if string zero length

	cpi	3		; check given length
	jnz	spd1		; error - wrong length
	inx	h
	inx	h
	mov	a, m
	ani	137O		; convert parity code to upper case
	mov	m, a

	lxi	d, tbuf1	; get index of given parameter
	lxi	h, parstr
	call	sposn
	ora	a
	jnz	spd2		; or fall through if error
spd1:	lxi	d, invmsg	; invalid input - try again
	call	prtstr
	jmp	sysspd

spd2:	adi	3		; get index to parval table
	rrc			; by dividing by 4 
	rrc
	ani	15		; mask out high bits
	lxi	h, parind
	mov	m, a		; and store it
	call	setpor		; set up port iaw index and speed bytes

	ret

prtmsg:	db	cr,lf,'Enter bit/char, parity, and stop bits required.'
	db	cr,lf,'(Bit 7/8   Parity N/O/E   Stop 1/2  - CR same) : $'
invmsg: db	cr,lf,'Invalid parameters$'

parind:	db	8		; default <8N2> index
parstr:	db	48,'7N1$7N2$7O1$7O2$7E1$7E2$'
	db	   '8N1$8N2$8O1$8O2$8E1$8E2$'
parval:	db	0,1,16+2,16+3,2,3
	db	4,5,16+6,16+7,6,7
tbuf	db	6
tbuf1	db	3,'8N2','$$$$'

;
; Set up the port using the table index in parind and the speed byte
;
setpor:
	lxi	h, parind
	mov	a, m
	dcr	a
	lxi	h, parval	; table base
	mvi	b, 0
	mov	c, a
	dad	b		; HL points at parameter value
	mov	a, m
	mov	b, a		; park parval

	ani	16		; the parity switch bit
	lxi	h, speed
	add	m		; this is now the baud rate byte
	mov	c, a		; park it

	mov	a, b		; sort out the parameter byte
	ani	7		; b/p/s only wanted
	adi	96		; RE, TE enable
	db	0EDh,039h,mnctrla	; output parity etc.
	mov	a, c
	db	0EDh,039h,mnctrlb	; output baud rate
	mvi	a, 0
	db	0EDh,039h,mnstat	; clear status
	db	0EDh,038h,mnstat	; read twice to reset DCD0
	db	0EDh,038h,mnstat
	ret

;
; Find substring position - Leventhal page 293, modified
; enter with subtring in DE and string in HL
; returns index in A or 0 for failure
;
sposn:
	mov	a, m		; exit if either string length zero
	ora 	a
	jz	notfnd
	sta	slen
	mov	b, a
	inx	h
	shld	string
	xchg
	mov	a, m
	ora 	a
	jz	notfnd
	sta	sublen
	mov	c, a
	inx	h
	shld	substg
	mov	a, b

; no of searches = stringlen - substrlen + 1
; if substr longer than string quit immediately

	sub	c
	jc	notfnd
	inr	a
	mov	c, a
	xra	a
	sta	index

; search until remaining string shorter than substring

slp1:	lxi	h, index
	inr	m
	lda	sublen
	mov	b, a
	lhld	substg
	xchg
	lhld	string

; try to match substring starting at index

cmplp:	ldax	d
	cmp	m
	jnz	slp2
	dcr	b
	jz	found
	inx	h
	inx	d
	jmp	cmplp

; arrive here if match fails

slp2:	dcr	c
	jz	notfnd
	lhld	string
	inx	h
	shld	string
	jmp	slp1

; found, return index

found:	lda	index
	ret

; not found, return zero

notfnd:	sub	a
	ret

string:	ds	2
substg:	ds	2
slen:	ds	1
sublen:	ds	1
index:	ds	1
ENDIF

	ret

;
;	Speed tables
; (Note that speed tables MUST be in alphabetical order for later
; lookup procedures, and must begin with a value showing the total
; number of entries.  The speed help tables are just for us poor
; humans.
;
; 	db	string length, string, divisor (2 bytes or 1 word, ab)
;		the data byte a is return in A and E, and b in D
;		only byte 'a' is the key for the table

IF sb6
spdtbl:	db	9			; 9 entries
	db	04h,'1200$',	0Bh,0Bh
	db	03h,'150$',	0Eh,0Eh
	db	05h,'19200$',	01h,01h
	db	04h,'2400$',	0Ah,0Ah
	db	03h,'300$',	0Dh,0Dh
	db	05h,'38400$',	00h,00h
	db	04h,'4800$',	09h,09h
	db	03h,'600$',	0Ch,0Ch
	db	04h,'9600$',	08h,08h

sphtbl: db	cr,lf
	db '   150  300  600  1200  2400  4800  9600  19200  38400$'
ENDIF

IF sb9
spdtbl:	db	7			; 7 entries
	db	04h,'1200$',	24h,24h
	db	05h,'19200$',	20h,20h
	db	04h,'2400$',	23h,23h
	db	03h,'300$',	26h,26h
	db	04h,'4800$',	22h,22h
	db	03h,'600$',	25h,25h
	db	04h,'9600$',	21h,21h

sphtbl: db	cr,lf
	db '   300  600  1200  2400  4800  9600  19200$'
ENDIF


;
;	This is the system-dependent SET PORT command.
;	HL contains the argument from the command table.
;
sysprt:
	ret

IF sb180
prttbl EQU	0		; SET PORT is not supported
prhtbl EQU	0
ENDIF

;
;	selmdm - select modem port
;	selcon - select console port
;	selmdm is called before using inpmdm or outmdm;
;	selcon is called before using inpcon or outcon.
;	For iobyt systems, diddle the I/O byte to select console or comm port;
;	For the rest, does nothing.
;	preserves bc, de, hl.
;
selmdm:
	ret

selcon:
	ret

;
;	Get character from console, or return zero.
;	result is returned in A.  destroys bc, de, hl.
;
inpcon:
	mvi	c,dconio	;Direct console I/O BDOS call.
	mvi	e,0FFH		;Input.
	call	BDOS

	ret

;
;	Output character in E to the console.
;	destroys bc, de, hl
;
outcon:
	mvi	c,dconio	;Console output bdos call.
	call	bdos		;Output the char to the console.

	ret

;
;	outmdm - output a char from E to the modem.
;		the parity bit has been set as necessary.
;	returns nonskip; bc, de, hl preserved.
;
outmdm:

IF sb180
	db	0EDh,038h,mnstat
	ani	output		; check status
	jz	outmdm		; wait until port is available
	mov	a, e
	db	0EDh,039h,mntxdat	; transmit
ENDIF

	ret

;
;	get character from modem; return zero if none available.
;	for IOBYT systems, the modem port has already been selected.
;	destroys bc, de, hl.
;
inpmdm:

IF sb180
	lxi	h, delfac	; loops to give delay
inpm1:	db	0EDh,038h,mnstat
	ani	input		; check status	
	jz	inpm2
	db	0EDh,038h,mnrddat	; get a byte
	ret

inpm2:	dcx	h		; no data
	mov	h, a
	ora	l
	jnz	inpm1		; still tries left
	ret			; with zero in A
ENDIF

	ret

;
;	flsmdm - flush comm line.
;	Modem is selected.
;	Currently, just gets characters until none are available.
;
flsmdm:
	call	inpmdm		; Try to get a character
	ora	a		; Got one?
	jnz	flsmdm		; If so, try for another
	ret			; Receiver is drained.  Return.

;

;
;       lptstat - get the printer status. Return a=0 if ok, or 0ffh if not.
lptstat:
IF iobyte       ;[33]
        call    bprtst          ; get status
ENDIF   ;iobyte[33]
IF NOT iobyte   ;[33]
        xra     a               ; assume it is ok.. this may not be necessary
ENDIF   ;iobyte [33]
        ret
;
;	outlpt - output character in E to printer
;	console is selected.
;	preserves de.
;
outlpt:
	push	d		; save DE in either case
	call	prtflt		; go through printer filter [30]
	ana	a		; if A = 0 do nothing,
	jz	outlp1		; if a=0 do nothing

outlp1:	pop	d		; restore saved register pair
	ret

; delchr - make delete look like a backspace.  Unless delete is a printing
;	character, we just need to print a backspace. (we'll output clrspc
;	afterwards)
delchr:

	mvi	e,bs		;get a backspace
	jmp	outcon

; erase the character at the current cursor position
clrspc:	mvi	e,' '
	call	outcon
	mvi	e,bs		;get a backspace
	jmp	outcon

; erase the current line
clrlin:	lxi	d,eralin
	jmp	prtstr

; erase the whole screen, and go home. preserves b (but not c)
clrtop:	lxi	d,erascr
	jmp	prtstr

IF sb180
sysver:	db	'MicroMint SB 180 '
ENDIF

IF sb6
	db	' (6 MHz)'
ENDIF

IF sb9
	db	' (9 MHz)'
ENDIF
	db	'$'

IF lasm
LINK CPXVDU.ASM		; get terminal defs etc
ENDIF	;lasm
