
; File: [6,20]ACDSKEL.MAC         Last edit: 02-DEC-1987 15:47:20 

	.TITLE ACD SKELETON VERSION 2.0
	.VERSION /V2.0X/
	.IDENT	/02DE87/
	.enable lc
.rem |

 Author: Jim Bostwick 
 History: 

	*** This version uses local output buffers ***
|

	.sbttl Documentation
.rem |

 Description:
	 Each port linked to the ACD consumes one ACB-sized chunk 
 of primary  pool (roughly 50. bytes). The ACB lives for as long as the port 
 is connected to the ACD.  
	Each port linked to the ACD also requires an output buffer. 
This is taken from a list of buffers allocated in ACD address space. 
For performance reasons, the size of the internal buffer
is fixed. If an output request overflows it's buffer, the QIO fails.
Buffer size and the number of buffers are both fixed at assembly time.
Buffers are allocated to the port at connect time, and deallocated
at disconnect time. 
|

	.sbttl STATUS CODES
.REM |

 The ACD will return the following IO status codes, in addition
 to those returned by the EXEC and TTDRV :

 DESCRIPTION: Internal ACD buffer overflow during OUTPUT message
		expansion:
   LOW WORD: IE.RBG (330, -40.) - "Illegal record size"
   HI WORD:  byte count of 1 

 DESCRIPTION: Internal ACD buffer allocation failure during CONNECT.
   LOW WORD: IE.NDR (270, -72.) - "No dynamic space available"
   HI WORD: 0
	NOTE: Only possible during CONNECT, and connection rejected
		if this occurs. 
|


	.sbttl Local Definitions
	.mcall PKTDF$,HWDDF$,UCBDF$
	.nlist sym
	HWDDF$
	PKTDF$
	UCBDF$
	.list sym
;
; **************** Local Macros *****************
;
	.macro push args
	.irp <args>,arg
	mov arg, -(sp)
	.endr
	.endm

	.macro pop args
	.irp <args>,arg
	mov (sp)+, arg
	.endr
	.endm


	.sbttl ACB Extension
.rem %
 	Other than the output buffers, scratch data for the ACD 
 is stored in an ACB in primary pool. An ACB is created for each 
 port that is linked to the ACD. ACD-specific storage is allocated 
 below offset A.RES in the ACB. Offsets above A.RES are defined by
 RSX, via the PKTDF$ macro. 

************************* NOTE **************************************
*   These are only examples - ACB offsets must be tailored to the   *
*   needs of particular ACD being built.                            *
*********************************************************************

		|			|
	a.res 	| end of RSX offsets	|
		|-----------------------|
	a.ist	| input state pointer 	| 
		|-----------------------|
	a.ics	| input checksum	|
		|-----------------------|
	a.ocs	| output checksum	|
		|-----------------------|
	a.isb	| input status/flags	| 
		|-----------------------|
	a.osb	| output status/flags	|
		|-----------------------|
	a.buf	| address of output buf |
		|-----------------------|

%

;
; Define additional ACB offsets relative to start of ACB (U.ACB). 
; (a.res is defined by the PKTDF$ macro)
;
a.ist = a.res		; input state pointer
a.ics = a.res + 2	; input checksum accumulator
a.ocs = a.res + 4	; output checksum accumulator
a.isb = a.res + 6	; input status
a.osb = a.res + 10	; output status
a.buf = a.res + 12	; output buffer address
a.end = a.buf		; always equate to last offset used!

;
; All ACD entry points have R5 pointing to offset u.tsta of the port
; UCB. We often want to access offsets U.AFLG and various offsets 
; in the ACB.   The following offsets are defined locally for 
; convenience. 
;
; Define some handy offsets to get from the input R5 to 
; interesting UCB offsets.
;
uaflg = u.aflg-u.tsta		; uaflg(r5) -> u.aflg in UCB
uaacb = u.acb-u.tsta		; uaacb(r5) -> ACB

	.sbttl ACB Definition
.rem |
 ACB - this section is required ACB definition. It must be the first
 	storage defined in the ACD. 

 
*************************** NOTE **************************************
*                                                                     *
*  MSKWRD follows DRIVER conventions for 'legal function mask'. If a  * 
*  bit is SET, ACCENT and DEQENT are called.  For a table of mask     *
*  word definitions, see chapter 4 of the "RSX Guide to Writing an    *
*  IO Driver".                                                        *
*                                                                     *
*  It is sufficient in many cases to use either 0,0 (never call ACD   *
*  at acceptance/deque) or -1,-1 (always call ACD at                  *
*  acceptance/deque). Specific masks are only required if the ACD     *
*  should ignore certain QIO functions, but act on others. The        *
*  other ACD entry points are always called, regardless of the        *
*  value of MSKWRD.                                                   *
*                                                                     *
***********************************************************************

|

mskwrd:	.word	0,0	; don't call ACD at ACCENT, DEQENT
;; mskwrd:	.word -1, -1 	; always call ACD at ACCENT, DEQENT

;
; The value of ACBLEN must be at least A.res, and will usually 
; include extra storage for ACD- (and port-) specific data. 
;
acblen:	.word	a.res+a.end

.rem |
 ACD Dispatch table 

 This table is required for all ACDs. The comments include
 the entry point number from Dale Donchin's MultiTasker article,
 the offset defined in PKTDF$ (for poking through TTDRV code), and
 a brief description.
    
		entry    number  offset    description
                -------  ------  ------    -----------
|
dsptbl:	.word	ACCENT	;  #1 - a.acce - IO request acceptance
	.word	DEQENT	;  #2 - a.dequ - IO request deque
	.word	POWENT	;  #3 - a.powe - Powerfail notification
	.word	INPENT	;  #4 - a.inpu - Input request completion
	.word	OUTENT	;  #5 - a.outp - Output request completion
	.word	CONENT	;  #6 - a.conn - Connection
	.word	DISENT	;  #7 - a.disc - Disconnect
	.word	RECENT	;  #8 - a.rece - Input character reception
	.word	PROENT	;  #9 - a.proc - Input character processing
	.word	TRAENT 	; #10 - a.tran - Output translation
	.word	CALENT 	; #11 - a.call - UA.TRA (input completion) callback

;
; Each ACD in the system is given a unique number. The global
; symbol $ACDNM could be redefined at taskbuild time. The ACD
; number may also be redefined at install time.  It is NOT 
; required that the assembly or taskbuild number match the installed
; number. 
;  

$ACDNM == 0
acdnum:	.word $acdnm

	.sbttl Local Data

.rem |

The ACD has one APR - 8KB - to work with. All code and data must
fit within this limit, or you must invent your own remapping code. 
   { Warning - the latter must be done VERY carefully! }

Although most examples use the ACB for storage, recall that
the ACB is created from primary pool - an often scarce and always
valuable resource. ACD buffers, if needed, will be the largest
consumer of pool by far. It is relatively simple to allocate these
buffers in ACD address space, and link them to the port via a pointer
kept in the ACB.  This technique can result in significant savings
of primary pool. 

The following EXAMPLE code shows how to allocate a 'pool' of buffers
within the ACD. These would then be allocated to the port at 
connect time, and returned to the 'pool' at disconnect time. This
is an example only - many other schemes are possible. 
|

;
; Define ACD buffer 'pool'. NBUF buffers of fixed length BUFLN are
; defined as a single-linked list in ACD address space. The list
; head is at BUFHD, with the first word of each buffer being 
; the link word (while it's in the pool). The list is null-terminated. 
; 

Nbuf = 8.		; total number of internal buffers
bufln = 80.		; buffer size
bufln = <bufln+1> & ^C1	; make sure it's even

;
; Define the linked list of available output buffers
;
	.even
bufhd:	.word .+2
	.rept <nbuf-1>
$$$ = . + obfln
	.word $$$
	.blkb <bufln-2>
	.endm
; define the last one with a null link word
	.word 0
	.blkw <obfln-2>
;	
; end of bufer list
; 	
;*** REMEMBER - The above is only an example. ***


	.sbttl NOOP Entry Points
; 
; Usually, not all of the entry points are used by a given ACD. 
; For simplicity, define any unused entry points here as 
; just a RETURN.  Be sure to also comment out the entry point
; label farther on as well. 
;	

accent:			; QIO acceptance
deqent:			; QIO deque
powent:			; powerfail routine
;	inpent:			; input request completion
;	outent:			; output QIO completion routine
;	conent:			; ACD connection
;	disent:			; ACD disconnect
recent:			; input character reception routine
;	proent:			; input character processing
;	traent:			; output translation
calent:			; system state callback routine
	return


	.sbttl ACCENT - Acceptance Routine
; ACCENT
;
; When a QIO is accepted by the EXEC and TTDRV, and while task 
; buffers are still mapped, this entry point is called. 
;
; REFERENCE: 
;	TTINI.MAC at label QUEUE::
;
; INPUT:
;	R1 =  IO function code * 2
;	R3 -> IO packet
;	R5 -> ucb+u.tsta
;
; OUTPUT:
;	UA.ALL - set if request should be accepted. If clear, 
;		refuse request passing A.IOS to task IOSB
;
; DEFAULT: 
;	UA.ALL
;
; ACTION:   ** This entry point not used by this ACD **
;
; REGISTERS:
; 	all but R0 preserved
;

;ACCENT:


	.sbttl DEQENT - Qio Initiation
;
; DEQENT
; 
; When a QIO is dequeued by TTDRV, we are called here.
; Task buffers are not generally available at this point. 
;
; REFERENCE: 
;	TTINI.MAC after label PKTDSP:
;
; INPUT:
;	R1 =  IO function code * 2
;	R3 -> IO packet
;	R5 -> ucb+u.tsta
;
; OUTPUT:
;	UA.ALL - set if QIO should be accepted. If clear, refuse
;		request, returning error in A.IOS to task IOSB.
;
; DEFAULT:
;	UA.ALL
;
; ACTION: ** This entry point not used by this ACD **
;
; REGISTERS:
;	all but R0 preserved
;

;DEQENT:


	.sbttl POWENT - Power Fail Entry
;
; POWENT
;
; This entry point provides Power Fail notification (only) to
; the ACD. 
;
; REFERENCE:
;	TTCAN.MAC at 30$ after TTPWUP::
;
; INPUT:
;	R5 -> ucb+u.tsta
;
; OUTPUT:
;
; ACTION:   *** This entry point not used by this ACD ***
;
; REGISTERS:
; 	all but r0 preserved
;

;POWENT:


	.sbttl INPENT - Input Completion Routine
;
; INPENT
;
; Called during completion of input (read) QIO. 
; 
; REFERENCE: 
;	TTRW.MAC at IRQDON:
;
; INPUT:
;	R3 -> IO packet
;	R5 -> ucb+u.tsta
;	A.IOS = reason for completion
;
; OUTPUT:
;	UA.TRA - transfer from ACD buffers at A.SMAP to task buffer.
;		if clear, TTDRV buffers copied to task
;	A.IOS - reason for completion (IOSB word 1 format) 
;		may be changed by ACD
;
; DEFAULT:
;	Transfer from TTDRV buffers. 
;
; ACTION:  ** This entry point not used by this ACD **
;	
; REGISTERS:
; 	all but R0 preserved
;

;INPENT:

.rem |

	Often, this entry point is used to reset the ACD for the
next input request. Doing so saves the trouble of decoding the 
QIO function at DEQENT.  Since we know that the current read operation
is finished here, we can safely and simply setup for the next one.
This is also desirable in that the next input could preceed the read
QIO - and be processed into the typeahead buffer. 

|


	.sbttl OUTENT - Output Completion Routine
;
; OUTENT
;
; Called during completion of output (write) QIO. 
;
; REFERENCE:
;	TTRW.MAC after ORQDON:
;
; INPUT:
;	R3 -> IO Packet
;	R5 -> ucb+u.tsta
;	A.IOS = completion status
;
; OUTPUT:
;
; ACTION:   
;
; REGISTERS:
; 	all but r0 preserved
;

;OUTENT:

.rem |
	Note well that, at this entry point, changes in IO status
must be copied directly to the IO Packet. This point is 
not made in the documentation. To return a DIFFERENT IOSB value
than that in A.IOS, move it to (r3). This is normally the 
io packet link word, but is used during completion as a temporary
repository for the IO status.  
	
	It may be desirable to use this entry point to reset the 
write side of the ACD for the next operation. Doing so saves the 
hassle of decoding the function code at DEQENT. 
|


	.sbttl CONENT - ACD Connection
;
; CONENT 
;
; ACD connection - called when a terminal is connected to the ACD.
;
; REFERENCE: 
;	TTATT.MAC at 770$ after MCACD::
;
; INPUT: 
;	R0 -> ACB
;	R5 -> UCB+U.TSTA
;
; OUTPUT:
;	none
;
; ACTION: 
;	A buffer is allocated from the internal pool, and saved
; 	in the ACB. If no buffer is available, the connection is 
; 	rejected. 
;
; REGISTERS:
;	all but R0 preserved
;

.rem |

	This entry point is most used to initialize port-specific
offsets in the ACB, allocate buffers, and so on. In the following
EXAMPLE, a buffer is allocated from the ACD pool (see definition
above), and linked to the ACB.  In this EXAMPLE, the buffer will
remain allocated to the port until it is disconnected from the ACD. 

|

CONENT:
	push <r1>		
	mov obfhd, r1		; point to buffer pool 
	bne 30$			; ne - buffers available
;
; no buffers available, reject the connection
;
	pop <r1>
	movb #ie.ndr, a.ios(r0)	; reject connection
	br 200$			; return

30$:	
	mov (r1), obfhd		; update listhead
	mov r1, a.buf(r0)	; save pointer in ACB

	*** 	further initialization here ***

	pop <r1> 		; restore remaining registers
200$:	return


	.sbttl DISENT - ACD Disconnect routine
;
; DISENT - ACD disconnect 
;
; Called when the ACD is disconnected from a port. 
;
; REFERENCE:
;	TTATT.MAC at 770$ after MCACD::
;
; INPUT:
;	R0 -> ACB
;	R5 -> UCB+U.TSTA
;
; OUTPUT:
;	none
;
; ACTION:
;	Terminal's buffer is re-linked to the internal pool. 
;
; REGISTERS:
;	all but R0 preserved
;

.rem |

	This entry point is used to clean up just before the ACD
is disconnected from the port. Note that at this point, TTDRV is
comitted to disconnecting from the ACD - you can't change that. 

Continuing the EXAMPLE use of an ACD buffer pool, we return the
port's buffer to the front of the ACD free list. 
|

DISENT:
	mov obfhd, @a.buf(r0)	; copy link
	mov a.buf(r0), obfhd	; link to free list
	return


	.sbttl RECENT - Input Char. Reception 
;
; RECENT
;
; Called during input character reception (hot off the interface). 
;
; REFERENCE:
;	TTICH.MAC at 10$ after ICHAR1::
;
; INPUT:
;	R2 = character received
;	R5 -> ucb + u.tsta
;
; OUTPUT:
;	R2 = character received (may be changed by ACD) 
;	UA.ACC - set if character to be accepted; if clear,
;		character ignored
;	UA.PRO - Do standard processing if set; if clear, 
;		treat character as 'pasall'
;	UA.ECH - echo character if set
;	UA.TYP - set to force character into typeahead buffer
;	
; DEFAULT:
;	UA.ACC!UA.PRO!UA.ECH - accept, process and echo character
;
; ACTION:   *** This entry point not used by this ACD ***
;
; REGISTERS:
; 	all but R0 preserved
;

;RECENT:

.rem |

For the exact distinction between this and PROENT
(below), you'll have to read the TTDRV sources. Basically,
this entry is at interrupt state (so you should do as little
as possible), while PROENT is at fork state. TTDRV processes
'immediate' things like ^U and ^C at this state. 

Another distinction is that this entry preceded a character's
entry into the typeahead, while PROENT is called from the output side
of the typeahead. 

An important use of this entry is to change the input
character to something else - especially if the translated
character is to be echoed. Later, at PROENT, you can change 
the echo again, but it is more trouble. 
|


	.sbttl  PROENT - Input Char. Processing
;
; PROENT
;
; Called during input character processing. (after the 
;	typeahead buffer)
;
; REFERENCE:
;	TTICH.MAC at 35$ after ICHAR2::
;
; INPUT:
;	R2 = character to be processed
;	R5 -> ucb + u.tsta
;
; OUTPUT:
;	R2 = character to process (may be changed)
;	UA.ECH - set if an echo should occur
;	UA.SPE - set if special echo is to be output
;		 from A.IMAP, A.IBUF, A.ILEN
;	UA.PUT - set if character to be put in input buffer
;	UA.COM - force completion of QIO using status in A.IOS
;	UA.CAL - set with UA.SPE if ACD needs to be called back
;		to deallocate special echo buffers. If so, callback
;		is at CALENT
; DEFAULT:
;	UA.ECH!UA.PUT - echo character and put in input buffer
;
; ACTION:   
;	
; REGISTERS:
; 	preserved
;

; PROENT:

.rem |

	This is the preferred point to do most input processing.
Note: 
The function of the UA.ECH bit is a bit unclear here. If the 
character in R2 is changed and UA.ECH set, the new character
is NOT echoed. I believe that at this point it already HAS been
echoed. 

If the UA.SPE bit is set along with UA.ECH, then TTDRV will echo
the string described by the 'interrupt buffer' offsets in the ACB.
If fixed echo strings are to be used (for example, acknowlegements
of a message), they may be defined once in the ACD, and simply
pointed to. Or, you may allocate a port-specific buffer and build
an arbitrary echo string. 

|

	.sbttl TRAENT - Output Char. Translation
;
; TRAENT
;
; Entered during output (write) requests, just before output 
; characters are copied from task to TTDRV buffers. 
;
; REFERENCE:
;	TTRW.MAC at 40$ after WRITE1:
;
; INPUT:
;	R3 -> io packet
;	R5 -> ucb+u.tsta
;
; OUTPUT:
;	UA.TRN - set if ACD will supply output, rather than task
;		If set, characters at A.SMAP, A.SBUF, A.SLEN
;		are copied to TTDRV buffers. ACD is then called
;		again at TRAENT, to set up additional output, or
;		just return.
;
; DEFAULT:
;	characters copied from task buffer to TTDRV buffer 
;
; ACTION: 
;
; REGISTERS:
; 	all except R0 preserved
;

;TRAENT:

.REM |

	The ACD interface is heavily oriented twoard input operations - 
probably because most nonstandard output can be prepared by the task
before issuing the QIO. This and the OUTENT completion entry point
are the only output-oriented ACD hooks. 

	In communications work, the ACD may be supplying protocol
'wrappings' for the user's message. The following EXAMPLE code
fragment shows how to set up to build output in the ACD's buffer,
using the user's buffer as input. It also shows how to set up 
a buffer from ACD address space in either the 'interrupt' or 
'system state' offsets of the ACB. 
|

	mov uaacb(r5), r0		; point to acb
;
; set up 'system state' ACB offsets to point to the 
; ACD buffer allocated to this port. 
;
	mov @#kisar5, a.smap(r0)	; set APR bias 
	mov a.buf(r0), a.sbuf(r0)	; copy buffer address
	clr a.slen(r0)			; no length yet
;
; access user's buffer via APR6
;
	push @#kisar6			; save APR 6 mapping
	mov	r3,r1			; copy i/o packet pointer
	add	#i.prm,r1		; r1 -> user buffer mapping
	mov	(r1)+,@#kisar6		; map user buffer
	mov	(r1)+,r2		; address
	mov	(r1)+,r1		; length
;
; at this point, 'expand' the user's buffer into our internal buffer
;
;****** DON'T FORGET TO RESTORE MAPPING **********

	pop @#kisar6			; restore APR6 mapping

;****** DON'T FORGET TO RESTORE MAPPING **********
;
; The real output is now in our buffer - tell TTDRV to use it,
; instead of the user's buffer. 
;
	bis	#ua.trn,u.aflg-u.tsta(r5) ; tell ttdrv we want to do translation
	; restore any saved registers
	return


	.sbttl CALENT - Callback Routine
;
; CALENT
;
; The ACD is called here during INPUT IO completion, if it has set
; the UA.TRA flag. The current buffer setup in A.SMAP, A.SBUF, and A.SLEN
; has been copied to the user buffer. ACD may deallocate the buffer, and/or
; set up another to be copied. 
;
; REFERENCE:
;	TTICH.MAC at ECHD1::	(echo processing)
;	TTICH.MAC at I2SPE::	(special echo processing)
;	TTRW.MAC after IRQDON:	(copy ACD to task buffers)
;	TTRW.MAC at 80$ after SOLIDN: (solicited input buffer copy)
;	TTSUB.MAC at UBTRA::	( special IOFIN processing for ACD 
;					buffers)
; INPUT:
;	R5 -> ucb+u.tsta
;
; OUTPUT:
; 	set UA.CAL to copy system state buffer to user, and call here again.
;
; ACTION:   *** This entry point not used by this ACD ***
;
; REGISTERS:
; 	preserved
;

;CALENT:

.rem |

This entry point can be called from at several places. If the ACD
sets up alternate buffers ( i.e., uses the 'interrupt' or 'system
state' descriptors in the ACB), it may need to be called back
after TTDRV has emptied those buffers. This is the place. 

Note that, in our EXAMPLE, the buffer is 'statically' allocated to
the ACB, and we don't need this entry point. 

Note that if you set up to be called here, you can pass another
buffer to TTDRV and again set UA.CAL. In which case, you will be
called here again when the second (or third or...) buffer has been
copied. 
|

