d$$bug=0 ; ; feh whoa hold everything. ; ; The i/o irp add-to-queue stuff does not always go thru the io_pending ; entry. Alternate solution: ; generate a new ucb (copy of real primary path ucb) and DDT entries ; as before. However, point all search routines to get to this one, not ; the real one we hide; just replace the links, but with OUR DDT pointed ; to in there. We will control all entry points and get them to the ; correct UCB, using $insioqc or whatever to get to the real one. Externally ; everything will get to the old entry. We will still need to monitor the ; external queue as now, because everything will go thru it,and we must ; manage the busy bit right, being sure to clear it when nothing more is ; pending in the external i/o queue. In addition we will have to reflect ; status & devdepend bits from the real irp. The fake ucb will have to be ; the one mounted etc. In case it's the boot device, reset the system ; pointers to the boot ucb to the fake one. Also check shdriver for ; pointers and reset. ; This can be handled rather cleanly IF the device was idle and unref'd ; when starting. If the device has been working a while, it is far from ; sure that this can be handled. ; ; It is conceivable to just use the original ucb in place and point ; to a new UCB to call dkdriver. We still would need to reflect mods ; in devcharn & sts back out and copy IRPs to the "inner" queue but all ; old UCB pointers would be valid. We would perhaps force mount verify ; to clear out I/O during the transition, so SCDRPs would come thru ; pointing at the correct UCB. In general interrupt vectors would be ; unclean though and since the underlying unit init has run, an unknown ; number of pointers may exist to the original UCB. ; ; The alternative could be to allow insioq(c) to call the driver pending ; io entry always, just using some flag. ; ; All things considered, the last alternative seems better. This allows ; the I/O to be cleanly trapped in all cases. Cost is low since the ; ddtab points to the routine needed anyway. Only a couple of places need ; to be altered. ; ; Notes to check for: ; 1. Does cancel drain IRPs from the queue? If so I need to count IRPs in ; the device queue after cancel so the count of busy IRPs can be ; correct. ; ANS: Cancel needs to pull stuff off the input queues if anything relevant ; is there. Done, for mp queue or for the underlying queue. Then the ; driver entry is called with stuff back on the driver queue. Still an ; issue is whether I want to temporarily replace irp$l_pid at this ; point. Not a dk issue, since dkdriver mishandles the field in any case. ; Unsure about du. ; ; 2. Any way to get the error counts moved when primary path's not used? ; ; ; 3. Be sure DKdriver won't pull IRPs off its queue while a pending ; entry is added. ; Ans: synch should take care of this...holding fork. ; ; 4. Check that the i/o in progress count is maintained right in all ; entries. ; ans: I think that's ok now ; ; 5. Can / does SHdriver ever pull IRPs off underlying device queues ; directly? ; Ans: yes, there are remque off the irp queue pointers here & there, ; but shdriver doesn't seem to remque off the underlying driver queues ; anywhere. ; ; 6. What needs to be here to support MV for foreign disks? (Or should MV ; be changed to allow it to work with /for mounts?) (but without vv)? ; Ans: It'd be simpler to alter MV, if that is politically feasible. ; Best if some other bit is permitted ("mv anyway"?) so this is ; selective. Define that & we need to test also in postio. ; (in fixsplit) ; ; 7. DK code to not allow serving till WE set it? And code here to set it? ; ; 8. Direct paths need to be allowed to be served, and if the preferred ; path is served to us and we have a direct path we need to allow ; our path to be served, either by mods to the original preferred ; name UCB or by another UCB, and use the direct path initially if ; the served one isn't busy. ; ; When our direct path fails we must of course also get servers using ; us to go into MV and find another path, so we only will look like ; a valid server path while our direct path is up. If it fails ; we must ensure that we don't look valid before we allow MV to ; finish. ; ; The notion here is that when we have a direct path to an underlying device ; (ANY of our paths!) we allow serving to be done; otherwise not. When ; our direct path is not available we do not let serving be done by us. ; Also we must make the direct path the one we use if possible. Possible ; in this context means the enable bit is also set (since there may be global ; reasons why we can't use a direct path). We can get servers to go elsewhere ; by returning MV type status to them. ; m$$trp=0 ;mousetrap info ffinish=0 x$$$dt=0 .iif ndf,mintries,mintries=3 ;allow 2 packacks before switch try$safe=0 evax=0 step2=1 .TITLE SWDRiver ;Failover SWitch driver .IDENT 'V01j' ; Copyright 1996 Digital Equipment Corp. ; All rights reserved ; Author: Glenn C. Everhart ; ; This driver is designed as a method of developing and presenting a ; software failover service which can be independent of underlying ; drivers to a large degree. While the service may be migrated into the ; other drivers proper, the intent here is to provide a function which ; will work immediately, BUT which will not be too intimately tied into ; other drivers, so that when a QIO server is built, this can be replaced ; by it if one wants. By the same token, this package is intended to allow ; failover from one "path" to a device to another "path" regardless of ; underlying technology, provided only that the "normal" VMS driver front ; end interface is preserved. (Due to its generality, this scheme should ; be OK for other failover types too.) ; ; The overall view of what happens is that a switch is inserted at the ;start_io entry (and other entries) of one of a pair of drivers, which ;represent the two paths to some storage. The intercept driver keeps a ;record of the two UCB addresses involved (and several other items) so it ;can direct I/O appropriately. Now, when the io$_packack from mount verify ;is seen for the Nth time (N is settable), the intercept code takes this ;as meaning that the original path is as quiesced as it can be made, and ;that an alternate path should be tried. When this alternate path is to be ;switched to, IRPs to the original driver are simply routed to the ;alternate path driver instead using the standard $insioqc entry point, or ;the appropriate alternate in case of mount verify packacks, and are ;"caught" at I/O post time and post processed in the context of the ;original driver (since it is the one that is mounted etc., so we must not ;louse up the file processing!!). This done, the IRP is posted and ;finished with. We keep a count of all IRPs put on an underlying driver ; queue (we have a separate multipath queue which serves instead of an ; underlying queue) and not posted, so we can tell when an IRP is in ; the "hands" of an underlying driver. If this count is zero we know ; the I/O is idle. Also we zero it at switch time, since MV will have ; cleared things up and if during a transition we somehow screwed up, ; this will get "in synch" again. (This is mainly paranoia in action.) ; ; If the second path sees a failure similar to the first, the driver will ; try and return. If this does not result in some non-packack I/Os ; however the drive will be left to the good (?) graces of mount verify ; rather than skipping back and forth indefinitely. ; ; Setup of this will for now be via a privileged image. To avoid having ; to allocate other storage, the intercept will save the original value ; of IRP$L_PID in IRP$Q_QIO_P2+4 during processing. The idea is to provide ; a very short detour path for I/O being rerouted. The scheme has the ; additional advantage that it can be used to route I/O between paths ; using the MSCP (or other) server and SCSI; it won't much care what ; details of the driver are. We especially do NOT want to be having to ; allocate more pool every IRP if we can possibly avoid it. On VAX ; we probably need to anyway, because the IRP has less room, and reusing ; these cells needs to be addressed more carefully in review. It appears ; nothing uses them at this point...certainly normal I/O processing ; does not...but any newer code that tries to "peek" into these ; arguments gets interfered with. It would make life easier if I/O ; interception might have a "stack" of a few pointers & statuses ; that could be used, or some dedicated pointers to such a stack which ; an intercept layer could maintain. Grabbing pointers willy-nilly ; is not really all one could like. For grabbing a DDT, and putting it ; into, say, an intercept UCB, storage is OK. For the per-IRP processing ; however you want to keep track of the original values in the IRP itself ; if at all possible, and a stack in the IRP would be a near-ideal place ; to put them, with a bottom-of-stack pointer to check against as ; well as a top pointer. Each part of a stream would manage its own ; handling. Also, irp$l_pid should be supplemented with another field ; giving the first, original PID of the I/O so that stream I/O need ; not be confused with real system I/O. ; ; Initially the start IO entry is captured for this, but it will be ; important to check other possible entries also. Since the IRPs will ; be "transparently" rerouted, most functions will be unaltered, but ; mount verify must be captured also. To keep the rest of VMS from ; using the second driver directly, it will be marked offline. ; ; Because the second drive will not be seen as mounted, mount verify ; will not be done on it, so the I/O post path used here will monitor ; this also, when using its secondary path, and call the executive ; exe$mount_ver routine when conditions are appropriate from within the ; context of the "correct" device. The packack IRPs from mount verify will ; also be forwarded the way mount verify does them, i.e., without ; synch on UCB busy, so that the driver will have its flow handled ; correctly. Since we will switch over only where the device has been ; already idled, we need not be bothered over the fact that the UCB ; busy flags will be in this fashion "multiplexed" and used for more ; than one driver...they will be used for exactly one at a time. Note that ; an extra I/O will be provided to do "manual" switching too, but ; this must be done when the driver is idle. Since this idleness condition ; cannot readily be known, it will remain a usage restriction. ; ; The set up of all this will be a tad tricky. First, the drivers must ; be set up (at least dkdriver) not to allow automatic MSCP serving of ; the disks where the path will not be the preferred one. Arranging for ; these to come up offline would do this. Alternatively so long as only ; one device of each pair is initially online, we can use that one. ; Having both devices come up online and then be MSCP served would however ; cause problems. (More info on how to tell where HSZs are is needed.) ; ; An HSZ connected lun will be "ready" from one side only. We need to ; ensure that side is used for this intercept and for all MSCP vectoring. ; Once this is done we can ensure things are handled correctly. While ; this could be done by excluding these devices from autoconfigure and ; performing manual configs (connects) and manually starting MSCP, this ; is not the preferable approach if it can be avoided. ; ; Note too that drivers wanting to use this intercept will operate ; cleanly provided they find start_io by following the chain ; UCB$L_DDT -> DDT$L_STARTIO chain to find the start-io entry point. ; Where a driver startio routine loops back to its start for more ; work, without following the chain, this intercept cannot guarantee ; at which IRP processing will be switched. Should the chain be followed ; in all cases, however, the switch will take effect at once. In either ; case, an IRP gets processed by one path or the other, not both. ; ; This driver will hook all entries normally used by disks that I know ; of, so that they will be dispatched properly. This means not only ; startio, but altstart, mountver, etc. It will also use the new-packet ; entry itself to count IRPs and take over irp$l_pid (checking at ; startio not to do it twice!) so that even IRPs dequeued internally by ; a driver will be counted properly, thus ensuring that our idle count ; is accurate. Altstart entries need to be counted too! ; The register dump routine is called only when the driver is active ; and should get the right UCB in any case, since if in the secondary path, ; the IRP and so on all validly point to the secondary UCB and have no ; reason to access the primary. The cancel entry must be fielded also since ; it can be called from outside and must be guaranteed also to have the ; right UCB. ; ; 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. ; ; Glenn Everhart ; everhart@Arisia.GCE.Com ; ; ; ; Code to notice when we build in an Alpha environment automagically. ; .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 ; 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. ; ; $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 $IPLDEF $DDTDEF ; DEFINE DISPATCH TBL... ; There are a couple of nonstandard names used here; define them locally. .IF DF,STEP2 DDT$L_FDT=DDT$PS_FDT_2 DDT$L_START=DDT$PS_START_2 .ENDC $PTEDEF $CANDEF $VADEF $IRPDEF ;DEFINE I/O REQUEST PACKET $IRPEDEF $PRDEF ;DEFINE PROCESSOR REGISTERS $SSDEF ;DEFINE SYSTEM STATUS CODES $UCBDEF ;DEFINE UNIT CONTROL BLOCK $FDT_CONTEXTDEF $FDTDEF .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 $cddbdef $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$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. ; ; ; align to quadword .=<<.+7>& ^C7> ; Align data structure .if df,m$$trp $def ucb$l_mtrp .blkl 16 .endc ; ; To ensure our consumers don't pull anything we haven't seen off of ; their input queue without us knowing, we will queue all incoming ; IRPs HERE, on our OWN UCB at this offset, and move them to the ; target device when we are ready for this to be done. This will make ; the data path rather symmetrical since ALL IRPs will move from the ; $QIO processor to here, to another device driver. ; ; When mount verify is being started, IRPs will also be replaced on ; this queue. When it is being ended, we will allow ONE IRP onto the ; target device and call the victim's MV end routne to restart things. ; This will basically always queue the IRP here after calling the ; underlying driver routine, so that the underlying driver will control ; the order. We will bring things back in the same order. At MV end ; we will process the leading IRP into the device queue and call the host ; MV end routine to start it up. If MV failed we have to flush I/O for ; the whole thing, of course. ; On return from end mv, we also will see if we have more to send the ; host and process that material in as needed. ; Thus we will have a MV mode of operation and a normal mode, since ; we don't switch at the same time as MV. ; (We must allow some MV activity first in case the problem is some "local" ; disturbance.) ; align to quadword .=<<.+7>& ^C7> ; Align data structure $DEF UCB$L_MPQUEUE ; alias for multipath queue hdr $DEF UCB$L_MYIRPH .BLKL 1 ; My IRP queue header $DEF UCB$L_MYIRPT .BLKL 1 ; (head and tail) $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 ;How many extends done $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$SWCONTFIL .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. ; In second version we add flags. This can be used to let intercepts know ; when they are prepared to handle fastio/finipl8 postprocessing. If they ; are they must set the FI8OK bit here. If they, or anyone below them, ; is not so prepared they must leave it clear. Processing must abide ; by this. (The bare device,when intercepted, will work ok anyhow if ; we have a legal driver structure. If someone intercepts nonstandardly ; (not using this code) they must ensure this flag is correct. $DEF UCB$L_ICPFGS .BLKL 2 ; Flags. Reserve 2 longs so we need ; not mess with this later. $VIELD UCB,0,<- ,- ; 1 if this intercept and all > ; below understand finipl8. $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=^XF007F000 + DDT$K_LENGTH + <256*> P.MAGIC=^XF007F000 + DDT$K_LENGTH + <256*> $DEF UCB$L_SW_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... ; fdt$k_length should be 68 longwords for the 64bit case, 66 longs for ; vms 6.1. The code even for 6.1 copied an extra quadword to be certain ; it got everything, so it actually requires no mods for 64 bit FDTs. ; It is essential that the complete FDT and DDT get copied. which ; symbolic use of length symbols will now assure. $DEF UCB$L_MYFDT .BLKL <+4>;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 ; The following lets us steal start-io and add error retries ; These entries therefore permit us to arrange that IRPs are queued ; properly to our input queue when mount verify is entered and/or ; exited. We intercept only the primary's entry points in this ; code, since VMS knows only about the primary as the mounted disk, ; but we record other drivers' entry addresses also so that we can ; conveniently call them when we need to. $DEF UCB$L_OMEDIA .BLKL 1 ;storage of orig. irp$l_media $DEF UCB$L_PPID .BLKL 1 ;store for IRP$L_PID contents $DEF UCB$L_RETRIES .BLKL 1 ;counter for i/o packacks $DEF UCB$L_HSTARTIO .BLKL 1 ;host driver start-io loc. $DEF UCB$L_INDRCT .BLKL 1 ;indirect flag $DEF UCB$L_MBXUCB .BLKL 1 ;mailbox to talk to server with $DEF UCB$L_DAEMON .BLKL 1 ;PID of server (to test that it's there) ; If zero this flag means we are using the direct path ; if 1, we are using indirect path (via althost) for I/O $DEF UCB$L_SAWSUCC .BLKL 1 ; 1 if we saw a success on current path ; Thus if we need to switch but have no successful I/O yet on the current ; path we're switching from (we initialize to 1 first) we must have switched ; to alt path, it still failed, and we want to switch back. In that case ; it looks like things are broken all over. Might as well switch, since ; one never knows which path will come up, but we can detect double ; failure at least. ; ; We keep the host device (the initial path one, whose startio we ; steal) UCB here, and also the UCB of the "althost", i.e., the ; device in the second path to the storage. This althost device is never ; mounted, and is to be made to look offline to the rest of VMS so that ; it never gets I/O. (This may be done by making the FDT functions ; all point to the illegal function entry, plus some tactic to keep ; MSCP's hands off...) ; By having both UCBs handy we can redirect I/O quickly when we need to. ; $DEF UCB$L_HSTUCB .BLKL 1 ;host ucb (quick ref) (main path) $DEF UCB$L_ALTHOST .BLKL 1 ;UCB of second-path driver .BLKL 8 ; Space for possibly more entries $DEF UCB$L_UCB0 .BLKL 1 ;UCB of unit 0 (to find daemon) $DEF UCB$L_SANITY .BLKL 1 ;sanity field ; To tell when I/O is still outstanding WE must keep track of it. ; This must be done whenever we alter an IRP, and counted down again when we ; un-alter an IRP. $DEF UCB$L_OUTSTND .BLKL 1 ;Outstanding i/o on current interface ; ; Additional entries we save & steal. ; These are used for handling our OWN IRP queue. ; ; Primary host entry pointers, then ; secondary hosts' pointers ; ; mount verify start or end $DEF UCB$L_OLDMV .BLKL 1 ;original mountver entry for dvr $DEF UCB$L_OLDMV2 .BLKL 1 ;mountver entry of althost .BLKL 8 ;allow some slop for more later ; alt start-io $DEF UCB$L_OALTST .BLKL 1 ;altstart entry of primary host $DEF UCB$L_OALTST2 .BLKL 1 ;altstart of althost .BLKL 8 ; The irp queueing cells save the pending_io entries $DEF UCB$L_QIRP .BLKL 1 ;IRP queueing entry of host $DEF UCB$L_QIRP2 .BLKL 1 ;IRP queueing entry of althost .BLKL 8 ; cancel-io $DEF UCB$L_CANCL .BLKL 1 ;Cancel entry host $DEF UCB$L_CANCL2 .BLKL 1 ;Cancel entry althost .BLKL 8 ; aux routine (for quorum lost processing) ; (dudriver uses this.) Gets control to one side or the other. $DEF UCB$L_AUX .BLKL 1 ; host aux routine $DEF UCB$L_AUX2 .BLKL 1 ; alt host aux routine .BLKL 8 ; Make the size of the enapth block the same as used for additional ; entries. $def UCB$L_ENAPTH .BLKL 10 ; flags whether a path is enabled. $def ucb$l_fgdun .blkl 1 ; Flag for startio area of irp bashed $def ucb$l_fgdun2 .blkl 1 ; similar for pstealstart ; $def UCB$L_cancln .blkl 1 ; temp save for cancel count ; The hack used by MSCP to let drivers tell when an IRP comes from it needs ; (alas) to be duplicated here (so we'll set the fake pcb+pcb$l_pid to the ; intercept address we use.) $def UCB$L_FAKEPCB .BLKB PCB$K_LENGTH ;fake PCB for this device .if df,d$$bug $def ucb$l_dbgdta .blkl 1 .endc $DEF UCB$K_SW_LEN .BLKL 1 ;LENGTH OF UCB ;UCB$K_SW_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 SW_UNITS=500 SW$DPT:: .IIF NDF,DPT$M_XPAMOD,DPT$M_XPAMOD=0 DPTAB - ;DPT CREATION MACRO END=SW_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_SW_LEN,- ;LENGTH OF UCB MAXUNITS=SW_UNITS,- ;FOR SANITY...CAN CHANGE NAME=SWDRIVER,- ;DRIVER NAME DPT=DRIVER$DPT 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 ; make OUR fork IPL not have bit 5 set, to tell fork dispatcher NOT to get ; a spinlock for OUR fork (so as not to interfere with any locks by having ; the fork dispatch return clear a lock out from under a driver!) 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. The actual characteristics don't matter much, ; just so the device is not picked up by anything as "interesting". 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$_MISC ;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,8 ;DEVICE IPL. (=IPL$_SYNCH) ; 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,SW_INT ;INTERRUPT SERVICE ROUTINE ADDRESS DPT_STORE DDB,DDB$L_DDT,D,SW$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 UCB,UCB$L_SANITY,L,<^A\SWIT\> 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. ; ;SW$DDT: ; Actually the presence of fastio in the intercept driver is of ; no importance either since it isn't really a disk... .IF DF,IRP$Q_QIO_P1 DDTAB - ;DDT CREATION MACRO DEVNAM=SW,- ;NAME OF DEVICE START=SW_STARTIO,- ;START I/O ROUTINE FUNCTB=SW_FUNCTABLE,- ;FUNCTION DECISION TABLE CTRLINIT=SW_CTRL_INIT,- UNITINIT=SW_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 FAST_FDT=ACP_STD$FASTIO_BLOCK ; Fast-IO FAST_FDT routine .IFF DDTAB - ;DDT CREATION MACRO DEVNAM=SW,- ;NAME OF DEVICE START=SW_STARTIO,- ;START I/O ROUTINE FUNCTB=SW_FUNCTABLE,- ;FUNCTION DECISION TABLE CTRLINIT=SW_CTRL_INIT,- UNITINIT=SW_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. ; ; NOTE: Be sure the FDT table is 8 byte aligned!!!! The addins below are ; 4 longwords which will not screw up quad alignment... .align quad .if df,d$$bug doxdt: .long 0 inmve: .long 0 ntruse: .long 0 ntrmsk: .long 0 ;save callers to getswucb gswu1: .long 0,0 gswu2: .long 0,0 gswu3: .long 0,0 gswu4: .long 0,0 .endc UCB0ADR: .LONG 0,0 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 /DFLG/ ;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 ; .ALIGN QUAD SW_FUNCTABLE: FDT_INI FDT_BUF - ; BUFFERED functions ; MOUNT VOLUME .IF DF,IRP$Q_QIO_P1 ; Note that as an intercept driver we copy the target FDT and actually don't ; need this, but do it for beauty. FDT_64 <- ; Functions supporting 64-bit addresses AVAILABLE,- ; Available (rewind/nowait clear valid) NOP,- ; No operation PACKACK,- ; Pack acknowledge READLBLK,- ; Read logical block forward READPBLK,- ; Read physical block forward READVBLK,- ; Read virtual block SENSECHAR,- ; Sense characteristics SENSEMODE,- ; Sense mode SETCHAR,- ; Set characterisitics SETMODE,- ; Set mode UNLOAD,- ; Unload volume WRITECHECK,- ; Write check WRITELBLK,- ; Write LOGICAL Block WRITEPBLK,- ; Write Physical Block WRITEVBLK> ; Write VIRTUAL Block .ENDC MYFDTSTART: ; IO$_FORMAT + modifiers (e.g. io$_format+128) as function code ; allows one to associate a SW unit and some other device; see ; the SW_format code comments for description of buffer to be passed. FDT_ACT SW_FORMAT,- ;point to host disk FDT_ACT SW_RETCTR,- ;point to host disk ;Use this function to go to main dsk ;or to alt disk depending on arg ; ; 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!) .if eq,1 ;never true fdt_act MFYFilt,- ;modify filter (e.g. extend) .endc MYFDTEND=. SW_UCBTBL:: .REPT SW_UNITS .long 0 .ENDR .LONG 0,0,0,0,0,0,0,0,0,0 SW_UCB:: .REPT SW_UNITS .long 0 .ENDR .LONG 0,0,0,0,0,0,0,0,0,0 ; OFFSET ADDRESS TABLE V_UNM=0 ; Note: code elsewhere assumes that the xxvc macro generates 8 bytes. ; If .address generates more than 4, it breaks as coded here!!! .MACRO XXVC LBLCT .ADDRESS SW_FXS'LBLCT .GLOBL SW_FXS'LBLCT .LONG 0 .ENDM SW_VOADT:: .REPT XXVC \V_UNM V_UNM = .ENDR ; second set of addresses that can be used for mscp stuff. .MACRO MXXVC LBLCT .ADDRESS MSW_FXS'LBLCT .GLOBL MSW_FXS'LBLCT .LONG 0 .ENDM V_UNM=0 MSW_VOADT:: .REPT MXXVC \V_UNM V_UNM = .ENDR DRIVER_CODE ; ; GETSWUCB - Find SW: 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. ; GETSWUCB: .JSB_ENTRY OUTPUT= ; clrl r0 ;no UCB initially found ; save the last few calls' addresses .if df,d$$bug movl gswu3,gswu4 movl gswu2,gswu3 movl gswu1,gswu2 movl gswu3+4,gswu4+4 movl gswu2+4,gswu3+4 movl gswu1+4,gswu2+4 movl (sp),gswu1 ;save return address movl r3,gswu1+4 ;save call irp too .endc 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)+,CONDITION=RESTORE,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 SWDRiver versions to call ; getSWucb, 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 GETSWUCB ;Find SW 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 ;++ ; ; SW_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 ;-- SW_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_SW_HOST_DESCR+4(R5) SUBL3 #8,IRP$L_QIO_P2(R3),- UCB$L_SW_HOST_DESCR(R5) BLEQ 100$ ;bad length MOVAB UCB$L_sw_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. ; This segment really needs to invalidate TBs when clobbering the ; DDT and possibly should have mutex too, but normal use is at ; system startup where this won't matter for now. 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) ; This allows us to create fake channels to nla0: and bash them to channels ; to the host devices associated if we need to...stuff like that. MOVAB SW_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 ; 380=ss$_ivstsflg MOVL #380,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 ; Now also check the FI8OK flag and ; set ours if the one previous has been set. ; R10 is the previous DDT pointer. If this is an intercept we can find HIS ; flag by offset from outs. BBC #UCB$V_FI8OK,(R10),102$ ; WE can handle IPL 8 fin and below can so set our flag to ok BISL #UCB$M_FI8OK,ucb$l_icpfgs(r5) ;say WE CAN handle finipl8 BRB 101$ 102$: ; Somewhere below cannot handle IPL8 fin so we can't either. ; Note if we can't handle it we always clear this bit. BICL #UCB$M_FI8OK,ucb$l_icpfgs(r5) ;say WE CAN'T handle finipl8 BRB 101$ 1$: ; First bash of a device, never before bashed. IPL8 finish has to be ok. ; Note we clear this bit instead of setting it if we cannot handle ipl 8 ; finish-up. BISL #UCB$M_FI8OK,ucb$l_icpfgs(r5) ;say WE CAN handle finipl8 101$: MOVL #1,UCB$L_MUNGD(R5) ;say we munged SW MOVL R10,UCB$L_PREVDDT(R5) ;set previous DDT address up CLRL UCB$L_INTCDDT(R5) ;clear intercepting DDT initially ; Now check the DEVICE UCB immediately below us too for some special ; pattern of weird bits in devchar2. This pattern must be chosen to be ; stuff that disks won't be affected by but that can be set by intercept ; drivers to flag that they can't handle ipl 8 stuff. If we are intercepting ; something that's a real device or an interceptor that's ok, no ; need to worry. Thus devices like vddriver, that aren't really ; intercept drivers but are pseudo devices, can tell the chain what ; is up. ; For this we select the pattern of all of: ; DET (device is detached terminal), SEX (serious exception handling), ; and HOC (host cache supp.)(dev_m_xxx for all). If all 3 are present we will ; not use ipl 8 stuff. If not all 3 are present they will be presumed to ; have no special meaning for us. ; This is chosen to because existing real devices won't have this pattern ; (strictly speaking it is senseless) and the bits shouldn't cause any ; trouble by being set. The devchar fields are too full to just grab ; another bit for this at present, though if a device driver stream ; facility should be added, this kind of thing ought to be "in there". movl ucb$l_devchar2(r11),r8 ; R8 is about to be munged bicl #^c,R8 ; Leave only the bits we care about cmpl #^c,R8 ; Are all three set? bneq 3$ ; If not, no special action ; Device can't handle ipl 8 posting so flag it thus. BICL #UCB$M_FI8OK,ucb$l_icpfgs(r5) ;say WE CAN'T handle finipl8 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,sw_FUNCTABLE+FDT_PREV ;save old FDT ucb address MOVL DDT$L_FDT(R10),UCB$L_OLDFDT(R5) MOVL UCB$L_UNIQID(R5),sw_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 ;actually with 64 bits the FDT length is 64 longs for function addresses, ; 2 longs for buffered, 2 longs for 64 bit mask ; Add a quadword slop to ensure we're long enough. ; movl #<70*4>,r0 ;byte count of a step 2 FDT + slop MOVL #,R0 ;byte count of a step 2 FDT + slop 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 SWDRiver that we service locally. Thus ; all entry cells for the rest will point in the SW FDT to ; exe$illiofunc. MOVAB G^EXE$ILLIOFUNC,R8 ;get the magic address MOVAB SW_FUNCTABLE,R10 ;R10 becomes SW 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 SW_FORMAT,R7 ; LET VICTIM'S FORMAT FDT BY .ENDC 75$: CMPL (R10),R8 ;this function hadled in SW? 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 ; SWDRiver 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 ; ; Set up victim's startio toour steal-startio after saving the address here MOVL DDT$L_START(R8),UCB$L_HSTARTIO(R5) ;save host start-io MOVL R11,UCB$L_HSTUCB(R5) ;save backpointer too MOVAB STEALSTART,DDT$L_START(R8) ;point at our startio ; ; Set up addresses of other internal routines, corresponding to ; the externally called entries that dkdriver or dudriver may ; use. ; save main host's entry addresses movl ddt$ps_mntver_2(r8),ucb$l_oldmv(r5) movl ddt$ps_altstart_2(r8),ucb$l_oaltst(r5) movl ddt$ps_pending_io(r8),ucb$l_qirp(r5) movl ddt$l_aux_routine(r8),ucb$l_aux(r5) movl ddt$ps_cancel_2(r8),ucb$l_cancl(r5) ; get aux host's entries too, if they're here. swctl image fills that in. pushl r8 movl ucb$l_althost(r5),r8 ;get alt ucb bgeq 12$ ;if none, skip movl ucb$l_ddt(r8),r8 ;point at alternate ddt ; store ddt entries from aux device also (alt host) movl ddt$ps_mntver_2(r8),ucb$l_oldmv2(r5) movl ddt$ps_altstart_2(r8),ucb$l_oaltst2(r5) ; No need for this count on secondary device. One count at primary is enough. ; However, ensure that dispatch goes to correct side. movl ddt$ps_pending_io(r8),ucb$l_qirp2(r5) movl ddt$l_aux_routine(r8),ucb$l_aux2(r5) movl ddt$ps_cancel_2(r8),ucb$l_cancl2(r5) 12$: popl r8 ; now insert the ones here movab steal_mount_ver,ddt$ps_mntver_2(r8) movab steal_altstart,ddt$ps_altstart_2(r8) movab steal_pending_io,ddt$ps_pending_io(r8) movab steal_aux_routine,ddt$l_aux_routine(r8) movab steal_cancel,ddt$ps_cancel_2(r8) ; ; Finally clobber the victim device's DDT pointer to point to our new ; one. Note this is only one instruction on Vax. On alpha, we'll use ; the "preserve atomicity" brackets to ensure the memory cell is ; updated atomically. (This should be a nobrainer.) Should still ; invalidate TB here too... ; ; Put an IMB so before we do this, the tables are in memory. That way ; when we replace the one pointer below, the tables to be pointed ; to are known valid. evax_imb .if df,evax .preserve Atomicity .endc MOVAB UCB$A_VICDDT(R5),UCB$L_DDT(R11) .if df,evax .nopreserve atomicity .endc ; Now another IMB so nothing more happens till this replacement is also ; in real memory, not just cache. evax_imb ; ; 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)+,- CONDITION=RESTORE,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 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 ; SW_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 SW_FUNCTABLE,R2 ;address of our FDT table CLRL R3 MOVAB <0-UCB$A_VICDDT>(R10),R4 ;initially point at our UCB ; Also set the SW 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 SW munged flag 4$: FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,CONDITION=RESTORE,PRESERVE=YES POPR #^M ;copy our prior DDT ptr to next one RSB .MACRO $ARG_DEF argument_list argument_offset = 4 .IRP argument, argument = argument_offset argument_offset = argument_offset + 4 .ENDR .ENDM $ARG_DEF ; ; SW_RETCTR : "forcibly" switch driver to use either main or alternate ; disk. Assumes one parameter, a buffer containing 1 for switch to main ; disk, 2 to switch to alternate disk. ; SW_RETCTR: $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 20$: MOVL IRP$L_QIO_P1(R3),R0 ;BUFF ADDRESS MOVL IRP$L_QIO_P2(R3),R1 ;BUFF LENGTH CALL_WRITECHK PUSHR #^M .PRESERVE ATOMICITY TSTL UCB$L_OUTSTND(R5) ;Bump outstanding I/O count BEQL 220$ .NOPRESERVE ATOMICITY ; Do not switch paths if any outstanding I/O exists on this path that has ; not returned in some fashion. Only switch if things are idle or timed out ; in case a failed controller should quickly come back. POPR #^M MOVL #SS$_ABORT,R0 BRW 52$ 220$: MOVL IRP$L_QIO_P1(R3),R0 MOVL (R0)+,R1 ;get option code BLEQ 100$ ;negative illegal CMPL R1,#2 ;3 and up illegal too BGTR 100$ ; 1 = go to main ; 2 = go to alt device DEVICELOCK SAVIPL=-(SP),PRESERVE=YES CMPL R1,#1 ;use main? BNEQ 40$ ; if neq no, use alt ; use main device. ; ; Also ensure against a race condition at switchover by clearing the retry ; counter used at packack, so that if some outstanding I/O causes a packack ; that logic doesn't switch back to the bad side. CLRL UCB$L_RETRIES(R5) ; Note that the switcher (our caller) should send packack directly to the ; "new" disk PRIOR to calling here. ; ; Is direct path enabled? tstl ucb$l_enapth(r5) ; direct path enabled? beql 50$ ; if not don't allow use CLRL UCB$L_INDRCT(R5) ; set to use direct path BRB 50$ 40$: ; ; Also ensure against a race condition at switchover by clearing the retry ; counter used at packack, so that if some outstanding I/O causes a packack ; that logic doesn't switch back to the bad side. CLRL UCB$L_RETRIES(R5) ; Note that the switcher (our caller) should send packack directly to the ; "new" disk PRIOR to calling here. tstl ucb$l_enapth+4(r5) ; is indirect path enabled? beql 50$ ; if not leave it alone MOVL #1,UCB$L_INDRCT(R5) ; set to use indirect path 50$: DEVICEUNLOCK NEWIPL=(SP)+,PRESERVE=YES POPR #^M MOVZWL #SS$_NORMAL,R0 ;SUCCESS 52$: CALL_FINISHIOC DO_RET=YES ; ret ; ; Steal pending io ; Input: arg1= queue hdr, arg2 and r3=irp ; called with calls #2... though R5 is UCB also at call ; R1=queue hdr after fetch and r3=ucb. ; Returns status in R0 ; ; Here I/O is about to be inserted at the underlying driver's queue ; for later work. Put it instead on our queue, but process pending entries ; of our queue and send to the underlying driver's queue. ; ; In that way, if the underlying path is known busy, we get called ; here and move everything to the current driver's queue. There is no ; good reason to leave it on our multi-input queue; it will go back ; there if MV starts, but we want to preserve all driver optimisations ; that keep multiple I/Os going. (Normally we should only have one ; queue entry to deal with.) ; ; If in mount verify, put pending stuff on multi-input queue instead though. ; ; If we already altered the IRP sometime before (eg in mount verify ; processing), don't do it twice. Just put it into the queue. Thus any ; calls from start-io that use insioq(c) and wind up putting an entry ; on the underlying busy queue of the main path will just get passed ; through. Other paths aren't so affected since their DDTs are intact. ; steal_pending_io: $DRIVER_PENDING_IO_ENTRY preserve=,FETCH=YES ;getswucb assumes PRIMARY host ucb in r5 ; r1 is listhead; r3 is irp .if df,d$$bug tstl ntruse beql 1700$ bbcc #1,ntrmsk,1700$ ; .iif df,x$$$dt,jsb g^ini$brk 1700$: .endc ; Fastio sometimes dispatches and calls the pending io entry. If this ; happens call underlying routine. bbs #IRP$V_MVIRP,IRP$L_STS(R3),630$ ;skip if a mv irp ; Test for shortcut indicating this IRP has been seen already and just ; queue it if so. tstl irp$q_qio_p3+4(r3) beql 630$ CLRL IRP$Q_QIO_P3+4(R3) ; Clear skipping flag one-shot PUSHL R3 PUSHL R1 CALLS #2,G^EXE_STD$INSERT_IRP RET 630$: PUSHL R5 PUSHL R3 ;; movl 4(ap),R1 ;queue hdr ;; MOVL 8(AP),R3 ; If irp$l_ucb is unsuitable, use call R5 ; System will call here for main device (i/o to main path) but ; subordinate paths are not trapped. ; We should not get to this point on secondary paths .if df,x$$$dt cmpl r5,irp$l_ucb(r3) beql 530$ jsb g^ini$brk ;*********debug******** 530$: .endc TSTL IRP$L_UCB(R3) BGEQ 30$ MOVL IRP$L_UCB(R3),R5 30$: movl r5,IRP$L_UCB(R3) ;fill in what we hope is right ; At this point R5 better be the UCB... ; Fastio sometimes dispatches and calls the pending io entry. If this ; happens call underlying routine. bbs #IRP$V_MVIRP,IRP$L_STS(R3),35$ ;skip if a mv irp bitl #,UCB$L_STS(R5) ;MV going on? beql 35$ ; if not in mv, call driver routines jsb g^ini$brk ;*********debug******** ; If in mount verify, don't add anything more to the device queue...leave ; it in the master input queue. ; (start-io processing will edit the IRP as needed). pushl r0 JSB GETSWUCB ;find intercept UCB tstl r0 beql 238$ ;if none, lose... pushl r1 movab ucb$l_mpqueue(r0),r1 ;get OUR queue hdr ; Insert the IRP in OUR queue if we are in MV (or about to be). ; That way we needn't constantly readjust. pushl r5 movl r0,r5 ;undo any prev. mods pushl r5 pushl r3 ;irp calls #2,undomods ;un-alter this irp if it had been ;altered by us. popl r5 ;{ PUSHL R3 PUSHL R1 CALLS #2,G^EXE_STD$INSERT_IRP ;} popl r1 popl r0 ;restore regs we saved brw 7$ 238$: .if df,x$$$dt jsb g^ini$brk ;*********debug******** .endc popl r0 brw 1$ ; NOT in mount verify. 35$: ; First be sure we aren't doing the IRP "processing" twice. If the IRP ; is already "processed", just queue it. ; ("Processing" in this case means setting the IRP up to go thru one ; or the other path (so the underlying driver layers will not object) and ; arranging to regain control at our "fixsplit" entry to put things back ; afterwards and count busy counts down. We count the "busy" count up when ; we put an IRP onto an underlying queue and down when the IRP finishes.) .if df,d$$bug tstl doxdt beql 703$ ; .iif df,x$$$dt, jsb g^ini$brk 703$: .endc pushl r0 ; Use R0 for intercept UCB all over pushr #^m JSB GETSWUCB ;find intercept ucb TSTL R0 ;did we get it? blss 991$ ; can't be a UCB...wrong address. Bug out. .iif df,x$$$dt, jsb g^ini$brk popr #^m popl r0 brw 1$ 991$: pushl r5 movl r0,r5 ;undo any prev. mods pushl r5 pushl r3 ;irp calls #2,undomods ;un-alter this irp if it had been ;altered by us. popl r5 ; Process the IRP now to go onto the underlying queue. ; Pstealstart sets the IRP up for the driver path we are currently using. pushl #0 ; arg is 0, don't queue CALLS #1,PSTEALSTART ; Process the IRP for the dest. we have. ; The IRP is now altered to go on a driver queue, but we didn't put it on ; yet. That will be done by the appropriate driver's routine now. ; 38$: popr #^m ; brb 338$ 338$: .iif df,m$$trp,movl #1,ucb$l_mtrp(r0) .iif df,m$$Trp,movl r3,ucb$l_mtrp+32(r0) ; TEST the entry. r1 is at 4(ap), queue hdr ; r3 at 8(ap) is IRP ; COUNT STUFF FIRST. THEN DISPATCH. ; &&&&&&&&&&& NEEDS COUNTING STUFF... ; Note that we only count for primary side. ; This is because everything goes thru there. PUSHL R11 ;R0 is return status, can't use it MOVL R0,R11 ; Point at SW UCB ; Push the original args as fetched from the caller. MOVL PENDING_IO_ARG$_LIST_HEAD(AP), R1 MOVL PENDING_IO_ARG$_IRP(AP), R3 PUSHL R3 ; IRP PUSHL R1 ; LIST HEAD ;; PUSHL PENDING_IO_ARG$_IRP(AP) ; IRP ;; PUSHL PENDING_IO_ARG$_LIST_HEAD(AP) ; Queue hdr TSTL UCB$L_INDRCT(R11) BNEQ 2$ ; Note these are the original entries from before we grabbed them,so ; we won't recurse into ourselves. ; ; (It ought to be impossible to have null entries in these cells; this is ; extra code to just be safe during tests.) TSTL UCB$L_QIRP(R11) ;call underlying "add to queue" BGEQ 10$ CALLS #2,@UCB$L_QIRP(R11) BRB 14$ ; If the driver has no call, call the standard thing 10$: CALLS #2,G^EXE_STD$INSERT_IRP BRB 14$ 2$: TSTL UCB$L_QIRP2(R11) ; Don't call a null entry BGEQ 11$ CALLS #2,@UCB$L_QIRP2(R11) ; Call this OR the "..qirp" entry above ; but not both. BRB 14$ ; If the driver has no call, call the standard thing 11$: CALLS #2,G^EXE_STD$INSERT_IRP 14$: POPL R11 popl r0 BRB 7$ 1$: ; If we couldn't find intercept, call the usual routine for this site and pray. ; (Should never happen.) .iif df,x$$$dt,jsb g^ini$brk ;**************** debug *********** PUSHL R3 PUSHL R1 CALLS #2,G^EXE_STD$INSERT_IRP 7$: POPL R3 POPL R5 RET ; ; aux routine (for revalidate) ; Tells mscp server to find another path ; Input DDT in R0 ; Implement by just calling the underlying driver. ; ; With the qio server, this is a good place to tell the server to make ; a new connection here. steal_aux_routine: .jsb_entry INPUT=,OUTPUT= ;R3 = CDDB ADDR ON INPUT .if df,d$$bug .iif df,x$$$dt, jsb g^ini$brk .endc PUSHL R5 ADDL3 #, R3, R5 ; R5 IS NOW UCB PUSHL R0 PUSHL R1 PUSHL R11 MOVL R0,R1 ;save call R0 in case routine uses it JSB GETSWUCB ;find intercept ucb TSTL R0 ;did we get it? BGEQ 1$ ; IF EQL NO MOVL R0,R11 MOVL R1,R0 ; Let call R0 look like it would have .iif df,m$$trp,movl #3,ucb$l_mtrp(r11) TSTL UCB$L_INDRCT(R11) BNEQ 2$ TSTL UCB$L_AUX(R11) BGEQ 1$ JSB @UCB$L_AUX(R11) BRB 1$ 2$: TSTL UCB$L_AUX2(R11) BGEQ 1$ JSB @UCB$L_AUX2(R11) 1$: POPL R11 POPL R1 POPL R0 POPL R5 RSB ; cancel (channel, irp, pcb, ucb, reason) ; Just dispatch to the correct underlying driver using the step 2 ; interface. ; Note we must check if any IRPs disappear so we can adjust outstanding io ; count... ; ; We need to handle cases where the cancel sys svc was run, and find ; and remove IRPs on our queues here and account for their removal ; (check where we would have copied irp$l_pid and so on) and ; post these if the reason for cancel is CAN$C_CANCEL or CAN$C_DASSGN ; and adjust the count of outstanding I/O accordingly. ; our call is ; PUSHL R8 ;Reason (Stack args for CALLS) ; PUSHL R5 ;UCB ; PUSHL R4 ;PCB ; PUSHL R3 ;IRP ; PUSHL R2 ;Channel ; CALLS #5, @DDT$PS_CANCEL_2(R0) ;CALL CANCEL I/O ROUTINE ; ; We need to trap he driver DDT$PS_CANCEL_SELECTIVE_2 entry too, whose call is ; .SET_REGISTERS READ=,WRITTEN= ; PUSHL R8 ;iosb count (Push args on stack for CAL$ ; PUSHL R7 ;iosb vector ; PUSHL R6 ;channel ; PUSHL R5 ;UCB ; PUSHL R4 ;PCB ; CALLS #5, @DDT$PS_CANCEL_SELECTIVE_2(R2) ; ; (This routine is usually not there). ; (In fact I can't find anything in [scsi] or [driver] that uses it. Looks ; like mostly a ttdriver thing. I won't bother with it here, just send ; to main path always to be junked there. ;&&&&&&&&& ; Note no drivers currently ever use cancel_selective, but we need to ; handle it. ; ; Cancel sys service will not recognize our IRPs at all, since it ; will find them either with a PID that they can't recognize or ; they won't be on a known queue. We however need to find and ; remove any such by looking where the PID is. ; ; The cancel entry is called with arguments as follows: ; CANCEL (channel, irp, pcb, ucb, reason) ; reason = CAN$C_CANCEL or CAN$C_DASSGN ; ; Fork lock is held. ; steal_cancel: $DRIVER_CANCEL_ENTRY PRESERVE = ,FETCH=NO .if df,d$$bug tstl ntruse beql 1700$ bbcc #2,ntrmsk,1700$ ; .iif df,x$$$dt,jsb g^ini$brk 1700$: .endc PUSHL R5 MOVL canarg$_ucb(AP),R5 ;GET UCB of the "prime" path device movl canarg$_chan(ap),r7 movl canarg$_irp(ap),r3 movl canarg$_reason(ap),r8 ;fetch the arguments into regs again movl canarg$_pcb(ap),r4 BGEQ 1$ jsb getswucb ;find intercept ucb tstl r0 ;did we get it? beql 1$ movl r0,r9 ; Put intercept UCB in R9 too ; ; This is called when channels are deassigned too. Omit all the ; shuffling and just pass to the desired underlying driver ; routine. Therefore snip out all the cruft in the ".if eq,1" block ; below. .if df,d$$bug tstl doxdt beql 702$ ; .iif df,x$$$dt, jsb g^ini$brk 702$: .endc .if eq,1 ;never ; Now copy host input queue to mpth one. R5 is now the phys dvc ucb. ; ; remove from back of old irp queue, insert at front of mpath one ; This will result in preserving the order. ; ; NOTA BENE:::: ; We may be able to optimize this using pointers ("move the headers") ; to get it all. ; movab ucb$l_ioqfl(r5),r11 ;get addr of start of queue 24$: movl ucb$l_ioqbl(r5),r10 ;get last queue entry cmpl r10,R11 ;did we come to start? beql 22$ ;if so we are done remque (r10),r3 ;get an entry bvs 22$ ; if vs, nothing there ; Undo mods to this queue element pushl r0 ; Push intercept ucb addr pushl r3 ; undo mods ; Reprocess everything to undo the IRP mods and replace on the multiport ; input queue. IN this way everything WE know of that is in the IRP will ; be in standard positions so cancel's recognition will be OK. We duplicate ; the logic because our queue is not the "standard" one. calls #2,undomods ; restore IRP to "pristine" cond'n insque (r3),ucb$l_mpqueue(r9) ;insert the IRP into mp queue brb 24$ 22$: ; ; Now try to replicate the sys$cancel processing on the multipath queue ; to remove anything that ought to be removed from there. ; ; At this point irp$l_pid is replaced so that code will work fairly well ; as is. We want now to ensure that any IRPs not already somewhere inside ; a driver are cancelled appropriately, so we so operate with each ; IRP still on our input queue (either at the multiport queue or at ; the underlying driver UCB queue). ; ; Note this code is largely taken from SYSCANCEL.MAR and will need to ; track edits made there if any. ; ; If the driver is now idle, we have to be sure we tickle it again. tstl r3 ; Any i/o going on? beql 160$ movab ucb$l_mpqueue(r9),r3 ; get our master listhead cmpl r3,(r3) ; Does the q look empty? beql 160$ ; if empty, don't mess with it. MOVL R3,R2 ;COPY ADDRESS OF I/O QUEUE LISTHEAD 120$: MOVL IRP$L_IOQFL(R2),R2 ;GET ADDRESS OF NEXT I/O PACKET IN QUEUE CMPL R2,R3 ;END OF QUEUE? BEQL 160$ ;IF EQL YES BBS #IRP$V_VIRTUAL,IRP$L_STS(R2),120$ ;IF SET, VIRTUAL I/O REQUEST CMPL IRP$L_PID(R2),PCB$L_PID(R4) ;PROCESS ID MATCH? BNEQ 120$ ;IF NEQ NO CMPL R7,IRP$L_CHAN(R2) ;I/O CHANNEL NUMBER MATCH? BNEQ 120$ ;IF NEQ NO MOVL IRP$L_IOQBL(R2),R2 ;GET BACKWARD LINK OF CURRENT ENTRY REMQUE @IRP$L_IOQFL(R2),R1 ;REMOVE I/O PACKET FROM QUEUE BBC #IRP$V_BUFIO,IRP$L_STS(R1),130$ ;IF CLR, DIRECT I/O REQUEST BICL #IRP$M_FUNC,IRP$L_STS(R1) ;CLEAR BUFFERED READ 130$: MOVZWL #SS$_CANCEL,IRP$L_MEDIA(R1) ;SET COMPLETION STATUS PUSHL R3 ; SAVE R3 MOVL R1,R3 ; COPY IRP ADDRESS .SET_REGISTERS READ=,WRITTEN= .if df,x$$$dt cmpl #^xb00bface,irp$q_qio_p6(r3) bneq 1131$ ; if boobytrap not seen skip ; jsb g^ini$brk 1131$: movl #^xb00bface,irp$q_qio_p6(r3) .endc pushl r0 JSB G^COM$POST_NOCNT ; POST IRP FOR COMPLETION popl r0 ; Must preserve intercept UCB! POPL R3 ; RESTORE R3 BRB 120$ ; 160$: ; (The driver cancel entry has forklock at the time of call from the ; sys$cancel service.) ; We must preserve some registers across the call, for safety. pushr #^m ; 43$: remque @ucb$l_mpqueue(r9),r3 ;get an IRP off our input queue bvs 42$ ; if nothing there skip out ; ok, we have an IRP to insert. Gotta relocate the sucker & put it onto the ; host driver's input queue that we are using. ; First we need to "process" it, though. pushl #1 ; Tell pstealstart to move the IRPs calls #1,PSTEALSTART ;go process the IRP to input queue ; The pstealstart routine does not start i/o, just moves IRPs to queues. brw 43$ 42$: popr #^m ; Now all IRPs should be on the correct underlying device UCB queues. ; .endc ;eq,1 ; Now if the cancel comes from MSCP, look over the IRPs to see if they ; have pcb$l_pid matching irp$l_pid and both negative. That will pretty ; well flag the IRP is from MSCP (this hack is used) so point the IRP ; at OUR fake PCB and fill it in. movl canarg$_pcb(ap),r4 .if ndf,can_any_srvr ;(we may want to do this for other servers too) cmpl canarg$_reason(ap),#CAN$C_MSCPSERVER ; This cancel for MSCP BNEQ 55$ ; if not skip this stuff .endc movab ucb$l_ioqfl(r5),r11 ;get addr of start of queue ; We're at fork here so just loop thru all the IRPs if any. movl R11,R10 ; working reg is R10 50$: movl (r10),r10 ; Get the next IRP if any cmpl R10,R11 ; Back at start? beql 55$ ; If so exit the loop ; Original irp$l_pid is at irp$q_qio_p2+4 tstl irp$q_qio_p2+4(r10) ; see if negative bgeq 50$ ; if not, not a server's cmpl irp$q_qio_p2+4(r10),pcb$l_pid(r4) ; match? bneq 50$ ; ok, this IRP looks like it came from MSCP (or maybe something else) ; that faked the pcb$l_pid cell. ; So do it ourselves. MOVZWL UCB$W_UNIT(R9),R7 ; Get the unit number ASHL #3,R7,R7 ; Turn into an offset ; Use our second set of entry vectors for postprocessing ; They'll work exactly like the other ones, but will not be the ; same so that a compare of pcb$l_pid with them won't come out equal ; unless we reset them here. That way something from an MSCP server ; will still appear to match even though we intercepted it because ; we will have played the same dirty trick MSCP does. MOVAB MSW_VOADT,R8 addl2 r8,r7 ; Form address of pointer movl (r7),irp$l_pid(r10) ; Stash that in the IRP movab ucb$l_fakepcb(r9),r8 ; get our fake PCB movl (r7),pcb$l_pid(r8) ; And make pid look ok movl r9,r4 ; then reset R4 so we use it ; No need to loop once we have at least one mscp irp... ; brw 50$ 55$: ; PUSHL canarg$_reason(AP) PUSHL canarg$_ucb(AP) ; A driver cancel will want to find its own UCB, not some other one's. ; Note however that we must ALSO ensure that any I/O that is on the master ; queue is made a candidate for cancel also. ; PUSHL R4 ; Use our fake pcb or orig real one PUSHL canarg$_irp(AP) PUSHL canarg$_chan(AP) TSTL UCB$L_INDRCT(R9) BNEQ 2$ ; path 1 being used movl ucb$l_hstucb(r9),r5 ;point R5 at the UCB of driver we call TSTL UCB$L_CANCL(R9) ; Be sure the entry is not null BGEQ 10$ ; If driver entry isn't there don't call movl ucb$l_hstucb(r9),r5 ;point R5 at the UCB of driver we call CALLS #5,@UCB$L_CANCL(R9) BRB 16$ 10$: ADDL2 #20,SP ;unpush args BRB 16$ 2$: ; path 2 TSTL UCB$L_CANCL2(R9) ; Be sure the driver entry exists BGEQ 20$ movl ucb$l_althost(r9),r5 ;point R5 at the UCB of driver we call CALLS #5,@UCB$L_CANCL2(R9) BRB 16$ 20$: ADDL2 #20,SP ;unpush args 16$: ; ; Note that dkdriver at least does not properly deal with IRPs from MSCP ; server so its driver cancel is almost useless. DUdriver does rather more ; but since irp$l_pid is clobbered here, its impact will be minimized. ; .if eq,1 ; I think this stuff is misled too ; If the driver queue is not empty, we might have caused its I/O queue ; to drain here during processing and inhibited further action. Thus ; check the driver queue underlying and if it is not empty, pull the first ; entry off the queue and reinsert with insioqc to get it where it needs ; to be. ; The underlying driver is inhibited from getting back to the start ; of its queue again because we still have the forklock, so an interrupt ; driven path that forks should still be unable to dequeue anything else ; until we give that up & drop IPL. Therefore we should be able to ; just pull the first I/O off and send it on down. movab ucb$L_IOQFL(R5),r11 ; get the IOQ address cmpl R11,(R11) ; Queue look empty? If so no work BEQL 516$ ; Branch around the work... ; Gotta pull an entry off. Do so and insioqc... remque @UCB$L_IOQFL(R5),R3 ; get an item bvs 516$ ; (If we fail skip it) pushl r0 ; Save status from cancel call_insioqc ; Put it back or call the driver popl r0 516$: .endc ; eq,1 ; At this point R0 should be driver result 1$: TSTL R0 BNEQ 30$ MOVL #SS$_WASSET,R0 ;caller ignores status 30$: POPL R5 RET ; ; ; Steal driver mount-verify entry. ; Args: R3, R5. Called by calls #2 ; Environment is fork thread,but does not lock/unlock anything. ; ; When this entry is called, R3 is the current IRP (if entering mv) ; which must be put back on the driver's input queue. ; ; If r3=0, we have no IRP but are at MV end and must restart ; things. ; ; If R3 is nonzero, we need to call the correct driver's underlying ; MV start routine (which may just stash the IRP on that driver's ; input queue) and then ourselves pull it back to the multiport ; input queue. If it is zero, we need to ensure that an entry off ; the multiport queue is processed thru to the driver's input ; queue and call the driver's MV entry to end its waiting. ; ; One complexity is that we need to be sure MV gets set on the device ; that is known to VMS (the primary path), but we will set this by hand ; for the case of the secondary path. Since we won't queue anything ; to actual start_io of the inactive path, whether any driver-internal ; busy bits are not set is immaterial. (In fact we do not want them set.) ; Therefore we only call one driver's MV start entry or finish entry, ; the one we are using. That will handle getting in-process IRPs dealt ; with. ; ; We will only put 1 IRP on the driver queue at end-MV since we switch ; paths only after a bit of work in MV may run. ; We need to clean up IRP mods though. ; Entered with a calls #2,@DDT$PS_MNTVER_2(R0) ; with r5=ucb, r3=irp steal_mount_ver: $driver_mntver_entry fetch=yes .if df,d$$bug tstl ntruse beql 1700$ bbcc #3,ntrmsk,1700$ ; .iif df,x$$$dt,jsb g^ini$brk 1700$: .endc ; r3 and r5 are args. jsb getswucb ;find intercept ucb tstl r0 ;did we get it? bneq 41$ ;If we got the intercept, branch ; We should never get here;following few instructions are what maybe ; would happen if we somehow didn't have intercepts right. ; This is an exceptional situation but clear MV. Hopefully we NEVER get ; here though. .iif df,x$$$dt,jsb g^ini$brk ;************debug********** tstl r3 ; mv end? blss 741$ ; if R3 is negative, doesn't look like it. ; Ending MV means clear MV status on master path (in case we are not ON ; master path). .if df,x$$$dt bitl #,UCB$L_STS(R5) ;bit should be clear!! beql 708$ jsb g^ini$brk bicl #,UCB$L_STS(R5) ;MV going on? 708$: .endc 741$: pushl r5 ; We want to preserve call R5 pushl r3 ; if we can't find intercept ucb, call std loc calls #2,g^IOC_STD$MNTVER brw 1$ 41$: pushl r5 movl r0,r11 ;keep intercept UCB pointer in R11 for now .iif df,m$$trp,movl r5,ucb$l_mtrp+36(r11) ; Now if this is the end call, grab an IRP tstl r3 ;start MV? bneq 52$ ; If here this is the end MV call, so pull our IRP in ; ; END - MV CALL ; Must restart I/O, so if there is any work on the common queue, load it. .iif df,m$$trp,movl r5,ucb$l_mtrp+44(r11) ; .iif df,x$$$dt,jsb g^ini$brk ;************debug********** pushl r8 ; Even if we would clear MV along a new path, the master path MUST be ; set so mntverip is clear on the master. Otherwise nothing will get sent ; to the device to get it going and all that happens is we'll stick more ; on a pending queue. We must also be sure that the secondary paths are not ; marked MV either, just for safety, since the system doesn't send IRPs ; there either. They should never BE so marked...we don't so mark them ; and they aren't mounted anyhow...but we do call the underlying mv start ; with them. movl ucb$l_hstucb(r11),r8 ; point at main path ; Clear mount verify status in main path now so IRPs can restart .if df,x$$$dt ;Since the upper levels are supposed to know only about main path ; this bit oughta be clear too!!! bitl #,UCB$L_STS(R8) ;bit should be clear!! beql 710$ jsb g^ini$brk 710$: .endc bicl #,UCB$L_STS(R8) ;Clear MV bits popl r8 pushr #^m 43$: remque @ucb$l_mpqueue(r11),r3 ;get an IRP off our input queue bvs 42$ ; if nothing there skip out ; ok, we have an IRP to insert. Gotta relocate the sucker & put it onto the ; host driver's input queue that we are using. ; First we need to "process" it, though. pushl r11 pushl #1 ; Note that the driver stop-MV call should restart I/O so we just fill the ; queue in here, don't try to get the queue going again. calls #1,PSTEALSTART ;go process the IRP to input queue .iif df,x$$$dt,jsb g^ini$brk ;************debug********** popl r11 ; The pstealstart routine does not start i/o, just moves IRPs to queues. 42$: ; No more IRPs remain in the multipath input queue, but all are in the ; driver's queue as a result of our processing. popr #^m ; .iif df,x$$$dt,jsb g^ini$brk ;************debug********** ; Call the underlying stop-MV call. .if df,x$$$dt bitl #,UCB$L_STS(R5) ;bit should be clear!! beql 709$ jsb g^ini$brk 709$: .endc tstl ucb$l_indrct(r11) ;this the normal path? bneq 58$ ;if neq no, alt path .iif df,x$$$dt,jsb g^ini$brk ;************debug********** tstl ucb$l_oldmv(r11) ;be sure an entry is there bgeq 62$ ;and if not forget it. pushl r5 pushl r3 ; Belt 'n' suspenders. Be sure the MV bits are clear!!! bicl #,UCB$L_STS(R5) ;Clear MV bits calls #2,@ucb$l_oldmv(r11) ;else call the original mv entry .iif df,x$$$dt,jsb g^ini$brk ;************debug********** brw 62$ 58$: .iif df,x$$$dt,jsb g^ini$brk ;************debug********** tstl ucb$l_oldmv2(r11) bgeq 62$ ; For multiple paths generalize to an addr array. movl ucb$l_althost(r11),r5 ;point at correct UCB pushl r5 pushl r3 ; belt 'n' suspenders bicl #,UCB$L_STS(R5) ;Clear MV bits calls #2,@ucb$l_oldmv2(r11) ;call alt. path's mv routine .iif df,d$$bug, incl inmve .iif df,x$$$dt, jsb g^ini$brk ;************debug********** brw 62$ ; now call host driver end-mv routine ; ; START-MV CALL 52$: .iif df,d$$bug, incl doxdt ; Following test should NEVER branch but be safe. bbs #IRP$V_MVIRP,IRP$L_STS(R3),11$ ;skip if a mv irp ; Start MV call. We have an IRP already, but need to get it to our multi ; input queue. Then we must drain anything else on that queue to the ; multiple queue. ; ; First call host driver insert-IRP routine, then move the whole queue ; to our "real" multi-path one. .iif df,m$$trp,movl r5,ucb$l_mtrp+48(r11) tstl ucb$l_oldmv(r11) ;is a mount ver entry ok? bgeq 11$ ; if not skip it tstl ucb$l_indrct(r11) ;is this using the indirect path? bneq 2$ ;if neq yes pushl r5 pushl r3 .if df,d$$bug tstl doxdt beql 701$ .iif df,x$$$dt, jsb g^ini$brk 701$: .endc calls #2,@ucb$l_oldmv(r11) ;else call the original mv entry ; The alternate path has a valid class queue etc. too brw 21$ ; repair R5 here to point at second disk 2$: ; For multiple paths generalize to an addr array. tstl ucb$l_oldmv2(r11) ;is a mount ver entry ok? bgeq 11$ movl ucb$l_althost(r11),r5 ;point at correct UCB pushl r5 pushl r3 .if df,d$$bug tstl doxdt beql 702$ .iif df,x$$$dt, jsb g^ini$brk 702$: .endc calls #2,@ucb$l_oldmv2(r11) 21$: ; ; Now copy host input queue to mpth one. R5 is now the phys dvc ucb. ; ; remove from back of old irp queue, insert at front of mpath one ; This will result in preserving the order. ; ; NOTA BENE:::: ; We may be able to optimize this using pointers ("move the headers") ; to get it all. ; ; ???? Is this correct??????? movab ucb$l_ioqfl(r5),r9 ;get addr of start of queue 24$: movl ucb$l_ioqbl(r5),r10 ;get last queue entry cmpl r10,r9 beql 22$ ; exit if we got to queue head remque (r10),r3 ;get an entry bvs 22$ ; if vs, nothing there ; Undo mods to this queue element pushl r11 ; Push intercept ucb addr pushl r3 ; undo mods ; Reprocess everything to undo the IRP mods and replace on the multiport ; input queue. ; .iif df,x$$$dt,jsb g^ini$brk ;************debug********** calls #2,undomods ; restore IRP to "pristine" cond'n ; .iif df,x$$$dt,jsb g^ini$brk ;************debug********** insque (r3),ucb$l_mpqueue(r11) ;insert the IRP into mp queue brb 24$ 22$: .iif df,x$$$dt,jsb g^ini$brk ;************debug********** ;Now an IRP is on the multipath input queue and we are done. 62$: 11$: popl r5 1$: ret ; ; ; Alt-start entry. This must notice when a driver is passed an I/O ; directly (without synch on UCB busy) and pass to the appropriate ; underlying driver. ; Note that the IRP needs to be preprocessed first though so that we ; can maintain control. ; Owns forklock on call. ; Since this won't change paths, just send it on... ; (Note this isn't queued in the normal way, and should always have ; an IRP for us...) ; STEAL_ALTSTART: $DRIVER_ALTSTART_ENTRY PRESERVE = MOVL altarg$_irp(AP),R3 ;F FF; Get current IRP MOVL altarg$_ucb(AP),R5 ;F FF; Get current UCB .if df,d$$bug tstl ntruse beql 1700$ bbcc #4,ntrmsk,1700$ .iif df,x$$$dt,jsb g^ini$brk 1700$: .endc ; ok, we have an IRP to process. Gotta relocate the sucker & put it onto the ; host driver's input that we are using. ; First we need to "process" it, though. pushl #0 ; do NOT put the IRP on a queue. calls #1,PSTEALSTART ;go process the IRP to input queue jsb getswucb ;find intercept ucb tstl r0 ;did we get it? bneq 21$ movl #ss$_badparam,r0 brw 1$ 21$: .iif df,m$$trp,movl #4,ucb$l_mtrp(r0) ; We now have the intercept loc. .if df,d$$bug tstl doxdt beql 702$ .iif df,x$$$dt, jsb g^ini$brk 702$: .endc pushl r11 movl r0,r11 tstl ucb$l_OALTST(r11) ;is ENTRY OK? bgeq 1$ ; if not skip it PUSHL R5 PUSHL R3 TSTL UCB$L_INDRCT(R11) BNEQ 2$ TSTL UCB$L_OALTST(R11) BGEQ 10$ CALLS #2,@UCB$L_OALTST(R11) BRB 1$ 10$: ADDL2 #8,SP ;pop 2 args BRB 7$ 2$: TSTL UCB$L_OALTST2(R11) BGEQ 11$ movl ucb$l_althost(r11),r5 ;point r5 at the secondary ucb FORKLOCK LOCK=UCB$B_FLCK(R5),SAVIPL=-(SP),PRESERVE=NO CALLS #2,@UCB$L_OALTST2(R11) FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,CONDITION=RESTORE,PRESERVE=NO BRB 7$ 11$: ADDL2 #8,SP ;pop 2 args 7$: popl r11 1$: ; Return whatever R0 status the altstarts returned. ; (Not actually looked at) RET ; undomods...undo mods done do an IRP moving to device queue. Call arg is ; intercept UCB, and IRP address. undomods: .call_entry, max_args=2 movl 4(ap),r3 ; get the IRP address movl 8(ap),r5 ; and intercept UCB MOVL R11,-(SP) ; Free up ol' reliable R11 as scratch MOVL R10,-(SP) ; Free R10 also ; Now the tricky bit. ; We must fill the appropriate address into IRP$L_PID for a call at ; I/O completion. We use a table of such routines, one per unit, ; all of the same size so we can calculate the address of the ; routines. However, since the routine addresses can be almost ; anywhere when the compiler gets done with them, we will ; use a table constructed BY the compiler of pointers to them all and ; access via that instead of just forming the address directly. The table ; entries will be left 2 longs in size each. ; Table SW_VOADT is what we need. Note however that the .address operators ; there probably need to change to some more general .linkage directive. ; ; Most of this setup should really be done at interrupt device unit init ; for production. ; MOVZWL UCB$W_UNIT(R5),R11 ; get our SW unit number ; Each linkage pair is 8 bytes long... ; Thus shift 3 bits to multiply by 8 ASHL #3,R11,R11 ; Make an offset to the linkage area MOVAB SW_VOADT,R10 ; get the table base ADDL2 R10,R11 ; r11 now points at the link addr ; Recognize if this is ours! CMPL IRP$L_PID(R3),(R11) ; Already clobbered this IRP? BNEQ 55$ ; If not, don't unclobber ; Looks like we DID clobber this IRP and need to fix it up again. movl (R3),IRP$L_PID(R3) ;restore IRP ptr movl IRP$Q_QIO_P2(R3),IRP$L_MEDIA(R3) ;get media clrl irp$q_qio_p2+4(r3) ;be sure we notice this was fixed movl (R3),IRP$L_STS(R3) MOVL (R3),IRP$L_UCB(R3) ;ORIGINAL UCB ; If moving this back to the mp queue, we may need to re-arbitrate if we ; see it at pending-io again, so allow this to happen. clrl irp$q_qio_p3+4(r3) ; say we need to check on this again ; Also uncount the pending I/O on the device decl ucb$l_outstnd(r5) ; count this down bgeq 55$ clrl ucb$l_outstnd(r5) ; clamp this non-negative 55$: ret ; ; Steal-startio. We get here first, and must arrange initial setup here ; so we can check I/O errors and handle them. Do this via stealing the ; irp$l_pid entry. On VAX we had to grab a special bit of pool to do this, ; but on AXP, by this point the irp$q_qio_p1 to _p6 are free to use, so just ; use P4 to hold the irp$l_pid field (so the error retry intercept doesn't ; walk on it if used) and use the 2nd long if we need it. TOORGJ: BRW TOORG AWAB: BRW AWAY ; on entry R3=IRP, r5=host UCB STEALSTART: $DRIVER_START_ENTRY .if df,d$$bug tstl ntruse beql 1700$ bbcc #5,ntrmsk,1700$ .iif df,x$$$dt,jsb g^ini$brk 1700$: .endc JSB GETSWUCB ;find intercept UCB TSTL R0 ;did we find it? BGEQ AWAB ;no, scram, but probably hang. ; For normal dispatching, IRPs may just be inserted on the normal UCB ; queue and could be pulled off once the underlying driver gets ; control. Therefore before allowing an IRP in here, check that ; the queue is also free of any IRPs not processed for the chosen ; path. Any waits in the underlying driver can cause problems ; however. This check is bullet-proof if the underlying driver doesn't ; go below fork, though. .iif df,m$$trp,movl #6,ucb$l_mtrp(r0) ; (Keep a back pointer to our master for now; helps debugging this code. ; Ultimately the reg use here needs rework.) MOVL R5,UCB$L_BACKLK(R0) ;else put it in now PUSHL R5 MOVL R0,R5 ;point at intercept ucb now clrl ucb$l_fgdun(r5) ;clear flag that we clobbered the IRP already ; allow external control over error reduction BITL #^X100000,UCB$L_CTLFLGS(R5) ;Turning on switch ability? BEQL TOORGJ ;if not skip out ; Flag that this IRP is coming from start-io where it will be processed ; fully, so let pending io just call the insert-irp routine directly movl #1,irp$q_qio_p3+4(r3) ; flag that this IRP is one of ours! ; be sure this is read or write, else just start orig. one ; Thus we don't mess with ANYTHING except read or write. Thus packack etc. ; would also go through basically unaltered. .IF NDF,EVAX EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$W_FUNC(R3),R0 .IFF EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$L_FUNC(R3),R0 .ENDC ASSUME IRP$S_FCODE LE 7 ; Allow byte mode dispatch ; io$_packack is 8 ; io$_writecheck is 10 ; io$_writepblk is 11 ; io$_readpblk is 12 ; allow checks on all 3 ; ; Allow some pack-acks through on original path since the mount verify ; might have been caused by a SCSI bus RESET, which is not necessarily ; an indication that the path is lost, but may mean only that somebody ; booted, a tape hung, etc. ; ; After some number of packacks however (mintries), we must presume that ; this SCSI connection isn't making much progress, and we had better try ; to fail over to an alternate if one exists. In addition, try to send ; notice to an attached daemon via a preallocated mailbox (it's up to the ; daemon to allocate it, fill it in here, and read it) that our device ; failed. The daemon can then take care of configuring other devices ; should that be required. ; ; See if all modifiers are set. If so, we'll strip them all but reroute to ; the original disk, not the alternate, giving a way for the switch server ; to send a packack to the direct path. ; In fact test only MVIRP so allow this as a way to get any (unmodified) ; I/O to the direct path. PUSHL R1 MOVZWL IRP$L_FUNC(R3),R1 ;get the whole function word BICL #^CIO$M_FMODIFIERS,R1 ;clear all but modifiers CMPL R1,#IO$M_FMODIFIERS ;This the flag case? BNEQ 21$ ;no, proceed normally POPL R1 ;first fix stack ; Now strip fmodifiers from IRP BICL #IO$M_FMODIFIERS,IRP$L_FUNC(R3) ;make it a normal packack ; r5 is intercept ucb BRW QORIG 21$: ; ; We need a way to get to the alternate also, since we set the disable- ; assign flag in that UCB. So use all mods except the 128 bit. CMPL R1,# BNEQ 22$ POPL R1 ; Set a temporary bit in UCB$L_INDRCT so this IRP gets dispatched to ; the desired side regardless of the rest. It gets cleared as soon ; as it is tested below. BICL #IO$M_FMODIFIERS,IRP$L_FUNC(R3) ;make it a normal irp BISL #256,UCB$L_INDRCT(R5) ; Set temporary-alt flag BRW 12$ ; Then dispatch to alternate side 22$: POPL R1 ; If we see over 3 packack mount verifies in a row, switch to the other ; side. ; We only switch paths on PACKACK from mount verify, not on other MV functs. BITL #IRP$M_MVIRP,IRP$L_STS(R3) ;this IRP from mnt verify? BEQL 10$ ; If not mnt vfy, branch CMPL R0,#IO$_PACKACK ; could this be a packack from mvfy BNEQ 10$ INCL UCB$L_RETRIES(R5) ;bump retry count .IIF NDF,MINTRIES,MINTRIES=3 ;allow 2 packacks before switch CMPL UCB$L_RETRIES(R5),#MINTRIES ;only 1 or 2? BLEQ 12$ ;path might be ok ; The current path is messed. Switch to other one ; ; ; SWITCH PATH LOGIC ; ; Here we switch to an alternate device if possible, send notice to an ; attached "server" so it can configure alternate devices if need be, ; and let the I/O by in the new direction. ; CLRL UCB$L_SAWSUCC(R5) ;say no success on this path yet ; swap path now. (Note this has no effect if althost is still null.) TSTL UCB$L_INDRCT(R5) ;on indirect path now? BNEQ 14$ ;no, switching to it ; Test the path enabled bits before setting up to use that path. tstl UCB$L_ENAPTH+4(R5) ; is path 1 enabled? beql 15$ ; if not, do not switch MOVL #1,UCB$L_INDRCT(R5) ;set on indirect path BRB 15$ 14$: tstl UCB$L_ENAPTH(R5) ; path 0 enabled? beql 15$ ; if not skip using .iif df,x$$$dt,jsb g^ini$brk ;************debug********** CLRL UCB$L_INDRCT(R5) ;was indirect path, use direct now 15$: CLRL UCB$L_RETRIES(R5) ;Allow another 3 tries at the new UCB CLRL UCB$L_OUTSTND(R5) ;say nothing's outstanding here any more ; Now send a message (if we can) to our swap-daemon so it can provide ; swaps of other stuff if it needs to. PUSHR #^M ;check daemon pid still valid PUSHR #^M MOVZWL G^SCH$GL_MAXPIX,R7 ;max process index in VMS 622$: MOVL G^SCH$GL_PCBVEC,R6 ;get PCB vector address MOVL (R6)[R7],R8 ;get a PCB address TSTL R8 ;system address should be < 0 BGEQ 23$ ;if it seems not to be a pcb forget it CMPL UCB$L_DAEMON(R5),PCB$L_PID(R8) ;this our process? BEQL 221$ ;if so, jump out of loop 23$: SOBGTR R7,622$ ;if not, look at next CLRL UCB$L_DAEMON(R5) ;if cannot find process, zero our flag 221$: POPR #^M TSTL UCB$L_DAEMON(R5) ;got our daemon process there? BEQLW 30$ ;if not, skip MBX.USE=56 ; Amount we need MBX.SIZ=64 ;(use next higher quadword size) ; We grab a little more stack than needed for the buffer so we can align it MOVL SP,R4 ; Point R4 at the buffer allocated SUBL #MBX.SIZ,SP ;"allocate" a buffer SUBL #MBX.USE,R4 ; Use a little less, but... BICL #7,R4 ; ...align the buffer on 8 byte bdy ; ; Buffer format: ; ; Fill in the message now as we need it. ; 0/1 flag for alt or direct device failed ; UCB address of failed device ; unit number of failed device ; allocation class of failed device ; counted device name (16 bytes in all) PUSHR #^M MOVL UCB$L_HSTUCB(R5),R0 ;get host ucb ; offsets of buffer swmsg$l_indrct=0 ;indirect or direct flag swmsg$l_ucb=4 ;UCB of host currently used swmsg$l_unit=8 ;unit number of host device swmsg$l_alloclass=12 ;alloc class of host device swmsg$a_name=16 ;counted device name (1 byte count, 15 bytes text) swmsg$l_stat=32 ;statusflag, 1 or add 16384 and possibly 8192 swmsg$l_node=36 ; MOVL UCB$L_INDRCT(R5),(R4)+ ;Tell daemon which unit... BEQL 24$ ;if we just left direct mode TSTL UCB$L_ALTHOST(R5) BGEQ 24$ ;don't use a null althost address MOVL UCB$L_ALTHOST(R5),R0 ;get ucb of alt. device 24$: MOVL R0,(R4)+ ;pass ucb of failed device MOVL UCB$L_DDB(R0),R1 ;Point at the DDB MOVZWL UCB$W_UNIT(R0),(R4)+ ;send unit number MOVL DDB$L_ALLOCLS(R1),(R4)+ MOVAB DDB$B_NAME_LEN(R1),R2 ;point at device name string MOVL (R2)+,(R4)+ ;fill in device name MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL #1,(R4)+ ;set a code that says we came from startio MOVL DDB$L_SB(R1),R2 ;GET SB of the node MOVAB SB$T_NODENAME(R2),R2 ;POINT at nodename MOVL (R2)+,(R4)+ ;nodename is 16 chars, count first MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ POPR #^M MOVL #MBX.USE,R3 ; r3 gets size MOVL UCB$L_MBXUCB(R5),R5 ; ucb of mailbox BGEQ 46$ ; if zero forget the write attempt. (>0 illegal too) ;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. CMPB #DYN$C_UCB,UCB$B_TYPE(R5) BNEQ 46$ ; Be sure this IS a UCB 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. BBC #DEV$V_MBX,UCB$L_DEVCHAR(R5),46$ ; If not a mailbox, reject it too cmpb #DC$_Mailbox,ucb$b_devclass(r5) bneq 46$ ; be sure it's a mailbox too ; Here send a message to the daemon and continue. It appears the mailbox ; is ok. CALL_WRTMAILBOX SAVE_R1=yes 46$: ADDL2 #MBX.SIZ,SP ;"deallocate" the space we grabbed 30$: POPR #^M BRB 11$ 10$: ; r5 is intercept ucb .iif df,m$$trp,movl #7,ucb$l_mtrp(r5) .iif df,m$$trp,movl r3,ucb$l_mtrp+16(r5) CLRL UCB$L_RETRIES(R5) ; Saw anything BUT a MV packack .if df,d$$bug tstl inmve beql 712$ .iif df,x$$$dt, jsb g^ini$brk 712$: .endc MOVL #1,UCB$L_SAWSUCC(R5) ;must have passed packack... 11$: 12$: ; r5 is intercept ucb ; now set up IRP, then call the previous start-io point at ; ucb$l_hstartio(r5) to do the work with registers put back. ; For Alpha, the stack manipulation here is messy to track in machine ; code, so do it in a register. This makes debug easier, and correctness ; easier to achieve. MOVL R11,-(SP) ; Free up ol' reliable R11 as scratch MOVL R10,-(SP) ; Free R10 also ; Now the tricky bit. ; We must fill the appropriate address into IRP$L_PID for a call at ; I/O completion. We use a table of such routines, one per unit, ; all of the same size so we can calculate the address of the ; routines. However, since the routine addresses can be almost ; anywhere when the compiler gets done with them, we will ; use a table constructed BY the compiler of pointers to them all and ; access via that instead of just forming the address directly. The table ; entries will be left 2 longs in size each. ; Table SW_VOADT is what we need. Note however that the .address operators ; there probably need to change to some more general .linkage directive. MOVZWL UCB$W_UNIT(R5),R11 ; get our SW unit number ; Each linkage pair is 8 bytes long... ; Thus shift 3 bits to multiply by 8 ASHL #3,R11,R11 ; Make an offset to the linkage area MOVAB SW_VOADT,R10 ; get the table base ADDL2 R10,R11 ; r11 now points at the link addr ; Recognize if this is ours! CMPL IRP$L_PID(R3),(R11) ; Already clobbered this IRP? BEQL 55$ ; If so don't do it twice! incl ucb$l_fgdun(r5) ; bump flag that we have to alter irp MOVL IRP$L_PID(R3),(R3) ;store original irp$l_pid field MOVL (R11),IRP$L_PID(R3) ; Now point irp$l_pid at a proper MOVL IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3) ;save media field too MOVL IRP$L_STS(R3),(R3) ;Save status incoming MOVL IRP$L_UCB(R3),(R3) ;save original ucb ; If this is the first time we alter an IRP, bump its count. .PRESERVE ATOMICITY INCL UCB$L_OUTSTND(R5) ;Bump outstanding I/O count .NOPRESERVE ATOMICITY .IF NDF,EVAX ; must add vSW$dpt address to this IF VAX ; (for AXP the address is ok as is. The difference has to do with the way ; driver loading differs on the 2 machines.) ; ; Mind, this code is not quite suited to VAX at the moment, but this area ; is a nasty surprise if one is not prepared for it. This will facilitate ; back-porting. MOVAB SW$DPT,R10 ;start of driver ADDL2 R10,IRP$L_PID(R3) ;now pid should get back ok .ENDC .iif df,m$$trp,movl #8,ucb$l_mtrp(r5) .iif df,m$$trp,movl r3,ucb$l_mtrp+20(r5) ;; .IF DF,FFINISH ; If sending to a shadow unit, with DEV$M_VRT set in devchar2 ; do NOT set finipl8 bit since shadow can't hack it. ; ; ????????????????? NOTE: ; for efficiency, use a different register here..we should compute target ; UCB once and do the test obly. ; PUSHL R5 TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 4212$ ; IF EQL NO MOVL UCB$L_ALTHOST(R5),R5 ; point at target indirect path UCB BRB 4213$ 4212$: MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB 4213$: ; ; R5 is now the targetted device UCB ; BITL #DEV$M_VRT,UCB$L_DEVCHAR2(R5) ;this a shadow dvc? BEQL 4214$ ; If not shadow device, branch ; Sending I/O to shdriver or the like. Since it loses on fastio finish ; do not set finipl8 bit etc. for it. POPL R5 BRB 209$ 4214$: POPL R5 ; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle ; finipl8 stuff. if clear, it cannot. BBS #IRP$V_MVIRP,IRP$L_STS(R3),209$ ;leave MV IRPs alone BBC #UCB$V_FI8OK,UCB$L_ICPFGS(R5),209$ ;skip if not using ffin BBC #0,UCB$L_SAWSUCC(R5),209$ ; if we may be transitioning use IPL 4 BBS #IRP$V_FINIPL8,IRP$L_STS(R3),209$ BBS #IRP$V_FAST_FINISH,IRP$L_STS(R3),209$ cmpl ucb$l_outstnd(r5),#10 bgequ 209$ ; Shadowing assumes IPL4 return unfortunately ; Test this too, in case shdriver is layered above us. (That should be ok ; but take no chances.) BBS #IRP$V_SHDIO,IRP$L_STS2(R3),209$ BISL #,IRP$L_STS(R3) ;set fast path 209$: ;; .ENDC 55$: ; r5 is intercept ucb MOVL (SP)+,R10 ; Restore R10 MOVL (SP)+,R11 ; get R11 back & clean stack now TSTL UCB$L_INDRCT(R5) ; Are we on the direct path? BEQL 113$ ; if so, just send the IRP on TSTL UCB$L_ALTHOST(R5) ; Ensure there IS an alternate host BLSS 112$ ; If UCB looks ok, call alt path 113$: BRW QORIG ; On the direct path just pass I/O on 112$: ; ; I/O to the alternate device. Must be rerouted and we must grab ; completion in case of split I/O etc. ; ; To accomplish this we need storage for: ; 1. Original irp$l_pid ; 2. Possibly packack count (use intercept UCB for this) ; ; start at with this so ASSUME that we have 2 longs available ; there. Use this one so the error intercept won't clobber it. ; ; MV IRPs need to be treated differently BITL #IRP$M_MVIRP,IRP$L_STS(R3) ;this IRP from mnt verify? ; (helper branches) BEQL 16$ ; If bit clear, this isn't Mnt Vfy p.ack BRW 1000$ ; reroute packack not synch'd by ucb busy 16$: ; Non-MV IRP here, for alternate target ; ; We don't unbusy the main device if mount verify is in progress, but ; otherwise we will unbusy the device now. Note that at the top of stack at ; this point is the original R5 of the main path device (which is mounted ; etc.) PUSHL R5 ; We need a register MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB BICL #UCB$M_BSY,UCB$L_STS(R5); unbusy direct line POPL R5 ; get back intercept dvr R5 now ; We need to get the original IRP$L_PID back to really finish the I/O. ; ; ; Note before we finish adjusting the IRP we must be sure we don't do it ; twice, so check and skip the adjustment if doing it the 2nd time. pushl r10 pushl r11 ; Now the tricky bit. ; We must fill the appropriate address into IRP$L_PID for a call at ; I/O completion. We use a table of such routines, one per unit, ; all of the same size so we can calculate the address of the ; routines. However, since the routine addresses can be almost ; anywhere when the compiler gets done with them, we will ; use a table constructed BY the compiler of pointers to them all and ; access via that instead of just forming the address directly. The table ; entries will be left 2 longs in size each. ; Table SW_VOADT is what we need. Note however that the .address operators ; there probably need to change to some more general .linkage directive. MOVZWL UCB$W_UNIT(R5),R11 ; get our SW unit number ; Each linkage pair is 8 bytes long... ; Thus shift 3 bits to multiply by 8 ASHL #3,R11,R11 ; Make an offset to the linkage area MOVAB SW_VOADT,R10 ; get the table base ADDL2 R10,R11 ; r11 now points at the link addr CMPL IRP$L_PID(R3),(R11) ; Already clobbered this IRP? BEQL 155$ ; If so don't do it twice! MOVL IRP$L_PID(R3),(R3) ;store original irp$l_pid field MOVL (R11),IRP$L_PID(R3) MOVL IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3) ;save media field too MOVL IRP$L_STS(R3),(R3) ;Save status incoming MOVL IRP$L_UCB(R3),(R3) ;save original ucb 155$: .iif df,m$$trp,movl #9,ucb$l_mtrp(r5) popl r11 popl r10 ; ; Now redirect to the alternate UCB ; Then return. Note we should always be on indirect path here...following ; bits are belt 'n' suspenders... PUSHL R5 TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 212$ ; IF EQL NO BICL #256,UCB$L_INDRCT(R5) ; Clear temporary-alt flag MOVL UCB$L_ALTHOST(R5),R5 ; point at target indirect path UCB BRB 213$ 212$: BICL #256,UCB$L_INDRCT(R5) ; Clear temporary-alt flag MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB 213$: MOVL R5,IRP$L_UCB(R3) ; Ensure the IRP looks right ; Geometry of the device must be the same in either case, so IRP$L_MEDIA ; better not have to be altered... ; We CALL the alternate start-io so as to keep linkages clean. CALL_INSIOQC ; send the IRP off to the alternate ; yes, double pop... .if df,d$$bug tstl inmve beql 713$ .iif df,x$$$dt, jsb g^ini$brk 713$: .endc POPL R5 ; get back intercept ucb .iif df,m$$trp,movl #10,ucb$l_mtrp(r5) POPL R5 ; get back original ucb ; Clear busy on the main host unit since that will have gotten the $qio in ; the first place. $reqcom in the secondary driver will have cleared busy ; there but not here; nor will postproc. do so. However, this code ; needs the ucb clean no more so allow more activity. MOVL #1,R0 ; say all ok BRW STSRET ; ; Handle packack IRPs when sent direct to alt host ; ; Note however that we need to handle these IRPs also so we complete ; them in the original context, not that of the alternate host device ; so packack will see a sensible apparent situation. ; (In other words, the UCB when MV gets these things back better still ; point at the main device, which MV thinks is what is in Mnt Vfy state ; so it can unbusy things properly when it gets done.) 1000$: .if df,d$$bug tstl doxdt beql 722$ .iif df,x$$$dt, jsb g^ini$brk 722$: .endc MOVL R11,-(SP) ; Free up ol' reliable R11 as scratch MOVL R10,-(SP) ; Free R10 also MOVZWL UCB$W_UNIT(R5),R11 ; get our SW unit number ASHL #3,R11,R11 ; Make an offset to the linkage area MOVAB SW_VOADT,R10 ; get the table base ADDL2 R10,R11 ; r11 now points at the link addr CMPL (R11),IRP$L_PID(R3) ; Already clobbered this? BEQL 56$ ; If fixing up IRP, always do it ALL. MOVL IRP$L_PID(R3),(R3) ;store original irp$l_pid field MOVL (R11),IRP$L_PID(R3) ; Now point irp$l_pid at a proper incl ucb$l_fgdun(r5) ; flag that we DO need to alter the IRP MOVL IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3) ;save media field too .iif df,m$$trp,movl #11,ucb$l_mtrp(r5) MOVL IRP$L_STS(R3),(R3) ;Save status incoming MOVL IRP$L_UCB(R3),(R3) ;save original ucb .IF NDF,EVAX MOVAB SW$DPT,R10 ;start of driver ADDL2 R10,IRP$L_PID(R3) ;now pid should get back ok .ENDC ;56$: ;; .IF DF,FFINISH ; If sending to a shadow unit, with DEV$M_VRT set in devchar2 ; do NOT set finipl8 bit since shadow can't hack it. PUSHL R5 TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 5212$ ; IF EQL NO MOVL UCB$L_ALTHOST(R5),R5 ; point at target indirect path UCB BRB 5213$ 5212$: MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB 5213$: BITL #DEV$M_VRT,UCB$L_DEVCHAR2(R5) ;this a shadow dvc? BEQL 5214$ ; Sending I/O to shdriver or the like. Since it loses on fastio finish ; do not set finipl8 bit etc. for it. POPL R5 BRB 208$ 5214$: POPL R5 ; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle ; finipl8 stuff. if clear, it cannot. BBS #IRP$V_MVIRP,IRP$L_STS(R3),208$ ;leave MV IRPs alone BBC #UCB$V_FI8OK,UCB$L_ICPFGS(R5),208$ ;skip if not using ffin BBC #0,UCB$L_SAWSUCC(R5),208$ ; if we may be transitioning use IPL 4 BBS #IRP$V_FINIPL8,IRP$L_STS(R3),208$ BBS #IRP$V_FAST_FINISH,IRP$L_STS(R3),208$ cmpl ucb$l_outstnd(r5),#10 bgequ 208$ ; Shadowing assumes IPL4 return unfortunately ; Test this too, in case shdriver is layered above us. (That should be ok ; but take no chances.) BBS #IRP$V_SHDIO,IRP$L_STS2(R3),208$ BISL #,IRP$L_STS(R3) ;set fast path 208$: ;; .ENDC .iif df,m$$trp,movl #12,ucb$l_mtrp(r5) MOVL IRP$L_UCB(R3),(R3) ;save original ucb 56$: MOVL (SP)+,R10 ; Restore R10 MOVL (SP)+,R11 ; get R11 back & clean stack now PUSHL R11 MOVL R5,R11 ; We need sw UCB too TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 312$ MOVL UCB$L_ALTHOST(R5),R5 ; get correct UCB BRB 313$ 312$: MOVL UCB$L_HSTUCB(R5),R5 313$: MOVL R5,IRP$L_UCB(R3) ; Ensure the IRP looks right FORKLOCK LOCK=UCB$B_FLCK(R5),- ; Lock the FORK spinlock SAVIPL=-(SP),- ; Save the current IPL PRESERVE=NO ; Don't preserve R0 .IF DF,IRP$M_LOCK_RELEASEABLE BICL #IRP$M_LOCK_RELEASEABLE, - ; Driver can not release forklock IRP$L_STS(R3) .ENDC ; CALL_INITIATE ; Start i/o on alt path directly ; FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock NEWIPL=(SP)+,- ; Restore previous IPL CONDITION=RESTORE,- ; Conditionally release lock PRESERVE=NO ; Don't preserve R0 POPL R11 POPL R5 ; get back original UCB BRW STSRET ; Now restore registers and go to the original routine. ; This is also where we come to try again. ; Assumes host ucb address on stack, SW ucb address in R5, IRP address in R3 TOORG: .iif df,m$$trp,movl #13,ucb$l_mtrp(r5) MOVL UCB$L_HSTARTIO(R5),R1 ;address of original routine BGEQ AWA2 ; if none, things are messed...probably will crash POPL R5 ; get back original UCB MOVL #1,R0 ; set ok status for now ; call original start-io (to ensure high regs are passed correctly) PUSHL R5 ; UCB ARG PUSHL R3 ; IRP ARG CALLS #2,(R1) ; CALL THE ORIGINAL STARTIO BRB AWAY AWA2: POPL R5 AWAY: ; Should get here only after original startio has been called & returned. ; early test code oughta be impossible. RET ; ; Send the IRP off to the primary path. Since we get called here when ; and only when ioc$initiate or equivalent has run, we need not requeue ; the IRP. Just call the original entry. QORIG: ; We only come here for the primary path so just restore original R5 here ; to get its UCB. ; On entry R5 is intercept ucb MOVL #1,R0 ; set ok status for now .iif df,m$$trp,movl #14,ucb$l_mtrp(r5) MOVL UCB$L_HSTARTIO(R5),R1 ;address of original routine BGEQ QAWA2 ; call original start-io (to ensure high regs are passed correctly) ; ; No need to popl and then pushl; "optimize" these away. ; POPL R5 ; restore original R5 ; PUSHL R5 ; UCB ARG movl (sp),r5 ;point R5 at original ucb movl r5,irp$l_ucb(r3) ;point the IRP at orig. path too PUSHL R3 ; IRP ARG CALLS #2,(R1) ; CALL THE ORIGINAL STARTIO ; Common return area. Since we are now leaving the intercept, make sure ; that i/o will continue if any is still there to do by moving an IRP ; onto the current dest. input queue ; ; reqcom in the underlying driver will not return to startio if it thinks ; that all outstanding I/O is finished. Thus we must arrange that it will ; find more such by looking here for more and continuing till all current ; I/O has been processed. When we put an IRP back into the multiport ; input queue we must ensure our "already processed" flags are reset also. ; STSRET: RET QAWA2: POPL R5 ret .SBTTL Packet Filter Routine ; ; Pstealstart - ; Subroutine that will "process" IRPs to format them to be put on ; appropriate queues. ; ; NOT part of driver start-io directly, and called when we have ; need to pull an IRP off our queue and get it onto a subordinate ; drive's queue. Inserts on one or other drive's queue, does not ; look for MV etc. here. ; ; on entry R3=IRP, r5=host UCB ; Argument #1, if 0 means do not move IRP to queues, if 1 means move PSTEALSTART: .call_entry ; Callers need R0 and R1 preserved, so just do that. pushl r0 pushl r1 movl 4(ap),R8 ;get "move args" flag JSB GETSWUCB ;find intercept UCB TSTL R0 ;did we find it? BGEQ PAWAB ;no, scram, but probably hang. .iif df,m$$trp,movl #36,ucb$l_mtrp(r0) MOVL R5,UCB$L_BACKLK(R0) ;Save back pointer for debugging PUSHL R5 MOVL R0,R5 ;point at intercept ucb now .iif df,m$$trp,movl #15,ucb$l_mtrp(r5) clrl ucb$l_fgdun2(r5) ;say initially didn't see we had to alter IRP ; allow external control over error reduction BITL #^X100000,UCB$L_CTLFLGS(R5) ;Turning on switch ability? BEQL PTOORG ;if not skip out ; Flag that this IRP is coming from start-io where it will be processed ; fully, so let pending io just call the insert-irp routine directly ; (no, don't do it since WE call the enqueue functions directly here.) ;;;;; movl #2,irp$q_qio_p3+4(r3) ; flag that this IRP is one of ours! ; be sure this is read or write, else just start orig. one ; Thus we don't mess with ANYTHING except read or write. Thus packack etc. ; would also go through basically unaltered. .IF NDF,EVAX ; Vax EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$W_FUNC(R3),R0 .IFF ; Alpha EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$L_FUNC(R3),R0 .ENDC ASSUME IRP$S_FCODE LE 7 ; Allow byte mode dispatch ; io$_packack is 8 ; io$_writecheck is 10 ; io$_writepblk is 11 ; io$_readpblk is 12 ; allow checks on all 3 ; ; Allow some pack-acks through on original path since the mount verify ; might have been caused by a SCSI bus RESET, which is not necessarily ; an indication that the path is lost, but may mean only that somebody ; booted, a tape hung, etc. ; ; After some number of packacks however (mintries), we must presume that ; this SCSI connection isn't making much progress, and we had better try ; to fail over to an alternate if one exists. In addition, try to send ; notice to an attached daemon via a preallocated mailbox (it's up to the ; daemon to allocate it, fill it in here, and read it) that our device ; failed. The daemon can then take care of configuring other devices ; should that be required. ; ; See if all modifiers are set. If so, we'll strip them all but reroute to ; the original disk, not the alternate, giving a way for the switch server ; to send a packack to the direct path. ; In fact test only MVIRP so allow this as a way to get any (unmodified) ; I/O to the direct path. PUSHL R1 MOVZWL IRP$L_FUNC(R3),R1 ;get the whole function word BICL #^CIO$M_FMODIFIERS,R1 ;clear all but modifiers CMPL R1,#IO$M_FMODIFIERS ;This the flag case? BNEQ 21$ ;no, proceed normally POPL R1 ;first fix stack ; Now strip fmodifiers from IRP BICL #IO$M_FMODIFIERS,IRP$L_FUNC(R3) ;make it a normal packack BRW PQORIG 21$: ; ; We need a way to get to the alternate also, since we set the disable- ; assign flag in that UCB. So use all mods except the 128 bit. CMPL R1,# BNEQ 22$ POPL R1 ; Set a temporary bit in UCB$L_INDRCT so this IRP gets dispatched to ; the desired side regardless of the rest. It gets cleared as soon ; as it is tested below. BICL #IO$M_FMODIFIERS,IRP$L_FUNC(R3) ;make it a normal irp BISL #256,UCB$L_INDRCT(R5) ; Set temporary-alt flag BRW 12$ ; Then dispatch to alternate side 22$: POPL R1 12$: ; now set up IRP, then call the previous start-io point at ; ucb$l_hstartio(r5) to do the work with registers put back. ; For Alpha, the stack manipulation here is messy to track in machine ; code, so do it in a register. This makes debug easier, and correctness ; easier to achieve. MOVL R11,-(SP) ; Free up ol' reliable R11 as scratch MOVL R10,-(SP) ; Free R10 also ; Now the tricky bit. ; We must fill the appropriate address into IRP$L_PID for a call at ; I/O completion. We use a table of such routines, one per unit, ; all of the same size so we can calculate the address of the ; routines. However, since the routine addresses can be almost ; anywhere when the compiler gets done with them, we will ; use a table constructed BY the compiler of pointers to them all and ; access via that instead of just forming the address directly. The table ; entries will be left 2 longs in size each. This is a tad wasteful, but ; if these internal addresses ever grow to 64 bits, we have the space ; allocated already. MOVZWL UCB$W_UNIT(R5),R11 ; get our SW unit number ; Each linkage pair is 8 bytes long... ; Thus shift 3 bits to multiply by 8 ASHL #3,R11,R11 ; Make an offset to the linkage area MOVAB SW_VOADT,R10 ; get the table base ADDL2 R10,R11 ; r11 now points at the link addr ; Note we should never see double clobbering here. CMPL IRP$L_PID(R3),(R11) ; Already clobbered this IRP? BEQL 55$ ; If so don't do it twice! incl ucb$l_fgdun2(r5) ; Flag the IRP MUST be specialized MOVL IRP$L_PID(R3),- (R3) ;store original irp$l_pid field MOVL (R11),IRP$L_PID(R3) ; Now point irp$l_pid at a proper MOVL IRP$L_UCB(R3),(R3) ;save original ucb ; Do all our modifications together MOVL IRP$L_MEDIA(R3),IRP$Q_QIO_P2(R3) ;save media field too .iif df,m$$trp,movl #46,ucb$l_mtrp(r5) MOVL IRP$L_STS(R3),(R3) ;Save status incoming .PRESERVE ATOMICITY INCL UCB$L_OUTSTND(R5) ;Bump outstanding I/O count .NOPRESERVE ATOMICITY .IF NDF,EVAX ; must add vSW$dpt address to this IF VAX ; (for AXP the address is ok as is. The difference has to do with the way ; driver loading differs on the 2 machines.) ; ; Mind, this code is not quite suited to VAX at the moment, but this area ; is a nasty surprise if one is not prepared for it. This will facilitate ; back-porting. MOVAB SW$DPT,R10 ;start of driver ADDL2 R10,IRP$L_PID(R3) ;now pid should get back ok .ENDC ;55$: ;; .IF DF,FFINISH ; If sending to a shadow unit, with DEV$M_VRT set in devchar2 ; do NOT set finipl8 bit since shadow can't hack it. PUSHL R5 TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 4212$ ; IF EQL NO MOVL UCB$L_ALTHOST(R5),R5 ; point at target indirect path UCB BRB 4213$ 4212$: MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB 4213$: BITL #DEV$M_VRT,UCB$L_DEVCHAR2(R5) ;this a shadow dvc? BEQL 4214$ ; Sending I/O to shdriver or the like. Since it loses on fastio finish ; do not set finipl8 bit etc. for it. POPL R5 BRB 209$ 4214$: POPL R5 ; ucb$m_fi8ok in ucb$l_icpfgs says whether our stack of intercepts can handle ; finipl8 stuff. if clear, it cannot. BBS #IRP$V_MVIRP,IRP$L_STS(R3),209$ ;leave MV IRPs alone BBC #UCB$V_FI8OK,UCB$L_ICPFGS(R5),209$ ;skip if not using ffin BBC #0,UCB$L_SAWSUCC(R5),209$ ; if we may be transitioning use IPL 4 BBS #IRP$V_FINIPL8,IRP$L_STS(R3),209$ BBS #IRP$V_FAST_FINISH,IRP$L_STS(R3),209$ cmpl ucb$l_outstnd(r5),#10 bgequ 209$ ; Shadowing assumes IPL4 return unfortunately ; Test this too, in case shdriver is layered above us. (That should be ok ; but take no chances.) BBS #IRP$V_SHDIO,IRP$L_STS2(R3),209$ BISL #,IRP$L_STS(R3) ;set fast path 209$: ;; .ENDC 55$: MOVL (SP)+,R10 ; Restore R10 MOVL (SP)+,R11 ; get R11 back & clean stack now .iif df,m$$trp,movl #17,ucb$l_mtrp(r5) TSTL UCB$L_INDRCT(R5) ; Are we on the direct path? BEQL 113$ ; if so, just send the IRP on TSTL UCB$L_ALTHOST(R5) ; Ensure there IS an alternate host BLSS 112$ ; If UCB looks ok, call alt path 113$: BRW PQORIG ; On the direct path just pass I/O on 112$: ; ; I/O to the alternate device. Must be rerouted and we must grab ; completion in case of split I/O etc. ; ; To accomplish this we need storage for: ; 1. Original irp$l_pid ; 2. Possibly packack count (use intercept UCB for this) ; ; start at with this so ASSUME that we have 2 longs available ; there. Use this one so the error intercept won't clobber it. ; ; We don't unbusy the main device if mount verify is in progress, but ; otherwise we will unbusy the device now. Note that at the top of stack at ; this point is the original R5 of the main path device (which is mounted ; etc.) PUSHL R5 ; We need a register MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB BICL #UCB$M_BSY,UCB$L_STS(R5); unbusy direct line POPL R5 ; get back intercept dvr R5 now ; ; Now redirect to the alternate UCB ; Then return. Note we should always be on indirect path here...following ; bits are belt 'n' suspenders... movl r5,r0 ; Need intercept UCB too. PUSHL R5 TSTL UCB$L_INDRCT(R5) ; INDIRECT? BEQL 212$ ; IF EQL NO BICL #256,UCB$L_INDRCT(R5) ; Clear temporary-alt flag MOVL UCB$L_ALTHOST(R5),R5 ; point at target indirect path UCB BRB 213$ 212$: ;;;; BICL #256,UCB$L_INDRCT(R5) ; Clear temporary-alt flag .iif df,m$$trp,movl #18,ucb$l_mtrp(r5) MOVL UCB$L_HSTUCB(R5),R5 ; POINT AT DIRECT UCB 213$: 1213$: MOVL R5,IRP$L_UCB(R3) ; make IRP point to the underlying dvc ; Geometry of the device must be the same in either case, so IRP$L_MEDIA ; better not have to be altered... ; Here put the IRP on the appropriate queue. Do NOT start it in this ; routine; let caller handle that. tstl R8 ; See if we should omit queueing beql 217$ ; if 0, do not queue (and don't need ; to lock.) ; R5 here is the underlying device, not the SW device. FORKLOCK LOCK=UCB$B_FLCK(R5),- ; Lock the FORK spinlock SAVIPL=-(SP),- ; Save the current IPL PRESERVE=NO ; Don't preserve R0 PUSHL R3 ; Push IRP PUSHAB UCB$L_IOQFL(R5) ; Also push I/O queue listhead CALLS #2,G^EXE_STD$INSERT_IRP ; Make the call. FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock NEWIPL=(SP)+,- ; Restore previous IPL CONDITION=RESTORE,- ; Conditionally release lock PRESERVE=NO ; Don't preserve R0 217$: ; yes, double pop... POPL R5 ; get back intercept ucb POPL R5 ; get back original ucb ; Clear busy on the main host unit since that will have gotten the $qio in ; the first place. $reqcom in the secondary driver will have cleared busy ; there but not here; nor will postproc. do so. However, this code ; needs the ucb clean no more so allow more activity. ; On return R3 is the IRP because it is the same R3 as we were called with. MOVL #1,R0 ; say all ok BRW PSTSRET ; Now restore registers and go to the original routine. ; This is also where we come to try again. ; Assumes host ucb address on stack, SW ucb address in R5, IRP address in R3 PTOORG: .iif df,m$$trp,movl #19,ucb$l_mtrp(r5) MOVL UCB$L_HSTARTIO(R5),R1 ;address of original routine BGEQ PAWA2 ; if none, things are messed...probably will crash POPL R5 ; get back original UCB movl r5,irp$l_ucb(r3) ; set IRP to point at orig. path MOVL #1,R0 ; set ok status for now FORKLOCK LOCK=UCB$B_FLCK(R5),- ; Lock the FORK spinlock SAVIPL=-(SP),- ; Save the current IPL PRESERVE=NO ; Don't preserve R0 PUSHL R3 ; Push IRP PUSHAB UCB$L_IOQFL(R5) ; Also push I/O queue listhead CALLS #2,G^EXE_STD$INSERT_IRP ; Make the call. FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock NEWIPL=(SP)+,- ; Restore previous IPL CONDITION=RESTORE,- ; Conditionally release lock PRESERVE=NO ; Don't preserve R0 BRB PAWAY PAWA2: POPL R5 PAWAB: PAWAY: ; Should get here only after original startio has been called & returned. popl r1 popl r0 RET ; ; Send the IRP off to the primary path. Since we get called here when ; and only when ioc$initiate or equivalent has run, we need not requeue ; the IRP. Just call the original entry. PQORIG: ; We only come here for the primary path so just restore original R5 here ; to get its UCB. MOVL #1,R0 ; set ok status for now .iif df,m$$trp,movl #20,ucb$l_mtrp(r5) MOVL UCB$L_HSTARTIO(R5),R1 ;address of original routine BGEQ PAWA2 ; ; In this routine we are processing IRPs, so just queue them, don't ; actually start I/O. ; call original start-io (to ensure high regs are passed correctly) ; ; No need to popl and then pushl; "optimize" these away. POPL R5 ; restore original R5 movl r5,irp$l_ucb(r3) ; set IRP to point at orig. path tstl R8 ; don't queue if r8=0 beql 217$ ; Insert the IRPs in the underlying queue in the "standard" way FORKLOCK LOCK=UCB$B_FLCK(R5),- ; Lock the FORK spinlock SAVIPL=-(SP),- ; Save the current IPL PRESERVE=NO ; Don't preserve R0 PUSHL R3 ; Push IRP PUSHAB UCB$L_IOQFL(R5) ; Also push I/O queue listhead CALLS #2,G^EXE_STD$INSERT_IRP ; Make the call. FORKUNLOCK LOCK=UCB$B_FLCK(R5),- ; Unlock the FORK spinlock NEWIPL=(SP)+,- ; Restore previous IPL CONDITION=RESTORE,- ; Conditionally release lock PRESERVE=NO ; Don't preserve R0 217$: ; Common return area. Since we are now leaving the intercept, make sure ; that i/o will continue if any is still there to do by moving an IRP ; onto the current dest. input queue ; ; reqcom in the underlying driver will not return to startio if it thinks ; that all outstanding I/O is finished. Thus we must arrange that it will ; find more such by looking here for more and continuing till all current ; I/O has been processed. When we put an IRP back into the multiport ; input queue we must ensure our "already processed" flags are reset also. ; PSTSRET: popl r1 popl r0 RET .SBTTL CONTROLLER INITIALIZATION ROUTINE ; ++ ; ; SW_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. ;-- SW_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 ;++ ; ; SW_UNIT_INIT - UNIT INITIALIZATION ROUTINE ; ; FUNCTIONAL DESCRIPTION: ; ; THIS ROUTINE SETS THE SW: 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. ; ;-- SW_UNIT_INIT: $DRIVER_UNITINIT_ENTRY ; Don't set unit online here. Priv'd task that assigns SW unit ; to a file does this to ensure only assigned SWn: get used. .iif df,d$$bug,movl #-1,ntrmsk .if df,m$$trp ;make the mousetrap site easy to find movl #^xDEADFACE,ucb$l_mtrp+4(r5) clrl ucb$l_mtrp(r5) .endc incl ucb$l_enapth(r5) incl ucb$l_enapth+4(r5) ;allow 2 paths for now movl #^A/SWIT/,ucb$l_sanity(r5) ; BISW #UCB$M_ONLINE,UCB$W_STS(R5) ;SET UCB STATUS ONLINE ;limit size of SW: data buffers SW_bufsiz=8192 MOVL #sw_BUFSIZ,UCB$L_MAXBCNT(R5) ;limit transfers to 8k .if df,d$$bug movab doxdt,ucb$l_dbgdta(r5) .endc 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 SW ; (note the id might be wrong but is attempt to get it.) (used only for ; MSCP serving.) BICL #UCB$M_ONLINE,UCB$L_STS(R5) ;SET UCB STATUS OFFLINE 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 SW: 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 MOVL #1,UCB$L_SAWSUCC(R5) ;Set initial success flag CLRL UCB$L_ALTHOST(R5) CLRL UCB$L_INDRCT(R5) ;use direct path first CLRL UCB$L_OUTSTND(R5) ;set no outstanding I/O yet MOVAB SW_UCB,UCB$L_HUCBS(R5) ;host ucb table CLRL CHNFLG ;initially set to use our chain of FDTs ; set up multiport queue movab ucb$l_mpqueue(r5),ucb$l_mpqueue(r5) movab ucb$l_mpqueue(r5),ucb$l_mpqueue+4(r5) TSTW UCB$W_UNIT(R5) ;this unit 0? BNEQ 2$ MOVL R5,UCB0ADR ;save unit 0 address (global to dvr) 2$: MOVL UCB0ADR,R0 ;get the ucb address BGEQ 3$ ;if obviously illegal skip ; Check the "unit 0 UCB" to ensure that it, as well as we, are compatible ; and both SWdriver units of similar vintage. CMPL UCB$L_SANITY(R0),#^A/SWIT/ ;does this look right? BNEQ 4$ ;no, not valid. MOVL UCB0ADR,UCB$L_UCB0(R5) ;save for all units BLSS 3$ ; oops! User did not configure unit 0 first, so we have no pointer to ; unit 0 available. Therefore disable this intercept from being turned ; on, since a backpointer to unit 0 for the server function is required. 4$: CLRL UCB$L_SANITY(R5) ;ensure we can't turn the unit on MOVL #2,R0 ;lose RET 3$: pushr #^m ; Now fill in our UCB table entry, so it need not be done for every ; I/O. This lets us find the intercept UCB quickly at postprocessing ; time (until we can get a stack in the IRP itself to save this stuff ; more sensibly!). ; MOVZWL UCB$W_UNIT(R5),R11 ; Need address cell ; following assumes that addresses are 32 bits long so shift by 2 gets us ; to an address offset. ASHL #2,R11,R11 ; to get ucb address back at i/o done MOVAB SW_UCBTBL,R10 ; Base of table of UCB addresses ADDL2 R11,R10 ; Make R10 point to cell for THIS UCB movl r5,(R10) ; fill in UCB tbl if this is our 1st time thru popr #^m MOVL #1,R0 RET ;++ ; ; SW_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. ; ;-- SW_STARTIO: $DRIVER_START_ENTRY ; ; PREPROCESS UCB FIELDS ; ; ASSUME RY_EXTENDED_STATUS_LENGTH EQ 8 ; CLRQ UCB$Q_SW_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 .if df,x$$$dt cmpl #^xb00bface,irp$q_qio_p6(r3) bneq 1131$ ; if boobytrap not seen skip jsb g^ini$brk 1131$: movl #^xb00bface,irp$q_qio_p6(r3) .endc REQCOM,ENVIRONMENT=CALL ; COMPLETE REQUEST ; ;; V_UNIT=0 V_UNM=1 .IF DF,EVAX SW_FXS0:: .JSB_ENTRY INPUT= .IFF SW_FXS0:: .ENDC MOVL I^#V_UNIT,R4 BSBW SW_FIXSPLIT ;GO HANDLE RSB SW_FXPL==<.-SW_FXS0> ;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT V_UNIT=V_UNIT+4 ;PASS TO NEXT UNIT .MACRO XVEC LBLC .IF DF,EVAX SW_FXS'LBLC:: .JSB_ENTRY INPUT= .IFF SW_FXS'LBLC:: .ENDC MOVL I^#V_UNIT,R4 BSBW SW_FIXSPLIT RSB .ENDM .REPEAT ; some extra for safety XVEC \V_UNM V_UNIT=V_UNIT+4 ;PASS TO NEXT UNIT V_UNM=V_UNM+1 .ENDR ; Second set of entries for use with MSCP packets. (Bogus, yes, but ; what can one do?) V_UNIT=0 V_UNM=1 .IF DF,EVAX MSW_FXS0:: .JSB_ENTRY INPUT= .IFF MSW_FXS0:: .ENDC MOVL I^#V_UNIT,R4 BSBW SW_FIXSPLIT ;GO HANDLE RSB MSW_FXPL==<.-SW_FXS0> ;LENGTH IN BYTES OF THIS LITTLE CODE SEGMENT V_UNIT=V_UNIT+4 ;PASS TO NEXT UNIT .MACRO MXVEC LBLC .IF DF,EVAX MSW_FXS'LBLC:: .JSB_ENTRY INPUT= .IFF MSW_FXS'LBLC:: .ENDC MOVL I^#V_UNIT,R4 BSBW SW_FIXSPLIT RSB .ENDM .REPEAT ; some extra for safety MXVEC \V_UNM V_UNIT=V_UNIT+4 ;PASS TO NEXT UNIT V_UNM=V_UNM+1 .ENDR ; ; Fixsplit is entered in the SWdriver context, and is entered only where ; the alternate path is in use. Its job is to take an IRP posted in the ; context of the alternate path and post it in the context of the ; primary path. In the case of intercept drivers like vddriver, this will ; be called after the vddriver-local posting code has run and put back ; our code (so the vddriver should unbusy itself by reqcom...). We need ; to check status so if special action (like M Vfy) is needed, we can take ; it. Since we are not synch'd with the victim driver, we cannot just reqcom ; the IRP but must post it. The original main path driver has not in fact ; even seen the IRP though, although ITS UCB will have been marked busy ; and then will be unbusied once secondary startio entry runs. ; ; At this point we need also to pull anything off the multipath input queue ; and grab it into the driver queue, if not a MV IRP. ; ; ; For this intercept it is vital that after we pull in IRPs off the ; multipath queue, that we also note if the queue was empty on our ; start and if so send the first packet off to the real device, so ; things don't get hung. ; .IF DF,EVAX SW_FIXSPLIT: .JSB_ENTRY .IFF SW_FIXSPLIT: .ENDC ; GET OLD PID.. ; IN OUR UCB$PPID LONGWORD... ;some cleanup for host needed here. Note that r5 enters as IRP address. ; Use initial R5 to help reset host's system... PUSHL R4 ;r4 enters with SW unit number MOVL R5,R3 ;put entering IRP addr in std place ; We'll use qio_p3+4 to set flags to skip redundant special processing ; so we can try to avoid extra calls. Clear them here though in case ; we get split-io recalls, which need to be re-arbitrated. CLRL IRP$L_QIO_P3+4(R3) ;clear our special flags MOVAB SW_UCBTBL,R5 ADDL2 (SP)+,R5 ;R5 NOW POINTS AT UCB ADDRESS MOVL (R5),R5 ;NOW HAVE SW UCB ADDRESS IN R5 ; notice stack is now clean too. MOVL R5,R4 ;we need the SW ucb at fork level ; ; set lock not releaseable by fastio code here as a precaution ; This may inhibit fastpath code from releasing locks on subsequent ; cycles for split I/O. .IF DF,IRP$V_LOCK_RELEASEABLE BBSS #IRP$V_LOCK_RELEASEABLE,IRP$L_STS(R3),20$ 20$: .ENDC ; ;; .IF DF,FFINISH PUSHL IRP$L_STS(R3) ;save status fields incoming ; We come in at IPL 8 here; fix up the IRP now. ; Ideally we probably should save & restore these instead. This is not ; good if used with fast dfdriver, for example, since it will set them ; too, then clear them and we'd get here at ipl 4. ; ; This code will work even if stuff above us uses these bits, or not. ; If code BELOW this level knows about them, all is well also. If not ; however, its synchronization can be loused up since it will have any ; i/o completion called at ipl 8, not 4...this is so if the code uses ; the irp$l_pid hook itself, that is. If it does not, all will be ; normal. For many cases this may not matter much. We could of course ; simply state that if the IRP is a system IRP on entry, we won't ; mess with its delivery at all, but will only try to facilitate OUR ; postprocessing. Use of this hook requires however that any lower ; processing that steals irp$l_pid again be tolerant of being called ; with finipl8 set and thus of having its postprocessing occur at ; ipl 8, not 4. Such stealing SHOULD save/restore the status bits ; too, so that it will not simply turn the bits off but rather ; save and restore them. Otherwise what will happen is that ; our return will occur at ipl 4 when not expecting to. BBS #IRP$V_FINIPL8,IRP$L_STS(R3),209$ ; if finipl8 bit is clear, we will need to devicelock to get to proper IPL ; Otherwise we should be here at IPL 8 already. ; ; Note THIS DRIVER'S DEVICE IPL is 8!! ; DEVICELOCK SAVIPL=-(SP),PRESERVE=YES ;Devicelock here is IPL 8 brb 1209$ 209$: pushl r5 1209$: MOVL (R3),IRP$L_STS(R3) ;Restore status incoming ;; .IFF ;ffinish ;; DEVICELOCK SAVIPL=-(SP),PRESERVE=YES ;Devicelock here is IPL 8 ;; .ENDC ;ffinish .iif df,m$$trp,movl r3,ucb$l_mtrp+24(r5) .iif df,m$$trp,movl #21,ucb$l_mtrp(r5) ; ; We set up the primary path UCB here. ; That is after all what VMS thinks we're using. ; MOVL UCB$L_HSTUCB(R5),R5 ;note SW ucb still in r4 ; ; At this point, one I/O has finished, and we use this opportunity to look ; for other work that might be on the multiport queue, as yet unmigrated to ; this driver. Nevertheless we'll make VERY sure it hasn't been already ; clobbered in that the reloc routine won't relocate the IRPs unless they ; look like we did not already do this. ; ; By draining the multiport input queue into the underlying driver queue ; at this point (as long as this isn't a MV IRP!) we should keep I/O going, ; much as happens in a "normal" situation with the driver doing reqcom ; and starting the next I/O if any exists. We just ensure that "any" will ; exist if any work was available on the multiport queue. ; ; Now we can move the IRPs to the driver queue if there are any on the ; queue. ; ; Get the forklock since other code will assume this is synch'ing the ; queue! FORKLOCK LOCK=UCB$B_FLCK(R5),- ; Lock the FORK spinlock SAVIPL=-(SP),- ; Save the current IPL PRESERVE=YES ; Do not bother if this IRP is a MV IRP. BBS #IRP$V_MVIRP,IRP$L_STS(R3),44$ ;this IRP from mnt verify? ; Now before other processing, try to ensure that IRPs on the multipath ; queue are pulled to underlying device's queue pushr #^m ; Also don't try to restart if this UCB is in (or about to be in) ; mount verify (the main UCB, that is). ; ; This way the i/o finish stuff doesn't fight with the pending I/O stuff ; in this driver to get I/O stopped. ; movl ucb$l_hstucb(r4),r6 bitl #,ucb$l_sts(r6) bneq 42$ ; skip if master is in MV status still ; ; Looks like the I/O completion is a normal one basically. ; (Note MV end call will have put I/O back on device queue if MV is ; ending; we don't need to handle that here.) ; ; First look to see if the input queue was empty. ; If the queue header points to its own address this is a pretty good ; indication that the queue is empty. ; Use R6 as a flag ; ;;;; movl irp$l_ucb(r3),r5 ; This is still the real host ; We really need for (p)stealstart to figure out which underlying queue to ; put things on, and it won't find it unless we point at the primary path. movl ucb$l_hstucb(r4),r5 .if df,d$$bug tstl inmve beql 712$ .iif df,x$$$dt, jsb g^ini$brk 712$: .endc ;443$: movab ucb$l_ioqfl(r5),r6 ; get the address of queue ; bgeq 43$ ; if illegal we won't touch (but will crash anyhow soon) ; cmpl r6,(r6) ; is queue empty? ; bneq 43$ ; if not, nothing special ; clrl r6 ; else flag that the q was empty at ; first so we can get the driver going ; 43$: remque @ucb$l_mpqueue(r4),r3 ;get an IRP off our input queue bvs 42$ ; if nothing there skip out ; ok, we have an IRP to insert. Gotta relocate the sucker & put it onto the ; host driver's input queue that we are using. ; First we need to "process" it, though. pushl r0 ; ; ????????????? could push just R6 here??? (0/non-0 test only) ; ; tstl r6 ; See if queue had been empty ; If the queue had been empty we better call insioqc here to get it going ; again. ; Then we better check again too! ; bneq 343$ ; pushl #0 ; If the queue was empty, we put this ; on separately ; brb 543$ ;343$: pushl #1 543$: ; Pstealstart will relocate the IRP to the correct underlying queue ; MV end should start pulling stuff off the queue also. calls #1,PSTEALSTART ;go process the IRP to input queue popl r0 tstl r6 ; Had queue initially been empty? bneq 43$ ; If not, go pull another entry off ; and move it, after testing again ; in case the driver finishes and ; will need more work. ; At this point the queue had been seen empty initially so let us call ; insioqc here. The IRP should be ready for this, and is addressed ; in R3 as expected .iif df,m$$trp,movl r3,ucb$l_mtrp+28(r4) .iif df,m$$trp,movl #22,ucb$l_mtrp(r4) ; We will call insioqc ONLY ONCE and will NOT recheck whether the queue ; was empty at start, since insioqc may (during MVfy) put things back ; on. .if df,d$$bug tstl inmve beql 714$ .iif df,x$$$dt, jsb g^ini$brk 714$: .endc ;???????????? move this to later?? omit? Beware if a split i/o is here! ;{ ; Because the master queue length gets bumped above us, decrement it again ; before ins here if still on master path. (This will call pending io here ; to actually insert the item.) ; Also flag that this IRP is already relocated ; movl #3,irp$q_qio_p3+4(r3) ; decl ucb$l_qlen(R5) ; call_insioqc ;} brw 43$ ; The pstealstart routine does not start i/o, just moves IRPs to queues. 42$: popr #^m 44$: ; ; r4 should still be SW ucb, r5=primary host ucb, r3=irp ; ; R5 points at host device UCB here, not to SW device UCB. ; ; Reset the IRP to have the original return ; Thus the IRP will really complete next, not come back here. ; ; This will then appear to be coming from the original driver. ; Note: we won't bother testing to see if irp$l_pid points to our code because ; unless it did, there'd be no way to get here. ; MOVL (R3),IRP$L_PID(R3) ;restore pid so post is normal ; clrl (R3) ; Be sure we note the IRP's NOT mung'd 844$: MOVL (R3),IRP$L_UCB(R3) ;restore original UCB ; ; However, where an IRP indicates the alternate device might have needed ; to start mount verify, we want to make it look like THIS device needs ; to...so check the conditions similarly to the way VMS normally does it. ; ; Note that com$post does not alter driver busy etc. PUSHL R4 ; We need the intercept and original UCB PUSHL R5 ; across the com$post call...so save here .IF NDF,EVAX EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$W_FUNC(R3),R0 .IFF EXTZV #IRP$V_FCODE,- ; Extract I/O function code #IRP$S_FCODE,- ; IRP$L_FUNC(R3),R0 .ENDC ASSUME IRP$S_FCODE LE 7 ; Allow byte mode dispatch ; io$_packack is 8, but treat ALL MVIRP's the same. None is synch'd by ; the bsy bit. BITL #IRP$M_MVIRP,IRP$L_STS(R3) ;this IRP from mnt verify? BNEQ 1000$ ; If mnt vfy, branch ; ; Get to this for all IRPs except those from mount verify. Those we handle ; separately. ; ; Run thru a few tests to be sure mount verify would want this I/O ; Note we presume (as is true in dkdriver) that the 16384 bit is ; set only if i/o was successful but completed with an HSZ40 notice ; that something happened to the "other" controller. ; MOVQ IRP$L_MEDIA(R3),R0 ;get final status ; finish undoing all IRP mods so it will not show evidence of our ; tampering. movl irp$q_qio_p2(r3),irp$l_media(r3) ; reset media BITL #16384,R0 ;Was our "failover/back" flag there? BNEQ 60$ ;if so implies success but send msg BLBS R0,50$ ;if I/O was OK no mnt ver ; I/O failed and may have been due to a condition that mount verify ; will want to deal with. .if df,m$$trp movl r5,ucb$l_mtrp+40(r4) ;store UCB we used .endc .if df,d$$bug cmpw #ss$_medofl,r0 bneq 707$ incl doxdt incl ntruse .iif df,x$$$dt,jsb g^ini$brk ;************debug********** 707$: .endc CMPB UCB$B_DEVCLASS(R5),#DC$_DISK ;disk device? BNEQ 50$ ;no mnt ver if not disk BITL #DEV$M_MNT,UCB$L_DEVCHAR(R5) ;mounted disk? BEQL 50$ BITL #DEV$M_FOR,UCB$L_DEVCHAR(R5) ;/foreign mounted? BNEQ 50$ ;if mounted foreign can't mntver either TSTL IRP$L_PID(R3) ; This an internal IRP? BLSS 50$ ; if so, MV won't want it. PUSHR #^M ; Note exe$mount_ver can clobber regs R0 to R5, so we save/restore ; around the call to be sure we understand what's going on. BBCC #UCB$V_MNTVERPND,UCB$L_STS(R5),53$ ; Clear MV in progress if we are going this way so it can really start. BICL #,UCB$L_STS(R5) 53$: ; EXE$MOUNT_VER might requeue the IRP to the device again after a host ; driver $REQCOM normally finds that mount ver is needed. Therefore to ; avoid problems, put the original media address back (R0,R1 have ; the I/O status at this point) and replace the status after the ; call if we are just going to post it straight-way. If we are ; in fact just supposed to exit and do nothing, leave the IRP ; alone since it will get reused. Since we have repaired the ; media field here, this will permit it to work correctly the ; next pass. MV will of course requeue the IRP to the master path ; UCB in this case (it never does anything on the secondary device ; regardless of completion since that device is marked unmounted...and ; had darn well better BE unmounted. Therefore it'll be unaware of ; the separate secondary...just the primary. WE have to do the juggling ; here. ; Note that by this point all fields of the IRP have been restored to ; normal, and also the "saved" irp$l_pid cell has been cleared so we ; will never see it as having valid data from a prior bash. ; MOVL IRP$Q_QIO_P2(R3),IRP$L_MEDIA(R3) ; RESTORE... ; media field in case IRP gets re-Qd pushl r0 JSB G^EXE$MOUNT_VER ;see if we should mount ver popl r0 ;;; BLBS R2,52$ BLBC R2,55$ ;if not skip com$post... 52$: POPR #^M BRW 652$ ; ; Return to 652$ also when we finish sending the message to the server ; where I/O status returned with 16384 bit set. Since that was an ; operation on the successful side, waiting to downcount the outstanding ; i/o on that device will not interfere with switching over other now-failed ; devices that are idle to the now-still-good side. ; 50$: ; I/O was a success and not from MV, so clear the primary's MV indicator .if eq,1 ;don't mess with this here BICL #,UCB$L_STS(R5) ;Clear MV bits .endc 652$: .PRESERVE ATOMICITY DECL UCB$L_OUTSTND(R4) ;Bump outstanding I/O count BGEQ 51$ CLRL UCB$L_OUTSTND(R4) ;make sure it doesn't go neg... 51$: .NOPRESERVE ATOMICITY ; R5 still is the host device UCB. ; Since we unbusy the main device when sending to the alternate path ; we need not unbusy again here, and should not. On the main path ; or within the secondary driver, the internal REQCOM logic will have ; unbusied those UCBs by now themselves, (or will do so when we return ; from here and the IRP posts and runs their intercept, if there's a ; pseudodriver under us.) ; ; Replace IRP status since we'll post it now. MOVL R0,IRP$L_MEDIA(R3) movl r1,IRP$L_MEDIA+4(r3) .iif df,m$$trp,movl #25,ucb$l_mtrp(r4) .if df,x$$$dt cmpl #^xb00bface,irp$q_qio_p6(r3) bneq 1131$ ; if boobytrap not seen skip jsb g^ini$brk 1131$: movl #^xb00bface,irp$q_qio_p6(r3) .endc PUSHL R5 ; UCB of primary path (used to bump i/o cnt) PUSHL R3 ; IRP CALLS #2,G^COM_STD$POST ;COM$POST REPLACEMENT BRW 2000$ 55$: ;MV should start so just finish off POPR #^M BRW 2000$ ; get to 1000$ if this is a packack completion, which we should handle with ; com$post only (in the context of the main path driver!) ; Also come here for any other MV operations. 1000$: ; Here complete the IRP with the original device context so mount verify ; sees the I/O as coming to and from the main path disk. ; ; In fact we will handle this I/O with REQCOM. This is what the mount ; verify path will expect. ; ; .iif df,x$$$dt, jsb g^ini$brk ;**************** debug ********* ; At this point R4 points at the SWdriver UCB still. PUSHL R0 PUSHL R1 1900$: ; Since we bump the outstanding count before dispatching to start, we ; must decrement it here also. .PRESERVE ATOMICITY DECL UCB$L_OUTSTND(R4) ;Bump outstanding I/O count BGEQ 951$ CLRL UCB$L_OUTSTND(R4) ;make sure it doesn't go neg... 951$: .NOPRESERVE ATOMICITY .iif df,m$$trp,movl #26,ucb$l_mtrp(r4) MOVQ IRP$L_MEDIA(R3),R0 ;get final status .if df,x$$$dt cmpl r5,ucb$l_hstucb(r4) ;check this IS the principal path ucb beql 1904$ ; Hit a breakpoint if UCB is wrong AND save a pointer so we know if we hit .iif df,m$$$trp,movl r5,ucb$l_mtrp+52(r4) jsb g^ini$brk movl ucb$l_hstucb(r4),r5 ; force it right so we can proceed too! 1904$: .endc movl r3,ucb$l_irp(r5) ; Be sure REQCOM can find the IRP .if df,x$$$dt cmpl #^xb00bface,irp$q_qio_p6(r3) bneq 1231$ ; if boobytrap not seen skip jsb g^ini$brk 1231$: movl #^xb00bface,irp$q_qio_p6(r3) .endc ; ; At this point the underlying layer has posted the IRP and we got hold ; of it. However, we don't know if the underlying layer did com$post, ; reqcom, or whatever. All we know is that we have the IRP, so we ; really have no right to mess with the underlying device busy bit. ; If the device is free it won't matter, but if it is busy, we can ; screw it up. .if df,d$$bug tstl inmve beql 715$ ; if both are set break once only tstl doxdt bneq 715$ .iif df,x$$$dt, jsb g^ini$brk 715$: tstl doxdt beql 719$ .iif df,x$$$dt, jsb g^ini$brk 719$: .endc call_post ; CALL_REQCOM ;let REQCOM do it ; .iif df,x$$$dt, jsb g^ini$brk ;**************** debug ********* POPL R1 POPL R0 2000$: POPL R5 POPL R4 ; Give back host device's forklock FORKUNLOCK LOCK=UCB$B_FLCK(R5),NEWIPL=(SP)+,- CONDITION=RESTORE,PRESERVE=YES MOVL #SS$_NORMAL,R0 MOVL R4,R5 ;we use pseudo dvc devicelock,not target's ;(SP) is IPL ; 4(sp) is old sts at this point. BBS #IRP$V_FINIPL8,4(SP),219$ DEVICEUNLOCK NEWIPL=(SP)+,PRESERVE=YES brb 1219$ 219$: movab 4(sp),sp 1219$: ; Following gets rid of the IRP$L_STS copy on the stack. MOVAB 4(SP),SP ;add a long to stack pointer .if df,d$$bug tstl inmve beql 717$ incl inmve cmpl inmve,#3 ;don't keep up forever blss 718$ clrl inmve clrl doxdt 718$: .iif df,x$$$dt, jsb g^ini$brk 717$: .endc MOVL UCB$L_HSTUCB(R4),R5 ;note SW ucb still in r4 ; Returning with R5 pointing at the host means the fork exit gets to ; clean up using the correct forkblock; ditto whatever else... ; ; .iif df,x$$$dt, jsb g^ini$brk ;**************** debug ********* RSB ; GET BACK TO HOST SOMETIME 60$: ; Now send a message (if we can) to our swap-daemon so it can provide ; swaps of other stuff if it needs to. bicl #16384,r0 ; clear the extra bit PUSHR #^M ;check daemon pid still valid. If it is not, we obviously cannot send a ; message to it via the mailbox. If we cannot find it, zero the flag ; so we cannot even look for it again. TSTL UCB$L_DAEMON(R5) ;Any daemon process to look for? BEQLW 30$ ;if not, skip PUSHR #^M MOVZWL G^SCH$GL_MAXPIX,R7 ;max process index in VMS 422$: MOVL G^SCH$GL_PCBVEC,R6 ;get PCB vector address MOVL (R6)[R7],R8 ;get a PCB address TSTL R8 ;system address should be < 0 BGEQ 23$ ;if it seems not to be a pcb forget it CMPL UCB$L_DAEMON(R5),PCB$L_PID(R8) ;this our process? BEQL 321$ ;if so, jump out of loop 23$: SOBGTR R7,422$ ;if not, look at next CLRL UCB$L_DAEMON(R5) ;if cannot find process, zero our flag 321$: POPR #^M TSTL UCB$L_DAEMON(R5) ;got our daemon process there? BEQLW 30$ ;if not, skip MBX.SIZ=48 ;(use next higher quadword size) ALO.SIZ=60 ; We grab a little more stack than needed for the buffer so we can align it MOVL SP,R4 ; Point R4 at the buffer allocated SUBL #ALO.SIZ,SP ;"allocate" a buffer SUBL #MBX.SIZ,R4 BICL #7,R4 ; align the buffer on 8 byte bdy ; ; Buffer format: ; ; Fill in the message now as we need it. ; 0/1 flag for alt or direct device failed ; UCB address of failed device ; unit number of failed device ; allocation class of failed device ; counted device name (16 bytes in all) PUSHR #^M MOVL UCB$L_HSTUCB(R5),R0 ;get host ucb MOVL UCB$L_INDRCT(R5),(R4)+ ;Tell daemon which unit... BEQL 24$ ;if we just left direct mode TSTL UCB$L_ALTHOST(R5) BGEQ 24$ ;don't use a null althost address MOVL UCB$L_ALTHOST(R5),R0 ;get ucb of alt. device 24$: MOVL R0,(R4)+ ;pass ucb of failed device MOVL UCB$L_DDB(R0),R1 ;Point at the DDB MOVZWL UCB$W_UNIT(R0),(R4)+ ;send unit number MOVL DDB$L_ALLOCLS(R1),(R4)+ MOVAB DDB$B_NAME_LEN(R1),R2 ;point at device name string MOVL (R2)+,(R4)+ ;fill in device name MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL IRP$L_MEDIA(R3),(R4)+ ;set a code that says we came from finish MOVL DDB$L_SB(R1),R2 ;GET SB of the node MOVAB SB$T_NODENAME(R2),R2 ;POINT at nodename MOVL (R2)+,(R4)+ ;nodename is 16 chars, count first MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ MOVL (R2)+,(R4)+ POPR #^M MOVL #MBX.SIZ,R3 ; r3 gets size MOVL UCB$L_MBXUCB(R5),R5 ; ucb of mailbox BGEQ 46$ ; if zero or + forget the write attempt ;ensure mailbox is not deleted and still connected to by server. ; 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. Note that earlier we ensured the ; server process still exists on this machine. CMPB #DYN$C_UCB,UCB$B_TYPE(R5) BNEQ 46$ ; Be sure this IS a UCB 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. BBC #DEV$V_MBX,UCB$L_DEVCHAR(R5),46$ ; If not a mailbox, reject it too cmpb #DC$_Mailbox,ucb$b_devclass(r5) bneq 46$ ; be sure it's a mailbox too ; Here send a message to the daemon and continue. It appears the mailbox ; is ok. CALL_WRTMAILBOX SAVE_R1=yes 46$: ADDL2 #ALO.SIZ,SP ;"deallocate" the space we grabbed 30$: POPR #^M MOVL #SS$_NORMAL,IRP$L_MEDIA(r3) ;restore success BRW 50$ SW_END: ;ADDRESS OF LAST LOCATION IN DRIVER .END