	.TITLE	SDDRIVER - VAX/VMS VIRT DISK DRIVER w/shadowing
; Striping Disk driver
	.IDENT	'V1a'
; Copyright 1994 Glenn C. Everhart
; All rights reserved
; gce 6/22/1991 add some extra stuff to handle SMP full checking
; more carefully...
; gce - striping driver 1/1994.
;	.link 'sys$system:sys.stb'/sel
; clu$$str=0	;define for dev$m_clu variant
;
; Changed by making it fd1 type and adding nodename prefix characteristic
; so that hopefully it will now work with MSCP.
;
; Added save/restore of IRP$L_MEDIA for second I/O completion (by VDDRIVER)
; so that error path code that assumes this field unaltered will work. 4/14/89
;
;md$stat=1
;x$$$dt=1	;call xdelta
; Note: define symbol VMS$V5 to assemble in VMS V5.x or later. Default
; assembly without this definition produces a VMS V4.x driver.
; define v5$picky also for normal SMP operations
vms$v5=1
v5$picky=1
; Glenn C. Everhart, 2/2/1989
; Merged in some of Marty Sasaki's changes
;USAPADDR=0
;
; FACILITY:
; 
; 	VAX/VMS VIRTUAL DISK DRIVER USING CONTIGUOUS FILES.
; 
; AUTHOR:
; 
; G. EVERHART
;
; 
; ABSTRACT:
; 
; 	THIS MODULE CONTAINS THE TABLES AND ROUTINES NECESSARY TO
; 	PERFORM ALL DEVICE-DEPENDENT PROCESSING OF AN I/O REQUEST
;	FOR VMS VIRTUAL DISKS ON CONTIG FILES.
;
;This driver is intended to act as a striping driver, in which several
; disks are treated as a single unit with little bits of the storage
; alternately in each of the several units. The disk is described by
; size and number of storage units and also chunk size (size of each
; piece of the disk storage that is on each) and reserved blocks.
; The first reserved block of the first unit (if any) is used to hold
; a description of the stripeset which asnsd can check to ensure that
; the pieces have been given correctly for old stripesets. The reserved
; blocks exist at the start of each storage area, so resulting lbn =
; start lbn + reserved size.
;
; Several contiguous areas can be used if desired, though the assign
; program will allow only up to 2 at a time.
; Note an /append & /enable switch pair allow one to add more
; units. It is expected most stripesets will however have only 2
; components. Note it makes no sense to stripe virtual disks on the same
; physical one. Should be OK to hang vd units on top of sd ones though,
; Also note we can declare this thing a cluster device and let each
; machine do its own i/o splitting & management.
;--
	.PAGE
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

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

;	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$DYNDEF ;define dynamic data types
	$DCDEF				;DEFINE DEVICE CLASS
	$DDBDEF				;DEFINE DEVICE DATA BLOCK
	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS
	$DPTDEF				;DEFINE DRIVER PROLOGUE TABLE
	$EMBDEF				;DEFINE ERROR MESSAGE BUFFER
	$IDBDEF				;DEFINE INTERRUPT DATA BLOCK
	$IODEF				;DEFINE I/O FUNCTION CODES
	$DDTDEF				; DEFINE DISPATCH TBL...
	$ptedef
	$vadef
	$prvdef
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$irpedef
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$arbdef
	$pcbdef
	$jibdef
p1=0	; first qio param
p2=4
p3=8
p4=12
p5=16
p6=20	;6th qio param offsets

	.IF	DF,VMS$V5	;VMS V5 + LATER ONLY
	$SPLCODDEF
	$cpudef
	.ENDC
; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS
;.=UCB$W_BCR+2				;BEGIN DEFINITIONS AT END OF UCB
.=UCB$K_LCL_DISK_LENGTH	;v4 def end of ucb
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
$DEF	UCB$PPID	.BLKL	1	;PID OF ORIGINAL PROCESS FROM IRQ BLK
; host descriptor areas
cntof:
$DEF	UCB$HUCB	.BLKL	1	;ADDRESS OF HOST UCB
$DEF	UCB$HLBN	.BLKL	1	;LBN OF HOST FILE
$DEF	UCB$HFSZ	.BLKL	1	;SIZE OF HOST FILE, BLKS
ucbcntsz=.-cntof
$def	ucb$hucb2	.blkl	1	;host UCB of file 2
$def	ucb$hlbn2	.blkl	1	;LBN of file 2
$def	ucb$hfsz2	.blkl	1
; One label block has 6 longs for fixed info plus 7 longs per
; container. This is enough for 17 containers. I dislike pushing
; things so allow up to 16 here.
			.blkl	30	; Space for 10 more info sets
			.blkl	12	;space for up to 4 more sets
			.blkl	3	;safety
; NOTE: It is important to ENSURE that we never clobber IRP$L_PID twice!
; therefore, adopt convention that UCB$PPID is cleared whenever we put
; back the old PID value in the IRP. Only clobber the PID where
; UCB$PPID is zero!!!
$DEF	UCB$stats	.BLKL	1	;STATUS CODE SAVE AREA
$DEF	UCB$OBCT	.BLKL	1	;STORE FOR IRP$L_OBCNT too
$def	ucb$totfsz	.blkl	2	;Total size of virt dsk
$def	ucb$lmedia	.blkl	1	;storage for IRP$L_MEDIA
$def	ucb$owind	.blkl	1	; store irp$l_wind...
$def	ucb$osegv	.blkl	1	; and irp$l_segvbn
$def	ucb$l_SD_host_descr	.blkl	2	; char string descr
;
$def	ucb$vdcontfil	.blkb	148
;
; striping extra fields
$def	ucb$grnsiz	.blkl	1	;size of a stripe chunk in blocks
$def	ucb$irpcnt	.blkl	1	;count of IRPs for this i/o
$def	ucb$ncont	.blkl	1	;number container files
$def	ucb$bcntwk	.blkl	1	;work storage for byte count used
$def	ucb$sublbn	.blkl	1	;lbn of current sub-irp
$def	ucb$grnbas	.blkl	1	;number of reserved blocks this seg
$def	ucb$subsva	.blkl	1	;system virt addr for current IRP
;
$def	ucb$l_rclok	.blkl	1	;Flag that keeps us from finishing I/O till
				;all IRPs are sent.
$def	ucb$q_SD_svaptetmp .blkl 2
$def	ucb$l_SD_flag	.blkl	1	;sanity flag to ensure we have right driver
; next 2 left over from vqdriver
$def	ucb$shmd	.blkl	1	;shadow mode. 0=use file 1 only
$def	ucb$rwlk	.blkl	1	; read/write interlock. Initialize to 1
$def	ucb$rwlbn	.blkl	1	; LBN of first block in special read/logical
$def	ucb$rwsz	.blkl	1	; size of special transfer
$def	ucb$llbn1	.blkl	1	; last LBN of file 1
$def	ucb$llbn2	.blkl	1	; last LBN of file 2
$def	ucb$ucbos	.blkl	1	; offset to ucb/lbn/fsz in ucb to use at fin-io
$def	ucb$rwdir	.blkl	1	;read (0) or write (1) I/O
$def	ucb$ercd1	.blkl	1	; store for error code of 1st write of 2
;
$DEF	UCB$K_SD_LEN	.BLKW	1	;LENGTH OF UCB
;UCB$K_SD_LEN=.				;LENGTH OF UCB
	$DEFEND	UCB			;END OF UCB DEFINITONS
 
	.SBTTL	STANDARD TABLES

; 
; DRIVER PROLOGUE TABLE
; 
; 	THE DPT DESCRIBES DRIVER PARAMETERS AND I/O DATABASE FIELDS
; 	THAT ARE TO BE INITIALIZED DURING DRIVER LOADING AND RELOADING
; 

	.if	df,clu$$str
	.globl	sdalcls	;alloc class, set with link
	.endc
	.PSECT	$$$105_PROLOGUE
SD_UNITS=8
; vms v6 needs xpamod flag on vax. SPTE fiddling here should be
; insensitive to xpa vs. non-xpa vaxen.
.IIF NDF,DPT$M_XPAMOD,DPT$M_XPAMOD=0
VE$DPT::
	DPTAB	-			;DPT CREATION MACRO
		END=SD_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		FLAGS=<DPT$M_SMPMOD!DPT$M_SVP!DPT$M_XPAMOD>,-	;smp ok
		DEFUNITS=2,-		;UNITS 0 THRU 1
		UCBSIZE=UCB$K_SD_LEN,-	;LENGTH OF UCB
		MAXUNITS=SD_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=SDDRIVER		;DRIVER NAME

	DPT_STORE INIT			;START CONTROL BLOCK INIT VALUES
	DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\>  ;DEFAULT ACP NAME
	DPT_STORE DDB,DDB$L_ACPD+3,B,DDB$K_PACK	;ACP CLASS
	.if	df,clu$$str
	DPT_STORE DDB,DDB$L_ALLOCLS,L,SDALCLS	;allocation class
	.endc
	.IF	NDF,VMS$V5
	DPT_STORE UCB,UCB$B_FIPL,B,8	;FORK IPL (VMS V4.X)
	.IFF	;DEFINE FOR VMS V5.X & LATER
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8  ;FORK IPL (VMS V5.X + LATER)
	.ENDC
; NOTE THESE CHARACTERISTICS HAVE TO LOOK LIKE THE "REAL" DISK.
	DPT_STORE UCB,UCB$L_DEVCHAR,L,-	;DEVICE CHARACTERISTICS
		<DEV$M_FOD-		; FILES ORIENTED
		!DEV$M_DIR-		; DIRECTORY STRUCTURED
		!DEV$M_AVL-		; AVAILABLE
		!DEV$M_SHR-		; SHAREABLE
		!DEV$M_IDV-		; INPUT DEVICE
		!DEV$M_ODV-		; OUTPUT DEVICE
		!DEV$M_RND>		; RANDOM ACCESS
	.if	ndf,clu$$str
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
		<DEV$M_NNM>		; Prefix name with "node$" (like rp06)
	.iff
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
		<DEV$M_NNM-		; Prefix name with "node$" (like rp06)
		!DEV$M_CLU>		; Declare this a cluster available dev
	.endc
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_DISK  ;DEVICE CLASS
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,512  ;DEFAULT BUFFER SIZE
; FOLLOWING DEFINES OUR DEVICE "PHYSICAL LAYOUT". It's faked here and
; this structure (64 sectors/trk, 1 trk/cyl, nn cylinders) forces
; VE: units to be in multiples of 64 blocks. It can be modified as
; appropriate. However, recall that one has 1 byte for sectors/trk
; and 16 bits for cylinder number and 1 byte for tracks/cylinder.
; The current structure allows vd: units as large as 65535*64 blocks
; (about 4 million blocks, or 2 gigabytes), which is probably big enough
; for most purposes. The actual size is set up in ASNVD which finds the
; number of cylinders to "fit" in the container file. For emulating other
; ODS-2 volumes, the appropriate physical structure should be emulated also.
; NO logic in this driver depends on this stuff. It just has to be there
; to keep INIT and friends happy.
;	(In fact the current ASNVD image for VD: does this. We however
; have to preset SOMETHING.)
	DPT_STORE UCB,UCB$L_SD_FLAG,L,<^A\GCYS\>	;FLAG SDdriver
	DPT_STORE UCB,UCB$B_TRACKS,B,1	; 1 TRK/CYL
	DPT_STORE UCB,UCB$B_SECTORS,B,64  ;NUMBER OF SECTORS PER TRACK
	DPT_STORE UCB,UCB$W_CYLINDERS,W,16  ;NUMBER OF CYLINDERS
; FAKE GEOMETRY TO MAKE TRANSLATION EASIER. HAVE PRIV'D IMAGE LATER
; RESET THE UCB$W_CYLINDERS TO WHATEVER'S DESIRED. JUST MAKE SURE IT'S
; A MULTIPLE OF 64 BLOCKS IN SIZE, WHICH OUGHT TO BE GOOD ENOUGH.
	DPT_STORE UCB,UCB$B_DIPL,B,21	;DEVICE IPL
	DPT_STORE UCB,UCB$B_ERTMAX,B,10	;MAX ERROR RETRY COUNT
	DPT_STORE UCB,UCB$W_DEVSTS,W,-	;INHIBIT LOG TO PHYS CONVERSION IN FDT
		<UCB$M_NOCNVRT>		;...
;
; don't mess with LBN; leave alone so it's easier to hack on...
;
	DPT_STORE REINIT		;START CONTROL BLOCK RE-INIT VALUES
;	DPT_STORE CRB,CRB$L_INTD+VEC$L_ISR,D,SD_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
	DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,-  ;CONTROLLER INIT ADDRESS
		      D,SD_ctrl_INIT		  ;...
	DPT_STORE CRB,CRB$L_INTD+VEC$L_UNITINIT,- ;UNIT INIT ADDRESS
		      D,SD_unit_INIT		  ;...
	DPT_STORE DDB,DDB$L_DDT,D,SD$DDT	  ;DDT ADDRESS

	DPT_STORE END			;END OF INITIALIZATION TABLE

; 
; DRIVER DISPATCH TABLE
; 
; 	THE DDT LISTS ENTRY POINTS FOR DRIVER SUBROUTINES WHICH ARE
; 	CALLED BY THE OPERATING SYSTEM.
; 
;SD$DDT:
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=SD,-		;NAME OF DEVICE
		START=SD_STARTIO,-	;START I/O ROUTINE
		FUNCTB=SD_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
		REGDMP=0,-	;REGISTER DUMP ROUTINE
		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
; 
; FUNCTION DECISION TABLE
; 
; 	THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; 	CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; 	PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
; 

SD_FUNCTABLE:
	FUNCTAB	,-			;LIST LEGAL FUNCTIONS
		<NOP,-			; NO-OP
		FORMAT,-		; We use format to point to file
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		READLBLK,-		; READ LOGICAL BLOCK
		WRITELBLK,-		; WRITE LOGICAL BLOCK
;		READPBLK,-		; READ PHYSICAL BLOCK 
;		WRITEPBLK,-		; WRITE PHYSICAL BLOCK
		READVBLK,-		; READ VIRTUAL BLOCK
		WRITEVBLK,-		; WRITE VIRTUAL BLOCK
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		CRESHAD,-		; CREATE SHADOW SET VIRTUAL DISK
		REMSHAD-		; REMOVE SHADOW SET MEMBER
		MOUNT>			; MOUNT VOLUME
; no-op phys I/O for a test here...
	FUNCTAB	,-			;BUFFERED FUNCTIONS
		<NOP,-
		FORMAT,-		; FORMAT
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
; NOTE SEPARATE CALL FOR PHYSICAL I/O SO WE CAN JUST CONVERT TO LOGICAL AND
; DO OUR THING... CONVERT TO A LOGICAL QIO THERE FOR "REAL" DRIVER ALSO
; SO IT CAN DO CONVERSION TO ITS IDEA OF PHYSICAL IF IT WISHES...
;
; LEAVE NORMAL ACP CALLS IN SO FILE STRUCTURED STUFF ON OUR VE: UNIT
; WILL WORK OK.
;
	functab	crrmshd,-
		<CRESHAD,REMSHAD>
	functab shdwck,-	;check write to shadow member
                <WRITELBLK,-	; Write LOGICAL Block
                WRITEPBLK,-                     ; Write Physical Block
                WRITEVBLK>                      ; Write VIRTUAL Block
	FUNCTAB	+ACP$READBLK,-		;READ FUNCTIONS
		<READLBLK,-		; READ LOGICAL BLOCK
		READPBLK,-
		READVBLK-		; READ VIRTUAL BLOCK
		>
	FUNCTAB	+ACP$WRITEBLK,-		;WRITE FUNCTIONS
		<WRITELBLK,-		; WRITE LOGICAL BLOCK
		WRITEPBLK,-
		WRITEVBLK-		; WRITE VIRTUAL BLOCK
		>
	FUNCTAB	+ACP$ACCESS,-		;ACCESS FUNCTIONS
		<ACCESS,-		; ACCEESS FILE / FIND DIRECTORY ENTRY
		CREATE-			; CREATE FILE AND/OR DIRECTORY ENTRY
		>
	FUNCTAB	+ACP$DEACCESS,-		;DEACCESS FUNCTION
		<DEACCESS-		; DEACCESS FILE
		>
	FUNCTAB	+ACP$MODIFY,-		;MODIFY FUNCTIONS
		<ACPCONTROL,-		; ACP CONTROL FUNCTION
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY-			; MODIFY FILE ATTRIBUTES
		>
	FUNCTAB	+ACP$MOUNT,-		;MOUNT FUNCTION
		<MOUNT>			; MOUNT VOLUME
        FUNCTAB +EXE$LCLDSKVALID,-      ;LOCAL DISK VALID FUNCTIONS
                <UNLOAD,-               ;UNLOAD VOLUME
                 AVAILABLE,-            ;UNIT AVAILABLE
                 PACKACK>               ;PACK ACKNOWLEDGE
	FUNCTAB	+EXE$ZEROPARM,-		;ZERO PARAMETER FUNCTIONS
		<UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE>		; AVAILABLE
	FUNCTAB	+EXE$ONEPARM,-		;ONE PARAMETER FUNCTION
		<FORMAT-		; FORMAT
		>
	FUNCTAB	+EXE$SENSEMODE,-	;SENSE FUNCTIONS
		<SENSECHAR,-		; SENSE CHARACTERISTICS
		SENSEMODE-		; SENSE MODE
		>
	FUNCTAB	+EXE$SETCHAR,-		;SET FUNCTIONS
		<SETCHAR,-		; SET CHARACTERISTICS
		SETMODE-		; SET MODE
		>
	.PAGE
; BLOCK OF UCB ADDRESSES
SD_UCBTBL::
	.BLKL	SD_UNITS
	.LONG 0,0	;SAFETY
	.PSECT	$$$115_DRIVER
	.SBTTL FDT Routines
; shadow write check. Only allow write to shadowset members if privd.
;
shdwck:
	bbs	#dev$v_shd,ucb$l_devchar2(r5),10$	;shadow set member?
	rsb				;if normal, return NOW.
10$:	movl	IRP$L_ARB(R3),R0	; get ARB blk addr
	beql	99$			; (lose if not there)
	ASSUME  PRV$V_SYSPRV LT 32
	bbc	#PRV$V_SYSPRV,ARB$Q_PRIV(R0),99$; No SYSPRV, illegal
	rsb				;ok, return
99$:	MOVZBL  #SS$_ILLIOFUNC,R0
	jmp	g^exe$finishioc		;error return...
;
; ; Dummy create/remove shadow set member functions.
; Lets VMS do all the actual work. Just does the dispatching here.
;
crrmshd:
	MOVL    G^EXE$GL_HBS_PTR,R0	;get host based shadowing pointer
	bgeq	10$			;if not filled in, forget it.
	jmp	(r0)
10$:	MOVZBL  #SS$_ILLIOFUNC,R0	;illegal if no dispatcher
	jmp	G^EXE$FINISHIOC		;so indicate this to caller
;
	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; SD_ctrl_INIT - CONTROLLER INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; noop
; INPUTS:
; R4 - CSR ADDRESS
; R5 - IDB ADDRESS
; R6 - DDB ADDRESS
; R8 - CRB ADDRESS
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 	THE DRIVER CALLS THIS ROUTINE TO INIT AFTER AN NXM ERROR.
;--
SD_ctrl_INIT:				;vd CONTROLLER INITIALIZATION
	CLRL	CRB$L_AUXSTRUC(R8)	; SAY NO AUX MEM
	RSB				;RETURN
	.PAGE
	.SBTTL	INTERNAL CONTROLLER RE-INITIALIZATION
;
; INPUTS:
;	R4 => controller CSR (dummy)
;	R5 => UCB
;
ctrl_REINIT:
	RSB		; RETURN TO CALLER
	.PAGE
	.SBTTL	UNIT INITIALIZATION ROUTINE
; MEDIA		- MSCP media identifier to VMS device type conversion
;
; Functional description:
;
;	This macro produces one entry in the MSCP media identifier to VMS 
;	device type conversion table.
;
; Parameters:
;
;	dd	the two character prefered device controller name ( the DD 
;		part of DDCn )
;	devnam	the hardware device name ( e.g. RA81 )
;	dtname	if DT$_'devnam' is not a legal VMS device type, this parameter 
;		gives the correct VMS device type for the device ( should be 
;		used only when DT$_'devnam' is not correct )
;-

	.MACRO	MEDIA DD, DEVNAM, DTNAME

$$BEGIN$$=-1
$$MEDIA$$=0
$$S$$=27
	.IRPC	$$L$$,<DD>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDR
	.IRPC	$$L$$,<DEVNAM>
	.IF	GE <$$S$$ - 7>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.IF_FALSE
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = <17-$$S$$>/5
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDC
	.ENDR
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = 3
	$$N$$ = %EXTRACT( $$BEGIN$$, 3, DEVNAM )
	$$MEDIA$$ = $$MEDIA$$ + $$N$$
;	.LONG	$$MEDIA$$
	.ENDM	MEDIA
;++
; 
; SD_unit_INIT - UNIT INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE SETS THE VD: ONLINE.
; 
; 	THE OPERATING SYSTEM CALLS THIS ROUTINE:
; 		- AT SYSTEM STARTUP
; 		- DURING DRIVER LOADING
; 		- DURING RECOVERY FROM POWER FAILURE
; 
; INPUTS:
; 
; 	R4	- CSR ADDRESS (CONTROLLER STATUS REGISTER)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
;	R8	- CRB ADDRESS
; 
; OUTPUTS:
; 
; 	THE UNIT IS SET ONLINE.
; 	ALL GENERAL REGISTERS (R0-R15) ARE PRESERVED.
; 
;--

	MEDIA SD,RP06	;define our media-id
SD_unit_INIT:				;vd UNIT INITIALIZATION
; Don't set unit online here. Priv'd task that assigns VD unit
; to a file does this to ensure only assigned VDn: get used.
;	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)  ;SET UCB STATUS ONLINE
;limit size of VD: data buffers
SD_bufsiz=8192
; (asnSD will set this to same as host...)
;	movl	#SD_bufsiz,ucb$l_maxbcnt(r5)	;limit transfers to 8k
	clrl	ucb$l_rclok(r5)		;initially clear io done lock
	MOVB	#DC$_DISK,UCB$B_DEVCLASS(R5) ;SET DISK DEVICE CLASS
; NOTE: we may want to set this as something other than an RX class
; disk if MSCP is to use it. MSCP explicitly will NOT serve an
; RX type device. For now leave it in, but others can alter.
; (There's no GOOD reason to disable MSCP, but care!!!)
	movl	#$$MEDIA$$,ucb$l_media_id(r5)	; set media id as SD
; (note the id might be wrong but is attempt to get it.) (used only for
; MSCP serving.)
	MOVB	#DT$_FD1,UCB$B_DEVTYPE(R5)  ;Make it foreign disk type 1
; (dt$_rp06 works but may confuse analyze/disk)
;;; NOTE: changed from fd1 type so MSCP will know it's a local disk and
;;; attempt no weird jiggery-pokery with the VD: device.
; MSCP may still refuse to do a foreign drive too; jiggery-pokery later
; to test if there's occasion to do so.
	RSB				;RETURN 
	.PAGE
	.SBTTL	FDT ROUTINES 
;++
; 
; SD_ALIGN - FDT ROUTINE TO TEST XFER BYTE COUNT
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE IS CALLED FROM THE FUNCTION DECISION TABLE DISPATCHER
; 	TO CHECK THE BYTE COUNT PARAMETER SPECIFIED BY THE USER PROCESS
; 	FOR AN EVEN NUMBER OF BYTES (WORD BOUNDARY).
; 
; INPUTS:
; 
; 	R3	- IRP ADDRESS (I/O REQUEST PACKET)
; 	R4	- PCB ADDRESS (PROCESS CONTROL BLOCK)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	R6	- CCB ADDRESS (CHANNEL CONTROL BLOCK)
; 	R7	- BIT NUMBER OF THE I/O FUNCTION CODE
; 	R8	- ADDRESS OF FDT TABLE ENTRY FOR THIS ROUTINE
; 	4(AP)	- ADDRESS OF FIRST FUNCTION DEPENDENT QIO PARAMETER
; 
; OUTPUTS:
; 
; 	IF THE QIO BYTE COUNT PARAMETER IS ODD, THE I/O OPERATION IS
; 	TERMINATED WITH AN ERROR. IF IT IS EVEN, CONTROL IS RETURNED
; 	TO THE FDT DISPATCHER.
; 
;--
SD_ALIGN:				;CHECK BYTE COUNT AT P1(AP)
	RSB				;EVEN - RETURN TO CALLER
	.PAGE
	.SBTTL	START I/O ROUTINE
;++
; 
; SD_STARTIO - START I/O ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS FORK PROCESS IS ENTERED FROM THE EXECUTIVE AFTER AN I/O REQUEST
; 	PACKET HAS BEEN DEQUEUED.
; 
; INPUTS:
; 
; 	R3		- IRP ADDRESS (I/O REQUEST PACKET)
; 	R5		- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	IRP$L_MEDIA	- PARAMETER LONGWORD (LOGICAL BLOCK NUMBER)
; 
; OUTPUTS:
; 
; 	R0	- FIRST I/O STATUS LONGWORD: STATUS CODE & BYTES XFERED
; 	R1	- SECOND I/O STATUS LONGWORD: 0 FOR DISKS
; 
; 	THE I/O FUNCTION IS EXECUTED.
; 
; 	ALL REGISTERS EXCEPT R0-R4 ARE PRESERVED.
; 
;--
	.if df,x$$$dt
rwflg:	.long	0
	.endc
; In fact, using XDelta, I have *never* seen requeue called.
REQUEUE:
	.if	df,x$$$dt
	jsb	g^ini$brk
	.endc
	JMP	G^EXE$INSIOQ		; REQUEUE packet to ourselves
; return to our caller direct from insioq.
; (note this also sets busy, so it will NOT loop forever.)
SD_STARTIO:				;START I/O OPERATION
; 
; 	PREPROCESS UCB FIELDS
; 
;	ASSUME	RY_EXTENDED_STATUS_LENGTH  EQ  8
;	CLRQ	UCB$Q_SD_EXTENDED_STATUS(R5)	; Zero READ ERROR REGISTER area.
; 
; 	BRANCH TO FUNCTION EXECUTION
	bbs	#ucb$v_online,-	; if online set software valid
		ucb$w_sts(r5),210$
216$:	movzwl	#ss$_volinv,r0	; else set volume invalid
	brw	resetxfr	; reset byte count & exit
210$:
	tstl	ucb$hucb(r5)	; do we have any host device?
	beql	216$		; if eql no, flag invalid volume.
			; THIS IS SAFETY FROM CONFIGURING FROM OUTSIDE
	tstl	ucb$hucb2(r5)	;need at least 2 containers
	beql	216$		;or we fail
; BEFORE GOING ON, WE WANT TO ENSURE THE UCB IS FREE.
	TSTL	UCB$PPID(R5)		; MAKE SURE we haven't got
					; a packet in process
	BNEQ	REQUEUE			; IF a packet's in process, requeue
					; back to this driver; do NOT process
					; immediately!
; Note...never seems to get to requeue (xdelta would catch it!)
; (that's a good sign; should never get there.)
	bisw	#ucb$m_online,ucb$w_sts(r5)	; set online
	bisw	#ucb$m_valid,ucb$w_sts(r5)	;set valid
; set ourselves as owners of channel for VD:
	movl	ucb$l_crb(r5),r0
	movl	crb$l_intd+vec$l_idb(r0),r0	;get idb address
;	cmpl	r5,idb$l_owner(r0)		;are we owners?
;	beql	214$			; if eql yes, all's well
;	REQPCHAN	; gain access to controller in "standard" way
214$:
;
10$:;	BBS	#IRP$V_PHYSIO,-		;IF SET - PHYSICAL I/O FUNCTION
;		IRP$W_STS(R3),20$	;...
	BBS	#UCB$V_VALID,-		;IF SET - VOLUME SOFTWARE VALID
		UCB$W_STS(R5),20$	;...
	MOVZWL	#SS$_VOLINV,R0		;SET VOLUME INVALID STATUS
	BRW	RESETXFR		;RESET BYTE COUNT AND EXIT
20$:
; IF WE GET A SEGMENT TRANSFER HERE (LOGICAL I/O)
; IT MUST BE UPDATED FOR HOST AND SHIPPED OUT.
; OUR UCB HAS BLOCK NUMBER INFO...
; FIND OUT IF THIS IS LOGICAL OR PHYSICAL I/O FIRST. THEN IF IT IS BUGGER
; THE I/O PACKET USING UCB INFO AND SEND TO THE REAL DRIVER...
; ALSO ENSURE WE ARE UNBUSIED...
;
; Sanity check for assignments: hucb2 field zero implies shmd must be
; zero.
	tstl	ucb$hucb2(r5)		;second UCB there?
	bneq	21$			;if neq yes
	clrl	ucb$shmd(r5)		;if not, ensure in single-file mode
21$:
;
	EXTZV	#IRP$V_FCODE,#IRP$S_FCODE,IRP$W_FUNC(R3),R1	; GET FCN CODE
	case	r1,<-			; Dispatch to function handling routine
		unload,-		; Unload
		nop,-			; Seek
		NOP,-			; Recalibrate(unsupported)
		nop,-			; Drive clear
		NOP,-			; Release port(unsupported)
		NOP,-			; Offset heads(unsupported)
		NOP,-			; Return to center
		nop,-			; Pack acknowledge
		NOP,-			; Search(unsupported)
		NOP,-			; Write check(unsupported)
		WRITEDATA,-		; Write data
		READDATA,-		; Read data
		NOP,-			; Write header(unsupported)
		NOP,-			; Read header(unsupported)
		NOP,-			; Place holder
		NOP,-			; Place holder
		available,-		; Available (17)
		NOP,NOP,NOP,-		; 18-20
		NOP,NOP,NOP,NOP,nop,nop,nop,NOP,NOP,nop,- ;21-30
		NOP,NOP,NOP,NOP,nop,NOP,nop,nop,nop,NOP,- ;31-40
		NOP,NOP,NOP,NOP,NOP,NOP,NOP,NOP,NOP,nop,- ;41-50
		NOP,NOP,NOP,NOP,nop,NOP,NOP,NOP,NOP,NOP,- ;51-60
		nop,-					  ;61
		>,LIMIT=#1

nop:				;unimplemented function
	brw	fexl
rwcmnj:	brw	rwcmn
readdata:
	movl	#ucb$hucb,ucb$ucbos(r5)	;store address of file 1
	clrl	ucb$rwdir(r5)	;set read
	brw	rwcmn
writedata:
	movl	#ucb$hucb,ucb$ucbos(r5)	;store address of file 1
	movl	#1,ucb$rwdir(r5)	;set write
rwcmn:
; debug using sda to peek
; NOW VALIDATED I/O FCN... MODIFY AND SEND OFF
; check size of device OK first
	CMPL	IRP$L_MEDIA(R3),UCB$totfsz(R5)	;BE SURE LBN OK
	blequ	65$
	brw	Fatalerr	;dismiss I/o if not ok block number
65$:
; HAVE TO BE CAREFUL WHAT WE SHIP TO REAL DRIVERS
; Now that we know IRP$L_MEDIA is ok in IRP, save it for restore at
; I/O completion by VDDRIVER
; (This also facilitates remodification for second pass on writes)
	movl	irp$l_media(r3),ucb$lmedia(r5)
	; Prepare to enter another context.
; SEND PKT OFF TO REAL DRIVER...
; First get address stuff to bash PID with
	MOVL	IRP$L_PID(R3),UCB$PPID(R5)	; SAVE PROCESS ID IN VD: UCB
	movzwl	irp$w_sts(r3),ucb$stats(r5)	;save original fcn code
	movl	irp$l_obcnt(r3),ucb$obct(r5)	;store obcnt field
	movl	irp$l_svapte(r3),ucb$q_SD_svaptetmp(r5) ;save svapte
	movl	irp$l_bcnt(r3),ucb$q_SD_svaptetmp+4(r5) ;and orig bcnt
; (actually shouldn't need to worry about driver post processing...
;  we do all that here via hack to change irp$l_pid field....)
	movl	irp$l_wind(r3),ucb$owind(r5)	;store window ptr
	movl	irp$l_segvbn(r3),ucb$osegv(r5)	;store segment vbn also
	MOVZWL	UCB$W_UNIT(R5),-(SP)	; BUILD ADDRESS OF UCB STORE
	ASHL	#2,(SP),(SP)		; WITH OFFSET * 4

	MOVAB	SD_UCBTBL,-(SP)		; GET TBL BASE IN STACK
	ADDL2	(SP)+,(SP)		; NOW ADD BASE + OFFSET
	MOVL	R5,@(SP)+		; AND STORE UCB ADDRESS IN SD_UCBTBL
; (THIS ALLOWS US TO GET IT BACK...)
	MOVZWL	UCB$W_UNIT(R5),-(SP)	; BUILD ADDRESS OF ENTRY NOW
	MULL2	#SD_FXPL,(SP)		; MULTIPLY OFFSET BY SIZE OF ENTRY
	MOVAB	SD_FXS0,IRP$L_PID(R3)	;AND BASH PID IN IRP SO WE
				; GET BACK CONTROL AT SD_FIXSPLIT (VIA JSB)
				; WHEN HOST'S I/O IS DONE.
	ADDL2	(SP)+,IRP$L_PID(R3)	;SET TO ENTER IN CORRECT
					; UNIT'S ENTRY
; At this point we have set the original IRP so it can be
; cloned and modified, and the subordinate IRPs sent to the
; host device(s).
	clrl	ucb$irpcnt(r5)	;initially no subordinate IRPs
	movl	irp$l_media(r3),ucb$sublbn(r5)	;start LBN for irp
	movl	irp$l_svapte(r3),ucb$subsva(r5)	;save sys virt addr at start
	clrq	irp$l_media(r3)		;zero status return in orig. irp
	movzwl	irp$w_bcnt(r3),ucb$bcntwk(r5)	;save bytecount needed
; For alpha we'll need to adjust irp$w_boff as well as irp$l_svapte
; here and may want to impose a restriction that the granule size
; be some multiple of page size for simplicity. We assume both
; are 512 bytes here.
	pushr	#^m<r6,r7>
shadloop:
	pushr	#^m<r0,r1,r2,r3,r4,r5>
; first grab an IRP
	movl	#irp$c_length,r1	;length needed
	jsb	g^exe$alononpaged	;get an IRP if we can (ok at fork ipl)
	blbs	r0,45$
	brw	lose1			;if we fail, no queue
45$:
	movb	#dyn$c_irp,irp$b_type(r2)
	movw	r1,irp$w_size(r2)	;set block type & size
	incl	ucb$irpcnt(r5)		;count up irp use.
			;(synch'd by ipl & forklock...
			; not dependent on inst. atomicity.)
; r1=size of buff, r2=addr of buff
	movl	r2,r6		; save new irp addr
	movl	r1,r7		;and size
	popr	#^m<r0,r1,r2,r3,r4,r5>
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9>
; We now count down that there are extra IRPs yet to do. If it
; is still nonzero we prevent reqcom of original IRP yet.
	decl	ucb$l_rclok(r5)		;count down extra irps
	bgeq	3334$			;skip if nonnegative now
	clrl	ucb$l_rclok(r5)		;don't let rclok go negative!
3334$:
	movl	r3,r8		;our irp addr
	pushr	#^m<r0,r1,r2,r3,r4,r5>
	movc3	r7,(r8),(r6)		;copy the IRP
	popr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc!!!
; now adjust IRP fields for THIS IRP
	movl	ucb$sublbn(r5),r0		;lbn desired
	jsb	getslb			;get device & lbn to start
; NOTE getslb assumes EQUAL SIZED container files!!!
; minimize bytes...
; note we use ucb$bcntwk(r5) not irp$l_bcnt(r3) since the irp bytecount
; is invariant during our subordinate jiggery-pokery.
	cmpw	ucb$bcntwk(r5),r2	;less than one seg to do?
	bgtru	3330$	;if gtr we will be back later. Flag.
	movzwl	ucb$bcntwk(r5),r2	;r2 is now bytes we move here
	brb	418$
3330$:
; Here we know we'll need another IRP and must ensure we don't
; count down to 0 if postprocessing beats us to it. We use this
; setting to keep a count down to zero from meaning we reqcom
; the initial IRP before sending all the subordinate ones we
; need to send out.
	incl	ucb$l_rclok(r5)		;set the latch to lock out reqcom
418$:	movl	r2,irp$l_bcnt(r6)	;fill bytes to move in
	movl	r1,irp$l_media(r6)	;r1 is lbn on host
	movab	ucb$hucb(r5),r4		;addr of 1st context blk
; get context blk address in ucb
	mull2	#ucbcntsz,r0		;context blk number
	addl2	r0,r4			;r4 now is context area
	movl	(r4),irp$l_ucb(r6)	;set irp owner ucb correctly
;	assume	ucb$hlbn-ucb$hucb eq 4
	addl2	4(r4),irp$l_media(r6)	;adjust host lbn

;now set up irp$l_svapte
; We assume initially that irp$l_svapte pointed to a contig.
; range of system addresses. NOTE VAX SPECIFIC STUFF HERE!!!
	movl	ucb$subsva(r5),irp$l_svapte(r6)	;sys address
; How far shall we advance now for the next irp?
	ashl	#-9,r2,r1		;r1 is no. blks shifted
; (NOTE: ALPHA pagesize violates above...)
	addl2	r1,ucb$sublbn(r5)
	ashl	#2,r1,r4		;mult by 4 for pte addr
	addl2	r4,ucb$subsva(r5)		;now next time's svapte should be ok
	subl2	r2,ucb$bcntwk(r5)	;subt from data yet to move
	bgeq	3331$
	clrl	ucb$bcntwk(r5)	;clamp bytecount positive
3331$:
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9>
	pushr	#^m<r0,r1,r2,r3,r4,r5>
; GRAB HOST UCB addr. into regs
	MOVL	IRP$L_UCB(R6),R5		;NOW POINT AT HOST UCB OURSELVES
	movl	r6,r3				;point at new IRP
; At this point we look a lot like a normal driver. r5 and r3
; point at the ucb and irp.
	MOVL	IRP$L_MEDIA(R3),R0	;GET LBN TO CONVERT
; host driver expects its physical address
	JSB	G^IOC$CVTLOGPHY		; LET THE EXEC DO IT
	.if	ndf,vms$v5	;ok for v4 and non smp v5
	JSB	G^EXE$INSIOQ		; INSERT PACKET INTO HOST'S QUEUE
	.iff
; insioqc handles locks right in v5 (et. seq. ...)
	JSB	G^EXE$INSIOQC	; insert packet and keep forklock
	;this reliably solves SMP problems
	.endc
	popr	#^m<r0,r1,r2,r3,r4,r5>	;get regs back
	tstl	ucb$bcntwk(r5)		;got more to do?
	bleq	3337$
	brw	shadloop		;if more, fire off another sub irp
3337$:
	clrl	ucb$l_rclok(r5)		;done now...allow reqcom
	popr	#^m<r6,r7>
	rsb				;finally return
; WE Now have queued the work to the real driver. Since the
; I/O may have splits, just await done return and let the
; SD_fixsplit processing get done our cleanup. Because we need
; to await this, just return with VD: unit STILL BUSY to ensure
; that we don't get thru here until we're GOOD AND READY!
402$:
	BRW	FEXL			;Else, branch to execute function.
lose1:
	popr	#^m<r0,r1,r2,r3,r4,r5>
	popr	#^m<r6,r7>
	clrl	ucb$l_rclok(r5)		;let reqcom happen now
	tstl	ucb$irpcnt(r5)		;ensure no imm. exit if subirp exists
	beql	fatalerr		;if none, fatal err now
	rsb
;
;	UNLOAD and AVAILABLE Functions
;	Clear UCB$V_VALID in UCB$W_STS
;
UNLOAD:
AVAILABLE:
;	BICW	#UCB$M_VALID, -		;Clear sofware volume valid bit.
;		UCB$W_STS(R5)
;	BRB	NORMAL			;Then complete the operation.
; 
; 	OPERATON COMPLETION
; 
FEXL:	; dummy entry ... should never get here
NORMAL:					;SUCCESSFUL OPERATION COMPLETE
	MOVZWL	#SS$_NORMAL,R0		;ASSUME NORMAL COMPLETION STATUS
	BRB	FUNCXT			;FUNCTION EXIT

FATALERR:				;UNRECOVERABLE ERROR
	MOVZWL	#SS$_DRVERR,R0		;ASSUME DRIVE ERROR STATUS

RESETXFR:	; dummy entry ... should never really get here
	MOVL	UCB$L_IRP(R5),R3	;GET I/O PKT
	MNEGW	IRP$W_BCNT(R3),UCB$W_BCR(R5) ; RESET BYTECOUNT
;	BRW	FUNCXT
FUNCXT:					;FUNCTION EXIT
	CLRL	R1			;CLEAR 2ND LONGWORD OF IOSB
	REQCOM				; COMPLETE REQUEST
	.PAGE
; 
PWRFAIL:				;POWER FAILURE
	BICW	#UCB$M_POWER,UCB$W_STS(R5)  ;CLEAR POWER FAILURE BIT
	MOVL	UCB$L_IRP(R5),R3	;GET ADDRESS OF I/O PACKET
	MOVQ	IRP$L_SVAPTE(R3),-	;RESTORE TRANSFER PARAMETERS
		UCB$L_SVAPTE(R5)	;...
	BRW	SD_STARTIO		;START REQUEST OVER
SD_INT::
SD_UNSOLNT::
	POPR	#^M<R0,R1,R2,R3,R4,R5>
	REI	;DUMMY RETURN FROM ANY INTERRUPT
	;;
; FIX SPLITS...
; RETURN IRP TO OUR UCB ADDRESS
; THEN REQCOM
;
; TRICK IS TO GET OUR UCB ADDRESS BACK WHEN WE REGAIN CONTROL. DO SO VIA
; JIGGERY-POKERY WITH THE ADDRESS WE CALL. STORE UCB ADDRESSES IN A TABLE
; INTERNALLY AND USE THE CALL ADDRESS TO GET WHERE WE ARE BACK AGAIN.
;
; Note: On entry, r5 points at the IRP we're to handle. We bash this
; information and regenerate it, since irp$l_ucb has already been
; bashed and can't locate our UCB anyway. Therefore we let the return
; address give us the VE: UCB address implicitly via local save and
; restore; each VE: unit returns to a different entry point which preloads
; r5 with a different offset. Once the ucb is located, ucb$l_irp gets
; back the IRP address. This is possibly extra work; one can imagine
; that some IRP fields like SEGVBN could be used to hold the VE: ucb
; address temporarily, since they are saved/restored internally. This
; would allow some address arithmetic to be dispensed with. The current
; method is merely intended to work and NOT force us to let the host driver
; (and maybe other host software) look at bogus IRP fields, and also
; let us remain blissfully ignorant of how VMS handles IRPEs for purposes
; of this driver. (ecch...having to figure out a way to keep i/o post
; processing out of OUR IRPEs (for sure) while using them for temporary
; storage... what a thought!)
;
; NOTE FOLLOWING CODE ASSUMES SD_UNITS IS 2 OR MORE.
V_UNIT=0
V_UNM=1
SD_FXS0::
	MOVL	I^#V_UNIT,R4
	BRW	SD_FIXSPLIT	;GO HANDLE
SD_FXPL==.-SD_FXS0	;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
	.MACRO	XVEC LBLC
SD_FXS'LBLC:
	MOVL	I^#V_UNIT,R4
	BRW	SD_FIXSPLIT
	.ENDM
	.REPEAT	<SD_UNITS-1>
	XVEC	\V_UNM
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
V_UNM=V_UNM+1
	.ENDR
SD_FIXSPLIT:
; GET OLD PID..
; IN OUR UCB$PPID LONGWORD...
; NOTE!!! PROBABLY NEEDS MODS FOR VMS V5!!!
;some cleanup for host needed here. Note that r5 enters as IRP address.
; Use initial R5 to help reset host's system...
	.if	df,x$$$dt
;	jsb	g^ini$brk
	.endc
	movl	irp$l_ucb(r5),r3	;get host's UCB addr
	movl	r5,r2		;store entry IRP address for check later
	PUSHL	R4	;NEED TO WORK IN R5
	MOVAB	SD_UCBTBL,R5
	ADDL2	(SP)+,R5	;R5 NOW POINTS AT UCB ADDRESS
	MOVL	(R5),R5		;NOW HAVE OUR UCB ADDRESS IN R5
	decw	ucb$w_qlen(r3)	;cleanup host's q len as ioc$iopost would have
	bgeq	6$
	clrw	ucb$w_qlen(r3)	;force queue length zero
6$:
; notice stack is now clean too.
	movl	r5,r4
	FORK			;go fork on our UCB now (SD: ucb)
	movl	r4,r5
	MOVL	R2,R3	; POINT R3 AT IRP AGAIN. Note this is our "scratch" IRP
	movl	ucb$l_irp(r5),r2	;get original irp addr
; Accumulate bytecount & error status, then junk subordinate IRP
	movq	irp$l_media(r3),r0	;get this irp status
	bisw	r0,irp$l_media(r2)	;r2 = orig irp
	addl2	r1,irp$l_media+4(r2)	;add count in
	ashl	#-16,r0,r0		;shift r0 down
	addw2	r0,irp$l_media+2(r2)	;add in bytes here
; now deallocate the IRP and count down till end.
	movl	r3,r0		;deallocate the irp
	jsb	g^com$drvdealmem	;let it go...
; note we are again synch'd by forklock...
	tstl	ucb$l_rclok(r5)	;is reqcom inhibited by more subirps expected?
	bneq	511$
	decl	ucb$irpcnt(r5)		;just handled the last IRP?
	bleq	107$			;if so go complete orig. irp
511$:	rsb
107$:
	movl	r2,r3		;now point at the original IRP
300$:
	TSTL	UCB$PPID(R5)	; ENSURE PID IS NONZERO AS SAVED
	BEQL	15$		; SKIP BASH IF NOT
	MOVL	UCB$PPID(R5),IRP$L_PID(R3)	;RESTORE THE OLD PID
; since we may now have later parts of virtual, paging, or swapping I/O
; to do, restore saved byte counts and function codes.
	movl	ucb$osegv(r5),irp$l_segvbn(r3)	;restore segment vbn also
	brb	1501$
15$:
	.if	df,x$$$dt
	jsb g^ini$brk		;mousetrap the illegal condition!
	.endc
	clrl	irp$l_pid(r3)	;make sure we DON'T get back here anyway!
; this is actually an error condition and should NEVER occur...
;	movl	ucb$obct(r5),irp$l_obcnt(r3)	;restore orig byte cnt
1501$:
	CLRL	UCB$PPID(R5)	; ZERO SAVED PID FIELD FOR CLEANLINESS
	MOVL	R5,IRP$L_UCB(R3)	;RESTORE SD: AS UCB IN IRP TOO
; GRAB R0 AND R1 AS REQCOM IN HOST DRIVER LEFT THEM...
	MOVL	IRP$L_MEDIA(R3),R0	;GET BACK R0
	MOVL	IRP$L_MEDIA+4(R3),R1	;AND R1
; R0, R1 ARE AS HOST DRIVER LEFT THEM. R5 POINTS TO CORRECT UCB.
;  ===> GO FOR IT !!!
;
; Now restore the original IRP$L_MEDIA field of the IRP in case error
; paths in IOC$REQCOM ever need it. Some very low XQP cache situations
; may occasionally need this, though in reasonable sysgen configs it
; should never be needed. This is the one area that got bashed during
; the earlier I/O completion processing in the host driver.
;
	MOVL	UCB$LMEDIA(R5),IRP$L_MEDIA(R3)
;
; notice that for virtual I/O, the IRP's IRP$L_SEGVBN longword still
; has the starting VIRTUAL block number of the I/O request in the context
; of the virtual disk. This must be present as any second and later parts
; of the I/O request modify that field to compute where to go for the
; next I/O. Due to getting back here, the host driver need never know
; about this; it is basically doing ONLY physical and logical I/O where
; this sort of completion jiggery-pokery does not occur.
;		- GCE
; Now go REALLY complete the I/O (possibly causing more I/O and certainly
; ensuring the VE: I/O queue is emptied and VE: unbusied after all is done.)
	JSB	@#IOC$REQCOM	; GO COMPLETE THE I/O REQUEST IN VE: CONTEXT
; (OR DO I/O SPLIT NEXT PART IN VE: CONTEXT!)
; ALSO, RETURN **HERE**, SO WE CAN WRAP UP ALL ELSE.
;	.IF	NDF,VMS$V5
;	ENBINT		; RESTORE IPL TO IPL$_IOPOST
;	.IFF
;	FORKUNLOCK	NEWIPL=(SP)+
;	.ENDC
; rsb exits the fork level.
; IPL4 level exited at fork above, with stack intact at that point.
; iopost saves/restores regs, so r5 bash is ok.
	RSB	; GET BACK TO HOST SOMETIME
;
; Partitioning function (from s93 sigtapes)
; Acts to find out what container file and what block within that
; file should be used for this piece of I/O and also how many
; bytes may be written and fit within this granule.
;getslb
; input: r5=ucb, r3=irp, r0=lbn to start
; output: r0=container number (0,1...ucb$ncont), r1=start lbn,
; r2 = number bytes that can be transferred in this chunk.
getslb:
	pushr	#^m<r3,r4,r5,r6,r7,r8,r9>
	clrl	r1
	ediv	ucb$grnsiz(r5),r0,r8,r9	;r8 = lbn/segsiz, r9=rem
	pushl	r9
	clrl	r9	;divd is 64 bits
	ediv	ucb$ncont(r5),r8,r7,r6	;r7 = seg on disk, r6=dsk index
	popl	r9
	mull3	r7,ucb$grnsiz(r5),r1	;r1 = start lbn except offset
	addl2	ucb$grnbas(r5),r1	;r1 = start lbn
; bytes allowed = 512*(segsz-rem)
	movl	ucb$grnsiz(r5),r2	;form max bytes
	subl2	r9,r2			;subtract offset
	bgtr	1111$			;must be positive
	movl	#1,r2			;clamp legal if not legal at first!
1111$:
	ashl	#9,r2,r2		;shift to multiply by 512
; Get container file number now
	movl	r6,r0			;container offset
	addl2	r9,r1			;update start LBN to correct one.
	popr	#^m<r3,r4,r5,r6,r7,r8,r9>
	rsb
SD_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.END
