zzxxirp$q_qio_p1 = 1
xxirp$q_qio_p1 = 1
delayun=0 ; hang on io$_unload till i/o completed by daemon
	.TITLE	ZRDRIVER - AXP/VMS VIRT DISK DRIVER, step 2 try
	.IDENT	'V02-004A'
evax = 1
alpha=1
bigpage=1
addressbits=32
	.enable SUP
; Copyright (c) 1994 Glenn C. Everhart
; All rights reserved
; define clslop to close a possible abort shutdown timing window
;del$alot=0	;define to grab/release pool as needed
;roopn=0	;define to force r/o opens except of indexf.sys
;clslop=1
vms$v5=1	;define for vms v5.x
; Version with VMS V5 syncrhonization code updated
; Added code to allow host program to re-increment driver ref count once it gets
; disk "mounted". This is for use with things like cryptodisk that will run
; normally in a subprocess. A detached process, where the disk might be dismounted
; and remounted by other processes, should NOT use this. (Mount may refuse to mount
; the disk if it finds a ref count nonzero. INIT certainly won't work with nonzero
; ref count. However, for a cryptodisk in a subprocess, it's best to have the ref
; count correct so a subprocess deletion will not leave the ZR: unit unusable.)
;
; DEsigned and made to work in VMS V4 by Glenn Everhart
;	(Everhart%Arisia.decnet@crd.ge.com)
; Fix to get it working correctly in VMS V5 by Chris Ho
;	(Chris%skat.usc.edu@oberon.usc.edu)
; THANKS!, Chris!	- gce
; Edit 4/14/89 to ensure IRP$L_MEDIA field of IRP gets saved/restored
; before passing it to IOC$REQCOM from here. (Avoids some problems where
; ACP cache params are waaaay too low.)
;$$xdt=1
; Call to sch$postef seems to get thru with success indicator, but
; host process is messed up. Therefore drop back and use a documented
; system call!!!
;   As it turns out, the sch$postef call was not a problem. The code
; has been left in the driver but commented out. If it is used, then
; ZRDRIVER sets event flag 10 for its host process to signal that there
; is work for it. As is, the new call appears more useful.
;  The driver will use exe$wrtmailbox to write a message to a
; mailbox which must be created first by the host. The message
; will be the buffer header (so some extra reads on the driver can
; be avoided.)
;  It is assumed that VMS will allow the host to continue to communicate
; with this driver even during times while it is allocated to another
; process since the host process will have a channel open to this
; driver (though the channel count is buggered herein to not show this).
;  Should this fail, we may have to make ALL I/O take place between some
; mailboxes and the driver. (ecch.)
; 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, 3/23/1989
;USAPADDR=0
;
; FACILITY:
; 
; 	AXP/VMS VIRTUAL DISK DRIVER USING PROCESS SLAVE
; 
; 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 VIA PROCESSES.
;
; Note:
; ZR: stands for zRemote disk. It is developed from the VMS VD: driver
; which uses contiguous files, but adds some new wrinkles.
;  The idea here is that ZR: would look "just" like a real device, but
; instead of managing some piece of hardware to handle its' I/O it will
; use an internal buffer (just assembled into the device, and with
; the device maximum transfer set small enough to fit in the buffer) and
; communicate with a VMS process to fill or empty the buffer of data.
;   The driver will "look" normal, but:
;	* Its startio entry will move data between the user buffer
;		and the internal buffer (using logic we can get from
;		a memory disk). Once it has done this (and the maxtransfer
;		size will guarantee VMS will never ask for too much at
;		one go), it will set an event flag for  whatever
;		process is doing real I/O for the ZR: unit.
;		If no process has set itself up as the unit's "host"
;		we return an error.
;		A special QIO is established which will let the "host"
;		process grab data from the driver or send it to the
;		driver. The data will include block number, I/O direction,
;		length, etc. ahead of the actual data.
;		The process can then handle the request ANY WAY	IT WANTS.
;	* ONE FDT routine will be reserved. It will have several functions
;		governed by the first word of the argument. Only one is used
;		to allow the rest of the I/O functions to be left alone.
;	* One will just mark the unit
;		online, to be used to have the control process tell ZR:
;		that it's ready to roll. Optionally it will be able to tell
;		ZR: it's to go offline. One does this by returning a
;		zero size etc. It is ASSUMED that the "host" process
;		has an exit AST set up so that before it exits (even if
;		exit is by force-exit) it will tell ZRDRV that the host
;		process no longer exists. It should also complete I/O
;		on any outstanding requests if possible.
;	* The second FDT routine will do I/O completion. It will cause
;		ZR:, which should still be busy, to grab the current IRP
;		and go to fork IPL. At this point it can complete the
;		I/O normally in ZR: context, subsequently returning at
;		the prior IPL to get back to the attached process Ok too.
;		This routine is the only real "magic" here. It must save
;		and restore R3 and ucb$l_irp so that it can first finish
;		I/O on the user process' packet and then later
;		complete the host process' I/O. The context in an FDT
;		routine would normally not assume the host's IRP is
;		queued to the driver yet, so this should be OK. Also we
;		do all this at high IPL so we don't really lose the process
;		context.
;	* Another pair will copy data between the driver's per-unit data
;		buffer and the "host" process. Since FDTs execute in process
;		context, this is a very easy way to move the data. We can't
;		use it to connect to the processes doing I/O to the virtual
;		disk because the start-io entry lacks process context. We
;		do things the "hard" way there. By setting up ucb$l_maxbcnt
;		to the size of our internal buffer, we guarantee that the
;		code for doing virtual I/O will never issue a single QIO$
;		call to the start-io routine with a bigger buffer.
;
; The usefulness of such a beast over VD: is manifold:
;	1. There's a full process there! Compressing/decompressing
;	data on the fly is simple if that's wanted. Indexing it is
;	less so, but that's supposedly what ISAM files are good for.
;	Encrypting data is simple also.
;	2. By having a process that talks over a net to another process
;	on another machine, logical I/O on ZR: can be turned magically
;	into logical I/O somewhere else. Imagine having one of these
;	on LITTLEVAX:: and an ZR: talking to it on BIGVAX::. Now you
;	can mount ZR: on BIGVAX as a read/only device and run
;	BACKUP from ZR: to tape. This solves the problem of backup
;	across networks. Since the protocol is block number, Backup
;	(or restore) could be used (/physical I suppose in some cases)
;	to non-VMS file structures, if a network connection exists.
;	Note that the internal buffer size in ZRDRIVER limits the maximum
;	logical transfer size. This may mean that it also limits the
;	blocksize for a BACKUP saveset created from the "disk".
;	If one had no money, and preferred patching sys$grant_license
;	to put 1 in r0 and ret, one might just use a LAVC for this
;	remote function. This solution, though, allows remote backup
;	without paying DEC anything more, is clean, and will work over
;	a wider area than a LAVC. (Won't get you fired, either.)
;	It could even be used for physical image backups of foreign file
;	structures. (LAVC can't even THINK about that one!) It's not as
;	clean as a remoteable ACP or XQP (let me think about that one 
;	for a while) for file moving, but has many uses.
;	3. If the process just opens a big but non-contiguous file,
;	it can do ITS' I/O to the file. Thus ANY big file can be a
;	virtual disk, contiguous or not. Of course, the system throughput
;	you eat may be your own...
;		It's assumed up to the attaching process how the physical
;	disk layout is set.
;
;	Notice that a still lower level of disk emulator can be devised.
;	Such a thing must capture COMPLETE QIO$ information, starting
;	from the FDT level, and ship the entire QIO$, along with all
;	process parameters, across the net to be done on another machine.
;	Such a thing is usable for magtape, to make tape mounted anywhere
;	on a DECnet look local. More usefully, though, if it's done at the
;	QIO$ level prior to XQP material, the process on another computer
;	can act as a file manager as well as a disk manager. This means that
;	the remote computer maintains information on ALL file operations
;	on the remote disk, so that the remote disk can actually be mounted
;	for both read and write on another machine. This is akin to VAXclusters,
;	but is both more and less general: more general since the disks
;	being made virtually local in this way can be ANYWHERE on a wide
;	area DECnet, not merely local, and less general in that all file
;	operations are performed on one site, which then must absorb the
;	entire load of the remote as well as its' local file ACP/XQP
;	operations. It is possible, but much more difficult, to use
;	something along these lines to remote a foreign file structure also,
;	dynamically translating I/O requests.
;
;	The approach here is to provide first a remote-able virtual disk,
;	remoting only block I/O to a process (and hence possibly another
;	machine). Then remoting tape will be tackled. This would allow
;	a large machine to make a tape drive available for a satellite
;	and let the satellite absorb the CPU cycles Backup soaks up. (The
;	remoted disk approach would allow the larger machine to back up
;	the remote's disks, but it would then have to execute Backup on
;	its' own.) Once these can be made to work it will be possible to
;	further extend the remote interface to ACP calls. This will be
;	a little weird, but logical I/O will look as it does for the
;	remoted device (so directories would work). File calls would
;	be captured and done on behalf of the driver remotely. I am not
;	CERTAIN this should be bothered with (normal DECnet file access
;	has a lot in common with it), but it should, if done right, be
;	faster than a lot of FAL operations.
;
;	For simplicity in testing, the first server task for the disk
;	driver will just have a large memory array which it will use for
;	storage. Thus it will be a lower-performance memory disk driver
;	with that host. One driver process per unit of disk will be
;	assumed. The driver notifies the "host" process there is something
;	to do by setting event flag 10. The host process then can go
;	  [now sends message to a mailbox; event flag code commented.]
;	  [uncomment if you want this to go back.]
;	ahead and use its' special QIOs to get or send data. If the
;	I/O request is a write to the device, the ZR: driver will fill
;	in its' buffer as the request indicated. The "host"
;	process should read the buffer (header and data) ALL in and
;	then use the part of the data indicated in the header as valid,
;	ignoring the rest. By the time the host process runs, the data
;	is already in the driver's buffer, copied there by start_io.
;	If the request is a read, the host process is expected to fill
;	in the data in the driver's buffer AND the header (it should not
;	mess with direction or block number data but should fill in data
;	and the I/O status block info, which is used verbatim.) It then
;	issues a "finish-IO" QIO$ (one of its' special ones) to complete
;	that I/O. The event flag 10 should be cleared by the host process
;	after the initial setting and before this finish-io qio$, since
;	the finish-io qio$ COULD result in another setting of the flag.
;
;	To minimize work, the host process may issue a read on the driver
;	buffer to get just the header, then decide whether to read all
;	the data if it's handling a write request. The buffer header
;	first longword will be zero in the event of a read seen by ZR:
;	(indicates the process must do I/O, then fill the buffer) and
;	will contain 1 if the I/O is a write seen by ZR:, in which case
;	the buffer contains data. Given that the byte count is already
;	in the header, the host process can decide how much data to read
;	in that case, and need not read the entire buffer size.
;
;--
	.PAGE
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

; 
; EXTERNAL SYMBOLS
; 
	.library /ALPHA$LIBRARY:LIB/
	.NOCROSS
	$ACBDEF		; Define AST Control Block offsets.
;	$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
	$arbdef
	$fibdef
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$irpedef
	$ipldef
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$pcbdef
	$jibdef

	.IF	DF,VMS$V5	;VMS V5 + LATER ONLY
	$cpudef		;thanks to Chris Ho for V5 fix
	$SPLCODDEF
	.ENDC
	.CROSS
; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS

;.=UCB$L_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.
;The following must match the same-named data in the ACB extension
	.blkl	2	;safety
$DEF	UCB_L_UCB	.BLKL	1	;Save UCB address here
$DEF	UCB_L_MEMBUF	.BLKL	1	;Address of buffer for this transfer
$DEF	UCB_L_NSPTS	.BLKL	1	;Number of SPTs required for buffer
$DEF	UCB_L_SVPN	.BLKL	1	;Starting system page number
$DEF	UCB_L_ADRSPT	.BLKL	1	;Address of first SPT used
$DEF	UCB_L_SVABUF	.BLKL	1	;System virtual address of user buffer
;
$DEF	UCB$HPID	.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 IRP BLK
$def	ucb$irps	.BLKL	1	;IRP save area during host proc action
$def	ucb$smbx	.BLKL	1	;mailbox UCB for work notices
; Define save areas for UCB fields needed for I/O copies and used in
; driver to process copies here.
$def	ucb$lsvapte	.blkl 1    ;saves ucb$l_svapte
$def	ucb$lsts	.blkl 1     ;saves ucb$l_sts
$def	ucb$lsvpn	.blkl 1  ; similar
$def	ucb$wboff	.blkl 1  ; similar
$def	ucb$lmedia	.blkl	1
$def	ucb$irplmedia	.blkl	1	;irp$l_media save
$def	ucb$wdirseq	.blkl	1
$def	ucb$lbcr	.blkl	1
; 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$L_MEMBUF	.BLKL	1	; MEMORY AREA
$DEF	UCB$L_MEMBF	.BLKL	1	; MEMORY BUFFER FOR CONTROL PROCESS
$DEF	UCB$stats	.BLKL	1	;STATUS CODE SAVE AREA
$def	ucb$jiggery	.blkl	1	;adjust to refcnt to fix up
; 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 status
; we send to the host driver to look like physical I/O is being
; done and save the real function code here. Later when ZR: 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$l_blk	.blkl	1	;block i/o if nonzero
$def	ucb$l_ucbtbl	.blkl	1	;table of ucb addresses
;$def	ucb$l_bufpol	.blkl	1	;buffer addresses table
$def	ucb$l_ctlfgs	.blkl	1	;control flags
$def	ucb$l_sanity	.blkl	1	;sanity test
	.if	df,delayun
$def	ucb$l_unload	.blkl	1	;set nonzero for unload
	.endc
	.if	ndf,xcldbg
$def	ucb$l_misc	.blkl	20	;debug
$def	ucb$l_misc2	.blkl	40
	.endc
; (bit 1 set implies disallow create, delete, or extend)
$DEF	UCB$K_ZR_LEN	.BLKL	1	;LENGTH OF UCB
;UCB$K_ZR_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
; 
; Since driver has to use 8K more or less of nonpaged pool for every
; unit, only allow 4 units by default.
ZR_UNITS=40
; NOTE MAX TRANSFER FOR UCB NEEDS TO BE SET TO ZR_BUFSIZ!!!
; UCB$L_MAXBCNT FIELD!!!
ZR_BUFSIZ=8192.
ZR_BFH=20
	.if	df,adrhak
;optional hack: store buffer header address in last longword of the buffer.
;This will generally need to be cleared before i/o termination!
ZR_BFH=24
	.endc	
; BUFFER HEADER FORMAT: (all longwords)
;	Transfer direction (0=read, 1=write) as seen from ZR:, that is,
;			read means ZR: is reading data from control proc.
;	Block number
;	Byte Count in data area
;	IOSB longword 1
;	IOSB longword 2
;
; followed immediately by data area (so we can pass ONE address to the
;	control process.)
ZR_tdir=0	;transfer direction
ZR_blkn=4	;block number
ZR_bcnt=8	;bytecount
ZR_isb1=12	;IOSB longword 1
ZR_isb2=16	;IOSB longword 2
ZR_BFSZ=ZR_BUFSIZ+ZR_BFH		; BUFFER, PLUS EXTRA HDR INFORMATION
	DRIVER_DATA
ZR$DPT::
	DPTAB	-			;DPT CREATION MACRO
		END=ZR_END,-		;END OF DRIVER LABEL
		ADAPTER=NULL,-		;ADAPTER TYPE = NONE (VIRTUAL)
		DEFUNITS=2,-		;UNITS 0 THRU 1
		UCBSIZE=UCB$K_ZR_LEN,-	;LENGTH OF UCB
		flags=<DPT$M_NOUNLOAD!dpt$m_svp>,-	; allocate a perm. page for safety
		MAXUNITS=ZR_UNITS,-	;FOR SANITY...CAN CHANGE
		NAME=ZRDRIVER,-		;DRIVER NAME
		STEP=2,SMP=YES
; Note that perm. page is allocated because IOC$movtouser and ioc$movfruser
; need it.
VD_BLKSIZ=512
; SET THIS SIZE TO MATCH ZR_BUF SIZE
DMA_MAXSIZ      =ZR_bufsiz
	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$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
		<DEV$M_NNM>		; Prefix name with "node$" (like rp06)
	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
; ZR: 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 ZR: 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 the host proc 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$L_MAXBCNT,L,ZR_BUFSIZ ; MAX TRANSFER SIZE
	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$L_ERTMAX,L,10	;MAX ERROR RETRY COUNT
	DPT_STORE UCB,UCB$L_DEVSTS,L,-	;INHIBIT LOG TO PHYS CONVERSION IN FDT
		<UCB$M_NOCNVRT>		;...
        DPT_STORE UCB,UCB$L_MAXBCNT,L,DMA_MAXSIZ ;Maximum byte count per segment
;
; 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,ZR_INT  ;INTERRUPT SERVICE ROUTINE ADDRESS
;	DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,-  ;CONTROLLER INIT ADDRESS
;		      D,ZR_ctrl_INIT		  ;...
;	DPT_STORE CRB,CRB$L_INTD+VEC$L_UNITINIT,- ;UNIT INIT ADDRESS
;		      D,ZR_unit_INIT		  ;...
	DPT_STORE DDB,DDB$L_DDT,D,ZR$DDT	  ;DDT ADDRESS

	DPT_STORE END			;END OF INITIALIZATION TABLE

; 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
; 
; DRIVER DISPATCH TABLE
; 
; 	THE DDT LISTS ENTRY POINTS FOR DRIVER SUBROUTINES WHICH ARE
; 	CALLED BY THE OPERATING SYSTEM.
; 
;ZR$DDT:
	.if	df,xxirp$q_qio_p1
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=ZR,-		;NAME OF DEVICE
		START=ZR_STARTIO,-	;START I/O ROUTINE
		FUNCTB=ZR_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=ZR_CTRL_INIT,-
		UNITINIT=ZR_UNIT_INIT,-
;		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
;		REGDMP=0,-	;REGISTER DUMP ROUTINE
;		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		FAST_FDT=ACP_STD$FASTIO_BLOCK,- ;fastio suppor
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
	.iff
	DDTAB	-			;DDT CREATION MACRO
		DEVNAM=ZR,-		;NAME OF DEVICE
		START=ZR_STARTIO,-	;START I/O ROUTINE
		FUNCTB=ZR_FUNCTABLE,-	;FUNCTION DECISION TABLE
		CTRLINIT=ZR_CTRL_INIT,-
		UNITINIT=ZR_UNIT_INIT,-
;		CANCEL=0,-		;CANCEL=NO-OP FOR FILES DEVICE
;		REGDMP=0,-	;REGISTER DUMP ROUTINE
;		DIAGBF=0,-  ;BYTES IN DIAG BUFFER
		ERLGBF=0	;BYTES IN
				;ERRLOG BUFFER
	.endc
; 
; FUNCTION DECISION TABLE
; 
; 	THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; 	CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; 	PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
; 

;ZR_FUNCTABLE:
	FDT_INI	ZR_FUNCTABLE
	FDT_BUF -			;BUFFERED FUNCTIONS
		<NOP,- ;FORMAT,-	; noop,FORMAT
		FORMAT,-		;format
		UNLOAD,-		; UNLOAD
		PACKACK,-		; PACK ACKNOWLEDGE
		AVAILABLE,-		; AVAILABLE
		SENSECHAR,-		; SENSE CHARACTERISTICS
		SETCHAR,-		; SET CHARACTERISTICS
		SENSEMODE,-		; SENSE MODE
		SETMODE,-		; SET MODE
		ACCESS,-		; ACCESS FILE / FIND DIRECTORY ENTRY
		ACPCONTROL,-		; ACP CONTROL FUNCTION
		CREATE,-		; CREATE FILE AND/OR DIRECTORY ENTRY
		DEACCESS,-		; DEACCESS FILE
		DELETE,-		; DELETE FILE AND/OR DIRECTORY ENTRY
		MODIFY,-		; MODIFY FILE ATTRIBUTES
		MOUNT>			; MOUNT VOLUME
        .if     df,zzxxirp$q_qio_p1
        FDT_64  <-                              ; Functions supporting 64-bit a$
                AVAILABLE,-                     ; Available (rewind/nowait clea$
                NOP,-                           ; No operation
                PACKACK,-                       ; Pack acknowledge
                READLBLK,-                      ; Read logical block forward
                READVBLK,-                      ; Read virtual block
                SENSECHAR,-                     ; Sense characteristics
                SENSEMODE,-                     ; Sense mode
                SETCHAR,-                       ; Set characterisitics
                SETMODE,-                       ; Set mode
                UNLOAD,-                        ; Unload volume
                WRITECHECK,-                    ; Write check
                WRITEPBLK,-                     ; Write Physical Block (bogus)
                READPBLK,-                      ; Read Physical Block (bogus)
                WRITELBLK,-                     ; Write LOGICAL Block
                WRITEVBLK>                      ; Write VIRTUAL Block
        .endc
	FDT_ACT	TL_ZR_ACCS,<ACCESS>	; access file ;OK
	fdt_act	tl_ZR_crea,<CREATE>	; create file ;OK
	fdt_act tl_ZR_dele,<DELETE>	; delete file ;OK
	fdt_act tl_ZR_deac,<DEACCESS>	; deaccess file ;OK
	fdt_act tl_ZR_modi,<MODIFY>	; modify file ;OK
	fdt_Act tl_ZR_format,<FORMAT>	;point to host disk ;OK
	fdt_act crrmshd,<CRESHAD,REMSHAD> ;create/remove shadowset members
	.if	ndf,xxirp$q_qio_p1
; for initial version leave this alone. Should be OK but r/w must support
; 64 bit addresses.
	fdt_act tl_ZR_wrtpl,<writepblk,writelblk> ;write phys/log ;OK
	fdt_act tl_ZR_wrtvr,<writevblk> ; write virtual ;OK
	.iff
        FDT_ACT ACP_STD$WRITEBLK,-              ;WRITE FUNCTIONS
                <WRITELBLK,-            ; WRITE LOGICAL BLOCK
                WRITEPBLK,-
                >
        FDT_ACT ACP_STD$WRITEBLK,-              ;WRITE FUNCTIONS
                <WRITEVBLK-             ; WRITE VIRTUAL BLOCK
                >
	.endc
; The rest of these are standard stuff
	fdt_act acp_std$readblk,<readlblk,readpblk,readvblk> ;read virt ;OK
	fdt_act acp_std$modify,<acpcontrol> ;OK
	fdt_act acp_std$mount,<mount> ;OK
	fdt_act exe_std$lcldskvalid,<unload,available,packack>
	fdt_act exe_std$zeroparm,<retcenter,nop>
;	fdt_act exe_std$oneparm,<format>
	fdt_act exe_std$sensemode,<sensechar,sensemode>
;	fdt_act exe_std$setchar,<setchar>
;	fdt_act exe_std$setmode,<setmode>
;
; ZR: Buffer Pool
; Stores data for communication with host process
; BUFFER HEADER FORMAT: (all longwords)
;	Transfer direction (0=read, 1=write) as seen from ZR:, that is,
;			read means ZR: is reading data from control proc.
;	Block number
;	Byte Count in data area
;	IOSB longword 1
;	IOSB longword 2
;
; followed immediately by data area (so we can pass ONE address to the
;	control process.)
ZR_BUFPOOL::
	.LONG 0,0	;SAFETY
	.LONG 0,0	;SAFETY
; ucb table, used externally if we like to access all UCBs from this driver
ZR_UCBTBL:
	.rept	ZR_units
	.long	0
	.endr
	.long	0,0,0,0
	.LONG 0,0	;SAFETY
	.LONG 0,0	;SAFETY

	.PAGE
	.SBTTL FDT Routines
	DRIVER_CODE
;	functab	ZR_safrw,-
;		<CREATE,DELETE>
;	functab	ZR_safrwx,-
;		<MODIFY>
;	functab	ZR_safdeac,-
;		<DEACCESS>
; Top level routines
; tl_ZR_crea: create handling. Calls internal checker, then std
; checker.
tl_ZR_accs: $driver_fdt_entry
;	jsb	ZR_safac
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_access
;999$:
	ret
; Checks for access. If ucb$l_ctlflgs bit 1 is clear, do nothing.
; Otherwise junk the request.
; ZR_safrw entry is for create and delete. We know these can never
; be allowed, regardless. Therefore junk them if filtering.
; Also clear the truncate bit on close to ensure a remote site cannot
; truncate a file. These operations are what can alter the storage
; areas of the disk in ways that can corrupt the system readily.
; What remains to be allowed are various mods to the system.
; Use another bit in ctlfgs to disallow writes also. These we will
; disallow SILENTLY, pretending to succeed.
;
tl_ZR_crea: $driver_fdt_entry	;create toplevel fdt
;	jsb	ZR_safrw		
;	blbc	r0,999$
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_access
	ret
;999$:
;	call_finishioc do_ret=no	;error return...
;	ret
tl_ZR_dele: $driver_fdt_entry	;delete toplevel fdt
;	jsb	ZR_safrwd
;	blbc	r0,999$
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_acp_modify
	ret
;999$:
;	call_finishioc do_ret=no	;error return...
;	ret

ZR_safrw: .jsb_entry output=<r0>
;	movl	#1,r0
;	bitl	#1,ucb$l_ctlfgs(r5)	;are we to be in "safe" mode?
;	beql	999$			;if eql no, let req. by
;	MOVZBL  #SS$_ILLIOFUNC,R0	;else return an error
;	rsb
;999$:
	rsb
ZR_safrwd: .jsb_entry output=<r0>
;	movl	#1,r0
;; Let 8 bit mean delete suppression as well as 1 bit, so we can only
;; do delete control if desired, leaving other stuff alone.
;	bitl	#9,ucb$l_ctlfgs(r5)	;are we to be in "safe" mode?
;	beql	999$			;if eql no, let req. by
;	MOVZBL  #SS$_ILLIOFUNC,R0	;else return an error
	rsb
;999$:
;	rsb
; ZR_safrwx entry is for io$_modify. If this is being used to extend
; a file or truncate a file, disallow the operation.
;
; safac - conditionally built. Forces all opens to r/o mode except
; the index file if selected.
ZR_safac: .jsb_entry output=<r0>
	movl	#1,r0
	.if	df,roopn
	bitl	#2,ucb$l_ctlfgs(r5)	;in /nowrite mode?
	beql	999$			;no, skip
; allow access but force read-only mode
	movl	irp$l_qio_p1(r3),r0	;get fib
	ifnord #4,4(r0),999$
	movl	4(r0),r0	;...from descriptor
	ifnord #4,fib$l_acctl(r0),999$
	cmpw	#1,fib$w_fid(r0)	;indexf.sys always ok
	beql	999$
	bicl	#fib$m_write,fib$l_acctl(r0)	;force r/o access
999$:
	.endc
	rsb
tl_ZR_modi: $driver_fdt_entry	;modify toplevel fdt
;	jsb	ZR_safrwx
;	blbc	r0,999$
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_acp_modify
	ret
;999$:
;	call_finishioc do_ret=no	;error return...
;	ret

;	fdt_act tl_ZR_wrtpl,<writepblk,writelblk> ;write phys/log
tl_ZR_wrtpl: $driver_fdt_entry
;	jsb	shdwck
;	blbc	r0,99$
; if faking writes succeeds, do the write
;	jsb	ZR_safwt
;	blbs	r0,80$
; looks ok, do normal call
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_writeblk
	ret
;99$:	MOVZBL  #SS$_ILLIOFUNC,R0
;80$:	call_finishioc do_ret=no	;error return...
;	ret
tl_ZR_wrtvr: $driver_fdt_entry
;	jsb	shdwck
;	blbc	r0,99$
; if faking writes succeeds, do the write
;	jsb	ZR_safwtv
;	blbs	r0,80$
; looks ok, do normal call
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_writeblk
	ret
;99$:	MOVZBL  #SS$_ILLIOFUNC,R0
;80$:
;	.if	ndf,xcldbg
;	movl	r3,ucb$l__misc+00(r5)
;	.endc
;	call_finishioc do_ret=no	;error return...
;	ret

ZR_safrwx: .jsb_entry output=<r0>
;	movl	#1,r0
;	bitl	#1,ucb$l_ctlfgs(r5)	;are we to be in "safe" mode?
;	beql	999$			;if eql no, let req. by
;; check if the user really wants to extend.
;	bitw	#^xFFC0,irp$l_func(r3) ;this a movefile or other modifier?
;	bneq	990$		;if so disallow that. Movefile should not be
;				; permitted from the remote.
;	movl	irp$l_qio_p1(r3),r0	;get fib
;	ifnord #4,4(r0),990$
;	movl	4(r0),r0	;...from descriptor
;	ifnord #4,fib$w_exctl(r0),990$
;	bitw	#<fib$m_extend!fib$m_trunc>,fib$w_exctl(r0)	;extending at all?
;	beql	999$			;if no extend, leave fib alone
;; Any extend must be disallowed if this is a remote mount.
;990$:	MOVZBL  #SS$_ILLIOFUNC,R0	;else return an error
;;	jmp	g^exe$finishioc		;error return...
	rsb
;999$:
;	movl	#1,r0
;	rsb
tl_ZR_deac: $driver_fdt_entry	;deaccess toplevel fdt
;	jsb	ZR_safdeac
;	blbc	r0,999$
;add an extra call to get to the std routine.
;	movl	#1,r0
	call_deaccess
	ret
;999$:
;	call_finishioc do_ret=no	;error return...
;	ret
; For file deaccess, just zero the bits that say to truncate the file
; so we don't, in protected mode, try to truncate. 
ZR_safdeac: .jsb_entry output=<r0>
;	bitl	#1,ucb$l_ctlfgs(r5)	;are we to be in "safe" mode?
;	beql	999$			;if eql no, let req. by
;; check if the user really wants to truncate
;	movl	irp$l_qio_p1(r3),r0	;get fib
;	ifnord #4,4(r0),999$
;	movl	4(r0),r0	;...from descriptor
;	ifnord #4,fib$w_exctl(r0),999$
;	bicw	#<fib$m_extend!fib$m_trunc>,fib$w_exctl(r0)	;extending at all?
;	brb	999$	; Force deaccess not to truncate
;; Disallow truncate on close, but allow the close to take place.
;;990$:	MOVZBL  #SS$_ILLIOFUNC,R0	;else return an error
;;;	jmp	g^exe$finishioc		;error return...
;999$:
;	movl	#1,r0
	rsb
ZR_safwt: .jsb_entry output=<r0>
;	bitl	#2,ucb$l_ctlfgs(r5)	; are we faking writes?
;	beql	999$			; if eql no, leave them alone
;	clrl	-(sp)			; else assemble an i/o status in mem.
;	movw	irp$l_bcnt(r3),2(sp)	; with the byte count
;	movw	#ss$_normal,(sp)
;	movl	(sp)+,r0		; get the status
;;	call_finishioc do_ret=no	;error return...
	rsb
;999$:
;	movl	#2,r0
;	rsb
ZR_safwtv: .jsb_entry output=<r0>
;	bitl	#2,ucb$l_ctlfgs(r5)	; are we faking writes?
;	beql	999$			; if eql no, leave them alone
;; allow write to indexf.sys
;	movl	irp$l_qio_p1(r3),r0	;get fib
;	ifnord #4,4(r0),999$
;	movl	4(r0),r0	;...from descriptor
;	ifnord #4,fib$w_fid(r0),999$
;	cmpw	#1,fib$w_fid(r0)	; indexf.sys is fid 1,1,0
;	beql	999$			; if that's it, allow access
;	clrl	-(sp)			; else assemble an i/o status in mem.
;	movw	irp$l_bcnt(r3),2(sp)	; with the byte count
;	movw	#ss$_normal,(sp)
;	movl	(sp)+,r0		; get the status
;;	call_finishioc do_ret=no	;error return...
	rsb
;999$:
;	movl	#2,r0
;	rsb
; shadow write check. Only allow write to shadowset members if privd.
;
shdwck: .jsb_entry output=<r0>
	bbs	#dev$v_shd,ucb$l_devchar2(r5),10$	;shadow set member?
	movl	#1,r0
	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
	movl	#1,r0
	rsb
99$:	MOVZBL  #SS$_ILLIOFUNC,R0
;	call_finishioc do_ret=no	;error return...
	rsb
;	ret
;
; ; Dummy create/remove shadow set member functions.
; Lets VMS do all the actual work. Just does the dispatching here.
;
crrmshd: $driver_fdt_entry fetch=yes
	MOVL    G^EXE$GL_HBS_PTR,R0	;get host based shadowing pointer
	bgeq	10$			;if not filled in, forget it.
	pushl	r6
	pushl	r5
	pushl	r4
	pushl	r3
	calls	#4,(r0)		;step2 call seq
	ret
10$:	MOVZBL  #SS$_ILLIOFUNC,R0	;illegal if no dispatcher
11$:	call_finishioc do_ret=no	;error return...
	ret
;++
;
; ZR_format - point to proper location on the host disk, finish I/O,
;		and other random control functions.
;
; With no function modifiers, this routine takes as arguments a buffer
; containing information on the desired function. This allows one
; QIO$ function to be usurped for use in communicating with a "host"
; process, rather than several. The FDT routines of the driver are
; used since they conveniently have access both to the driver's
; internal buffers AND to the "host" process' address space.
;
; This routine does virtually no checking, so the parameters must be
; correct.
;
; Inputs:
;	r3 - IRP address
;	p1 - pointer to buffer. The buffer has the following format:
;		Longword 0 - index of function to handle. 0 = declare
;				process (set up for a process to handle
;				driver's actual work). 1= finish I/O.
;				2=copy data to driver buffer from control
;				process. 3=copy data to control process
;				from driver buffer for this unit.
;  Add code so that if longword 0 is 10 we increment the ref count again.
;	If longword 0 is 0, the rest of the buffer has the following
;			meanings:
;	     longword 1 - PID of current process, as flag we're turning on
;			  or zero to disable the disk
;	     longword 2 - Max number blocks for this disk
;	     longword 3 - UCB address of mailbox to be sent messages
;		longwords 4,5,6=number tracks,sectors,cylinders if conditional
;			is not set no$phy$geo
;
;	p2 - size of the above buffer
;--
p1=0	; first QIO param
p2=4	; second QIO param
TL_ZR_format: $driver_fdt_entry
	bicl3	#io$m_fcode,irp$l_func(r3),r0	;mask off function code
	bneq	20$			;branch if modifiers, special
	movl	#ss$_illiofunc,r0
	call_abortio do_ret=no
	ret
; clean up stack from writechkr, then return error to our caller.
10$:
	movl	(sp)+,r5		; restore regs
	movl	(sp)+,r3
; r0 already is error status
;	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe$abortio
20$:
	movl	irp$l_qio_p1(r3),r0		;buffer address
	movl	irp$l_qio_p2(r3),r1		;length of buffer
	pushl	r3
	pushl	r5
	call_writechkr
;	jsb	g^exe_std$writechk	;read access? doesn't return on error
	blbs	r0,21$			;if ok, branch
	brb	10$			; if bad, clean stack & abort i/o
21$:
	movl	(sp)+,r5		;get regs back
	movl	(sp)+,r3
;	clrl	irp$l_bcnt(r3)		;paranoia, don't need to do this...
	movl	irp$l_qio_p1(r3),r0		;get buffer address
	.if	ndf,xcldbg
	movl	r3,ucb$l_misc+04(r5)
	.endc
	tstl	(r0)			; this a setup access?
	beql	82$
	brw	finio
82$:
;	bneq	finio			; if not, go finish I/O
; If this is declare-io, the hlbn field is meaningless...never used.
;	movl	(r0)+,-			;move starting lbn
;		ucb$hlbn(r5)
;	blss	40$
	tstl	(r0)+			;pass the initial word
	.if	df,clslop
	clrl	ucb$l_blk(r5)		;clear blocking field
	.endc
	movl	(r0)+,-			;host pid (flag)
		ucb$HPID(r5)
;	bleq	10$			; ok to zero this really
	movl	(r0),ucb$l_maxblock(r5)	; size of disk
	movl	(r0),r1			; get size
; Note this is the only place ZRDRIVER cares about physical drive
; layout, assuming 64 sectors/track and 1 track/cylinder
; To remove this dependency define the conditional
	ashl	#-6,r1,r1		; divide by blocks/cyl to get cyls
	; (have to use a genuine divide for blk/cyl not a power of 2!)
	movw	R1,ucb$w_cylinders(R5)	; Store cylinders in volume also
; N.B. - must change this if you change physical form factor!!!
	movl	(r0)+,ucb$hfsz(r5)	; store twice
	beql	40$		; zero is not valid
	movl	(r0)+,ucb$smbx(r5)	; store UCB address of mailbox unit
	beql	40$			; zero is NOT valid.
	.if     ndf,no$phy$geo  ;if defined, means no physical geometry
				;handled within ZRDRIVER
; Get physical geometry from caller's buffer.
	tstb	(r0)			; look like geometry stuff is here?
	beql	41$			; no, use defaults here already
	tstb	4(r0)			;make sure we have all
	beql	41$			;if no sectors/trk, scram
	tstw	8(r0)			;got cylinders?
	beql	41$			;zero cylinders also illegal
; Can now comment out tests below since we already know they're nonzero.
	movb	(r0),ucb$b_tracks(r5)	;save no. tracks
;	beql	40$			;0 illegal but go ahead & use default
	tstl	(r0)+			;pass track #
	movb	(r0),ucb$b_sectors(r5)	;save no. sectors/track
;	beql	40$			; 0 illegal
	tstl	(r0)+
	movw	(r0),ucb$w_cylinders(R5) ; Store cylinders in volume also
;	beql	40$
	.endc
; Note that setting zero size means we go offline. Host process
; should do this before exiting!!!
41$:
	clrl	ucb$ppid(r5)		; mark driver free of old pids
	bisl	#ucb$m_valid,ucb$l_sts(r5)	;set volume valid
	bisl	#ucb$m_online,ucb$l_sts(r5)	;set unit online
; Must decrement the ref count to allow the host process to SHARE
; this device with services like init, mount, etc., which check
; this. 
	decl	ucb$l_refc(r5)			;decrement ref count
	movl	#1,ucb$jiggery(r5)		;add 1 to fix
	bgeq	38$				;if non negative, ok
	clrl	ucb$jiggery(r5)			;or add 0 if we went neg
	clrl	ucb$l_refc(r5)			;if we went neg, clear it to 0
38$:
	.if	ndf,xcldbg
	movl	r3,ucb$l_misc+08(r5)
	.endc
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=yes	;error return...
40$:
	.if	ndf,xcldbg
	movl	r3,ucb$l_misc2+52(r5) ; flag if we ever get here
	.endc
	bicl	#ucb$m_valid,ucb$l_sts(r5)	;set volume invalid
	bicl	#ucb$m_online,ucb$l_sts(r5)	;set unit offline
	addl2	ucb$jiggery(r5),ucb$l_refc(r5)	;re-increment ref count
	clrl	ucb$jiggery(r5)
;	incl	ucb$l_refc(r5)			;re-increment ref count
;undoes the decrement just above, so that the deassign service can
; totally free this device as needed.
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=yes	;error return...
;
; Finio
;  Complete current I/O
; Call buffer like ZR_format
;	Buffer:
;		Flag 0=setup, 1= finish I/O
;		Function (0=read, 1=write)
;		Block #	(1 longword)
;		Bytes in buffer
;		I/O status (normally 1 but can vary)
;		2 longwords
; (Assumes the process has already moved the data to the driver's
;  buffer...needs cmkrnl)
bumpctj:	jmp	bumprefc	;re-increment ref count
hdcopyj:	jmp	hdcopy
dhcopyj:	jmp	dhcopy
hdcopyk:	jmp	hdcopyd
dhcopyk:	jmp	dhcopyd
finio:
	.if	ndf,xcldbg
; keep current and last 5 codes so we get an idea what happened
	movl	ucb$l_misc+28(r5),ucb$l_misc+32(r5) ;keep last code and this
	movl	ucb$l_misc+24(r5),ucb$l_misc+28(r5) ;keep last code and this
	movl	ucb$l_misc+20(r5),ucb$l_misc+24(r5) ;keep last code and this
	movl	ucb$l_misc+16(r5),ucb$l_misc+20(r5) ;keep last code and this
	movl	(r0),ucb$l_misc+16(r5) ;save irp and buffer word
	movl	r3,ucb$l_misc+12(r5)
	.endc
	cmpl	(r0),#1			; This a finish-IO call?
	beql	10$			; if eql yes
; Insert additional chains of logic here...
; For example we may want to use this "I/O" as a way to get data
; copied to/from the control task. Since it's entered in the context
; of the control task, it's a VERY convenient way to get data between
; the control task and driver. We must however roll our own data moving
; (to a degree anyway) between user task and driver (user task=the one
; that thinks this is a disk and is accessing it that way!).
	cmpl	(r0),#2		; copy data from process to driver?
	beql	hdcopyj		; yes, go do it.
	cmpl	(r0),#3		; copy data from driver to process?
	beql	dhcopyj		; yes, go do it.
	cmpl	(r0),#4		; copy data from process to driver?
	beql	hdcopyk		; yes, go do it.
	cmpl	(r0),#5		; copy data from driver to process?
	beql	dhcopyk		; yes, go do it.
	cmpl	(r0),#10	; 10 to re-increment ucb$l_refc
	beql	bumpctj		; if that's function, go do it.
; not legal... signal error.
8$:	movzwl	#SS$_BADPARAM,r0	;illegal parameter
	.if	ndf,xcldbg
	movl	r3,ucb$l_misc+36(r5)
	.endc
	clrl	r1
	call_abortio do_ret=no
	ret
118$:	movzwl	#SS$_SYNCH,R0
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe_std$abortio
10$:
	.if	ndf,xcldbg
	movl	r3,ucb$l_misc+40(r5)
	.endc
; Now actually finish up the I/O...
	tstl	ucb$irps(r5)	; make sure there IS an IRP in the works
	beql	118$		; if not then exit here before any damage
; Save the host proc status
	pushl	r6
	movl	ucb$l_membf(r5),r6	; get memory header again
	bgeq	6501$
	addl	#ZR_isb1,r6		; point at IOSB first longword data
	tstw	16(r0)		;any user i/o status there?
	beql	6501$		;if eql no
	movw	16(r0),(r6)	;else return status
; also return byte count if none was there.
	tstw	2(r6)		; a bytecount there already?
	bneq	6501$		; if neq yes, leave it alone
	movw	12(r0),2(r6)	; else fill it in now.
6501$:
	popl	r6
; now finish off the irp for the virtual device that signals we
; should finish the saved one also.
	movl	irp$l_bcnt(r3),r0	;byte count
	ashl	#16,r0,r0		;move up
	incl	r0			;add normal status
; now R0 is proper for status
	clrl	r1
	movl	r0,irp$l_iost1(r3)
	movl	r1,irp$l_iost2(r3)	;set status
; duplicate finishioc logic
; BUT omit RET.
        FORKLOCK UCB$B_FLCK(R5),-       ;LOCK FORK THREADS/USE IPL OF LOCK
                PRESERVE=NO             ; DON'T PRESERVE R0
        INCL    UCB$L_OPCNT(R5)         ;INCREMENT OPERATIONS COMPLETED
        MOVZWL  S^#SS$_NORMAL,R0        ;SET NORMAL COMPLETION STATUS

60$:    ; Make these I/O's complete synchronously by using per-CPU queue

        find_cpu_data R1                ; Get per-CPU database address
        INSQUE  (R3),@CPU$L_PSBL(R1)    ; INSQUE into Q for this CPU.
        SOFTINT #IPL$_IOPOST            ; Signal I/O post interrupt

        FORKUNLOCK UCB$B_FLCK(R5)       ;UNLOCK FORK THREADS/USE SAME IPL
; Now fork to get to the correct stack and IPL for further
; I/O completion.
;	Original pri0 thread returns, since stack is clean.
;
; This completes the client's I/O and hopefully does no double forking.
;fake up stack so that we fork BUT return after the fork to get back to
; the qio return code below.
;
	fork	frkprc,nonfk,environment=call
nonfk:
; fake qioreturn call. We don't have device lock here, so no need
; to release it...
	movl	#ss$_fdt_compl,r0	;done this fdt
	setipl ipl=#0,environ=UNIPROCESSOR	;drop ipl...
	ret
;	jmp	g^exe$qioreturn		;return all's well
;
; IN THIS FORK, WE SHOULD BE NOW AT FORK IPL AND HOLDING ANY NEEDED
; FORK LOCKS.
; NOTE THAT WE MUST *NOT* CALL IOC$REQCOM AT HIGHER THAN FORK IPL
frkprc:	fork_routine,environment=call
; Now forked to get to fork IPL and onto the interrupt stack, and then
; complete the client's I/O whose address was saved earlier.
;
	movl	r3,-(sp)
	.if	ndf,xcldbg
	movl	ucb$l_misc2+60(r5),ucb$l_misc2+64(r5) ;save last 2
	movl	ucb$irps(r5),ucb$l_misc2+60(r5)
;
	.endc
	movl	ucb$irps(r5),r3	; get IRP from I/O that start-io was doing
	bneq	48$
	jmp	dunfrk
48$:
;	beql	dunfrk
	clrl	ucb$irps(r5)	; zero to avoid going thru twice!
	CLRL	UCB$PPID(R5)	; ZERO SAVED PID FIELD FOR CLEANLINESS
	movl	r3,ucb$l_irp(r5)	; save in UCB for now also
; now the UCB is set for finishing off this IRP
; Ghod knows what registers are needed, so save a whole bunch of them.
	movl  ucb$lsvapte(r5),ucb$l_svapte(r5) ;restore other ucb fields
	movl  ucb$lsts(r5),ucb$l_sts(r5)
	movl  ucb$lsvpn(r5),ucb$l_svpn(r5)
	movl  ucb$wboff(r5),ucb$l_boff(r5)
	movw	ucb$wdirseq(r5),ucb$w_dirseq(r5)
		;;;;;	movl	ucb$lmedia(r5),ucb$l_media(r5)
		;;;;	movl	ucb$lbcr(r5),ucb$l_bcr(r5)	;restore fields
	pushr	#^m<r2,r3,r4,r5,r6>
; Now all registers are free for our messups
; First, if data needs to be moved to user process, go move it!!
	movl	ucb$l_membf(r5),r6	; buffer header
	bgeq	13$
	tstl	(r6)+			; if zero, a read was posted
; on a read, we need to get the data moved. On a write we did it in startio.
	bneq	13$			; if not eql, branch; it was a write
	tstl	(r6)+		; pass block number
	movl	(r6)+,r1	; bytes to move
	movl	ucb$l_membuf(r5),r2	; disk buffer memory address
; protect regs from movtouser
	pushr	#^m<r3,r4,r5>
	tstl	ucb$l_svapte(r5)	;ensure this exists
	beql	11$			;if it doesn't, scram out NOW
				; and don't generate crash.
;
	jsb	movtouser	; go move the data to user memory (client)
;
11$:
	popr	#^m<r3,r4,r5>
13$:
	.if	ndf,xcldbg
	movl	ucb$l_membf(r5),r6	; get memory header again
	movl	(r6),ucb$l_misc+44(r5) ;r/w flag
	movl	r3,ucb$l_misc+48(r5) ;and irp
	.endc
	movl	ucb$l_membf(r5),r6	; get memory header again
	bgeq	515$	;(should never have to branch but be safe)
	addl	#ZR_isb1,r6		; point at IOSB first longword data
; Before return to our caller, fill buffer start with a flag word
; This allows the host process to check for possible race conditions.
; Once we start I/O, this will contain 0 or 1.
	movl	ucb$l_membf(r5),r2	;get memory buffer address
	movzbl	#255,(r2)		; set it to 255 as flag nothing's there
;
; 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$stats(r5),irp$l_sts(r3)	;restore orig function code
15$:	CLRL	UCB$PPID(R5)	; ZERO SAVED PID FIELD FOR CLEANLINESS
; GRAB R0 AND R1 AS REQCOM IN HOST DRIVER LEFT THEM...
; Get back IOSB data. We get it out of the buffer header area where we
; PRESUME the host process left it. It should reflect the actual
; I/O completion status, which we are simply passing along to the
; process that things (snigger) that ZR: is a disk!
; Driver initializes this to success, so a normal write from client to
; ZR: to host will not have to have host write to ZR: to set I/O status
; unless something goes wrong.
	movl	(r6)+,r0		; r0
	.if	df,clslop
	cmpw	r0,#ss$_accvio
	bneq	115$			;if not a fatal err in host proc, cont
	incl	ucb$l_blk(r5)		;if error seen, set the blocking flag
115$:
	.endc
	movl	(r6),r1			; and r1
515$:
;		- GCE
; Now restore IRP$L_MEDIA as saved at start of I/O here.
	movl	ucb$irplmedia(r5),irp$l_media(r3)
; (This avoids some potential problems during error paths in ioc$reqcom)
;
; Now go REALLY complete the I/O (possibly causing more I/O and certainly
; ensuring the ZR: I/O queue is emptied and ZR: unbusied after all is done.)
; Do the request COMPLETION on the packet, but via JSB so we can get back
; and restore IPL and synchronization to where we started it.
;
	.if	df,del$alot
; If this block is included we will deallocate pool a lot, grabbing
; it as needed. This might be needed for very dynamic situations with
; LOTS of these disk units around, to avoid hanging onto really
; enormous amounts of pool, but will slow down operation by requiring
; continual reallocation. Skip the deallocation in here at any rate
; if we have something more in our i/o queue...
	tstw	ucb$w_qlen(r5)	;our queue length +?
	bgtr	615$		;if so just leave the buffer allocated.
	pushr	#^m<r0,r1,r2,r3>
; deallocate the memory buffer area now. This will guard against any race
; conditions with i/o splits, recalls, etc.
	movl	ucb$l_membf(r5),r0	;where to free
	bgeq	414$			; if illegal forget it
	movl	#<ZR_bufsiz+ZR_bfh>,r1	;size needed
	jsb	g^exe$deanonpgdsiz	;free the space
414$:	clrl	ucb$l_membf(r5)		;flag buffer gone
	clrl	ucb$l_membuf(r5)
	popr	#^m<r0,r1,r2,r3>
615$:
	.endc
	call_reqcom
;	JSB	g^IOC$REQCOM	; GO COMPLETE THE I/O REQUEST IN ZR: CONTEXT
;
; (OR DO I/O SPLIT NEXT PART IN ZR: CONTEXT!)
; ALSO, RETURN **HERE**, SO WE CAN WRAP UP ALL ELSE.
; Now get back our registers and the "host" process' IRP and finish that
; I/O up also like a good FDT routine!
;
	popr	#^m<r2,r3,r4,r5,r6>
; now back to normal prio and out...
; Fork dispatcher will handle IPL etc. for us.
dunfrk:
	movl	(sp)+,r3	;restore bashed r3
        movzwl  #ss$_normal,r0
	ret
hdcopy:
; Copy data from process to driver's buffer.
; 1st param is buffer addr
; 2nd param is length of data to move. We assume this is the whole
;   data buffer INCLUDING the header.
	tstl	(r0)+		; pass function header
	movl	(r0)+,r1	; grab address in program
	movl	(r0)+,r0	; grab number of bytes to move
	cmpl	r0,#<ZR_bufsiz+ZR_bfh>	;ensure length is OK
	blequ	1$		; if ok, go ahead and copy
3$:	movzwl	#SS$_OFFSET_TOO_BIG,r0	;illegal parameter
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe$abortio
1$:
	tstl	r0		; no zero length either
	beql	3$		; to avoid other ills
; We're at ASTDEL here, so can fault if we need to.
; Therefore use Movc3 to do the move.
	pushl	r5
	pushl	r4	;preserve some regs
	pushl	r3
	pushl	r2
	movl	ucb$l_membf(r5),r2	;get memory buffer address
	bgeq	7$
	movc3	r0,(r1),(r2)		;do the copy
7$:
	popl	r2
	popl	r3
	popl	r4
	popl	r5			;get regs back
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=no
	ret
;	jmp	g^exe$finishioc			;wrap things up.
dhcopy:
; Copy data from driver's buffer to process
; 1st param is buffer addr in process
; 2nd param is length of data to move. We assume this is the whole
;   data buffer INCLUDING the header.
	tstl	(r0)+		; pass function header
	movl	(r0)+,r1	; grab address in program
	movl	(r0)+,r0	; grab number of bytes to move
	cmpl	r0,#<ZR_bufsiz+ZR_bfh>	;ensure length is OK
	blequ	61$		; if ok, go ahead and copy
63$:	movzwl	#SS$_offset_too_big,r0	;illegal parameter
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe$abortio
61$:
	tstl	r0		; no zero length either
	beql	63$		; to avoid other ills
; We're at ASTDEL here, so can fault if we need to.
; Therefore use Movc3 to do the move.
	pushl	r5
	pushl	r4	;preserve some regs
	pushl	r3
	pushl	r2
	movl	ucb$l_membf(r5),r2	;get memory buffer address
	bgeq	7$
	movc3	r0,(r2),(r1)		;do the copy
7$:	popl	r2
	popl	r3
	popl	r4
	popl	r5			;get regs back
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=no
	ret
;	jmp	g^exe$finishioc			;wrap things up.
; Data copy routines... exactly like full copy but they skip the
; ZR_bfh byte header. This can be used to avoid extra data copies in the
; host process.
hdcopyd:
; Copy data from process to driver's buffer.
; 1st param is buffer addr
; 2nd param is length of data to move. We assume this is the whole
;   data buffer INCLUDING the header.
	tstl	(r0)+		; pass function header
	movl	(r0)+,r1	; grab address in program
	movl	(r0)+,r0	; grab number of bytes to move
	cmpl	r0,#<ZR_bufsiz>	;ensure length is OK
	blequ	1$		; if ok, go ahead and copy
3$:	movzwl	#SS$_OFFSET_TOO_BIG,r0	;illegal parameter
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe$abortio
1$:
	tstl	r0		; no zero length either
	beql	3$		; to avoid other ills
; We're at ASTDEL here, so can fault if we need to.
; Therefore use Movc3 to do the move.
	pushl	r5
	pushl	r4	;preserve some regs
	pushl	r3
	pushl	r2
	movl	ucb$l_membuf(r5),r2	;get memory buffer address
	bgeq	7$
	movc3	r0,(r1),(r2)		;do the copy
7$:	popl	r2
	popl	r3
	popl	r4
	popl	r5			;get regs back
	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=no
	ret
;	jmp	g^exe$finishioc			;wrap things up.
dhcopyd:
; Copy data from driver's buffer to process
; 1st param is buffer addr in process
; 2nd param is length of data to move. We assume this is the whole
;   data buffer INCLUDING the header.
	tstl	(r0)+		; pass function header
	movl	(r0)+,r1	; grab address in program
	movl	(r0)+,r0	; grab number of bytes to move
	cmpl	r0,#<ZR_bufsiz>	;ensure length is OK
	blequ	61$		; if ok, go ahead and copy
63$:	movzwl	#SS$_offset_too_big,r0	;illegal parameter
	clrl	r1
	call_abortio do_ret=no
	ret
;	jmp	g^exe$abortio
61$:
	tstl	r0		; no zero length either
	beql	63$		; to avoid other ills
; We're at ASTDEL here, so can fault if we need to.
; Therefore use Movc3 to do the move.
	pushl	r5
	pushl	r4	;preserve some regs
	pushl	r3
	pushl	r2
	movl	ucb$l_membuf(r5),r2	;get memory buffer address
	bgeq	7$
	movc3	r0,(r2),(r1)		;do the copy
7$:	popl	r2
	popl	r3
	popl	r4
	popl	r5			;get regs back
brfcd:	movzwl	#ss$_normal,r0			;success
	call_finishioc do_ret=no
	ret
;	jmp	g^exe$finishioc			;wrap things up.
bumprefc:
	addl2	ucb$jiggery(r5),ucb$l_refc(r5)
	clrl	ucb$jiggery(r5)
;	incl	ucb$l_refc(r5)		;re-increment reference count
	brb	brfcd			; then return success

	.SBTTL	CONTROLLER INITIALIZATION ROUTINE
; ++
; 
; ZR_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.
;--
	.if	df,$$xdt
ZR_xdt:	.long	0
	.endc
				;ZR CONTROLLER INITIALIZATION
ZR_ctrl_INIT: $driver_ctrlinit_entry
	CLRL	CRB$L_AUXSTRUC(R8)	; SAY NO AUX MEM
	.if df,$$xdt
	clrl	ZR_xdt
	.endc
        movzwl  #ss$_normal,r0
	Ret
	.PAGE
	.SBTTL	UNIT INITIALIZATION ROUTINE
;++
; 
; ZR_unit_INIT - UNIT INITIALIZATION ROUTINE
; 
; FUNCTIONAL DESCRIPTION:
; 
; 	THIS ROUTINE SETS THE ZR: 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 ZR,RP06	;define our media-id
ZR_unit_INIT: $driver_unitinit_entry
; Don't set unit online here. Priv'd task that assigns ZR unit
; to a file does this to ensure only assigned ZRn: get used.
;	BISL	#UCB$M_ONLINE,UCB$l_STS(R5)  ;SET UCB STATUS ONLINE
	MOVL	#ZR_BUFSIZ,UCB$L_MAXBCNT(R5) ;SET MAX TRANSFER SIZE
	.iif df,delayun,clrl	ucb$l_unload(r5)	;clear unload flag
	MOVB	#DC$_DISK,UCB$B_DEVCLASS(R5) ;SET DISK DEVICE CLASS
        movl    #^xa9876543,ucb$l_sanity(r5)    ;save magic no.
; 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 ZR (get it
						;	right; alter const!)
; (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.
	clrl	ucb$jiggery(r5)	;no ref count adjustment yet
;
; SET UP BUFFER ADDRESS
	PUSHL	R0
	PUSHL	R1
	MOVZWL	UCB$W_UNIT(R5),R0	; GET UNIT NUMBER
	movab	ZR_ucbtbl,ucb$l_ucbtbl(r5)
	movab	ZR_ucbtbl,r1
	movl	r5,(r1)[r0]		; store this ucb address
;	movab	ZR_bufpool,r1
;	movl	r1,ucb$l_bufpol(r5)	;set up buffer pool tbl addr
;	clrl	(r1)[r0]		; set no buffer yet
	CLRL	UCB$L_MEMBF(R5)		;Set no mem area initially
	CLRL	UCB$L_MEMBUF(R5)	; POINT TO DATA AREA
	POPL	R1
	POPL	R0
	movl	r5,ucb_l_ucb(r5)		;initially pointer our ucb
	clrl	ucb$l_blk(r5)		;clr blocking stuff
        movzwl  #ss$_normal,r0
	RET				;RETURN 
	.PAGE
	.SBTTL	START I/O ROUTINE
;++
; 
; ZR_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,$$xdt
	jsb	g^ini$brk
	movl	r3,r3		;flag to debugging person things are weird
	.endc
	call_insioqc
	ret
; return to our caller direct from insioq.
; (note this also sets busy, so it will NOT loop forever.)
ZR_STARTIO: $driver_start_entry
	.if	ndf,xcldbg
; save some IRP fields
; save last irp too so we can tell this for last two seen
	movl	ucb$l_misc2+16(r5),ucb$l_misc2+32(r5)
	movl	ucb$l_misc2+20(r5),ucb$l_misc2+36(r5)
	movl	ucb$l_misc2+24(r5),ucb$l_misc2+40(r5)
	movl	ucb$l_misc2+28(r5),ucb$l_misc2+44(r5)
	movl	ucb$l_misc2+00(r5),ucb$l_misc2+16(r5)
	movl	ucb$l_misc2+04(r5),ucb$l_misc2+20(r5)
	movl	ucb$l_misc2+08(r5),ucb$l_misc2+24(r5)
	movl	ucb$l_misc2+12(r5),ucb$l_misc2+28(r5)
	movl	r3,ucb$l_misc2(r5)	;irp address
	movl	irp$l_func(r3),ucb$l_misc2+4(r5) ;function
	movl	irp$l_bcnt(r3),ucb$l_misc2+8(r5) ;bytecount
	movl	irp$l_abcnt(r3),ucb$l_misc2+12(r5) ;accumulated bytecount
;
	.endc
; 
; 	BRANCH TO FUNCTION EXECUTION
	bbs	#ucb$v_online,-	; if online set software valid
		ucb$l_sts(r5),210$
216$:	movzwl	#ss$_volinv,r0	; else set volume invalid
	brw	resetxfr	; reset byte count & exit
210$:
	tstl	ucb$HPID(r5)	; do we have any host control process yet?
	beql	216$		; if eql no, flag invalid volume.
			; THIS IS SAFETY FROM CONFIGURING FROM OUTSIDE
; BEFORE GOING ON, WE WANT TO ENSURE THE UCB IS FREE.
; (N.B. - As far as I can tell, this code is NEVER used. However, keep
;  it in case some future VMS devices or add-ons might try some custom
;  jiggery-pokery thinking they know about this device!!)
; Check that the process pointed to by ucb$hpid(r5) is really in
; the system. This wil guard against writing to a mailbox which may
; have just been deleted...
	.if	df,clslop
	tstl	ucb$l_blk(r5)	;blocked i/o
	bneq	216$		;if so junk it here
; closes possible timing loop when host process hits fatal error
	.endc
	.iif df,delayun,clrl	ucb$l_unload(r5)	;clear unload flag
	.if ndf,x$hpid
	pushr  #^m<r6,r7,r8>
	.if	df,$$xdt
	jsb	g^ini$brk
	.endc
	movzwl	g^sch$gl_maxpix,r7	;max process index in VMS
; note we have the synch lock at this point already so don't bother
; to lock again...
211$:
	movl	g^sch$gl_pcbvec,r6	;get pcb vector address
	movl	(r6)[r7],r8		;get a PCB address
;	movl	@L^sch$gl_pcbvec[r7],r8	;get a PCB address
	tstl	r8		;system address should be < 0
	bgeq	213$			;if it seems not to be a pcb forget it
	cmpl	ucb$hpid(r5),pcb$l_pid(r8)	;this our process?
	beql	212$			;if so, jump out of loop
213$:	sobgtr	r7,211$			;if not, look at next
	clrl	ucb$hpid(r5)		;if cannot find process, zero our flag
212$:
	popr   #^m<r6,r7,r8>
	.endc ;x$hpid
; retest the ucb$hpid field in case we found it bogus and zeroed it.
	tstl	ucb$HPID(r5)	; do we have any host control process yet?
	beql	216$		; if eql no, flag invalid volume.
	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!
	bisl	#ucb$m_online,ucb$l_sts(r5)	; set online
	bisl	#ucb$m_valid,ucb$l_sts(r5)	;set valid
; set ourselves as owners of channel for ZR:
	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$l_STS(R3),20$	;...
	BBS	#UCB$V_VALID,-		;IF SET - VOLUME SOFTWARE VALID
		UCB$L_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$L_FUNC(R3),R1	; GET FCN CODE
	case	r1,<-			; Dispatch to function handling routine
		nop,-			; no-op
		unload,-		; Unload
		nop,-			; Seek
		NOP,-			; Recalibrate(unsupported)
		nop,-			; Drive clear
		NOP,-			; Release port(unsupported)
		NOP,-			; Offset heads(unsupported)
		retctr,-			; Return to center
		nop,-			; Pack acknowledge
		NOP,-			; Search(unsupported)
		WRITEDATA,-		; Write check(treat as write)
		WRITEDATA,-		; Write data
		READDATA,-		; Read data
		NOP,-			; Write header(unsupported)
		NOP,-			; Read header(unsupported)
		NOP,-			; Place holder
		NOP,-			; Place holder
		available,-		; Available (17)
		>,LIMIT=#0

nop:				;unimplemented function
	brw	fexl

writedata:
; On write data, before we do anything else, we must copy the data from
; the calling process into driver space so it'll be where the control
; process (which the driver talks to) can find it. Do that here.
	pushr	#^m<r0,r2>
	movl	ucb$l_membuf(r5),r2	;mem address
	blss	413$
; Need to allocate a buffer and fill driver pointers in. Do so.
	movl	#ZR_bfsz,r1	;length to grab
	jsb	g^exe$alonpagvar	;get the memory
	blbc	r0,x51$			;fatal if we fail
        pushr   #^m<r0,r1,r2,r3,r4,r5>  ;save regs from movc5
        movc5   #0,(r2),#0,r1,(r2)	;zero the memory initially
        popr    #^m<r0,r1,r2,r3,r4,r5>  ;save regs from movc5
	movl	r2,ucb$l_membf(r5)	;store address
	addl2	#ZR_bfh,r2		;pass header
	movl	r2,ucb$l_membuf(r5)	;store data addr
413$:
	movl	irp$l_bcnt(r3),r1	;number bytes to move
	cmpl	r1,#ZR_bufsiz		;double check all well
	blequ	x50$		; if lequ all's ok
	movl	#ZR_bufsiz,r1
	movl	r1,irp$l_bcnt(r3)	;just scale the request down if
					;byte count too big...just as FDT
					;should have done (if MSCP server
	brb	x50$			;hadn't been f**ked up)
x51$:	popr	#^m<r0,r2>
	brw	fatalerr
x50$:	pushl	r3	; save r3
; note that MOVFRUSER must execute at fork IPL. We're at fork here though.
	jsb	MOVFRUSER	; go move the data from user process to here
	movl	(sp)+,r3	; get back IRP addr
	movl	#1,r2	;set write direction
	brw	RW_COMN
readdata:
	pushr	#^m<r0,r2>
; On read-data, we move data from driver area to user at END of I/O,
; hence nothing special here.
; check too-large reads also
	cmpl	irp$l_bcnt(r3),#ZR_bufsiz
;	bgtr	x51$	;if byte count too big, return error NOW
;			;don't allow it to corrupt transfer later.
	bleq	4321$
	movl	#ZR_bufsiz,irp$l_bcnt(r3)	;reset to what
				; we can handle if requested byte
				; count was too big. This compensates
				; for brain damage in MSCP server
				; operation in this area.
4321$:
	clrl	r2	;set read direction
;
RW_COMN:
; Save some UCB fields we might need at completion time
; Store irp$l_media field. (Actually, WE never double bash this
; in ZRDRIVER, but it's a good idea to save it anyhow...)
	movl	irp$l_media(r3),ucb$irplmedia(r5)
	movl  ucb$l_svapte(r5),ucb$lsvapte(r5) ;store in our local fields
	movl  ucb$l_sts(r5),ucb$lsts(r5)
	movl  ucb$l_svpn(r5),ucb$lsvpn(r5)
	movl  ucb$l_boff(r5),ucb$wboff(r5)  ;these are needed during i/o data copy
	movw	ucb$w_dirseq(r5),ucb$wdirseq(r5)
;	movl	ucb$l_media(r5),ucb$lmedia(r5)
;	movl	ucb$l_bcr(r5),ucb$lbcr(r5)
; Store transfer information where the host process can get it easily
	movl	ucb$l_membf(r5),r0	; get buffer header address
	blss	413$
; Must allocate a buffer now if none existed (usual for reads)
; so data can be copied in.
	pushl	r2
	movl	#ZR_bfsz,r1	;length to grab
	jsb	g^exe$alonpagvar	;get the memory
	blbc	r0,414$			;fatal if we fail
        pushr   #^m<r0,r1,r2,r3,r4,r5>  ;save regs from movc5
        movc5   #0,(r2),#0,r1,(r2)	;zero the memory initially
        popr    #^m<r0,r1,r2,r3,r4,r5>  ;save regs from movc5
	movl	r2,ucb$l_membf(r5)	;store address
	addl2	#ZR_bfh,r2		;pass header
	movl	r2,ucb$l_membuf(r5)	;store data addr
	movl	r2,r0			;get pointer to r0 where code needs it
	subl2	#ZR_bfh,r0
	popl	r2
	brb	413$
414$:	popl	r2
415$:	brw	x51$
413$:
;	movl	ucb$l_membf(r5),r0	; get buffer header address
	movl	r2,(r0)+		; set transfer direction
	.if	df,tellhostvirt
	bbc	#irp$v_virtual,irp$l_sts(r3),2413$
	bisw    #1,-2(r0)               ; set a virtual flag in hi word
					; so the host can tell this is virt
2413$:
	.endc
	movl	irp$l_media(r3),(r0)+	; save block number
	cmpl	(r0),#ZR_bufsiz		; ensure legal byte count for buffer
	bgtru	x51$			; if too large, return error
	movl	irp$l_bcnt(r3),(r0)+	; byte count
        movw   #ss$_Normal,(r0)+        ; initially set up success on I/O
	movw	ucb$l_bcnt(r5),(r0)+	;preset to say we transferred everything
	clrl	(r0)+			;(set status 2 to 0)
			; too (needed for i/o completion)
	popr	#^m<r0,r2>
; ss$_normal = 1
; debug using sda to peek
; NOW VALIDATED I/O FCN... MODIFY AND SEND OFF
	movl	r3,ucb$irps(r5)		; Save this IRP address for cleanup.
	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.
;
	TSTL	UCB$PPID(R5)		; GUARD AGAINST DOUBLE BASH
	BNEQ	12$
	MOVL	IRP$L_PID(R3),UCB$PPID(R5)	; SAVE PROCESS ID IN ZR: UCB
; make it look to host as physical i/o
	movl	irp$l_sts(r3),ucb$stats(r5)	;save original fcn code
;	bicl	#<irp$m_pagio!irp$m_swapio!irp$m_virtual>,-
;		irp$l_sts(r3) ;say not page/swp, not virtual
;	bisl	#irp$m_physio,irp$l_sts(r3)	;say it IS physical i/o
12$:
	pushl	r6
	PUSHL	R5 ; save our UCB just in case...
; Note VMS' definition of corrupt stack is SP > FP I think..
; Should be ok here.
	PUSHL	R4		; SAVE R4 AND R3 ALSO SINCE THEY'RE FORK
	PUSHL	R3		; CONTEXT.
	PUSHL	R2
	PUSHL	R1
	PUSHL	R0
;
	.if	df,adrhak
;optional hack: store buffer header address in last longword of the buffer.
; The buffer header is made an extra longword long so the completion
; area is unaltered.
	pushl	r4
	movl	ucb$l_membf(r5),r4	; get buffer header address
	bgeq	712$
	movl	r4,20(r4)		; store in last header word
712$:
	popl	r4
; This would be used where it was desired to have the host use 
; change mode to kernel to copy data between driver buffer and 
; host space; this might be shorter than the QIO route. By passing
; the kernel address to the host, this is facilitated. Not defining
; the conditional allows the present host, which has the header
; size of 20 bytes hardcoded in places, to function. Since this
; is an extra header word, no changes to other functions are needed.
	.endc	
; Set up for posting event flag #10 (local) to our control process
; This code was commented out during development but should be OK if
;  you want it.
;	movl	ucb$hpid(r5),r1	; Host process PID
;	clrl	r2		; no priority increment
;	movl	#10,r3		; Set event flag 10 as flag to tell "host"
;				; process there's work...
;	jsb	@#SCH$POSTEF	; go post the event flag
;
; Actually use write to mailbox instead of setting event flag...cleaner.
; To reenable posting ef 10 instead of mailbox comment out block of
; code:
;  from here:
	movl	ucb$l_membf(r5),r4	; get buffer header address
	bgeq	46$		; lose if it is 0. Likely will hang
				; some i/o but not crash at least.
	movl	#ZR_bfh,r3	; buffer header size in bytes
	movl	ucb$smbx(r5),r5	; ucb of mailbox
	beql	46$		; if zero forget the write attempt
;ensure mailbox is not deleted
; At process deletion, the host process may be blown away before the
; device is dismounted. Since the host process has the only known
; channel to that mailbox, cleaning that channel can mean the
; ucb is no longer valid. Do some extra checks here to make certain
; this cannot happen. Also, if we see the mailbox unref'd or
; not online, clear OUR ref to it so we won't be fooled by
; later reuse of the memory.
	.if	df,$$xdt
	jsb	g^ini$brk
	.endc
	bitl	#ucb$m_online,ucb$l_sts(r5)	;ucb marked online?
	beql	46$		;if not marked online don't try a write
	tstl	ucb$l_refc(r5)	;is the UCB referenced by someone?
			;host process should have a channel open to the
			;mailbox before we get to it. If it does not,`
			;then we must NOT use it.
	bleq	46$		;no refs means it might be deleted so
				;don't write to it. This is mainly a
				;problem during process deletion.
				; also disallow any stray negative counts
				; in case somethign messed up.
	tstl	ucb$l_orb(r5)	;finally ensure nonzero orb addr
	bgeq	46$		;if zero, can't use either.
; in fact if the address is not in system space it looks invalid. Since
; all system addresses are negative, we can test for lots of bogus addresses
; all at once.
;	pushr	#^m<r0,r1,r2,r3>
;;check readability of orb
;	movab	ucb$l_orb(r5),r0	;address to check
;	movl	(r0),r0
;	movab	#10,r1			;check a few bytes
;; (actually the prober instruction will wind up checking a whole page
;;  so we don't depend strongly on the length here. Unfortunately the
;;  Vax architecture manual doesn't describe the PROBER instruction directly
;;  so I use the darn subroutine instead.)
;	clrl	r3			;psl access is ok
;	jsb	G^exe$prober
;	blbc	r0,146$			;if fail, can't read orb
;	popr	#^m<r0,r1,r2,r3>	;get back our registers
	call_wrtmailbox save_r1=YES
;	jsb	G^exe$wrtmailbox	;emit the message
;to here
	blbs	r0,43$		; if success, go complete
	brb	46$
;146$:
;part of ORB was unreadable. Fail the I/O and give up.
;	popr	#^m<r0,r1,r2,r3>
;gets regs back and then give up.
;
46$:
; oh heck...
; host is gone... somehow we couldn't write the mailbox.
; (The mailbox should ALWAYS be world writeable)
; finish the I/O and abort it...then take ourselves offline to
; prevent further mischief.
	POPL	R0
	POPL	R1
	POPL	R2
	POPL	R3			;GET BACK FORK CONTEXT
	POPL	R4			; (R3, R4) IN CASE OUR CALLER NEEDS IT
	POPL	R5
	popl	r6
	clrl	ucb$hpid(r5)	; zero our magic indicator
	bicl	#<ucb$m_online!ucb$m_valid>,ucb$l_sts(r5)	;offline
	addl2	ucb$jiggery(r5),ucb$l_refc(r5)
	clrl	ucb$jiggery(r5)	;re increment ref count if not done already
;	incl	ucb$l_refc(r5)			;re-increment ref count
;undoes the decrement done at assign time, so that the deassign service can
; totally free this device as needed.
;	bicl	#ucb$m_valid,ucb$l_sts(r5)	;invalid too
; This will hopefully fix up things so the rest of any I/O queue will just
; be flushed quickly.
; (Unfortunately we can't easily test how many refs there should be...
; one hopes that the sys services just decremented the ref count when the
; process got blown away; this will allow the client to decrement back
; to zero...)
	jmp	fatalerr		; finish the I/O with fatal driver err
43$:
;
; Here get the data into buffer or pull it out and genrate AST to control
; process.
	POPL	R0
	POPL	R1
	POPL	R2
	POPL	R3			;GET BACK FORK CONTEXT
	POPL	R4			; (R3, R4) IN CASE OUR CALLER NEEDS IT
	POPL	R5
	popl	r6
; 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
; ZR_fixsplit processing get done our cleanup. Because we need
; to await this, just return with ZR: unit STILL BUSY to ensure
; that we don't get thru here until we're GOOD AND READY!
; just go return at low prio
; Now all we can do is done...
; Just return to system and await completion of process' I/O by our
; control process (host process) so we can complete action on the
; whole thing!
	RET		; return...don't drop prio here
; (dispatcher oughta deal with that...)
;
;	UNLOAD and AVAILABLE Functions
;	Clear UCB$V_VALID in UCB$L_STS
;
UNLOAD:
	.if	df,delayun
	incl	ucb$l_unload(r5)	;set unload flag
	movl	r3,ucb$irps(r5)	;save irp of unload in our ucb
	movl	irp$l_media(r3),ucb$irplmedia(r5)
	movl  ucb$l_svapte(r5),ucb$lsvapte(r5) ;store in our local fields
	movl  ucb$l_sts(r5),ucb$lsts(r5)
	movl  ucb$l_svpn(r5),ucb$lsvpn(r5)
	.if	ndf,evax
	movw  ucb$w_boff(r5),ucb$wboff(r5)  ;these are needed during i/o data copy
	.iff
	movl  ucb$l_boff(r5),ucb$wboff(r5)  ;these are needed during i/o data copy
	.endc
	movw	ucb$w_dirseq(r5),ucb$wdirseq(r5)
;	movl	ucb$l_media(r5),ucb$lmedia(r5)
;	movl	ucb$l_bcr(r5),ucb$lbcr(r5)
	TSTL	UCB$PPID(R5)		; GUARD AGAINST DOUBLE BASH
	BNEQ	12$
	MOVL	IRP$L_PID(R3),UCB$PPID(R5)	; SAVE PROCESS ID IN ZR: UCB
; make it look to host as physical i/o
	.if	ndf,evax
	movzwl	irp$w_sts(r3),ucb$stats(r5)	;save original fcn code
	.iff
	movl	irp$l_sts(r3),ucb$stats(r5)	;save original fcn code
	.endc
12$:
	.endc
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8>
	movl	#14747,r8
	brb	uracmn
retctr:
AVAILABLE:
; send a notice to the host process that we're going bye-bye. No
; further action needed with driver though...just do it.	movl	ucb$l_membf(r5),r4	; get buffer header address
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8>
	clrl	r8
uracmn:
	subl2	#32,sp		; send 64 bytes
	movl	sp,r4		; buffer addr in r4
	movl	#20,r3		; size the event loop expects
	movl	ucb$smbx(r5),r5	; ucb of mailbox
	bgeq	643$		; if zero forget the write attempt
	bitl	#ucb$m_online,ucb$l_sts(r5)	;ucb marked online?
	beql	643$		;if not marked online don't try a write
	tstl	ucb$l_refc(r5)	;is the UCB referenced by someone?
	bleq	643$		;no refs means it might be deleted
; just send the msg
	movl	#512,(r4)
	movl	#1024,4(r4)
	movl	#2048,8(r4)
	movl	#4096,12(r4)	;make a unique signature
	movl	r8,16(r4)	;send unload flag too
	call_wrtmailbox save_r1=YES	;send the message
	blbs	r0,644$		;if it looks ok, go on
643$:
	addl2	#32,sp
	popr	#^m<r2,r3,r4,r5,r6,r7,r8>
	.iif df,delayun,clrl	ucb$l_unload(r5)
	brb	fexl			;if no daemon, don't wait for it
644$:
	addl2	#32,sp
	popr	#^m<r2,r3,r4,r5,r6,r7,r8>
	.if	df,delayun
	tstl	ucb$l_unload(r5)	;unload
	beql	fexl			;if not, exit now
	ret				;else wait for daemon
	.endc
;	BICL	#UCB$M_VALID, -		;Clear sofware volume valid bit.
;		UCB$L_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
;	MNEGL	IRP$L_BCNT(R3),UCB$L_BCR(R5) ; RESET BYTECOUNT
;	BRW	FUNCXT
FUNCXT:					;FUNCTION EXIT
	CLRL	R1			;CLEAR 2ND LONGWORD OF IOSB
	REQCOM environment=call		; COMPLETE REQUEST
	.PAGE
; 
;PWRFAIL:				;POWER FAILURE
;	BICL	#UCB$M_POWER,UCB$L_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	ZR_STARTIO		;START REQUEST OVER
;ZR_INT::
ZR_UNSOLNT:: .call_entry input=<r3,r4,r5>
	RET
;	POPR	#^M<R0,R1,R2,R3,R4,R5>
	;;
; 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 FOLLOWING CODE ASSUMES ZR_UNITS IS 2 OR MORE.
V_UNIT=0
V_UNM=1
;
; Memory move logic
;
;
;	This code replaces the system routines IOC$MOVFRUSER and IOC$MOVTOUSER.
;	It also duplicates the effect of the code in IOC$INITBUFWIND and 
;	IOC$FILSPT.  Note that the register conventions are different, though!
;
;	Calling conventions:
;
;	R1 = byte count
;	R2 = memory disk buffer address
;	R5 = UCB address
;		UCB contains UCB$L_SVAPTE, UCB$L_BOFF, UCB$L_SVPN, UCB$L_STS
;
;	Destroys R0,R4; changes UCB$L_SVAPTE, UCB$L_STS; RETURNS at end
;
;	Move from system memory to user buffer
;
MOVTOUSER: .jsb_entry
	pushl	irp$l_media(r3)
	pushl	ucb$l_svapte(r5)
	pushl	ucb$l_sts(r5)
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r1,r7		;save byte count
	movl	r2,r1		;buffer address to DEC register input
	movl	r7,r2		;byte count set for DEC routine
;	jsb	g^ioc$movtouser	;call DEC routine to move memory
	call_movtouser
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	popl	ucb$l_sts(r5)
	popl	ucb$l_svapte(r5)
	popl	irp$l_media(r3)
	RSB
;
;	Move from user buffer to system memory
;
MOVFRUSER: .jsb_entry
	pushl	irp$l_media(r3)
	pushl	ucb$l_svapte(r5)
	pushl	ucb$l_sts(r5)
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r1,r7		;save byte count
	movl	r2,r1		;buffer address to DEC register input
	movl	r7,r2		;byte count set for DEC routine
;	jsb	g^ioc$movfruser	;call DEC routine to move memory
	call_movfruser
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	popl	ucb$l_sts(r5)
	popl	ucb$l_svapte(r5)
	popl	irp$l_media(r3)
	RSB
ZR_END:					;ADDRESS OF LAST LOCATION IN DRIVER
	.END
