%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[8,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.

    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)
                            AND (.inquirybuf[4] GEQ 98))
                         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 (.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;

    ! 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;

    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[8,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)
                            AND (.inquirybuf[4] GEQ 98))
                 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
