;
; Production, Alpha version of SWCTL, the control program and server
; for multipath switching. This part is left in MACRO due to its
; somewhat intimate working with SWDRIVER (and due to schedule tightness)
;
lvdsk=0 ;no "no-assign" bit..testing only condition
m$$trp=0
;Learn if this is being built for Alpha or not.
EVAX=1
.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
	.TITLE	SWCtl	;control SWdriver
	.IDENT	'V002'
; By Glenn C. Everhart
; axp step 2 (64 bit capable) version
;
; FACILITY:
; 
;
;   This program takes a command of form
; SWctl/flags/share:SWunit SWan: maindisk: altdisk: [altdisk2: [altdisk3:]]
;  where SWAn: refers to a local unit of SWdriver, which is an intercept
;  driver furnished to allow path failover from maindisk: to altdisk:
;  where both of these are visible at the machine in some fashion.
;
; Up to 8 paths will be supported for now. A single path is all that
; will be required initially.
;
; The idea is that SWdriver will then do a fast reroute of I/O from the
; initial-path disk (maindisk:) to the secondary path disk (altdisk:)
; if the path to maindisk: fails, or back if the path to the secondary
; disk fails. This image will set up the connections, and the first time
; it is run it will also set up a mailbox and begin listining for 
; communication from its monitored driver units about path failure
; events, so that it can respond appropriately. The SWdriver will get
; I/O functions to switch "sides" of its I/O built into it so that
; this program can carry out needed maintenance based on user configuration
; information.
;
; The basic underlying operation then will recognize mount verify (and
; some custom conditions) and use it to switch sides.
;
; This program will search for multiple UCBs if passed two identical 
; device specs, using techniques that won't be tricked by the normal
; code in ioc$searchdev, and if it finds them it will use the one
; found by normal ioc$searchdev as the primary, the other as the alternate.
; This will be detected by the UCB addresses being the same. If the UCBs
; found by searchdev are the same and no alternate can be found, the
; SW device will not be enabled nor connected and an error will be returned.
; If alternate UCBs are found however, or if the device UCBs passed in are
; different, the same hiding technique used by dutusubs will be used to
; hide the alternate device so normal VMS will not see it, so long as
; the device being hidden is idle. If it is not, swdriver will not be
; connected, since the presumption is that the primary device must be the
; one initially known to the rest of VMS. (Similar recognition code
; in dkdriver will test likewise when it starts.) An override will be
; permitted if command line specs say so, but it should be used with
; great caution.
;
; Note: define VMS$V5 to build for Version 5.x of VMS & later
VMS$V5=1
;
; 
; AUTHOR:
; 
; G. EVERHART
;
; 04-Aug-1989	D. HITTNER	Cleaned up definitions, added messages
; 29-Aug-1989   G. Everhart	Added more flexible device geometry selection
;--
	.PAGE
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

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

	$dyndef
	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$ATRDEF
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$DCDEF				;DEFINE DEVICE CLASS
	$DDBDEF				;DEFINE DEVICE DATA BLOCK
	$DDTDEF				;DEFINE DRIVER DISPATCH TBL
	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS
	$DPTDEF				;DEFINE DRIVER PROLOGUE TABLE
	$DVIDEF				;Symbols for $GETDVI service.
	$EMBDEF				;DEFINE ERROR MESSAGE BUFFER
	$FABDEF
	$FATDEF
	$FIBDEF				;Symbols for file information block.
	$IDBDEF				;DEFINE INTERRUPT DATA BLOCK
	$IODEF				;DEFINE I/O FUNCTION CODES
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$NAMDEF
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$RMSDEF
	$SBDEF
	$SCSDEF
	$pcbdef
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$STSDEF				;Symbols for returned status.
	$TPADEF				;Symbols for LIB$TPARSE calls.
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$XABDEF

; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; DEFINE THESE SO WE KNOW WHERE IN THE UCB TO ACCESS. WE MUST
; SET THE ONLINE BIT OR CLEAR IT, AND ALSO SET
; UCB$HUCB (HOST UCB ADDRESS), UCB$HFSZ (HOST FILE SIZE),
; AND UCB$HLBN (HOST LOGICAL BLOCK NUMBER OF FILE START)
;
	$FDT_CONTEXTDEF
	$FDTDEF
; Local parameters
AUXUCBCT	=	8

	$DEFINI UCB
.=UCB$K_LCL_DISK_LENGTH	;v4 def end of ucb
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
; Leave thisfield first so we can know all diskswill have it at the
; same offset.
;
;
; align to quadword
	.=<<.+7>& ^C7>          ; Align data structure
	.if df,m$$trp
$def	ucb$l_mtrp	.blkl	16
	.endc
;
; We REQUIRE for correctness that our consumer drivers all call the
; driver start entry via ddt$ps_start2 for every IRP in the input
; queue. This means dkdriver must be fixed!
;
; 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.
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

; 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
$DEF	UCB$K_SW_LEN	.BLKL	1	;LENGTH OF UCB
;UCB$K_SW_LEN=.				;LENGTH OF UCB
	$DEFEND	UCB			;END OF UCB DEFINITONS
;
; Macro to check return status of system calls.
;
	.MACRO	ON_ERR	THERE,?HERE
	BLBS	R0,HERE
	BRW	THERE
HERE:	.ENDM	ON_ERR

;
;
;
	.PSECT	ADVDD_DATA,RD,WRT,NOEXE,LONG

;
; KERNEL ARG LIST
K_ARG:
	.LONG	4			;4 ARGS: sw dvc, mainhost, althost
	.ADDRESS	DEV_BUF_DESC
	.ADDRESS	VDV_BUF_DESC
	.ADDRESS	AHS_BUF_DESC
	.ADDRESS	MBX_BUF_DESC
; getucb argument list
k_arg1	.long	2			; Arg for looking up a UCB
	.ADDRESS	AHS_BUF_DESC	; Secondary buffer descriptor
	.ADDRESS	AHSUCB		; UCB return address
DEFAULT_DEVICE:
	.ASCID	/SYS$DISK/

	.ALIGN LONG

; Block of information we can pass to SWdriver to set up all the
; switch characteristics we need

BASH_INFO:
; Misc data used during init
VPID:	.BLKL	1		; Owner of SW device (if any).
mPID:	.BLKL	1               ; Owner of MBXice (if any).
VUNIT:	.BLKL	1		; ascii UNIT NUMBER swaN
; Number of aux paths sent to the server
Numaux:	.long	0
; UCB storage for UCBs we keep around.

SWUCB: .LONG 0		;LOCAL SW device UCB
MBXUCB:	.LONG	0
; The following are UCBs for each device
HSTUCB:	.LONG	0	;SERVED UCB ADDRESS
; Space to hold secondary paths (+ extra for safety)
AHSUCB:	.LONG	0
	.BLKL	AUXUCBCT
; channel numbers

my_chnls:
SWCHN:	.LONG	0	;CHANNEL HOLDERS
MBCHN:  .long   0       ; channel for mailbox
; Channel to "primary path"
PrimPthChn:	.LONG	0
; Channels to aux paths
AHCHN:	.LONG	0
	.BLKL	AUXUCBCT
AHCHNW:	.LONG	0	;working store for channel

BASH_INFO_LENGTH = . - BASH_INFO


IOSB:	.LONG	0,0
IOSTATUS: .BLKQ 1
BUFG:	.LONG	1		;BASH FLAG
	.LONG	1000		;
DEV_BUF:			; Buffer to hold device name.
	.BLKB	40
DEV_BUF_SIZ = . - DEV_BUF
BUSZ=.-BUFG
DEV_BUF_DESC:
	.LONG	DEV_BUF_SIZ
	.ADDRESS	DEV_BUF
AHSNM:	.WORD	 255.	;LENGTH
	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
	.ADDRESS	ahs_buf
AHS_BUF_DESC:			; Descriptor pointing to device name.
	.LONG	 AHS_BUF_SIZ
	.ADDRESS AHS_BUF
AHS_BUF:			; Buffer to hold device name.
	.BLKB	40
AHS_BUF_SIZ = . - AHS_BUF

MBX_ITEM_LIST:                  ; MBXice list for $GETDVI.
        .WORD    MBX_BUF_SIZ    ; Make sure we a have a physical device name.
        .WORD    DVI$_DEVNAM
        .ADDRESS MBX_BUF
        .ADDRESS MBX_BUF_DESC
        .WORD    4              ; See if someone has this device allocated.
        .WORD    DVI$_PID
        .ADDRESS mPID
        .LONG    0
        .WORD    4
        .WORD    DVI$_DEVCLASS  ; Check for a terminal.
        .ADDRESS MBX_CLASS
        .LONG    0
        .LONG    0              ; End if item list.
MBX_BUF:                        ; Buffer to hold MBXice name.
        .BLKB   40
MBX_BUF_SIZ = . - MBX_BUF

MBX_BUF_DESC:                   ; Descriptor pointing to MBXice name.
        .LONG    MBX_BUF_SIZ
        .ADDRESS MBX_BUF

mbx_class:
	.long	0
PID:				; Owner of device (if any).
	.BLKL	1

DEV_ITEM_LIST:			; Device list for $GETDVI.
	.WORD	 DEV_BUF_SIZ	; Make sure we a have a physical device name.
	.WORD	 DVI$_DEVNAM
	.ADDRESS DEV_BUF
	.ADDRESS DEV_BUF_DESC
	.WORD	 4		; See if someone has this device allocated.
	.WORD	 DVI$_PID
	.ADDRESS PID
	.LONG	 0
	.WORD	 4
	.WORD	 DVI$_DEVCLASS	; Check for a terminal.
	.ADDRESS DEV_CLASS
	.LONG	 0
	.LONG	 0		; End if item list.

DEV_CLASS:
	.LONG	1
AHSPID:				; Owner of device (if any).
	.BLKL	1

; BUFFER FOR COPIES OF DRIVR DATA
BUFHDR:	.LONG	0,0,0,0,0
BUF:	.BLKL	8192.	; DATA AREA
	.LONG	0,0	;SAFETY BUFFERS

AHS_ITEM_LIST:			; Device list for $GETDVI.
	.WORD	 AHS_BUF_SIZ	; Make sure we a have a physical device name.
	.WORD	 DVI$_DEVNAM
	.ADDRESS AHS_BUF
	.ADDRESS AHS_BUF_DESC
	.WORD	 4		; See if someone has this device allocated.
	.WORD	 DVI$_PID
	.ADDRESS AHSPID
	.LONG	 0
	.WORD	 4
	.WORD	 DVI$_DEVCLASS	; Check for a terminal.
	.ADDRESS AHS_CLASS
	.LONG	 0
	.LONG	 0		; End if item list.

AHS_CLASS:
	.LONG	1
;**
VBUFG:	.LONG	2	;DEASSIGN BASH FLAG. dEASSIGN VICTIM DVC, NOT sw: DVC.
	.LONG	1000
VDV_BUF:			; Buffer to hold VDVice name.
	.BLKB	40
VDV_BUF_SIZ = . - VDV_BUF
VBUSZ=.-VBUFG
VDV_BUF_DESC:			; Descriptor pointing to VDVice name.
	.LONG	 VDV_BUF_SIZ
	.ADDRESS VDV_BUF


VDV_ITEM_LIST:			; VDVice list for $GETDVI.
	.WORD	 VDV_BUF_SIZ	; Make sure we a have a physical device name.
	.WORD	 DVI$_DEVNAM
	.ADDRESS VDV_BUF
	.ADDRESS VDV_BUF_DESC
	.WORD	 4		; See if someone has this device allocated.
	.WORD	 DVI$_PID
	.ADDRESS VPID
	.LONG	 0
	.WORD	 4
	.WORD	 DVI$_DEVCLASS	; Check for a terminal.
	.ADDRESS VDV_CLASS
	.long	0
	.word	4
	.word	DVI$_UNIT
	.address vunit
	.LONG	 0
	.LONG	 0		; End if item list.

VDV_CLASS:
	.LONG	1
;**
DEFNAM:

WRK:	.BLKL	1	;SCRATCH INTEGER
; DESCRIPTOR FOR SWAn: "FILENAME"
	.ALIGN LONG
SWFNM:	.WORD	 255.	;LENGTH
VDFTP:	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
	.ADDRESS	SWFNMD
SWFNMD:	.BLKB	256.	; DATA AREA
	.ALIGN LONG
WRKSTR:	.WORD	20	;LENGTH
	.BYTE	DSC$K_DTYPE_T	;TEXT
	.BYTE	1	;STATIC
	.ADDRESS	WRKDAT
WRKDAT:	.BLKB	20
	.BYTE	0,0,0,0	;SAFETY
;
; command line input descriptor etc.
	.align long
dcl_cmd:        .ASCID  /SWCTL /
cmdbuff: .word 0		;longword align things
	.BLKL   256             ;This buffer must be contig with dcl_cmd
        .ALIGN  LONG
cmdline:        .WORD   0
                .BYTE   DSC$K_DTYPE_T
                .BYTE   DSC$K_CLASS_D
                .ADDRESS 0
        .EXTRN  ASNVD_CLD
        .ALIGN LONG
; DESCRIPTOR FOR NODE$FWAN: DEVICE NAME
	.ALIGN LONG
DDFNM:	.WORD	 255.	;LENGTH
DDFTP:	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
DDFNA:	.ADDRESS	DDFNMD
DDFNMD:	.BLKB	256.	; DATA AREA
AHFNM:	.WORD	 255.	;LENGTH
AHFTP:	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
AHFNA:	.ADDRESS	AHFNMD
AHFNMD:	.BLKB	256.	; DATA AREA
;
; Pointer array to strings so we can index conveniently
pptrs:
	.address	p3dsc
	.address	p4dsc
	.address	p5dsc
	.address	p6dsc
	.ADDRESS	P7DSC
	.ADDRESS	P8DSC
	.ADDRESS	P9DSC
	.ADDRESS	P10DSC
	.ADDRESS	P11DSC
 ASSUME AUXUCBCT LT 9
;
; Strings needed to parse command line qualifiers
;

P1DSC:	.ASCID	/UNIT/
P2DSC:	.ASCID	/FNAM/
P3DSC:	.ASCID	/ALTHST/
P4DSC:	.ASCID	/AHS2/
P5DSC:	.ASCID	/AHS3/
P6DSC:	.ASCID	/AHS4/
P7DSC:	.ASCID	/AHS5/
P8DSC:	.ASCID	/AHS6/
P9DSC:	.ASCID	/AHS7/
P10DSC:	.ASCID	/AHS8/
P11DSC:	.ASCID	/AHS9/

DEADS:	.ASCID	/DEASSIGN/	;deassign SW: from disk (turn off)
STPDS:	.ASCID	/SETUP/	;retry errors on r/w
SHRDSC:	.ASCID	/SHARE/	;share with other SW unit (use same daemon, do NOT
			; run this image as a new daemon)
SRVDS:	.ASCID	/SERVE/	;i.e., just fire up the driver hookup
OVRDS:	.ASCID	/OVERRIDE/
	.ALIGN LONG
; ucb DATA AREA
ovrfg:	.long	0
srvfg:	.long	0	;1 if no serve set
SHRFG:	.LONG	0	;Flags sharing another SW unit's server
STPFG:	.LONG	0	;flag we're setting up, as opposed to tearing down,
			;a connection
DEAFG:	.LONG	0
;
;
ERROR:	.LONG	2
MESS:	.LONG	SS$_ABORT
	.LONG	0


; 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
;
	.PSECT	ADVDD_CODE,RD,NOWRT,EXE,LONG
; Entry to take a command line and set up switching code.
;
; link with swctl.cld's object file.
; Take one argument, a cmd line descriptor.
	.ENTRY	SWCTL,^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>
; First check for /Deassign, which is used to return the primary path
; to normal and disconnect all other paths from use, taking the switching
; driver out of an I/O pathway
;
;
; First parse the command line.
	movl	4(ap),r1	;address of input command line
	bneq	21$
; Foo, no command line. Darn if I know what to emit.
19$:	movl	#ss$_baditmcod,r0
	ret			;if none, return
21$:
	pushab	cmdline		;output of cmd line
	pushaq	dcl_cmd		;prepend "SWCTL "
	pushl	R1		;input command line
	CALLS   #3,G^STR$CONCAT	; concatinate
	pushal	swctl_cld	;DCL module
	pushab	cmdline		;and our command
	calls	#2,g^CLI$DCL_PARSE
	blbc	r0,19$		; if we fail, return now. Do no mischief.
	CLRL	DEAFG	;NOT DEASSIGN
	PUSHAB	DEADS
	CALLS	#1,G^CLI$PRESENT
	CMPL	R0,#CLI$_PRESENT	;THERE?
	BNEQ	100$
	INCL	DEAFG
100$:
;
; /SETUP is expected and needed when establishing a switched path connection.
; 
	CLRL	STPFG
	PUSHAB	STPDS			;/SETUP?
	CALLS	#1,G^CLI$PRESENT
	CMPL	R0,#CLI$_PRESENT	;THERE?
	BNEQ	1001$
	INCL	STPFG
1001$:
	clrl	ovrfg
;
; /OVERRIDE tells the program to go ahead and set up switching even if
; the device being switched is mounted or active.
;
	PUSHAB	OVRDS			;/OVERRIDE
	CALLS	#1,G^CLI$PRESENT
	CMPL	R0,#CLI$_PRESENT	;THERE?
	bneq	40$
	incl	ovrfg			;override ok on alt. dsk activity
40$:
;
; /SERVE is specified when we want this server to stay active instead of
; merely exiting after setting up a device pairing. If the server is not
; active, the driver is able to switch paths when mount verify occurs on
; its own, but it has no more intelligence than that.
;
	CLRL	SRVFG			;SERVED NOT OK
	PUSHAB	SRVDS			;/SERVED SPEC'D?
        CALLS   #1,G^CLI$PRESENT
        CMPL    R0,#CLI$_PRESENT        ;THERE?
        BNEQ    1004$
	incl	srvfg		; serving ok
1004$:
;
; Get the SWAn: pseudodevice specification and any remaining devices
; from the command line: primary path, and any secondary paths.
;
	PUSHAB	WRK		;PUSH LONGWORD ADDR FOR RETLENGTH
	PUSHAB	SWFNM		;ADDRESS OF DESCRIPTOR TO RETURN
	PUSHAB	P1DSC		; GET P1 (FDn: UNIT)
	CALLS	#3,G^CLI$GET_VALUE	;GET VALUE OF NAME TO SWFNM
	ON_ERR	ADVDD_EXIT
	PUSHAB	WRK			; GET 2ND FILE (served unit)
	PUSHAB	DDFNM			; & ITS DESCRIPTOR
	PUSHAB	P2DSC			; & PARAMETER NAME 'P2'
	CALLS	#3,G^CLI$GET_VALUE	; GET FNM
	ON_ERR	ADVDD_EXIT
; Be sure at least one secondary path exists
	PUSHAB	WRK		;ret len
	PUSHAB	AHFNM		;filename (device name, actually)
	PUSHAB	P3DSC		;get alt host value
	CALLS	#3,G^CLI$GET_VALUE
	ON_ERR	ADVDD_EXIT

; Get a channel to the primary path

	$ASSIGN_S -			; Get a channel to the 
		DEVNAM=DDFNM,-		; device for host file
		CHAN=PrimPthChn
	ON_ERR	ADVDD_EXIT
; LET ERRORS BY FOR THIS SINCE WE GET OUR INFO VIA OPEN ANYWAY SO
; CHANNEL REALLY DOESN'T HAVE TO BE THERE.
; Get the physical device name, and see if this device has an owner.
; (We must do this so we can get the host UCB address)
;
; Fill in info about real device name
	$GETDVI_S -
		CHAN=PrimPthChn,-		; Command line has device name.
		ITMLST=DEV_ITEM_LIST
	BLBS	R0,740$
	BRW	ADVDD_EXIT
740$:
; Get the primary path UCB
	movab	dev_buf_desc,k_arg1+4		; Get channel info for prime path
	movab	hstucb,k_arg1+8
	$CMKRNL_S ROUTIN=GETUCB,ARGLST=K_ARG1
290$:
; MUST HAVE ASSIGNMENT TO SW: UNIT IN ANY CASE.
	$ASSIGN_S -
		DEVNAM=SWFNM,-		; GET CHANNEL FOR SWn:
		CHAN=SWCHN
	ON_ERR	ADVDD_EXIT		; SKIP OUT IF ERROR
	$GETDVI_S -
		CHAN=SWCHN,-		; Command line has device name.
		ITMLST=VDV_ITEM_LIST
	BLBS	R0,140$
	BRW	ADVDD_EXIT
140$:
	MOVAB	VDV_BUF_DESC,K_ARG1+4		; Get info on SW device full name & unit
	MOVAB	SWUCB,K_ARG1+8
	$CMKRNL_S ROUTIN=GETUCB,ARGLST=K_ARG1
; 
; Loop over all secondary paths here and fill in UCBs etc. for them all
; However allow none to be there, since they may get added later.
;
	CLRL	R11		; Use R11 as loop counter
142$:
	PUSHAB	WRK		;ret len
	PUSHAB	AHFNM		;filename (device name, actually)
	PUSHL	PPDSC[R11]	; GET the secondary path now
	CALLS	#3,G^CLI$GET_VALUE
	ON_ERR	143$

	$ASSIGN_S DEVNAM=AHFNM,CHAN=AHCHNW	;get a channel to alt host
	ON_ERR	143$		;(must be careful here. If dvc offline
					; this may fail so we must revisit this
					; part of the design)
	$GETDVI_S -
		CHAN=AHCHNW, ITMLST=AHS_ITEM_LIST
	ON_ERR	143$
	MOVL	AHCHNW,AHCHN[R11]
; Get the UCB now

	MOVAB	AHS_BUF_DESC,K_ARG1+4		; Get info on SW device full name & unit
	MOVAL	AHSUCB[R11],K_ARG1+8
	$CMKRNL_S ROUTIN=GETUCB,ARGLST=K_ARG1
	TSTL	AHSUCB[R11]			; Did we find a valid UCB?
	BGEQ	143$				; If not don't count it
	INCL	R11				; Count up paths tried
	MOVL	R11,NUMAUX			; Update count of secondary paths
	CMPL	R11,#AUXUCBCT			; If not too many go on
	BGEQ	143$				; Otherwise skip out
	BRW	142$
143$:
;
; By this time, we have a list of UCBs and of channels to the auxiliary
; paths.
; This gets info for all n parameters.
;
; Now we also need to create a mailbox if not sharing with unit 0. For
; simplicity just let it be that unit 0 is the servr and others share
; its info.
	CMPL	VUNIT,#0		; is this unit 0?
	BNEQ	144$			; if not, skip MBX create
; This unit will do the serving.
	$CREMBX_S PRMFLG=#0,CHAN=MBCHN,MAXMSG=#576,BUFQUO=#5760,-
		PROMSK=#0
	ON_ERR	ADVDD_EXIT
;now need the info on mailbox name we just made up
	$GETDVI_S -
		CHAN=MBCHN,-    ; Command line has device name.
		ITMLST=MBX_ITEM_LIST
	ON_ERR	ADVDD_eXIT
144$:
	MOVAB	MBX_BUF_DESC,K_ARG1+4		; Get info on SW device full name & unit
	MOVAB	MBXUCB,K_ARG1+8
	$CMKRNL_S ROUTIN=GETUCB,ARGLST=K_ARG1
; Here do the real work in kernel mode, having now the device
; descriptions and channels to the devces even!
	$CMKRNL_S -
		ROUTIN=BASHUCB,ARGLST=K_ARG
	CMPL	R0,#SS$_NORMAL				;Any errors?
	BEQL	300$					;No, skip error routine
	MOVL	R0,MESS					;Move error to message
;;;	BRW	300$
301$:
; ERROR RETURN ... CLOSE FAB & LEAVE
	$PUTMSG_S	MSGVEC=ERROR			;Pump out error message
; deassign logic
307$:	MOVL	#2,BUFG	;unmung fcn
	$QIOW_S CHAN=SWCHN,EFN=#4,FUNC=#<IO$_FORMAT+128>,IOSB=IOSB,-
	P1=BUFG,P2=#BUSZ
; after unbashing the current host, take the SW unit offline
; Now deassign channels
	addl3	numaux,#2,r11		; total channels
305$:	movl	my_chnls[r11],r10
	$dassgn_s chan=r10
	sobgtr	r11,305$
	RET
300$:
; At this point we still have valid channels to all the devices.
; However, we're going to have a server here that will handle MANY
; MANY tuples of devices, so we can't leave them around.
; So we'll close the channels all after connect processing is
; done, to avoid running out.
;
	TSTL	DEAFG		;if /deas, do $qio, then knl work
	BNEQ	307$
; Since that worked OK, send the format function to the SW unit to
; finish bashing the host disk.
	MOVL	#1,BUFG		;SET TO BASH DEVICE
	$QIOW_S CHAN=SWCHN,EFN=#4,FUNC=#<IO$_FORMAT+128>,IOSB=IOSB,-
	P1=BUFG,P2=#BUSZ
; Now if this is unit 0, start reading the mailbox.
	CMPL	VUNIT,#0		; is this unit 0?
	BNEQ	X303$			; if not, skip MBX create
	TSTL	SRVFG			; /SERVE NOT COMMANDED?
	BEQL	X303$			; IF SO THEN SKIP LOOP TOO
; ok, we need to run as a server,so set up to do so.
	CALLS	#0,G^GETCONFIG
; Now deassign channels
;my_chnls & numaux
	addl3	numaux,#2,r11		; total channels
308$:	movl	my_chnls[r11],r10
	$dassgn_s chan=r10
	sobgtr	r11,308$
EVTLOOP:
; Read the mailbox to get our data
; Use QIOW$ to assure that we don't do anything until there is work.
; (this also avoids having to use internal routines to control
;  host execution.)
;
; Note that we get these messages from the switch driver as "advisory"
; messages, and they are not needed nor are used for I/O synchronization.
; In order to synchronize I/O, we issue calls to swdriver instead (via SWCHN)
; to switch channels. For simplicity these function calls are done in two
; routines, getconfig and procmsg, which can and should be written in a
; higher order language to make them easier to understand. They will,
; respectively, read in the configuration information so they know when
; a failover or failback comes in which devices "should go where", and 
; will handle messages for particular device messages. Messages of this
; kind come in only when failover or failback situations exist, so the
; traffic rate of them should be quite low.
;
	$QIOW_S EFN=#10,CHAN=MBCHN,-
	IOSB=IOSB,FUNC=#IO$_READLBLK,P1=BUFHDR,P2=#56
	ON_ERR	ADVDD_EXIT	; SKIP OUT IF ERROR
; Now check on data
; If needed, switch other units.
; This is done in a routine so the bulk of the server's work can be in
; some other language than MACRO32. The mailbox channel will be passed
; also so that the routine can send itself commands to be done
; separately.
	pushab	MBCHN
	pushab	SWCHN		; pass channel to SW device
	pushab	bufhdr		; pass the message to our routine
	calls	#3,g^procmsg
	BRW	EVTLOOP

; BE SURE WE DON'T LEAVE THE CHANNELS ASSIGNED TO THE DEVICES
; EITHER...
X303$:
; Now deassign channels
	addl3	numaux,#2,r11		; total channels
305$:	movl	my_chnls[r11],r10
	$dassgn_s chan=r10
	sobgtr	r11,305$
	RET
	RET
advdd_exit:
; Now deassign channels
	addl3	numaux,#2,r11		; total channels
305$:	movl	my_chnls[r11],r10
	$dassgn_s chan=r10
	sobgtr	r11,305$
	RET

; Get a UCB entry
; Call passes a device descriptor and returns a UCB of the device or zero
; (so these can be passed to swdriver for final hook-up)
; The idea here is to keep all processing that is specific to the
; switching driver in that driver, so it must check its list of UCBs
; and set up its own structures.
; Call pushes a UCB return address and a string desc. address
;
; Call getucb(devicename-by-descriptor,ucb-address)
;
GETUCB:	.ENTRY,^m<r2,r3,r4,r5,r6,r7,r8,R9,R10,R11>
	CLRL	@8(AP)			;ZERO RETURN DATA FIRST
	JSB	G^SCH$IOLOCKW		;;; LOCK I/O DATABASE
	MOVL	4(AP),R1		;;; ADDRESS DVC NAME DESCRIPTORS (target)
	JSB	G^IOC$SEARCHDEV		;;; GET UCB ADDRESS INTO R1 for tgt
	BLBS	R0,1$
	MOVL	R1,@8(AP)		;;; Return the UCB if we found one OK
1$:
	JSB	G^SCH$IOUNLOCK		;;; UNLOCK I/O DATABASE (DROP IPL)
	MOVL	#SS$_NORMAL,R0
	RET

; BASHUCB - AREA TO MESS UP UCB WITH OUR FILE DATA
; BEWARE BEWARE BEWARE
;  runs in KERNEL mode ... HAS to be right.
;  Saves lots of registers so they're free...
	.ENTRY	BASHUCB,^M<R2,R3,R4,R5,R6,R7,R8,r9,r10,r11>
	MOVL	G^CTL$GL_PCB,R4		;;; NEED OUR PCB (VMS V5)
	CLRL	HSTUCB
	CLRL	MBXUCB
	JSB	G^SCH$IOLOCKW		;;; LOCK I/O DATABASE
	MOVL	4(AP),R1		;;; ADDRESS DVC NAME DESCRIPTORS (target)
	JSB	G^IOC$SEARCHDEV		;;; GET UCB ADDRESS INTO R1 for tgt
	BLBS	R0,660$
12$:	BRW	BSH_XIT
660$:
;
80$:
; Hstucb is the primary disk's ucb. Ahsucb is the secondary's UCB.
	MOVL	R1,HSTUCB		;;; SAVE HOST UCB ADDRESS
	MOVL	R1,R11			;use r11 for target UCB
	BEQL	166$			;;; ... BUT ZERO UCB ADDRESS LOOKS BAAAAD
	MOVL	12(AP),R1		;althost address
	JSB	G^IOC$SEARCHDEV
	BLBC	R0,12$			;if none, bug out
	MOVL	R1,AHSUCB		;else save the UCB address for later
	TSTL	DEAFG			;if deassigning, no need for mbx
	bneq	90$
; get mailbox and althost addresses
	CMPL	VUNIT,#0		; is this unit SWA0?
	BNEQ	90$			; if not, skip mbx lookup
	tstl	srvfg			; If not running a server don't save
	beql	90$			; the ucb.
	MOVL	16(AP),R1		; mailbox descriptor
	JSB	G^IOC$SEARCHDEV		; go look up
	BLBC	R0,12$
	MOVL	R1,MBXUCB		; save mailbox UCB if we got it
90$:
	MOVL	8(AP),R1		;;; ADDRESS SWn NAME DESCRIPTORS
	JSB	G^IOC$SEARCHDEV		;;; GET UCB ADDRESS INTO R1
	BLBS	R0,160$
93$:	BRW	BSH_XIT
160$:
	MOVL	R1,SWUCB		;;; store SW ucb
; Avoid problems by being sure this is really swdriver AND (later) that
; we really are intercepting a disk class device.
	CMPL	UCB$L_SANITY(R1),#^A/SWIT/	;Is this
					; really SWdriver?
	BEQL	162$
	BRW	1176$			; if not, do NOT continue
162$:
	MOVL	R1,R5			;use r5 for local ucb (SW dvc)
	BEQL	166$			;fail if no ucb...
; BUGGER THE UCB
; ASSUMES FILE LBN AND SIZE ALREADY RECORDED
; ALSO ASSUMES THAT ZERO LBN OR SIZE MEANS THIS ENTRY NEVER CALLED.
; (REALLY ONLY WORRY ABOUT ZERO SIZE; IF WE OVERMAP A REAL DEVICE
; THEN ZERO INITIAL LBN COULD BE OK.)
;
; CHECK REF COUNT FIRST... ONLY CAN GET AWAY WITH THIS ON DEVICE
; NOBODY'S USING...
; .. fake this since device may have count messed by advd somehow
; but will be allocated if mounted.
; ... for now ...
554$:
	BRB	164$	;(it doersn't matter if the local disk is in
			; use...we don't bother it.)
166$:	BRW	165$
164$:
; check that both UCBs are disk devices at least!
; We can't be sure all the device characteristics will be the
; same for the local device and the MSCP served remote one (and
; in fact they are not all alike!) but at least they had better
; both be disks or this function is not even approximately
; correct and will probably be quickly fatal to the system.
	TSTL	DEAFG	;/deas? r11 invalid.
	BEQL	1164$
; for deassign, must set SW offline so it can be turned on again
; but just do all work here & scram.
	CMPL	UCB$L_ICSIGN(R5),#MAGIC	;got right magic no.?
	BNEQ	1176$		;if not then not SWdriver
; clear online & valid on SW dvc for next time
	.IF	DF,EVAX
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)	;set SW unit online
	BICL	#UCB$M_VALID,UCB$L_STS(R5)	; & valid
	.IFF
	BICW	#UCB$M_ONLINE,UCB$W_STS(R5)	;set SW unit online
	BICW	#UCB$M_VALID,UCB$W_STS(R5)	; & valid
	.ENDC
	movl	ahsucb,r1		; get alt ucb
	bgeq	1166$			; if illegal leave alone
1166$:	MOVL	#1,R0
	BRW	BSH_XIT		;unlock & leave
1176$:	MOVL	#SS$_DRVERR,R0
	BRW	BSH_XIT
1164$:
; be sure target is a disk for now, to ensure the target UCB doesn't go
; away. Expand this list later if we want to protect tape, or other
; weird stuff.
	CMPB	UCB$B_DEVCLASS(R11),#DC$_DISK
	BNEQ	1176$			;if not disk exit now.
; be sure intercept driver is one of ours further (should be redundant)
	CMPL	UCB$L_ICSIGN(R5),#MAGIC	;got right magic no.?
	BNEQ	1176$		;if not then not SWdriver
; Be sure the SW unit is not online yet. If it is, someone else will
; be using its UCB so we don't want to screw this up.
	.IF	DF,EVAX
	BITL	#UCB$M_ONLINE,UCB$L_STS(R5)	;set SW unit online
	BNEQ	166$
	.IFF
	BITW	#UCB$M_ONLINE,UCB$W_STS(R5)	;set SW unit online
	BNEQ	166$
	.ENDC
; Looks like we're gonna do the assign. Store backpointer for driver to
; check before unmung.
	MOVL	R11,UCB$L_VICT(R5)		;store ucb of victim in SW ucb
; Now get on with the tricky part, replacing the DDT. Do this
; at device IPL so we have reasonable certainty nobody will mess with
; these structures until we get them all put into proper order.
; The DDT structure is 64 bytes long, so grab a block of pool of 64 bytes
; size and copy the existing DDT into it.
; (it is possible to save the old address if the conditional is used)
	.IF	DF,EVAX
	BISL	#UCB$M_ONLINE,UCB$L_STS(R5)	;set SW unit online
	BISL	#UCB$M_VALID,UCB$L_STS(R5)	; & valid
	.IFF
	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)	;set SW unit online
	BISW	#UCB$M_VALID,UCB$W_STS(R5)	; & valid
	.ENDC
; no need to copy geometry actually
	CLRL	UCB$L_CTLFLGS(R5)
; set NOT to retry at first
	TSTL	STPFG		;/setup?
	BEQL	61$		;if not branch
; Here we must see if primary and secondary are the same, as may happen if
; an MSCP path exists that has been hidden already.
	CMPL	HSTUCB,AHSUCB		;Are these two the same?
	BNEQ	84$			;if not, normal function
; check_extrapath should replace ahsucb if it finds one and ensure
; it is different. It will also crosslink the two if idle.
	MOVL	HSTUCB,R1
	CMPB	#DC$_DISK,UCB$B_DEVCLASS(R1) ;No crosslink unless disks
	BNEQ	84$
	BSBW	CHECK_EXTRAPATH		;go find alt path if any
84$:
; ucb addresses better differ now!
	CMPL	HSTUCB,AHSUCB		;Are these two the same?
	BNEQ	85$
	MOVL	#SS$_DUPLNAM,R0		;If the UCBs are the same complain
	BRW	BSH_XIT
85$:
	TSTL	AHSUCB		;do we even have an alt ucb?
	BGEQ	61$		;if no skip
	BISL	#^X100000,UCB$L_CTLFLGS(R5)	;say to allow switch
61$:
	MOVL	AHSUCB,UCB$L_ALTHOST(R5)
	MOVL	HSTUCB,UCB$L_HSTUCB(R5)
	PUSHL	R8
	MOVL	UCB$L_UCB0(R5),R8	;R8 = UCB 0
	BGEQ	10010$			;if illegal, lose...bad configuration
; We must be initialized unit 0 first!
	TSTW	UCB$W_UNIT(R5)	;IS THIS UNIT 0?
	BNEQ	81$
	MOVL	MBXUCB,UCB$L_MBXUCB(R5)	;STORE MAILBOX
	MOVL	G^CTL$GL_PCB,R4
	MOVL	PCB$L_PID(R4),UCB$L_DAEMON(R5)	;SAVE THIS DAEMON PID TOO
; Unit 0 doesn't have a unit 0 to get from...
	BRB	881$
81$:
	MOVL	UCB$L_MBXUCB(R8),UCB$L_MBXUCB(R5)	;get these in later UCBs
	MOVL	UCB$L_DAEMON(R8),UCB$L_DAEMON(R5)
881$:
;
; This arranges that the server is shared by all drivers that use this code.
; However, this server is only called into play rarely.
;
; If the primary is not valid and the secondary is, start off with the
; system pointing at the secondary.
	PUSHL	R7
	PUSHL	R8
	MOVL	HSTUCB,R7	; PRIMARY UCB
	MOVL	AHSUCB,R8	; SECONDARY UCB
	BGEQ	83$		; BE SURE A SECONDARY IS THERE
	CLRL	UCB$L_INDRCT(R5)
	BITL	#UCB$M_VALID,UCB$L_STS(R7)	; IS PRIMARY VALID?
	BNEQ	83$				; IF SO BRANCH, ALL WELL
	BITL	#UCB$M_VALID,UCB$L_STS(R8)	; PRIMARY INVALID.SECOND OK?
	BEQL	83$				; IF NOT LEAVE ALONE
	MOVL	#1,UCB$L_INDRCT(R5)		; ELSE SET TO START ON ALT PATH
83$:
	POPL	R8
	POPL	R7
; Now we must set the secondary device inaccessible.
	MOVL	AHSUCB,R8
; We need the volume to appear valid so packacks etc. will get through
; even though we don't want the volume to appear as a disk to anyone
; (so that it can't be mounted)
	.if	ndf,lvdsk
	BISL	#UCB$M_VALID,UCB$L_STS(R8)	;SET VOLUME VALID
; Set not a disk so that if we leave this device in a show device display
; it will not be picked up as a disk, or if a user goes through with
; DCL looking for disk class devices, the hidden name will not be
; found (or similarly with ioc$scan_iodb callers and the like)
;
	MOVB	#DC$_MISC,UCB$B_DEVCLASS(R8)	;SET NOT A DISK
	BISL	#UCB$M_NO_ASSIGN,UCB$L_STS(R8)	;HIDE THE DEVICE
	.endc
;
; To keep "show device" from seeing this device, we can set the CDP bit
; (if it is not set or if the alternate ucb is not set) and set the
; alternate UCB as the primary path. HOWEVER setting the no_assign bit
; above already does the same thing, so just leave things alone other
; than that.
	.if	df,x$link
	BSBW	INTERSWP			; Crosslink the UCBs
	.endc
10005$:
;
10010$:	POPL	R8
1000$:
165$:
	MOVL	#SS$_NORMAL,R0
BSH_XIT:
	PUSHL	R0
	JSB	G^SCH$IOUNLOCK		;;; UNLOCK I/O DATABASE (DROP IPL)
	POPL	R0			;;; REMEMBER R0
	RET	;;; BACK TO USER MODE NOW
;
;
; CHECK-EXTRAPATH
; 
; This routine is called in the server since it looks only in standard
; UCB areas. It presumes that the server holds the I/O mutex at the
; time of the call, and looks for another UCB acting as a served
; path to the currently being looked-at UCB.
;
; Looks for other local UCBs that have the same device name as the
; one we're working on. If found it will "hide" the other UCB
; but leave it usable by the switch code.
;
; Output: If another path is found, it will be marked CDP and 2P
; and cross-linked with this UCB so that this local path will
; be used instead of the other path. This will be done only for an
; apparently idle other path.
;
CHECK_EXTRAPATH: .JSB_ENTRY
	MOVL	HSTUCB,R5		; Get the primary's UCB
	MOVL	UCB$L_DDB(R5),R0	; GET THE DDB
	MOVL	DDB$L_ALLOCLS(R0),R8	; GET THE ALLOC CLASS
	BEQL	90$			; SKIP IF THIS WON'T TELL...
	MOVAB	DDB$T_NAME(R0),R7	; GET NAME INFO
	MOVZWL	UCB$W_UNIT(R5), R6	; GET UNIT NO.
; This will find a master unit if present. "Hidden" 2p units will not be
; seen always, so we look for that below.
	JSB	LOOKUP_LOCAL_UCB  
	TSTL	R0			; Was a local UCB found?
	BEQL	89$			; Branch if no local UCB found.
	TSTL	OVRFG			; If override spec'd omit idle test
	BNEQ	43$			; and link up anyhow.
	TSTL	UCB$L_REFC(R0)		; Is the other UCB in use?
	BNEQ	90$			; If so don't mess with it.
43$:
	MOVL	R0,AHSUCB		; Save the alternate UCB found
89$:
; Check if maybe the devices were interlinked already and we got the wrong
; one. Thus if a second UCB exists because MSCP started too, this will
; find that UCB and let us start allowing switches between the two.
;
	MOVL	AHSUCB,R0
	MOVL	HSTUCB,R5
	BEQL	90$
	CMPL	R0,R5			; If we found "ourselves" do not use
	BNEQ	90$			; If the devices are now separate, done
	BITL	#DEV$M_2P,UCB$L_DEVCHAR2(R5)	; device marked 2 path?
	BEQL	90$			; if not, no hope
	CMPL	UCB$L_2P_ALTUCB(R5),R5	; be sure these differ if so
	BEQL	90$			; if equal no sense messing
	TSTL	UCB$L_2P_ALTUCB(R5)	; Be sure UCB is at least a candidate
	BGEQ	90$
	MOVL	UCB$L_2P_ALTUCB(R5),AHSUCB	; Else fill alt ucb in here
90$:
	RSB

	.SBTTL	LOOKUP_LOCAL_UCB - Search for a local UCB.
;++
;
;	LOOKUP_LOCAL_UCB - Search for a local UCB
;
; Functional Description:
;
;	This routine searches the local I/O database for a UCB whose
;	allocation class, and unit name (DDCn form), match those input.
;
; Inputs:
;
;	R6	desired unit number
;	R7	address of desired DDC counted ASCII string
;	R8	desired allocation class (presumably not zero)
;
; Outputs:
;
;	R0	address of a UCB meeting the criteria, or zero if none found
;
;	R0 through R4 are destroyed.
;	All other registers are preserved.
;
; Notes:
;
;	Hopefully, the fullness of time will allow this routine to be
;	replaced by a similar routine in the executive.
;
;--

LOOKUP_LOCAL_UCB:

	.JSB_ENTRY INPUT=   <		       R6,R7,R8>,-
		   OUTPUT=  <R0		      >,-
		   SCRATCH= <R0,R1,R2,R3,R4   >,-
		   PRESERVE=<>

	ASSUME	DDB$L_LINK EQ 0
	MOVAL	G^IOC$GL_DEVLIST, R4	; Get initial DDB address.

10$:	MOVL	DDB$L_LINK(R4), R4	; Get next DDB.
	BEQL	90$			; Branch if no more DDBs.
	CMPL	R8, DDB$L_ALLOCLS(R4)	; Right allocation class?
	BNEQ	10$			; Branch if wrong allocation class.
	CMPB	(R7), DDB$T_NAME(R4)	; Right "DDC" string size?
	BNEQ	10$			; Branch if wrong "DDC" string size.
	MOVZBL	(R7), R1		; Get "DDC" string size.
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; PROTECT REGS
	CMPC3	R1, 1(R7), -		; Right "DDC" name?
		DDB$T_NAME+1(R4)
	BEQL	12$
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; PROTECT REGS
	BRW	10$
12$:
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; PROTECT REGS
	MOVAB	<DDB$L_UCB -		; Initialize UCB address.
		-UCB$L_LINK>(R4), R0

20$:	MOVL	UCB$L_LINK(R0), R0	; Get next UCB address.
	BEQL	90$			; Branch if no more UCBs.
	CMPW	R6, UCB$W_UNIT(R0)	; Is this the right unit?
	BGTRU	20$			; Branch if more units to check.
	BNEQ	90$			; Branch if not right unit.
	RSB				; Return if unit found.

90$:	CLRL	R0			; Indicate no UCB found.
	RSB				; Exit
	.END SWCTL
