%TITLE 'IOGEN$SCSI_CONFIG, common SCSI bus autoconfiguration (BSR)' MODULE iogen$scsi_config (IDENT = 'X-22', ENVIRONMENT(NOFP)) = BEGIN ! ! Copyright © Digital Equipment Corporation, 1991, 1996 All Rights Reserved. ! Unpublished rights reserved under the copyright laws of the United States. ! ! The software contained on this media is proprietary to and embodies the ! confidential technology of Digital Equipment Corporation. Possession, use, ! duplication or dissemination of the software and media is authorized only ! pursuant to a valid written license from Digital Equipment Corporation. ! ! RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the U.S. ! Government is subject to restrictions as set forth in Subparagraph ! (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, as applicable. ! !++ ! COMPONENT: ! ! IOGEN ! ! MODULE DESCRIPTION: ! ! This module is responsible for autoconfiguring a SCSI bus. It can be used ! by any BSR to configure the devices accessible through a specific SCSI port ! once that port has been configured. ! ! AUTHOR: ! ! Richard W. Critz, Jr. ! ! CREATION DATE: 22-Nov-1991 ! ! ! MODIFICATION HISTORY: ! ! X-22 GCE001 Glenn C. Everhart 20-May-1997 ! Support multipath; specifically, support device allocation ! classes coming from HSZ50/70 and linking in SWdriver. ! X-21 GP0001 Genady Perchenko 7-May-1997 ! QAR EVMS-RAVEN 659 ! Added decice_ok variable check prior to the call of $fao ! in order to prevent passing uninitialized .prefix to the ! fao call. ! ! X-20 LSS0366 Leonard S. Szubowicz 6-May-1996 ! Merge of edit X-13A1. ! Fix for EVMS-GRYPHON QAR 538 and EVMS-GOBLIN QAR 170. Allow ! sufficient time for PKSDRIVER to bring the KZPSA PCI-to-SCSI ! adapter on-line before IOGEN$SCSI-CONFIG gives up on it. There ! are two relevant changes here. First the PKSDRIVER on-line ! sequence takes about 7 seconds but IOGEN$SCSI-CONFIG only allows ! about 7 to 8 seconds by doing at most 8 attempts spaced 1 second ! apart. This edit raises the maximum attempt count to 12. ! Secondly, the expected wait of 1 second between retries can be ! arbitrarily shortened by any other asynchronous activity in this ! process that sets EXE$C_SYSEFN. An AST routine, an IOSB, and ! the $SYNCH service are now used to assure that we don't resume ! before the 1 second interval expires. ! ! X-19 JCH710d John C. Hallyburton, Jr. 15-Mar-1996 ! Beware the ideas of March! Have IOGEN$LOG get the device name ! after the connect, so it gets displayed correctly. ! ! Update Copyright year. ! ! X-18 JCH710c John C. Hallyburton, Jr. 6-Jan-1996 ! Epiphany: During asynchronous polling you can get a bad ! status and later reference the spdt, which is only defined ! when SS$_NORMAL is returned. So move the code that defines ! the spdt out of the status switch. ! ! Remove asynchronous polling diagnostic messages. ! ! X-17 JCH710b John C. Hallyburton, Jr. 22-Dec-1995 ! Set multihost bit in SPDT if another CPU is found on the bus. ! Set tape flag in SPDT if a tape is found on the bus. ! Pass SPDT to SCCPUVER routine CLU$CHECK_INQUIRY. ! ! X-16 MCY Mary Yuryan 28-Nov-1995 ! Add support for high SCSI ID's. Change max_scsi_id from ! 7 to 31. Modify IOGEN$SCSI_CONFIG to terminate ID scan when ! ss$_toofewdev has been returned. ! ! X-15 JCH710a John C. Hallyburton, Jr. 28-Nov-1995 ! STAR:: DOCD$:[EVMS.PROJECT_DOCUMENTS]FS-SCSI-NAMING.PS ! Per Coughlan/Critz email, have configure code call [CLUSTER] ! module SCCPUVER routine CLU$CHECK_INQUIRY when finding a CPU ! while probing a SCSI bus. This will replace the STACONFIG ! checks. ! ! X-14 JCH710 John C. Hallyburton, Jr. 16-Oct-1995 ! When configuring a SCSI port, get the port allocation class ! and pass it to $LOAD_DRIVER. ! DOCD$:[EVMS.PROJECT_DOCUMENTS]FS-SCSI-NAMING.PS ! ! X-13 RWC150 Richard W. Critz, Jr. 12-Apr-1995 ! In doing RWC149, I spent a great deal of time in the SCSI-2 spec ! to determine exactly the behavior that was appropriate. As part ! of that, I tightened the checks made on the INQUIRY responses ! coming back from the targets. This resulted in some changes ! from the initial implementation (which was pretty much a ! translation of the equivalent VAX code). It turns out that (at ! least) the TZ30 and the TK50 do not conform to the SCSI ! specification as it relates to INQUIRY responses. The stricter ! code implemented in RWC149 prevents the TZ30 and TK50 from being ! autoconfigured. While one could argue that this is really a ! service to mankind, I really have no interest in explaining why ! this is so in response to CLDs. Thus, this change goes back to ! the TZ30-friendly level of checking on the inquiry responses. ! ! X-12 RWC149 Richard W. Critz, Jr. 17-Feb-1995 ! Add support for configuring non-zero LUNs. ! ! X-11 RWC146 Richard W. Critz, Jr. 18-May-1994 ! The loops that configure devices incorrectly configure ! unsupported devices if a supported device was previously ! configured by that loop. This change insures that only ! supported devices are configured by this module. ! ! X-10 RWC109 Richard W. Critz, Jr. 9-Dec-1992 ! Restore the access mode parameter on the assign IOGEN$SCSI_POLL. ! It was accidentally deleted in RWC108. ! ! X-9 RWC108 Richard W. Critz, Jr. 4-Dec-1992 ! The fix in X-8 only masks the problem and doesn't actually solve ! it. The real problem is that polling takes a long time. As a ! result, the lock request made by IOGEN$SCSI_POLL_DONE ends up ! dying in a false deadlock detection. Although the manual claims ! that LCK$M_NODLCKWT should not be used with $ENQW, it is safe to ! do so in this case since all that's necessary is to extend the ! wait time in SCSI_POLL_DONE to allow polling to actually ! complete. ! ! X-8 RWC107 Richard W. Critz, Jr. 18-Nov-1992 ! When DCL runs down an image, it does it only for user mode. ! This means that channels assigned in any mode other than user ! are not deassigned and any I/O in progress on those non-user ! mode channels continues without cancellation. Under exceptional ! conditions, this can cause a kernel mode AST to be delivered to ! a non-existent address on any user of the multi-threaded polling ! service. ! ! ----------------Masterpack cleanup requires resynch of generation number------ ! ! X-8 BAP078 Bridget Powers 24-Sep-1992 ! Renamed EXE$ALONONPAGED_LINKAGE to ..._2 and ! EXE$ALOP1IMAG_LINKAGE to ..._2 and ! EXE$DEAP1_LINKAGE to ..._2 to ! resolve BLISS linkage naming conflicts ! ! X-7 RWC080 Richard W. Critz, Jr. 23-Mar-1992 ! Allow their to be no CRB address in the bus array entry passed ! to IOGEN$SCSI_POLL_DONE. ! ! X-6 RWC079 Richard W. Critz, Jr. 16-Mar-1992 ! Add routines to implement multi-threaded polling. Add ! IO$M_INHERLOG to polling QIOs. ! ! X-5 RWC075 Richard W. Critz, Jr. 2-Mar-1992 ! Integrate autoconfigure logging. ! ! X-4 RWC074 Richard W. Critz, Jr. 26-Feb-1992 ! Allow retries when a status of SS$_DEVOFFLINE is returned by the ! $QIO to the SCSI port. The number of retries (currently 8) is ! set in the local variable RETRIES. ! ! X-3 RWC059 Richard W. Critz, Jr. 16-Dec-1991 ! Fix build bugs. ! ! X-2 RWC057 Richard W. Critz, Jr. 11-Dec-1991 ! The aforementioned "real code".... ! ! X-1 RWC055 Richard W. Critz, Jr. 22-Nov-1991 ! Original stub to facilitate building. Real code to be provided ! later. ! !-- ! ! TABLE OF CONTENTS: ! FORWARD ROUTINE iogen$scsi_config, cleanup, offline_timer_ast : NOVALUE, allocate_busarray, rewrite_busarray, iogen$scsi_poll, iogen$scsi_poll_done, maybe_crash, scsi_lock, scsi_poll_setup, orbit : NOVALUE, do_qio : NOVALUE, poll_io_done : NOVALUE; ! ! INCLUDE FILES: ! LIBRARY 'sys$library:lib'; LITERAL inquiry$t_hszalc = 102; REQUIRE 'lib$:inquirydef'; ! ! MACROS: ! MACRO kernel_call (kroutine) [] = BEGIN EXTERNAL ROUTINE sys$cmkrnl; LOCAL args : VECTOR[%LENGTH,LONG,SIGNED] INITIAL(LONG(%LENGTH-1 %IF %LENGTH GTR 1 %THEN , %REMAINING %FI)); sys$cmkrnl(kroutine, args) END %; ! ! EQUATED SYMBOLS: ! LITERAL false = 1 EQL 0, true = NOT false, max_scsi_id = 31, max_scsi_lun = 7, scsi_nodes = max_scsi_id + 1, max_retries = 12; LITERAL hsz_inq_sn1 = 36, ! offset for HSZ devices to this controller s/n hsz_inq_sn2 = 46, ! offset for HSZ devices to other controller s/n mhsz_inq_alcls = 102; ! offset in INQUIRY data to allocation class macro hsz_inq_alcls = 102,0,8,0 % ; ! ! OWN STORAGE: ! OWN allocls, ! OWN storage so we can statically allocate it in an itemlist dev_ucb; ! Device UCB allocated by LOAD_DRIVER, used for name formatting OWN gotsw : INITIAL(0); OWN dvcalloclass : VECTOR[scsi_nodes,LONG] INITIAL(REP scsi_nodes OF (0)); BIND one_second = PLIT LONG(-1*10*1000*1000, -1), ! delta time of 1 sec noadap = $itmlst_uplit((itmcod=iogen$_noadapter, bufadr=0), (itmcod=iogen$_ucb, bufadr=dev_ucb), (itmcod=iogen$_allocls, bufadr=allocls) ); ! ! EXTERNAL REFERENCES: ! EXTERNAL LITERAL iogen$_scsipoll; EXTERNAL clu$gb_vaxcluster, exe$gl_sysid_lock : SIGNED LONG, ioc_std$searchdev, ioc$gl_naming; EXTERNAL ROUTINE INI$BRK: NOVALUE; LINKAGE exe$alononpaged_linkage_2 = JSB (REGISTER = 1; ! size of block required REGISTER = 1, ! actual size of allocated block REGISTER = 2), ! address of allocated block ioc$verifychan_linkage = JSB (REGISTER = 0; ! channel number REGISTER = 1) : ! address of CCB NOPRESERVE(0,1,2,3), exe$alop1imag_linkage_2 = JSB (REGISTER = 1; ! size of block required REGISTER = 1, ! actual size of allocated block REGISTER = 2) : ! address of allocated block NOPRESERVE(0,1,2,3), exe$deap1_linkage_2 = JSB (REGISTER = 0, ! address of block to deallocate REGISTER = 1) : ! size of block to deallocate NOPRESERVE(0,1,2,3); EXTERNAL ROUTINE clu$check_inquiry, clu$check_scsi_cpu, exe$alononpaged : exe$alononpaged_linkage_2, ioc$verifychan : ioc$verifychan_linkage, ioc_std$cvt_devnam, exe$alop1imag : exe$alop1imag_linkage_2, exe$deap1 : exe$deap1_linkage_2, ioc_std$test_switch : NOVALUE, iogen$ac_select, iogen$class_match : NOVALUE, iogen$log, sys$load_driver; forward routine k$ioc$searchdev; linkage rl$search_dev = jsb ( register = 1, register = 4; register = 1, register = 2, register = 3 ) : nopreserve ( 2, 3, 4); macro r_ioc$searchdev = rl$search_dev %; ! ! IOC$SEARCHDEV ! ! rl$search_dev = jsb ( register = 1, register = 4; ! register = 1, register = 2, ! register = 3 ) global routine j$ioc$searchdev (in1, in4; out1, out2, out3) : r_ioc$searchdev = begin local res0, res1 : volatile, res2 : volatile, res3 : volatile; res0 = kernel_call(k$ioc$searchdev, .in1, .in4, res1, res2, res3); out1 = .res1; out2 = .res2; out3 = .res3; return (.res0); end; routine k$ioc$searchdev (in1, in4, out1, out2, out3) = begin external routine ioc$searchdev : r_ioc$searchdev; local res0; res0 = ioc$searchdev(.in1, .in4; .out1, .out2, .out3); return (.res0); end; %SBTTL 'IOGEN$SCSI_CONFIG, configure a SCSI bus' GLOBAL ROUTINE iogen$scsi_config( handle, portnam : REF BLOCK[,BYTE]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine is responsible for autoconfiguring all supported devices on a ! SCSI bus. If there is no bus array associated with the SCSI port's CRB, it ! allocates and initializes one. It then makes a working copy of this bus ! array on the stack so that it doesn't have to bounce back and forth between ! exec and kernel modes. It makes two passes over this copy of the busarray. ! The first is to poll the unidentified SCSI bus id's to find what is there. ! The second is to configure those devices found in the first pass which are ! supported by default. ! ! FORMAL PARAMETERS: ! ! handle - autoconfiguration handle, a "magic number" ! portnam - address of SCSI port device name's descriptor ! ! IMPLICIT INPUT PARAMETERS: ! ! bus array associated with SCSI port's CRB ! ! IMPLICIT OUTPUT PARAMETERS: ! ! bus array associated with SCSI port's CRB ! ! RETURN VALUE: ! ! SS$_NORMAL ! SS$_ABORT ! SS$_INSFMEM ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LITERAL pssz = 8; ! Asciz port string size (bytes) LOCAL workarray : BLOCKVECTOR[scsi_nodes,busarrayentry$k_length,BYTE] VOLATILE, devnambuf : VECTOR[8,BYTE] VOLATILE, devnam : VECTOR[2,LONG,SIGNED] INITIAL(0,devnambuf), dispnambuf : VECTOR[64, BYTE] VOLATILE, dispname : VECTOR[2, LONG] ALIAS, inquirybuf : BLOCK[inquiry$k_length + 70,BYTE], ! size is 106 or more iosb : VECTOR[4,WORD], ccb : REF BLOCK[,BYTE], ddb : REF BLOCK[,BYTE], ucb : REF BLOCK[,BYTE], crb : REF BLOCK[,BYTE], spdt : REF BLOCK[,BYTE], busarray : REF BLOCK[,BYTE], retries : INITIAL(max_retries), controller, tmpnam : INITIAL('HSZ'), status, status2, channel, portz: VECTOR[pssz, BYTE] ALIAS, ! Create ASCIZ port name prefix, driver, savalcls, ! Save possible port alloc class here to be overridden by device alloclass if any device_ok; LOCAL sw01 : REF BLOCK[,BYTE], sw02 : REF BLOCK[,BYTE], sw03 : REF BLOCK[,BYTE], sw0got : INITIAL(0), sw0nam : VECTOR[64,BYTE] INITIAL('SWA0'), sw0dsc : VECTOR[2,LONG,SIGNED] INITIAL(4,sw0nam); ! 14 is DSC$K_DTYPE_T ! Note that we might reasonably put a vector to hold all the worldwide IDs we ! see in at this point too. BIND port = portnam[dsc$a_pointer] : REF VECTOR[,BYTE]; ! Create ASCIZ version of port name. Trust me, you need this. CH$COPY(.portnam[dsc$w_length], ! Source bytecount .portnam[dsc$a_pointer], ! Source address 0, ! Fill byte pssz, ! Destination length portz); ! Destination address ! Get allocation class (if any) matching port name. Output variable ! allocls is passed to sys$load_driver via the "noadap" itemlist entry. INCR id from 0 to max_scsi_id DO BEGIN dvcalloclass[.id] = 0; END; kernel_call(iogen$class_match, portz, allocls); ! Begin by assigning a channel to the port device and then tracking down ! the associated CRB and bus array. iogen$log(.handle, iogen$_scsipoll, .portnam, 0); IF NOT $assign(devnam = .portnam, chan = channel) THEN RETURN ss$_abort; IF NOT (status = ioc$verifychan(.channel; ccb)) THEN RETURN cleanup(.status, .channel); ucb = .ccb[ccb$l_ucb]; crb = .ucb[ucb$l_crb]; ddb = .ucb[ucb$l_ddb]; spdt = .ucb[ucb$l_pdt]; IF .crb[crb$ps_busarray] EQLA 0 THEN IF NOT (status = kernel_call(allocate_busarray, .crb)) THEN RETURN cleanup(.status, .channel); busarray = .crb[crb$ps_busarray]; ! Now copy the bus array entries into the local work array and extract the ! controller letter for all devices on this bus. CH$MOVE(%ALLOCATION(workarray), busarray[busarray$q_entry_list], workarray); controller = .port[2]; ! Now poll each SCSI ID on the bus if we haven't already found the device ! to be in use. INCR id FROM 0 TO max_scsi_id DO BEGIN BIND device_map = workarray[.id, busarray$q_hw_id] : VECTOR[,BYTE]; BIND dvc_alcls = dvcalloclass[.id]; INCR lun FROM 0 TO max_scsi_lun DO BEGIN DO BEGIN IF (status = $qiow ( chan = .channel, efn = exe$c_sysefn, func = io$_readlblk OR io$m_inherlog, iosb = iosb, p1 = inquirybuf, p2 = %ALLOCATION(inquirybuf), p3 = .id, p4 = .lun ^ 16)) THEN status = .iosb[0]; ! If the port is still offline (some braindead ports like the ! NCR 53C710 on Cobra take forever to initialize), wait for a ! second and try again if there are any retries remaining. I ! intentionally do not reset the retry count once I get through ! to the port since it should remain online once it comes online ! the first time. IF .status EQL ss$_devoffline THEN BEGIN IF (retries = .retries - 1) LEQ 0 THEN RETURN cleanup(ss$_abort, .channel) ELSE BEGIN LOCAL timer_iosb : VECTOR[2,LONG] INITIAL(0) VOLATILE; ! Start a one second timer which will trigger an AST ! that fills in the timer IOSB. If we couldn't start ! the timer, just keep going... IF $setimr( daytim = one_second, efn = exe$c_sysefn, astadr = offline_timer_ast, reqidt = timer_iosb) THEN $synch(efn = exe$c_sysefn, iosb = timer_iosb); END END END WHILE .status EQL ss$_devoffline; SELECTONE .status OF SET [ss$_normal]: IF .iosb[1] GEQ 4 AND .inquirybuf[inquiry$v_qualifier] EQL 0 THEN BEGIN device_map[.lun] = .inquirybuf[inquiry$b_dev_type] + 1; ! If the inquiry buffer shows this is an HSZ device and has the dual path ! bit set, capture the device allocation class value (if any) to use ! later. Then it can be passed to driver load code. Because it is ! from the device itself no cluster synch need be tried at this point. ! ! If we were doing full WWID naming we would need here to arbitrate the name. ! (We would also need to be sure a cluster was up already!!!) ! IF (CH$EQL(3,inquirybuf[inquiry$t_product_id],3,tmpnam,0)) THEN BEGIN dvc_alcls = .inquirybuf[hsz_inq_alcls]; !%IF %SDEBUG ! debug ***** dvc_alcls = 33; !%FI END; IF (.inquirybuf[inquiry$b_dev_type] EQL scsi$k_tape) THEN kernel_call(orbit, spdt[spdt$l_sts], spdt$m_sts_saw_tape); ! X-15 If we're in a cluster and detect another CPU, ! invoke SCSI cluster CPU verification code. ! If we get a bad return, don't configure port! IF (.inquirybuf[inquiry$b_dev_type] EQL scsi$k_cpu) AND .clu$gb_vaxcluster GTR 0 THEN BEGIN local s; kernel_call(orbit, spdt[spdt$l_sts], spdt$m_sts_multihost); s = clu$check_inquiry(inquirybuf, .allocls, portz, .id, .ddb, .spdt); IF NOT .s THEN RETURN cleanup(.s, .channel); END END ELSE device_map[.lun] = 0; [ss$_devalloc]: device_map[.lun] = 0; [ss$_nosuchdev]: EXITLOOP; [ss$_toofewdev]: EXITLOOP; [ss$_insfmem]: RETURN cleanup(ss$_abort, .channel); TES END; if .status EQL ss$_toofewdev THEN EXITLOOP; END; ! If we found a CPU on the bus, see that it obeys class restrictions ! Note: if old naming then this check would be wrong IF .ioc$gl_naming AND .spdt[spdt$v_sts_multihost] THEN BEGIN LOCAL sts; sts = CLU$CHECK_SCSI_CPU(portz, .allocls, .ddb); IF NOT .sts THEN maybe_crash(.sts); IF NOT .sts THEN RETURN cleanup(ss$_abort, .channel); END; ! No longer need a channel to the port so deassign it. $dassgn(chan = .channel); ! Ensure the switching driver is loaded and load it here if it was not. ! (We need to connect units if they are needed later.) This means ! look for SWA0: here. Since it is loaded with /noadapter like ! the SCSI devices we can use some structures that are here already. sw0got = j$ioc$searchdev(sw0dsc,sw01,sw02,sw03); sw0got = .sw0got AND 1; IF (.gotsw EQL 0 OR (.sw0got EQL 0 AND .gotsw LSS 1)) THEN BEGIN ! Load the driver now. savalcls = .allocls; allocls = -1; ! SW has no allocation class driver = %ASCID'SYS$SWDRIVER'; status2 = sys$load_driver( iogen$_connect, ! connect a device sw0dsc, ! device name .driver, ! driver name noadap, ! /NOADAPTER allocls ucb iosb); gotsw = .gotsw + 1; allocls = .savalcls; END; ! We now know what's out there on the bus. Configure those devices that ! VMS supports. INCR id FROM 0 TO max_scsi_id DO BEGIN BIND device_map = workarray[.id, busarray$q_hw_id] : VECTOR[,BYTE]; BIND dvc_alcls = dvcalloclass[.id]; INCR lun FROM 0 TO max_scsi_lun DO BEGIN ! Decide if device is supported and, if so, what the prefix and ! driver name are. device_ok = false; SELECTONE .device_map[.lun] - 1 OF SET [scsi$k_disk, scsi$k_cdrom, scsi$k_optical, scsi$k_worm]: BEGIN prefix = %ASCID'DKA'; driver = %ASCID'SYS$DKDRIVER'; device_ok = true END; [scsi$k_tape]: BEGIN prefix = %ASCID'MKA'; driver = %ASCID'SYS$MKDRIVER'; device_ok = true END; TES; ! Produce device name. IF .device_ok THEN BEGIN devnam[0] = %ALLOCATION(devnambuf); $fao(%ASCID'!AS!UL', devnam, devnam, .prefix, .id*100+.lun); devnambuf[2] = .controller; ! insert real controller letter END; ! Configure the device if that is permissible. IF .device_ok AND iogen$ac_select(.handle, devnam) THEN BEGIN savalcls = .allocls; IF (.dvc_alcls GTR 0) THEN BEGIN allocls = .dvc_alcls; ! Bias the allocation class by 40000000 hex to flag this is a device class allocls = .allocls + 1073741824; END; IF (status = sys$load_driver( iogen$_connect, ! connect a device devnam, ! device name .driver, ! driver name noadap, ! /NOADAPTER allocls ucb iosb)) THEN status = .iosb[0]; allocls = .savalcls; ! Get display name for logging IF .status AND (.dev_ucb LSS 0) THEN BEGIN kernel_call(ioc_std$cvt_devnam, %ALLOCATION(dispnambuf), dispnambuf, -2, .dev_ucb, dispname[0]); dispname[1] = dispnambuf; kernel_call(ioc_std$test_switch,.dev_ucb); END; iogen$log(.handle, .status, dispname, .driver); allocls = .savalcls; END; END; END; ! Now update the bus array with the current information. kernel_call(rewrite_busarray, %ALLOCATION(workarray), workarray, .busarray); RETURN ss$_normal; END; %SBTTL 'CLEANUP, common cleanup routine' ROUTINE cleanup(status, channel) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine provides a simple way to get out of a code flow without ! leaving a dangling channel. ! ! FORMAL PARAMETERS: ! ! status - status to be returned to the caller ! channel - channel to be deassigned ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! status as passed in ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL ccb: REF BLOCK[,BYTE] INITIAL(0), ddb: REF BLOCK[,BYTE] INITIAL(0), ucb: REF BLOCK[,BYTE] INITIAL(0); $dassgn(chan = .channel); RETURN .status END; %SBTTL 'ALLOCATE_BUSARRAY, allocate an initialize a SCSI bus array' ROUTINE allocate_busarray(crb : REF BLOCK[,BYTE]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine allocates and initializes a bus array with enough slots to ! describe a SCSI bus and attaches it to the specified CRB. ! ! FORMAL PARAMETERS: ! ! crb - address of the CRB with which the newly allocated bus array is ! associated ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL ! SS$_INFSMEM ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL busarray : REF BLOCK[,BYTE], requested_size, actual_size, status; ! Determine the appropriate size for the bus array and allocate sufficient ! pool to contain one. requested_size = busarrayheader$k_length + scsi_nodes * busarrayentry$k_length; IF NOT (status = exe$alononpaged(.requested_size; actual_size, busarray)) THEN RETURN .status; ! Now, zero the newly allocated block and fill in the appropriate fields in ! the bus array header. CH$FILL(0, .actual_size, .busarray); busarray[busarray$w_size] = .actual_size; busarray[busarray$b_type] = dyn$c_misc; busarray[busarray$b_subtype] = dyn$c_busarray; busarray[busarray$l_bus_type] = bus$_scsi; busarray[busarray$l_bus_node_cnt] = scsi_nodes; ! Finally, save the bus array address in the CRB. crb[crb$ps_busarray] = .busarray; RETURN ss$_normal END; %SBTTL 'REWRITE_BUSARRAY, copy the working bus array into pool' ROUTINE rewrite_busarray( length, workarray, busarray : REF BLOCK[,BYTE]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine copies the working bus array entry list back into the real one ! in non-paged pool. This routine exists because pool can only be written in ! kernel mode. ! ! FORMAL PARAMETERS: ! ! length - length (in bytes) of the working array ! workarray - address of the working array ! busarray - address of the bus array in pool ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN CH$MOVE(.length, .workarray, busarray[busarray$q_entry_list]); RETURN ss$_normal END; %SBTTL 'OFFLINE_TIMER_AST, port offline timer AST routine' ROUTINE offline_timer_ast(timer_iosb : REF VECTOR[2,LONG]) : NOVALUE = !++ ! FUNCTIONAL DESCRIPTION: ! ! This timer expiration AST routine facilitates resumption of a non-AST-level ! code thread that is awaiting expiration of a $SETIMR timer via the $SYNCH ! system service. ! ! This AST routine receives the timer request ID as its AST parameter which ! it expects to be the address of a conventional IOSB. This routine simply ! fills in the completion status in the IOSB. This allows waiting code ! to use the $SYNCH service to insure that it is resumed after the timer ! interval expires and not because the event flag used with the $SETIMR was ! set by some unrelated asynchronous activity. ! ! This routine is used by IOGEN$SCSI_CONFIG to provide a one second wait ! after the SS$_DEVOFFLINE status is returned on an attempt to poll a SCSI ! port. Attempts to issue a $QIO to the port fail with the SS$_DEVOFFLINE ! status until the port initialization sequence sets the on-line bit in the ! port UCB. ! ! FORMAL PARAMETERS: ! ! timer_iosb - address of an IOSB. The value SS$_NORMAL is written into ! the first longword of this IOSB. ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! None ! ! SIDE EFFECTS: ! ! Any side-effects from writing a completion status into the specified IOSB, ! e.g. resumption of a waiting code thread. !-- BEGIN timer_iosb[0] = ss$_normal; ! Fill in first longword of IOSB END; %SBTTL 'IOGEN$SCSI_POLL, initiate asynchronous polling on a SCSI port' GLOBAL ROUTINE iogen$scsi_poll( handle, portnam : REF BLOCK[,BYTE], crb_return : SIGNED LONG) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine starts an asynchronous thread polling a specified SCSI bus. ! Each polling thread must hold a Concurrent Write (CW) mode lock on the ! SCSIPOLL resource. IOGEN$SCSI_POLL_DONE acquires an exclusive mode lock on ! this resource. These modes are selected so that the lock manager provides ! synchronization at the end of the polling process. Actual polling behavior ! is equivalent to that done in the synchronous poller, IOGEN$SCSI_CONFIG. ! ! FORMAL PARAMETERS: ! ! handle - autoconfigure's magic number ! portnam - address of the SCSI port device name's descriptor ! crb_return - address to return CRB address for SCSI port ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_ABORT ! SS$_INFSMEM ! errors from SCSI_POLL_SETUP ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL sptb : REF BLOCK[,BYTE], actual_size, status; ! First, allocate and initialize the SPTB. Obtain the SCSIPOLL lock. IF NOT (status = exe$alop1imag(sptb$k_length; actual_size, sptb)) THEN RETURN .status; INCR id from 0 to max_scsi_id DO BEGIN dvcalloclass[.id] = 0; END; CH$FILL(0, .actual_size, .sptb); sptb[sptb$l_size] = .actual_size; sptb[sptb$l_retries] = max_retries; IF (sptb[sptb$l_lkid] = scsi_lock(lck$k_cwmode)) EQL 0 THEN RETURN ss$_abort; iogen$log(.handle, iogen$_scsipoll, .portnam, 0, true); IF NOT (status = kernel_call(scsi_poll_setup, .sptb, .portnam, .crb_return)) THEN BEGIN $deq(lkid = .sptb[sptb$l_lkid]); exe$deap1(.sptb, .sptb[sptb$l_size]) END; RETURN .status END; %SBTTL 'IOGEN$SCSI_POLL_DONE, configure devices after asynchronous polling' GLOBAL ROUTINE iogen$scsi_poll_done( handle, port : REF BLOCK[,BYTE]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine picks up after a bus has actually been polled. Connecting ! devices cannot be done in parallel or asynchronously so this routine makes ! no attempt at being multithreaded. ! ! FORMAL PARAMETERS: ! ! handle - autoconfigure's magic number ! port - address of the SCSI port's bus array entry ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL devnambuf : VECTOR[8,BYTE] VOLATILE, devnam : VECTOR[2,LONG,SIGNED] INITIAL(0, devnambuf), dispnambuf : VECTOR[64, BYTE] VOLATILE, dispname : VECTOR[2, LONG] ALIAS, iosb : VECTOR[4,WORD], crb : REF BLOCK[,BYTE], busarrayhead : REF BLOCK[,BYTE] INITIAL(0), busarray : REF BLOCKVECTOR[,busarrayentry$k_length,BYTE], portnambuf : VECTOR[8,BYTE] VOLATILE, controller, status, status2, prefix, driver, device_ok, savalcls, ! Save possible port alloc class here to be overridden $ sw01 : REF BLOCK[,BYTE], sw02 : REF BLOCK[,BYTE], sw03 : REF BLOCK[,BYTE], tmpnam : INITIAL('HSZ'), sw0got : INITIAL(0), sw0nam : VECTOR[64,BYTE] INITIAL('SWA0'), sw0dsc : VECTOR[2,LONG,SIGNED] INITIAL(4,sw0nam), lkid; ! First, acquire the SCSIPOLL lock to insure that all polling is complete. lkid = scsi_lock(lck$k_exmode); ! Now that we have the lock, we have exclusive access to allocls, which ! is in OWN storage. Borrow devnambuf to make up a PKc port name, call ! iogen$class_match to return the allocation class in allocls for later ! input to sys$load_driver via the "noadap" itemlist. portnambuf[0] = %C'P'; portnambuf[1] = %C'K'; portnambuf[2] = .port[busarray$b_ctrlltr]; portnambuf[3] = 0; kernel_call(iogen$class_match, portnambuf, allocls); ! Now, walk through the CRBs busarray and configure anything we actually ! know about. crb = .port[busarray$ps_crb]; IF .crb NEQA 0 THEN busarrayhead = .crb[crb$ps_busarray]; IF .busarrayhead EQLA 0 THEN BEGIN $deq(lkid = .lkid); RETURN ss$_normal END; busarray = busarrayhead[busarray$q_entry_list]; controller = .port[busarray$b_ctrlltr]; ! Load swdriver if we haven't yet got it there sw0got = j$ioc$searchdev(sw0dsc,sw01,sw02,sw03); sw0got = .sw0got AND 1; IF (.sw0got EQL 0 AND .gotsw EQL 0) THEN BEGIN ! Load the driver now. savalcls = .allocls; allocls = -1; ! SW has no allocation class driver = %ASCID'SYS$SWDRIVER'; status2 = sys$load_driver( iogen$_connect, ! connect a device sw0dsc, ! device name .driver, ! driver name noadap, ! /NOADAPTER allocls ucb iosb); gotsw = 1; allocls = .savalcls; END; INCR id FROM 0 TO max_scsi_id DO BEGIN BIND device_map = busarray[.id, busarray$q_hw_id] : VECTOR[,BYTE]; INCR lun FROM 0 TO max_scsi_lun DO BEGIN ! Decide if device is supported and, if so, what the prefix and ! driver name are. device_ok = false; SELECTONE .device_map[.lun] - 1 OF SET [scsi$k_disk, scsi$k_cdrom, scsi$k_optical, scsi$k_worm]: BEGIN prefix = %ASCID'DKA'; driver = %ASCID'SYS$DKDRIVER'; device_ok = true END; [scsi$k_tape]: BEGIN prefix = %ASCID'MKA'; driver = %ASCID'SYS$MKDRIVER'; device_ok = true END; TES; ! Produce device name. devnam[0] = %ALLOCATION(devnambuf); $fao(%ASCID'!AS!UL', devnam, devnam, .prefix, .id*100+.lun); devnambuf[2] = .controller; ! insert real controller letter ! Configure the device if that is permissible. IF .device_ok AND iogen$ac_select(.handle, devnam) THEN BEGIN savalcls = .allocls; IF (.dvcalloclass[.id] NEQ 0) THEN BEGIN allocls = .dvcalloclass[.id]; END; IF (status = sys$load_driver( iogen$_connect, ! connect a device devnam, ! device name .driver, ! driver name noadap, ! /NOADAPTER allocls ucb iosb)) THEN status = .iosb[0]; ! Get display name for logging IF .status AND (.dev_ucb LSS 0) THEN BEGIN kernel_call(ioc_std$cvt_devnam, %ALLOCATION(dispnambuf), dispnambuf, -2, .dev_ucb, dispname[0]); dispname[1] = dispnambuf; END; allocls = .savalcls; iogen$log(.handle, .status, dispname, .driver) END END; END; ! Now release the SCSIPOLL lock. $deq(lkid = .lkid); RETURN ss$_normal END; %SBTTL 'SCSI_LOCK, acquire the SCSIPOLL lock' ROUTINE scsi_lock(lock_mode) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine takes out the SCSIPOLL lock at the specified mode. The lock is ! a sublock of the SYSID lock in order to insure that it's visible only on the ! local node. ! ! FORMAL PARAMETERS: ! ! lock_mode - lock manager mode at which to acquire the SCSIPOLL lock ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! lock id; 0 on error ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL lksb : VECTOR[2,LONG]; ! Take out a lock on the SCSIPOLL resource in the mode specified. Since the ! external entry points to this module are all called from exec mode, use ! the SYSID lock as the parent to lock the resource only on this node. $enqw ( lkmode = .lock_mode, efn = exe$c_sysefn, lksb = lksb, flags = lck$m_nodlckwt, resnam = %ASCID 'SCSIPOLL', parid = .exe$gl_sysid_lock); IF .lksb[0] THEN RETURN .lksb[1] ELSE RETURN 0 END; %SBTTL 'SCSI_POLL_SETUP, kernel mode initialization of SCSI polling' ROUTINE scsi_poll_setup( sptb : REF BLOCK[,BYTE], portnam : REF BLOCK[,BYTE], crb_return : SIGNED LONG) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This is a kernel mode routine that finishes setting up a polling thread and ! fires the first shot on the bus. ! ! FORMAL PARAMETERS: ! ! sptb - the SCSI Poll Thread Block describing this thread's context ! portnam - address of the SCSI port's device name descriptor ! crb_return - address at which to write the CRB address ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_ABORT ! SS$_INSFMEM ! SS$_NORMAL ! ! SIDE EFFECTS: ! ! May allocate a bus array to the port's CRB ! !-- BEGIN LOCAL ccb : REF BLOCK[,BYTE], ucb : REF BLOCK[,BYTE], crb : REF BLOCK[,BYTE], status; ! Begin by assigning a channel to the port device and then tracking down ! the associated CRB and bus array. The channel must be assigned in exec ! mode because of the braindamaged mode checking that IOC$VERIFYCHAN does. IF NOT $assign ( devnam = .portnam, chan = sptb[sptb$l_channel], acmode = psl$c_exec) THEN RETURN ss$_abort; IF NOT (status = ioc$verifychan(.sptb[sptb$l_channel]; ccb)) THEN RETURN cleanup(.status, .sptb[sptb$l_channel]); ucb = .ccb[ccb$l_ucb]; .crb_return = crb = .ucb[ucb$l_crb]; IF .crb[crb$ps_busarray] EQLA 0 THEN IF NOT (status = allocate_busarray(.crb)) THEN RETURN cleanup(.status, .sptb[sptb$l_channel]); sptb[sptb$ps_busarray] = .crb[crb$ps_busarray]; ! Now, kick off the polling by initiating the first $QIO. do_qio(.sptb); RETURN ss$_normal END; %SBTTL 'DO_QIO, queue a polling I/O based on the contents of an SPTB' ROUTINE do_qio(sptb : REF BLOCK[,BYTE]) : NOVALUE = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine queues a polling I/O to the appropriate bus/bus id pair based ! on the contents of the SPTB. It implements once per second retry logic for ! device offline errors from the port. ! ! FORMAL PARAMETERS: ! ! sptb - address of the SCSI Poll Thread Block ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! None ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL inquirybuf : REF VECTOR[110,BYTE], tmpnam : INITIAL('HSZ'), status; ! Attempt to queue the I/O. If it returns a device offline status, ! decrement the retry count and set a timer to try again in 1 second if ! there are any retries remaining. inquirybuf = sptb[sptb$t_inquirybuf]; status = $qio ( chan = .sptb[sptb$l_channel], efn = exe$c_sysefn, func = io$_readlblk OR io$m_inherlog, iosb = sptb[sptb$q_iosb], astadr = poll_io_done, astprm = .sptb, p1 = sptb[sptb$t_inquirybuf], p2 = inquiry$k_length, p3 = .sptb[sptb$l_scsi_id], p4 = .sptb[sptb$l_lun] ^ 16); IF NOT .status THEN IF .status EQL ss$_devoffline THEN IF (sptb[sptb$l_retries] = .sptb[sptb$l_retries] - 1) LEQ 0 THEN BEGIN $dassgn(chan = .sptb[sptb$l_channel]); $deq(lkid = .sptb[sptb$l_lkid]); exe$deap1(.sptb, .sptb[sptb$l_size]) END ELSE $setimr ( daytim = one_second, efn = exe$c_sysefn, astadr = do_qio, reqidt = .sptb) ELSE BEGIN IF (CH$EQL(3,inquirybuf[inquiry$s_product_id],3,tmpnam,0)) THEN BEGIN IF (.inquirybuf[inquiry$t_hszalc+1] NEQ 0) THEN BEGIN allocls = .inquirybuf[inquiry$t_hszalc+1]; allocls = .allocls + 1073741824; dvcalloclass[.sptb[sptb$l_scsi_id]] = .allocls; END; END; $dassgn(chan = .sptb[sptb$l_channel]); $deq(lkid = .sptb[sptb$l_lkid]); exe$deap1(.sptb, .sptb[sptb$l_size]) END END; %SBTTL 'POLL_IO_DONE, a polling $QIO has completed' ROUTINE poll_io_done(sptb : REF BLOCK[,BYTE]) : NOVALUE = !++ ! FUNCTIONAL DESCRIPTION: ! ! This is the completion AST for polling I/Os. It updates the port's bus ! array (hung from the CRB) based on the results of the poll. It then ! increments the SCSI_ID field in the SPTB and triggers the next polling I/O ! if there are more ID's to explore. ! ! If we find a CPU response, go out and verify it follows naming conventions ! we are using. If not, stop the polling before any devices are configured. ! ! FORMAL PARAMETERS: ! ! sptb - address of the SCSI Poll Thread Block ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! None ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL ccb: REF BLOCK[,BYTE], ddb: REF BLOCK[,BYTE], ucb: REF BLOCK[,BYTE], spdt: REF BLOCK[,BYTE], sts; BIND inquirybuf = sptb[sptb$t_inquirybuf] : BLOCK[,BYTE], busarrayhead = sptb[sptb$ps_busarray] : REF BLOCK[,BYTE], busarray = busarrayhead[busarray$q_entry_list] : BLOCKVECTOR[,busarrayentry$k_length,BYTE], device_map = busarray[.sptb[sptb$l_scsi_id], busarray$q_hw_id] : VECTOR[,BYTE]; BUILTIN BARRIER; ! Fetch some pointers ioc$verifychan(.sptb[sptb$l_channel]; ccb); ucb = .ccb[ccb$l_ucb]; ddb = .ucb[ucb$l_ddb]; spdt = .ucb[ucb$l_pdt]; ! If something was found, save the hardware id in the bus array. If the ! device was marked as allocated, set the no_reconnect bit in the bus array. SELECTONE .sptb[sptb$w_status] OF SET [ss$_normal]: BEGIN IF .sptb[sptb$w_retlen] GEQ 4 AND .inquirybuf[inquiry$v_qualifier] EQL 0 THEN device_map[.sptb[sptb$l_lun]] = .inquirybuf[inquiry$b_dev_type] + 1 ELSE device_map[.sptb[sptb$l_lun]] = 0; IF (.inquirybuf[inquiry$b_dev_type] EQL scsi$k_tape) THEN kernel_call(orbit, spdt[spdt$l_sts], spdt$m_sts_saw_tape); ! X-15 If we're in a cluster and detect another CPU, ! invoke SCSI cluster CPU verification code. ! If we get a bad return, don't configure port! IF (.inquirybuf[inquiry$b_dev_type] EQL scsi$k_cpu) AND .clu$gb_vaxcluster GTR 0 THEN BEGIN local s; kernel_call(orbit, spdt[spdt$l_sts], spdt$m_sts_multihost); s = clu$check_inquiry(inquirybuf, .allocls, ddb[ddb$t_name_str], .sptb[sptb$l_scsi_id] , .ddb, .spdt); IF NOT .s THEN ! Bad status == stop polling, don't configure BEGIN sptb[sptb$l_lun] = max_scsi_lun; sptb[sptb$l_scsi_id] = max_scsi_id END END END; [ss$_devalloc]: device_map[.sptb[sptb$l_lun]] = 0; [ss$_toofewdev]: sptb[sptb$l_lun] = max_scsi_lun; [ss$_nosuchdev]: sptb[sptb$l_lun] = max_scsi_lun; TES; IF (sptb[sptb$l_lun] = .sptb[sptb$l_lun] + 1) LEQ max_scsi_lun THEN do_qio(.sptb) ELSE IF (sptb[sptb$l_scsi_id] = .sptb[sptb$l_scsi_id] + 1) LEQ max_scsi_id AND .sptb[sptb$w_status] NEQ ss$_toofewdev THEN BEGIN sptb[sptb$l_lun] = 0; do_qio(.sptb) END ELSE BEGIN BARRIER(); ! If we found a CPU on the bus, see that it obeys class restrictions ! Done only for new naming IF .ioc$gl_naming AND .spdt[spdt$v_sts_multihost] THEN BEGIN LOCAL sts; sts = CLU$CHECK_SCSI_CPU(ddb[ddb$t_name_str], .allocls, .ddb); IF NOT .sts THEN maybe_crash(.sts); END; $dassgn(chan = .sptb[sptb$l_channel]); $deq(lkid = .sptb[sptb$l_lkid]); exe$deap1(.sptb, .sptb[sptb$l_size]) END END; ! of POLL_IO_DONE %SBTTL 'Kernel mode logical OR routine' ROUTINE orbit(longword, bitmask) : NOVALUE = !++ ! FUNCTIONAL DESCRIPTION: ! ! Sets a mask in a longword. ! ! FORMAL PARAMETERS: ! ! longword - Address of a longword ! bitmask - Bits (by value) to set in the longword ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! None ! ! SIDE EFFECTS: ! ! Bits set in longword ! !-- .longword = ..longword OR .bitmask; %SBTTL 'Possibly halt CPU' ROUTINE maybe_crash(status) = !++ ! FUNCTIONAL DESCRIPTION: ! ! Anytime the configuration laws are seen to be violated, a duly authorized ! agent is called into action. For SCSI clusters it is this routine. Here we ! decide if we want to crash the system or not. If the violation occurs early ! on in the life of the system then yes, we want to crash. If not then we let ! the system continue. ! ! FORMAL PARAMETERS: ! ! status - status from [CLUSTER] subsystem call ! ! IMPLICIT INPUT PARAMETERS: ! ! System uptime ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! Irrelevant ! ! SIDE EFFECTS: ! ! System halt and/or reboot ! !-- BEGIN return .status; ! Code yet to be written END; END ! End of module ELUDOM