noswpio$=0
fu.outst=0
; The notion here is that when we have a direct path to an underlying device
; (ANY of our paths!) we allow serving to be done; otherwise not. When
; our direct path is not available we do not let serving be done by us.
; Also we must make the direct path the one we use if possible. Possible
; in this context means the enable bit is also set (since there may be global
; reasons why we can't use a direct path). We can get servers to go elsewhere
; by returning MV type status to them.
;
;m$$trp=0 ;mousetrap info
ffinish=0
x$$$dt=0
	.iif	ndf,mintries,mintries=8	;allow 2 packacks before switch
try$safe=0
evax=0
step2=1
	.TITLE	SWDRiver	;Failover SWitch driver
	.IDENT	'V02'
; Copyright 1996-1997 Digital Equipment Corp.
; All rights reserved
;  Author: Glenn C. Everhart
;
; This driver is designed as a method of developing and presenting a
; software failover service which can be independent of underlying
; drivers to a large degree. While the service may be migrated into the
; other drivers proper, the intent here is to provide a function which
; will work immediately, BUT which will not be too intimately tied into
; other drivers, so that when a QIO server is built, this can be replaced
; by it if one wants. By the same token, this package is intended to allow
; failover from one "path" to a device to another "path" regardless of
; underlying technology, provided only that the "normal" VMS driver front
; end interface is preserved. (Due to its generality, this scheme should
; be OK for other failover types too.)
;
; The overall view of what happens is that a switch is inserted at the
; start_io entry (and other entries) of one of a pair of drivers, which
; represent the two paths to some storage. The intercept driver keeps a
; record of the two UCB addresses involved (and several other items) so it
; can direct I/O appropriately. Now, when the io$_packack from mount verify
; is seen for the Nth time (N is settable), the intercept code takes this
; as meaning that the original path is as quiesced as it can be made, and
; that an alternate path should be tried. When this alternate path is to be
; switched to, IRPs to the original driver are simply routed to the
; alternate path driver instead using the standard $insioqc entry point, or
; the appropriate alternate in case of mount verify packacks, and are
; "caught" at I/O post time and post processed in the context of the
; original driver (since it is the one that is mounted etc., so we must not
; louse up the file processing!!). This done, the IRP is posted and
; finished with. We keep a count of all IRPs put on an underlying driver
; queue (we have a separate multipath queue which serves instead of an
; underlying queue) and not posted, so we can tell when an IRP is in
; the "hands" of an underlying driver. If this count is zero we know
; the I/O is idle. Also we zero it at switch time, since MV will have
; cleared things up and if during a transition we somehow screwed up,
; this will get "in synch" again. (This is mainly paranoia in action.)
;
; If the second path sees a failure similar to the first, the driver will
; try and return. If this does not result in some non-packack I/Os
; however the drive will be left to the good (?) graces of mount verify
; rather than skipping back and forth indefinitely.
;
; Setup of this will for now be via a privileged image. To avoid having
; to allocate other storage, the intercept will save the original value
; of IRP$L_PID in IRP$Q_QIO_P2+4 during processing. The idea is to provide
; a very short detour path for I/O being rerouted. The scheme has the
; additional advantage that it can be used to route I/O between paths
; using the MSCP (or other) server and SCSI; it won't much care what
; details of the driver are. We especially do NOT want to be having to
; allocate more pool every IRP if we can possibly avoid it. On VAX
; we probably need to anyway, because the IRP has less room, and reusing
; these cells needs to be addressed more carefully in review. It appears
; nothing uses them at this point...certainly normal I/O processing
; does not...but any newer code that tries to "peek" into these
; arguments gets interfered with. It would make life easier if I/O
; interception might have a "stack" of a few pointers & statuses
; that could be used, or some dedicated pointers to such a stack which
; an intercept layer could maintain. Grabbing pointers willy-nilly
; is not really all one could like. For grabbing a DDT, and putting it
; into, say, an intercept UCB, storage is OK. For the per-IRP processing
; however you want to keep track of the original values in the IRP itself
; if at all possible, and a stack in the IRP would be a near-ideal place
; to put them, with a bottom-of-stack pointer to check against as
; well as a top pointer. Each part of a stream would manage its own
; handling. Also, irp$l_pid should be supplemented with another field
; giving the first, original PID of the I/O so that stream I/O need
; not be confused with real system I/O.
;
; Initially the start IO entry is captured for this, but it will be
; important to check other possible entries also. Since the IRPs will
; be "transparently" rerouted, most functions will be unaltered, but
; mount verify must be captured also. To keep the rest of VMS from
; using the second driver directly, it will be marked offline.
;
; Because the second drive will not be seen as mounted, mount verify
; will not be done on it, so the I/O post path used here will monitor
; this also, when using its secondary path, and call the executive
; exe$mount_ver routine when conditions are appropriate from within the
; context of the "correct" device. The packack IRPs from mount verify will
; also be forwarded the way mount verify does them, i.e., without
; synch on UCB busy, so that the driver will have its flow handled
; correctly. Since we will switch over only where the device has been
; already idled, we need not be bothered over the fact that the UCB
; busy flags will be in this fashion "multiplexed" and used for more
; than one driver...they will be used for exactly one at a time. Note that
; an extra I/O will be provided to do "manual" switching too, but
; this must be done when the driver is idle. Since this idleness condition
; cannot readily be known, it will remain a usage restriction.
;
; The set up of all this will be a tad tricky. First, the drivers must
; be set up (at least dkdriver) not to allow automatic MSCP serving of
; the disks where the path will not be the preferred one. Arranging for
; these to come up offline would do this. Alternatively so long as only
; one device of each pair is initially online, we can use that one.
; Having both devices come up online and then be MSCP served would however
; cause problems. (More info on how to tell where HSZs are is needed.)
;
; An HSZ connected lun will be "ready" from one side only. We need to
; ensure that side is used for this intercept and for all MSCP vectoring.
; Once this is done we can ensure things are handled correctly. While
; this could be done by excluding these devices from autoconfigure and
; performing manual configs (connects) and manually starting MSCP, this
; is not the preferable approach if it can be avoided.
;
; Note too that drivers wanting to use this intercept will operate
; cleanly provided they find start_io by following the chain
; UCB$L_DDT -> DDT$L_STARTIO chain to find the start-io entry point.
; Where a driver startio routine loops back to its start for more
; work, without following the chain, this intercept cannot guarantee
; at which IRP processing will be switched. Should the chain be followed
; in all cases, however, the switch will take effect at once. In either
; case, an IRP gets processed by one path or the other, not both.
;
; This driver will hook all entries normally used by disks that I know
; of, so that they will be dispatched properly. This means not only
; startio, but altstart, mountver, etc. It will also use the new-packet
; entry itself to count IRPs and take over irp$l_pid (checking at
; startio not to do it twice!) so that even IRPs dequeued internally by
; a driver will be counted properly, thus ensuring that our idle count
; is accurate. Altstart entries need to be counted too!
; The register dump routine is called only when the driver is active
; and should get the right UCB in any case, since if in the secondary path,
; the IRP and so on all validly point to the secondary UCB and have no
; reason to access the primary. The cancel entry must be fielded also since
; it can be called from outside and must be guaranteed also to have the
; right UCB.
;
; The buffer to pass to connect is of form:
;buf: .long	1	;bash flag
; .long 1000	;dummy size of disk. Must be > 0
; .ascid /devicename/	;device name we should connect to
;
; The disconnect buffer is just like the connect one, except the
; buffer starts with ".long 2" instead of ".long 1"
;
;
; Some sanity checks will refuse to disconnect the intercept unless
; the right device is given and unless an intercept had been done
; in the first place. This is a shade crude, but necessary to prevent
; corruption of the I/O database. It is expected that the intercepts
; here get set up at boot time. 
;
; Glenn Everhart
; everhart@Arisia.GCE.Com
;
;
;
; Code to notice when we build in an Alpha environment automagically.
;
.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
; above for Alpha only.
;
; Glenn C. Everhart 1994
;
;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/
; There are lots of defs here...more than are really needed, but they
; do no harm & are likely to be useful in intercept code.
;

;	$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
	$IPLDEF
	$DDTDEF				; DEFINE DISPATCH TBL...
; There are a couple of nonstandard names used here; define them locally.
	.IF DF,STEP2
	DDT$L_FDT=DDT$PS_FDT_2
	DDT$L_START=DDT$PS_START_2
	.ENDC
	$PTEDEF
	$CANDEF
	$VADEF
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$IRPEDEF
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	$FDT_CONTEXTDEF
	$FDTDEF
	.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
	$cddbdef
	$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$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.
;
;
; align to quadword
	.=<<.+7>& ^C7>          ; Align data structure
	.if df,m$$trp
$def	ucb$l_mtrp	.blkl	16
	.endc
;
; To ensure our consumers don't pull anything we haven't seen off of
; their input queue without us knowing, we will queue all incoming
; IRPs HERE, on our OWN UCB at this offset, and move them to the
; target device when we are ready for this to be done. This will make
; the data path rather symmetrical since ALL IRPs will move from the
; $QIO processor to here, to another device driver.
;
; Unfortunately the 6.2 through 7.1 dkdrivers do this. Might be
; better to just fix them...let them do a callout.
;
; When mount verify is being started, IRPs will also be replaced on
; this queue. When it is being ended, we will allow ONE IRP onto the
; target device and call the victim's MV end routne to restart things.
; This will basically always queue the IRP here after calling the
; underlying driver routine, so that the underlying driver will control
; the order. We will bring things back in the same order. At MV end
; we will process the leading IRP into the device queue and call the host
; MV end routine to start it up. If MV failed we have to flush I/O for
; the whole thing, of course.
;   On return from end mv, we also will see if we have more to send the
; host and process that material in as needed. 
; Thus we will have a MV mode of operation and a normal mode, since
; we don't switch at the same time as MV.
; (We must allow some MV activity first in case the problem is some "local"
; disturbance.)

; align to quadword
	.=<<.+7>& ^C7>          ; Align data structure
$DEF	UCB$L_MPQUEUE			; alias for multipath queue hdr
$DEF	UCB$L_MYIRPH	.BLKL	1	; My IRP queue header
$DEF	UCB$L_MYIRPT	.BLKL	1	; (head and tail)
$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 extends done
$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
; The following lets us remember what the original stolen device is
; so we can prevent double bashes...
$DEF	UCB$SWCONTFIL	.BLKB	80
$DEF	UCB$L_ASTEN	.BLKL	1		;ast enable mask store
;
; 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.
; In second version we add flags. This can be used to let intercepts know
; when they are prepared to handle fastio/finipl8 postprocessing. If they
; are they must set the FI8OK bit here. If they, or anyone below them,
; is not so prepared they must leave it clear. Processing must abide
; by this. (The bare device,when intercepted, will work ok anyhow if
; we have a legal driver structure. If someone intercepts nonstandardly
; (not using this code) they must ensure this flag is correct.
$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$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. If the DDT gets added to, this magic
; number automatically changes and the interception code won't
; presume it can chain correctly.
MAGIC=^XF007F000 + DDT$K_LENGTH + <256*<UCB$S_PPDEND-UCB$S_PPDBGN-16>>
P.MAGIC=^XF007F000 + DDT$K_LENGTH + <256*<UCB$S_PPDEND-UCB$S_PPDBGN-16>>
$DEF	UCB$L_SW_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
;
; This layout is much easier to deal with than the VAX or STEP1 one...
; fdt$k_length should be 68 longwords for the 64bit case, 66 longs for
; vms 6.1. The code even for 6.1 copied an extra quadword to be certain
; it got everything, so it actually requires no mods for 64 bit FDTs.
; It is essential that the complete FDT and DDT get copied. which
; symbolic use of length symbols will now assure.
$DEF	UCB$L_MYFDT	.BLKL	<<FDT$K_LENGTH/4>+4>;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
; The following lets us steal start-io and add error retries
; These entries therefore permit us to arrange that IRPs are queued
; properly to our input queue when mount verify is entered and/or
; exited. We intercept only the primary's entry points in this
; code, since VMS knows only about the primary as the mounted disk,
; but we record other drivers' entry addresses also so that we can
; conveniently call them when we need to.
$DEF    UCB$L_OMEDIA    .BLKL   1       ;storage of orig. irp$l_media
$DEF    UCB$L_PPID      .BLKL   1       ;store for IRP$L_PID contents
$DEF    UCB$L_RETRIES   .BLKL   1       ;counter for i/o packacks
$DEF    UCB$L_HSTARTIO  .BLKL   1       ;host driver start-io loc.
$DEF	UCB$L_INDRCT	.BLKL	1	;indirect flag
$DEF	UCB$L_MBXUCB	.BLKL	1	;mailbox to talk to server with
$DEF	UCB$L_DAEMON	.BLKL	1	;PID of server (to test that it's there)
	; If zero this flag means we are using the direct path
	; if 1, we are using indirect path (via althost) for I/O
$DEF	UCB$L_SAWSUCC	.BLKL	1	; 1 if we saw a success on current path
; Thus if we need to switch but have no successful I/O yet on the current
; path we're switching from (we initialize to 1 first) we must have switched
; to alt path, it still failed, and we want to switch back. In that case
; it looks like things are broken all over. Might as well switch, since
; one never knows which path will come up, but we can detect double
; failure at least.
;
; We keep the host device (the initial path one, whose startio we
; steal) UCB here, and also the UCB of the "althost", i.e., the
; device in the second path to the storage. This althost device is never
; mounted, and is to be made to look offline to the rest of VMS so that
; it never gets I/O. (This may be done by making the FDT functions
; all point to the illegal function entry, plus some tactic to keep
; MSCP's hands off...)
; By having both UCBs handy we can redirect I/O quickly when we need to.
;
$DEF    UCB$L_HSTUCB    .BLKL   1       ;host ucb (quick ref) (main path)
$DEF	UCB$L_ALTHOST	.BLKL	1	;UCB of second-path driver
			.BLKL	8	; Space for possibly more entries
$DEF	UCB$L_UCB0	.BLKL	1	;UCB of unit 0 (to find daemon)
$DEF	UCB$L_SANITY	.BLKL	1	;sanity field
; To tell when I/O is still outstanding WE must keep track of it.
; This must be done whenever we alter an IRP, and counted down again when we
; un-alter an IRP.
$DEF	UCB$L_OUTSTND	.BLKL	1	;Outstanding i/o on current interface
;
; Additional entries we save & steal.
; These are used for handling our OWN IRP queue.
;
; Primary host entry pointers, then
; secondary hosts' pointers
;
; mount verify start or end
$DEF	UCB$L_OLDMV	.BLKL	1	;original mountver entry for dvr
$DEF	UCB$L_OLDMV2	.BLKL	1	;mountver entry of althost
			.BLKL	8	;allow some slop for more later
; alt start-io
$DEF	UCB$L_OALTST	.BLKL	1	;altstart entry of primary host
$DEF	UCB$L_OALTST2	.BLKL	1	;altstart of althost
			.BLKL	8
; The irp queueing cells save the pending_io entries
$DEF	UCB$L_QIRP	.BLKL	1	;IRP queueing entry of host
$DEF	UCB$L_QIRP2	.BLKL	1	;IRP queueing entry of althost
			.BLKL	8
; cancel-io
$DEF	UCB$L_CANCL	.BLKL	1	;Cancel entry host
$DEF	UCB$L_CANCL2	.BLKL	1	;Cancel entry althost
			.BLKL	8
; aux routine (for quorum lost processing)
; (dudriver uses this.) Gets control to one side or the other.
$DEF	UCB$L_AUX	.BLKL	1	; host aux routine
$DEF	UCB$L_AUX2	.BLKL	1	; alt host aux routine
			.BLKL	8

; Make the size of the enapth block the same as used for additional
; entries.
$def	UCB$L_ENAPTH	.BLKL	10	; flags whether a path is enabled.

$def	ucb$l_fgdun	.blkl	1	; Flag for startio area of irp bashed
$def	ucb$l_fgdun2	.blkl	1	; similar for pstealstart
;
$def	UCB$L_cancln	.blkl	1	; temp save for cancel count
$def	ucb$l_mypost	.blkl	1	; irp$l_pid contents if we ashed

$def	UCB$L_PTHERRS	.BLKL	1	; Count err returns in a row
; The hack used by MSCP to let drivers tell when an IRP comes from it needs
; (alas) to be duplicated here (so we'll set the fake pcb+pcb$l_pid to the
; intercept address we use.)
$def	UCB$L_FAKEPCB	.BLKB	PCB$K_LENGTH	;fake PCB for this device
	.if df,d$$bug
$def	ucb$l_dbgdta	.blkl	1
	.endc
$DEF	UCB$K_SW_LEN	.BLKL	1	;LENGTH OF UCB
;UCB$K_SW_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
SW_UNITS=500
SW$DPT::
 .IIF NDF,DPT$M_XPAMOD,DPT$M_XPAMOD=0
	DPTAB	-			;DPT CREATION MACRO
		END=SW_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_SW_LEN,-	;LENGTH OF UCB
		MAXUNITS=SW_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=SWDRIVER,-		;DRIVER NAME
		DPT=DRIVER$DPT
	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
; make OUR fork IPL not have bit 5 set, to tell fork dispatcher NOT to get
; a spinlock for OUR fork (so as not to interfere with any locks by having
; the fork dispatch return clear a lock out from under a driver!)
	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. The actual characteristics don't matter much,
; just so the device is not picked up by anything as "interesting".
	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$_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,8	;DEVICE IPL. (=IPL$_SYNCH)
;	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,SW_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
	DPT_STORE DDB,DDB$L_DDT,D,SW$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 UCB,UCB$L_SANITY,L,<^A\SWIT\>
	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.
; 
;SW$DDT:
; Actually the presence of fastio in the intercept driver is of
; no importance either since it isn't really a disk...
	.IF	DF,IRP$Q_QIO_P1
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=SW,-		;NAME OF DEVICE
		START=SW_STARTIO,-	;START I/O ROUTINE
		FUNCTB=SW_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=SW_CTRL_INIT,-
		UNITINIT=SW_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
		FAST_FDT=ACP_STD$FASTIO_BLOCK	; Fast-IO FAST_FDT routine
	.IFF
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=SW,-		;NAME OF DEVICE
		START=SW_STARTIO,-	;START I/O ROUTINE
		FUNCTB=SW_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=SW_CTRL_INIT,-
		UNITINIT=SW_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
	.ENDC
; 
; FUNCTION DECISION TABLE
; 
; 	THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; 	CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; 	PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
; 
; NOTE: Be sure the FDT table is 8 byte aligned!!!! The addins below are
; 4 longwords which will not screw up quad alignment...
	.align quad
	.if	df,d$$bug
doxdt:	.long	0
inmve:	.long	0
ntruse:	.long	0
ntrmsk:	.long	0
;save callers to getswucb
gswu1:	.long	0,0
gswu2:	.long	0,0
gswu3:	.long	0,0
gswu4:	.long	0,0
	.endc
UCB0ADR:	.LONG	0,0
	.ASCII	/DFLG/	;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
;	.ALIGN QUAD
SW_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
	.IF	DF,IRP$Q_QIO_P1
; Note that as an intercept driver we copy the target FDT and actually don't 
; need this, but do it for beauty.
	FDT_64	<-				; Functions supporting 64-bit addresses
		AVAILABLE,-			; Available (rewind/nowait clear valid)
		NOP,-				; No operation
		PACKACK,-			; Pack acknowledge
		READLBLK,-			; Read logical block forward
		READPBLK,-			; Read physical block forward
		READVBLK,-			; Read virtual block
		SENSECHAR,-			; Sense characteristics
		SENSEMODE,-			; Sense mode
		SETCHAR,-			; Set characterisitics 
		SETMODE,-			; Set mode
		UNLOAD,-			; Unload volume
		WRITECHECK,-			; Write check
		WRITELBLK,-			; Write LOGICAL Block
		WRITEPBLK,-			; Write Physical Block
		WRITEVBLK>			; Write VIRTUAL Block
	.ENDC
MYFDTSTART:
; IO$_FORMAT + modifiers (e.g. io$_format+128) as function code
; allows one to associate a SW unit and some other device; see
; the SW_format code comments for description of buffer to be passed.
	FDT_ACT SW_FORMAT,-		;point to host disk
		<FORMAT>
	FDT_ACT SW_RETCTR,-		;point to host disk
		<RETCENTER>		;Use this function to go to main dsk
					;or to alt disk depending on arg
;
; 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!)
	.if	eq,1	;never true
	fdt_act MFYFilt,-
		<MODIFY>		;modify filter (e.g. extend)
	.endc
MYFDTEND=.
SW_UCBTBL::
	.REPT	SW_UNITS
	.long	0
	.ENDR
	.LONG	0,0,0,0,0,0,0,0,0,0
SW_UCB::
	.REPT	SW_UNITS
	.long	0
	.ENDR
	.LONG	0,0,0,0,0,0,0,0,0,0
; OFFSET ADDRESS TABLE
V_UNM=0
; Note: code elsewhere assumes that the xxvc macro generates 8 bytes.
; If .address generates more than 4, it breaks as coded here!!!
        .MACRO XXVC LBLCT
        .ADDRESS        SW_FXS'LBLCT
        .GLOBL  SW_FXS'LBLCT
        .LONG   0
        .ENDM
SW_VOADT::
        .REPT   <SW_UNITS+4>
        XXVC    \V_UNM
V_UNM = <V_UNM+1>
        .ENDR
; second set of addresses that can be used for mscp stuff.
        .MACRO MXXVC LBLCT
        .ADDRESS        MSW_FXS'LBLCT
        .GLOBL  MSW_FXS'LBLCT
        .LONG   0
        .ENDM
V_UNM=0
MSW_VOADT::
        .REPT   <SW_UNITS+4>
        MXXVC    \V_UNM
V_UNM = <V_UNM+1>
        .ENDR

	DRIVER_CODE
;
; GETSWUCB - Find SW: 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.
;
; The trick that we have the victim DDT in our intercept UCB and thus can
; find the intercept UCB relatively fast is the best feature here.
; This gives simple lookup of victim driver from intercept code.
; If we can be sure that the intercept situation is static, we can
; avoid a couple PAL calls here that do synch. stuff, but for this
; example, leave 'em in.
;
GETSWUCB: .JSB_ENTRY OUTPUT=<R0>
;	clrl	r0	;no UCB initially found
; save the last few calls' addresses
	.if	df,d$$bug
	movl	gswu3,gswu4
	movl	gswu2,gswu3
	movl	gswu1,gswu2
	movl	gswu3+4,gswu4+4
	movl	gswu2+4,gswu3+4
	movl	gswu1+4,gswu2+4
	movl	(sp),gswu1	;save return address
	movl	r3,gswu1+4	;save call irp too
	.endc
	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)+,CONDITION=RESTORE,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.
;
; If we get here and the DDT points now to someone ELSE'S UCB instead
; of ours, we must keep looking to find OUR UCB. This is done by
; searching the chain we establish so this intercept driver can
; find its own UCB in a finite search. If of course it is the only
; intercept, it gets it right away.
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 SWDRiver versions to call
; getSWucb, 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.
; Thus, any "continue" returns of our code must wind up calling "pors"
; instead of doing a RET. This will pass the call control along.
        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	GETSWUCB		;Find SW 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...or the next intercept's down anyhow.
	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

;++
;
; SW_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
;--
SW_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$
	MOVL	(R0)+,R6		;Size of virtual disk (ignored)
70$:
	MOVAB	(R0),-			;name of "real" disk
		UCB$L_SW_HOST_DESCR+4(R5)
        SUBL3   #8,IRP$L_QIO_P2(R3),-
                UCB$L_SW_HOST_DESCR(R5)
	BLEQ	100$			;bad length
	MOVAB	UCB$L_sw_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.

; This segment really needs to invalidate TBs when clobbering the
; DDT and possibly should have mutex too, but normal use is at
; system startup where this won't matter for now.

	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)
; This allows us to create fake channels to nla0: and bash them to channels
; to the host devices associated if we need to...stuff like that.
	MOVAB	SW_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
; 380=ss$_ivstsflg
        MOVL    #380,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
; Now also check the FI8OK flag and
; set ours if the one previous has been set.
; R10 is the previous DDT pointer. If this is an intercept we can find HIS
; flag by offset from outs.
	BBC	#UCB$V_FI8OK,<UCB$L_INTCDDT-UCB$L_ICPFGS>(R10),102$
; WE can handle IPL 8 fin and below can so set our flag to ok
	BISL	#UCB$M_FI8OK,ucb$l_icpfgs(r5)	;say WE CAN handle finipl8
	BRB	101$
102$:
; Somewhere below cannot handle IPL8 fin so we can't either.
; Note if we can't handle it we always clear this bit.
	BICL	#UCB$M_FI8OK,ucb$l_icpfgs(r5)	;say WE CAN'T handle finipl8
	BRB	101$
1$:
; First bash of a device, never before bashed. IPL8 finish has to be ok.
; Note we clear this bit instead of setting it if we cannot handle ipl 8
; finish-up.
	BISL	#UCB$M_FI8OK,ucb$l_icpfgs(r5)	;say WE CAN handle finipl8
101$:	MOVL	#1,UCB$L_MUNGD(R5)	;say we munged SW
        MOVL    R10,UCB$L_PREVDDT(R5)   ;set previous DDT address up
        CLRL    UCB$L_INTCDDT(R5)       ;clear intercepting DDT initially
; Now check the DEVICE UCB immediately below us too for some special
; pattern of weird bits in devchar2. This pattern must be chosen to be
; stuff that disks won't be affected by but that can be set by intercept
; drivers to flag that they can't handle ipl 8 stuff. If we are intercepting
; something that's a real device or an interceptor that's ok, no
; need to worry. Thus devices like vddriver, that aren't really
; intercept drivers but are pseudo devices, can tell the chain what
; is up.
; For this we select the pattern of all of:
; DET (device is detached terminal), SEX (serious exception handling),
; and HOC (host cache supp.)(dev_m_xxx for all). If all 3 are present we will
; not use ipl 8 stuff. If not all 3 are present they will be presumed to
; have no special meaning for us.
; This is chosen to because existing real devices won't have this pattern
; (strictly speaking it is senseless) and the bits shouldn't cause any
; trouble by being set. The devchar fields are too full to just grab
; another bit for this at present, though if a device driver stream
; facility should be added, this kind of thing ought to be "in there".
	movl	ucb$l_devchar2(r11),r8	; R8 is about to be munged
	bicl	#^c<DEV$M_DET!DEV$M_SEX!DEV$M_HOC>,R8
					; Leave only the bits we care about
	cmpl	#^c<DEV$M_DET!DEV$M_SEX!DEV$M_HOC>,R8
					; Are all three set?
	bneq	3$			; If not, no special action
; Device can't handle ipl 8 posting so flag it thus.
	BICL	#UCB$M_FI8OK,ucb$l_icpfgs(r5)	;say WE CAN'T handle finipl8
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,sw_FUNCTABLE+FDT_PREV    ;save old FDT ucb address
	MOVL	DDT$L_FDT(R10),UCB$L_OLDFDT(R5)
        MOVL    UCB$L_UNIQID(R5),sw_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
;actually with 64 bits the FDT length is 64 longs for function addresses,
; 2 longs for buffered, 2 longs for 64 bit mask
; Add a quadword slop to ensure we're long enough.
;	movl	#<70*4>,r0		;byte count of a step 2 FDT + slop
	MOVL	#<FDT$K_LENGTH+8>,R0	;byte count of a step 2 FDT + slop
	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 SWDRiver that we service locally. Thus
; all entry cells for the rest will point in the SW FDT to
; exe$illiofunc.
	MOVAB	G^EXE$ILLIOFUNC,R8	;get the magic address
	MOVAB	SW_FUNCTABLE,R10	;R10 becomes SW FDT tbl
	ADDL2	#8,R10			;point at functions
	ADDL2	#8,R9			;his new FDT...
	MOVL	#64,R11			;64 functions
; The code below will let the victim driver's IO$_format FDT entry not be
; messed with...
        .IF     NDF,B$FMT$
        PUSHL   R7
        MOVAB   SW_FORMAT,R7            ; LET VICTIM'S FORMAT FDT BY
        .ENDC
75$:	CMPL	(R10),R8		;this function hadled in SW?
	BEQL	76$			;if eql no, skip
	MOVL	(R10),(R9)		;if we do it point his fdt at our fcn
        .IF     NDF,B$FMT$
        CMPL    (R10),R7                ;this our io$_format
        BEQL    76$                     ;if so leave victim's alone
        .ENDC
; (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
        .IF     NDF,B$FMT$
        POPL    R7                      ;get back victim fdt
        .ENDC
; SWDRiver 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
;
; Set up victim's startio toour steal-startio after saving the address here
        MOVL    DDT$L_START(R8),UCB$L_HSTARTIO(R5)      ;save host start-io
        MOVL    R11,UCB$L_HSTUCB(R5)    ;save backpointer too
        MOVAB   STEALSTART,DDT$L_START(R8)      ;point at our startio
;
; Set up addresses of other internal routines, corresponding to
; the externally called entries that dkdriver or dudriver may
; use.
; save main host's entry addresses
	movl	ddt$ps_mntver_2(r8),ucb$l_oldmv(r5)
	movl	ddt$ps_altstart_2(r8),ucb$l_oaltst(r5)
	movl	ddt$ps_pending_io(r8),ucb$l_qirp(r5)
	movl	ddt$l_aux_routine(r8),ucb$l_aux(r5)
	movl	ddt$ps_cancel_2(r8),ucb$l_cancl(r5)
; get aux host's entries too, if they're here. swctl image fills that in.
	pushl	r8
	movl	ucb$l_althost(r5),r8	;get alt ucb
	bgeq	12$			;if none, skip
	movl	ucb$l_ddt(r8),r8	;point at alternate ddt
; store ddt entries from aux device also (alt host)
	movl	ddt$ps_mntver_2(r8),ucb$l_oldmv2(r5)
	movl	ddt$ps_altstart_2(r8),ucb$l_oaltst2(r5)
; No need for this count on secondary device. One count at primary is enough.
; However, ensure that dispatch goes to correct side.
	movl	ddt$ps_pending_io(r8),ucb$l_qirp2(r5)
	movl	ddt$l_aux_routine(r8),ucb$l_aux2(r5)
	movl	ddt$ps_cancel_2(r8),ucb$l_cancl2(r5)
12$:
	popl	r8
; now insert the ones here
	movab	steal_mount_ver,ddt$ps_mntver_2(r8)
	movab	steal_altstart,ddt$ps_altstart_2(r8)
	movab	steal_pending_io,ddt$ps_pending_io(r8)
	movab	steal_aux_routine,ddt$l_aux_routine(r8)
	movab	steal_cancel,ddt$ps_cancel_2(r8)
;
; Finally clobber the victim device's DDT pointer to point to our new
; one. Note this is only one instruction on Vax. On alpha, we'll use
; the "preserve atomicity" brackets to ensure the memory cell is
; updated atomically. (This should be a nobrainer.) Should still
; invalidate TB here too...
;
; Put an IMB so before we do this, the tables are in memory. That way
; when we replace the one pointer below, the tables to be pointed
; to are known valid.
	evax_imb
	.if df,evax
	.preserve Atomicity
	.endc
        MOVAB   UCB$A_VICDDT(R5),UCB$L_DDT(R11)
	.if df,evax
	.nopreserve atomicity
	.endc
; Now another IMB so nothing more happens till this replacement is also
; in real memory, not just cache.
	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)+,-
         CONDITION=RESTORE,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>
	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
; SW_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   SW_FUNCTABLE,R2         ;address of our FDT table
        CLRL    R3
	MOVAB	<0-UCB$A_VICDDT>(R10),R4 ;initially point at our UCB
; Also set the SW 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 SW munged flag
4$:
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,CONDITION=RESTORE,PRESERVE=YES
        POPR    #^M<R0,R1,R2,R3,R4,R5,R10,R11>
                                        ;copy our prior DDT ptr to next one
	RSB
        .MACRO  $ARG_DEF argument_list
        argument_offset = 4
        .IRP    argument, <argument_list>
        argument = argument_offset
        argument_offset = argument_offset + 4
        .ENDR
        .ENDM   $ARG_DEF
;
; SW_RETCTR : "forcibly" switch driver to use either main or alternate
; disk. Assumes one parameter, a buffer containing 1 for switch to main
; disk, 2 to switch to alternate disk.
;
SW_RETCTR: $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>
10$:
	MOVZWL	#SS$_BADPARAM,R0	;illegal parameter
	CLRL	R1
	CALL_ABORTIO
	RET
20$:
        MOVL    IRP$L_QIO_P1(R3),R0     ;BUFF ADDRESS
        MOVL    IRP$L_QIO_P2(R3),R1     ;BUFF LENGTH
	CALL_WRITECHK
	PUSHR	#^M<R0,R1>
	.PRESERVE ATOMICITY
	TSTL	UCB$L_OUTSTND(R5)	;Bump outstanding I/O count
	BEQL	220$
	.NOPRESERVE ATOMICITY
; Do not switch paths if any outstanding I/O exists on this path that has
; not returned in some fashion. Only switch if things are idle or timed out
; in case a failed controller should quickly come back.
	POPR	#^M<R0,R1>
	MOVL	#SS$_ABORT,R0
	BRW	52$
220$:
	MOVL	IRP$L_QIO_P1(R3),R0
	MOVL	(R0)+,R1		;get option code
	BLEQ	100$			;negative illegal
	CMPL	R1,#2			;3 and up illegal too
	BGTR	100$
; 1 = go to main
; 2 = go to alt device
	DEVICELOCK SAVIPL=-(SP),PRESERVE=YES
	CMPL	R1,#1			;use main?
	BNEQ	40$			; if neq no, use alt
; use main device.
;
; Also ensure against a race condition at switchover by clearing the retry
; counter used at packack, so that if some outstanding I/O causes a packack
; that logic doesn't switch back to the bad side.
	CLRL	UCB$L_RETRIES(R5)
; Note that the switcher (our caller) should send packack directly to the
; "new" disk PRIOR to calling here.
; 
; Is direct path enabled?
	tstl	ucb$l_enapth(r5)	; direct path enabled?
	beql	50$			; if not don't allow use
	CLRL	UCB$L_INDRCT(R5)	; set to use direct path
	BRB	50$
40$:
;
; Also ensure against a race condition at switchover by clearing the retry
; counter used at packack, so that if some outstanding I/O causes a packack
; that logic doesn't switch back to the bad side.
	CLRL	UCB$L_RETRIES(R5)
; Note that the switcher (our caller) should send packack directly to the
; "new" disk PRIOR to calling here.
	tstl	ucb$l_enapth+4(r5)	; is indirect path enabled?
	beql	50$			; if not leave it alone
	MOVL	#1,UCB$L_INDRCT(R5)	; set to use indirect path
50$:
	DEVICEUNLOCK NEWIPL=(SP)+,PRESERVE=YES
	POPR	#^M<R0,R1>
	MOVZWL	#SS$_NORMAL,R0			;SUCCESS
52$:	CALL_FINISHIOC DO_RET=YES
;	ret
;
; Steal pending io
; Input: arg1= queue hdr, arg2 and r3=irp
; called with calls #2... though R5 is UCB also at call
; R1=queue hdr after fetch and r3=ucb.
; Returns status in R0
; 
; Here we must redirect the call to the right underlying driver.
; We must also be sure the return code is as left by the called
; routine.
;
steal_pending_io: $DRIVER_PENDING_IO_ENTRY preserve=<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,R11>,FETCH=YES,output=<R0>
;getswucb assumes PRIMARY host ucb in r5
; r1 is listhead; r3 is irp
; If irp$l_ucb is unsuitable, use call R5
; System will call here for main device (i/o to main path) but
; subordinate paths are not trapped.
; We should not get to this point on secondary paths
	TSTL	IRP$L_UCB(R3)
	BGEQ	30$
	MOVL	IRP$L_UCB(R3),R5
30$:
	movl	r5,IRP$L_UCB(R3)	;fill in what we hope is right
; At this point R5 better be the UCB...
	JSB	GETSWUCB	;find intercept ucb
	TSTL	R0		;did we get it?
	blss	991$
; can't be a UCB...wrong address. Bug out.
	brw	1$
991$:
	.iif df,m$$trp,movl #1,ucb$l_mtrp(r0)
	.iif df,m$$Trp,movl r3,ucb$l_mtrp+32(r0)
; TEST the entry. r1 is at 4(ap), queue hdr
; r3 at 8(ap) is IRP
; COUNT STUFF FIRST. THEN DISPATCH.
; &&&&&&&&&&& NEEDS COUNTING STUFF...
; Note that we only count for primary side.
; This is because everything goes thru there.
	PUSHL	R11			;R0 is return status, can't use it
	MOVL	R0,R11			; Point at SW UCB

; Push the original args as fetched from the caller.
        MOVL    PENDING_IO_ARG$_LIST_HEAD(AP), R1
        MOVL    PENDING_IO_ARG$_IRP(AP), R3
 	PUSHL	R3			; IRP
	PUSHL	R1			; LIST HEAD
;;	PUSHL	PENDING_IO_ARG$_IRP(AP)		; IRP
;;	PUSHL	PENDING_IO_ARG$_LIST_HEAD(AP)	; Queue hdr

	TSTL	UCB$L_INDRCT(R11)
	BNEQ	2$
; Note these are the original entries from before we grabbed them,so
; we won't recurse into ourselves.
;
; (It ought to be impossible to have null entries in these cells; this is
; extra code to just be safe during tests.)
	TSTL	UCB$L_QIRP(R11)		;call underlying "add to queue"
	BGEQ	10$
	CALLS	#2,@UCB$L_QIRP(R11)
	BRB	14$
; If the driver has no call, call the standard thing
; Should never be able to happen; ddtab ALWAYS would have an address!
10$:	CALLS   #2,G^EXE_STD$INSERT_IRP
	BRB	14$
2$:
	TSTL	UCB$L_QIRP2(R11)	; Don't call a null entry
	BGEQ	11$
	CALLS	#2,@UCB$L_QIRP2(R11)	; Call this OR the "..qirp" entry above
					; but not both.
	BRB	14$
; If the driver has no call, call the standard thing
11$:	CALLS	#2,G^EXE_STD$INSERT_IRP
14$:	POPL	R11
	BRB	7$
1$:
; If we couldn't find intercept, call the usual routine for this site and pray.
; (Should never happen.)
	.iif df,x$$$dt,jsb g^ini$brk ;**************** debug ***********
	PUSHL	R3
	PUSHL	R1
	CALLS	#2,G^EXE_STD$INSERT_IRP
7$:
; Note that all regs except R0 will get restored at exit for Alpha.
; On Vax we'd need to arrange this with pushr/popr here.
	RET
;
; aux routine (for revalidate)
; Tells mscp server to find another path
; Input DDT in R0
; Implement by just calling the underlying driver.
;
; With the qio server, this is a good place to tell the server to make
; a new connection here.
steal_aux_routine: .jsb_entry INPUT=<R3>,OUTPUT=<R3>
;R3 = CDDB ADDR ON INPUT
	.if	df,d$$bug
	.iif df,x$$$dt, jsb g^ini$brk
	.endc
        ADDL3   #<CDDB$L_UCBCHAIN -             ; Get "previous" UCB address.
                 -UCB$L_CDDB_LINK>, R3, R5
; R5 IS NOW UCB
;	PUSHL	R1
;	PUSHL	R11
	MOVL	R0,R1		;save call R0 in case routine uses it
	JSB	GETSWUCB	;find intercept ucb
	TSTL	R0		;did we get it?
	BGEQ	1$		; IF EQL NO
	MOVL	R0,R11
	MOVL	R1,R0		; Let call R0 look like it would have
	.iif df,m$$trp,movl #3,ucb$l_mtrp(r11)
	TSTL	UCB$L_INDRCT(R11)
	BNEQ	2$
	TSTL	UCB$L_AUX(R11)
	BGEQ	1$
	JSB	@UCB$L_AUX(R11)
	BRB	1$
2$:
	TSTL	UCB$L_AUX2(R11)
	BGEQ	1$
	JSB	@UCB$L_AUX2(R11)
1$:
;	POPL	R11
;	POPL	R1
	RSB
; cancel (channel, irp, pcb, ucb, reason)
; Just dispatch to the correct underlying driver using the step 2
; interface.
; Note we must check if any IRPs disappear so we can adjust outstanding io
; count...
;
; We need to handle cases where the cancel sys svc was run, and find
; and remove IRPs on our queues here and account for their removal
; (check where we would have copied irp$l_pid and so on) and
; post these if the reason for cancel is  CAN$C_CANCEL or CAN$C_DASSGN
; and adjust the count of outstanding I/O accordingly.
; our call is

;        PUSHL   R8                      ;Reason (Stack args for CALLS)
;        PUSHL   R5                      ;UCB
;        PUSHL   R4                      ;PCB
;        PUSHL   R3                      ;IRP
;        PUSHL   R2                      ;Channel
;        CALLS   #5, @DDT$PS_CANCEL_2(R0) ;CALL CANCEL I/O ROUTINE

;
; In principle,
; we need to trap he driver DDT$PS_CANCEL_SELECTIVE_2 entry too, whose call is
;        .SET_REGISTERS  READ=<R0,R4,R5,R6,R7,R8>,WRITTEN=<R0,R1,R2,R3>
;        PUSHL   R8                      ;iosb count (Push args on stack for CAL$
;        PUSHL   R7                      ;iosb vector
;        PUSHL   R6                      ;channel
;        PUSHL   R5                      ;UCB
;        PUSHL   R4                      ;PCB
;        CALLS   #5, @DDT$PS_CANCEL_SELECTIVE_2(R2)
;
; (This routine is usually not there).
; (In fact I can't find anything in [scsi] or [driver] that uses it. Looks
; like mostly a ttdriver thing. I won't bother with it here, just send
; to main path always to be junked there.
; Note no drivers currently ever use cancel_selective, but we need to
; handle it.
;
; Cancel sys service will not recognize our IRPs at all, since it
; will find them either with a PID that they can't recognize or
; they won't be on a known queue. We however need to find and
; remove any such by looking where the PID is.
;
; The cancel entry is called with arguments as follows:
;       CANCEL  (channel, irp, pcb, ucb, reason)
; reason = CAN$C_CANCEL or CAN$C_DASSGN
;
; Fork lock is held.
;
steal_cancel: $DRIVER_CANCEL_ENTRY PRESERVE = <R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>,FETCH=NO
	.if	df,d$$bug
	tstl	ntruse
	beql	1700$
	bbcc	#2,ntrmsk,1700$
;	.iif df,x$$$dt,jsb g^ini$brk
1700$:
	.endc
	PUSHL	R5
	MOVL	canarg$_ucb(AP),R5	;GET UCB of the "prime" path device
	movl	canarg$_chan(ap),r7
	movl	canarg$_irp(ap),r3
	movl	canarg$_reason(ap),r8	;fetch the arguments into regs again
	movl	canarg$_pcb(ap),r4
	BGEQ	1$
	jsb	getswucb	;find intercept ucb
	tstl	r0		;did we get it?
	beql	1$
	movl	r0,r9		; Put intercept UCB in R9 too

;
; This is called when channels are deassigned too. Omit all the
; shuffling and just pass to the desired underlying driver
; routine. Therefore snip out all the cruft in the ".if eq,1" block
; below.
	.if	df,d$$bug
	tstl	doxdt
	beql	702$
;	.iif df,x$$$dt, jsb g^ini$brk
702$:
	.endc
;
; In fact for best general results we should arrange that sys$cancel
; be taught where to find the original PID here, since IRP$L_PID is
; in fact reused as a postprocessing hook. Having an IRP cell which
; always contains the PID would be best...and in fact there is a
; thread_pid cell that almost fills the bill. 
;
; >>>> FIX THIS <<<<
; FOR THE CURRENT VERSION of this code, however, we will not try to
; duplicate sys$cancel processing. Since IRP$L_PID is basically 
; always pointing to a system address, sys$cancel won't do much but
; should be fixed up to do so. When a stack is added to the IRP,
; add an original_pid cell too!!! And arrange for sysqioreq to fill
; it in!!!

; Now if the cancel comes from MSCP, look over the IRPs to see if they
; have pcb$l_pid matching irp$l_pid and both negative. That will pretty
; well flag the IRP is from MSCP (this hack is used) so point the IRP
; at OUR fake PCB and fill it in.
	movl	canarg$_pcb(ap),r4
	.if	ndf,can_any_srvr ;(we may want to do this for other servers too)
	cmpl	canarg$_reason(ap),#CAN$C_MSCPSERVER	; This cancel for MSCP
	BNEQ	55$				; if not skip this stuff
	.endc
	movab	ucb$l_ioqfl(r5),r11	;get addr of start of queue
; We're at fork here so just loop thru all the IRPs if any.
	movl	R11,R10			; working reg is R10
50$:	movl	(r10),r10		; Get the next IRP if any
	cmpl	R10,R11			; Back at start?
	beql	55$			; If so exit the loop
; Original irp$l_pid is at irp$q_qio_p2+4
	tstl	irp$q_qio_p2+4(r10)	; see if negative
	.if	ndf,can_any_srvr
	bgeq	50$			; if not, not a server's
	.iff
	bneq	50$			; Zero means skip it.
	.endc
	cmpl	irp$q_qio_p2+4(r10),pcb$l_pid(r4) ; match?
	bneq	50$
; ok, this IRP looks like it came from MSCP (or maybe something else)
; that faked the pcb$l_pid cell.
; So do it ourselves.
	MOVZWL  UCB$W_UNIT(R9),R7	; Get the unit number
	ASHL	#3,R7,R7		; Turn into an offset
; Use our second set of entry vectors for postprocessing
; They'll work exactly like the other ones, but will not be the
; same so that a compare of pcb$l_pid with them won't come out equal
; unless we reset them here. That way something from an MSCP server
; will still appear to match even though we intercepted it because
; we will have played the same dirty trick MSCP does.
	MOVAB	MSW_VOADT,R8		; Get our alternate-finish tbl
	addl2	r8,r7			; Form address of pointer
	movl	(r7),irp$l_pid(r10)	; Stash that in the IRP
	movab	ucb$l_fakepcb(r9),r8	; get our fake PCB
	movl	(r7),pcb$l_pid(r8)	; And make pid look ok
	movl	r9,r4			; then reset R4 so we use it
; No need to loop once we have at least one mscp irp...
;	brw	50$
55$:
;
	PUSHL	canarg$_reason(AP)
	PUSHL	canarg$_ucb(AP)
; A driver cancel will want to find its own UCB, not some other one's.
; Note however that we must ALSO ensure that any I/O that is on the master
; queue is made a candidate for cancel also.
;
	PUSHL	R4			; Use our fake pcb or orig real one
	PUSHL	canarg$_irp(AP)
	PUSHL	canarg$_chan(AP)
	TSTL	UCB$L_INDRCT(R9)
	BNEQ	2$
; path 1 being used
	movl	ucb$l_hstucb(r9),r5	;point R5 at the UCB of driver we call
	TSTL	UCB$L_CANCL(R9)		; Be sure the entry is not null
	BGEQ	10$			; If driver entry isn't there don't call
	movl	ucb$l_hstucb(r9),r5	;point R5 at the UCB of driver we call
	CALLS	#5,@UCB$L_CANCL(R9)
	BRB	16$
10$:	ADDL2	#20,SP		;unpush args
	BRB	16$
2$:
; path 2
	TSTL	UCB$L_CANCL2(R9)	; Be sure the driver entry exists
	BGEQ	20$
	movl	ucb$l_althost(r9),r5	;point R5 at the UCB of driver we call
	CALLS	#5,@UCB$L_CANCL2(R9)
	BRB	16$
20$:	ADDL2	#20,SP		;unpush args
16$:
;
; Note that dkdriver at least does not properly deal with IRPs from MSCP
; server so its driver cancel is almost useless. DUdriver does rather more
; but since irp$l_pid is clobbered here, its impact will be minimized.
; 
; At this point R0 should be driver result
1$:	TSTL	R0
	BNEQ	30$
	MOVL	#SS$_WASSET,R0	;caller ignores status
30$:
	POPL	R5
	RET
;
;
; Steal driver mount-verify entry.
; Args: R3, R5. Called by calls #2
; Environment is fork thread,but does not lock/unlock anything.
;
; When this entry is called, R3 is the current IRP (if entering mv)
; which must be put back on the driver's input queue.
;
; If r3=0, we have no IRP  but are at MV end and must restart
; things.
;
; If R3 is nonzero, we need to call the correct driver's underlying
; MV start routine (which may just stash the IRP on that driver's
; input queue) and then ourselves pull it back to the multiport
; input queue. If it is zero, we need to ensure that an entry off
; the multiport queue is processed thru to the driver's input
; queue and call the driver's MV entry to end its waiting.
;
; One complexity is that we need to be sure MV gets set on the device
; that is known to VMS (the primary path), but we will set this by hand
; for the case of the secondary path. Since we won't queue anything
; to actual start_io of the inactive path, whether any driver-internal
; busy bits are not set is immaterial. (In fact we do not want them set.)
; Therefore we only call one driver's MV start entry or finish entry,
; the one we are using. That will handle getting in-process IRPs dealt
; with.
;
; We will only put 1 IRP on the driver queue at end-MV since we switch
; paths only after a bit of work in MV may run.
; We need to clean up IRP mods though.
; Entered with a calls #2,@DDT$PS_MNTVER_2(R0)
; with r5=ucb, r3=irp
steal_mount_ver: $driver_mntver_entry fetch=yes
	.if	df,d$$bug
	tstl	ntruse
	beql	1700$
	bbcc	#3,ntrmsk,1700$
;	.iif df,x$$$dt,jsb g^ini$brk
1700$:
	.endc
; r3 and r5 are args.
	jsb	getswucb	;find intercept ucb
	tstl	r0		;did we get it?
	bneq	41$		;If we got the intercept, branch

; We should never get here;following few instructions are what maybe
; would happen if we somehow didn't have intercepts right.
; This is an exceptional situation but clear MV. Hopefully we NEVER get
; here though.
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	tstl	r3	; mv end?
	blss	741$	; if R3 is negative, doesn't look like it.
; Ending MV means clear MV status on master path (in case we are not ON
; master path).
	.if	df,x$$$dt
	bitl	#<UCB$M_MNTVERIP>,UCB$L_STS(R5)	;bit should be clear!!
	beql	708$
	jsb g^ini$brk
	bicl	#<UCB$M_MNTVERIP>,UCB$L_STS(R5) ;MV going on?
708$:
	.endc
741$:	pushl	r5		; We want to preserve call R5
	pushl	r3		; if we can't find intercept ucb, call std loc
	calls	#2,g^IOC_STD$MNTVER
	brw	1$	

41$:
	pushl	r5
	movl	r0,r11		;keep intercept UCB pointer in R11 for now
	.iif df,m$$trp,movl r5,ucb$l_mtrp+36(r11)
; Now if this is the end call, grab an IRP
	tstl	r3		;start MV?
	bneq	52$		;if this is start-mv then handle below.
; If here this is the end MV call, so pull our IRP in
;
; END - MV CALL
; Must restart I/O, so if there is any work on the common queue, load it.
	.iif df,m$$trp,movl r5,ucb$l_mtrp+44(r11)
;	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	pushl	r8
; Even if we would clear MV along a new path, the master path MUST be
; set so mntverip is clear on the master. Otherwise nothing will get sent
; to the device to get it going and all that happens is we'll stick more
; on a pending queue. We must also be sure that the secondary paths are not
; marked MV either, just for safety, since the system doesn't send IRPs
; there either. They should never BE so marked...we don't so mark them
; and they aren't mounted anyhow...but we do call the underlying mv start
; with them.
	movl	ucb$l_hstucb(r11),r8	; point at main path
; Clear mount verify status in main path now so IRPs can restart
	.if	df,x$$$dt
;Since the upper levels are supposed to know only about main path
; this bit oughta be clear too!!!
	bitl	#<UCB$M_MNTVERIP>,UCB$L_STS(R8)	;bit should be clear!!
	beql	710$
	jsb g^ini$brk
710$:
	.endc
	bbcc	#ucb$v_mntverpnd,ucb$l_sts(r8),843$	;clear MV bits
	bbcc	#ucb$v_mntverip,ucb$l_sts(r8),843$
843$:
	popl	r8

; Call the underlying stop-MV call.
	.if	df,x$$$dt
	bitl	#<UCB$M_MNTVERIP>,UCB$L_STS(R5)	;bit should be clear!!
	beql	709$
	jsb g^ini$brk
709$:
	.endc
	tstl	ucb$l_indrct(r11)	;this the normal path?
	bneq	58$		;if neq no, alt path
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	tstl	ucb$l_oldmv(r11)	;be sure an entry is there
	bgeq	62$		;and if not forget it.
	pushl	r5	
	pushl	r3
; Belt 'n' suspenders. Be sure the MV bits are clear!!!
	bicl	#<UCB$M_MNTVERIP!UCB$M_MNTVERPND>,UCB$L_STS(R5) ;Clear MV bits
	calls	#2,@ucb$l_oldmv(r11)	;else call the original mv entry
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	brw	62$
58$:
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	tstl    ucb$l_oldmv2(r11)
	bgeq	62$
; For multiple paths generalize to an addr array.
	movl	ucb$l_althost(r11),r5	;point at correct UCB
	pushl	r5
	pushl	r3
	bicl	#<UCB$M_MNTVERIP!UCB$M_MNTVERPND>,UCB$L_STS(R5) ;Clear MV bits
	calls	#2,@ucb$l_oldmv2(r11)	;call alt. path's mv routine
	.iif df,d$$bug, incl inmve

	.iif df,x$$$dt, jsb g^ini$brk	;************debug**********
	brw	62$		; now call host driver end-mv routine
;
; START-MV CALL
; What we pull off an alt queue must be reset here, put on main one.
52$:
	.iif df,d$$bug, incl doxdt
; Following test should NEVER branch but be safe.
	bbs	#IRP$V_MVIRP,IRP$L_STS(R3),11$	;skip if a mv irp
; Start MV call. We have an IRP already, but need to get it to our main
;
; First call host driver insert-IRP routine, then move the whole queue
; to our "real" multi-path one.
	.iif df,m$$trp,movl r5,ucb$l_mtrp+48(r11)
	tstl	ucb$l_oldmv(r11)	;is a mount ver entry ok?
	bgeq	11$		; if not skip it
	tstl	ucb$l_indrct(r11)	;is this using the indirect path?
	bneq	2$		;if neq yes
	pushl	r5	
	pushl	r3
	.if	df,d$$bug
	tstl	doxdt
	beql	701$
	.iif df,x$$$dt, jsb g^ini$brk
701$:
	.endc
	calls	#2,@ucb$l_oldmv(r11)	;else call the original mv entry
; The alternate path has a valid class queue etc. too
	brw	21$
; repair R5 here to point at second disk
2$:
; For multiple paths generalize to an addr array.
	tstl	ucb$l_oldmv2(r11)	;is a mount ver entry ok?
	bgeq	11$
	movl	ucb$l_althost(r11),r5	;point at correct UCB
	pushl	r5
	pushl	r3
	.if	df,d$$bug
	tstl	doxdt
	beql	702$
	.iif df,x$$$dt, jsb g^ini$brk
702$:
	.endc
	calls	#2,@ucb$l_oldmv2(r11)
21$:
;
; Now copy host input queue to main one. R5 is now the phys dvc ucb.
;
; remove from back of old irp queue, insert at front of mpath one
; This will result in preserving the order.
;
; NOTA BENE::::
;    We may be able to optimize this using pointers ("move the headers")
;    to get it all.
;
; We expect this queue will often be quite short, usually zero, by
; the time we get here, because of DKdriver's set_single operation
; when it gets the mv start.
;


	tstl	ucb$l_indrct(R11)	; if on main path no need for this
	beql	22$
	movl	ucb$l_hstucb(r11),r8	; point at main path
	movab	ucb$l_ioqfl(r5),r9	;get addr of start of queue
24$:	movl	ucb$l_ioqbl(r5),r10	;get last queue entry
	cmpl	r10,r9
	beql	22$			; exit if we got to queue head
	remque	(r10),r3		;get an entry
	bvs	22$			; if vs, nothing there
; Undo mods to this queue element
	pushl	r11			; Push intercept ucb addr
	pushl	r3			; undo mods
; Reprocess everything to undo the IRP mods and replace on the main
; input queue.
;	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	calls	#2,undomods		; restore IRP to "pristine" cond'n
;	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	insque	(r3),ucb$l_ioqfl(r8)    ;insert the IRP into main queue
	brb	24$
22$:
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
;Now an IRP is on the multipath input queue and we are done.
62$:
11$:
	popl	r5
1$:
	ret
;
;
; Alt-start entry. This must notice when a driver is passed an I/O
; directly (without synch on UCB busy) and pass to the appropriate
; underlying driver.
; Note that the IRP needs to be preprocessed first though so that we
; can maintain control.
; Owns forklock on call.
; Since this won't change paths, just send it on...
; (Note this isn't queued in the normal way, and should always have
; an IRP for us...)
;
STEAL_ALTSTART:
	$DRIVER_ALTSTART_ENTRY  PRESERVE = <R3,R4,R5,R11>
        MOVL    altarg$_irp(AP),R3                      ;F FF; Get current IRP
        MOVL    altarg$_ucb(AP),R5                      ;F FF; Get current UCB
; ok, we have an IRP to process. Gotta relocate the sucker & put it onto the
; host driver's input that we are using.
; First we need to "process" it, though.
	PUSHL	#0			; do NOT put the IRP on a queue.
	CALLS	#1,PSTEALSTART		;go process the IRP to input queue
	JSB	GETSWUCB	;Find intercept ucb
	TSTL	R0		;did we get it?
	BNEQ	21$
	MOVL	#SS$_BADPARAM,R0
	BRW	1$
21$:
	.iif df,m$$trp,movl #4,ucb$l_mtrp(r0)
; We now have the intercept loc.
	MOVL	R0,R11
	TSTL	UCB$L_OALTST(r11)	; Is there an entry to call?
	BGEQ	1$			; If not skip it
	PUSHL	R5
	PUSHL	R3
	TSTL	UCB$L_INDRCT(R11)
	BNEQ	2$
	TSTL	UCB$L_OALTST(R11)
	BGEQ	10$
	CALLS	#2,@UCB$L_OALTST(R11)
	BRB	1$
10$:	ADDL2	#8,SP	;pop 2 args
	BRB	7$
2$:
	TSTL	UCB$L_OALTST2(R11)
	BGEQ	11$
	movl	ucb$l_althost(r11),r5	;point r5 at the secondary ucb
	FORKLOCK LOCK=UCB$B_FLCK(R5),SAVIPL=-(SP),PRESERVE=NO
	CALLS	#2,@UCB$L_OALTST2(R11)
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,CONDITION=RESTORE,PRESERVE=NO
	BRB	7$
11$:	ADDL2	#8,SP	;pop 2 args
7$:
1$:	; Return whatever R0 status the altstarts returned.
	; (Not actually looked at)
	RET
; undomods...undo mods done do an IRP moving to device queue. Call arg is
; intercept UCB, and IRP address.
undomods: .call_entry, max_args=2
	movl	4(ap),r3		; get the IRP address
	movl	8(ap),r5		; and intercept UCB
; Now the tricky bit.
; We must fill the appropriate address into IRP$L_PID for a call at
; I/O completion. We use a table of such routines, one per unit,
; all of the same size so we can calculate the address of the
; routines. However, since the routine addresses can be almost
; anywhere when the compiler gets done with them, we will
; use a table constructed BY the compiler of pointers to them all and
; access via that instead of just forming the address directly. The table
; entries will be left 2 longs in size each.
; Table SW_VOADT is what we need. Note however that the .address operators
; there probably need to change to some more general .linkage directive.
;
; Most of this setup should really be done at interrupt device unit init
; for production.
;
; Recognize if this is ours!
	CMPL	IRP$L_PID(R3),UCB$L_MYPOST(R5)	; Already clobbered this IRP?
	BNEQ	55$			; If not, don't unclobber

; Looks like we DID clobber this IRP and need to fix it up again.

	movl	<IRP$Q_QIO_P2+4>(R3),IRP$L_PID(R3)	;restore IRP ptr
	movl	IRP$Q_QIO_P2(R3),IRP$L_MEDIA(R3)	;get media
	clrl	irp$q_qio_p2+4(r3)	;be sure we notice this was fixed
	movl	<IRP$Q_QIO_P1+4>(R3),IRP$L_STS(R3)
	MOVL	<IRP$Q_qio_p3>(R3),IRP$L_UCB(R3)	;ORIGINAL UCB
; If moving this back to the mp queue, we may need to re-arbitrate if we
; see it at pending-io again, so allow this to happen.
	clrl	irp$q_qio_p3+4(r3)	; say we need to check on this again
; Also uncount the pending I/O on the device
	decl	ucb$l_outstnd(r5)	; count this down
	bgeq	55$
	clrl	ucb$l_outstnd(r5)	; clamp this non-negative
55$:
	ret
;
; Steal-startio. We get here first, and must arrange initial setup here
; so we can check I/O errors and handle them. Do this via stealing the
; irp$l_pid entry. On VAX we had to grab a special bit of pool to do this,
; but on AXP, by this point the irp$q_qio_p1 to _p6 are free to use, so just
; use P4 to hold the irp$l_pid field (so the error retry intercept doesn't
; walk on it if used) and use the 2nd long if we need it.
TOORGJ: BRW     TOORG
AWAB:   BRW     AWAY
; on entry R3=IRP, r5=host UCB
STEALSTART: $DRIVER_START_ENTRY
	.if	df,d$$bug
	tstl	ntruse
	beql	1700$
	bbcc	#5,ntrmsk,1700$
	.iif df,x$$$dt,jsb g^ini$brk
1700$:
	.endc
	JSB	GETSWUCB		;find intercept UCB
	TSTL	R0			;did we find it?
        BGEQ    AWAB            ;no, scram, but probably hang.
; For normal dispatching, IRPs may just be inserted on the normal UCB
; queue and could be pulled off once the underlying driver gets
; control. Therefore before allowing an IRP in here, check that
; the queue is also free of any IRPs not processed for the chosen
; path. Any waits in the underlying driver can cause problems
; however. This check is bullet-proof if the underlying driver doesn't
; go below fork, though. 

	.iif df,m$$trp,movl #6,ucb$l_mtrp(r0)
; (Keep a back pointer to our master for now; helps debugging this code.
; Ultimately the reg use here needs rework.)
        MOVL    R5,UCB$L_BACKLK(R0)     ;else put it in now
	PUSHL	R5
        MOVL    R0,R5           ;point at intercept ucb now
	clrl	ucb$l_fgdun(r5)	;clear flag that we clobbered the IRP already
; allow external control over error reduction
        BITL    #^X100000,UCB$L_CTLFLGS(R5)      ;Turning on switch ability?
        BEQL    TOORGJ          ;if not skip out
; Flag that this IRP is coming from start-io where it will be processed
; fully, so let pending io just call the insert-irp routine directly
	movl	#1,irp$q_qio_p3+4(r3)	; flag that this IRP is one of ours!
; be sure this is read or write, else just start orig. one
; Thus we don't mess with ANYTHING except read or write. Thus packack etc.
; would also go through basically unaltered.
	.IF	NDF,EVAX
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$W_FUNC(R3),R0
	.IFF
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$L_FUNC(R3),R0
	.ENDC
        ASSUME  IRP$S_FCODE LE 7        ; Allow byte mode dispatch
; io$_packack is 8
; io$_writecheck is 10
; io$_writepblk is 11
; io$_readpblk is 12
; allow checks on all 3
;
; Allow some pack-acks through on original path since the mount verify
; might have been caused by a SCSI bus RESET, which is not necessarily
; an indication that the path is lost, but may mean only that somebody
; booted, a tape hung, etc.
;
; After some number of packacks however (mintries), we must presume that
; this SCSI connection isn't making much progress, and we had better try
; to fail over to an alternate if one exists. In addition, try to send
; notice to an attached daemon via a preallocated mailbox (it's up to the
; daemon to allocate it, fill it in here, and read it) that our device
; failed. The daemon can then take care of configuring other devices 
; should that be required.
;
; See if all modifiers are set. If so, we'll strip them all but reroute to
; the original disk, not the alternate, giving a way for the switch server
; to send a packack to the direct path.
; In fact test only MVIRP so allow this as a way to get any (unmodified)
; I/O to the direct path.
	PUSHL	R1
	MOVZWL	IRP$L_FUNC(R3),R1	;get the whole function word
	BICL	#^CIO$M_FMODIFIERS,R1	;clear all but modifiers
	CMPL	R1,#IO$M_FMODIFIERS	;This the flag case?
	BNEQ	21$			;no, proceed normally
	POPL	R1			;first fix stack
; Now strip fmodifiers from IRP
	BICL	#IO$M_FMODIFIERS,IRP$L_FUNC(R3)	;make it a normal packack
; r5 is intercept ucb
	BRW	QORIG
21$:
;
; We need a way to get to the alternate also, since we set the disable-
; assign flag in that UCB. So use all mods except the 128 bit.
	CMPL	R1,#<IO$M_FMODIFIERS&^C128>
	BNEQ	22$
	POPL	R1
; Set a temporary bit in UCB$L_INDRCT so this IRP gets dispatched to
; the desired side regardless of the rest. It gets cleared as soon
; as it is tested below.
	BICL	#IO$M_FMODIFIERS,IRP$L_FUNC(R3)	;make it a normal irp
	BISL	#256,UCB$L_INDRCT(R5)	; Set temporary-alt flag
	BRW	12$		; Then dispatch to alternate side
22$:
	POPL	R1
; If we see over 3 packack mount verifies in a row, switch to the other
; side.
; We only switch paths on PACKACK from mount verify, not on other MV functs.
	BITL	#IRP$M_MVIRP,IRP$L_STS(R3)	;this IRP from mnt verify?
	BEQL	10$			; If not mnt vfy, branch
	CMPL	R0,#IO$_PACKACK		; could this be a packack from mvfy
	BNEQ	10$
	INCL	UCB$L_RETRIES(R5)	;bump retry count
	.IIF	NDF,MINTRIES,MINTRIES=3	;allow 2 packacks before switch
	CMPL	UCB$L_RETRIES(R5),#MINTRIES	;only 1 or 2?
	BLEQ	12$			;path might be ok
; The current path is messed. Switch to other one
;
;
; SWITCH PATH LOGIC
;
; Here we switch to an alternate device if possible, send notice to an
; attached "server" so it can configure alternate devices if need be,
; and let the I/O by in the new direction.
;
	CLRL	UCB$L_SAWSUCC(R5)	;say no success on this path yet
; swap path now. (Note this has no effect if althost is still null.)
	TSTL	UCB$L_INDRCT(R5)	;on indirect path now?
	BNEQ	14$			;no, switching to it
; Test the path enabled bits before setting up to use that path.
	tstl	UCB$L_ENAPTH+4(R5)	; is path 1 enabled?
	beql	15$			; if not, do not switch
	MOVL	#1,UCB$L_INDRCT(R5)	;set on indirect path
	BRB	15$
14$:	
	tstl	UCB$L_ENAPTH(R5)	; path 0 enabled?
	beql	15$			; if not skip using
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
	CLRL	UCB$L_INDRCT(R5)	;was indirect path, use direct now
15$:
	CLRL	UCB$L_RETRIES(R5)	;Allow another 3 tries at the new UCB
	CLRL	UCB$L_OUTSTND(R5)	;say nothing's outstanding here any more
; Now send a message (if we can) to our swap-daemon so it can provide
; swaps of other stuff if it needs to.
	PUSHR	#^M<R0,R1,R2,R3,R4,R5,R6>
;check daemon pid still valid
        PUSHR  #^M<R6,R7,R8>
        MOVZWL  G^SCH$GL_MAXPIX,R7      ;max process index in VMS
622$:
        MOVL    G^SCH$GL_PCBVEC,R6      ;get PCB vector address
        MOVL    (R6)[R7],R8             ;get a PCB address
        TSTL    R8              ;system address should be < 0
        BGEQ    23$                     ;if it seems not to be a pcb forget it
        CMPL    UCB$L_DAEMON(R5),PCB$L_PID(R8)  ;this our process?
        BEQL    221$                    ;if so, jump out of loop
23$:    SOBGTR  R7,622$                  ;if not, look at next
        CLRL    UCB$L_DAEMON(R5)        ;if cannot find process, zero our flag
221$:
        POPR   #^M<R6,R7,R8>
        TSTL    UCB$L_DAEMON(R5)        ;got our daemon process there?
	BEQLW	30$			;if not, skip
MBX.USE=56	; Amount we need
MBX.SIZ=64	;(use next higher quadword size)
; We grab a little more stack than needed for the buffer so we can align it
	MOVL	SP,R4		; Point R4 at the buffer allocated
	SUBL	#MBX.SIZ,SP	;"allocate" a buffer
	SUBL	#MBX.USE,R4	; Use a little less, but...
	BICL	#7,R4		; ...align the buffer on 8 byte bdy
;
; Buffer format:
;
; Fill in the message now as we need it.
; 0/1 flag for alt or direct device failed
; UCB address of failed device
; unit number of failed device
; allocation class of failed device
; counted device name (16 bytes in all)
	PUSHR	#^M<R0,R1,R2,R4>
	MOVL	UCB$L_HSTUCB(R5),R0	;get host ucb
; offsets of buffer
swmsg$l_indrct=0	;indirect or direct flag
swmsg$l_ucb=4		;UCB of host currently used
swmsg$l_unit=8		;unit number of host device
swmsg$l_alloclass=12	;alloc class of host device
swmsg$a_name=16		;counted device name (1 byte count, 15 bytes text)
swmsg$l_stat=32		;statusflag, 1 or add 16384 and possibly 8192
swmsg$l_node=36
;
	MOVL	UCB$L_INDRCT(R5),(R4)+	;Tell daemon which unit...
	BEQL	24$			;if we just left direct mode
	TSTL	UCB$L_ALTHOST(R5)
	BGEQ	24$			;don't use a null althost address
	MOVL	UCB$L_ALTHOST(R5),R0	;get ucb of alt. device
24$:	MOVL	R0,(R4)+		;pass ucb of failed device
	MOVL	UCB$L_DDB(R0),R1	;Point at the DDB
	MOVZWL	UCB$W_UNIT(R0),(R4)+	;send unit number
	MOVL	DDB$L_ALLOCLS(R1),(R4)+
	MOVAB	DDB$B_NAME_LEN(R1),R2	;point at device name string
	MOVL	(R2)+,(R4)+		;fill in device name
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	#1,(R4)+		;set a code that says we came from startio
	MOVL	DDB$L_SB(R1),R2		;GET SB of the node
	MOVAB	SB$T_NODENAME(R2),R2	;POINT at nodename
	MOVL	(R2)+,(R4)+		;nodename is 16 chars, count first
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	POPR	#^M<R0,R1,R2,R4>
	MOVL	#MBX.USE,R3	; r3 gets size


	MOVL	UCB$L_MBXUCB(R5),R5 ; ucb of mailbox
	BGEQ	46$		; if zero forget the write attempt. (>0 illegal too)
;ensure mailbox is not deleted
; At process deletion, the host process may be blown away before the
; device is dismounted. Since the host process has the only known
; channel to that mailbox, cleaning that channel can mean the
; ucb is no longer valid. Do some extra checks here to make certain
; this cannot happen. Also, if we see the mailbox unref'd or
; not online, clear OUR ref to it so we won't be fooled by
; later reuse of the memory.
	CMPB	#DYN$C_UCB,UCB$B_TYPE(R5)
	BNEQ	46$		; Be sure this IS a UCB
	BITL	#UCB$M_ONLINE,UCB$L_STS(R5)	;ucb marked online?
	BEQL	46$		;if not marked online don't try a write
	TSTL	UCB$L_REFC(R5)	;is the UCB referenced by someone?
			;host process should have a channel open to the
			;mailbox before we get to it. If it does not,`
			;then we must NOT use it.
	BLEQ	46$		;no refs means it might be deleted so
				;don't write to it. This is mainly a
				;problem during process deletion.
				; also disallow any stray negative counts
				; in case somethign messed up.
	TSTL	UCB$L_ORB(R5)	;finally ensure nonzero orb addr
	BGEQ	46$		;if zero, can't use either.
	BBC	#DEV$V_MBX,UCB$L_DEVCHAR(R5),46$ ; If not a mailbox, reject it too
	cmpb	#DC$_Mailbox,ucb$b_devclass(r5)
	bneq	46$		; be sure it's a mailbox too
; Here send a message to the daemon and continue. It appears the mailbox
; is ok.
	CALL_WRTMAILBOX SAVE_R1=yes
46$:
	ADDL2	#MBX.SIZ,SP		;"deallocate" the space we grabbed
30$:
	POPR	#^M<R0,R1,R2,R3,R4,R5,R6>
	BRB	11$
10$:

; r5 is intercept ucb

	.iif df,m$$trp,movl #7,ucb$l_mtrp(r5)
	.iif df,m$$trp,movl r3,ucb$l_mtrp+16(r5)
	CLRL	UCB$L_RETRIES(R5)	; Saw anything BUT a MV packack
	.if	df,d$$bug
	tstl	inmve
	beql	712$
	.iif	df,x$$$dt, jsb g^ini$brk
712$:
	.endc
	MOVL	#1,UCB$L_SAWSUCC(R5)	;must have passed packack...
11$:

12$:

; r5 is intercept ucb

; now set up IRP, then call the previous start-io point at
; ucb$l_hstartio(r5) to do the work with registers put back.
; For Alpha, the stack manipulation here is messy to track in machine
; code, so do it in a register. This makes debug easier, and correctness
; easier to achieve.
;
; Now the tricky bit.
; We must fill the appropriate address into IRP$L_PID for a call at
; I/O completion. We use a table of such routines, one per unit,
; all of the same size so we can calculate the address of the
; routines. However, since the routine addresses can be almost
; anywhere when the compiler gets done with them, we will
; use a table constructed BY the compiler of pointers to them all and
; access via that instead of just forming the address directly. The table
; entries will be left 2 longs in size each.
; Table SW_VOADT is what we need. Note however that the .address operators
; there probably need to change to some more general .linkage directive.
; Recognize if this is ours!
	CMPL	IRP$L_PID(R3),irp$l_mypost(R5)	; Already clobbered this IRP?
	BEQL	55$			; If so don't do it twice!
	incl	ucb$l_fgdun(r5)		; bump flag that we have to alter irp
	MOVL	IRP$L_PID(R3),<IRP$Q_QIO_P2+4>(R3)	;store original irp$l_pid field
        MOVL    irp$l_mypost(R5),IRP$L_PID(R3)     ; Now point irp$l_pid at a proper
	MOVL	IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3)	;save media field too
	MOVL	IRP$L_STS(R3),<IRP$Q_QIO_P1+4>(R3)	;Save status incoming
	MOVL	IRP$L_UCB(R3),<IRP$Q_qio_p3>(R3) ;save original ucb
; If this is the first time we alter an IRP, bump its count.
	.PRESERVE ATOMICITY
	INCL	UCB$L_OUTSTND(R5)	;Bump outstanding I/O count
	.NOPRESERVE ATOMICITY
        .IF     NDF,EVAX
; must add vSW$dpt address to this IF VAX
; (for AXP the address is ok as is. The difference has to do with the way
; driver loading differs on the 2 machines.)
;
; Mind, this code is not quite suited to VAX at the moment, but this area
; is a nasty surprise if one is not prepared for it. This will facilitate
; back-porting.
        MOVAB   SW$DPT,R10     ;start of driver
        ADDL2   R10,IRP$L_PID(R3)       ;now pid should get back ok
        .ENDC
	.iif df,m$$trp,movl #8,ucb$l_mtrp(r5)
	.iif df,m$$trp,movl r3,ucb$l_mtrp+20(r5)
;;	.IF	DF,FFINISH
; If sending to a shadow unit, with DEV$M_VRT set in devchar2
; do NOT set finipl8 bit since shadow can't hack it.
;
; ????????????????? NOTE:
; for efficiency, use a different register here..we should compute target
; UCB once and do the test obly.
;
	PUSHL	R5
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	4212$			; IF EQL NO
	MOVL	UCB$L_ALTHOST(R5),R5	; point at target indirect path UCB
	BRB	4213$
4212$:
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
4213$:
;
; R5 is now the targetted device UCB
;
	BITL	#DEV$M_VRT,UCB$L_DEVCHAR2(R5)	;this a shadow dvc?
	BEQL	4214$			; If not shadow device, branch
; Sending I/O to shdriver or the like. Since it loses on fastio finish
; do not set finipl8 bit etc. for it.
	POPL	R5
	BRB	209$
4214$:
	POPL	R5
; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle
; finipl8 stuff. if clear, it cannot.
	BBS	#IRP$V_MVIRP,IRP$L_STS(R3),209$	;leave MV IRPs alone
	BBC	#UCB$V_FI8OK,UCB$L_ICPFGS(R5),209$	;skip if not using ffin
	.if	ndf,noswpio$
; Mount verify will allow start on swap i/o so allow it here IF it isn't
; already set!!
	bbs	#irp$v_swapio,irp$l_sts(r3),209$
	.endc
	.iif df,chk.ss,BBC #0,UCB$L_SAWSUCC(R5),209$	; if we may be transitioning use IPL 4
	BBS	#IRP$V_FINIPL8,IRP$L_STS(R3),209$
	BBS	#IRP$V_FAST_FINISH,IRP$L_STS(R3),209$
; Shadowing assumes IPL4 return unfortunately
; Test this too, in case shdriver is layered above us. (That should be ok
; but take no chances.)
	BBS	#IRP$V_SHDIO,IRP$L_STS2(R3),209$
	.if	df,fu.outst
	cmpl	ucb$l_outstnd(r5),#10
	bgequ	209$
	.endc
	.if	ndf,noswpio$
	bisl	#<irp$m_swapio>,irp$l_sts(r3)
	.endc
	BISL	#<IRP$M_FINIPL8!IRP$M_FAST_FINISH>,IRP$L_STS(R3) ;set fast path
209$:
;;	.ENDC
55$:
; r5 is intercept ucb
	TSTL	UCB$L_INDRCT(R5)	; Are we on the direct path?
	BEQL	113$			; if so, just send the IRP on
	TSTL	UCB$L_ALTHOST(R5)	; Ensure there IS an alternate host
	BLSS	112$			; If UCB looks ok, call alt path
113$:
	BRW	QORIG			; On the direct path just pass I/O on
112$:
;
; I/O to the alternate device. Must be rerouted and we must grab
; completion in case of split I/O etc.
;
; To accomplish this we need storage for:
;  1. Original irp$l_pid
;  2. Possibly packack count (use intercept UCB for this)
;
; start at <irp$q_qio_p2> with this so ASSUME that we have 2 longs available
; there. Use this one so the error intercept won't clobber it.
;
; MV IRPs need to be treated differently
	BITL	#IRP$M_MVIRP,IRP$L_STS(R3)	;this IRP from mnt verify?
; (helper branches)
	BEQL	16$			; If bit clear, this isn't Mnt Vfy p.ack
	BRW	1000$			; reroute packack not synch'd by ucb busy
16$:	; Non-MV IRP here, for alternate target
;
; We don't unbusy the main device if mount verify is in progress, but
; otherwise we will unbusy the device now. Note that at the top of stack at
; this point is the original R5 of the main path device (which is mounted
; etc.)
	PUSHL	R5			; We need a register
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
	BICL	#UCB$M_BSY,UCB$L_STS(R5); unbusy direct line
	POPL	R5			; get back intercept dvr R5 now
; We need to get the original IRP$L_PID back to really finish the I/O.
;
;
; Note before we finish adjusting the IRP we must be sure we don't do it
; twice, so check and skip the adjustment if doing it the 2nd time.
;
; Now the tricky bit.
; We must fill the appropriate address into IRP$L_PID for a call at
; I/O completion. We use a table of such routines, one per unit,
; all of the same size so we can calculate the address of the
; routines. However, since the routine addresses can be almost
; anywhere when the compiler gets done with them, we will
; use a table constructed BY the compiler of pointers to them all and
; access via that instead of just forming the address directly. The table
; entries will be left 2 longs in size each.
; Table SW_VOADT is what we need. Note however that the .address operators
; there probably need to change to some more general .linkage directive.
	CMPL	IRP$L_PID(R3),ucb$l_mypost(R5)	; Already clobbered this IRP?
	BEQL	155$			; If so don't do it twice!
	MOVL	IRP$L_PID(R3),<IRP$Q_QIO_P2+4>(R3)	;store original irp$l_pid field
	MOVL	ucb$l_mypost(R5),IRP$L_PID(R3)
	MOVL	IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3)	;save media field too
	MOVL	IRP$L_STS(R3),<IRP$Q_QIO_P1+4>(R3)	;Save status incoming
	MOVL	IRP$L_UCB(R3),<IRP$Q_qio_p3>(R3) ;save original ucb
155$:
	.iif df,m$$trp,movl #9,ucb$l_mtrp(r5)
;
; Now redirect to the alternate UCB
; Then return. Note we should always be on indirect path here...following
; bits are belt 'n' suspenders...
	PUSHL	R5
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	212$			; IF EQL NO
	BICL	#256,UCB$L_INDRCT(R5)	; Clear temporary-alt flag
	MOVL	UCB$L_ALTHOST(R5),R5	; point at target indirect path UCB
	BRB	213$
212$:
	BICL	#256,UCB$L_INDRCT(R5)	; Clear temporary-alt flag
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
213$:
	MOVL	R5,IRP$L_UCB(R3)	; Ensure the IRP looks right
; Geometry of the device must be the same in either case, so IRP$L_MEDIA
; better not have to be altered...
; We CALL the alternate start-io so as to keep linkages clean.
	CALL_INSIOQC			; send the IRP off to the alternate
; yes, double pop...
	.if	df,d$$bug
	tstl	inmve
	beql	713$
	.iif	df,x$$$dt, jsb g^ini$brk
713$:
	.endc
	POPL	R5	; get back intercept ucb
	.iif df,m$$trp,movl #10,ucb$l_mtrp(r5)
	POPL	R5	; get back original ucb
; Clear busy on the main host unit since that will have gotten the $qio in
; the first place. $reqcom in the secondary driver will have cleared busy
; there but not here; nor will postproc. do so. However, this code
; needs the ucb clean no more so allow more activity.
	MOVL	#1,R0	; say all ok
	BRW	STSRET
;
; Handle packack IRPs when sent direct to alt host
;
; Note however that we need to handle these IRPs also so we complete
; them in the original context, not that of the alternate host device
; so packack will see a sensible apparent situation.
; (In other words, the UCB when MV gets these things back better still
; point at the main device, which MV thinks is what is in Mnt Vfy state
; so it can unbusy things properly when it gets done.)
1000$:
	.if	df,d$$bug
	tstl	doxdt
	beql	722$
	.iif	df,x$$$dt, jsb g^ini$brk
722$:
	.endc
	.if	ndf,evax
	pushl	r10
	.endc
	CMPL	ucb$l_mypost(R5),IRP$L_PID(R3)	; Already clobbered this?
	BEQL	56$
; If fixing up IRP, always do it ALL.
	MOVL	IRP$L_PID(R3),<IRP$Q_QIO_P2+4>(R3)	;store original irp$l_pid field
        MOVL    ucb$l_mypost(R5),IRP$L_PID(R3)     ; Now point irp$l_pid at a proper
	incl	ucb$l_fgdun(r5)		; flag that we DO need to alter the IRP
	MOVL	IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3)	;save media field too
	.iif df,m$$trp,movl #11,ucb$l_mtrp(r5)
	MOVL	IRP$L_STS(R3),<IRP$Q_QIO_P1+4>(R3)	;Save status incoming
	MOVL	IRP$L_UCB(R3),<IRP$Q_qio_p3>(R3) ;save original ucb
        .IF     NDF,EVAX
        MOVAB   SW$DPT,R10     ;start of driver
        ADDL2   R10,IRP$L_PID(R3)       ;now pid should get back ok
        .ENDC
;56$:
;;	.IF	DF,FFINISH
; If sending to a shadow unit, with DEV$M_VRT set in devchar2
; do NOT set finipl8 bit since shadow can't hack it.
	PUSHL	R5
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	5212$			; IF EQL NO
	MOVL	UCB$L_ALTHOST(R5),R5	; point at target indirect path UCB
	BRB	5213$
5212$:
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
5213$:
	BITL	#DEV$M_VRT,UCB$L_DEVCHAR2(R5)	;this a shadow dvc?
	BEQL	5214$
; Sending I/O to shdriver or the like. Since it loses on fastio finish
; do not set finipl8 bit etc. for it.
	POPL	R5
	BRB	208$
5214$:
	POPL	R5
; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle
; finipl8 stuff. if clear, it cannot.
	BBS	#IRP$V_MVIRP,IRP$L_STS(R3),208$	;leave MV IRPs alone
	BBC	#UCB$V_FI8OK,UCB$L_ICPFGS(R5),208$	;skip if not using ffin
	.if	ndf,noswpio$
; Mount verify will allow start on swap i/o so allow it here IF it isn't
; already set!!
	bbs	#irp$v_swapio,irp$l_sts(r3),208$
	.endc
	.iif df,chk.ss,BBC #0,UCB$L_SAWSUCC(R5),208$	; if we may be transitioning use IPL 4
	BBS	#IRP$V_FINIPL8,IRP$L_STS(R3),208$
	BBS	#IRP$V_FAST_FINISH,IRP$L_STS(R3),208$
	.if	df,fu.outst
	cmpl	ucb$l_outstnd(r5),#10
	bgequ	208$
	.endc
; Shadowing assumes IPL4 return unfortunately
; Test this too, in case shdriver is layered above us. (That should be ok
; but take no chances.)
	BBS	#IRP$V_SHDIO,IRP$L_STS2(R3),208$
	.if	ndf,noswpio$
	bisl	#<irp$m_swapio>,irp$l_sts(r3)
	.endc
	BISL	#<IRP$M_FINIPL8!IRP$M_FAST_FINISH>,IRP$L_STS(R3) ;set fast path
208$:
;;	.ENDC
	.iif df,m$$trp,movl #12,ucb$l_mtrp(r5)
	MOVL	IRP$L_UCB(R3),<IRP$Q_qio_p3>(R3) ;save original ucb
56$:
	.if	ndf,evax
        MOVL    (SP)+,R10               ; Restore R10
	.endc
	PUSHL	R11
	MOVL	R5,R11			; We need sw UCB too
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	312$
	MOVL	UCB$L_ALTHOST(R5),R5	; get correct UCB
	BRB	313$
312$:	MOVL	UCB$L_HSTUCB(R5),R5
313$:
	MOVL	R5,IRP$L_UCB(R3)	; Ensure the IRP looks right
        FORKLOCK LOCK=UCB$B_FLCK(R5),-  ; Lock the FORK spinlock
                SAVIPL=-(SP),-          ; Save the current IPL
                PRESERVE=NO             ; Don't preserve R0
	.IF	DF,IRP$M_LOCK_RELEASEABLE
        BICL    #IRP$M_LOCK_RELEASEABLE, - ; Driver can not release forklock
                IRP$L_STS(R3)
	.ENDC
;
	CALL_INITIATE			; Start i/o on alt path directly
;
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock
                NEWIPL=(SP)+,-          ; Restore previous IPL
                CONDITION=RESTORE,-     ; Conditionally release lock
                PRESERVE=NO             ; Don't preserve R0
	POPL	R11
	POPL	R5			; get back original UCB
	BRW	STSRET

; Now restore registers and go to the original routine.
; This is also where we come to try again.
; Assumes host ucb address on stack, SW ucb address in R5, IRP address in R3
TOORG:
	.iif df,m$$trp,movl #13,ucb$l_mtrp(r5)
	MOVL    UCB$L_HSTARTIO(R5),R1   ;address of original routine
        BGEQ    AWA2            ; if none, things are messed...probably will crash
        POPL    R5              ; get back original UCB
        MOVL    #1,R0           ; set ok status for now
; call original start-io (to ensure high regs are passed correctly)
	PUSHL	R5		; UCB ARG
	PUSHL	R3		; IRP ARG
	CALLS	#2,(R1)		; CALL THE ORIGINAL STARTIO
	BRB	AWAY
AWA2:	POPL	R5
AWAY:
; Should get here only after original startio has been called & returned.
; early test code oughta be impossible.
	RET
;
; Send the IRP off to the primary path. Since we get called here when
; and only when ioc$initiate or equivalent has run, we need not requeue
; the IRP. Just call the original entry.
QORIG:
; We only come here for the primary path so just restore original R5 here
; to get its UCB.
; On entry R5 is intercept ucb

        MOVL    #1,R0           ; set ok status for now
	.iif df,m$$trp,movl #14,ucb$l_mtrp(r5)
	MOVL    UCB$L_HSTARTIO(R5),R1   ;address of original routine
	BGEQ	QAWA2
; call original start-io (to ensure high regs are passed correctly)
;
; No need to popl and then pushl; "optimize" these away.
;	POPL	R5		; restore original R5
;	PUSHL	R5		; UCB ARG
	movl	(sp),r5		;point R5 at original ucb
	movl	r5,irp$l_ucb(r3)	;point the IRP at orig. path too
	PUSHL	R3		; IRP ARG
	CALLS	#2,(R1)		; CALL THE ORIGINAL STARTIO
; Common return area. Since we are now leaving the intercept, make sure
; that i/o will continue if any is still there to do by moving an IRP
; onto the current dest. input queue
;
; reqcom in the underlying driver will not return to startio if it thinks
; that all outstanding I/O is finished. Thus we must arrange that it will
; find more such by looking here for more and continuing till all current
; I/O has been processed. When we put an IRP back into the multiport
; input queue we must ensure our "already processed" flags are reset also.
;
STSRET:
	RET
QAWA2:	POPL	R5
	ret

	.SBTTL	Packet Filter Routine
;
; Pstealstart -
; Subroutine that will "process" IRPs to format them to be put on
; appropriate queues.
;
; NOT part of driver start-io directly, and called when we have
; need to pull an IRP off our queue and get it onto a subordinate
; drive's queue. Inserts on one or other drive's queue, does not
; look for MV etc. here.
;
; on entry R3=IRP, r5=host UCB
; Argument #1, if 0 means do not move IRP to queues, if 1 means move
PSTEALSTART: .call_entry
; Callers need R0 and R1 preserved, so just do that.
	pushl	r0
	pushl	r1
	movl	4(ap),R8		;get "move args" flag
	JSB	GETSWUCB		;find intercept UCB
	TSTL	R0			;did we find it?
        BGEQ    PAWAB            ;no, scram, but probably hang.
	.iif df,m$$trp,movl #36,ucb$l_mtrp(r0)
        MOVL    R5,UCB$L_BACKLK(R0)     ;Save back pointer for debugging
	PUSHL	R5
        MOVL    R0,R5           ;point at intercept ucb now
	.iif df,m$$trp,movl #15,ucb$l_mtrp(r5)
	clrl	ucb$l_fgdun2(r5)	;say initially didn't see we had to alter IRP
; allow external control over error reduction
        BITL    #^X100000,UCB$L_CTLFLGS(R5)      ;Turning on switch ability?
        BEQL    PTOORG          ;if not skip out
; Flag that this IRP is coming from start-io where it will be processed
; fully, so let pending io just call the insert-irp routine directly
; (no, don't do it since WE call the enqueue functions directly here.)
;;;;;	movl	#2,irp$q_qio_p3+4(r3)	; flag that this IRP is one of ours!
; be sure this is read or write, else just start orig. one
; Thus we don't mess with ANYTHING except read or write. Thus packack etc.
; would also go through basically unaltered.
	.IF	NDF,EVAX
; Vax
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$W_FUNC(R3),R0
	.IFF
; Alpha
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$L_FUNC(R3),R0
	.ENDC
        ASSUME  IRP$S_FCODE LE 7        ; Allow byte mode dispatch
; io$_packack is 8
; io$_writecheck is 10
; io$_writepblk is 11
; io$_readpblk is 12
; allow checks on all 3
;
; Allow some pack-acks through on original path since the mount verify
; might have been caused by a SCSI bus RESET, which is not necessarily
; an indication that the path is lost, but may mean only that somebody
; booted, a tape hung, etc.
;
; After some number of packacks however (mintries), we must presume that
; this SCSI connection isn't making much progress, and we had better try
; to fail over to an alternate if one exists. In addition, try to send
; notice to an attached daemon via a preallocated mailbox (it's up to the
; daemon to allocate it, fill it in here, and read it) that our device
; failed. The daemon can then take care of configuring other devices 
; should that be required.
;
; See if all modifiers are set. If so, we'll strip them all but reroute to
; the original disk, not the alternate, giving a way for the switch server
; to send a packack to the direct path.
; In fact test only MVIRP so allow this as a way to get any (unmodified)
; I/O to the direct path.
	PUSHL	R1
	MOVZWL	IRP$L_FUNC(R3),R1	;get the whole function word
	BICL	#^CIO$M_FMODIFIERS,R1	;clear all but modifiers
	CMPL	R1,#IO$M_FMODIFIERS	;This the flag case?
	BNEQ	21$			;no, proceed normally
	POPL	R1			;first fix stack
; Now strip fmodifiers from IRP
	BICL	#IO$M_FMODIFIERS,IRP$L_FUNC(R3)	;make it a normal packack
	BRW	PQORIG
21$:
;
; We need a way to get to the alternate also, since we set the disable-
; assign flag in that UCB. So use all mods except the 128 bit.
	CMPL	R1,#<IO$M_FMODIFIERS&^C128>
	BNEQ	22$
	POPL	R1
; Set a temporary bit in UCB$L_INDRCT so this IRP gets dispatched to
; the desired side regardless of the rest. It gets cleared as soon
; as it is tested below.
	BICL	#IO$M_FMODIFIERS,IRP$L_FUNC(R3)	;make it a normal irp
	BISL	#256,UCB$L_INDRCT(R5)	; Set temporary-alt flag
	BRW	12$		; Then dispatch to alternate side
22$:
	POPL	R1
12$:
; now set up IRP, then call the previous start-io point at
; ucb$l_hstartio(r5) to do the work with registers put back.
; For Alpha, the stack manipulation here is messy to track in machine
; code, so do it in a register. This makes debug easier, and correctness
; easier to achieve.
        .iif ndf,evax,MOVL    R10,-(SP)               ; Free R10 also
; Now the tricky bit.
; We must fill the appropriate address into IRP$L_PID for a call at
; I/O completion. We use a table of such routines, one per unit,
; all of the same size so we can calculate the address of the
; routines. However, since the routine addresses can be almost
; anywhere when the compiler gets done with them, we will
; use a table constructed BY the compiler of pointers to them all and
; access via that instead of just forming the address directly. The table
; entries will be left 2 longs in size each. This is a tad wasteful, but
; if these internal addresses ever grow to 64 bits, we have the space
; allocated already.
; Note we should never see double clobbering here.
	CMPL	IRP$L_PID(R3),ucb$l_mypost(R5)	; Already clobbered this IRP?
	BEQL	55$			; If so don't do it twice!
	incl	ucb$l_fgdun2(r5)	; Flag the IRP MUST be specialized
	MOVL	IRP$L_PID(R3),-
		<IRP$Q_QIO_P2+4>(R3)	;store original irp$l_pid field
        MOVL    ucb$l_mypost(R5),IRP$L_PID(R3)     ; Now point irp$l_pid at a proper
	MOVL	IRP$L_UCB(R3),<IRP$Q_qio_p3>(R3) ;save original ucb
; Do all our modifications together
	MOVL	IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3)	;save media field too
	.iif df,m$$trp,movl #46,ucb$l_mtrp(r5)
	MOVL	IRP$L_STS(R3),<IRP$Q_QIO_P1+4>(R3)	;Save status incoming
	.PRESERVE ATOMICITY
	INCL	UCB$L_OUTSTND(R5)	;Bump outstanding I/O count
	.NOPRESERVE ATOMICITY
        .IF     NDF,EVAX
; must add vSW$dpt address to this IF VAX
; (for AXP the address is ok as is. The difference has to do with the way
; driver loading differs on the 2 machines.)
;
; Mind, this code is not quite suited to VAX at the moment, but this area
; is a nasty surprise if one is not prepared for it. This will facilitate
; back-porting.
        MOVAB   SW$DPT,R10     ;start of driver
        ADDL2   R10,IRP$L_PID(R3)       ;now pid should get back ok
        .ENDC
;55$:
;;	.IF	DF,FFINISH
; If sending to a shadow unit, with DEV$M_VRT set in devchar2
; do NOT set finipl8 bit since shadow can't hack it.
	PUSHL	R5
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	4212$			; IF EQL NO
	MOVL	UCB$L_ALTHOST(R5),R5	; point at target indirect path UCB
	BRB	4213$
4212$:
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
4213$:
	BITL	#DEV$M_VRT,UCB$L_DEVCHAR2(R5)	;this a shadow dvc?
	BEQL	4214$
; Sending I/O to shdriver or the like. Since it loses on fastio finish
; do not set finipl8 bit etc. for it.
	POPL	R5
	BRB	209$
4214$:
	POPL	R5
; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle
; finipl8 stuff. if clear, it cannot.
	BBS	#IRP$V_MVIRP,IRP$L_STS(R3),209$	;leave MV IRPs alone
	BBC	#UCB$V_FI8OK,UCB$L_ICPFGS(R5),209$	;skip if not using ffin
	.iif df,chk.ss,BBC #0,UCB$L_SAWSUCC(R5),209$	; if we may be transitioning use IPL 4
	BBS	#IRP$V_FINIPL8,IRP$L_STS(R3),209$
	BBS	#IRP$V_FAST_FINISH,IRP$L_STS(R3),209$
; Shadowing assumes IPL4 return unfortunately
; Test this too, in case shdriver is layered above us. (That should be ok
; but take no chances.)
	BBS	#IRP$V_SHDIO,IRP$L_STS2(R3),209$
	.if	df,fu.outst
	cmpl	ucb$l_outstnd(r5),#10
	bgequ	209$
	.endc
	.if	ndf,noswpio$
; Mount verify will allow start on swap i/o so allow it here IF it isn't
; already set!!
	bbs	#irp$v_swapio,irp$l_sts(r3),209$
	bisl	#<irp$m_swapio>,irp$l_sts(r3)
	.endc
	BISL	#<IRP$M_FINIPL8!IRP$M_FAST_FINISH>,IRP$L_STS(R3) ;set fast path
209$:
;;	.ENDC
55$:
        .iif ndf,evax,MOVL    (SP)+,R10               ; Restore R10
	.iif df,m$$trp,movl #17,ucb$l_mtrp(r5)
	TSTL	UCB$L_INDRCT(R5)	; Are we on the direct path?
	BEQL	113$			; if so, just send the IRP on
	TSTL	UCB$L_ALTHOST(R5)	; Ensure there IS an alternate host
	BLSS	112$			; If UCB looks ok, call alt path
113$:
	BRW	PQORIG			; On the direct path just pass I/O on
112$:
;
; I/O to the alternate device. Must be rerouted and we must grab
; completion in case of split I/O etc.
;
; To accomplish this we need storage for:
;  1. Original irp$l_pid
;  2. Possibly packack count (use intercept UCB for this)
;
; start at <irp$q_qio_p2> with this so ASSUME that we have 2 longs available
; there. Use this one so the error intercept won't clobber it.
;
; We don't unbusy the main device if mount verify is in progress, but
; otherwise we will unbusy the device now. Note that at the top of stack at
; this point is the original R5 of the main path device (which is mounted
; etc.)
	PUSHL	R5			; We need a register
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
	BICL	#UCB$M_BSY,UCB$L_STS(R5); unbusy direct line
	POPL	R5			; get back intercept dvr R5 now
;
; Now redirect to the alternate UCB
; Then return. Note we should always be on indirect path here...following
; bits are belt 'n' suspenders...
	movl	r5,r0			; Need intercept UCB too.
	PUSHL	R5
	TSTL	UCB$L_INDRCT(R5)	; INDIRECT?
	BEQL	212$			; IF EQL NO
	BICL	#256,UCB$L_INDRCT(R5)	; Clear temporary-alt flag
	MOVL	UCB$L_ALTHOST(R5),R5	; point at target indirect path UCB
	BRB	213$
212$:
;;;;	BICL	#256,UCB$L_INDRCT(R5)	; Clear temporary-alt flag
	.iif df,m$$trp,movl #18,ucb$l_mtrp(r5)
	MOVL	UCB$L_HSTUCB(R5),R5	; POINT AT DIRECT UCB
213$:
1213$:
	MOVL	R5,IRP$L_UCB(R3)	; make IRP point to the underlying dvc
; Geometry of the device must be the same in either case, so IRP$L_MEDIA
; better not have to be altered...
; Here put the IRP on the appropriate queue. Do NOT start it in this
; routine; let caller handle that.
	tstl	R8			; See if we should omit queueing
	beql	217$			; if 0, do not queue (and don't need
					; to lock.)

; R5 here is the underlying device, not the SW device.

        FORKLOCK LOCK=UCB$B_FLCK(R5),-  ; Lock the FORK spinlock
                SAVIPL=-(SP),-          ; Save the current IPL
                PRESERVE=NO             ; Don't preserve R0
	PUSHL	R3			; Push IRP
	PUSHAB	UCB$L_IOQFL(R5)		; Also push I/O queue listhead
	CALLS	#2,G^EXE_STD$INSERT_IRP ; Make the call.
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock
                NEWIPL=(SP)+,-          ; Restore previous IPL
                CONDITION=RESTORE,-     ; Conditionally release lock
                PRESERVE=NO             ; Don't preserve R0
217$:
; yes, double pop...
	POPL	R5	; get back intercept ucb
	POPL	R5	; get back original ucb
; Clear busy on the main host unit since that will have gotten the $qio in
; the first place. $reqcom in the secondary driver will have cleared busy
; there but not here; nor will postproc. do so. However, this code
; needs the ucb clean no more so allow more activity.
; On return R3 is the IRP because it is the same R3 as we were called with.
	MOVL	#1,R0	; say all ok
	BRW	PSTSRET

; Now restore registers and go to the original routine.
; This is also where we come to try again.
; Assumes host ucb address on stack, SW ucb address in R5, IRP address in R3
PTOORG:
	.iif df,m$$trp,movl #19,ucb$l_mtrp(r5)
	MOVL    UCB$L_HSTARTIO(R5),R1   ;address of original routine
        BGEQ    PAWA2           ; if none, things are messed...probably will crash
        POPL    R5              ; get back original UCB
	movl	r5,irp$l_ucb(r3) ; set IRP to point at orig. path
        MOVL    #1,R0           ; set ok status for now
        FORKLOCK LOCK=UCB$B_FLCK(R5),-  ; Lock the FORK spinlock
                SAVIPL=-(SP),-          ; Save the current IPL
                PRESERVE=NO             ; Don't preserve R0
	PUSHL	R3			; Push IRP
	PUSHAB	UCB$L_IOQFL(R5)		; Also push I/O queue listhead
	CALLS	#2,G^EXE_STD$INSERT_IRP ; Make the call.
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock
                NEWIPL=(SP)+,-          ; Restore previous IPL
                CONDITION=RESTORE,-     ; Conditionally release lock
                PRESERVE=NO             ; Don't preserve R0
	BRB	PAWAY
PAWA2:	POPL	R5
PAWAB:
PAWAY:
; Should get here only after original startio has been called & returned.
	popl	r1
	popl	r0
	RET
;
; Send the IRP off to the primary path. Since we get called here when
; and only when ioc$initiate or equivalent has run, we need not requeue
; the IRP. Just call the original entry.
PQORIG:
; We only come here for the primary path so just restore original R5 here
; to get its UCB.

        MOVL    #1,R0           ; set ok status for now
	.iif df,m$$trp,movl #20,ucb$l_mtrp(r5)
	MOVL    UCB$L_HSTARTIO(R5),R1   ;address of original routine
	BGEQ	PAWA2
;
; In this routine we are processing IRPs, so just queue them, don't
; actually start I/O.
; call original start-io (to ensure high regs are passed correctly)
;
; No need to popl and then pushl; "optimize" these away.
	POPL	R5		; restore original R5
	movl	r5,irp$l_ucb(r3) ; set IRP to point at orig. path
	tstl	R8		; don't queue if r8=0
	beql	217$
; Insert the IRPs in the underlying queue in the "standard" way
        FORKLOCK LOCK=UCB$B_FLCK(R5),-  ; Lock the FORK spinlock
                SAVIPL=-(SP),-          ; Save the current IPL
                PRESERVE=NO             ; Don't preserve R0
	PUSHL	R3			; Push IRP
	PUSHAB	UCB$L_IOQFL(R5)		; Also push I/O queue listhead
	CALLS	#2,G^EXE_STD$INSERT_IRP ; Make the call.
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock
                NEWIPL=(SP)+,-          ; Restore previous IPL
                CONDITION=RESTORE,-     ; Conditionally release lock
                PRESERVE=NO             ; Don't preserve R0
217$:
; Common return area. Since we are now leaving the intercept, make sure
; that i/o will continue if any is still there to do by moving an IRP
; onto the current dest. input queue
;
; reqcom in the underlying driver will not return to startio if it thinks
; that all outstanding I/O is finished. Thus we must arrange that it will
; find more such by looking here for more and continuing till all current
; I/O has been processed. When we put an IRP back into the multiport
; input queue we must ensure our "already processed" flags are reset also.
;
PSTSRET:
	popl	r1
	popl	r0
	RET



	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; SW_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.
;--
SW_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
;++
; 
; SW_UNIT_INIT - UNIT INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE SETS THE SW: 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.
; 
;--

SW_UNIT_INIT: $DRIVER_UNITINIT_ENTRY
; Don't set unit online here. Priv'd task that assigns SW unit
; to a file does this to ensure only assigned SWn: get used.
	.iif df,d$$bug,movl #-1,ntrmsk
	.if	df,m$$trp
;make the mousetrap site easy to find
	movl	#^xDEADFACE,ucb$l_mtrp+4(r5)
	clrl	ucb$l_mtrp(r5)
	.endc
	incl	ucb$l_enapth(r5)
	incl	ucb$l_enapth+4(r5)	;allow 2 paths for now
	movl	#^A/SWIT/,ucb$l_sanity(r5)
;	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)  ;SET UCB STATUS ONLINE
;limit size of SW: data buffers
SW_bufsiz=8192
	MOVL	#sw_BUFSIZ,UCB$L_MAXBCNT(R5)	;limit transfers to 8k
	.if df,d$$bug
	movab	doxdt,ucb$l_dbgdta(r5)
	.endc
	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 SW
; (note the id might be wrong but is attempt to get it.) (used only for
; MSCP serving.)
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)  ;SET UCB STATUS OFFLINE
	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 SW: 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
	MOVL	#1,UCB$L_SAWSUCC(R5)	;Set initial success flag
	CLRL	UCB$L_ALTHOST(R5)
	CLRL	UCB$L_INDRCT(R5)	;use direct path first
	CLRL	UCB$L_OUTSTND(R5)	;set no outstanding I/O yet
	MOVAB	SW_UCB,UCB$L_HUCBS(R5)	;host ucb table
; set up multiport queue
	movab	ucb$l_mpqueue(r5),ucb$l_mpqueue(r5)
	movab	ucb$l_mpqueue(r5),ucb$l_mpqueue+4(r5)
	TSTW	UCB$W_UNIT(R5)		;this unit 0?
	BNEQ	2$
	MOVL	R5,UCB0ADR		;save unit 0 address (global to dvr)
2$:
	MOVL	UCB0ADR,R0		;get the ucb address
	BGEQ	3$			;if obviously illegal skip
; Check the "unit 0 UCB" to ensure that it, as well as we, are compatible
; and both SWdriver units of similar vintage.
	CMPL	UCB$L_SANITY(R0),#^A/SWIT/	;does this look right?
	BNEQ	4$			;no, not valid.
	MOVL	UCB0ADR,UCB$L_UCB0(R5)	;save for all units
	BLSS	3$
; oops! User did not configure unit 0 first, so we have no pointer to
; unit 0 available. Therefore disable this intercept from being turned
; on, since a backpointer to unit 0 for the server function is required.
4$:	CLRL	UCB$L_SANITY(R5)	;ensure we can't turn the unit on
	MOVL	#2,R0			;lose
	RET
3$:
	pushr	#^m<r10,r11>
; Now fill in our UCB table entry, so it need not be done for every
; I/O. This lets us find the intercept UCB quickly at postprocessing
; time (until we can get a stack in the IRP itself to save this stuff
; more sensibly!).
;
        MOVZWL  UCB$W_UNIT(R5),R11      ; Need address cell
; following assumes that addresses are 32 bits long so shift by 2 gets us
; to an address offset.
        ASHL    #2,R11,R11              ; to get ucb address back at i/o done
        MOVAB   SW_UCBTBL,R10           ; Base of table of UCB addresses
        ADDL2   R11,R10                 ; Make R10 point to cell for THIS UCB
	movl	r5,(R10)		; fill in UCB tbl if this is our 1st time thru
        MOVZWL  UCB$W_UNIT(R5),R11      ; get our SW unit number
; Each linkage pair is 8 bytes long...
; Thus shift 3 bits to multiply by 8
        ASHL    #3,R11,R11              ; Make an offset to the linkage area
        MOVAB   SW_VOADT,R10            ; get the table base
        ADDL2   R10,R11                 ; r11 now points at the link addr
	movl	r11,ucb$l_mypost(r5)	; Save our clobber addr for fast ref
	popr	#^m<r10,r11>
	MOVL	#1,R0
	RET
;++
; 
; SW_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.
; 
;--
SW_STARTIO: $DRIVER_START_ENTRY
; 
; 	PREPROCESS UCB FIELDS
; 
;	ASSUME	RY_EXTENDED_STATUS_LENGTH  EQ  8
;	CLRQ	UCB$Q_SW_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
	.if	df,x$$$dt
	cmpl	#^xb00bface,irp$q_qio_p6(r3)
	bneq	1131$		; if boobytrap not seen skip
	jsb g^ini$brk
1131$:
	movl	#^xb00bface,irp$q_qio_p6(r3)
	.endc
	REQCOM,ENVIRONMENT=CALL		; COMPLETE REQUEST
; 
	;;
V_UNIT=0
V_UNM=1
	.IF	DF,EVAX
SW_FXS0:: .JSB_ENTRY INPUT=<R5>
	.IFF
SW_FXS0::
	.ENDC
	MOVL	I^#V_UNIT,R4
	BSBW	SW_FIXSPLIT	;GO HANDLE
	RSB
SW_FXPL==<.-SW_FXS0>	;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
	.MACRO	XVEC LBLC
	.IF	DF,EVAX
SW_FXS'LBLC:: .JSB_ENTRY INPUT=<R5>
	.IFF
SW_FXS'LBLC::
	.ENDC
	MOVL	I^#V_UNIT,R4
	BSBW	SW_FIXSPLIT
	RSB
	.ENDM
	.REPEAT	<SW_UNITS+4>	; some extra for safety
	XVEC	\V_UNM
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
V_UNM=V_UNM+1
	.ENDR
; Second set of entries for use with MSCP packets. (Bogus, yes, but
; what can one do?)
V_UNIT=0
V_UNM=1
	.IF	DF,EVAX
MSW_FXS0:: .JSB_ENTRY INPUT=<R5>
	.IFF
MSW_FXS0::
	.ENDC
	MOVL	I^#V_UNIT,R4
	BSBW	SW_FIXSPLIT	;GO HANDLE
	RSB
MSW_FXPL==<.-SW_FXS0>	;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
	.MACRO	MXVEC LBLC
	.IF	DF,EVAX
MSW_FXS'LBLC:: .JSB_ENTRY INPUT=<R5>
	.IFF
MSW_FXS'LBLC::
	.ENDC
	MOVL	I^#V_UNIT,R4
	BSBW	SW_FIXSPLIT
	RSB
	.ENDM
	.REPEAT	<SW_UNITS+4>	; some extra for safety
	MXVEC	\V_UNM
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
V_UNM=V_UNM+1
	.ENDR
;
; Fixsplit is entered in the SWdriver context, and is entered only where
; the alternate path is in use. Its job is to take an IRP posted in the
; context of the alternate path and post it in the context of the
; primary path. In the case of intercept drivers like vddriver, this will
; be called after the vddriver-local posting code has run and put back
; our code (so the vddriver should unbusy itself by reqcom...). We need
; to check status so if special action (like M Vfy) is needed, we can take
; it. Since we are not synch'd with the victim driver, we cannot just reqcom
; the IRP but must post it. The original main path driver has not in fact
; even seen the IRP though, although ITS UCB will have been marked busy
; and then will be unbusied once secondary startio entry runs.
;
; At this point we need also to pull anything off the multipath input queue
; and grab it into the driver queue, if not a MV IRP.
;
;
; For this intercept it is vital that after we pull in IRPs off the
; multipath queue, that we also note if the queue was empty on our
; start and if so send the first packet off to the real device, so
; things don't get hung.
;
	.IF	DF,EVAX
SW_FIXSPLIT: .JSB_ENTRY
	.IFF
SW_FIXSPLIT:
	.ENDC
; GET OLD PID..
; IN OUR UCB$PPID LONGWORD...
;some cleanup for host needed here. Note that r5 enters as IRP address.
; Use initial R5 to help reset host's system...
	PUSHL	R4		;r4 enters with SW unit number
	MOVL	R5,R3		;put entering IRP addr in std place
; We'll use qio_p3+4 to set flags to skip redundant special processing
; so we can try to avoid extra calls. Clear them here though in case
; we get split-io recalls, which need to be re-arbitrated.
	CLRL	IRP$L_QIO_P3+4(R3)	;clear our special flags
	MOVAB	SW_UCBTBL,R5
	ADDL2	(SP)+,R5	;R5 NOW POINTS AT UCB ADDRESS
	MOVL	(R5),R5		;NOW HAVE SW UCB ADDRESS IN R5
; notice stack is now clean too.
	MOVL	R5,R4		;we need the SW ucb at fork level
;
; set lock not releaseable by fastio code here as a precaution
; This may inhibit fastpath code from releasing locks on subsequent
; cycles for split I/O.
	.IF	DF,IRP$V_LOCK_RELEASEABLE
	BBSS	#IRP$V_LOCK_RELEASEABLE,IRP$L_STS(R3),20$
20$:
	.ENDC
;
;;	.IF	DF,FFINISH
	PUSHL	IRP$L_STS(R3)		;save status fields incoming
; We come in at IPL 8 here; fix up the IRP now.
; Ideally we probably should save & restore these instead. This is not
; good if used with fast dfdriver, for example, since it will set them
; too, then clear them and we'd get here at ipl 4.
;
; This code will work even if stuff above us uses these bits, or not.
; If code BELOW this level knows about them, all is well also. If not
; however, its synchronization can be loused up since it will have any
; i/o completion called at ipl 8, not 4...this is so if the code uses
; the irp$l_pid hook itself, that is. If it does not, all will be
; normal. For many cases this may not matter much. We could of course
; simply state that if the IRP is a system IRP on entry, we won't
; mess with its delivery at all, but will only try to facilitate OUR
; postprocessing. Use of this hook requires however that any lower
; processing that steals irp$l_pid again be tolerant of being called
; with finipl8 set and thus of having its postprocessing occur at
; ipl 8, not 4. Such stealing SHOULD save/restore the status bits
; too, so that it will not simply turn the bits off but rather 
; save and restore them. Otherwise what will happen is that
; our return will occur at ipl 4 when not expecting to.
	BBS	#IRP$V_FINIPL8,IRP$L_STS(R3),209$
; if finipl8 bit is clear, we will need to devicelock to get to proper IPL
; Otherwise we should be here at IPL 8 already.
;
; Note THIS DRIVER'S DEVICE IPL is 8!!
;
	DEVICELOCK SAVIPL=-(SP),PRESERVE=YES	;Devicelock here is IPL 8
	brb	1209$
209$:	pushl	r5
1209$:	MOVL	<IRP$Q_QIO_P1+4>(R3),IRP$L_STS(R3) ;Restore status incoming
;;	.IFF ;ffinish
;;	DEVICELOCK SAVIPL=-(SP),PRESERVE=YES	;Devicelock here is IPL 8
;;	.ENDC ;ffinish
	.iif df,m$$trp,movl r3,ucb$l_mtrp+24(r5)	
	.iif df,m$$trp,movl #21,ucb$l_mtrp(r5)
;
; We set up the primary path UCB here.
; That is after all what VMS thinks we're using.
;
	MOVL	UCB$L_HSTUCB(R5),R5	;note SW ucb still in r4
;
; At this point, one I/O has finished, and we use this opportunity to look
; for other work that might be on the multiport queue, as yet unmigrated to
; this driver. Nevertheless we'll make VERY sure it hasn't been already
; clobbered in that the reloc routine won't relocate the IRPs unless they
; look like we did not already do this.
;
; By draining the multiport input queue into the underlying driver queue
; at this point (as long as this isn't a MV IRP!) we should keep I/O going,
; much as happens in a "normal" situation with the driver doing reqcom
; and starting the next I/O if any exists. We just ensure that "any" will
; exist if any work was available on the multiport queue.
;
; Now we can move the IRPs to the driver queue if there are any on the
; queue.
;
; Get the forklock since other code will assume this is synch'ing the
; queue!
        FORKLOCK LOCK=UCB$B_FLCK(R5),-  ; Lock the FORK spinlock
                SAVIPL=-(SP),-          ; Save the current IPL
                PRESERVE=YES

; Do not bother if this IRP is a MV IRP.
	BBS	#IRP$V_MVIRP,IRP$L_STS(R3),44$	;this IRP from mnt verify?

; Now before other processing, try to ensure that IRPs on the multipath
; queue are pulled to underlying device's queue

        pushr   #^m<r0,r1,r2,r3,r4,r5,r6>
; Also don't try to restart if this UCB is in (or about to be in)
; mount verify (the main UCB, that is).
;
; This way the i/o finish stuff doesn't fight with the pending I/O stuff
; in this driver to get I/O stopped.
;
	movl	ucb$l_hstucb(r4),r6
	bitl	#<ucb$m_mntverip!ucb$m_mntverpnd>,ucb$l_sts(r6)
	bneq	42$		; skip if master is in MV status still
;
; Looks like the I/O completion is a normal one basically.
; (Note MV end call will have put I/O back on device queue if MV is
; ending; we don't need to handle that here.)
;
; First look to see if the input queue was empty.
; If the queue header points to its own address this is a pretty good
; indication that the queue is empty.
; Use R6 as a flag
;
;;;;	movl	irp$l_ucb(r3),r5	; This is still the real host
; We really need for (p)stealstart to figure out which underlying queue to
; put things on, and it won't find it unless we point at the primary path.
	movl	ucb$l_hstucb(r4),r5
	.if	df,d$$bug
	tstl	inmve
	beql	712$
	.iif	df,x$$$dt, jsb g^ini$brk
712$:
	.endc
;443$:	movab	ucb$l_ioqfl(r5),r6	; get the address of queue
;	bgeq	43$			; if illegal we won't touch (but will crash anyhow soon)
;	cmpl	r6,(r6)			; is queue empty?
;	bneq	43$			; if not, nothing special
;	clrl	r6			; else flag that the q was empty at
					; first so we can get the driver going
;
43$:    remque  @ucb$l_mpqueue(r4),r3           ;get an IRP off our input queue
        bvs     42$                     ; if nothing there skip out
; ok, we have an IRP to insert. Gotta relocate the sucker & put it onto the
; host driver's input queue that we are using.
; First we need to "process" it, though.
        pushl   r0
;
; ????????????? could push just R6 here??? (0/non-0 test only)
;
;	tstl	r6			; See if queue had been empty
; If the queue had been empty we better call insioqc here to get it going
; again.
; Then we better check again too!
;	bneq	343$
;	pushl	#0			; If the queue was empty, we put this
					; on separately
;	brb	543$
;343$:
        pushl   #1
543$:
; Pstealstart will relocate the IRP to the correct underlying queue
; MV end should start pulling stuff off the queue also.
        calls   #1,PSTEALSTART          ;go process the IRP to input queue
        popl    r0
	tstl	r6			; Had queue initially been empty?
	bneq	43$			; If not, go pull another entry off
					; and move it, after testing again
					; in case the driver finishes and
					; will need more work.
; At this point the queue had been seen empty initially so let us call
; insioqc here. The IRP should be ready for this, and is addressed
; in R3 as expected
	.iif df,m$$trp,movl r3,ucb$l_mtrp+28(r4)
	.iif df,m$$trp,movl #22,ucb$l_mtrp(r4)
; We will call insioqc ONLY ONCE and will NOT recheck whether the queue
; was empty at start, since insioqc may (during MVfy) put things back
; on.
	.if	df,d$$bug
	tstl	inmve
	beql	714$
	.iif	df,x$$$dt, jsb g^ini$brk
714$:
	.endc


;???????????? move this to later?? omit? Beware if a split i/o is here!
;{
; Because the master queue length gets bumped above us, decrement it again
; before ins here if still on master path. (This will call pending io here
; to actually insert the item.)
; Also flag that this IRP is already relocated
;	movl	#3,irp$q_qio_p3+4(r3)
;	decl	ucb$l_qlen(R5)
;	call_insioqc
;}

	brw	43$
; The pstealstart routine does not start i/o, just moves IRPs to queues.
42$:
        popr   #^m<r0,r1,r2,r3,r4,r5,r6>
44$:
;
; r4 should still be SW ucb, r5=primary host ucb, r3=irp
;
; R5 points at host device UCB here, not to SW device UCB.
;
; Reset the IRP to have the original return
; Thus the IRP will really complete next, not come back here.
;
; This will then appear to be coming from the original driver.
; Note: we won't bother testing to see if irp$l_pid points to our code because
; unless it did, there'd be no way to get here.
;

	MOVL	<IRP$Q_QIO_P2+4>(R3),IRP$L_PID(R3)	;restore pid so post is normal
;	clrl	<IRP$Q_QIO_P2+4>(R3)		; Be sure we note the IRP's NOT mung'd
844$:	MOVL	<IRP$Q_qio_p3>(R3),IRP$L_UCB(R3) ;restore original UCB
;
; However, where an IRP indicates the alternate device might have needed
; to start mount verify, we want to make it look like THIS device needs
; to...so check the conditions similarly to the way VMS normally does it.
;
; Note that com$post does not alter driver busy etc.
	PUSHL	R4		; We need the intercept and original UCB
	PUSHL	R5		; across the com$post call...so save here
	.IF	NDF,EVAX
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$W_FUNC(R3),R0
	.IFF
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$L_FUNC(R3),R0
	.ENDC
        ASSUME  IRP$S_FCODE LE 7        ; Allow byte mode dispatch
; io$_packack is 8, but treat ALL MVIRP's the same. None is  synch'd by
; the bsy bit.

	BITL	#IRP$M_MVIRP,IRP$L_STS(R3)	;this IRP from mnt verify?
	BNEQ	1000$			; If mnt vfy, branch
;
; Get to this for all IRPs except those from mount verify. Those we handle
; separately.
;
; Run thru a few tests to be sure mount verify would want this I/O
; Note we presume (as is true in dkdriver) that the 16384 bit is
; set only if i/o was successful but completed with an HSZ40 notice
; that something happened to the "other" controller.
;
	MOVQ	IRP$L_MEDIA(R3),R0	;get final status
; finish undoing all IRP mods so it will not show evidence of our
; tampering.
	movl	irp$q_qio_p2(r3),irp$l_media(r3)	; reset media
	BITL	#16384,R0		;Was our "failover/back" flag there?
	BNEQ	60$			;if so implies success but send msg
	BLBS	R0,850$			;if I/O was OK no mnt ver
; I/O failed and may have been due to a condition that mount verify
; will want to deal with.
;
; There are certain conditions which a single path might consider fatal
; that we should deal with too. These include the following statuses:
; SS$_DEVOFFLINE
; SS$_CTRLERR
; SS$_LINKABORT
; SS$_IVSTSFLG
; SS$_DRVERR
; SS$_PATHLOST
;
SW_TOLERRS=10
; If one of these is found for the Nth time,
; default SW_TOLERRS=10, change the error to SS$_MEDOFL and let mount
; verify be attempted if possible.
; I/O failed and may have been due to a condition that mount verify
; will want to deal with.
	.if	df,m$$trp
	movl	r5,ucb$l_mtrp+40(r4)	;store UCB we used
	.endc
	.if	df,d$$bug
	cmpw	#ss$_medofl,r0
	bneq	707$
	incl	doxdt
	incl ntruse
	.iif df,x$$$dt,jsb g^ini$brk	;************debug**********
707$:
	.endc
	CMPB	UCB$B_DEVCLASS(R5),#DC$_DISK	;disk device?
	BNEQ	50$			;no mnt ver if not disk
	BITL	#DEV$M_MNT,UCB$L_DEVCHAR(R5)	;mounted disk?
	BEQL	50$
	BITL	#DEV$M_FOR,UCB$L_DEVCHAR(R5)	;/foreign mounted?
	BNEQ	50$			;if mounted foreign can't mntver either
	TSTL	IRP$L_PID(R3)		; This an internal IRP?
	BLSS	50$			; if so, MV won't want it.
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>
;
; Check for one of our "special" errors.
	CMPW	#SS$_CTRLERR,R0
	BEQL	710$
	CMPW	#SS$_DRVERR,R0
	BEQL	710$
	CMPW	#SS$_DEVOFFLINE,R0
	BEQL	710$
	CMPW	#SS$_IVSTSFLG,R0
	BEQL	710$
	CMPW	#SS$_PATHLOST,R0
	BEQL	710$
	CMPW	#SS$_BUS_PHASE_ERROR,R0
	BEQL	710$
	CMPW	#SS$_LINKABORT,R0	;PKS generates these!!!
	BNEQ	740$
710$:
; Aha...this is one of the kinds of errors we should eventually use to
; try and get MV going if we are here to do something like switch
; a path.
	TSTL	UCB$L_PTHERRS(R4)
	BNEQ	730$
; On the first such error of 10, reset to medofl.
	MOVL	#SS$_MEDOFL,R0		; Else change the error to medofl
730$:
	INCL	UCB$L_PTHERRS(R4)	;Bump count of successive errors
	CMPL	UCB$L_PTHERRS(R4),#SW_TOLERRS	; Too many?
	BLEQ	740$			; if not yet a bunch of them seen
					; just process normally
; Then zero the error counter for next time. We must presume the packacks
; mount verify may send will fail if the path is really bad, or perhaps
; get it going again if not.
; Reset every 10th time by.
	CLRL	UCB$L_PTHERRS(R4)	;Zero count of successive errors
740$:
; Note exe$mount_ver can clobber regs R0 to R5, so we save/restore
; around the call to be sure we understand what's going on.
	BBCC	#UCB$V_MNTVERPND,UCB$L_STS(R5),53$
; Clear MV in progress if we are going this way so it can really start.
	BICL	#<UCB$M_MNTVERPND!UCB$M_MNTVERIP>,UCB$L_STS(R5)
53$:
; EXE$MOUNT_VER might requeue the IRP to the device again after a host
; driver $REQCOM normally finds that mount ver is needed. Therefore to
; avoid problems, put the original media address back (R0,R1 have
; the I/O status at this point) and replace the status after the
; call if we are just going to post it straight-way. If we are
; in fact just supposed to exit and do nothing, leave the IRP
; alone since it will get reused. Since we have repaired the
; media field here, this will permit it to work correctly the
; next pass. MV will of course requeue the IRP to the master path
; UCB in this case (it never does anything on the secondary device
; regardless of completion since that device is marked unmounted...and
; had darn well better BE unmounted. Therefore it'll be unaware of
; the separate secondary...just the primary. WE have to do the juggling
; here.
; Note that by this point all fields of the IRP have been restored to
; normal, and also the "saved" irp$l_pid cell has been cleared so we
; will never see it as having valid data from a prior bash.
;
	MOVL	IRP$Q_QIO_P2(R3),IRP$L_MEDIA(R3) ; RESTORE...
					; media field in case IRP gets re-Qd
	pushl	r0
	JSB	G^EXE$MOUNT_VER		;see if we should mount ver
	popl	r0
;;;	BLBS	R2,52$
	BLBC	R2,55$			;if not skip com$post...
52$:	POPR	#^M<R0,R1,R2,R3,R4,R5>
	BRW	652$
;
; Return to 652$ also when we finish sending the message to the server
; where I/O status returned with 16384 bit set. Since that was an
; operation on the successful side, waiting to downcount the outstanding
; i/o on that device will not interfere with switching over other now-failed
; devices that are idle to the now-still-good side.
;
850$:	CLRL	UCB$L_PTHERRS(R4)	;Zero count of successive errors
50$:
; I/O was a success and not from MV, so clear the primary's MV indicator
	.if	eq,1	;don't mess with this here
	BICL	#<UCB$M_MNTVERIP!UCB$M_MNTVERPND>,UCB$L_STS(R5) ;Clear MV bits
	.endc
652$:
	.PRESERVE ATOMICITY
	DECL	UCB$L_OUTSTND(R4)	;Bump outstanding I/O count
	BGEQ	51$
	CLRL	UCB$L_OUTSTND(R4)	;make sure it doesn't go neg...
51$:
	.NOPRESERVE ATOMICITY
; R5 still is the host device UCB.
; Since we unbusy the main device when sending to the alternate path
; we need not unbusy again here, and should not. On the main path
; or within the secondary driver, the internal REQCOM logic will have
; unbusied those UCBs by now themselves, (or will do so when we return
; from here and the IRP posts and runs their intercept, if there's a
; pseudodriver under us.)
;
; Replace IRP status since we'll post it now.
	MOVL	R0,IRP$L_MEDIA(R3)
	movl	r1,IRP$L_MEDIA+4(r3)
	.iif df,m$$trp,movl #25,ucb$l_mtrp(r4)
	.if	df,x$$$dt
	cmpl	#^xb00bface,irp$q_qio_p6(r3)
	bneq	1131$		; if boobytrap not seen skip
	jsb g^ini$brk
1131$:
	movl	#^xb00bface,irp$q_qio_p6(r3)
	.endc
	PUSHL	R5		; UCB of primary path (used to bump i/o cnt)
	PUSHL	R3		; IRP
	CALLS	#2,G^COM_STD$POST	;COM$POST REPLACEMENT
	BRW	2000$
55$:	;MV should start so just finish off
	POPR	#^M<R0,R1,R2,R3,R4,R5>
	BRW	2000$

; get to 1000$ if this is a packack completion, which we should handle with
; com$post only (in the context of the main path driver!)
; Also come here for any other MV operations.
1000$:

; Here complete the IRP with the original device context so mount verify
; sees the I/O as coming to and from the main path disk.
;
; In fact we will handle this I/O with REQCOM. This is what the mount
; verify path will expect.
;
;	.iif	df,x$$$dt, jsb g^ini$brk  ;**************** debug *********
; At this point R4 points at the SWdriver UCB still.
	PUSHL	R0
	PUSHL	R1
1900$:
; Since we bump the outstanding count before dispatching to start, we
; must decrement it here also.
	.PRESERVE ATOMICITY
	DECL	UCB$L_OUTSTND(R4)	;Bump outstanding I/O count
	BGEQ	951$
	CLRL	UCB$L_OUTSTND(R4)	;make sure it doesn't go neg...
951$:
	.NOPRESERVE ATOMICITY
	.iif df,m$$trp,movl #26,ucb$l_mtrp(r4)
	MOVQ	IRP$L_MEDIA(R3),R0	;get final status

	.if	df,x$$$dt
	cmpl	r5,ucb$l_hstucb(r4)	;check this IS the principal path ucb
	beql	1904$
; Hit a breakpoint if UCB is wrong AND save a pointer so we know if we hit
	.iif df,m$$$trp,movl r5,ucb$l_mtrp+52(r4)
	jsb g^ini$brk
	movl	ucb$l_hstucb(r4),r5	; force it right so we can proceed too!
1904$:
	.endc

	movl	r3,ucb$l_irp(r5)	; Be sure REQCOM can find the IRP

	.if	df,x$$$dt
	cmpl	#^xb00bface,irp$q_qio_p6(r3)
	bneq	1231$		; if boobytrap not seen skip
	jsb g^ini$brk
1231$:
	movl	#^xb00bface,irp$q_qio_p6(r3)
	.endc
;
; At this point the underlying layer has posted the IRP and we got hold
; of it. However, we don't know if the underlying layer did com$post,
; reqcom, or whatever. All we know is that we have the IRP, so we
; really have no right to mess with the underlying device busy bit.
; If the device is free it won't matter, but if it is busy, we can
; screw it up.
	.if	df,d$$bug
	tstl	inmve
	beql	715$
; if both are set break once only
	tstl	doxdt
	bneq	715$
	.iif	df,x$$$dt, jsb g^ini$brk
715$:
	tstl	doxdt
	beql	719$
	.iif	df,x$$$dt, jsb g^ini$brk
719$:
	.endc
	call_post
;	CALL_REQCOM			;let REQCOM do it
;	.iif	df,x$$$dt, jsb g^ini$brk  ;**************** debug *********
	POPL	R1
	POPL	R0
2000$:
	POPL	R5
	POPL	R4
; Give back host device's forklock
        FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,-
         CONDITION=RESTORE,PRESERVE=YES
	MOVL	#SS$_NORMAL,R0
	MOVL	R4,R5		;we use pseudo dvc devicelock,not target's
;(SP) is IPL
; 4(sp) is old sts at this point.
	BBS	#IRP$V_FINIPL8,4(SP),219$
	DEVICEUNLOCK NEWIPL=(SP)+,PRESERVE=YES
	brb	1219$
219$:
	movab	4(sp),sp
1219$:
; Following gets rid of the IRP$L_STS copy on the stack.
	MOVAB	4(SP),SP		;add a long to stack pointer
	.if	df,d$$bug
	tstl	inmve
	beql	717$
	incl	inmve
	cmpl	inmve,#3	;don't keep up forever
	blss	718$
	clrl	inmve
	clrl	doxdt
718$:
	.iif	df,x$$$dt, jsb g^ini$brk
717$:
	.endc

	MOVL	UCB$L_HSTUCB(R4),R5	;note SW ucb still in r4
; Returning with R5 pointing at the host means the fork exit gets to
; clean up using the correct forkblock; ditto whatever else...
;
;	.iif	df,x$$$dt, jsb g^ini$brk  ;**************** debug *********
	RSB	; GET BACK TO HOST SOMETIME
60$:
; An alternate success is also not an error
	CLRL	UCB$L_PTHERRS(R4)	;Zero count of successive errors
; Now send a message (if we can) to our swap-daemon so it can provide
; swaps of other stuff if it needs to.
	bicl	#16384,r0	; clear the extra bit
	PUSHR	#^M<R0,R1,R2,R3,R4,R5,R6>
;check daemon pid still valid. If it is not, we obviously cannot send a
; message to it via the mailbox. If we cannot find it, zero the flag
; so we cannot even look for it again.
        TSTL    UCB$L_DAEMON(R5)        ;Any daemon process to look for?
	BEQLW	30$			;if not, skip
        PUSHR  #^M<R6,R7,R8>
        MOVZWL  G^SCH$GL_MAXPIX,R7      ;max process index in VMS
422$:
        MOVL    G^SCH$GL_PCBVEC,R6      ;get PCB vector address
        MOVL    (R6)[R7],R8             ;get a PCB address
        TSTL    R8              ;system address should be < 0
        BGEQ    23$                     ;if it seems not to be a pcb forget it
        CMPL    UCB$L_DAEMON(R5),PCB$L_PID(R8)  ;this our process?
        BEQL    321$                    ;if so, jump out of loop
23$:    SOBGTR  R7,422$                  ;if not, look at next
        CLRL    UCB$L_DAEMON(R5)        ;if cannot find process, zero our flag
321$:
        POPR   #^M<R6,R7,R8>
        TSTL    UCB$L_DAEMON(R5)        ;got our daemon process there?
	BEQLW	30$			;if not, skip
MBX.SIZ=48	;(use next higher quadword size)
ALO.SIZ=60
; We grab a little more stack than needed for the buffer so we can align it
	MOVL	SP,R4		; Point R4 at the buffer allocated
	SUBL	#ALO.SIZ,SP	;"allocate" a buffer
	SUBL	#MBX.SIZ,R4
	BICL	#7,R4		; align the buffer on 8 byte bdy
;
; Buffer format:
;
; Fill in the message now as we need it.
; 0/1 flag for alt or direct device failed
; UCB address of failed device
; unit number of failed device
; allocation class of failed device
; counted device name (16 bytes in all)
	PUSHR	#^M<R0,R1,R2,R4>
	MOVL	UCB$L_HSTUCB(R5),R0	;get host ucb
	MOVL	UCB$L_INDRCT(R5),(R4)+	;Tell daemon which unit...
	BEQL	24$			;if we just left direct mode
	TSTL	UCB$L_ALTHOST(R5)
	BGEQ	24$			;don't use a null althost address
	MOVL	UCB$L_ALTHOST(R5),R0	;get ucb of alt. device
24$:	MOVL	R0,(R4)+		;pass ucb of failed device
	MOVL	UCB$L_DDB(R0),R1	;Point at the DDB
	MOVZWL	UCB$W_UNIT(R0),(R4)+	;send unit number
	MOVL	DDB$L_ALLOCLS(R1),(R4)+
	MOVAB	DDB$B_NAME_LEN(R1),R2	;point at device name string
	MOVL	(R2)+,(R4)+		;fill in device name
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	IRP$L_MEDIA(R3),(R4)+	;set a code that says we came from finish
	MOVL	DDB$L_SB(R1),R2		;GET SB of the node
	MOVAB	SB$T_NODENAME(R2),R2	;POINT at nodename
	MOVL	(R2)+,(R4)+		;nodename is 16 chars, count first
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	MOVL	(R2)+,(R4)+
	POPR	#^M<R0,R1,R2,R4>
	MOVL	#MBX.SIZ,R3	; r3 gets size
	MOVL	UCB$L_MBXUCB(R5),R5 ; ucb of mailbox
	BGEQ	46$		; if zero or + forget the write attempt
;ensure mailbox is not deleted and still connected to by server.
; At process deletion, the host process may be blown away before the
; device is dismounted. Since the host process has the only known
; channel to that mailbox, cleaning that channel can mean the
; ucb is no longer valid. Do some extra checks here to make certain
; this cannot happen. Also, if we see the mailbox unref'd or
; not online, clear OUR ref to it so we won't be fooled by
; later reuse of the memory. Note that earlier we ensured the
; server process still exists on this machine.
	CMPB	#DYN$C_UCB,UCB$B_TYPE(R5)
	BNEQ	46$		; Be sure this IS a UCB
	BITL	#UCB$M_ONLINE,UCB$L_STS(R5)	;ucb marked online?
	BEQL	46$		;if not marked online don't try a write
	TSTL	UCB$L_REFC(R5)	;is the UCB referenced by someone?
			;host process should have a channel open to the
			;mailbox before we get to it. If it does not,`
			;then we must NOT use it.
	BLEQ	46$		;no refs means it might be deleted so
				;don't write to it. This is mainly a
				;problem during process deletion.
				; also disallow any stray negative counts
				; in case somethign messed up.
	TSTL	UCB$L_ORB(R5)	;finally ensure nonzero orb addr
	BGEQ	46$		;if zero, can't use either.
	BBC	#DEV$V_MBX,UCB$L_DEVCHAR(R5),46$ ; If not a mailbox, reject it too
	cmpb	#DC$_Mailbox,ucb$b_devclass(r5)
	bneq	46$		; be sure it's a mailbox too
; Here send a message to the daemon and continue. It appears the mailbox
; is ok.
	CALL_WRTMAILBOX SAVE_R1=yes
46$:
	ADDL2	#ALO.SIZ,SP		;"deallocate" the space we grabbed
30$:
	POPR	#^M<R0,R1,R2,R3,R4,R5,R6>
	MOVL	#SS$_NORMAL,IRP$L_MEDIA(r3)	;restore success
	BRW	50$
SW_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.END
