;
FQDRIVER.MAR AXP/VMS VIRT DISK DRIVER
.TITLE FQDRIVER - AXP/VMS VIRT DISK DRIVER
.IDENT 'V02-003Cb'
evax = 1
alpha=1
bigpage=1
addressbits=32
.enable SUP
; Uncopyright 1988, 1989, 1990, 1991 Glenn C. Everhart
; PUBLIC DOMAIN. May be used by anyone for anything. Enjoy.
; define clslop to close a possible abort shutdown timing window
;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 FQ: 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
; FQDRIVER 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:
; FQ: stands for "File Disk". It is developed from the VMS VD: driver
; which uses contiguous files, but adds some new wrinkles.
; The idea here is that FQ: 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 FQ: 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 FQ:
; that it's ready to roll. Optionally it will be able to tell
; FQ: 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 FQDRV 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
; FQ:, 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 FQ: 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 FQ: can be turned magically
; into logical I/O somewhere else. Imagine having one of these
; on LITTLEVAX:: and an FQ: talking to it on BIGVAX::. Now you
; can mount FQ: on BIGVAX as a read/only device and run
; BACKUP from FQ: 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 FQDRIVER 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 FQ: 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 FQ:
; (indicates the process must do I/O, then fill the buffer) and
; will contain 1 if the I/O is a write seen by FQ:, 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
$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 FQ: 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$K_FQ_LEN .BLKL 1 ;LENGTH OF UCB
;UCB$K_FQ_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.
FQ_UNITS=4
; NOTE MAX TRANSFER FOR UCB NEEDS TO BE SET TO FQ_BUFSIZ!!!
; UCB$L_MAXBCNT FIELD!!!
FQ_BUFSIZ=8192.
FQ_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!
FQ_BFH=24
.endc
; BUFFER HEADER FORMAT: (all longwords)
; Transfer direction (0=read, 1=write) as seen from FQ:, that is,
; read means FQ: 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.)
FQ_tdir=0 ;transfer direction
FQ_blkn=4 ;block number
FQ_bcnt=8 ;bytecount
FQ_isb1=12 ;IOSB longword 1
FQ_isb2=16 ;IOSB longword 2
FQ_BFSZ=FQ_BUFSIZ+FQ_BFH ; BUFFER, PLUS EXTRA HDR INFORMATION
DRIVER_DATA
FQ$DPT::
DPTAB - ;DPT CREATION MACRO
END=FQ_END,- ;END OF DRIVER LABEL
ADAPTER=NULL,- ;ADAPTER TYPE = NONE (VIRTUAL)
DEFUNITS=2,- ;UNITS 0 THRU 1
UCBSIZE=UCB$K_FQ_LEN,- ;LENGTH OF UCB
flags=,- ; allocate a perm. page for safety
MAXUNITS=FQ_UNITS,- ;FOR SANITY...CAN CHANGE
NAME=FQDRIVER,- ;DRIVER NAME
STEP=1,SMP=YES
; Note that perm. page is allocated because IOC$movtouser and ioc$movfruser
; need it.
VD_BLKSIZ=512
; SET THIS SIZE TO MATCH FQ_BUF SIZE
DMA_MAXSIZ =fq_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
; RANDOM ACCESS
DPT_STORE UCB,UCB$L_DEVCHAR2,L,- ;DEVICE CHARACTERISTICS
; 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
; FQ: 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 FQ: 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 ASNFQ 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,FQ_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
;...
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,FQ_INT ;INTERRUPT SERVICE ROUTINE ADDRESS
; DPT_STORE CRB,CRB$L_INTD+VEC$L_INITIAL,- ;CONTROLLER INIT ADDRESS
; D,FQ_ctrl_INIT ;...
; DPT_STORE CRB,CRB$L_INTD+VEC$L_UNITINIT,- ;UNIT INIT ADDRESS
; D,FQ_unit_INIT ;...
DPT_STORE DDB,DDB$L_DDT,D,FQ$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.
;
;FQ$DDT:
DDTAB - ;DDT CREATION MACRO
DEVNAM=FQ,- ;NAME OF DEVICE
START=FQ_STARTIO,- ;START I/O ROUTINE
FUNCTB=FQ_FUNCTABLE,- ;FUNCTION DECISION TABLE
CTRLINIT=FQ_CTRL_INIT,-
UNITINIT=FQ_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
;
; FUNCTION DECISION TABLE
;
; THE FDT LISTS VALID FUNCTION CODES, SPECIFIES WHICH
; CODES ARE BUFFERED, AND DESIGNATES SUBROUTINES TO
; PERFORM PREPROCESSING FOR PARTICULAR FUNCTIONS.
;
FQ_FUNCTABLE:
FUNCTAB ,- ;LIST LEGAL FUNCTIONS
; MOUNT VOLUME
FUNCTAB ,- ;BUFFERED FUNCTIONS
; MOUNT VOLUME
FUNCTAB FQ_ALIGN,- ;TEST ALIGNMENT FUNCTIONS
functab FQ_format,- ;point to host disk
;
; LEAVE NORMAL ACP CALLS IN SO FILE STRUCTURED STUFF ON OUR FQ: UNIT
; WILL WORK OK.
;
FUNCTAB +ACP$READBLK,- ;READ FUNCTIONS
FUNCTAB +ACP$WRITEBLK,- ;WRITE FUNCTIONS
FUNCTAB +ACP$ACCESS,- ;ACCESS FUNCTIONS
FUNCTAB +ACP$DEACCESS,- ;DEACCESS FUNCTION
FUNCTAB +ACP$MODIFY,- ;MODIFY FUNCTIONS
FUNCTAB +ACP$MOUNT,- ;MOUNT FUNCTION
; MOUNT VOLUME
FUNCTAB +EXE$LCLDSKVALID,- ;Local disk valid functions
FUNCTAB +EXE$ZEROPARM,- ;ZERO PARAMETER FUNCTIONS
; AVAILABLE
FUNCTAB +EXE$ONEPARM,- ;ONE PARAMETER FUNCTION
FUNCTAB +EXE$SENSEMODE,- ;SENSE FUNCTIONS
FUNCTAB +EXE$SETCHAR,- ;SET FUNCTIONS
;
; FQ: Buffer Pool
; Stores data for communication with host process
; BUFFER HEADER FORMAT: (all longwords)
; Transfer direction (0=read, 1=write) as seen from FQ:, that is,
; read means FQ: 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.)
FQ_BUFPOOL::
.REPT FQ_UNITS
; header area
.rept FQ_bfh
.byte 0
.endr
; data area. Init to 0 to ensure it gets loaded!
FQ_bf16=FQ_bufsiz/16
.rept FQ_bf16
.long 0,0,0,0
.endr
; .BLKB FQ_BUFSIZ ;DATA AREA
.ENDR
;
;
.LONG 0,0 ;SAFETY
.LONG 0,0 ;SAFETY
.PAGE
.SBTTL FDT Routines
DRIVER_CODE
;++
;
; FQ_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
FQ_format: .jsb_entry input=
bicl3 #io$m_fcode,irp$l_func(r3),r0 ;mask off function code
bneq 20$ ;branch if modifiers, special
movzwl #ss$_normal,r0
rsb ;regular processing
; 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
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
jsb g^exe$writechkr ;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
tstl (r0) ; this a setup access?
beql 82$
jmp 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
clrl ucb$l_blk(r5) ;clear blocking field
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 FQDRIVER 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 FQDRIVER
; 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$:
movzwl #ss$_normal,r0 ;success
jmp g^exe$finishioc ;wrap things up.
40$:
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
jmp g^exe$finishioc ;wrap things up.
;
; Finio
; Complete current I/O
; Call buffer like FQ_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:
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
clrl r1
jmp g^exe$abortio
10$:
; Now actually finish up the I/O...
tstl ucb$irps(r5) ; make sure there IS an IRP in the works
beql 8$ ; 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
addl #FQ_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
6501$:
popl r6
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
nonfk:
movl #ss$_Normal,r0 ;success code
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: .jsb_entry
; 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)
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!
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
; 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
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
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
13$: movl ucb$l_membf(r5),r6 ; get memory header again
addl #FQ_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
;
; 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$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 FQ: is a disk!
; Driver initializes this to success, so a normal write from client to
; FQ: to host will not have to have host write to FQ: 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
; - 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 FQ: I/O queue is emptied and FQ: 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.
;
JSB g^IOC$REQCOM ; GO COMPLETE THE I/O REQUEST IN FQ: CONTEXT
;
; (OR DO I/O SPLIT NEXT PART IN FQ: 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
; 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
rsb
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,# ;ensure length is OK
blequ 1$ ; if ok, go ahead and copy
3$: movzwl #SS$_BADPARAM,r0 ;illegal parameter
clrl r1
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
movc3 r0,(r1),(r2) ;do the copy
popl r2
popl r3
popl r4
popl r5 ;get regs back
movzwl #ss$_normal,r0 ;success
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,# ;ensure length is OK
blequ 61$ ; if ok, go ahead and copy
63$: movzwl #SS$_BADPARAM,r0 ;illegal parameter
clrl r1
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
movc3 r0,(r2),(r1) ;do the copy
popl r2
popl r3
popl r4
popl r5 ;get regs back
movzwl #ss$_normal,r0 ;success
jmp g^exe$finishioc ;wrap things up.
; Data copy routines... exactly like full copy but they skip the
; FQ_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,# ;ensure length is OK
blequ 1$ ; if ok, go ahead and copy
3$: movzwl #SS$_BADPARAM,r0 ;illegal parameter
clrl r1
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
movc3 r0,(r1),(r2) ;do the copy
popl r2
popl r3
popl r4
popl r5 ;get regs back
movzwl #ss$_normal,r0 ;success
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,# ;ensure length is OK
blequ 61$ ; if ok, go ahead and copy
63$: movzwl #SS$_BADPARAM,r0 ;illegal parameter
clrl r1
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
movc3 r0,(r2),(r1) ;do the copy
popl r2
popl r3
popl r4
popl r5 ;get regs back
brfcd: movzwl #ss$_normal,r0 ;success
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
; ++
;
; FQ_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
FQ_xdt: .long 0
.endc
;FQ CONTROLLER INITIALIZATION
FQ_ctrl_INIT: .jsb_entry input=,-
OUTPUT= ,-
PRESERVE=,-
SCRATCH=<>
CLRL CRB$L_AUXSTRUC(R8) ; SAY NO AUX MEM
.if df,$$xdt
clrl FQ_xdt
.endc
movzwl #ss$_normal,r0
RSB ;RETURN
.PAGE
.SBTTL INTERNAL CONTROLLER RE-INITIALIZATION
;
; INPUTS:
; R4 => controller CSR (dummy)
; R5 => UCB
;
ctrl_REINIT: .jsb_entry input=
movzwl #ss$_normal,r0
RSB ; RETURN TO CALLER
.PAGE
.SBTTL UNIT INITIALIZATION ROUTINE
;++
;
; FQ_unit_INIT - UNIT INITIALIZATION ROUTINE
;
; FUNCTIONAL DESCRIPTION:
;
; THIS ROUTINE SETS THE FQ: 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.
;
;--
FQ_unit_INIT: .jsb_entry input=,output=,-
PRESERVE=,-
SCRATCH=
; Don't set unit online here. Priv'd task that assigns FQ unit
; to a file does this to ensure only assigned FQn: get used.
; BISL #UCB$M_ONLINE,UCB$l_STS(R5) ;SET UCB STATUS ONLINE
MOVL #FQ_BUFSIZ,UCB$L_MAXBCNT(R5) ;SET MAX TRANSFER SIZE
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 #^X310c4080,ucb$l_media_id(r5) ; set media id as FQ (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
MULL2 #FQ_BFSZ,R0 ; MULTIPLY BY SIZE OF BUFFERS
MOVAB FQ_BUFPOOL,R1 ; GET ADDRESS OF BUFFER POOL
ADDL2 R1,R0 ; POINT R0 AT THIS UNIT'S BUFFER
MOVL R0,UCB$L_MEMBF(R5) ; STORE TOTAL BUFFER START
ADDL2 #FQ_BFH,R0 ; PASS HEADER
MOVL R0,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
RSB ;RETURN
.PAGE
.SBTTL FDT ROUTINES
;++
;
; FQ_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.
;
;--
nolchk=0
FQ_ALIGN: .jsb_entry input=,output= ;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
movzwl #ss$_normal,r0
RSB ;EVEN - RETURN TO CALLER
.if ndf,nolchk
10$: MOVZWL #SS$_IVBUFLEN,R0 ;SET BUFFER ALIGNMENT STATUS
JMP G^EXE$ABORTIO ;ABORT I/O
.endc
.PAGE
.SBTTL START I/O ROUTINE
;++
;
; FQ_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
JMP g^EXE$INSIOQc ; REQUEUE packet to ourselves (no need to get lock)
; return to our caller direct from insioq.
; (note this also sets busy, so it will NOT loop forever.)
FQ_STARTIO: .jsb_entry input=,- ;START I/O OPERATION
OUTPUT= ,-
PRESERVE=,-
SCRATCH=<>
;
; 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
.if ndf,x$hpid
pushr #^m
.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
.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 FQ:
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)
NOP,- ; 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
movl ucb$l_membuf(r5),r2 ;mem address
movl irp$l_bcnt(r3),r1 ;number bytes to move
cmpl r1,#FQ_bufsiz ;double check all well
blequ x50$ ; if lequ all's ok
movl #FQ_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
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
; 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),#FQ_bufsiz
; bgtr x51$ ;if byte count too big, return error NOW
; ;don't allow it to corrupt transfer later.
bleq 4321$
movl #FQ_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 FQDRIVER, 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
movl r2,(r0)+ ; set transfer direction
movl irp$l_media(r3),(r0)+ ; save block number
cmpl (r0),#FQ_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
; 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 FQ: UCB
; make it look to host as physical i/o
movl irp$l_sts(r3),ucb$stats(r5) ;save original fcn code
; bicl #,-
; 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
movl r4,20(r4) ; store in last header word
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
movl #FQ_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
;;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 ;get back our registers
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
;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$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
; FQ_fixsplit processing get done our cleanup. Because we need
; to await this, just return with FQ: 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!
RSB ; return...don't drop prio here
; (dispatcher oughta deal with that...)
;
; UNLOAD and AVAILABLE Functions
; Clear UCB$V_VALID in UCB$L_STS
;
UNLOAD:
AVAILABLE:
; 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 ; 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 FQ_STARTIO ;START REQUEST OVER
;FQ_INT::
FQ_UNSOLNT:: .call_entry input=
RET
; POPR #^M
;;
; 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 FQ_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 ucb$l_svapte(r5)
pushl ucb$l_sts(r5)
pushr #^m
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
popr #^m
popl ucb$l_sts(r5)
popl ucb$l_svapte(r5)
RSB
;
; Move from user buffer to system memory
;
MOVFRUSER: .jsb_entry
pushl ucb$l_svapte(r5)
pushl ucb$l_sts(r5)
pushr #^m
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
popr #^m
popl ucb$l_sts(r5)
popl ucb$l_svapte(r5)
RSB
FQ_END: ;ADDRESS OF LAST LOCATION IN DRIVER
.END