; 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 ,arg mov arg, -(sp) .endr .endm .macro pop args .irp ,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 = & ^C1 ; make sure it's even ; ; Define the linked list of available output buffers ; .even bufhd: .word .+2 .rept $$$ = . + obfln .word $$$ .blkb .endm ; define the last one with a null link word .word 0 .blkw ; ; 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 mov obfhd, r1 ; point to buffer pool bne 30$ ; ne - buffers available ; ; no buffers available, reject the connection ; pop 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 ; 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. |