Google
 

Trailing-Edge - PDP-10 Archives - BB-P363B-SM_1985 - mcb/drivers/le.m11
There are no other files named le.m11 in the archive.
.enabl	lc
.title	LE	LP11 LLC and Driver
.ident	"X02090"

;                    COPYRIGHT (c) 1980, 1981, 1982
;                    DIGITAL EQUIPMENT CORPORATION
;                        Maynard, Massachusetts
;
;     This software is furnished under a license and may  be  used
;     and copied only in accordance with the terms of such license
;     and with the inclusion of the above copyright notice.   This
;     software  or any other copies thereof may not be provided or
;     otherwise made available to any other person.  No  title  to
;     and ownership of the software is hereby transferred.
;
;     The information  in  this  software  is  subject  to  change
;     without  notice  and should not be construed as a commitment
;     by DIGITAL EQUIPMENT CORPORATION.
;
;     DIGITAL assumes no responsibility for the use or reliability
;     of  its  software  on  equipment  which  is  not supplied by
;     DIGITAL.
;
;
; Originally written by	TOM POWERS
;		December, 1976
;
; X01010 thru X01100 omitted because following update constituted
;		such a major change that previous audit trail
;		was rendered meaningless - or at best useless.
;
; X02000	10-June-81 Buren Hoffman
;		Extensive modifications to correspond to MCB V3.0,
;		and a major facelift to aid maintainability.
;
; X02010	12-June-81	Buren Hoffman
;		Fixed old calls to $CCBGT and $CCBRT to use new
;		$CBBGT and $CBBRT.
;
; X02020	16-June-81	Buren Hoffman
;		Added NOOP dispatches for Power-Failure and Initialization
;		entries in the Timer Dispatch table.
;
; X02030	16-June-81	Buren Hoffman
;		Minor bug fixes (i.e., typos, ...)
;
; X02040	18-June-81	Buren Hoffman
;		Call TIMPIN routine for process initialization
;
; X02050	9-July-81	Buren Hoffman
;		Simplified interrupt handling routine by removing
;		compression-pair handling, and restructuring routine.
;
; X02060	12-July-81	Buren Hoffman
;		Fixed bug in interrupt handler - device was being force fed
;		whether it wanted it or not.
;
; X02070	15-July-81	Buren Hoffman
;		Corrected problem in context switch.  Register was being
;		clobbered.
;
; X02080	5-August-81	Buren Hoffman
;		Reduced number of LPT physical buffers from 12 to 6.
;
; x02090	14-sept-81	Ron Platukis
;		Fix routine SN$ATT to not set overprint error if there
;		-was a page limit overflow.
;
.sbttl	Macro Calls & Dispatch Entries

.mcall	NURDF$,CCBDF$,SYNDF$,SCDF$
.mcall	PHD$B,DSP$,DSP$B,DSP$E,PUSH$S,POP$S,MAP$,SMAP$,DAT$,END$
	NURDF$
	CCBDF$
	SYNDF$
	SCDF$

;+
;  Identify us to MCB
;+
	PHD$B	LE
	PHD$D	LEMCB
	PHD$E

.psect	$PLIT$,D,RO

;+
; Dispatch tables
;-
LEMCB:	DSP$B
	 DSP$				; Asynchronous trap
	 DSP$	FC.XME			; Xmit enable
	 DSP$	FC.RCE			; Receive enable
	 DSP$	FC.KIL			; Kill
	 DSP$	FC.CTL,LECTL		; Control		(start / stop)
	 DSP$	FC.TIM,LETIM		; Timeout		(clock)
	 DSP$	FC.XCP,LEDONE		; Xmit complete		(resource ret)
	 DSP$	FC.RCP,LEDSP		; Receive complete	(arriving data)
	 DSP$	FC.KCP			; Kill complete
	 DSP$	FC.CCP			; Control complete
	DSP$E

;+
; Control dispatch
;-
LECTL:	CALLR	@1$(R3)
1$:	DSP$B
	 DSP$	
	 DSP$	FM.STR,DVOPEN		;Start device
	 DSP$	FM.STP,DVCLOS		;Stop device
	DSP$E

;+
; Timer dispatch
;-
LETIM:	CALLR	@1$(R3)
1$:	DSP$B
	 DSP$	FM.STM			;Short timer
	 DSP$	FM.LTM,CLOCK		;Long timer
	 DSP$	FM.PWF,NOOP		;Power failure
	 DSP$	FM.PIN,TIMPIN		;Initialization
	DSP$E
.SBTTL	LE Data Base Offsets

; NURD LP11 Device driver Data Base

	DAT$	DSR
L.TIMC:	.blkb	1		; Timer byte
L.NRD:	.blkb	1		; NRD's PIX
L.CSR:	CSR$W			; One word CSR pointer
	DTXTSZ= 16.		; Data/features process context allocated
L.DTXT:	.blkw	DTXTSZ		; Context block for data/features process
L.DTXL:	.blkb	1		; Number of words on stack at sleep time
	.even
L.DFSP:	.blkw	1		; Stack origin
L.SYN:	SYN$X			; Synch block
L.ASTS:	.blkw	1		; Action status bit map
	LA.CLO== 1		; Closed
	LA.PAU = 2		; Paused
	LA.ADN = 4		; Abort when done with DDM queue
	LA.ABO = 10		; Aborting
	LA.ABE = 20		; Clear abort on EOF (else on clear)
	LA.CPC = 40		; Clear page counter enable
	LA.AKR = 100		; Acknowledge requested
	LA.ERR = 200		; An error has been noted
	LA.EOF = 400		; Current message has EOF
	LA.EOR = 1000		; End of record (EOR) encountered
	LA.CTL = 2000		; Control message to return
	LA.TMO = 4000		; Device time out
	LA.PLE = 10000		; Page limit error occurred
	LA.INT = 20000		; Interrupt in progress
L.PUBP:	.blkw	2		; Buffer pointer doubleword
L.PUBS:	.blkw	1		; Buffer size down counter
L.DDMQ:	LST$D			; CCBs queued for output
L.PGCT:	.blkw	1		; Physical page count
L.PGLM::.blkw	1		; Page limit register
L.PLNC:	.blkb	1		; Physical line count
L.HUNG:	.blkb	1		; Device hung timer

;  Miscellaneous items
L.IOCN:	.blkw	1		; Outstanding I/O count
L.IOWT:	.blkw	1		; Close wait flag (close CCB address)

;  Control message processing data base
L.CMSQ:	LST$D			; Control message queue
L.RECO:	.blkb	1		; Control message resource error flag
L.RATT:	.blkb	1		; Pending attention message flag - reason code
L.CAPP:	.blkb	1		; Pending capabilities message flag

;  Data & features processing data base
L.LNCT:	.blkb	1		; Line counter
L.COCT:	.blkb	1		; Column counter
	.even
L.DLCQ:	LST$D			; CCBs queued into DLC
L.MFLG:	.blkb	1		; Current NURD message flags
L.DMSQ:	.blkb	1		; Last good/current data sequence numbers
L.DMSG:	.blkb	1		; Data message segment count byte
L.DMSS:	.blkb	1		; Data message current segment size
L.CAPN:	.blkb	1		; Current index into capabilities list
L.NFSP:	.blkb	1		; No. feature specs in current message
	.even
L.DMCR:	.blkw	1		; Char repeat count, 2 bytes used as word
L.DMAD:	.blkw	2		; Data message address doubleword
L.DMBS:	.blkw	1		; Data message buffer size
L.PLBQ:	LST$D			; Free physical buffer queue
L.PLBP:	.blkw	2		; Character pointer doubleword
L.PLBS:	.blkw	1		; Buffer size down counter
L.PBRC:	.blkb	1		; Temp byte
	.even
L.MSGQ:	LST$D			; Outgoing message queue
L.MSGA:	.blkw	2		; Current message buffer address doubleword
L.MSGS:	.blkw	1		; Current message buffer size

; LP11 common feature list
L.CFET:	.blkb	1		; Number of common features 
	.blkb	1		; FE.DAT     supported
	.blkb	1		; FE.SER not supported
	.blkb	1		; FE.LCR     supported
	.blkb	1		; FE.FWD     supported
	.blkb	1		; FE.EOF not supported
	.blkb	1		; FE.DVT not supported
	.blkb	1		; FE.TRN     supported
	.blkb	1		; FE.FNM not supported
	.blkb	1		; FE.DWD     supported
	LCFETL== .-L.CFET-1

L.DFET:	.blkb	1		; Number of device specific features
	.blkb	1		; 129    not defined
	.blkb	1		; LP.HT      supported
	.blkb	1		; LP.SFC     supported
	.blkb	1		; LP.OVP not supported
	.blkb	1		; LP.CVF not supported
	.blkb	1		; LP.FCC     supported
	.blkb	1		; LP.VFR not supported
	.blkb	1		; LP.CHS     supported
	.blkb	1		; LP.PLE     supported
	.blkb	1		; LP.OPV not supported
	LDFETL== .-L.DFET-1

; Feature blocks
F.FSB = 0			; (BM) Features status byte
	FB.CL0 = 0		; Class 0 feature => <bit> form
	FB.CL1== 1		; Class 1 feature => <cnt><...> form
	FB.WRD = 2		; Feature is a word and F.FCV is offset to it
	FB.STR = 4		; Feature is a string and F.FCV is the length
				;   current value begins at F.FCV+1
				; Std value(if any) begins at F.FCV+1+(F.FCV)
	FB.STE = 10		; Standard value exists
	FB.SST = 20		; Standard is set
	FB.CP = 40		; Change pending
	FB.CA = 100		; Change allowed
F.FCV = 1			; Feature current value byte
F.FSV = 2			; Feature standard value byte

LE.DAT::.blkb	1			; Data mode
	LEDATS== FB.CL1!FB.SST!FB.STE!FB.CA ; Initial status of LE.DOF & LE.DAT
	.blkb	1			; ASCII
	.blkb	1			; ASCII is standard
LE.LCR::.blkb	1			; Lower case raise
	LELCRS== FB.CL0!FB.CA		; Initial status of LE.LCR
	.blkb	1			; Off
LE.FWD::.blkb	1			; Form width
	.blkb	1
LE.TRN::.blkb	1			; Record truncation
	LETRNS== FB.CL0!FB.CA!FB.SST!FB.STE ; Initial status of LE.TRN
	.blkb	1			; Off
	.blkb	1			; Off is standard
LE.DWD::.blkb	1			; Device width
	.blkb	1			; Standard width

; Device specific features
LE.HT::	.blkb	1			; Horizontal tab
	LEHTS== FB.CL1!FB.SST!FB.STE	; Initial status of LE.HT
	.blkb	1			; Standard tabs on
	.blkb	1
LE.SFC::.blkb	1			; Standard VFU
	LESFCS== FB.CL0!FB.SST!FB.STE	; Initial status of LE.SFC
	.blkb	1			; On
	.blkb	1
LE.FCC::.blkb	1			; Fortran carriage control
	LEFCCS== FB.CL0!FB.CA!FB.SST!FB.STE ; Initial status of LE.FCC
	.blkb	1			; Off
	.blkb	1
LE.CHS::.blkb	1			; Character set
	.blkb	1			; 96 Character ASCII
LE.PLE::.blkb	1			; Page limit enforcement
	LEPLES== FB.CL1!FB.WRD!FB.CA!FB.SST!FB.STE ; Initial status of LE.PLE
	.blkb	1			; Page counter
	.blkb	2			; Standard = 0 => Off

	END$

.psect	$CODE$,I,RO
.sbttl	Start / Stop Device  (FC.CTL)
;+
; Initialize unit
;	R4	CCB
;	R5	Database
;-
DVOPEN:	PUSH$S	R4			; Save incoming CCB pointer
	BIT	#LA.CLO,L.ASTS(R5)	; Closed?
	BEQ	101$			; No, then how can it be opened?
	MOVB	C.PIX(R4),L.NRD(R5)	; Remember NRD's PIX
	CLR	@L.CSR(R5)		; Be very pessimistic
	CLR	L.ASTS(R5)		; Zap the action status word
	CLR	L.PUBS(R5)		; Flush current print buffer
	CLRB	L.DTXL(R5)		; Nothing waiting
	CLR	L.DMAD(R5)		; Flush source buffer in use
	CLR	L.DMAD+2(R5)
	CLR	L.DMBS(R5)
	CLRB	L.DMSQ(R5)		; The data message sequence number
	CLR	L.MSGA(R5)		; Current out message
	CLR	L.MSGA+2(R5)
	CLR	L.MSGS(R5)
	CLR	L.IOCN(R5)		; OUTSTANDING IO COUNT
	CLR	L.IOWT(R5)		; CLOSE-WAIT FLAG
	CLRB	L.HUNG(R5)		; HUNG DEVICE TIMER
	CLRB	L.RECO(R5)		; CONTROL RESOURCE ERROR FLAG
	CLRB	L.RATT(R5)		; PENDING ATTENTION MESSAGE FLAG
	CLRB	L.CAPP(R5)		; PENDING CAPABILITIES MESSAGE FLAG
	CLR	L.PGCT(R5)		; THE PAGE COUNTER
	MOV	#DEL,R0			; INITIALIZE LP11 XLATION TABLE
	CLRB	LEASC(R0)		; SUPPRESS DEL
	DEC	R0

10$:	MOVB	R0,LEASC(R0)		; MOST OF TABLE IS ASCII TO ASCII
	SOB	R0,10$
	MOV	#US,R0			; SUPPRESS CONTROL CODES

12$:	CLRB	LEASC(R0)
	SOB	R0,12$
	CLRB	LEASC			; SUPPRESS NUL
	MOV	#FCXTB,R0		; GET ADDRESS OF EXCEPTION TABLE
15$:	MOVB	(R0)+,R1		; GET LEASC OFFSET
	BMI	20$			; DONE IF BYTE NEGATIVE
	MOVB	(R0)+,LEASC(R1)	; STUFF IN REPLACEMENT DATA
	BR	15$ 

; Set all features with standards to standard
20$:	MOV	#L.CFET,R0		; COMMON FEATURES
	CALL	200$			; DO ALL
	MOV	#L.DFET,R0		; DEVICE SPECIFIC FEATURES
	CALL	200$			; ALL OF THEM TOO
; INIT PHYSICAL BUFFER POOL
	MOV	#LEPBFS,R0		; RESERVE SEVERAL
55$:	CALL	$RDBGT			; GET A PHYSICAL BUFFER
	BCS	70$			; LOSE
	CLR	C.STS(R4)		; INIT STATUS
	CLR	C.STK(R4)		; CLEAR CCB POINTER
	CLRB	C.PIX(R4)	 	; MARK THE CCB AS STRICTLY LOCAL
	ENQ$	L.PLBQ			; QUEUE THE BUFFER TO THE FREE LIST
	SOB	R0,55$

70$:	TST	L.PLBQ(R5)		; FAIL TO OPEN IF NONE AVAILABLE
	BEQ	100$
	CLR	L.PLBP(R5)		; INIT PHYSICAL BUFFER IN USE
	CLR	L.PLBP+2(R5)
	CLR	L.PLBS(R5)
	MOV	#S.SSUC,R3		; RETURN SUCCESS
	INCB	(R5)			; START THE TIMER
	CALL	$LTMEN			; ...
	BR	104$

100$:	BIS	#LA.CLO,L.ASTS(R5)	; NOTE REMAINING CLOSED
101$:	MOV	#S.ERBO,R3		; FAILED TO OPEN

104$:	POP$S	R4			; RESTORE POINTER FOR COMMAND CCB
	BR	CTLXIT			; Leave


; Feature setting routine
200$:	ADD	R5,R0			; R0/FEATURE LIST OFFSET
	CLR	R1
	BISB	(R0)+,R1		; GET NO. OF FEATURES
210$:	CLR	R3
	BISB	(R0)+,R3		; GET ADDRESS OF FEATURE BLOCK
	BEQ	220$			; EMPTY SLOT
	ADD	R5,R3
	CALL	SETSTD			; SET IT TO STANDARD
220$:	SOB	R1,210$
NOOP:	RETURN
;+
; Stop unit
;
;	R4	CCB
;	R5	Database
;-

DVCLOS:	PUSH$S	R4			; SAVE COMMAND CCB POINTER
	CALL	$LESTP			; CLEAR PRINTER (SCREECHING HALT)
	CLRB	(R5)			; Stop timer
	BIS	#LA.CLO,L.ASTS(R5)	; MARK AS CLOSED
	CALL	Q$PURG			; FLUSH THE QUEUES

10$:	DEQ$	L.PLBQ,20$		; Get next physical buffer from queue
	CALL	$RDBRT			; Return it to system
	BR	10$			; Continue

20$:	DEQ$	L.CMSQ,30$		; FLUSH ANY OUTSTANDING CONTROL MESSAGES
	MOVB	#FC.RCE,C.FNC(R4)	;  and return them
	CALL	$SCHED			;   ...
	BR	20$

30$:	POP$S	R4			; RESTORE COMMAND CCB POINTER
	MOV	R4,L.IOWT(R5)		; SET CLOSE WAIT FLAG

; Complete close only when all CCB'S returned
STOPWT:	TST	L.IOCN(R5)
	BEQ	10$
	INCB	(R5)			; Restart timer
	RETURN

10$:	CLRB	(R5)			; STOP THE CLOCK
	MOV	L.IOWT(R5),R4		; GET THE CLOSE CCB BACK
	CLR	L.IOWT(R5)		; COVER OUR TRACKS
	MOV	#S.SSUC,R3

CTLXIT:	MOV	R3,C.STS(R4)		; Set status
	MOVB	#FC.CCP,C.FNC(R4)	;   and function for completion
	CALLR	$SCHED			; Leave
.sbttl	Timer handler  (FC.TIM)
;+
;	R5	Database address
;-

CLOCK:	TST	L.IOWT(R5)		; CHECK FOR CLOSE WAIT
	BNE	STOPWT			; PREEMPTS ALL ELSE
	TSTB	L.RECO(R5)		; CHECK CONTROL RESOURCE ERROR
	BEQ	10$
	CLRB	L.RECO(R5)		; YES - FLUSH THE FLAG
	CALL	MSGFIN			; RESTART THE PROCESS

10$:	TSTB	L.DTXL(R5)		; Anything waiting for tick?
	BEQ	20$			; Appears not
	CALL	ZZ$WAK			; Yes, resume it - it will return here

20$:	BIT	#LA.ERR,L.ASTS(R5)	; CHECK DEVICE PROBLEMS
	BEQ	30$			; NOT BECAUSE OF DEVICE ERROR
	TST	@L.CSR(R5)		; DEVICE ERROR WAS REASON
	BMI	30$			; STILL HAVING PROBLEMS
	CALL	SN$ALR			; CURE HAS BEEN EFFECTED, NOTIFY USER
	BCS	30$			; COULDN'T SEND ONE, COME BACK LATER
	BIC	#LA.ERR,L.ASTS(R5)	; FLUSH ERROR BUT LEAVE LA.PAU
					; USER MUST RESUME

30$:	TSTB	L.HUNG(R5)		; CHECK HUNG TIMER
	BEQ	50$			; NOT SET
	DECB	L.HUNG(R5)		; COUNT THE TOCK
	BNE	50$			; STILL OK
	BIS	#LA.TMO,L.ASTS(R5)	; DEVICE IS COMATOSE
	TSTB	L.RATT(R5)		; CHECK PENDING ATTENTION MESSAGE
	BNE	55$			; YES - DON'T GENERATE ANOTHER
	MOVB	#N.ASTC,L.RATT(R5)	; SET PENDING ATTENTION FLAG
					;   (REASON CODE)

50$:	TSTB	L.RATT(R5)		; CHECK PENDING ATTENTION MESSAGE
	BEQ	60$
55$:	CALL	SN$ATT			; TRY TO SEND ONE
60$:	TSTB	L.CAPP(R5)		; CHECK PENDING CAPABILITIES MESSAGE
	BEQ	70$
	CALL	SN$CAP			; TRY TO SEND ONE
70$:	INCB	(R5)			; RESET THE CLOCK FOR NEXT TOCK
	RETURN
.sbttl	Resource Return
;+
; Resource return
;-
LEDONE:	DEC	L.IOCN(R5)		; COUNT THE RETURNED RESOURCE
	BGE	10$
	CLR	L.IOCN(R5)		; BLEW IT SOMEWHERE
10$:	MOV	#80.,R1			; SET SIZE OF RETURNING BUFFER
	CALLR	$CBBRT			; SEND IT BACK, AND CCB TOO
.sbttl	Message Received  (FC.RCP)
;+
; Dispatch according to the type of NURD message connected to the
; current CCB chain, which consists of but a single message.
;
;  Inputs:	R4 - Points to first (or only) CCB in chain
;		R5 - Points to LE data base
;
;  Outputs:	Dispatches to message handler, or returns an
;		error if the type is illegal or out of range.
;		The called routine is responsible for queuing
;		messages to be returned to the higher level.
;
;  Message Format:	<msgtype><msgflgs><msgtxt.....>
;-

LEDSP:	BIT	#LA.CLO,L.ASTS(R5)	; DEVICE CLOSED?
	BNE	60$			; YES, HENCE INACCESSIBLE
	MAP$	C.BUF(R4)		; GET MAPPING
	MOV	C.BUF+2(R4),R0		; POINT TO CCB DATA BUFFER
	MOVB	(R0),R1			; GET NURD MESSAGE TYPE
	BIC	#^C<NM.TYP>,R1		; ISOLATE JUST MESSAGE TYPE
	CMP	R1,#NRDOTR		; TYPE OUT OF RANGE?
	BLO	30$			; NO, OKAY TO DISPATCH
	MOV	#NRDOTR,R1		; YES, SPECIFY ILLEGAL NURD HANDLER
30$:	ASL	R1			; GET WORD OFFSET
	CALLR	@40$(R1)		; DISPATCH TO MESSAGE PROCESSOR
					;   AND RETURN


40$:	DSP$B
	DSP$	,MSGDAT			;  0 - DATA MESSAGE
	DSP$	,50$			;  1 - ATTENTION (ILLEGAL IF REC'D)
	DSP$	,MSGDAT			;  2 - FEATURES MESSAGE
	DSP$	,MSGCTL			;  3 - CONTROL MESSAGE
  NRDOTR = <.-40$>/2
	DSP$	,50$			;  N - ILLEGAL NURD TYPE SPEC
	DSP$E

50$:	MOV	#S.EIDM,C.STS(R4)
	BR	70$
60$:	MOV	#S.EABO,C.STS(R4)
70$:	MOVB	#FC.RCE,C.FNC(R4)
	CALLR	$SCHED
;+
; Queue Incoming NURD Data, Control and Features Messages
;
;	R4	CCB to queue
;	R5	Database address
;-

MSGDAT:	BIT	#LA.ABO,L.ASTS(R5)	; Check for abort state
	BEQ	MSGQUE			; Ok to proceed
	MOV	#S.EABO,C.STS(R4)	; Aborting
MSGX:	MOVB	#FC.RCE,C.FNC(R4)	; ...
	CALLR	$SCHED

MSGQUE:	ENQ$	L.DLCQ
	CMP	R4,L.DLCQ(R5)		; Was queue empty ?
	BNE	35$			; No, routines of interest are working
	TSTB	L.DTXL(R5)		; Maybe waiting for packet ?
	BNE	35$			; Could be

30$:	MOV	SP,L.DFSP(R5)		; Set the stack origin for this process
	CALL	RD$BYT			; Extract message type
	MOV	R0,R1
	CALL	RD$BYT			; Extract message flags
	MOV	R0,L.MFLG(R5)
	ASL	R1			; Make a dispatch
	CALL	@40$(R1)		;   and go
	MOV	L.DLCQ(R5),R4		; Get next message
	BNE	30$			; ...
35$:	RETURN				; Done

40$:	DSP$B
	DSP$	,DATSYN			;  0 - Data message
	DSP$	,MSGX			;  1 - Attention (illegal if rec'd)
	DSP$	,FTRSYN			;  2 - Features message
	DSP$	,CTLSYN			;  3 - Control message
	DSP$E
;+
; NURD Data Message Handler
;
; Virtual (NURD) to physical (LP11) data translation.
; Unload NURD buffer into physical buffer and ship physical buffers
; down to driver as they are filled, saving virtual machine buffer
; address in C.PRM5 of the physical buffer until the corresponding
; physical data has been output or aborted.
;
; This routine is called from MSGDAT at the main entry, and entered at
; various recovery points, depending upon the nature of the failure
; which caused the suspension.
;
; Source buffer has been set up and NURD type and message flag bytes
; (1st & 2nd bytes) have been extracted.
;
;	R4	CCB
;	R5	Database address
;
;	Data message format:	<seq no.><flgs><seg cnt><segs......>
;	Data seg format:	<cnt><txt....> or <sign+cnt><repeat char>
;-

DATSYN:	CALL	RD$BYT			; GET SEQUENCE NUMBER
	MOVB	R0,L.DMSQ(R5)		; SAVE THE CURRENT SEQ NUMBER
	CALL	RD$BYT			; GET THE FLAGS BYTE
	BIT	#ND.ACK,R0		; CHECK FOR ACK REQ'D
	BEQ	14$			; NO ACK REQUEST
	BIS	#LA.AKR,L.ASTS(R5)	; NOTE ACK REQUESTED
14$:	BIT	#ND.EOF,R0		; CHECK FOR EOF
	BEQ	16$
	BIS	#LA.EOF,L.ASTS(R5)	; SET THIS MESSAGE HAS EOF
16$:	CALL	RD$BYT			; GET SEGMENT COUNT BYTE
	MOVB	R0,L.DMSG(R5)		; SAVE THE SEGMENT COUNT OF MESSAGE
	BEQ	60$			; EMPTY MESSAGE, SKIP DATA PROCESSING

; Process message data
20$:	CALL	RD$BYT			; GET NEXT SEGMENT HDR BYTE
	TSTB	R0			; DISPATCH BASED ON SEGMENT TYPE
	BMI	28$			; THIS IS A COMPRESSED SEGMENT
	BGT	32$			; THIS IS A NORMAL DATA SEGMENT
; This is an EOR
	TSTB	LE.FCC+F.FCV(R5)	; IS FORTRAN CARRIAGE CONTROL SET?
	BEQ	24$			; NO, EOR IS THEN NOT SIGNIFICANT
	BIS	#LA.EOR,L.ASTS(R5)	; YES, SET EOR TO FLAG NEXT CHAR AS CC
24$:	BR	56$			; PROCESS END OF SEGMENT

; Process compressed data segment
28$:	MOVB	#1,L.DMSS(R5)		; Set segment size to one character
	BIC	#177600,R0		; Isolate repeat count
	MOV	R0,L.DMCR(R5)		;  and save it
	BR	36$			; Process data in normal way

; Process normal data segment
32$:	MOVB	R0,L.DMSS(R5)		; Set segment size
	CLR	L.DMCR(R5)		; No repeat count

;Normal segment processor
36$:	CALL	RD$BYT			; Get character data byte from message
	BIC	#^C177,R0		; SAVE ONLY LOW SEVEN BITS OF CHARACTER
	MOVB	LEASC(R0),R0		; GET MAPPED VALUE OF THE CHARACTER
	MOVB	R0,L.PBRC(R5)		; SAVE A COPY OF THE CHARACTER

40$:	CLR	R0			; GET THE BYTE BACK
	BISB	L.PBRC(R5),R0
	BIT	#LA.EOR,L.ASTS(R5)	; END OF RECORD ON?
	BEQ	42$			; NO, PROCEED WITHOUT INCIDENT
					; YES, BYTE IS FORTRAN CARRIAGE CHAR
	CALL	FORM$F			; HANDLE FORTRAN CARRIAGE CONTROL
	BIC	#LA.EOR,L.ASTS(R5)	; FLUSH END OF RECORD
	BR	52$			; OKAY, DO NEXT BYTE

42$:	TSTB	R0			; CHECK FOR CHARACTER TYPE
	BEQ	54$			; IGNORE CHARACTERS MAPPED TO ZERO
	BPL	44$			; DIRECTLY TRANSMIT PRINTING CHARACTERS

; MANIPULATIONS REQ'D ON FORMS CONTROL CHARS
	CALL	FORM$X			; DO FORMS CONTROL TRANSLATION
	BR	52$			; SUCCEEDED, CONTINUE CURRENT PROCESSING

44$:	CALL	PR$CHR			; STUFF RESULTING CHAR TO OUTPUT
52$:	DEC	L.DMCR(R5)		; DECREMENT CHAR REPEAT COUNT
	BGT	40$			; MORE TO DO ON CURRENT CHAR

54$:	CLR	L.DMCR(R5)		; KEEP REPEAT CNT = 0
	DECB	L.DMSS(R5)		; DECREMENT SEGMENT SIZE
	BNE	36$			; SEGMENT NOT EMPTY, GET NEXT CHAR

; SEGMENT FINISHED, CLEAN UP
56$:	DECB	L.DMSG(R5)		; DECREMENT COUNT OF SEGMENTS
	BNE	20$			; NOT DONE, GO BACK FOR NEXT SEGMENT

; Message complete, drop into end-of-message code
60$:	MOV	L.PLBQ(R5),R4		; GET PHYSICAL BUFFER CCB ADDRESS
	BEQ	86$
	MOV	L.ASTS(R5),C.STS(R4)	; MARK PHYSICAL BUF
86$:	CALL	RD$DQX			; PURGE
	BIC	#LA.EOF!LA.AKR,L.ASTS(R5) ; CLEAR SAVED DATA FLAGS
	RETURN
;+
; NURD Control Message Handler
;
; Called from LEDSP
;
;	R0	Buffer pointer to NURD message type
;	R4	CCB
;	R5	Database
;	KISAR6	Mapped to buffer
;
; Control message format: <seq no.><command><result>
;-

.enabl	lsb

MSGCTL:	CMPB	#S$INT,C.MOD(R4)	; CHECK FOR INTERRUPT MESSAGE
	BEQ	10$			; Tis
	CALLR	MSGQUE			; OTHERS ARE SYNCH'D WITH DATA STREAM

10$:	ENQ$	L.CMSQ			; QUEUE THE CCB FOR LATER
	CMP	#N.CCMD+1,C.CNT(R4)	; CHECK TO SEE IF ENOUGH SENT
	BGT	100$			; NOT EVEN 4 LOUSY BYTES!
	MOVB	N.CCMD(R0),R1		; GET CONTROL COMMAND BYTE
	BLE	100$			; SKIP OUT ON ILLEGAL CODE
	CMP	#CTLLEN,R1		; CHECK RANGE OF COMMAND
	BLOS	100$			; OUT OF RANGE, SKIP OUT
	ASL	R1			; IN RANGE, GET AS WORD OFFSET
	CLR	R0			; INIT COMPLETION CODE
	CALL	@CTLDSP(R1)		; EXECUTE DETAILED CONTROL ROUTINE
	CMP	R0,#-1			; MORE TO DO ?
	BEQ	15$			; NO
;+
; Here when finished, or when performing a retry to get a resource
;-
MSGFIN:	CALL	BF$SDB			; GET A RESPONSE BUFFER
	BCC	20$			; OK
	INCB	L.RECO(R5)
15$:	RETURN
20$:	PUSH$S	R4			; SAVE THE RESPONSE CCB
	DEQ$	L.CMSQ			; GET THE MESSAGE CCB BACK
	MAP$	C.BUF(R4)		; R0/RESULT
	MOV	C.BUF+2(R4),R1
	MOVB	N.CCMD(R1),R2		; R2/COMMAND
	MOVB	N.CSEQ(R1),R1		; R1/SEQ NO. OF CONTROL MESSAGE
	MOVB	#FC.RCE,C.FNC(R4)	; ACK the control message
	CALL	$SCHED			; ...
	POP$S	R4			; GET THE RESPONSE CCB BACK
	MAP$	C.BUF(R4)		; MAP TO THE BUFFER
	MOV	C.BUF+2(R4),R3		; GET BUF PTR
	MOVB	#NM.CTL,(R3)+		; FORMAT A NURD CONTROL MESSAGE
	CLRB	(R3)+			; NO MESSAGE FLAGS
	MOVB	R1,(R3)+		; SEQ NO.
	MOVB	R2,(R3)+		; COMMAND
	MOVB	R0,(R3)			; RESULT
	MOV	#N.CRES+1,C.CNT(R4)	; SET THE BUFFER COUNT
	INC	L.IOCN(R5)		; Count the I/O
	CALLR	$SCHED			; Queue it and return

; Illegal function
100$:	DEQ$	L.CMSQ			; Get the CCB back
	MOV	#S.ERBO,C.STS(R4)	; Reject this one
	MOVB	#FC.RCE,C.FNC(R4)	; ...
	CALL	$SCHED			; ...
	CALL	$LESTP			; Turn off the device
	BIS	#LA.ADN,L.ASTS(R5)	; Abort when print queue done
	MOV	#-1,R0			; Say we were here
	CALLR	$LESTR			; Turn on printer so it can die

;+
; The NURD control dispatch table
;-
CTLDSP:	.word	100$		;  0 - CODE ZERO IS RESERVED
	.word	CTLABE		;* 1 - ABORT TO END OF FILE
	.word	CTLABT		;* 2 - ABORT ALL
	.word	CTLABC		;  3 - ABORT CLEAR
	.word	CTLSTS		;  4 - STATUS REQUESTED, SEND ATTENTION
	.word	CTLDMP		;  5 - DUMP OUTPUT BUFFERS
	.word	CTLPAU		;* 6 - PAUSE
	.word	CTLRSM		;* 7 - RESUME FROM ERROR OR PAUSE
	.word	100$		;  8 - UNDEFINED
	.word	CTLCPB		;  9 - SEND CAPABILITIES MESSAGE
  CTLLEN = <. - CTLDSP>/2	;* = special action taken
.dsabl	lsb
;+
; Non-interrupt control message processing
;-
CTLSYN:	CALL	RD$BYT			; GET SEQ NO.
	MOVB	R0,L.DMSQ(R5)		; SAVE IT FOR RESPONSE
	CALL	RD$BYT			; GET COMMAND
	MOV	R0,R1
	CMP	#CTLLEN,R1		; CHECK RANGE OF COMMAND
	BLOS	50$			; OUT OF RANGE, SKIP OUT
	PUSH$S	R1			; SAVE COMMAND CODE FOR RESPONSE
	ASL	R1			; IN RANGE, GET AS WORD OFFSET
	CLR	R0			; INIT COMPLETION FLAG
	CALL	@CTLDSP(R1)		; Do it - return with R0 = result code
	POP$S	R1			; GET COMMAND CODE BACK
	CMP	R0,#-1			; MORE TO DO ?
	BEQ	60$			; NO
	PUSH$S	R0			; SAVE RESPONSE CODE
	CALL	RD$DQX			; Complete the message
	MOV	#NM.CTL,R0		; CONSTRUCT A CONTROL MESSAGE
	CALL	MS$BYT			; CONTROL CODE
	CLR	R0
	CALL	MS$BYT			; NULL MESSAGE FLAGS
	MOVB	L.DMSQ(R5),R0
	CALL	MS$BYT			; SEQ NO. OF ORIGINAL COMMAND
	MOV	R1,R0
	CALL	MS$BYT			; COMMAND
	POP$S	R0
	CALL	MS$BYT			; RESPONSE CODE
	DEQ$	L.MSGQ			; GET BUFFER OFF QUEUE
	SUB	L.MSGS(R5),C.CNT(R4)	; SET MESSAGE SIZE
	INC	L.IOCN(R5)		; ONE MORE OUTSTANDING I/O
	CALL	$SCHED			; SEND IT
	CLR	L.MSGS(R5)		; CLEAN UP TRACKS
	TSTB	L.RATT(R5)		; CHECK FOR OTHER MESSAGES TO GO OUT
	BEQ	20$			; NO ATTENTION MESSAGES
	CALL	SN$ATT			; PENDING ATTENTION MESSAGE

20$:	TSTB	L.CAPP(R5)		; TRY CAPABILITIES MESSAGE
	BNE	30$
	RETURN
30$:	CALLR	SN$CAP			; PENDING CAPABILITIES MESSAGE

50$:	CALL	$LESTP			; FLUSH THE DEVICE INTERRUPTS
	BIS	#LA.ADN,L.ASTS(R5)	; ABORT LINK WHEN PRINT QUEUE DONE
	CALL	RD$DQX			; Ack the message
60$:	RETURN
;+
; Clear ABORT status
;-
CTLABC:	BIT	#LA.ABE!LA.ABO,L.ASTS(R5) ; ABORT SET?
	BEQ	10$			; NO
	BIC	#LA.ABE!LA.ABO,L.ASTS(R5) ; CLEAR ABORTING
	RETURN

10$:	MOV	#NR.ACN,R0		; NOT IN ABORT STATE
	RETURN

;+
; Abort to end of file
;-
CTLABE:	MOV	#NR.NOE,R0		; EOF NOT DEFINED FOR OUTPUT DEVICE
	RETURN

;+
; Abort until clear received
;-
CTLABT:	CALL	$LESTP			; FLUSH THE DEVICE
	BIS	#LA.ABO,L.ASTS(R5)	; NOTE ABORTING
	MOV	L.DDMQ(R5),R1		; CHECK ALL QUEUES
	BIS	L.PLBP+2(R5),R1
	BIS	L.DLCQ(R5),R1
	BIS	L.MSGQ(R5),R1
	BISB	L.DTXL(R5),R1		; CHECK SUSPENDED PROCESSES
	BNE	10$			; NR.ABS (ABORT STARTED)
	MOV	#NR.NAB,R0		; NOTHING TO ABORT
10$:	CALLR	Q$PURG			; CLEAR THE WORLD

;+
; Request capabilities
;-
CTLCPB:	INCB	L.CAPP(R5)		; SET CAPABILITIES PENDING FLAG
	RETURN

;+
; Dump output buffers
;-
CTLDMP:	TSTB	L.RATT(R5)		; PENDING ATTENTION?
	BNE	10$			; YES
	TSTB	L.CAPP(R5)		; PENDING CAPABILITIES?
	BNE	10$			; YES
	TST	L.DDMQ(R5)		; NOT TO DLC, CHECK ON PHYSICAL QUEUE
	BNE	10$			; IT'S THERE, OKAY
	MOV	#NR.NOB,R0		; NOTHING TO DUMP
10$:	RETURN
;+
; Pause output
;-
CTLPAU:	BIT	#LA.PAU!LA.ADN,L.ASTS(R5) ; ALREADY PAUSED/PAUSING?
	BNE	20$			; YES
	TST	L.DLCQ(R5)		; ANYTHING QUEUED?
	BNE	10$			; YES, OKAY TO PAUSE
	TST	L.DDMQ(R5)		; NONE IN VIRTUAL, CHECK PHYSICAL
	BNE	10$
	MOV	#NR.NDP,R0		; NOTHING TO PAUSE
10$:	CALL	$LESTP			; ENSURE ONLY ONE ATTENTION MESSAGE
	BIS	#LA.PAU,L.ASTS(R5)	; PAUSE NO MATTER WHAT
	CALLR	$LEGO			; MAKE SURE IT HAPPENS

20$:	MOV	#NR.PAU,R0		; ALREADY PAUSED
	RETURN


;+
; RESUME OUTPUT
;-
CTLRSM:	BIT	#LA.PAU!LA.ADN,L.ASTS(R5) ; CHECK PAUSEDNESS
	BEQ	20$			; NOTHING HUNG
	BIC	#LA.PAU!LA.ADN!LA.ERR!LA.PLE,L.ASTS(R5)	; No pause conditions
	TST	L.DDMQ(R5)		; CHECK FOR ANY CURRENT DATA XFER'S
	BEQ	10$			; SOMETHING IN PRINT Q
	CALL	$LEGO			; TURN ON DEVICE AGAIN
	BR	30$
10$:	TST	L.DLCQ(R5)		; RESUME NON-INTERRUPT MESSAGE PROCESSING
	BNE	30$			; SOMETHING WAITING TO PROCESSED
	MOV	#NR.NDR,R0		; NOTHING TO RESUME
	BR	30$

20$:	MOV	#NR.NPS,R0		; NOT PAUSED
30$:	RETURN

;+
; Status request
;-
CTLSTS:	TSTB	L.RATT(R5)		; CHECK ATTENTION ALREADY PENDING
	BNE	10$			; DON'T MUNG ORIGNAL REASON
	MOVB	#N.ARQS,L.RATT(R5)	; SET ATTENTION PENDING FLAG
10$:	RETURN
;+
; NURD Features message handlers
;
; Feature message format:
;   <seq no.><no. feature specs><...feature specs...>
; 
; Feature spec format:
;   <feature id><feature flags><class><response>[<feature data>]
;
; Feature data format:
;   <cnt><...data...>
; 
; NOTE: Incoming message has only one feature spec.
; 
; Buffer has been set up and NURD type and flags bytes
; (1st & 2nd bytes) have been extracted.
; 
;	R4	CCB at top of L.DLCQ
;	R5	Database address
;	KISAR6	Mapped to data buffer
;-
	.enabl	lsb

FTRSYN:	MOV	#NM.FTR,R0		; BEGIN A REPLY
	CALL	MS$BYT			; INSERT NURD MESSAGE TYPE
	CLR	R0
	CALL	MS$BYT			; INSERT MESSAGE FLAGS
	CALL	RD$BYT			; GET SEQ NO.
	MOVB	R0,L.DMSQ(R5)
	CALL	MS$BYT			; SEND IT BACK
	CALL	RD$BYT			; GET NO. FEATURE SPECS
	CLR	R0			;  BUT IGNORE IT
	MOVB	R0,L.NFSP(R5)		; SAVE IT FOR SOMTHING
	CALL	MS$BYT			; STUFF IT IN REPLY
	CALL	RD$BYT			; GET FEATURE ID
	MOV	R0,R2
	CALL	RD$BYT			; GET FEATURE FLAGS
	MOV	R0,R1
	CALL	RD$BYT			; GET CLASS
	SWAB	R1
	BIS	R0,R1			; R1/FLAGS,,CLASS
	CALL	RD$BYT			; READ THE RESPONSE FIELD AND IGNORE IT

	CLRB	L.CAPN(R5)		; INIT INDEX INTO CAPABILITIES LIST
	CMPB	#FE.ALL,R2		; CHECK FOR ALLNESS
	BNE	30$			; SINGLE FEATURE
	CLR	R2			; INIT COMMON FID'S
	MOVB	L.CFET(R5),L.CAPN(R5)	; GET COUNT OF COMMON ENTRIES
	BIT	#NF.CMD*400,R1		; ALL FEATURES - CHECK READ/SET
	BEQ	10$			; READ - CONTINUE
	CMPB	#FC.SST,R1		; SET - CHECK SET TO STANDARD
	BNE	FERERR			; NO - ILLEGAL COMBINATION

10$:	INCB	R2			; ADVANCE THRU LIST
	BMI	15$			; GRUBBLING THRU DEVICE FEATURES
	CMPB	R2,L.CAPN(R5)		; CHECK RANGE
	BLOS	20$			; OK
	MOVB	L.DFET(R5),L.CAPN(R5)	; INIT FOR DEVICE SPECFIC FEATURES
	MOV	#201,R2
15$:	MOV	R2,R0			; DEVICE FEATURE - HACK OFF SIGN
	BIC	#^C177,R0
	CMPB	R0,L.CAPN(R5)		; CHECK RANGE
	BHI	70$			; DONE
20$:	CALL	FTRFND			; LOOK IT UP
	BCC	40$			; SUPPORTED FEATURE
	BR	10$			; TRY NEXT ONE

30$:	CALL	FTRFND			; LOOK UP FEATURE 
	BCS	FERUSF			; UNSUPPORTED FEATURE

; AT THIS POINT:
;	R1	Feature message-flags(hi) + class(lo)
;	R2	Feature id (byte)
;	R3	Points to feature status byte
;	R5	Database address
;	L.DMSQ	Sequence no.
;	L.NFSP	Number of specs

40$:	BITB	#FB.CP,(R3)		; CHECK FOR CHANGE PENDING
	BNE	FERCPN			; CAN'T READ OR SET IF SO
	BIT	#NF.CMD*400,R1		; FEATURE READ OR FEATURE SET?
	BEQ	FTRSHO			; READ - GO PROCESS FEATURE READ
	CALLR	FTRSET			; SET - GO PROCESS IN FEATURE SET
FTRCON:	TSTB	L.CAPN(R5)		; CHECK FOR ALLNESS
	BNE	10$			; YES

70$:	MOV	L.MSGQ(R5),R4		; GET BEG OF MESSAGE
	MAP$	C.BUF(R4)
	MOV	C.BUF+2(R4),R0		; GET THE PTR
	MOVB	L.NFSP(R5),N.NSPC(R0)	; SET IN THE FINAL SPEC COUNT
	CALL	RD$DQX			; RETURN ORGINAL MESSAGE TO NRD
	DEQ$	L.MSGQ			; GET BUFFER OFF QUEUE
	SUB	L.MSGS(R5),C.CNT(R4)	; SET MESSAGE SIZE
	INC	L.IOCN(R5)		; ONE MORE OUTSTANDING I/O
	CLR	L.MSGS(R5)		; CLEAN UP TRACKS
	CALLR	$SCHED			; SEND IT

FERUSF:	PUSH$S	#FR.USF
	BR	100$
FERBCL:	PUSH$S	#FR.BCL
	BR	100$
FERNST:	PUSH$S	#FR.NST
	BR	100$
FERERR:	PUSH$S	#FR.ERR
	BR	100$
FERCPN:	PUSH$S	#FR.CPN
	BR	100$
FERNEB:	PUSH$S	#FR.NEB
	BR	100$
FERDNP:	PUSH$S	#FR.DNP
100$:	MOV	R2,R0			; Insert <fid><flags><class><error>
	CALL	MS$BYT			; FID
	MOV	R1,R0
	SWAB	R0
	CALL	MS$BYT			; Flags
	MOV	R1,R0
	CALL	MS$BYT			; Class
	POP$S	R0
	CALL	MS$BYT			; Error
	INCB	L.NFSP(R5)		; Count the spec
	BR	FTRCON

.dsabl	lsb
;+
; Features Read
;
;	R1	Feature message-flags(hi) + class(lo)
;	R2	Feature id (byte)
;	R3	Points to feature status byte
;	R5	Database address
;
; Reply has been formatted thru <no. specs>
; Insert in message:
;	<fid><flgs><class><resp><data>
;-

FTRSHO:	MOV	R2,R0
	CALL	MS$BYT			; INSERT FID
	MOV	R1,R0			; GET READ/SET BIT
	SWAB	R0
	BIC	#^C<NF.CMD>,R0		; NOW ISOLATED
	BITB	#FB.SST,(R3)		; FEATURE SET TO STANDARD?
	BEQ	10$			; NO
	BIS	#NF.STD,R0		; SET TO STANDARD
10$:	CALL	MS$BYT			; INSERT FLAGS

	CLRB	R1			; SET FEATURE CLASS IN R1(LO)
	BITB	#FB.CL1,(R3)		; CHECK CLASS 1
	BEQ	15$			; CLASS 0
	BISB	#FC.CL1,R1		; SET CLASS 1
15$:	MOV	R1,R0
	CALL	MS$BYT			; INSERT CLASS

	CLR	R0
	CALL	MS$BYT			; RESP - NO ERRORS

	TSTB	R1			; CHECK CLASS FOR LENGTH OF DATA
	BEQ	20$			; 1 BIT
					; FEATURE DEPENDENT
	BITB	#FB.WRD!FB.STR,(R3)	; LOOK FOR THOSE MORE THAN 1 BYTE
	BNE	50$			; YES
					; NOT MULTIPLE BYTE DATA, SO 1 BYTE
	INC	R0			; COUNT = 1
	CALL	MS$BYT			; INSERT COUNT

20$:	MOVB	1(R3),R0		; COPY FEATURE TABLE DATA ENTRY
30$:	CALL	MS$BYT			; INSERT DATA

40$:	INCB	L.NFSP(R5)		; COUNT THE SPEC
	BR	FTRCON			; CONTINUE FEATURE HACKING

50$:	BITB	#FB.STR,(R3)		; WORD OR STRING ?
	BNE	60$			; STRING 
					; WORD, F.FCV = DB OFFSET TO WORD 
	MOV	#2,R0			; 2 BYTE VALUE
	CALL	MS$BYT			; INSERT CNT

	CLR	R0
	BISB	F.FCV(R3),R0
	ADD	R5,R0
	MOV	(R0),R0			; YES, READ SETTING FROM THE Database
	CALL	MS$BYT			; INSERT LO BYTE

	SWAB	R0
	BR	30$			; INSERT HI BYTE

; STRING - F.FCV HAS THE LENGTH,  CURRENT VALUE BEGINS AT F.FCV+1
60$:	INC	R3			; THIS DEPENDS ON F.FCV=1
	MOVB	(R3)+,R0		; GET THE LENGTH
	CALL	MS$BYT			; INSERT IN MESSAGE
	MOV	R0,R4			; SAVE IT
70$:	MOVB	(R3)+,R0		; INSERT THE STRING IN MESSAGE
	CALL	MS$BYT
	DECB	R4			; COUNT THE BYTE
	BNE	70$
	BR	40$
;+
; Features set
;
;	R1	Feature message-flags(hi) + class(lo)
;	R2	Feature id (byte)
;	R3	Points to the feature status byte
;	R5	Database address
;
; Reply has been formatted thru <no. specs=1>
;-

FTRSET:	CMPB	#FC.SST,R1		; CHECK STANDARDNESS
	BEQ	80$			; STANDARD RESULT
	BITB	#FB.CA,(R3)		; REAL SET - CHECK IF SETABLE
	BEQ	FERERR			; NO
	MOVB	(R3),R0			; FEATURE IS SETTABE
	XOR	R1,R0			; CHECK CLASS SPEC
	BIC	#^C<FB.CL1>,R0		; R0/ <CLASS SPEC'D>.NOT.EQUIV. <FEATURE CLASS>
	BNE	FERBCL			; RESULT SHOULD BE 0
	CMPB	#FC.CL1,R1		; CHECK DATA CLASS
	BNE	10$			; CLASS 0

; CLASS 1 - FEATURE DEPENDENT
	CALL	RD$BYT			; FEATURE DEPENDENT DATA - GET LENGTH
	BITB	#FB.WRD!FB.STR,(R3)	; CHECK MULTIPLE BYTE DATA
	BNE	30$			; GO DO IT
	CMPB	#1,R0			; ONE BYTER ?
	BNE	FERERR			; NO
10$:	CALL	RD$BYT			; CLASS 0 OR CLASS 1(1 BYTE), GET BYTE
	MOVB	R0,1(R3)		; SET FEATURE CURRENT VALUE
20$:	BICB	#FB.SST,(R3)		; NOT SET TO STANDARD
	BR	FTRSHO			; NOW READ THE FEATURE

30$:	BITB	#FB.STR,(R3)		; Word or string ?
	BNE	40$			; String
	CMPB	#2,R0			; 2 BYTE LENGTH
	BNE	50$			; SCREWED IT UP
	CALL	RD$BYT			; GET THE PAGE LIMIT
	MOVB	R0,L.PBRC(R5)		; HIDE THE LO PART 
	CALL	RD$BYT			; GET THE HI PART
	SWAB	R0
	BISB	L.PBRC(R5),R0		; R0/NEW WORD VALUE
	MOV	R0,-(SP)
	CLR	R0
	BISB	F.FCV(R3),R0
	ADD	R5,R0
	MOV	(SP)+,(R0)		; SET THE NEW VALUE IN WORD
	BR	20$

; STRING - F.FCV HAS THE LENGTH, CURRENT VALUE BEGINS AT F.FCV+1
40$:	PUSH$S	R3			; SAVE THE FEATURE BLOCK PTR
	INC	R3			; THIS DEPENDS ON F.FCV=1
	CMPB	R0,(R3)+		; CHECK THE LENGTH
	BEQ	60$			; OK
	POP$S	R3			; LENGTH ERROR - TO BEGINNING OF BLOCK
50$:	CALLR	FERERR			; AND REPORT THE ERROR
60$:	PUSH$S	R1			; SAVE THE VARIABLES
	MOV	R0,R1			; R1/COUNT, R3/PTR TO CURRENT VALUE
70$:	CALL	RD$BYT			; XFER NEW TO CURRENT VALUE
	MOVB	R0,(R3)+		; STUFF THE BYTE
	SOB	R1,70$			; AND COUNT IT
	POP$S	<R1,R3>
	CALLR	FTRSHO			; NOW READ THE FEATURE

80$:	CALL	SETSTD			; SET FEATURE TO STANDARD
	BCC	90$
	CALLR	FERNST			; NO STANDARD TO SET
90$:	CALLR	FTRCON			; DON'T READ IT
;+
; Set feature to its standard value
;
;	R3	Pointer to feature block in database
;	R5	Database address
;-

SETSTD:	BITB	#FB.STE,(R3)		; SEE IF IT HAS ONE
	BNE	10$			; YES
	SEC				; NO STANDARD TO SET
	RETURN

10$:	BITB	#FB.WRD!FB.STR,(R3)	; CHECK MULTIPLE BYTE VALUES
	BNE	20$			; YES
					; SINGLE BYTE VALUE
	MOVB	F.FSV(R3),F.FCV(R3)	; SET CURRENT VALUE = STANDARD VALUE
	BISB	#FB.SST,(R3)		; MARK FEATURE AS SET TO STANDARD
	BR	30$

; MULTIPLE BYTE VALUE
20$:	BITB	#FB.STR,(R3)		; Word or string ?
	BNE	40$			; STRING
	PUSH$S	R5			; WORD VALUE
	CLR	-(SP)
	MOVB	F.FCV(R3),(SP)
	ADD	(SP)+,R5		; R5 /PTR TO CURRENT VALUE
	MOVB	F.FSV(R3),(R5)+		; XFER LO BYTE
	MOVB	F.FSV+1(R3),(R5)	; XFER HI BYTE
	POP$S	R5

30$:	CLC
	RETURN

40$:	PUSH$S	<R0,R1,R3>		; STRING - F.FCV HAS THE LENGTH
					;   CURRENT VALUE BEGINS AT F.FCV+1
					; STANDARD VALUE BEGINS AT F.FCV+1+(F.FCV)
	INC	R3			; DEPENDS ON F.FCV=1
	CLR	R0
	BISB	(R3)+,R0		; GET THE COUNT
	MOV	R0,R1
	ADD	R3,R1			; R0/COUNT,R1/SOURCE ADDR,R3/DEST ADDR
45$:	MOVB	(R1)+,(R3)+		; XFER THE STANDARD VALUE
	SOB	R0,45$			; COUNT IT
	POP$S	<R3,R1,R0>
	CLC
	RETURN
;+
; Feature finder
;
; FTRFND looks for a feature with FID in R2 and returns a pointer
; to the feature block in R3, if successful.  Normal carry flag
; condition for success or failure.

FTRFND:	MOV	#L.CFET,R3		; TRY COMMON FEATURE 1ST
	PUSH$S	R2			; SAVE FID CAUSE IT WILL BE MANGLED
	TSTB	R2			; CHECK IF DEVICE SPECIFIC FTR
	BPL	10$			; COMMON, SKIP
	MOV	#L.DFET,R3		; DEVICE SPECIFIC, GET POINTER
	BIC	#177600,R2		; TRUNCATE TO JUST OFFSET ABOVE 128

10$:	BEQ	20$			; ZERO IS AN ILLEGAL FEATURE CODE
	ADD	R5,R3			; R3 -> FEATURE LIST IN DATABASE
	CMP	R2,(R3)			; CHECK ON ID RANGE
	BHIS	20$			; SKIP IF OUT OF RANGE
					; R3 POINTS TO BASE OF FEATURES LIST,
					; WHICH HOLDS HIGHEST STORED CODE
	ADD	R2,R3			; ADD FID TO POINT TO ADDR OF BLOCK
	MOVB	(R3),R3			; ENTRY IS DB OFFSET TO FEATURE BLOCK
	BEQ	20$			; UNSUPPORTED FEATURE
	BIC	#^C377,R3		; FLUSH SIGN EXTENSION
	ADD	R5,R3			; R3 -> FEATURE BLOCK
	POP$S	R2
	CLC
	RETURN

20$:	POP$S	R2			; UNSUPPORTED FEATURE
	SEC
	RETURN
.sbttl	Support Routines
;+
; The following pages, up to start of interrupt handler code,
; contain the various support routines needed in LE.
;
; The routines are arranged in alphabetical order.
;-




;+
; Physical Buffer Management
;-
BF$SDB:	PUSH$S	R1			; Save R1
	MOV	#80.,R1			; Size of buffer wanted
	CALL	$CBBGT			; Get a message buffer
	BCS	10$			; Oops
	MOV	R1,C.CNT(R4)		; Set size
	MOVB	#S$PEOM,C.PRM1+1(R4)	; Init as a whole message
	MOVB	#FC.XME,C.FNC(R4)	; Set function code
	MOVB	#S$SND,C.MOD(R4)	; Assume normal data
	MOVB	L.NRD(R5),C.PIX(R4)	; Set PIX
10$:	POP$S	R1			; Restore R1
	RETURN				;   and leave
;+
; Fortran carriage control processing
;
; Maps the first character of each "record" into one or more
; forms control  characters:
;  ** INPUT **      ** OUTPUT **
; 	Space		LF
; 	*		DC3
; 	+		CR
; 	-		DC3,DC3,LF
; 	/		DC4
; 	0		DC3,LF
; 	1		FF
; 	2		DLE
; 	3		VT
; 	Other		Space
;-
FORM$F:	MOV	#FCCTBL,R1		; SCAN TABLE FOR 1 CHARACTER XLATIONS
	MOV	#FCCTBN,R2
10$:	CMPB	(R1)+,R0
	BEQ	100$			; FOUND IT
	INC	R1			; SKIP EQUIVALENT
	SOB	R2,10$
	CMPB	#'-,R0			; CHECK MULTIPLE CHARACTER XLATIONS
	BEQ	20$
	CMPB	#'0,R0
	BEQ	30$
	MOV	#SPA,R0			; NOT AN FCC CHARACTER
	CALLR	PR$CHR			; JUST PRINT A SPACE
20$:	MOV	#3,R0			; CHECK FOR ROOM IN CURRENT BUFFER
	CALL	PR$SPC			; GET RID OF THIS BUFFER IF NOT ENOUGH
25$:	MOV	#DC3,R0			; DO DC3,DC3,LF
	CALL	110$			; PRINT THW DC3
30$:	MOV	#2,R0			; CHECK FOR ROOM IN CURRENT BUFFER
	CALL	PR$SPC			; GET RID OF THIS BUFFER IF NOT ENOUGH
35$:	MOV	#DC3,R0			; DO DC3,LF
	CALL	110$			; PRINT THE DC3
	CALLR	PR$LF			; DO A LINE FEED

100$:	MOVB	(R1),R0			; GET SINGLE CHARACTER EQUIVALENT
110$:	MOVB	LEASC(R0),R0		; Map the character to our set
	CALLR	FORM$X

FCCTBL:	.byte	' ,LF
	.byte	'*,DC3
	.byte	'+,CR
	.byte	'/,DC4
	.byte	'1,FF
	.byte	'2,DLE
	.byte	'3,VT
  FCCTBN=<.-FCCTBL>/2
;+
; Horizontal forms control translation
;
; R0/2*<translated character>
;-
FORM$H:	ASLB	R0			; TEST FOR TAB OR CR
	BPL	10$			; HT
	CLRB	L.COCT(R5)		; CLEAR COLUMN COUNT, A CARRIAGE RETURN
	MOVB	#CR,R0			; SET CR CODE
	CALLR	PR$BYT			; PUT IT DOWN

10$:	MOVB	L.COCT(R5),R0		; GET CURRENT COLUMN COUNT
	MOVB	R0,R3			; NO REAL LIST EXISTS, TAB BY 8'S
	BISB	#7,R3			; SET COUNT TO NEXT TAB STOP
	INCB	R3			;    PLUS 1
	CMPB	R3,LE.FWD+F.FCV(R5)	; STOP PAST FORM WIDTH?
	BLOS	30$			; NO (BYTES ARE ALL UNSIGNED NUMBERS)
	TSTB	LE.TRN+F.FCV(R5)	; YES, LINE TRUNCATION ON?
	BNE	20$			; YES, TAB TO JUST EOL
	CALL	PR$LF			; NO, WRAP TO NEXT LINE
	BR	10$			; NOW DO TAB

20$:	MOVB	LE.FWD+F.FCV(R5),R3	; SET TAB STOP JUST AT FORMS WIDTH
30$:	MOVB	R3,R2			; SAVE NEW COLUMN COUNT
	SUB	R0,R2			; COMPUTE NUMBER OF
	MOVB	#SPA,R0			;   SPACES TO DO
	BIC	#177400,R2		; PREVENT SIGN PROBLEMS
	BEQ	60$			; NONE! (CAN HAPPEN IF TRUNC ON AT EOL)

40$:	CALL	PR$BYT			; Output a space
	SOB	R2,40$			; DO UNTIL DONE

50$:	MOVB	R3,L.COCT(R5)		; TAB STOP VALUE IS NEW COLUMN COUNT
60$:	RETURN
;+
; Vertical forms control translation
;
; R0/2*<translated character>
; FCXTB has translation image of all forms control characters.
;-
FORM$V:	CLRB	L.COCT(R5)		; CLEAR COL COUNTER ON ALL VFE'S
	BIC	#177400,R0		; CLEAR HIGH BYTE OF R0
	BNE	10$			; NOT AN EXPLICIT FORM FEED
	CALLR	PR$FF			; EXPLICIT FORM FEED

10$:	MOV	CHMAP(R0),R0		; GET CHANNEL MAP
	MOVB	L.LNCT(R5),R1		; GET THE LINE COUNTER
	ASL	R1			;   AS A WORD OFFSET
	ADD	#VFU66,R1		; Calculate position in VFU
	CLR	R2			; Clear a line spacing counter

20$:	TST	(R1)+			; UP LINE POINTER, CHECK FOR END OF VFU
	BPL	30$			; NOT AT END OF VFU (MARKED AS MINUS)
	CALLR	PR$FF			; JUST DO AN FF AND QUIT

30$:	INC	R2			; UP THE LINE SPACING COUNTER
	BIT	R0,(R1)			; STOP MATCH AT CURRENT LINE LOCATION?
	BEQ	20$			; NOT YET
	MOVB	#LF,R0			; SET LF CODE IN TO R0

40$:	CALL	PR$BYT			; Print a line feed
	INCB	L.LNCT(R5)		; Count lines as we go
	SOB	R2,40$
	RETURN


;+
; Forms control translation
;-
FORM$X:	ASLB	R0			; Horizontal or vertical ?
	BPL	FORM$V			; Vertical
	BR	FORM$H			; Horizontal
;+
; NURD message builder
;
;	R0	Byte to put into buffer
;	R5	Database address
;-
MS$BYT:	DEC	L.MSGS(R5)		; COUNT IT
	BLT	20$			; BUFFER ALREADY FULL
	MAP$	L.MSGA(R5)		; MAP TO BUFFER
	MOVB	R0,@L.MSGA+2(R5)	; INSERT CHAR
	INC	L.MSGA+2(R5)		; ADVANCE PTR
	RETURN

20$:	CLR	L.MSGS(R5)		; RESET THE COUNT
25$:	PUSH$S	R4
	CALL	BF$SDB			; GET A BUFFER
	BCS	30$			; Oops
	MOV	C.BUF(R4),L.MSGA(R5)	; MAKE IT THE CURRENT BUFFER
	MOV	C.BUF+2(R4),L.MSGA+2(R5)
	MOV	C.CNT(R4),L.MSGS(R5)
	ENQ$	L.MSGQ			; ADD IT TO END OF LIST
	POP$S	R4
	BR	MS$BYT			; NOW STUFF THE CHAR

30$:	POP$S	R4
	CALL	ZZ$SLP			; WAIT A SEC AND TRY AGAIN
	BR	25$
;+
; Put a byte into the physical output buffer
;-
PR$BYT:	DEC	L.PLBS(R5)		; Decrement the counter
	BLT	10$			; Buffer, if any, is full already
	MAP$	L.PLBP(R5)		; Map it
	MOVB	R0,@L.PLBP+2(R5)	; Stash byte in physical buffer
	INC	L.PLBP+2(R5)		; Advance the pointer
	RETURN

10$:	CLR	L.PLBS(R5)		; CORRECT DECREMENT OVERRUN
	TST	L.PLBP+2(R5)		; PHYSICAL BUFFER IN PROCESS?
	BEQ	20$			; NO, GO GET ONE
	CALL	PR$DQX			; STUFF THE LP	

20$:	CALL	PR$NEW			; SET UP A NEW BUFFER
	BR	PR$BYT			; TRY AGAIN


;+
; Put a printing character into the physical output buffer.
;-
PR$CHR:	CMPB	L.COCT(R5),LE.FWD+F.FCV(R5) ; COL CT LE FORM WIDTH?
	BLO	28$			; YES, OKAY (UNSIGNED COMPARISON)
	TSTB	LE.TRN+F.FCV(R5)	; LINE TRUNCATION ON?
	BNE	35$			; IF SO, EAT CHARACTERS TIL FC COMES
	CALL	PR$LF			; FORCE WRAP TO NEXT LINE

28$:	TSTB	LE.LCR+F.FCV(R5)	; UPPER CASE ONLY ON?
	BEQ	30$			; NO
	CMPB	R0,#140			; YES, IS CHAR LOWER CASE?
	BLO	30$			; NO
	BIC	#40,R0			; YES, CLEAR UNSHIFT BIT

30$:	CALL	PR$BYT			; PUT CURRENT CHARACTER TO BUFFER
	INCB	L.COCT(R5)		; COUNT THE COLUMN
35$:	RETURN
;+
; Dequeue and transmit physical buffer.
;-
PR$DQX:	DEQ$	L.PLBQ			; Dequeue the physical buffer
	MOV	.RDBSZ,C.CNT(R4)	; Buffer is originally this big
	SUB	L.PLBS(R5),C.CNT(R4)	; Subtract unused count
	CLR	L.PLBP+2(R5)		; Clear current buffer
	CLR	L.PLBS(R5)		;  and size
	CALLR	$LEQUE			; Send it




;+
; Print a form feed, and adjust counters
;-
PR$FF:	PUSH$S	R0
	MOVB	#FF,R0			; SET FF CHAR
	CALL	PR$BYT			; TRY TO PUT IT
	POP$S	R0
	CLRB	L.LNCT(R5)		; CLEAR LINE COUNT
	CLRB	L.COCT(R5)		; CLEAR COLUMN COUNTER
	RETURN
;+
; Called to return a data CCB.
;	R4	CCB
;	R5	Database address
;
; The CCB may be a physical buffer that has been printed and may
; have a source buffer pointer in C.STK (to be returned).  The
; CCB may be other than a physical buffer, being circulated through
; the driver queue to synchronize its transmission with the printed
; data.
;-
PR$FIN:	TSTB	C.PIX(R4)		; Physical buffer?
	BNE	100$			; No, a source buffer in disguise
	PUSH$S	R4			; Yes, save its address
	MOV	C.STK(R4),R4		; Get source buffer address
	BEQ	26$			; None here
	CALL	100$			; Return source buf to owner

26$:	POP$S	R4			; Restore physical buffer address
	CLR	C.STK(R4)		; Assure no pointer left here
	BIT	#LA.EOF,C.STS(R4)	; Check this buffer for eof condition
	BEQ	30$
	BIS	#LA.CPC,L.ASTS(R5)	; IF SO, SET FLAG TO CLEAR PAGE COUNT
					;   BEFORE NEXT OUTPUT
30$:	BIT	#LA.AKR,C.STS(R4)	; MARKED TO ACKNOWLEDGE DATA MESSAGE?
	BEQ	45$			; NO
	TSTB	L.RATT(R5)		; ATTENTION PENDING?
	BEQ	40$			; NO, SET UP TO ACK
	CMPB	L.RATT(R5),#N.AACK	; IS CURRENT REASON MORE IMPORTANT?
	BHI	45$			; YES, LET THE PENDING REASON STAND
40$:	MOVB	#N.AACK,L.RATT(R5)	; NO, USE ACKNOWLEDGE AS THE REASON
	CALL	SN$ATT			; TRY TO SEND AN ATTENTION

45$:	ENQ$	L.PLBQ			; Queue physical buffer for future use
	RETURN

; Return a source buffer to NRD
100$:	MOV	#S.SSUC,C.STS(R4)	; ACK A NRD message
	BIT	#LA.ABO,L.ASTS(R5)	; Abort in progress ?
	BEQ	101$			; No, serenity
	MOV	#S.EABO,C.STS(R4)	; Indicate purge
101$:	MOVB	#FC.RCE,C.FNC(R4)	; Return it
	CALLR	$SCHED			; ...
;+
; Print a line feed
;-
PR$LF:	CLR	R1
	BISB	L.LNCT(R5),R1		; GET LINE COUNTER BYTE
	ASL	R1			;   AS WORD OFFSET
	ADD	#VFU66,R1		; POINT INTO CURRENT VFU
	TST	(R1)+			; UP LINE POINTER
	BMI	PR$FF			; SKIP IF PAST END OF TABLE
	BIT	#VFBP8,(R1)		; ***TABLE SPECIFIC HACK
	BEQ	PR$FF			; APPARENTLY IN MARGIN, GO DO TOF
	PUSH$S	R0
	MOVB	#LF,R0			; SET CHAR
	CALL	PR$BYT			; DO IT
	POP$S	R0
	INCB	L.LNCT(R5)		; UP LINE COUNTER
	CLRB	L.COCT(R5)		; CLEAR COL CTR WHEN GOING TO NEW LINE
	RETURN				; CARRY STAYS AS SET BY LAST CALL


;+
; Set up new physical output buffer
;-
PR$NEW:	MOV	L.PLBQ(R5),R4		; IS ONE ENQUEUED?
	BEQ	10$			; NO
	CLR	C.STS(R4)		; INIT STATUS
	MOV	C.BUF(R4),L.PLBP(R5)	; SET NEW ADDRESS DOUBLEWORD
	MOV	C.BUF+2(R4),L.PLBP+2(R5) 
	MOV	.RDBSZ,L.PLBS(R5)	; SET BUFFER MAX SIZE
	RETURN

10$:	CALL	ZZ$SLP			; SLEEP TIL A BUFFER MEANDERS BACK
	BR	PR$NEW


;+
; Check for room in current buffer.  Start new buffer if needed.
;	R0	Number of bytes of space needed
;-
PR$SPC:	TST	L.PLBP+2(R5)		; BUFFER IN USE ?
	BEQ	20$			; NOPE - GO GET ONE
	CMP	R0,L.PLBS(R5)		; ROOM ?
	BGT	10$
	RETURN

10$:	CALL	PR$DQX			; NO ROOM - FLUSH CURRENT
20$:	CALLR	PR$NEW			;  AND GET NEW ONE
;+
; Purge the queues
;
; Abort all from  <DDMQ>, <DLCQ>, and <MSGQ>   (in that order).
; Assumes that printer is stopped.
;-
Q$PURG:	PUSH$S	<R0,R1,R3,R4>		; SAVE CURRENT CCB, DATABASE ADDR, ETC.

10$:	DEQ$	L.DDMQ,20$		; RETURN PHYSICAL BUFFERS TO THE PB Q
	CALL	PR$FIN			; RETURN ANY MESSAGE BUFS
	BR	10$			;   AND RESTORE PHYSICAL BUFS TO LIST

20$:	DEQ$	L.DLCQ,30$		; Abort messages waiting to be done
	MOV	#S.EABO,C.STS(R4)	; ...
	MOVB	#FC.RCE,C.FNC(R4)	; ...
	CALL	$SCHED			; ...
	BR	20$			; Look on

30$:	DEQ$	L.MSGQ,40$		; FLUSH ANYTHING GOING OUT
	MOV	#80.,R1			; SET SIZE OF BUFFER
	CALL	$CBBRT			; RETURN IT
	BR	30$

40$:	CLRB	L.DTXL(R5)		; FLUSH ANY SUSPENDED PROCESSING
	CLR	L.PUBS(R5)		; NO CURRENT PRINT BUFFER
	CLR	L.PLBP+2(R5)		; NO PHYSICAL BUFFER IN USE
	CLR	L.PLBS(R5)
	CLR	L.DMAD+2(R5)		; NO CURRENT SOURCE BUFFER
	CLR	L.DMAD(R5)
	CLR	L.DMBS(R5)		; MAY HAVE BEEN A BUFFER IN PROGRESS
	CLR	L.MSGS(R5)		;  ...
	POP$S	<R4,R3,R1,R0>
	RETURN
;+
; Read a data byte.
;-
RD$BYT:	DEC	L.DMBS(R5)		; Decrement counter
	BLT	5$			; Buffer, if any, is drained
	MAP$	L.DMAD(R5)		; Map it
	CLR	R0			; Get a byte from buffer
	BISB	@L.DMAD+2(R5),R0	; ...
	INC	L.DMAD+2(R5)		; Advance source pointer
	RETURN				; Done

5$:	TST	L.DMAD+2(R5)		; IS A BUFFER IN PROCESS?
	BEQ	10$			; NO BUFFER IN USE
	MOV	L.DLCQ(R5),R4		; CHECK FOR MESSAGE CONTINUITY
	BITB	#S$PEOM,C.PRM1+1(R4)
	BEQ	9$	

; A message fragment, can't continue
	CALL	$LESTP			; Stop printer interrupts
	BIS	#LA.ADN,L.ASTS(R5)	; ABORT LINK WHEN DDM QUEUE IS EMPTY

9$:	CALL	RD$DQX			; EMPTIED BUFFER ON TOP
	BIT	#S$PEOM,R0		; CHECK IF EOM
	BEQ	10$			; NO
	MOV	L.DFSP(R5),SP		; YES, RECOVER STACK POINTER
	RETURN				;  SO WE CAN QUIT

; No buffer, so set one up
10$:	MOV	L.DLCQ(R5),R4		; IS THERE A QUEUED ENTRY?
	BNE	15$			; YES
	CALL	ZZ$SLP			; WAIT TIL NEXT SEGMENT ARRIVES
	BR	10$

15$:	MOV	C.BUF(R4),L.DMAD(R5)	; GET ADDRESS DOUBLEWORD AS PTR
	MOV	C.BUF+2(R4),L.DMAD+2(R5); ...
	MOV	C.CNT(R4),L.DMBS(R5)	; GET BUFFER COUNT AS SIZE COUNTER
	BR	RD$BYT			; TRY AGAIN
;+
; Dequeue and return a source buffer to NRD
;-
RD$DQX:	CLR	L.DMAD+2(R5)		; Indicate "no buffer in use"
	CLR	L.DMBS(R5)		; ...
	DEQ$	L.DLCQ			; Dequeue message just don
	MOVB	C.PRM1+1(R4),R0		; Remember EOM status
	TST	L.PLBP+2(R5)		; Physical buffer in use ?
	BEQ	10$			; No, have to make do without one
	PUSH$S	R4			; Yes, save the buffer address
	MOV	L.PLBQ(R5),R4		; Get current physical buffer address
	TST	C.STK(R4)		; Already have a stacked message?
	BEQ	20$			; No, so go ahead and stack on it
	POP$S	R4			; Yes, so just stick it directly on que
10$:	CALLR	$LEQUE			; ...

20$:	POP$S	C.STK(R4)		; Ok, tie it to physical buffer
	CALLR	PR$DQX			; Queue physical buffer to printer
;+
; Send alert message
;- 
SN$ALR:	CALL	BF$SDB			; Get a message buffer
	BCS	10$			; Oops
	MOV	C.BUF+2(R4),R1		; Get buffer pointer
	MOVB	#NM.ALR,(R1)+		; Complete alert message (1 word)
	CLRB	(R1)+			; Clear NURD message flags
	MOV	#2,C.CNT(R4)		; Set message length
	MOVB	#S$SNI,C.MOD(R4)	; Alert is an interrupt message
	INC	L.IOCN(R5)		; Count the I/O
	CALL	$SCHED			;  and send it
10$:	RETURN




;+
; Send attention message
;
; Allocate an SDB and build attention message.
; Reason code is in L.RATT.
;-
SN$ATT:	PUSH$S	<R0,R1,R2,R3,R4>	; Preserve registers
	MOVB	L.RATT(R5),R2		; GET EXCUSE CODE FOR ATTENTION MESSAGE
	BEQ	110$			; NO ATTENTION IS PENDING, LEAVE
					; BUILD AND SEND AN ATTENTION MESSAGE
	CALL	BF$SDB			; GET A MESSAGE BUFFER
	BCS	110$			; FAILED, EXIT - WE'LL BE BACK
	MOV	C.BUF+2(R4),R1		; GET POINTER TO START OF BUFFER
	MOVB	#NM.ATT,(R1)+		; LOAD MESSAGE TYPE TO BUFFER
	CLRB	(R1)+			; CLEAR NURD MESSAGE FLGS
	MOVB	L.DMSQ(R5),(R1)+	; STORE LAST GOOD SEQUENCE NUMBER
	MOVB	R2,(R1)+		; LOAD ATTENTION CODE
	CLR	R2			; CLEAR FLAGS ACCUMULATOR
	MOV	L.ASTS(R5),R0		; GET DEVICE STATUS
	BIT	#LA.ADN,R0		; ABORT WHEN DONE ?
	BEQ	10$
	BIS	#NA.FAT,R2		; SOME FATAL ERROR - USUALLY USER'S

10$:	BIT	#LA.ERR,R0		; PRINTER ERROR ON?
	BEQ	11$			; NO
	BIS	#NA.OFL,R2		; YES, NOTE OFFLINE

11$:	BIT	#LA.PAU,R0		; PAUSED?
	BEQ	20$			; NO
	BIS	#NA.PAU,R2		; YES

20$:	BIS	#200,R2			; TENTATIVELY SET EXTEND FLAG
	MOVB	R2,(R1)+		; THAT'S IT FOR BYTE 1 FLAGS
	CLR	R2			; CLEAR FOR BYTE 2 FLAGS
	BIT	#LA.TMO,R0		; HUNG?
	BEQ	30$			; NO
	BIS	#NA.DTO,R2		; YES

30$:	BIS	#200,R2			; TENTATIVELY SET EXTEND FLAG
	MOVB	R2,(R1)+		; THAT'S IT FOR BYTE 2
	CLR	R2			; CLEAR FOR BYTE 3 FLAGS

40$:	MOVB	R2,(R1)			; THAT'S IT FOR BYTE 3 FLAGS
	BNE	50$			; 3 BYTE FEILD
	BICB	#200,-(R1)		; NOT MORE THAN 2 BYTES
	BNE	50$			; 2 BYTER
	BICB	#200,-(R1)		; ONLY 1 BYTE

50$:	INC	R1			; ADVANCE R1 TO NEXT AVAILABLE BYTE
	MOV	L.PGCT(R5),R3		; GET PAGE COUNT ACCUMULATOR
	MOVB	R3,(R1)+		; STORE LOW BYTE OF COUNT
	SWAB	R3			; GET HIGH BYTE
	MOVB	R3,(R1)+		; STORE IT TOO
	SUB	C.BUF+2(R4),R1		; COMPUTE MESSAGE SIZE
	MOV	R1,C.CNT(R4)		; STUFF IT INTO CCB
	INC	L.IOCN(R5)		; Count the I/O
	CALL	$SCHED			;  and send it
	CLRB	L.RATT(R5)		; CLEAR PENDING REQUEST FOR ATTENTION
110$:	POP$S	<R4,R3,R2,R1,R0>	; RESTORE REGS
	RETURN				; EXIT SEND ATTENTION
;+
; Send capabilities message
;
; Message Format: <no. features><...fid's...>
;-
SN$CAP:	CALL	BF$SDB			; GET A MESSAGE BUFFER
	BCS	100$
	MOV	C.BUF+2(R4),R3		; GET BUFFER ADDRESS
					; FORMAT A NURD MESSAGE
	MOVB	#NM.CAP,(R3)+		; NURD MESSAGE TYPE = CAPABILITIES
	CLRB	(R3)+			; NO FLAGS
	PUSH$S	R3			; SAVE PTR TO CNT
	CLRB	(R3)+			; INIT CNT
	MOV	#3,C.CNT(R4)		; SET BUFFER CNT = BYTES INSERTED
	PUSH$S	R5			; SAVE THE DATABASE PTR
	ADD	#L.CFET,R5		; GET R5 -> COMMON FEATURES LIST
	CLR	R1			; INIT THE FID

10$:	CLR	R0			; GET LENGTH OF FEATURE LIST
	BISB	(R5)+,R0
	BEQ	31$			; NO FEATURES!!

20$:	INC	R1			; ADVANCE THE FID
	TSTB	(R5)+			; CHECK FOR SUPPORT OF IT
	BEQ	30$			; NOPE
	MOVB	R1,(R3)+		; YES - STORE FID IN MESSAGE
	INC	C.CNT(R4)		; COUNT IT
30$:	SOB	R0,20$
31$:	TSTB	R1			; CHECK WHICH FEATURE LIST
	BMI	40$			; DEVICE SPECIFIC - DONE
	MOV	#200,R1			; INIT FID FOR DEVICE SPECIFIC FEATURES
	BR	10$			; PROCESS THAT LIST

40$:	POP$S	R5			; GET THE DATABASE PTR BACK
	MOV	C.CNT(R4),R0		; GET THE BUFFER CNT
	SUB	#3,R0			; CALC NO. FEATURES FOUND
	MOVB	R0,@(SP)+		; STORE CNT IN CNT BYTE OF MESSAGE
	INC	L.IOCN(R5)		; Count the I/O
	CALL	$SCHED			;  and send it
	CLRB	L.CAPP(R5)		; FLUSH THE FLAG
100$:	RETURN
;+
; Data / Features HIBER Function
;
; Called by process to wait for a clock tick.
;-
ZZ$SLP:	MOV	R0,L.DTXT(R5)		; STORE THE REGISTERS 1ST
	MOV	#L.DTXT+2,R0
	ADD	R5,R0			; R0 -> R1 SLOT
	MOV	R1,(R0)+
	MOV	R2,(R0)+
	MOV	R3,(R0)+
	MOV	R4,(R0)+
	CLRB	L.DTXL(R5)		; INIT THE COUNT

10$:	CMPB	#DTXTSZ-5,L.DTXL(R5)	; CHECK INCIPIENT OVERFLOW
	BHI	20$
	BPT				; PROGRAM BUG
20$:	MOV	(SP)+,(R0)+		; XFER NEXT STACK WORD
	INCB	L.DTXL(R5)		; COUNT IT
	CMP	L.DFSP(R5),SP		; CHECK ORIGIN
	BNE	10$			; MORE
	RETURN


;+
; Clock has ticked.
;-
ZZ$WAK:	MOV	SP,L.DFSP(R5)		; SET THE NEW STACK ORIGIN
	CLR	R0
	BISB	L.DTXL(R5),R0		; GET NO. OF WORDS TO RETURN TO STACK
	ASL	R0
	ADD	#L.DTXT+12,R0
	ADD	R5,R0			; R0 -> 1ST WORD TO RETURN TO STACK

10$:	MOV	-(R0),-(SP)		; XFER THE STACK CONTENTS
	DECB	L.DTXL(R5)		; COUNT THEM
	BNE	10$
	MOV	-(R0),R4		; RESTORE THE REGISTERS
	MOV	-(R0),R3
	MOV	-(R0),R2
	MOV	-(R0),R1
	MOV	-(R0),R0
	RETURN				; Return to original caller of ZZ$SLP
.sbttl	Interrupt Service
;+
; Configuration stuff
;-
	LEPBFS	=	6		; NUMBER OF PHYSICAL BUFFERS TO HOARD
	LEHNG	=	5		; HUNG DEVICE TIME CONSTANT


;+
; LP11 hardware device register definitions
;-
	LPS	= 0			; LINE PRINTER STATUS REGISTER
		ERROR	= 100000	;  GENERAL (ONLY KNOWN) ERROR BIT
		DONE	= 200		;  CHARACTER DONE/PRINTER READY
		INTENB	= 100		;  INTERRUPT ENABLE
	LPB	= 2			; LINE PRINTER DATA BUFFER REGISTER
					;   SEVEN BIT ASCII, WRITE ONLY
	LESZ	= 4			; SIZE OF LP11 REGISTER BLOCK IN BYTES


;+
; Queue output image buffers to be sent to LPT.
; Start interrupt processing if appropriate
;
;	R4	CCB
;	R5	Database address
;-
	.enabl	LSB

$LEQUE:	CALL	$LESTP			; FLUSH INTERRUPTS WHILE WE DIDDLE Q'S
	ENQ$	L.DDMQ			; QUEUE UP INCOMING OUTPUT BUFFER

; START THE DEVICE IF IT SHOULD BE
$LESTR:	BIT	#LA.PAU,L.ASTS(R5)	; ARE WE PAUSED?
	BNE	10$			; EVERYTHING IS QUIESENT


$LEGO:	MOVB	#LEHNG,L.HUNG(R5)	; SET THE HUNG TIMER
	MOV	#INTENB,@L.CSR(R5)	; GOOSE IT
10$:	RETURN
	.DSABL	LSB


$LESTP:	CLR	@L.CSR(R5)		; STOP THE INTERRUPTS
	CLRB	L.HUNG(R5)		; AND FLUSH THE HUNG TIMER
	RETURN
;+
; Field interrupts:
;	Dispatch to error if error set,
;	  alternate error if pause set,
;	  else output next character.
;	If last character of buffer sent, go to interrupt on done
;	to return used buffer and start next one.
;
;	R5	Database address
;-

$LEINT::BIT	#LA.INT,L.ASTS(R5)	; Already handling interrupt ?
	BEQ	1$			; No, so go to it
	RETURN				; Yes, so dismiss this one
1$:	BIS	#LA.INT,L.ASTS(R5)	; Say we are processing an interrupt
	CLRB	L.HUNG(R5)		; Reset the hung timer
	MOV	R5,R4			; Build pointer to SYNCH block
	ADD	#L.SYN,R4		;  ...
	MOV	#5$,S.DSP(R4)		; Set return address
	CALLR	$SYNCH			; Go

; Here at synch level
5$:	BIC	#LA.INT,L.ASTS(R5)	; Clear interrupt level stuff
	MOV	L.CSR(R5),R3		; Get CSR address
	CLR	(R3)			; Turn off printer
	BIT	#LA.PAU,L.ASTS(R5)	; Ought we pause?
	BNE	INXPAU			; Yes
	MAP$	L.PUBP(R5)		; Set mapping for present output
	MOV	L.PUBP+2(R5),R2		; R2 / Buffer pointer
	MOV	L.PUBS(R5),R1		; R1 / Buffer count
	BEQ	40$			; An empty one !

10$:	TST	(R3)			; Check the printer status
	BGT	15$			; All is ok
	MOV	R1,L.PUBS(R5)		; Some problem, we will wait a bit
	MOV	R2,L.PUBP+2(R5)		; Save the state
	MOV	#LA.ERR,R0		; Assume an error
	TST	(R3)			; Ehh ?
	BMI	INTPAU			; An error
	MOVB	#LEHNG,L.HUNG(R5)	; Full up, so reset the hung timer
	MOV	#INTENB,(R3)		; Restart the device
	BR	LEXIT			; Wait till later

15$:	MOVB	(R2)+,R0		; Printer is ready, get next character
	CALL	LEPRT			;   and print it
	BCC	20$			; Jump ahead if it went ok
	DEC	R1			; Page-limit exceeded. FF went out
	MOV	R1,L.PUBS(R5)		; Save state information
	MOV	R2,L.PUBP+2(R5)		; ...
	MOVB	#N.APLE,L.RATT(R5)	; Request attention message
	MOV	#LA.PLE,R0		; Set status correctly
	BR	INTPAU			; Get on out

20$:	SOB	R1,10$			; Keep going while printer is hungry

30$:	DEQ$	L.DDMQ			; Take just-finished buffer off queue
	CALL	PR$FIN			; Return buffer
40$:	MOV	L.DDMQ(R5),R4		; Get address of top element of queue
	BEQ	60$			; None there, exit
	TSTB	C.PIX(R4)		; Check for circulating source buffer
	BNE	30$			; If so, just return it
	BIT	#LA.CPC,L.ASTS(R5)	; Check if last buffer had EOF
	BEQ	50$			; No
	CLR	L.PGCT(R5)		; Yes - clear page counter
	BIC	#LA.CPC,L.ASTS(R5)	; Reset the flag

50$:	MAP$	C.BUF(R4)		; Set mapping
	MOV	C.BUF(R4),L.PUBP(R5)	;   and save it too
	MOV	C.BUF+2(R4),R2		; Buffer pointer
	MOV	C.CNT(R4),R1		; Buffer length
	BEQ	30$			; Empty buffers are empty
	MOV	L.CSR(R5),R3		; Get CSR address again
	BR	10$			; Continue where we left off

60$:	CLR	L.PUBS(R5)		; Clear L.PUBS for next startup
	BIT	#LA.ADN,L.ASTS(R5)	; Check for auto death
	BEQ	LEXIT			; Nope
	CLR	R0			; Yes - pause now
;	BR	INTPAU			; ...


INTPAU:	BIS	#LA.PAU,R0		; Always have this bit lit
	BIS	R0,L.ASTS(R5)		; Put whole thing into database
INXPAU:	TSTB	L.RATT(R5)		; Attention already pending?
	BNE	LEXIT			; Yes, don't overwrite reason code
	MOVB	#N.ASTC,L.RATT(R5) 	; No, note status change
	CALL	SN$ATT			; Build and send attention message

LEXIT:	RETURN				; Leave
; Printer output routine
LEPRT:	CMPB	#LF,R0			; CHECK FOR LF'S AND FF'S
	BNE	30$
	MOVB	R0,2(R3)		; LF - PRINT IT
	INCB	L.PLNC(R5)		; COUNT IT
	CMPB	#PGLEN,L.PLNC(R5)	; CHECK FOR PAGE BOUNDARY
	BGT	50$			; OK

20$:	CLRB	L.PLNC(R5)		; PAGE - CLEAR LINE COUNT
	INC	L.PGCT(R5)		; COUNT THE PAGE
	TST	L.PGLM(R5)		; CHECK PAGE LIMIT
	BEQ	50$			; NOT SET
	CMP	L.PGCT(R5),L.PGLM(R5)	; COMPARE COUNT TO LIM
	BLE	50$			; STILL OK
	SEC				; TOO BAD
	RETURN

30$:	CMPB	#FF,R0			; CHECK FF
	BNE	40$			; NOT A CRITICAL CHARACTER
	CMPB	#PGLEN-1,L.PLNC(R5)	; CHECK FOR LAST LINE
	BLE	35$			; BOF - FF IS SPECIAL CASE
	MOVB	R0,2(R3)		; Not BOF - Print FF and count page
	BR	20$

35$:	MOVB	#LF,2(R3)		; LINE FEED AT BOF TO AVOID BLANK PAGE
	BR	20$			; COUNT THE PAGE

40$:	MOVB	R0,2(R3)		; JUST PRINT THE CHARACTER
50$:	CLC
	RETURN
.sbttl	Miscellaneous Tables
;+
; The following table defines the default contents for the LP11
; vertical format unit (VFU) for a 66 line page.
; NOTE: All VFU actions also perform carriage return action.
;
; Channel  1 - (top of form) line 1 only
; Channel  2 - half of form stop
; Channel  3 - every other line from line 1 up to and including line 59
; Channel  4 - every third line from line 1 up to and including line 58
; Channel  5 - every line
; Channel  6 - every tenth line from line 1 up to and including line 51
; Channel  7 - every 20th  line from line 1 up to and including line 41
; Channel  8 - next line in form
;
; Standard extension to standard vertical format unit
; Channel  9 - every fifth line from line 1 up to and including line 56
; Channel 10 - quarter of form stops
; Channel 11 - alternate top of form
; Channel 12 - (bottom of form) line 60 at 6 lpi
;-

; Set forms info
	PGLEN = 66.		; 66 lines per page
	FRMLEN = 60.		; 60 lines of form

; Name the FCXTB flags
FCCHR	=	200		; CHARACTER IS A FORMS CONTROL CHARACTER
HFCHR	=	100		; CHARACTER IS A HORIZONTAL CONTROL CHARACTER
HFCCR	=	 40		; CHARACTER IS A CARRIAGE RETURN CHARACTER


; Support Macros
.macro 	.ZER $$
	.P'$$=0
.endm	.ZER

.macro	SETCHN	A,B		; 'OR' new channel setting into map word
	.P'B = .P'B ! A
.endm	SETCHN

.macro	WRDVFU	X
	.word	.P'X
.endm	WRDVFU

.macro	DEFPAR	CHN,LINC,LAST
	$$ = 1
.rept	LAST/LINC		; Number of stops in channel
	SETCHN	CHN,\$$		; 'OR' in next stop
	$$ = $$ + LINC
.endr
.endm	DEFPAR
.if ndf VCH1			; DEFINE VFU CHANNEL TAGS
	$$ = 1
.irp	X,<1,2,3,4,5,6,7,8,9,10,11,12>
	VCH'X = X'. - 1		; NUMERIC CHANNEL TAGS
	VFBP'X = $$		; POSITIONAL CHANNEL TAGS
	$$ = $$*2
.endr
.endc


.if ndf .P1
	$$=0			; INIT THE LINE SYMBOLS .P<NN>
.rept PGLEN
	$$=$$+1
	.ZER \$$
.endr

	; SET THE MASK BITS IN THE PARTITION WORDS ACCORDING TO
	; THE FORMULA DEFINED IN THE ARGUMENTS TO DEFPAR
	DEFPAR	VFBP1,1,1		; TOP OF FORM
	DEFPAR	VFBP2,<<FRMLEN/2>>,FRMLEN ; HALF OF FORM STOPS
	DEFPAR	VFBP3,2.,FRMLEN		; EVERY OTHER LINE
	DEFPAR	VFBP4,3.,FRMLEN		; EVERY THIRD LINE
	DEFPAR	VFBP5,1.,PGLEN		; EVERY LINE TO END
	DEFPAR	VFBP6,10.,FRMLEN	; EVERY TENTH LINE
	DEFPAR	VFBP7,20.,FRMLEN	; EVERY TWENTIETH LINE
	DEFPAR	VFBP8,1.,FRMLEN		; SINGLE SPACE IN FORM
	DEFPAR	VFBP9,5.,FRMLEN		; EVERY FIFTH LINE
	DEFPAR	VFBP10,<<FRMLEN/4>>,FRMLEN  ; QUARTER OF FORM STOPS
	DEFPAR	VFBP11,1,1		; ALTERNATE TOP OF FORM
	.P75 = .P75 ! VFBP12		; SPECIAL CASE FOR BOF
	.P102 = .P102 ! 100000		; FLAG LAST WORD OF VFU IMAGE TABLE
.endc
.sbttl	Tables
;*******
; USE THE DEFINED PARTITION WORDS TO BUILD A COMPLETE VFU IMAGE, AND
; ASSIGN PARTITIONS FOR THE WHOLD PAGE.
VFU66:	$$ = 0
	.rept	PGLEN
	$$=$$+1
	WRDVFU	\$$
	.endr	; R PGLEN
	  VFU66S = .-VFU66		; SIZE OF VFU IMAGE

; $LERAM FCT INDEX TO CHANNEL MAP
CHMAP:	.word	VFBP1		;  0
	.word	VFBP2		;  1
	.word	VFBP3		;  2
	.word	VFBP4		;  3
	.word	VFBP5		;  4
	.word	VFBP6		;  5
	.word	VFBP7		;  6
	.word	VFBP8		;  7
	.word	VFBP9		;  8
	.word	VFBP10		;  9
	.word	VFBP11		;  10
	.word	VFBP12		;  11
	.word	0		;  12 UNUSED ENTRY (NO STOPS SET)
	.word	0		;  13 UNUSED ENTRY (NO STOPS SET)
	.word	0		;  14 UNUSED ENTRY (NO STOPS SET)
	.word	0		;  15 UNUSED ENTRY (NO STOPS SET)


; LP11 CHARACTER TRANSLATION MAP, INITIALIZED TO NURD ASCII BY OPEN.
; POTENTIALLY CHANGED BY FEATURES SETS. ROOM FOR A FULL SEVEN-BIT CODE.
LEASC:	.blkb	128.
	.even


; FORMS CONTROL EXCEPTION TABLE
;	 FORMAT: .byte <LEASC OFFSET>,<TRANS CONTENTS>
FCXTB:	.byte	HT,FCCHR!HFCHR
	.byte	LF,FCCHR!VCH8
	.byte	VT,FCCHR!VCH7
	.byte	FF,FCCHR!0
	.byte	CR,FCCHR!HFCHR!HFCCR
	.byte	DLE,FCCHR!VCH2
	.byte	DC1,FCCHR!VCH3
	.byte	DC2,FCCHR!VCH4
	.byte	DC3,FCCHR!VCH5
	.byte	DC4,FCCHR!VCH6
; 	.byte	FS,FCCHR!VCH9		; Channels 9-12 not supported
; 	.byte	GS,FCCHR!VCH10
; 	.byte	RS,FCCHR!VCH11
; 	.byte	US,FCCHR!VCH12
	.word	-1			; Table ends on negative offset byte

.end