	.library /sys$share:lib/
	$wcbdef
.ntype	__,R31			;  set EVAX nonzero if R31 is a register
.if eq <__ & ^xF0> - ^x50
EVAX = 1
.iff
;EVAX = 0
.endc
	.if	df,evax
alpha=1
bigpage=1
addressbits=32
.iif ndf WCB$W_NMAP, evax=2		;... EVAX=2 -> Step2 (ndf as of T2.0)
.iif ndf WCB$W_NMAP, step2=1		;... EVAX=2 -> Step2 (ndf as of T2.0)
	.endc
	.if	ndf,step2	;use vax or step1 code in here only
	.TITLE	JFDRiver	;skeleton driver implementing ucb linkage
	.IDENT	'V01b'
; Copyright 1993 Glenn C. Everhart
; All rights reserved
; Author: Glenn C. Everhart
;
;evax = 1
; Above automates defining "evax". Also define "step2" where needed.

	.if	ndf,evax
	.macro	driver_data
	.PSECT	$$$105_PROLOGUE
	.endm
	.macro driver_code
	.PSECT	$$$115_DRIVER
	.endm
	.endc
; above for Alpha only.
;
; Function: Implement frag avoider for generic disks.
;x$$$dt=0
;
;vms$$v6=0	;add forvms v6 def'n
vms$v5=1
; define v5$picky also for SMP operation
v5$picky=1
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

; 
; EXTERNAL SYMBOLS
; 
	.library /SYS$SHARE:LIB/

;	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$DYNDEF ;define dynamic data types
	$DCDEF				;DEFINE DEVICE CLASS
	$DDBDEF				;DEFINE DEVICE DATA BLOCK
	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS
	$DPTDEF				;DEFINE DRIVER PROLOGUE TABLE
	$EMBDEF				;DEFINE ERROR MESSAGE BUFFER
	$IDBDEF				;DEFINE INTERRUPT DATA BLOCK
	$IODEF				;DEFINE I/O FUNCTION CODES
	$DDTDEF				; DEFINE DISPATCH TBL...
	$ptedef
	$vadef
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$irpedef
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	.if	df,step2
	$fdt_contextdef
	.endc
	$sbdef	; system blk offsets
	$psldef
	$prdef
	$acldef
	$rsndef				;define resource numbers
	$acedef
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$pcbdef
	$statedef
	$jibdef
	$acbdef
	$vcbdef
	$arbdef
	$wcbdef
	$ccbdef
	$fcbdef
	$phddef
        $RABDEF                         ; RAB structure defs
        $RMSDEF                         ; RMS constants
; defs for acl hacking
	$fibdef
	$atrdef
p1=0	; first qio param
p2=4
p3=8
p4=12
p5=16
p6=20	;6th qio param offsets

	.IF	DF,VMS$V5	;VMS V5 + LATER ONLY
	$SPLCODDEF
	$cpudef
	.ENDC
; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS

;.=UCB$W_BCR+2				;BEGIN DEFINITIONS AT END OF UCB
.=UCB$K_LCL_DISK_LENGTH	;v4 def end of ucb
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
; Leave thisfield first so we can know all diskswill have it at the
; same offset.
$def	ucb$l_oldfdt	.blkl	1	;fdt tbl of prior fdt chain
;
; Add other fields here if desired.
;
$def	ucb$l_ctlflgs	.blkl	1		;flags to control modes
$def	ucb$l_cbtctr	.blkl	1		;how many extents
$def	ucb$l_cbtini	.blkl	1		;init for counter
; preceding 2 fields allow specifying of contig-best-try extents
; on every Nth extend, not every one. This should still help keep
; file extensions from preferentially picking up chaff
$def	ucb$JFcontfil	.blkb	80
;
$DEF	ucb$l_minxt	.blkl	1		;min. extent
$def	ucb$l_maxxt	.blkl	1		;max extent
$def	ucb$l_frac	.blkl	1		;fraction to extend by
$def	ucb$l_slop	.blkl	1		;slop blocks to leave free
; DDT intercept fields
; following must be contiguous.
$def    ucb$s_ppdbgn            ;add any more prepended stuff after this
$def    ucb$l_uniqid    .blkl   1       ;driver-unique ID, gets filled in
                                        ; by DPT address for easy following
                                        ; by SDA
$def    ucb$l_intcddt   .blkl   1       ; Our interceptor's DDT address if
                                        ; we are intercepted
$def    ucb$l_prevddt   .blkl   1       ; previous DDT address
$def    ucb$l_icsign    .blkl   1       ; unique pattern that identifies
                                        ; this as a DDT intercept block
; NOTE: Jon Pinkley suggests that the DDT size should be encoded in part of this
; unique ID so that incompatible future versions will be guarded against.
$DEF	UCB$L_ICPFGS	.BLKL	2	; Flags. Reserve 2 longs so we need
					; not mess with this later.
	$VIELD UCB,0,<-
		<FI8OK,,M>,-		; 1 if this intercept and all
			>		; below understand finipl8.
$def	ucb$l_ufil1	.blkl	8	; for others' intercepts if needed
$def    ucb$s_ppdend
$def    ucb$a_vicddt    .blkb   ddt$k_length
                                        ; space for victim's DDT
			.blkl	4	;safety
$def	ucb$l_backlk	.blkl	1	;backlink to victim ucb
; Make the "unique magic number" depend on the DDT length, and on the
; length of the prepended material. If anything new is added, be sure that
; this magic number value changes.
magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
p.magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
				;an ACE is there or not.
	.if	df,step2
magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
p.magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
	.endc
$DEF	UCB$L_JF_HOST_DESCR	.BLKL	2	;host dvc desc.
;
; Set FDT table start mask for each unit by keeping it here.
; We need just enough to get back to user's FDTs.
	.if	ndf,step2
$def	ucb$l_fdtlgl	.blkl	2	;legal fcn msks
$def	ucb$l_fdtbuf	.blkl	2	;buffered fcn msks
$def	ucb$l_fdtmfy	.blkl	3	;modify fcn
$def	ucb$l_fdtbak	.blkl	3	;"go back" fcn
	.iff
$def	ucb$l_myfdt	.blkl	70	;copy of orig. fdt
	.endc
$def	ucb$l_vict	.blkl	1	;victim ucb for checking
$DEF	UCB$K_JF_LEN	.BLKW	1	;LENGTH OF UCB
;UCB$K_JF_LEN=.				;LENGTH OF UCB

	$DEFEND	UCB			;END OF UCB DEFINITONS
 
	.SBTTL	STANDARD TABLES

; 
; DRIVER PROLOGUE TABLE
; 
; 	THE DPT DESCRIBES DRIVER PARAMETERS AND I/O DATABASE FIELDS
; 	THAT ARE TO BE INITIALIZED DURING DRIVER LOADING AND RELOADING
; 
	driver_data
JF_UNITS=100
VJF$DPT::
.iif ndf,spt$m_xpamod,dpt$m_xpamod=0
	.if	df,evax
	.if	ndf,step2
	DPTAB	-			;DPT CREATION MACRO
		END=JF_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		FLAGS=DPT$M_SMPMOD!dpt$m_xpamod!DPT$M_NOUNLOAD, - ;SET TO USE SMP,xa
		DEFUNITS=2,-		;UNITS 0 THRU 1 thru 31
		step=1,-
		UCBSIZE=UCB$K_JF_LEN,-	;LENGTH OF UCB
		MAXUNITS=JF_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=JFDRIVER		;DRIVER NAME
	.iff ;step2
	DPTAB	-			;DPT CREATION MACRO
		END=JF_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		FLAGS=DPT$M_SMPMOD!dpt$m_xpamod!DPT$M_NOUNLOAD, - ;SET TO USE SMP,xa
		DEFUNITS=2,-		;UNITS 0 THRU 1 thru 31
		step=1,-
		UCBSIZE=UCB$K_JF_LEN,-	;LENGTH OF UCB
		MAXUNITS=JF_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=JFDRIVER		;DRIVER NAME
	.endc ;step2
	.iff
	DPTAB	-			;DPT CREATION MACRO
		END=JF_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		FLAGS=DPT$M_SMPMOD!dpt$m_xpamod!DPT$M_NOUNLOAD, - ;SET TO USE SMP,xa
		DEFUNITS=2,-		;UNITS 0 THRU 1 thru 31
		UCBSIZE=UCB$K_JF_LEN,-	;LENGTH OF UCB
		MAXUNITS=JF_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=JFDRIVER		;DRIVER NAME
	.endc

	DPT_STORE INIT			;START CONTROL BLOCK INIT VALUES
	DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\>  ;DEFAULT ACP NAME
	DPT_STORE DDB,DDB$L_ACPD+3,B,DDB$K_PACK	;ACP CLASS
	.IF	NDF,VMS$V5
	DPT_STORE UCB,UCB$B_FIPL,B,8	;FORK IPL (VMS V4.X)
	.IFF	;DEFINE FOR VMS V5.X & LATER
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8  ;FORK IPL (VMS V5.X + LATER)
	.ENDC
; These characteristics for an intercept driver shouldn't look just
; like a real disk unless it is prepared to handle being mounted, etc.
; Therefore comment a couple of them out.
	DPT_STORE UCB,UCB$L_DEVCHAR,L,-	;DEVICE CHARACTERISTICS
		<DEV$M_SHR-		; SHAREABLE
		!DEV$M_AVL-		; AVAILABLE
		!DEV$M_IDV-		; INPUT DEVICE
		!DEV$M_ODV-		; OUTPUT DEVICE
		!DEV$M_RND>		; RANDOM ACCESS
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
		<DEV$M_NNM>		; Prefix name with "node$" (like rp06)
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_MISC  ;DEVICE CLASS
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,512  ;DEFAULT BUFFER SIZE
; FOLLOWING DEFINES OUR DEVICE "PHYSICAL LAYOUT". It's faked here.
	DPT_STORE UCB,UCB$B_TRACKS,B,1	; 1 TRK/CYL
	DPT_STORE UCB,UCB$B_SECTORS,B,64  ;NUMBER OF SECTORS PER TRACK
	DPT_STORE UCB,UCB$W_CYLINDERS,W,16  ;NUMBER OF CYLINDERS
	DPT_STORE UCB,UCB$B_DIPL,B,21	;DEVICE IPL
	.if	ndf,evax
	DPT_STORE UCB,UCB$B_ERTMAX,B,10	;MAX ERROR RETRY COUNT
	DPT_STORE UCB,UCB$W_DEVSTS,W,-	;INHIBIT LOG TO PHYS CONVERSION IN FDT
		<UCB$M_NOCNVRT>		;...
	.iff
	DPT_STORE UCB,UCB$L_DEVSTS,L,-	;INHIBIT LOG TO PHYS CONVERSION IN FDT
		<UCB$M_NOCNVRT>		;...
	.endc
;
; don't mess with LBN; leave alone so it's easier to hack on...
;
	DPT_STORE REINIT		;START CONTROL BLOCK RE-INIT VALUES
;	DPT_STORE CRB,CRB$L_INTD+VEC$L_ISR,D,VR_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
	.if	ndf,evax
	DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,-  ;CONTROLLER INIT ADDRESS
		      D,JF_ctrl_INIT		  ;...
	DPT_STORE CRB,CRB$L_INTD+VEC$L_UNITINIT,- ;UNIT INIT ADDRESS
		      D,JF_unit_INIT		  ;...
	.endc
	DPT_STORE DDB,DDB$L_DDT,D,JF$DDT	  ;DDT ADDRESS
	.if	ndf,evax
        DPT_STORE UCB,UCB$L_UNIQID,D,dpt$tab    ;store DPT address
	.iff
        DPT_STORE UCB,UCB$L_UNIQID,D,evms$driver_dpt ;store DPT address
	.endc                                   ; (change "XX" to device
                                                ; mnemonic correct values)
        DPT_STORE UCB,UCB$L_ICSIGN,L,magic      ; Add unique pattern (that might
                                                ; bring back some memories in
                                                ; DOS-11 users)

; HISTORICAL NOTE: under DOS-11, one would get F012 and F024 errors
; on odd address and illegal instruction traps. If we don't have
; this magic number HERE, on the other hand, we're likely to see
; bugchecks in VMS due to uncontrolled bashing of UCB fields!
	DPT_STORE END			;END OF INITIALIZATION TABLE

; 
; DRIVER DISPATCH TABLE
; 
; 	THE DDT LISTS ENTRY POINTS FOR DRIVER SUBROUTINES WHICH ARE
; 	CALLED BY THE OPERATING SYSTEM.
; 
	.if	df,evax
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=JF,-		;NAME OF DEVICE
		START=JF_STARTIO,-	;START I/O ROUTINE
		FUNCTB=JF_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=JF_CTRL_INIT,-
		UNITINIT=JF_UNIT_INIT,-
;		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
;		REGDMP=0,-	;REGISTER DUMP ROUTINE
;		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
	.iff
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=JF,-		;NAME OF DEVICE
		START=JF_STARTIO,-	;START I/O ROUTINE
		FUNCTB=JF_FUNCTABLE,-	;FUNCTION DECISION TABLE
;		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
;		REGDMP=0,-	;REGISTER DUMP ROUTINE
;		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
	.endc
; 
; FUNCTION DECISION TABLE
; 
; 	THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; 	CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; 	PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
; 
; code chaining data:
chnflg:	.long	1	;chain or use our FDT chain flag...use ours if 0
myonoff:
fdtonoff: .long 0	;switch my fdt stuff off if non-0
	.ascii	/flag/	;define your own unique flag here; just leave it 4 bytes long!
	.long 0		;fdt tbl from before patch
fdt_chn  = -12
fdt_prev = -4
fdt_idnt = -8
JF_FUNCTABLE:
newfdt:
	.if	ndf,step2
	FUNCTAB	,-			;LIST LEGAL FUNCTIONS
		<NOP,-			; NO-OP
		FORMAT,-		; We use format to point to file
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		READLBLK,-		; READ LOGICAL BLOCK
		WRITELBLK,-		; WRITE LOGICAL BLOCK
		READPBLK,-		; READ PHYSICAL BLOCK 
		WRITEPBLK,-		; WRITE PHYSICAL BLOCK
		READVBLK,-		; READ VIRTUAL BLOCK
		WRITEVBLK,-		; WRITE VIRTUAL BLOCK
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
; no-op phys I/O for a test here...
	FUNCTAB	,-			;BUFFERED FUNCTIONS
		<NOP,-
		FORMAT,-		; FORMAT
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
; io$_format + modifiers (e.g. io$_format+128) as function code
; allows one to associate a JF unit and some other device; see
; the JF_format code comments for description of buffer to be passed.
	functab JF_format,-		;point to host disk
		<FORMAT>
;
; First our very own filter routines
;
; Following FDT function should cover every function in the local
; FDT entries between "myfdtbgn" and "myfdtend", in this case just
; mount and modify. Its function is to switch these off or on at
; need.
	Functab fdtswitch,-
		<mount,modify,create,deaccess,access>
myfdtbgn=.
; Leave a couple of these in place as an illustration. You would of course
; need to insert your own if you're messing with FDT code, or remove these if
; you don't want to. The FDT switch logic is a waste of time and space if
; you do nothing with them...
; They don't actually do anything here, but could be added to. Throw in one
; to call some daemon at various points and it can act as a second ACP
; when control is inserted at FDT time (ahead of the DEC ACP/XQP code!)
mymfy:
	FuncTab MFYFilt,-
		<MODIFY>		;modify filter (e.g. extend)
myfdtend=.
; Note that if we want to allow numerous disk drivers to be patched
; by this one there is not a unique path to the original fdt
; routine. Therefore use a UCB cell for the patch, not a cell
; ahead of the FDT. That way each unit gets a good return
; path. That's why there's an "oldfdt" cell in the UCB here.
;
; Following contains all legal functions in mask...
; That way it can transfer all control to a "previous" FDT chain.
mybak:
	FuncTab fdttoorig,-
		<NOP,-			; NO-OP
		FORMAT,-		; We use format to point to file
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		READLBLK,-		; READ LOGICAL BLOCK
		WRITELBLK,-		; WRITE LOGICAL BLOCK
		READPBLK,-		; READ PHYSICAL BLOCK 
		WRITEPBLK,-		; WRITE PHYSICAL BLOCK
		READVBLK,-		; READ VIRTUAL BLOCK
		WRITEVBLK,-		; WRITE VIRTUAL BLOCK
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
                CRESHAD,-                       ; Create a shadow set virtual u$
                DIAGNOSE,-                      ; Special pass-through function
                REMSHAD,-                       ; Remove a shadow set member
		DSE,-			;data security erase
                SETPRFPATH,-            ;  Set preferred path
                READRCT,-              ;  Read RCT block
                ADDSHAD,-              ;  Add a shadow set member
                SHADMV,-                ;  Invoke shadow set mount verification
                 SEEK,-                 ;SEEK CYLINDER
                 RECAL,-                ;RECALIBRATE
                 DRVCLR,-               ;DRIVE CLEAR
                 RELEASE,-              ;RELEASE PORT
                 OFFSET,-               ;OFFSET HEADS
                 RETCENTER,-            ;RETURN HEADS TO CENTERLINE
                 SEARCH,-               ;SEARCH FOR SECTOR
                 READPRESET,-           ;READ IN PRESET
                 WRITEHEAD,-            ;WRITE HEADER AND DATA
                 READHEAD,-             ;READ HEADER AND DATA
                 WRITECHECKH,-          ;WRITE CHECK HEADER AND DATA
                 STARTSPNDL,-           ;START SPINDLE
                WRITETRACKD,-           ;WRITE TRACK DESCRIPTOR
                READTRACKD,-            ;READ TRACK DESCRIPTOR
                COPYSHAD,-             ;  Do shadow set copies
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
; Now the "standard" disk FDT routines needed to let ODS-2 work (or ods-1 !)
; (Where we are doing read - or possibly write- virtual by hand ourselves
;  we may never get to these BTW...)
	FUNCTAB	+ACP$READBLK,-		;READ FUNCTIONS
		<READLBLK,-		; READ LOGICAL BLOCK
		READPBLK,-
		READVBLK-		; READ VIRTUAL BLOCK
		>
	FUNCTAB	+ACP$WRITEBLK,-		;WRITE FUNCTIONS
		<WRITELBLK,-		; WRITE LOGICAL BLOCK
		WRITEPBLK,-
		WRITEVBLK-		; WRITE VIRTUAL BLOCK
		>
	FUNCTAB	+ACP$ACCESS,-		;ACCESS FUNCTIONS
		<ACCESS,-		; ACCEESS FILE / FIND DIRECTORY ENTRY
		CREATE-			; CREATE FILE AND/OR DIRECTORY ENTRY
		>
	FUNCTAB	+ACP$DEACCESS,-		;DEACCESS FUNCTION
		<DEACCESS-		; DEACCESS FILE
		>
	FUNCTAB	+ACP$MODIFY,-		;MODIFY FUNCTIONS
		<ACPCONTROL,-		; ACP CONTROL FUNCTION
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY-			; MODIFY FILE ATTRIBUTES
		>
	FUNCTAB	+ACP$MOUNT,-		;MOUNT FUNCTION
		<MOUNT>			; MOUNT VOLUME
        FUNCTAB +EXE$LCLDSKVALID,-      ;LOCAL DISK VALID FUNCTIONS
                <UNLOAD,-               ;UNLOAD VOLUME
                 AVAILABLE,-            ;UNIT AVAILABLE
                 PACKACK>               ;PACK ACKNOWLEDGE
	FUNCTAB	+EXE$ZEROPARM,-		;ZERO PARAMETER FUNCTIONS
		<UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE>		; AVAILABLE
	FUNCTAB	+EXE$ONEPARM,-		;ONE PARAMETER FUNCTION
		<FORMAT-		; FORMAT
		>
	FUNCTAB	+EXE$SENSEMODE,-	;SENSE FUNCTIONS
		<SENSECHAR,-		; SENSE CHARACTERISTICS
		SENSEMODE-		; SENSE MODE
		>
	FUNCTAB	+EXE$SETCHAR,-		;SET FUNCTIONS
		<SETCHAR,-		; SET CHARACTERISTICS
		SETMODE-		; SET MODE
		>
	.long	-1,-1		; catch-all mask
fcnca:	.long	0		;fill in in unit init
	.iff	;step2
	FDT_INI
	FDT_BUF, -	; BUFFERED functions
		<NOP,-
		FORMAT,-		; FORMAT
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
myfdtstart:
; io$_format + modifiers (e.g. io$_format+128) as function code
; allows one to associate a JF unit and some other device; see
; the JF_format code comments for description of buffer to be passed.
	fdt_act JF_format,-		;point to host disk
		<format>
;
; First our very own filter routines
;
; Following FDT function should cover every function in the local
; FDT entries between "myfdtbgn" and "myfdtend", in this case just
; mount and modify. Its function is to switch these off or on at
; need.
myfdtbgn=.
; Leave a couple of these in place as an illustration. You would of course
; need to insert your own if you're messing with FDT code, or remove these if
; you don't want to. The FDT switch logic is a waste of time and space if
; you do nothing with them...
; They don't actually do anything here, but could be added to. Throw in one
; to call some daemon at various points and it can act as a second ACP
; when control is inserted at FDT time (ahead of the DEC ACP/XQP code!)
	fdt_act MFYFilt,-
		<MODIFY>		;modify filter (e.g. extend)
myfdtend=.
	.endc ;step2

	driver_code
	.if	df,evax
fcae:	.jsb_entry
	.iff
fcae:
	.endc
	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	clrl	r1
	jmp	g^exe$abortio
; fdtswitch -
;   Based on state of "myonoff" variable either enable or disable
; my FDT processing, allowing the FDT chain to remain always intact.
; This needs to be the first of a chain of FDT entries added to the
; FDT processing of a driver.

	.if	ndf,step2
	.if	df,evax
fdtswitch: .jsb_entry output=<r8>
	.iff
fdtswitch:
	.endc
	tstl	fdtonoff		;global on/off
	bneq	1$
	rsb				;go to next FDT if null
1$:	addl2	#<myfdtend-myfdtbgn>,r8	;pass our fdt codes
	rsb				;return to std
; fdttoorig -
;  This entry continues FDT processing at the point after the new
; entries by returning to the original FDT chain at the point where
; that chain begins. (It is presumed that FDT entries will always be
; added ahead of existing ones due to the nonreturning nature of
; FDT processing.) This is done instead of simply duplicating the
; DEC FDT entries because in this way multiple FDT patches can
; coexist, as would be impossible if this trick were not used. As
; can be seen, its overhead is minimal.
;  The old FDT location is kept in the UCB for our device because
; that allows us to get back to different FDTs when several drivers'
; FDT chains are pointed here first.
	.if	df,evax
fdttoorig: .jsb_entry output=<r8>
	.iff
fdttoorig:
	.endc
; As a performance feature, use a switch to let us just use the
; FDT chain here rather than continuing an old one. This needs to
; be settable externally since there is no need to return down a
; chain unless something else is IN the chain.
; Control this with chnflg
;	tstl	chnflg
;	beql	2$			;just continue if chnflg is 0
	pushl	r0
; (this routine gets called a fair bit and if GETJFUCB can be
;  called less, things speed up.)
	jsb	getJFucb		;get UCB for JF unit from stolen
					;one
	tstl	r0			;r0 is return UCB
	bgeq	1$			;if not negative, not a UCB
	tstl	ucb$l_oldfdt(r0)	;a prior fdt exist?
	beql	1$
        movl    ucb$l_oldfdt(r0),r8      ;point to original FDT point
        addl2   #<16-12>,r8      ;pass the 2 entry masks
1$:                                 ;back up since sysqioreq adds 12
	popl	r0
2$:        rsb                      ;off to the previous FDT routines.
	.endc ;step2
;
; GETJFUCB - Find JF: UCB address, given r5 points to UCB of the patched
; device. Return the UCB in R0, which should return 0 if we can't find
; it.
;   This routine is called a lot and therefore is made as quick as
; it well can be, especially for the usual case.
	.if	df,evax
getJFucb: .jsb_entry output=<r0>
	.iff
getJFucb:
	.endc
;	clrl	r0	;no UCB initially found
	pushl	r10
	pushl	r11	;faster than pushr supposedly
;	pushr	#^m<r10,r11>
; Assumes that R5 is the UCB address of the device that has had some
; code intercepted and that we are in some bit of code that knows
; it is in an intercept driver. Also assumes R11 may be used as
; scratch registers (as is true in FDT routines). Control returns at
; label "err" if the DDT appears to have been clobbered by
; something not following this standard, if conditional "chk.err"
; is defined.
;       Entry: R5 - victim device UCB address
;       Exit: R11 - intercept driver UCB address
chk.err=0
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
; note we know our virtual driver's DPT address!!!
	.if	ndf,evax
        movab   dpt$tab,r11              ;magic pattern is DPT addr.
	.iff
	movab	evms$driver_dpt,r11
	.endc
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
; (don't preserve r0 since we clobber it anyway.)
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=NO
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
;        beql    1$                      ;if eql yes, end search
;
; The somewhat odd layout here removes extra branches in the
; most common case, i.e., finding our driver the very first time
; through. The "bneq" branch next time is usually NOT taken.
;
	bneq	5$			;check next in chain if not us
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        movab   <0-ucb$a_vicddt>(r10),r11       ;point R11 at the intercept UCB
;	brb	4$	; note in this layout we can comment this out.
4$:
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=NO
; NOW clobber r0 and put things back.
	movl	r11,r0
;	popr	#^m<r10,r11>
	popl	r11
	popl	r10	;supposedly faster than popr
	rsb
; Make very sure this DDT is inside a UCB bashed according to our
; specs. The "p.magic" number reflects some version info too.
; If this is not so, not much sense searching more.
5$:     cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneq    3$                     ;exit if this is nonstd bash
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
					;chain
        bgeq    3$                     ; (error check if not negative)
        brb     2$                      ;then check again
;1$:
3$:
	clrl	r11	;return 0 if nothing found
	brb	4$
;
; Few macros for long distance branches...
;
	.macro	beqlw	lbl,?lbl2
	bneq	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bneqw	lbl,?lbl2
	beql	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bleqw	lbl,?lbl2
	bgtr	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bgeqw	lbl,?lbl2
	blss	lbl2
	brw	lbl
lbl2:
	.endm
; allocate does not zero its result area.
; This macro makes it easy to zero an allocated area before using it.
; Leaves no side effects...just zeroes the area for "size" bytes
; starting at "addr".
	.macro	zapz	addr,size
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	movc5	#0,addr,#0,size,addr
	popr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	.endm
;
	.SBTTL Our FDT Filter Routines
PopOut:
	popr	#^m<r0,r5>
pors:
	.if	df,step2
; Here need to return to the "standard" FDT routine. Do so by computing
; the address in the FDT table of the normal host and calling that, then
; returning.
        EXTZV   #IRP$V_FCODE,#IRP$S_FCODE,IRP$L_FUNC(R3),R1     ; GET FCN CODE
	pushr	#^m<r7,r8,r9>
	jsb	getJFucb		;find JF UCB checking for extra links
	tstl	r0			;got it?
	bgeq	199$			;if not skip out
	movab	ucb$l_oldfdt(r0),r7	;get address of previous FDT
	bgeq	199$			;ensure ok...
;	movl	ucb$l_ddt(r5),r7	;find FDT
; Here rely on the fact that we got here via our modified FDT call and that
; the orig. FDT is stored just a bit past the current one.
;	movl	<ucb$l_oldfdt-ucb$l_myfdt>(r7),r7	;point at orig. FDT
	addl2	#8,r7			;point at one of 64 fdt addresses
	movl	(r7)[r1],r8		;r7 is desired routine address
;now call the "official" FDT code
	pushl	r6	;ccb
	pushl	r5	;ucb
	pushl	r4	;pcb
	pushl	r3	;irp
	calls	#4,(r8)			;Call the original routine
	popr	#^m<r6,r7,r8,r9>
; Now return as the original routine would.
	ret
199$:
	popr	#^m<r6,r7,r8,r9>
	movl	#16,r0
	call_abortio,do_ret=yes
	.iff
	rsb
	.endc
	.if	df,evax
	.if	ndf,step2
mfyfilt: .jsb_entry	;filter on MODIFY requests (e.g. extend)
	.iff ;step2
mfyfilt: $driver_fdt_entry	;filter on MODIFY requests (e.g. extend)
	.endc ;step2
	.iff
mfyfilt:	;filter on MODIFY requests (e.g. extend)
	.endc
; First do some preliminary checks for sanity.
; 1. Channel must NOT be kernel mode
; 2. Not a movefile
	tstl	r6		;is there a CCB (must be +)
	bleq	pors		;if not skip out
	cmpb	ccb$b_amod(r6),#1	;knl mode access?
	beql	pors		;leave knl mode chnls alone!
;funct modifiers are bits 6-15
; this is hex ffc0
; Normal io$_modify should have no modifiers, so if it has it's
; for something else; leave that alone.
	.if	ndf,evax
	bitw	#^xDFC0,irp$w_func(r3) ;this a movefile or other modifier?
	.iff
	bitw	#^xDFC0,irp$l_func(r3) ;this a movefile or other modifier?
	.endc
	bneq	pors		;if so ignore it here.
	pushr	#^m<r0,r5>
; original r5 now at 4(sp). Must get that to continue the ops.
	jsb	getJFucb		;find JFdriver ucb
	tstl	r0
	bgeqw	popout
	movl	r5,ucb$l_backlk(r0)	;save link'd ucb in ours too.
	movl	r0,r5			;point R5 at JF UCB
	bitl	i^#2,ucb$l_ctlflgs(r5)	;look at mfy?
	bneq	171$			;if neq yes
; (test later will see about space control if doing this)
701$:
	popr	#^m<r0,r5>
	brw	pors
171$:
; here we can modify request fields in the FIB the user supplies to reduce
; fragmentation...e.g. set fib$l_exsz bigger or set fib$m_alconb bit
; in fib$w_exctl IFF fib$m_alcon is not set & set fib$m_aldef.
;
	.if	ndf,evax
	movl	p1(ap),r0	;get fib
	.iff
	movl	irp$l_qio_p1(r3),r0
	.endc
	ifnord #4,4(r0),701$
	movl	4(r0),r0	;...from descriptor
	ifnord #4,fib$w_exctl(r0),701$
	bitw	#fib$m_extend,fib$w_exctl(r0)	;extending at all?
	beqlw	162$			;if no extend, leave fib alone
; Because contiguous best try allocation flushes the entire extend cache,
; it can cause a tremendous performance hit. Therefore allow it to be
; separately switched so that the benefits of longer extents can be had
; if desired without forcing this flushing every time a file is extended.
	bitl	i^#32,ucb$l_ctlflgs(r5)		;separate control for setting contig best try
	beql	1$
; leave contig and contig-best-try alone
	bitw	#<fib$m_alcon!fib$m_alconb>,fib$w_exctl(r0)	;contig alloc?
	bneq	1$		;if contig leave it alone
; allow this on every nth extend.
; This will allow periodic flushes of the extent cache but will let
; it not be made totally useless. By flushing the extent cache periodically
; we can try to reduce the fragmentation it induces.
; if bit 16384 is not set, do not set aldef.
	bitl	i^#16384,ucb$l_ctlflgs(r5)	;allow aldef?
	beql	704$
	bisw	#<fib$m_aldef>,fib$w_exctl(r0) ;set to use vol default if
704$:					;bigger than program's
	decl	ucb$l_cbtctr(r5)	;count down
	bgtr	1$			;and if >0 don't set cbt yet
	movl	ucb$l_cbtini(r5),ucb$l_cbtctr(r5)	;else reset counter
	bisw	#<fib$m_alconb>,fib$w_exctl(r0) ;else turn on contig best
					;try and turn on use of
					;system default extension if
					;larger than program default
1$:
; One can add code to check file size and bump extension by more than default if
; it's big (for example, extend by 10% of its' size, not by a few blocks at a time).
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8>
	bitw	#fib$m_alcon,fib$w_exctl(r0)	;contig extend?
	bneq	222$		; if so don't touch size.
	movl	ccb$l_wind(r6),r7	;get window block
	bgeq	222$			;guard
	movl	wcb$l_fcb(r7),r8	;and file control blkock
	bgeq	222$			;guard
	movl	fcb$l_filesize(r8),r6	;get filesize
	beql	222$
;
; The fraction starts at 1/4, but can be anywhere from 1/1 to 1/1000
	divl2	ucb$l_frac(r5),r6	;get 1/4 of current size or so
	incl	r6			;plus one...for good luck
;fncymod=1	;chop this if desired
	cmpl	r6,ucb$l_maxxt(r5)	;extending over max (nominally 120000)
	bleq	1222$
	movl	ucb$l_maxxt(r5),r6	;clamp to max what we're forcing
1222$:
	cmpl	r6,ucb$l_minxt(r5)	;if less than 10 leave alone too
	bgeq	1223$
	movl	ucb$l_minxt(r5),r6	;at least grab this minimum
1223$:
; never try to grab over1/8 of total free space.
	movl	ucb$l_backlk(r5),r8	;get host ucb (set just above)
	bgeq	222$			;(better be there)
	movl	ucb$l_vcb(r8),r8	;point at vcb
	bgeq	222$
	movl	vcb$l_free(r8),r8	;no. blks free
	ashl	#-3,r8,r8		;free space /8
	cmpl	r6,r8			;extent over freespc/8?
	bleq	3223$			;if not all still ok
	movl	r8,r6			;else clamp to free/8
3223$:
	cmpl	r6,fib$l_exsz(r0)	;make sure we're increasing size
	bleq	222$			;if less than user wants, leave alone

; if 4 bit is clear, allow size ctl always. Otherwise only if aldef set.
	bitl	i^#4,ucb$l_ctlflgs(r5)
	beql	2222$
	bitw	#<fib$m_aldef>,fib$w_exctl(r0) ;set to use vol default if
	beql	222$			;if aldef NOT set, leave size alone.
2222$:
	movl	r6,fib$l_exsz(r0)	;fill in as new extend size
222$:
	popr	#^m<r2,r3,r4,r5,r6,r7,r8>
162$:
	popr	#^m<r0,r5>
	movl	#1,r0
	brw	pors
;++
;
; JF_format - bash host disk tables to point at ours.
;
; With no function modifiers, this routine takes as arguments the name
; of the host disk (the real disk where the virtual disk will exist),
; the size of the virtual disk, and the LBN where the virtual disk
; will start. After these are set up, the device is put online and is
; software enabled.
;
; This routine does virtually no checking, so the parameters must be
; correct.
;
; Inputs:
;	p1 - pointer to buffer. The buffer has the following format:
;	     longword 0 - (was hlbn) - flag for function. 1 to bash
;			  the targetted disk, 2 to unbash it, else
;			  illegal.
;	     longword 1 - virtual disk length, the number of blocks in
;			  the virtual disk. If negative disables
;			  FDT chaining; otherwise ignored.
;	     longword 2 through the end of the buffer, the name of the
;			  virtual disk. This buffer must be blank
;			  padded if padding is necessary
;
;
;	p2 - size of the above buffer
;--
	.if	df,evax
	.if	ndf,step2
JF_format: .jsb_entry
	.iff
JF_format: $driver_fdt_entry
	.endc ;step2
	.iff
JF_format:
	.endc
	.iif	df,x$$$dt,jsb g^ini$brk
	.if	df,evax
	bicl3	#io$m_fcode,irp$l_func(r3),r0	;mask off function code
	.iff
	bicw3	#io$m_fcode,irp$w_func(r3),r0	;mask off function code
	.endc
	bneq	20$			;branch if modifiers, special
;thus, normal io$_format will do nothing.
	rsb				;regular processing
100$:
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
10$:
	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	clrl	r1
	.if	ndf,step2
	jmp	g^exe$abortio
	.iff
	call_abortio do_ret=yes
	.endc
20$:
	.if	ndf,evax
	movl	p1(ap),r0		;buffer address
	movl	p2(ap),r1		;length of buffer
	.iff
	movl	irp$l_qio_p1(r3),r0
	movl	irp$l_qio_p2(r3),r1
	.endc
	jsb	g^exe$writechk		;read access? doesn't return on error
	.iif	df,x$$$dt,jsb g^ini$brk
;	clrl	irp$l_bcnt(r3)		;paranoia, don't need to do this...
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	.if	ndf,evax
	movl	p1(ap),r0		;get buffer address
	.iff
	movl	irp$l_qio_p1(r3),r0
	.endc
	movl	(r0)+,r7		;get option code
	bleq	100$			;0 or negative illegal
	cmpl	r7,#2			;3 and up illegal too
	bgtr	100$
	incl	chnflg
	movl	(r0)+,r6		;size of virtual disk (ignored)
	bleq	70$
	clrl	chnflg			;if 0 or neg. size don't chain...
70$:
	movab	(r0),-			;name of "real" disk
		ucb$l_JF_host_descr+4(r5)
	.if	ndf,evax
	subl3	#8,p2(ap),-		;set length of name in descriptor
		ucb$l_JF_host_descr(r5)
	.iff
	subl3	#8,irp$l_qio_p2(r3),-		;set length of name in descriptor
		ucb$l_JF_host_descr(r5)
	.endc
	bleq	100$			;bad length
	movab	ucb$l_JF_host_descr(r5),r1	;descriptor for...
	jsb	g^ioc$searchdev		;search for host device
	.iif	df,x$$$dt,jsb g^ini$brk
	blbs	r0,30$			;branch on success
; fail the associate...
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movzwl	#ss$_nosuchdev+2,r0	;make an error, usually a warning
	clrl	r1
	.if	ndf,step2
	jmp	g^exe$abortio
	.iff
	call_abortio do_ret=yes
	.endc
30$:	;found the device
; r1 is target ucb address...
; move it to r11 to be less volatile
	movl	r1,r11
	cmpl	r7,#1		;bashing the target UCB?
	bneq	31$		;if neq it's unmung
	jsb	mung		;go mung target...
	brb	32$
31$:
; Be sure we unmung the correct disk or we can really screw up a system.
	cmpl	r11,ucb$l_vict(r5)	;undoing right disk?
	bneq	32$			;if not skip out, do nothing.
	jsb	umung		;unmung target
32$:
;	bisw	#ucb$m_valid,ucb$w_sts(r5)	;set volume valid
;	bisw	#ucb$m_online,ucb$w_sts(r5)	;set unit online
;	movl	ucb$l_irp(r5),r3		;restore r3, neatness counts
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movzwl	#ss$_normal,r0			;success
	.if	ndf,step2
	jmp	g^exe$finishioc			;wrap things up.
	.iff
	call_finishioc do_ret=yes
	.endc
	.if	df,evax
mung: .jsb_entry
	.iff
mung:
	.endc
; steal DDT from host. Assumes that the intercept UCB address
; is in R5 (that is, the UCB in which we will place the DDT copy),
; and that the UCB of the device whose DDT we are stealing is
; pointed to by R11. All registers are preserved explicitly so that
; surrounding code cannot be clobbered. R0 is returned as a status
; code so that if it returns with low bit clear, it means something
; went wrong so the bash did NOT occur. This generally means some other
; code that does not follow this standard has grabbed the DDT already.
; The following example assumes the code lives in a driver so the
; unique ID field and magic number are set already.
	movab	fcae,fcnca	;ensure final FDT entry is filled in
        pushr   #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
; Acquire victim's fork lock to synchronize all this.
        movl    #ss$_normal,r0          ;assume success
        forklock ucb$b_flck(r11),-
	savipl=-(sp),preserve=YES
; find the current DDT address from the UCB (leaving the copy in
; the DDB alone)
        movl    ucb$l_ddt(r11),r10      ;point at victim's DDB
; see if this DDT is the same as the original
        movl    ucb$l_ddb(r11),r9       ;the ddb$l_ddt is the original
        cmpl    ddb$l_ddt(r9),r10       ;bashing driver the first time?
        beql    1$                      ;if eql yes
; driver was bashed already. Check that the current basher followed the
; standard. Then continue if it looks OK.
        cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
                                        ;does the magic pattern exist?
; if magic pattern is missing things are badly messed.
        beql    2$                      ;if eql looks like all's well
        movl    #2,r0                   ;say things failed
        brw     100$                    ;(brb might work too)
2$:
; set our new ddt address in the previous interceptor's slot
        movab   ucb$a_vicddt(r5),<ucb$l_intcddt-ucb$a_vicddt>(r10)
                                        ;store next-DDT address relative
                                        ;to the original victim one
1$:
        movl    r10,ucb$l_prevddt(r5)   ;set previous DDT address up
        clrl    ucb$l_intcddt(r5)       ;clear intercepting DDT initially
3$:
        pushl   r5
; copy a little extra for good luck...
        movc3   #<ddt$k_length+12>,(r10),ucb$a_vicddt(r5)    ;copy the DDT
        popl    r5                      ;get UCB pointer back (movc3 bashes it)
;
; Here make whatever mods to the DDT you need to.
;
; FOR EXAMPLE make the following mods to the FDT pointer
; (These assume the standard proposed for FDT pointers)
        movab   ucb$a_vicddt(r5),r8     ;get a base register for the DDT
        movl    r5,JF_functable+fdt_prev    ;save old FDT ucb address
	movl	ddt$l_fdt(r10),ucb$l_oldfdt(r5) ;save orig. fdt addr
        movl    ucb$l_uniqid(r5),JF_functable+fdt_idnt ;save unique ID also
; copy legal and buffered entry masks of original driver.
; HOWEVER, set mask for format entry to be nonbuffered here since
; we deal with it.
	pushr	#^m<r6,r7,r8,r9,r10,r11>
	.if	ndf,step2
	movab	ucb$l_fdtlgl(r5),r9	;our function table dummy in UCB
	movl	ddt$l_fdt(r10),r7	;victim's FDT table
; We want all functions legal in the victim's FDT table to be legal
; here.
	movl	(r7),(r9)+		;1st half legal mask
	movl	4(r7),(r9)+		;2nd half legal mask
	movl	8(r7),(r9)+		;1st half buffered mask
	movl	12(r7),(r9)+		;2nd half buffered mask
; Now copy in our modify & back-to-original FDT cells.
; Thus every unit has its own legal & buffered masks, then goes to
; original FDT, and we don't mess with OUR FDTs.
; (Also original FDT tables aren't messed either.)
	movl	mymfy,(r9)+		; modify template 1
	movl	mymfy+4,(r9)+		; & 2
	movl	mymfy+8,(r9)+		;and address
; Set -1 to set ALL possible function bits so we always go back.
	movl	#-1,(r9)+		;then catch-all "go back"
	movl	#-1,(r9)+		; to original fdt
	movl	mybak+8,(r9)		; and address of same.
	.iff ;step2
	movab	ucb$l_myfdt(r5),r9	;our function table dummy in UCB
	movl	ddt$l_fdt(r10),r7	;victim's FDT table
; We want all functions legal in the victim's FDT table to be legal
; here.
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;preserve regs from movc
	movl	#<68*4>,r0		;byte count of a step 2 FDT
	movc3	r0,(r7),(r9)		;copy his FDT to ours
	popr	#^m<r0,r1,r2,r3,r4,r5>	;preserve regs from movc
; Now copy in our modify & back-to-original FDT cells.
; We will do this in our FDT table by having FDT definitions only
; for those functions in JFdriver that we service locally. Thus
; all entry cells for the rest will point in the JF FDT to
; exe$illiofunc.
	movab	g^exe$illiofunc,r8	;get the magic address
	movab	JF_functable,r10	;r10 becomes JF FDT tbl
	addl2	#8,r10			;point at functions
	addl2	#8,r9			;his new FDT...
	movl	#64,r11			;64 functions
75$:	cmpl	(r10),r8		;this function hadled in JF?
	beql	76$			;if eql no, skip
	movl	(r10),(r9)		;if we do it point his fdt at our fcn
; (NOTE: our functions MUST therefore call the previous FDT's functions at
;  end of their processing.)
76$:	cmpl	(r10)+,(r9)+		;pass the entry
	sobgtr	r11,75$			;do all functions
; JFdriver FDT table. Last entry goes to user's original FDT chain.
;
; Thus we simply insert our FDT processing ahead of normal stuff, but
; all fcn msks & functions will work for any driver.
	.endc ;step2
	popr	#^m<r6,r7,r8,r9,r10,r11>
	.if	ndf,step2
        movab	ucb$l_fdtlgl(r5),ddt$l_fdt(r8) ;point at our FDT table
	.iff
        movab	ucb$l_myfdt(r5),ddt$l_fdt(r8) ;point at our FDT table
	.endc
        clrl    myonoff                 ;turn my FDTs on
;
; Finally clobber the victim device's DDT pointer to point to our new
; one.
; First invalidate trans. buffers so we are sure all updates to the
; new DDTAB made it into the memory copy of DDTAB
	.iif ndf,evax, invalidate_tb
	.iif df, evax, evax_imb
        movab   ucb$a_vicddt(r5),ucb$l_ddt(r11)
; Now invalidate trans. buffers so everyone (including us!) that
; tries to get the ddtab finds our new one
	.iif df, evax, evax_imb
	.iif ndf,evax, invalidate_tb
; Now the DDT used for the victim device unit is that of our UCB
; and will invoke whatever special processing we need. This processing in
; the example here causes the intercept driver's FDT routines to be
; used ahead of whatever was in the original driver's FDTs. Because
; the DDT is modified using the UCB pointer only, target device units
; that have not been patched in this way continue to use their old
; DDTs and FDTs unaltered.
;
; Processing complete; release victim's fork lock
100$:
        forkunlock lock=ucb$b_flck(r11),newipl=(sp)+,-
         preserve=YES
        popr    #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	rsb
	.if	df,evax
umung: .jsb_entry
	.iff
umung:
	.endc
;
; Entry: R11 points at victim device UCB and current driver is the one
; desiring to remove its entry from the DDT chain. Thus its xx$dpt: address
; is the one being sought. ("Current driver" here means the intercept
; driver.)
;   It is assumed that the driver knows that the DDT chain was patched
; so that its UCB contains an entry in the DDT chain
        pushr   #^m<r0,r1,r2,r3,r4,r5,r10,r11>
	movl	r11,r5			;hereafter use r5 as victim's UCB
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
        movl    ucb$l_ddb(r5),r1        ;get ddb of victim
        movl    ddb$l_ddt(r1),r1        ;and real original DDT
        movl    r10,r0                  ;save ucb$l_ddt addr for later
	.if	ndf,evax
        movab   DPT$tab,r11             ;magic pattern is DPT addr.
	.iff
	movab	evms$driver_dpt,r11
	.endc
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=YES
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
        beql    1$                      ;if eql yes, end search
        .if     df,chk.err
        cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneqw    4$                     ;exit if this is nonstd bash
        .endc   ;chk.err
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
                                        ;chain
        .if     df,chk.err
        bgeqw   4$                     ; (error check if not negative)
        .endc   ;chk.err
        brb     2$                      ;then check again
1$:
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        tstl    <ucb$l_intcddt-ucb$a_vicddt>(r10)       ;were we intercepted?
        bgeq    3$                      ;if geq no, skip back-fixup
; we were intercepted. Fix up next guy in line.
        movl    <ucb$l_intcddt-ucb$a_vicddt>(r10),r11  ;point at interceptor
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),<ucb$l_prevddt-ucb$a_vicddt>(r11)
3$:
; if we intercepted someone, fix up our intercepted victim to skip by
; us also.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r2    ;did we intercept
                                        ;original driver?
        cmpl    r2,r1                   ;test if this is original
        beql    5$                      ;if eql yes, no bash
; replace previous intercept address by ours (which might be zero)
        movl    <ucb$l_intcddt-ucb$a_vicddt>(r10),<ucb$l_intcddt-ucb$a_vicddt>(r2)
5$:
; Here remove FDT entries from the list if they were modified.
; This needs a scan of the FDT chain starting at the victim's
; ddt$l_fdt pointer and skipping around any entry that has address
; JF_functable:
;  The FDT chain is singly linked. The code here assumes everybody
; plays by the same rules!
; NOTE: Omit this code if we didn't insert our FDT code in the chain!!!
        movl    ddt$l_fdt(r0),r1        ;start of FDT chain
        movab   JF_functable,r2         ;address of our FDT table
        clrl    r3
	movab	<0-ucb$a_vicddt>(r10),r4 ;initially point at our ucb
; Also set the JF device offline when we unbash it. This is a simple
; flag that ctl prog. can use to tell if it's been used already.
	.if	df,evax
	bicl	#<ucb$m_valid!ucb$m_online>,ucb$l_sts(r4)
	.iff
	bicw	#<ucb$m_valid!ucb$m_online>,ucb$w_sts(r4)
	.endc
6$:     cmpl    r1,r2                   ;current fdt point at us?
        beql    7$                      ;if eql yes, fix up chain
        movl    r1,r3                   ;else store last pointer
        movl    fdt_prev(r1),r4         ;and point at next
	bgeq	8$
	movl	ucb$l_oldfdt(r4),r1	;where last FDT pointer is in the ucb
;;;BUT not all UCBs will have the fdt offset at the same place!!!
;;;HOWEVER we will leave this in, putting the oldfdt field first after
;;;the regular UCB things.
        bgeq    8$                      ;if not sys addr, no messin'
        brb     6$                      ;look till we find one.
7$:
;r3 is 0 or fdt pointing to our block next
;r1 points at our fdt block
        tstl    r3                      ;if r3=0 nobody points at us
        bgeq    8$                      ;so nothing to do
	movl	fdt_prev(r1),r4
	bgeq	17$
	movl	ucb$l_oldfdt(r4),-(sp)	;save old fdt loc
	movl	fdt_prev(r3),r4
	blss	18$
	tstl	(sp)+
	brb	17$
18$:	movl	(sp)+,ucb$l_oldfdt(r4)
17$:    movl    fdt_prev(r1),fdt_prev(r3)  ;else point our next-fdt pointer at
                                        ;last fdt addr.
8$:
;
; Finally if the victim UCB DDT entry points at ours, make it point at
; our predecessor. If it points at a successor, we can leave it alone.
        cmpl    r10,r0                  ;does victim ucb point at our DDT?
        bneq    4$                      ;if not cannot replace it
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),ucb$l_ddt(r5)
4$:
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES
       popr    #^m<r0,r1,r2,r3,r4,r5,r10,r11>
                                        ;copy our prior DDT ptr to next one
	rsb

	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; JF_ctrl_INIT - CONTROLLER INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; noop
; INPUTS:
; R4 - CSR ADDRESS
; R5 - IDB ADDRESS
; R6 - DDB ADDRESS
; R8 - CRB ADDRESS
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 	THE DRIVER CALLS THIS ROUTINE TO INIT AFTER AN NXM ERROR.
;--
	.if	df,evax
	.if	ndf,step2
JF_ctrl_INIT: .jsb_entry		;JF CONTROLLER INITIALIZATION
	.iff
JF_ctrl_INIT: $driver_ctrlinit_entry
	.endc
	.iff
JF_ctrl_INIT:		;JF CONTROLLER INITIALIZATION
	.endc
;	CLRL	CRB$L_AUXSTRUC(R8)	; SAY NO AUX MEM
	movl	#1,r0
	.iif ndf,step2,RSB				;RETURN
	.iif df,step2,ret
	.SBTTL	INTERNAL CONTROLLER RE-INITIALIZATION
;
; INPUTS:
;	R4 => controller CSR (dummy)
;	R5 => UCB
;
	.if	ndf,evax
ctrl_REINIT:
	.iff
ctrl_REINIT: .jsb_entry
	.endc
	.iif ndf,step2,RSB				;RETURN
	.iif df,step2,ret
	.SBTTL	UNIT INITIALIZATION ROUTINE
;++
; 
; JF_unit_INIT - UNIT INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE SETS THE JF: ONLINE.
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 
; INPUTS:
; 
; 	R4	- CSR ADDRESS (CONTROLLER STATUS REGISTER)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
;	R8	- CRB ADDRESS
; 
; OUTPUTS:
; 
; 	THE UNIT IS SET ONLINE.
; 	ALL GENERAL REGISTERS (R0-R15) ARE PRESERVED.
; 
;--
	.if	df,evax
	.if	ndf,step2
JF_unit_INIT: .jsb_entry			;JF UNIT INITIALIZATION
	.iff
JF_unit_INIT: $driver_unitinit_entry
	.endc
	.iff
JF_unit_INIT:; .jsb_entry			;JF UNIT INITIALIZATION
	.endc
	movab	fcae,fcnca	; set up catch-all final FDT
; Don't set unit online here. Priv'd task that assigns JF unit
; to a file does this to ensure only assigned JFn: get used.
;	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)  ;SET UCB STATUS ONLINE
;limit size of JF: data buffers
JF_bufsiz=127*512
	movl	#JF_bufsiz,ucb$l_maxbcnt(r5)	;limit transfers to 8k
	MOVB	#DC$_MISC,UCB$B_DEVCLASS(R5) ;SET DISK DEVICE CLASS

; NOTE: we may want to set this as something other than an RX class
; disk if MSCP is to use it. MSCP explicitly will NOT serve an
; RX type device. For now leave it in, but others can alter.
; (There's no GOOD reason to disable MSCP, but care!!!)
	movl	#^Xb22d4001,ucb$l_media_id(r5)	; set media id as JF
; (note the id might be wrong but is attempt to get it.) (used only for
; MSCP serving.)
	MOVB	#DT$_FD1,UCB$B_DEVTYPE(R5)  ;Make it foreign disk type 1
; (dt$_rp06 works but may confuse analyze/disk)
;;; NOTE: changed from fd1 type so MSCP will know it's a local disk and
;;; attempt no weird jiggery-pokery with the JF: device.
; MSCP may still refuse to do a foreign drive too; jiggery-pokery later
; to test if there's occasion to do so.
; Set up crc polynomial
;	clrl	chnflg		;initially set to use our chain of FDTs
	movl	#1,chnflg
	movl	#1,r0
	.iif ndf,step2,RSB				;RETURN
	.iif df,step2,ret

	.SBTTL	START I/O ROUTINE
;++
; 
; JF_STARTIO - START I/O ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS FORK PROCESS IS ENTERED FROM THE EXECUTIVE AFTER AN I/O REQUEST
; 	PACKET HAS BEEN DEQUEUED.
; 
; INPUTS:
; 
; 	R3		- IRP ADDRESS (I/O REQUEST PACKET)
; 	R5		- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	IRP$L_MEDIA	- PARAMETER LONGWORD (LOGICAL BLOCK NUMBER)
; 
; OUTPUTS:
; 
; 	R0	- FIRST I/O STATUS LONGWORD: STATUS CODE & BYTES XFERED
; 	R1	- SECOND I/O STATUS LONGWORD: 0 FOR DISKS
; 
; 	THE I/O FUNCTION IS EXECUTED.
; 
; 	ALL REGISTERS EXCEPT R0-R4 ARE PRESERVED.
; 
;--
	.if	df,evax
	.if	ndf,step2
JF_STARTIO: .jsb_entry output=<r0,r1>		;START I/O OPERATION
	.iff
JF_STARTIO: $driver_start_entry
	.endc
	.iff
JF_STARTIO:				;START I/O OPERATION
	.endc
; 
; 	PREPROCESS UCB FIELDS
; 
;	ASSUME	RY_EXTENDED_STATUS_LENGTH  EQ  8
;	CLRQ	UCB$Q_JF_EXTENDED_STATUS(R5)	; Zero READ ERROR REGISTER area.
; 
; 	BRANCH TO FUNCTION EXECUTION
	bbs	#ucb$v_online,-	; if online set software valid
		ucb$l_sts(r5),210$
216$:	movzwl	#ss$_volinv,r0	; else set volume invalid
	brw	resetxfr	; reset byte count & exit
210$:
; Unless we use this entry, we want to junk any calls here.
	brb	216$		;just always say invalid volume.

; Get here for other start-io entries if the virtual disk code is
; commented out also, as it must be.
RESETXFR:	; dummy entry ... should never really get here
	MOVL	UCB$L_IRP(R5),R3	;GET I/O PKT
	.iif ndf,evax,MNEGW IRP$W_BCNT(R3),UCB$W_BCR(R5) ; RESET BYTECOUNT
;	BRW	FUNCXT
FUNCXT:					;FUNCTION EXIT
	CLRL	R1			;CLEAR 2ND LONGWORD OF IOSB
	.if	df,step2
	REQCOM environment=call		; COMPLETE REQUEST
	.iff
	REQCOM
	.endc
	.if	ndf,evax
FATALERR:				;UNRECOVERABLE ERROR
	MOVZWL	#SS$_DRVERR,R0		;ASSUME DRIVE ERROR STATUS
	brb	funcxt
; 
PWRFAIL:				;POWER FAILURE
	BICW	#UCB$M_POWER,UCB$L_STS(R5)  ;CLEAR POWER FAILURE BIT
	MOVL	UCB$L_IRP(R5),R3	;GET ADDRESS OF I/O PACKET
	MOVQ	IRP$L_SVAPTE(R3),-	;RESTORE TRANSFER PARAMETERS
		UCB$L_SVAPTE(R5)	;...
	BRW	JF_STARTIO		;START REQUEST OVER
	.endc
	.if	ndf,evax
JF_INT::
JF_UNSOLNT::
	POPR	#^M<R0,R1,R2,R3,R4,R5>
	REI	;DUMMY RETURN FROM ANY INTERRUPT
	.endc	;;

JF_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.iff	;step2
	.TITLE	JFDRiver	;skeleton driver implementing ucb linkage
	.IDENT	'V01h'
mf$tst=0 ; save fib expansion stuff to see if we get there
; Copyright 1993,1994 Glenn C. Everhart
; All rights reserved
;  Author: Glenn C. Everhart
;
; mods:
; 7/8/94 gce -step2 conversion begun
; remember to define xx$nor sometime to wrtchk args!!
;
;
real_pvt=0	;define to include code that on bit 2048 prevents opens on
		;assigned devices, privs or not.
.ntype	__,R31			;  set EVAX nonzero if R31 is a register
.if eq <__ & ^xF0> - ^x50
EVAX = 1
.iff
;EVAX = 0
.endc
	.if	df,evax
evax = 1
alpha=1
bigpage=1
addressbits=32
;					;... EVAX=1 -> Step1
.iif ndf WCB$W_NMAP, evax=2		;... EVAX=2 -> Step2 (ndf as of T2.0)
.iif ndf WCB$W_NMAP, step2=1		;... EVAX=2 -> Step2 (ndf as of T2.0)
	.endc
;x$$$dt=0
; above for Alpha only.
;
; Glenn C. Everhart, November 1993
;
;vms$$v6=0	;add forvms v6 def'n
vms$v5=1
; define v5$picky also for SMP operation
v5$picky=1
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

; 
; EXTERNAL SYMBOLS
; 
	.library /SYS$SHARE:LIB/

;	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$DYNDEF ;define dynamic data types
	$DCDEF				;DEFINE DEVICE CLASS
	$DDBDEF				;DEFINE DEVICE DATA BLOCK
	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS
	$DPTDEF				;DEFINE DRIVER PROLOGUE TABLE
	$EMBDEF				;DEFINE ERROR MESSAGE BUFFER
	$IDBDEF				;DEFINE INTERRUPT DATA BLOCK
	$IODEF				;DEFINE I/O FUNCTION CODES
	$DDTDEF				; DEFINE DISPATCH TBL...
	.if df,step2
	ddt$l_fdt=ddt$ps_fdt_2
	.endc
	$ptedef
	$vadef
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$irpedef
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	.if	df,step2
	$fdt_contextdef
	.endc
	$sbdef	; system blk offsets
	$psldef
	$prdef
	$acldef
	$rsndef				;define resource numbers
	$acedef
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$pcbdef
	$statedef
	$jibdef
	$acbdef
	$vcbdef
	$arbdef
	$wcbdef
	$ccbdef
	$fcbdef
	$phddef
        $RABDEF                         ; RAB structure defs
        $RMSDEF                         ; RMS constants
; defs for acl hacking
	$fibdef
	$atrdef
p1=0	; first qio param
p2=4
p3=8
p4=12
p5=16
p6=20	;6th qio param offsets

	.IF	DF,VMS$V5	;VMS V5 + LATER ONLY
	$SPLCODDEF
	$cpudef
	.ENDC
; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS
;.=UCB$W_BCR+2				;BEGIN DEFINITIONS AT END OF UCB
.=UCB$K_LCL_DISK_LENGTH	;v4 def end of ucb
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
; Leave thisfield first so we can know all diskswill have it at the
; same offset.
;
;
$def	ucb$l_hucbs	.blkl	1	;host ucb table
;
; Add other fields here if desired.
;
$def	ucb$l_ctlflgs	.blkl	1		;flags to control modes
;
$def	ucb$l_cbtctr	.blkl	1		;how many extents
$def	ucb$l_cbtini	.blkl	1		;init for counter
; preceding 2 fields allow specifying of contig-best-try extents
; on every Nth extend, not every one. This should still help keep
; file extensions from preferentially picking up chaff
$def	ucb$JFcontfil	.blkb	80
$def	ucb$l_asten	.blkl	1		;ast enable mask store
;
$DEF	ucb$l_minxt	.blkl	1		;min. extent
$def	ucb$l_maxxt	.blkl	1		;max extent
$def	ucb$l_frac	.blkl	1		;fraction to extend by
$def	ucb$l_slop	.blkl	1		;slop blocks to leave free
; DDT intercept fields
; following must be contiguous.
$def    ucb$s_ppdbgn            ;add any more prepended stuff after this
$def    ucb$l_uniqid    .blkl   1       ;driver-unique ID, gets filled in
                                        ; by DPT address for easy following
                                        ; by SDA
$def    ucb$l_intcddt   .blkl   1       ; Our interceptor's DDT address if
                                        ; we are intercepted
$def    ucb$l_prevddt   .blkl   1       ; previous DDT address
$def    ucb$l_icsign    .blkl   1       ; unique pattern that identifies
                                        ; this as a DDT intercept block
; NOTE: Jon Pinkley suggests that the DDT size should be encoded in part of this
; unique ID so that incompatible future versions will be guarded against.
$DEF	UCB$L_ICPFGS	.BLKL	2	; Flags. Reserve 2 longs so we need
					; not mess with this later.
	$VIELD UCB,0,<-
		<FI8OK,,M>,-		; 1 if this intercept and all
			>		; below understand finipl8.
$def	ucb$l_usr8	.blkl	8
$def    ucb$s_ppdend
$def    ucb$a_vicddt    .blkb   ddt$k_length
                                        ; space for victim's DDT
			.blkl	4	;safety
$def	ucb$l_backlk	.blkl	1	;backlink to victim ucb
; Make the "unique magic number" depend on the DDT length, and on the
; length of the prepended material. If anything new is added, be sure that
; this magic number value changes.
magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
p.magic=^xF0070000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn>>
$DEF	UCB$L_JF_HOST_DESCR	.BLKL	2	;host dvc desc.
;
; Store copy of victim FDT table here for step 2 Alpha driver.
; assumes FDT table is 64+2 longs long
$def	ucb$l_myfdt	.blkl	70	;user FDT tbl copy + slop for safety
$def	ucb$l_oldfdt	.blkl	1	;fdt tbl of prior fdt chain
$def	ucb$l_vict	.blkl	1	;victim ucb, for unmung check
$def	ucb$l_mungd	.blkl	1	;munged flag, 1 if numg'd
$def	ucb$l_exempt	.blkl	4	;exempt PIDs
$DEF	UCB$K_JF_LEN	.BLKW	1	;LENGTH OF UCB
;UCB$K_JF_LEN=.				;LENGTH OF UCB
	$DEFEND	UCB			;END OF UCB DEFINITONS
 
	.SBTTL	STANDARD TABLES

; 
; DRIVER PROLOGUE TABLE
; 
; 	THE DPT DESCRIBES DRIVER PARAMETERS AND I/O DATABASE FIELDS
; 	THAT ARE TO BE INITIALIZED DURING DRIVER LOADING AND RELOADING
; 
	driver_data
JF_UNITS=300
JF$DPT::
.iif ndf,spt$m_xpamod,dpt$m_xpamod=0
	DPTAB	-			;DPT CREATION MACRO
		END=JF_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		FLAGS=DPT$M_SMPMOD!dpt$m_xpamod!DPT$M_NOUNLOAD, - ;SET TO USE SMP,xa
		DEFUNITS=2,-		;UNITS 0 THRU 1 thru 31
		step=2,-
		UCBSIZE=UCB$K_JF_LEN,-	;LENGTH OF UCB
		MAXUNITS=JF_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=JFDRIVER		;DRIVER NAME
	DPT_STORE INIT			;START CONTROL BLOCK INIT VALUES
	DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\>  ;DEFAULT ACP NAME
	DPT_STORE DDB,DDB$L_ACPD+3,B,DDB$K_PACK	;ACP CLASS
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8  ;FORK IPL (VMS V5.X + LATER)
; These characteristics for an intercept driver shouldn't look just
; like a real disk unless it is prepared to handle being mounted, etc.
; Therefore comment a couple of them out. Thus it won't look file oriented
; nor directory structured.
	DPT_STORE UCB,UCB$L_DEVCHAR,L,-	;DEVICE CHARACTERISTICS
		<DEV$M_SHR-		; SHAREABLE
;		!DEV$M_DIR-		; DIRECTORY STRUCTURED
		!DEV$M_AVL-		; AVAILABLE
;		!DEV$M_FOD-		; FILES ORIENTED
		!DEV$M_IDV-		; INPUT DEVICE
		!DEV$M_ODV-		; OUTPUT DEVICE
		!DEV$M_RND>		; RANDOM ACCESS
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
		<DEV$M_NNM>		; Prefix name with "node$" (like rp06)
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_DISK  ;DEVICE CLASS
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,512  ;DEFAULT BUFFER SIZE
; FOLLOWING DEFINES OUR DEVICE "PHYSICAL LAYOUT". It's faked here.
	DPT_STORE UCB,UCB$B_TRACKS,B,1	; 1 TRK/CYL
	DPT_STORE UCB,UCB$B_SECTORS,B,64  ;NUMBER OF SECTORS PER TRACK
	DPT_STORE UCB,UCB$W_CYLINDERS,W,16  ;NUMBER OF CYLINDERS
	DPT_STORE UCB,UCB$B_DIPL,B,21	;DEVICE IPL
;	DPT_STORE UCB,UCB$B_ERTMAX,B,10	;MAX ERROR RETRY COUNT
	DPT_STORE UCB,UCB$L_DEVSTS,L,-	;INHIBIT LOG TO PHYS CONVERSION IN FDT
		<UCB$M_NOCNVRT>		;...
;
; don't mess with LBN; leave alone so it's easier to hack on...
;
	DPT_STORE REINIT		;START CONTROL BLOCK RE-INIT VALUES
;	DPT_STORE CRB,CRB$L_INTD+VEC$L_ISR,D,JF_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
	DPT_STORE DDB,DDB$L_DDT,D,JF$DDT	  ;DDT ADDRESS
        DPT_STORE UCB,UCB$L_UNIQID,D,DRIVER$DPT    ;store DPT address
                                                ; (change "XX" to device
                                                ; mnemonic correct values)
        DPT_STORE UCB,UCB$L_ICSIGN,L,magic      ; Add unique pattern (that might
                                                ; bring back some memories in
                                                ; DOS-11 users)

; HISTORICAL NOTE: under DOS-11, one would get F012 and F024 errors
; on odd address and illegal instruction traps. If we don't have
; this magic number HERE, on the other hand, we're likely to see
; bugchecks in VMS due to uncontrolled bashing of UCB fields!
	DPT_STORE END			;END OF INITIALIZATION TABLE

; 
; DRIVER DISPATCH TABLE
; 
; 	THE DDT LISTS ENTRY POINTS FOR DRIVER SUBROUTINES WHICH ARE
; 	CALLED BY THE OPERATING SYSTEM.
; 
;JF$DDT:
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=JF,-		;NAME OF DEVICE
		START=JF_STARTIO,-	;START I/O ROUTINE
		FUNCTB=JF_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=JF_CTRL_INIT,-
		UNITINIT=JF_UNIT_INIT,-
		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
		REGDMP=0,-	;REGISTER DUMP ROUTINE
		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
; 
; FUNCTION DECISION TABLE
; 
; 	THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; 	CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; 	PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
; 
chnflg:	.long	0	;chain or use our FDT chain flag...use ours if 0
myonoff:
fdtonoff: .long 0	;switch my fdt stuff off if non-0
	.ascii	/flag/	;define your own unique flag here; just leave it 4 bytes long!
	.long 0		;fdt tbl from before patch
fdt_chn  = -12
fdt_prev = -4
fdt_idnt = -8
JF_FUNCTABLE:
	FDT_INI
	FDT_BUF -	; BUFFERED functions
		<NOP,-
		FORMAT,-		; FORMAT
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
myfdtstart:
; io$_format + modifiers (e.g. io$_format+128) as function code
; allows one to associate a JF unit and some other device; see
; the JF_format code comments for description of buffer to be passed.
	fdt_act JF_format,-		;point to host disk
		<format>
;
; First our very own filter routines
;
; Following FDT function should cover every function in the local
; FDT entries between "myfdtbgn" and "myfdtend", in this case just
; mount and modify. Its function is to switch these off or on at
; need.
myfdtbgn=.
; Leave a couple of these in place as an illustration. You would of course
; need to insert your own if you're messing with FDT code, or remove these if
; you don't want to. The FDT switch logic is a waste of time and space if
; you do nothing with them...
; They don't actually do anything here, but could be added to. Throw in one
; to call some daemon at various points and it can act as a second ACP
; when control is inserted at FDT time (ahead of the DEC ACP/XQP code!)
	fdt_act MFYFilt,-
		<MODIFY>		;modify filter (e.g. extend)
myfdtend=.
JF_ucb:
JF_utb:
	.rept	JF_units
	.long	0
	.endr
	.long	0,0,0,0,0,0,0,0,0,0

	driver_code
;
; GETJFUCB - Find JF: UCB address, given r5 points to UCB of the patched
; device. Return the UCB in R0, which should return 0 if we can't find
; it.
;   This routine is called a lot and therefore is made as quick as
; it well can be, especially for the usual case.
getJFucb: .jsb_entry output=<r0>
;	clrl	r0	;no UCB initially found
	pushl	r10
	pushl	r11	;faster than pushr supposedly
;	pushr	#^m<r10,r11>
; Assumes that R5 is the UCB address of the device that has had some
; code intercepted and that we are in some bit of code that knows
; it is in an intercept driver. Also assumes R11 may be used as
; scratch registers (as is true in FDT routines). Control returns at
; label "err" if the DDT appears to have been clobbered by
; something not following this standard, if conditional "chk.err"
; is defined.
;       Entry: R5 - victim device UCB address
;       Exit: R11 - intercept driver UCB address
chk.err=0
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
; note we know our virtual driver's DPT address!!!
        movab   DRIVER$dpt,r11              ;magic pattern is DPT addr.
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
; (don't preserve r0 since we clobber it anyway.)
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=NO
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
;        beql    1$                      ;if eql yes, end search
;
; The somewhat odd layout here removes extra branches in the
; most common case, i.e., finding our driver the very first time
; through. The "bneq" branch next time is usually NOT taken.
;
	.branch_unlikely
	bneq	5$			;check next in chain if not us
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        movab   <0-ucb$a_vicddt>(r10),r11       ;point R11 at the intercept UCB
;	brb	4$	; note in this layout we can comment this out.
4$:
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=NO
; NOW clobber r0 and put things back.
	movl	r11,r0
;	popr	#^m<r10,r11>
	popl	r11
	popl	r10	;supposedly faster than popr
	rsb
; Make very sure this DDT is inside a UCB bashed according to our
; specs. The "p.magic" number reflects some version info too.
; If this is not so, not much sense searching more.
5$:     cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneq    3$                     ;exit if this is nonstd bash
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
					;chain
        bgeq    3$                     ; (error check if not negative)
        brb     2$                      ;then check again
;1$:
3$:
	clrl	r11	;return 0 if nothing found
	brb	4$
;
; Few macros for long distance branches...
;
	.macro	beqlw	lbl,?lbl2
	bneq	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bneqw	lbl,?lbl2
	beql	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bleqw	lbl,?lbl2
	bgtr	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bgeqw	lbl,?lbl2
	blss	lbl2
	brw	lbl
lbl2:
	.endm
; allocate does not zero its result area.
; This macro makes it easy to zero an allocated area before using it.
; Leaves no side effects...just zeroes the area for "size" bytes
; starting at "addr".
	.macro	zapz	addr,size
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	movc5	#0,addr,#0,size,addr
	popr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	.endm
;
	.SBTTL Our FDT Filter Routines
; These routines are edited from the JFDRiver versions to call
; getJFucb, assuming they are called with R5 pointing at the patched
; driver's UCB.
; INPUTS:
; 
; 	R3	- IRP ADDRESS (I/O REQUEST PACKET)
; 	R4	- PCB ADDRESS (PROCESS CONTROL BLOCK)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	R6	- CCB ADDRESS (CHANNEL CONTROL BLOCK)
; 	R7	- BIT NUMBER OF THE I/O FUNCTION CODE
; 	R8	- ADDRESS OF FDT TABLE ENTRY FOR THIS ROUTINE
; 	(AP)	- ADDRESS OF FIRST QIO PARAMETER
; Filter routines.
; These do the interesting stuff.
;
PopOut:
	popr	#^m<r0,r5>
pors:
; Here need to return to the "standard" FDT routine. Do so by computing
; the address in the FDT table of the normal host and calling that, then
; returning. Thus the only FDT routines in THIS driver are the ones
; it needs for its own work, not any standard ones. This calls those.
        EXTZV   #IRP$V_FCODE,#IRP$S_FCODE,IRP$L_FUNC(R3),R1     ; GET FCN CODE
	pushr	#^m<r6,r7,r8,r9,r10>
	movl	r1,r10
	jsb	getJFucb		;find JF UCB checking for extra links
	tstl	r0			;got it?
	bgeq	199$			;if not skip out
	movl	ucb$l_oldfdt(r0),r7	;get address of previous FDT
	bgeq	199$			;ensure ok...
;	movl	ucb$l_ddt(r5),r7	;find FDT
; Here rely on the fact that we got here via our modified FDT call and that
; the orig. FDT is stored just a bit past the current one.
;	movl	<ucb$l_oldfdt-ucb$l_myfdt>(r7),r7	;point at orig. FDT
	addl2	#8,r7			;point at one of 64 fdt addresses
	movl	(r7)[r10],r8		;r7 is desired routine address
;now call the "official" FDT code
	pushl	r6	;ccb
	pushl	r5	;ucb
	pushl	r4	;pcb
	pushl	r3	;irp
	calls	#4,(r8)			;Call the original routine
	popr	#^m<r6,r7,r8,r9,r10>
; Now return as the original routine would.
	ret
199$:
	popr	#^m<r6,r7,r8,r9,r10>
	movl	#16,r0
	call_abortio
	ret
;	rsb
mfyfilt: $driver_fdt_entry	;filter on MODIFY requests (e.g. extend)
; First do some preliminary checks for sanity.
; 1. Channel must NOT be kernel mode
; 2. Not a movefile
	tstl	r6		;is there a CCB (must be +)
	bleq	pors		;if not skip out
	cmpb	ccb$b_amod(r6),#1	;knl mode access?
	bleq	pors		;leave knl mode chnls alone!
;funct modifiers are bits 6-15
; this is hex ffc0
; Normal io$_modify should have no modifiers, so if it has it's
; for something else; leave that alone.
	.if	ndf,evax
	bitw	#^x1FC0,irp$w_func(r3) ;this a movefile or other modifier?
	.iff
	bitl	#^xDFC0,irp$l_func(r3) ;this a movefile or other modifier?
	.endc
	bneq	pors		;if so ignore it here.
	pushr	#^m<r0,r5>
; original r5 now at 4(sp). Must get that to continue the ops.
	jsb	getJFucb		;find JFDRiver ucb
	tstl	r0
	bgeqw	popout
	movl	r5,ucb$l_backlk(r0)	;save link'd ucb in ours too.
	movl	r0,r5			;point R5 at JF UCB
;make sure not a knl mode channel (leave the XQP channel alone!!!)
	cmpb	ccb$b_amod(r6),#1	;this the XQP's chnl?
	bleqw	popout			; if so scram NOW.
; Now ensure that this call is not in the same JOB as the daemon.
; (This lets the daemon spawn processes to do some work.)
	bitl	i^#2,ucb$l_ctlflgs(r5)	;look at mfy?
	bneqw	mfycmn			;if neq yes
; (test later will see about space control if doing this)
701$:
	popr	#^m<r0,r5>
	brw	pors
mspcj:	popl	r0
	brw	popout
mfycmn:
; here we can modify request fields in the FIB the user supplies to reduce
; fragmentation...e.g. set fib$l_exsz bigger or set fib$m_alconb bit
; in fib$w_exctl IFF fib$m_alcon is not set & set fib$m_aldef.
;
	pushl	r0
	.if	ndf,evax
	movl	p1(ap),r0	;get fib
	.iff
	movl	irp$l_qio_p1(r3),r0
	.endc
; remember to define xx$nor sometime to wrtchk args!!
	.iif df,xx$nor,ifnord #4,4(r0),mspcj
	movl	4(r0),r0	;...from descriptor
	.iif df,xx$nor,ifnord #4,fib$w_exctl(r0),mspcj
	bitw	#fib$m_extend,fib$w_exctl(r0)	;extending at all?
	beqlw	mspc			;if no extend, leave fib alone
; Because contiguous best try allocation flushes the entire extend cache,
; it can cause a tremendous performance hit. Therefore allow it to be
; separately switched so that the benefits of longer extents can be had
; if desired without forcing this flushing every time a file is extended.
	bitl	i^#32,ucb$l_ctlflgs(r5)		;separate control for setting contig best try
	beql	1$
; leave contig and contig-best-try alone
	bitw	#<fib$m_alcon!fib$m_alconb>,fib$w_exctl(r0)	;contig alloc?
	bneq	1$		;if contig leave it alone
; allow this on every nth extend.
; This will allow periodic flushes of the extent cache but will let
; it not be made totally useless. By flushing the extent cache periodically
; we can try to reduce the fragmentation it induces.
; if bit 16384 is not set, do not set aldef.
	bitl	i^#16384,ucb$l_ctlflgs(r5)	;set aldef all the time?
	beql	704$
	bisw	#<fib$m_aldef>,fib$w_exctl(r0) ;set to use vol default if
704$:					;bigger than program's
	decl	ucb$l_cbtctr(r5)	;count down
	bgtr	1$			;and if >0 don't set cbt yet
	movl	ucb$l_cbtini(r5),ucb$l_cbtctr(r5)	;else reset counter
	bisw	#<fib$m_alconb>,fib$w_exctl(r0) ;else turn on contig best
					;try and turn on use of
					;system default extension if
					;larger than program default
1$:
; One can add code to check file size and bump extension by more than default if
; it's big (for example, extend by 10% of its' size, not by a few blocks at a time).
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8>
	bitw	#<fib$m_alcon>,fib$w_exctl(r0)	;contig alloc?
	bneqw	222$	;leave size alone for contig alloc
	movl	ccb$l_wind(r6),r7	;get window block
	bgeq	222$			;guard
	movl	wcb$l_fcb(r7),r8	;and file control blkock
	bgeq	222$			;guard
	movl	fcb$l_filesize(r8),r6	;get filesize
	beql	222$
; It is suggested to divide by acp$gb_window instead of 10...
; this is the acp_window sysgen param (default 7), the number of retrieval pointers
; present per window by default. This has no direct relation to size, but one must
; expect at least one retrieval pointer needs to change. In the default situation
; say 1/4th of file size can be used.
;
; The fraction starts at 1/4, but can be anywhere from 1/1 to 1/1000
	divl2	ucb$l_frac(r5),r6	;get 1/4 of current size or so
	incl	r6			;plus one...for good luck
	cmpl	r6,ucb$l_maxxt(r5)	;extending over max (nominally 120000)
	bleq	1222$
	movl	ucb$l_maxxt(r5),r6	;clamp to max what we're forcing
1222$:
	cmpl	r6,ucb$l_minxt(r5)	;if less than 10 leave alone too
	bgeq	1223$
	movl	ucb$l_minxt(r5),r6	;at least grab this minimum
1223$:
; never try to grab over1/8 of total free space.
	movl	ucb$l_backlk(r5),r8	;get host ucb (set just above)
	bgeq	222$			;(better be there)
	movl	ucb$l_vcb(r8),r8	;point at vcb
	bgeq	222$
	movl	vcb$l_free(r8),r8	;no. blks free
	ashl	#-3,r8,r8		;free space /8
	cmpl	r6,r8			;extent over freespc/8?
;	bgtr	222$			;if so don't push it here
	bleq	3223$			;if not all still ok
	movl	r8,r6			;else clamp to free/8
3223$:
	cmpl	r6,fib$l_exsz(r0)	;make sure we're increasing size
	bleq	222$			;if less than user wants, leave alone

; if 4 bit is clear, allow size ctl always. Otherwise only if aldef set.
	bitl	i^#4,ucb$l_ctlflgs(r5)
	beql	2222$
	bitw	#<fib$m_aldef>,fib$w_exctl(r0) ;set to use vol default if
	beql	222$			;if aldef NOT set, leave size alone.
2222$:
	movl	r6,fib$l_exsz(r0)	;fill in as new extend size
222$:
	popr	#^m<r2,r3,r4,r5,r6,r7,r8>
; fall thru to space control
mspc:	popl	r0
	popr	#^m<r0,r5>
	movl	#1,r0
	brw	pors
;++
;
; JF_format - bash host disk tables to point at ours.
;
; With no function modifiers, this routine takes as arguments the name
; of the host disk (the real disk where the virtual disk will exist),
; the size of the virtual disk, and the LBN where the virtual disk
; will start. After these are set up, the device is put online and is
; software enabled.
;
; This routine does virtually no checking, so the parameters must be
; correct.
;
; Inputs:
;	p1 - pointer to buffer. The buffer has the following format:
;	     longword 0 - (was hlbn) - flag for function. 1 to bash
;			  the targetted disk, 2 to unbash it, else
;			  illegal.
;	     longword 1 - virtual disk length, the number of blocks in
;			  the virtual disk. If negative disables
;			  FDT chaining; otherwise ignored.
;	     longword 2 through the end of the buffer, the name of the
;			  virtual disk. This buffer must be blank
;			  padded if padding is necessary
;
;
;	p2 - size of the above buffer
;--
JF_format: $driver_fdt_entry
	bicw3	#io$m_fcode,irp$l_func(r3),r0	;mask off function code
	bneq	20$			;branch if modifiers, special
;thus, normal io$_format will do nothing.
	brw pors			;regular processing
100$:
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
10$:
	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	clrl	r1
	call_abortio
	ret
;	jmp	g^exe$abortio
20$:
        movl    irp$l_qio_p1(r3),r0     ;buff address
        movl    irp$l_qio_p2(r3),r1     ;buff length
	call_writechk
;	jsb	g^exe$writechk		;read access? doesn't return on error
;	clrl	irp$l_bcnt(r3)		;paranoia, don't need to do this...
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	irp$l_qio_p1(r3),r0
	movl	(r0)+,r7		;get option code
	bleq	100$			;0 or negative illegal
	cmpl	r7,#2			;3 and up illegal too
	bgtr	100$
	incl	chnflg
	movl	(r0)+,r6		;size of virtual disk (ignored)
	bleq	70$
	clrl	chnflg			;if 0 or neg. size don't chain...
70$:
	movab	(r0),-			;name of "real" disk
		ucb$l_JF_host_descr+4(r5)
        subl3   #8,irp$l_qio_p2(r3),-
                ucb$l_JF_host_descr(r5)
	bleq	100$			;bad length
	movab	ucb$l_JF_host_descr(r5),r1	;descriptor for...
	jsb	g^ioc$searchdev		;search for host device
	blbs	r0,30$			;branch on success
; fail the associate...
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movzwl	#ss$_nosuchdev+2,r0	;make an error, usually a warning
	clrl	r1
	call_abortio
	ret
;	jmp	g^exe$abortio		;exit with error
30$:	;found the device
; r1 is target ucb address...
; move it to r11 to be less volatile
	movl	r1,r11
	cmpl	r7,#1		;bashing the target UCB?
	bneq	31$
	jsb	mung		;go mung target...
	brb	32$
31$:
; Be sure we unmung the correct disk or we can really screw up a system.
	cmpl	r11,ucb$l_vict(r5)	;undoing right disk?
	bneq	32$			;if not skip out, do nothing.
	jsb	umung		;unmung target
32$:
;	bisw	#ucb$m_valid,ucb$w_sts(r5)	;set volume valid
;	bisw	#ucb$m_online,ucb$w_sts(r5)	;set unit online
;	movl	ucb$l_irp(r5),r3		;restore r3, neatness counts
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=yes
;	jmp	g^exe$finishioc			;wrap things up.
mung: .jsb_entry
; steal DDT from host. Assumes that the intercept UCB address
; is in R5 (that is, the UCB in which we will place the DDT copy),
; and that the UCB of the device whose DDT we are stealing is
; pointed to by R11. All registers are preserved explicitly so that
; surrounding code cannot be clobbered. R0 is returned as a status
; code so that if it returns with low bit clear, it means something
; went wrong so the bash did NOT occur. This generally means some other
; code that does not follow this standard has grabbed the DDT already.
; The following example assumes the code lives in a driver so the
; unique ID field and magic number are set already.
	tstl	ucb$l_mungd(r5)		;already munged/not deassigned?
	beql	6$
	rsb				;no dbl bash
6$:
        pushr   #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
; Acquire victim's fork lock to synchronize all this.
        movl    #ss$_normal,r0          ;assume success
        forklock ucb$b_flck(r11),-
	savipl=-(sp),preserve=YES
; find the current DDT address from the UCB (leaving the copy in
; the DDB alone)
        movl    ucb$l_ddt(r11),r10      ;point at victim's DDB
; fill in host ucb tbl (makes chnl handling faster)
	movab	JF_ucb,ucb$l_hucbs(r5)
	movl	ucb$l_hucbs(r5),r9	;get ucb table
	movzwl	ucb$w_unit(r5),r0	;get unit no.
	moval	(r9)[r0],r9		;point into tbl
	movl	r11,(r9)		;save target ucb addr in tbl
; see if this DDT is the same as the original
        movl    ucb$l_ddb(r11),r9       ;the ddb$l_ddt is the original
        cmpl    ddb$l_ddt(r9),r10       ;bashing driver the first time?
        beql    1$                      ;if eql yes
; driver was bashed already. Check that the current basher followed the
; standard. Then continue if it looks OK.
        cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
                                        ;does the magic pattern exist?
; if magic pattern is missing things are badly messed.
        beql    2$                      ;if eql looks like all's well
        movl    #2,r0                   ;say things failed
        brw     100$                    ;(brb might work too)
2$:
; set our new ddt address in the previous interceptor's slot
        movab   ucb$a_vicddt(r5),<ucb$l_intcddt-ucb$a_vicddt>(r10)
                                        ;store next-DDT address relative
                                        ;to the original victim one
;	movl	<ucb$l_icpfgs-ucb$a_vicddt>(r10),ucb$l_icpfgs(r5) ;copy fi8ok
1$:
	movl	#1,ucb$l_mungd(r5)	;say we munged JF
        movl    r10,ucb$l_prevddt(r5)   ;set previous DDT address up
        clrl    ucb$l_intcddt(r5)       ;clear intercepting DDT initially
3$:
        pushl   r5
; copy a little extra for good luck...
        movc3   #<ddt$k_length+12>,(r10),ucb$a_vicddt(r5)    ;copy the DDT
        popl    r5                      ;get UCB pointer back (movc3 bashes it)
;
; Here make whatever mods to the DDT you need to.
;
; FOR EXAMPLE make the following mods to the FDT pointer
; (These assume the standard proposed for FDT pointers)
        movab   ucb$a_vicddt(r5),r8     ;get a base register for the DDT
        movl    r5,JF_functable+fdt_prev    ;save old FDT ucb address
	movl	ddt$l_fdt(r10),ucb$l_oldfdt(r5)
        movl    ucb$l_uniqid(r5),JF_functable+fdt_idnt ;save unique ID also
; copy legal and buffered entry masks of original driver.
; HOWEVER, set mask for format entry to be nonbuffered here since
; we deal with it.
	pushr	#^m<r6,r7,r8,r9,r10,r11>
	movab	ucb$l_myfdt(r5),r9	;our function table dummy in UCB
	movl	ddt$l_fdt(r10),r7	;victim's FDT table
; We want all functions legal in the victim's FDT table to be legal
; here.
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;preserve regs from movc
	movl	#<68*4>,r0		;byte count of a step 2 FDT
	movc3	r0,(r7),(r9)		;copy his FDT to ours
	popr	#^m<r0,r1,r2,r3,r4,r5>	;preserve regs from movc
; Now copy in our modify & back-to-original FDT cells.
; We will do this in our FDT table by having FDT definitions only
; for those functions in JFDRiver that we service locally. Thus
; all entry cells for the rest will point in the JF FDT to
; exe$illiofunc.
	movab	g^exe$illiofunc,r8	;get the magic address
	movab	JF_functable,r10	;r10 becomes JF FDT tbl
	addl2	#8,r10			;point at functions
	addl2	#8,r9			;his new FDT...
	movl	#64,r11			;64 functions
75$:	cmpl	(r10),r8		;this function hadled in JF?
	beql	76$			;if eql no, skip
	movl	(r10),(r9)		;if we do it point his fdt at our fcn
; (NOTE: our functions MUST therefore call the previous FDT's functions at
;  end of their processing.)
76$:	cmpl	(r10)+,(r9)+		;pass the entry
	sobgtr	r11,75$			;do all functions
; JFDRiver FDT table. Last entry goes to user's original FDT chain.
;
; Thus we simply insert our FDT processing ahead of normal stuff, but
; all fcn msks & functions will work for any driver.
	popr	#^m<r6,r7,r8,r9,r10,r11>
; Now point the user's FDT at our bugger'd copy.
        movab	ucb$l_myfdt(r5),ddt$l_fdt(r8) ;point at our FDT table
        clrl    myonoff                 ;turn my FDTs on
;
; Finally clobber the victim device's DDT pointer to point to our new
; one.
	.iif df, evax, evax_imb
        movab   ucb$a_vicddt(r5),ucb$l_ddt(r11)
	.iif df, evax, evax_imb
; Now the DDT used for the victim device unit is that of our UCB
; and will invoke whatever special processing we need. This processing in
; the example here causes the intercept driver's FDT routines to be
; used ahead of whatever was in the original driver's FDTs. Because
; the DDT is modified using the UCB pointer only, target device units
; that have not been patched in this way continue to use their old
; DDTs and FDTs unaltered.
;
; Processing complete; release victim's fork lock
100$:
        forkunlock lock=ucb$b_flck(r11),newipl=(sp)+,-
         preserve=YES
        popr    #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	rsb
umung: .jsb_entry
;
; Entry: R11 points at victim device UCB and current driver is the one
; desiring to remove its entry from the DDT chain. Thus its xx$dpt: address
; is the one being sought. ("Current driver" here means the intercept
; driver.)
;   It is assumed that the driver knows that the DDT chain was patched
; so that its UCB contains an entry in the DDT chain
        pushr   #^m<r0,r1,r2,r3,r4,r5,r10,r11>
;	.iif	df,x$$$dt,jsb g^ini$brk	;***********************debug*********
	movl	r11,r5			;hereafter use r5 as victim's UCB
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
        movl    ucb$l_ddb(r5),r1        ;get ddb of victim
        movl    ddb$l_ddt(r1),r1        ;and real original DDT
        movl    r10,r0                  ;save ucb$l_ddt addr for later
        movab   DRIVER$DPT,r11             ;magic pattern is DPT addr.
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=YES
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
        beql    1$                      ;if eql yes, end search
        .if     df,chk.err
        cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneqw    4$                     ;exit if this is nonstd bash
        .endc   ;chk.err
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
                                        ;chain
        .if     df,chk.err
        bgeqw   4$                     ; (error check if not negative)
        .endc   ;chk.err
        brb     2$                      ;then check again
1$:
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        tstl    <ucb$l_intcddt-ucb$a_vicddt>(r10)       ;were we intercepted?
        bgeq    3$                      ;if geq no, skip back-fixup
; we were intercepted. Fix up next guy in line.
        movl    <ucb$l_intcddt-ucb$a_vicddt>(r10),r11  ;point at interceptor
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),<ucb$l_prevddt-ucb$a_vicddt>(r11)
3$:
; if we intercepted someone, fix up our intercepted victim to skip by
; us also.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r2    ;did we intercept
                                        ;original driver?
        cmpl    r2,r1                   ;test if this is original
        beql    5$                      ;if eql yes, no bash
; replace previous intercept address by ours (which might be zero)
        movl    <ucb$l_intcddt-ucb$a_vicddt>(r10),<ucb$l_intcddt-ucb$a_vicddt>(r2)
5$:
; Here remove FDT entries from the list if they were modified.
; This needs a scan of the FDT chain starting at the victim's
; ddt$l_fdt pointer and skipping around any entry that has address
; JF_functable:
;  The FDT chain is singly linked. The code here assumes everybody
; plays by the same rules!
; NOTE: Omit this code if we didn't insert our FDT code in the chain!!!
        movl    ddt$l_fdt(r0),r1        ;start of FDT chain
        movab   JF_functable,r2         ;address of our FDT table
        clrl    r3
	movab	<0-ucb$a_vicddt>(r10),r4 ;initially point at our ucb
; Also set the JF device offline when we unbash it. This is a simple
; flag that ctl prog. can use to tell if it's been used already.
	bicl	#<ucb$m_valid!ucb$m_online>,ucb$l_sts(r4)
6$:     cmpl    r1,r2                   ;current fdt point at us?
        beql    7$                      ;if eql yes, fix up chain
        movl    r1,r3                   ;else store last pointer
        movl    fdt_prev(r1),r4         ;and point at next
	bgeq	8$
	movl	ucb$l_oldfdt(r4),r1	;where last FDT pointer is in the ucb
;;;BUT not all UCBs will have the fdt offset at the same place!!!
;;;HOWEVER we will leave this in, putting the oldfdt field first after
;;;the regular UCB things.
        bgeq    8$                      ;if not sys addr, no messin'
        brb     6$                      ;look till we find one.
7$:
;r3 is 0 or fdt pointing to our block next
;r1 points at our fdt block
        tstl    r3                      ;if r3=0 nobody points at us
        bgeq    8$                      ;so nothing to do
	movl	fdt_prev(r1),r4
	bgeq	17$
	movl	ucb$l_oldfdt(r4),-(sp)	;save old fdt loc
	movl	fdt_prev(r3),r4
	blss	18$
	tstl	(sp)+
	brb	17$
18$:	movl	(sp)+,ucb$l_oldfdt(r4)
17$:    movl    fdt_prev(r1),fdt_prev(r3)  ;else point our next-fdt pointer at
                                        ;last fdt addr.
8$:
;
; Finally if the victim UCB DDT entry points at ours, make it point at
; our predecessor. If it points at a successor, we can leave it alone.
        cmpl    r10,r0                  ;does victim ucb point at our DDT?
        bneq    4$                      ;if not cannot replace it
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),ucb$l_ddt(r5)
	clrl	<ucb$l_mungd-ucb$a_vicddt>(r10)	;zero JF munged flag
4$:
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES
       popr    #^m<r0,r1,r2,r3,r4,r5,r10,r11>
                                        ;copy our prior DDT ptr to next one
	rsb

	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; JF_ctrl_INIT - CONTROLLER INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; noop
; INPUTS:
; R4 - CSR ADDRESS
; R5 - IDB ADDRESS
; R6 - DDB ADDRESS
; R8 - CRB ADDRESS
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 	THE DRIVER CALLS THIS ROUTINE TO INIT AFTER AN NXM ERROR.
;--
JF_ctrl_INIT: $driver_ctrlinit_entry
;	CLRL	CRB$L_AUXSTRUC(R8)	; SAY NO AUX MEM
	movl	#1,r0
	Ret				;RETURN
	.SBTTL	INTERNAL CONTROLLER RE-INITIALIZATION
;
; INPUTS:
;	R4 => controller CSR (dummy)
;	R5 => UCB
;
	.SBTTL	UNIT INITIALIZATION ROUTINE
;++
; 
; JF_unit_INIT - UNIT INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE SETS THE JF: ONLINE.
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 
; INPUTS:
; 
; 	R4	- CSR ADDRESS (CONTROLLER STATUS REGISTER)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
;	R8	- CRB ADDRESS
; 
; OUTPUTS:
; 
; 	THE UNIT IS SET ONLINE.
; 	ALL GENERAL REGISTERS (R0-R15) ARE PRESERVED.
; 
;--

JF_unit_INIT: $driver_unitinit_entry
; Don't set unit online here. Priv'd task that assigns JF unit
; to a file does this to ensure only assigned JFn: get used.
;	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)  ;SET UCB STATUS ONLINE
;limit size of JF: data buffers
JF_bufsiz=8192
	movl	#JF_bufsiz,ucb$l_maxbcnt(r5)	;limit transfers to 8k
	MOVB	#DC$_MISC,UCB$B_DEVCLASS(R5) ;SET DISK DEVICE CLASS
	clrl	ucb$l_mungd(r5)			;not mung'd yet
; NOTE: we may want to set this as something other than an RX class
; disk if MSCP is to use it. MSCP explicitly will NOT serve an
; RX type device. For now leave it in, but others can alter.
; (There's no GOOD reason to disable MSCP, but care!!!)
	movab	DRIVER$DPT,ucb$l_uniqid(r5)
	movl	#^Xb22d4001,ucb$l_media_id(r5)	; set media id as JF
; (note the id might be wrong but is attempt to get it.) (used only for
; MSCP serving.)
	MOVB	#DT$_FD1,UCB$B_DEVTYPE(R5)  ;Make it foreign disk type 1
; (dt$_rp06 works but may confuse analyze/disk)
;;; NOTE: changed from fd1 type so MSCP will know it's a local disk and
;;; attempt no weird jiggery-pokery with the JF: device.
; MSCP may still refuse to do a foreign drive too; jiggery-pokery later
; to test if there's occasion to do so.
; Set up crc polynomial
	movab	JF_utb,ucb$l_hucbs(r5)	;host ucb table
	clrl	chnflg		;initially set to use our chain of FDTs
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)  ;SET UCB STATUS OFFLINE
	movl	#1,r0
	ret
;++
; 
; JF_STARTIO - START I/O ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS FORK PROCESS IS ENTERED FROM THE EXECUTIVE AFTER AN I/O REQUEST
; 	PACKET HAS BEEN DEQUEUED.
; 
; INPUTS:
; 
; 	R3		- IRP ADDRESS (I/O REQUEST PACKET)
; 	R5		- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	IRP$L_MEDIA	- PARAMETER LONGWORD (LOGICAL BLOCK NUMBER)
; 
; OUTPUTS:
; 
; 	R0	- FIRST I/O STATUS LONGWORD: STATUS CODE & BYTES XFERED
; 	R1	- SECOND I/O STATUS LONGWORD: 0 FOR DISKS
; 
; 	THE I/O FUNCTION IS EXECUTED.
; 
; 	ALL REGISTERS EXCEPT R0-R4 ARE PRESERVED.
; 
;--
JF_STARTIO: $driver_start_entry
; 
; 	PREPROCESS UCB FIELDS
; 
;	ASSUME	RY_EXTENDED_STATUS_LENGTH  EQ  8
;	CLRQ	UCB$Q_JF_EXTENDED_STATUS(R5)	; Zero READ ERROR REGISTER area.
; 
; 	BRANCH TO FUNCTION EXECUTION
	bbs	#ucb$v_online,-	; if online set software valid
		ucb$l_sts(r5),210$
216$:	movzwl	#ss$_volinv,r0	; else set volume invalid
	brw	resetxfr	; reset byte count & exit
210$:
; Unless we use this entry, we want to junk any calls here.
	brb	216$		;just always say invalid volume.

; Get here for other start-io entries if the virtual disk code is
; commented out also, as it must be.
;FATALERR:				;UNRECOVERABLE ERROR
;	MOVZWL	#SS$_DRVERR,R0		;ASSUME DRIVE ERROR STATUS

RESETXFR:	; dummy entry ... should never really get here
	MOVL	UCB$L_IRP(R5),R3	;GET I/O PKT
;	MNEGW	IRP$W_BCNT(R3),UCB$W_BCR(R5) ; RESET BYTECOUNT
;	BRW	FUNCXT
FUNCXT:					;FUNCTION EXIT
	CLRL	R1			;CLEAR 2ND LONGWORD OF IOSB
	REQCOM,environment=call		; COMPLETE REQUEST
; 
;PWRFAIL:				;POWER FAILURE
;	BICW	#UCB$M_POWER,UCB$W_STS(R5)  ;CLEAR POWER FAILURE BIT
;	MOVL	UCB$L_IRP(R5),R3	;GET ADDRESS OF I/O PACKET
;	MOVQ	IRP$L_SVAPTE(R3),-	;RESTORE TRANSFER PARAMETERS
;		UCB$L_SVAPTE(R5)	;...
;	BRW	JF_STARTIO		;START REQUEST OVER
;JF_INT::
;JF_UNSOLNT::
;	POPR	#^M<R0,R1,R2,R3,R4,R5>
;	REI	;DUMMY RETURN FROM ANY INTERRUPT
	;;
JF_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.ENDC	;step2
	.END
