#pragma module SWDRIVER "X-1"

/************************************************************************/
/*									*/
/* Copyright ) Digital Equipment Corporation, 1997	                */
/* All Rights Reserved.							*/
/* Unpublished rights reserved under the copyright laws of the United	*/
/* States.								*/
/*									*/
/* The software contained on this media is proprietary to and embodies	*/
/* the confidential technology of Digital Equipment Corporation.	*/
/* Possession, use, duplication or dissemination of the software and	*/
/* media is authorized only pursuant to a valid written license from	*/
/* Digital Equipment Corporation.					*/
/*									*/
/* RESTRICTED RIGHTS LEGEND   Use, duplication, or disclosure by the	*/
/* U.S. Government is subject to restrictions as set forth in		*/
/* Subparagraph (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19,	*/
/* as applicable.							*/
/*									*/
/************************************************************************/

/************************************************************************/
/*									*/
/* Facility:								*/
/*	Switching Pseudo Driver						*/
/*									*/
/* Abstract:								*/
/*	This driver acts to switch I/O paths between several devices    */
/*	which are intended to all point at the same physical storage.   */
/*	It reuses a "prime" or "master" or "principal" UCB of the unit	*/
/*	which it intercepts, and directs I/O either to that driver or	*/
/*	to another unit which must first have been registered with it.	*/
/*	The IO$_PHYSICAL function of THIS driver is used to control it,	*/
/*	controlling the connection or disconnection of units to the	*/
/*	tuples controlled here, controlling manual failover or failback	*/
/*	(either when the unit is idle, or idling the device first and	*/
/*	un-idling after), and the start_io entry is used to direct	*/
/*	IRPs to underlying paths via the ioc$initiate route provided	*/
/*	the underlying unit is not in Mnt Ver or its shadowing equiv.	*/
/*	Apart from this the intercepted unit appears normal.		*/
/*      In addition to facilities to control this driver via IO$_PHYSICAL*/
/*      a routine to handle most all the same inputs is provided,       */
/*      pointed to by its DDT entry ddt$ps_mntver_2 (see routine        */
/*      fakeformat() below ).                                           */

/* Author:								*/
/*	Glenn C. Everhart						*/
/*									*/
/*									*/
/* Revision History:							*/
/*	X-1	Glenn C. Everhart			10-Feb-1997     */
/*		Initial version.					*/
/*									*/
/************************************************************************/
/* Build instructions:							*/
/* ===================							*/
/*									*/
/*	$ CC_OPT = "/DEFINE=(BASEALIGN_SUPPORT)"			*/
/*	$ IF P1 .NES. ""						*/
/*	$ THEN								*/
/*	$  IF P1 .NES. "DEBUG" THEN EXIT %X14         ! SS$_BADPARAM	*/
/*	$  CC_OPT = "/DEBUG/NOOPTIMIZE/DEFINE=(DEBUG,BASEALIGN_SUPPORT)" */
/*	$ ENDIF								*/
/*	$								*/
/*	$! Compile the driver						*/
/*	$								*/
/*	$ CC/STANDARD=RELAXED_ANSI89/INSTRUCTION=NOFLOATING_POINT -	*/
/*		/EXTERN=STRICT 'CC_OPT' -				*/
/*		/LIS=SWDRIVER/MACHINE_CODE/OBJ=SWDRIVER -		*/
/*		SWDRIVER+SYS$LIBRARY:SYS$LIB_C.TLB/LIBRARY		*/
/*	$								*/
/*	$! Link the driver						*/
/*	$								*/
/*	$ LINK/ALPHA/USERLIB=PROC/NATIVE_ONLY/BPAGE=14/SECTION/REPLACE-	*/
/*	        /NODEMAND_ZERO/NOTRACEBACK/SYSEXE/NOSYSSHR-		*/
/*	        /SHARE=SYS$SWDRIVER-               ! Driver image	*/
/*	        /DSF=SYS$SWDRIVER-                 ! Debug symbol file	*/
/*	        /SYMBOL=SYS$SWDRIVER-              ! Symbol table	*/
/*	        /MAP=SYS$SWDRIVER/FULL/CROSS -     ! Map listing	*/
/*	        SYS$INPUT:/OPTIONS					*/
/*	SYMBOL_TABLE=GLOBALS						*/
/*	CLUSTER=VMSDRIVER,,,-						*/
/*	        SWDRIVER.OBJ,-						*/
/*	        SYS$LIBRARY:VMS$VOLATILE_PRIVATE_INTERFACES/INCLUDE=(BUGCHECK_CODES)/LIB,- */
/*	        SYS$LIBRARY:STARLET/INCLUDE:(SYS$DRIVER_INIT,SYS$DOINIT) */
/*	COLLECT=NONPAGED_EXECUTE_PSECTS/ATTRIBUTES=RESIDENT,-		*/
/*	        $CODE$							*/
/*	PSECT_ATTR=$LINK$,WRT						*/
/*	PSECT_ATTR=$INITIAL$,WRT					*/
/*	PSECT_ATTR=$LITERAL$,NOPIC,NOSHR,WRT				*/
/*	PSECT_ATTR=$READONLY$,NOPIC,NOSHR,WRT				*/
/*	PSECT_ATTR=$$$105_PROLOGUE,NOPIC				*/
/*	PSECT_ATTR=$$$110_DATA,NOPIC					*/
/*	PSECT_ATTR=$$$115_LINKAGE,WRT					*/
/*	COLLECT=NONPAGED_READWRITE_PSECTS/ATTRIBUTES=RESIDENT,$PLIT$, -	*/
/*              $INITIAL$,$GLOBAL$,$OWN$,$$$105_PROLOGUE,$$$110_DATA,	*/
/*		$$$115_LINKAGE,$BSS$,$DATA$,$LINK$,$LITERAL$,$READONLY$	*/
/*	PSECT_ATTR=EXEC$INIT_CODE,NOSHR					*/
/*	COLLECT=INITIALIZATION_PSECTS/ATTRIBUTES=INITIALIZATION_CODE,-	*/
/*	        EXEC$INIT_000, EXEC$INIT_001,EXEC$INIT_002,-		*/
/*		EXEC$INIT_CODE,EXEC$INIT_LINKAGE,EXEC$INIT_SSTBL_000,-	*/
/*		EXEC$INIT_SSTBL_001,EXEC$INIT_SSTBL_002			*/
/*	$QUIT:								*/
/*	$ EXIT $STATUS							*/

/* Define system data structure types and constants */

/* This driver is able to handle an extended IRP which has a context
   stack. Until the definitions can be checked in, handle the IRP
   definition locally. */

#define STDIRP 0 /* for now, use non standard IRP def */
#define IRP_CTXSTKSIZ 64 /* size of context stack in longs */ 
#define IO$M_FMODIFIERS 0xFFC0
#define IO$V_FMODIFIERS 6
#define HYSTERESIS 400
#define UCB$M_HIDEUCB 0x80000000
#define UCB$V_HIDEUCB 31
#if STDIRP
#include	irpdef			/* I/O request packet */
#endif
/* Definition for build purposes of an IRP with the extra fields we need */
#if !STDIRP
/*** MODULE $IRPDEF ***/
#ifndef __IRPDEF_LOADED
#define __IRPDEF_LOADED 1

#pragma __nostandard			 /* This file uses non-ANSI-Standard features */
#pragma __member_alignment __save
#pragma __nomember_alignment
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __save		 /* Save the previously-defined required ptr size */
#pragma __required_pointer_size __short		 /* And set ptr size default to 32-bit pointers */
#endif
 
#ifdef __cplusplus
    extern "C" {
#define __unknown_params ...
#define __optional_params ...
#else
#define __unknown_params
#define __optional_params ...
#endif
 
#if !defined(__VAXC)
#define __struct struct
#define __union union
#else
#define __struct variant_struct
#define __union variant_union
#endif
 
/*+                                                                         */
/* IRP - I/O REQUEST PACKET                                                 */
/*                                                                          */
/* I/O REQUEST PACKETS ARE CONSTRUCTED BY THE QUEUE I/O REQUEST SYSTEM      */
/* SERVICE. THE CONTENT OF AN I/O REQUEST PACKET DESCRIBES A FUNCTION TO    */
/* BE PERFORMED ON A DEVICE UNIT.                                           */
/*                                                                          */
/* NOTE: Several fields of the IRP must be at the same offsets as their     */
/* corresponding fields in the IRPE and CDRP.  The equivalency of these     */
/* offsets is verified by ASSUME statements in the [LIB]VFY_IRP_A_LIKES.MAR */
/* module.  These ASSUMEs may need to be altered as well whenever an IRP    */
/* field is removed or altered.                                             */
/*-                                                                         */
#include <diobmdef.h>		/* Define the DIOBM type; IRP contains an embedded DIOBM type */
#define IRP$M_WLE_REUSE 0x1
#define IRP$M_WLE_SUPWL 0x2
#define IRP$M_BUFIO 0x1
#define IRP$M_FUNC 0x2
#define IRP$M_PAGIO 0x4
#define IRP$M_COMPLX 0x8
#define IRP$M_VIRTUAL 0x10
#define IRP$M_CHAINED 0x20
#define IRP$M_SWAPIO 0x40
#define IRP$M_DIAGBUF 0x80
#define IRP$M_PHYSIO 0x100
#define IRP$M_TERMIO 0x200
#define IRP$M_MBXIO 0x400
#define IRP$M_EXTEND 0x800
#define IRP$M_FILACP 0x1000
#define IRP$M_MVIRP 0x2000
#define IRP$M_SRVIO 0x4000
#define IRP$M_CCB_LOOKED_UP 0x8000
#define IRP$M_CACHE_PAGIO 0x10000
#define IRP$M_FILL_BIT 0x20000
#define IRP$M_BUFOBJ 0x40000
#define IRP$M_TRUSTED 0x80000
#define IRP$M_FASTIO_DONE 0x100000
#define IRP$M_FASTIO 0x200000
#define IRP$M_FAST_FINISH 0x400000
#define IRP$M_DOPMS 0x800000
#define IRP$M_HIFORK 0x1000000
#define IRP$M_SRV_ABORT 0x2000000
#define IRP$M_LOCK_RELEASEABLE 0x4000000
#define IRP$M_DID_FAST_FDT 0x8000000
#define IRP$M_SYNCSTS 0x10000000
#define IRP$M_FINIPL8 0x20000000
#define IRP$M_START_PAST_HWM 0x1
#define IRP$M_END_PAST_HWM 0x2
#define IRP$M_ERASE 0x4
#define IRP$M_PART_HWM 0x8
#define IRP$M_LCKIO 0x10
#define IRP$M_SHDIO 0x20
#define IRP$M_CACHEIO 0x40
#define IRP$M_WLE 0x80
#define IRP$M_CACHE_SAFE 0x100
#define IRP$M_NOCACHE 0x200
#define IRP$M_ABORTIO 0x400
#define IRP$M_FORCEMV 0x800
#define IRP$M_HBRIO 0x1000
#define IRP$M_VIASWITCH 0x2000
#define IRP$M_FCODE 0x3F
#define IRP$K_CDRP 304                  /* Offset to the CDRP within the IRP  */
#define IRP$C_CDRP 304                  /* Offset to the CDRP within the IRP */
#define IRP$M_PIO_ERROR 0x1
#define IRP$M_PIO_FANOUT 0x2
#define IRP$M_PIO_NOQUE 0x4
#define IRP$M_PIO_CANCEL 0x8
#define IRP$M_PIO_CTHRDOK 0x10
#define IRP$M_PIO_PHASEII 0x20
#define IRP$M_SHD_EXPEL_REMOVED 0x1
#define IRP$M_CLN_READY 0x1
#define IRP$M_CLN_DONE 0x2
#define IRP$M_CPY_FINI 0x4
#define IRP$K_BT_LEN 400
#define IRP$C_BT_LEN 400
#define IRP$K_CD_LEN 408
#define IRP$C_CD_LEN 408
	
 
#ifdef __cplusplus		    /* Define structure prototypes */
struct _wcb;
struct _ucb;
struct _ctxb;
struct _shad;
struct _hrb;
struct _bufio;
struct _irpe;
struct _fdt_context;
struct _arb;
struct _kpb;
struct _ccb;
struct _fkb;
struct _cdt;
struct _cdrp;
#endif		/* #ifdef __cplusplus */
 
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
typedef struct _irp {
#pragma __nomember_alignment
    struct _irp *irp$l_ioqfl;           /*I/O QUEUE FORWARD LINK            */
    struct _irp *irp$l_ioqbl;           /*I/O QUEUE BACKWARD LINK           */
    unsigned short int irp$w_size;      /*SIZE OF IRP IN BYTES              */
    unsigned char irp$b_type;           /*STRUCTURE TYPE FOR IRP            */
    __union  {
        unsigned char irp$b_rmod;       /*ACCESS MODE OF REQUEST            */
        __struct  {
            unsigned irp$v_mode : 2;    /* MODE SUBFIELD                    */
            unsigned irp$v_fill_22 : 6;
            } irp$r_rmod_bits;
        } irp$r_rmod_overlay;
    unsigned int irp$l_pid;             /*PROCESS ID OF REQUESTING PROCESS  */
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_param_0;          /*For PAGEFAULT and IOCIOPOST       */
        __struct  {
#pragma __nomember_alignment
            int irp$l_acb64x_offset;    /* Offset to ACB64X structure embedded in this IRP */
            char irp$b_fill_23 [4];
            } irp$r_fill_1;
        } irp$r_fill_0;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_param_1;          /*For PAGEFAULT and IOCIOPOST       */
        __struct  {
#pragma __nomember_alignment
            unsigned int irp$l_acb_flags; /* ACB flags; valid only if ACB$M_FLAGS_VALID in RMOD set */
            unsigned int irp$l_thread_pid; /* (Reserved for Kernel Threads) */
            } irp$r_fill_3;
        } irp$r_fill_2;
    __union  {
        struct _wcb *irp$l_wind;        /*ADDRESS OF WINDOW BLOCK           */
        struct _irp *irp$l_mirp;        /*LINK TO MASTER IRP                */
        void (*irp$l_kast)();           /*PIGGY BACK KERNEL AST ADDRESS     */
        } irp$r_wind_overlay;
    struct _ucb *irp$l_ucb;             /*ADDRESS OF DEVICE UCB             */
    __union  {
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __long		 /* And set ptr size default to 64-bit pointers */
        void (*irp$pq_acb64_ast)();     /* 64-bit user AST routine address  */
#else
	unsigned __int64 irp$pq_acb64_ast;
#endif
#pragma __nomember_alignment
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __short		 /* And set ptr size default to 32-bit pointers */
#endif
        struct _irp *irp$l_shd_iofl;    /* Link to clone IRPs               */
        struct _ctxb *irp$l_ctxb;       /* Link to CTXB                     */
        int irp$l_iirp_p0;              /* Generic parameter cell in internal IRPs */
        } irp$r_acb64_ast_overlay;
    __union  {
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
        unsigned __int64 irp$q_acb64_astprm; /* 64-bit user AST parameter value */
#pragma __nomember_alignment
        struct _shad *irp$l_shad;       /* SHAD address                     */
        struct _hrb *irp$l_hrb;         /* HRB address                      */
        int irp$l_mv_tmo;               /* Timeout value in internal mount verification IRPs */
        int irp$l_iirp_p1;              /* Generic parameter cell in internal IRPs */
        } irp$r_acb64_astprm_overlay;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    unsigned __int64 irp$q_user_thread_id; /* Unique user thread identifier */
#pragma __nomember_alignment
    unsigned char irp$b_efn;            /*EVENT FLAG NUMBER AND EVENT GROUP  */
    unsigned char irp$b_pri;            /*BASE PRIORITY OF REQUESTING PROCESS  */
    unsigned char irp$b_cln_indx;       /*Shadow Clone membership index     */
    __union  {                          /* Write log flags.                 */
        unsigned char irp$b_wlg_flags;  /* These flags are shared by DUDRIVER and SHDRIVER and MSCP. */
        __struct  {                     /* Write log Flags Status Bits      */
            unsigned irp$v_wle_reuse : 1; /* Reuse writelog entry           */
            unsigned irp$v_wle_supwl : 1; /* Supplementary writelog         */
            unsigned irp$v_fill_24 : 6;
            } irp$r_wlg_flag_bits;
/*                                                                          */
        } irp$r_wlg_flags_overlay;
/*                                                                          */
    unsigned int irp$l_chan;            /* Process I/O channel              */
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __long		 /* And set ptr size default to 64-bit pointers */
        void *irp$pq_iosb;              /* 64-bit address of caller's IOSB  */
#else
	unsigned __int64 irp$pq_iosb;
#endif
        __struct  {
#pragma __nomember_alignment
            __union  {
                unsigned int irp$l_cln_wle; /* write log entry              */
                __int64 irp$q_param_2;  /* For PAGEFAULT and IOCIOPOST (Kthreads) */
                int irp$l_iirp_p2;      /* Generic parameter cell in internal IRPs */
                } irp$r_iosb_overlay;
            } irp$r_fill_5;
        } irp$r_fill_4;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        unsigned __int64 irp$q_status;  /*Big time REQUEST STATUS           */
        __struct  {
#pragma __nomember_alignment
            __union  {
                unsigned int irp$l_sts; /* Status                           */
                __struct  {
                    unsigned irp$v_bufio : 1; /* BUFFERED I/O FLAG ;THESE BITS  */
                    unsigned irp$v_func : 1; /* 1=>READ FUNCTION ;MUST BE ADJACENT  */
                    unsigned irp$v_pagio : 1; /* PAGING I/O FLAG ;AND IN ORDER  */
                    unsigned irp$v_complx : 1; /* COMPLEX BUFFERED I/O      */
                    unsigned irp$v_virtual : 1; /* VIRTUAL I/O FUNCTION     */
                    unsigned irp$v_chained : 1; /* CHAINED BUFFERED I/O OPERATION  */
                    unsigned irp$v_swapio : 1; /* SWAP I/O OPERATION        */
                    unsigned irp$v_diagbuf : 1; /* DIAGNOSTIC BUFFER ALLOCATED  */
                    unsigned irp$v_physio : 1; /* PHYSICAL I/O              */
                    unsigned irp$v_termio : 1; /* TERMINAL I/O (FOR SELECTING PRIORITY INC)  */
                    unsigned irp$v_mbxio : 1; /* MAILBOX BUFFERED READ      */
                    unsigned irp$v_extend : 1; /* AN IRPE IS LINKED TO THIS IRP  */
                    unsigned irp$v_filacp : 1; /* FILE ACP I/O (BOTH DIOCNT AND BIOCNT)  */
                    unsigned irp$v_mvirp : 1; /* MOUNT VERIFICATION IRP     */
                    unsigned irp$v_srvio : 1; /* SERVER TYPE I/O (TRIGGER MOUNTVER ON ERROR BUT DON'T STALL) */
                    unsigned irp$v_ccb_looked_up : 1; /* Set if IRP$PS_CCB contains valid CCB address */
                    unsigned irp$v_cache_pagio : 1; /* Cached page i/o      */
                    unsigned irp$v_fill_bit : 1; /* Unused                  */
                    unsigned irp$v_bufobj : 1; /* Set if buffer object I/O  */
                    unsigned irp$v_trusted : 1; /* Set if trusted Component I/O */
                    unsigned irp$v_fastio_done : 1; /* Set if this is an available Fast-IO IRP */
                    unsigned irp$v_fastio : 1; /* Set if IRP created by $IO_SETUP -- special delete action */
                    unsigned irp$v_fast_finish : 1; /* Set if IPL8 completion is expected */
                    unsigned irp$v_dopms : 1; /* =1 if this IRP should call PMS$ logging routines */
                    unsigned irp$v_hifork : 1; /* Device fork IPL > IPL$C_SCS */
                    unsigned irp$v_srv_abort : 1; /* Server I/O should be aborted */
                    unsigned irp$v_lock_releaseable : 1; /* Forklock can be released in favor of PM spinlock on Start I/O */
                    unsigned irp$v_did_fast_fdt : 1; /* Fast-IO may have locked buffers via standard FDT dispatch */
                    unsigned irp$v_syncsts : 1; /* VIOC can return SS$_SYNC on HIT if set. */
                    unsigned irp$v_finipl8 : 1; /* Finish at IPL8 hook      */
                    unsigned irp$v_fill_25 : 2;
                    } irp$r_fill_9;
                } irp$r_fill_8;
            __union  {
                unsigned int irp$l_sts2; /* EXTENSION OF STATUS WORD        */
                __struct  {
                    unsigned irp$v_start_past_hwm : 1; /* I/O STARTS PAST HIGHWATER MARK */
                    unsigned irp$v_end_past_hwm : 1; /* I/O ENDS PAST HIGHWATER MARK */
                    unsigned irp$v_erase : 1; /* ERASE I/O FUNCTION         */
                    unsigned irp$v_part_hwm : 1; /* PARTIAL HIGHWATER MARK UPDATE */
                    unsigned irp$v_lckio : 1; /* Locked I/O request (DECnet) */
                    unsigned irp$v_shdio : 1; /* This is a shadowing IRP    */
                    unsigned irp$v_cacheio : 1; /* uses VBN cache buffers   */
                    unsigned irp$v_wle : 1; /* I/O USES A WRITE LOG ENTRY   */
                    unsigned irp$v_cache_safe : 1; /* this indicates that   */
/* the request has been                                                     */
/* checked as regards                                                       */
/* caching.                                                                 */
                    unsigned irp$v_nocache : 1; /* IO$M_NOVCACHE was        */
/* set in QIO function                                                      */
                    unsigned irp$v_abortio : 1; /* set in EXE$ABORTIO       */
                    unsigned irp$v_forcemv : 1; /* set to indicate forced MV in progress */
                    unsigned irp$v_hbrio : 1; /* This is a host based raid IRP. */
		    unsigned irp$v_viaswitch : 1; /* was gotstk*/
                    unsigned irp$v_fill_26 : 2;
                    } irp$r_fill_11;
                } irp$r_fill_10;
            } irp$r_fill_7;
        } irp$r_fill_6;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __long		 /* And set ptr size default to 64-bit pointers */
    struct _pte *irp$pq_va_pte;         /* 64-bit process virtual addr of PTE  */
#else
	unsigned __int64 irp$pq_va_pte;
#endif
#pragma __nomember_alignment
    __union  {
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __short		 /* And set ptr size default to 32-bit pointers */
#endif
        void *irp$l_svapte;             /* 32-bit S0/S1 address of first PTE  */
        struct _bufio *irp$ps_bufio_pkt; /* Pointer to buffered I/O packet  */
        } irp$r_svapte_overlay;
    unsigned int irp$l_bcnt;            /*BYTE COUNT OF TRANSFER            */
    unsigned int irp$l_boff;            /* Byte offset                      */
    __union  {
        unsigned int irp$l_oboff;       /* Original BOFF, for segmented DIO */
        unsigned int irp$l_aboff;       /* "Ambient" BOFF, for NETDRIVER    */
        } irp$r_oboff_overlay;
    struct _irpe *irp$l_extend;         /* ADDRESS OF IRPE                  */
    struct _fdt_context *irp$ps_fdt_context; /* Contains addr of the FDT Context structure */
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    DIOBM irp$r_diobm;                  /* Embedded DIOBM to handle cross-process 32-bit PTE access */
#pragma __nomember_alignment
    __union  {
        unsigned int irp$l_iost1;       /*FIRST I/O STATUS LONGWORD (FOR I/O POST)  */
        int irp$l_media;                /*MEDIA ADDRESS                     */
        } irp$r_iost1_overlay;
    __union  {
        unsigned int irp$l_iost2;       /*SECOND I/O STATUS LONGWORD        */
        __union  {
            int irp$l_tt_term;          /*ADDRESS OF READ TERMINATORS MASK  */
            unsigned char irp$b_carcon; /*CARRIAGE CONTROL                  */
            } irp$r_tt_term_overlay;
        __union  {
            unsigned short int irp$w_shd_copy_type; /*TYPE OF COPY TO PERFORM */
            __struct  {
                unsigned short int irp$w_shd_vun; /* VIRTUAL UNIT NUMBER    */
                __union  {
                    unsigned short int irp$w_shd_dev_type; /*DEVICE TYPE    */
                    unsigned short int irp$w_shd_mscp_disk_modifier; /*FIELD FOR MODIFIERS */
                    } irp$r_shd_iost2_inner;
                } irp$r_shd_iost2_stuff;
            } irp$r_shd_iost2_overlay;
        } irp$r_iost2_overlay;
    __union  {
        unsigned __int64 irp$q_nt_prvmsk; /* PRIVILEGE MASK FOR DECNET      */
        unsigned __int64 irp$q_station; /* STATION FIELD FOR DECNET DRIVERS */
        __union  {
            unsigned __int64 irp$q_tt_state; /* TERMINAL STATE DEFINITIONS  */
            __struct  {
                unsigned int irp$l_abcnt; /* ACCUMULATED BYTES TRANSFERED   */
                unsigned int irp$l_obcnt; /* ORIGINAL TRANSFER BYTE COUNT   */
                } irp$r_tt_state_fields;
            } irp$r_tt_state_overlay;
        } irp$r_nt_prvmsk_overlay;
    __union  {
        unsigned int irp$l_func;        /* I/O function code                */
        __struct  {
            unsigned irp$v_fcode : 6;   /* FUNCTION CODE FIELD              */
            unsigned irp$v_fmod : 10;   /* FUNCTION MODIFIER FIELD          */
            } irp$r_func_bits;
        } irp$r_func_overlay;
    unsigned int irp$l_segvbn;          /* VIRTUAL BLOCK NUMBER OF CURRENT SEGMENT  */
    __union  {
        void *irp$l_diagbuf;            /* DIAGNOSTIC BUFFER ADDRESS        */
        void *irp$l_scb_buf;            /* SCB BUFFER ADDRESS               */
        unsigned short int irp$w_tt_prmpt; /* PROMPT SIZE                   */
        } irp$r_diagbuf_overlay;
    __union  {
        unsigned int irp$l_seqnum;      /* SEQUENCE NUMBER                  */
        struct _ucb *irp$l_dcd_src_ucb; /* DISK COPY DATA SOURCE UCB        */
        } irp$r_seqnum_overlay;
    struct _arb *irp$l_arb;             /* ACCESS RIGHTS BLOCK ADDRESS      */
    __union  {
        void *irp$l_keydesc;            /* ADDRESS OF ENCRYPTION DESCRIPTOR */
        unsigned int irp$l_wle_ptr;     /* Clone Write log index            */
        unsigned char irp$b_cpy_mode;   /* Copy mode identifier             */
        } irp$r_keydesc_overlay;
    struct _kpb *irp$ps_kpb;            /* Pointer to KP block              */
    struct _ccb *irp$ps_ccb;            /* Pointer to CCB for this I/O      */
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_qio_p1;           /* QIO argument #1 (64-bits)        */
        __struct  {
#pragma __nomember_alignment
            int irp$l_qio_p1;           /*     (low-order 32-bit)           */
            char irp$b_fill_27 [4];
            } irp$r_fill_13;
        } irp$r_fill_12;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_qio_p2;           /* QIO argument #2 (64-bits)        */
        __struct  {
#pragma __nomember_alignment
            int irp$l_qio_p2;           /*     (low-order 32-bit)           */
            char irp$b_fill_28 [4];
            } irp$r_fill_15;
        } irp$r_fill_14;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
#pragma __nomember_alignment
        __int64 irp$q_qio_p3;           /* QIO argument #3 (64-bits)        */
        int irp$l_qio_p3;               /*     (low-order 32-bit)           */
        __int64 irp$q_param_3;          /*     (for PAGEFAULT and IOCIOPOST) */
        } irp$r_qio_p3_overlay;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_qio_p4;           /* QIO argument #4 (64-bits)        */
        __struct  {
#pragma __nomember_alignment
            int irp$l_qio_p4;           /*     (low-order 32-bit)           */
            char irp$b_fill_29 [4];
            } irp$r_fill_17;
        } irp$r_fill_16;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_qio_p5;           /* QIO argument #5 (64-bits)        */
        __struct  {
#pragma __nomember_alignment
            int irp$l_qio_p5;           /*     (low-order 32-bit)           */
            char irp$b_fill_30 [4];
            } irp$r_fill_19;
        } irp$r_fill_18;
#if !defined(__NOBASEALIGN_SUPPORT)  && !defined(__cplusplus)   /* If using pre DECC V4.0 or C++ */
#pragma __nomember_alignment __quadword
#else
#pragma __nomember_alignment
#endif
    __union  {
        __int64 irp$q_qio_p6;           /* QIO argument #6 (64-bits)        */
        __struct  {
#pragma __nomember_alignment
            int irp$l_qio_p6;           /*     (low-order 32-bit)           */
            char irp$b_fill_31 [4];
            } irp$r_fill_21;
        } irp$r_fill_20;
/* ALL FIELDS INSERTED ABOVE THIS POINT IN THE IRP 		            */
/* MUST BE CHANGED IN THE CDRPDEF.SDL FILE.			            */
	
/* Standard IRP must contain space for Class Driver CDRP fields.            */
    struct _fkb *irp$l_fqfl;            /* Fork Queue FLINK                 */
    struct _fkb *irp$l_fqbl;            /* Fork Queue Blink                 */
    unsigned short int irp$w_cdrpsize;  /* Size field for positive section only  */
    unsigned char irp$b_cd_type;        /* Type, always of interest         */
    unsigned char irp$b_flck;           /* Fork Lock number                 */
    void (*irp$l_fpc)();                /* Fork PC                          */
    __int64 irp$q_fr3;                  /* Fork R3                          */
    __int64 irp$q_fr4;                  /* Fork R4                          */
    void (*irp$l_savd_rtn)();           /* Saved return address from level 1 JSB  */
    void *irp$l_msg_buf;                /* Address of allocated MSCP buffer  */
    unsigned int irp$l_rspid;           /* Allocated Request ID             */
    struct _cdt *irp$l_cdt;             /* Address of Connection Descriptor Table  */
    unsigned __int64 irp$q_res_wait_state; /* SCS Resource Wait State       */
    int irp$l_scs_stall_data;           /* Data cell used by SCS to save data over a stall */
    void *irp$l_rwcptr;                 /* RWAITCNT pointer                 */
    void *irp$l_bd_addr;                /* Address of Buffer Descriptor that maps I/O buffer */
    void *irp$l_rbun;                   /* Address of Resource Bundle       */
    void *irp$l_lbufh_ad;               /* Local BUFfer Handle ADress       */
    char irp$b_fill_32 [4];
	
/*	Extensions to the CDRP within the IRP                               */
    __union  {
/* Host-Based Shadowing Extension                                           */
        __struct  {
            unsigned char irp$b_shd_pio_cnt; /* Tot num phys IRPs assoc.    */
            unsigned char irp$b_shd_pio_act; /* Tot num phys IRPs active.   */
/* Note Keep SHD_PIO_FLAGS, SHD_PIO_ERRCNT,  contiguous.                    */
            __union  {
                unsigned char irp$b_shd_pio_flags; /* Master Flags Byte     */
                __struct  {
                    unsigned irp$v_pio_error : 1; /* Errant clone in Chain  */
                    unsigned irp$v_pio_fanout : 1; /* Chained Clones.       */
                    unsigned irp$v_pio_noque : 1; /* Don't queue to server  */
                    unsigned irp$v_pio_cancel : 1; /* This master cancelled */
                    unsigned irp$v_pio_cthrdok : 1; /* Copy thread validated. */
                    unsigned irp$v_pio_phaseii : 1; /* Bi-phasic Phase II write */
                    unsigned irp$v_fill_33 : 2;
                    } irp$r_pio_bits;
                } irp$r_pio_flags_overlay;
            unsigned char irp$b_shd_pio_errcnt; /* Number of errors in chain */
            unsigned char irp$b_shd_pio_errindex; /* Index of erring device */
            unsigned char irp$b_shd_pio_errsev; /* Relative error severity  */
            short int irp$w_shd_filler;
            unsigned __int64 irp$q_shd_lock_fr0; /* Lock fork R0            */
            unsigned __int64 irp$q_shd_lock_fr1; /* Lock fork R1            */
            unsigned __int64 irp$q_shd_lock_fr2; /* Lock fork R2            */
            unsigned __int64 irp$q_shd_lock_fr4; /* Lock fork R4            */
            unsigned __int64 irp$q_shd_lock_fr5; /* Lock fork R5            */
            void (*irp$l_shd_lock_fpc)(); /* Lock fork PC                   */
            unsigned int irp$l_shd_pio_error; /* BCNT and Error Status (SS$_) */
            struct _irp *irp$l_shd_pio_lnk; /* Link to clone IRP(s)         */
            int (*irp$l_shdspc)();      /* Shadowing return PC              */
            struct _irp *irp$l_shd_control_irp; /* address of control IRP   */
            int irp$l_shd_temp;         /* used for temporary storage       */
            unsigned __int64 irp$q_shd_saved_r1; /* second save area for WLG */
            unsigned __int64 irp$q_shd_saved_r2;
            unsigned __int64 irp$q_shd_saved_r4;
            unsigned int irp$l_shd_svd_cnt_irp; /* save SHD_CONTROL_IRP     */
            unsigned int irp$l_shd_saved_status; /* save area for status    */
            unsigned int irp$l_shd_wlg_mode_fpc; /* saved PC for WLG_MODE fork */
            unsigned int irp$l_shd_perlkid; /* holds sublock id for         */
/* per-disk                                                                 */
            unsigned int irp$l_shd_expel_timer; /* Clone error timer        */
            __union  {
                unsigned int irp$l_shd_expel_flags; /* Clone IRP flags      */
                __struct  {
                    unsigned irp$v_shd_expel_removed : 1; /* Device is expelled */
                    unsigned irp$v_fill_34 : 7;
                    } irp$r_expel_bits;
                } irp$r_expel_flags_overlay;
            unsigned int irp$l_shd_expel_mask; /* indicate units to be expelled in MIRP */
            unsigned __int64 irp$q_shd_reserv_q8; /* will be needed for 64-bit saves */
            unsigned __int64 irp$q_shd_reserv_q9; /* will be needed for 64-bit saves */
            unsigned __int64 irp$q_shd_reserv_q10; /* will be needed for 64-bit saves */
            __union  {
                unsigned char irp$b_shd_flags; /* Shadow Clone Flags        */
                __struct  {             /* Clone Flags Status Bits          */
                    unsigned irp$v_cln_ready : 1; /* Clone is ready for I/O */
                    unsigned irp$v_cln_done : 1; /* Clone has done I/O      */
                    unsigned irp$v_cpy_fini : 1; /* Copy is complete.       */
                    unsigned irp$v_fill_35 : 5;
                    } irp$r_shd_flag_bits;
/*                                                                          */
                } irp$r_shd_flags_overlay;
            unsigned long irp$l_curcspx; /* Current context stack pointer */
	    long irp$l_stkflgsx;
/*            struct stkfs {*/
/*                   unsigned irp$v_onstackx : 1;*/ /* current data is on the ctx stk */
/*                   unsigned irp$v_csfil : 31; */
/*            } irp$l_stkflgsx; */
            unsigned long irp$a_ctxstkx[IRP_CTXSTKSIZ];

/*                                                                          */
            } irp$r_shadowing_extension;
/*	Block Transfer Extension                                            */
        __struct  {
            unsigned int irp$l_lboff;   /* Local Byte OFFset                */
            __union  {
                void *irp$l_rbufh_ad;   /* Remote BUFfer Handle ADress      */
                struct _cdrp *irp$l_cdrpfl;
                } irp$r_rbufh_ad_overlay;
            unsigned int irp$l_rboff;   /* Remote Byte OFFset               */
            unsigned int irp$l_xct_len; /* Transfer length in bytes         */
            } irp$r_blk_xfer_extension;
/*	Class Driver Extension                                              */
        __struct  {
            char irp$t_lbufhndl [12];   /* Local buffer handle              */
            unsigned int irp$l_ubarsrce; /* Scratch Cell used for DU/TUDRIVER convenience */
            unsigned int irp$l_dutuflags; /* Class driver status flags:     */
            unsigned short int irp$w_dutucntr; /* General purpose counter   */
            unsigned short int irp$w_endmsgsiz; /* Size of most recent MSCP end message */
            } irp$r_cls_drv_extension;
/* File system extensions                                                   */
        unsigned int irp$l_erase_vbn;   /* VBN to start HWM erase           */
        } irp$r_cdrp_extensions;
    char irp$b_fill_36 [3];
    } IRP;
 
#define irp$b_rmod irp$r_rmod_overlay.irp$b_rmod
#define irp$v_mode irp$r_rmod_overlay.irp$r_rmod_bits.irp$v_mode
#define irp$q_param_0 irp$r_fill_0.irp$q_param_0
#define irp$l_acb64x_offset irp$r_fill_0.irp$r_fill_1.irp$l_acb64x_offset
#define irp$q_param_1 irp$r_fill_2.irp$q_param_1
#define irp$l_acb_flags irp$r_fill_2.irp$r_fill_3.irp$l_acb_flags
#define irp$l_thread_pid irp$r_fill_2.irp$r_fill_3.irp$l_thread_pid
#define irp$l_wind irp$r_wind_overlay.irp$l_wind
#define irp$l_mirp irp$r_wind_overlay.irp$l_mirp
#define irp$l_kast irp$r_wind_overlay.irp$l_kast
#define irp$pq_acb64_ast irp$r_acb64_ast_overlay.irp$pq_acb64_ast
#define irp$l_shd_iofl irp$r_acb64_ast_overlay.irp$l_shd_iofl
#define irp$l_ctxb irp$r_acb64_ast_overlay.irp$l_ctxb
#define irp$l_iirp_p0 irp$r_acb64_ast_overlay.irp$l_iirp_p0
#define irp$q_acb64_astprm irp$r_acb64_astprm_overlay.irp$q_acb64_astprm
#define irp$l_shad irp$r_acb64_astprm_overlay.irp$l_shad
#define irp$l_hrb irp$r_acb64_astprm_overlay.irp$l_hrb
#define irp$l_mv_tmo irp$r_acb64_astprm_overlay.irp$l_mv_tmo
#define irp$l_iirp_p1 irp$r_acb64_astprm_overlay.irp$l_iirp_p1
#define irp$b_wlg_flags irp$r_wlg_flags_overlay.irp$b_wlg_flags
#define irp$v_wle_reuse irp$r_wlg_flags_overlay.irp$r_wlg_flag_bits.irp$v_wle_reuse
#define irp$v_wle_supwl irp$r_wlg_flags_overlay.irp$r_wlg_flag_bits.irp$v_wle_supwl
#define irp$pq_iosb irp$r_fill_4.irp$pq_iosb
#define irp$l_cln_wle irp$r_fill_4.irp$r_fill_5.irp$r_iosb_overlay.irp$l_cln_wle
#define irp$q_param_2 irp$r_fill_4.irp$r_fill_5.irp$r_iosb_overlay.irp$q_param_2
#define irp$l_iirp_p2 irp$r_fill_4.irp$r_fill_5.irp$r_iosb_overlay.irp$l_iirp_p2
#define irp$q_status irp$r_fill_6.irp$q_status
#define irp$l_sts irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$l_sts
#define irp$v_bufio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_bufio
#define irp$v_func irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_func
#define irp$v_pagio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_pagio
#define irp$v_complx irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_complx
#define irp$v_virtual irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_virtual
#define irp$v_chained irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_chained
#define irp$v_swapio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_swapio
#define irp$v_diagbuf irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_diagbuf
#define irp$v_physio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_physio
#define irp$v_termio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_termio
#define irp$v_mbxio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_mbxio
#define irp$v_extend irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_extend
#define irp$v_filacp irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_filacp
#define irp$v_mvirp irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_mvirp
#define irp$v_srvio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_srvio
#define irp$v_ccb_looked_up irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_ccb_looked_up
#define irp$v_cache_pagio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_cache_pagio
#define irp$v_fill_bit irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_fill_bit
#define irp$v_bufobj irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_bufobj
#define irp$v_trusted irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_trusted
#define irp$v_fastio_done irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_fastio_done
#define irp$v_fastio irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_fastio
#define irp$v_fast_finish irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_fast_finish
#define irp$v_dopms irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_dopms
#define irp$v_hifork irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_hifork
#define irp$v_srv_abort irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_srv_abort
#define irp$v_lock_releaseable irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_lock_releaseable
#define irp$v_did_fast_fdt irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_did_fast_fdt
#define irp$v_syncsts irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_syncsts
#define irp$v_finipl8 irp$r_fill_6.irp$r_fill_7.irp$r_fill_8.irp$r_fill_9.irp$v_finipl8
#define irp$l_sts2 irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$l_sts2
#define irp$v_start_past_hwm irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_start_past_hwm
#define irp$v_end_past_hwm irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_end_past_hwm
#define irp$v_erase irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_erase
#define irp$v_part_hwm irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_part_hwm
#define irp$v_lckio irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_lckio
#define irp$v_shdio irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_shdio
#define irp$v_cacheio irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_cacheio
#define irp$v_wle irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_wle
#define irp$v_cache_safe irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_cache_safe
#define irp$v_nocache irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_nocache
#define irp$v_abortio irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_abortio
#define irp$v_forcemv irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_forcemv
#define irp$v_hbrio irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_hbrio
#define irp$v_gotstk irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_viaswitch
#define irp$v_viaswitch irp$r_fill_6.irp$r_fill_7.irp$r_fill_10.irp$r_fill_11.irp$v_viaswitch
#define irp$l_svapte irp$r_svapte_overlay.irp$l_svapte
#define irp$ps_bufio_pkt irp$r_svapte_overlay.irp$ps_bufio_pkt
#define irp$l_oboff irp$r_oboff_overlay.irp$l_oboff
#define irp$l_aboff irp$r_oboff_overlay.irp$l_aboff
#define irp$l_iost1 irp$r_iost1_overlay.irp$l_iost1
#define irp$l_media irp$r_iost1_overlay.irp$l_media
#define irp$l_iost2 irp$r_iost2_overlay.irp$l_iost2
#define irp$l_tt_term irp$r_iost2_overlay.irp$r_tt_term_overlay.irp$l_tt_term
#define irp$b_carcon irp$r_iost2_overlay.irp$r_tt_term_overlay.irp$b_carcon
#define irp$w_shd_copy_type irp$r_iost2_overlay.irp$r_shd_iost2_overlay.irp$w_shd_copy_type
#define irp$w_shd_vun irp$r_iost2_overlay.irp$r_shd_iost2_overlay.irp$r_shd_iost2_stuff.irp$w_shd_vun
#define irp$w_shd_dev_type irp$r_iost2_overlay.irp$r_shd_iost2_overlay.irp$r_shd_iost2_stuff.irp$r_shd_iost2_inner.irp$w_shd_dev_ty\
pe
#define irp$w_shd_mscp_disk_modifier irp$r_iost2_overlay.irp$r_shd_iost2_overlay.irp$r_shd_iost2_stuff.irp$r_shd_iost2_inner.irp$w_\
shd_mscp_disk_modifier
#define irp$q_nt_prvmsk irp$r_nt_prvmsk_overlay.irp$q_nt_prvmsk
#define irp$q_station irp$r_nt_prvmsk_overlay.irp$q_station
#define irp$q_tt_state irp$r_nt_prvmsk_overlay.irp$r_tt_state_overlay.irp$q_tt_state
#define irp$l_abcnt irp$r_nt_prvmsk_overlay.irp$r_tt_state_overlay.irp$r_tt_state_fields.irp$l_abcnt
#define irp$l_obcnt irp$r_nt_prvmsk_overlay.irp$r_tt_state_overlay.irp$r_tt_state_fields.irp$l_obcnt
#define irp$l_func irp$r_func_overlay.irp$l_func
#define irp$v_fcode irp$r_func_overlay.irp$r_func_bits.irp$v_fcode
#define irp$v_fmod irp$r_func_overlay.irp$r_func_bits.irp$v_fmod
#define irp$l_diagbuf irp$r_diagbuf_overlay.irp$l_diagbuf
#define irp$l_scb_buf irp$r_diagbuf_overlay.irp$l_scb_buf
#define irp$w_tt_prmpt irp$r_diagbuf_overlay.irp$w_tt_prmpt
#define irp$l_seqnum irp$r_seqnum_overlay.irp$l_seqnum
#define irp$l_dcd_src_ucb irp$r_seqnum_overlay.irp$l_dcd_src_ucb
#define irp$l_keydesc irp$r_keydesc_overlay.irp$l_keydesc
#define irp$l_wle_ptr irp$r_keydesc_overlay.irp$l_wle_ptr
#define irp$b_cpy_mode irp$r_keydesc_overlay.irp$b_cpy_mode
#define irp$q_qio_p1 irp$r_fill_12.irp$q_qio_p1
#define irp$l_qio_p1 irp$r_fill_12.irp$r_fill_13.irp$l_qio_p1
#define irp$q_qio_p2 irp$r_fill_14.irp$q_qio_p2
#define irp$l_qio_p2 irp$r_fill_14.irp$r_fill_15.irp$l_qio_p2
#define irp$q_qio_p3 irp$r_qio_p3_overlay.irp$q_qio_p3
#define irp$l_qio_p3 irp$r_qio_p3_overlay.irp$l_qio_p3
#define irp$q_param_3 irp$r_qio_p3_overlay.irp$q_param_3
#define irp$q_qio_p4 irp$r_fill_16.irp$q_qio_p4
#define irp$l_qio_p4 irp$r_fill_16.irp$r_fill_17.irp$l_qio_p4
#define irp$q_qio_p5 irp$r_fill_18.irp$q_qio_p5
#define irp$l_qio_p5 irp$r_fill_18.irp$r_fill_19.irp$l_qio_p5
#define irp$q_qio_p6 irp$r_fill_20.irp$q_qio_p6
#define irp$l_qio_p6 irp$r_fill_20.irp$r_fill_21.irp$l_qio_p6
#define irp$b_shd_pio_cnt irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$b_shd_pio_cnt
#define irp$b_shd_pio_act irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$b_shd_pio_act
#define irp$b_shd_pio_flags irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$b_shd_pio_flags
#define irp$v_pio_error irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_error
#define irp$v_pio_fanout irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_fanout
#define irp$v_pio_noque irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_noque
#define irp$v_pio_cancel irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_cancel
#define irp$v_pio_cthrdok irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_cthrdok
#define irp$v_pio_phaseii irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_pio_flags_overlay.irp$r_pio_bits.irp$v_pio_phaseii
#define irp$b_shd_pio_errcnt irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$b_shd_pio_errcnt
#define irp$b_shd_pio_errindex irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$b_shd_pio_errindex
#define irp$b_shd_pio_errsev irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$b_shd_pio_errsev
#define irp$q_shd_lock_fr0 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_lock_fr0
#define irp$q_shd_lock_fr1 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_lock_fr1
#define irp$q_shd_lock_fr2 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_lock_fr2
#define irp$q_shd_lock_fr4 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_lock_fr4
#define irp$q_shd_lock_fr5 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_lock_fr5
#define irp$l_shd_lock_fpc irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_lock_fpc
#define irp$l_shd_pio_error irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_pio_error
#define irp$l_shd_pio_lnk irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_pio_lnk
#define irp$l_shdspc irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shdspc
#define irp$l_shd_control_irp irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_control_irp
#define irp$l_shd_temp irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_temp
#define irp$q_shd_saved_r1 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_saved_r1
#define irp$q_shd_saved_r2 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_saved_r2
#define irp$q_shd_saved_r4 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_saved_r4
#define irp$l_shd_svd_cnt_irp irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_svd_cnt_irp
#define irp$l_shd_saved_status irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_saved_status
#define irp$l_shd_wlg_mode_fpc irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_wlg_mode_fpc
#define irp$l_shd_perlkid irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_perlkid
#define irp$l_shd_expel_timer irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_expel_timer
#define irp$l_shd_expel_flags irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_expel_flags_overlay.irp$l_shd_expel_flags
#define irp$v_shd_expel_removed irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_expel_flags_overlay.irp$r_expel_bits.irp$v_sh\
d_expel_removed
#define irp$l_shd_expel_mask irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_shd_expel_mask
#define irp$q_shd_reserv_q8 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_reserv_q8
#define irp$q_shd_reserv_q9 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_reserv_q9
#define irp$q_shd_reserv_q10 irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$q_shd_reserv_q10
#define irp$b_shd_flags irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_shd_flags_overlay.irp$b_shd_flags
#define irp$v_cln_ready irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_shd_flags_overlay.irp$r_shd_flag_bits.irp$v_cln_ready
#define irp$v_cln_done irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_shd_flags_overlay.irp$r_shd_flag_bits.irp$v_cln_done
#define irp$v_cpy_fini irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$r_shd_flags_overlay.irp$r_shd_flag_bits.irp$v_cpy_fini
#define irp$l_curcsp irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_curcspx
#define irp$v_onstack (irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_stkflgsx & 1)
#define irp$a_ctxstk irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$a_ctxstkx[0]
#define irp$l_stkflgs irp$r_cdrp_extensions.irp$r_shadowing_extension.irp$l_stkflgsx
#define irp$l_cdrpfl irp$r_cdrp_extensions.irp$r_blk_xfer_extension.irp$r_rbufh_ad_overlay.irp$l_cdrpfl
#define irp$l_rboff irp$r_cdrp_extensions.irp$r_blk_xfer_extension.irp$l_rboff
#define irp$l_xct_len irp$r_cdrp_extensions.irp$r_blk_xfer_extension.irp$l_xct_len
#define irp$t_lbufhndl irp$r_cdrp_extensions.irp$r_cls_drv_extension.irp$t_lbufhndl
#define irp$l_ubarsrce irp$r_cdrp_extensions.irp$r_cls_drv_extension.irp$l_ubarsrce
#define irp$l_dutuflags irp$r_cdrp_extensions.irp$r_cls_drv_extension.irp$l_dutuflags
#define irp$w_dutucntr irp$r_cdrp_extensions.irp$r_cls_drv_extension.irp$w_dutucntr
#define irp$w_endmsgsiz irp$r_cdrp_extensions.irp$r_cls_drv_extension.irp$w_endmsgsiz
#define irp$l_erase_vbn irp$r_cdrp_extensions.irp$l_erase_vbn
 
#define IRP$K_LENGTH 744                /* LENGTH OF STANDARD IRP           */
#define IRP$C_LENGTH 744                /* LENGTH OF STANDARD IRP           */
#define IRP$S_IRPDEF 744                /* OLD IRP SIZE FOR COMPATIBILITY   */
 
#pragma __member_alignment __restore
#ifdef __INITIAL_POINTER_SIZE			 /* Defined whenever ptr size pragmas supported */
#pragma __required_pointer_size __restore		 /* Restore the previously-defined required ptr size */
#endif
#ifdef __cplusplus
    }
#endif
#pragma __standard
 
#endif /* __irpdef loaded */ 

#endif
/* end STDIRP conditional stuff */

/* Definitions of other SWdriver structures */

#include	candef
#include	ccbdef			/* Channel control block */
#include	cddbdef			/* Cluster DDB def */
#include	crbdef			/* Controller request block */
#include	cramdef			/* Controller register access method */
#include	dcdef			/* Device codes */
#include	ddbdef			/* Device data block */
#include	ddtdef			/* Driver dispatch table */
/* temp def of nosrv bit */
#define DEV$M_NOSRV 0
/* use dev$m_fill_2 bit for serve... */
#define DEV$M_SERVESOMEHOW 134217728    /* should cover both bits */
#include	devdef			/* Device characteristics */
#include	dptdef			/* Driver prologue table */
#include	dyndef			/* Dynamic type definitions */
#include	embdvdef		/* Error log entry for devices */
#include	fdtdef			/* Function decision table */
#include	fdt_contextdef			/* Function decision table */
#include	fkbdef			/* Fork block */
#include	idbdef			/* Interrupt data block */
#include	iocdef			/* IOC constants */
#include	iodef			/* I/O function codes */
#include	orbdef			/* Object rights block */
#include	pagedef			/* Get page definitions and disk block size */
#include	pcbdef			/* Process control block */
#include	ptedef			/* Page Table entry definitions */
#include	sbdef			/* System block def */
#include	ssdef			/* System service status codes */
#include	stddef			/* Common definitions */
#include	stsdef			/* Status value fields */
#include	ucbdef			/* Unit control block */
#include	vadef			/* Virtual address definitions */


#define FDT$PS_FUNC_RTN 8

/* Define function prototypes for system routines */

#include	acp_routines		/* ACP$ and ACP_STD$ routines */
#include	erl_routines		/* erl$ and erl_std$ routines */
#include	exe_routines
#include	ioc_routines
#include	sch_routines
#include	com_routines


#include	exe_routines		/* exe$ and exe_std$ routines */
#include	ioc_routines		/* ioc$ and ioc_std$ routines */
#include	ldr_routines		/* ldr$ and ldr_std$ routines */


/*#include	sch_routines		/* sch$ and sch_std$ routines */

/* Define various device driver macros */

#include	vms_drivers		/* Device driver support macros */


#include	vms_macros		/* Define bug_check and such */

#include	descrip

#define IO$S_FCODE 6

/* Define the DEC C functions used by this driver */

#include	builtins		/* C builtin functions */
#include	string			/* String rtns from "kernel CRTL" */

/* Define some useful types */

typedef unsigned short int WORD;	/* Define a WORD (16 bits) */
typedef unsigned char BYTE;		/* Define a BYTE (8 bits) */
typedef unsigned int UINT;		/* Usigned int (32 bits) */
typedef unsigned char uchar;

/* Define constants specific to this driver */

enum {				/* Timeout times */
    TIMEOUT_TIME=   10,			/* I/O Timeout time (seconds) */
    DRQ_TIME	= 1000*1000,		/* DRQ wait time (1 millisecond) */
    RESET_TIME	=    2,			/* Reset time (seconds) */
    READY_TIME	= 1000*100		/* Ready time (100 microseconds) */
};

enum {				/* Controller constants */
    DEVICE_IPL	=   8			/* IPL of device, notice same as fork */
};

/* Define some useful constants */
/* NUM_HOSTS must be a power of 2 */
#define NUM_HOSTS 8			/* Number of paths we can handle */

enum {				/* Geometry and transfer constants */
    MAX_SECTOR	=  256,			/* Max sectors per track */
    MAX_HEAD	=   16,			/* Max heads per cylinder */
    MAX_CYLINDER= 8192,			/* Max cylinders per drive */
    MAX_XFER	=  1,			/* Max transfer size (sectors) * (127 temp to 1) */
    BLK_SIZE	= IOC$C_DISK_BLKSIZ,	/* Size of a disk block (in bytes) */
    BLK_MASK	= IOC$M_BLOCK_BYTEMASK,	/* "Byte within block" mask */
    BLK_SHIFT	=    9			/* Shift factor for blocks to bytes */
};

/* External references */

extern	int	MMG$GL_BWP_MASK;	/* Byte-within-page mask */
extern	int	MMG$GL_PAGE_SIZE;	/* Page size in bytes */
extern	int	MMG$GL_VPN_TO_VA;	/* Page to byte shift count */
extern	PTE	*MMG$GL_SPTBASE;	/* Base of system page table */
extern  long    SCH$GL_PCBVEC;
extern  long    SCH$GL_MAXPIX;
extern	int	MMG$GL_PTE_OFFSET_TO_VA;/* Shift for PTE to VA conversion */
extern	DDT	driver$ddt;		/* Prototype DDT */
extern	DPT	driver$dpt;		/* Prototype DPT */
extern	FDT	driver$fdt;		/* Prototype FDT */
extern	long	clu$gl_allocls;		/* cpu allocation class */
#ifdef DEBUG
extern void ini$brk(void);		/* Breakpoint routine */
#endif
extern	UCB	*sys$ar_bootucb;	/* Boot UCB */
extern  int     call_macro_pass_r3(int address, char* cddb);
extern  long	exe$gl_abstim;

/* Shortcuts for some of the external references */

#define	_ddt	driver$ddt		/* Abbreviation for DDT */
#define	_dpt	driver$dpt		/* Abbreviation for DPT */
#define	_fdt	driver$fdt		/* Abbreviation for FDT */



/* the auxiliary structure we need if an IRP lacks enough IRP stack to
   hold our context data. This has been referred to at some places as
   the "IRP context block" though that also refers to an area of IRP
   stack (which must be added to irpdef).
*/
/* The structure we use to hold IRP context blocks */
/* If an IRP is too short for an IRP stack we use this, or if the IRP
   stack gets filled. */

#define IRP$K_LENGTH_V71 568		/* IRP size if no IRP stack */
						/* (+ bit of slop ) */
typedef struct axs {
    long*	axs$l_fwd;		/* forward pointer	*/
    long*	axs$l_back;		/* back pointer		*/
    WORD	axs$w_size;		/* size			*/
    uchar	axs$b_type;		/* type			*/
    uchar	axs$b_fil1;		/* filler 1		*/
    long *	axs$l_irp;		/* IRP we point at 	*/
    long	axs$l_orgucb;		/* original UCB for IRP	*/
    long	axs$l_pid;		/* irp$l_pid save	*/
    long	axs$l_orgics;		/* original intercept stack state */
    long	axs$l_orgstat;		/* IRP stat save	*/
    long	axs$l_media;		/* irp$l_media save	*/
    long	axs$l_flgs;		/* flags		*/
    long	axs$l_swucb;		/* Switch UCB save	*/
#ifdef DEBUG
    long	axs$l_func;		/* irp$l_func */
#endif
    } AXS;

/* The structure of an IRP stack entry as we use it. No headers needed. */
typedef struct axsstk {
    long *	axs$l_irp;		/* IRP we point at 	*/
    long	axs$l_orgucb;		/* original UCB for IRP	*/
    long	axs$l_pid;		/* irp$l_pid save	*/
    long	axs$l_orgics;		/* original intercept stack state */
    long	axs$l_orgstat;		/* IRP stat save	*/
    long	axs$l_media;		/* irp$l_media save	*/
    long	axs$l_flgs;		/* flags		*/
    long	axs$l_swucb;		/* SW UCB		*/
    } AXSSTK;


/* Define Device-Dependent Unit Control Block with extensions for SW device */

/* First, structures needed within that won't nest 			*/
/* about all the paths.							*/


/* The structure in which we save info about the entries for UCBs
   that are intercepted. For symmetry the main UCB info is also
   stored here in entry 0. */
typedef struct pthptr {
    DT_UCB *	ucb$l_hstucb;		/* UCB of path			*/
    void *	ucb$l_oldmv;		/* original mount ver entry	*/
    void *	ucb$l_oaltst;		/* original altstart entry	*/
    void *	ucb$l_qirp;		/* original pending_io entry	*/
    void *	ucb$l_cancl;		/* original cancel io entry	*/
    void *	ucb$l_aux;		/* original auxiliary io entry	*/
    long	ucb$l_enapth;		/* path enabled flag		*/
    uint64	ucb$q_pthtim;		/* time of last I/O		*/
    long	ucb$l_pthios;		/* Count of I/Os on this path	*/
    long	ucb$l_ptherr;		/* Count of errors on this path	*/
    } PTHPTR;    

void __PAL_IMB(void);
void __MB(void);

typedef struct vddt {
     char ddtchr[DDT$K_LENGTH];
     } DDTA;
/* Now the UCB extension itself 			*/
/* Note that since we need to do address arithmetic to go from DDT to
   intercept UCB, define a union that will give us the offset as a sizeof
   operator which can then be used reasonably. */

typedef struct sw_ucb {
/* we need to find UCB start, so will need to subtract a sizeof from the  */
/* DDTab of the intercepted drive, so form a union here so we can do that.*/
/*  union{ */
   struct AhdofD {
    DT_UCB	ucb$r_dtucb;		/* Generic UCB */
    union xx3{
        UINT	lbn;			/* Block number */
        struct {
            BYTE	sec;		/* Sector number */
            BYTE	trk;		/* Track number */
            WORD	cyl;		/* Cylinder number */
        } pa;
    } ucb$l_media; /* end media field union */
    UINT	ucb$l_org_media;	/* Original LBN */
    void	*ucb$l_org_svapte;	/* Original SVAPTE address */
    UINT	ucb$l_org_bcnt;		/* Original byte count */
    UINT	ucb$l_org_boff;		/* Original byte offset */
    union xx4 {
        UINT	lw;			/* Flags */
        struct bits {
            UINT	lba : 1;	/* LBA addressing */
            UINT	dma : 1;	/* DMA capable */
        };
    } ucb$l_flags; /* end flags union */
     struct {
/* The following area is the start of the intercept block needed for intercepts
   of driver entries. It has the following structure:

  Unique ID - generally the DPT address of the intercept driver, which will
		be unique for that driver on a given machine.
  Intercept DDT - the address of our interceptor's DDT. That is, if a second
		intercept occurs this cell should be filled in with a pointer
		to the interceptor's copy of the DDT, so this becomes a
		forward linked list outbound. If there is no intercept this
		should be null.
  Previous DDT - This cell holds a pointer to the DDT address as it was in
		the ucb$l_ddt of the intercepted driver before we intercepted
		the I/O and replaced the address with our own. This is an
		inbound list, singly linked. We use these two lists to permit
		intercepts to be added or removed.

 (Two lists are used here since the intercepted UCB in general has nowhere
  to keep a list head, though we need to be able to go either way.)

  ICsign	- This cell holds a "magic number" which acts as a flag that
		the intercept we find is using the same scheme we are. This
		value is computed as hex F0070000 + DDT$K_LENGTH +
		256*(length of the part of the intercept block that 
		precedes the DDT copy). Thus it is sensitive to the amount
		of "stuff" preceding the DDT copy and to the DDT length.
  Flags 1	- Longword of flags. Only ones defined are fi8ok and
		nofipl8, to flag whether IPL8 postprocessing could be used
		with the intercept code of this intercept and all
		prior ones.
  Flags 2	- Spare longword of flags
  Userfiller[8] - 8 more longwords, reserved for any third parties who
		might need or want to keep extra information in their
		intercept blocks
  DDT		- Copy of the intercepted driver's DDT, edited to point
		to whatever code is inserted first. The theory is that
		code wanting to continue with the intercepted code
		original function can follow the back link and use that
		to call the original DDT functions.

Note too that the DDT contains an FDT pointer and several more, and that
by inserting a copy of the original FDT with edits it is possible in like
manner to chain FDT intercepts. At the end of an inserted FDT bit of code,
one would locate and call the previous FDT table's routne.

The presumption is also that intercepts are added by grabbing the
intercepted driver's UCB$L_DDT pointer and that intercepts generally
do not know of other intercepts' existence, save that when locating
their intercept UCBs (or other data structures; the intercept blocks don't
have to be inside UCBs) they check that the one they pick has the correct
driver DPT address. Thus their intercepts would be called first, so that
"inner" pointers grabbed at any stage will be valid. (That is, pointers to
"previous" routine addresses grabbed by an intercept driver.)

*/
/* prepend start */
     long       ucb$l_uniqid;           /* Unique ID to identify this UCB */
     long *	ucb$l_intcddt;		/* Our interceptor's DDT addr 	*/
     long *	ucb$l_prevddt;		/* Previous DDT address 	*/
     long	ucb$l_icsign;		/* Unique "magic number" that shows */
			/* that this area is a DDT intercept block in this  */
			/* format. The magic number encodes the DDT length  */
			/* to ensure compatibility in linking also.         */
     union xx5 {
        UINT	lfg;			/* Block number */
        struct {
            UINT	ucb$v_fi8ok : 1;	/* OK to post at IPL8       */
            UINT	ucb$v_nofipl8 : 1;	/* NOT OK to post at IPL8   */
        } pa;
     } ucb$l_icpflgs;
     union xx6{
        UINT	lfg2;			/* Block number */
        struct {
            UINT	FILLER : 1;	/* filler; this for future flgs */
        } pa;
     } ucb$l_icpflg2;
/* Since this same intercept will be needed for all user intercepts also,
   or the traceback will fail, leave some space to be used by such intercepts
   should any third party applications need it. 8 longwords should be enough
   for anything I can currently imagine, particularly since it can be used
   to point to other structures should more space be needed. */
     long userfiller[8];			/* Space for user intercept data */
/* prepend end */
    } DI_PPD;
   } ucb$ahdofddt;
/*  char ucbpfx[10];*/ /* smaller than the struct for now */
/*  }; */ /* end prefix union */

/* NOTA BENE */
/* This field must be 8 byte aligned, so be sure that the UCB
   ahead of it is such that this is the case. If the distance between
   the DDT and immediately preceding fields changes, other intercepts
   may not interoperate with SWDRIVER */

    DDTA ucb$a_vicddt; /* Our intercept DDT copy	*/
/* a magic number has to go into the ucb$l_icsign long */
    FDT		ucb$l_myfdt;		/* Copy of user FDT	*/
    long *	ucb$l_backlk;		/* Points at intercepted UCB	*/
    long *	ucb$l_oldfdt;		/* Pointer to previous FDT */
    long *	ucb$l_vict;		/* Address of intercepted UCB */
    long	ucb$l_mungd;		/* Flag that this UCB was altered */
    long	ucb$l_retries;		/* Counter of MV packacks	*/
    void *	ucb$l_hstartio;		/* Host original start_io address */
    long	ucb$l_mbxucb;		/* UCB of mailbox to talk to server */
    long	ucb$l_daemon;		/* IPID of server process here 	*/
    long	ucb$l_sawsucc;		/* Flag that we saw something not a packack*/
    long	ucb$l_indrct;		/* Which path we are using	*/
    long	ucb$l_outstnd;		/* Number of outstanding IRPs on curr pth */
    long	ucb$l_ptherrs;		/* Counter of errors in a row	*/
    uint64	ucb$q_swtime;		/* Time of last path switch	*/
    long *	ucb$l_mypost;		/* Address we'll use for post proc. */
    long	ucb$l_pthord[NUM_HOSTS];/* Order we look for paths in   */
#ifdef DEBUG
    AXS *	ucb$l_axsque;		/* make it easy to find the axs queue */
    long	ucb$l_dbgsts;		/* save last io status returned */
    long	ucb$l_irpsts;
    long 	ucb$l_irpfunc;
    long	ucb$l_irpadr;
    long	ucb$l_irppid;
    long	ucb$l_irpadrp;
#endif
    IRP*	ucb$l_errirp;		/* IRP we replaced err code for	*/
    IRP*	ucb$l_delayirp;		/* Pointer to IRP we use to delay with */
/* keep pointer around for IRP we delay with for debug */
    long	ucb$l_dwell;		/* Dwell time for switch hysteresis */
    struct sw_ucb * ucb$l_delayucb;     /* Pointer to UCB we use to delay with */
    long	ucb$l_mscpctrl;		/* CDDB controller type if an MSCP dvc */
    long	ucb$l_srvbits;		/* mscp or qioserver serveable bits of master */
    long	ucb$l_errchgwedge;	/* nonzero if we're changing errs to medofl */
    PTHPTR      ucb$a_hstdta[NUM_HOSTS]; /* Host data about each path	*/
    PCB		ucb$l_fakepcb;		/* Fake PCB for MSCP cancels	*/
    } SW_UCB;

/* the "56" in the def of "magic" below must be the size of the DI_PPD
   area in SW_UCB in bytes, to ensure compatible intercepts. */
/* (In Macro I'd use offsets) */
#define magic  (0xF0070000 + DDT$K_LENGTH + (256 * 56))
#define ucb$r_SW_ucb ucb$ahdofddt.ucb$r_dtucb.ucb$r_dpucb.ucb$r_erlucb.ucb$r_ucb
#define ucb$r_SW_erl ucb$ahdofddt.ucb$r_dtucb.ucb$r_dpucb.ucb$r_erlucb
#define ucb$r_SW_dp  ucb$ahdofddt.ucb$r_dtucb.ucb$r_dpucb
#define ucb$r_SW_dt  ucb$ahdofddt.ucb$r_dtucb

#define	baseucb	ucb->ucb$r_SW_ucb



#define	IS_SET(reg,bits) ( (reg & bits) == bits )
#define	IS_CLEAR(reg,bits) ( (reg & bits) == 0 )

#define	$SUCCESS(code)	( (code & STS$M_SUCCESS) == 1)
#define	$FAIL(code)	( (code & STS$M_SUCCESS) == 0)

#define	TRUE	1
#define	FALSE	0


/* Prototypes for driver routines defined in this module */

int	driver$init_tables();
void	struc_init(CRB *crb, DDB *ddb, IDB *idb, ORB *orb, SW_UCB *ucb);
void	struc_reinit(CRB *crb, DDB *ddb, IDB *idb, ORB *orb, SW_UCB *ucb);
int	ctrl_init(IDB *idb, DDB *ddb, CRB *crb);
int	unit_init(IDB *idb, SW_UCB *ucb);
void	unit_init_fork(void *fr3, IDB *idb, SW_UCB *ucb);
void    send_daemon_msg(IRP * irp,SW_UCB* myswucb,UCB* origucb, int rtncd);
int steal_cancel(int chan, IRP* irp, PCB* pcb, UCB* ucb, int reason);
int steal_aux_routine(char* cddb);
int steal_pending(IRP* qhdr, IRP* irp, UCB* ucb);
void steal_altstart(IRP* irp, UCB* ucb);
void unfixirp(IRP* irp, SW_UCB* myswucb, int npath);
void fixirp(IRP* irp, SW_UCB* myswucb, int npath);
void steal_mount_ver(IRP* irp, UCB* ucb);
void steal_start(IRP* irp, UCB* ucb);
int fdt_to_original(IRP* irp,PCB* pcb,SW_UCB* ucb,CCB* ccb);
int sw_format(IRP *irp, PCB *pcb, SW_UCB *ucb, CCB *ccb);
int sw_fakeformat(char* buffer, int bufsiz, SW_UCB *ucb);
void mung(SW_UCB* swucb, UCB* vicucb, DDB* vicddb, SB* vicsb, long mysw);
void unmung(SW_UCB* swucb, UCB* vicucb, DDB* vicddb, SB* vicsb, long mysw);
void sw_findelio(IRP* irp, SW_UCB* swucb, SW_UCB* ucb);
SW_UCB* getswucb(UCB* inucb);
int sw_cloneducb(UCB* cloneducb, DDT* ddt, PCB* pcb, UCB* templateucb);
void fixsplit(IRP* irp);
void startio(IRP* irp, UCB *ucb);


/* list head for aux structure */
AXS* SW_AXSHH;
AXS* SW_AXSTT;

/* Last path number we started mv on */
long lastmvgo;


/* DRIVER$INIT_TABLES - Initialize Driver Tables
/*									*/
/* This routine is used to initialize the driver tables.  The DPT, DDT	*/
/* and FDT structures are set up.					*/
/*									*/
/* Usage:								*/
/*	status = driver$init_tables();					*/
/*									*/
/* Input:								*/
/*	none								*/
/*									*/
/* Output:								*/
/*	none								*/
/*									*/
/* Return value:							*/
/*	SS$_NORMAL	tables successfully set up			*/

int driver$init_tables()  {

/* Finish initialization of the Driver Prologue Table (DPT) */

    ini_dpt_name        (&_dpt,"SWDRIVER");	/* Driver name */
    ini_dpt_flags	(&_dpt,DPT$M_SMPMOD);	/* Set flags */
    ini_dpt_maxunits    (&_dpt,1000);		/* 1000 units max */
    ini_dpt_ucbsize     (&_dpt,sizeof(SW_UCB));	/* UCB size */
    ini_dpt_struc_init  (&_dpt,struc_init);	/* Structure init rtn */
    ini_dpt_struc_reinit(&_dpt,struc_reinit);	/* Structure reinit rtn */
    ini_dpt_end         (&_dpt);

/* Finish initialization of the Driver Dispatch Table (DDT) */

    ini_ddt_ctrlinit    (&_ddt,ctrl_init);	/* Controller init rtn */
    ini_ddt_unitinit    (&_ddt,unit_init);	/* Unit init rtn */
    ini_ddt_cloneducb	(&_ddt,sw_cloneducb);   /* identify cloned ucb routine */
    ini_ddt_start       (&_ddt,startio); /* Exec's Start I/O rtn */
    ini_ddt_cancel      (&_ddt,ioc_std$cancelio); /* Cancel rtn */
    ini_ddt_end         (&_ddt);

/* Finish initialization of the Function Decision Table (FDT) */

    ini_fdt_act(&_fdt,IO$_READLBLK,  acp_std$readblk,	DIRECT_64);
    ini_fdt_act(&_fdt,IO$_READPBLK,  acp_std$readblk,	DIRECT_64);
    ini_fdt_act(&_fdt,IO$_READVBLK,  acp_std$readblk,	DIRECT_64);
    ini_fdt_act(&_fdt,IO$_WRITECHECK,acp_std$readblk,	DIRECT_64);

    ini_fdt_act(&_fdt,IO$_ACCESS,    acp_std$access,	BUFFERED);
    ini_fdt_act(&_fdt,IO$_CREATE,    acp_std$access,	BUFFERED);

    ini_fdt_act(&_fdt,IO$_DEACCESS,  acp_std$deaccess,	BUFFERED);

    ini_fdt_act(&_fdt,IO$_ACPCONTROL,acp_std$modify,	BUFFERED);
    ini_fdt_act(&_fdt,IO$_DELETE,    acp_std$modify,	BUFFERED);
    ini_fdt_act(&_fdt,IO$_MODIFY,    acp_std$modify,	BUFFERED);

    ini_fdt_act(&_fdt,IO$_MOUNT,     acp_std$mount,	BUFFERED);

    ini_fdt_act(&_fdt,IO$_WRITELBLK, acp_std$writeblk,	DIRECT_64);
    ini_fdt_act(&_fdt,IO$_WRITEPBLK, acp_std$writeblk,	DIRECT_64)
    ini_fdt_act(&_fdt,IO$_WRITEVBLK, acp_std$writeblk,	DIRECT_64);

    ini_fdt_act(&_fdt,IO$_UNLOAD,    exe_std$lcldskvalid,BUFFERED_64);
    ini_fdt_act(&_fdt,IO$_AVAILABLE, exe_std$lcldskvalid,BUFFERED_64);
    ini_fdt_act(&_fdt,IO$_PACKACK,   exe_std$lcldskvalid,BUFFERED_64);

    ini_fdt_act(&_fdt,IO$_NOP,	     exe_std$zeroparm,	BUFFERED_64);

    ini_fdt_act(&_fdt,IO$_PHYSICAL,  sw_format,		DIRECT);


    ini_fdt_act(&_fdt,IO$_SEEK,      exe_std$oneparm,	BUFFERED);
    ini_fdt_act(&_fdt,IO$_FORMAT,    exe_std$oneparm,	BUFFERED);

    ini_fdt_act(&_fdt,IO$_SETMODE,   exe_std$setchar,	BUFFERED_64);
    ini_fdt_act(&_fdt,IO$_SETCHAR,   exe_std$setchar,	BUFFERED_64);

    ini_fdt_act(&_fdt,IO$_SENSEMODE, exe_std$sensemode,	BUFFERED_64);
    ini_fdt_act(&_fdt,IO$_SENSECHAR, exe_std$sensemode,	BUFFERED_64);

    ini_fdt_end(&_fdt);

/* If we got this far then everything worked, so return success. */

    return SS$_NORMAL;			/* Return with success status */
}


/* STRUC_INIT - Device Data Structure Initialization Routine
/*									*/
/* This routine is used to initialize the data structures at driver	*/
/* loading time.							*/
/*									*/
/* Usage:								*/
/*	struc_init(crb, ddb, idb, orb, ucb)				*/
/*									*/
/* Input:								*/
/*	crb	pointer to CRB						*/
/*	ddb	pointer to DDB						*/
/*	idb	pointer to IDB						*/
/*	orb	pointer to ORB						*/
/*	ucb	pointer to UCB						*/
/*									*/
/* Output:								*/
/*	none								*/
/*									*/
/* Return value:							*/
/*	none								*/

void struc_init(CRB *crb, DDB *ddb, IDB *idb, ORB *orb, SW_UCB *ucb) {

/* Initialize the fork lock and device IPL fields */

    baseucb.ucb$b_flck = SPL$C_IOLOCK8;	/* set up fork lock index */
    baseucb.ucb$b_dipl = DEVICE_IPL;	/*  and device IPL (8 for this case!)*/

/* Initialize some UCB fields */
/* we do NOT set the DEV$M_FOD bit since we don't want file operations on
   the switch driver. No need for such. */

    baseucb.ucb$l_devchar = (DEV$M_DIR + DEV$M_AVL + DEV$M_ELG +
                             DEV$M_IDV + DEV$M_ODV + DEV$M_SHR + DEV$M_RND);
    baseucb.ucb$l_devchar2 = DEV$M_NLT;		/* Not a "last trk" dvc */
    baseucb.ucb$b_devclass = DC$_MISC; 		/* Device class is random dvc */
    baseucb.ucb$b_devtype  = DT$_FD2;		/* Device type for DDR */
    baseucb.ucb$w_devbufsiz= BLK_SIZE;		/* Size of a block */
    baseucb.ucb$l_devsts   = UCB$M_NOCNVRT;	/* Do NOT convert LBNs */
    return;
}


/* STRUC_REINIT - Device Data Structure Re-Initialization Routine
/*									*/
/* This routine is used to reinitialize the data structures at driver	*/
/* reloading time.							*/
/*									*/
/* Usage:								*/
/*	struc_init(crb, ddb, idb, orb, ucb)				*/
/*									*/
/* Input:								*/
/*	crb	pointer to CRB						*/
/*	ddb	pointer to DDB						*/
/*	idb	pointer to IDB						*/
/*	orb	pointer to ORB						*/
/*	ucb	pointer to UCB						*/
/*									*/
/* Output:								*/
/*	none								*/
/*									*/
/* Return value:							*/
/*	none								*/

void struc_reinit (CRB *crb, DDB *ddb, IDB *idb, ORB *orb, SW_UCB *ucb) {
    
    ddb->ddb$ps_ddt = &_ddt;		/* Point ddb to the ddt */

    return;				/* Return to caller */
}

/*	sw-format fdt	- IO$_PHYSICAL local FDT processing		*/
/*									*/
/*	This routine handles the FDT processing for the IO$_PHYSICAL	*/
/* function. This function is used by SWdriver to do initialization and */
/* setup so that its server need not know the format of its local UCB	*/
/* extension. It takes one argument, a buffer whose contents are 	*/
/* interpreted to perform the operations. Connecting or disconnecting	*/
/* to a disk are included in functions here.				*/
/*									*/
/* Input:								*/
/*	irp	pointer to IRP						*/
/*	pcb	pointer to PCB						*/
/*	ucb	pointer to UCB						*/
/*	ccb	pointer to CCB						*/
/* P1,P2 point to an input control block. */
/*
Buffer formats:

ufunc	uswitch lo	uswitch hi	ulen		rest
1					dvc name len	dvc name	bash
2					dvc name len	dvc name	unbash
3	unit to sw to	idlfgs=0	switch, err if dvc bsy
3	"		idlfgs=1	save next irp, busy dvc
3	"		idlfgs=2	restart i/o, unbusying dvc, no switch
4	unit to rpt	?		statblk len	statblk data rpt stats
			Returns statistics on device paths this SW unit is
			using in an array.
5	UCB of device			x		x
		returns SW UCB, pathpointer array address, and some other
			info. Switch part of array gets the SW UCB or zero
			if none is found. This request can be sent to ANY
			SWdriver unit for any device to find out if the device
			is intercepted. It will return the SW ucb and info
			about the SWdriver unit that intercepted the device
			if it is intercepted.
6	IPID of server (both halves)	mbx name len	mbx name
			Sets the mailbox UCB and server IPID (if they
			are legal & found) into this SW driver unit UCB
			so it will communicate with the mailbox to talk
			to the server.
7	x		x	x	x	x	
			Clears the SWdriver's references to the server.
8			Returns a magic number for this driver, adding 4
			if the driver is online. Use from user mode code
			to ensure you are talking to swdriver.
9	As 1 and 2, but ulen is device UCB, not name. DDB and SB are
10		obtained from the UCB pointer and DDB pointer.
		This is done to simplify kernel mode setup.
11			x	x	x	x	x
			Returns SW UCB pointer, value of ucb$l_indrct,
			and the PTHPTR structure addresses of all PTHPTR
			structures, and of the one which is current.
12
			Functions like 5, except that no input UCB need
			be supplied. It returns information about the
			devices the pointed-to SW device has associated
			with it.
*/
/*									*/
/* Output:								*/
/*									*/
/* Return value:							*/
/*	SS$_FDT_COMPL	shows that the routine completed correctly	*/

int sw_format(IRP *irp, PCB *pcb, SW_UCB *ucb, CCB *ccb) {

/*
  Define the input buffer type as a union struct so each function can
  find a convenient type to use.
*/
#define MPSZ 128
#define MPSZQ 64
#define MPDN 512
/* Number of paths that will fit for statistics report */
#define MPNPTH ((MPSZ-5)/4)
  typedef struct inbuf {
    long ufunc; /* user function code (eg, bash/unbash) */
    long uswitch; /* info passed in (eg add, which unit, encoded) */
    long ulen;    /* length of device name passed */
    union  {
      char dvcname[MPDN];
      long moreprm[MPSZ];
    } ;
  } INBUF;
  typedef struct qinbuf {
    long ufunc; /* user function code (eg, bash/unbash) */
    long uswitch; /* info passed in (eg add, which unit, encoded) */
    long ulen;    /* length of device name passed */
    union  {
      char dvcname[MPDN];
      uint64 moreprm[MPSZQ];
    } ;
  } QINBUF;
  INBUF InBuf;
  int tstat;
  int tfcn;
  SB* mysb;
  INBUF * ibp;
  QINBUF * qibp;
  UCB* myucb;
  DDB* myddb;
  UCB* scratchucb;
  PTHPTR* mypthptr;
  SW_UCB* myswucb;
/* allow us to pull a quad into 2 longs */
  union {
    uint64 ttim;
    long ttiml[2];
    }timu;
  long myswitch;
  int trtns;
  int myswmod;
  int myunit;
  SW_UCB* scrswucb;
  int ufcode;
  int savipl;
  int curunit;	/* currently used subunit, for where we are switching */
  int kk,kkk,kkkk; /* temps */
  int i,ii,iii; /* temps */
  PCB* trypcb;
  UCB* tstucb;
  DP_UCB* mydpucb;
  long* pcbptr;
  PTHPTR * newpath;
  struct dsc$descriptor_s mydvcnm;

/* Ensure that the function is called with some modifier
   as a protection against someone randomly trying to format the SW 
   device */
    if (irp->irp$v_fmod == 0) {
    return (fdt_to_original(irp,pcb,ucb,ccb));
    }
/* check the buffer is writeable */
/* Also ensure it is the size we expect. */
   tstat = exe_std$writechk(irp,pcb,(UCB*)ucb,(void*)irp->irp$q_qio_p1,
			irp->irp$q_qio_p2);
   if (!$VMS_STATUS_SUCCESS(tstat) || irp->irp$q_qio_p2 < sizeof(INBUF)){
     return (exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_BADPARAM));
   };
   myswucb = (SW_UCB*) ucb;
   tfcn = irp->irp$v_fcode;
   ibp = (INBUF*) irp->irp$q_qio_p1;
   qibp = (QINBUF*)ibp;
   scrswucb = ucb;
/* Set up the descriptor */
   mydvcnm.dsc$w_length = strlen(ibp->dvcname);
   mydvcnm.dsc$b_class = DSC$K_CLASS_S;
   mydvcnm.dsc$a_pointer = &ibp->dvcname[0];
   mydvcnm.dsc$b_dtype = DSC$K_DTYPE_T;
   myswitch = ibp->uswitch;
/* extract word fields out of "myswitch" */
   myunit = myswitch & 65535;
   myswmod = (myswitch >> 16);
   
   ufcode = ibp->ufunc;
   scratchucb = (UCB*) ucb;
   switch (ufcode)
     {
/* 1 bashes disk, 2 unbashes */
/* 3 returns swdriver address and pthptr address if bashed by it. */
/* In these cases the "myswitch" arg low word is the unit to set or clear */

       case 1:{
              trtns = 1;
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
#ifdef DEBUG
/*  ini$brk(); */
#endif
	      sch_std$iolockw(pcb);
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat)){
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  if(myucb->ucb$v_online){
		    mung(scrswucb,myucb,myddb,mysb,myswitch);
                  } else {
                    trtns = 32;
                  }
		} else {
/* can't find device. Err...*/
/* Must place error in the fdt context area */
                }

	      sch_std$iounlock(pcb);
              irp->irp$l_media = trtns; /* return good status */
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
              }
       case 2:{
              
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
	      sch_std$iolockw(pcb);
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat)){
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  unmung(scrswucb,myucb,myddb,mysb,myswitch);
		};

	      sch_std$iounlock(pcb);
              irp->irp$l_media = 1; /* return good status */
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
              }
/* more options here */
/* Rather than use a different code, we will use this area to do manual
   switching also. The buffer format will in this case use the myswitch
   variable as two words also. The format will be

   Word 0: unit to switch to (0 to NUM_HOSTS)
   Word 1: idle-device flags: If 0, just return an error if the
				device was busy (active I/O count non zero)
				and return success only if the unit was idle
				and successfully switched.
			      If 1, flag startio intercept to save the
				next IRP address and leave the device busy
				until we later unbusy it. The unbusy call will
				be able to switch or not.
			      If 2, restart I/O with no switch using the saved
				IRP if any. If there is none, just return.

Note that the busying will not alter the outstanding I/O count ucb$l_outstnd
since it is purely inside the switching driver here. The caller can block or
unblock I/O but will need to wait for I/O to drain to switch paths; this is
easier in user mode so we'll leave it for that. We'll restart I/O by forking
and at fork level running REQCOM.
*/
       case 3:{
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
/* Again unit 65535 means "the next enabled unit" */
	        if (myunit > NUM_HOSTS){
		  for (kkk = 1; kkk <= NUM_HOSTS; kkk++){
/* get index of the next path (but go far enough to come back if we must) */
		    kkkk = (kkk + ucb->ucb$l_indrct)%NUM_HOSTS;
		    newpath = &ucb->ucb$a_hstdta[kkkk];
		    if (newpath->ucb$l_enapth){
		      myunit = kkkk;
		      break;
                    }
		  }
	        }
	        if (myunit > NUM_HOSTS){
/* if still above maxunits, error return */
		  return(exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_ABORT));
		}
		  newpath = &ucb->ucb$a_hstdta[myunit];
		  switch (myswmod){
		    case 0:{
		      if ((ucb->ucb$l_outstnd != 0 && irp->irp$l_qio_p4 != 31416)){
		        return(exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_IVSTSFLG));
		      } else {
/* OK, looks like we should switch the unit. Do so. */
/* (MANUAL switch here !! ) */
			device_lock(scratchucb->ucb$l_dlck,1,&savipl); /* *** fix this *** */
			ucb->ucb$l_indrct = myunit;
			ucb->ucb$l_dwell = exe$gl_abstim;
			ucb->ucb$l_retries = 0;
			device_unlock((struct _spl*)scratchucb->ucb$l_dlck,savipl,1);/* *** fix this *** */
			return(exe_std$finishio(irp,(UCB*)ucb));
		      }
		    }
		    case 1:{
/* flag this IRP is really for us and send to normal processing for the
   host device. If no host device just junk it. */
/* Since this is just a $qio, just queue it to the prime channel via
   exe$std_insioqc after setting irp$q_qio_p5 to the swucb value we
   have */
/* The start code to interpret this must exist ... doesn't yet */
			if (ucb->ucb$l_delayirp != 0) return(exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_IVSTSFLG));
			newpath = &ucb->ucb$a_hstdta[0];
			(long)irp->irp$q_qio_p5 = (long)ucb;
			ucb->ucb$l_errchgwedge = 0;
			ucb->ucb$l_delayirp = irp;
			irp->irp$l_ucb = (UCB*)newpath->ucb$l_hstucb;
			ucb->ucb$l_delayucb = ucb;
			exe_std$insioqc(irp, (UCB*)newpath->ucb$l_hstucb);
/* do NOT finish the I/O up yet. */
/* The IRP we queued to the target will not finish just yet but we get
   thru the FDT stuff here. The IRP will finish due to stuff in startio
   and our later call that will finish the IRP */
			return SS$_FDT_COMPL;		/* exit */
		    }
		    case 2:{
/* Here we finish up the IRP that was queued before. */
/* if QIO P4 is a -1, force outstanding I/O to zero. */
			if(irp->irp$l_qio_p4 == 31416)ucb->ucb$l_outstnd = 0;
			if (ucb->ucb$l_delayirp == 0) return(exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_IVSTSFLG));
/* Now we know that startio got the IRP. Whether or not there has been a call
   to switch units, complete that I/O. */
	/* gotta fork here and finish the IRP in fork level */
			fork(sw_findelio,(__int64)ucb->ucb$l_delayirp,(__int64)ucb->ucb$l_delayucb,(UCB*)ucb);
			exe_std$finishio(irp,(UCB*)ucb);
			return SS$_FDT_COMPL;
		    }
		    default:{
		      return(exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_BADPARAM));
		    }
		  }
	      sch_std$iounlock(pcb);
              irp->irp$l_media = 1; /* return good status */
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
	      }

       case 4:{
/* Use subfunction 4 for reporting statistics, which include I/O counts
   and error counts for all subpaths, and things like time of last switch
   and anything else that looks worth having. We'll just dump the numbers
   into the user's buffer which we know to be writable already. */
                ibp->moreprm[0] = NUM_HOSTS; /* Tell how long tables are */
                ibp->moreprm[1] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
	        qibp->moreprm[2] = myswucb->ucb$q_swtime;                
		kkk = 6;
                i = 0; /* use to count paths we actually see */
		for (kk = 0; kk < NUM_HOSTS; kk++){
		  mypthptr = &ucb->ucb$a_hstdta[kk];
		  if (kk < MPNPTH){
/* Store statistics data for output. */
/* The data is I/O count, error count, and time of last I/O for each path */
                    if ((long)mypthptr->ucb$l_hstucb < 0) i++;
		    ibp->moreprm[kkk++] = mypthptr->ucb$l_pthios; /* I/O count this path */
		    ibp->moreprm[kkk++] = mypthptr->ucb$l_ptherr; /* error cnt */
		    timu.ttim = mypthptr->ucb$q_pthtim; /* get time */
		    ibp->moreprm[kkk++] = timu.ttiml[0]; /* i/o time */
		    ibp->moreprm[kkk++] = timu.ttiml[1]; /* i/o time */
		  }
		}
                ibp->moreprm[0] = i;
                irp->irp$l_media = 1; /* return good status */
		return(exe_std$finishio(irp,(UCB*)ucb));
		break;
              }
       case 5:{
/* Use subfunction 5 to get back information about bashes. In this case
   we expect we are being called by some external code which needs to find
   out if the device being passed is in fact having its path intercepted by
   swdriver. Rather than make other code need to know the details of our
   internal structures, use this code to export the knowledge. On input
   look for a device UCB address, and if it is intercepted by a SW UCB
   return the SW unit number and the pthptr array address.
*/
	      if (myswitch < 0){
/* UCB address looks potentially valid */
	        myswucb = getswucb((UCB*) myswitch);
                ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	        scratchucb=(UCB*)myswucb;
                if ((long)scratchucb < 0){
                  ibp->moreprm[0] = scratchucb->ucb$w_unit; /* return SW unit no. */
	          ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer
						      array also. */
                  ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
                  ibp->moreprm[3] = &myswucb->ucb$l_pthord[0]; /* path sel order addr */
                  ibp->moreprm[4] = NUM_HOSTS; /* Tell how long tables are */
                  ibp->moreprm[5] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                  ibp->moreprm[6] = (long)myswucb->ucb$l_vict; /* intercepted ucb */
                  ibp->moreprm[7] = myswucb->ucb$l_outstnd; /* Current I/O count */
                }
	      } else {
	/* error to fdt */
	      }
/* complete this I/O */
                irp->irp$l_media = 1; /* return good status */
		return(exe_std$finishio(irp,(UCB*)ucb));
		break;
              }
       case 6:{
/* Use subfunction 6 to insert the server mailbox UCB and PID */
/* Input in device name area is MB device name stuff so we look it up
   (to reduce kernel code elsewhere) and PID in the other long. */
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
	      sch_std$iolockw(pcb);
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat) && (myswitch > 0)){
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
                  ibp->uswitch = myucb; /* return mbx ucb */
                  ucb->ucb$l_mbxucb = myucb;
/* Let's be sure the IPID here is a process IPID that really exists*/

                  pcbptr = (long*) SCH$GL_PCBVEC;
                  if ((long)pcbptr >= 0)return;
                  kkk = 0;
                  for (kk=0; kk < SCH$GL_MAXPIX; kk++){
                    trypcb = *(PCB**)pcbptr++;
                    if (trypcb->pcb$l_pid == myswucb->ucb$l_daemon ){
                      kkk = 1;
                      break;
                    }
                  }
		  if (kkk == 1){
                    ucb->ucb$l_daemon = myswitch; /* IPID of the daemon */
		  }
                irp->irp$l_media = 1; /* return good status */
		} else {
/* can't find device. Err...*/
/* Must place error in the fdt context area */
                }

	      sch_std$iounlock(pcb);
	      return(exe_std$finishio(irp,(UCB*)ucb));
/* complete this I/O */
		break;
              }
       case 7:{
/* Function 7 just deletes the reference to the server */
	      ucb->ucb$l_daemon = 0;
	      ucb->ucb$l_mbxucb = 0;
                irp->irp$l_media = 1; /* return good status */
		return(exe_std$finishio(irp,(UCB*)ucb));
		break;
              }
       case 8:{
/* Function 8 sanity checks that this IS an SW driver by returning a
   magic number, octal 14747 or 6631 decimal. */
/* This is the pdp11 opcode for "mov -(pc),-(pc)" which instruction
   copies itself backwards in memory until it wraps around 32 kw.
   It is weird but odd, so will be taken for a kind of success. */
                irp->irp$l_iost2 = ucb->ucb$l_indrct;
		irp->irp$l_media = 6631;
/* Add 4 if the SW unit is offline */
/* This should generally be the case for first-time access */
		if (ucb->ucb$r_SW_ucb.ucb$v_valid == 0) irp->irp$l_media = irp->irp$l_media + 4;
		return(exe_std$finishio(irp,(UCB*)ucb));
		break;
              }
       case 9:{
#ifdef DEBUG
/*  ini$brk();*/
#endif
              trtns = 8;
	      sch_std$iolockw(pcb);
/*              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);*/
		if ((ibp->ulen) < 0){
                  myucb = (UCB*)ibp->ulen;
                  myddb = myucb->ucb$l_ddb;
                  mysb = (struct _sb *)myddb->ddb$l_sb;
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  if(myucb->ucb$v_online){
		    mung(scrswucb,myucb,myddb,mysb,myswitch);
                    trtns = 1;
                  } else {
                    trtns = 32; /* rtn bad status if we fail */
                  }
                irp->irp$l_media = trtns; /* return good status */
		} else {
/* can't find device. Err...*/
/* However we manage by trtns mech, to return a failure */
                irp->irp$l_media = trtns; /* return good status */
                }

	      sch_std$iounlock(pcb);
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
              }
       case 10:{
              
	      sch_std$iolockw(pcb);
/*              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);*/
		if ((ibp->ulen) < 0){
		  if (myswmod > 0){
                  myucb = (UCB*)ibp->ulen;
                  myddb = myucb->ucb$l_ddb;
                  mysb = (struct _sb *)myddb->ddb$l_sb;
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  unmung(scrswucb,myucb,myddb,mysb,myswitch);
                  irp->irp$l_media = 1; /* return good status */
		};

	      sch_std$iounlock(pcb);
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
              }
       case 11:{
              
	      sch_std$iolockw(pcb);
/* Return the path block for this SW unit */
		if ((ibp->ulen) > 0){
		  /* fill in outputs here */
                  ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	          scratchucb=(UCB*)myswucb;
                  if ((long)scratchucb < 0){
                    ibp->moreprm[0] = myswucb->ucb$l_indrct; /* return path in use */
	            ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer */
	            ibp->moreprm[2] = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct]; /* Rtn addr of path pointer */
                    irp->irp$l_media = 1; /* return good status */
                  }
		};

	      sch_std$iounlock(pcb);
	      return(exe_std$finishio(irp,(UCB*)ucb));
              break;
              }
       case 12:{
/* Use subfunction 12 to get back information about bashes. In this case
   we report on the SW device that the function invokes.
*/
              irp->irp$l_media = 1; /* return good status default*/
	      if ((long)ucb < 0){
/* UCB address looks potentially valid */
	        myswucb = ucb;
                ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	        scratchucb=(UCB*)myswucb;
                if ((long)scratchucb < 0){
                  ibp->moreprm[0] = scratchucb->ucb$w_unit; /* return SW unit no. */
	          ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer
						      array also. */
                  ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
                  ibp->moreprm[3] = &myswucb->ucb$l_pthord[0]; /* path sel order addr */
                  ibp->moreprm[4] = NUM_HOSTS; /* Tell how long tables are */
                  ibp->moreprm[5] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                  ibp->moreprm[6] = (long)myswucb->ucb$l_vict; /* intercepted ucb */
                  ibp->moreprm[7] = myswucb->ucb$l_outstnd; /* Current I/O count */
                  irp->irp$l_media = 1; /* return good status */
                }
	      } else {
	/* error  */
                  irp->irp$l_media = SS$_BADPARAM; /* return fail status */
	      }
/* complete this I/O */
		return(exe_std$finishio(irp,(UCB*)ucb));
		break;
              }
/* more options here */

       default:
         {return (exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_BADPARAM));
          break;}
     }

    return (exe_std$abortio(irp,pcb,(UCB*)ucb,SS$_BADPARAM));
/*    return SS$_FDT_COMPL;	*/	/*  and exit */
}


/* finish delayed io routine, a fork routine */
/*
  Abstract:
	The sw_findelio routine finishes up I/O on the IRP that
	was used to idle a device, permitting the target device
	I/O to resume.
  Inputs:
	irp	The IRP that was used for delay
	swucb	The UCB of the SW device
        ucb	Target device UCB
  Outputs:
        I/O on the IRP is completed and target I/O resumes.
*/

void sw_findelio(IRP* irp, SW_UCB* swucb, SW_UCB* ucb){
/* just finish the I/O and flag we did so. */
	swucb->ucb$l_delayirp = 0; /* flag we completed this */
/*	if (ucb->ucb$l_outstnd == 0){*/
	ucb->ucb$r_SW_ucb.ucb$l_irp = irp;
	ioc_std$reqcom(SS$_NORMAL,0,(UCB*)ucb);
/*	}*/
}

/* mung subfunction.							*/
/* Abstract:                                                            */
/* This function does the actual stealing of the DDT from a device and	*/
/* sets up the intercept chain properly for the new DDT intercept.	*/
/* Expects to hold i/o mutex.						*/
/* 									*/
/* Inputs:
   swucb   - UCB of SW device which will control use of the path
   vicucb  - UCB of device being added as a path controlled
   vicddb  - DDB of device being added as a controlled path
   vicsb   - SB of device being added  "  "     "       "
   mysw    - index being added into the tuple (0-max). Greater
		than the max paths implies use next free path.

   Outputs:
   vicucb is intercepted if this is the first path being added to
		the SW device. Otherwise, entry points for the
		path are recorded, as is its UCB address.  Vicucb
		UCB is hidden from system access.
   If the intercept is not the first, it will add to existing ones
		provided the format standards here are followed. If
		they are not, intercept will not take place.

   Synchronization:
   Takes out fork lock and returns it. Requires IO write mutex be held
		on call.

*/   

void mung(SW_UCB* swucb, UCB* vicucb, DDB* vicddb, SB* vicsb, long mysw){
    int myunit;
    int savipl;
    DDT* vicddt;
    DDT* swddt;
    FDT* vicfdt;
    FDT* swfdt;
    DDT* swcpyddt;
    SW_UCB* svicucb;
    long* long1;
    long * long2;
    long * long3; /* scratch pointers */
    long * long4;
    PTHPTR * thisbash;
    PTHPTR * wrkbash;
    int thisunit;
    int kk,kkk,kkkk;
    UCB* scratchucb;
    SW_UCB* icucb;
    (SW_UCB*)scratchucb = swucb;
    myunit = mysw;
    thisunit = mysw & 65535;
    if (thisunit != 0 && swucb->ucb$l_mungd == 0) return;
    fork_lock(scratchucb->ucb$b_flck,(int*)&savipl);
    vicddt = vicucb->ucb$l_ddt;
/* On entry we need to examine the "mysw" argument. It has the following
   structure:
    1. word, the device we are adding to the tuple. If not zero, then unit
		zero MUST have been filled in earlier.
*/
/* check that if the device being added to the tuple is nonzero, that
   unit zero exists and is set up already. If this passes, be sure also
   that the unit being added in is not already in use for something.
   As a convenience we will "rule" that a "unit" of 65535 means the
   next free unit. The user interface can use this, but preserve the
   ability lower down to specify. */
    if (thisunit != 0 && swucb->ucb$l_mungd == 0) return;
    svicucb = (SW_UCB*)vicucb;
    if (thisunit > NUM_HOSTS){
      for (kk=0; kk < NUM_HOSTS; kk++){
	wrkbash=&swucb->ucb$a_hstdta[kk];
        if (wrkbash->ucb$l_hstucb == 0){
          thisunit = kk;
	  break;
        }
      }
    }
    if (thisunit >= NUM_HOSTS) return;
/* Now we know that "thisunit" is in the permitted range. Since we ANDed with
   65535 before, it can't very well be negative. */
    thisbash = &swucb->ucb$a_hstdta[0];
    if (thisunit != 0 ){
      if (thisbash->ucb$l_hstucb == 0) return;
/* Return if this is a "second" (or later) path and no first is set up. */
    }
    thisbash = &swucb->ucb$a_hstdta[thisunit];
    if (thisunit != 0 ){
      if (thisbash->ucb$l_hstucb != 0) return;
/* Return if this is a "second" (or later) path and previously set up. */
    }
/* */

/* If we are doing unit 0 here we replace DDT and also make an edited FDT
   copy. If doing other units, we just fill in the PTHPTR structure in the
   intercept UCB. */
   if (thisunit == 0){
/* See if this is the original DDT */
      if ((long)vicddt != (long)vicddb->ddb$l_ddt) {
        /* Not the same, so the UCB looks to have been intercepted */
        /* Check that the intercept followed our standard	*/
/* First make a pointer to the start of the SW UCB presuming the
   intercept IS using our methods.  If it is not, as a general
   matter we are going to reference a few longs ahead of the DDTAB,
   and give up unless our magic number is there. This will always be
   OK if we are using the same intercept as the other guy, almost
   always will be fine other times, and for switching will basically
   always be OK because the switch intercept will be put in place at
   load time and is almost guaranteed to be first. This stuff is in
   here, though, in case of later users of the technique and (importantly)
   for test uses where someone may manually command to connect a few UCBs
   into a test tuple.
*/
          (char*) icucb = (char*)vicddb->ddb$l_ddt - 
			(long)offsetof(SW_UCB,ucb$a_vicddt);
          if (icucb->ucb$ahdofddt.DI_PPD.ucb$l_icsign != magic){
	      fork_unlock(scratchucb->ucb$b_flck,savipl,SMP_RESTORE);
	      return;
	  }
/* The magic pattern looks OK, so we should be able to store the new DDT
   address in OUR intercept UCB into the victim's forward pointer and
   the back address to his in our interecept UCB. */
          (long)icucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt = (long)&svicucb->ucb$a_vicddt;

          swucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.pa.ucb$v_fi8ok = icucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.pa.ucb$v_fi8ok; 
          (long)swucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt = (long)vicucb->ucb$l_ddt;
/* end for now of second bash stuff */
      } else {
/* New bash of a never intercepted UCB */
          swucb->ucb$l_mungd = 1;
          swucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt = (long*)vicucb->ucb$l_ddt;
          swucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt = 0;
          swucb->ucb$l_vict = vicucb;
/* test here would let us know not to set finipl8 fit */
/* Otherwise it's a new intercept; set finipl8 ok flag */
          if (svicucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.pa.ucb$v_nofipl8 == 0)swucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.pa.ucb$v_fi8ok = 1;
/* copy the victim ddt to our site */
          memcpy(&swucb->ucb$a_vicddt,vicucb->ucb$l_ddt,sizeof(DDT));
          swcpyddt = (DDT*)&swucb->ucb$a_vicddt;

/* copy FDT stuff too */
/* Some implicit information is used here about the format of a driver
   FDT table. */
/* Specifically, FDT$PS_FUNC_RTN must be 8  (8 = 2* sizeof(long) )*/
          swucb->ucb$l_oldfdt =(long*)swcpyddt->ddt$ps_fdt_2;
          memcpy(&swucb->ucb$l_myfdt,vicddt->ddt$ps_fdt_2,sizeof(FDT));
/* now edit this copy of his FDT to use SW routines if present */
/* note: involves some knowledge of step 2 fdt tbl format. */
          (long)long1 = (long)&swucb->ucb$l_myfdt + ( 2 * sizeof(long));
          long2 = (long*) &driver$fdt;
          (long)long2 = (long) long2 +  ( 2 * sizeof(long));
/* now we're pointing at the 64 function pointers */
	  long3 = (long*)&EXE$ILLIOFUNC;	/* What illegal funcs get filled in with */
	  long4 = (long*)&sw_format;     /* Our IO$_PHYSICAL routine */
	  for (kkk=0; kkk < 64; kkk++){
/* If the SW FDT does NOT point to illiofunc and does not point to our IO$_PHYSICAL
   then we want the replacement FDT to point at our routines (first; they will
   call the original ones afterwards). */
	     if (((long)*long2 != (long)long3) && ((long)*long2 != (long)long4)){
		  *long1 = (long)long2; /* Make the new FDT routine point at our stuff*/
	     }
/* Bump our pointers past this entry and look at the next now */
	     long1++;
	     long2++;
	  }
/* At this point the replacement FDT routine contains our FDT pointers */
/* Also, the replacement IO$_PHYSICAL is unchanged; to control the SW device */
/* an app should send that function to the SW device, not to some intercepted */
/* one. By ensuring that the FDT routines change only where the SW device has */
/* some other interception, we facilitate our later use of this code. It may */
/* be desired in the future to have FDT routines switched, and with this code */
/* in place, that switching is simple. */
      }
/* Now save other pointers we need before switching anything */
/* First, for convenience of debug, store a couple pointers in the
   intercept block filler area, one to the SW UCB, one to the
   start of the host data arrays. */
          swucb->ucb$ahdofddt.DI_PPD.userfiller[0] = (long)swucb;
          swucb->ucb$ahdofddt.DI_PPD.userfiller[1] = (long)&swucb->ucb$a_hstdta[0];
/* original start-io */
	  (void *)swucb->ucb$l_hstartio = (void *)vicddt->ddt$ps_start_2;
/* Main UCB address, both copies */
	  swucb->ucb$l_backlk = (long*)vicucb;
/* Point the DDT copy's start_2 address at our routine, and similarly with */
/* all other entries we need to intercept, after saving the originals */
/* (we save the originals for speed of access) */
     thisbash->ucb$l_hstucb = (struct _dt_ucb*)vicucb;
     thisbash->ucb$l_oldmv  = (long*)vicddt->ddt$ps_mntver_2;
     thisbash->ucb$l_oaltst = (long*)vicddt->ddt$ps_altstart_2;
     thisbash->ucb$l_qirp   = (long*)vicddt->ddt$ps_pending_io;
/* N.B. - altstart routine is called via JSB so we need some special linkage
   and a macro jacket to call out from there. */
     thisbash->ucb$l_aux    = (long*)vicddt->ddt$l_aux_routine;
     thisbash->ucb$l_cancl  = (long*)vicddt->ddt$ps_cancel_2;
     thisbash->ucb$l_enapth = 1;
     thisbash->ucb$q_pthtim = 0;
     thisbash->ucb$l_pthios = 0;
     thisbash->ucb$l_ptherr = 0; /* initialize all other entries */
/* Now insert the internal entries into the DDT copy we have made. Note that
   this copy is NOT yet active at this moment. */
     (void*)swcpyddt->ddt$ps_mntver_2    = (void*)&steal_mount_ver;
     (void*)swcpyddt->ddt$ps_altstart_2  = (void*)&steal_altstart;
     (void*)swcpyddt->ddt$ps_pending_io  = (void*)&steal_pending;
     (void*)swcpyddt->ddt$ps_start_2     = (void*)&steal_start;
     (void*)swcpyddt->ddt$l_aux_routine  = (void*)&steal_aux_routine;
     (void*)swcpyddt->ddt$ps_cancel_2    = (void*)&steal_cancel;
/* Now replace the DDT pointer in the victim's UCB, bracketing with IMB */
     __PAL_IMB();
     vicucb->ucb$l_ddt = swcpyddt;
     __PAL_IMB();
   } else {  /* thisunit == 0 */
     thisbash->ucb$l_hstucb = (struct _dt_ucb *)vicucb;
     (void*)thisbash->ucb$l_oldmv  = (void*)vicddt->ddt$ps_mntver_2;
     (void*)thisbash->ucb$l_oaltst = (void*)vicddt->ddt$ps_altstart_2;
     (void*)thisbash->ucb$l_qirp   = (void*)vicddt->ddt$ps_pending_io;
/* N.B. - altstart routine is called via JSB so we need some special linkage
   and a macro jacket to call out from there. */
     (void*)thisbash->ucb$l_aux    = (void*)vicddt->ddt$l_aux_routine;
     (void*)thisbash->ucb$l_cancl  = (void*)vicddt->ddt$ps_cancel_2;
     thisbash->ucb$l_enapth = 1;
     thisbash->ucb$q_pthtim = 0;
     thisbash->ucb$l_pthios = 0;
     thisbash->ucb$l_ptherr = 0; /* initialize all other entries */
/* Now change the device class of the secondary path so it does not look
   like a disk any more. Eventually remove it from the device queue
   completely. */
     vicucb->ucb$b_devclass = DC$_MISC;
/* Hide the UCB from search routines */
     vicucb->ucb$l_devchar2 |= UCB$M_HIDEUCB;
/* For the present also inhibit assigning channels to the device. */
     vicucb->ucb$l_sts |= UCB$M_NO_ASSIGN;
   }
    swucb->ucb$r_SW_ucb.ucb$v_valid = 1; 
    swucb->ucb$r_SW_ucb.ucb$v_online = 1; 
/* Now done with mung setup. */
    fork_unlock(swucb->ucb$r_SW_ucb.ucb$b_flck,savipl,SMP_RESTORE);
}

/* unmung subfunction.							 */
/* Abstract:								 */
/* This function undoes the mung operation, restoring drives to their    */
/* original state. It must be called with the same SW and device matches */
/* as had been originally used. */
/* Expects to hold i/o mutex.						*/
/* Note: you only call unmung for the master path device. It clears all */
/* other references.							*/
/* 									*/
/* Inputs:
   swucb   - UCB of SW device which will control use of the path
   vicucb  - UCB of device being added as a path controlled
   vicddb  - DDB of device being added as a controlled path
   vicsb   - SB of device being added  "  "     "       "
   mysw    - index being added into the tuple (0-max). Greater
		than the max paths implies use next free path.

   Outputs:
   vicucb is unintercepted if this is the first path being added to
		the SW device. Otherwise, entry points for the
		path are recorded, as is its UCB address.  Vicucb
		hiding logic is however not undone currently.
   The intercept is removed from the host device chain. This will work
		even if multiple intercepts exist, so long as all follow
		the format standard here.

   Synchronization:
   Takes out fork lock and returns it. Requires IO write mutex be held
		on call.

*/   
void unmung(SW_UCB* swucb, UCB* vicucb, DDB* vicddb, SB* vicsb, long mysw){
    int myunit;
    int savipl;
    DDT* vicddt;
    DDT* swddt;
    FDT* vicfdt;
    FDT* swfdt;
    DDT* swcpyddt;
    long* long1;
    long * long2;
    long * long3; /* scratch pointers */
    long * long4;
    DPT* mydpt;
    PTHPTR * thisbash;
    SW_UCB* stepucb;
    PTHPTR * wrkbash;
    int thisunit;
    int kk,kkk,kkkk;
    SW_UCB* icucb;
    SW_UCB* nxtucb;
    long mymagic;
    vicddt = (DDT*)vicddb->ddb$l_ddt;
    swcpyddt = vicucb->ucb$l_ddt;
    mymagic = magic;
/* The victim UCB better point at the same DDT that we have in our intercept
   UCB or there's an error. */
    if ((long)swcpyddt != (long)&swucb->ucb$a_vicddt) return;
/* be certain we can undo the "standard bash" */
    fork_lock(swucb->ucb$r_SW_ucb.ucb$b_flck,(int*)&savipl);
    vicddt = (DDT*)vicddb->ddb$l_ddt;
    swcpyddt = vicucb->ucb$l_ddt;
/* If we can undo std bash, then update the links and so on. */
    mydpt=&driver$dpt;
    (char*)stepucb=(char*)vicddt - (long)offsetof(SW_UCB,ucb$a_vicddt);
    while ((long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_uniqid != (long)mydpt) {
      if ((long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_icsign != mymagic ||
                (long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt >= 0){
        fork_unlock(swucb->ucb$r_SW_ucb.ucb$b_flck,savipl,SMP_RESTORE);
	return;
      }
      (char *)stepucb = (char*)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt - (long)offsetof(SW_UCB,ucb$a_vicddt);
      if ((long)stepucb >= 0){
        fork_unlock(swucb->ucb$r_SW_ucb.ucb$b_flck,savipl,SMP_RESTORE);
	return;
      }
    } 
/* now we have the intercept ucb in stepucb */
	if ((long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt < 0){
/* We were intercepted. Fix up the next guy in line. */
	  (long)nxtucb = (long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt - (long)offsetof(SW_UCB,ucb$a_vicddt);
	  nxtucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt = stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt;
	}
/* If we intercepted someone, fix the previous guy in line too. */
	if ((long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt < 0){
	  (long)icucb = (long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt - (long)offsetof(SW_UCB,ucb$a_vicddt);
/* Is what we intercepted the virgin driver ? */
	  if ((long)icucb != (long)vicucb) icucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt = stepucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt;
	}
/* Now if victim's ucb ddt entry points at us, make it point at our
   predecessor. If if points at a successor we can leave it alone. */
	if ((long)swcpyddt == (long)&stepucb->ucb$a_vicddt){
     __PAL_IMB();
	  (long)vicucb->ucb$l_ddt = (long)stepucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt;
     __PAL_IMB();
          swucb->ucb$l_mungd = 0;
	}

/* Note that if we hid the UCB before, we really should unhide it. When
   we remove the UCB from system lists in mung, this module should 
   be altered to add them back in. */

/* now clear the other fields of swucb that flag interception */
    thisbash = &swucb->ucb$a_hstdta[0];
    thisbash->ucb$l_hstucb = 0;
    thisbash->ucb$l_oldmv  = 0;
    thisbash->ucb$l_oaltst = 0;
    thisbash->ucb$l_qirp   = 0;
/* N.B. - altstart routine is called via JSB so we need some special linkage
   and a macro jacket to call out from there. */
    thisbash->ucb$l_aux    = 0;
    thisbash->ucb$l_cancl  = 0;
    thisbash->ucb$l_enapth = 0;
    thisbash->ucb$q_pthtim = 0;
    thisbash->ucb$l_pthios = 0;
    thisbash->ucb$l_ptherr = 0; /* initialize all other entries */
    swucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt = 0;
    swucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt = 0;
/* That looks like about it. */
    swucb->ucb$r_SW_ucb.ucb$v_valid = 0; 
    swucb->ucb$r_SW_ucb.ucb$v_online = 0; 

    fork_unlock(swucb->ucb$r_SW_ucb.ucb$b_flck,savipl,SMP_RESTORE);
}	/* end of unmung */


/* GetSWUCB routine */
/* Abstract:		*/
/* This routine is called with the UCB of an intercepted device, and must
   locate the intercept UCB from it. It uses the fact that the intercept
   code puts the DDT physically into the intercept UCB to compute this
   but chains along in case of multiple intercepts.

   Inputs:
        inucb -	UCB of device that might be intercepted
   Implicit inputs:
	Intercept block format defined here must be followed by
		any DDT intercepts.
   Outputs: 
        UCB of SW device intercepting the device, if any.

   Note:
	This routine does not depend on SW unit; it will find the
	intercept so long as the intercept block scheme defined
	here is followed. Conversely it will fail if this scheme
	is broken, since it cannot reliably track intercepts back
	to their source unless it knows their format.

*/
/* Notice that this code presumes that a device is intercepted by a SWdriver
   unit once only, and will find that unit. */
SW_UCB* getswucb(UCB* inucb){
   DPT* magicdpt;
   DDT* ddtp;
   SW_UCB* myswucb;
   int savipl;

   ddtp = inucb->ucb$l_ddt;
   fork_lock(inucb->ucb$b_flck,(int*)&savipl);
     (char*)myswucb=(char*)ddtp - (long)offsetof(SW_UCB,ucb$a_vicddt);
     while ((long)myswucb < 0){
       if ((long)myswucb->ucb$ahdofddt.DI_PPD.ucb$l_uniqid == (long)&driver$dpt){
/* The UCB has the unique id of THIS driver, so we have found OUR UCB|| */
/* Thus we're done; return our UCB as the result.			*/
         fork_unlock(inucb->ucb$b_flck,savipl,SMP_RESTORE);
	 return myswucb;
       }
       if (myswucb->ucb$ahdofddt.DI_PPD.ucb$l_icsign != magic){
/* The UCB found isn;t one of ours, uses a different intercept, so return fail */
         fork_unlock(inucb->ucb$b_flck,savipl,SMP_RESTORE);
	 return 0;
       }
       if ((long)myswucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt < 0){
/* Note that even though we're trying to compute a "UCB", we actually only
   look at offsets within the intercept area here which should be present
   in any intercept UCB that uses this scheme. While this construct is
   a bit odd looking (it's not so odd in macro32 btw) it is actually
   not relying on anything except the intercept block format. */

/* Get the previous DDT. Looks like this device has been intercepted more than
   once.  */
         (char*)myswucb = (char*)myswucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt - (long)offsetof(SW_UCB,ucb$a_vicddt);
       } else {
         myswucb = 0;
       }
     }
   fork_unlock(inucb->ucb$b_flck,savipl,SMP_RESTORE);
   return 0;
}

/* call_orig_fdt  routine		*/
/* Call this after an FDT intercept to call the original FDT processing
   where this is needed for a function. This routine follows the FDT
   chain back and calls the previous FDT processing. */
/*
   Abstract:
   This routine is used to find the FDT code which was intercepted
	by our intercept, and to call it, following the pointers to
	the previous DDT intercept or the original DDT to find the
	FDT code. Where intercepted FDT code is meant to logically
	perform functions ahead of the original code, and where
	it is necessary for the original code to be run, this
	routine may be called to transfer control to the original
	FDT code, rather than having to perform such calls separately
	for each FDT routine. The routine has some knowledge of the
	format of a Step 2 FDT table.
   Inputs:
	origirp	- The IRP we are working on
	origpcb	- PCB of process
        origucb	- UCB of the device intercepted
        origccb - CCB of the channel
   Outputs:
        Original FDT code is called. Status from this returns.
*/
int call_orig_fdt(IRP* origirp, PCB* origpcb, UCB* origucb, CCB* origccb){
  SW_UCB* myswucb;
  FDT* myfdt;
/* long1 is a pointer to an array of pointers to function returning int */
  int (*(*long1)[64])();
  int (* longfcn)();
  int myfcn;
  int mystat;
  mystat = 0;
  if ((long)(myswucb = getswucb(origucb)) < 0){
    myfdt = (FDT*)myswucb->ucb$l_oldfdt; /* get the original FDT (as we intercepted)*/
    (long)long1 = (long) myfdt + 2 * sizeof(long);
    /* Now we have the address of the original FDT routine for a step2 fdt */
    /* Now get the function number and call the procedure addressed */
    myfcn = origirp->irp$v_fcode;
    longfcn = *long1[myfcn];
    mystat = longfcn(origirp, origpcb, origucb, origccb);
  }
  return (mystat);
}


/* fixsplit routine */
/* This routine is called at I/O post time, being passed the address of
   the IRP being used. It will wherever possible be called directly from
   fork IPL but must be prepared to fork at need. It checks for I/O
   status that should start mount verify (or the shdriver analogue)
   and starts that processing if necessary, or restores IRP context
   to that obtaining at entry to the switching code and completes the
   IRP. */
/* The auxiliary queue header is (axs*) SW_AXSHH and is initialized at unit
   init as a queue. */
/*
  Abstract:
   This routine gains control, generally at IPL 8 (via fast_finish),
   when an IRP is posted. It counts down outstanding I/O, checks for
   whether mount verify needs to be started (and starts it if so),
   pops original context back and re-posts the IRP as it would have
   been posted had the intercept not been present.

  Inputs:
   irp - The IRP that was posted.
  Implicit inputs:
   If the IRP is of the 7.1 type, a structure pointed to within SWDRIVER
	must exist to hold IRP context.
  Outputs:
   IRP is posted, MV started if appropriate, outstanding I/O counted down.
*/


void fixsplit(IRP* irp){
    AXS * myaxsp;
    AXSSTK* myaxsp2;
    int kk,kkk,kkkk,kkkkk; /* temps */
    long mystat1,mystat2;
    int oldipl;
    PTHPTR* mypath;
    AXS* myaxe;
    UCB* origucb;
    UCB* mastrucb;
    long savipl;
    SW_UCB* myswucb;
    long pid,origics,orgstat,media,flgs;
    long curstat, curucb;
    
    /* First find the UCB we started with. This means first checking to see
       if the IRP is long enough to have an internal arg stack, and then
       whether the context info we need is on that stack. If either is
       false, we must locate the context information in pool using our
       auxiliary information queue header which is global to this driver.
       If not we can get the info right out of the IRP. */
    /* The irp$v_onstack flag is for longer IRPs that have the flag and
       signals that the context didn't fit on the IRP stack and thus a
       separate axs structure had to be allocated */
    /* The irp$v_gotstk flag is to be added to indicate in a positive way
       that a stack exists. It may need to be pulled if there are too few 
       bits left. */
    pid = 0;
    origics = 0;
    orgstat = 0;
    origucb = 0;
    media = 0;  /* zero context variables initially */

/* check that irp is long enough, flagged for stack, and has valid internal
   stack pointer. Otherwise treat as NOT an IRP with an arg stack */
    if ((irp->irp$w_size <= IRP$K_LENGTH_V71) || 
        ((irp->irp$l_stkflgs & 1) == 0) ||
/*        (irp->irp$v_gotstk == 0) || */
        ((unsigned)irp->irp$l_curcsp - 
          (unsigned)irp > (unsigned)IRP$K_LENGTH )){
/* The info we want must be on an auxiliary structure. Therefore go get that
   and fix up the IRP, keeping local copies of info so we can use them in
   deciding things like how to synch IPL. */

      myaxsp = SW_AXSHH;	/* get the initial list element */
      while ((long)myaxsp != (long)&SW_AXSHH){
        if ((long)myaxsp->axs$l_irp == (long)irp){
/* Found our aux entry here. Grab off the values we need from it and break
   out of the while loop. */
	  origucb = (UCB*) myaxsp->axs$l_orgucb;
	  pid = myaxsp->axs$l_pid;
          if (irp->irp$w_size >= IRP$K_LENGTH){
	    origics = myaxsp->axs$l_orgics;
          }
	  media = myaxsp->axs$l_media;
	  myswucb = (SW_UCB*)myaxsp->axs$l_swucb;
	  orgstat = myaxsp->axs$l_orgstat;
/* Now remque this entry and deallocate it; we are done looking for it. */
	  __PAL_REMQUEL(myaxsp,(void*)&myaxe);
/* deallocate the pool now. */
	  kkk = exe_std$deanonpgdsiz(myaxsp,sizeof(AXS));
	  myaxsp = (AXS*)&SW_AXSHH;
	  break;
        } else {
	  myaxsp = (AXS*)myaxsp->axs$l_fwd;	/* get next entry */
	}
      }
    } else {
/* The information should be in the IRP itself, so just grab it off the IRP
   stack. */
/* we increment the stack after fillin, so must decrement pointer to use */
      irp->irp$l_curcsp = irp->irp$l_curcsp - sizeof(AXSSTK);
      myaxsp2 = (AXSSTK*)irp->irp$l_curcsp;
      origucb = (UCB*)myaxsp2->axs$l_orgucb;
      pid = myaxsp2->axs$l_pid;
      origics = myaxsp2->axs$l_orgics;
      media = myaxsp2->axs$l_media;
      myswucb = myaxsp2->axs$l_swucb;
      orgstat = myaxsp2->axs$l_orgstat;
    }
/* Now restore the IRP to prior configuration including its stack */
/* irp$v_onstack must be one of these flags */
      if (irp->irp$w_size >= IRP$K_LENGTH){
/* Only restore stack stuff if the IRP is long enough! */
        irp->irp$l_stkflgs = origics;
      }
/* Now we should have the original (sw unit) UCB */
/* Reset the IRP state to what it needs to be */
      curstat = irp->irp$l_sts; /* Need this to handle IPL right */
      curucb = (long)irp->irp$l_ucb;
      irp->irp$l_pid = pid;
      irp->irp$l_sts = orgstat;
      irp->irp$l_ucb = origucb;
/* Get the I/O status first before restoring IRP */
      mystat1 = irp->irp$l_iost1;
      mystat2 = irp->irp$l_iost2;
/* Do NOT alter status yet, though we may need to do so if we have to
   enter MV */
/*      irp->irp$l_media = media; */
      mastrucb = origucb;
#ifdef DEBUG
/* Save off the i/o status of the last IRP for debug */
      myswucb->ucb$l_dbgsts = mystat1;
      myswucb->ucb$l_irpadrp = (long)irp;
#endif
/* Now get to the fork level for host device if we need to. */

      if ((orgstat & IRP$M_FINIPL8) == 0){
        fork_lock(origucb->ucb$b_flck,(int*)&savipl);
      }
      mypath = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct];
/* count down active IRPs now. */
      if (  --myswucb->ucb$l_outstnd < 0) myswucb->ucb$l_outstnd = 0;
/* maintain path pointer statistics */
      mypath->ucb$q_pthtim = exe$gl_abstim; /* record time of last i/o done */
      mypath->ucb$l_pthios++;		    /* count i/o done */
      if ((mystat1 & 1) == 0)mypath->ucb$l_ptherr++; /* count errors */
/* Don't check for mount verify begin on MV's IRPs nor on IRPs from
   shdriver if this is a shadow set member */
      if ((irp->irp$v_mvirp == 0) && 
	((origucb->ucb$l_devchar2 & DEV$M_SSM) == 0 || (irp->irp$v_func != IO$_PACKACK ) )){
/* Not a MV IRP */
	if ((mystat1 & 16384) != 0 && (mystat1 & 1) == 1 ){
/* Here if we have failover or failback */
/* Send a message to our server process if one exists and looks active, and
   then complete the IRP with success. */
/* The send-message routine checks that the daemon exists and sends the
   message if and only if so. */
	  send_daemon_msg(irp,myswucb,origucb,mystat1);
	  mystat1 = mystat1 - 16384;
/* Following duplicates code below for success. */
/* If this IRP is one we changed to a medofl state, clear the flag and
   let it by now. We only allow one IRP to be modified like this to keep
   any media errors from causing hangs or looping. */
          if ((long)irp == (long)myswucb->ucb$l_errirp){
	    myswucb->ucb$l_errirp = 0;
	    myswucb->ucb$l_errchgwedge = 0;
	  }
/* Now store I/O status in the IRP */
          irp->irp$l_iost1 = mystat1;
          irp->irp$l_iost2 = mystat2;
/* Postprocess the IRP now */
          com_std$post_nocnt(irp);
          if ((orgstat & IRP$M_FINIPL8) == 0){
            fork_unlock(origucb->ucb$b_flck,savipl,SMP_RESTORE);
          }
          return; 
        } else {
/* not failover or failback */
/* See if we should start mount verify */
	if (!$VMS_STATUS_SUCCESS(mystat1)){
/* IRP was not a success, so try an error case. */
	  if ((int)origucb->ucb$b_devclass == DC$_DISK &&
           (((int)origucb->ucb$l_devchar & DEV$M_MNT) != 0) &&
           (((int)origucb->ucb$l_devchar & DEV$M_FOR) == 0) &&
	   pid > 0){
	    kkk = mystat1 & 65535; /* mask off low word */
/* Since we don't know clearly if the connection is bad, instead on a set
   of errors, TRY to let MV start and see if the connection really is bad. */
	    if (myswucb->ucb$l_errchgwedge == 0 &&
		(kkk == SS$_CTRLERR ||
                 kkk == SS$_DRVERR  ||
                 kkk == SS$_DEVOFFLINE ||
                 kkk == SS$_IVSTSFLG ||
                 kkk == SS$_PATHLOST ||
                 kkk == SS$_BUS_PHASE_ERROR ||
		 kkk == SS$_LINKABORT )){
/* Store this IRP's address for later if we had none */
/* If however we're within the hysteresis time after a switch (HYSTERESIS sec) then
   leave the status alone and let things fail however they will. If that
   has happened it appears the failure couldn't be fixed by path flipping
   anyway so let the errors by unaltered. */
               if ((myswucb->ucb$l_dwell != 0) &&
	           (myswucb->ucb$l_dwell + HYSTERESIS) > exe$gl_abstim){
	         if ((long)myswucb->ucb$l_errirp == 0)myswucb->ucb$l_errirp = (long)irp;
	         myswucb->ucb$l_errchgwedge = 1;
/* ... and replace the status with ss$_medofl now to let MV have a chance */
	         mystat1 = mystat1 - kkk + SS$_MEDOFL;
               }
	    }
	    if (origucb->ucb$v_mntverpnd){
	      origucb->ucb$v_mntverpnd = 0;
	      origucb->ucb$v_mntverip = 0; /* clr mv in prog so it can start */
	    }
	    irp->irp$l_media = media; /* restore media for poss. retry */
/* See if we need to start mount verify now. */
	    kkk = exe_std$mount_ver(mystat1, mystat2, irp, origucb);
/* Note that if this is a shadowset member we must add code to test that case. */
            if (!$VMS_STATUS_SUCCESS(kkk)){
/* must return here if going into MV without doing anything to the IRP */
            if ((orgstat & IRP$M_FINIPL8) == 0){
              fork_unlock(origucb->ucb$b_flck,savipl,SMP_RESTORE);
            }
	      return; 
            }
          }
	} /* end of test on 16384 bit */
/* Here we must process the IRP either because it was successful or because
   MV processing said we must. */
/* If this IRP is one we changed to a medofl state, clear the flag and
   let it by now. We only allow one IRP to be modified like this to keep
   any media errors from causing hangs or looping. */
        if ((long)irp == (long)myswucb->ucb$l_errirp){
	  myswucb->ucb$l_errirp = 0;
	  myswucb->ucb$l_errchgwedge = 0;
	}
/* Now store I/O status in the IRP */
        irp->irp$l_iost1 = mystat1;
        irp->irp$l_iost2 = mystat2;
/* Postprocess the IRP now */
/*        com_std$post(irp,origucb);*/
        com_std$post_nocnt(irp);
        if ((orgstat & IRP$M_FINIPL8) == 0){
          fork_unlock(origucb->ucb$b_flck,savipl,SMP_RESTORE);
        }
        return; 
        }
      } else {
/* MV irp or shadow packack */
/* If this IRP is one we changed to a medofl state, clear the flag and
   let it by now. We only allow one IRP to be modified like this to keep
   any media errors from causing hangs or looping. */
        if ((long)irp == (long)myswucb->ucb$l_errirp){
	  myswucb->ucb$l_errirp = 0;
	  myswucb->ucb$l_errchgwedge = 0;
	}
/* Now store I/O status in the IRP */
        irp->irp$l_iost1 = mystat1;
        irp->irp$l_iost2 = mystat2;
/* Postprocess the IRP now */
        com_std$post_nocnt(irp);
        if ((orgstat & IRP$M_FINIPL8) == 0){
          fork_unlock(origucb->ucb$b_flck,savipl,SMP_RESTORE);
        }
        return; 
      }

}


/* CTRL_INIT - Controller Initialization Routine			*/
/*									*/
/* This routine is used to perform controller specific initialization	*/
/* and is called by 1) system startup, 2) during driver loading and	*/
/* 3) during power failure recovery.					*/
/*									*/
/* Usage:								*/
/*									*/
/*	status = ctrl_init (idb, ddb, crb)				*/
/*									*/
/* Input:								*/
/*	idb	pointer to the idb					*/
/*	ddb	pointer to the ddb					*/
/*	crb	Pointer to the crb					*/
/*									*/
/* Output:								*/
/*   None.								*/
/*									*/
/* Return value:							*/
/*   status     SS$_NORMAL - unit was initialized successfully.		*/
 
int ctrl_init (IDB *idb, DDB *ddb, CRB *crb)  {

#ifdef DEBUG
/*        ini$brk ();		*/	/* Request breakpoint */
#endif

    return SS$_NORMAL;			/* Return SUCCESS */
}


/* UNIT_INIT - Unit Initialization Routine				*/
/*									*/
/* This routine is used to perform unit specific initialization		*/
/* and is called by 1) system startup, 2) during driver loading and	*/
/* 3) during power failure recovery.					*/
/*									*/
/* This routine does very little work.  Its primary job is to start up	*/
/* the fork process that will do the bulk of the unit initialization.	*/
/*									*/
/* Usage:								*/
/*									*/
/*	status = unit_init (idb, ucb)					*/
/*									*/
/* Input:								*/
/*	idb	pointer to the IDB					*/
/*	ucb	pointer to the UCB					*/
/*									*/
/* Output:								*/
/*   None.								*/
/*									*/
/* Return value:							*/
/*   status     SS$_NORMAL - unit was initialized successfully.		*/
 
int unit_init (IDB *idb, SW_UCB *ucb)  {
    PCB* fakepcb;
    DDT* myddt;

    if (baseucb.ucb$v_power)		/* Is this power recovery ? */
        return SS$_NORMAL;		/* Power recovery - just exit */

    baseucb.ucb$v_online = 0;		/* Start with unit offline */
    baseucb.ucb$v_valid = 0;
    ucb->ucb$ahdofddt.ucb$l_flags.lw = 0;		/* Clear all the flags */
    baseucb.ucb$v_template = 1; /* set as a template device */
    myddt = (DDT*)baseucb.ucb$l_ddt;
    myddt->ddt$ps_mntver_2 = &sw_fakeformat;
/* Set up and queue fork process to complete the unit initialization */

    SW_AXSHH = (struct axs*)&SW_AXSHH;	/* Initialize the aux structure queue header */
    SW_AXSTT = (struct axs*)&SW_AXSHH;
    fakepcb = (PCB*)&ucb->ucb$l_fakepcb;
    fakepcb->pcb$l_pid = (unsigned int)&fixsplit;
    baseucb.ucb$l_fpc = &unit_init_fork;/* Point to fork routine address */
    exe_std$primitive_fork(0,(int64)idb,(FKB *) ucb);/* Start fork process */
    return SS$_NORMAL;			/* Return with success */
}


/* UNIT_INIT_FORK - Unit Initialization Fork Routine			*/
/*									*/
/* This is the fork routine that does the bulk of the unit		*/
/* initialization work.							*/
/*									*/
/* Usage:								*/
/*									*/
/*	unit_init_fork ( fr3, idb, ucb)					*/
/*									*/
/* Input:								*/
/*	fr3	Fork routine parameter (unused)				*/
/*	idb	pointer to the IDB					*/
/*	ucb	pointer to the UCB					*/
/*									*/
/* Output:								*/
/*   None.								*/
/*									*/
/* Return value:							*/
/*	none								*/

void unit_init_fork(void *fr3, IDB *idb, SW_UCB *ucb) {

int	status;				/* Return status value */
int	page_cnt;			/* Number of pages to allocate */
int	offset;				/* PTE offset in page table */
int	csr_base;			/* Base CSR address */
CDDB*   mycddb;
UCB* scratchu2;
MSCP_UCB*	scratchucb;
int kkk; 				/* scratch */
PTHPTR* pptr;				/* pointer to path descriptor */

/* be sure our auxiliary structures are cleared */
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_uniqid = (long)&driver$dpt;
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_icsign = magic;
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_intcddt = 0;
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_prevddt = 0;
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflg2.lfg2 = 0;
    ucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.lfg = 0;
    ucb->ucb$l_mungd = 0;
    (long)ucb->ucb$l_vict = 0;
    ucb->ucb$l_backlk = 0;
    ucb->ucb$l_retries = 0;
    (long)ucb->ucb$l_hstartio = 0;
    (long)ucb->ucb$l_oldfdt = 0;
    ucb->ucb$l_daemon = 0;
    ucb->ucb$l_sawsucc = 0;
    ucb->ucb$l_indrct = 0; /* start with 0th path */
    ucb->ucb$l_mbxucb = 0;
    ucb->ucb$l_ptherrs = 0; /* no errs here yet */
    ucb->ucb$l_errchgwedge = 0;
    ucb->ucb$l_errirp = 0;
#ifdef DEBUG
    ucb->ucb$l_axsque = (struct axs*)&SW_AXSHH;
#endif
    ucb->ucb$q_swtime = 0;
    ucb->ucb$l_mypost = (long*)&fixsplit;
    ucb->ucb$l_outstnd = 0; /* no outstanding i/o yet. */
    ucb->ucb$l_mscpctrl = 0;
    scratchu2 = (UCB*) ucb;
    scratchucb = (MSCP_UCB*) ucb;
    ucb->ucb$l_srvbits = scratchu2->ucb$l_devchar2;
    if ((long)mycddb = (long)scratchucb->ucb$l_cddb < 0){
/* get initial mscp controller model info if any */
      ucb->ucb$l_mscpctrl = mycddb->cddb$b_cntrlmdl;
    }
    for (kkk = 0;kkk < NUM_HOSTS; kkk++){
      pptr = &ucb->ucb$a_hstdta[kkk];
      (long)pptr->ucb$l_hstucb = 0;
      (long)pptr->ucb$l_oldmv = 0;
      (long)pptr->ucb$l_oaltst = 0;
      (long)pptr->ucb$l_qirp = 0;
      (long)pptr->ucb$l_cancl = 0;
      (long)pptr->ucb$l_aux = 0;
      pptr->ucb$l_enapth = 0;	/* disable and null the paths initially */
      pptr->ucb$q_pthtim = 0;
      pptr->ucb$l_pthios = 0;	/* zero i/o and err counts also */
      pptr->ucb$l_ptherr = 0;
      ucb->ucb$l_pthord[kkk] = kkk; /* initially try paths in order */
    }
    baseucb.ucb$v_valid = 0;		/* Set unit initially not valid */
    baseucb.ucb$v_online = 1;		/* Set the unit as ONLINE */
    return;				/*  and return to the caller */
}


/* STARTIO - Start I/O Routine						*/
/*									*/
/* This routine receives IRPs sent to SWAn: units directly which are	*/
/* to be sent to underlying devices. These are sent either normally or	*/
/* without UCB Busy synchronization as Mount Verify IRPs, depending on  */
/* function modifiers, and become IO$_AVAILABLE or IO$_PACKACK packets  */
/* sent to the underlying device.					*/
/*									*/
/* Input:								*/
/*	irp        Pointer to I/O request packet			*/
/*	ucb        Pointer to unit control block			*/
/*									*/
/* Output:								*/
/*	none								*/

void startio(IRP *irp,UCB *ucb) {

SW_UCB* scratchucb;
int	iost1, iost2;			/* IOSB fields */
int	temp;				/* Temporary value */
UCB * myucb;
long func;
long k,kk;
PTHPTR * mypath;

/* We will use this entry for sending IRPs to underlying devices using
   ioc$initiate and the like provided everything is synchronized ok,
   i.e., the tuple is not in mount verify. */
   scratchucb = (SW_UCB*) ucb;
/* We will use the function modifier bits again to select a path to send
   the I/O to. This is a "fire and forget" type interface, which will
   set the MVIRP bit in the IRP and send it to the underlying path. */

   irp->irp$v_mvirp = 1;
   kk = irp->irp$v_fmod; /* if ALL fcn modifier bits are set this is
                            a NORMAL packack to the main UCB */
   if (kk == (IO$M_FMODIFIERS >> IO$V_FMODIFIERS)){
/* Normal send-io-on case. */
     mypath = &scratchucb->ucb$a_hstdta[0];
     (long)myucb = (long)mypath->ucb$l_hstucb;
     if ((long)myucb >= 0){
       iost1 = SS$_BADPARAM;
       ioc_std$reqcom(iost1,iost2,(UCB *)ucb);	/* Finish I/O */
       return;
     }
     irp->irp$v_func = IO$_PACKACK;      /* Force this to be packack */
     if ((myucb->ucb$v_bsy == 0) && (myucb->ucb$v_valid == 1 )
         && (myucb->ucb$v_mntverip == 0) && (myucb->ucb$v_online == 1)
         && (myucb->ucb$v_dismount == 0)){
/* The UCB looks OK, so send the IRP to it. */
       irp->irp$l_ucb = myucb;
       irp->irp$v_fmod = 0;
       exe_std$insioqc(irp,myucb);
       ucb->ucb$v_bsy = 0; /* unbusy the SW UCB */
       return;
     } else {
       iost1 = SS$_BADPARAM;
/* return will unbusy the sw device startio... */
       ucb->ucb$l_irp = irp; /* belt 'n' suspenders */
       ioc_std$reqcom(iost1,iost2,(UCB *)ucb);	/* Finish I/O */
       return;
     }

   } else {
/* Send mvirp to some particular path */
     k = irp->irp$v_fmod & 0x1f; /* K is now the index of which path to use*/
     k = k % (NUM_HOSTS);
     mypath = &scratchucb->ucb$a_hstdta[k];
     irp->irp$v_fmod = 0;
     (long)myucb = (long)mypath->ucb$l_hstucb;
/* minimal checking here...just send the I/O off */
/* However, ensure it's available or packack */
     if ((irp->irp$v_func == IO$_AVAILABLE) ||
         (irp->irp$v_func == IO$_PACKACK)){
/* Function looks OK */
       irp->irp$l_ucb = myucb;
       ioc_std$initiate(irp, myucb);
/* Fall thru to return */
       ucb->ucb$v_bsy = 0; /* unbusy the SW UCB */
     } else {   

/* I/O is done, release channel and return information about the I/O */
       iost1 = SS$_BADPARAM;
       ioc_std$reqcom(iost1,iost2,(UCB *)ucb);	/* Finish I/O */
     }
   return;
  }
}

/* Send_daemon_msg entry

Processing:
   Sends a message to a preregistered server IF the server exists and
   the mailbox looks usable. The contents of the message are in a local
   structure here and it is sent via wrtmailbox.

Inputs:
  IRP of the request
  SW UCB (switching driver UCB in use)
  Main path UCB

Outputs:
  Message to mailbox established and pointed to within SW UCB if
  the mailbox and process appear valid.
*/
void send_daemon_msg(IRP * irp,SW_UCB* myswucb,UCB* origucb, int rtncode){
  struct mbmsg {
   int pthidx;		/* which path we are using */
   int swunit;		/* Unit of SW device */
   int allocls;		/* Allocation class of main path */
   char dvcnam[12];	/* Character device name from DDB */
   int mrtncode;	/* Return code so we can tell where we handled this */
   char nodename[12];	/* Node name from SB */
  } mbxmsg;
  DDB* myddb;
  SB* mysb;
  long kk,kkk,kkkk;
  long* pcbptr;
  PCB* trypcb;
  UCB* tstucb;

  /* First check that the host UCB exists and things look valid */
  if (myswucb->ucb$l_daemon == 0)return;
  if (myswucb->ucb$l_mbxucb >= 0)return;
  pcbptr = (long*) SCH$GL_PCBVEC;
  if ((long)pcbptr >= 0)return;
  kkk = 0;
  for (kk=0; kk < SCH$GL_MAXPIX; kk++){
    trypcb = *(PCB**)pcbptr++;
    if (trypcb->pcb$l_pid == myswucb->ucb$l_daemon ){
      kkk = 1;
      break;
    }
  }
  if (kkk == 0){
    myswucb->ucb$l_daemon = 0;
    return;
  }
/* The server process is still in the current list. Now be sure
   the mailbox looks sane & used */
  tstucb = (UCB*)myswucb->ucb$l_mbxucb;
  if (tstucb->ucb$b_type != DYN$C_UCB)return; /* mbx ucb must be a ucb */
  if (tstucb->ucb$v_online == 0)return; /* UCB must be online */
  if (tstucb->ucb$l_refc <= 0)return; /* Somene needs to be accessing it */
  if (tstucb->ucb$b_devclass != DC$_MAILBOX)return; /* must be a mailbox */
  if ((tstucb->ucb$l_devchar & DEV$M_MBX) == 0)return;
/* don't check ORB here because v7.2 may not use orb... */

/* The mailbox and so forth look ok, so form and send the message. */
  mbxmsg.pthidx = myswucb->ucb$l_indrct;
  mbxmsg.swunit = (int)myswucb->ucb$r_SW_ucb.ucb$w_unit;
  myddb = origucb->ucb$l_ddb;   /* find the DDB of master path */
  memcpy(&mbxmsg.dvcnam[0],&myddb->ddb$b_name_len,12);
  mbxmsg.allocls = myddb->ddb$l_allocls;
  mbxmsg.mrtncode = rtncode;
  mysb = (struct _sb *)myddb->ddb$l_sb;
  memcpy(&mbxmsg.nodename[0],&mysb->sb$t_nodename,16);
  kkk = exe_std$wrtmailbox((struct _mb_ucb *)myswucb->ucb$l_mbxucb,sizeof(mbxmsg),&mbxmsg);
  return;
}

/*
 * Steal-Start Entry  (stealstart)
 * 
 * Function:
 *  This entry replaces the primary path driver's START_IO entry
 *  and gains control for all calls thereto, thus seeing all normal
 *  IRPs coming into the system. (Additional entries gain control of
 *  other driver entry points to control abnormal IRPs coming in from
 *  altstart and the like.)
 *  It functions as the primary switch site to switch IRPs from the
 *  master path to whichever subordinate driver should process them
 *  (the master or another). It also monitors I/O requests for some
 *  specially qualified ones which are to be to subordinate particular
 *  paths, and directs these, and monitors for MV IRPs which are then
 *  counted and used as signals for path switching.
 *
 * Inputs:
 *  IRP - the IRP the user has queued
 *  UCB - The UCB of the primary path which the I/O request has come in for
 *
 * Outputs:
 *  Control transferred as appropriate with IRP and switching UCB selected
 *  as appropriate. This code also will generate calls to send messages
 *  to the switching server if it exists.
 */
void steal_start(IRP* irp, UCB* ucb){
  SW_UCB * myswucb;
  PTHPTR * mypath;
  PTHPTR * mypath0;
  UCB* origucb;
  void *(* myentry)(IRP* xirp, UCB* xucb);
  UCB* scratchucb;
  UCB* scratchucb2;
  MSCP_UCB* scratchucb3;
  UCB* scratchucb4;
  DDB* scratchddb;
  FDT* swfdt;
  UCB* hstucb;
  FDT* genfdt;
  long k,kk,kkk,kkkk,kkkkk,kkkkkk,kkkkkkk; /* short term temps */

   origucb = ucb;
   myswucb = getswucb((UCB*)ucb);
#ifdef DEBUG
   myswucb->ucb$l_irpsts = irp->irp$l_sts;
   myswucb->ucb$l_irpfunc = irp->irp$l_func;
   myswucb->ucb$l_irpadr = (long)irp;
   myswucb->ucb$l_irppid = (long)irp->irp$l_pid;
#endif
#ifdef ZAPQUD
   if (ucb->ucb$v_mntverip != 0 && irp->irp$v_mvirp == 0 && irp->irp$l_pid 
       == (long)&fixsplit){
/* Someone re-sent us an IRP we had sent to startio below us again.
   Since MV is supposed to be going on, put this on the queue and
   return if not on the queue now. */
     unfixirp(irp, myswucb, 0);
     __PAL_INSQUEL(ucb->ucb$l_ioqfl,irp);
     return;
   }
#endif
/* We should always find an intercept UCB if we've gotten to this point!
   It should only be able to fail if someone does an incompatible intercept
   and then we'll very shortly crash, leaving obvious traces of what
   went wrong. */
/* Because this is "not supposed to" happen leave a test out of this code
   for now. It is possible to follow the original DDT vector and bail out
   by sending I/O via that original DDT (following UCB->DDB->DDT) but with
   the result that switching gets silently disabled. Be noisy if someone
   screws us up. */
   myswucb->ucb$l_backlk = (long*)ucb;
/* Check for stall special case */
/* This is flagged by IRP p5 set to the address of our SW UCB */
/* If this is that special case, just leave the UCB stalled */
   if ((long)myswucb == (long)irp->irp$q_qio_p5)return;
/* Check that the IRP may validly be sent. This means that either the function
   must be one of those the MSCP server routinely passes or that the FDT
   address which would be used for the function matches the master path's
   FDT address that will have been used here. If it does not, we will return
   an illegal function error right here to block any problems. */

/* For simplicity here, the SW FDT will reflect functions that MSCP would
   allow (except for the addition of IO$_PHYSICAL) */

   k = irp->irp$v_fmod;
   k = k & 0xffffffe0;
/* 6 bits for function */
   if (k == ((IO$M_FMODIFIERS>>IO$S_FCODE) & 0xffffffe0)){
     if (myswucb->ucb$l_indrct != 0 &&
       (k != ((IO$M_FMODIFIERS>>IO$S_FCODE) & 0xffffffe0))){
/* master path I/O is always OK, but we must check all other. */
/* However, if this is an abstraction breaking IRP don't check. The server
   must get these right! */
       kk = irp->irp$v_fcode;
       kk = kk & 0x3f;
       mypath = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct];
/* Is the function one that SWdriver considers OK and thus should be ok for
   MSCP? */

       scratchucb = (UCB*) myswucb;
       swfdt = scratchucb->ucb$l_ddt->ddt$ps_fdt_2;
       if (kk == IO$_PHYSICAL || swfdt->fdt$ps_func_rtn[kk] == EXE$ILLIOFUNC){
/* The function is format, or is one that is not used for mscp, so we will
   see if the FDT that WAS used is the one that the underlying driver path
   we will use was expecting to have been used. If NOT, we will generate an
   ILLIOFUNC return right here. */
         mypath0 = &myswucb->ucb$a_hstdta[0];
/* Find the two FDT tables involved */
         scratchucb2 = (UCB*)mypath0->ucb$l_hstucb;
	 swfdt = scratchucb2->ucb$l_ddt->ddt$ps_fdt_2;
         scratchucb2 = (UCB*)mypath->ucb$l_hstucb;
         genfdt = scratchucb2->ucb$l_ddt->ddt$ps_fdt_2;
         if (swfdt->fdt$ps_func_rtn[kk] != genfdt->fdt$ps_func_rtn[kk]){
/* FDT addresses do NOT match, and it is essential they should. So
   junk this I/O before it can do any damage||! */
	   ucb->ucb$l_irp = irp;
           ioc_std$reqcom(SS$_ILLIOFUNC,0,ucb);
	   return;
         }
       }
     }
   }
/* Be sure the IRP is not a mount verify IRP */
   if (irp->irp$v_mvirp == 0){

/* Test for possible "chocolate" functions */

/* master path I/O is always OK, but we must check all other. */
/* However, if this is an abstraction breaking IRP don't check. The server
   must get these right! */
       kk = irp->irp$v_fcode;
       kk = kk & 0x3f; /* be sure no sign extending is done */
       mypath = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct];
/* Is the function one that SWdriver considers OK and thus should be ok for
   MSCP? */

       scratchucb = (UCB*) myswucb;
       swfdt = scratchucb->ucb$l_ddt->ddt$ps_fdt_2;
       if (myswucb->ucb$l_indrct != 0){
         if (kk == IO$_PHYSICAL || swfdt->fdt$ps_func_rtn[kk] == EXE$ILLIOFUNC){
/* The function is format, or is one that is not used for mscp, so we will
   see if the FDT that WAS used is the one that the underlying driver path
   we will use was expecting to have been used. If NOT, we will generate an
   ILLIOFUNC return right here. */
           mypath0 = &myswucb->ucb$a_hstdta[0];
/* Find the two FDT tables involved */
           scratchucb2 = (UCB*)mypath0->ucb$l_hstucb;
	   swfdt = scratchucb2->ucb$l_ddt->ddt$ps_fdt_2;
           scratchucb2 = (UCB*)mypath->ucb$l_hstucb;
           genfdt = scratchucb2->ucb$l_ddt->ddt$ps_fdt_2;
           if (swfdt->fdt$ps_func_rtn[kk] != genfdt->fdt$ps_func_rtn[kk]){
/* FDT addresses do NOT match, and it is essential they should. So
   junk this I/O before it can do any damage||! */
	     ucb->ucb$l_irp = irp;
             ioc_std$reqcom(SS$_ILLIOFUNC,0,ucb);
	     return;
           }
         }
       }
/* First test for "special" I/Os that should go to particular paths
   breaking the abstraction. These have all but low 5 bits of func
   modifiers set (assumes 32 subpaths max) and the low fcn mfy bits
   flag which path to use. */
     k = irp->irp$v_fmod;
     k = k & 0xffffffe0;
     if (k == ((IO$M_FMODIFIERS>>IO$S_FCODE) & 0xffffffe0)){
/* I/O should be routed to one of the subordinate paths. */
       k = irp->irp$v_fmod & 0x1f; /* K is now the index of which path to use*/
/* Protect against num_hosts being less than 1f hex */
       k = k % (NUM_HOSTS);
       irp->irp$v_fmod = 0; /* Clear the modifiers for underlying paths */
       mypath = &myswucb->ucb$a_hstdta[k];
/* mypath is now the path pointer we should use. Send I/O there. Note
   however that if k==0, we special case to avoid recursion. */ 
/* fix the IRP up to send there first. */
/* Do this in one place for simpler understanding */
       fixirp(irp,myswucb,k);
       if (k != 0){
         ucb->ucb$v_bsy = 0;
/* i/o to a secondary path */
         exe_std$insioqc(irp,(UCB*)mypath->ucb$l_hstucb);
         return;
       } else {
/* i/o to main path */
	 (long)myentry = (long)myswucb->ucb$l_hstartio;
	 myentry(irp,ucb);
         return;
       } 
     }
/* Not special case I/O to particular path, so everything else here
   follows the abstraction that there is one device and several paths
   one of which is used at a time. */
/* However, ignore IRPs that dk may just send at us a second time */
     if (irp->irp$l_pid != (long)&fixsplit){
       myswucb->ucb$l_errchgwedge = 0;
       myswucb->ucb$l_retries = 0; /* Flag we saw something that was NOT a mvirp */
       myswucb->ucb$l_sawsucc = 1;
     }
/*     myswucb->ucb$l_outstnd++; */
     fixirp(irp,myswucb,k = myswucb->ucb$l_indrct); /* fix up IRP for path */
     if (k != 0){
/* i/o to a secondary path */
/* Unbusy main path at this point too. */
       ucb->ucb$v_bsy = 0;
       mypath = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct];
       exe_std$insioqc(irp,(UCB*)mypath->ucb$l_hstucb);
       return;
     } else {
/* i/o to main path */
       (long)myentry = (long)myswucb->ucb$l_hstartio;
       myentry(irp,ucb);
       return;
     } 
   } else {
/* mount verify IRP, presumably not synch'd by ucb busy */
/* Here, note, we must check for packack and do path switch if that is
   what is called for||! */
/* Run the path check stuff first */
     if (irp->irp$v_fcode == IO$_PACKACK){
       myswucb->ucb$l_sawsucc = 0;
#define MIN_RETRIES 3
       if (myswucb->ucb$l_retries++ > MIN_RETRIES){
/* */
/* */
/* */
/*    SWITCH PATH CODE */
/* */
/* */
/* */
/* We have retried too often, BUT be sure no dwell time limit exists and
   don't switch if in the dwell time. */
         if ((myswucb->ucb$l_dwell == 0) ||
            ((unsigned)(myswucb->ucb$l_dwell + HYSTERESIS) < (unsigned)exe$gl_abstim)){
/* OK, we're not in a dwell interval and have counted enough packacks to flip
   to a next path. Do so if we can. */
           myswucb->ucb$l_dwell = exe$gl_abstim;
           myswucb->ucb$l_retries = 0;
           myswucb->ucb$l_outstnd = 0; /* rezero outstanding at switch */
/* note that we cannot rezero outstanding I/O if we switch dynamically in
   the future sometime, but this guards against erroneous busy indicators
   if something goes REALLY wedged on one path. */

/* now find a path if possible */
           kk = (myswucb->ucb$l_indrct ); /* current path */
/* the indirection array pthord should hold the desired order of trials. */

/* Find where in the pthord array we currently are. */
	   kkk = -1;
           for (k=0; k< NUM_HOSTS; k++){
	     if (myswucb->ucb$l_pthord[k] == kk){
	       kkk = k;
               break;
             }
           }
/* index "kkk" is where we are on the old path in the path order select
   list. */
	   kk = -1;
	   kkkkk = kkk;
           for (k=0; k< NUM_HOSTS; k++){
	     kkk = (kkk+1) % NUM_HOSTS; /* look at the next path */
             kkkk = myswucb->ucb$l_pthord[kkk]; /* get the path index */
             mypath = &myswucb->ucb$a_hstdta[kkkk];
	     if (mypath->ucb$l_enapth != 0){
/* Looks like we have a usable path now, finally. Therefore switch to it. */
/* Don't switch to an offline disk though!! */
               hstucb = (UCB*) mypath->ucb$l_hstucb;
	       if ((long)mypath->ucb$l_hstucb < 0 && hstucb->ucb$v_online == 1){
                 myswucb->ucb$l_indrct = kkkk;
	         kk = kkkk;
                 break;
               }
	     }
           } /* end loop over enabled paths */
/* If the enabled list is empty, something is really wedged so as a last ditch
   attempt to go again, try switching while ignoring the enable bits. */
           if (kk < 0){
             kkk = kkkkk;
             for (k=0; k< NUM_HOSTS; k++){
	       kkk = (kkk+1) % NUM_HOSTS; /* look at the next path */
               kkkk = myswucb->ucb$l_pthord[kkk]; /* get the path index */
               mypath = &myswucb->ucb$a_hstdta[kkkk];
	       if ((long)mypath->ucb$l_hstucb < 0){
/* Looks like we have a usable path now, finally. Therefore switch to it. */
/* Do however be certain it is online. */
                 hstucb = (UCB*) mypath->ucb$l_hstucb;
	         if (hstucb->ucb$v_online == 1){
                   myswucb->ucb$l_indrct = kkkk;
	           kk = kkkk;
		   break;
                 }
               }
             }
           } /* end of hunt ignoring enable/disable */
/* Is the new path local? If so, allow MSCP etc. serving of the path. */
/* A path being local gets tested much as the mscp server does it except
   for this bit...should become exactly the same once we get a new bit.*/
	   scratchucb3 = (MSCP_UCB*)mypath->ucb$l_hstucb;
	   scratchucb4 = (UCB*)mypath->ucb$l_hstucb;
	   scratchddb = (DDB*)scratchucb4->ucb$l_ddb;
           kkkkkk = scratchucb4->ucb$l_devchar2; /* 6 k's */
/* here kkkkkk (6 k's) is the devchar2 field of the underlying path */
	   if (myswucb->ucb$l_indrct == 0)kkkkkk = myswucb->ucb$l_srvbits;
/* Initially disallow serving */
           ucb->ucb$l_devchar2 |= DEV$M_SRV;
/* Now if the secondary path is NOT an MSCP one, be sure the main path is not
	marked MSCP either.  If this chosen path IS the primary, recall
	the original cddb value though. */
/* Set up the MSCP controller model the way it should be to reflect the 
   path chosen. */
/* NOTE add some logic for QIO server case here */
           if ((long)scratchucb3->ucb$l_cddb < 0){
/* By default clear the ctrlmdl field if it looks like a CDDB is there
   even if this isn't really an MSCP path. */
             scratchucb3->ucb$l_cddb->cddb$b_cntrlmdl = 0;
           }
/* Now if the path is marked MSCP, set the controller model
   appropriately. The idea is the MSCP server will otherwise normally
   try to serve UCBs that look like SCSI disks, but if the main path
   is an MSCP path we need to make it look local and serveable if the
   real path we're using is OK. */
           if (((scratchucb4->ucb$l_devchar2 & DEV$M_MSCP) != 0) &&
	      ((long)scratchucb3->ucb$l_cddb < 0)){
/* looks like we have a cddb */
	     kkkkkkk = scratchucb3->ucb$l_cddb->cddb$b_cntrlmdl; /* 7 k's */
	     if (myswucb->ucb$l_indrct == 0)kkkkkkk = myswucb->ucb$l_mscpctrl;
/* now kkkkkkk (7 k's) is the ctrl model to use, fill 'er in. */
/* Thus if it fills in an emulator value that reflects the path we're on and
   conversely. */
	     scratchucb3->ucb$l_cddb->cddb$b_cntrlmdl = kkkkkkk;
	   } /* end of dev$m_mscp test */

/* See if the path should be served, testing the stuff the MSCP server does. */

	   if (((kkkkkk &   /* 6 k's */
               (DEV$M_NOCLU|DEV$M_SRV|DEV$M_VRT))
	       == 0) && ((scratchddb->ddb$l_allocls == clu$gl_allocls)
	       || (scratchddb->ddb$v_pac != 0))){
/* Add more tests here like the MSCP server (for now; be like QIO server
   later too.) */
/* Here clear the "mscp served" bit to sllow mscp serving */
	      ucb->ucb$l_devchar2 &= ~DEV$M_SRV;
	   }
/* The path is now the best we can find, even if it is just the original one
   again. */
/* Tell the server we just switched. */
	     send_daemon_msg(irp,myswucb,origucb,1);
/* That is it for path swapping. Now merge to handle sending the I/O on. */
         }
       }
     }
/* At this point we have a MVIRP, of some kind, not necc. a packack. Send it
   to the proper path, using ioc$initiate & friends since this isn't synch'd
   by the UCB busy bit. */
/* Unfix it before fixing it, so regardless of what happens it gets fixed
   right for THIS path. */
/* (these can after all just evaporate...) */
/* Note unfix will do nothing if the IRP wasn't fixed by us. */
     unfixirp(irp,myswucb,k=0);
     fixirp(irp,myswucb,k = myswucb->ucb$l_indrct); /* fix up IRP for path */
/*     myswucb->ucb$l_outstnd++;*/
     if (k != 0){
/* i/o to a secondary path */
       mypath = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct];
       ioc_std$initiate(irp,(UCB*)mypath->ucb$l_hstucb);
       return;
     } else {
/* i/o to main path */
       (long)myentry = (long)myswucb->ucb$l_hstartio;
       myentry(irp,ucb);
       return;
     } 
   }
}

/* fixirp - 
 *
 * Abstract:
 * Customizes an IRP for the path it will be using and saves 
 * pre-existing fields that are changed to either an IRP context
 * stack or to an auxiliary structure it allocates, links in, and 
 * fills in.
 *
 * Inputs:
 *  irp		- The IRP to alter
 *  myswucb	- The UCB of the SW device being used
 *  npath	- The path index currently in use
 *
 * Outputs:
 *  none
 */

void fixirp(IRP* irp, SW_UCB* myswucb, int npath){
  int k,kk,kkk,kkkk,kkkkk;
  long lk;
  PTHPTR * mypath;
  AXS * myaxs;
  UCB* orgucb;
  AXSSTK * myaxsstk;
  char * poolp;

   mypath = &myswucb->ucb$a_hstdta[npath];

/* Do nothing if the IRP is already altered, as flagged by having its
   irp$l_pid address pointing at fixsplit. */
   if (irp->irp$l_pid == (long) &fixsplit )return;

/* DKdriver has a habit of using a "golden" IRP whose only purpose is
   to ensure its busy bit is not set. We could wind up seeing this IRP
   in our intercept if we intercept a DK unit, but this is not a real
   IRP and really needs to just be junked. It gets junked inside DK
   startio if sent there. Now, it has a peculiar structure in that
   it is physically located inside the DK UCB. We don't really want to
   process it, but rather just continue to look for work, so if we find
   this has gotten in, don't alter it but send it by. Don't count it 
   either. Recognize it by seeing if the IRP is physically inside the
   master UCB and has svapte=0, func=0. If we aren't along the main path,
   this busy bit stuff happens below our level and can be ignored...so only
   do the test if npath==0. */
   if (npath == 0){
     orgucb = (UCB*)mypath->ucb$l_hstucb;
/* orgucb is the UCB address of the prime path UCB, so we can check the
   IRP to see if it's inside... */
     if ((long)irp->irp$l_svapte == 0 && (long)irp->irp$l_func == 0){
       lk = orgucb->ucb$w_size;
       lk = lk + (long)orgucb;
       if (((unsigned)irp > (unsigned)orgucb) &&
          ((unsigned)irp < (unsigned)lk)){
/* This looks like the golden IRP. Just junk it by returning with no mods
   to the IRP, leaving it alone to be junked inside dkdriver. */
         return;
       }
     }
   }

   myswucb->ucb$l_outstnd++;

/* Now see whether we can use the IRP's internal argument stack
   to hold our context information. If so, we will use that. However
   if this cannot be done we must now allocate an axs structure
   to keep the old IRP information we must replace. */

    if ((irp->irp$w_size <= IRP$K_LENGTH_V71) ||
/*        (irp->irp$v_gotstk == 0) || */
/*        (irp->irp$v_onstack == 0) || */
        ((unsigned)irp->irp$l_curcsp - 
          (unsigned)irp > ((unsigned)IRP$K_LENGTH - 
          (unsigned)sizeof(AXSSTK)) )){
/* The IRP either is an old type one too short for a stack, or it has a
   stack, but the stack is too short for a new intercept to be added to
   its context, or its stack pointer cell is invalid (ie, doesn't point to
   the same IRP area if it points to anything at all.) Therefore we will
   need to allocate an axs structure to hold our context. Otherwise we'll
   put it into an axsstk structure, which is a bit shorter. */
      k = exe_std$alononpaged(sizeof(AXSSTK),&kk,&poolp);
      if (!$VMS_STATUS_SUCCESS(k))return;
      (char*)myaxs = (char*)poolp;
      myaxs->axs$w_size = kk;
      myaxs->axs$b_type = DYN$C_MISC;
      myaxs->axs$l_irp = (long *)irp;
      myaxs->axs$l_orgucb = (long)irp->irp$l_ucb;
      myaxs->axs$l_pid = irp->irp$l_pid;
      myaxs->axs$l_orgstat = irp->irp$l_sts;
      myaxs->axs$l_media=irp->irp$l_media;
      myaxs->axs$l_swucb=(long)myswucb;
#ifdef DEBUG
      myaxs->axs$l_func = (long)irp->irp$l_func;
#endif
      if(irp->irp$w_size >= IRP$K_LENGTH){
/* Save old arg sp too if irp is long enough */
        myaxs->axs$l_orgics = (long)irp->irp$l_curcsp;
      }
/* If the IRP is one that is of the sort that HAS a stack but where the
   stack may have overflowed, set flags appropriately within it. The
   irp$v_onstack flag should be set to 1 on irp creation like gotstk
   but we need to clear it if the irp stack fills. */
      myaxs->axs$l_flgs = 0;
      if ((irp->irp$w_size >= IRP$K_LENGTH) &&
/*         (irp->irp$v_gotstk == 1) && */
         ((unsigned)irp->irp$l_curcsp -
          (unsigned)irp < ((unsigned)IRP$K_LENGTH))){
/* looks like a valid stack-equipped IRP so set its flags too */
        myaxs->axs$l_flgs = irp->irp$l_stkflgs;
/*      irp->irp$l_stkflgs = irp->irp$l_stkflgs & 0xfffffffe; */
        irp->irp$l_stkflgs = irp->irp$l_stkflgs & 0xfffffffe;	/* Turn off lo bit for no stack */
      }
/* Now put the axs structure on its queue so we can make the irp right for
   the new path next. */
      __PAL_INSQUEL(&SW_AXSHH,myaxs);
    } else {
/* need an axsstk since this seems to be a new IRP */
      myaxsstk = irp->irp$l_curcsp;
      irp->irp$l_curcsp = irp->irp$l_curcsp + sizeof(AXSSTK);
      myaxsstk->axs$l_irp = irp;
      myaxsstk->axs$l_orgucb = irp->irp$l_ucb;
      myaxsstk->axs$l_pid = irp->irp$l_pid;
      myaxsstk->axs$l_orgics = (long)myaxsstk;
      myaxsstk->axs$l_orgstat = irp->irp$l_sts;
      myaxsstk->axs$l_media = irp->irp$l_media;
      myaxsstk->axs$l_flgs = irp->irp$l_stkflgs;
      myaxsstk->axs$l_swucb = (long)myswucb;
      irp->irp$l_stkflgs |= 1;
/* Now the information is saved in either case. */
    }
/* At this point we must make the IRP usable in the new path and ensure
   we can get control back. */
      (DT_UCB*)irp->irp$l_ucb = mypath->ucb$l_hstucb; /* Make IRP usable on the path */
      irp->irp$l_pid = (unsigned int)&fixsplit; /* Arrange to see it at postprc time */
      irp->irp$v_viaswitch = 1; /* Say we have had this IRP */

/* Note well:
   The refusal to set fast_finish and finipl8 for mvirp or shdio irp
   cases is due to warnings in their comments. Since we will pass the
   completion on "invisibly", the test below can probably be completely
   eliminated. Only the shortness of time to test things that way
   prevents me from removing it. - Glenn Everhart 7/23/97 */

      if ((irp->irp$v_mvirp == 0) &&
          (myswucb->ucb$ahdofddt.DI_PPD.ucb$l_icpflgs.pa.ucb$v_fi8ok == 1) &&
          (irp->irp$v_shdio == 0) ){
/* The IRP looks OK to do IPL 8 local postprocessing with,so do so. */

/* (It is probably OK to set these bits in ALL cases!!!) */

        irp->irp$v_fast_finish = 1;
        irp->irp$v_finipl8 = 1;

      }
/* mount verify could start on paging, swapping, or served I/O but we
   don't want to permit that, */
      irp->irp$v_swapio = 0; /* Don't let MV start below us. We need to know. */          
      irp->irp$v_pagio = 0;
      irp->irp$v_srvio = 0;
/* that's it. */
      return;
}

/* Steal mount verify entry point STEAL_MOUNT_VER (irp, ucb)
 *
 *  This routine gains control when mount verify is being started or
 *  stopped (at start, the IRP entry is filled in; at MV end the IRP
 *  argument is null). On MV start it is expected to get the IRP into
 *  the right UCB queue, and on MV end it is expected to start the first
 *  IRP on the queue.
 *
 * Inputs:
 *  IRP or 0 - the IRP to be stalled, or 0 if MV is over
 *  UCB      - the UCB of the device concerned.
 *
 * Outputs:
 *  IRP queued or I/O restarted
 *
 * Processing:
 *   This code will call the underlying routines. On MV start it
 *   will call underlying MV start, then if the path is an alternate
 *   one will move the IRPs to the master queue in order. On MV end
 *   it will move IRPs to the appropriate device queue (processing them
 *   first) and call underlying MV end to restart I/O.
 */
void steal_mount_ver(IRP* irp, UCB* ucb){
  SW_UCB * myswucb;
  PTHPTR * mypath;
  PTHPTR * mastpth;
  UCB*  mastucb;
  UCB* lastmvu;
  PTHPTR *lastmpth;
  long xirp;
  IRP* qirp;
  IRP* qirp2;
  long ucbsiz;
  UCB* myscrucb;
/* mventry is pointer to function returning int */
  int (*mventry)();
  long k,kk,kkk,kkkk,kkkkk; /* short term temps */
  int npath;

   myswucb = getswucb((UCB*)ucb);
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];
   myscrucb = (UCB*)mypath->ucb$l_hstucb;
   mastpth = &myswucb->ucb$a_hstdta[0];
   mastucb = (UCB*)mastpth->ucb$l_hstucb;

/* the MASTER UCB is always what is passed!!!
   Be sure underlying entry is called with its correct UCB!!! */

/* See if this is begin-MV or end-MV */
   if ((long)irp != 0){
/* MV beginning */
/* Call the underlying MV routine first */
     kk = SS$_BADPARAM;
     (long)mventry = (long)mypath->ucb$l_oldmv;
     lastmvgo = npath; /* remember which path we last put into mv */
/* MSCP may be touchy about MV of the path ucb; dk doesn't really care. */
#ifdef MVPATHUCB
     if(((long)mypath->ucb$l_oldmv) < 0) kk = mventry(irp, myscrucb);
#else
     if(((long)mypath->ucb$l_oldmv) < 0) kk = mventry(irp, ucb);
#endif
/* Now if we're using anything other than the primary path, get any
   IRP that is on the secondary's path and put it on the primary's
   path, AHEAD of whatever else may be there. */     

/* Now if we're using anything other than the primary path, get any
   IRP that is on the secondary's path and put it on the primary's
   path, AHEAD of whatever else may be there. */     

     if (npath != 0){
       while(((long)myscrucb->ucb$l_ioqfl != 
                (long)&myscrucb->ucb$l_ioqfl) &&
                (kk = __PAL_REMQUEL(myscrucb->ucb$l_ioqbl,
                 (void*)&qirp) >= 0)) {
         unfixirp(qirp,myswucb,npath); /* fix up IRP for path */
/* Test if this was the golden IRP of dkdriver and do NOT move that
   to another queue where another driver might get it. */
/* Only compare against the UCB addresses here. */
#ifdef GOLDTEST
         ucbsiz = ucb->ucb$w_size;
         ucbsiz = ucbsiz + (long)ucb;
         if (((unsigned)irp < (unsigned)ucb) || (unsigned)irp >
           (unsigned)ucbsiz){
#endif
           __PAL_INSQUEL(ucb->ucb$l_ioqfl,qirp);
#ifdef GOLDTEST
         }
#endif
       }
     }

   } else {
/* MV end */
/* get IRPs onto the right queue */
     if (npath != 0){
/* For scsi paths, leave one at master level */
     xirp = (long)ucb->ucb$l_ioqbl;
/* For mscp paths, move everything to the mscp path. */
     if ((myscrucb->ucb$l_devchar2 & DEV$M_MSCP) != 0)xirp = (long)&ucb->ucb$l_ioqfl;
/* leave one IRP at the main level to get that going if anything is there
   to be started. */
       while((xirp != (long)ucb->ucb$l_ioqfl) &&
                (kk = __PAL_REMQUEL(ucb->ucb$l_ioqbl,(void*)&qirp) >= 0)) {
/* If an IRP was specialized for the other path, be sure we set it for
   THIS new path! */
         unfixirp(qirp,myswucb,npath); /* just in case, unfix first */
         fixirp(qirp,myswucb,npath); /* fix up IRP for path */ 
         __PAL_INSQUEL(myscrucb->ucb$l_ioqfl,
                       qirp);
       }
     }
/* Ensure master AND subordinate UCBs MV bits are cleared */
     mastucb->ucb$v_online = 1; /* MSCP fiddles with online bit. Try to compensate. */

/* Go over the whole MASTER queue and unfix the IRPs. */
     qirp = ucb->ucb$l_ioqfl;
/* Fix up the whole queue */
/* (At this point we're supposed to be in MV state so the queue should be
   stable. ) */
/* This will ensure nothing that has been pre-fixed for some other path
   will be on the queue any more. */
     while ((long)qirp != (long)&ucb->ucb$l_ioqfl){
       if((long)qirp < 0){
         unfixirp(qirp, myswucb, npath); /* Note it won't unfix unless fixed */
         qirp= qirp->irp$l_ioqfl;
       } else {
         break;
       }
     }
#ifdef MVPATHUCB
/* Now if the last path we put in MV is neither the master path nor the
   one we're about to take out, do end mv on that path too. */
     if (lastmvgo != npath && lastmvgo != 0){
       lastmpth = &myswucb->ucb$a_hstdta[lastmvgo];
       lastmvu = (UCB*)lastmpth->ucb$l_hstucb;
       mventry = (long)lastmpth->ucb$l_oldmv;
       kk = mventry(irp,lastmvu);
       lastmpth = 0;
     }
#endif
/* We mustn't clear mv in progress on main path just here...this is belt and
   suspenders stuff for subordinate paths (which should never need it) */
     if (npath != 0){
       myscrucb->ucb$v_mntverip = 0;
       myscrucb->ucb$v_mntverpnd = 0;
     }
     kk = SS$_BADPARAM;
     (long)mventry = (long)mypath->ucb$l_oldmv;
     if(((long)mypath->ucb$l_oldmv) < 0) kk = mventry(irp, myscrucb);
/* End mv on the main path as well as the underlying one to ensure
   things get going again. */
/* this should clear master MV in progress and so forth too. */
     (long)mventry = (long)mastpth->ucb$l_oldmv;
     kk = mventry(irp,mastucb);
     mastucb->ucb$v_online = 1; /* MSCP fiddles with online bit. Try to compensate. */
/* Clear busy on master if master MV failure was flagged, and set valid
   on master also. Do this only where using a different path... */
     if (npath != 0){
       if (mastucb->ucb$v_valid == 0){
         mastucb->ucb$v_bsy = 0;
         mastucb->ucb$v_valid = 1;
       }
     }

#ifdef GOTTAUNBUSYMAIN
/* We kind of expect MV end to unbusy this, but if it does not, we need
   to do it here. */
     mastucb->ucb$v_bsy = 0; /* unbusy the main UCB */
#endif

     return;
   }
}
/* unfixirp - 
 *
 * Abstract:
 * Un-Customizes an IRP for the path it will be using and restores
 * pre-existing fields from where fixirp saved them.
 *
 * Inputs:
 *  irp		- The IRP to alter
 *  myswucb	- The UCB of the SW device being used
 *  npath	- The path index currently in use
 *
 * Outputs:
 *  none
 */

void unfixirp(IRP* irp, SW_UCB* myswucb, int npath){
  int k,kk,kkk,kkkk,kkkkk;
  PTHPTR * mypath;
  AXS * myaxs;
  AXS * myaxsp;
  AXSSTK * myaxsstk;
  char * poolp;

/* Do nothing unless the IRP is already altered, as flagged by having its
   irp$l_pid address pointing at fixsplit. */
   if (irp->irp$l_pid != (long) &fixsplit )return;

   if (  --myswucb->ucb$l_outstnd < 0) myswucb->ucb$l_outstnd = 0;
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];

/* Now see whether we used the IRP's internal argument stack
   to hold our context information. If so, we will use that. However
   if this cannot be done we must now allocate an axs structure
   to keep the old IRP information we must replace. */

    if ((irp->irp$w_size <= IRP$K_LENGTH_V71) ||
/*        (irp->irp$v_gotstk == 0) || */
        ((irp->irp$l_stkflgs & 1) == 0) || 
        ((unsigned)irp->irp$l_curcsp - 
          (unsigned)irp > ((unsigned)IRP$K_LENGTH) )){
/* The IRP either is an old type one too short for a stack, or it has a
   stack, but the stack was too short for a new intercept to be added to
   its context, or its stack pointer cell is invalid (ie, doesn't point to
   the same IRP area if it points to anything at all.) Therefore we will
   need to allocate an axs structure to hold our context. Otherwise we'll
   put it into an axsstk structure, which is a bit shorter. */

/* The info we want must be on an auxiliary structure. Therefore go get that
   and fix up the IRP, keeping local copies of info so we can use them in
   deciding things like how to synch IPL. */

      myaxsp = SW_AXSHH;	/* get the initial list element */
      while ((long)myaxsp != (long)&SW_AXSHH){
        if ((long)myaxsp->axs$l_irp == (long)irp){
/* Found our aux entry here. Grab off the values we need from it and break
   out of the while loop. */
          irp->irp$l_sts = myaxsp->axs$l_orgstat;
          irp->irp$l_pid = myaxsp->axs$l_pid;
          irp->irp$l_ucb = (UCB*)myaxsp->axs$l_orgucb;
          irp->irp$l_media = myaxsp->axs$l_media;
          if (((unsigned)irp->irp$l_curcsp - (unsigned)irp) <= ((unsigned)IRP$K_LENGTH)
             && (irp->irp$w_size >= IRP$K_LENGTH_V71) ) irp->irp$l_stkflgs = myaxsp->axs$l_flgs;
/* Now remque this entry and deallocate it; we are done looking for it. */
	  __PAL_REMQUEL(myaxsp,&myaxs);
/* deallocate the pool now. */
	  kkk = exe_std$deanonpgdsiz(myaxsp,sizeof(AXS));
	  (long)myaxsp = (long)&SW_AXSHH;
	  break;
        }
        else {
	  myaxsp = (AXS*)myaxsp->axs$l_fwd;	/* get next entry */
	}
      }
    } else {
/* need an axsstk since this seems to be a new IRP */
      irp->irp$l_curcsp = irp->irp$l_curcsp - sizeof(AXSSTK);
      myaxsstk = irp->irp$l_curcsp;
      irp->irp$l_sts = myaxsstk->axs$l_orgstat;
      irp->irp$l_pid = myaxsstk->axs$l_pid;
      irp->irp$l_ucb = (UCB*)myaxsstk->axs$l_orgucb;
      irp->irp$l_media = myaxsstk->axs$l_media;
          if (((unsigned)irp->irp$l_curcsp - (unsigned)irp) <= ((unsigned)IRP$K_LENGTH)
             && (irp->irp$w_size >= IRP$K_LENGTH_V71) ) irp->irp$l_stkflgs = myaxsstk->axs$l_flgs;
/* Now the information is unsaved in either case. */
    }
/* that's it. */
}

/* steal_altstart */
/* Abstract:		*/
/* This routine gains control whenever an altstart call is made on the
   main path. Its sole function is to count the I/O up and ensure it
   gets to the right disk underlying. Because altstart is not controlled
   by UCB busy, it will call the underlying altstart entries directly and
   not touch busy. */
/*
   Inputs:
      irp	IRP to be sent to underlying alt start
      ucb	UCB of prime path
   Outputs:
      Altstart is called on the appropriate underlying device path
      and outstanding I/O counted up (and it gets counted down at
      fixsplit).	
*/
void steal_altstart(IRP* irp, UCB* ucb){
  SW_UCB * myswucb;
  PTHPTR * mypath;
  UCB* myscrucb;
  void *(*myentry)();
  int npath;
  int savipl;
  long k,kk,kkk,kkkk,kkkkk; /* short term temps */

   myswucb = getswucb((UCB*)ucb);
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];
   myscrucb = (UCB*)mypath->ucb$l_hstucb;
   if (npath != 0)fork_lock(myscrucb->ucb$b_flck,
                            &savipl);
   if ((long)mypath->ucb$l_oaltst < 0){
/* Note we must count up IRPs and fix the IRPs too. */
/*     myswucb->ucb$l_outstnd++; */
     fixirp(irp, myswucb, npath);
     (long)myentry = (long)mypath->ucb$l_oaltst;
/* We always enter pointing at the main ucb, but need to get this
   to the underlying one! */
     myentry(irp, myscrucb);   
   }
   if (npath != 0)fork_unlock(myscrucb->ucb$b_flck,
                            savipl,SMP_RESTORE);
   return;
}
/* Steal_pending */
/* Abstract:	*/
/* This entry ensures that pending I/O is dispatched by the underlying
   driver correctly.  We won't edit the IRP here at all, just leave it
   for later */
/* NOTE this entry requires a NON-STANDARD CALL!!! since R5 at entry is
   assumed to be the UCB, otherwise unavailable!!!! */
/*
  Inputs: Queue header, IRP, UCB
   		
  Outputs: The IRP is put onto the queue for the device by calling the
	appropriate underlying pending_io entry point, with returns
	from the underlying driver. Additional logic exists in some
	drivers (fastpath) to decide at times not to queue the IRP so
	this routine is merely a switch.
*/

#pragma linkage pendlnk = (parameters (r16, r17, r5), result (r0))
#pragma use_linkage pendlnk (steal_pending)
int steal_pending(IRP* qhdr, IRP* irp, UCB* ucb){
  SW_UCB * myswucb;
  PTHPTR * mypath;
  UCB* myscrucb;
  int npath;
  int savipl;
  int (*myentry)();
  long kk; /* short term temps */

   myswucb = getswucb((UCB*)ucb);
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];
   kk = SS$_NORMAL;
   myscrucb = (UCB*)mypath->ucb$l_hstucb;
   if (npath != 0)fork_lock(myscrucb->ucb$b_flck,
                            &savipl);
   if ((long)mypath->ucb$l_oaltst < 0){
     (long)myentry = (long)mypath->ucb$l_qirp;
/* irp$l_ucb will be filled in when this IRP gets to start-io */
     kk = myentry(qhdr, irp);
   }
   if (npath != 0)fork_unlock(myscrucb->ucb$b_flck,
                            savipl,SMP_RESTORE);
   return (kk);
}

/* Steal_Aux_Routine */
/* Abstract: 		*/
/* This entry is called by JSB, and needs both nonstandard call pragmas and
   to call glue to call the outbound routine correctly. On input
   R3 is its CDDB address and no IRP is to be found. */
/* We merely dispatch to the right underlying routine here. This entry
   is used in DUdriver to "go look for another MSCP path" just before MV
   starts. */
/*
   Inputs: CDDB pointer
   Outputs: Auxiliary processing is called in the correct (ie, active)
	underlying path. This processing is generally used to tell MSCP
	to look for another path prior to starting mount verify. The
	path is called here via some macro "glue" because the call
	convention does not work with C. If the underlying path had no
	auxiliary entry, this entry just returns.
*/

#pragma linkage auxlnk = (parameters (r3), result (r3))
#pragma use_linkage auxlnk (steal_aux_routine)
int steal_aux_routine(char* cddb){
   UCB* myucb;
   SW_UCB * myswucb;
   PTHPTR * mypath;
   int npath;
   int savipl;
   UCB* myscrucb;
   long kk; /* short term temps */

   (char*) myucb = cddb + offsetof(CDDB,cddb$l_ucbchain) - offsetof(MSCP_UCB,ucb$l_cddb_link);
   myswucb = getswucb((UCB*)myucb);
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];
   kk = SS$_NORMAL;
   myscrucb = (UCB*)mypath->ucb$l_hstucb;
   if (npath != 0)fork_lock(myscrucb->ucb$b_flck,
                            &savipl);
   if ((long)mypath->ucb$l_oaltst < 0){
     kk = call_macro_pass_r3((int)mypath->ucb$l_aux,cddb);
   }
   if (npath != 0)fork_unlock(myscrucb->ucb$b_flck,
                            savipl,SMP_RESTORE);
   return (kk);
}

/* steal_cancel */
/* Abstract:	*/
/* This routine gains control at driver cancel I/O calls. Note we don't
   bother with cancel_selective here (no disk driver uses it) but
   must handle driver cancel I/O.
   Driver cancel is present to allow I/O from a particular process and
   channel to be pulled out of queues. Unfortunately we alter irp$l_pid
   here, so the system routine can't in general find IRPs for the desired
   process. It would be desirable here to overcome this, since we *do*
   know where the IRP information is. Cancel is called fairly often,
   however...at every file close for example...so any code added here
   must be fast. Duplicating a fairly involved stretch of Macro-32 from
   the syscancel.mar routine seems like a poor idea from a maintenance
   point of view, but may be needed (or another call site for it
   may be) since each intercept using aux structures may keep its
   data in disjoint places. I *really* wish irp$l_pid didn't get
   reused for a postprocessing hook; a separate field would be SOOOOO
   much cleaner ... */
/* We will thus just dispatch down, but will pass a fake PCB down
   with pcb$l_pid pointing to this intercept's site for MSCP
   cancels. */
/* Inputs:
   Driver cancel-io inputs: channel, IRP, PCB, UCB, cancel reason
   Outputs: Cancel request is sent on to the appropriate underlying
	driver (whichever is the currently in use one). If the
	cancel is from the MSCP server, we duplicate the MSCP server's
	trick of supplying a fake PCB so that the IRP$L_PID value
	in the IRPs matches PCB$L_PID of the supplied fake PCB.

	We do not do this trick for other reasons (any more than the
	MSCP server does!).
*/
int steal_cancel(int chan, IRP* irp, PCB* pcb, UCB* ucb, int reason){
  SW_UCB * myswucb;
  PTHPTR * mypath;
  int npath;
  int savipl;
  UCB* myucb;
  int (*myentry)();
  long kk; /* short term temps */
  PCB* fakepcb;

   fakepcb = pcb;
   myswucb = getswucb((UCB*)ucb);
   npath = myswucb->ucb$l_indrct;
   mypath = &myswucb->ucb$a_hstdta[npath];
   kk = SS$_NORMAL;
/* If the cancel is from MSCP and the reason for cancel is
   CAN$C_MSCPSERVER, we want to make this look like the PCB
   PID matches the packets. Use the same filthy dirty trick that
   MSCP does and fake the PCB! */
   if (reason == CAN$C_MSCPSERVER){
     fakepcb = &myswucb->ucb$l_fakepcb;
   }
   if ((long)mypath->ucb$l_cancl < 0){
     (long)myentry = (long)mypath->ucb$l_cancl;
/*     irp->irp$l_ucb=myucb=(UCB*)mypath->ucb$l_hstucb;  */
     myucb=(UCB*)mypath->ucb$l_hstucb;
     kk = myentry(chan,irp,fakepcb,myucb,reason);   
   }
   return(kk);
}
/* fdt_to_original -
   send the I/O to the original FDT routine, i.e., the one from whatever
   we intercepted. */
/* When called, UCB is our SW_UCB since this gets called for FDT
   code handled locally. Where this goes on, we need to send the
   code to the device we intercepted. */
/* Inputs:
   IRP from the user
   PCB for the user process
   SW_UCB for our intercept unit
   CCB for the channel used

   Outputs:
   Call to intercepted driver unit with the same arguments except
	for the UCB which is the intercepted master UCB.
 
   This routine is called only if the IO$_PHYSICAL code is given to
   the SW driver and is not modified as all such are expected to
   be. It will in general be needed only where the FDT routines
   are being modified. If one uses SWdriver as a model of how to 
   intercept FDTs, this routine does what is needed to continue FDT
   processing after local FDT preprocessing has run, unless the I/O
   is in fact completed locally (i.e., in the intercept driver).
 */

int fdt_to_original(IRP* irp,PCB* pcb,SW_UCB* ucb,CCB* ccb){
long myfcn;
UCB* icucb;
FDT* oldfdt;
int (* oldfcn)();
   icucb = (UCB*)ucb->ucb$l_backlk;
   myfcn = irp->irp$v_fcode;
   oldfdt = (FDT*)ucb->ucb$l_oldfdt;
   (long)oldfcn = (long)(sizeof(long) * myfcn) + (long)FDT$PS_FUNC_RTN +
                    (long)oldfdt;
   return (oldfcn(irp, pcb, icucb, ccb));
}

/* driver cloned UCB entry */
/* Abstract:								*/
/* We use this scheme to make it easier to load units. The swdriver will
therefore get loaded ALL the time, but only the template UCB will be set
up initially. Then here we will fill in a cloned UCB, and also clear the
deleteucb bit so the UCB will remain. That way code that wants to set
up a switch driver unit need only assign a channel to get it created...no
need to load the driver, connect a unit etc. etc.
*/
/*
  Inputs:
     cloneducb		-	The UCB just cloned by the system
     ddt		-	The DDT the system just created
     pcb		-	fork flag. If 1, call is from IPL 8 and
				we just call unit_init_fork directly.
				Otherwise we fork to call it.
     templateucb	-	Template (generally SWA0:) UCB

  Outputs:
     The template UCB is initialized and set up, either immediately on
		return (if pcb == 1) or after a fork has run. Synch is
		standard cloned-UCB synch. Note however that the deleteucb
		bit is CLEARED here so the created SW UCBs are not deleted
		when no channels to them exist.
*/

int sw_cloneducb(UCB* cloneducb, DDT* ddt, PCB* pcb, UCB* templateucb){
 SW_UCB* myswucb;
 SW_UCB* tempswucb;
 PCB* fakepcb;

  cloneducb->ucb$v_deleteucb = 0; /* do NOT delete this new UCB */
  cloneducb->ucb$v_template = 0;  /* be sure this ucb is not marked as a template */
  myswucb = (SW_UCB*)cloneducb;
  fakepcb = (PCB*)&myswucb->ucb$l_fakepcb;
  fakepcb->pcb$l_pid = (unsigned int)&fixsplit;
  cloneducb->ucb$l_fpc = &unit_init_fork;/* Point to fork routine address */
  if ((long)pcb == 1){
/* If the PCB is 1, this call is from ipl 8 and may as well just call the
   unit_init_fork routine directly. */
   unit_init_fork(0,(IDB*)fakepcb,myswucb);
  } else {
    exe_std$primitive_fork(ddt,pcb,(FKB *) cloneducb);/* Start fork process */
  }
  return SS$_NORMAL;                  /* Return with success */
}
/*	sw-fakeformat		*/
/*									*/
/* Abstract:								*/
/*      This routine assumes it is entered at IPL 8 holding forklock and
   is designed to do what io$_format processing would do, more or less,
   from IPL 8 threads of code in places like DUdriver that must set
   up switching when device UCBs are created. */
/*									*/
/* Input:								*/
/*      buffer  pointer to argument buffer				*/
/*	bufsiz	size of argument buffer					*/
/*	ucb	pointer to SW UCB					*/
/*
Buffer formats:

ufunc	uswitch lo	uswitch hi	ulen		rest
1					dvc name len	dvc name	bash
2					dvc name len	dvc name	unbash
3	unit to sw to	idlfgs=0	switch, err if dvc bsy
3	"		idlfgs=1	save next irp, busy dvc
3	"		idlfgs=2	restart i/o, unbusying dvc, no switch
4	unit to rpt	?		statblk len	statblk data rpt stats
			Returns statistics on device paths this SW unit is
			using in an array.
5	UCB of device			x		x
		returns SW UCB, pathpointer array address, and some other
			info. Switch part of array gets the SW UCB or zero
			if none is found. This request can be sent to ANY
			SWdriver unit for any device to find out if the device
			is intercepted. It will return the SW ucb and info
			about the SWdriver unit that intercepted the device
			if it is intercepted.
6	IPID of server (both halves)	mbx name len	mbx name
			Sets the mailbox UCB and server IPID (if they
			are legal & found) into this SW driver unit UCB
			so it will communicate with the mailbox to talk
			to the server.
7	x		x	x	x	x	
			Clears the SWdriver's references to the server.
8			Returns a magic number for this driver, adding 4
			if the driver is online. Use from user mode code
			to ensure you are talking to swdriver.
9	As 1 and 2, but ulen is device UCB, not name. DDB and SB are
10		obtained from the UCB pointer and DDB pointer.
		This is done to simplify kernel mode setup.
11			x	x	x	x	x
			Returns SW UCB pointer, value of ucb$l_indrct,
			and the PTHPTR structure addresses of all PTHPTR
			structures, and of the one which is current.
12
			Functions like 5, except that no input UCB need
			be supplied. It returns information about the
			devices the pointed-to SW device has associated
			with it.

*/
/*									*/
/* Output:								*/
/*									*/
/* Return value:							*/
/*	SS$_FDT_COMPL	shows that the routine completed correctly	*/

int sw_fakeformat(char* buffer, int bufsiz, SW_UCB *ucb) {

/*
  Define the input buffer type as a union struct so each function can
  find a convenient type to use.
*/
#define MPSZ 128
#define MPSZQ 64
#define MPDN 512
/* Number of paths that will fit for statistics report */
#define MPNPTH ((MPSZ-5)/4)
  typedef struct inbuf {
    long ufunc; /* user function code (eg, bash/unbash) */
    long uswitch; /* info passed in (eg add, which unit, encoded) */
    long ulen;    /* length of device name passed */
    union  {
      char dvcname[MPDN];
      long moreprm[MPSZ];
    } ;
  } INBUF;
  typedef struct qinbuf {
    long ufunc; /* user function code (eg, bash/unbash) */
    long uswitch; /* info passed in (eg add, which unit, encoded) */
    long ulen;    /* length of device name passed */
    union  {
      char dvcname[MPDN];
      uint64 moreprm[MPSZQ];
    } ;
  } QINBUF;
  INBUF InBuf;
  int tstat;
  int tfcn;
  SB* mysb;
  INBUF * ibp;
  QINBUF * qibp;
  UCB* myucb;
  DDB* myddb;
  UCB* scratchucb;
  PTHPTR* mypthptr;
  SW_UCB* myswucb;
/* allow us to pull a quad into 2 longs */
  union {
    uint64 ttim;
    long ttiml[2];
    }timu;
  long myswitch;
  int myswmod;
  int myunit;
  SW_UCB* scrswucb;
  int ufcode;
  int savipl;
  int curunit;	/* currently used subunit, for where we are switching */
  int kk,kkk,kkkk; /* temps */
  PCB* trypcb;
  UCB* tstucb;
  DP_UCB* mydpucb;
  long* pcbptr;
  PTHPTR * newpath;
  struct dsc$descriptor_s mydvcnm;

/* check the buffer is writeable */
   if (bufsiz < sizeof(INBUF)){
     return (SS$_BADPARAM);
   };
   myswucb = ucb;
   ibp = (INBUF*) buffer;
   qibp = (QINBUF*)ibp;
   scrswucb = ucb;
/* Set up the descriptor */
   mydvcnm.dsc$w_length = strlen(ibp->dvcname);
   mydvcnm.dsc$b_class = DSC$K_CLASS_S;
   mydvcnm.dsc$a_pointer = &ibp->dvcname[0];
   mydvcnm.dsc$b_dtype = DSC$K_DTYPE_T;
   myswitch = ibp->uswitch;
/* extract word fields out of "myswitch" */
   myunit = myswitch & 65535;
   myswmod = (myswitch >> 16);
   
   ufcode = ibp->ufunc;
   scratchucb = (UCB*) ucb;
   switch (ufcode)
     {
/* 1 bashes disk, 2 unbashes */
/* 3 returns swdriver address and pthptr address if bashed by it. */
/* In these cases the "myswitch" arg low word is the unit to set or clear */
/* Note: don't really use functions 1 and 2 here except in tests if
   even then. Use 9 and 10 instead. */

       case 1:{
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
#ifdef DEBUG
/*  ini$brk(); */
#endif
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat)){
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  if(myucb->ucb$v_online){
                    mung(scrswucb,myucb,myddb,mysb,myswitch);
                  } else {
                    return (SS$_DEVOFFLINE);
                  }
		} else {
/* can't find device. Err...*/
                  return (SS$_DEVOFFLINE);
                }
	      return(SS$_NORMAL);
              }
       case 2:{
              
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat)){
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  unmung(scrswucb,myucb,myddb,mysb,myswitch);
		};

	      return(SS$_NORMAL);
              break;
              }
/* more options here */
/* Rather than use a different code, we will use this area to do manual
   switching also. The buffer format will in this case use the myswitch
   variable as two words also. The format will be

   Word 0: unit to switch to (0 to NUM_HOSTS)
   Word 1: idle-device flags: If 0, just return an error if the
				device was busy (active I/O count non zero)
				and return success only if the unit was idle
				and successfully switched.
			      If 1, flag startio intercept to save the
				next IRP address and leave the device busy
				until we later unbusy it. The unbusy call will
				be able to switch or not.
			      If 2, restart I/O with no switch using the saved
				IRP if any. If there is none, just return.

Note that the busying will not alter the outstanding I/O count ucb$l_outstnd
since it is purely inside the switching driver here. The caller can block or
unblock I/O but will need to wait for I/O to drain to switch paths; this is
easier in user mode so we'll leave it for that. We'll restart I/O by forking
and at fork level running REQCOM.
*/
       case 3:{
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
/* Again unit 65535 means "the next enabled unit" */
	        if (myunit > NUM_HOSTS){
		  for (kkk = 1; kkk <= NUM_HOSTS; kkk++){
/* get index of the next path (but go far enough to come back if we must) */
		    kkkk = (kkk + ucb->ucb$l_indrct)%NUM_HOSTS;
		    newpath = &ucb->ucb$a_hstdta[kkkk];
		    if (newpath->ucb$l_enapth){
		      myunit = kkkk;
		      break;
                    }
		  }
	        }
	        if (myunit > NUM_HOSTS){
/* if still above maxunits, error return */
		  return(SS$_ABORT);
		}
		  newpath = &ucb->ucb$a_hstdta[myunit];
		  switch (myswmod){
		    case 0:{
		      if (ucb->ucb$l_outstnd != 0){
		        return(SS$_IVSTSFLG);
		      } else {
/* OK, looks like we should switch the unit. Do so. */
/* (MANUAL switch here !! ) */
			device_lock(scratchucb->ucb$l_dlck,1,&savipl); /* *** fix this *** */
			ucb->ucb$l_indrct = myunit;
			ucb->ucb$l_dwell = exe$gl_abstim;
			ucb->ucb$l_retries = 0;
			device_unlock((struct _spl*)scratchucb->ucb$l_dlck,savipl,1);/* *** fix this *** */
			return(SS$_NORMAL);
		      }
		    }
		    case 1:{
/* dummy this case here...cannot do it without i/o */
			return (SS$_UNSUPPORTED);		/* exit */
		    }
		    case 2:{
/* Here we finish up the IRP that was queued before. */
/* dummy this case here...cannot do it without i/o */
			return(SS$_IVSTSFLG);
		    }
		    default:{
		      return(SS$_BADPARAM);
		    }
		  }
	      return(SS$_NORMAL);
              break;
	      }

       case 4:{
/* Use subfunction 4 for reporting statistics, which include I/O counts
   and error counts for all subpaths, and things like time of last switch
   and anything else that looks worth having. We'll just dump the numbers
   into the user's buffer which we know to be writable already. */
                ibp->moreprm[0] = NUM_HOSTS; /* Tell how long tables are */
                ibp->moreprm[1] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
	        qibp->moreprm[2] = myswucb->ucb$q_swtime;                
		kkk = 6;
		for (kk = 0; kk < NUM_HOSTS; kk++){
		  mypthptr = &ucb->ucb$a_hstdta[kk];
		  if (kk < MPNPTH){
/* Store statistics data for output. */
/* The data is I/O count, error count, and time of last I/O for each path */
		    ibp->moreprm[kkk++] = mypthptr->ucb$l_pthios; /* I/O count this path */
		    ibp->moreprm[kkk++] = mypthptr->ucb$l_ptherr; /* error cnt */
		    timu.ttim = mypthptr->ucb$q_pthtim; /* get time */
		    ibp->moreprm[kkk++] = timu.ttiml[0]; /* i/o time */
		    ibp->moreprm[kkk++] = timu.ttiml[1]; /* i/o time */
		  }
		}
		return(SS$_NORMAL);
		break;
              }
       case 5:{
/* Use subfunction 5 to get back information about bashes. In this case
   we expect we are being called by some external code which needs to find
   out if the device being passed is in fact having its path intercepted by
   swdriver. Rather than make other code need to know the details of our
   internal structures, use this code to export the knowledge. On input
   look for a device UCB address, and if it is intercepted by a SW UCB
   return the SW unit number and the pthptr array address.
*/
	      if (myswitch < 0){
/* UCB address looks potentially valid */
	        myswucb = getswucb((UCB*) myswitch);
                ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	        scratchucb=(UCB*)myswucb;
                if ((long)scratchucb < 0){
                  ibp->moreprm[0] = scratchucb->ucb$w_unit; /* return SW unit no. */
	          ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer
						      array also. */
                  ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
                  ibp->moreprm[3] = &myswucb->ucb$l_pthord[0]; /* path sel order addr */
                  ibp->moreprm[4] = NUM_HOSTS; /* Tell how long tables are */
                  ibp->moreprm[5] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                  ibp->moreprm[6] = (long)myswucb->ucb$l_vict; /* intercepted ucb */
                  ibp->moreprm[7] = myswucb->ucb$l_outstnd; /* Current I/O count */
                }
	      } else {
	/* error to fdt */
	      }
/* complete this I/O */
		return(SS$_NORMAL);
		break;
              }
       case 6:{
/* Use subfunction 6 to insert the server mailbox UCB and PID */
/* Input in device name area is MB device name stuff so we look it up
   (to reduce kernel code elsewhere) and PID in the other long. */
              mydvcnm.dsc$a_pointer = & ibp->dvcname[0];
              mydvcnm.dsc$w_length = ibp->ulen;
              tstat = ioc_std$searchdev(&mydvcnm,&myucb,&myddb,&mysb);
		if ($VMS_STATUS_SUCCESS(tstat) && (myswitch > 0)){
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
                  ibp->uswitch = myucb; /* return mbx ucb */
                  ucb->ucb$l_mbxucb = myucb;
/* Let's be sure the IPID here is a process IPID that really exists*/

                  pcbptr = (long*) SCH$GL_PCBVEC;
                  if ((long)pcbptr >= 0)return;
                  kkk = 0;
                  for (kk=0; kk < SCH$GL_MAXPIX; kk++){
                    trypcb = *(PCB**)pcbptr++;
                    if (trypcb->pcb$l_pid == myswucb->ucb$l_daemon ){
                      kkk = 1;
                      break;
                    }
                  }
		  if (kkk == 1){
                    ucb->ucb$l_daemon = myswitch; /* IPID of the daemon */
		  }
		} else {
/* can't find device. Err...*/
/* Must place error in the fdt context area */
                }

	      return(SS$_NORMAL);
/* complete this I/O */
		break;
              }
       case 7:{
/* Function 7 just deletes the reference to the server */
	      ucb->ucb$l_daemon = 0;
	      ucb->ucb$l_mbxucb = 0;
		return(SS$_NORMAL);
		break;
              }
       case 8:{
/* Function 8 sanity checks that this IS an SW driver by returning a
   magic number, octal 14747 or 6631 decimal. */
/* This is the pdp11 opcode for "mov -(pc),-(pc)" which instruction
   copies itself backwards in memory until it wraps around 32 kw.
   It is weird but odd, so will be taken for a kind of success. */
		kkk = 6631;
/* Add 4 if the SW unit is offline */
/* This should generally be the case for first-time access */
		kkk = kkk + 4;
		return(kkk);
		break;
              }
       case 9:{
#ifdef DEBUG
/*  ini$brk();*/
#endif
		if ((ibp->ulen) < 0){
                  myucb = (UCB*)ibp->ulen;
                  myddb = myucb->ucb$l_ddb;
                  mysb = (struct _sb *)myddb->ddb$l_sb;
/* in this case, "myswitch" is the index (counting from zero) of the
   path within this tuple for the switch driver. 			*/
		  if (myswmod > 0){
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  if(myucb->ucb$v_online){
                    mung(scrswucb,myucb,myddb,mysb,myswitch);
                  } else {
                    return (SS$_DEVOFFLINE);
                  }
		} else {
/* can't find device. Err...*/
                  return (SS$_DEVOFFLINE);
                }
	      return(SS$_NORMAL);
              break;
              }
       case 10:{
              
		if ((ibp->ulen) < 0){
		  if (myswmod > 0){
                  myucb = (UCB*)ibp->ulen;
                  myddb = myucb->ucb$l_ddb;
                  mysb = (struct _sb *)myddb->ddb$l_sb;
/* if switch modifier exists, use "2p" alias here. */
		    mydpucb = (DP_UCB*)myucb;
		    if ((myucb->ucb$l_devchar2 & DEV$M_2P) != 0){
/* If we are supposed to use the alternate UCBs, set that linkage up */
		      myddb = (DDB*)mydpucb->ucb$l_2p_ddb;
		      myucb = mydpucb->ucb$l_2p_altucb;
		    }
		  }
		  unmung(scrswucb,myucb,myddb,mysb,myswitch);
		};

	      return(SS$_NORMAL);
              break;
              }
       case 11:{
              
/* Return the path block for this SW unit */
		if ((ibp->ulen) > 0){
		  /* fill in outputs here */
                  ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	          scratchucb=(UCB*)myswucb;
                  if ((long)scratchucb < 0){
                    ibp->moreprm[0] = myswucb->ucb$l_indrct; /* return path in use */
	            ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer */
	            ibp->moreprm[2] = &myswucb->ucb$a_hstdta[myswucb->ucb$l_indrct]; /* Rtn addr of path pointer */
                  }
		};

	      return(SS$_NORMAL);
              break;
              }
       case 12:{
/* Use subfunction 5 to get back information about bashes. In this case
   report on this SW device
*/
	      if ((long)ucb < 0){
/* UCB address looks potentially valid */
	        myswucb = ucb;
                ibp->ulen = (long)myswucb; /* Return the SW ucb unit we found or zero */
	        scratchucb=(UCB*)myswucb;
                if ((long)scratchucb < 0){
                  ibp->moreprm[0] = scratchucb->ucb$w_unit; /* return SW unit no. */
	          ibp->moreprm[1] = &myswucb->ucb$a_hstdta[0]; /* Rtn addr of path pointer
						      array also. */
                  ibp->moreprm[2] = myswucb->ucb$l_indrct; /* return which is current path */
                  ibp->moreprm[3] = &myswucb->ucb$l_pthord[0]; /* path sel order addr */
                  ibp->moreprm[4] = NUM_HOSTS; /* Tell how long tables are */
                  ibp->moreprm[5] = sizeof(PTHPTR); /* Tell how long path tbl entry is*/
                  ibp->moreprm[6] = (long)myswucb->ucb$l_vict; /* intercepted ucb */
                  ibp->moreprm[7] = myswucb->ucb$l_outstnd; /* Current I/O count */
                }
	      } else {
	/* error to fdt */
                return(SS$_BADPARAM);
	      }
/* complete this I/O */
		return(SS$_NORMAL);
		break;
              }
/* more options here */
/* Rather than use a different code, we will use this area to do manual
   switching also. The buffer format will in this case use the myswitch
   variable as two words also. The format will be

   Word 0: unit to switch to (0 to NUM_HOSTS)
   Word 1: idle-device flags: If 0, just return an error if the
				device was busy (active I/O count non zero)
				and return success only if the unit was idle
				and successfully switched.
			      If 1, flag startio intercept to save the
				next IRP address and leave the device busy
				until we later unbusy it. The unbusy call will
				be able to switch or not.
			      If 2, restart I/O with no switch using the saved
				IRP if any. If there is none, just return.

Note that the busying will not alter the outstanding I/O count ucb$l_outstnd
since it is purely inside the switching driver here. The caller can block or
unblock I/O but will need to wait for I/O to drain to switch paths; this is
easier in user mode so we'll leave it for that. We'll restart I/O by forking
and at fork level running REQCOM.
*/

       default:
         {return (SS$_NORMAL);
          break;}
     }

    return (SS$_NORMAL);
}
