	.TITLE	QTDRV
	.IDENT	"V1.0"
;
;   Author: D. Mischler  30-AUG-88
;
;   This program implements the loadable QT: driver.  This
;   driver is used to perform read-ahead caching on streaming
;   tape drives.  It will not work unless the tape is mounted
;   foreign.  Write-behind caching is not normally used because
;   of problems in handling the end of a tape.  This driver
;   requires the internal I/O completion support in M-PLUS V3.0
;   and later.
;

	.MCALL	DDT$,PKTDF$
	PKTDF$		; Define I/O packet offsets.

;
;   Macro to build I/O function validation table entry.
;
	.MACRO	IOFUNC	FUNC,PROADR
	.WORD	FUNC,PROADR
	.ENDM	IOFUNC
	.PAGE
	.PSECT
;
;   Driver dispatch table definition (note that there are no controllers).
;
	DDT$	QT,0,NONE,,,NEW

;
;   I/O function validation (dispatch) table.
;
FUNTBL:	IOFUNC	IO.RLB,RLBINI	; Read logical block.
	IOFUNC	IO.WLB,WLBINI	; Write logical block.
	IOFUNC	IO.EOF,CTLINI	; Write tape mark.
	IOFUNC	IO.ERS,CTLINI	; Write extended gap.
	IOFUNC	IO.RWD,REWIND	; Rewind tape.
	IOFUNC	IO.RWU,REWIND	; Rewind and unload tape.
	IOFUNC	IO.SEC,SENSE	; Sense characteristics.
	IOFUNC	IO.SMO,CTLINI	; Mount and set characteristics.
	IOFUNC	IO.SPB,CTLINI	; Space blocks.
	IOFUNC	IO.SPF,CTLINI	; Space files.
	IOFUNC	IO.STC,CTLINI	; Set characteristics.
FUNTSZ	=	<.-FUNTBL>/4	; Number of table entries.


;
;   I/O initiation entry point.
;
QTINI:	CALL	$GTPKT		; Get an I/O packet, is one available?
	BCS	10$		; No, go to sleep.
	BIT	#QT.IP,U.CW3(R5); Is an overlapped operation in progress?
	BEQ	QTDSP		; No, dispatch this packet immediately.
	BIS	#QT.PO,U.CW3(R5); Indicate a pending operation.
10$:	RETURN			; Go to sleep for a while.
;   Dispatch I/O packet pointed to by R1.
QTDSP:	BIS	#QT.XF,U.CW3(R5); Assume function initiates a transfer.
	MOV	#FUNTBL,R0	; Point to function dispatch table.
	MOV	#FUNTSZ,R2	; Get number of entries in table.
10$:	CMP	I.FCN(R1),(R0)	; Found specified I/O function in table?
	BEQ	20$		; Yes, go to it.
	ADD	#4,R0		; Point to next table entry.
	SOB	R2,10$		; Search entire table if necessary.
	MOV	#IE.IFC&377,R0	; Get illegal function error code.
	CALL	$IOALT		; Terminate I/O with an error.
	BR	QTINI		; See if another packet is waiting.
;   I/O function found in table.
20$:	BIT	#QT.WE,U.CW3(R5); Write-behind error encountered?
	BEQ	30$		; No, make the poor luser happy.
	CMP	#REWIND,2(R0)	; Will function rewind tape?
	BEQ	30$		; Yes, allow it.
	CMP	#SENSE,2(R0)	; Will function sense characteristics?
	BEQ	30$		; Yes, allow it.
	MOV	#IE.FHE&377,R0	; Get fatal hardware error status.
	CALL	$IOALT		; Post failure completion.
	BR	QTINI		; Look for another packet.

30$:	JMP	@2(R0)		; Process legal I/O function.
	.PAGE
;
;   Initiate rewind and unload operations here.
;
REWIND:	CALL	FLUSH		; Flush read-ahead queue.
	BIC	#QT.WE,U.CW3(R5); Definitely not at end of tape.
;
;   Initiate sense characteristics functions here.
;
SENSE:	NOP			; Make function start address unique.
;
;   Initiate control functions here.
;
CTLINI:	BIC	#QT.XF,U.CW3(R5); Function does not initiate a transfer.
;
;   Initiate write functions here.
;
WLBINI:	TST	U.RALH(R5)		; Is read-ahead queue empty?
	BEQ	TPOSOK			; Yes, no need to reposition tape.
	CALL	FLUSH			; Flush the read-ahead queue.
	MOV	R0,U.BUF+2(R5)		; Save read-ahead record count.
	MOV	U.FBDL(R5),R0		; Point to last buffer flushed.
	CMPB	#IE.EOF,BD.IOS(R0)	; Was an EOF mark read?
	BNE	SPBINI			; No, just space back some blocks.
	DEC	U.BUF+2(R5)		; Remove EOF block from count.
	MOV	U.IPKT(R5),R1		; Point to internal I/O packet.
	MOV	#IO.SPF,I.FCN(R1)	; Set up I/O function code.
	MOV	#SPFCOM!1,I.IOSB+4(R1)	; Set up completion routine address.
	MOV	#-1,I.PRM(R1)		; Set up number of EOF marks to skip.
	CALLR	QR1PRQ			; Queue up IO.SPF request.
;   Here when IO.SPF completes.
SPFCOM:	MOV	R3,R1			; Reposition I/O packet address.
	MOV	I.IOSB(R1),R5		; Recover QT: UCB address.
	MOV	I.PRM+6(R1),R0		; Get IO.SPF completion status.
	CMP	#IS.SUC,R0		; Did IO.SPF complete normally?
	BNE	SPXERR			; No, complain.
;   Issue IO.SPB to position tape to the correct record.
SPBINI:	MOV	U.BUF+2(R5),R0		; Get record count, zero?
	BEQ	TPOSOK			; Yes, do what we came here for.
	NEG	R0			; Indicate backwards spacing.
	MOV	U.IPKT(R5),R1		; Point to internal I/O packet.
	MOV	#IO.SPB,I.FCN(R1)	; Set up I/O function code.
	MOV	#SPBCOM!1,I.IOSB+4(R1)	; Set up completion routine address.
	MOV	R0,I.PRM(R1)		; Set up number of records to skip.
	CALLR	QR1PRQ			; Queue up IO.SPB request.
;   Here when IO.SPB completes.
SPBCOM:	MOV	R3,R1			; Reposition I/O packet address.
	MOV	I.IOSB(R1),R5		; Recover QT: UCB address.
	MOV	I.PRM+6(R1),R0		; Get IO.SPB completion status.
	CMP	#IS.SUC,R0		; Did IO.SPB complete normally?
	BEQ	TPOSOK			; Yes, do what the user wants.
;   IO.SPF or IO.SPB failed.
SPXERR:	CALL	$IOALT			; Return failure to user.
	BR	QTINI			; Check for another packet.
	.PAGE
;
;   Tape is positioned correctly.
;
TPOSOK:	BIT	#QT.WB,U.CW3(R5)	; Are write-behind operations enabled?
	BEQ	10$			; No, just pass request through.
	MOV	U.SCB(R5),R4		; Get SCB address.
	MOV	S.PKT(R4),R1		; Get I/O packet address.
	CMP	#IO.WLB,I.FCN(R1)	; Is function a logical write?
	BEQ	WBHINI			; Yes, buffer data and complete I/O.
	TST	U.WBLH(R5)		; Anything on the write-behind queue?
	BNE	WBHIDL			; Yes, wait for it to unwind.
;   Write-behind caching is disabled or inactive.
10$:	CALL	CURPRQ			; Pass request to physical driver.
	JMP	QTINI			; Get next I/O packet.
;   Wait for write-behind queue to empty.
WBHIDL:	BIS	#QT.PO,U.CW3(R5)	; Indicate a pending operation.
	RETURN				; Take care of it later.
	.PAGE
;
;   Current I/O packet is a candidate for write-behind caching.
;
WBHINI:	MOV	U.FBDL(R5),R0		; Is a buffer available?
	BEQ	WBHIDL			; No, hang waiting for a buffer.
	MOV	(R0),U.FBDL(R5)		; Set up next free buffer descriptor.
	CMP	U.CNT(R5),BD.LEN(R0)	; Is write request too long for buffer?
	BHI	25$			; Yes, can't write-behind.
	BIC	#APR6,I.PRM+2(R1)	; Remove APR 6 base from source address.
	BIS	#APR5,I.PRM+2(R1)	; Make it an APR 5 address.
	MOV	BD.ADR(R0),I.PRM+6(R1)	; Copy destination buffer address.
	MOV	BD.ADR+2(R0),I.PRM+10(R1)
	MOV	R0,I.PRM+12(R1)		; Save buffer descriptor address.
	MOV	R1,U.BUF(R5)		; Save I/O packet address.
;   Move up to MAXMOV*64. bytes per iteration.
10$:	MOV	U.BUF(R5),R4		; Get I/O packet address.
	MOV	I.PRM+4(R4),R0		; Get remaining bytes to move, zero?
	BEQ	30$			; Yes, finish up.
	CMP	R0,#MAXMOV*64.		; Is there more data than is allowed?
	BLOS	20$			; No, go ahead.
	MOV	#MAXMOV*64.,R0		; Limit transfer size.
20$:	SUB	R0,I.PRM+4(R4)		; Reduce byte count by transfer size.
	MOV	I.PRM(R4),R1		; Get source address bias.
	ADD	#MAXMOV,I.PRM(R4)	; Adjust bias for next iteration.
	MOV	I.PRM+2(R4),R2		; Get source address displacement.
	MOV	I.PRM+6(R4),R3		; Get destination address bias.
	ADD	#MAXMOV,I.PRM+6(R4)	; Adjust bias for next iteration.
	MOV	I.PRM+10(R4),R4		; Get destination address displacement.
	CALL	$BLXIO			; Perform transfer.
	TST	$FRKHD			; Any fork requests pending?
	BEQ	10$			; No, keep going.
	CALL	$FORK			; Let somebody else have a turn.
	BR	10$			; Continue transfer.
;
;   User buffer is too long for write-behind to work.
;
25$:	MOV	R0,U.FBDL(R5)		; Put buffer back on free list.
	MOV	#IE.RBG&377,R0		; Get completion status.
	CALL	$IOALT			; Issue error completion.
	JMP	QTINI			; Get next I/O packet.
	.PAGE
;
;   User buffer has been copied: queue write-behind, issue I/O completion.
;
30$:	MOV	I.PRM+12(R4),R0		; Recover buffer descriptor address.
	MOV	R0,@U.WBLH+2(R5)	; Link new descriptor to end of list.
	MOV	R0,U.WBLH+2(R5)		; Make it the newest entry.
	CLR	(R0)			; Terminate write-behind list.
	MOV	U.CNT(R5),R1		; Get number of bytes "written".
	MOV	R1,BD.IOS(R0)		; Save byte count in buffer descriptor.
	MOV	#IS.SUC,R0		; Get success status code.
	CALL	$IODON			; Complete IO.WLB function.
	BIT	#QT.WO,U.CW3(R5)	; Is a write-behind already active?
	BNE	.QTINI			; Yes, get next I/O packet.
;   Issue a write if any write-behind requests are outstanding.
WBHWRT:	MOV	U.WBLH(R5),R0		; Is write-behind queue empty?
	BEQ	10$			; Yes, see if an operation is pending.
	MOV	U.IPKT(R5),R1		; Point to internal I/O packet.
	MOV	#IO.WLB,I.FCN(R1)	; Set up I/O function code.
	MOV	#WBHCOM!1,I.IOSB+4(R1)	; Set up completion routine address.
	MOV	BD.ADR(R0),I.PRM(R1)	; Set up buffer address doubleword.
	MOV	BD.ADR+2(R0),I.PRM+2(R1)
	MOV	BD.IOS(R0),I.PRM+4(R1)	; Set up byte count.
	BIS	#QT.WO!QT.XF,U.CW3(R5)	; Indicate a write-behind is active.
	CALL	QR1PRQ			; Queue up IO.WLB request.
	BIT	#QT.PO,U.CW3(R5)	; Is an operation pending?
	BEQ	.QTINI			; No, look for next I/O packet.
	RETURN
;   Write-behind queue is empty.
10$:	BIT	#QT.PO,U.CW3(R5)	; Is an operation pending?
	BEQ	.QTINI			; No, see if a packet is available.
	BIC	#QT.PO,U.CW3(R5)	; Clear pending operation.
	MOV	U.SCB(R5),R4		; Find SCB.
	MOV	S.PKT(R4),R1		; Get I/O packet address.
	JMP	QTDSP			; Re-dispatch pending operation.
.QTINI:	JMP	QTINI			; Look for another I/O request.
;
;   Write-behind completion routine.
;
WBHCOM:	MOV	I.IOSB(R3),R5		; Get QT: UCB address back.
	BIC	#QT.WO,U.CW3(R5)	; No write-behind operation is active.
10$:	MOV	U.WBLH(R5),R0		; Get buffer descriptor address.
	MOV	(R0),U.WBLH(R5)		; Save address of next, zero?
	BNE	20$			; No, list head is fine.
	MOV	R5,U.WBLH+2(R5)		; Reinitialize write-behind list head.
	ADD	#U.WBLH,U.WBLH+2(R5)
20$:	MOV	U.FBDL(R5),(R0)		; Return buffer descriptor to free list.
	MOV	R0,U.FBDL(R5)
	CMP	#IS.SUC,I.PRM+6(R3)	; Did write operation complete normally?
	BEQ	WBHWRT			; Yes, try to issue another write.
	BIS	#QT.WE,U.CW3(R5)	; Indicate a write-behind error.
	TST	U.WBLH(R5)		; Any more writes outstanding?
	BNE	10$			; Yes, flush them.
	BR	WBHWRT			; Finish up.
	.PAGE
;
;   Initiate a read operation.
;
RLBINI:	MOV	U.RALH(R5),R0	; Has desired data already been read?
	BEQ	RLBREQ		; No, start reading.
	MOV	(R0),U.RALH(R5)	; Save address of next read-ahead block, zero?
	BNE	10$		; No, list head is OK.
	MOV	R5,U.RALH+2(R5)		; Save UCB address as list head.
	ADD	#U.RALH,U.RALH+2(R5)	; Add list head offset to set up header.
10$:	CMPB	#IE.DAO,BD.IOS(R0)	; Record too long for buffer?
	BEQ	20$			; Yes, transfer what was read.
	CMPB	#IS.SUC,BD.IOS(R0)	; Did read-ahead complete normally?
	BNE	80$			; No, skip buffer transfer.
20$:	CMP	BD.IOS+2(R0),U.CNT(R5)	; Is record larger than user buffer?
	BLOS	30$			; No, don't worry about it.
	MOV	#IE.DAO&377,BD.IOS(R0)	; Indicate a data overrun.
	BR	40$			; Go perform transfer.
;   User buffer is long enough for record.
30$:	MOV	BD.IOS+2(R0),U.CNT(R5)	; Copy actual transfer length.
40$:	MOV	U.SCB(R5),R4		; Get SCB address.
	MOV	S.PKT(R4),R4		; Get address of current I/O packet.
	MOV	R4,U.BUF(R5)		; Save packet address for easy access.
	MOV	BD.ADR+2(R0),R1		; Get source address displacement.
	BIC	#160000,R1		; Remove APR selection bits.
	BIS	#APR5,R1		; Make it an APR 5 displacement.
	MOV	R1,I.PRM+6(R4)		; Save source displacement in packet.
	MOV	BD.ADR(R0),I.PRM+4(R4)	; Save source bias in I/O packet.
	MOV	R0,I.PRM+10(R4)		; Save buffer descriptor address.
;   Move data to task buffer: up to MAXMOV*64 bytes per iteration.
50$:	MOV	U.BUF(R5),R4		; Get I/O packet address.
	MOV	U.CNT(R5),R0		; Get remaining bytes to move, zero?
	BEQ	70$			; Yes, finish up.
	CMP	R0,#MAXMOV*64.		; Is there more data than is allowed?
	BLOS	60$			; No, go ahead.
	MOV	#MAXMOV*64.,R0		; Limit transfer size.
60$:	SUB	R0,U.CNT(R5)		; Reduce byte count by transfer size.
	MOV	I.PRM+4(R4),R1		; Get source address bias.
	ADD	#MAXMOV,I.PRM+4(R4)	; Adjust bias for next iteration.
	MOV	I.PRM+6(R4),R2		; Get source address displacement.
	MOV	I.PRM(R4),R3		; Get destination address bias.
	ADD	#MAXMOV,I.PRM(R4)	; Adjust bias for next iteration.
	MOV	I.PRM+2(R4),R4		; Get destination address displacement.
	CALL	$BLXIO			; Perform transfer.
	TST	$FRKHD			; Any fork requests pending?
	BEQ	50$			; No, try next transfer.
	CALL	$FORK			; Service pending fork requests.
	BR	50$			; Move next block of data.
;   Complete data buffer has been transferred.
70$:	MOV	I.PRM+10(R4),R0	; Recover buffer descriptor address.
80$:	MOV	U.FBDL(R5),(R0)	; Link next free buffer descriptor to this one.
	MOV	R0,U.FBDL(R5)	; Make this one free too.
	MOV	BD.IOS+2(R0),R1	; Get 2nd I/O status word.
	MOV	BD.IOS(R0),R0	; Get 1st I/O status word.
	CALL	$IODON		; Finish processing for this packet.
	JMP	QTINI		; See if another packet is available.
	.PAGE
;
;   Initiate a requested read.
;
RLBREQ:	BIS	#QT.PO,U.CW3(R5); Indicate an operation is pending.
	BIC	#QT.RH,U.CW3(R5); Make sure read-ahead halted bit is clear.
;   Initiate a read-ahead operation.
RAHINI:	BIT	#QT.RH,U.CW3(R5); Has read-ahead been halted?
	BNE	RAHLTD		; Yes, do not issue a read-ahead request.
	MOV	U.FBDL(R5),R0	; Get free buffer descriptor address, zero?
	BEQ	RAHLTD		; Yes, no read-ahead is possible.
	MOV	(R0),U.FBDL(R5)	; Set up address of next free descriptor.
	MOV	U.IPKT(R5),R1		; Get internal I/O packet address.
	MOV	#IO.RLB,I.FCN(R1)	; Set up I/O function code.
	MOV	#RAHCOM!1,I.IOSB+4(R1)	; Set up completion routine address.
	MOV	R0,I.AST(R1)		; Set up back link to buffer descriptor.
	MOV	BD.ADR(R0),I.PRM(R1)	; Set up buffer address doubleword.
	MOV	BD.ADR+2(R0),I.PRM+2(R1)
	MOV	BD.LEN(R0),I.PRM+4(R1)	; Set up buffer length.
	BIS	#QT.IP,U.CW3(R5)	; Indicate a read-ahead is in progress.
	CALL	QR1PRQ			; Queue up read-ahead I/O request.
	BIT	#QT.PO,U.CW3(R5)	; Is an operation pending?
	BEQ	10$			; No, go away.
	TST	U.RALH(R5)		; Is a read-ahead packet available?
	BEQ	10$			; No, wait for a while.
	MOV	U.SCB(R5),R4		; Get SCB address.
	MOV	S.PKT(R4),R1		; Get I/O packet address.
	CMP	#IO.RLB,I.FCN(R1)	; Is pending operation IO.RLB?
	BEQ	RAHLTD			; Yes, process it now.
	BIS	#QT.RH,U.CW3(R5)	; Stop reading ahead after this record.
10$:	RETURN
;
;   Internal I/O completion routine for read-ahead operations.
;   On entry:	R3 -> internal I/O packet.
;
RAHCOM:	MOV	I.IOSB(R3),R5		; Get QT: UCB address.
	BIC	#QT.IP,U.CW3(R5)	; Read-ahead no longer in progress.
	MOV	I.AST(R3),R0		; Get buffer descriptor address.
	MOV	R0,@U.RALH+2(R5)	; Append it to read-ahead list.
	MOV	R0,U.RALH+2(R5)		; Make it the last entry.
	CLR	(R0)			; Terminate read-ahead list.
	MOV	I.PRM+6(R3),BD.IOS(R0)	; Copy I/O status words.
	MOV	I.PRM+10(R3),BD.IOS+2(R0)
	CMPB	#IS.SUC,BD.IOS(R0)	; Did operation complete normally?
	BEQ	RAHINI			; Yes, issue next read-ahead.
	BIS	#QT.RH,U.CW3(R5)	; Indicate read-ahead is halted.
;   No further read-ahead is possible.
RAHLTD:	BIT	#QT.PO,U.CW3(R5)	; Is an operation pending?
	BEQ	10$			; No, wait for one.
	BIC	#QT.PO,U.CW3(R5)	; Clear pending operation.
	MOV	U.SCB(R5),R4		; Find SCB.
	MOV	S.PKT(R4),R1		; Get I/O packet address.
	JMP	QTDSP			; Dispatch pending packet.
10$:	RETURN
	.PAGE
;
;   QTDRV unit status change entry point.
;
QTUCB:	BCS	UCBOFF			; If going offline then branch.
	MOV	U.PHYU(R5),R0		; Get physical UCB address.
	TST	U.OWN(R0)		; Is physical device allocated?
	BNE	10$			; Yes, complain.
	MOV	R5,U.OWN(R0)		; Allocate physical device.
	BICB	#US.MNT,U.STS(R0)	; Indicate physical device is mounted
	BISB	#US.FOR,U.STS(R0)	;  foreign.
	MOV	#I.LGTH,R1		; Get I/O packet length.
	CALL	$ALOCB			; Allocate an I/O packet, OK?
	BCS	10$			; No, reject unit status change.
	MOV	R0,U.IPKT(R5)		; Save I/O packet address.
	MOV	#1,I.PRI(R0)		; Set up I/O priority and EFN.
	MOV	$MCRPT,I.TCB(R0)	; Claim that the packet belongs to MCR.
	MOV	R5,I.IOSB(R0)		; Set up back link to QT: UCB.
	MOV	KISAR5,I.IOSB+2(R0)	; Set up completion routine mapping.
	MOV	R5,U.RALH+2(R5)		; Set up read-ahead list head.
	ADD	#U.RALH,U.RALH+2(R5)
	MOV	R5,U.WBLH+2(R5)		; Set up write-behind list head.
	ADD	#U.WBLH,U.WBLH+2(R5)
	RETURN
;   Reject unit status change.
10$:	MOVB	#IE.ALC,$SCERR	; Indicate status change error.
	RETURN
;
;   QT: unit is going offline.  Deallocate I/O packet & buffer descriptors.
;   Detach from buffering region and physical device unit.
;
UCBOFF:	MOV	U.RPCB(R5),R0		; Get address of buffer region PCB.
	CLRB	P.RMCT(R0)		; Indicate region is not attached,
	BIC	#P2.LMA,P.ST2(R0)	;  and may be shuffled or deleted.
	MOV	U.PHYU(R5),R0		; Get physical device UCB address.
	BISB	#US.MNT,U.STS(R0) 	; "Dismount" physical device.
	BICB	#US.FOR,U.STS(R0)
	CLR	U.OWN(R0)		; Deallocate physical unit.
	CLR	U.PHYU(R5)		; Deassign physical device.
	MOV	U.IPKT(R5),R0		; Get internal I/O packet address.
	CLR	U.IPKT(R5)		; Forget I/O packet address.
	CALL	$DEPKT			; Release I/O packet to pool.
	CALL	FLUSH			; Flush read-ahead queue.
10$:	MOV	U.FBDL(R5),R0		; Any free buffer descriptors left?
	BEQ	20$			; No, all done.
	MOV	(R0),U.FBDL(R5)		; Save address of next descriptor.
	MOV	#BD.SIZ,R1		; Get size of a buffer descriptor.
	CALL	$DEACB			; Release it to pool.
	BR	10$			; Do 'em all.
;   QT: unit is in offline state: exit.
20$:	RETURN

QTCAN:
QTKRB:
QTOUT:
QTPWF:	RETURN
	.PAGE
;
;   Routine to pass the current I/O packet to the physical driver.
;
CURPRQ:	MOV	U.SCB(R5),R4		; Get SCB address.
	BICB	#US.BSY,U.STS(R5)	; Indicate UCB is not busy.
	CLRB	S.STS(R4)		; Ditto for the SCB.
	MOV	S.PKT(R4),R1		; Get current I/O packet address.
;
;   Routine to queue the I/O packet pointed to by R1 to the physical driver.
;
QR1PRQ:	MOV	R5,-(SP)	; Save QT: device UCB address.
	MOV	U.CW3(R5),R0	; Get current driver flags.
	MOV	U.PHYU(R5),R5	; Get physical device address.
	MOV	R5,I.UCB(R1)	; Save it in the I/O packet.
	BIT	#QT.XF,R0	; Does this operation cause a transfer?
	BEQ	10$		; No, address format is immaterial.
;   Make sure I/O buffer address format is OK.
	MOV	R1,R0		; Preserve I/O packet address.
	MOV	I.PRM(R0),R1	; Get buffer relocation bias.
	MOV	I.PRM+2(R0),R2	; Get displacement address.
	CALL	$MPPHY		; Reformat address if appropriate.
	MOV	R1,I.PRM(R0)	; Save possibly modified address.
	MOV	R2,I.PRM+2(R0)
	MOV	R0,R1		; Restore I/O packet address.
10$:	CALL	$DRQRQ		; Queue packet to physical driver.
	MOV	(SP)+,R5	; Recover QT: device UCB address.
	RETURN

;
;   Routine to flush read-ahead queue.
;   On entry:	R5 -> QT: UCB.
;   On exit:	R0 contains number of buffers flushed.
;
FLUSH:	CLR	-(SP)		; Zero buffer flush count.
10$:	MOV	U.RALH(R5),R0	; Any data on read-ahead queue?
	BEQ	30$		; No, flushing is redundant.
	MOV	(R0),U.RALH(R5)	; Save address of next read-ahead block, zero?
	BNE	20$		; No, list head is OK.
	MOV	R5,U.RALH+2(R5)	; Save UCB address as start on list head.
	ADD	#U.RALH,U.RALH+2(R5) ; Add list head offset to set up header.
20$:	MOV	U.FBDL(R5),(R0)	; Link buffer descriptor to old newest.
	MOV	R0,U.FBDL(R5)	; Make buffer descriptor newest.
	INC	(SP)		; Count buffer just flushed.
	BR	10$		; Flush entire queue.
;   All buffers have been flushed.
30$:	MOV	(SP)+,R0	; Get number of buffers flushed.
	RETURN

	.END
