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 .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=,- ; 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 ; 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 ; 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 ;... 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$$,
$$TEMP$$ = ^A/$$L$$/ - ^X40 .IF GT $$TEMP$$ $$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$> .ENDC $$S$$ = $$S$$ - 5 .ENDR .IRPC $$L$$, .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,irp$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 ; MOUNT VOLUME .if df,irp$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 file ;OK fdt_act tl_ZR_crea, ; create file ;OK fdt_act tl_ZR_dele, ; delete file ;OK fdt_act tl_ZR_deac, ; deaccess file ;OK fdt_act tl_ZR_modi, ; modify file ;OK fdt_Act tl_ZR_format, ;point to host disk ;OK fdt_act crrmshd, ;create/remove shadowset members .if ndf,irp$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, ;write phys/log ;OK fdt_act tl_ZR_wrtvr, ; write virtual ;OK .iff FDT_ACT ACP_STD$WRITEBLK,- ;WRITE FUNCTIONS FDT_ACT ACP_STD$WRITEBLK,- ;WRITE FUNCTIONS .endc ; The rest of these are standard stuff fdt_act acp_std$readblk, ;read virt ;OK fdt_act acp_std$modify, ;OK fdt_act acp_std$mount, ;OK fdt_act exe_std$lcldskvalid, fdt_act exe_std$zeroparm, ; fdt_act exe_std$oneparm, fdt_act exe_std$sensemode, ; fdt_act exe_std$setchar, ; fdt_act exe_std$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,- ; ; functab ZR_safrwx,- ; ; functab ZR_safdeac,- ; ; Top level routines ; tl_ZR_crea: create handling. Calls internal checker, then std ; checker. tl_ZR_accs: $driver_fdt_entry .if ndf,xcldbg movl r0,ucb$l_misc+00(r5) .endc jsb ZR_safac ;add an extra call to get to the std routine. movl #1,r0 call_access 999$: .if ndf,xcldbg movl r0,ucb$l_misc+04(r5) .endc 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 .if ndf,xcldbg movl r0,ucb$l_misc+08(r5) .endc jsb ZR_safrw blbc r0,999$ ;add an extra call to get to the std routine. movl #1,r0 call_access .if ndf,xcldbg movl r0,ucb$l_misc+12(r5) .endc ret 999$: .if ndf,xcldbg movl #-3,ucb$l_misc+12(r5) ;flag err rtn if we got it movl #8,r0 .endc call_finishioc do_ret=no ;error return... ret tl_ZR_dele: $driver_fdt_entry ;delete toplevel fdt .if ndf,xcldbg movl r0,ucb$l_misc+16(r5) .endc jsb ZR_safrwd blbc r0,999$ ;add an extra call to get to the std routine. movl #1,r0 call_acp_modify .if ndf,xcldbg movl r0,ucb$l_misc+20(r5) .endc ret 999$: .if ndf,xcldbg movl #-3,ucb$l_misc+20(r5) movl #4,r0 .endc call_finishioc do_ret=no ;error return... ret ZR_safrw: .jsb_entry output= 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= 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= 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 .if ndf,xcldbg movl r0,ucb$l_misc+24(r5) .endc jsb ZR_safrwx blbc r0,999$ ;add an extra call to get to the std routine. movl #1,r0 call_acp_modify .if ndf,xcldbg movl r0,ucb$l_misc+28(r5) .endc ret 999$: .if ndf,xcldbg movl #-3,ucb$l_misc+28(r5) movl #6,r0 .endc call_finishioc do_ret=no ;error return... ret ; fdt_act tl_ZR_wrtpl, ;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 .if ndf,xcldbg movl r0,ucb$l_misc+32(r5) .endc ; 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 .if ndf,xcldbg movl r0,ucb$l_misc+36(r5) .endc ret ;99$: MOVZBL #SS$_ILLIOFUNC,R0 80$: .if ndf,xcldbg movl #-3,ucb$l_misc+36(r5) movl #14,r0 .endc call_finishioc do_ret=no ;error return... ret ZR_safrwx: .jsb_entry output= 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$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 .if ndf,xcldbg movl r0,ucb$l_misc+40(r5) .endc jsb ZR_safdeac ; blbc r0,999$ ;add an extra call to get to the std routine. movl #1,r0 call_deaccess .if ndf,xcldbg movl r0,ucb$l_misc+44(r5) .endc 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= 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$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= 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= 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= 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 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 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 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$: movzwl #ss$_normal,r0 ;success call_finishioc do_ret=yes ;error return... 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 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: 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 call_abortio do_ret=no ret ; jmp g^exe_std$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 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 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,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) 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 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 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 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 ; ; 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 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 ; 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 #,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 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 ; 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,# ;ensure length is OK blequ 1$ ; if ok, go ahead and copy 3$: movzwl #SS$_BADPARAM,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,# ;ensure length is OK blequ 61$ ; if ok, go ahead and copy 63$: movzwl #SS$_BADPARAM,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,# ;ensure length is OK blequ 1$ ; if ok, go ahead and copy 3$: movzwl #SS$_BADPARAM,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,# ;ensure length is OK blequ 61$ ; if ok, go ahead and copy 63$: movzwl #SS$_BADPARAM,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 ; ; 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 .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 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 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 ;save regs from movc5 movc5 #0,(r2),#0,r1,(r2) ;zero the memory initially popr #^m ;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 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),#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 ;save regs from movc5 movc5 #0,(r2),#0,r1,(r2) ;zero the memory initially popr #^m ;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 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 ; 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$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 ;;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 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 ;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 ; 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 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 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 .iif df,delayun,clrl ucb$l_unload(r5) brb fexl ;if no daemon, don't wait for it 644$: addl2 #32,sp popr #^m .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= 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 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 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 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 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 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