
;			  ***  SEROUT.RSP  ***

;			  A Concurrent CP/M-86
;		       Resident System Process for
;	          Queue Driven Serial Character Output

;	Oct 19, 1983				   Dean Ballard


;	This program reads the serial out queue and sends those
;	characters to the asynchronous communications port.  It
;	is interrupt driven in that it performs a flag-wait any
;	time that the async port is not ready to transmit chars.
;	Setting up the actual interrupt routine is the respons-
;	ibility of the companion Serial Input routine.


;	First the hardware independent equate values.
;	These have to do with the queue management, and generally
;	pertain to the application program side of things.

q_mes_size	equ	17	; byte count plus 16 characters
out_q_num	equ	2	; number of out queue messages

bdos		equ	0E0h	; bdos interrupt number

c_detach	equ	93h	; detach console
dev_flag_wait	equ	84h	; wait for a flag
dev_flag_set	equ	85h	; release a flag
p_delay		equ	8Dh	; delay dx ticks
p_term		equ	8Fh	; terminate process
q_make		equ	86h	; create queue code
q_open		equ	87h	; open queue code
q_read		equ	89h	; read queue code
q_write		equ	8Bh	; write queue code

;	RSP origin equates

rsp_header	equ	00h	; start of data seg
rsp_pd		equ	10h	; process descriptor offset
rsp_uda		equ	40h	; user data area offset
rsp_stack_top	equ	13Ah	; UDA stack
rsp_data_end	equ	140h	; end of rsp stuff

;	Data structure for information shared with interrupt routine

t_wait		equ	byte ptr 0	; flag wait semaphore
t_count		equ	byte ptr 1	; chars left in message
t_ptr_off	equ	word ptr 2	; offset of trans message
t_ptr_seg	equ	word ptr 4	; segment of trans message
rec_prot	equ	byte ptr 6	; receive protocol code
tran_prot	equ	byte ptr 7	; transmit protocol code


;	Now the hardware specific equates
;	These have to do with the I/O port and interrupt management,
;	and are, for the most part, specific to the IBM PC

;	async port bit patterns

dr		equ	01h			; data ready
tbe		equ	20h			; xmit buf empty
r_t_mask	equ	03h			; rec/trans int en
modem_mask	equ	0Bh			; dtr, cts, en int



;	**************************
;	***  Code begins here  ***
;	**************************

	cseg

	org	00h			; small model

serial_out:
	call	which_port		; are we port 0 or port 1 ?
	call	is_it_there		; if the port is not present
	jnz	terminate_out		;   then terminate the process

	call	open_out		; open the output queue
	call	get_t_block		; copy address to local mem

out_loop:
	call	read_q			; wait for queue to fill
	call	send_chars		; transmit queue message
	jmps	out_loop		; go forever

terminate_out:
	mov	pd_flag,0		; turn off the keep flag
	mov	cl,p_term
	mov	dl,0
	int	bdos			; back to bdos


;	***************************
;	***  Setup Subroutines  ***
;	***************************

;	see which port we are, and adjust params if necessary

which_port:
	mov	al,ncp			; get copy number
	cmp	al,0			; if port = 0
	jz	which_port_done		;   then leave params alone

	add	al,'0'			; change port number to ascii
	mov	qpb_out_p,al		;   and update queue name

	mov	si,offset port1_params	; now adjust parameters
	mov	di,offset port0_params
	mov	cx,port_param_size
	cld
	rep	movsb			; copy port1 over port0

which_port_done:
	ret


;	check to see if the port is present
;	exit:	zf set if port is present

is_it_there:
	mov	dx,port_int_id		; interrupt ident port
	in	al,dx
	test	al,0F8h			; should be zero if present
	ret


;	Open the output queue
;	If the open fails, it is because the companion Serial Input
;	routine has not yet made the queue.  Wait here until it does.

open_out:
	mov	cl,q_open
	mov	dx,offset qpb_out	; output queue param block
	int	bdos			; open it

	test	ax,ax			; if not successful
	jnz	open_out		;   then try again
	ret


;	copy t_block address to local memory
;	this address is passed by the companion Serial In routine

get_t_block:
	mov	cl,q_read
	mov	dx,offset qpb_out	; output queue param block
	int	bdos			; read shared data pointer

	les	di,dword ptr msg_out	; get segment and offset
	mov	t_block_o,di
	mov	t_block_s,es		; save for wait loop
	mov	es:t_ptr_seg[di],ds	; tell int where to find
	ret				;   our trans message


;	**************************
;	***  Loop Subroutines  ***
;	**************************

;	read one message from the queue
;	return only valid data messages

read_q:
	mov	cl,q_read
	mov	dx,offset qpb_out	; output queue param block
	int	bdos			; wait here until ready

	mov	ax,word ptr msg_out	; al = count, ah = 1st byte
	cmp	al,q_mes_size		; check message count
	jb	read_q_done		; return if valid data msg

	les	di,t_block		; point to shared data
	cmp	al,0FEh			; recieve protocol code
	jnz	read_q1
	mov	es:rec_prot[di],ah	; set receive protocol
read_q1:
	cmp	al,0FFh			; trans protocol code
	jnz	read_q
	mov	es:tran_prot[di],ah	; set transmit protocol
	jmps	read_q			; go back for another

read_q_done:
	ret				; return when ready to send


;	send a new queue message to the interrupt routine

send_chars:
	les	di,t_block		; point to shared data
	mov	si,offset msg_out
	lodsb				; get message count
	mov	es:t_ptr_off[di],si	; save the message pointer
	mov	es:t_count[di],al	;   and the message count

send_again:
	les	di,t_block		; point to shared data
	mov	es:t_wait[di],0FFh	; tell int we're waiting
	call	fake_int		; and fake an interrupt

	mov	cl,dev_flag_wait
	mov	dl,t_flag		; transmit flag
	int	bdos			; wait here for int done

	les	di,t_block		; point to shared data
	cmp	es:t_count[di],0	; if whole message sent
	jz	send_done		;   then we're done

	mov	cl,p_delay		; if not, there must be
	mov	dx,2			;   some protocol hang up
	int	bdos			; so, wait a bit
	jmps	send_again		;   and try again

send_done:
	ret


;	Interrupt entries to kick off a transmit burst

fake_int0:
	int	12			; port 0 interrupt
	ret

fake_int1:
	int	11			; port 1 interrupt
	ret


;	*****************************
;	***  RSP header, pd, uda  ***
;	*****************************

	dseg

	org	rsp_header		; header start

	dw	0,0
ncp	db	1,0			; one copy
	dw	0,0,0
	dw	0,0


	org	rsp_pd			; process descriptor

	dw	0,0			; link, thread
	db	0			; ready to run
	db	181			; priority better than PIN's
pd_flag	dw	2			; process flag "keep"
	db	'SerOut  '		; process name
	dw	rsp_uda/10h		; uda segment
	dw	0,0			; disk, user, reserved
	dw	1			; for shared code
	dw	0,0,0			; and a mess of zeros
	dw	0,0,0
	dw	0,0,0
	dw	0,0,0


	org	rsp_uda			; user data area

	dw	0,0,0,0,0,0		; no dma buffer
	dw	0,0,0,0,0,0
	dw	0,0,0,0,0,0
	dw	0,0,0,0,0,0
	dw	0,0,rsp_stack_top,0,0,0
	dw	0,0,0,0,0,0
	dw	0,0,0,0,0,0
	dw	0,0,0,0,0,0
	db	1,0,0,0,0,0		; don't switch from UDA
					; stack at SUP entry
	org	rsp_stack_top

	dw	offset serial_out	; code start
	dw	0			; code seg (genccpm)
	dw	0			; flags (genccpm)

;	******************************
;	***  Our data begins here  ***
;	******************************

	org	rsp_data_end		; above the rsp stuff

;	first we have parameters for port 0

port0_params	rb	0
p0_base		equ	03F8h		; port base address
port_int_id	dw	p0_base + 2	; ro int ident
fake_int	dw	fake_int0	; port 0's interrupt
t_flag		db	09h		; XIOS transmit flag

;	if using port 1, these values are copied over port 0's

port1_params	rb	0
p1_base		equ	02F8h		; port base address
		dw	p1_base + 2	; ro int ident
		dw	fake_int1	; port 1's int
		db	0Bh		; xmit flag

port_param_size	equ	offset $ - offset port1_params

t_block_o	rw	1		; address of interrupt
t_block_s	rw	1		;   shared data block
t_block		equ	dword ptr t_block_o

qpb_out		dw	0,0,0,msg_out
		db	'SerOut'
qpb_out_p	db	'0 '

msg_out		rb	q_mes_size
		db	00			; pad

	end
