.TITLE QRDRiver ;skeleton driver implementing ucb linkage .IDENT 'V01h' ; Copyright 1993,1994 Glenn C. Everhart ; All rights reserved ; Author: Glenn C. Everhart ; May be copied or used only with inclusion of the above notice. ; However may be used freely given this. ; ; This is an example intercept driver for VMS AXP 6.1 and later (i.e., a ; step 2 driver intercept) which shows how to add FDT time intercepts ; ahead of the normal ones. It defines FDT_ACT entries here ONLY ; for those functions to be serviced in this driver and these should ; return via the PORS label, which will call the original FDT routines ; after servicing things here (unless of course local routines finish ; the I/O off locally in normal step 2 driver ways like calls to the ; finishio or abortio routines). ; ; It is expected that initially an io$_format+128 function will be ; issued with the buffer as described herein to get the driver connected ; with some other device. This calls the mung routine which actually ; intercepts the I/O in such a way that it can be cleared. The intercept ; can be removed with another io$_format+128 call with the buffer set ; to clear the connect. ; ; The buffer to pass to connect is of form: ;buf: .long 1 ;bash flag ; .long 1000 ;dummy size of disk. Must be > 0 ; .ascid /devicename/ ;device name we should connect to ; ; The disconnect buffer is just like the connect one, except the ; buffer starts with ".long 2" instead of ".long 1" ; ; ; Some sanity checks will refuse to disconnect the intercept unless ; the right device is given and unless an intercept had been done ; in the first place. This is a shade crude, but necessary to prevent ; corruption of the I/O database. It is expected that the intercepts ; here get set up at boot time. ; ; For an example, a little intercept of io$_modify is given which ; can allow extends to be forced to be contiguous-best-try (cbt) ; every Nth time (which tends to keep the extent caches flushed). ; Once this is set up, the FDT entry just calls the normal VMS ; modify FDT functions and lets the I/O go through. It checks that ; it is not messing with a kernel channel, nor with requests for ; contiguous extension, nor with movefile requests, but lets ; other activity go on unchanged. ; ; This is offered since the intercept I published before for intercept ; drivers got badly broken for step2 FDT intercepts. This one on the ; other hand will work. ; ; Note that by testing in the mung routine for FDT address equal to ; the local io$_format intercept fdt, it's possible also to leave ; the target disk's IO$_format FDT entry strictly alone and allow ; that to go thru unaltered. Sending IO$_format+128 to THIS driver ; controls it, but sending to the original driver in that case just ; does what the original driver likes... ; ; Glenn Everhart ; everhart@Arisia.GCE.Com ; ; .ntype __,R31 ; set EVAX nonzero if R31 is a register .if eq <__ & ^xF0> - ^x50 EVAX = 1 .iff ;EVAX = 0 .endc .if df,evax evax = 1 alpha=1 bigpage=1 addressbits=32 ; ;... EVAX=1 -> Step1 .iif ndf WCB$W_NMAP, evax=2 ;... EVAX=2 -> Step2 (ndf as of T2.0) .iif ndf WCB$W_NMAP, step2=1 ;... EVAX=2 -> Step2 (ndf as of T2.0) .endc ;x$$$dt=0 ; above for Alpha only. ; ; Glenn C. Everhart 1994 ; ;vms$$v6=0 ;add forvms v6 def'n vms$v5=1 ; define v5$picky also for SMP operation v5$picky=1 .SBTTL EXTERNAL AND LOCAL DEFINITIONS ; ; EXTERNAL SYMBOLS ; .library /SYS$SHARE:LIB/ ; There are lots of defs here...more than are really needed, but they ; do no harm & are likely to be useful in intercept code. ; ; Note: ; Probably the easiest way to pull code into nonpaged pool in AXP ; VMS is to build it into some sort of fake driver so the driver ; loader gets it loaded for you. In general an executive module ; loader will work, but having something that looks like a driver ; as here also provides the option of having extra controls in a ; well defined path. Hence this driver is able to (ab)use the ; io$_format opcode (to itself) to connect or disconnect to some ; victim device. A little more fiddling would allow us not to touch ; the io$_format entry for the victim device...that is left as an ; exercise for the reader...but it shows one of the conveniences ; of the technique. If a generic exec module were inserted, there'd ; still be a need to control it. The $qio technique makes it possible ; to pass buffers of commands to the code without having to muck ; with any system services, and without need of defining any new ; ones. The code doesn't have to have anything at all to do with ; what drivers normally do...it can be whatever cruft you usually ; insert in pool for Ghod knows what reason...and can be hooked in ; in ANY way convenient. You just shove it in and let it get loaded ; as you please. ; ; $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... .if df,step2 ddt$l_fdt=ddt$ps_fdt_2 .endc $ptedef $vadef $IRPDEF ;DEFINE I/O REQUEST PACKET $irpedef $PRDEF ;DEFINE PROCESSOR REGISTERS $SSDEF ;DEFINE SYSTEM STATUS CODES $UCBDEF ;DEFINE UNIT CONTROL BLOCK .if df,step2 $fdt_contextdef .endc $sbdef ; system blk offsets $psldef $prdef $acldef $rsndef ;define resource numbers $acedef $VECDEF ;DEFINE INTERRUPT VECTOR BLOCK $pcbdef $statedef $jibdef $acbdef $vcbdef $arbdef $wcbdef $ccbdef $fcbdef $phddef $RABDEF ; RAB structure defs $RMSDEF ; RMS constants ; defs for acl hacking $fibdef $atrdef p1=0 ; first qio param p2=4 p3=8 p4=12 p5=16 p6=20 ;6th qio param offsets .IF DF,VMS$V5 ;VMS V5 + LATER ONLY $SPLCODDEF $cpudef .ENDC ; ; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS ; $DEFINI UCB ;START OF UCB DEFINITIONS ;.=UCB$W_BCR+2 ;BEGIN DEFINITIONS AT END OF UCB .=UCB$K_LCL_DISK_LENGTH ;v4 def end of ucb ; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK. ; Add our stuff at the end to ensure we don't mess some fields up that some ; areas of VMS may want. ; Leave thisfield first so we can know all diskswill have it at the ; same offset. ; ; $def ucb$l_hucbs .blkl 1 ;host ucb table ; ; Add other fields here if desired. ; $def ucb$l_ctlflgs .blkl 1 ;flags to control modes ; $def ucb$l_cbtctr .blkl 1 ;init for counter $def ucb$l_cbtini .blkl 1 ;init for counter ; preceding 2 fields allow specifying of contig-best-try extents ; on every Nth extend, not every one. This should still help keep ; file extensions from preferentially picking up chaff ; The following lets us remember what the original stolen device is ; so we can prevent double bashes... $def ucb$QRcontfil .blkb 80 $def ucb$l_asten .blkl 1 ;ast enable mask store ; ; DDT intercept fields ; following must be contiguous. $def ucb$s_ppdbgn ;add any more prepended stuff after this $def ucb$l_uniqid .blkl 1 ;driver-unique ID, gets filled in ; by DPT address for easy following ; by SDA $def ucb$l_intcddt .blkl 1 ; Our interceptor's DDT address if ; we are intercepted $def ucb$l_prevddt .blkl 1 ; previous DDT address $def ucb$l_icsign .blkl 1 ; unique pattern that identifies ; this as a DDT intercept block ; NOTE: Jon Pinkley suggests that the DDT size should be encoded in part of this ; unique ID so that incompatible future versions will be guarded against. $def ucb$s_ppdend $def ucb$a_vicddt .blkb ddt$k_length ; space for victim's DDT .blkl 4 ;safety $def ucb$l_backlk .blkl 1 ;backlink to victim ucb ; Make the "unique magic number" depend on the DDT length, and on the ; length of the prepended material. If anything new is added, be sure that ; this magic number value changes. magic=^xF024F000 + ddt$k_length + <256*> p.magic=^xF024F000 + ddt$k_length + <256*> $DEF UCB$L_QR_HOST_DESCR .BLKL 2 ;host dvc desc. ; ; Store copy of victim FDT table here for step 2 Alpha driver. ; assumes FDT table is 64+2 longs long ; ; This layout is much easier to deal with than the VAX or STEP1 one... $def ucb$l_myfdt .blkl 70 ;user FDT tbl copy + slop for safety $def ucb$l_oldfdt .blkl 1 ;fdt tbl of prior fdt chain $def ucb$l_vict .blkl 1 ;victim ucb, for unmung check $def ucb$l_mungd .blkl 1 ;munged flag, 1 if numg'd $def ucb$l_exempt .blkl 4 ;exempt PIDs $DEF UCB$K_QR_LEN .BLKW 1 ;LENGTH OF UCB ;UCB$K_QR_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 ; driver_data QR_UNITS=300 QR$DPT:: .iif ndf,spt$m_xpamod,dpt$m_xpamod=0 DPTAB - ;DPT CREATION MACRO END=QR_END,- ;END OF DRIVER LABEL ADAPTER=NULL,- ;ADAPTER TYPE = NONE (VIRTUAL) FLAGS=DPT$M_SMPMOD!dpt$m_xpamod!DPT$M_NOUNLOAD, - ;SET TO USE SMP,xa DEFUNITS=2,- ;UNITS 0 THRU 1 thru 31 step=2,- UCBSIZE=UCB$K_QR_LEN,- ;LENGTH OF UCB MAXUNITS=QR_UNITS,- ;FOR SANITY...CAN CHANGE NAME=QRDRIVER ;DRIVER NAME DPT_STORE INIT ;START CONTROL BLOCK INIT VALUES DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\> ;DEFAULT ACP NAME DPT_STORE DDB,DDB$L_ACPD+3,B,DDB$K_PACK ;ACP CLASS DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ;FORK IPL (VMS V5.X + LATER) ; These characteristics for an intercept driver shouldn't look just ; like a real disk unless it is prepared to handle being mounted, etc. ; Therefore comment a couple of them out. Thus it won't look file oriented ; nor directory structured. 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. DPT_STORE UCB,UCB$B_TRACKS,B,1 ; 1 TRK/CYL DPT_STORE UCB,UCB$B_SECTORS,B,64 ;NUMBER OF SECTORS PER TRACK DPT_STORE UCB,UCB$W_CYLINDERS,W,16 ;NUMBER OF CYLINDERS DPT_STORE UCB,UCB$B_DIPL,B,21 ;DEVICE IPL ; DPT_STORE UCB,UCB$B_ERTMAX,B,10 ;MAX ERROR RETRY COUNT DPT_STORE UCB,UCB$L_DEVSTS,L,- ;INHIBIT LOG TO PHYS CONVERSION IN FDT ;... ; ; 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,QR_INT ;INTERRUPT SERVICE ROUTINE ADDRESS DPT_STORE DDB,DDB$L_DDT,D,QR$DDT ;DDT ADDRESS DPT_STORE UCB,UCB$L_UNIQID,D,DRIVER$DPT ;store DPT address ; (change "XX" to device ; mnemonic correct values) DPT_STORE UCB,UCB$L_ICSIGN,L,magic ; Add unique pattern (that might ; bring back some memories in ; DOS-11 users) ; HISTORICAL NOTE: under DOS-11, one would get F012 and F024 errors ; on odd address and illegal instruction traps. If we don't have ; this magic number HERE, on the other hand, we're likely to see ; bugchecks in VMS due to uncontrolled bashing of UCB fields! 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. ; ;QR$DDT: DDTAB - ;DDT CREATION MACRO DEVNAM=QR,- ;NAME OF DEVICE START=QR_STARTIO,- ;START I/O ROUTINE FUNCTB=QR_FUNCTABLE,- ;FUNCTION DECISION TABLE CTRLINIT=QR_CTRL_INIT,- UNITINIT=QR_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. ; chnflg: .long 0 ;chain or use our FDT chain flag...use ours if 0 myonoff: fdtonoff: .long 0 ;switch my fdt stuff off if non-0 .ascii /flag/ ;define your own unique flag here; just leave it 4 bytes long! .long 0 ;fdt tbl from before patch fdt_chn = -12 fdt_prev = -4 fdt_idnt = -8 QR_FUNCTABLE: FDT_INI FDT_BUF - ; BUFFERED functions ; MOUNT VOLUME myfdtstart: ; io$_format + modifiers (e.g. io$_format+128) as function code ; allows one to associate a QR unit and some other device; see ; the QR_format code comments for description of buffer to be passed. fdt_act QR_format,- ;point to host disk ; ; First our very own filter routines ; ; Following FDT function should cover every function in the local ; FDT entries between "myfdtbgn" and "myfdtend", in this case just ; mount and modify. Its function is to switch these off or on at ; need. myfdtbgn=. ; Leave a couple of these in place as an illustration. You would of course ; need to insert your own if you're messing with FDT code, or remove these if ; you don't want to. The FDT switch logic is a waste of time and space if ; you do nothing with them... ; They don't actually do anything here, but could be added to. Throw in one ; to call some daemon at various points and it can act as a second ACP ; when control is inserted at FDT time (ahead of the DEC ACP/XQP code!) fdt_act MFYFilt,- ;modify filter (e.g. extend) myfdtend=. QR_ucb: QR_utb: .rept QR_units .long 0 .endr .long 0,0,0,0,0,0,0,0,0,0 driver_code ; ; GETQRUCB - Find QR: UCB address, given r5 points to UCB of the patched ; device. Return the UCB in R0, which should return 0 if we can't find ; it. ; This routine is called a lot and therefore is made as quick as ; it well can be, especially for the usual case. ; ; The trick that we have the victim DDT in our intercept UCB and thus can ; find the intercept UCB relatively fast is the best feature here. ; This gives simple lookup of victim driver from intercept code. ; If we can be sure that the intercept situation is static, we can ; avoid a couple PAL calls here that do synch. stuff, but for this ; example, leave 'em in. ; getQRucb: .jsb_entry output= ; clrl r0 ;no UCB initially found pushl r10 pushl r11 ;faster than pushr supposedly ; pushr #^m ; Assumes that R5 is the UCB address of the device that has had some ; code intercepted and that we are in some bit of code that knows ; it is in an intercept driver. Also assumes R11 may be used as ; scratch registers (as is true in FDT routines). Control returns at ; label "err" if the DDT appears to have been clobbered by ; something not following this standard, if conditional "chk.err" ; is defined. ; Entry: R5 - victim device UCB address ; Exit: R11 - intercept driver UCB address chk.err=0 movl ucb$l_ddt(r5),r10 ;get the DDT we currently have ; note we know our virtual driver's DPT address!!! movab DRIVER$dpt,r11 ;magic pattern is DPT addr. ; lock this section with forklock so we can safely remove ; entries at fork also. Use victim device forklock. ; (don't preserve r0 since we clobber it anyway.) forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=NO 2$: cmpl (r10),R11 ;this our own driver? ; beql 1$ ;if eql yes, end search ; ; The somewhat odd layout here removes extra branches in the ; most common case, i.e., finding our driver the very first time ; through. The "bneq" branch next time is usually NOT taken. ; .branch_unlikely bneq 5$ ;check next in chain if not us ; At this point R10 contains the DDT address within the intercept ; driver's UCB. Return the address of the intercept driver's UCB next. movab <0-ucb$a_vicddt>(r10),r11 ;point R11 at the intercept UCB ; brb 4$ ; note in this layout we can comment this out. 4$: forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=NO ; NOW clobber r0 and put things back. movl r11,r0 ; popr #^m popl r11 popl r10 ;supposedly faster than popr rsb ; Make very sure this DDT is inside a UCB bashed according to our ; specs. The "p.magic" number reflects some version info too. ; If this is not so, not much sense searching more. ; ; If we get here and the DDT points now to someone ELSE'S UCB instead ; of ours, we must keep looking to find OUR UCB. This is done by ; searching the chain we establish so this intercept driver can ; find its own UCB in a finite search. If of course it is the only ; intercept, it gets it right away. 5$: cmpl (r10),#p.magic bneq 3$ ;exit if this is nonstd bash ; follow DDT block chain to next saved DDT. movl (r10),r10 ;point R10 at the next DDT in the ;chain bgeq 3$ ; (error check if not negative) brb 2$ ;then check again ;1$: 3$: clrl r11 ;return 0 if nothing found brb 4$ ; ; Few macros for long distance branches... ; .macro beqlw lbl,?lbl2 bneq lbl2 brw lbl lbl2: .endm .macro bneqw lbl,?lbl2 beql lbl2 brw lbl lbl2: .endm .macro bleqw lbl,?lbl2 bgtr lbl2 brw lbl lbl2: .endm .macro bgeqw lbl,?lbl2 blss lbl2 brw lbl lbl2: .endm ; allocate does not zero its result area. ; This macro makes it easy to zero an allocated area before using it. ; Leaves no side effects...just zeroes the area for "size" bytes ; starting at "addr". .macro zapz addr,size pushr #^m ;save regs from movc5 movc5 #0,addr,#0,size,addr popr #^m ;save regs from movc5 .endm ; .SBTTL Our FDT Filter Routines ; These routines are edited from the QRDRiver versions to call ; getQRucb, assuming they are called with R5 pointing at the patched ; driver's UCB. ; 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 ; (AP) - ADDRESS OF FIRST QIO PARAMETER ; Filter routines. ; These do the interesting stuff. ; PopOut: popr #^m pors: ; Here need to return to the "standard" FDT routine. Do so by computing ; the address in the FDT table of the normal host and calling that, then ; returning. Thus the only FDT routines in THIS driver are the ones ; it needs for its own work, not any standard ones. This calls those. ; Thus, any "continue" returns of our code must wind up calling "pors" ; instead of doing a RET. This will pass the call control along. EXTZV #IRP$V_FCODE,#IRP$S_FCODE,IRP$L_FUNC(R3),R1 ; GET FCN CODE pushr #^m movl r1,r10 jsb getQRucb ;find QR UCB checking for extra links tstl r0 ;got it? bgeq 199$ ;if not skip out movl ucb$l_oldfdt(r0),r7 ;get address of previous FDT bgeq 199$ ;ensure ok... ; movl ucb$l_ddt(r5),r7 ;find FDT ; Here rely on the fact that we got here via our modified FDT call and that ; the orig. FDT is stored just a bit past the current one. ; movl (r7),r7 ;point at orig. FDT addl2 #8,r7 ;point at one of 64 fdt addresses movl (r7)[r10],r8 ;r7 is desired routine address ;now call the "official" FDT code...or the next intercept's down anyhow. pushl r6 ;ccb pushl r5 ;ucb pushl r4 ;pcb pushl r3 ;irp calls #4,(r8) ;Call the original routine popr #^m ; Now return as the original routine would. ret 199$: popr #^m movl #16,r0 call_abortio ret ; rsb mfyfilt: $driver_fdt_entry ;filter on MODIFY requests (e.g. extend) ; First do some preliminary checks for sanity. ; 1. Channel must NOT be kernel mode ; 2. Not a movefile tstl r6 ;is there a CCB (must be +) bleq pors ;if not skip out cmpb ccb$b_amod(r6),#1 ;knl mode access? bleq pors ;leave knl mode chnls alone! ;funct modifiers are bits 6-15 ; this is hex ffc0 ; Normal io$_modify should have no modifiers, so if it has it's ; for something else; leave that alone. .if ndf,evax bitw #^x1FC0,irp$w_func(r3) ;this a movefile or other modifier? .iff bitl #^xDFC0,irp$l_func(r3) ;this a movefile or other modifier? .endc bneq pors ;if so ignore it here. pushr #^m ; original r5 now at 4(sp). Must get that to continue the ops. jsb getQRucb ;find QRDRiver ucb tstl r0 bgeqw popout movl r5,ucb$l_backlk(r0) ;save link'd ucb in ours too. movl r0,r5 ;point R5 at QR UCB ;make sure not a knl mode channel (leave the XQP channel alone!!!) cmpb ccb$b_amod(r6),#1 ;this the XQP's chnl? bleqw popout ; if so scram NOW. ; Now ensure that this call is not in the same JOB as the daemon. ; (This lets the daemon spawn processes to do some work.) bitl i^#2,ucb$l_ctlflgs(r5) ;look at mfy? bneqw mfycmn ;if neq yes ; (test later will see about space control if doing this) 701$: popr #^m brw pors mspcj: popl r0 brw popout mfycmn: ; here we can modify request fields in the FIB the user supplies to reduce ; fragmentation...e.g. set fib$l_exsz bigger or set fib$m_alconb bit ; in fib$w_exctl IFF fib$m_alcon is not set & set fib$m_aldef. ; pushl r0 .if ndf,evax movl p1(ap),r0 ;get fib .iff movl irp$l_qio_p1(r3),r0 .endc xx$nor=0 .iif df,xx$nor,ifnord #4,4(r0),mspcj movl 4(r0),r0 ;...from descriptor .iif df,xx$nor,ifnord #4,fib$w_exctl(r0),mspcj bitw #fib$m_extend,fib$w_exctl(r0) ;extending at all? beqlw mspc ;if no extend, leave fib alone ; Because contiguous best try allocation flushes the entire extend cache, ; it can cause a tremendous performance hit. Therefore allow it to be ; separately switched so that the benefits of longer extents can be had ; if desired without forcing this flushing every time a file is extended. bitl i^#32,ucb$l_ctlflgs(r5) ;separate control for setting contig best try beql 1$ ; leave contig and contig-best-try alone bitw #,fib$w_exctl(r0) ;contig alloc? bneq 1$ ;if contig leave it alone ; allow this on every nth extend. ; This will allow periodic flushes of the extent cache but will let ; it not be made totally useless. By flushing the extent cache periodically ; we can try to reduce the fragmentation it induces. ; if bit 16384 is not set, do not set aldef. decl ucb$l_cbtctr(r5) ;count down bgtr 1$ ;and if >0 don't set cbt yet movl ucb$l_cbtini(r5),ucb$l_cbtctr(r5) ;else reset counter bisw #,fib$w_exctl(r0) ;else turn on contig best ;try and turn on use of ;system default extension if ;larger than program default 1$: mspc: popl r0 popr #^m movl #1,r0 brw pors ;++ ; ; QR_format - bash host disk tables to point at ours. ; ; With no function modifiers, this routine takes as arguments the name ; of the host disk (the real disk where the virtual disk will exist), ; the size of the virtual disk, and the LBN where the virtual disk ; will start. After these are set up, the device is put online and is ; software enabled. ; ; This routine does virtually no checking, so the parameters must be ; correct. ; ; Inputs: ; p1 - pointer to buffer. The buffer has the following format: ; longword 0 - (was hlbn) - flag for function. 1 to bash ; the targetted disk, 2 to unbash it, else ; illegal. ; longword 1 - virtual disk length, the number of blocks in ; the virtual disk. If negative disables ; FDT chaining; otherwise ignored. ; longword 2 through the end of the buffer, the name of the ; virtual disk. This buffer must be blank ; padded if padding is necessary ; ; ; p2 - size of the above buffer ;-- QR_format: $driver_fdt_entry bicw3 #io$m_fcode,irp$l_func(r3),r0 ;mask off function code bneq 20$ ;branch if modifiers, special ;thus, normal io$_format will do nothing. brw pors ;regular processing 100$: popr #^m 10$: movzwl #SS$_BADPARAM,r0 ;illegal parameter clrl r1 call_abortio ret ; jmp g^exe$abortio 20$: movl irp$l_qio_p1(r3),r0 ;buff address movl irp$l_qio_p2(r3),r1 ;buff length call_writechk ; jsb g^exe$writechk ;read access? doesn't return on error ; clrl irp$l_bcnt(r3) ;paranoia, don't need to do this... pushr #^m movl irp$l_qio_p1(r3),r0 movl (r0)+,r7 ;get option code bleq 100$ ;0 or negative illegal cmpl r7,#2 ;3 and up illegal too bgtr 100$ incl chnflg movl (r0)+,r6 ;size of virtual disk (ignored) bleq 70$ clrl chnflg ;if 0 or neg. size don't chain... 70$: movab (r0),- ;name of "real" disk ucb$l_QR_host_descr+4(r5) subl3 #8,irp$l_qio_p2(r3),- ucb$l_QR_host_descr(r5) bleq 100$ ;bad length movab ucb$l_QR_host_descr(r5),r1 ;descriptor for... jsb g^ioc$searchdev ;search for host device blbs r0,30$ ;branch on success ; fail the associate... popr #^m movzwl #ss$_nosuchdev+2,r0 ;make an error, usually a warning clrl r1 call_abortio ret ; jmp g^exe$abortio ;exit with error 30$: ;found the device ; r1 is target ucb address... ; move it to r11 to be less volatile movl r1,r11 cmpl r7,#1 ;bashing the target UCB? bneq 31$ jsb mung ;go mung target... brb 32$ 31$: ; Be sure we unmung the correct disk or we can really screw up a system. cmpl r11,ucb$l_vict(r5) ;undoing right disk? bneq 32$ ;if not skip out, do nothing. jsb umung ;unmung target 32$: ; bisw #ucb$m_valid,ucb$w_sts(r5) ;set volume valid ; bisw #ucb$m_online,ucb$w_sts(r5) ;set unit online ; movl ucb$l_irp(r5),r3 ;restore r3, neatness counts popr #^m movzwl #ss$_normal,r0 ;success call_finishioc do_ret=yes ; jmp g^exe$finishioc ;wrap things up. mung: .jsb_entry ; steal DDT from host. Assumes that the intercept UCB address ; is in R5 (that is, the UCB in which we will place the DDT copy), ; and that the UCB of the device whose DDT we are stealing is ; pointed to by R11. All registers are preserved explicitly so that ; surrounding code cannot be clobbered. R0 is returned as a status ; code so that if it returns with low bit clear, it means something ; went wrong so the bash did NOT occur. This generally means some other ; code that does not follow this standard has grabbed the DDT already. ; The following example assumes the code lives in a driver so the ; unique ID field and magic number are set already. tstl ucb$l_mungd(r5) ;already munged/not deassigned? beql 6$ rsb ;no dbl bash 6$: pushr #^m ; Acquire victim's fork lock to synchronize all this. movl #ss$_normal,r0 ;assume success forklock ucb$b_flck(r11),- savipl=-(sp),preserve=YES ; find the current DDT address from the UCB (leaving the copy in ; the DDB alone) movl ucb$l_ddt(r11),r10 ;point at victim's DDB ; fill in host ucb tbl (makes chnl handling faster) movab QR_ucb,ucb$l_hucbs(r5) movl ucb$l_hucbs(r5),r9 ;get ucb table movzwl ucb$w_unit(r5),r0 ;get unit no. moval (r9)[r0],r9 ;point into tbl movl r11,(r9) ;save target ucb addr in tbl ; see if this DDT is the same as the original movl ucb$l_ddb(r11),r9 ;the ddb$l_ddt is the original cmpl ddb$l_ddt(r9),r10 ;bashing driver the first time? beql 1$ ;if eql yes ; driver was bashed already. Check that the current basher followed the ; standard. Then continue if it looks OK. cmpl (r10),#p.magic ;does the magic pattern exist? ; if magic pattern is missing things are badly messed. beql 2$ ;if eql looks like all's well movl #2,r0 ;say things failed brw 100$ ;(brb might work too) 2$: ; set our new ddt address in the previous interceptor's slot movab ucb$a_vicddt(r5),(r10) ;store next-DDT address relative ;to the original victim one 1$: movl #1,ucb$l_mungd(r5) ;say we munged QR movl r10,ucb$l_prevddt(r5) ;set previous DDT address up clrl ucb$l_intcddt(r5) ;clear intercepting DDT initially 3$: pushl r5 ; copy a little extra for good luck... movc3 #,(r10),ucb$a_vicddt(r5) ;copy the DDT popl r5 ;get UCB pointer back (movc3 bashes it) ; ; Here make whatever mods to the DDT you need to. ; ; FOR EXAMPLE make the following mods to the FDT pointer ; (These assume the standard proposed for FDT pointers) movab ucb$a_vicddt(r5),r8 ;get a base register for the DDT movl r5,QR_functable+fdt_prev ;save old FDT ucb address movl ddt$l_fdt(r10),ucb$l_oldfdt(r5) movl ucb$l_uniqid(r5),QR_functable+fdt_idnt ;save unique ID also ; copy legal and buffered entry masks of original driver. ; HOWEVER, set mask for format entry to be nonbuffered here since ; we deal with it. pushr #^m movab ucb$l_myfdt(r5),r9 ;our function table dummy in UCB movl ddt$l_fdt(r10),r7 ;victim's FDT table ; We want all functions legal in the victim's FDT table to be legal ; here. pushr #^m ;preserve regs from movc movl #<68*4>,r0 ;byte count of a step 2 FDT movc3 r0,(r7),(r9) ;copy his FDT to ours popr #^m ;preserve regs from movc ; Now copy in our modify & back-to-original FDT cells. ; We will do this in our FDT table by having FDT definitions only ; for those functions in QRDRiver that we service locally. Thus ; all entry cells for the rest will point in the QR FDT to ; exe$illiofunc. movab g^exe$illiofunc,r8 ;get the magic address movab QR_functable,r10 ;r10 becomes QR FDT tbl addl2 #8,r10 ;point at functions addl2 #8,r9 ;his new FDT... movl #64,r11 ;64 functions ; The code below will let the victim driver's IO$_format FDT entry not be ; messed with... .if ndf,b$fmt$ pushl r7 movab qr_format,r7 ; let victim's format fdt by .endc 75$: cmpl (r10),r8 ;this function hadled in QR? beql 76$ ;if eql no, skip movl (r10),(r9) ;if we do it point his fdt at our fcn .if ndf,b$fmt$ cmpl (r10),r7 ;this our io$_format beql 76$ ;if so leave victim's alone .endc ; (NOTE: our functions MUST therefore call the previous FDT's functions at ; end of their processing.) 76$: cmpl (r10)+,(r9)+ ;pass the entry sobgtr r11,75$ ;do all functions .if ndf,b$fmt$ popl r7 ;get back victim fdt .endc ; QRDRiver FDT table. Last entry goes to user's original FDT chain. ; ; Thus we simply insert our FDT processing ahead of normal stuff, but ; all fcn msks & functions will work for any driver. popr #^m ; Now point the user's FDT at our bugger'd copy. movab ucb$l_myfdt(r5),ddt$l_fdt(r8) ;point at our FDT table clrl myonoff ;turn my FDTs on ; ; Finally clobber the victim device's DDT pointer to point to our new ; one. movab ucb$a_vicddt(r5),ucb$l_ddt(r11) ; Now the DDT used for the victim device unit is that of our UCB ; and will invoke whatever special processing we need. This processing in ; the example here causes the intercept driver's FDT routines to be ; used ahead of whatever was in the original driver's FDTs. Because ; the DDT is modified using the UCB pointer only, target device units ; that have not been patched in this way continue to use their old ; DDTs and FDTs unaltered. ; ; Processing complete; release victim's fork lock 100$: forkunlock lock=ucb$b_flck(r11),newipl=(sp)+,- preserve=YES popr #^m rsb umung: .jsb_entry ; ; Entry: R11 points at victim device UCB and current driver is the one ; desiring to remove its entry from the DDT chain. Thus its xx$dpt: address ; is the one being sought. ("Current driver" here means the intercept ; driver.) ; It is assumed that the driver knows that the DDT chain was patched ; so that its UCB contains an entry in the DDT chain pushr #^m ; .iif df,x$$$dt,jsb g^ini$brk ;***********************debug********* movl r11,r5 ;hereafter use r5 as victim's UCB movl ucb$l_ddt(r5),r10 ;get the DDT we currently have movl ucb$l_ddb(r5),r1 ;get ddb of victim movl ddb$l_ddt(r1),r1 ;and real original DDT movl r10,r0 ;save ucb$l_ddt addr for later movab DRIVER$DPT,r11 ;magic pattern is DPT addr. ; lock this section with forklock so we can safely remove ; entries at fork also. Use victim device forklock. forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=YES 2$: cmpl (r10),R11 ;this our own driver? beql 1$ ;if eql yes, end search .if df,chk.err cmpl (r10),#p.magic bneqw 4$ ;exit if this is nonstd bash .endc ;chk.err ; follow DDT block chain to next saved DDT. movl (r10),r10 ;point R10 at the next DDT in the ;chain .if df,chk.err bgeqw 4$ ; (error check if not negative) .endc ;chk.err brb 2$ ;then check again 1$: ; At this point R10 contains the DDT address within the intercept ; driver's UCB. Return the address of the intercept driver's UCB next. tstl (r10) ;were we intercepted? bgeq 3$ ;if geq no, skip back-fixup ; we were intercepted. Fix up next guy in line. movl (r10),r11 ;point at interceptor movl (r10),(r11) 3$: ; if we intercepted someone, fix up our intercepted victim to skip by ; us also. movl (r10),r2 ;did we intercept ;original driver? cmpl r2,r1 ;test if this is original beql 5$ ;if eql yes, no bash ; replace previous intercept address by ours (which might be zero) movl (r10),(r2) 5$: ; Here remove FDT entries from the list if they were modified. ; This needs a scan of the FDT chain starting at the victim's ; ddt$l_fdt pointer and skipping around any entry that has address ; QR_functable: ; The FDT chain is singly linked. The code here assumes everybody ; plays by the same rules! ; NOTE: Omit this code if we didn't insert our FDT code in the chain!!! movl ddt$l_fdt(r0),r1 ;start of FDT chain movab QR_functable,r2 ;address of our FDT table clrl r3 movab <0-ucb$a_vicddt>(r10),r4 ;initially point at our ucb ; Also set the QR device offline when we unbash it. This is a simple ; flag that ctl prog. can use to tell if it's been used already. bicl #,ucb$l_sts(r4) 6$: cmpl r1,r2 ;current fdt point at us? beql 7$ ;if eql yes, fix up chain movl r1,r3 ;else store last pointer movl fdt_prev(r1),r4 ;and point at next bgeq 8$ movl ucb$l_oldfdt(r4),r1 ;where last FDT pointer is in the ucb ;;;BUT not all UCBs will have the fdt offset at the same place!!! ;;;HOWEVER we will leave this in, putting the oldfdt field first after ;;;the regular UCB things. bgeq 8$ ;if not sys addr, no messin' brb 6$ ;look till we find one. 7$: ;r3 is 0 or fdt pointing to our block next ;r1 points at our fdt block tstl r3 ;if r3=0 nobody points at us bgeq 8$ ;so nothing to do movl fdt_prev(r1),r4 bgeq 17$ movl ucb$l_oldfdt(r4),-(sp) ;save old fdt loc movl fdt_prev(r3),r4 blss 18$ tstl (sp)+ brb 17$ 18$: movl (sp)+,ucb$l_oldfdt(r4) 17$: movl fdt_prev(r1),fdt_prev(r3) ;else point our next-fdt pointer at ;last fdt addr. 8$: ; ; Finally if the victim UCB DDT entry points at ours, make it point at ; our predecessor. If it points at a successor, we can leave it alone. cmpl r10,r0 ;does victim ucb point at our DDT? bneq 4$ ;if not cannot replace it movl (r10),ucb$l_ddt(r5) clrl (r10) ;zero QR munged flag 4$: forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES popr #^m ;copy our prior DDT ptr to next one rsb .SBTTL CONTROLLER INITIALIZATION ROUTINE ; ++ ; ; QR_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. ;-- QR_ctrl_INIT: $driver_ctrlinit_entry ; CLRL CRB$L_AUXSTRUC(R8) ; SAY NO AUX MEM movl #1,r0 Ret ;RETURN .SBTTL INTERNAL CONTROLLER RE-INITIALIZATION ; ; INPUTS: ; R4 => controller CSR (dummy) ; R5 => UCB ; .SBTTL UNIT INITIALIZATION ROUTINE ;++ ; ; QR_unit_INIT - UNIT INITIALIZATION ROUTINE ; ; FUNCTIONAL DESCRIPTION: ; ; THIS ROUTINE SETS THE QR: 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. ; ;-- QR_unit_INIT: $driver_unitinit_entry ; Don't set unit online here. Priv'd task that assigns QR unit ; to a file does this to ensure only assigned QRn: get used. ; BISW #UCB$M_ONLINE,UCB$W_STS(R5) ;SET UCB STATUS ONLINE ;limit size of QR: data buffers QR_bufsiz=8192 movl #QR_bufsiz,ucb$l_maxbcnt(r5) ;limit transfers to 8k MOVB #DC$_MISC,UCB$B_DEVCLASS(R5) ;SET DISK DEVICE CLASS clrl ucb$l_mungd(r5) ;not mung'd yet ; 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!!!) movab DRIVER$DPT,ucb$l_uniqid(r5) movl #^Xb22d4001,ucb$l_media_id(r5) ; set media id as QR ; (note the id might be wrong but is attempt to get it.) (used only for ; MSCP serving.) MOVB #DT$_FD1,UCB$B_DEVTYPE(R5) ;Make it foreign disk type 1 ; (dt$_rp06 works but may confuse analyze/disk) ;;; NOTE: changed from fd1 type so MSCP will know it's a local disk and ;;; attempt no weird jiggery-pokery with the QR: device. ; MSCP may still refuse to do a foreign drive too; jiggery-pokery later ; to test if there's occasion to do so. ; Set up crc polynomial movab QR_utb,ucb$l_hucbs(r5) ;host ucb table clrl chnflg ;initially set to use our chain of FDTs BICL #UCB$M_ONLINE,UCB$L_STS(R5) ;SET UCB STATUS OFFLINE movl #1,r0 ret ;++ ; ; QR_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. ; ;-- QR_STARTIO: $driver_start_entry ; ; PREPROCESS UCB FIELDS ; ; ASSUME RY_EXTENDED_STATUS_LENGTH EQ 8 ; CLRQ UCB$Q_QR_EXTENDED_STATUS(R5) ; Zero READ ERROR REGISTER area. ; ; 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$: ; Unless we use this entry, we want to junk any calls here. brb 216$ ;just always say invalid volume. ; Get here for other start-io entries if the virtual disk code is ; commented out also, as it must be. ;FATALERR: ;UNRECOVERABLE ERROR ; MOVZWL #SS$_DRVERR,R0 ;ASSUME DRIVE ERROR STATUS RESETXFR: ; dummy entry ... should never really get here MOVL UCB$L_IRP(R5),R3 ;GET I/O PKT ; MNEGW IRP$W_BCNT(R3),UCB$W_BCR(R5) ; RESET BYTECOUNT ; BRW FUNCXT FUNCXT: ;FUNCTION EXIT CLRL R1 ;CLEAR 2ND LONGWORD OF IOSB REQCOM,environment=call ; COMPLETE REQUEST ; ;PWRFAIL: ;POWER FAILURE ; BICW #UCB$M_POWER,UCB$W_STS(R5) ;CLEAR POWER FAILURE BIT ; MOVL UCB$L_IRP(R5),R3 ;GET ADDRESS OF I/O PACKET ; MOVQ IRP$L_SVAPTE(R3),- ;RESTORE TRANSFER PARAMETERS ; UCB$L_SVAPTE(R5) ;... ; BRW QR_STARTIO ;START REQUEST OVER ;QR_INT:: ;QR_UNSOLNT:: ; POPR #^M ; REI ;DUMMY RETURN FROM ANY INTERRUPT ;; QR_END: ;ADDRESS OF LAST LOCATION IN DRIVER .END