	.title	zqdrv  QUEUE ACP PSEUDO DEVICE DRIVER
	.IDENT 	/2.4/
	.RAD50	/2.4/
;.begin.doc ************************** begin.doc
;  
; .c ;MODULE
; .c ;^&ZQDRV\&
; .nf
; .x ZQDRV>Defined
; Source:ZQDRV.MAC
; Designer           :EARL LAKIA
; Author             :EARL LAKIA
; Date of last update:06-JAN-87
; Revision level     :2.4
;  
; .SK
; .fill
; .SK
; Description:
; .sk    
; 	This is the pseudo device driver for allowing the 
; connection of the QUEACP with application tasks.  This driver
; is pretty simple; it only does address relocation and the
; like valid only when the requesting task is the current task.
; NOTE: No special buffers are relocated, just the normal
; buffer address specified in a transfer function.  This
; driver also provides for the normal I/O queuing of I/O
; requests that we desire for the queue process ACP.
; When a task exits, this driver is always called and
; the driver will allocate a piece of pool and queue
; a "fake" I/O packet to the ACP. This fake packet will tell the
; ACP to look for any message ids that were possibly
; connected to by this task and disconnect the task from them.
;
;
; .NOFILL
;   
; THE QIO CALL IS:
;       QIO$    FNC,LUN,EFN,PRI,ISB,AST,<P1,P2,P3,P4,P5,P6>
; 
;
; .fill
; .LIST
; .le;IO.WVB- Write a message to a specified message id queue.
; .list
; .le;P1= Message buffer address.
; .le;P2= Byte count of user message.
; .le;P3= Address of message id buffer (16 bytes long).
; .le;P4= Destroyed when id buffer relocated.
; .le;P5= Type of message (0= deletable if queue is full, 1= not deletable).
; .els
; .le;IO.WVB+1- Same as IO.WVB, but only used by the routing task, RTRRSX,
; to bypass writing a new header when the message is received from the
; remote node.
; .le;IO.RVB- Read a message from a specified message id queue.
; .list
; .le;P1= Message buffer address.
; .le;P2= Byte count of user buffer to receive message.
; .le;P3= Address of message id buffer (16 bytes long).
; .els
; .le;IO.ACR- Connect a message id queue to a task.
; .list
; .le;P1= Address of message id buffer (16 bytes long).
; .le;P2= 16.
; .le;P3= Event flag to set when the number of messages in the 
; queue goes from zero to one.
; .els
; .le;IO.ACW- Acknowlege a message read from a message id queue.
; .list
; .le;P1= Address of message id buffer (16 bytes long).
; .le;p2= 16.
; .le;P3= Internal ID. (This id was returned when a read 
; occurred in the upper byte of the I/O status word. It 
; is a sequential number that is simply checked as a 
; sequence number.)
; .els
; .le;IO.RNA- Replace read but not acknowleged message back at
; beginning of the message id queue.
; .list
; .le;P1= Address of message id buffer (16 bytes long).
; .le;P2= 16.
; .els
; .le;IO.DEL- Delete a specified number of messages without acknowledging 
; from a message id queue.
; .list
; .le;P1= Address of message id buffer (16 bytes long).
; .le;p2= 16.
; .le;P3= Number of messages to be deleted.
; .els
; .le;IO.RAT- Read message id queue list head.
; .list
; .le;P1= Buffer address to receive list head (see MIDDEF).
; .le;P2= Byte count of user buffer.
; .LE;P3= Address of message id buffer (16 bytes long).
; .els
; .le;IO.ACE- Read the MAQUEUE header.
; .list
; .le;P1= Buffer address to receive queue header (see QHDDEF).
; .le;P2= Byte count of user buffer.
; .els
; .le;IO.APC- Mount/dismount the desired pseudo device unit with 
; the ACP (IO.APC!1= Mount, IO.APC!2= Dismount).
; .le;IO.DAC- Set up exit handling I/O rundown function.  This
; I/O never completes.  The ACP is called with a new packet
; when the task exits which will clean up in the event the task
; attached to a message id.  This function must be called prior to
; any calls to connect to a message id.
; .le;IO.KIL- Standard device functioning.
; .le;IO.ATT- Standard device functioning.
; .le;IO.DET- Standard device functioning.
; .els
; .FILL
; .SK
; 	Possible error codes:
; .LIST 1,' '
; .le;IE.IFC (-02) -- Invalid function code.
; .le;IE.SPC (-06) -- Bad buffer address.
; .le;IE.DNA (-07) -- ZQ: device not mounted.
; .le;IE.WLK (-12) -- A connect request has been received from a task that
; does not have a pending I/O.  Subroutine INITQ was probably not called
; to set up the task exit handlers.
; .le;IE.LCK (-27) -- Trying to mount ZQ: device when aleady mounted; trying
; to dismount ZQ: device when already dismounted.
; .le;IE.ILL (-42) -- The ACP is not installed in the system.
; .le;IE.NFW (-69) -- ACP not found in system task directory when trying to
; mount ZQ: device.
; .le;IE.IQU (-91) -- Trying to mount ZQ: device with a task that is not 
; marked as an ACP.
; .els
; end.doc ****************************** end.doc  
	.page
;
; PARAMETERS NEEDED FOR THIS DRIVER
;
ZQCNT=1		; NUMBER OF ZQ DEVICES
;
        .MCALL  PKTDF$,ABODF$,DEVDF$,TCBDF$,HWDDF$                              
        PKTDF$                                                                  
        HWDDF$                                                                  
        TCBDF$                                                                  
        DEVDF$                                                                  
        ABODF$  
	.IF NDF,R$$MPL
	.ERROR	; THIS DRIVER ONLY WORKS ON MPLUS SYSTEMS
	.ENDC
	.MCALL	KRBDF$,CTBDF$
	KRBDF$
	GTPKT$
;                                                                               
;  DRIVER DISPATCH.TABLE                                                        
;                                                                               
;
        .GLOBL  $ZQTBL
$ZQTBL:
	DDT$	ZQ,ZQCNT,NONE	 	; NO INPUT OR OUTPUT INTERRUPTS
	.PAGE
;
; UCB OF DEVICE
;
ZQUCBA:
	.WORD	0	; UNIT STATUS FOR ZQ0:
;
; TABLE OF LEGAL FUNCTION CODES AND RELOCATION
; REQUIREMENTS
;
FNCTBL:
	.WORD	IO.RVB
	.WORD	P3REL
	.WORD	IO.WVB
	.WORD	P3REL
	.WORD	IO.ACR
	.WORD	TSTIOS
	.WORD	IO.ACW
	.WORD	NOREL
	.WORD	IO.RNA
	.WORD	NOREL
	.WORD	IO.DEL
	.WORD	NOREL
	.WORD	IO.RAT
	.WORD	P3REL
	.WORD	IO.ACE
	.WORD	NOREL
	.WORD	IO.APC
	.WORD	MNTDIS	; MOUNT/DISMOUNT
	.WORD	IO.DAC	
	.WORD	QUEIT	; ONLY FUNCTION CODE THAT GET'S QUEUED
	.WORD	0	; LIST TERMINATOR
;
; THE NAME OF OUR ACP
;
R50ACP:
	.RAD50	/QUEACP/
	.PAGE
;
; DRIVER INITIALZATION CODE
;
; This drivr has the UC.QUE bit set in the device tables
; such that we may relocate some of the addresses that
; are in the parameter list.  The executive will only 
; relocate those buffers where p1= buffer, p2= byte count.
; All other buffers will require that we do the relocation and
; for non standard calls, we must have the control bit set
; so that the executive does not try to relocate the non
; present buffer.
; 
;
; THE DEVICE DEPENDENT DATA IN THE I/O PACKET LOOKS LIKE THE FOLLOWING:
; FOR TRANSFER TYPE (IE: P1= BUFF, P2= COUNT)
;	I.PRM
;	I.PRM+2	ADDRESS  DOUBLE WORD FOR DATA BUFFER
;	I.PRM+4	BYTE COUNT FOR DATA TRANSFER
;	I.PRM+6 P3
;	I.PRM+10 P4
;	I.PRM+12 P6
;	I.PRM+14 P7
;
;	INPUT  FROM EXEC:
;	R1= ADDRESS OF I/O PACKET
;	R4= ADDRESS OF SCB
;	R5= ADDRESS OF UCB
;
ZQINI::
	MOV	#FNCTBL,R3	; FUNCTION TABLE ADDRESS
	MOV	I.FCN(R1),R4	; GET FUNCTION
	BIC	#377,R4		; ONLY WANT UPPER HALF NO MODIFIERS
10$:
	TST	(R3)		; AT END OF LIST?
	BNE	15$		; NO
	MOV	#IE.IFC&377,R0	; ERROR CODE= INVALID FUNCTION
	BR	20$		; GO RETURN
15$:
	CMP	(R3)+,R4	; IS THIS THE FUNCTION BEING REQUESTED?
	BNE	30$		; NO
	CALL	@(R3)		; GO DO REQUIRED WORK
	BCC	50$		; EVERYTHING OK, THEN QUEUE IT 
	MOV	#IE.SPC&377,R0		; RETURN ERROR CODE, BAD BUFFER ADDRESS
20$:
	MOV	R1,R3		; GET I/O PACKET ADDRESS
	CLR	R1		; BYTE COUNT= 0
	CALLR	$IOFIN			; RETURN ERROR CODE TO USER AND EXIT
30$:
	TST	(R3)+		; POINT TO NEXT FUNCTION CODE
	BR	10$		; GO SEE IF NEXT MATCH
;
; EVERYTHING WAS OK, NOW QUEUE THE PACKET TO OURSELVES.  THIS
; WILL ACTUALLY CAUSE A QUEUE TO THE ACP WHEN THE GET PACKET IS
; EXECUTED BY THE MAIN LINE CODE.
;
; R4= SCB
; R1= I/O PACKET
;
50$:
	MOV	R1,R3			; COPY I/O PACKET ADDRESS
	MOV	I.UCB(R1),R5		; GET UCB
	MOV	U.ACP(R5),R0		; GET ACP TCB ADDRESS
	BNE	60$			; ACP THERE?
55$:
	MOVB	#IE.ILL,R0		; BOGUS ERROR CODE
	CLR	R1
	CALLR	$IOFIN
60$:
	CMP	R50ACP,T.NAM(R0)	; MATCH ON ACP NAMES?
	BNE	55$
	CALL	$EXRQF			; QUEUE TO ACP
	RETURN	
	.PAGE
;
; QUEUE THIS I/O PACKET TO OURSELVES, WE NEVER DEQUEUE THEM EXCEPT 
; ON THE I/O CANCEL.
; FUNCTION CODE: IO.DAC
; 	R5= UCB
;	R1=I/O PACKET
;	(SP)= RET ADDR.
QUEIT:
	MOV	U.SCB(R5),R0	; LIST HEAD= SCB
	CALL	$QINSP	; QUEUE TO OURSELVES BY PRIORITY
	TST	(SP)+	; POP RET ADDR
	RETURN		; BUT WE NEVER DEQUEUE IT !!!!
	.PAGE
;
; CONNECT FOR A OF A MESSAGE ID READ, 
; MAKE SURE HAVE AN OUTSTANDING I/O PACKET.
;	(SP)= RET ADR
;	R5= UCB
;	R1= I/O PACKET
;
TSTIOS:
	MOV	U.SCB(R5),R4	; GET SCB
10$:
	MOV	(R4),R2		; TRAVERSE THE SCB I/O LIST
	BEQ	30$		; AT END, OOPS
	MOV	R2,R4		; COPY FOR NEXT TRAVERSE
	CMP	$TKTCB,I.TCB(R2)	; DO WE HAVE A PENDING I/O FOR THIS
					; TASK?
	BNE	10$		; NOT THIS PACKET
	JMP	NOREL		; YES, THEN OK TO LET HIM CONNECT
30$:
	TST	(SP)+		; POP RET. ADDR
	MOV	R1,R3		; GET I/O PACKET ADDR.
	MOVB	#IE.WLK&377,R0	; ERROR CODE
	CALLR	$IOFIN		; DONE WITH THIS TURKEY
	.PAGE
;
; CHECK/RELOCATE PARAMETER P3 WHICH IS THE MESSAGE
; ID BUFFER (SHOULD BE 16 BYTES) READABLE
;
; R5= UCB
; (SP)= RETURN ADDRESS
;
P3REL:
	BITB	#US.MNT!US.MDM,U.STS(R5) ; IS DEVICE DISMOUNTED? OR MARKED
	BEQ	5$			; NO, ITS OK
	TST	(SP)+			; POP RET. ADDR.
	MOV	R1,R3
	MOV	#IE.DNA&377,R0		; NO, RETURN DEVICE NOT ATTACHED
	CLR	R1			
	CALLR	$IOFIN			; ALL DONE THIS PACKET
5$:
	MOV	R1,-(SP)		; SAVE I/O PACKET ADDRESS
	MOV	I.PRM+6(R1),R0		; STARTING ADDRESS
	MOV	#16.,R1			; SIZE OF BUFFER
	CALL	$CKBFR			; CHECK THE BUFFER
	BCC	10$			; WAS IT VALID
	MOV	(SP)+,R1		; RESTORE R1
	RETURN				; LET MAINLINE RETURN
;
; RELOCATE THE BUFFER AND STORE BACK IN THE I/O PACKET
; 
10$:
	MOV	(SP),R1			; RESTORE R1
	MOV	I.PRM+6(R1),R0		; GET VIRTUAL ADDRESS TO RELOCATE
	CALL	$RELOC			; RELOCATE THE ADDR OF THE CONTROL BUFR
	MOV	(SP),R3			; I/O PACKET ADDRESS
	MOV	R1,I.PRM+6(R3)		; STORE DISPLACEMENT
	MOV	R2,I.PRM+10(R3)		; AND DISP. IN BLOCK
	MOV	(SP)+,R1		; RESTORE R1
	CLC				; SET SUCCESS
	RETURN
	.PAGE
;
; NO RELOCATION, HOWEVER, THE DEVICE MUST BE MOUNTED
;	(SP)= RET ADDR
;	R5= UCB
;	R1= I/O PACKET ADDR.
;
NOREL:
	BITB	#US.MNT!US.MDM,U.STS(R5) ; IS DEVICE DISMOUNTED? OR MARKED
	BEQ	5$			; NO, ITS OK
	TST	(SP)+			; POP RET. ADDR.
	MOV	R1,R3
	MOV	#IE.DNA&377,R0		; NO, RETURN DEVICE NOT ATTACHED
	CLR	R1			
	CALL	$IOFIN			; ALL DONE THIS PACKET
	RETURN				; GO GET ANOTHER PACKET
5$:
	CLC
	RETURN
	.PAGE
;
; MOUNT/DISMOUNT COMMANDS THEY ARE HANDLED STRICKLY BY THIS ROUTINE
;	IF MOUNT, THEN FIND THE TASK IN THE SYSTEM TASK DIRECTORY
; AND REQUEST  THE TASK TO RUN.  FOR DISMOUNT, MARK THE DEVICE AS
; MARKED FOR DISMOUNT AND QUEUE THE REQUEST TO THE ACP WHICH WILL
; EVENTUALLY ACTUALLY SET THE US.MNT BIT AND CLEAR THE AP5
; MAPPING LOCATION (U.ACP).
;
MNTDIS:
	TST	(SP)+		; POP RETURN ADDRESS, WE DONT RETURN
	CMPB	#1,I.FCN(R1)	; IS THIS A MOUNT COMMAND
	BNE	70$		; NO,
;
; MOUNT; FIND QUEACP'S TCB, REQUEST IT TO RUN.  THE INITIALIZATION
; CODE FOR THIS ACP WILL CLEAR THE US.MNT FLAG, AND STORE IT'S 
; TCB ADDRESS
;
	BITB	#US.MNT,U.STS(R5) ; ARE WE CURRENTLY DISMOUNTED
	BNE	50$			; YES, THEN OK TO MOUNT
40$:
	MOV	#IE.LCK&377,R0		; ERROR CODE
45$:
	MOV	R1,R3
	CLR 	R1	
	CALL	$IOFIN		; ALL DONE
	RETURN		; GO GET NEXT PACKET
;
; NOT MOUNTED, THEN MOUNT BY REQUESTING THE ACP TO RUN
;
50$:
	BISB	#US.VV,U.STS(R5) ; SET VOLUME VALID BIT
	MOV	#R50ACP,R3	; RAD50 NAME
	CALL	$SRSTD		; SEE IF FOUND
	BCC	55$		; FOUND HIM
	MOV	#IE.NFW&377,R0	; ERROR CODE
	BR	45$
55$:
	BIT	#T3.ACP,T.ST3(R0)	; IS FOUND TASK REALLY AN ACP?
	BNE	60$			;YES
	MOV	#IE.IQU&377,R0		; ERROR
	BR	45$			; GO RETURN STATUS
60$:
;
; R0= TCB
; R1= PACKET
;
	CALL	$EXRQP		; REQUEST THE ACP TO DO THE WORK
				; INCLUDING THE I/O DONE
	RETURN			; SEE IF MORE WORK
;
; DISMOUNT
;
70$:
	BITB	#US.MDM!US.MNT,U.STS(R5) ; ARE WE CURRENTLY DISMOUNTED
				; OR MARKED FOR DISMOUNT?
	BNE	40$		; ALREADY DISMOUNTED
	BISB	#US.MDM,U.STS(R5)	; MARK DISMOUNT IN PROGRESS
	BICB	#US.VV,U.STS(R5)	; CLEAR VOLUME VALID BIT
	MOV	U.ACP(R5),R0		; GET TCB OF ACP
	BEQ	50$			; WHAT NOT FOUND?
	BR	60$			; FOUND, GO SEND REQUEST TO THE ACP
	.PAGE
;
; I/O CANCEL-
;
; R5= UCB
; R4= SCB
; R3= CONTROLLER INDEX
; R2= undefined
; R1= ADDRESS OF TCB OF CURRENT TASK
; R0= ADDRESS OF CURRENT I/O PACKET (FROM S.PKT, THIS
;	MAY NOT VALID IF U.STS DOES NOT SHOW IT AS BEING
;	ACTIVE WITH I/O.
; 
; The module IOKIL only cancels the entries after S.LHD, not the
; one pointed to by S.LHD.  Since we also request to be notified
; if any I/O packet has been removed, we may be called even for
; I/O packets that have already been dethreaded.
;
ZQCAN::
	TSTB	T.IOC(R1)	; TEST I/O COUNT FOR ZERO
	BNE	5$		; IF I/O COUNT IS ZERO, THEN THIS IS
				;   AN ASSIGN LUN DIRECTIVE, NO
				;   I/O PACKET EXISTS
	RETURN			;   SO RETURN
5$:
	MTPS	#PR0		; ENABLE INTERUPTS
	MOV	R1,-(SP)	; SAVE R1
;
; THE FOLLOWING CODE IS STOLLEN FROM THE $IOKILL FOR FLUSHING
; I/O TO THIS UNIT.  NOTE: WE DON'T TEST UCB ON SCB THREAD AS
; WE ONLY HAVE A SINGLE UCB.
;
10$:
	MOV	U.SCB(R5),R4	; GET SCB
	MOV	R4,R3		; COPY ADDR. OF I/O QUEUE LIST HEAD
20$:
	MOV	R3,R2		; SAVE ADDR. OF CURRENT ENTRY
	MOV	(R2),R3		; GET ADDR. OF NEXT ENTRY
	BEQ	40$		; END OF LIST
30$:
	CMP	R1,I.TCB(R3)	; IS THIS PACKET FOR THIS TASK?
	BNE	20$		; NO.
	MOV	(R3),(R2)	; YES, REMOVE FROM THREADED LIST
	BNE	35$		; NO NEW LAST POINTER TO UPDATE
	MOV	R2,2(R4)	; SET NEW LAST POINTER
35$:
	MOV	#IE.ABO&377,R0	; STATUS
	CLR	I.AST(R3)	; NO AST
	CALL	$IOFIN		; COMPLETE THIS I/O
	BR	10$		; GO SEE IF ANOTHER FOR THIS TASK
				; NORMALLY THERE SHOULDN'T BE
;
; WE WANT TO SEND A SPECIAL PACKET
; TO THE ACP TO LET IT KNOW THAT THIS TASK IS EXITING.
;
40$:
	TST	U.ACP(R5)	; IS THE ACP PRESENT?
	BEQ	70$		; NO, THEN MUST NOT BE MOUNTED
	CMP	U.ACP(R5),R1	; ACP EXITING?
	BEQ	70$		; YES, THEN DON'T SEND TO IT
	BITB	#US.MNT!US.MDM,U.STS(R5) ; IS DEVICE DISMOUNTED? OR MARKED
	BNE	70$		; YES, THEN DON'T SEND TO IT
	MOV	R5,-(SP)	; SAVE UCB
	MOV	#I.LGTH,R1	; GET AN I/O PACKET FROM PRIMARY POOL
	CALL	$ALOCB		; GET THE PACKET
	BCS	65$		; GOT A PACKET? NO, THEN GIVE UP
;
; NOW FILL IN THE ALLOCATED PACKET AS IF IT WERE AN I/O PACKET
;  (WELL SOMEWHAT).  SINCE THE TCB FOR THIS TASK MAY NOT BE
; PRESENT WHEN THIS PACKET IS DEQUED BY THE ACP, STORE THE 
; RAD50 TASK NAME.
;
;	R0= ALLOCATED PACKET
;
	MOV	(SP),R5			; RESTORE UCB
	MOV	R0,R1			; NEED R1= PACKET
	MOV	#IO.DAC,I.FCN(R1)	; FUNCTION CODE
	MOV	2(SP),R3		; GET TCB OF EXITING TASK
	MOV	R3,I.TCB(R1)		; STORE TCB ADDRESS OF TASK EXITING
	MOV	T.NAM(R3),I.PRM(R1)		; STORE RAD50 TASK NAME UPPER
	MOV	T.NAM+2(R3),I.PRM+2(R1)		; STORE RAD 50 TASK NAME LOWER
	MOV	R5,I.UCB(R1)		; UCB ADDRESS
	MOV	U.ACP(R5),R0		; TCB OF ACP
	CALL	$EXRQF			; QUEUE IT TO THE ACP
65$:
	TST	(SP)+			; POP R5
70$:
	TST	(SP)+			; POP R1
	RETURN			; DONE, LET ACP FINISH
;
; TIMEOUT ENTRIES
; (NOT USED BY THIS DO NOTHING DRIVER)
ZQOUT::
	RETURN
;                                                                               
; POWER FAIL OR BOOT ENTRY                                                      
;
;	R5= UCB
;	R4= SCB
;	R3= INDEX
;
ZQPWF::
	MOV	R5,ZQUCBA
;
; MARK THE DEVICE AS NOT MOUNTED
;
	BICB	#US.MDM,U.STS(R5)	; CLEAR MARKED FOR DISMOUNT
	BISB	#US.MNT,U.STS(R5)	; SET  NOT MOUNTED
	CLR	U.ACP(R5)		; CLEAR TCB ADDRESS FOR ACP
	TSTB	U.UNIT(R5)		; IS THIS ZQ0:
	BEQ	10$			; YES
	BISB	#US.BSY,U.STS(R5)	; MARK DEVICE AS BUSY
10$:
	RETURN
;
; CONTROLLER STATUS CHANGE? SHOULDN'T HAPPEN FOR THIS TYPE OF DEVICE
;
ZQKRB::
	RETURN
;
; UNIT STATUS CHANGE, NO CONTROLLERS, SHOULDN'T OCURR FOR THIS TYPE OF
; DEVICE.
; 
; 
ZQUCB::
	RETURN
	.END
