.title CDDRIVER .sbttl Disk Cache Driver .ident /041389/ ; ;+ ;***** ; ; ; CDDRIVER - Caches data read from a disk device to improve I/O ; performance, albeit at the expense of CPU cylces required to ; move data. This particular version of CDDRIVER attempts to ; implement a "fully associative" type of cache. Comments ; preceeding each major subsection describe the driver's operation ; in more detail. ; ; Basically, CDDRIVER hooks the start I/O entry point of the device ; driver controlling the disk to be cached so that all I/O requests ; are first evaluated by CDDRIVER to determine if it is a read ; operation that can be satisfied from the cache. If the read ; cannot be satisfied from the cache, the request is returned to ; the disk driver. On I/O completion, CDDRIVER is recalled by the ; VMS I/O post-processing routine and the data read from disk is ; placed in the cache. Write requests are always passed on to the ; disk driver, and depending on the initialization options selected ; (see IO$_SETMODE) the data written to disk may be updated in ; cache as well. ; ;- ;***** ; ; Paul Sorenson ; AEP/Engineering Computer Support Center ; Columbus, OH 43215 ; ;***** ; ; Update 7/25/88 -- PRS; Remove support for redirecting QIOs for the ; cache driver to the cached disk's driver. XQP/ACP functions ; do not work properly presumably because the XQP/ACP references ; the CCB UCB field rather than the IRP's UCB field. Until a ; means can be found around this, the entire disk must be cached. ; ; Update 9/7/88 -- PRS; Move PIB queue lists from UCB to local data. ; Since CDDRIVER must potentially scan all lists in all UCBs ; anyway, maintaining a single list for all units of the driver is ; easier to handle. Also note that in caching a virtual disk ; with my VDDRIVER, the IRP$L_UCB field can no longer be relied ; upon to contain the same UCB address at CD_STARTIO and ; CD_IOPOST! ; ; Update 9/15/88 -- PRS; Reflect changes in UCB fields (see CDDEFIN.MAR) ; add IO$_SENSEMODE FDT entries ; ; Update 9/27/88 -- PRS; Add support for recording all write (read/write) ; operations to user's buffer via IO$_READxBLK buffered I/O ; function. Use system subroutine type timer queue entries ; to control timeout. Reference separate unique fork block ; to drop IPL to IPL$_QUEUEAST since UCB$B_FIPL must now be ; set to IPL$_SYNCH. ; ; Update 11/22/88 -- PRS; Add checks so won't deplete free page list ; when extending cache. ; ; Update 11/28/88 -- PRS; Link pool packets allocated for CIBs to special ; list to aid in deallocation. ; ; Update 4/13/89 -- PRS; Fix bug in handling IO$V_CD_ZERO @CD_SETMODE ; and alter logic flow per changes made for V5. ; ;***** ;-- .page .SBTTL External and Local Symbol Definitions ; ; External symbols ; $ACBDEF ; AST control block $CANDEF ; Cancel reason codes $CCBDEF ; Channel control block $CRBDEF ; Channel request block $DCDEF ; Device classes and types $DDBDEF ; Device data block $DDTDEF ; Driver dispatch table $DEVDEF ; Device characteristics $DPTDEF ; Driver prologue table $DYNDEF ; Dynamic data structures $FCBDEF ; File control block $FKBDEF ; Fork block $IDBDEF ; Interrupt data block $IODEF ; I/O function codes $IPLDEF ; Hardware IPL definitions $IRPDEF ; I/O request packet $JIBDEF ; Job information block $PFNDEF ; PFN data base symbols $PCBDEF ; Process control block $PRDEF ; Processor registers $PRTDEF ; Page protection $PSLDEF ; Processor status longword $PTEDEF ; Page table entry $SSDEF ; System status codes $TQEDEF ; Timer queue element $UCBDEF ; Unit control block $VADEF ; Virtual address fields $VECDEF ; Interrupt vector block $WCBDEF ; Window control block CD$IODEF CD$CIBDEF CD$PIBDEF CD$UCBDEF ; ; Local symbols ; ; ; Argument list (AP) offsets for device-dependent QIO parameters ; P1 = 0 ; First QIO parameter P2 = 4 ; Second QIO parameter P3 = 8 ; Third QIO parameter P4 = 12 ; Fourth QIO parameter P5 = 16 ; Fifth QIO parameter P6 = 20 ; Sixth QIO parameter ; ; Other constants ; CD_FIPL = IPL$_QUEUEAST ; default fork IPL CD_BLKSIZ = 512 ; Default buffer size CD_MAXRECL = 28 ; largest byte count record moved to ; IO$_READxBLK buffer ; ; .page .sbttl Standard Tables ; ; Driver prologue table...contents established along the lines of a ; standard VMS disk driver though many of the definitions are unused ; or may even be altered at runtime depending on the disk being cached. ; DPTAB - ; DPT-creation macro END=CD_END,- ; End of driver label ADAPTER=NULL,- ; Adapter type FLAGS=DPT$M_SVP,- ; System page table entry reqd UCBSIZE=,- ; Length of UCB NAME=CDDRIVER,- ; Driver name UNLOAD=CD_UNLOAD,- ; Routine to unload driver MAXUNITS=8 ; Max # units DPT_STORE INIT ; Start of load ; initialization table DPT_STORE UCB,UCB$B_FIPL,B,IPL$_SYNCH ; Device fork IPL DPT_STORE UCB,UCB$B_DIPL,B,IPL$_SYNCH ; Device interrupt IPL (dummy) DPT_STORE UCB,UCB$L_DEVCHAR,L,- ; Device characteristics ; output device DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ; Device characteristics ; prefix name with "node$" DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_MISC ; Device class (??) DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,- ; Default buffer size CD_BLKSIZ DPT_STORE REINIT ; Start of reload ; initialization table DPT_STORE DDB,DDB$L_DDT,D,CD$DDT ; Address of DDT DPT_STORE CRB,- ; Address of controller CRB$L_INTD+VEC$L_INITIAL,- ; initialization routine D,CD_CTRL_INIT DPT_STORE CRB,- ; Address of device CRB$L_INTD+VEC$L_UNITINIT,- ; unit initialization D,CD_UNIT_INIT ; routine DPT_STORE END ; End of initialization ; tables ; ; Driver dispatch table ; DDTAB - ; DDT-creation macro DEVNAM=CD,- ; Name of device START=CD_STARTIO,- ; Start I/O routine ALTSTART=CD_ALTSTARTIO,- ; Alternate entry point CANCEL=CD_CANCELIO,- ; Cancel I/O routine FUNCTB=CD_FUNCTABLE ; FDT address ; ; Function decision table... ; CD_FUNCTABLE: FUNCTAB ,- ; Valid I/O functions ; read logical block FUNCTAB ,- ; Buffered I/O functions ; read logical block FUNCTAB CD_SETMODE,- ; FDT routine for CDDRIVER specific FUNCTAB CD_SENSEMODE,- ; FDT routine for CDDRIVER specific FUNCTAB +EXE$SENSEMODE,- ; standard VMS FDT routine for ; sense characteristics FUNCTAB CD_READBLK,- ; FDT routine for CDDRIVER specific ; read logical block ; ; .page .sbttl Local Data Structures ; ; *** The following local data structures are defined by the CD_UNIT_INIT ; and/or CD_CTRL_INIT routine to allow the driver to restore its own ; context when called by the driver of the disk being cached. ; UCB: ; filled in with address of first UCB .BLKL 1 PENDQFL: ; queue list head for pending I/O requests .BLKL 1 PENDQBL: .BLKL 1 FREEQFL: ; queue list head for free PIBs .BLKL 1 FREEQBL: .BLKL 1 ; DEBUG =0 ; define "DEBUG" symbol to enable XDELTA breakpoints ; .page .sbttl Controller Initialization Routine ; ;+ ; ; *** CD_CTRL_INIT -- Readies controller for I/O operations ; ; Functional description: ; ; The operating system calls this routine in 3 places: ; ; at system startup ; during driver loading and reloading ; during recovery from a power failure ; ; Since there is no physical controller for the cache driver, ; this routine is simply used to setup the contents of the ; local data structures (UCB, PENDQxL, and FREEQxL) required ; by the driver. The UCB field is used to save the address ; of the first UCB attached to the "controller" and is ; referenced whenever the cache driver is called by the disk ; driver "start I/O" routine. The PENDQxL and FREEQxL fields ; define two queue list heads for maintaing the "pending I/O ; identificiation block" (PIB) structures required to save/restore ; information regarding each active I/O request. ; ; *** ; ; On Entry: ; ; R4 = address of the CSR (controller status register) ; R5 = address of the IDB (interrupt data block) ; R6 = address of the DDB (device data block) ; R8 = address of the CRB (channel request block) ; IPL = IPL$_POWER ; ; returns: ; ; (this routine must preserve all registers except R0-R3) ; ;- CD_CTRL_INIT: ; Initialize controller .IIF DEFINED DEBUG, JSB G^INI$BRK MOVL DDB$L_UCB(R6),UCB ; save address of 1st UCB locally MOVAL PENDQFL,PENDQFL ; zero pending queue list head MOVAL PENDQFL,PENDQBL MOVAL FREEQFL,FREEQFL ; zero free queue list head MOVAL FREEQFL,FREEQBL RSB ; Return ; .page .sbttl Unit Initialization Routine ; ;+ ; ; *** CD_UNIT_INIT -- Readies unit for I/O operations ; ; Functional description: ; ; The operating system calls this routine after calling the ; controller initialization routine: ; ; at system startup ; during driver loading (NOT RELOADING !!) ; during recovery from a power failure ; ; The contents of special UCB fields that must be initialized are ; established by this routine. In particular: ; ; Initialize the cache LBN and "reference" queues ; Set device status flags ; Reserve two system page table entries for use in mapping ; users' data buffers (in addition to the SPTE reserved by ; DPT$V_SVP for mapping the cache) ; Save the address of the first UCB associated with the device ; locally to allow a "context switch" from that of the ; cached disk's driver at CD_STARTIO ; Initialize constant fields within TQE reserved for ; IO$_READxBLK!IO$M_TIMED functions ; Initialize constant fields within fork block reserved for ; synchronizing access to the cache when CD_FIPL ; save registers MOVC5 #0,(R5),#0,#,UCB$T_CD_INFO(R5) ; zero UCB information array POPR #^M ; restore registers ; MOVL #!!,- UCB$T_CD_FKB+FKB$W_SIZE(R5) ; init constants in special fork block MOVL #!!,- UCB$T_CD_TQE+TQE$W_SIZE(R5) ; init constants in timer queue element MOVAL CD_TIMEDIO,UCB$T_CD_TQE+TQE$L_FPC(R5) ; define timeout routine entry point ; MOVL #2,R1 ; need 2 extra SPTEs to map user buffers JSB G^IOC$ALLOSPT ; grab SPTEs BLBC R0,99$ ; branch on error ASHL #2,R2,R0 ; convert SVPN into byte offset and ADDL2 R3,R0 ; add base addr of system page table MOVL R0,UCB$L_CD_USPTE(R5) ; save address of SPTE MOVL #,(R0)+ MOVL #,(R0) ; fill in SPTEs = valid, kernal r/w ASHL #9,R2,R0 ; convert SVPN to system virt addr BBSS #VA$V_SYSTEM,R0,34$ ; make it S0 address 34$: MOVL R0,UCB$L_CD_USVA(R5) ; save system virtual address to ; reference in accessing user buffers ; MOVL UCB$L_SVPN(R5),R2 ; pickup SVPN for mapping cache buffers ASHL #2,R2,R0 ; convert SVPN into byte offset and ADDL2 R3,R0 ; add base addr of system page table MOVL R0,UCB$L_CD_CSPTE(R5) ; save address of SPTE to map cache MOVL #,(R0) ; fill in SPTE = valid, kernal r/w ASHL #9,R2,R0 ; convert SVPN to system virt addr BBSS #VA$V_SYSTEM,R0,44$ ; make it S0 address 44$: MOVL R0,UCB$L_CD_CSVA(R5) ; save system virtual address to ; reference in accessing cache buffers ; ; *** add additional initialization code here... ; BISW #UCB$M_ONLINE,UCB$W_STS(R5) ; Set unit online on success 99$: RSB ; Return ; .page .sbttl Driver Unload Routine ; ;+ ; ; *** CD_UNLOAD -- Called by VMS when the driver is being reloaded. ; Though the documentation is sketchy, it appears that the ; driver may block unloading/reloading by returning an error ; indication to the caller. This will be done if the ; cache is active. ; ; !!!!!NOTE!!!!!! ; As currently coded with the pending/free PIB queue lists in the ; driver's local address space, all the PIBs allocated must ; be consolidated and released as IRPs to non-paged pool or ; the cache driver must be flagged as not reloadable. In the ; latter case, this routine should not be included in the driver. ; If the PIBs are not released, the non-paged pool associated with ; them is lost forever (unitl next system boot) with each reload. ; ; *** ; ; On entry: ; ; ??? ; ; returns: ; ; R0 = SS$_NORMAL if cache is inactive; SS$_DEVACTIVE otherwise ; ;- ; CD_UNLOAD: .IIF DEFINED DEBUG, JSB G^INI$BRK MOVL UCB,R0 ; get local copy of UCB address 2$: BITL #UCB$M_CD_ACTIVE!UCB$M_CD_ALLOC,UCB$L_DEVDEPEND(R0) ; test for cache active and/or allocated BNEQ 8$ ; branch if either, block reload MOVL UCB$L_LINK(R0),R0 ; step to next UCB BNEQ 2$ ; loop til done MOVAL PENDQFL,R0 ; check pending queue for outstanding I/O CMPL R0,4(R0) ; queue empty ?? BNEQ 8$ ; branch if not MOVZWL #SS$_NORMAL,R0 ; setup successful status BRB 9$ 8$: MOVZWL #SS$_DEVACTIVE,R0 ; setup error status 9$: RSB ; .page .sbttl READxBLK FDT Routine ; ;+ ; ; *** CD_READBLK -- Called to pre-process the IO$_READxBLK function which ; will return a record of each write QIO operation (or read ; and write operations with IO$M_CD_RRD modifier) issued to the ; cached disk in the user's buffer. Two special subfunctions/modifiers ; may be specified with the IO$_READxBLK function: ; ; IO$M_TIMED - Controls the timeout for I/O completion ; based on the value in QIO parameter 3 (P3) ; specified in mSecs with a resolution of +/- 10mSec. ; ; IO$M_CD_RRD - Includes records for each read operation ; queued to the cached disk. ; ; QIO parameter 1 (P1) must define the address of the buffer ; to be filled with records of each write (and read) operation ; intercepted by the cache driver. QIO parameter 2 (P2) defines ; the size of the P1 buffer. QIO parameter 3 (P3) along with ; the IO$M_TIMED modifier specifies a timeout value for the ; I/O in milliseconds. ; ; The format of the data records placed in the user's buffer ; are described in the comments for the CD_RECORD subroutine. ; ; *** ; ; On Entry: ; ; R0-R2 - scratch registers ; R3 - address of the IRP (I/O request packet) ; R4 - address of the PCB (process control block) ; R5 - address of the UCB (unit control block) ; R6 - address of the CCB (channel control block) ; R7 - bit number of the I/O function code ; R8 - address of the FDT table entry for this routine ; R9-R11 - scratch registers ; AP - address of the 1st function dependent QIO parameter ; ; returns: ; ; T.B.S. ; ;- CD_READBLK: MOVZWL #SS$_DEVINACT,R0 ; assume device is inactive BBC #UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),29$ ; branch if caching inactive MOVZWL #SS$_BADPARAM,R0 ; assume no buffer to fill MOVL P2(AP),R1 ; pickup buffer size CMPL R1,#CD_MAXRECL ; must be at least 1 record long BLSSU 29$ ; branch if not MOVL P1(AP),R0 ; pickup buffer address JSB G^EXE$READCHK ; check buffer access (sets IRP$W_BCNT) PUSHL R3 ; save IRP address ADDL2 #12,R1 ; add in overhead of system buffer JSB G^EXE$BUFFRQUOTA ; check for quota limitations BLBC R0,28$ ; branch on error JSB G^EXE$ALLOCBUF ; allocate buffer from non-paged pool BLBC R0,28$ ; branch on error MOVL (SP)+,R3 ; recover IRP address MOVW R1,IRP$W_BOFF(R3) ; save buffer packet size MOVL R2,IRP$L_SVAPTE(R3) ; and start address MOVL PCB$L_JIB(R4),R0 ; pickup JIB address SUBL2 R1,JIB$L_BYTCNT(R0) ; adjust buffered I/O byte count ADDL3 #12,R2,(R2)+ ; save start address of data MOVL P1(AP),(R2) ; save address of process's buffer CLRL IRP$L_MEDIA(R3) ; assume no timeout BBC #IO$V_TIMED,IRP$W_FUNC(R3),24$ ; branch if I/O not timed MULL3 #10*1000,P3(AP),IRP$L_MEDIA(R3) ; save timeout in 100nSec units 24$: JMP G^EXE$QIODRVPKT ; queue packet to cache driver ; 28$: MOVL (SP)+,R3 ; recover IRP address 29$: BRW CD_FINISHIOC ; force I/O completion ; .page .sbttl SENSEMODE/SENSECHAR FDT Routine ; ;+ ; ; *** CD_SENSEMODE -- Called to pre-process the IO$_SENSEMODE/IO$_SENSECHAR ; functions. One special subfunction/modifier may be specified ; with the IO$_SENSEMODE/IO$_SENSECHAR function: ; ; IO$M_CD_GETINFO - Returns an information block with ; pertinent preformance characteristics for the ; cache driver. ; ; QIO parameter 1 (P1) must define the address of a buffer ; which will be filled with the information from the UCB fields ; beginning with UCB$T_CD_INFO up to the end of the UCB. ; QIO parameter 2 (P2) defines the size of the P1 buffer; ; data that cannot fit within the specified buffer will be ; truncated and SS$_BUFFEROVF status returned. ; ; When the IO$M_CD_GETINFO modifier is not specified, CDDRIVER ; returns control to the standard VMS EXE$SENSEMODE routine ; which returns the contents of UCB$L_DEVDEPEND in the second ; I/O status long word. ; ; *** ; ; On Entry: ; ; R0-R2 - scratch registers ; R3 - address of the IRP (I/O request packet) ; R4 - address of the PCB (process control block) ; R5 - address of the UCB (unit control block) ; R6 - address of the CCB (channel control block) ; R7 - bit number of the I/O function code ; R8 - address of the FDT table entry for this routine ; R9-R11 - scratch registers ; AP - address of the 1st function dependent QIO parameter ; ; returns: ; ; T.B.S. ; ;- CD_SENSEMODE: BBS #IO$V_CD_GETINFO,IRP$W_FUNC(R3),2$ ; branch if special function RSB ; else, return through EXE$SENSEMODE 2$: MOVL P2(AP),R1 ; pickup size of user buffer BEQL 12$ ; branch if undefined MOVL P1(AP),R0 ; pickup user buffer address JSB G^EXE$READCHK ; verify write access to user's buffer CLRL R2 ; assume cache inactive BBC #UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),9$ ; branch if inactive MOVL #UCB$K_CD_LENGTH-UCB$T_CD_INFO,R2 ; pickup number of bytes of info 9$: PUSHR #^M ; free registers MOVC5 R2,UCB$T_CD_INFO(R5),#0,R1,(R0) ; copy information to user buffer POPR #^M ; recover registers 12$: MOVZWL #SS$_NORMAL,R0 ; setup completion status MOVL #UCB$K_CD_LENGTH-UCB$T_CD_INFO,R1 ; get # bytes of information CMPL P2(AP),R1 ; check if data was truncated BGEQ 18$ ; branch if not MOVZWL #SS$_BUFFEROVF,R0 ; reset status MOVL P2(AP),R1 ; and return user buffer size 18$: INSV R1,#16,#16,R0 ; save transfer byte count in IOSB MOVL UCB$L_CD_DSKUCB(R5),R1 ; setup second I/O status long word CD_FINISHIO: JMP G^EXE$FINISHIO ; and complete the I/O ; .page .sbttl SETMODE/SETCHAR FDT Routine ; ;+ ; ; *** CD_SETMODE -- Called to pre-process the IO$_SETMODE/IO$_SETCHAR ; functions. There are currently four major functions controlled ; by I/O function modifiers: ; ; IO$M_CD_STARTUP - Starts disk data caching ; IO$M_CD_SHUTDOWN- Terminates disk data caching ; IO$M_CD_PURGE - Invalidates all cached data ; IO$M_CD_EXTEND - Adds blocks to cache ; ; These modifiers are mutually exclusive; specifying more than ; one major modifier returns the SS$_ILLIOFUNC error. ; ; A special modifier, IO$M_CD_ZERO may be specified alone or in ; combination with any other major modifier. This modifier zeroes ; the contents of all the statistical counters maintained by ; the cache driver. ; ; The following additional modifiers may be specified only for the ; IO$M_STARTUP function: ; ; IO$M_CD_FLUSH - When specified, disk write operations ; invalidate any cached data; otherwise, ; data already cached is updated both on disk ; and cache. ; IO$M_CD_LOAD - When specified, all data written to disk ; is loaded into the cache (only effective ; when IO$M_CD_FLUSH is NOT specified); ; otherwise, only data already cached is updated ; on a write to disk. ; IO$M_CD_PAGSWAPIO- When specified, page and swap I/O operations ; are cached; otherwise, page and swap I/O ; operations are not cached. Even if not ; being cached, page or swap write operations ; invalidate any cached data. ; ; All SETMODE/SETCHAR function processing is performed immediately ; and may necessitate raising the IPL to fork level to synchronize ; access with common/system data structures. ; ; ; *** ; ; On Entry: ; ; R0-R2 - scratch registers ; R3 - address of the IRP (I/O request packet) ; R4 - address of the PCB (process control block) ; R5 - address of the UCB (unit control block) ; R6 - address of the CCB (channel control block) ; R7 - bit number of the I/O function code ; R8 - address of the FDT table entry for this routine ; R9-R11 - scratch registers ; AP - address of the 1st function dependent QIO parameter ; ; returns: ; ; T.B.S. ; ;- .enable LSB CD_SETMODE: MOVZWL #SS$_NORMAL,R0 ; assume success BICL3 #^C,IRP$W_FUNC(R3),R1 ; pickup major modifiers only FFS #IO$V_FMODIFIERS,#IO$S_FMODIFIERS,R1,R2 ; determine major modifier bit BEQL 12$ ; branch if none specified, ; check for IO$M_CD_ZERO BBCC R2,R1,5$ ; clear major modifier bit 5$: MOVZWL #SS$_ILLIOFUNC,R0 ; assume error TSTL R1 ; more than one major modifier ?? BNEQ CD_FINISHIOC ; branch if yes, illegal CMPL R2,#IO$V_CD_STARTUP ; startup function ?? BEQL 14$ ; branch if yes MOVZWL #SS$_DEVINACT,R0 ; else, assume cache is inactive 12$: BBC #UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),CD_FINISHIOC ; abort SHUTDOWN/EXTEND/PURGE if ; cache is inactive DSBINT #IPL$_SYNCH ; else, must synchronize with driver 14$: CASEB R2,#IO$V_FMODIFIERS,#4 15$: .word STARTUP-15$ .word SHUTDOWN-15$ .word EXTEND-15$ .word PURGE-15$ ; ; *** Common SETMODE completion handling...zero out statistical counters ; if IO$M_CD_ZERO modifier specified for any IO$_SETMODE function. ; ZERO: BBC #IO$V_CD_ZERO,IRP$W_FUNC(R3),24$ ;; branch if IO$M_CD_ZERO not spec'd ZERO1: PUSHR #^M ;; free registers MOVC5 #0,(R5),#0,#,UCB$T_CD_STATS(R5) ;; zero out cache statistics POPR #^M ;; restore registers 24$: ENBINT ;; re-enable interrupts ;* BRB CD_FINISHIOC ; and finish I/O ; CD_FINISHIOC: JMP G^EXE$FINISHIOC ; finish off request ; SHUTDOWN: ; *** Terminate disk caching BSBW CD_SHUTDOWN ;; shutdown disk caching BRB ZERO ;; branch to common exit point ; PURGE: ; *** Invalidate/purge entire contents of cache BSBW CD_PURGE ;; purge contents of cache BRB ZERO ;; branch to common exit point ; EXTEND: ; *** Add memory to cache MOVZWL #SS$_BADPARAM,R0 ;; assume error MOVL P1(AP),R1 ;; get # blocks to add BLEQ CD_FINISHIOC ;; branch if undefined ADDL3 R1,UCB$L_CD_CACHESIZE(R5),R2 ;; compute ultimate cache size CMPL R2,#CD_CACHESIZE ;; new size > maximum allowed ?? BGTRU ZERO ;; branch if yes, bad number BSBW CD_EXTEND ;; add memory to cache BRB ZERO ;; branch to common exit point ; STARTUP: ; *** Handle the IO$M_STARTUP function... ; running at IPL$_ASTDEL !! MOVZWL #SS$_DEVACTIVE,R0 ; assume error BBS #UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),CD_FINISHIOC ; branch if caching already active MOVZWL #SS$_BADPARAM,R0 ; assume error in cache parameters CMPL P2(AP),#CD_CACHESIZE ; size of cache > maximum allowed ?? BGTRU CD_FINISHIOC ; branch if yes, illegal ; MOVL #UCB$M_CD_DEFAULT,R0 ; get default device dependent status MOVZWL IRP$W_FUNC(R3),R1 ; pickup I/O function word BBC #IO$V_CD_FLUSH,R1,110$ ; branch to enable caching writes BISL #UCB$M_CD_FLUSH,R0 ; else, invalidate cache on write 110$: BBC #IO$V_CD_LOAD,R1,112$ ; branch to update cached data on write BISL #UCB$M_CD_LOAD,R0 ; else, load all data written 112$: BBC #IO$V_CD_PAGSWAPIO,R1,114$ ; branch to ignore page/swap I/O BISL #UCB$M_CD_PAGSWAPIO,R0 ; else, cache page/swap I/O 114$: MOVL R0,UCB$L_DEVDEPEND(R5) ; save new settings ; ; *** Following code extracted from EXE$QIOREQ to validate an I/O channel ; and determine the UCB address of the disk to be cached. This was ; chosen to allow the SYS$ASSIGN system service to do all the work ; involved with logical name translation and scanning of the device ; tables. ; MOVZWL #SS$_IVCHAN,R0 ; assume error BICL3 #<^XFFFF0000!>,P1(AP),R1 ; get channel assigned to disk BEQL 129$ ; branch if illegal CMPW R1,G^CTL$GW_CHINDX ; legal number ?? BGTRU 129$ ; branch if not MNEGL R1,R1 ; convert to channel index ADDL2 G^CTL$GL_CCBBASE,R1 ; get address of CCB MOVZWL #SS$_NOPRIV,R0 ; assume error MOVPSL R2 ; read current PSL EXTZV #PSL$V_PRVMOD,#PSL$S_PRVMOD,R2,R2 ; extract previous mode field CMPB R2,CCB$B_AMOD(R1) ; caller have priv to access channel ?? BLEQ 130$ ; branch if yes, continue 129$: BRW CD_FINISHIOC ; long branch to terminate I/O 130$: MOVL CCB$L_UCB(R1),R2 ; pickup disk's UCB address MOVL P2(AP),R1 ; recover # pages (blocks) for cache DSBINT #IPL$_SYNCH ; disable interrupts BSBB CD_STARTUP ;; attempt to start cache BRW ZERO1 ;; branch to common exit point .disable LSB ; ; .page .sbttl Startup Disk Data Caching ; ;+ ; ; *** CD_STARTUP -- Initiates caching of disk unit. In associating a disk ; device to the cache driver, the disk driver's DDT table entry for ; the start I/O routine is altered to point to the cache driver's ; start I/O routine. If multiple UCBs were allowed for the cache ; driver, each of which may be directed to different units of the ; same generic disk device/controller, some mechanism must be provided ; for the cache driver to determine the true disk driver start I/O ; routine even after the disk is redirected to the cache driver. ; This disk start I/O routine address is saved in the cache driver's ; UCB along with the address of the DDT which has been redirected ; to the cache driver's start I/O entry point. ; ; If the disk's DDT start I/O entry point has already been ; redirected to the cache driver, each cache driver UCB is scanned ; to find one associated with the same disk device and the saved ; start I/O routine address is copied from that UCB. ; This is required to allow any random sequence of starting and ; stopping disk caching of units on a single generic disk such ; that the disk driver's start I/O point can be restored once caching ; of all the disk's units is terminated. ; ; *** ; ; On Entry: ; ; R1 = Number of pages/blocks to allocate to cache ; R2 = UCB address of disk driver to be cached ; R5 = Address of cache driver UCB ; IPL = IPL$_SYNCH ; ; returns: ; ; R0 = status: ; SS$_NORMAL = successful completion ; SS$_DUPUNIT = caching already active for the specified ; device ; SS$_NOTFILEDEV = attempt to cache non-disk device ; ; R9-R11 destroyed by CD_EXTEND !! ; ;- ; CD_STARTUP: PUSHL R1 ;; save R1 MOVZWL #SS$_NOTFILEDEV,R0 ;; assume error CMPB #DC$_DISK,UCB$B_DEVCLASS(R2) ;; caching disk device ?? BNEQ 9$ ;; branch if not, exit MOVL UCB,R0 ;; setup to scan all other active caches 6$: MOVL UCB$L_CD_DSKUCB(R0),R1 ;; get cached disk's UCB address BEQL 14$ ;; branch if undefined, cache inactive CMPL R1,R2 ;; caching same unit ?? BNEQ 10$ ;; branch if not, okay MOVZWL #SS$_DUPUNIT,R0 ;; setup error status 9$: BRB 39$ ;; and exit 10$: CMPL UCB$L_DDT(R1),UCB$L_DDT(R2) ;; caching same generic device driver ?? BNEQ 14$ ;; branch if not MOVL UCB$L_CD_DSKSTARTIO(R0),UCB$L_CD_DSKSTARTIO(R5) ;; save disk driver's start_io entry pt 14$: MOVL UCB$L_LINK(R0),R0 ;; step to next UCB BNEQ 6$ ;; branch if defined, check it ; MOVL (SP),R1 ;; recover # pages for cache BEQL 20$ ;; branch if 0, allowed BSBW CD_EXTEND ;; grab memory for cache BLBC R0,39$ ;; branch if fails 20$: DSBINT UCB$B_FIPL(R2) ;; make sure we're at disk's FIPL MOVL R2,UCB$L_CD_DSKUCB(R5) ;; save disk driver's UCB address BISL #UCB$M_CD_ACTIVE,UCB$L_DEVDEPEND(R5) ;; and mark cache active ADDL3 UCB$L_DDB(R2),#DDB$T_NAME,R0 ;; point to disk device name in DDB MOVQ (R0)+,UCB$T_CD_NAME(R5) ;; save name of disk device MOVQ (R0),UCB$T_CD_NAME+8(R5) MOVW UCB$W_UNIT(R2),UCB$W_CD_UNIT(R5) ;; save unit number of cached disk MOVL UCB$L_DDT(R2),R0 ;; pickup disk driver's DDT address MOVL R0,UCB$L_CD_DSKDDT(R5) ;; save it in cache UCB for CD_STARTIO MOVL UCB$L_DDT(R5),R1 ;; pickup cache driver's DDT address CMPL DDT$L_START(R0),DDT$L_ALTSTART(R1) ;; disk start_io already revectored ?? BEQL 36$ ;; branch if yes MOVL DDT$L_START(R0),UCB$L_CD_DSKSTARTIO(R5) ;; save disk's start_io entry point MOVL DDT$L_ALTSTART(R1),DDT$L_START(R0) ;; redirect disk IRPs to cache driver 36$: ENBINT ;; drop back to caller's IPL MOVZWL #SS$_NORMAL,R0 ;; show success INSV UCB$L_CD_CACHESIZE(R5),#16,#16,R0 ;; return cache block size in status 39$: MOVL (SP)+,R1 ;; recover R1 RSB ; .page .sbttl Shutdown Disk Data Caching ; ;+ ; ; *** CD_SHUTDOWN -- Terminates disk data caching for specific unit. If there ; are no other units being cached for the same driver, the disk driver's ; start I/O entry point is restored. All pages allocated to the cache ; are released to the free page list. ; ; NOTE: synchronization with CD_STARTIO, CD_IOPOST, and VMS is assured ; provided this routine is called at IPL$_SYNCH by virtue of a ; DSBINT from any IPL below IPL$_QUEUEAST. ; ; *** ; ; On Entry: ; ; R5 = Address of cache driver UCB ; IPL = IPL$_SYNCH ; ; returns: ; ; R0 = Status: ; SS$_NORMAL = success ; SS$_DEVINACT = caching is already disabled ; ; R1-R2, R9-R10 destroyed !! ; ;- ; CD_SHUTDOWN: BICL #UCB$M_CD_ACTIVE,UCB$L_DEVDEPEND(R5) ;; flag cache inactive immediately MOVL UCB$L_CD_DSKUCB(R5),R2 ;; get disk driver's UCB address BEQL 19$ ;; branch if undefined (never ?) CLRL UCB$L_CD_DSKUCB(R5) ;; wipe out cached disk UCB address DSBINT UCB$B_FIPL(R2) ;; make sure we're at disk's FIPL MOVL UCB$L_DDT(R2),R2 ;; point to disk driver's DDT MOVL UCB,R0 ;; setup to scan all UCB's 8$: MOVL UCB$L_CD_DSKUCB(R0),R1 ;; get cached disk's UCB address BEQL 12$ ;; branch if caching inactive CMPL UCB$L_DDT(R1),R2 ;; caching active for another unit ?? BEQL 15$ ;; branch if yes, leave start_io addr 12$: MOVL UCB$L_LINK(R0),R0 ;; step to next UCB BNEQ 8$ ;; branch if defined, check it MOVL UCB$L_CD_DSKSTARTIO(R5),DDT$L_START(R2) ;; restore proper disk start_io routine 15$: ENBINT ;; drop back to caller's IPL BSBW CD_DELETE ;; delete cache(R0-R2, R9-R11 destroyed) MOVZWL #SS$_NORMAL,R0 ;; and set I/O completion status 19$: RSB ; ; .page .sbttl Extend Cache Memory ; ;+ ; ; *** CD_EXTEND -- Called to allocate memory from the free page list for use ; as Cache Data Buffers (CDBs), allocate Cache Identification Blocks ; (CIBs) from non-paged pool, and insert appropriate mapping ; information for each cache data buffer in the CIB. ; ; A check is made to avoid depleting the free page list prior to ; allocating a packet from non-paged pool to hold the CIBs ; required to control the pages being added to the cache. ; Once the packet of CIBs is allocated, pages are removed from ; the free page list by MMG$ALLOCPFN, the PFN data base is set ; appropriately, mapping information for each page is set in the ; CIBs, the LBN associated with the CIBs set to -1 (largest LBN), ; and the CIBs are inserted at the tail of both the "referenced" ; and "LBN" queues. ; ; *** ; ; On Entry: ; ; R1 = number of pages/blocks to add to cache ; R5 = Cache driver UCB address ; IPL = IPL$_SYNCH ; ; returns: ; ; R0 = Status: ; SS$_NORMAL = success ; SS$_INSFMEM = insufficient free memory or non-paged ; pool for cache ; R1 = number of pages actually added to cache ; ; R9-R11 destroyed !! ; ;- ; CD_EXTEND: PUSHR #^M ;; free registers MOVL R1,R4 ;; save number of pages to add to cache CLRL R6 ;; zero # pages added to cache SUBL3 R1,G^SCH$GL_FREECNT,R0 ;; compute # free pages after extend CMPL R0,G^SGN$GL_FREELIM ;; will it drop too low ?? BLEQ 14$ ;; branch if yes, abort operation ; ; *** Next check is adopted from code in VMS MEMORYALC module to check for ; depleting "fluid" pages ; MOVZWL G^MPW$GW_LOLIM,R0 ;; get low limit on modified page list ADDL2 G^SGN$GL_FREELIM,R0 ;; add in low limit on free page list ADDL2 R1,R0 ;; add in pages to remove from free list SUBL3 R0,G^PFN$GL_PHYPGCNT,R0 ;; compute fluid pages left over CMPL R0,G^SGN$GL_MAXWSCNT ;; enough fluid pages for largest WS ?? BGTR 15$ ;; branch if yes 14$: BRB 40$ ;; abort extend ; 15$: MOVL G^PFN$AW_REFCNT,R9 ;; get pointers into PFN data base MOVL G^PFN$AB_STATE,R10 MOVL G^PFN$AB_TYPE,R11 ; 18$: MULL2 #CIB$K_LENGTH,R1 ;; compute size of packet required ADDL2 #12,R1 ;; for CIBs plus 3 long-word header JSB G^EXE$ALONPAGVAR ;; allocate pool for CIBs BLBC R0,40$ ;; branch on error MOVL R2,R8 ;; save address of packet allocated MOVL R1,8(R8) ;; save size of packet in header ADDL3 #12,R8,R7 ;; point R7 to start of CIBs ; ; *** Fill in CIB fields in non-paged pool packet previously allocated. ; In the event the allocation of a PFN failed, the extend operation ; is terminated ; 24$: JSB G^MMG$ALLOCPFN ;; get page from free page list ;; (R2-R3 destroyed) MOVL R0,CIB$L_PFN(R7) ;; save PFN ("valid" bit clear) BLSS 34$ ;; branch on allocation failure MOVL #-1,CIB$L_LBN(R7) ;; set LBN to largest unsigned value INSQUE CIB$L_LBNQFL(R7),@UCB$L_CD_LBNQBL(R5) ;; insert CIB at tail of LBN queue INSQUE CIB$L_REFQFL(R7),@UCB$L_CD_REFQBL(R5) ;; insert CIB at tail of reference queue ; ; *** Fill in the PFN data base. PTE entry intentionally left 0 since ; page is not mapped by PTE yet ; INCW (R9)[R0] ;; increment page reference count MOVB #,(R10)[R0] ;; flag page "active & delete contents" MOVB #1,(R11)[R0] ;; flag page as "system" DECL G^PFN$GL_PHYPGCNT ;; show one less free page ; ADDL2 #CIB$K_LENGTH,R7 ;; step to next CIB AOBLSS R4,R6,24$ ;; loop for all requested pages ; 34$: TSTL R6 ;; added anything to cache ?? BNEQ 39$ ;; branch if yes MOVL R8,R0 ;; recover start address MOVL 8(R8),R1 ;; and size of packet JSB G^EXE$DEANONPGDSIZ ;; deallocate packet of CIBs BRB 40$ ;; and finish up 39$: INSQUE (R8),UCB$L_CD_EXTQFL(R5) ;; link new CIB packet to extend queue 40$: MOVZWL #SS$_INSFMEM,R0 ;; setup failure status MOVL R6,R1 ;; recover # pages added to cache BEQL 45$ ;; branch if at least something added ; ; *** Determine status of request...may want to add a "partial success" status ; code for times when can't get all pages requested (i.e. R4 .NE. R1 -- ; should be rare) ; MOVZWL #SS$_NORMAL,R0 ;; show success BISL #UCB$M_CD_ALLOC,UCB$L_DEVDEPEND(R5) ;; set "cache allocated" flag 45$: ADDL2 R1,UCB$L_CD_CACHESIZE(R5) ;; compute new size of cache INSV UCB$L_CD_CACHESIZE(R5),#16,#16,R0 ;; return cache block size in status POPR #^M ;; recover rest of registers RSB ; .page .sbttl Delete Cache Control and Data Structures ; ;+ ; ; *** CD_DELETE -- Called to release all cache identification (CIB) and ; data (CDB) block structures. The caller is responsible for ; insuring that all pending references to the cache have ; been completed and that all future modifications to the UCB ; are blocked until an IO$_SETMODE!IO$M_STARTUP request is processed. ; ; To delete the cache structures, CD_DELETE scans the LBN ; queue releasing all cache data buffers (CDBs) to the free ; page list. Then, the non-paged pool packets allocated to ; define the CIBs required with each extend operation are ; deallocated. ; ; *** ; ; On Entry: ; ; R5 = Address of cache driver UCB ; IPL = IPL$_SYNCH ; ; returns: ; ; All cache data structures deleted ; ; R0-R2, R9-R11 destroyed ; ;- CD_DELETE: PUSHR #^M ;; free registers MOVAL UCB$L_CD_LBNQFL(R5),R4 ;; pickup address of LBN queue list head CLRL @4(R4) ;; set zero link at end of CIB list MOVL (R4),R4 ;; pickup 1st CIB address BEQL 20$ ;; branch if nothing to delete MOVL G^PFN$AW_REFCNT,R9 ;; get pointers into PFN data base MOVL G^PFN$AB_STATE,R10 MOVL G^PFN$AL_PTE,R11 ; ; *** Release each page from cache to front of free page list by virtue of ; "delete contents" flag being set in PFN STATE array ; 10$: EXTZV #PTE$V_PFN,#PTE$S_PFN,CIB$L_PFN(R4),R0 ;; pull PFN from CIB CLRW (R9)[R0] ;; clear reference count MOVB #,(R10)[R0] ;; flag page "active & delete contents" ;; (insures modify bit cleared) CLRL (R11)[R0] ;; make sure PTE is 0 JSB G^MMG$RELPFN ;; release page to free page list ;; (R1-R3 destroyed !!) INCL G^PFN$GL_PHYPGCNT ;; show one more page available MOVL (R4),R4 ;; step to next CIB BNEQ 10$ ;; branch if more ; ; *** Deallocate packets of CIBs to free pool based on size saved in ; 3 long-word header. ; 20$: REMQUE @UCB$L_CD_EXTQFL(R5),R0 ;; pickup address of CIB packet BVS 25$ ;; branch if empty MOVL 8(R0),R1 ;; set size of pool block to deallocate JSB G^EXE$DEANONPGDSIZ ;; release space to non-paged pool BRB 20$ ;; branch to release next block 25$: MOVAL UCB$L_CD_LBNQFL(R5),R0 ;; clear LBN queue list head MOVL R0,(R0) MOVL R0,4(R0) MOVAL UCB$L_CD_REFQFL(R5),R0 ;; clear reference queue list head MOVL R0,(R0) MOVL R0,4(R0) CLRL UCB$L_CD_CACHESIZE(R5) ;; clear size of cache from UCB BICL #UCB$M_CD_ALLOC,UCB$L_DEVDEPEND(R5) ;; drop cache allocated flag POPR #^M ;; recover registers RSB ; .page .sbttl Purge Cache Contents ; ;+ ; ; *** CD_PURGE -- Called to purge/invalidate entire contents of disk ; cache; processes IO$_SETMODE!IO$M_PURGE QIO funtion. ; Invalidation of cache data simply involves clearing ; the "valid" flag bit associated with the cache data buffer ; PFN field and setting the cached logical block number to -1. ; ; IPL must be raised to IPL$_QUEUEAST to reference/manipulate ; the cache data structures. ; ; *** ; ; On entry: ; ; R5 = Address of cache driver's UCB ; IPL >/= IPL$_QUEUAST ; ; returns: ; ; R0 = status (always SS$_NORMAL) ; R1 = destroyed, contents of cache invalidated ; ;- CD_PURGE: MOVAL UCB$L_CD_LBNQFL(R5),R0 ;; recover LBN queue list head address MOVL R0,R1 ;; save it 2$: MOVL (R0),R0 ;; step to next entry CMPL R0,R1 ;; hit end-of-list ?? BEQL 8$ ;; branch if yes MOVL #-1,CIB$L_LBN(R0) ;; invalidate LBN BICL #^C,CIB$L_PFN(R0) ;; clear everything but PFN BRB 2$ ;; and loop til done 8$: MOVZWL #SS$_NORMAL,R0 ;; setup return status RSB ; ; .page .sbttl Cache Driver Start I/O Entry Point ; ;+ ; ; *** CD_STARTIO -- Called in handling IO$_READxBLK QIO requests for the ; cache driver. These requests allow a process to monitor all ; direct I/O write operations (and read operations when IO$M_CD_RRD ; specified) directed to the cached disk. The user's buffer is ; filled with records by CD_RECORD as each I/O is intercepted ; by the cache driver. As long as UCB$V_BSY remains set, it is ; assumed there is a valid user buffer defined by UCB$L_SVAPTE/ ; UCB$W_BCNT. I/O completes when the user's buffer is filled, ; the I/O is cancelled (CD_CANCELIO), or the timeout interval ; expires (CD_TIMEDIO). ; ; I/O timeout is handled directly through a timer queue element ; which is part of the cache driver UCB. This facility allows ; timed I/O with a resolution of +/- 10mSec rather than the ; standard +/- 1Sec available through the normal driver services ; (WFIKPCH/WFIRCH). ; ; *** ; ; On entry: ; ; R3 = IRP address ; R5 = Cache driver UCB address ; IPL = IPL$_SYNCH (cache driver fork IPL) ; ;- CD_STARTIO: ASSUME IPL$_TIMER EQ IPL$_SYNCH BICL #UCB$M_CD_TIMED!UCB$M_CD_RRD,UCB$L_DEVDEPEND(R5) ;; clear all I/O request flags MOVL IRP$L_MEDIA(R3),R2 ;; pickup timeout interval BEQL 10$ ;; branch if no timeout specified ADDL2 #UCB$T_CD_TQE,R5 ;; point to TQE for timeout MOVL R3,TQE$L_FR3(R5) ;; save IRP address in TQE MOVQ G^EXE$GQ_SYSTIME,R0 ;; pickup current system time ADDL2 R2,R0 ;; compute quad-word expiration time ADWC #0,R1 JSB G^EXE$INSTIMQ ;; insert TQE in timer queue SUBL2 #UCB$T_CD_TQE,R5 ;; recover UCB address BISL #UCB$M_CD_TIMED,UCB$L_DEVDEPEND(R5) ;; show timer active 10$: BBC #IO$V_CD_RRD,IRP$W_FUNC(R3),12$ ;; branch if not recording read QIOs BISL #UCB$M_CD_RRD,UCB$L_DEVDEPEND(R5) ;; enable recording of read QIOs 12$: ADDL2 #12,UCB$L_SVAPTE(R5) ;; point to start of system buffer RSB ;; wait for I/O completion ; .page .sbttl Cancel I/O Entry Point ; ;+ ; ; *** CD_CANCELIO -- Called to handle the device specific actions required ; to terminate an active IO$_READxBLK request for the cache driver. ; ; *** ; ; On Entry: ; ; R2 = Channel index number ; R3 = Address of current IRP ; R4 = Address of PCB for current process ; R5 = Cache driver's UCB address ; R8 = Reason for cancel: ; CAN$C_CANCEL = Call from $CANCEL or $DALLOC ; CAN$C_DASSGN = Call from $DASSGN ; IPL = IPL$_SYNCH (cache driver fork IPL) ; ;- CD_CANCELIO: BBC #UCB$V_BSY,UCB$W_STS(R5),9$ ;; branch if device is idle CMPL IRP$L_PID(R3),PCB$L_PID(R4) ;; PIDs match ?? BNEQ 9$ ;; branch if not CMPW R2,IRP$W_CHAN(R3) ;; same channel ?? BNEQ 9$ ;; branch if not MOVZWL #SS$_CANCEL,R0 ;; setup completion status BRB CD_REQCOM ;; complete the request 9$: RSB ; .page .sbttl Timeout Entry Point ; ;+ ; ; *** CD_TIMEDIO -- Called to terminate an active IO$_READxBLK request ; after the time interval specified with the IO$M_TIMED subfunction ; bit has elapsed. ; ; *** ; ; On entry: ; ; R3 = IRP address ; R5 = Address of TQE in cache driver's UCB ; IPL = IPL$_TIMER (must be = IPL$_SYNCH) ; ;- CD_TIMEDIO: SUBL2 #UCB$T_CD_TQE,R5 ;; point R5 to start of UCB BICL #UCB$M_CD_TIMED,UCB$L_DEVDEPEND(R5) ;; show timer expired MOVZWL #SS$_TIMEOUT,R0 ;; setup completion status BSBB CD_REQCOM ;; complete the I/O ADDL2 #UCB$T_CD_TQE,R5 ;; point to TQE again RSB ;; return to EXE$SWTIMINT ; .page .sbttl READxBLK Completion Handler ; ;+ ; ; *** CD_REQCOM -- Called to force completion of the current active ; IO$_READxBLK request. Any active timer queue element must be ; removed from the timer's queue, the number of bytes to be ; transferred from the system buffer to user's read buffer must ; be computed, and the I/O packet is queued for standard VAX/VMS ; I/O post-processing. ; ; *** ; ; On Entry: ; ; R3 = IRP Address ; R5 = Cache driver UCB address ; IPL = IPL$_SYNCH ; ; CD_REQCOM: BBCC #UCB$V_CD_TIMED,UCB$L_DEVDEPEND(R5),2$ ;; branch if timer inactive REMQUE UCB$T_CD_TQE+TQE$L_TQFL(R5),R1 ;; pull entry from timer queue 2$: SUBL3 @IRP$L_SVAPTE(R3),UCB$L_SVAPTE(R5),R1 ;; compute number of bytes in buffer MOVW R1,IRP$W_BCNT(R3) ;; set number of bytes to copy INSV R1,#16,#16,R0 ;; OR byte count with status CLRL R1 ;; zero 2nd status long-word REQCOM ;; finish I/O ; .page .sbttl Record Direct I/O Operations in User Buffer ; ;+ ; ; *** CD_RECORD -- Inserts pertinent information regarding direct I/O ; operations directed to the cached disk driver. Two forms ; of records are written sequentially to the user's buffer until ; the buffer is filled, a user specified time period has elapsed, ; or the I/O is cancelled: ; ; Logical or Physical I/O: ; ; +-------+-------+-------+-------+ 0 ; | IRP$W_STS | IRP$W_FUNC | ; +-------+-------+-------+-------+ 4 ; | Starting Logical Block | ; +-------+-------+-------+-------+ 8 ; | Transfer Block Count | ; +-------+-------+-------+-------+ 12 ; | Requestor's PID | ; +-------+-------+-------+-------+ 16 ; ; Virtual I/O: ; ; +-------+-------+-------+-------+ 0 ; | IRP$W_STS | IRP$W_FUNC | ; +-------+-------+-------+-------+ 4 ; | Starting Logical Block | ; +-------+-------+-------+-------+ 8 ; | Transfer Block Count | ; +-------+-------+-------+-------+ 12 ; | Requestor's PID | ; +-------+-------+-------+-------+ 16 ; | Starting Virtual Block | ; +-------+-------+-------+-------+ 20 ; |Sequence number| File number | ; +-------+-------+-------+-------+ 24 ; | (reserved) | Rel Vol number| ; +-------+-------+-------+-------+ 28 ; ; *** ; ; On entry: ; ; R1 = starting LBN ; R2 = # blocks to transfer (rounded up) ; R3 = IRP address ; R4 = I/O function code ; R5 = cache driver UCB address ; IPL= IPL$_QUEUEAST ; ;- CD_RECORD: DSBINT #IPL$_SYNCH ; block any I/O cancel/timeout events BBC #UCB$V_BSY,UCB$W_STS(R5),29$ ;; branch if no active IO$_READxBLK PUSHL R1 ;; free R1 MOVL UCB$L_SVAPTE(R5),R0 ;; pickup system buffer address SUBW #16,UCB$W_BCNT(R5) ;; adjust bytes count for next fields MOVW IRP$W_FUNC(R3),(R0)+ ;; save I/O function MOVW IRP$W_STS(R3),(R0)+ ;; and status flags MOVQ R1,(R0)+ ;; save starting LBN/block_count MOVL IRP$L_PID(R3),(R0)+ ;; save PID BBC #IRP$V_VIRTUAL,IRP$W_STS(R3),20$ ;; branch if not virtual I/O SUBW #12,UCB$W_BCNT(R5) ;; adjust bytes count for next fields MOVL IRP$L_SEGVBN(R3),(R0)+ ;; save starting VBN CLRQ (R0) ;; clear file-id MOVL IRP$L_WIND(R3),R1 ;; pickup WCB address BEQL 20$ ;; branch if undefined MOVL WCB$L_FCB(R1),R1 ;; pickup FCB address BEQL 20$ ;; branch if undefined MOVQ FCB$W_FID_NUM(R1),(R0)+ ;; save 3-word file-id CLRW -2(R0) ;; zero segmented I/O flag CMPL IRP$L_BCNT(R3),IRP$L_OBCNT(R3) ;; segmented I/O ?? BEQL 20$ ;; branch if not INCW -2(R0) ;; set segmented I/O flag 20$: MOVL R0,UCB$L_SVAPTE(R5) ;; update buffer address CMPW UCB$W_BCNT(R5),#CD_MAXRECL ;; enough left for biggest record ?? BGEQU 28$ ;; branch if yes PUSHR #^M ;; save registers MOVZWL #SS$_NORMAL,R0 ;; setup completion status MOVL UCB$L_IRP(R5),R3 ;; reload R3 with READxBLK IRP addr BSBW CD_REQCOM ;; finish out request POPR #^M ;; recover registers 28$: MOVL (SP)+,R1 ;; recover R1 ; 29$: ENBINT ;; drop back to caller's IPL RSB ; .page .sbttl Cache Driver Alternate Start I/O Entry Point ; ;+ ; ; *** CD_ALTSTARTIO -- Called to initiate an I/O request for the cached ; disk's driver. The driver dispatch table of the driver for the ; disk being cached is altered to point to this routine while ; the cache driver is active (UCB$V_CD_ACTIVE bit set). All ; I/O requests for all (!!) units attached to that controller ; will enter here. So, appropriate checks must be made to only ; allow those requests for the unit being cached to be acted ; on by the cache driver and redirect all other requests back ; to the disk driver's start I/O routine. ; ; The cache driver only operates on direct I/O's; all ; buffered I/O functions are immediately dispatched back to the ; disk driver. Direct I/O functions are evaluated to determine ; if the cache driver should get involved in the transfer. ; The action to be taken for the request depends if the request ; is a read or write data function. ; ; The logical blocks specified in a read transfer are checked against ; the cache contents, and, if the entire range of blocks to be ; transferred is cached, the request can be satisfied without invoking ; the disk driver. If any data is required from disk, the I/O request ; is dispatched to the disk driver after saving pertinent information ; from the IRP in a "pending I/O identification block" (PIB) and altering ; the IRP$L_PID field to force the VMS I/O post-processing routine ; to reenter the cache driver at CD_IOPOST. CD_IOPOST is responsible ; for loading the data read from disk into the cache to complete the ; operation. ; ; One of three actions are possible when handling write requests. ; First, if any data within the range of the write request is cached, ; it may be invalidated to force a read from disk whenever it is ; needed again. Second, the data written to any blocks currently ; cached may be updated with the new information. Finally, the cache ; can be loaded with the data being written to disk. The decision ; as to which method should be used is controlled by the IO$M_FLUSH ; and IO$M_LOAD modifiers specified when the cache is initialized via ; the IO$_SETMODE function. If IO$M_FLUSH was specified, all writes ; to disk force invalidation of any cached data. If IO$M_LOAD was ; specified, the entire write operation is loaded into cache. ; By default, only those blocks currently cached will be reloaded ; with the data being written to disk. Whenever data is loaded into ; cache, the I/O completion status must be checked to insure that the data ; was written successfully. This is accomplished in the same manner ; employed for read operations: a PIB is allocated for the request and ; the IRP$L_PID field is altered to reenter the cache driver ; at CD_IOPOST on completion of the I/O to load the cache. ; ; *** ; ; On Entry: ; ; R3 = Address of I/O request packet ; R5 = Disk driver's UCB address ; IPL = Disk driver's fork IPL ; ; ;- ; .enable LSB ; 99$: ;* BUG_CHECK HALT ; CD_ALTSTARTIO: MOVAL 99$,R0 ;; setup fatal return address MOVL UCB,R4 ;; get 1st cache driver UCB address 2$: CMPL UCB$L_DDT(R5),UCB$L_CD_DSKDDT(R4) ;; caching some unit for disk driver ?? BNEQ 7$ ;; branch if not MOVL UCB$L_CD_DSKSTARTIO(R4),R0 ;; save disk's start_io entry point CMPL R5,UCB$L_CD_DSKUCB(R4) ;; is this specific unit being cached ?? BEQL 10$ ;; branch if yes 7$: MOVL UCB$L_LINK(R4),R4 ;; step to next UCB BNEQ 2$ ;; branch if defined, check it 9$: JMP (R0) ;; return control to disk driver ; 10$: BBS #IRP$V_BUFIO,IRP$W_STS(R3),9$ ;; ignore all buffered I/Os BBS #IRP$V_PHYSIO,IRP$W_STS(R3),14$ ;; branch if physical I/O, recover LBN MOVL IRP$L_MEDIA(R3),R1 ;; pickup LBN from IRP BBS #UCB$V_NOCNVRT,UCB$W_DEVSTS(R5),24$ ;; branch if FDT did not convert LBN ; ; *** Regenerate disk LBN from cylinder/track/sector ; 14$: MOVZBL UCB$B_TRACKS(R5),R0 ;; get tracks/cylinder MOVZWL IRP$L_MEDIA+2(R3),R1 ;; get cylinder number MULL2 R0,R1 ;; multiply by tracks/cylinder MOVZBL IRP$L_MEDIA+1(R3),R0 ;; get track number ADDL2 R0,R1 ;; accumulate tracks MOVZBL UCB$B_SECTORS(R5),R0 ;; get sectors/track MULL2 R0,R1 ;; multiply by sectors/track MOVZBL IRP$L_MEDIA(R3),R0 ;; get sector number ADDL2 R0,R1 ;; accumulate sectors ; ; *** Compute number of blocks being transferred, fork to special ; IPL$_QUEUEAST level, and diverge for read or write function processing. ; 24$: ADDL3 #^X1FF,IRP$L_BCNT(R3),R2 ;; compute # blocks to read/write ASHL #-9,R2,R2 ;; (rounded up) MOVAL UCB$Q_CD_FLBN(R4),R5 ;; point R5 to special fork block MOVQ R1,(R5)+ ;; save starting LBN/block_count in UCB JSB G^EXE$FORK ;; drop to IPL$_QUEUEAST MOVQ -(R5),R1 ; restore starting LBN/block_count MOVL R4,R5 ; point R5 to cache driver UCB EXTZV #IRP$V_FCODE,#IRP$S_FCODE,IRP$W_FUNC(R3),R4 ; extract I/O function code BBC #IRP$V_FUNC,IRP$W_STS(R3),WRITE ; branch if write function .disable LSB ; .page ; ; *** Handle read functions. Two classes of direct read I/O operations are ; recognized: ; ; 1) IO$_READxBLK functions ; 2) All others ; ; The second case (IO$_READHEAD, etc.) is handled by simply returning ; control to the disk driver. The first case may be subdivided into ; "normal" reads and paging or swapping reads. Dependent on the ; option selected when the cache was initialized, paging and swapping ; I/O may be ignored (default action) since reading the same block(s) ; more than once with this type of operation should be relatively ; infrequent and not benefit from caching. ; ; For normal read transfer requests, the cache driver attempts to ; either read all data from the cache, or setup a list of "pending" ; CIBs in the LBN queue to be loaded with the data once it is read. ; If the entire request can be resolved from the contents of the ; cache, the user's buffer is filled from the cache and the I/O is ; completed without intervention from the disk driver. ; ; When any portion of a read transfer is not cached, the entire ; transfer operation is passed back to the disk driver. The ; reasoning behind this action was, ; ; o A disk read must be performed for at least that ; portion of data not cached anyway ; o Most transfers are small (<10 blocks) such that ; disk seek time >> data transfer time ; o Data transfers from disk are DMA requiring no ; CPU cycles whereas copying data from cache does ; ; Prior to returning control to the disk driver, a list of ; CIBs corresponding with each block in the transfer request ; is setup in the LBN queue. If a block of data already ; resides in the cache, the CIB associated with that block ; is left alone. For blocks not cached, a "pending" CIB is ; allocated which will be loaded from the user's buffer ; when the read completes. The IRP$L_PID field, starting ; LBN and address of the associated CIB are saved in a ; "pending I/O identification block" (PIB), and the IRP$L_PID ; field is reset to force IOC$IOPOST to call the cache ; driver back at CD_IOPOST on completion of the operation. ; ; NOTE: Cache driver uses the special IO$_QUEUEAST fork IPL ; to do all of its work !! Also, when transfer byte count is ; not block aligned, the entire last block must be cached to ; be resolved as a "hit". CD_REASSIGN is called to assign ; CIBs for all non-cached data AND determine if the entire ; transfer can be resolved from the cache. However, when the data ; is loaded into cache, only the full blocks read will be loaded ; into the cache. The end result is that a CIB will be ; assigned to cache the last block of data which is only partially ; transfered into the user's buffer, and will never be declared ; valid. Ultimately this CIB will trickle through the reference ; queue and be reassigned. It has been judged that the extra ; checking required to circumvent this problem is not worth it. ; ADDENDUM: One can't simply force this last CIB to the end of the ; reference queue since an overlapping I/O operation issued while ; the first I/O is still pending may include that final block in toto ; and find the CIB reassigned by the time the second QIO completes. ; ; *** ; ; On entry: ; ; R1 = starting LBN ; R2 = # blocks to transfer (rounded up) ; R3 = IRP address ; R4 = I/O function code ; R5 = cache driver UCB address ; IPL= IPL$_QUEUEAST ; READ: BBC #UCB$V_CD_RRD,UCB$L_DEVDEPEND(R5),2$ ; branch if not recording read QIOs BSBW CD_RECORD ; record operation in user buffer 2$: CMPL R4,#IO$_READPBLK ; read data request ?? BNEQ 19$ ; branch if not, ignore it BITW #IRP$M_SWAPIO!IRP$M_PAGIO,IRP$W_STS(R3) ; swapping or paging I/O ?? BEQL 7$ ; branch if not, cache it BBC #UCB$V_CD_PAGSWAPIO,UCB$L_DEVDEPEND(R5),19$ ; branch if not caching page/swap I/O 7$: INCL UCB$L_CD_RDCNT(R5) ; increment # reads processed ADDL2 R2,UCB$L_CD_RDBLK(R5) ; accumulate # blocks read BBC #UCB$V_CD_ALLOC,UCB$L_DEVDEPEND(R5),19$ ; branch if no cache allocated MOVL R2,R4 ; save read block count CLRL R0 ; zero CIB address for CD_REASSIGN BSBW CD_REASSIGN ; assign CIBs for all blocks not cached CMPL R2,R4 ; # blks in cache = # blks to read ?? BNEQ CD_RECALL ; branch if not, back to disk driver ADDL2 R2,UCB$L_CD_RDHIT(R5) ; count # cache hits BSBW CD_READ ; load user's buffer from cache MOVL UCB$L_CD_DSKUCB(R5),R5 ; switch back to disk driver's UCB SETIPL UCB$B_FIPL(R5) ; synchronize with disk driver REQCOM ; and complete the request ; 19$: BRW CD_RETURN ; pass control back to disk driver ; .page ; ; *** Handle write functions. Three classes of disk write operations ; are recognized: ; ; 1) IO$_WRITExBLK functions ; 2) IO$_WRITECHECK ; 3) All others ; ; Handling of the third case is simple: any cached data is invalidated. ; The relative frequency and type of these requests is unknown, but ; it is most likely a small fraction of the total write requests. The ; function seen most often is IO$_DSE (data storage erase) presumably ; issued by the XQP/ACP as a file is extended. ; ; The write-check function is also assumed to be relatively infrequent ; and closely associated with a IO$_WRITExBLK request. The cache ; driver is only concerned with the completion status of these ; requests since an I/O error would indicate a serious discrepency ; in the contents of the disk. In the same manner discussed previously ; for handling read I/O completion, the IRP$L_PID field, starting ; LBN and address of the associated CIB are saved in a ; "pending I/O identification block" (PIB), and the IRP$L_PID ; field is reset to force IOC$IOPOST to call the cache ; driver back at CD_IOPOST on completion of the operation. ; CD_IOPOST is responsible for invalidating any cached data when ; an I/O error is detected. ; ; Handling of IO$_WRITExBLK requests is modified by the UCB$V_CD_FLUSH ; and UCB$V_CD_LOAD flags defined when the cache was initialized ; (IO$M_CD_FLUSH or IO$M_CD_LOAD IO$_SETMODE modifiers). If ; UCB$V_CD_FLUSH has been set, all writes to the disk invalidate any ; cached data and the data written is NOT loaded into the cache, ; ie. handled identically to the third condition. Invalidating ; any cached data is also done when performing pageing or swapping ; I/O and the UCB$V_CD_PAGSWAPIO flag is cleared (disables caching ; of page/swap operations). ; ; With UCB$V_CD_FLUSH cleared, data from the user's buffer will be ; loaded into cache. The UCB$V_CD_LOAD flag controls what is loaded ; into the cache. When this bit is clear only those blocks currently ; cached will be reloaded with the new data being written. When this ; bit is set (via the IO$_SETMODE IO$M_CD_LOAD modifier) the entire ; transfer operation will be loaded into cache on I/O completion. ; ; The decision to either load data here or defer the load until I/O ; completion has gone back and forth. The advantage of loading data ; here is that subsequent reads for the same blocks could be ; resolved from cache while the write is still in progress. The ; obvious disadvantage is that, if the write fails, the cache contents ; do not truly reflect the data on disk until CD_IOPOST invalidates ; the cache. Also, overlapping write requests for the same block(s) ; enter this driver in FIFO/priority order but may be executed by ; the disk port driver in some other order depending on the seek ; optimization algorithm employed. This could also result ; in the cache contents not reflecting the true contents of the disk. ; ; With loading of cache deferred until I/O completion, the problem ; of the port driver re-ordering the execution of requests is avoided ; since I/O completion is handled in strict FIFO order. The ; disadvantage is that a read request for the same block(s) being ; written will require an extra disk read operation (unless some means ; to determine that the blocks are pending a load on write and ; the read request could be blocked until I/O completion processing ; loads the cache). While no hard data has been collected, it is ; assumed that the probability of issuing a read and write request ; for the same block(s) at the same time is low so loading of ; cache will be deferred to I/O post-processing. ; ; ; NOTE: Regardless of type of direct write I/O operation being ; performed, the cache driver forks to IPL$_QUEUEAST to manipulate ; the cache data structures. ; ; *** ; ; On entry: ; ; R1 = starting LBN ; R2 = # blocks to transfer (rounded up) ; R3 = IRP address ; R4 = I/O function code ; R5 = cache driver UCB address ; IPL= IPL$_QUEUEAST ; WRITE: BSBW CD_RECORD ; record operation in user buffer CLRL R0 ; init CIB addr for ; CD_INVALIDATE/REASSIGN CMPL R4,#IO$_WRITEPBLK ; write data request ?? BNEQ 15$ ; branch if not BITW #IRP$M_SWAPIO!IRP$M_PAGIO,IRP$W_STS(R3) ; paging or swapping I/O ?? BEQL 7$ ; branch if not, cache it BBC #UCB$V_CD_PAGSWAPIO,UCB$L_DEVDEPEND(R5),15$ ; branch if not caching page/swap I/O 7$: INCL UCB$L_CD_WRCNT(R5) ; increment # writes processed ADDL2 R2,UCB$L_CD_WRBLK(R5) ; accumulate # blocks written BBC #UCB$V_CD_ALLOC,UCB$L_DEVDEPEND(R5),CD_RETURN ; branch if no cache allocated BBS #UCB$V_CD_FLUSH,UCB$L_DEVDEPEND(R5),18$ ; branch if loading of cache disabled ; ; *** Setup to load cache. Two options controlled by UCB$V_CD_LOAD: ; 1) If clear, load only those blocks already present in cache ; 2) If set, load all blocks being written (truncated) ; BBC #UCB$V_CD_LOAD,UCB$L_DEVDEPEND(R5),CD_RECALL ; branch if only reloading cached data ASHL #-9,IRP$L_BCNT(R3),R2 ; recover truncated # blocks to write BSBW CD_REASSIGN ; assign CIBs for all blks being written BRB CD_RECALL ; branch to recall cache driver on ; I/O completion ; ; *** For non-standard direct I/O write functions, play it safe by ; invalidating any data that may be cached. ; 15$: BBC #UCB$V_CD_ALLOC,UCB$L_DEVDEPEND(R5),CD_RETURN ; branch if no cache allocated CMPL R4,#IO$_WRITECHECK ; write-check ?? BEQL CD_RECALL ; branch if yes, trap I/O completion 18$: BSBW CD_INVALIDATE ; invalidate any cached data 19$: BRB CD_RETURN ; and return to disk driver ; .page ; ; *** Setup for recall of cache driver on I/O completion ; CD_RECALL: REMQUE @FREEQFL,R2 ; pull PIB from free list BVC 20$ ; branch if successful PUSHR #^M ; free registers DSBINT #IPL$_SYNCH ; synchronize with VMS MOVL #IRP$K_LENGTH,R1 ;; get size of block desired JSB G^EXE$ALONONPAGED ;; allocate IRP from non-paged pool ENBINT ;; drop back to IPL$_QUEUEAST BLBS R0,10$ ; branch on success ;* BUG_CHECK ; take system down HALT 10$: SUBL2 #PIB$K_LENGTH,R1 ; full PIB left in packet ?? BLEQ 15$ ; branch if not INSQUE (R2),FREEQFL ; link PIB to free list ADDL2 #PIB$K_LENGTH,R2 ; step to start of next PIB BRB 10$ ; and continue 15$: POPR #^M ; restore registers BRB CD_RECALL ; and try again ; 20$: MOVL R3,PIB$L_IRP(R2) ; save IRP address MOVL IRP$L_PID(R3),PIB$L_PID(R2) ; save PID MOVL R5,PIB$L_UCB(R2) ; save cache driver's UCB address MOVQ R0,PIB$L_CIB(R2) ; save CIB address and starting LBN MOVAL CD_IOPOST,IRP$L_PID(R3) ; reset PID to call CD_IOPOST INSQUE (R2),@PENDQBL ; insert PIB at end of pending list ; ; *** Return control to disk driver's start I/O routine at proper IPL ; CD_RETURN: PUSHL UCB$L_CD_DSKSTARTIO(R5) ; establish disk driver's entry point MOVL UCB$L_CD_DSKUCB(R5),R5 ; switch back to disk driver's UCB SETIPL UCB$B_FIPL(R5) ;; synchronize with disk driver RSB ;; and chain through disk's start_io ; ; .page .sbttl Cache Driver I/O Post Processing ; ;+ ; *** CD_IOPOST -- Called directly by "jump subroutine" from IOC$IOPOST ; to re-enter the cache driver. At this point, data read from ; disk will be updated in the cache. To restore the proper ; context for the I/O request, a "pending I/O identification block" ; (PIB) must exist in the cache driver's pending queue that points ; to the IRP being processed so that the IRP$L_PID field may ; be restored. The PIB also contains the cache driver UCB address ; and other transfer related information required to complete the ; operation. ; ; Evaluating the completion status of the request is the next step ; in I/O post-processing. In the event the transfer (read or write) ; completed with an error, any cached data is immediately invalidated ; and control returns to the normal VMS I/O post-processing procedure. ; ; On successful completion of a read transfer, the "cache identification ; block" (CIB) assigned for the first block of the transfer is ; recovered from the PIB, and CD_LOAD is called to copy ; all full disk blocks read from the user's data buffer into the cache. ; Only those disk blocks which have a corresponding CIB assigned in ; the LBN queue and are flagged as "invalid" will be loaded with data ; from the user's buffer. This action is important since ; it allows any write operation spanning some or all of the ; same LBNs to invalidate the CIBs previously assigned for a read ; operation thereby blocking the loading of old data into the cache. ; Also, there is no reason to load an already "valid" cache block with ; the same data when two transfers overlap. ; ; Write request completion is handled in a similar manner to ; read operations with the exception that the data is unconditionally ; loaded into cache overwriting any previously cached data. Note that ; if loading cache on a write request has been disabled ; via the IO$_SETMODE!IO$M_STARTUP!IO$M_FLUSH QIO function, the ; CD_STARTIO routine will not setup the request for I/O completion ; processing through this routine. ; ; *** ; ; On Entry: ; ; R5 = IRP address ; IPL = IPL$_IOPOST ; ; (R0-R5 free for use) ; .enable LSB CD_IOPOST: MOVL R5,R3 ; copy IRP address MOVAL PENDQFL,R2 ; point to pending queue list head MOVL R2,R1 ; copy list head address DSBINT #CD_FIPL ; sync to special cache driver FIPL 4$: MOVL (R2),R2 ; step to next PIB CMPL R2,R1 ; at end-of-list ?? BNEQ 8$ ; branch if not, check PIB ;* BUG_CHECK ; else, fatal error...crash system HALT 8$: CMPL PIB$L_IRP(R2),R3 ; PIB linked to IRP ?? BNEQ 4$ ; branch if not, check again ; 10$: REMQUE (R2),R2 ; remove PIB from pending queue MOVL PIB$L_PID(R2),IRP$L_PID(R3) ; restore PID in IRP and... INSQUE (R3),G^IOC$GL_PSFL ; put IRP back in post-proc queue MOVL PIB$L_UCB(R2),R5 ; recover cache driver's UCB address MOVQ PIB$L_CIB(R2),R0 ; restore PIB$L_CIB/LBN to R0/R1 INSQUE (R2),FREEQFL ; insert PIB on free queue BBC #UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),49$ ; branch if caching has been disabled ; ; *** Evaluate completion status. If an error occurs or the transfer byte ; count doesn't the match requested byte count, any cached data is ; invalidated. ; ; *** ; ; Required Registers: ; ; R0 = CIB address allocated for caching first LBN transferred ; R1 = starting LBN ; R3 = IRP address ; R5 = Cache driver UCB address ; 30$: MOVL IRP$L_BCNT(R3),R2 ; get # bytes that should've transfered BLBC IRP$L_IOST1(R3),45$ ; branch on I/O error, invalidate cache TSTW IRP$L_OBCNT+2(R3) ; original byte count > 64K ?? BEQL 36$ ; branch if not (usually) CMPL R2,IRP$L_IOST1+2(R3) ; check extended transfer count BRB 37$ ; branch to check condition codes 36$: CMPW R2,IRP$L_IOST1+2(R3) ; check transfer count 37$: BNEQ 45$ ; branch on error, invalidate cache ; ; *** Load cache from user's buffer on successful completion ; 40$: ASHL #-9,R2,R2 ; get truncated # of blocks transferred BSBW CD_LOAD ; load cache BRB 49$ ; and exit ; ; *** Invalidate any cached data on I/O error ; 45$: ADDL2 #^X1FF,R2 ; round byte count to next higher blk ASHL #-9,R2,R2 ; compute # of blocks to invalidate BSBW CD_INVALIDATE ; invalidate cached data ; 49$: ENBINT ; drop back to IPL$_IOPOST RSB ; and return to IOC$IOPOST .disable LSB ; ; .page ; ; *** Local subroutine to accumulate some statistics on data pulled from cache ; CD_STATS: BBC #CIB$V_VALID,CIB$L_PFN(R0),6$ ; branch if data already invalid BBS #CIB$V_REREAD,CIB$L_PFN(R0),6$ ; branch if data read more than once BBS #CIB$V_WRLOAD,CIB$L_PFN(R0),5$ ; branch if loaded by write INCL UCB$L_CD_RDONCE(R5) ; increment the read-once count BRB 6$ 5$: INCL UCB$L_CD_WRONCE(R5) ; increment the write-once count ; 6$: MOVL #-1,CIB$L_LBN(R0) ; set invalid LBN BICL #^C,CIB$L_PFN(R0) ; clear all flags invalidating data RSB ; and we're done ; .page .sbttl Invalidate Cache Data ; ;+ ; *** CD_INVALIDATE -- Called to invalidate a range of potentially ; cached disk blocks. The CIB associated with the cached block is moved ; to the end of the "reference" queue since it is now free, ; and also moved to the end of the LBN queue with its LBN number ; set to -1. Statistics are accumulated for each block invalidated ; by this procedure based on flags maintained in the high-order bits ; of the PFN field. ; ; *** ; ; On entry: ; R0 = Address of first CIB in range to be invalidated (or 0) ; R1 = Starting LBN ; R2 = # of consecutive LBNs to invalidate ; IPL = IPL$_QUEUEAST ; ; returns: ; R0-R2 destroyed ; ; ;- CD_INVALIDATE: BSBB CD_LOCATE ; make sure pointing to nearest ; cached LBN >/= to LBN in R1 2$: CMPL R1,CIB$L_LBN(R0) ; LBN = cached LBN ?? BNEQ 12$ ; branch if not, skip it PUSHL CIB$L_LBNQBL(R0) ; save pointer to previous CIB REMQUE CIB$L_REFQFL(R0),R0 ; remove CIB from referenced list and INSQUE (R0),@UCB$L_CD_REFQBL(R5) ; insert at end of referenced queue REMQUE CIB$L_LBNQFL-CIB$L_REFQFL(R0),R0 ; remove CIB from LBN list and INSQUE (R0),@UCB$L_CD_LBNQBL(R5) ; insert at end of LBN queue BSBB CD_STATS ; accumulate stats MOVL @(SP)+,R0 ; recover addr of next CIB in LBN queue 12$: INCL R1 ; step to next LBN SOBGTR R2,2$ ; and loop til done RSB ; return to caller ; .page .sbttl Reassign Blocks to Cache ; ;+ ; *** CD_REASSIGN -- Called to create a set of consecutive CIBs corresponding ; with a range of LBNs. If an LBN is already cached, no modification ; to that entry is performed. For LBNs not cached, the least ; referenced cache block is reassigned to cache the new data ; but the data is flagged as "invalid". Statistics are accumulated ; for each block reassigned by this procedure based on flags maintained ; in the high-order bits of the PFN field. ; ; Note that the reassignment of CIBs is in reverse order (high to low ; LBNs) so that the CIB corresponding to the starting LBN is always ; valid and placed at the head of the "reference" queue. ; ; ; *** ; ; On entry: ; ; R0 = Address of first CIB with LBN >/= LBN in R1 (or 0) ; R1 = Starting LBN ; R2 = # of consecutive LBNs to assign ; IPL = IPL$_QUEUEAST ; ; returns: ; ; R0 = Address of first CIB in range assigned to cache data ; R1 = Starting LBN ; R2 = # of valid LBNs in cache ; ;- CD_REASSIGN: PUSHL R3 ; free R3 PUSHL #0 ; clear number of valid LBNs in cache ADDL2 R2,R1 ; compute ending LBN +1 BSBB CD_LOCATE ; make sure pointing to CIB whose ; LBN > ending LBN 4$: DECL R1 ; step to preceeding LBN MOVL CIB$L_LBNQBL(R0),R0 ; and preceeding CIB CMPL R1,CIB$L_LBN(R0) ; LBN cached ?? BEQL 20$ ; branch if yes, skip reassignment MOVL R0,R3 ; save current CIB address REMQUE @UCB$L_CD_REFQBL(R5),R0 ; get addr of least referenced data INSQUE (R0),UCB$L_CD_REFQFL(R5) ; and reinsert it at head of queue SUBL2 #CIB$L_REFQFL,R0 ; point to start of CIB BSBB CD_STATS ; get stats on CIB being reassigned MOVL R1,CIB$L_LBN(R0) ; establish new LBN CMPL R0,R3 ; reassigned current CIB ?? BEQL 24$ ; branch if yes, LBN queue ok (unlikely) REMQUE (R0),R0 ; pull new CIB from LBN queue INSQUE (R0),(R3) ; reinsert CIB in LBN queue BRB 24$ ; and continue 20$: BBC #CIB$V_VALID,CIB$L_PFN(R0),22$ ; branch if data is invalid INCL (SP) ; count # valid LBNs in cache 22$: REMQUE CIB$L_REFQFL(R0),R3 ; pull entry from reference queue INSQUE (R3),UCB$L_CD_REFQFL(R5) ; and re-insert at head 24$: SOBGTR R2,4$ ; and loop til done MOVQ (SP)+,R2 ; restore R2-R3 RSB ; and return to caller ; ; .page .sbttl Locate Cache Control Block by LBN ; ;+ ; ; *** CD_LOCATE -- Called to search the list of CIB's for an entry ; associated with the same or next higher logical block number ; as that specified by the caller. This determines the starting point ; in the LBN queue list for data when a cache "hit" is made, ; or the location in the LBN queue list where data resolved ; from a cache "miss" should be inserted. ; ; Various schemes have been thought of to minimize the number of ; CIBs that CD_LOCATE must search. There will be a point where ; the complexity and/or CPU cost of implementing some sort of ; fast lookup or hashing algorithm outweighs a simple direct ; search of the cache. The following code uses the entry ; at the head of the "reference" queue list as the place ; in the LBN queue to begin a simple search. A more elegant ; approach may follow in future releases. ; ; *** ; ; On Entry: ; ; R0 = Address of CIB to start search with; if undefined (0) ; CD_LOCATE determines where to start the search ; R1 = LBN to search for ; R5 = Address of cache driver UCB ; IPL = IPL$_QUEUEAST ; ; returns: ; ; R0 = Address of CIB containing data for LBN specified ; in R1 or next higher LBN that is in the cache ; ; R1-R5 preserved ; ;- CD_LOCATE: PUSHL R2 ; free R2 MOVAL UCB$L_CD_LBNQFL(R5),R2 ; pickup LBN queue list head TSTL R0 ; caller supplied start point ?? BNEQ 5$ ; branch if yes SUBL3 #CIB$L_REFQFL,UCB$L_CD_REFQFL(R5),R0 ; start at most recently used CIB 5$: CMPL CIB$L_LBN(R0),R1 ; check starting LBN BEQL 20$ ; branch if same, done BGTRU 14$ ; branch if higher, scan backwards 8$: ; *** scan forward through LBN queue MOVL CIB$L_LBNQFL(R0),R0 ; step to next CIB CMPL R0,R2 ; hit end of list ?? BEQL 20$ ; branch if yes, done CMPL CIB$L_LBN(R0),R1 ; cached LBN < caller's LBN ?? BLSSU 8$ ; branch if yes, continue scan forward BRB 20$ ; else, done 14$: ; *** scan backward through LBN queue MOVL CIB$L_LBNQBL(R0),R0 ; step to previous CIB CMPL R0,R2 ; hit beginning of list ?? BEQL 19$ ; branch if yes, almost done CMPL CIB$L_LBN(R0),R1 ; cached LBN > caller's LBN ?? BGTRU 14$ ; branch if yes, continue scan backward BEQL 20$ ; branch if same, done 19$: MOVL CIB$L_LBNQFL(R0),R0 ; point to 1st CIB with larger LBN 20$: MOVL (SP)+,R2 ; recover R2 RSB ; .page .sbttl Load Data into Cache Buffers ; ;+ ; *** CD_LOAD -- Transfers data from user's buffer to cache blocks. ; Pages of the user's buffer are mapped by PFN through the ; system virtual page table entries pointed to by UCB$L_CD_USPTE; ; each page of cache is mapped via the system virtual page ; table entries pointed to by UCB$L_CD_CSPTE. Since the user's ; buffer does not have to be page aligned, two pages must be ; mapped by contiguous page table entries to allow transfer of ; a full 512 byte page with a single MOVC3 instruction. ; All mapping is performed by CD_MAP. ; ; Each block (page) of data transferred from disk to the user's ; buffer will be loaded into the cache based on the linked ; list of cache identification blocks previously allocated ; by the cache driver's start I/O routine. Three special conditions ; must be handled by CD_LOAD: ; ; 1) No CIB in LBN queue for data in user's buffer. ; 2) CIB for LBN data is flagged as invalid. ; 3) CIB for LBN data is flagged as valid. ; ; In the first case, the block of data in the user's buffer cannot be ; cached. The most likely circumstance for this is when a write ; follows a read which is pending and invalidates CIBs associated ; with blocks being read. The second case is the "normal" condition; ; the data from the user's buffer is copied to the cache and the ; "valid" bit is set in the CIB. Handling of the third case is ; dependent on the type of I/O transfer involved. If the transfer ; was a read, the data in the user's buffer is skipped since the ; cache already contains the same data. If the transfer was a write, ; the contents of the cache is overwritten with the data from the ; user's buffer. Whenever a block is loaded into cache in response ; to a write request, the "write load" flag is set along with "valid" ; to aid in collecting statistics on whether loading cache for ; write operations improves performance. ; ; *** ; ; On entry: ; ; R0 = Address of CIB reserved for data from starting LBN ; R1 = Starting LBN ; R2 = Number of blocks (pages) to load into cache ; R3 = IRP address ; R5 = Cache driver UCB address ; IPL = IPL$_QUEUEAST ; ; returns: ; ; R0-R2,R4 = destroyed ; R3 = IRP address ; R5 = Cache driver UCB address ; ;- ; CD_LOAD: BSBB CD_LOCATE ; make sure starting CIB is correct MOVQ IRP$L_SVAPTE(R3),UCB$L_SVAPTE(R5) ; copy transfer parameters to UCB 2$: CMPL R1,CIB$L_LBN(R0) ; LBN cached by current CIB ?? BNEQ 16$ ; branch if not, skip data BBC #IRP$V_FUNC,IRP$W_STS(R3),7$ ; branch if write QIO BBS #CIB$V_VALID,CIB$L_PFN(R0),16$ ; branch if data valid, skip load INCL UCB$L_CD_RDLOAD(R5) ; count # blocks loaded by read BRB 10$ ; and load cache 7$: INCL UCB$L_CD_WRLOAD(R5) ; count # blocks loaded by write BICL #^C,CIB$L_PFN(R0) ; wipe out all flags BBSS #CIB$V_WRLOAD,CIB$L_PFN(R0),10$ ; show that cache loaded by write 10$: PUSHR #^M ; free critical registers BSBB CD_MAP ; map cache and user buffer MOVC3 #512,(R1),(R0) ; copy data to cache POPR #^M ; restore critical registers BBSS #CIB$V_VALID,CIB$L_PFN(R0),15$ ; show data valid 15$: MOVL CIB$L_LBNQFL(R0),R0 ; step to next CIB 16$: ADDL2 #4,UCB$L_SVAPTE(R5) ; step over page of data INCL R1 ; step to next LBN SOBGTR R2,2$ ; loop through entire transfer RSB ; and return ; ; .page .sbttl Read Data from Cache ; ;+ ; *** CD_READ -- Transfers data from cache blocks to user's I/O buffer. ; The number of bytes to be transferred from the cache is ; defined in the IRP$L_BCNT field; the user's transfer buffer ; is defined in the IRP$L_SVAPTE field. ; ; THIS ROUTINE ASSUMES THE CALLER HAS ALREADY VERIFIED THAT THE ; ENTIRE DATA TRANSFER CAN BE SATISFIED BY REFERENCING THE NEXT ; "N" BLOCKS OF DATA FROM THE CACHE !! ; ; ; *** ; ; On entry: ; ; R0 = Address of CIB reserved for data from starting LBN ; R1 = Starting LBN ; R2 = Number of blocks to transfer (rounded up) ; R3 = IRP address ; R5 = Cache driver's UCB address ; IPL = IPL$_QUEUEAST ; ; returns: ; ; R0 = low-order long-word for I/O status block ; R1 = high-order long-word for I/O status block ; R2 = (destroyed) ; R3 = IRP address ; R4 = (destroyed) ; R5 = Cache driver's UCB address ; ;- ; CD_READ: MOVQ IRP$L_SVAPTE(R3),UCB$L_SVAPTE(R5) ; copy transfer parameters to UCB MOVL IRP$L_BCNT(R3),R2 ; pickup transfer byte count MOVL #512,R4 ; setup # of bytes/block 3$: CMPL R4,R2 ; full block (page) left ?? BLEQU 6$ ; branch if yes MOVL R2,R4 ; else, truncate bytes read 6$: PUSHR #^M ; free critical registers BSBB CD_MAP ; map next page in cache (R2/R3 lost) MOVC3 R4,(R0),(R1) ; copy data from cache to user's buffer POPR #^M ; restore registers ADDL2 #4,UCB$L_SVAPTE(R5) ; update transfer SVAPTE BBSS #CIB$V_REREAD,CIB$L_PFN(R0),12$ ; show data has been read 12$: MOVL CIB$L_LBNQFL(R0),R0 ; step to next block SUBL2 R4,R2 ; adjust byte count left BNEQ 3$ ; loop til done MOVL IRP$L_BCNT-2(R3),R0 ; set low-order byte count... MOVW #SS$_NORMAL,R0 ; ...completion status... MOVZWL IRP$L_BCNT+2(R3),R1 ; ...and high-order byte count RSB ; return to caller ; ; .page .sbttl Map Cache and I/O (User) Buffers ; ;+ ; ; *** CD_MAP -- Called to map a page of cached data and two pages ; of the current I/O buffer to system virtual addresses. Two pages ; are mapped for the user's I/O buffer to guarantee valid mapping ; of a full 512 byte page regardless of the buffer's byte offset. ; ; The page of cache memory mapped is dictated by the CIB$L_PFN field ; in the Cache Indentification Block (CIB) passed through R0. ; The I/O buffer address is resolved from the UCB$L_SVAPTE and ; UCB$W_BOFF fields in the cache driver's UCB. The value ; saved at UCB$L_SVAPTE is not altered prior to returning ; control to the caller !! ; ; *** ; ; On Entry: ; ; R0 = Address of CIB associated with cached data to be mapped ; R5 = Address of cache driver's UCB ; IPL = IPL$_QUEUEAST ; ; returns: ; ; R0 = System virtual address of page in cache data buffer ; R1 = System virtual address of page in I/O data buffer ; ; R2-R3 destroyed ; R4-R5 preserved ; ;- CD_MAP: INSV CIB$L_PFN(R0),#PTE$V_PFN,#PTE$S_PFN,@UCB$L_CD_CSPTE(R5) ; insert PFN of cached data in SPTE MOVL UCB$L_CD_USPTE(R5),R2 ; pickup SPTE address to map user buffer MOVL @UCB$L_SVAPTE(R5),R3 ; pickup contents of user's PTE BLSS 4$ ; branch if valid JSB G^IOC$PTETOPFN ; get PFN for invalid PTE 4$: INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R2) ; insert PFN of user buffer in SPTE MOVL UCB$L_CD_USVA(R5),R1 ; get SVA of user buffer just mapped ;* INVALID R1 ; invalidate translation buffer MTPR R1,#PR$_TBIS ; (for V5, don't have INVALID anymore) MOVZWL UCB$W_BOFF(R5),R0 ; get byte offset into page BEQL 18$ ; branch if page aligned MOVL UCB$L_SVAPTE(R5),R3 ; get address of user PTE again MOVL 4(R3),R3 ; and pickup contents of next PTE BLSS 14$ ; branch if valid JSB G^IOC$PTETOPFN ; get PFN for invalid PTE 14$: INSV R3,#PTE$V_PFN,#PTE$S_PFN,4(R2) ; insert PFN for 2nd user page in SPTE ADDL3 #512,R1,R2 ; get SVA of 2nd page ;* INVALID R2 ; and invalidate translation buffer MTPR R2,#PR$_TBIS ; (for V5, don't have INVALID anymore) BISL R0,R1 ; merge byte offset into address 18$: MOVL UCB$L_CD_CSVA(R5),R0 ; pickup SVA for cache buffer mapped ;* INVALID R0 ; and invalidate translation buffer MTPR R0,#PR$_TBIS ; (for V5, don't have INVALID anymore) RSB ; CD_END: .END