; File: C2ACD.MAC Last edit: 2-DEC-1987 17:12:48 .TITLE COMPUWEIGH 2000 ACD VERSION 2.0X .IDENT /02DE87/ .enable lc .rem | Author: Jim Bostwick History: 6-AUG-1987 01:00:48 JMB - rewrite to use state table, add output protocol processing. 13-AUG-1987 17:49:06 JMB - add code to use local buffer pool 1-SEP-1987 10:08:00 PTH - recognize single byte output as a DLE sequence, adding only the DLE prefix on output, adding no protocol nor checksum. Valid transactions are two or more bytes, and protocol is added along with checksum. 18-SEP-1987 15:56:38 JMB - fixed input checksum validation 19-OCT-1987 18:34:50 JMB - fixed output IOSB return status code 20-OCT-1987 16:34:55 PTH - on ACK or NAK sequence read, return a length of zero instead of 1 30-NOV-1987 19:12:11 JMB - eliminate redundant clr a.ios during output processing 02-DEC-1987 16:49:10 JMB - add reference information to ACD entry point comments. *** This version uses local output buffers *** | .sbttl DOCUMENTATION .REM | *USER* .hl 2 Description This User Guide is for Version 2.0 of the the C2ACD Ancillary Control Driver (ACD). This ACD implements the Compuweigh 2000 communications protocol for RSX-11M+ systems. Full input protocol validation and stripping, and output protocol construction, is handled. Checksum generation and checking are also handled within the ACD. The port using this ACD basically looks to the application program like a FDX terminal. The user passes only the message text and receives message text in return. Retries, ACK/NAK message generation, and content (text) validation are up to the caller. The ACD recognizes DLE sequences (DLE ACK, DLE NAK), and sends those without added protocol or checksum. Valid transactions must be 2 or more bytes long, as a transaction code by itself is a word (2 bytes). This ACD preserves the echo defaults of the terminal driver. If the port is set NOSLAVE and ECHO, all characters will be echoed as they are processed by the ACD. If the port is set SLAVE and ECHO, only those characters passed to a QIO buffer will be echoed. If the port is set SLAVE and NOECHO, no characters will ever be echoed. The ACD can handle up to eight ports at one time; there is no code or data in the ACD which cares (or knows) how many ports are being serviced. However, internal pool contains a fixed number of buffers, which effectively limits the number of simultaneous connections. Each port linked to the acd consumes one ACB-sized chunk of primary pool (about 50. bytes). The ACB lives for as long as the port is connected to the ACD. Each port linked to the ACD also requires output buffer. These are taken from a list of buffers in ACD 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. At this time, the ACD buffer is 300 decimal bytes per terminal. This means that a the maximum possible length of a message supplied by a caller (containing no DLE characters in the message) could be 294 decimal bytes. An the minimum possible length of a message (containing all DLE characters) is 147 decimal bytes. *ENDUSER* | .sbttl STATUS CODES ; .rem | *USER* .hl 2 Error Codes The ACD will return the following IO status codes, in addition to those normally returned by the EXEC and TTDRV. .lit DESCRIPTION: Message received, checksum okay (read) LOW BYTE, LOW WORD: IO.SUC (1) HIGH BYTE, LOW WORD: zero (0) HIGH WORD: message byte count DESCRIPTION: ACK received (read) LOW BYTE, LOW WORD: IO.SUC (1) HIGH BYTE, LOW WORD: (6 octal) HIGH WORD: zero (0) DESCRIPTION: NAK received (read) LOW BYTE, LOW WORD: IO.SUC (1) HIGH BYTE, LOW WORD: (25 octal) HIGH WORD: zero (0) DESCRIPTION: Message received, bad checksum (read) LOW BYTE, LOW WORD: IE.CKS (342 octal, -30 decimal) HIGH BYTE, LOW WORD: zero (0) HIGH WORD: message byte count DESCRIPTION: Invalid DLE sequence (invalid DLE sequence encountered within the message text - only DLE DLE and DLE ETX are possible). (read) LOW BYTE, LOW WORD: IE.IES (256 octal, -82 decimal) HIGH BYTE, LOW WORD: first character following DLE (part of sequence) HIGH WORD: byte count of message received up until DLE DESCRIPTION: Invalid DLE sequence (not a ACK or NAK) specified. (write) LOW BYTE, LOW WORD: IE.IES (256 octal, -82 decimal) HIGH BYTE, LOW WORD: zero (0) HIGH WORD: unchanged length value as supplied by caller NOTE: a single NULL character is output on specified LUN in place of caller specified message. DESCRIPTION: Internal ACD buffer overflow during OUTPUT message after message expansion (adding protocol and checksum). (write) LOW BYTE, LOW WORD: IE.RBG (330, -40.) - "Illegal record size" HIGH BYTE, LOW WORD: zero (0) HIGH WORD: unchanged length value as supplied by caller NOTE: a single NULL character is output on specified LUN in place of caller specified message. DESCRIPTION: Internal ACD buffer allocation failure during CONNECT. LOW BYTE, LOW WORD: IE.NDR (270, -72.) - "No dynamic space available" HIGH BYTE, LOW WORD: zero (0) NOTE: Only possible during CONNECT, and connection rejected if this occurs. .eli Remember that the IO status of the QIO is returned as a BYTE INTEGER and not a WORD INTEGER. If you are using a high level language that works with word integers or two-word integers, you must convert the byte integer to that format. .nt QIOW routines for P3UTIL currently fill out negative byte integer in IOSB to create negative word integer. In doing so, the high byte (the first char after DLE) will be written over. This is no longer the accepted way of handling the IO status, and may change in the future. The QIO (no wait) P3UTIL routines return the IO status block verbatim, and DO NOT convert negative byte integer to negative word integer. This is the accepted way of handling the IO status, leaving it up to the high level language programmer to convert the status to another format if required. .en .eli *ENDUSER* | .sbttl Local Definitions .mcall PKTDF$,HWDDF$,ucbdf$ .nlist sym hwddf$ pktdf$ ucbdf$ ,,ttdef .list sym .list meb .macro push args .irp arg, mov arg, -(sp) .endr .endm .macro pop args .irp arg, mov (sp)+, arg .endr .endm ;debug = 0 ; debug true if defined ;goodcs = 0 ; debug - if defined -> checksum always good ; ; Define ASCII control characters of interest ; .if df debug ; use printing 'control' characters ; ; These are the ascii codes for the following control characters when ; we are doing testing and want printable characters to use. ; DLE = '# STX = '$ ETX = '% ACK = '^ NAK = '& .iff ; no debug - use ANSI control characters ; ; These are the ascii codes for the following control chararcters ; DLE = 16. STX = 2. ETX = 3. ACK = 6. NAK = 21. .endc ; if debug .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. 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.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.siz = a.buf +2 ; size of acb for this ACD ; ; Define flag offsets for a.isb ,a.osb ; ; These words are used as flags words until completion time. ; Then an IO return code is put there for the completion routine ; to transfer to TTDRV (via a.ios). ; as.odd = 1 ; set if on odd byte of input (for csum calc.) ; ; define offsets to get to common things from the U.TSTA pointer ; All entry points have ucb+u.tsta in R5. The following get to ; interesting UCB offsets from that point. ; uaflg = u.aflg-u.tsta uaacb = u.acb-u.tsta .sbttl ACB Definition ; ; ACB - this section is required ACB definition. It must be the first ; data defined in the ACD. ; ; ; MSKWRD follows DRIVER conventions for 'legal function mask'. If a bit ; is SET, ACCENT and DEQENT are called. 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. ; Note that the remaining ACD entry points are always called, regardless ; of the value of MSKWRD. ; mskwrd: .word 0,0 ; don't call ACD at ACCENT, DEQENT acblen: .word a.siz ; ; 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 .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 may be redefined at taskbuild or installation time. ; .globl $ACDNM ;acdnum: .word $acdnm acdnum: .word 40 .sbttl Local Data ; ; Define output buffer size. ; ; Buffer size is really a guess: ; The largest string which can be passed to the ACD is 255. bytes. ; I assume we won't see the pathological all-dle case, and add ; 75 bytes for protocol (6 bytes) and expansion. This allows for ; a bit less than one in 4 bytes. ; ; The number of buffers is also arbitrarily fixed at 8. This restricts ; the number of ports which may be simultaneously connected to the ACD. ; obfln = 300. ; initial output buffer size ; the size is a guess obfln = & ^C1 ; make sure it's even Nbuf = 8. ; total number of internal buffers ; ; Define the linked list of available output buffers ; .even Obfhd: .word .+2 .rept $$$ = . + obfln .word $$$ .blkb .endm ; define the last one with a null link word .word 0 .blkw ; ; end of bufer list ; .sbttl NOOP Entry Points ; ; Define any unused entry points here as just a RETURN. ; 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 in 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 not generally available at this point. ; Initialize ACB for new operation. ; ; REFERENCE: ; TTINI.MAC after label PKTDSP: ; ; 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, 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: ; The input state machine is reinitialized for the next read. ; ; REGISTERS: ; all but R0 preserved ; INPENT: mov uaacb(r5), r0 ; point to ACB mov #inst1, a.ist(r0) ; reset state pointer clr a.isb(r0) ; reset flags/status clr a.ics(r0) ; zero checksum return .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: ; Copy status from a.osb to IO Packet link word ; ; REGISTERS: ; all but r0 preserved ; OUTENT: mov uaacb(r5), r0 ; point to ACB mov a.osb(r0), (r3) ; copy IO status clr a.osb(r0) ; set up for next output return .sbttl CONENT - ACD Connection ; ; CONENT ; ; ACD connection - when the terminal is connected to the ACD, we ; initialize the ACB area to known conditions, allocate the ; first output buffer, and generally set up to work. ; ; REFERENCE: ; TTATT.MAC at 770$ after MCACD:: ; ; INPUT: ; R0 -> ACB ; R5 -> UCB+U.TSTA ; ; OUTPUT: ; none ; ; ACTION: ; Output buffer allocated, initialized and saved in ACB. ; Return Ie.dnr (no dynamic resources) error if buffer ; allocation fails. Initialize input state. ; ; REGISTERS: ; all but R0 preserved ; CONENT: push mov obfhd, r1 ; point to buffer pool ; br 30$ 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 ; ; Initialize input state machine for first read. ; mov #inst1, a.ist(r0) ; set state pointer clr a.isb(r0) ; clear status/flags clr a.ics(r0) ; clear checksum clr a.osb(r0) ; initialize output state 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: ; output buffer linked to front of ; internal free buffer list ; ; REGISTERS: ; all but R0 preserved ; 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: .sbttl PROENT - Input Char. Processing ; ; PROENT ; ; Called during input character processing. ; ; 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: The character is passed through the input state machine. ; ; REGISTERS: ; preserved ; PROENT: mov uaacb(r5),r0 ; r0 -> ACB push ; save some registers call @a.ist(r0) ; dispatch into input state machine pop ; restore registers return .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: ; Users buffer is expanded into ACD output buffer, via ; output state machine. Protocol and checksum are added. ; ; On second pass (TTDRV callback), we just return. We know ; to do this by the non-zero a.osb value. ; ; REGISTERS: ; all except R0 preserved ; TRAENT: mov uaacb(r5), r0 ; point to acb tst a.osb(r0) ; callback? bne 100$ ; y - br push push @#kisar6 ; save APR 6 mapping 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 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 call expout ; bis #ua.trn,uaflg(r5) ; tell ttdrv we want to translate add #20000, a.sbuf(r0) ; ttdrv will map buffer thru APR6 10$: pop @#kisar6 ; restore APR6 mapping pop 100$: 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 ; .sbttl INST - Input State Machine .rem | The input character handling is done via a state machine. This has the advantages of compact code which is very easy to modify for different protocols. A disadvantage is that the code is somewhat less easy to read than in other approaches. Input states are denoted as INSTn, where 'n' is the state number. Other support routines have entry points which begin with IN, but have no state or trailing digit. Several standard exits from the machine are provided: INXT - accept character INXT1 - ignore character INFIN - accept character and complete INFIN1 - ignore character and complete QIO INCSM is the input checksum accumulator. | ; ; INPUT: ; r0 -> ACB ; r2 = input character ; ; OUTPUT: ; ; ACTION: see individual states ; ; REGISTERS: ; all but r0 preserved ; ; ; INST1 - wait for start of message, denoted by . ; INST1: cmpb #dle, r2 ; start of control seq? bne 10$ ; no - br mov #INST2, a.ist(r0) ; take transition 10$: jmp INXT1 ; exit - reject char ; ; INST2 - finish initial control sequence ; has been seen: determine what second char is ; INST2: cmpb #stx, r2 ; start of message? bne 20$ ; no - br mov #inst3, a.ist(r0) ; transition br 100$ ; exit - reject char 20$: mov #inst1, a.ist(r0) ; reset state for next msg cmpb #ack, r2 ; ACK message? beq 40$ ; yes - br cmpb #nak, r2 ; NAK message? bne 100$ ; no - br 40$: movb #is.suc, a.ios(r0) ; set IOSB status - success code in low byte movb r2, (r0) ; and escape sequence in high byte jmp INFIN1 ; reject char and complete 100$: ; illegal initial control sequence is just ignored jmp INXT1 ; ignore char and exit ; ; INST3 - accumulate message text and build checksum ; accept all but characters ; INST3: call incsm ; accumulate checksum cmpb #dle, r2 ; is dle? bne 10$ ; no - br mov #inst4, a.ist(r0) ; take transition jmp INXT1 ; ignore char and exit 10$: jmp INXT ; accept char and exit ; ; INST4 - embedded seen in text ; Next char should be : ; - put ONE dle in buffer ; - end of text - complete message ; *any* - illegal - complete with error ; ; The protocol defines any embedded DLE sequence inside a message that is not ; or to be an error. This includes , ; which could show up if an end of message was not detected. ; INST4: call incsm ; accumulate checksum cmpb #dle, r2 ; ? bne 10$ ; no - br mov #inst3, a.ist(r0) ; transition to normal input jmp inxt ; accept char and exit 10$: cmpb #etx, r2 ; end of message? bne 20$ ; no - br mov #inst5, a.ist(r0) ; transition to bcc br inxt1 ; ignore char and exit 20$: ; *any* is an error movb #ie.ies, a.ios(r0) ; return error code in low byte movb r2, (r0) ; and invalid sequence value in high byte jmp infin1 ; ignore char and complete ; ; INST5 - get first checksum byte ; first, check for odd message length, and ; do checksum with null byte to even it out if necessary ; INST5: bit #as.odd, a.isb(r0) ; odd checksum? beq 10$ ; no - br push clr r2 call incsm ; even out checksum pop 10$: cmpb r2, a.ics(r0) ; compare lo checksum .if df goodcs br 20$ ; force good checksum .iff beq 20$ ; ok - br .endc clr a.ios(r0) ; return error - clear high byte movb #ie.cks, a.ios(r0) ; bad checksum error code into low byte mov #inst1, a.ist(r0) ; reset machine jmp infin1 ; reject char and complete 20$: mov #inst6, a.ist(r0) ; transition jmp inxt1 ; reject char and exit ; ; INST6 - get second checksum byte and complete io ; INST6: mov #is.suc, a.ios(r0) ; assume success mov inst1, a.ist(r0) ; reset machine cmpb r2, a.ics+1(r0) ; check hi checksum .if df goodcs br 10$ ; force good checksum .iff beq 10$ ; ok - br .endc clr a.ios(r0) ; return error - clear high byte movb #ie.cks, a.ios(r0) ; bad checksum error code into low byte 10$: jmp infin1 ; reject char and complete ;**** End of Input States - utiltity routines follow INXT1: bic #ua.put, uaflg(r5) ; say reject character INXT: return ; intxt takes defaults INFIN: bis #ua.put!ua.com, uaflg(r5) ; accept and complete return INFIN1: bis #ua.com, uaflg(r5) ; (reject, force completion) bic #ua.put, uaflg(r5) return .sbttl INCSM - Input Checksum Processing ; INCSM - Input Checksum Processing ; ; This routine accumulates the input checksum. ; ; INPUT: ; r0 -> ACB ; r2 = input character ; ; OUTPUT: ; checksum accumulated in a.ics of ACB ; ; REGISTERS: ; preserved ; INCSM: push mov #as.odd, r1 xor r1,a.isb(r0) ; toggle even/odd byte flag bne 100$ ; was even - br ; ; odd (high) byte ; swab r2 ; get char in high byte bic #377, r2 ; zap low bits xor r2, a.ics(r0) ; add into checksum asl a.ics(r0) ; rotate checksum bcc 50$ ; bis #1, a.ics(r0) ; propogate carry 50$: swab r2 ; restore r2 br 1000$ ; exit ; ; even (low) byte ; 100$: bic #^C377, r2 ; clear high bits xor r2, a.ics(r0) ; accumulate checksum 1000$: pop return .sbttl EXPOUT - Output Buffer Expansion .rem | Output processing is all done via the TRAENT - the output translation entry point. It calls EXPOUT which expands the user's buffer into our buffer, adding protocol and checksum. User buffer may contain a single character (one byte) that is assumed to be a DLE sequence, or a two or more byte transaction (since a transaction code is always two bytes, transaction must be two or more bytes long). The only DLE sequences that are recognized are and . All others will be rejected as errors. Expansion of a DLE sequence, means that a character will be placed in front of the user supplied character. No checksum is used on DLE sequences. Expansion of a transaction means that a sequence will be placed in front of the user supplied transaction, a sequence will be placed behind the user supplied transaction, and a checksum (two bytes) will be placed after the . Any characters inside the transaction text will be replaced by sequences. INPUT: (to the machine) R0 -> ACB R1 = user buffer length R2 -> user buffer (mapped thru APR6) R3 -> io packet R4 R5 -> ucb+u.tsta Offsets in ACB: a.smap = mapping for ACD buffer a.sbuf -> ACD buffer (APR5 mapping) a.slen = 0 OUTPUT: (from the machine) R0 -> ACB R1 = 0 R2 -> past user buffer R3 -> io packet R4 R5 -> ucb+u.tsta Offsets in ACB: a.smap = mapping for ACD buffer a.sbuf -> ACD buffer a.slen = length to send out REGISTERS: r1,r2 trashed others preserved NOTE: If the ACD buffer overflows, or other errors (less than two bytes, or invalid DLE sequence) occur, we must still supply something to output, since we can't abort the QIO at this (TRAENT) point. In this case, the output buffer contains a null character and a.slen = 1. The error code is in a.osb of the iosb. | EXPOUT: push mov a.sbuf(r0), r4 ; point to output buffer clr a.osb(r0) ; initialize output status clr a.ocs(r0) ; initialize output checksum mov #, r5 ; initilize output buffer size cmp r1, #1 ; check user buffer length blo expovf ; branch if less than 1 chars, not possible bhi 20$ ; branch if more than 1, not a DLE sequence cmpb #ack, (r2) ; see if it is a DLE ACK sequence beq 10$ ; branch if yes cmpb #nak, (r2) ; see if it is a DLE NAK sequence bne expies ; branch if not, no other DLE sequence allowed 10$: movb #dle,(r4)+ ; transfer DLE sequence to output buffer movb (r2)+,(r4)+ ; adding a DLE and the user supplied char br 400$ ; no additional formatting or checksum needed 20$: movb #dle, (r4)+ ; standard transaction, stuff movb #stx, (r4)+ tst r1 ; any user length? beq 250$ ; no - br (? we're just doing our job) 100$: movb (r2)+, r3 ; get a char movb r3, (r4)+ ; put in output buffer call oucsm ; process checksum dec r5 ; out of room? beq expovf ; yes - error out cmpb #dle, r3 ; is it dle? bne 200$ ; no - br movb r3, (r4)+ ; put in output buffer again call oucsm ; process checksum again dec r5 ; out of room? beq expovf ; yes - error out 200$: dec r1 ; drop user buffer count bne 100$ ; loop till done ; ; stuff in suffix ; 250$: movb #dle, r3 movb r3, (r4)+ ; stuff in output buffer call oucsm ; process checksum dec r5 ; out of room? beq expovf ; yes - error out movb #etx, r3 movb r3, (r4)+ ; stuff in output buffer call oucsm ; process checksum dec r5 ; out of room? beq expovf ; yes - error out ; ; finish up checksum ; bit #as.odd, a.osb(r0) ; finishing up on an odd byte? beq 300$ ; no - br clr r3 ; feed oucsm a null to even up call oucsm ; ; stuff the two checksum bytes ; 300$: movb a.ocs(r0), (r4)+ dec r5 ; out of room? beq expovf ; yes - error out movb a.ocs+1(r0), (r4)+ ; ; compute real output length, and finish up ; 400$: sub a.buf(r0), r4 ; get length mov r4, a.slen(r0) ; put in acb mov a.buf(r0), a.sbuf(r0) ; restore buffer pointer mov #is.suc, a.osb(r0) ; show success to OUTENT pop return ; ; If we have a two byte user output that is not a valid DLE sequence, ; an error is declared. expies: clr a.osb(r0) ; return error - clear high byte movb #ie.ies, a.osb(r0) ; illegal escape sequence code low byte br expnul ; send out null only ; ; If we exhaust the ACD output buffer, an error is declared. expovf: clr a.osb(r0) ; return error - clear high byte movb #ie.rbg, a.osb(r0) ; illegal record size code low byte ; Since TTDRV is GOING to send something, we fake out a single ; null character. Error indication is in a.osb(r0). ; expnul: mov a.buf(r0), a.sbuf(r0) ; reset output buffer pointer clr @a.sbuf(r0) ; get null bytes mov #1, a.slen(r0) ; say length = 1 pop return .sbttl OUCSM - Compute Output checksum ; ; OUCSM - Compute Output Checksum ; ; INPUT: ; R3 = character to output ; R0 -> ACB ; ; OUTPUT: ; ; ACTION: ; Checksum updated ; ; REGISTERS: ; preserved ; OUCSM: push mov #as.odd, r1 xor r1, a.osb(r0) ; toggle even/odd byte bne 100$ ; was even - br ; ; odd (high) byte ; swab r3 ; get in high byte bic #377, r3 ; zap low bits xor r3, a.ocs(r0) ; add into checksum asl a.ocs(r0) ; rotate checksum bcc 50$ ; bis #1, a.ocs(r0) ; propogate carry 50$: swab r3 ; restore r3 br 1000$ ; take common exit ; ; even (low) byte ; 100$: bic #^C377, r3 ; zap hi byte xor r3, a.ocs(r0) ; add into checksum 1000$: pop return .END