	.title	CDDRIVER
	.sbttl	Block-Set Associative Mapping Disk Cache Driver
	.ident	/082092/
;
;+
;*****
;
;	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 implements a
;	 block-set associative cache; "block" size is fixed at one disk
;	 block, blocks/set and number of sets are established
;	 by the user.  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
;
;*****
;	Modification History
;
;	JLPxxx  Jonathan L Pinkley
;		Westinghouse
;		18901 Euclid Avenue
;		Cleveland, OH 44117
;		(216)486-8300 x1335
;
;	JLP001	20-Aug-1992	Handle requests from the MSCP server
;		
;	  Note Well:  Only use this driver to cache disks that are local
;	  to your system.  If the host that is serving the device is not
;	  the only path to the device, do not use this driver, unless
;	  the device is a read only device like a CD-ROM drive.
;
;	  There is NO provision for cache coherency.  It is not safe to
;	  to have even one writer, if there are other readers with local
;	  caching.  Think about it.  If system A and system B have both
;	  done a cache load on LBN 10, and then system A writes to LBN 10,
;	  LBN 10 will be changed on the disk, but when system B reads
;	  LBN 10, it finds that it is in its cache and just returns the
;	  value it had stored locally.
;
;	  It is safe however to server a read/write drive to a Cluster,
;	  assuming there is only a single path from the caching host 
;	  to the disk.  This is true for VAXStation SCSI disks.  The
;	  cache will will be served to the cluster.  If you read logical 
;	  blocks 1-100 into the cache, and then read them again from 
;	  any node in the cluster, before they are flushed, the read 
;	  will be handled from the cache.
;
;	  This is phase 1 of some modifications I hope to make to prevent
;	  disk thrashing on a DRM-600 6 disk CD-ROM minichanger.
;
;--

	.page
	.SBTTL	Conditional assembly definitions

;XDELTA = 1		; define "XDELTA" symbol to enable XDELTA breakpoints

CD_RECORD_ALL = 1	; define if you want all I/O operations to the
			; device to be recored, whether they are acted
			; on by CDDRIVER or just passed back to the
			; client driver.  Useful mostly for debugging
			; purposes.

;DBG_HLT = 1		; define this if you want some additional sanity
			; checks to be performed.  These will not prevent
			; crashes, but will cause them to happen sooner
			; than they would otherwise.  Also, more information
			; about the driver state will be available if we
			; crash sooner than later.  This shouldn't need to
			; be defined after the driver is debugged.

;DBG_CNT = 1		; define this if you want the drive to keep some
			; additional counters.  Again, mostly for debugging.
;



	.page
	.SBTTL	External and Local Symbol Definitions

;
; External symbols
;

	$ACBDEF				; AST control block
	$CANDEF				; Cancel reason codes
	$CCBDEF				; Channel control block
	$CPUDEF				; per-CPU data base
	$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
	$SPLCODDEF			; Fork spin lock indices
	$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		; cache driver specific definitions
	CD$PIBDEF
	CD$TAGDEF
	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_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=<UCB$K_CD_LENGTH>,-	; 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_FLCK,B,SPL$C_IOLOCK8 ; 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
		<DEV$M_AVL-			;   available
		!DEV$M_SHR-			;   shareable
		!DEV$M_IDV-			;   input device
		!DEV$M_ODV>			;   output device
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,-	; Device characteristics
		<DEV$M_NNM>			;   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
		<SETMODE,-		;  set mode
		 SETCHAR,-		;  set characteristics
		 SENSEMODE,-		;  sense mode
		 SENSECHAR,-		;  sense characteristics
		 READVBLK,-		;  read virtual block
		 READPBLK,-		;  read physical block
		 READLBLK>		;  read logical block

	FUNCTAB	,-			; Buffered I/O functions
		<SETMODE,-		;  set mode
		 SETCHAR,-		;  set characteristics
		 SENSEMODE,-		;  sense mode
		 SENSECHAR,-		;  sense characteristics
		 READVBLK,-		;  read virtual block
		 READPBLK,-		;  read physical block
		 READLBLK>		;  read logical block

	FUNCTAB CD_SETMODE,-		; FDT routine for CDDRIVER specific
		<SETMODE,-		;  control functions
		 SETCHAR>

	FUNCTAB	CD_SENSEMODE,-		; FDT routine for CDDRIVER specific
		<SENSEMODE,-		;  control functions
		 SENSECHAR>

	FUNCTAB	+EXE$SENSEMODE,-	; standard VMS FDT routine for
		<SENSEMODE,-		;  sense mode
		 SENSECHAR>		;  sense characteristics

	FUNCTAB	CD_READBLK,-		; FDT routine for CDDRIVER specific
		<READVBLK,-		;  read virtual block
		 READPBLK,-		;  read physical block
		 READLBLK>		;  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

;
; The following are debug counter locations.
;

.if df, DBG_CNT

SIO_OPS:			; count of times through the CDA0 altstartio
	.BLKL	1
SIO_SRV:			; count of server i/os
	.BLKL	1
SIO_IGN:			; count of times cd was skipped
	.BLKL	1
SIO_R:				; count of times cd read was entered
	.BLKL	1
SIO_W:				; count of times cd write was entered
	.BLKL	1
.endc

;
	.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 XDELTA,	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:
;
;		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 at IPL$_QUEUEAST
;
; ***
;
;	On Entry:
;
;		R4  = address of the CSR (controller status register)
;		R5  = address of the UCB (unit control block)
;		IPL = IPL$_POWER
;
;	returns:
;
;		(this routine must preserve all registers except R0-R3)
;
;-
;

CD_UNIT_INIT:				; Initialize unit
.IIF DEFINED XDELTA,	JSB	G^INI$BRK
	MOVL	UCB$L_DDB(R5),R0	; point to device data block
	MOVL	DDB$L_UCB(R0),UCB	; keep "UCB" pointing to 1st UCB
;
	MOVL	#UCB$M_CD_DEFAULT,UCB$L_DEVDEPEND(R5)	; init device status
	CLRL	UCB$L_CD_DSKUCB(R5)	; clear cached disk UCB address
	ASSUME	UCB$L_CD_SETSIZE+4 EQ UCB$L_CD_SETCOUNT
	CLRQ	UCB$L_CD_SETSIZE(R5)	; zero set size (blocks/set) and
					;  number of sets in cache
	CLRL	UCB$L_CD_CACHESIZE(R5)	; zero size of cache (SETSIZE*SETCOUNT)
	ASSUME	UCB$L_CD_TAGSIZE+4 EQ UCB$L_CD_TAGADDR
	CLRQ	UCB$L_CD_TAGSIZE(R5)	; zero size and address of tag block
;
	PUSHR	#^M<R3,R4,R5>		; save registers
	MOVC5	#0,(R5),#0,#<UCB$K_CD_LENGTH-UCB$T_CD_INFO>,UCB$T_CD_INFO(R5)
					; zero UCB information array
	POPR	#^M<R3,R4,R5>		; restore registers
;
	MOVL	#<SPL$C_QUEUEAST*16777216>!<DYN$C_FRK*65536>!<FKB$C_LENGTH>,-
		UCB$T_CD_FKB+FKB$W_SIZE(R5)
					; init constants in special fork block
	MOVL	#<TQE$C_SSSNGL*16777216>!<DYN$C_TQE*65536>!<TQE$C_LENGTH>,-
		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
;
; *** Allocate 2 additional SPTE's to map user's QIO buffers, and compute
;	addresses of the system page table entries and system virtual addresses
;	to be used in mapping cache and user buffers.
;
	MOVL	#2,R2			; need 2 extra SPTEs to map user buffers
	JSB	G^LDR$ALLOC_PT		; grab SPTEs
	BLBC	R0,99$			; branch on error
	MOVL	G^LDR$GL_SPTBASE,R3	; get base address of system page table
	SUBL3	R3,R1,R0		; subtract out base address of SPT and
	ASHL	#7,R0,R0		;  convert SPTE addr to system VA
	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	R1,UCB$L_CD_USPTE(R5)	; save addr of system page table entry
	MOVL	#<PTE$M_VALID!PTE$C_KW!PTE$M_PFN>,(R1)+
	MOVL	#<PTE$M_VALID!PTE$C_KW!PTE$M_PFN>,(R1)
					; fill in SPTEs = valid, kernal r/w
	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	#<PTE$M_VALID!PTE$C_KW!PTE$M_PFN>,(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 (until next system boot) with each reload.
;
; ***
;
;	On entry:
;
;		???
;
;	 returns:
;
;		R0  = SS$_NORMAL if cache is inactive; SS$_DEVACTIVE otherwise
;
;-
;

CD_UNLOAD:
.IIF DEFINED XDELTA,	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$DEBIT_BYTCNT_ALO	; check for quota violation, allocate
					;  buffer, dec. JIB byte count quota
	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
	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<R3,R4,R5>		; free registers
	MOVC5	R2,UCB$T_CD_INFO(R5),#0,R1,(R0)
					; copy information to user buffer
	POPR	#^M<R3,R4,R5>		; 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
;
;
;-
	.enable LSB
CD_SETMODE:
	MOVZWL	#SS$_NORMAL,R0		; assume success
	BICL3	#^C<IO$M_CD_SETMODES>,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
	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, dispatch @IPL$_ASTDEL
	MOVZWL	#SS$_DEVINACT,R0	; assume cache is inactive
12$:
	BBC	#UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),CD_FINISHIOC
					; abort SHUTDOWN/EXTEND/PURGE if
					;  cache is inactive
	FORKLOCK lock=UCB$B_FLCK(R5),-	; else, must synchronize with driver
		 savipl=-(SP),-
		 preserve=NO
14$:
	CASEB	R2,#IO$V_FMODIFIERS,#3
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.
;	Must own driver's fork lock to complete the I/O through ZERO or ZERO1.
;
ZERO:
	BBC	#IO$V_CD_ZERO,IRP$W_FUNC(R3),24$
					;; branch if IO$M_CD_ZERO not spec'd
ZERO1:
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	;; free registers
	MOVC5	#0,(R5),#0,#<UCB$K_CD_LENGTH-UCB$T_CD_STATS>,UCB$T_CD_STATS(R5)
					;; zero out cache statistics
	POPR	#^M<R0,R1,R2,R3,R4,R5>	;; restore registers
24$:
	FORKUNLOCK lock=UCB$B_FLCK(R5),- ;; release driver's fork lock
		   newipl=(SP)+,-
		   preserve=YES
;*	BRB	CD_FINISHIOC		;  and finish I/O
;
CD_FINISHIOC:
	JMP	G^EXE$FINISHIOC		; finish off request
;
SHUTDOWN:		; *** Terminate disk caching
	BSBW	CD_SHUTDOWN		;; shutdown cache driver
	BRB	ZERO			;;  and branch to common handler
;
PURGE:			; *** Purge contents of cache
	BSBW	CD_PURGE		;; invalidate all cached data
	BRB	ZERO			;;  and branch to common handler
;
EXTEND:			; *** Add memory to cache
	MOVZWL	#SS$_BADPARAM,R0	;; assume error
	MOVL	P1(AP),R1		;; get # sets to add
	BLEQ	ZERO			;; abort I/O if undefined
	MULL2	UCB$L_CD_SETSIZE(R5),R1	;; compute # pages required
	ADDL3	R1,UCB$L_CD_CACHESIZE(R5),R2
					;; compute ultimate cache size
	CMPL	R2,#CD_CACHESIZE	;; new size > maximum allowed ??
	BGTRU	ZERO			;; abort I/O if yes
	BSBW	CD_EXTEND		;; add memory to cache
	BRB	ZERO			;;  and branch to common handler
;
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
	MOVZBL	P3(AP),UCB$L_CD_SETSIZE(R5) ; establish assoc set size
	BNEQ	100$			; branch if defined
	INCL	UCB$L_CD_SETSIZE(R5)	; default set size to 1 (direct-mapped)
100$:
	MOVL	P2(AP),R1		; get # sets desired
	BLEQ	106$			; branch if 0, okay
	MULL2	UCB$L_CD_SETSIZE(R5),R1	; compute total cache size
	CMPL	R1,#CD_CACHESIZE	; size of cache > maximum allowed ??
	BGTRU	CD_FINISHIOC		; branch if yes, illegal
;
106$:
	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!<CCB$C_LENGTH-1>>,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
	MULL3	P2(AP),UCB$L_CD_SETSIZE(R5),R1
					; recover # pages (blocks) for cache
	FORKLOCK lock=UCB$B_FLCK(R2),-	; raise IPL to disk driver's fork level
		 savipl=-(SP),-
		 preserve=NO
	MOVB	UCB$B_FLCK(R2),UCB$B_FLCK(R5)
					;; reset cache driver's fork lock index
					;;  to match disk driver's !!
	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 = Cached disk driver's fork IPL
;
;	 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
5$:
	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$:
	BRW	38$			;;  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	5$			;; 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,38$			;; branch if fails
20$:
	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$:
	MOVZWL	#SS$_NORMAL,R0		;; show success
	INSV	UCB$L_CD_CACHESIZE(R5),#16,#16,R0
					;; return cache block size in status
38$:
	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.
;
; ***
;
;	On Entry:
;
;		R5  = Address of cache driver UCB
;		IPL = Cached disk driver's fork IPL
;
;	 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	16$			;; branch if undefined (never ?)
	CLRL	UCB$L_CD_DSKUCB(R5)	;; wipe out cached disk UCB address
	MOVL	UCB$L_DDT(R2),R2	;; point to disk driver's DDT
	MOVL	UCB,R0			;; setup to scan all UCB's
6$:
	MOVL	UCB$L_CD_DSKUCB(R0),R1	;; get cached disk's UCB address
	BEQL	10$			;; branch if caching inactive
	CMPL	UCB$L_DDT(R1),R2	;; caching active for another unit ??
	BEQL	14$			;; branch if yes, leave start_io addr
10$:
	MOVL	UCB$L_LINK(R0),R0	;; step to next UCB
	BNEQ	6$			;; branch if defined, check it
	MOVL	UCB$L_CD_DSKSTARTIO(R5),DDT$L_START(R2)
					;; restore proper disk start_io routine
14$:
	BSBW	CD_DELETE		;; delete cache(R0-R2, R9-R11 destroyed)
	MOVZWL	#SS$_NORMAL,R0		;;  and set I/O completion status
16$:
	RSB
;
;
	.page
	.sbttl	Extend Cache Memory
;
;+
;
; *** CD_EXTEND -- Called to allocate memory from the free page list for use
;	as Cache Data Buffers (CDBs), and allocate a block of memory for Cache
;	Tags from non-paged pool inserting appropriate mapping
;	information for each Cache Data Buffer into the Tag entries.
;
;	When called to extend the size of the cache (UCB$L_CACHESIZE > 0)
;	a new block is allocated from non-paged pool (if required) and
;	the contents of the current tag block is moved to the new tag
;	block prior to adding the additional tags/buffers to the cache.
;
; ***
;
;	On Entry:
;
;		R1  = number of pages/blocks to add to cache
;		R5  = Cache driver UCB address
;		IPL = Cached disk driver's fork IPL
;
;	 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 !!
;
;-
;	

	ASSUME	UCB$L_CD_TAGSIZE+4 EQ UCB$L_CD_TAGADDR
	ASSUME	TAG$L_LBN+4 EQ TAG$L_PFN
	ASSUME	TAG$K_LENGTH EQ 8
CD_EXTEND:
	PUSHR	#^M<R2,R3,R4,R6,R7,R8>	;; free registers
	CLRL	R6			;; zero # pages added to cache
	MOVL	R1,R4			;; copy number of pages to add to cache
	ADDL2	UCB$L_CD_CACHESIZE(R5),R1 ;; add current cache size to extent
	INCL	R1			;; add one more tag for bookkeeping
					;;  (depends on TAG$K_LENGTH >/= 4)
	MULL2	#TAG$K_LENGTH,R1	;; determine # bytes req'd for tags
	CMPL	R1,UCB$L_CD_TAGSIZE(R5)	;; current tag block big enough ??
	BLEQU	20$			;; branch if yes
	JSB	G^EXE$ALONPAGVAR	;; else, allocate new/1st block for tags
	BLBC	R0,29$			;; branch on error
	MULL3	#TAG$K_LENGTH,UCB$L_CD_CACHESIZE(R5),R0
					;; compute # bytes of tags to copy
	BEQL	19$			;; branch if none (1st time)
	MOVQ	R1,-(SP)		;; save R1/R2
	MOVQ	R4,-(SP)		;;  and R4/R5
	MOVC3	R0,@UCB$L_CD_TAGADDR(R5),(R2) ;; copy tags to new block
	MOVQ	(SP)+,R4		;; recover R4/R5
	MOVL	UCB$L_CD_TAGADDR(R5),R0	;; get address...
	MOVL	UCB$L_CD_TAGSIZE(R5),R1	;;  and size of old tag block
	JSB	G^EXE$DEANONPGDSIZ	;; release block to free pool
	MOVQ	(SP)+,R1		;; recover R1/R2
19$:
	MOVQ	R1,UCB$L_CD_TAGSIZE(R5)	;; save size and start address of
					;;  new tag block
20$:	
	LOCK	lockname=MMG,-		;; assume we need this lock !?
		preserve=NO
	SUBL3	R4,G^SCH$GL_FREECNT,R0	;; compute # free pages after extend
	CMPL	R0,G^SGN$GL_FREELIM	;; will it drop too low ??
	BLEQ	29$			;; 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	R4,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	30$			;; branch if yes
29$:
	BRB	50$			;; abort extend
;
30$:
	MOVL	UCB$L_CD_SETSIZE(R5),R7	;; pickup # blocks/set
	MULL3	#TAG$K_LENGTH,UCB$L_CD_CACHESIZE(R5),R8
					;; compute current # bytes of tags
	ADDL2	UCB$L_CD_TAGADDR(R5),R8	;; point to end of tags
	MOVL	G^PFN$AW_REFCNT,R9	;; get pointers into PFN data base
	MOVL	G^PFN$AB_STATE,R10
	MOVL	G^PFN$AB_TYPE,R11
;
40$:
	MOVL	#-1,(R8)+		;; set dummy non-zero LBN
	JSB	G^MMG$ALLOCPFN		;; get page from free page list
					;;  (R2-R3 destroyed)
	MOVL	R0,(R8)+		;; save PFN (flags byte clear)
	BLSS	50$			;; branch on allocation failure
;
; *** 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	#<PFN$M_DELCON!PFN$C_ACTIVE>,(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
;
	SOBGTR	R7,40$			;; loop to allocate 1 set of blocks
	INCL	UCB$L_CD_SETCOUNT(R5)	;; show one more set allocated
	MOVL	UCB$L_CD_SETSIZE(R5),R7	;; recover # blocks/set
	ADDL	R7,UCB$L_CD_CACHESIZE(R5) ;; add blocks allocated to cache size
	ADDL	R7,R6			;; update blocks allocated
	CMPL	R6,R4			;; satisfied request ??
	BLSSU	40$			;; branch if not
;
50$:
	MULL3	#TAG$K_LENGTH,UCB$L_CD_CACHESIZE(R5),R0
					;; compute # bytes of tags
	BEQL	54$			;; branch if nothing allocated
	ADDL	UCB$L_CD_TAGADDR(R5),R0	;; point to end of current tags
	CLRL	(R0)			;; set guard word at end of tags
54$:
	MOVZWL	#SS$_INSFMEM,R0		;; assume failure
	MOVL	R6,R1			;; recover # pages added to cache
	BEQL	59$			;; branch if nothing 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
59$:
	UNLOCK	lockname=MMG,-		;; release lock !?
		condition=RESTORE,-
		preserve=NO
	INSV	UCB$L_CD_CACHESIZE(R5),#16,#16,R0
					;; return cache block size in status
	POPR	#^M<R2,R3,R4,R6,R7,R8>	;; recover rest of registers
	RSB
;
	.page
	.sbttl	Delete Cache Tag and Data Structures
;
;+
;
; *** CD_DELETE -- Called to release all cache data blocks and tag
;	block.  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.
;
;
; ***
;
;	On Entry:
;
;		R5  = Address of cache driver UCB
;		IPL = Cached disk driver's fork IPL
;
;	 returns:
;
;		All cache data structures deleted
;
;		R0-R2, R9-R11 destroyed
;
;-

CD_DELETE:
	BBCC	#UCB$V_CD_ALLOC,UCB$L_DEVDEPEND(R5),29$
					;; drop "allocated" flag, abort call if
					;;  already deallocated
	PUSHR	#^M<R3,R4>		;; free registers
	ADDL3	#TAG$L_PFN,UCB$L_CD_TAGADDR(R5),R4
					;; pickup addr of PFN field in 1st tag
	LOCK	lockname=MMG,-		;; assume we need this lock !?
		preserve=NO
	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,(R4),R0
					;; pull PFN from tag block
	CLRW	(R9)[R0]		;; clear reference count
	MOVB	#<PFN$M_DELCON!PFN$C_ACTIVE>,(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
	ADDL2	#TAG$K_LENGTH,R4	;; step to PFN in next tag block
	SOBGTR	UCB$L_CD_CACHESIZE(R5),10$ ;; loop til done
;
	UNLOCK	lockname=MMG,-		;; release lock !?
		condition=RESTORE,-
		preserve=NO
	MOVL	UCB$L_CD_TAGADDR(R5),R0	;; pickup address...
	MOVL	UCB$L_CD_TAGSIZE(R5),R1	;;  and size of tag block
	JSB	G^EXE$DEANONPGDSIZ	;; release space to non-paged pool
	ASSUME	UCB$L_CD_TAGSIZE+4 EQ UCB$L_CD_TAGADDR
	CLRQ	UCB$L_CD_TAGSIZE(R5)	;; clear misc UCB structures
	ASSUME	UCB$L_CD_SETCOUNT+4 EQ UCB$L_CD_CACHESIZE
	CLRQ	UCB$L_CD_SETCOUNT(R5)
	POPR	#^M<R3,R4>		;; recover registers
;
29$:
	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
;
;-

	ASSUME	TAG$L_LBN+4 EQ TAG$L_PFN
	ASSUME	TAG$K_LENGTH EQ 8
CD_PURGE:
	MOVL	UCB$L_CD_TAGADDR(R5),R0	;; get address of tag block
	BEQL	8$			;; branch if undefined
	MOVL	UCB$L_CD_CACHESIZE(R5),R1 ;; get number of tags in cache
4$:
	MOVL	#-1,(R0)+		;; set bogus non-zero LBN
	BSBW	CD_STATS		;; accum stats on cache block
	TSTL	(R0)+			;; step over PFN
	SOBGTR	R1,4$			;;  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 = Cached disk driver's 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 = Cached disk driver's 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
	FORKLOCK lock=UCB$B_FLCK(R5),-	; grab cache driver's fork spin lock
		 savipl=-(SP),-
		 preserve=NO
	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
	FORKUNLOCK lock=UCB$B_FLCK(R5),- ;; release driver's fork spin lock
		   newipl=(SP)+,-
		   preserve=NO
	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 = Cached disk driver's fork IPL
;
;
CD_REQCOM:
	BBCC	#UCB$V_CD_TIMED,UCB$L_DEVDEPEND(R5),2$
					;; branch if timer inactive
	LOCK	lockname=TIMER,-	;; grab timer spin lock
		preserve=YES
	REMQUE	UCB$T_CD_TQE+TQE$L_TQFL(R5),R1
					;; pull entry from timer queue
	UNLOCK	lockname=TIMER,-	;; release timer spin lock
		condition=RESTORE,-
		preserve=YES
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
;		| Segmented I/O | 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 = Cached disk driver's fork IPL
;
;-
CD_RECORD:
	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
	DECW	-2(R0)			;; set segmented I/O flag true
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<R2,R3,R4,R5>	;; 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<R2,R3,R4,R5>	;; recover registers
28$:
	MOVL	(SP)+,R1		;; recover R1
;
29$:
	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 which will load the cache.
;
; ***
;
;	On Entry:
;
;		R3  = Address of I/O request packet
;		R5  = Disk driver's UCB address
;		IPL = Cached disk driver's fork IPL
;
;
;-
;
	.enable LSB
;
99$:
;
;  The intent is to never get here, but if we do we want to find out.
;  If we don't crash here, we will crash within a short time anyway
;  so lets crash is a way that is easy to find.  This originally
;  was just the HALT.  On the VAXStation 3100, this resulted in a 
;  reboot without producing a crash dump.  I want a dump when the
;  system crashes.  Accessing a non-existant S1 page works fine on
;  a VAXStation 3100.
;
;*	BUG_CHECK

	TSTL	@#-1			;; Access non-existant S1 page
	HALT
;
CD_ALTSTARTIO:
	MOVAL	99$,R0			;; setup fatal return address
	MOVL	UCB,R4			;; get 1st cache driver UCB address

.iif df dbg_cnt, INCL SIO_OPS		;; increment times visited

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$:

.iif df dbg_cnt, INCL SIO_IGN		;; increment times the driver skipped
.iif df dbg_hlt, TSTL R0		;;   sanity check, should be S0 address
.iif df dbg_hlt, BGEQ 99$		;;   if P0 or P1 stop NOW!

	JMP	(R0)			;; return control to disk driver

10$:

.if ndf CD_RECORD_ALL

;  Note that we will still pass any buffered I/O functions directly to the
;  real driver, however, if we are recording everything, we need to do 
;  some more stuff before we can dismiss this IRP.  

	BBS	#IRP$V_BUFIO,IRP$W_STS(R3),9$
					;; ignore all buffered I/Os
.endc

	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, 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)
	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

.if df CD_RECORD_ALL

;  Now if we were recording everything, and this was a buffered I/O, 
;  we have to restore driver context and return to the real driver.

	BSBW CD_RECORD	;; record operation in user buffer
	BBC	#IRP$V_BUFIO,IRP$W_STS(R3),25$
					;; ignore all buffered I/Os
	BRW	CD_RETURN		;; restore disk context and 
					;;  let the original driver
					;;  handle it.
25$:
.endc

	BBS	#IRP$V_FUNC,IRP$W_STS(R3),READ
					;; branch if read function
	BBC	#IRP$V_SRVIO,IRP$W_STS(R3),WRITE ;; if not from MSCP server 
					;; assume it is a write
; JLP001   Jon Pinkley
; if we get here, it is an I/O that was generated by the MSCP server.
; In the cases I have seen, the IRP$W_STS field never had the IRP$V_FUNC
; bit set, so it could not be counted on to differentiate read and write
; I/O.  Also, it appears that all the I/O from the server appears as 
; Physical I/O function codes, without the IRP$V_PHYSIO bit set, and the 
; MEDIA field has LBN's in it.  This allowed the old CDDRIVER to work 
; with served devices, although the counters were counting all server 
; I/O's as writes.  Since the caching driver thought the I/O's were
; writes, no attempt was made to get data from the cache, and therefore
; all I/O from a client resulted in real disk I/O operations.
; Also note that the virtual I/O functions have already been handled by
; the client FDT routines, so srvio operations will never be for Virtual 
; I/O.  I am not sure about paging and swapping I/O from a client.

.iif df DBG_CNT, INCL SIO_SRV		;; increment server io ops

	CMPL	R4, #IO$_READPBLK	;; check for a served read
	BEQL	READ			;;  and do the right thing
	CMPL	R4, #IO$_WRITEPBLK	;; check for a served write
	BEQL	WRITE			;;  and treat it as a write
	BRW	CD_RETURN		;; otherwise let the original
					;;  driver handle it
	
	.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 pass control to the disk
;	driver to read the data which can then be loaded into the cache
;	on I/O completion.  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, the IRP$L_PID field and
;	starting LBN 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.
;
; ***
;
;	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 = Cached disk driver's fork IPL
;
READ:

.iif df DBG_CNT, INCL SIO_R	;; increment read ops in cdriver
.iif ndf CD_RECORD_ALL, BSBW CD_RECORD	;; record operation in user buffer

	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	6$			;; branch if not, cache it
	BBC	#UCB$V_CD_PAGSWAPIO,UCB$L_DEVDEPEND(R5),19$
					;; branch if not caching page/swap I/O
6$:
	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
	BSBW	CD_CHECK		;; check for blocks in cache
	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
	BLBC	R0,CD_RECALL		;; branch on error, back to disk driver
	MOVL	UCB$L_CD_DSKUCB(R5),R5	;; switch back to disk driver's UCB
	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.
;
;	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.
;
;	However, at this point in the I/O request nothing is done to the
;	cache.  The main reason for this is the remote possibility that
;	a read may be pending completion for the same blocks.  If the cache
;	is invalidated or loaded here, I/O post-processing of the read may
;	reload the cache with stale data.  To prevent this, some mechanism would
;	have to be implemented to block loading of these "stale" blocks.
;	Also, due to seek optimization algorithms, the order requests are
;	performed is not necessarily the order in which they are processed
;	by the driver start I/O routine.
;
;	The cache driver defers all handling of write requests to I/O
;	completion processing.  In the same manner discussed previously
;	for handling read I/O completion, the IRP$L_PID field and starting
;	LBN 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.
;	
; ***
;
;	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 = Cached disk driver's fork IPL
;
WRITE:

.iif df DBG_CNT, INCL SIO_W		;; increment write ops in cd
.iif ndf CD_RECORD_ALL, BSBW CD_RECORD	;; record operation in user buffer

	CMPL	R4,#IO$_WRITEPBLK	;; write data request ??
	BNEQ	8$			;; branch if not
	BITW	#IRP$M_SWAPIO!IRP$M_PAGIO,IRP$W_STS(R3)
					;; paging or swapping I/O ??
	BEQL	6$			;; branch if not, cache it
	BBC	#UCB$V_CD_PAGSWAPIO,UCB$L_DEVDEPEND(R5),8$
					;; branch if not caching page/swap I/O
6$:
	INCL	UCB$L_CD_WRCNT(R5)	;; increment # writes processed
	ADDL2	R2,UCB$L_CD_WRBLK(R5)	;; accumulate # blocks written
8$:
	BBC	#UCB$V_CD_ALLOC,UCB$L_DEVDEPEND(R5),CD_RETURN
					;; branch if no cache allocated
	BRB	CD_RECALL		;; branch to recall cache driver on
					;;  I/O completion
;
	.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<R0,R1,R3>		;; free registers
	MOVL	#IRP$K_LENGTH,R1	;; get size of block desired
	JSB	G^EXE$ALONONPAGED	;; allocate IRP from non-paged pool
	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<R0,R1,R3>		;; 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
	MOVL	R1,PIB$L_LBN(R2)	;; save 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
;
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
	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/written 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, CD_LOAD is called to copy
;	all full disk blocks read from the user's data buffer into the cache.
;	In the event the block is already cached, no loading of data is
;	performed.
;
;	On successful completion of a write transfer, the particular type
;	of write request and control flags established when the cache was
;	initialized determine if the blocks transfered are loaded or invalidated
;	in the cache.  IO$_WRITExBLK and IO$_WRITECHECK transfers will be
;	handled in the following fashion:
;
;		1) IO$_WRITExBLK and IO$_WRITECHECK with IO$M_FLUSH option
;		    forces all cached blocks to be invalidated.
;		2) IO$_WRITExBLK and IO$_WRITECHECK with IO$M_LOAD option
;		    forces all cached blocks to be loaded into cache.
;		3) IO$_WRITExBLK and IO$_WRITECHECK with neither LOAD or FLUSH
;		    options loads only those blocks already cached.
;		4) Page/Swap IO$_WRITExBLK requests with IO$M_PAGSWAPIO option
;		    forces all cached blocks to be invalidated.
;		5) Any other direct I/O write function forces invalidation
;		    of any cached data.
;
; ***
;
;	On Entry:
;
;		R5  = IRP address
;		IPL = IPL$_IOPOST
;
;		(R0-R5 free for use)
;
	.enable LSB
CD_IOPOST:
	MOVL	R5,R3			; copy IRP address
	MOVL	IRP$L_UCB(R3),R5	; get disk driver's UCB address
	MOVAL	PENDQFL,R2		; point to pending queue list head
	MOVL	R2,R1			; copy list head address
	FORKLOCK lock=UCB$B_FLCK(R5),-	; sync to disk/cache driver fork IPL
		savipl=-(SP),-
		preserve=NO
5$:
	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	5$			;; 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
;
; *** Re-inserting IRP to post-processing queue appears to be more involved
;	with SMP.  Based on SDA examination of IOC$IOPOST routine, "primary"
;	processor may empty two queues but all others empty only their
;	per-CPU queue.  IOC$REQCOM inserts things on the global self-relative
;	queue @IOC$GQ_POSTIQ, so how do things ever get to the per-CPU
;	post processing queue ??
;
	FIND_CPU_DATA	R0		;; get to per-CPU database
	CMPL	CPU$L_PHY_CPUID(R0),G^SMP$GL_PRIMID
					;; primary CPU ??
	BNEQ	19$			;; branch if not
	CLRL	R0			;; init interlock timeout
15$:
	INSQHI	(R3),G^IOC$GQ_POSTIQ	;; push IRP back on post-proc queue
	BCC	20$			;; branch if inserted	
	AOBLSS	#^X0DBBA0,R0,15$	;; try again, "DBBA0" constant from VMS
;*	BUG_CHECK	;#^X47C		;; drop system
	HALT
19$:
	INSQUE	(R3),CPU$L_PSFL(R0)	;; push IRP back on post-proc queue
20$:
	MOVL	PIB$L_UCB(R2),R5	;; recover cache driver's UCB address
	MOVL	PIB$L_LBN(R2),R1	;; restore starting LBN
	INSQUE	(R2),FREEQFL		;; insert PIB on free queue
	BBC	#UCB$V_CD_ACTIVE,UCB$L_DEVDEPEND(R5),49$
					;; branch if caching has been disabled
;
; *** Caching active, 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:
;
;		R1 = starting LBN
;		R3 = IRP address
;		R5 = Cache driver UCB address
;
	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	29$			;; branch if not (usually)
	CMPL	R2,IRP$L_IOST1+2(R3)	;; check extended transfer count
	BRB	30$			;; branch to check condition codes
29$:
	CMPW	R2,IRP$L_IOST1+2(R3)	;; check transfer count
30$:
	BNEQ	45$			;; branch on error, invalidate cache
	EXTZV	#IRP$V_FCODE,#IRP$S_FCODE,IRP$W_FUNC(R3),R4
					;; extract I/O function code
	CMPL	R4,#IO$_READPBLK	;; was this a read request?
	BEQL	40$			;; if so, load cache
;
; *** Determine action to take on completion of write based on function
;
	BBS	#UCB$V_CD_FLUSH,UCB$L_DEVDEPEND(R5),45$
					;; branch if flushing cache on any write
	BITW	#IRP$M_SWAPIO!IRP$M_PAGIO,IRP$W_STS(R3)
					;; paging or swapping I/O ??
	BEQL	36$			;; branch if not
	BBC	#UCB$V_CD_PAGSWAPIO,UCB$L_DEVDEPEND(R5),45$
					;; branch if not caching page/swap I/O
36$:
	CMPL	R4,#IO$_WRITECHECK	;; write check ??
	BEQL	49$			;; branch if yes, no change on disk
	CMPL	R4,#IO$_WRITEPBLK	;; "normal" write ??
	BNEQ	45$			;; branch if not, invalidate cache
;
; *** Load cache from user's buffer on successful completion of I/O
;
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$:
	FORKUNLOCK lock=UCB$B_FLCK(R5),- ;; drop back to IPL$_IOPOST
		   newipl=(SP)+,-
		   preserve=NO
	RSB				;  and return to IOC$IOPOST
	.disable LSB
;
;

	.page
;
; *** Local subroutine to accumulate some statistics on data pulled from cache
;
CD_STATS:
	BBC	#TAG$V_VALID,(R0),6$	; branch if data already invalid
	BBS	#TAG$V_REREAD,(R0),6$	; branch if data read more than once	
	BBS	#TAG$V_WRLOAD,(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$:
	BICL	#^C<TAG$M_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.  Any cache tags associated with the cached
;	block are cleared.  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:
;		R1  = Starting LBN
;		R2  = # of consecutive LBNs to invalidate
;		IPL = Cached disk driver's fork IPL
;
;	returns:
;		R0-R2 destroyed
;	
;
;-

CD_INVALIDATE:
	PUSHL	R3			; free R3
	BSBB	CD_LOCATE		; pickup address of tag set for
					;  starting LBN
2$:
	MOVL	UCB$L_CD_SETSIZE(R5),R3	; get number of tags/set	
3$:
	CMPL	(R0)+,R1		; tag match desired LBN ??
	BNEQ	6$			; branch if not
	MOVL	#-1,-4(R0)		; reset LBN to bogus non-zero value
	BSBB	CD_STATS		; accumulate stats, clear flags
6$:
	TSTL	(R0)+			; skip over PFN field
	SOBGTR	R3,3$			;  loop through all tags in set
	INCL	R1			; bump LBN
	TSTL	(R0)			; hit end of tag list ??
	BNEQ	12$			; branch if not
	MOVL	UCB$L_CD_TAGADDR(R5),R0	; wrap to start of tag list
12$:
	SOBGTR	R2,2$			;  loop through all LBNs
	MOVL	(SP)+,R3		; recover R3
	RSB				; return to caller

;
	.page
	.sbttl	Determine if Disk Blocks are Cached
;
;+
; *** CD_CHECK -- Called to check the cache tag entries corresponding
;	with a range of LBNs.  If the LBN is cached, its reference count
;	is reset to the largest unsigned integer count.  Otherwise, the
;	reference count for valid data is decremented to show its a candidate
;	to be reused.
;
; ***
;
;	On entry:
;
;		R1  = Starting LBN
;		R2  = # of consecutive LBNs to assign
;		IPL = Cached disk driver's fork IPL
;
;	returns:
;
;		R0  = Address of first tag set assigned to cache data
;		R1  = Starting LBN
;		R2  = # of valid LBNs in cache
;	
;-
	ASSUME	TAG$L_LBN+4 EQ TAG$L_PFN
	ASSUME	TAG$K_LENGTH EQ 8
CD_CHECK:
	BSBB	CD_LOCATE		; find starting tag set for LBN
	PUSHR	#^M<R0,R1,R3>		; save registers
	PUSHL	#0			; clear number of valid LBNs in cache
3$:
	MOVL	UCB$L_CD_SETSIZE(R5),R3	; get number of tags/set	
4$:
	CMPL	(R0)+,R1		; tag match desired LBN ??
	BNEQ	10$			; branch if not
	BBC	#TAG$V_VALID,(R0),12$	; branch if data invalid (never !?)
	INCL	(SP)			; count valid LBNs
	BISL	#<TAG$M_REFCNT>,(R0)	; reset reference count to all 1's
	BRB	12$
10$:
	BBC	#TAG$V_VALID,(R0),12$	; branch if data invalid
	SUBL2	#TAG$K_REFCNT,(R0)	; adjust reference count
12$:
	TSTL	(R0)+			; skip over PFN field
	SOBGTR	R3,4$			; loop through all tags in set
	INCL	R1			; step to next LBN
	TSTL	(R0)			; hit end of tag list ??
	BNEQ	18$			; branch if not
	MOVL	UCB$L_CD_TAGADDR(R5),R0	; wrap to start of tag list
18$:
	SOBGTR	R2,3$			; loop through all LBNs
	MOVL	(SP)+,R2		; recover # cached LBNs
	POPR	#^M<R0,R1,R3>
	RSB				;  and return to caller
;
;
	.page
	.sbttl	Locate Cache Tag Block by LBN
;
;+
;
; *** CD_LOCATE -- Determines address of a set of tag entries that may
;	be used to cache a particular LBN.  The tag set is determined
;	by the remainder of dividing the LBN by the number of sets in the
;	cache.  The number and size of the cache tags associated with each
;	set are then used to compute the address in the cache tag block
;	for start of the set.
;
; ***
;
;	On Entry:
;
;		R1  = starting LBN
;		R5  = Address of cache driver UCB
;		IPL = Cached disk driver's fork IPL
;
;	 returns:
;
;		R0  = Address of first tag block in n-block set associated with
;			LBN specified in R1
;
;		R1-R5 preserved
;
;-

CD_LOCATE:
	DIVL3	UCB$L_CD_SETCOUNT(R5),R1,R0
					; divide LBN by number of sets in cache
	MULL2	UCB$L_CD_SETCOUNT(R5),R0 ; and compute remainder to determine
	SUBL3	R0,R1,R0		;   which tag set to start with
	MULL2	UCB$L_CD_SETSIZE(R5),R0	; multiply by number of tags/set
	MULL2	#TAG$K_LENGTH,R0	;  then number of bytes/tag
	ADDL2	UCB$L_CD_TAGADDR(R5),R0	; add in base address of tag block
	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 contents of
;	the cache tag block.  If a tag is found with the same LBN
;	and the contents of the block are valid, the data is overwritten
;	if the I/O was a write, or skipped if the I/O was a read.
;	If a tag is found with the same LBN and the contents of the
;	block is invalid (should never happen), the data is loaded on
;	a read, loaded on a write if IO$M_LOAD option has been
;	specified, or is skipped on a write if IO$M_LOAD has not bee
;	specified.  If no tag is found for the LBN, the I/O was 
;	a write, and the IO$M_LOAD option has not been specified,
;	the data is skipped.  Finally, if no tag is found for the LBN
;	and the I/O was a read or write with IO$M_LOAD specified,
;	the oldest tag in the set is overwritten with the new LBN
;	and the data is loaded into the cache.
;
; ***
;
;	On entry:
;
;		R1  = Starting LBN
;		R2  = Number of blocks (pages) to load into cache
;		R3  = IRP address
;		R5  = Cache driver UCB address
;		IPL = Cached disk driver's fork IPL
;
;	 returns:
;
;		R0-R2,R4 = destroyed
;		R3 = IRP address
;		R5 = Cache driver UCB address
;
;-
;
	ASSUME	TAG$L_LBN+4 EQ TAG$L_PFN
	ASSUME	TAG$K_LENGTH EQ 8
CD_LOAD:
	PUSHL	R8			; free R8
	MOVQ	R6,-(SP)		; free R6-R7
	EXTZV	#IRP$V_FCODE,#IRP$S_FCODE,IRP$W_FUNC(R3),R8
					; extract I/O function code
	MOVQ	IRP$L_SVAPTE(R3),UCB$L_CD_SVAPTE(R5)
					; copy transfer parameters to UCB
	MULL3	#TAG$K_LENGTH,UCB$L_CD_SETSIZE(R5),R7
					; determine # bytes per tag set
	BSBB	CD_LOCATE		; get address of first tag set
4$:
	ADDL3	R0,R7,R6		; save start address of next tag set
	MOVL	R0,R4			; seed pointer to oldest tag
6$:
	CMPL	R1,(R0)+		; LBN cached by current tag ??
	BNEQ	12$			; branch if not
	BBC	#TAG$V_VALID,(R0),10$	; branch if data invalid (never ?!)
	CMPL	R8,#IO$_READPBLK	; was this a read request?
	BNEQ	20$			; branch if write QIO, reload cache
	BRB	30$			; else, skip load on read
10$:
	MOVL	#-1,-4(R0)		; for internal consistency, set bogus
	BICL2	#^C<TAG$M_PFN>,(R0)	;  non-zero LBN with oldest tag
12$:
	CMPL	(R0)+,TAG$L_PFN(R4)	; current tag older than last ??
	BGEQU	15$			; branch if not
	SUBL3	#TAG$K_LENGTH,R0,R4	; remember oldest tag address
15$:
	CMPL	R0,R6			; hit end of tag set ??
	BLSSU	6$			; branch if not, keep checking
;
; *** If here, no tag in set matches LBN.  If the block should be loaded
;	need to reassign the oldest tag to map the new data.
;
	CMPL	R8,#IO$_READPBLK	; was this a read request?
	BEQL	18$			; always load cache on read
	BBC	#UCB$V_CD_LOAD,UCB$L_DEVDEPEND(R5),30$
					; branch if loading on write disabled
18$:
	MOVL	R4,R0			; recover address of oldest tag in set
	MOVL	R1,(R0)+		; reset correct LBN
;
; *** Load Cache block from user's data buffer.
;
20$:
	BSBW	CD_STATS		; accumulate some stats on tag
	CMPL	R8,#IO$_READPBLK	; was this a read request?
	BNEQ	24$			; branch if a write QIO

	INCL	UCB$L_CD_RDLOAD(R5)	; show one more block loaded by read
	BRB	26$

24$:
	INCL	UCB$L_CD_WRLOAD(R5)	; show one more block loaded by write
	BISL	#TAG$M_WRLOAD,(R0)	; show block loaded by write
26$:
	PUSHR	#^M<R0,R1,R2,R3,R5>	; free critical registers
	BSBW	CD_MAP			; map cache and user buffer
	MOVC3	#512,(R1),(R0)		; copy data to cache
	POPR	#^M<R0,R1,R2,R3,R5>	; restore critical registers
	BISL	#TAG$M_REFCNT!TAG$M_VALID,(R0)
					; set reference count and valid flag
30$:
	ADDL2	#4,UCB$L_CD_SVAPTE(R5)	; step over page of data
	INCL	R1			; step to next LBN
	MOVL	R6,R0			; get address of next tag set
	TSTL	(R0)			; hit end of tags ??
	BNEQ	36$			; branch if not
	MOVL	UCB$L_CD_TAGADDR(R5),R0	; wrap to start of tag sets
36$:
	DECL	R2			; adjust count left
	BLEQ	39$
	BRW	4$			; loop through entire transfer
39$:
	MOVQ	(SP)+,R6		; recover R6/R7
	POPL	R8			; recover R8
	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 tag set associated with starting LBN
;		R1  = Starting LBN
;		R2  = Number of blocks to transfer (rounded up)
;		R3  = IRP address
;		R5  = Cache driver's UCB address
;		IPL = Cached disk driver's fork IPL
;
;	 returns:
;
;		Low bit clear in R0 on error, with:
;			R1  = Starting LBN
;			R2  = (destroyed)
;			R3  = IRP address
;			R4  = (destroyed)
;			R5  = Cache driver's UCB address
;
;		Low bit set in R0 on success, with:
;			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
;
;-
;
	ASSUME	TAG$L_LBN+4 EQ TAG$L_PFN
	ASSUME	TAG$K_LENGTH EQ 8
CD_READ:
	MOVQ	R6,-(SP)		; free R6/R7
	PUSHL	R1			; save starting LBN for error recovery
	MOVQ	IRP$L_SVAPTE(R3),UCB$L_CD_SVAPTE(R5)
					; copy transfer parameters to UCB
	MULL3	#TAG$K_LENGTH,UCB$L_CD_SETSIZE(R5),R7

					; determine # bytes per tag set
	MOVL	IRP$L_BCNT(R3),R2	; pickup transfer byte count
	MOVL	#512,R4			; setup # of bytes/block
5$:
	ADDL3	R0,R7,R6		; save start address of next tag set
6$:
	CMPL	R1,(R0)+		; LBN cached by current tag ??
	BEQL	12$			; branch if yes, copy data
	TSTL	(R0)+			; skip over PFN
	CMPL	R0,R6			; hit end of tag set ??
	BLSSU	6$			; branch if not
10$:			; *** Valid data for LBN not in tag set, bad error
	CLRL	R0			; flag error
	BRB	39$
12$:
	BBC	#TAG$V_VALID,(R0),10$	; branch if not valid, bad error
	CMPL	R4,R2			; full block (page) left ??
	BLEQU	16$			; branch if yes
	MOVL	R2,R4			; else, truncate bytes to read
16$:
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; 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<R0,R1,R2,R3,R4,R5>	; restore registers
;
20$:
	BISL	#TAG$M_REREAD,(R0)	; show data has been read again
	SUBL2	R4,R2			; adjust byte count left
	BLEQ	30$			; branch if done
	ADDL2	#4,UCB$L_CD_SVAPTE(R5)	; update transfer SVAPTE
	INCL	R1			; update LBN
	MOVL	R6,R0			; point to next tag set
	TSTL	(R0)			; hit end of tags ??
	BNEQ	5$			; branch if not, continue
	MOVL	UCB$L_CD_TAGADDR(R5),R0	; wrap to head of tag block
	BRB	5$			;  and continue
;
30$:
	MOVL	IRP$L_BCNT-2(R3),R0	; set low-order byte count...
	MOVW	#SS$_NORMAL,R0		;  ...completion status...
	MOVZWL	IRP$L_BCNT+2(R3),(SP)	; overwrite saved starting LBN with
					;  high-order byte count
;
39$:
	MOVL	(SP)+,R1		; recover starting LBN...or byte count
	MOVQ	(SP)+,R6		; recover R6/R7
	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 PFN field
;	passed through R0.  The I/O buffer address is resolved from
;	the UCB$L_CD_SVAPTE and UCB$W_CD_BOFF fields in the cache driver's UCB.
;	The value saved at UCB$L_CD_SVAPTE is not altered prior to returning
;	control to the caller !!
;
;	For VMS V5 with SMP, the INVALID macro has been replaced by the
;	INVALIDATE_TB macro.  The latter form forces invalidation of
;	translation buffers on all CPUs.  But, the cache driver is the only
;	code using the PTE's in question, and it has already synchronized
;	with all other CPU code threads by virtue of the driver's fork
;	spin lock.  Therefore, there is really no need to invalidate all
;	CPU translation buffers since they can't get at the PTE until
;	we release the SPL$C_IOLOCKx spin lock.
;
; ***
;
;	On Entry:
;
;		R0  = Address of PFN field for cached data to be mapped
;		R5  = Address of cache driver's UCB
;		IPL = Cached disk driver's fork IPL
;
;	 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:

	MOVL	UCB$L_CD_CSVA(R5),R2	; pickup SVA for cache buffer mapped
;*	INVALIDATE_TB	addr=R2,-	; invalidate translation buffer
;*	inst1=<INSV (R0),#PTE$V_PFN,#PTE$S_PFN,@UCB$L_CD_CSPTE(R5)>
;*					;  inserting PFN of cached data in SPTE
	MTPR	R2,#PR$_TBIS		; invalidate local CPU TB
	INSV	(R0),#PTE$V_PFN,#PTE$S_PFN,@UCB$L_CD_CSPTE(R5)
					;  and insert PFN of cached data in SPTE
	MOVL	UCB$L_CD_USPTE(R5),R0	; pickup SPTE address to map user buffer
	MOVL	@UCB$L_CD_SVAPTE(R5),R3	; pickup contents of user's PTE
	BLSS	5$			; branch if valid
	JSB	G^IOC$PTETOPFN		; get PFN for invalid PTE
5$:
	MOVL	UCB$L_CD_USVA(R5),R1	; get SVA for user buffer being mapped
;*	INVALIDATE_TB	addr=R1,-	; invalidate translation buffer
;*			inst1=<INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R0)>
;*					;  inserting PFN of user buffer in SPTE
	MTPR	R1,#PR$_TBIS		; invalidate local CPU TB
	INSV	R3,#PTE$V_PFN,#PTE$S_PFN,(R0)
					;  and insert PFN of cached data in SPTE
	TSTW	UCB$W_CD_BOFF(R5)	; page aligned buffer ??
	BEQL	18$			; branch if yes
	MOVL	UCB$L_CD_SVAPTE(R5),R3	; get address of user PTE again
	MOVL	4(R3),R3		;  and pickup contents of next PTE
	BLSS	15$			; branch if valid
	JSB	G^IOC$PTETOPFN		; get PFN for invalid PTE
15$:
	ADDL3	#512,R1,R2		; get SVA of 2nd page
;*	INVALIDATE_TB	addr=R2,-		; invalidate translation buffer
;*			inst1=<INSV R3,#PTE$V_PFN,#PTE$S_PFN,4(R0)>
;*					;  inserting PFN for 2nd page in SPTE
	MTPR	R2,#PR$_TBIS		; invalidate local CPU TB
	INSV	R3,#PTE$V_PFN,#PTE$S_PFN,4(R0)
					;  and insert PFN of cached data in SPTE

	BISW	UCB$W_CD_BOFF(R5),R1	; merge byte offset into address	
18$:
	MOVL	UCB$L_CD_CSVA(R5),R0	; recover SVA for cache buffer mapped
	RSB

;
CD_END:
	.END
