#pragma module MMOV$ICBM "V1"

/*
 *    MMOV$ICBM -- MultiMedia Configuration Building Module
 *
 *    Copyright © Digital Equipment Corporation, 1994 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.
 * 
 *    -----------------------------------------------------------------------
 *
 * ABSTRACT:
 *
 *  This is the IOGEN Configuration Building Module for the Multimedia Services
 *  for OpenVMS.  The primary function of this module is to load the video 
 *  driver for the AV301/321 FullSupreme Video PCI module, and the J300 Sound
 *  and Motion TurboChannel module; and to load the sound driver for the
 *  Microsoft Windows Sound System ISA module.
 *
 *  This module is a shareable image invoked by IOGEN as part of system 
 *  autoconfiguration.  It initializes an Autoconfiguration Bus Mapping table,
 *  passes it back to IOGEN, and contains routines that load the video driver
 *  for the TurboChannel or the PCI, and the audio driver for the ISA and EISA
 *  buses.
 *
 * ENVIRONMENT:
 *
 *      Merged as a shareable image by IOGEN Autoconfiguration.  Called in
 *      EXEC mode at IPL 0.  This image must be installed as a known image
 *      ($ INSTALL ADD SYS$SHARE:MMOV$ICBM_07.EXE, for example) in order
 *      for AUTOCONFIGURE to activate it.  If it's being activated by a test
 *      image (as in debugging) it need not be installed.
 *
 *      The image name must include the system type and optionally the
 *      CPU type.  These are obtained using the DCL lexical function
 *      F$GETSYI, parameters "SYSTYPE" and "CPUTYPE".  The systype and
 *      CPU type are appended to the end of the image name following an
 *      underscore.  These values must be two hex digits each.  For example,
 *      on a DEC3000 Model 300, systype is 07 and CPU type is 02, so the
 *      ICBM image name on this system is MMOV$ICBM_0702.EXE.  The CPU
 *      type is optional; the ICBM name could be MMOV$ICBM_07.EXE.  The
 *      systype and CPU type are used by AUTOCONFIGURE to form the image
 *      name.  The same image can be used on many different systems, as
 *      long as it is name appropriately.
 *
 * AUTHOR:
 *
 *      The Multimedia Group
 *
 */

/*
 *  Include files
 */
#include <adpdef.h>             /* Adapter control block        */
#include <busarraydef.h>        /* Bus array                    */
#include <crbdef.h>             /* Channel request block        */
#include <ctype.h>              /* Character type macros        */
#include <dcdef.h>              /* Adapter type codes           */
#include <descrip.h>            /* String descriptor definitions*/
#include <hwrpbdef.h>           /* HWRPB field definitions      */
#include <ioc_routines.h>       /* for ioc$node_data            */
#include <iocdef.h>             /* for IOC$K_EISA_IRQ           */
#include <iogendef.h>           /* IOGEN symbols and item codes */
#include <ssdef.h>              /* system error codes           */
#include <starlet.h>            /* system service prototypes    */
#include <string.h>             /* C string definitions         */
#include <stsdef.h>             /* status decoding macros       */
#include <vecdef.h>             /* interrupt vector symbols     */

/*
 *  External routines in IOGEN
 */
int     iogen$ac_select ();
int     iogen$assign_controller ();
int     iogen$log ();
int     sys$load_driver ();

/*
 *  External references to system globals
 *
 *  Define a pointer to the hardware RPB.  This is used to locate the
 *  EISA configuration pointer table, which in turn is used to determine
 *  if a device supported by this ICBM exists on the system.
 *
 *  Use exe$gpl_hwrpb_l, the 32-bit pointer, instead of exe$gpq_hwrpb, a
 *  64-bit pointer.
 */
extern HWRPB *exe$gpl_hwrpb_l;

/*
 *  Other external definitions
 *
 *  These are IOGEN status codes.  They are exported as the addresses of
 *  data cells from the IOGEN shareable image.
 */
extern int IOGEN$_ICBM_OK;        /* Exported by IOGEN shareable image */
extern int IOGEN$_EXCLUDE;        /* Exported by IOGEN shareable image */

/*
 *  Routine prototypes
 */
int             iogen$icbm_init (ABM **abm);
static int      mmov$configure_bus (int handle, ADP *adp);
static int      mmov$configure_bus_eisa (int handle, ADP *adp);
static void     connect_the_driver (int handle, ADP *adp, int mct_index, 
                                    BUSARRAYENTRY *ba);
static int      write_crb_reconnect (CRB *crb, BUSARRAYENTRY *ba);

/*
 *  Data structures used within this module.
 */
/*
 * The form of a SYS$LOAD_DRIVER itemlist entry.
 */
typedef struct itemlist
    {
    short int   buffer_size;
    short int   item_code;
    void        *buffer_address;
    short       *return_length;
    } ITEMLIST;

/*
 *  Autoconfigure bus mapping table.  This table includes an entry
 *  for each bus this ICBM knows how to configure.  Each entry 
 *  in the table consists of an adapter type and an associated
 *  routine in the ICBM that configures devices on this adapter.
 *  Autoconfigure refers to this table as it scans the system ADP
 *  list, to determine if this ICBM needs to be called.
 *
 *  The ABM structure is defined in IOGENDEF.H.
 */
static ABM mmov_abm[] =
    {
    AT$_PCI,  mmov$configure_bus,
    AT$_TC,   mmov$configure_bus,
    AT$_ISA,  mmov$configure_bus,
    AT$_EISA, mmov$configure_bus_eisa,
    0,        0                         /* Zero signals end of table */
    };

/*
 *  Multimedia device configuration table.  This table includes the
 *  8-byte device ID string the ICBM uses to determine if a device
 *  exists on the system, the device name and the driver name that
 *  corresponds to the device, and the adapter type for the bus the
 *  the device is on.
 *
 *  The device ID is assumed to be no larger than 8 bytes, and if it's
 *  smaller than 8 bytes, the significant data is in the lower bytes
 *  and the upper bytes are zero.  This is done to simplify comparisons;
 *  the device ID is treated as a 64-bit integer and one comparison is
 *  done.
 *
 *  The form of the device ID differs for different buses.  Sometimes
 *  it differs for the same device.
 *
 *  For the AV301/AV321, the device ID is the contents of the 32-bit
 *  PCI configuration space PCI ID register.  The upper 32 bits are
 *  zero.
 *
 *  For the AV300-AA, the device ID is the first 8 bytes of the
 *  Turbochannel option ROM.  This is the ASCII string "AV300-AA".
 *
 *  For the Microsoft Sound Board, the device ID varies.  On ISA
 *  machines, the console command "add_sound" sets the device ID to
 *  the string "PCXBJ"; VMS copies only the first four bytes of this
 *  string to the bus array device ID field, resulting in "PCXB".
 *
 *  On EISA machines, the default ECU configuration file identifies
 *  the Microsoft Sound Board as "ISA2000".  The ICBM checks for this
 *  complete string.
 *
 *  "MSB" as a device ID for the Microsoft Sound Board is included in
 *  the configuration table because it was useful in debugging ISA
 *  configurations.
 *
 *  The complete list of devices supported by this ICBM is:
 *
 *      AV301 -- FullVideo Supreme video capture PCI card
 *      AV321 -- FullVideo Supreme JPEG video capture PCI card with
 *                      on-board JPEG compression/decompression
 *      AV300 -- Sound and Motion J300 video capture Turbochannel card
 *                      with on-board JPEG compression/decompression
 *                      (Note that this board also has sound capability,
 *                      which is not supported!)
 *      Microsoft Sound System Sound Card
 *      Oak Technologies Mozart Sound Card
 *      Sound card in Digital PCI workstations that are compatible
 *                      with the Microsoft Sound System.
 *
 *
 *  The configuration table definition is below.  The union for the device
 *  ID is necessary because the version of the DEC C compiler in use doesn't
 *  provide a way to statically initialize a 64-bit field.  Note that the
 *  low-order longword is first, then the high-order longword.
 */
static struct
    {
    union
        {
        struct
            {
            unsigned int id_l;
            unsigned int id_h;
            } id_fields;
        unsigned __int64 id;
        } u_id;
    char *device_name;
    char *driver_name;
    unsigned int adp_type;
    } config_table[] =

    {
   /*
    *  8-byte device ID    device     driver        adapter
    *   low       high      name       name 
    *
    *   AV301 device ID
    */
    0x00131011, 0x00000000, "VI", "MMOV$VIDRIVER",  AT$_PCI,

    /*  AV321 device ID  */
    0x000E1011, 0x00000000, "VI", "MMOV$VIDRIVER",  AT$_PCI,

    /* "AV300-AA" in ASCII  */
    0x30335641, 0x41412D30, "VI", "MMOV$VIDRIVER",  AT$_TC,

    /*  "MSB" in ASCII */
    0x0042534D, 0x00000000, "AU", "MMOV$MSBDRIVER", AT$_ISA,

    /*  "PCXB" in ASCII */
    0x42584350, 0x00000000, "AU", "MMOV$MSBDRIVER", AT$_ISA,

    /*  "ISA2000" in ASCII */
    0x32415349, 0x00303030, "AU", "MMOV$MSBDRIVER", AT$_EISA
    };

static int config_table_size = sizeof(config_table) / sizeof(config_table[0]);

/*
 *  iogen$icbm_init -- IOGEN initialization routine for this ICBM
 *
 *  This routine is called by IOGEN when the ICBM is loaded.  It
 *  returns the address of the Autoconfiguration Bus Mapping Table (ABM).
 *
 *  Inputs:
 *
 *      abm -- address of a longword cell to receive the address
 *              of the ABM.
 *
 *  Outputs:
 *
 *      The address of the MMOV ABM is written to the input address.
 *
 *  Status Returns:
 *
 *      IOGEN$_ICBM_OK
 *
 *      Note:  the "&" operator is used below because this symbol is
 *      exported from the IOGEN shareable image as if it were the
 *      address of a data cell.
 */
int iogen$icbm_init(ABM **abm)
    {
    *abm = mmov_abm;
    return (int) &IOGEN$_ICBM_OK;
    }

/*
 *  mmov$configure_bus -- look for our devices in the ADP's bus array.
 *
 *  This routine scans the bus array pointed to by the input ADP for
 *  devices supported by this ICBM.  If a supported device is found, the
 *  associated driver is loaded and the unit connected.  If no device is
 *  found, exit.
 *
 *  Inputs:
 *
 *      handle -- magic number interpreted by the autoconfigure support
 *            routines
 *      adp -- pointer to the ADP to be checked for supported devices.
 *
 *  Implicit Inputs:
 *
 *      The multimedia device configuration table.
 *
 *  Outputs:
 *
 *      none
 *
 *  Implicit Outputs:
 *
 *      A device driver may be loaded, and a device unit created.
 *
 *  Status Returns:
 *
 *      SS$_NORMAL
 *
 *      The only errors that could be propagated up are those from
 *      the connect_the_driver routine, which does not return any errors.
 *
 */
static int mmov$configure_bus (int handle, ADP *adp)
    {
    int i, j, k;

    union
      {
      char    tempid1[8];
      __int64 tempid2;
      } u_tempid;
       
    char *id_ptr;

    BUSARRAY_HEADER *bah;
    BUSARRAYENTRY  *ba;

    bah = (BUSARRAY_HEADER *) adp->adp$ps_bus_array;
    ba = (BUSARRAYENTRY *) &bah->busarray$q_entry_list;

    /*
     *  Scan through the bus array entry list looking for anything in
     *  the configuration table.  For each one found, load the driver and
     *  connect the unit.  All the bus differences, et al., are handled in
     *  the connect_the_driver routine.
     */
    for (i = 0; i < bah->busarray$l_bus_node_cnt; i++)
        {
        /*
         *  The busarray$q_hw_id field is a 64-bit quantity.  Do a simple
         *  64-bit comparison to look for a match.  If this is an
         *  ISA ID, this was entered by the user at the console using the
         *  isacfg console command.  The user entered an up to 8-byte ASCII
         *  string.  Convert this string to upper case before doing the
         *  comparison, as a convenience to the user.
         */
        if (adp->adp$l_adptype == AT$_ISA)
            {
            id_ptr = (char *) &ba[i].busarray$q_hw_id;
            for (k = 0; k < 8; k++)
                {
                u_tempid.tempid1[k] = toupper(id_ptr[k]);
                }
            }
        else
            {
            u_tempid.tempid2 = ba[i].busarray$q_hw_id;
            }

        /*
         *  Compare the hardware ID in the bus array slot with each entry
         *  in the configuration table.  Load the driver on the first match
         *  in the configuration table.  As one last consistency check, 
         *  make sure the adapter type in the configuration table matches
         *  that in the ADP.
         */
        for (j = 0; j < config_table_size; j++)
            {
            if ( u_tempid.tempid2 == config_table[j].u_id.id )
                {
                if (config_table[j].adp_type == adp->adp$l_adptype)
                    {
                    connect_the_driver(handle, adp, j,
                                       (BUSARRAYENTRY *) &ba[i]);
                    break;
                    }
                }
            }   /* for (j = 0; j < config_table_size; j++) */

        }  /* for (i = 0; i < bah->busarray$l_bus_node_cnt; i++) */

    return (SS$_NORMAL);
    }

/*
 *  mmov$configure_bus_eisa -- look for our devices in the console
 *                              data block
 *
 *      This routine locates our devices in the EISA Configuration
 *      Pointer Table.  This table is pointed to by the HWRPB offset
 *      HWRPB$IL_CDB_OFFSET_L.  This value is added to the base
 *      address of the HWRPB to get the address of the configuration
 *      pointer table.  The configuration pointer table consists
 *      of a collection of 3-longword items.  Each item contains
 *      a 7-byte ASCII identification string, followed by a 32-
 *      bit offset.  The offset is from the base of the Pointer
 *      Table, and points to the compressed configuration data
 *      block for that device.  The configuration data block, and
 *      the information in the pointer table, all come from data
 *      entered by the user using the ECU at the console prompt.
 *
 *      This is the only way to determine if an ISA device supported
 *      by this ICBM is configured on the system.
 *
 *      This routine is different from the mmov$configure_bus
 *      routine, because the location of the data we're looking
 *      for is very different.  The only thing in the bus array
 *      that's valid for an ISA device on an EISA bus is the CSR,
 *      and only the connect_the_driver routine cares about that.
 *
 *  Inputs:
 *
 *      handle -- magic number interpreted by the autoconfigure support
 *                 routines
 *      adp -- pointer to the ADP
 *
 *  Implicit Inputs:
 *
 *      The multimedia device configuration table.
 *      The HWRPB, and the EISA configuration pointer
 *      table.
 *
 *  Outputs:
 *
 *      none
 *
 *  Implicit Outputs:
 *
 *      A device driver may be loaded, and a device unit created.
 *
 *  Status Returns:
 *
 *      SS$_NORMAL -- everything worked.
 *
 *      The only errors that could be propagated up are those from
 *      the connect_the_driver routine, which does not return any errors.
 *
 */
static int mmov$configure_bus_eisa (int handle, ADP *adp)
    {
    int found, i, j, status;

    HWRPB *hwrpb;

    BUSARRAY_HEADER *bah;
    BUSARRAYENTRY  *ba;

/*
 *  Define a structure for EISA configuration pointer table items.  Note
 *  that although the first 8 bytes are ASCII in the table, define it
 *  as an int64 here to make comparisons with the device configuration
 *  table easier.   Use the member_alignment pragma to make sure the
 *  compiler aligns the members on longword (4-byte) boundaries; otherwise
 *  the compiler inserts padding before the offset member that doesn't
 *  match how the data is laid out in memory.
 */
#pragma __member_alignment save
#pragma __nomember_alignment LONGWORD

typedef struct _cpt {  
             __int64 device_id;
             void   *offset;
             } EISA_CPT;

#pragma __member_alignment restore

    EISA_CPT *cpt;

    /*
     *  Get the pointer to the bus array header, and a pointer to
     *  the first entry in the bus array.
     */
    bah = (BUSARRAY_HEADER *) adp->adp$ps_bus_array;
    ba = (BUSARRAYENTRY *) &bah->busarray$q_entry_list;

    /*
     *  Locate the configuration pointer table.  Note that the HWRPB cell
     *  is called "cdb_offset"; this cell points to the CPT, and the
     *  offset member of the CPT points to the CDB for each EISA slot.
     */
    hwrpb = exe$gpl_hwrpb_l;
    cpt = (EISA_CPT *) ( (char *)hwrpb + hwrpb->hwrpb$il_cdb_offset_l);

    /*
     *  Scan through the CPT looking for our device.
     */
    for (i = 0; i < bah->busarray$l_bus_node_cnt; i++)
        {
        /*
         *  Compare the hardware ID in the EISA configuration pointer table
         *  with each entry in the configuration table.  Load the driver on
         *  the first match in the configuration table.  As one last
         *  consistency check, make sure the adapter type in the configuration
         *  table matches that in the ADP.
         */
        for (j = 0; j < config_table_size; j++)
            {
            if ( cpt[i].device_id == config_table[j].u_id.id )
                {
                if (config_table[j].adp_type == adp->adp$l_adptype)
                    {
                    connect_the_driver(handle, adp, j,
                          (BUSARRAYENTRY *) &ba[i]);
                    break;
                    }
                }
            }  /* for (j = 0; j < config_table_size; j++) */

        }  /* for (i = 0; i < bah->busarray$l_bus_node_cnt; i++) */

    return (SS$_NORMAL);
    }

/*
 * connect_the_driver -- loads the driver and connects the unit
 *
 *  This routine loads the device driver specified by the configuration
 *  table entry, based on information in the ADP and the bus array
 *  entry.
 *
 *  Note that if the device is on an EISA bus, the IRQ is nowhere to be
 *  found in the ADP or bus array entry.  Unfortunately, this is needed to
 *  load the driver.  So, the code has to jump through a couple of extra
 *  hoops to find this value, as described below.
 *
 *  Inputs:
 *
 *      handle -- the autoconfigure magic number
 *      adp -- pointer to the ADP
 *      mct_index -- index into the multimedia configuration table
 *      ba -- pointer to the bus array entry for the device
 *
 *  Outputs:
 *
 *      none
 *
 *  Side Effects:
 *
 *      A device driver is loaded, and the required portions of the I/O
 *      database are created.  The driver's controller and unit
 *      initialization routines are executed.
 *
 *  Status Returns:
 *
 *      none.
 *
 */
static void connect_the_driver(int handle, ADP *adp, int mct_index, BUSARRAYENTRY *ba)
    {
    int status, temp_vector;
    char *device_name_pointer;
    short int iosb[4];
    ITEMLIST itemlist[6];               /* for LOAD_DRIVER */
    int arglist[4];                     /* for CMKRNL calls */
    CRB *crb;

    struct dsc$descriptor driver_name_desc;
    struct dsc$descriptor device_name_desc;
    char device_name[4];

    /*
     *  Set up the driver name descriptor.
     */
    driver_name_desc.dsc$w_length = strlen(config_table[mct_index].driver_name);
    driver_name_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    driver_name_desc.dsc$b_class = DSC$K_CLASS_S;
    driver_name_desc.dsc$a_pointer = config_table[mct_index].driver_name;

    /*
     *  Construct the device name.  The first two letters are the device
     *  name from the configuration table, the next is the controller 
     *  letter, which come from either the bus array entry or from an 
     *  IOGEN routine, followed by an ASCII "0".
     */
     /*
      *  First fill in the device name descriptor.
      */
    device_name_desc.dsc$w_length = 4;
    device_name_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    device_name_desc.dsc$b_class = DSC$K_CLASS_S;
    device_name_desc.dsc$a_pointer = &device_name[0];

    /*
     *  Now, construct the device name proper, leaving out the 
     *  controller letter.
     */
    device_name_pointer = config_table[mct_index].device_name;
    device_name[0] = device_name_pointer[0];
    device_name[1] = device_name_pointer[1];
    device_name[3] = '0';

    /*
     *  If the system has assigned a controller letter already, use it.
     *  Otherwise, ask IOGEN to assign one.  iogen$assign_controller returns
     *  the assigned controller letter in busarray$b_ctrlltr.
     */
    if (ba->busarray$b_ctrlltr == 0)
        {
        status = iogen$assign_controller (handle, device_name, ba);
        if (!$VMS_STATUS_SUCCESS(status))
            {
            device_name[2] = '?';
            iogen$log(handle, status, &device_name_desc, &driver_name_desc);
            return;
            }
       }
    device_name[2] = ba->busarray$b_ctrlltr;

    /*
     *  Check whether the device is being implicitly or explicitly excluded.
     *  AUTOCONFIGURE will return either SS$_NORMAL, to indicate the device
     *  should be configured; or IOGEN$_EXCLUDE, which means the device
     *  is implicitly or explicitly excluded.  In the latter case, simply
     *  return.
     */
    status = iogen$ac_select(handle, &device_name_desc);
    if (status == (int) &IOGEN$_EXCLUDE)  return;

    /*
     *  Check to see this device is already configured.  If so, don't
     *  do it again -- multiple connects aren't allowed.  If the no_reconnect
     *  bit in the bus array entry flags is set, the device has already been
     *  connected.
     */
    if (ba->busarray$v_no_reconnect)  return;

    /*
     *  Now fill in the item list for $load_driver, specifying the adapter,
     *  the CSR, the interrupt vector, the node number, and a pointer to
     *  receive the address of the created CRB.  Like all standard VMS
     *  item lists, it is terminated by a zero.
     */

    /*  The adapter   */
    itemlist[0].buffer_size = sizeof(adp->adp$l_tr);
    itemlist[0].item_code = IOGEN$_ADAPTER;
    itemlist[0].buffer_address = &adp->adp$l_tr;
    itemlist[0].return_length = 0;

    /*  The CSR address */
    itemlist[1].buffer_size = sizeof(ba->busarray$q_csr);
    itemlist[1].item_code = IOGEN$_CSR;
    itemlist[1].buffer_address = &ba->busarray$q_csr;
    itemlist[1].return_length = 0;

    /*
     *  The interrupt vector.  This is located in a different place for
     *  each bus, and is especially complicated for EISA.
     */
    itemlist[2].buffer_size = sizeof(ba->busarray$l_bus_specific_l);
    itemlist[2].item_code = IOGEN$_VECTOR;
    itemlist[2].return_length = 0;

    switch (adp->adp$l_adptype)
        {
        case AT$_PCI:
            /*
             *  If this is the PCI device, the interrupt vector comes
             *  from the low longword of the bus_specific field of the
             *  bus array entry.
             */
            itemlist[2].buffer_address = &ba->busarray$l_bus_specific_l;
            break;

        case AT$_TC:
            /*
             *  If it's Turbochannel, the vector comes from the
             *  autoconfig cell.
             */
            itemlist[2].buffer_address = &ba->busarray$l_autoconfig;
            break;

        case AT$_ISA:
            /*
             *  If this is ISA, the vector is the bus_specific_l
             *  cell times 4.
             */
            temp_vector = ba->busarray$l_bus_specific_l * 4;
            itemlist[2].buffer_address = &temp_vector;
            break;

        case AT$_EISA:
            /*
             *  If this is EISA, ask the system, via a call
             *  to IOC$NODE_DATA, which has to be called in kernel mode.
             *  This routine requires a CRB, which, unfortunately, isn't
             *  allocated 'til after the SYS$LOAD_DRIVER call.  So,
             *  allocate a private, local CRB, and fill in the values 
             *  needed by IOC$NODE_DATA, namely CRB$L_NODE (which comes
             *  from BUSARRAY$L_NODE_NUMBER), and the address of the ADP.
             *  Don't worry about the rest of the local CRB contents, since
             *  the code path thru IOC$NODE_DATA doesn't touch anything else.
             *
             *  When IOC$NODE_DATA returns, multiply the IRQ times 4.
             */
            {
            CRB local_crb;
            VEC *vec;

            local_crb.crb$l_node = ba->busarray$l_node_number;
            vec = (VEC *) &local_crb.crb$l_intd;
            vec->vec$ps_adp = adp;

            arglist[0] = 3;
            arglist[1] = (int) &local_crb;
            arglist[2] = IOC$K_EISA_IRQ;
            arglist[3] = (int) &temp_vector;
            status = sys$cmkrnl (ioc$node_data, arglist);
            temp_vector *= 4;
            itemlist[2].buffer_address = &temp_vector;
            break;
            }

        default:
            /*
             *  If control gets to here, it's an adapter type this
             *  routine doesn't know how to handle.  Simply return.
             */
            return;
            break;
        }  /*  switch (adp->adp$l_adptype) */

    /*  The node number */
    itemlist[3].buffer_size = sizeof(ba->busarray$l_node_number);
    itemlist[3].item_code = IOGEN$_NODE;
    itemlist[3].buffer_address = &ba->busarray$l_node_number;
    itemlist[3].return_length = 0;

    /*  A pointer to the CRB to be allocated  */
    itemlist[4].buffer_size = sizeof(crb);
    itemlist[4].item_code = IOGEN$_CRB;
    itemlist[4].buffer_address = &crb;
    itemlist[4].return_length = 0;

    /*  Terminate the list */
    itemlist[5].buffer_size = 0;
    itemlist[5].item_code = 0;
    itemlist[5].buffer_address = 0;
    itemlist[5].return_length = 0;

    status = sys$load_driver(IOGEN$_CONNECT, &device_name_desc,
                             &driver_name_desc, itemlist, iosb);

    if ($VMS_STATUS_SUCCESS(status)) status = (int) iosb[0];

    /*
     *  Log errors here through IOGEN's logging function.  This
     *  information is output when the /LOG qualifier is used on
     *  the AUTOCONFIGURE command.
     */
    iogen$log(handle, status, &device_name_desc, &driver_name_desc);

    /*
     * sys$load_driver can return two errors which can be ignored:
     * SS$_DEVEXISTS and SS$_DEVOFFLINE.  DEVEXISTS can never happen
     * if the no_reconnect bit is used. Logic above uses this bit to
     * prevent attempting to load the driver twice.  DEVOFFLINE means
     * that for some reason, the device didn't come on line by the 
     * time sys$load_driver completed its work.  This could be because
     * some units take a long time to come on line.  Or it could be
     * because the driver detected some error during controller or
     * unit initialization -- the multimedia video and audio drivers
     * work this way.  Either way, these are not errors as far as
     * the ICBM is concerned, so they are ignored.
     *
     * On any other error, exit here without changing the state of the
     * no_reconnect bit.
     */
    if ( (status == SS$_DEVEXISTS) || (status == SS$_DEVOFFLINE))
        status = SS$_NORMAL;
    if (!$VMS_STATUS_SUCCESS(status)) return;

    /*
     * Now that the driver is loaded and the unit connected, save the
     * address of the CRB in the bus array entry.  Also, set the
     * no_reconnect flag to indicate the unit cannot be re-connected.
     * These operations must be done in kernel mode, because the memory
     * containing the bus array entry is not writeable in exec mode.
     *
     * Normally, these two functions would be perfomed by a callback to
     * IOGEN.  However, the IOGEN shareable image does not export these
     * functions.  So, there is one little kernel-mode routine to do
     * the work.  Finally, note that the status from the sys$cmkrnl call
     * is ignored.  It's there as a debugging aid.
     */
    arglist[0] = 2;
    arglist[1] = (int) crb;
    arglist[2] = (int) ba;
    status = sys$cmkrnl (write_crb_reconnect, arglist);

    return;
    }

/*
 * write_crb_reconnect -- write the CRB address and set the no_reconnect bit
 *
 *  This routine duplicates the actions of the kernel mode IOGEN routines
 *  IOGEN$WRITE_IOGEN_CRB and IOGEN$SET_NORECONNECT, which are not exported
 *  by the IOGEN shareable image.  The address of the specified CRB is written
 *  into busarray$ps_crb, and the no_reconnect bit is set.
 *
 *  This routine must be called in kernel mode, because the memory containing
 *  the bus array entry is not writeable in exec mode.
 *
 *  Inputs:
 *
 *      crb -- address of the CRB to write into the bus array entry
 *      ba  -- pointer to the bus array entry
 *
 *  Outputs:
 *
 *      none
 *
 *  Side Effects:
 *
 *      none
 *
 *  Completion Codes:
 *
 *      SS$_NORMAL
 *
 */
static int write_crb_reconnect (CRB *crb, BUSARRAYENTRY *ba)
    {
    ba->busarray$ps_crb = crb;
    ba->busarray$v_no_reconnect = 1;
    return SS$_NORMAL;
    }
