; * * * * * * * * * * * * * * *  version 2.9  * * * * * * * * * * * * * * *
; [36]	Add calls to support Concurrent CP/M.
; [34]	Make BREAK be correct length (250 ms).
; * * * * * * * * * * * * * * *  version 2.7  * * * * * * * * * * * * * * *
; [30d] Add SET PORT command, currently unimplemented.
; [30c] Isolate all machine dependencies in KERIO.
; [30a] Add keyboard DEL key alteration for APC
;	RonB, 04/18/84
; * * * * * * * * * * * * * * *  version 2.6  * * * * * * * * * * * * * * *
; [28e] Switch to local stack on interrupts.
;	RonB, 03/28/84
; * * * * * * * * * * * * * * *  version 2.4  * * * * * * * * * * * * * * *
; [20b] Add PRTBRK to send break & set correct clock rate for NEC.
; [20d] Add a pseudo time-out to PRTOUT so it doesn't loop forever.
;	RonB,03/02/84
; [19a] Add XON/XOFF type flow control
; [19b]	Clear screen and beginning and end of program.
; [19e]	Add PRTBRK to send break to port (Rainbow only)
; [19g]	Put in EQU for clock rate for timing loops.
;	Rg, 2/84 	<Oc.Garland%CU20B@Columbia-20>
; * * * * * * * * * * * * * * *  version 2.3  * * * * * * * * * * * * * * *
;  [par] Added calls to set parity, strip parity on input if
;        other than none parity is called for.
;	 JD, 2/84
; * * * * * * * * * * * * * * *  version 2.2  * * * * * * * * * * * * * * *
;  [2]	Add a de-initialization routine for the serial port, to restore
;	changed interrupt vectors, etc.
;	RonB,12/23/83
;  [1]	Add I/O support for the NEC Advanced Personal Computer
;	RonB,12/23/83
; * * * * * * * * * * * * * * *  version 2.0  * * * * * * * * * * * * * * *

; This module contains all the low level communications port I/O
; routines.

; Here are the I/O routines for the NEC APC.

	CSEG $

; Clock rate *10 for timing loops	;[19g]
clckrt	equ	49		;[19g]  4.9 Mhz			;[20b]

; Interrupt vector locations, in data segment 0

mnioff	equ	84h		;sio interrupt offset
mniseg	equ	86h		;sio interrupt segment

; 8259 Interrupt controller (master)

iccmd	equ	20h		;interrupt command register
icmask	equ	22h		;interrupt mask register

; 8259 commands and masks

icEOI	equ	20h		;end of interrupt (command)
ictmof	equ	08h		;disable timer (mask)
icmnof	equ	02h		;disable RS232 (mask)

; 8253-5 Interval Timer

tmdata	equ	2bh		;baud set (chan 1)
tmcmd	equ	2fh		;baud timer command port

; 8253 Timer commands

tmch1	equ	76h		;select & init timer channel 1

; 8251A USART controller

mndata	equ	30h		;    data port
mnsts1	equ	32h		;in  status port
mnsts2	equ	34h		;in  nec special status port
mncmd	equ	32h		;out command port
mnmsk	equ	34h		;out interrupt mask port
mntdc	equ	36h		;out transmit disable port

; 8251 status port 1 bits

mninp	equ	02h		;receive ready value
mnout	equ	01h		;send ready value
mndsr	equ	80h		;data set ready

; 8251 status port 2 bits

mncts	equ	04h		;clear to send

; 8251 initialization instructions

;	command instructions

ctxe	equ	01h		;transmit enable
cdtr	equ	02h		;dtr signal high
crxe	equ	04h		;receive enable
cbrk	equ	08h		;send break
cerr	equ	10h		;error reset
crts	equ	20h		;rts signal high
cmode	equ	40h		;reset - go to mode instruction format
chunt	equ	80h		;hunt for sync characters

;	mode instructions

m1x	equ	01h		;baud rate factor: 1x
m16x	equ	02h		;                  16x
m64x	equ	03h		;                  64x
m5d	equ	00h		;data bits: 5
m6d	equ	04h		;           6
m7d	equ	08h		;           7
m8d	equ	0Ch		;           8
mpn	equ	00h		;parity: none
mpo	equ	10h		;        odd
mpe	equ	30h		;        even
m1s	equ	40h		;stop bits: 1
m15s	equ	80h		;           1.5
m2s	equ	0C0h		;           2

; 8251 interrupt mask port bits

txmsk	equ	01h		;disable transmit complete interrupt
rxmsk	equ	02h		;disable receive complete interrupt
tbemsk	equ	04h		;disable transmit buffer empty interrupt

outlmt	EQU	1000H		;Number of times to check output status
				; before giving up on send.		;[20d]

; dispatch:	Under Concurrent CP/M, releases the processor for other
;		use when waiting for either the receive or the send status
;		routines to indicate success.

dispatch:
	push	ax
	push	bx
	push	cx
	mov	cl,8Eh			; P-Dispatch
	int	224
	pop	cx
	pop	bx
	pop	ax
	ret


; Test if port is ready to send next char.  Returns RSKP if ready.
; Trashes dx.

outwt:	cmp	floctl, floxon	;are we doing flow-control?	[19a] start
	jne	outwt1		;no - go on
	cmp	xofrcv, true	;are we being held?
	je	outwt3		;yes - return status not ready
outwt1:	push	ax
	mov	dx,mnsts1
	in	al,dx
	and	al,mndsr+mnout
	sub	al,mndsr+mnout
	jnz	outwt2
	mov	dx,mnsts2
	in	al,dx
	and	al,mncts
	jnz	outwt4
outwt2:	pop	ax
outwt3:	call	dispatch	;let other CCPM processes have a chance	;[36]
	 ret
outwt4:	pop	ax
	jmp	rskp


; Output data to port. Trashes DX and prints char in AL.

outchr:	mov	dx,mndata
	out	dx,al
	ret


; Output the character in AL, checking first to make sure the port is clear.

prtout:	call	dopar		;[par] set parity
	push	dx
	push	cx						;[20d] begin
	mov	cx,outlmt
prtou2:	call	outwt		;Wait until the port is ready
	 loop	prtou2		; or too much time has passed.
	 nop
	call	outchr		;Output it.
	pop	cx						;[20d] end
	pop	dx
	ret


; Test if data is available from port.

instat:	cmp	mnchrn,0	;Any chars in the buffer?
	jne	inst2
	call	dispatch	;let other CCPM processes have a chance	;[36]
	 ret
inst2:	jmp	rskp


; Input data from port.  Preserves all registers and returns char in
; AL.  Gets the char from the ring buffer.  Assumes a char is
; already there.

inchr:	push	bx
	cli			;Disable interrupts while were are playing.
	dec	mnchrn		;Decrement the number of chars in the buffer.
	mov	bx,mnchop	;Get the pointer into the buffer.
	inc	bx		;Increment to the next char.
	cmp	bx,offset mnchrs+mnchnd ;Past the end?
	jb	inchr2
	mov	bx, offset mnchrs ;If so wrap around to the start.
inchr2:	mov	mnchop,bx	;Save the updated pointer.
	mov	al,[bx]		;Get the character.
	sti			;All done, we can restore interrupts.
	pop	bx
	cmp	parflg,parnon	;[par] no parity?
	je	inchr3		;[par] yup, don't bother stripping
	and	al,7fh		;[par] checking parity, strip off
inchr3:	cmp	floctl, floxon	;do flow-control?		[19a] start
	je	inchr4		;If yes jump
	ret
inchr4:	cmp	xofsnt, true	;Have we sent an XOFF
	je	inchr5		;Jump if yes
	ret
inchr5:	cmp	mnchrn, mntrg1	;Under the low trigger point?
	jb	inchr6		;yes - jump
	ret
inchr6:	push	ax		;save current character
	mov	al, xon
	call	prtout		;send an XON
	mov	xofsnt, false	;turn off the flag
	pop	ax		;get back character
	ret			;				[19a] end



mnax	dw	0		;Storage in CSEG		;[28e] begin
mnsp	dw	0		;  for use by interrupt handler
mnsseg	dw	0
mndseg	dw	0

; This routine handles the interrupts on input.

mnint:	cli
	mov	cs:mnax, ax	;Save interrupt stack location.
	mov	ax, sp
	mov	cs:mnsp, ax
	mov	ax, ss
	mov	cs:mnsseg, ax
	mov	ax, cs:mndseg	;Switch to our internal stack.
	mov	ss, ax
	mov	sp, offset mnstk
	push	ds		;Save all registers.
	push	es
	push	bp
	push	di
	push	si
	push	dx
	push	cx
	push	bx
	mov	ds, ax		;Get our data segment address.
	call	mnproc		;Process the character.
	mov	dx, iccmd
	mov	al, icEOI	;signal end of interrupt to controller
	out	dx, al
	pop	bx		;Restore all registers.
	pop	cx
	pop	dx
	pop	si
	pop	di
	pop	bp
	pop	es
	pop	ds
	mov	ax, cs:mnsp	;Restore the original stack.
	mov	sp, ax
	mov	ax, cs:mnsseg
	mov	ss, ax
	mov	ax, cs:mnax
	iret			;Return from the interrupt.	;[28e] end


; This routine (called by MNINT) gets a char from the serial port
; and puts it in the ring buffer.

mnproc:	mov	dx,mnsts1
	in	al,dx		;Get the port status.
	and	al,mninp	;Is a character waiting?
	jnz	mnpro2		;   Yes, go take care of it.
	 ret			;   No, just a false alarm.

mnpro2:	mov	dx,mndata
	in	al,dx		;Read the char.
	cmp	floctl, floxon	;are we doing flow-control ?	[19a] start
	jne	mnpr2b		;no - go on
	cmp	al, xoff	;is it an XOFF?
	jne	mnpr2a		;no - go on
	mov	xofrcv, true	;set the flag
	ret
mnpr2a:	cmp	al, xon		;an XON?
	jne	mnpr2b		;no
	mov	xofrcv, false	;clear the flag
	ret			;				[19a] end
mnpr2b:	cmp	mnchrn,mnchnd	;Is the buffer full?
	je	mnperr		;If so, take care of the error.
	inc	mnchrn		;Increment the character count.
	mov	bx,mnchip	;Get the buffer input pointer.
	inc	bx		;Increment it.
	cmp	bx,offset mnchrs+mnchnd ;Past the end?
	jb	mnpro3
	mov	bx, offset mnchrs ;Yes, point to the start again.
mnpro3:	mov	mnchip,bx	;Save the pointer.
	mov	[bx],al		;Put the character in the buffer.
	cmp	floctl, floxon	;do flow-control?		[19a] start
	je	mnpro4		;If yes jump
	ret
mnpro4:	cmp	xofsnt, true	;Have we sent an XOFF
	jnz	mnpro5
	ret			;return if we have
mnpro5:	cmp	mnchrn, mntrg2	;Past the High trigger point?
	ja	mnpro6		;yes - jump
	ret
mnpro6:	mov	al, xoff
	call	prtout		;send an XOFF
	mov	xofsnt, true	;set the flag
	ret			;				[19a] End

mnperr:	ret			;Just return on an error for now.


; prtbrk - send a break		;				[20b] start

prtbrk:				;
	mov	dx,mncmd	;break goes to command port
	mov	al,cbrk+crts+cerr+crxe+cdtr+ctxe ;add break to normal command
	out	dx,al
	mov	ax, 275		;.. for 275 millisec's		[34]
	call	mswait		;				[34]
	mov	al,crts+cerr+crxe+cdtr+ctxe ;RTS & DTR high, Rx & Tx enabled
	out	dx,al		;return to normal setting
	ret			;				 [19e] end

mswait:				;				[34] start
	mov	cx,5*clckrt	; inner loop count for 1 millisec.
mswai1:
	sub	cx,1		;** inner loop takes 20 clock cycles
	jnz	mswai1		;**
	dec	ax		; outer loop counter
	jnz	mswait		; wait another millisecond
	ret			;				[34] end


; serini - This routine initializes all devices that need it.
;	   Called at the start of the program.

serini:	cmp	mninit,0FFh	; must only do this initialization once
	je	serin2
	mov	mninit,0FFh

	push	es

; Make DEL key return a 7F code rather than the 18 (control-X)	;[30a] begin
; that it ordinarily does.  This involves modifying a key
; conversion table in the BIOS.  Version 1.107 and later
; contain a pointer to this table at 40:256C, but earlier
; versions which do not are still in common use, so I will
; try to find it with a direct search.

	cld
	mov	ax, 40h		;BIOS segment
	mov	es, ax
	mov	di, 2500h	;Start of BIOS
	mov	al, 0FCh	;Key gives this value
	mov	ah, 18h		;Translated to this value
	mov	cx, -1
serina:	repnz	scasb		;Look for the key value
	or	cx, cx		;Zero if search failed
	jz	serinb		; If so, do nothing
	cmp	es:[di], ah	;Translation value next?
	jne	serina		; If not, keep looking
	mov	kbpat, di	;Save the location we're patching.
	mov	al, 7Fh		;Insert a DEL character
	mov	es:[di], al
serinb:				;...and continue	;[30a] end


	mov	dx,icmask
	in	al,dx		;get current interrupt mask
	mov	mnxmsk,al	;save it for restore

;	NEC recommends that the timer be turned off during interrupt-driven
;	serial I/O, but this disables the clock and keyboard repeat.  I have
;	not had any bad results from leaving it enabled.  I will leave the
;	disabling code here in case something develops. -- RonB

;	or	al,ictmof+icmnof;mask off timer and sio interrupts
	or	al,icmnof	;mask off sio interrupt
	out	dx,al

	mov	ax,ds		;save data segment in cseg
	mov	cs:mndseg,ax	;   for use by the interrupt handler

	mov	ax,0		;point to zero page to replace
	mov	es,ax		;the sio interrupt vector
	mov	ax,es:.mniseg	;after first saving the current vector
	mov	mnxseg,ax
	mov	ax,es:.mnioff
	mov	mnxoff,ax
	cli
	mov	ax,cs
	mov	es:.mniseg,ax
	mov	ax,offset mnint
	mov	es:.mnioff,ax
	sti

	call	stmode		;set mode & baud to defaults
	call	stbaud

	mov	dx,mntdc
	mov	al,00h		;enable transmission of data
	out	dx,al

	mov	dx,mndata	;dummy read to clear buffer
	in	al,dx

	mov	dx,mnmsk
	mov	al,txmsk+tbemsk	;set interrupt mask (enable read int)
	out	dx,al

	mov	dx,icmask
	in	al,dx		;enable sio interrupts
	and	al,not icmnof
	out	dx,al

	pop	es

serin2:	ret


; serfin - this routine is used to "undo" what serini has done, called
;	   just before exiting back to cp/m.

serfin:
	cmp	mninit,0FFh	;check if initialization has been done
	jne	serfn2		;if not, don't de-initialize
	mov	mninit,0

	push	es

; Unpatch the keyboard conversion table				;[30a] begin
; Restore control-X value for DEL key from our 7F value.

	les	di, dword ptr kbpat		;Get the patch location
	or	di, di		;Did we patch it at all?
	jz	serfia		; If not, skip this
	mov	kbpat, 0	;Show no longer patched
	mov	al, 18h		;Restore control-X value
	mov	es:[di], al
serfia:				;...and continue	;[30a] end

	cli
	mov	dx,icmask
	mov	al,mnxmsk	;restore the old interrupt mask
	out	dx,al
	mov	ax,0
	mov	es,ax
	mov	ax,mnxseg	;restore sio interrupt vector
	mov	es:.mniseg,ax
	mov	ax,mnxoff
	mov	es:.mnioff,ax
	sti

	pop	es

serfn2:	ret


; This routine clears the serial port input buffer.  It is called to
; clear out excess NAKs that can result from server mode operation.

cfibf:	mov	mnchrn, 0	;Say no characters in the buffer.
	mov	mnchip, OFFSET mnchrs-1+mnchnd ;Reset input pointer.
	mov	mnchop, OFFSET mnchrs-1+mnchnd ;Reset output pointer.
	ret

; set the parity, number of data bits, and number of stop bits

stmode:	mov	dx,mncmd
	mov	al,0		;recommended reset procedure:
	out	dx,al		;three 0's followed by a cmode
	mov	al,0
	out	dx,al
	mov	al,0
	out	dx,al
	mov	al,cmode	;enable mode setting
	out	dx,al
	mov	al,m1s		;1 stop, no parity, 8 data, 16x baud
	add	al,mpn		;Note: these adds are distinct to
	add	al,m8d		;      allow the 8251 time to reset
	add	al,m16x
	out	dx,al
	mov	al,crts+cerr+crxe+cdtr+ctxe ;RTS & DTR high, Rx & Tx enabled
	out	dx,al
	ret


; set the baud rate

stbaud:	mov	al,mnbaud	;get the baud rate information
	cmp	al,12		;check for valid range (0-12)
	ja	stb02
	mov	bx,offset baudtb;get address of baud rate table
	add	al,al		;compute word offset
	mov	ah,0
	add	bx,ax
	mov	dx,tmcmd
	mov	al,tmch1	;select timer channel 1
	out	dx,al
	mov	dx,tmdata
	mov	ax,[bx]		;get value
	out	dx,al		;output low byte
	mov	al,ah
	out	dx,al		;output high byte
stb02:	ret

	dseg $

;	Serial port default parameters

mnbaud	db	6		;300 baud

;	Interval Timer values (assumes 16x baud rate mode)

baudtb	dw	0C00h		;50 baud	0
	dw	0800h		;75 baud	1
	dw	0600h		;100 baud	2
	dw	0574h		;110 baud	3
	dw	0400h		;150 baud	4
	dw	0300h		;200 baud	5
	dw	0200h		;300 baud	6
	dw	0100h		;600 baud	7
	dw	0080h		;1200 baud	8
	dw	0040h		;2400 baud	9
	dw	0020h		;4800 baud	10
	dw	0010h		;9600 baud	11
	dw	0008h		;19200 baud	12

mninit	db	0		;set to 0FFh if initialization has been done
mnxmsk 	db	0		;8259 interrupt mask storage
mnxseg	dw	0		;system sio interrupt vector
mnxoff	dw	0

mnchnd	equ	512		;Size of circular buffer.
mnchrs	rb	mnchnd		;Circular character buffer for input.
mnchip	dw	mnchrs-1+mnchnd	;Input pointer into character buffer.
mnchop	dw	mnchrs-1+mnchnd	;Output pointer into character buffer.
mnchrn	dw	0		;Number of chars in the buffer.

mntrg1	equ	128		;[19a] Low trigger point for Auto XON/XOFF
mntrg2	equ	384		;[19a] High trigger point for Auto XON/XOFF

floctl	db	1		;[19a] If floctl=floxon do Auto XON/XOFF logic
xofsnt	db	0		;[19a] set if XOFF was sent
xofrcv	db	0		;[19a] set if XOFF was recieved

	rw	32		;Interrupt stack		;[28e]
mnstk	dw	0		;bottom of stack		;[28e]

kbpat	dw	0000h,0040h	;patch location for DEL key	;[30a]


	CSEG $

; The following routines do the SET and SHOW for the machine dependent
; features of Kermit.  At present there are only two:  baud rate setting
; and port selection.

;	This is the SET BAUD rate subcommand

bdset:	mov	dx, offset bdtab
	mov	bx, offset bdhlp
	mov	ah, cmkey
	call	comnd
	 jmp	r
	mov	temp1, bx
	mov	ah, cmcfm
	call	comnd		;Get a confirm.
	 jmp	r		; Didn't get a confirm.
	mov	bx, temp1
	mov	mnbaud, bl	;Set the baud rate table index.
	call	stbaud
	jmp	rskp

;	This is the SET PORT subcommand (not implemented in APC)

prtset:	mov	ah, cmcfm
	call	comnd		;Get a confirm.
	 jmp	$+3		; Didn't get a confirm.
	mov	dx, offset infms6 ;Tell user it's not implemented
	call	tcrmsg
	jmp	rskp


; The following procedures implement the SHOW command for the system
; dependent features of baud rate and port selection.

shobd:	mov	dx, offset bdst	;Baud rate string.
	call	tcrmsg
	mov	al, mnbaud	;Print the keyword corresponding to the
	mov	bx, offset bdtab ; current value of mnbaud.
	call	tabprt
	ret

shoprt:	ret			;Port selection not implemented.


	DSEG $

bdtab	db	13		;Thirteen entries		;[6] begin
	db	3,'100$'
	dw	0002H
	db	3,'110$'
	dw	0003H
	db	4,'1200$'
	dw	0008H
	db	3,'150$'
	dw	0004H
	db	5,'19200$'
	dw	000CH
	db	3,'200$'
	dw	0005H
	db	4,'2400$'
	dw	0009H
	db	3,'300$'
	dw	0006H
	db	4,'4800$'
	dw	000AH
	db	2,'50$'
	dw	0000H
	db	3,'600$'
	dw	0007H
	db	2,'75$'
	dw	0001H
	db	4,'9600$'
	dw	000BH						;[6] end

bdhlp	db	cr,lf,'	50	100	150	300	1200	4800	19200'
	db	cr,lf,'	75	110	200	600	2400	9600$'



; The following routines do screen control.  These are isolated here because
; the screen control sequences are likely to vary from system to system, even
; though the Rainbow and APC (the only systems implemented to date) both use
; ANSI sequences for this purpose.

	CSEG $

; POSCUR - positions cursor to row and col (each 1 byte) pointed to by dx.

poscur:	mov	bx, dx		;Do ANSI cursor positioning.
	mov	cl, 10
	mov	al, [bx]	;Get row value
	sub	ah, ah
	div	cl		;units digit in ah, tens digit in al
	add	ax, '00'	;Convert both to ASCII
	mov	word ptr anspos+2, ax	;Save reversed (al,ah)
	mov	al, 1[bx]	;Do same for column value
	sub	ah, ah
	div	cl
	add	ax, '00'
	mov	word ptr anspos+5, ax
	mov	dx, offset anspos	;Print cursor positioning string.
	call	tmsg
	ret

; CLRSCR - homes cursor and clears screen.

clrscr:	mov	dx, offset apccls
	call	tmsg
	ret

; CLRLIN - clears from cursor to end of line.

clrlin:	mov	dl, cr		;Go to beginning of line
	call	bout
clreol:	mov	dx, offset ansclr ;Clear from cursor to end of line
	call	tmsg
	ret

; REVON - turns on reverse video display

revon:	mov	dx, offset ansron
	call	tmsg
	ret

; REVOFF - turns off reverse video display

revoff:	mov	dx, offset ansrof
	call	tmsg
	ret

; BLDON - turns on bold (highlighted) display

bldon:	ret

; BLDOFF - turns off bold (highlighted) display

bldoff:	ret


	DSEG $

anspos	db	esc,'[00;00H$'	;Position cursor to row and column
apccls	db	1Eh,1Ah,'$'	;Home cursor and clear screen
ansclr	db	esc,'[K$'	;Clear from cursor to end of line
ansron	db	esc,'[7m$'	;Turn on reverse video
ansrof	db	esc,'[m$'	;Turn off reverse video


; Here tab expansion is done if necessary.  If not, just return retskp.

	cseg $

dotab:	cmp	dl, tab		;A tab?
	je	dotab1
	jmp	rskp		;No, just proceed.
dotab1:	mov	curbuf, 2	;Report cursor position command.
	mov	dx, offset curbuf
	mov	cl, 7
	int	220		;Special BIOS function.
	mov	al, curbuf+2	;Column position in binary (0-79).
	and	al, 07h		;Number of spaces needed
	mov	cx, 0008h	;  = 8 - (col mod 8)
	sub	cl, al
dotab2:	push	cx
	mov	dl, ' '
	call	dbout2
	pop	cx
	loop	dotab2
	ret

	dseg $
curbuf	db	2,0,0		;command, row, column of cursor position ;[7]

delstr  db	10O,10O,'$'	;Delete string.

system	db	'        NEC Advanced Personal Computer$'	;[1][20a]