Folks -
  Here's a virtual disk driver for VMS. It makes a contiguous file
look like a volume; I've tested it on VMS V4.7 with no problems and
would like someone with V5 to try it if you will please. I've fixed
the serious problems that existed with the one I wrote a couple years
ago and this one is conceptually clean and tests error free. Note
you must define vms$v5 to assemble for V5.
  Thanks to all who reply.
Glenn Everhart
Everhart%Arisia.decnet@ge-crd.arpa

-h- VDDRIVER.DOC
VMS Virtual Disk

Have you ever wanted to break up those 3 GB single volume disks?
Ever wanted to be able to put a few tens of thousands of blocks
into a temporary ODS1 structure? Wanted lots of volume ACLs but not
had volumes to use?
  This is the answer. VD: for VMS!
  VD: (vddriver) allows you to take a contiguous file on any VMS
volume and treat it as a complete disk volume of its own. You assign
a file to a VD: unit or deassign it using a privileged image, and
the driver will make the file look like a volume. Any normal VMS
commands will act as though VD: were just another attached disk
on your system.
  An earlier driver I did had serious design problems with multiblock
QIOs which this one fixes (sheepish grin) and this driver has been
tried on VMS V4.7 with no problems whatever. VMS V5 code exists and
is conditionalized by the VMS$V5 symbol. It has not yet been tested.
  This driver uses the same mechanism for controlling its host driver
that the MSCP server does, and should be able to work on any VMS
device, bar none, though I have not tried it with the MSCP server; some
"device ID" strings may not be quite right as I have them. They
represent my best guess as to a servable disk however.
  I have executed images from VD:, written to it, read from it, and
compared files on the host disk with virgin copies to make sure nothing
was corrupted. All works as it should. Tests were done on a VAX11/750
running VMS 4.7. 

	Generation:

	Use the command file VDMAKE.COM to assemble and build the
VD: driver. Comments therein describe how to use SYSGEN to actually
load the driver. I assume you can copy VDDRIVER.EXE into sys$system.
	You must set command asnvd.cld before using ASNVD.EXE, and
you MUSH have the CMKRNL privilege to use it, or install ASNVD
with that privilege. This is needed to access the host and the VD:
UCBs. You need to be able to read file attributes of the container
file also.
	ADVD/ASSIGN VDn: device:[directory]file.type
		assigns the file to device VDn:
	ADVD/DEASSIGN VDn:
		deassigns the file from device VDn:

Note that the file is NOT locked. Nor does ADVD know how to create
the file. Another limitation of the system as presented here is that
ASNVD has a hardwired physical disk structure of 64 sectors/track,
1 track/cylinder, and as many cylinders as needed to cover the file.
This means that the container file will be used only out to the
last integral multiple of 64 blocks. Another structure can be generated,
as VDDRIVER does not care, but the ASNVD image lacks such niceties. To
produce images of physical disks, you may want to make modified versions
of ASNVD to reflect their physical structure. The structure chosen was
designed to allow fairly large virtual disks with only one filestructure.
	You need to edit VDDRIVERV3.MAR if you want to increase the
number of units beyond the default maximum of 8. The program works by
using a hook in VMS I/O postprocessing that calls code in VDDRIVER
when doing postprocessing. The hook is that if IRP$L_PID in an IRP
is negative, the iopost interrupt does a JSB to that address rather
than any postprocessing. VDDRIVER saves the original IRP$L_PID,
stuffs its own postprocessor address there (one address per unit, hence
a table of entries), and queues the packet to the host. On host I/O
completion, the packet comes back to VD: which restores IRP$L_PID
and performs normal I/O completion in the context of the VD: driver.
This means that all completion processing, including multipart virtual
I/O and page/swap I/O is done correctly regardless of how many
parts are needed. Since the MSCP server uses this hook also, it is
likely to remain in VMS indefinitely. It offers interesting possibilities
for building VD: versions that do shadowing or encryption, since
control is returned after the I/O also. The method is clean and has
no problems operating. Since the I/O packet is operated on directly,
it does NOT impose QIO overhead twice, and is fairly efficient.

	I would like to ask anyone willing to test VD: in VMS V5.x
systems to please try it out and let me know what if anything is
needed. I have done my best to insert VMS V5.x code here, but
have not the facilities for a complete test locally.
	Thank you
Glenn Everhart
Everhart%Arisia.decnet@ge-crd.arpa	Arpanet
Everhart@Arisia.ge.com			uucp
voice: 215 354 7610 office    215 358 3866 home


-h- ASNVD.CLD
! Set up VD: units with ADVD, assuming VDDRIVER is already
! loaded somewhere.
DEFINE VERB ADVD
   IMAGE VD$DST:ASNVD.EXE
     parameter p1,prompt="VDn:",value(required,type=$device),label=UNIT
     parameter p2,label=FNAM,prompt="file",value(type=$infile)
     qualifier ASSIGN
     qualifier DEASSIGN
; Uses ASNVD image. Note that vddriverv3a.mar contains code to
; handle the assign function in the FORMAT code. For now we don't
; use this, but just let the priv'd image do it.
; Needs CMKRNL at least. Maybe some others.
-h- ASNVD.MAR
	.TITLE	ADVD - VAX/VMS VIRT DISK DRIVER ASSIGN/DEASSIGN
	.IDENT	'V01-000'
;
; FACILITY:
; 
; ASSIGN/DEASSIGN VIRTUAL DISK TASK THAT WORKS WITH VDDRIVER
;  ESTABLISHES CONNECTION (OR BREAKS IT) BETWEEN A LUN OF
;  VD: AND A CONTIGUOUS FILE.
;
; N.B. UNTESTED!!! USE AT YOUR OWN RISK.
;
; Command format:
; VDCTL/switches VDn: file
;  where a .CLD file is expected so that this can all be parsed by
;  the CLI. The legal switches will just be /ASSIGN or /DEASSIGN
;  to specify which operation is required. In the /DEASSIGN
;  case no filename is needed of course; the virtual disk must
;  however be dismounted before this utility will allow it to
;  be deassigned. The ucb$w_refc field must be zero before the
;  deassign is thus permitted.
;  We must set the UCB$L_MAXBLOCK longword to the size of the file
;  here also. This requires reading the statistics on the file to
;  discover the size if contiguous; the statistics block will
;  show zero if noncontig...
;    We also set the number of "cylinders" for a fakeout structure.
; I don't really think that physical I/O needs to even be legal, but
; for the sake of argument & trying to get SOMETHING that works,
; I've left it in for now. The driver world be simpler just
; removing readpblk and writepblk from the legal-function FDT masks
; but there's always the chance SOMETHING will need it...
;
; 
; AUTHOR:
; 
; G. EVERHART
;--
	.PAGE
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

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

	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$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
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SCSDEF
	$SBDEF
	$STSDEF
	$STSDEF		; Symbols for returned status.
	$DVIDEF		; Symbols for $GETDVI service.
	$DCDEF		; Symbols for device type.
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK

; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; DEFINE THESE SO WE KNOW WHERE IN THE UCB TO ACCESS. WE MUST
; SET THE ONLINE BIT OR CLEAR IT, AND ALSO SET
; UCB$HUCB (HOST UCB ADDRESS), UCB$HFSZ (HOST FILE SIZE),
; AND UCB$HLBN (HOST LOGICAL BLOCK NUMBER OF FILE START)
;
; Note: These MUST match the definitions in VDDRIVER. Don't
; change one without changing the other to match it!!!
;	G. Everhart 9/5/1986
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS

;.=UCB$W_BCR+2				;BEGIN DEFINITIONS AT END OF UCB
.=ucb$k_lcl_disk_length  ; vms v4, right out of the book...
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
$DEF	UCB$W_DY_WPS	.BLKW	1	;Words per sector.
$DEF	UCB$W_DY_CS	.BLKW	1	;CONTROL STATUS REGISTER
;UCB$HUCB=.
$DEF	UCB$W_DY_DB	.BLKW	1	;UCB ADDRESS OF HOST DRIVER
$DEF	UCB$W_DY_DPN	.BLKW	1	;(LONGWORD)
;UCB$HLBN=.
$DEF	UCB$L_DY_DPR	.BLKL	1	;START LBN OF HOST CONTIG FILE
;UCB$HFSZ=.		; HOST FILE SIZE IN BLKS (RANGE CHECK)
$DEF	UCB$L_DY_FMPR	.BLKL	1	;
$DEF	UCB$L_DY_PMPR	.BLKL	1	;PREVIOUS MAP REGISTER
$DEF	UCB$B_DY_ER	.BLKB	1	;SPECIAL ERROR REGISTER
			.BLKB	1	;Reserved.
$DEF	UCB$B_DY_LCT	.BLKB	1	;LOOP COUNTER
$DEF	UCB$B_DY_XBA	.BLKB	1	;BUS ADDRESS EXTENSION BITS
$DEF	UCB$W_DY_PWC	.BLKW	1	;PARTIAL WORD COUNT
$DEF	UCB$W_DY_SBA	.BLKW	1	;SAVED BUFFER ADDRESS
$DEF	UCB$L_DY_XFER	.BLKL	1	;TRANSFER FUNCTION CSR BITS
$DEF	UCB$L_DY_LMEDIA	.BLKL	1	;LOGICAL MEDIA ADDRESS
$DEF	UCB$Q_DY_EXTENDED_STATUS	; Area into which we do READ ERROR
			.BLKQ	1	;  REGISTER command.

;	RY_EXTENDED_STATUS_LENGTH = .-UCB$Q_DY_EXTENDED_STATUS

$DEF	UCB$Q_DY_SVAPTETMP		; Area in which we save UCB fields -
			.BLKQ	1	;  SVAPTE, BOFF, and BCNT.
$DEF	UCB$L_DY_MAPREGTMP		; Area in which we save CRB fields -
			.BLKL	1	;  MAPREG, NUMREG, and DATAPATH.
$DEF	UCB$L_DY_SAVECS	.BLKL	1	; Area in which we save CS and DB regs.
$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
$DEF	UCB$PPID	.BLKL	1	;PID save area for active requests
$DEF	UCB$stats	.BLKL	1	;IRP STATUS CODE SAVE AREA
$DEF	UCB$OBCT	.BLKL	1	;STORE FOR IRP$L_OBCNT too
; Since I/O postprocessing on virtual or paging I/O makes lots of
; assumptions about location of window blocks, etc., which are
; not true here (wrong UCB mainly), we'll bash the function code status
; we send to the host driver to look like logical I/O is being
; done and save the real status code here. Later when VD: does
; I/O completion processing, we'll replace the original function
; from here back in the IRP. This will be saved/restored along with
; ucb$ppid (irp$l_pid field) and so synchronization will be detected
; with ucb$ppid usage.
$def	ucb$owind	.blkl	1	; store irp$l_wind...
$def	ucb$osegv	.blkl	1	; and irp$l_segvbn
$def	ucb$fb1		.blkl	10	; fork block 1 (extra frk blks)
;$def	ucb$fb2		.blkl	10	;fork block 2
;
$def	ucb$k_vd_len	.blkw	1	;length
;UCB$K_DY_LEN=.				;LENGTH OF UCB

	$DEFEND	UCB			;END OF UCB DEFINITONS

; TO SET ONLINE:
;	BISW	#UCB$M_ONLINE,UCB$W_STS(R5)  ;SET UCB STATUS ONLINE

	$FIBDEF			; Symbols for file information block.
	$IODEF			; Symbols for QIO functions.
	$DVIDEF			; Symbols for $GETDVI calls.
	$TPADEF			; Symbols for LIB$TPARSE calls.
; Macro to check return status of system calls.
;
	.MACRO	ON_ERR	THERE,?HERE
	BLBS	R0,HERE
	BRW	THERE
HERE:	.ENDM	ON_ERR

	.PSECT	ADVDD_DATA,RD,WRT,NOEXE,LONG

DEFAULT_DEVICE:
	.ASCID	/SYS$DISK/

	$ATRDEF
	$FABDEF
	$FATDEF
	$FIBDEF
	$IODEF
	$NAMDEF
	$RMSDEF
	$XABDEF
	.ALIGN LONG
DFAB_BLK: $FAB FNM=<VD0.DSK>,XAB=FNXAB,FAC=<GET,PUT>,DNM=<VDCONT.DSK>
;
FNXAB:	$XABFHC	; XAB STUFF TO GET LBN, SIZE
	.BLKL	20 ;SAFETY
IOSTATUS: .BLKQ 1
DEV_BUF:			; Buffer to hold device name.
	.BLKB	40
DEV_BUF_SIZ = . - DEV_BUF

DEV_BUF_DESC:			; Descriptor pointing to device name.
	.LONG	 DEV_BUF_SIZ
	.ADDRESS DEV_BUF

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

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

DEV_CLASS:
	.LONG	1
;**
VDV_BUF:			; Buffer to hold VDVice name.
	.BLKB	40
VDV_BUF_SIZ = . - VDV_BUF

VDV_BUF_DESC:			; Descriptor pointing to VDVice name.
	.LONG	 VDV_BUF_SIZ
	.ADDRESS VDV_BUF

VPID:				; Owner of VDVice (if any).
	.BLKL	1

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

VDV_CLASS:
	.LONG	1
;**
DEFNAM:

WRK:	.BLKL	1	;SCRATCH INTEGER
; DESCRIPTOR FOR VDn: "FILENAME"
	.ALIGN LONG
VDFNM:	.WORD	 255.	;LENGTH
VDFTP:	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
	.ADDRESS	VDFNMD
VDFNMD:	.BLKB	256.	; DATA AREA
;
; DESCRIPTOR FOR DVn:DSKFIL "FILENAME"
	.ALIGN LONG
DDFNM:	.WORD	 255.	;LENGTH
DDFTP:	.BYTE	DSC$K_DTYPE_T	;TEXT TYPE
	.BYTE	1	; STATIC STRING
DDFNA:	.ADDRESS	DDFNMD
DDFNMD:	.BLKB	256.	; DATA AREA
DDCHN:	.LONG	0
VDCHN:	.LONG	0	;CHANNEL HOLDERS
;
; FOR initial use, don't bother allocating the file. Assume the
; user can somehow allocate a contiguous file of the size he wants
; for himself.
;
ASDSC:	.ASCID	/ASSIGN/
DASDSC:	.ASCID	/DEASSIGN/
P1DSC:	.ASCID	/UNIT/
P2DSC:	.ASCID	/FNAM/
	.EVEN
ASDAS:	.LONG	0	;DEFAULT DEASSIGN
;
; ucb data area
HSTUCB:	.LONG	0	;HOST UCB ADDRESS
HSTLBN:	.LONG	0	;LBN OF 1ST BLK OF HOST FILE
HSTFSZ:	.LONG	0	;LENGTH IN BLOCKS OF HOST FILE
; debug words
vducb: .long 0
vdsts:	.long 0
vdsts2:	.long 0
vdsts3:	.long 0
vdcyl:	.long 0
vdprog:	.long 0	;counts where we've been
;
;
	.PSECT	ADVDD_CODE,RD,NOWRT,EXE,LONG
	.ENTRY	ADVDD,^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>

	MOVL	#1,ASDAS	;SET ASSIGN
	PUSHAB	WRK		;PUSH LONGWORD ADDR FOR RETLENGTH
	PUSHAB	VDFNM		;ADDRESS OF DESCRIPTOR TO RETURN
	PUSHAB	P1DSC		; GET P1 (VDn: UNIT)
	CALLS	#3,G^CLI$GET_VALUE	;GET VALUE OF NAME TO VDFNM
	ON_ERR	ADVDD_EXIT
;
; IF "DEASSIGN" WE DON'T NEED 2ND ARG... SEE...
;
	PUSHAB	DASDSC	; 'DEASSIGN'
	CALLS	#1,G^CLI$PRESENT	; IS /DEASSIGN USED?
	CMPL	R0,#CLI$_PRESENT	; IF EQ YES
	BEQL	DAS1

	PUSHAB	WRK		; GET 2ND FILE (REAL FILE)
	PUSHAB	DDFNM		; & ITS DESCRIPTOR
	PUSHAB	P2DSC		; & PARAMETER NAME 'P2'
	CALLS	#3,G^CLI$GET_VALUE	; GET FNM
	ON_ERR	ADVDD_EXIT
	BRB	DAS2
DAS1:	CLRL	ASDAS		; FLAG /DEAS
DAS2:

	TSTL	ASDAS		; IF 0, DEASSIGNING SO NO CHNL FOR HOST
				; FILE
	BEQL	290$
	$ASSIGN_S -				; Get a channel to the 
		DEVNAM=DDFNM,-		; device for host file
		CHAN=DDCHN
	ON_ERR	ADVDD_EXIT
; LET ERRORS BY FOR THIS SINCE WE GET OUR INFO VIA OPEN ANYWAY SO
; CHANNEL REALLY DOESN'T HAVE TO BE THERE.
; Get the physical device name, and see if this device has an owner.
; (We must do this so we can get the host UCB address)
	$GETDVI_S -
		CHAN=ddchn,-	; Command line has device name.
		ITMLST=DEV_ITEM_LIST
	BLBS	R0,40$
	BRW	advdd_EXIT
40$:
290$:
; MUST HAVE ASSIGNMENT TO VD: UNIT IN ANY CASE.
	$ASSIGN_S -
		DEVNAM=VDFNM,-	; GET CHANNEL FOR VDn:
		CHAN=VDCHN
	ON_ERR	ADVDD_EXIT	; SKIP OUT IF ERROR
	$GETDVI_S -
		CHAN=vdchn,-	; Command line has device name.
		ITMLST=VDV_ITEM_LIST
	BLBS	R0,140$
	BRW	advdd_EXIT
140$:
;
; NOW LOCATE THE FILE AND VERIFY IT'S REALLY CONTIGUOUS, AND FIND
; OUT HOW BIG IT IS. STORE RESULTS IN HSTLBN AND HSTFSZ AND
; CALL KERNEL ROUTINE TO BASH THE VDn: UCB APPROPRIATELY.
;
; DON'T NEED TO DO THIS FOR DEASSIGN SO CHECK THAT FIRST...
	TSTL	ASDAS		; IF ZERO WE DEASSIGN
	BEQL	296$
; OPEN THE FILE, CHECK ITS INITIAL LBN
; IF ERROR OR NOT CONTIG, EXIT...
; DO VIA OPENING FILE AND READING ITS' STATBLOCK VIA
; QIO...
; SET UP FOR FILENAME WE REALLY FOUND IN FAB...
	MOVL	DDFNA,DFAB_BLK+FAB$L_FNA	;SET UP FILENAME ADDR
	MOVB	DDFNM,DFAB_BLK+FAB$B_FNS	;AND LENGTH
	$OPEN	FAB=DFAB_BLK
	BLBC	R0,300$		; FAILURE IF FILE WON'T OPEN
; FNXAB HAS INFO ON LBN, SIZE
	MOVL	FNXAB+XAB$L_SBN,HSTLBN	; GET HOST'S LBN
	BEQL	301$		;;; RESTRICTION FOR NOW ...
				;;; IF ZERO, FILE NONCONTIG
				;;; SO FORGET IT...
	MOVL	FNXAB+XAB$L_HBK,HSTFSZ	; GET FILE SIZE. (CHECK THAT BELOW)
	.if	ndf,phy$io
	BICL2	#63,HSTFSZ	;;;MAKE A MULTIPLE OF 64 BLKS
;;; As long as the driver doesn't do physical I/O we can omit this...
;;; except INIT seems unhappy then
	.endc
;;;HAS TO BE A MULTIPLE OF 64 BLKS DUE TO FAKED-OUT PHYSICAL DRIVE
;;; STRUCTURE OF 64 SECTORS/TRACK, 1 TRACK/CYL, NNN CYLINDERS...

	TSTL	HSTFSZ		; HOST SIZE POSITIVE
	BLSS	301$		; IF <0 OR =0 THEN ILLEGAL; BUG OUT
; ELSE ISSUE THE REQUESTS TO GET THE DEVICES...
296$:

	$CMKRNL_S -
		ROUTIN=BASHUCB,ARGLST=K_ARG
	BRW	300$
301$:
; ERROR RETURN ... CLOSE FAB & LEAVE
	$CLOSE FAB=DFAB_BLK
300$:
; BE SURE WE DON'T LEAVE THE CHANNELS ASSIGNED TO THE DEVICES
; EITHER...
	$DASSGN_S CHAN=VDCHN
	TSTL	ASDAS		; IF ZERO WE DEASSIGN
	beql	540$		; if zero, no file chnl to deassign
	$DASSGN_S CHAN=DDCHN		;CLEAN UP I/O CHANNELS
540$:	; skip deassign file chnl on advd/deassign
	; to avoid final error msg
	RET
advdd_exit:
	RET
;
; KERNEL ARG LIST
K_ARG:
	.LONG	2	;2 ARGS: HOST-DVC NAME, VD DVC NAME
	.ADDRESS	DEV_BUF_DESC
	.ADDRESS	VDV_BUF_DESC
;	.ADDRESS	DDFNM
;	.ADDRESS	VDFNM

; BASHUCB - AREA TO MESS UP UCB WITH OUR FILE DATA
; BEWARE BEWARE BEWARE
;  runs in KERNEL mode ... HAS to be right.

	.ENTRY	BASHUCB,^M<R2,R3,R4,R5,R6,R7,R8>
; TAKEN LOOSELY FROM ZERO.MAR
	.if	ndf,vms$v5
	MOVL	G^SCH$GL_CURPCB,R4	;;; NEED OUR PCB
	.iff
	MOVL	G^CTL$GL_PCB,R4		;;; NEED OUR PCB (VMS V5)
	.endc
	JSB	G^SCH$IOLOCKW		;;; LOCK I/O DATABASE
	CLRL	HSTUCB			;;; ZERO "HOST" UCB
	movl	#1,vdprog	;;; got to start
	TSTL	ASDAS			;;; IF DEASSIGN, ZERO
	BEQL	90$			;;; SO IF EQUAL SKIP LOCATE HOST UCB
	MOVL	4(AP),R1		;;; ADDRESS DVC NAME DESCRIPTORS
	JSB	G^IOC$SEARCHDEV		;;; GET UCB ADDRESS INTO R1
	BLBS	R0,60$
	BRW	BSH_XIT
60$:
;	TSTL	UCB$L_PID(R1)		;;; ENSURE DVC NOT ALLOCATED
;	BEQL	80$
;	MOVL	#SS$_DEVALLOC,R0
;	BRW	BSH_XIT
; ALLOCATED OK SINCE IT COULD JUST BE PRIVATE MOUNT...
;
80$:
	movl	#2,vdprog	;;; got host ucb ok
	MOVL	R1,HSTUCB		;;; SAVE HOST UCB ADDRESS
	BEQL	167$		;;; ... BUT ZERO UCB ADDRESS LOOKS BAAAAD
90$:
	MOVL	8(AP),R1		;;; ADDRESS VDn NAME DESCRIPTORS
	movl	#3,vdprog	;;; got vdn: descriptor
	JSB	G^IOC$SEARCHDEV		;;; GET UCB ADDRESS INTO R1
	BLBS	R0,160$
	BRW	BSH_XIT
160$:
	movl	r1,vducb	;;; store vd ucb
	movl	#4,vdprog	;;; got vd ucb
	TSTL	UCB$L_PID(R1)		;;; ENSURE DVC NOT ALLOCATED
	BEQL	180$
165$:
	MOVL	#SS$_DEVALLOC,R0
167$:	BRW	BSH_XIT
180$:
	movl	#5,vdprog	;;; not allocated yet
; BUGGER THE UCB
; ASSUMES FILE LBN AND SIZE ALREADY RECORDED
; ALSO ASSUMES THAT ZERO LBN OR SIZE MEANS THIS ENTRY NEVER CALLED.
; (REALLY ONLY WORRY ABOUT ZERO SIZE; IF WE OVERMAP A REAL DEVICE
; THEN ZERO INITIAL LBN COULD BE OK.)
;
; CHECK REF COUNT FIRST... ONLY CAN GET AWAY WITH THIS ON DEVICE
; NOBODY'S USING...
; .. fake this since device may have count messed by advd somehow
; but will be allocated if mounted.
; ... for now ...
	movzwl  ucb$w_refc(r1),vdsts2 ;;; save status for debug
	CMPW	UCB$W_REFC(R1),#1	;;; CHECK COUNT VS 1 FOR THIS
	BGTRU	165$
;	TSTW	UCB$W_REFC(R1)		;;; IF MOUNTED DON'T TOUCH
;	BNEQ	165$			;;; IF NEQ IT'S ACCESSED...
	movl	#6,vdprog	;;; not mounted either
	MOVL	HSTUCB,UCB$HUCB(R1)	;;; SAVE HOST UCB OR 0
	BNEQ	184$			;;; IF NE, OK NOW
;;; ZERO -- DEASSIGNING. FLAG VOLUME INVALID
	BICW	#UCB$M_ONLINE,UCB$W_STS(R1) ;;; FLAG OFFLINE
	BICW	#UCB$M_VALID,UCB$W_STS(R1) ;;; AND INVALID
	BRW	200$
184$:	MOVL	HSTLBN,UCB$HLBN(R1)	;;; SAVE HOST'S LBN
	MOVL	HSTFSZ,UCB$HFSZ(R1)	;;; AND FILE SIZE
	MOVL	HSTFSZ,UCB$L_MAXBLOCK(R1) ;;; (SAVE SIZE TWICE, FOR RMS
	clrl	ucb$ppid(r1)		;;;zero original PID
	movl	#65535,ucb$l_maxbcnt(r1) ;;;store max bytes as a word
	movl	#7,vdprog	;;; filled in stuff
					;;; AND QIO CHECKS, AND OUR SAFETY
					;;; ONES)
	MOVL	HSTFSZ,R0		;;; GET HOST SIZE
	ASHL	#-6,R0,R0		;;; GET # CYLINDERS IN SIZE NOW
	MOVW	R0,UCB$W_CYLINDERS(R1)	;;; SAVE IN UCB FOR REST OF VMS
	movl	r0,vdcyl	;;; store cylinders for debug
	movl	#8,vdprog
	BISW	#UCB$M_ONLINE,UCB$W_STS(R1) ;;; FLAG ONLINE NOW
	BISW	#UCB$M_VALID,UCB$W_STS(R1) ;;; AND VOL VALID
	movzwl	ucb$w_sts(r1),vdsts	;;; save VD status
;;; THAT'S IT... SHOULD BE OK NOW.
200$:
	MOVL	#SS$_NORMAL,R0
BSH_XIT:
	PUSHL	R0
	JSB	G^SCH$IOUNLOCK		;;; UNLOCK I/O DATABASE (DROP IPL)
	POPL	R0			;;; REMEMBER R0
	RET	;;; BACK TO USER MODE NOW
	.END ADVDD
-h- VDDRIVERV3A.MAR
	.TITLE	VDDRIVER - VAX/VMS VIRT DISK DRIVER
	.IDENT	'V03-001A'
;d$$bug=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.
; Glenn C. Everhart, 1/11/1989
; Merged in some of Marty Sasaki's changes
;USAPADDR=0
;d$$bug=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.
;
; Note:
;	This driver has an FDT table that will look just like other
;	disks for everything, but will NOT do buffered I/O.
;	In its' FDT routines, which will be dummies, it will just
;	modify logical block numbers in the I/O packets, assume
;	the buffered bit in irp$w_STS clear (like all disk drivers 
;	except floppy drivers), do a range check to make sure
;	the LBN used is in the legal range for this particular unit
;	ov VD:, and reset things to call the real driver's FDT
;	routines and let IT do the work. It will unbusy itself
;	before losing control.
;		The idea is that only I/O "gets at the physical
;	storage", so only that need be munged. Since this happens
;	only for read/write logical/physical, we just leave OUR
;	FDT routines in there for everything else. In just assuming
;	the buffered bit in the I/O we MAY mess up some quotas (this
;	will eventually get cleaned up), but won't lose buffers or
;	otherwise mess up things. Since most drivers have nonbuffered
;	I/O, we'll usually be just fine. (Disk drivers, that is.)
;	(In fact only RX01 class real drivers are buffered. Don't
;	put vd: units on those!!!)
;		To call the "real" FDT routines we'll just reset the
;	registers (and the IORP) to the real device and return. The
;	exec routines that handle FDT routines will take it from there
;	and do the FDT processing in the real driver. We just have to
;	let them work...
;		The FDT routines are called from SYSQIOREQ.MAR for
;	future reference.
;		The main work is in the start-IO area. There, the I/O
;	packet is edited to relocate the I/O to the contiguous file
;	and sent to the real driver. First however we bash the IRP$L_PID
;	field to get control back (via jsb) here. At that point,
;	the UCB and IRP pointers are restored, the IRP returned to point
;	at VD: (and the correct unit), and the normal IOC$REQCOM call
;	is made to finish off the I/O packet in VD: context. (This allows
;	any i/o splits to be handled correctly and SHOULD allow MSCP
;	to work OK if enabled.) The vd: unit stays busy until reqcom
;	calls free it. Normal reqcom completion in the host will free
;	THAT unit also; we don't need to mess with it. The VD: unit will
;	eventually RSB its way back to normal processing in the rest of
;	VMS. 
;
;	The logical I/O FDT entry has been commented out to keep the
;	operation simpler. It is OK as is, but things are cleaner with
;	NO prospect of clobbering any arguments.
;
; Note that ASNVD simply inserts a phony device structure for VD: units
; of 64 sectors per track, one track/cylinder, n cylinders. INIT seems
; to need this, though physical I/O is disabled.
;--
	.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
	$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
	$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
	.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.
$DEF	UCB$W_VD_WPS	.BLKW	1	;Words per sector.
$DEF	UCB$W_VD_CS	.BLKW	1	;CONTROL STATUS REGISTER
$DEF	UCB$W_VD_DB	.BLKW	1	;UCB ADDRESS OF HOST DRIVER
$DEF	UCB$W_VD_DPN	.BLKW	1	;(LONGWORD)
$DEF	UCB$L_VD_DPR	.BLKL	1	;START LBN OF HOST CONTIG FILE
$DEF	UCB$L_VD_FMPR	.BLKL	1	;
$DEF	UCB$L_VD_PMPR	.BLKL	1	;PREVIOUS MAP REGISTER
$DEF	UCB$B_VD_ER	.BLKB	1	;SPECIAL ERROR REGISTER
			.BLKB	1	;Reserved.
$DEF	UCB$B_VD_LCT	.BLKB	1	;LOOP COUNTER
$DEF	UCB$B_VD_XBA	.BLKB	1	;BUS ADDRESS EXTENSION BITS
$DEF	UCB$W_VD_PWC	.BLKW	1	;PARTIAL WORD COUNT
$DEF	UCB$W_VD_SBA	.BLKW	1	;SAVED BUFFER ADDRESS
$DEF	UCB$L_VD_XFER	.BLKL	1	;TRANSFER FUNCTION CSR BITS
$DEF	UCB$L_VD_LMEDIA	.BLKL	1	;LOGICAL MEDIA ADDRESS
$DEF	UCB$Q_VD_EXTENDED_STATUS	; Area into which we do READ ERROR
			.BLKQ	1	;  REGISTER command.
$DEF	UCB$Q_VD_SVAPTETMP		; Area in which we save UCB fields -
			.BLKQ	1	;  SVAPTE, BOFF, and BCNT.
$DEF	UCB$L_VD_MAPREGTMP		; Area in which we save CRB fields -
			.BLKL	1	;  MAPREG, NUMREG, and DATAPATH.
$DEF	UCB$L_VD_SAVECS	.BLKL	1	; Area in which we save CS and DB regs.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
$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
$DEF	UCB$PPID	.BLKL	1	;PID OF ORIGINAL PROCESS FROM IRQ BLK
; 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$owind	.blkl	1	; store irp$l_wind...
$def	ucb$osegv	.blkl	1	; and irp$l_segvbn
; Since I/O postprocessing on virtual or paging I/O makes lots of
; assumptions about location of window blocks, etc., which are
; not true here (wrong UCB mainly), we'll bash the function code
; we send to the host driver to look like logical I/O is being
; done and save the real function code here. Later when VD: does
; I/O completion processing, we'll replace the original function
; from here back in the IRP. This will be saved/restored along with
; ucb$ppid (irp$l_pid field) and so synchronization will be detected
; with ucb$ppid usage.
$def	ucb$fb1		.blkl	10	; fork block 1 
;$def	ucb$fb2		.blkl	10	;fork block 2
; define extra fork blks to try to avoid double forking possibilities
$def	ucb$l_vd_host_descr	.blkl	2	; char string descr
;
$DEF	UCB$K_VD_LEN	.BLKW	1	;LENGTH OF UCB
;UCB$K_VD_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
; 
	.PSECT	$$$105_PROLOGUE
VD_UNITS=8
VD$DPT::
	DPTAB	-			;DPT CREATION MACRO
		END=VD_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		DEFUNITS=2,-		;UNITS 0 THRU 1
		UCBSIZE=UCB$K_VD_LEN,-	;LENGTH OF UCB
		MAXUNITS=VD_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=VDDRIVER		;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	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
	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
; VD: 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.
	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,VD_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
	DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,-  ;CONTROLLER INIT ADDRESS
		      D,VD_ctrl_INIT		  ;...
	DPT_STORE CRB,CRB$L_INTD+VEC$L_UNITINIT,- ;UNIT INIT ADDRESS
		      D,VD_unit_INIT		  ;...
	DPT_STORE DDB,DDB$L_DDT,D,VD$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.
; 
;VD$DDT:
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=VD,-		;NAME OF DEVICE
		START=VD_STARTIO,-	;START I/O ROUTINE
		FUNCTB=VD_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.
; 

VD_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
		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
	FUNCTAB	VD_ALIGN,-		;TEST ALIGNMENT FUNCTIONS
		<READLBLK,-		; READ LOGICAL BLOCK
		READVBLK,-		; READ VIRTUAL BLOCK
;		READPBLK,-
;		WRITEPBLK,-
		WRITELBLK,-		; WRITE LOGICAL BLOCK
		WRITEVBLK-		; WRITE VIRTUAL BLOCK
		>
;	FUNCTAB VD_FAKEOUT,-		; FAKEOUT FOR LOGICAL OR PHYSICAL I/O
;		<READLBLK,-	; TO OUR LOCAL ROUTINE.
;		WRITELBLK>
	functab vd_format,-		;point to host disk
		<format>
; 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 VD: UNIT
; WILL WORK OK.
;
	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$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
	.SBTTL FDT Routines
;++
;
; vd_format - point to proper location on the host disk
;
; With no function modifiers, this routine takes as arguments the name
; of the host disk (the real disk where the virtual disk will exist),
; the size of the virtual disk, and the LBN where the virtual disk
; will start. After these are set up, the device is put online and is
; software enabled.
;
; This routine does virtually no checking, so the parameters must be
; correct.
;
; Inputs:
;	p1 - pointer to buffer. The buffer has the following format:
;	     longword 0 - starting LBN, where the virtual disk starts
;	     		  on the real disk.
;	     longword 1 - virtual disk length, the number of blocks in
;			  the virtual disk.
;	     longword 2 through the end of the buffer, the name of the
;			  virtual disk. This buffer must be blank
;			  padded if padding is necessary
;
;	p2 - size of the above buffer
;--
vd_format:
	bicw3	#io$m_fcode,irp$w_func(r3),r0	;mask off function code
	bneq	20$			;branch if modifiers, special
	rsb				;regular processing
10$:
	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	clrl	r1
	jmp	g^exe$abortio
20$:
	movl	p1(ap),r0		;buffer address
	movl	p2(ap),r1		;length of buffer
	jsb	g^exe$writechk		;read access? doesn't return on error
	clrl	irp$l_bcnt		;paranoia, don't need to do this...
	movl	p1(ap),r0		;get buffer address
	movl	(r0)+,-			;move starting lbn
		ucb$hlbn(r5)
	blss	10$
	movl	(r0)+,-			;size of virtual disk
		ucb$hfsz(r5)
	bleq	10$
	movl	(r0),-			;name of "real" disk
		ucb$l_vd_host_descr+4(r5)
	subl3	#8,p2(ap),-		;set length of name in descriptor
		ucb$l_vd_host_descr(r5)
	bleq	10$			;bad length
	moval	ucb$l_vd_host_descr(r5),r1	;descriptor for...
	jsb	g^ioc$searchdev		;search for host device
	blbs	r0,30$			;branch on success
	movzwl	#ss$_nosuchdev+2,r0	;make an error, usually a warning
	clrl	r1
	jmp	g^exe$abortio		;exit with error
30$:
	addl3	ucb$hfsz(r5),- 	;end of virtual device
		ucb$hlbn(r5),r0
	cmpl	ucb$l_maxblock(r1),r0		; < end of real disk?
	blss	10$
	movl	r1,ucb$hucb(r5)	;stash the ucb
	clrl	ucb$ppid(r5)		; mark driver free of old pids
	bisw	#ucb$m_valid,ucb$w_sts(r5)	;set volume valid
	bisw	#ucb$m_online,ucb$w_sts(r5)	;set unit online
	movl	ucb$l_irp(r5),r3		;restore r3, neatness counts
	movzwl	#ss$_normal,r0			;success
	jmp	g^exe$finishioc			;wrap things up.

	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; VD_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.
;--
	.PSECT	$$$115_DRIVER
VD_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
;++
; 
; VD_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.
; 
;--

VD_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
	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	#^Xb12c4001,ucb$l_media_id(r5)	; set media id as VD
; (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 drive
; 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 
;++
; 
; VD_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.
; 
;--

VD_ALIGN:				;CHECK BYTE COUNT AT P1(AP)
	.if	ndf,nolchk
; note: not fully tested but a MINOR mod... therefore conditioned.
	tstw	6(ap)			;test high order half of
					; byte count specified
	bneq	10$			; if bigger than 65k call error
	.endc
	BLBS	4(AP),10$		;IF LBS - ODD BYTE COUNT
	RSB				;EVEN - RETURN TO CALLER
10$:	MOVZWL	#SS$_IVBUFLEN,R0	;SET BUFFER ALIGNMENT STATUS
	JMP	G^EXE$ABORTIO		;ABORT I/O
	.PAGE
	.SBTTL	START I/O ROUTINE

; VD_FAKEOUT 
; CALLED TO DO MOST OF THE REAL WORK...
;
; This entry gets control on logical I/O and relocates the block
; number to the "host" disk. Split I/O is not really an issue
; here. Therefore it just passes control to the FDT routines
; of the "host" driver. The logic is largely duplicated at the
; start-IO point and is therefore redundant here to some extent.
;
; ENTRY:
; R0= FDT ENTRY (OURS ON ENTRY; HOST'S ON EXIT)
; R1, R2 SCRATCH
; R3= I/O PKT ADDRESS
; R4= PCB ADDRESS
; R5= UCB ADDRESS (OURS ON ENTRY; HOST'S ON EXIT)
; R6 = CCB ADDRESS
; R7= I/O FCT CODE BIT NUMBER
; R8= FDT DISPATCH ADDRESS (ADD 12. TO IT EVERY PASS THRU INCLUDINBG FIRST)
;		(OURS ON ENTRY; HOST'S ON EXIT)
; R9, R10, R11 = SCRATCH
; AP = ADDRESS OF 1ST FUNCT DEPENDENT PARAMETER
;VD_FAKEOUT:
;; FIRST ENSURE ALL'S WELL FOR LBN RANGE
;	CMPL	UCB$HFSZ(R5),8(AP)	;LBN OF FILE > REQ LBN?
;	BGTRU	1$		; IF GT THEN OK
;	MOVZWL	#SS$_BADPARAM,R0	;IF NOT, BAD PARAMS
;	JMP	G^EXE$ABORTIO		;ABORT I/O
;1$:
;; NOW FIND HOST UCB
;	TSTL	UCB$HUCB(R5)		; BE SURE WE HAVE SOMETHING IN UCB ADDR
;	BNEQ	2$
;	MOVZWL	#SS$_VOLINV,R0		;SET VOLUME INVALID STATUS
;	JMP	G^EXE$ABORTIO		;ABORT I/O
;2$:
;; NOW ADJUST LBN REQUEST AND FAKE THINGS SO IT APPEARS LBN REQUEST
;; IS FOR REAL DEVICE.
;;
;;
;; NOTE: As far as I can tell, VMS does not call this from kernel mode,
;; so we'll just bash the kernel stack copy of the user mode QIO
;; parameters. The parameters for disk block ONLY exist on stack at
;; this point, but it's the KERNEL stack, not the USER stack. Were this
;; not so, we'd have to play games with extra UCB store or IRPEs or the
;; like...probably special IRPEs.
;;
;	ADDL	UCB$HLBN(R5),8(AP)	; ADD THE LBN ON STACK BEFORE STORED
;;
;	MOVL	UCB$HUCB(R5),R5		; R5 NOW IS HOST'S UCB ADDRESS
;	MOVL	UCB$L_DDT(R5),R0
;	MOVL	DDT$L_FDT(R0),R8	; POINT AT HOST'S FDT AREA
;;
;; NOW FILL IN HOST UCB IN I/O PKT
;; (no segmented i/o since this is LOGICAL I/O)
;;	CMPL	IRP$L_OBCNT(R3),IRP$L_BCNT(R3) ;SEE IF WE'RE
;				; GONNA NEED TO GET BACK TO THIS
;				; DRIVER FOR 2ND OR LATER SEGMENTED
;				; I/O PACKETS...
;;	BNEQ	542$		; IF NOT SAME ASSUME SO
;	MOVL	R5,IRP$L_UCB(R3)	; PACKET MOVES TO HOST...
;;542$:
;;  LBN is stored in I/O packet at IRP$L_MEDIA as LBN here since we
;; set the no-convert option. Therefore requeue to I/O driver of host
;; after mod to that.
;;
;; Now forward to the host driver's FDT routines.
;;
;; PASS FIRST 2 QUADWORDS OF MASKS
;	ADDL	#4,R8		; WHEN WE RETURN WE GO TO 1ST FDT 
;				; ROUTINE OF REAL DRIVER. SEE
;				; SYSQIOREQ.MAR FOR DETAILS.
;; NOW ALL SET. ONLY LOGICAL I/O GETS HERE AND IT'LL NOW BE DISPATCHED
;; FOR THE HOST DRIVER WITH THE MODIFIED I/O PACKET PARAMETERS.	
;; SINCE FDT PROCESSING JUST GOES MERRILY ALONG FOR ALL FDT ROUTINES,
;; IT WILL NOW DO SO IN THE HOST DRIVER USING THE HOST DRIVER'S FDT
;; ROUTINES, BUT WITH LBN CORRECTED TO POINT TO OUR ASSOCIATED FILE.
;; THIS WILL GET US, WITH LITTLE OVERHEAD, TO A VIRTUAL DISK ON A CONTIGUOUS
;; FILE. NOTE ONLY LOGICAL I/O IS TRANSFORMED; THE REST IS FILE-STRUCTURED
;; STUFF AND WILL GO ON AS IF WE HAD REAL INDEPENDENT STORAGE ON THIS
;; "DEVICE".
;	RSB	; "BACK" TO SYSTEM TO GET TO HOST'S FDT'S NEXT.

;++
; 
; VD_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.
; 
;--
REQUEUE:
	.if	df,d$$bug
	movl	r3,dbgdta+68	;store offending IRP here
	.endc
	JMP	EXE$INSIOQ		; REQUEUE packet to ourselves
; return to our caller direct from insioq.
; (note this also sets busy, so it will NOT loop forever.)
VD_STARTIO:				;START I/O OPERATION
; 
; 	PREPROCESS UCB FIELDS
; 
;	ASSUME	RY_EXTENDED_STATUS_LENGTH  EQ  8
;	CLRQ	UCB$Q_VD_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$
	movzwl	#ss$_volinv,r0	; else set volume invalid
	brw	resetxfr	; reset byte count & exit
210$:
; 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!
	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...
;
	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

readdata:
writedata:
; debug using sda to peek
; NOW VALIDATED I/O FCN... MODIFY AND SEND OFF
	.if	df,d$$bug
	MOVL	#1,DBGDTA	;TELL THAT WE GOT HERE
	MOVL	R5,DBGDTA+4	;SAVE OUR UCB ADDR
	MOVL	R3,DBGDTA+8	; AND I/O PKT
	MOVL	IRP$L_MEDIA(R3),DBGDTA+12	;STORE BLK NUMBER GIVEN
	MOVL	IRP$L_OBCNT(R3),DBGDTA+16	;ALSO ORIG BYTE CNT
	MOVL	R1,DBGDTA+36	;SAVE FCN OFFSET
	.endc
	CMPL	IRP$L_MEDIA(R3),UCB$HFSZ(R5)	;BE SURE LBN OK
	blequ	65$
	brw	Fatalerr
65$:
;	BGTRU	FATLJ		;IF NOT OK JUST DISMISS I/O
; HAVE TO BE CAREFUL WHAT WE SHIP TO READ DRIVER
	; Prepare to enter another context.
; SEND PKT OFF TO REAL DRIVER...
	ADDL2	UCB$HLBN(R5),IRP$L_MEDIA(R3)	;ADJUST LBN IN IO PKT
	.if	df,d$$bug
	MOVL	IRP$L_MEDIA(R3),DBGDTA+20
	MOVL	UCB$HLBN(R5),DBGDTA+24
	MOVL	UCB$HFSZ(R5),DBGDTA+28
	MOVL	IRP$L_SEGVBN(R3),DBGDTA+32
	.endc
;
; NOW we have to fix up the media address for the host...
; ... otherwise we confuse the heck out of things by making
; ... the host (who is expecting a track/sect/cyl number) get really
; ... goofy numbers. Cheat by using exec routine after a bit more messup.
; Ideally we should also avoid moving the packet (next) to host if
; any segmented I/O may occur but ignore that for now; it will be
; rare and I want to get this working in the 99.9% cases...
	MOVL	UCB$HUCB(R5),IRP$L_UCB(R3)	;FIX UP PTR IN I/O PKT
; GRAB HOST PID
	TSTL	UCB$PPID(R5)		; GUARD AGAINST DOUBLE BASH
	BNEQ	12$
	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
	bicw	#<irp$m_pagio!irp$m_swapio!irp$m_virtual>,-
		irp$w_sts(r3) ;say not page/swp, not virtual
	bisw	#irp$m_physio,irp$w_sts(r3)	;say it IS physical i/o
	movl	irp$l_obcnt(r3),ucb$obct(r5)	;store obcnt field
;	movl	irp$l_bcnt(r3),irp$l_obcnt(r3)	;and reset to actual
					; requested so driver NEVER sees
					; need to do postprocessing requeues
					; in host context. (we do that in OUR
					; context.)
	movl	irp$l_wind(r3),ucb$owind(r5)	;store window ptr
;	clrl	irp$l_wind(r3)		;make irp look "physical"
	movl	irp$l_segvbn(r3),ucb$osegv(r5)	;store segment vbn also
;	clrl	irp$l_segvbn(r3)	;and bash irp's
12$:	MOVZWL	UCB$W_UNIT(R5),-(SP)	; BUILD ADDRESS OF UCB STORE
	ASHL	#2,(SP),(SP)		; WITH OFFSER * 4
	.IF	DF,D$$BUG
	MOVAB	VD_UCBTBL,DBGDTA+60
	MOVAB	VD_FXS0,DBGDTA+64
	.ENDC
	MOVAB	VD_UCBTBL,-(SP)		; GET TBL BASE IN STACK
	ADDL2	(SP)+,(SP)		; NOW ADD BASE + OFFSET
	MOVL	R5,@(SP)+		; AND STORE UCB ADDRESS IN VD_UCBTBL
; (THIS ALLOWS US TO GET IT BACK...)
	MOVZWL	UCB$W_UNIT(R5),-(SP)	; BUILD ADDRESS OF ENTRY NOW
	MULL2	#VD_FXPL,(SP)		; MULTIPLY OFFSET BY SIZE OF ENTRY
	MOVAB	VD_FXS0,IRP$L_PID(R3)	;AND BASH PID IN IRP SO WE
				; GET BACK CONTROL AT VD_FIXSPLIT (VIA JSB)
				; WHEN HOST'S I/O IS DONE.
	ADDL2	(SP)+,IRP$L_PID(R3)	;SET TO ENTER IN CORRECT
					; UNIT'S ENTRY
	.IF	DF,D$$BUG
	MOVL	UCB$PPID(R5),DBGDTA+40	;SAVE OLD PID
	MOVL	IRP$L_PID(R3),DBGDTA+44	;AND NEW "PID"
	MOVZWL	IRP$W_STS(R3),DBGDTA+48 ;STATUS BYTE
	MOVL	#IRP$M_PHYSIO,DBGDTA+52	;AND PHYS I/O BIT
	.ENDC
	PUSHL	R5 ; save our UCB just in case...
	PUSHL	R4		; SAVE R4 AND R3 ALSO SINCE THEY'RE FORK
	PUSHL	R3		; CONTEXT.
	PUSHL	R2
	PUSHL	R1
	PUSHL	R0
	MOVL	UCB$HUCB(R5),R5		;NOW POINT AT HOST UCB OURSELVES
;
;;;
	MOVL	IRP$L_MEDIA(R3),R0	;GET LBN TO CONVERT
; Note that the host driver normally will get physical I/O addresses
; in this entry. Logical I/O is converted to physical in FDT
; routines for most drivers; the few exceptions inhibit conversion
; via IOC$CVTLOGPHY anyway. Therefore we must ALWAYS convert to
; physical.
	JSB	G^IOC$CVTLOGPHY		; LET THE EXEC DO IT
; Logical I/O... relocate it here
; Already adjusted the logical blk # earlier
	.if	df,d$$bug
	MOVL	IRP$L_MEDIA(R3),DBGDTA+56	;SAVE FOR DEBUG EXAM
	.endc
; next op may mess up some regs. Also we cannot access the packet once
; we give it to the host driver thus:
	JSB	G^EXE$INSIOQ		; INSERT PACKET INTO HOST'S QUEUE
	POPL	R0
	POPL	R1
	POPL	R2
	POPL	R3			;GET BACK FORK CONTEXT
	POPL	R4			; (R3, R4) IN CASE OUR CALLER NEEDS IT
	POPL	R5
; NOW HAVE OUR OWN UCB ADDRESS BACK
; WE Now have queued the work to the real driver. Since the
; I/O may have splits, just await done return and let the
; vd_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!
; just go return at low prio
	movl	#1,r0	; indicate success...
	RSB		; return...don't drop prio here
;	JMP	G^EXE$QIORETURN
402$:
	BRW	FEXL			;Else, branch to execute function.
;
;	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	VD_STARTIO		;START REQUEST OVER
	.if	df,d$$bug
DBGDTA:	.BLKL	6	;AREA TO HOLD DISPLAYS
	.BLKL	20
	.BLKL	20
	.BLKL	40
	.endc
VD_INT::
VD_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 VD: UCB address implicitly via local save and
; restore; each VD: 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 vd: 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 VD_UNITS IS 2 OR MORE.
V_UNIT=0
V_UNM=1
VD_FXS0::
	MOVL	#V_UNIT,R5
	BRW	VD_FIXSPLIT	;GO HANDLE
VD_FXPL==.-VD_FXS0	;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
	.MACRO	XVEC LBLC
VD_FXS'LBLC:
	MOVL	#V_UNIT,R5
	BRW	VD_FIXSPLIT
	.ENDM
	.REPEAT	<VD_UNITS-1>
	XVEC	\V_UNM
V_UNIT=V_UNIT+4		;PASS TO NEXT UNIT
V_UNM=V_UNM+1
	.ENDR
VD_FIXSPLIT:
; GET OLD PID..
; IN OUR UCB$PPID LONGWORD...
	.IF	NDF,VMS$V5
; assume ipl$_synch = 8
	DSBINT	ipl=#8		; GO TO FORK IPL
	.ENDC
; NOTE!!! PROBABLY NEEDS MODS FOR VMS V5!!!
	PUSHL	R5	;NEED TO WORK IN R5
	MOVAB	VD_UCBTBL,R5
	ADDL2	(SP)+,R5	;R5 NOW POINTS AT UCB ADDRESS
	MOVL	(R5),R5		;NOW HAVE OUR UCB ADDRESS IN R5
	.IF	DF,VMS$V5
	DEVICELOCK SAVIPL=-(SP)
	.ENDC
	.if	df,d$$bug
	movl	r5,dbgdta+72	;save ucb addr as flag we got here
	movl	ucb$l_irp(r5),dbgdta+76	;save irp addr also
	.endc
; now get back to fork level so our REQCOM processing will be correct again.
;	ADDL2	#UCB$FB1,R5		;POINT AT SECOND FORK BLK
;	FORK		;SYNCH IPL, BUT DON'T CLOBBER MAIN UCB
;	SUBL2	#UCB$FB1,R5		;POINT AT UCB AGAIN
	MOVL	UCB$L_IRP(R5),R3	; POINT R3 AT IRP AGAIN
	.if	df,d$$bug
	movl	r5,dbgdta+80	;save ucb addr as flag we got here
	movl	ucb$l_irp(r5),dbgdta+84	;save irp addr also
	movl	ucb$ppid(r5),dbgdta+88	;save old pid field also
	.endc
	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$obct(r5),irp$l_obcnt(r3)	;restore orig byte cnt
	movw	ucb$stats(r5),irp$w_sts(r3)	;restore orig function code
;	movl	ucb$owind(r5),irp$l_wind(r3)	;restore window pointer
;	movl	ucb$osegv(r5),irp$l_segvbn(r3)	;restore segment vbn also
15$:	CLRL	UCB$PPID(R5)	; ZERO SAVED PID FIELD FOR CLEANLINESS
	MOVL	R5,IRP$L_UCB(R3)	;RESTORE VD: 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 !!!
; 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 VD: I/O queue is emptied and VD: unbusied after all is done.)
	.if	df,x$$$dt
	jsb	G^ini$brk	; call XDELTA if x$$$dt defined
	nop
	nop
	.endc
	JSB	@#IOC$REQCOM	; GO COMPLETE THE I/O REQUEST IN VD: CONTEXT
; (OR DO I/O SPLIT NEXT PART IN VD: CONTEXT!)
; ALSO, RETURN **HERE**, SO WE CAN WRAP UP ALL ELSE.
	.IF	NDF,VMS$V5
	ENBINT		; RESTORE IPL TO IPL$_IOPOST
	.IFF
	DEVICEUNLOCK	NEWIPL=(SP)+
	.ENDC
	RSB	; GET BACK TO HOST SOMETIME
; BLOCK OF UCB ADDRESSES
VD_UCBTBL::
	.BLKL	VD_UNITS
	.LONG 0,0	;SAFETY
VD_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.END
-h- VDMAKE.COM
$set noon
$set verify
$macro asnvd+sys$library:lib.mlb/lib
$macro vddriverv3a+sys$library:lib.mlb/lib
$LINK/EXE=VDDRIVER.EXE/CONTIGUOUS VDDRIVERV3A,SYS$SYSTEM:SYS.STB/SELECT,-
 SYS$INPUT/OPTION
BASE=0
$
$! sysgen connect vda0:/noadapter/maxunits=1/driver=vddriver
$! is the sort of command needed to load vddriver...
$link asnvd+sys$system:sys.stb/sel
$! Note we must use SET COMMAND ASNVD to use the thing. Edit or
$! define the distribution directory logical.
$set noverify
