.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