/**
 * @file ix1a.c
 *
 * IX1a - The IX1 active boards CAPI manager driver module for *BSD.
 *
 * Copyright: 2004 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: ix1a.c,v 1.1.2.1 2006/02/28 21:35:20 thomas Exp $
 * $Project     CAPI for BSD $
 * $Target      ix1a - CAPI manager driver for IX1 active ISDN controllers $
 * @date        11.07.2004
 * @author      "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

/** @mainpage IX1 active ISDN boards (now Digi Datafire)
 *
 * This driver module handles the following boards with their original names
 * from ITK Telekommunikation AG:
 *
 * - IX1 Basic ISA
 * - IX1 Basic PCI
 * - IX1 Octo
 * - IX1 Multimodem
 * - IX1 Primary PCI
 *
 * These boards are also known under their respective "new" names fro Digi
 * International, Inc. (took over ITK):
 *
 * - Digi Datafire Basic ISA
 * - Digi Datafire Basic PCI
 * - Digi Datafire Octo
 * - Digi Datafire Multimodem
 * - Digi Datafire Primary PCI
 *
 * All these boards are active ISDN boards that directly accept and deliver CAPI
 * messages. So all features of the boards can be used without implementing any
 * protocol within the driver. The driver works together with the tool ix1actl.
 * It is needed to download the boostrap and the firmware onto the board. In
 * addition the board must get its line configuration. This data is stored in a
 * configuration file that holds data records for all installed boards. Ix1actl
 * reads this file and provides every board with the data needed to operate it.
 *
 * Before a board may get its firmware it must be initialised. This is done by
 * the driver in the attach method. For ISA boards there must be some entries in
 * /boot/device.hints for the probe/attach procedure of the BSD kernel to
 * function. Each of the supported ISA board types an i/o port and an irq number
 * must be configured. But as the board types cannot be distinguished before
 * firmware is loaded, one must also provide some flags values. All ISA boards
 * have two (Basic) or three (Multimodem) jumpers. They form a binary value
 * between 0 and 3 or 7. The least significant nibble of the flags value for an
 * ISA board is reserved for the jumper setting of the board. The next nibble is
 * used to specify if the board is a simple Basic board, if it is extended to be
 * an Octo board (a Basic board will become an Octo only through the different
 * firmware) or if it is a Multimodem board.
 *
 * So a Basic ISA board is declared through the following entries in
 * device.hints (both jumpers are assumed to be set):
 *
 * @code
 * hint.ix1a.0.at="isa"
 * hint.ix1a.0.port="0x380"
 * hint.ix1a.0.irq="5"
 * hint.ix1a.0.flags="0x03"
 * @endcode
 *
 * A Basic ISA with an attached Octo extension board (this combination is
 * shortly called an Octo) is defined as follows (both jumpers are assumed to be
 * set):
 *
 * @code
 * hint.ix1a.0.at="isa"
 * hint.ix1a.0.port="0x380"
 * hint.ix1a.0.irq="5"
 * hint.ix1a.0.flags="0x13"
 * @endcode
 *
 * Now finally a Multimodem is declared like this:
 *
 * @code
 * hint.ix1a.0.at="isa"
 * hint.ix1a.0.port="0x380"
 * hint.ix1a.0.irq="5"
 * hint.ix1a.0.flags="0x27"
 * @endcode
 *
 * For the PCI boards certainly nothing needs to be configured. Everything is
 * done through the automatic PCI configuration mechanism.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/mbuf.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <sys/bus.h>
#include <machine/resource.h>
#include <sys/sysctl.h>
#include <isa/isavar.h>
#include <dev/pci/pcivar.h>

/* import includes */
#include <c4b/kcapimgr/capi_drv.h>

#define __IX1A__

/* local includes */
#include <c4b/ix1a/ix1a_var.h>
#include <c4b/ix1a/ix1a_misc.h>
#include <c4b/ix1a/ix1a_isa.h>
#include <c4b/ix1a/ix1a_bpci.h>
#include <c4b/ix1a/ix1a_ppci.h>
#include <c4b/ix1a/ix1a_shm.h>





/* === Private definitions =============================================== */





/* --- support for sysctl variables --- */

/** The node for the ix1a device driver under "capi". */
SYSCTL_NODE (_capi, OID_AUTO, ix1a, CTLFLAG_RW, 0,
             "IX1 active ISDN controller driver");

/** String needed for the version variable of the driver. */
static char g_szVersion [16] = "";

/** The variable for the version number of the driver. */
SYSCTL_STRING (_capi_ix1a, OID_AUTO, version, CTLFLAG_RD,
               g_szVersion, 0, "Version number");

/** The variable for the logging level. */
SYSCTL_INT (_capi_ix1a, OID_AUTO, loglevel, CTLFLAG_RW,
            &e_iIx1aLogLevel, 0, "Logging level");



/* --- support for tunable 'constants' in the kernel environment --- */

TUNABLE_INT (IX1A_TUNABLE_LOGLEVEL, &e_iIx1aLogLevel);



/* --- Identification of the ISA board types through the device flags --- */

/** @name Identification of the ISA board types through the device flags. */
/** @{ */

/** Bit mask to extract the jumper setting from the device flags. */
#define IX1A_DEVFLAGS_JUMPER_MASK       0x0007

/** Bit mask to extract the board type from the device flags. */
#define IX1A_DEVFLAGS_TYPE_MASK         0x00F0

/** Shift value to normalize the board type from the device flags. */
#define IX1A_DEVFLAGS_TYPE_SHIFT        4

/** Board type Basic within the device flags (normalised). */
#define IX1A_DEVFLAGS_TYPE_BASIC        0

/** Board type Octo within the device flags (normalised). */
#define IX1A_DEVFLAGS_TYPE_OCTO         1

/** Borad type Multimodem within the device flags (normalised). */
#define IX1A_DEVFLAGS_TYPE_MULTIMODEM   2

/** @} */



/**
 * The driver specific CAPI controller number of the last registered
 * controller.
 */
static size_t g_nLastDriverCtlrNum = 0;

/**
 * Mapping from unique CAPI manager controller number to the softc structure
 * of the corresponding board.
 *
 * @note CAPI defines a maximum of 127 controller numbers from 1 to 127 (0 is
 *       not used).
 */
static Ix1aSc_t *g_apMapUniqueCtlrToSc [128] = { NULL };

/**
 * Mapping from driver specific CAPI controller number to the softc structure
 * of the corresponding board.
 *
 * @note CAPI defines a maximum of 127 controller numbers from 1 to 127 (0 is
 *       not used).
 */
static Ix1aSc_t *g_apMapDrvCtlrToSc [128] = { NULL };





/* === Definitions for public declarations =============================== */





/**
 * Mapping from driver specific CAPI controller number to the port data.
 *
 * @note CAPI defines a maximum of 127 controller numbers from 1 to 127 (0 is
 *       not used).
 */
Ix1aPortData_t *e_apMapDrvCtlrToPortData [128] = { NULL };



/* --- the logging functionality --- */

#ifdef IX1A_LOG_LEVEL
int e_iIx1aLogLevel = IX1A_LOG_LEVEL;
#else /* IX1A_LOG_LEVEL */
int e_iIx1aLogLevel = LOG_ERROR;
#endif /* IX1A_LOG_LEVEL */





/* === Prototypes for private functions ================================== */





/* --- driver functions for the isa driver --- */

/**
 * Probe for installed isa controller.
 *
 * This function is called to check if the (ISA) device specified does really
 * exist and is working (at least as far it is possible without any firmware).
 *
 * The three ISA board types (let's count the Octo as a separate board type)
 * are completely passive after boot or after a hardware reset. Some i/o
 * operations must be performed to tell them their i/o base address and to make
 * them react on further i/o operations. Strictly speaking this should be done
 * in the attach routing. But otherwise we cannot detect if there is really a
 * board for the device specification given as a function argument.
 *
 * @param dev                    I: The device entry for the board to probe.
 *
 * @retval 0                     The board device was found and has its i/o
 *                               address assigned.
 * @retval Else                  The board was not found or is not working. The
 *                               result value is an errno value.
 */
static int ix1a_isa_probe
   (device_t dev);

/**
 * Attach installed controller to isa bus.
 *
 * This function is called to prepare a device for later use. After successful
 * execution of this function, the device shall be ready for operation.
 *
 * For the ix1a ISA device driver this means to setup the data maintained for
 * the board, to prepare the board for the firmware download and to register the
 * board (i.e. all its controllers) at the CAPI manager.
 *
 * @param dev                    I: The device entry for the board to attach.
 *
 * @retval 0                     The board was successfully attached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the attach operation.
 */
static int ix1a_isa_attach
   (device_t dev);

/**
 * Detach installed controller from isa bus.
 *
 * The detach function is called when the device driver module is unloaded from
 * memory. It must turn the device specified into disabled state, because the
 * device will be destroyed and all related memory removed after this call.
 *
 * For the ix1a driver this requires to unregister the board (i.e. the CAPI
 * controllers related to the board) at the CAPI manager. Last the board must
 * be disabled and all its resources are released.
 *
 * @param dev                    I: The device entry for the board to detach.
 *
 * @retval 0                     The board was successfully detached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the detach operation.
 */
static int ix1a_isa_detach
   (device_t dev);

/**
 * Shut down installed controller.
 *
 * This function is called on system shutdown. The ix1a device driver will
 * disable the specified device. This will also terminate all currently running
 * connections.
 *
 * @param dev                    I: The device entry for the board to shutdown.
 *
 * @retval 0                     The board was successfully shut down.
 * @retval Else                  An errno value representing the error occurred
 *                               during the shutdown operation.
 */
static int ix1a_isa_shutdown
   (device_t dev);



/* --- driver functions for the pci driver --- */

/**
 * Probe for installed pci controller.
 *
 * This function is called to check if the (PCI) device specified can be driven
 * by this device driver. This is accomplished by checking the PCI vendor and
 * sub-vendor id of the device. If these ids match any of the values for the
 * active IX1 boards handled by this driver, the probe operation will be
 * successful.
 *
 * @param dev                    I: The device entry for the board to probe.
 *
 * @retval 0                     The board device was found.
 * @retval Else                  The device specified does not represent an
 *                               active IX1 board driven by this device driver.
 *                               The result value is an errno value.
 */
static int ix1a_pci_probe
   (device_t dev);

/**
 * Attach installed controller to PCI bus.
 *
 * This function is called to prepare a device for later use. After successful
 * execution of this function, the device shall be ready for operation.
 *
 * For the ix1a PCI device driver this means to setup the data maintained for
 * the board, to prepare the board for the firmware download and to register
 * the board (i.e. all its controllers) at the CAPI manager.
 *
 * Both supported board types need some operations to be prepared for the later
 * firmware download. These tasks are performed by specialised functions. After
 * successful board activation its ports are registered at the CAPI manager as
 * CAPI controllers.
 *
 * @param dev                    I: The device entry for the board to attach.
 *
 * @retval 0                     The board was attached successfully.
 * @retval Else                  An errno value representing the error occurred
 *                               during the attach operation.
 */
static int ix1a_pci_attach
   (device_t dev);

/**
 * Detach installed controller from PCI bus.
 *
 * The detach function is called when the device driver module is unloaded from
 * memory. It must turn the device specified into disabled state, because the
 * device will be destroyed and all related memory removed after this call.
 *
 * For the ix1a driver this requires to unregister the board (i.e. the CAPI
 * controllers related to the board) at the CAPI manager. Last the board must
 * be disabled and all its resources released.
 *
 * @param dev                    I: The device entry for the board to detach.
 *
 * @retval 0                     The board was successfully detached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the detach operation.
 */
static int ix1a_pci_detach
   (device_t dev);

/**
 * Shut down installed PCI controller.
 *
 * This function is called on system shutdown. The avmaic device driver will
 * perform a reset operation on the specified device to leave it in a disabled
 * state. This will also terminate all currently running connections.
 *
 * @param dev                    I: The device entry for the board to shutdown.
 *
 * @retval 0                     The board was successfully shut down.
 * @retval Else                  An errno value representing the error occurred
 *                               during the shutdown operation.
 */
static int ix1a_pci_shutdown
   (device_t dev);



/* --- prototypes for the functions to be registered at the CAPI manager --- */

static CAPIDrv_RegisterFct_t   ix1a_capidrv_register;
static CAPIDrv_ReleaseFct_t    ix1a_capidrv_release;
static CAPIDrv_PutMessageFct_t ix1a_capidrv_put_message;
static CAPIDrv_ResetCtlrFct_t  ix1a_capidrv_reset_ctlr;



/* --- several helper functions --- */

/**
 * Register a board at the CAPI manager.
 *
 * This function is called after the hardware related operations of the attach
 * function for a board are successfully completed. For every ISDN port of the
 * board specified by dev and pSc the registration structure is filled an the
 * port is registered as a CAPI controller at the CAPI manager.
 *
 * @pre This function must be called with controller access obtained through
 *      ix1amisc_get_ctlr_access().
 *
 * @param dev                    I: The device entry for the board to register.
 * @param pSc                    I/O: The softc structure of the board to
 *                                  register.
 *
 * @retval 0                     The register operation was successful.
 * @retval Else                  An errno value representing the error occurred
 *                               during the register operation. In most cases
 *                               this will be a CAPI result value.
 */
static int ix1a_register_at_capimgr
   (device_t  dev, 
    Ix1aSc_t *pSc);

/**
 * Unregister all controllers at the CAPI manager.
 *
 * This function is called when the ix1a kernel module is unloaded from memory.
 * It must perform a controller release operation for all CAPI controllers
 * maintained by this driver. The result will be to release all memory related
 * to these CAPI controllers and the termination of all still active
 * connections for each controller.
 *
 * @param None.
 *
 * @return Nothing.
 */
static void ix1a_unregister_all_controllers (void);

/**
 * Perform the attach operations for a Basic PCI board.
 *
 * @param dev                   I: The device for the board to attach.
 *
 * @retval 0                    The attach operation was successful, the board
 *                              is operational and awaiting its firmware.
 * @retval Else                 The attach operation failed, the board is not
 *                              operational.
 */
static int ix1a_basic_pci_attach
   (device_t dev);

/**
 * Perform the attach operations for a Basic PCI board.
 *
 * @param dev                   I: The device for the board to attach.
 *
 * @retval 0                    The attach operation was successful, the board
 *                              is operational and awaiting its firmware.
 * @retval Else                 The attach operation failed, the board is not
 *                              operational.
 */
static int ix1a_primary_pci_attach
   (device_t dev);

/**
 * Disable a board.
 *
 * This function is called to set the board specified to disabled state. It
 * will not do anything until it is re-enabled by a download operation. All
 * allocated resources remain active. The tasks necessary are determined from
 * the respective board type.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 disable.
 *
 * @return Nothing.
 */
static void ix1a_disable_board
   (Ix1aSc_t *pSc);

/**
 * Perform general detach operations common to all board types.
 *
 * The goal for a call to this function is to release all resources allocated
 * for the board specified. It is expected that the softc structure was
 * correctly initialised before.
 *
 * @param dev                   I: The device entry for the detach operation.
 *
 * @return Nothing.
 */
static void ix1a_common_detach
   (device_t dev);

/**
 * Initialise the device's softc structure for board operation.
 *
 * Within this function all fields of the board's softc structure are
 * initialised for operation.
 *
 * @pre The board type must already be set 
 *
 * @param dev                   I: The device the softc structure belongs to.
 * @param pSc                   I/O: The softc structure to initialise.
 * @param cardType              I: The card type to fill the softc structure
 *                                 for.
 * @param iJumperSetting        I: The value for the jumper setting of ISA
 *                                 boards (values from 0 to 7). For PCI boards
 *                                 this parameter is not used.
 *
 * @retval 0                    Initialisation successful.
 * @retval Else                 Error occurred, softc structure not
 *                              initialised.
 */
static int ix1a_init_softc
   (device_t        dev,
    Ix1aSc_t       *pSc,
    Ix1aCardType_t  cardType,
    int             iJumperSetting);



/* --- Handling module load and unload operations --- */

/**
 * Handling of module load/unload events.
 *
 * This the handler function of all kernel module related events. On module
 * load some internal data structures are initialized. All board device
 * releated startup operations are triggered by other mechanisms.
 *
 * On module unload all maintained CAPI controllers will be unregistered at the
 * CAPI manager and rendered to disabled state.
 *
 * @param mod                    I: The handle of _this_ module.
 * @param iType                  I: The type of module event that occurred (in
 *                                  fact of type modeventtype_t).
 * @param pUnused                I: Unused address of module specific data.
 */
static int ix1a_modevent
   (module_t  mod,
    int       iType,
    void     *pUnused);
    




/* --- definitions to register the driver with the kernel --- */

/** Flag for received first MOD_LOAD. */
static int g_fLoadEventReceived = 0;

/** The array of driver methods for the ISA device driver. */
static device_method_t g_ix1a_isa_methods [] =
   {
      DEVMETHOD (device_probe,    ix1a_isa_probe),
      DEVMETHOD (device_attach,   ix1a_isa_attach),
      DEVMETHOD (device_detach,   ix1a_isa_detach),
      DEVMETHOD (device_shutdown, ix1a_isa_shutdown),
      /*DEVMETHOD (device_suspend,  ix1a_isa_suspend),*/
      /*DEVMETHOD (device_resume,   ix1a_isa_resume),*/
      { NULL, NULL }
   };

/** The ISA device driver. */
static driver_t g_ix1a_isa_driver =
   {
      IX1A_DEVICE_CLASS_NAME, g_ix1a_isa_methods, sizeof (Ix1aSc_t)
   };

/** ISA PNP ids for the devices, there are nont. */
static struct isa_pnp_id g_ix1a_pnp_ids [] =
   {
      { 0x0, NULL }
   };

/** The array of driver methods for the PCI device driver. */
static device_method_t g_ix1a_pci_methods [] =
   {
      /* device interface */
      DEVMETHOD (device_probe,     ix1a_pci_probe),
      DEVMETHOD (device_attach,    ix1a_pci_attach),
      DEVMETHOD (device_detach,    ix1a_pci_detach),
      DEVMETHOD (device_shutdown,  ix1a_pci_shutdown),
      /*DEVMETHOD (device_suspend,  ix1a_pci_suspend),*/
      /*DEVMETHOD (device_resume,   ix1a_pci_resume),*/
      { NULL, NULL }
   };

/** The PCI device driver. */
static driver_t g_ix1a_pci_driver =
   {
      IX1A_DEVICE_CLASS_NAME, g_ix1a_pci_methods, sizeof (Ix1aSc_t)
   };

/**
 * The device class for the ix1a device driver.
 *
 * It is shared by the ISA and the PCI driver. So the unit numbers are unique
 * for all controllers, ISA or PCI.
 */
static devclass_t g_ix1a_devclass;

/** The module definition for the ISA device driver. */
DRIVER_MODULE (ix1a, isa, g_ix1a_isa_driver, g_ix1a_devclass,
               ix1a_modevent, NULL);

/** The module definition for the PCI device driver. */
DRIVER_MODULE (ix1a, pci, g_ix1a_pci_driver, g_ix1a_devclass,
               ix1a_modevent, NULL);

/** The version definition for the ix1a kernel module. */
MODULE_VERSION (ix1a,
                (IX1A_DRIVER_VERSION_MAJOR << 16) |
                   IX1A_DRIVER_VERSION_MINOR);

/**
 * Dependency definition for the ix1a kernel module to the kernel CAPI manager.
 *
 * @note The CAPI manager interface for controller drivers is not expected to
 *       change within a major version. So any minor version will do, provided
 *       the major version is as expected.
 */
MODULE_DEPEND (ix1a, kcapimgr,
               (CAPIMAN_VERSION_MAJOR << 16) | CAPIMAN_VERSION_MINOR,
               (CAPIMAN_VERSION_MAJOR << 16) | CAPIMAN_VERSION_MINOR,
               (CAPIMAN_VERSION_MAJOR << 16) | 0x0000FFFF);





/* === Implementation of public functions ================================ */





/* === Implementation of private functions =============================== */





/**
 * Probe for installed isa controller.
 *
 * This function is called to check if the (ISA) device specified does really
 * exist and is working (at least as far it is possible without any firmware).
 *
 * The three ISA board types (let's count the Octo as a separate board type)
 * are completely passive after boot or after a hardware reset. Some i/o
 * operations must be performed to tell them their i/o base address and to make
 * them react on further i/o operations. Strictly speaking this should be done
 * in the attach routing. But otherwise we cannot detect if there is really a
 * board for the device specification given as a function argument.
 *
 * @param dev                    I: The device entry for the board to probe.
 *
 * @retval 0                     The board device was found and has its i/o
 *                               address assigned.
 * @retval Else                  The board was not found or is not working. The
 *                               result value is an errno value.
 */

static int ix1a_isa_probe
   (device_t dev)
{
   Ix1aSc_t      *pSc;
   unsigned long  ulFlags;
   int            iJumperSetting;
   int            iBoardType;
   int            iRes;

   /* IX1 active controllers are pure isa or pci boards, no isa-PnP devices */
   iRes = ISA_PNP_PROBE (device_get_parent (dev), dev, g_ix1a_pnp_ids);
   if (iRes == ENXIO)
   {
      return (ENXIO);
   }
   
   /* determine which card type should be probed and the boards jumper setting;
    * the lower four bits represent the jumper setting (values 0-7), the next
    * four upper bits denote the board type
    */
   ulFlags = (unsigned long) device_get_flags (dev);
   iJumperSetting = (int) (ulFlags & IX1A_DEVFLAGS_JUMPER_MASK);
   iBoardType = (int) ((ulFlags & IX1A_DEVFLAGS_TYPE_MASK) >>
                       IX1A_DEVFLAGS_TYPE_SHIFT);
   DBG (LOG_DEBUG, device_get_unit (dev),
        "Try to detect ISA board for flags 0x%04lX",
        ulFlags);
                       
   /* acquire the needed resources for the board check */
   pSc = device_get_softc (dev);
   bzero (pSc, sizeof (*pSc));
   pSc->iUnit = device_get_unit (dev);
   pSc->iJumperSetting = iJumperSetting;
   iRes = ix1aisa_init_resinfo (dev, &(pSc->resInfo));
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* now finally check for an existing and working board, but do only basic
    * tests
    */
   switch (iBoardType)
   {
      case IX1A_DEVFLAGS_TYPE_BASIC:
         device_set_desc (dev,
                          ix1amisc_get_card_type_name
                             (IX1A_CARD_TYPE_BASIC_S0_ISA));
         iRes = ix1aisa_init_board
                   (dev, pSc, IX1A_CARD_TYPE_BASIC_S0_ISA, 0);
         break;
         
      case IX1A_DEVFLAGS_TYPE_OCTO:
         device_set_desc (dev,
                          ix1amisc_get_card_type_name
                             (IX1A_CARD_TYPE_OCTO_S0_ISA));
         iRes = ix1aisa_init_board
                   (dev, pSc, IX1A_CARD_TYPE_OCTO_S0_ISA, 0);
         break;
         
      case IX1A_DEVFLAGS_TYPE_MULTIMODEM:
         device_set_desc (dev,
                          ix1amisc_get_card_type_name
                             (IX1A_CARD_TYPE_MMOD_ISA));
         iRes = ix1aisa_init_board
                   (dev, pSc, IX1A_CARD_TYPE_MMOD_ISA, 0);
         break;
         
      default:
         device_printf (dev,
                        "ERROR: Invalid board type %d in device flags 0x%04lX, unable to probe\n",
                        iBoardType, ulFlags);
         iRes = ENXIO;
         break;
   }
   device_set_desc (dev, ix1amisc_get_card_type_name (pSc->cardType));

   /* release all previously acquired resources */
   ix1amisc_release_resinfo (dev, &(pSc->resInfo));

   return (iRes);
} /* ix1a_isa_probe */





/**
 * Attach installed controller to isa bus.
 *
 * This function is called to prepare a device for later use. After successful
 * execution of this function, the device shall be ready for operation.
 *
 * For the ix1a ISA device driver this means to setup the data maintained for
 * the board, to prepare the board for the firmware download and to register the
 * board (i.e. all its controllers) at the CAPI manager.
 *
 * @param dev                    I: The device entry for the board to attach.
 *
 * @retval 0                     The board was successfully attached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the attach operation.
 */

static int ix1a_isa_attach
   (device_t dev)
{
   Ix1aSc_t      *pSc;
   unsigned long  ulFlags;
   int            iJumperSetting;
   int            iBoardType;
   int            iRes;

   /* determine which card type should be attached and the boards jumper
    * setting; the lower four bits represent the jumper setting (values 0-7),
    * the upper four bits denote the board type
    */
   /* Note: As the probe operation was successful, the device flags are
    *       expected to be valid.
    */
   ulFlags = (unsigned long) device_get_flags (dev);
   iJumperSetting = (int) (ulFlags & IX1A_DEVFLAGS_JUMPER_MASK);
   iBoardType = (int) ((ulFlags & IX1A_DEVFLAGS_TYPE_MASK) >>
                       IX1A_DEVFLAGS_TYPE_SHIFT);
   DBG (LOG_DEBUG, device_get_unit (dev),
        "Attach ISA board with device flags 0x%04lX",
        ulFlags);

   /* initialise the softc structure according to the board type and jumper
    * setting
    */
   pSc = device_get_softc (dev);
   bzero (pSc, sizeof (*pSc));
   switch (iBoardType)
   {
      case IX1A_DEVFLAGS_TYPE_BASIC:
         iRes = ix1a_init_softc
                   (dev, pSc, IX1A_CARD_TYPE_BASIC_S0_ISA, iJumperSetting);
         break;
         
      case IX1A_DEVFLAGS_TYPE_OCTO:
         iRes = ix1a_init_softc
                   (dev, pSc, IX1A_CARD_TYPE_OCTO_S0_ISA, iJumperSetting);
         break;
         
      case IX1A_DEVFLAGS_TYPE_MULTIMODEM:
         iRes = ix1a_init_softc
                   (dev, pSc, IX1A_CARD_TYPE_MMOD_ISA, iJumperSetting);
         break;
         
      default:
         device_printf (dev,
                        "ERROR: Invalid board type %d in device flags 0x%04lX, unable to attach\n",
                        iBoardType, ulFlags);
         iRes = ENXIO;
         break;
   }
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* acquire the needed resources for the board */
   iRes = ix1aisa_init_resinfo (dev, &(pSc->resInfo));
   if (iRes != 0)
   {
      (void) ix1a_isa_detach (dev);
      return (iRes);
   }
   
   /* check again for a working board, but now do extensive tests */
   iRes = ix1aisa_init_board (dev, pSc, pSc->cardType, 1);
   if (iRes != 0)
   {
      (void) ix1a_isa_detach (dev);
      return (iRes);
   }
   pSc->state = IX1A_STATE_DOWN;
   DBG (LOG_INFO, pSc->iUnit, "Board state set to DOWN");
   
   /* if attaching the hardware was successful, we must register each port of
    * the board at the CAPI manager
    */
   iRes = ix1a_register_at_capimgr (dev, pSc);
   if (iRes != 0)
   {
      (void) ix1a_isa_detach (dev);
   }

   return (iRes);
} /* ix1a_isa_attach */





/**
 * Detach installed controller from isa bus.
 *
 * The detach function is called when the device driver module is unloaded from
 * memory. It must turn the device specified into disabled state, because the
 * device will be destroyed and all related memory removed after this call.
 *
 * For the ix1a driver this requires to unregister the board (i.e. the CAPI
 * controllers related to the board) at the CAPI manager. Last the board must
 * be disabled and all its resources are released.
 *
 * @param dev                    I: The device entry for the board to detach.
 *
 * @retval 0                     The board was successfully detached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the detach operation.
 */

static int ix1a_isa_detach
   (device_t dev)
{
   DBG (LOG_TRACE, device_get_unit (dev), "Starting detach operation");
        
   /* CAPI release and controller reset is performed by the shutdown call */
   (void) ix1a_isa_shutdown (dev);

   /* finally release all isa bus resources */
   ix1a_common_detach (dev);

   DBG (LOG_TRACE, device_get_unit (dev), "Detach operation complete");
   
   return (0);
} /* ix1a_isa_detach */





/**
 * Shut down installed controller.
 *
 * This function is called on system shutdown. The ix1a device driver will
 * disable the specified device. This will also terminate all currently running
 * connections.
 *
 * @param dev                    I: The device entry for the board to shutdown.
 *
 * @retval 0                     The board was successfully shut down.
 * @retval Else                  An errno value representing the error occurred
 *                               during the shutdown operation.
 */

static int ix1a_isa_shutdown
   (device_t dev)
{
   Ix1aSc_t       *pSc;
   Ix1aPortData_t *pPort;
   int             i;

   DBG (LOG_TRACE, device_get_unit (dev), "Starting shutdown operation");
   
   /* perform CAPI release */
   pSc = (Ix1aSc_t *) device_get_softc (dev);
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      device_printf (dev, "ERROR: Unable to obtain controller access for shutdown");
      return (EIO);
   }
   if (pSc->state >= IX1A_STATE_INITIALISING)
   {
      for (i = pSc->nPorts - 1; i >= 0; i--)
      {
         pPort = &(pSc->aPortData [i]);
         if (pPort->uUniqueCapiCtlrNum != 0)
         {
            mtx_unlock (&(pSc->mtxAccess));
            (void) kcapi_ctlr_release (pPort->uUniqueCapiCtlrNum);
            mtx_lock (&(pSc->mtxAccess));
            g_apMapUniqueCtlrToSc [pPort->uUniqueCapiCtlrNum] = NULL;
            pPort->uUniqueCapiCtlrNum = 0;
            if (pPort->uDriverCapiCtlrNum != 0)
            {
               g_apMapDrvCtlrToSc [pPort->uDriverCapiCtlrNum] = NULL;
               e_apMapDrvCtlrToPortData [pPort->uDriverCapiCtlrNum] = NULL;
               pPort->uDriverCapiCtlrNum = 0;
            }
         }
      }
   }

   /* perform generic controller reset */
   ix1a_disable_board (pSc);
   ix1amisc_release_ctlr_access (pSc);

   DBG (LOG_TRACE, device_get_unit (dev), "Shutdown operation complete");
   
   return (0);
} /* ix1a_isa_shutdown */





/**
 * Probe for installed pci controller.
 *
 * This function is called to check if the (PCI) device specified can be driven
 * by this device driver. This is accomplished by checking the PCI vendor and
 * sub-vendor id of the device. If these ids match any of the values for the
 * active IX1 boards handled by this driver, the probe operation will be
 * successful.
 *
 * @param dev                    I: The device entry for the board to probe.
 *
 * @retval 0                     The board device was found.
 * @retval Else                  The device specified does not represent an
 *                               active IX1 board driven by this device driver.
 *                               The result value is an errno value.
 */

static int ix1a_pci_probe
   (device_t dev)
{
   unsigned vid;
   unsigned did;
   
   /* determine vendor and device id */
   vid = pci_get_vendor (dev);
   did = pci_get_device (dev);

   /* for a Basic PCI there are two id combinations: original ITK and Compaq
    * OEM
    */
   if ((vid == IX1A_ITK_VENDOR_ID && did == IX1A_ITK_BASIC_DEVICE_ID) ||
       (vid == IX1A_COMPAQ_VENDOR_ID && did == IX1A_COMPAQ_BASIC_DEVICE_ID))
   {
      device_set_desc
         (dev, ix1amisc_get_card_type_name (IX1A_CARD_TYPE_BASIC_PCI));
      return (0);
   }
   
   /* for the Primary PCI there is one vendor id, but there are two device ids
    */
   if (vid == IX1A_GALILEO_VENDOR_ID &&
       (did == IX1A_GALILEO_PRIMARY_DEVICE_ID ||
        did == IX1A_ITK_PRIMARY_DEVICE_ID))
   {
      device_set_desc
         (dev, ix1amisc_get_card_type_name (IX1A_CARD_TYPE_PRIMARY_PCI));
      return (0);
   }

   /* if this point is reached, this device cannot be driven by the ix1a
    * driver
    */
   return (ENXIO);
} /* ix1a_pci_probe */





/**
 * Attach installed controller to PCI bus.
 *
 * This function is called to prepare a device for later use. After successful
 * execution of this function, the device shall be ready for operation.
 *
 * For the ix1a PCI device driver this means to setup the data maintained for
 * the board, to prepare the board for the firmware download and to register
 * the board (i.e. all its controllers) at the CAPI manager.
 *
 * Both supported board types need some operations to be prepared for the later
 * firmware download. These tasks are performed by specialised functions. After
 * successful board activation its ports are registered at the CAPI manager as
 * CAPI controllers.
 *
 * @param dev                    I: The device entry for the board to attach.
 *
 * @retval 0                     The board was attached successfully.
 * @retval Else                  An errno value representing the error occurred
 *                               during the attach operation.
 */

static int ix1a_pci_attach
   (device_t dev)
{
   Ix1aSc_t *pSc;
   int       iRes;

   /* determine and set card type */
   /* Note: It is not necessary to check all ids. We must only distinguish
    *       between Basic and Primary PCI, because the probe operation was
    *       successful.
    */
   pSc = device_get_softc (dev);
   bzero (pSc, sizeof (*pSc));
   pSc->iUnit = device_get_unit (dev);
   switch (pci_get_device (dev))
   {
      case IX1A_ITK_BASIC_DEVICE_ID:
      case IX1A_COMPAQ_BASIC_DEVICE_ID:
         iRes = ix1a_basic_pci_attach (dev);
         break;
         
      case IX1A_ITK_PRIMARY_DEVICE_ID:
      case IX1A_GALILEO_PRIMARY_DEVICE_ID:
         iRes = ix1a_primary_pci_attach (dev);
         break;
         
      default:
         device_printf (dev, "Got unsupported device with vendor id 0x%04X, device id 0x%04X to attach\n",
                        pci_get_vendor (dev), pci_get_device (dev));
         iRes = ENXIO;
         break;
   }

   /* if attaching the hardware was successful, we must register each port of
    * the controller at the CAPI manager
    */
   if (iRes == 0)
   {
      iRes = ix1a_register_at_capimgr (dev, pSc);
      if (iRes != 0)
      {
         ix1a_pci_detach (dev);
      }
   }

   return (iRes);
} /* ix1a_pci_attach */





/**
 * Detach installed controller from PCI bus.
 *
 * The detach function is called when the device driver module is unloaded from
 * memory. It must turn the device specified into disabled state, because the
 * device will be destroyed and all related memory removed after this call.
 *
 * For the ix1a driver this requires to unregister the board (i.e. the CAPI
 * controllers related to the board) at the CAPI manager. Last the board must
 * be disabled and all its resources released.
 *
 * @param dev                    I: The device entry for the board to detach.
 *
 * @retval 0                     The board was successfully detached.
 * @retval Else                  An errno value representing the error occurred
 *                               during the detach operation.
 */

static int ix1a_pci_detach
   (device_t dev)
{
   DBG (LOG_TRACE, device_get_unit (dev), "Starting detach operation");
   
   /* CAPI release and controller reset is performed by the shutdown call */
   (void) ix1a_pci_shutdown (dev);

   /* finally release all pci bus resources */
   ix1a_common_detach (dev);

   DBG (LOG_TRACE, device_get_unit (dev), "Detach operation complete");
   
   return (0);
} /* ix1a_pci_detach */





/**
 * Shut down installed PCI controller.
 *
 * This function is called on system shutdown. The avmaic device driver will
 * perform a reset operation on the specified device to leave it in a disabled
 * state. This will also terminate all currently running connections.
 *
 * @param dev                    I: The device entry for the board to shutdown.
 *
 * @retval 0                     The board was successfully shut down.
 * @retval Else                  An errno value representing the error occurred
 *                               during the shutdown operation.
 */

static int ix1a_pci_shutdown
   (device_t dev)
{
   Ix1aSc_t       *pSc;
   Ix1aPortData_t *pPort;
   int             i;

   DBG (LOG_TRACE, device_get_unit (dev), "Starting shutdown operation");
   
   /* perform CAPI release */
   pSc = (Ix1aSc_t *) device_get_softc (dev);
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      device_printf (dev, "ERROR: Unable to obtain controller access for shutdown");
      return (EBUSY);
   }
   if (pSc->state >= IX1A_STATE_INITIALISING)
   {
      for (i = pSc->nPorts - 1; i >= 0; i--)
      {
         pPort = &(pSc->aPortData [i]);
         if (pPort->uUniqueCapiCtlrNum != 0)
         {
            mtx_unlock (&(pSc->mtxAccess));
            (void) kcapi_ctlr_release (pPort->uUniqueCapiCtlrNum);
            mtx_lock (&(pSc->mtxAccess));
            g_apMapUniqueCtlrToSc [pPort->uUniqueCapiCtlrNum] = NULL;
            pPort->uUniqueCapiCtlrNum = 0;
            if (pPort->uDriverCapiCtlrNum != 0)
            {
               g_apMapDrvCtlrToSc [pPort->uDriverCapiCtlrNum] = NULL;
               e_apMapDrvCtlrToPortData [pPort->uDriverCapiCtlrNum] = NULL;
               pPort->uDriverCapiCtlrNum = 0;
            }
         }
      }
   }

   /* perform generic controller reset */
   ix1a_disable_board (pSc);
   ix1amisc_release_ctlr_access (pSc);

   DBG (LOG_TRACE, device_get_unit (dev), "Shutdown operation complete");
   
   return (0);
} /* ix1a_pci_shutdown */





/**
 * Register an application at a controller.
 *
 * This function will first perform several checks on the parameters supplied.
 * If the parameters passed are verified successfully, the registration request
 * is forwarded to the controller type specific function. The result is
 * delivered to the caller.
 *
 * @param uDrvCtlrNum           I: Driver specific controller number.
 * @param uMaxLogicalConnections
 *                              I: Maximum number of active B3-connections.
 * @param uMaxBDataBlocks       I: Maximum number of unacknowledged incoming
 *                                 data blocks per connection.
 * @param uMaxBDataLen          I: Maximum data block length to use.
 * @param puApplID              I/O: When calling this function the CAPI manager
 *                                 provides its assigned applicatoin id. On
 *                                 successful return the controller driver must
 *                                 fill in his assigned internal appl. id if it
 *                                 needs application id mapping. If no
 *                                 application id mapping is needed by the
 *                                 controller, this value must be left
 *                                 unchanged.
 *
 * @retval CAPI_OK              Application registration was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h.
 */

static unsigned ix1a_capidrv_register
   (unsigned  uDrvCtlrNum,
    unsigned  uMaxLogicalConnections,
    unsigned  uMaxBDataBlocks,
    unsigned  uMaxBDataLen,
    unsigned *puApplID)
{
   Ix1aSc_t *pSc;
   unsigned  uRes;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (g_apMapDrvCtlrToSc) ||
       g_apMapDrvCtlrToSc [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }
   pSc = g_apMapDrvCtlrToSc [uDrvCtlrNum];
   
   /* check parameters */
   if (! puApplID)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Invalid app.id pointer 0x%p", puApplID);
      return (CRE_INVALID_PARAM);
   }
   if (uMaxLogicalConnections == 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Appl. id %u: Invalid null value for max. no. connections",
           *puApplID);
      return (CRE_INVALID_CONNECT_NUM);
   }
   
   /* check for valid board state */
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      printf ("%s%d: %s: Unable to get board access",
              pSc->szDriverName, pSc->iUnit, __FUNCTION__);
      return (CRE_OS_RESOURCE_ERROR);
   }
   if (pSc->state < IX1A_STATE_READY)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded", uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }
   if (pSc->state != IX1A_STATE_READY)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG (LOG_DEBUG, pSc->iUnit, "Controller %u busy", uDrvCtlrNum);
      return (CRE_BUSY);
   }
   
   /* now perform the registration operation */
   uRes = ix1ashm_app_register (pSc,
                                uMaxLogicalConnections,
                                uMaxBDataBlocks,
                                uMaxBDataLen,
                                puApplID);
   if (uRes == CAPI_OK)
   {
      DBG (LOG_TRACE, pSc->iUnit,
           "Appl. id %u successfully registered at controller %u",
           *puApplID, uDrvCtlrNum);
   }
   
   ix1amisc_release_ctlr_access (pSc);

   return (uRes);
} /* ix1a_capidrv_register */





/**
 * Releasing an application at a controller.
 *
 * This function will first perform several checks on the parameters supplied.
 * If the parameters passed are verified successfully, the release request is
 * forwarded to the controller type specific function. The result is delivered
 * to the caller.
 *
 * @param uDrvCtlrNum           I: Driver specific controller number.
 * @param uCtlrApplID           I: Controller specific application id from
 *                                 registration. If no application id mapping is
 *                                 needed, this is the public CAPI manager
 *                                 assigned id.
 *
 * @retval CAPI_OK              Application release was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h.
 */

static unsigned ix1a_capidrv_release
   (unsigned uDrvCtlrNum,
    unsigned uCtlrApplID)
{
   Ix1aSc_t *pSc;
   unsigned  uRes;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (g_apMapDrvCtlrToSc) ||
       g_apMapDrvCtlrToSc [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }
   pSc = g_apMapDrvCtlrToSc [uDrvCtlrNum];
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      printf ("%s%d: %s: Unable to get board access",
              pSc->szDriverName, pSc->iUnit, __FUNCTION__);
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* check for valid unit and port state */
   if (pSc->state < IX1A_STATE_READY)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded", uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* perform the release operation according to the card type */
   uRes = ix1ashm_app_release (pSc, uCtlrApplID);
   if (uRes == CAPI_OK)
   {
      DBG (LOG_TRACE, pSc->iUnit,
           "Appl. id %u successfully released at controller %u",
           uCtlrApplID, uDrvCtlrNum);
   }
   
   ix1amisc_release_ctlr_access (pSc);

   return (uRes);
} /* ix1a_capidrv_release */





/**
 * Send a CAPI message to a controller.
 *
 * This function will first perform several checks on the parameters supplied.
 * If the parameters passed are verified successfully, the put-message request
 * is forwarded to the controller type specific function. The result is
 * delivered to the caller.
 *
 * @param uDrvCtlrNum           I: Driver specific controller number.
 * @param uCtlrApplID           I: Controller specific application id from
 *                                 registration.
 * @param pmbMsg                I: The CAPI message to send. A Data-B3-Request
 *                                 contains the address of a second mbuf in the
 *                                 pmbMsg->m_next member. If the function call
 *                                 returns with CAPI_OK, the driver has taken
 *                                 over ownership of the mbuf(s). It is
 *                                 responsible to release it (them) when it
 *                                 (they) are not needed any more.
 *
 * @retval CAPI_OK              The driver accepted the message.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h.
 */

static unsigned ix1a_capidrv_put_message
   (unsigned     uDrvCtlrNum,
    unsigned     uCtlrApplID,
    struct mbuf *pmbMsg)
{
   Ix1aSc_t *pSc;
   unsigned  uCmd;
   unsigned  uRes;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (g_apMapDrvCtlrToSc) ||
       g_apMapDrvCtlrToSc [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pSc = g_apMapDrvCtlrToSc [uDrvCtlrNum];
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      printf ("%s%d: %s: Unable to get board access",
              pSc->szDriverName, pSc->iUnit, __FUNCTION__);
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* check for valid unit and port state */
   if (pSc->state < IX1A_STATE_READY)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded", uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   if (pSc->state != IX1A_STATE_READY)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG (LOG_TRACE, pSc->iUnit, "Controller %u busy", uDrvCtlrNum);
      return (CME_BUSY);
   }
   
   /* perform the put message operation according to the card type */
   uCmd = CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *));
   uRes = ix1ashm_put_message (pSc, uCtlrApplID, pmbMsg);
   if (uRes == CAPI_OK)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "CAPI message 0x%04X successfully sent to controller %u for appl. id %u",
           uCmd, uDrvCtlrNum, uCtlrApplID);
      kcapi_free_mbuf (pmbMsg);
   }
   
   ix1amisc_release_ctlr_access (pSc);

   return (uRes);
} /* ix1a_capidrv_put_message */





/**
 * Reset and/or download the controller.
 *
 * This call is controller specific, its support is optional. But for active
 * ISDN adapters it is the only way to download the software and settings onto
 * the adapter. Even passive controllers may obtain the configuration for the
 * line the controller is attached to.
 *
 * How many data blocks must be specified and what their contents must look like
 * is driver specific. You have to look at the specification for the driver of
 * the controller in question. In general every driver that needs some download
 * will come with its own downloading program that indirectly calls this
 * function.
 *
 * The ix1a device driver expects three data blocks (if this is not only a
 * reset operation with no data blocks). The first block must contain the
 * primary boot code for the board. The second is the board's firmware that
 * is loaded after the boot code. And the last block contains a binary
 * configuration data block to be passed unmodified to the board after the
 * firmware load operation. The layout of this configuration data block is
 * documented within the ix1actl program (used to download an active ix1
 * board).
 *
 * @note This call is only allowed if the calling processes effective user id
 *       has superuser privileges (ensured by the CAPI manager).
 *
 * @param uUniqueCtlrNum        I: Unique controller number assigned by the CAPI
 *                                 manager on controller registration, to reset
 *                                 or download.
 * @param nNumDataBlocks        I: Number of data blocks to be sent to the
 *                                 controller. It is the length of the array at
 *                                 paDataBlocks.
 * @param paDataBlocks          I: Array of data blocks to be sent to the
 *                                 controller.
 *
 * @retval CAPI_OK              The download operation was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h.
 */

static unsigned ix1a_capidrv_reset_ctlr
   (unsigned             uUniqueCtlrNum,
    size_t               nNumDataBlocks,
    CAPICtlrDataBlock_t *paDataBlocks)
{
   Ix1aSc_t       *pSc;
   Ix1aPortData_t *pPortData;
   unsigned        uDriverCtlrNum;
   unsigned        uResTotal;
   unsigned        uRes;
   size_t          n;
   int             i;
   
   /* get softc structure and port data for the controller specified */
   if (uUniqueCtlrNum >= ARRAY_COUNT (g_apMapUniqueCtlrToSc) ||
       g_apMapUniqueCtlrToSc [uUniqueCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u, no softc mapping",
            uUniqueCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pSc = g_apMapUniqueCtlrToSc [uUniqueCtlrNum];
   pPortData = &(pSc->aPortData [0]);
   if (! ix1amisc_get_ctlr_access (pSc))
   {
      printf ("%s%d: %s: Unable to get board access",
              pSc->szDriverName, pSc->iUnit, __FUNCTION__);
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* download is only allowed for the first port of a board */
   if (pPortData->uUniqueCapiCtlrNum != uUniqueCtlrNum)
   {
      ix1amisc_release_ctlr_access (pSc);
      DBG0 (LOG_ERROR,
            "Controller no. %u is not the first port of the board (%u), download only allowed on first port",
            uUniqueCtlrNum, pPortData->uUniqueCapiCtlrNum);
      return (CRE_RESET_NOT_SUPPORTED);
   }
   
   /* distinguish between a reset and a real download operation */
   if (nNumDataBlocks == 0)
   {
      ix1a_disable_board (pSc);
      DBG (LOG_INFO, pSc->iUnit, "Board disabled");
      uResTotal = CAPI_OK;
   }
   else
   {
      int iRes;
      
      /* perform the reset operation according to the card type */
      iRes = ix1amisc_download_board (pSc, nNumDataBlocks, paDataBlocks);
      if (iRes == 0)
      {
         uResTotal = CAPI_OK;
      }
      else
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Download operation failed (error code %d)",
              iRes);
         ix1a_disable_board (pSc);
         uResTotal = CRE_OS_RESOURCE_ERROR;
      }
   }
   
   /* if the download was successful, all ports of the board must be enabled
    * at the CAPI manager
    */
   if (uResTotal == CAPI_OK && pSc->state >= IX1A_STATE_READY)
   {
      DBG (LOG_INFO, pSc->iUnit, "Board \"%s\" successfully downloaded",
           pSc->szCardName);
           
      for (i = 0; (size_t) i < pSc->nPorts; i++)
      {
         pPortData = &(pSc->aPortData [i]);
         
         /* first some version data must be transfered to the register params
          */
         /* The boards' serial numbers are sometimes longer than 7 characters,
          * so we only deliver the _last_ 7 characters of the serial number
          */
         n = strlen (pSc->szPureSerialNo);
         if (n >= sizeof (pPortData->regParams.szSerialNumber))
         {
            strcpy (pPortData->regParams.szSerialNumber,
                    pSc->szPureSerialNo + n + 1 -
                       sizeof (pPortData->regParams.szSerialNumber));
         }
         else
         {
            strcpy (pPortData->regParams.szSerialNumber, pSc->szPureSerialNo);
         }
         /* the CAPI profile will have changed after a successful download  */
         bcopy (pSc->abCapiProfile, &(pPortData->regParams.profile),
                sizeof (pPortData->regParams.profile));
         
         /* now perform the enable operation */
         uUniqueCtlrNum = pPortData->uUniqueCapiCtlrNum;
         uDriverCtlrNum = pPortData->uDriverCapiCtlrNum;
         mtx_unlock (&(pSc->mtxAccess));
         uRes = kcapi_ctlr_enable (uUniqueCtlrNum, uDriverCtlrNum);
         mtx_lock (&(pSc->mtxAccess));
         if (uRes == CAPI_OK)
         {
            DBG (LOG_TRACE, pSc->iUnit,
                 "Port %u: Controller %u/%u successfully enabled at the CAPI manager",
                 pPortData->regParams.uPortIdx,
                 uUniqueCtlrNum, uDriverCtlrNum);
         }
         else
         {
            DBG (LOG_ERROR, pSc->iUnit,
                 "Port %u: Error 0x%04X enabling controller %u/%u at the CAPI manager",
                 pPortData->regParams.uPortIdx, uRes,
                 uUniqueCtlrNum, uDriverCtlrNum);
            if (uResTotal == CAPI_OK)
            {
               uResTotal = uRes;
            }
         }
      }
   }

   ix1amisc_release_ctlr_access (pSc);
   
   return (uResTotal);
} /* ix1a_capidrv_reset_ctlr */





/**
 * Register a board at the CAPI manager.
 *
 * This function is called after the hardware related operations of the attach
 * function for a board are successfully completed. For every ISDN port of the
 * board specified by dev and pSc the registration structure is filled an the
 * port is registered as a CAPI controller at the CAPI manager.
 *
 * @pre This function must be called with controller access obtained through
 *      ix1amisc_get_ctlr_access().
 *
 * @param dev                    I: The device entry for the board to register.
 * @param pSc                    I/O: The softc structure of the board to
 *                                  register.
 *
 * @retval 0                     The register operation was successful.
 * @retval Else                  An errno value representing the error occurred
 *                               during the register operation. In most cases
 *                               this will be a CAPI result value.
 */

static int ix1a_register_at_capimgr
   (device_t  dev, 
    Ix1aSc_t *pSc)
{
   Ix1aPortData_t           *pPortData;
   CAPICtlrRegisterParams_t *pRegParams;
   int                       i;
   unsigned                  uRes;
   int                       iResTotal;

   /* check for valid address for board data */
   if (pSc == NULL)
   {
      device_printf (dev, "Invalid address for softc structure\n");
      return (ENXIO);
   }

   /* check if there is still room to register this board and its controller(s)
    */
   if (pSc->nPorts > 1)
   {
      /* this is a multi-port board --> its internal controller numbers must
       * start with a value of 1, 5, 9, etc. behind the last assigned internal
       * controller number
       */
      /* Note: The newly computed "last driver controller number" must be a
       *       valid controller number, i.e. it must be a valid array index.
       */
      if (((g_nLastDriverCtlrNum + 3) / 4) * 4 + pSc->nPorts >=
             ARRAY_COUNT (g_apMapDrvCtlrToSc))
      {
         device_printf (dev,
                        "Unable to register board \"%s\", out of driver specific controller numbers (last assigned: %zu)\n",
                        pSc->szCardName, g_nLastDriverCtlrNum);
         return (ENXIO);
      }
   }
   else
   {
      /* this is a single-port board --> its internal controller number may
       * take any value from 1 to 127
       */
      if (g_nLastDriverCtlrNum + pSc->nPorts >=
             ARRAY_COUNT (g_apMapDrvCtlrToSc))
      {
         device_printf (dev,
                        "Unable to register board \"%s\", out of driver specific controller numbers\n",
                        pSc->szCardName);
         return (ENXIO);
      }
   }
    
   /* register all ports of the board at the CAPI manager */
   for (i = 0, iResTotal = 0; (size_t) i < pSc->nPorts; i++)
   {
      /* fill the registration data for the port */
      pPortData = &(pSc->aPortData [i]);
      pRegParams = &(pPortData->regParams);
      pRegParams->uIfVersion = CAPI_DRIVER_IF_VERSION;
      pRegParams->pszCtlrName = pPortData->szCtlrPortName;
      pRegParams->pszCtlrTypeName = device_get_desc (dev);
      pRegParams->pszDriverName = pSc->szDriverName;
      pRegParams->uDrvUnitNumber = (unsigned) (pSc->iUnit);
      pRegParams->uNumPorts = (unsigned) (pSc->nPorts);
      pRegParams->uPortIdx = (unsigned) i;
      if (pSc->nPorts > 1)
      {
         pPortData->regParams.ulFlags = CAPI_CTLRFLAG_NEED_APPL_ID_MAPPING |
                                        CAPI_CTLRFLAG_REGISTER_AT_FIRST_PORT |
                                        CAPI_CTLRFLAG_RESET_FIRST_PORT;
      }
      else
      {
         pPortData->regParams.ulFlags = CAPI_CTLRFLAG_NEED_APPL_ID_MAPPING;
      }
      pRegParams->pszManufacturer = pSc->szManufacturer;
      pRegParams->uDriverMajor    = IX1A_DRIVER_VERSION_MAJOR;
      pRegParams->uDriverMinor    = IX1A_DRIVER_VERSION_MINOR;
      bcopy (pSc->abCapiProfile, &(pRegParams->profile),
             sizeof (pSc->abCapiProfile));
      pRegParams->pfnRegister     = ix1a_capidrv_register;
      pRegParams->pfnRelease      = ix1a_capidrv_release;
      pRegParams->pfnPutMessage   = ix1a_capidrv_put_message;
      pRegParams->pfnResetCtlr    = ix1a_capidrv_reset_ctlr;
      pRegParams->pfnPutNotifyMsg = NULL;
      /* the remaining fields are filled when downloading the software */

      /* register the port as a controller at the CAPI manager */
      uRes = kcapi_ctlr_register (pRegParams,
                                  &(pPortData->uUniqueCapiCtlrNum));
      if (uRes != CAPI_OK)
      {
         device_printf (dev,
                        "Attach failed, error 0x%04X registering at the CAPI manager\n",
                        uRes);
         iResTotal = ENXIO;
         break;
      }
      if (pPortData->uUniqueCapiCtlrNum <= 0 ||
          pPortData->uUniqueCapiCtlrNum >
             ARRAY_COUNT (g_apMapUniqueCtlrToSc) ||
          g_apMapUniqueCtlrToSc [pPortData->uUniqueCapiCtlrNum] != NULL)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Port %u: Got invalid controller number %u registering at the CAPI manager",
              (unsigned) i, pPortData->uUniqueCapiCtlrNum);
         iResTotal = ENXIO;
         break;
      }

      /* set the mapping from the controller number to the softc structure */
      if (pSc->nPorts > 1)
      {
         /* multi-port board --> must use controller number mapping, because
          * the internal controller numbers must start at 1, 5, 9, etc.
          */
         pPortData->uDriverCapiCtlrNum =
            ((g_nLastDriverCtlrNum + 3) / 4) * 4 + i + 1;
      }
      else
      {
         /* single-port board --> if still available use the same controller
          * number as assigned by the CAPI manager, else use the next free
          * internal one with controller number mapping
          */
         if (g_apMapDrvCtlrToSc [pPortData->uUniqueCapiCtlrNum] !=
                NULL)
         {
            pPortData->uDriverCapiCtlrNum = g_nLastDriverCtlrNum + 1;
         }
         else
         {
            pPortData->uDriverCapiCtlrNum = pPortData->uUniqueCapiCtlrNum;
         }
      }
      g_apMapUniqueCtlrToSc [pPortData->uUniqueCapiCtlrNum] = pSc;
      g_apMapDrvCtlrToSc [pPortData->uDriverCapiCtlrNum] = pSc;
      e_apMapDrvCtlrToPortData [pPortData->uDriverCapiCtlrNum] = pPortData;

      device_printf (dev,
                     "\"%s\" successfully attached as CAPI controller %u/%u\n",
                     pPortData->szCtlrPortName, pPortData->uUniqueCapiCtlrNum,
                     pPortData->uDriverCapiCtlrNum);
   }
   if (iResTotal == 0 &&
       pSc->aPortData [0].uDriverCapiCtlrNum > g_nLastDriverCtlrNum)
   {
      g_nLastDriverCtlrNum =
         pSc->aPortData [0].uDriverCapiCtlrNum + pSc->nPorts - 1;
   }
   
   /* if an error occurred, we must undo all performed registrations and then
    * return an error value
    */
   if (iResTotal != 0)
   {
      for (i = 0; (size_t) i < pSc->nPorts; ++i)
      {
         pPortData = &(pSc->aPortData [i]);
         if (pPortData->uUniqueCapiCtlrNum != 0)
         {
            (void) kcapi_ctlr_release (pPortData->uUniqueCapiCtlrNum);
         }
         g_apMapUniqueCtlrToSc [pPortData->uUniqueCapiCtlrNum] = NULL;
         g_apMapDrvCtlrToSc [pPortData->uDriverCapiCtlrNum] = NULL;
         e_apMapDrvCtlrToPortData [pPortData->uDriverCapiCtlrNum] = NULL;
         pPortData->uUniqueCapiCtlrNum = 0;
         pPortData->uDriverCapiCtlrNum = 0;
      }
   }
   
   return (iResTotal);
} /* ix1a_register_at_capimgr */





/**
 * Unregister all controllers at the CAPI manager.
 *
 * This function is called when the ix1a kernel module is unloaded from memory.
 * It must perform a controller release operation for all CAPI controllers
 * maintained by this driver. The result will be to release all memory related
 * to these CAPI controllers and the termination of all still active
 * connections for each controller.
 *
 * @param None.
 *
 * @return Nothing.
 */

static void ix1a_unregister_all_controllers (void)
{
   devclass_t      dc;
   device_t        dev;
   Ix1aSc_t       *pSc;
   Ix1aPortData_t *pPort;
   int             i;
   int             j;
   
   /* check if our device class was really created */
   dc = devclass_find (IX1A_DEVICE_CLASS_NAME);
   if (! dc)
   {
      return;
   }
   
   /* for all ports on all units perform CAPI manager release and hardware
    * reset
    */
   for (i = devclass_get_maxunit (dc) - 1; i >= 0; i--)
   {
      dev = devclass_get_device (dc, i);
      if (dev)
      {
         pSc = (Ix1aSc_t *) device_get_softc (dev);
         if (pSc != NULL)
         {
            if (! ix1amisc_get_ctlr_access (pSc))
            {
               continue;
            }
            if (pSc->state >= IX1A_STATE_INITIALISING && pSc->nPorts > 0)
            {
               for (j = pSc->nPorts - 1; j >= 0; j--)
               {
                  pPort = &(pSc->aPortData [j]);
                  if (pPort->uUniqueCapiCtlrNum != 0)
                  {
                     mtx_unlock (&(pSc->mtxAccess));
                     (void) kcapi_ctlr_release (pPort->uUniqueCapiCtlrNum);
                     mtx_lock (&(pSc->mtxAccess));
                     g_apMapUniqueCtlrToSc [pPort->uUniqueCapiCtlrNum] = NULL;
                     pPort->uUniqueCapiCtlrNum = 0;
                     if (pPort->uDriverCapiCtlrNum != 0)
                     {
                        g_apMapDrvCtlrToSc [pPort->uDriverCapiCtlrNum] = NULL;
                        e_apMapDrvCtlrToPortData [pPort->uDriverCapiCtlrNum] =
                           NULL;
                        pPort->uDriverCapiCtlrNum = 0;
                     }
                  }
               }
            }
            ix1a_disable_board (pSc);
            ix1amisc_release_ctlr_access (pSc);
         }
      }
   }
   g_nLastDriverCtlrNum = 0;
   
} /* ix1a_unregister_all_controllers */





/**
 * Perform the attach operations for a Basic PCI board.
 *
 * @param dev                   I: The device for the board to attach.
 *
 * @retval 0                    The attach operation was successful, the board
 *                              is operational and awaiting its firmware.
 * @retval Else                 The attach operation failed, the board is not
 *                              operational.
 */
static int ix1a_basic_pci_attach
   (device_t dev)
{
   Ix1aSc_t *pSc;
   int       iRes;

   DBG (LOG_TRACE, device_get_unit (dev), "Attach Basic PCI board");

   /* initialise the softc structure according to the board type */
   pSc = device_get_softc (dev);
   iRes = ix1a_init_softc (dev, pSc, IX1A_CARD_TYPE_BASIC_PCI, 0);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* acquire the needed resources for the board */
   iRes = ix1abpci_init_resinfo (dev, &(pSc->resInfo));
   if (iRes != 0)
   {
      (void) ix1a_pci_detach (dev);
      return (iRes);
   }
   
   /* check for a working board */
   iRes = ix1abpci_init_board (dev, pSc);
   if (iRes != 0)
   {
      (void) ix1a_pci_detach (dev);
      return (iRes);
   }
   pSc->state = IX1A_STATE_DOWN;
   DBG (LOG_INFO, pSc->iUnit, "Board state set to DOWN");
   
   DBG (LOG_TRACE, pSc->iUnit, "Basic PCI board successfully attached");
   
   return (0);
} /* ix1a_basic_pci_attach */





/**
 * Perform the attach operations for a Basic PCI board.
 *
 * @param dev                   I: The device for the board to attach.
 *
 * @retval 0                    The attach operation was successful, the board
 *                              is operational and awaiting its firmware.
 * @retval Else                 The attach operation failed, the board is not
 *                              operational.
 */

static int ix1a_primary_pci_attach
   (device_t dev)
{
   Ix1aSc_t *pSc;
   int       iRes;

   DBG (LOG_DEBUG, device_get_unit (dev), "Attach Primary PCI board");

   /* initialise the softc structure according to the board type */
   pSc = device_get_softc (dev);
   iRes = ix1a_init_softc (dev, pSc, IX1A_CARD_TYPE_PRIMARY_PCI, 0);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* acquire the needed resources for the board */
   iRes = ix1appci_init_resinfo (dev, &(pSc->resInfo));
   if (iRes != 0)
   {
      (void) ix1a_pci_detach (dev);
      return (iRes);
   }
   
   /* check for a working board */
   iRes = ix1appci_init_board (dev, pSc);
   if (iRes != 0)
   {
      (void) ix1a_pci_detach (dev);
      return (iRes);
   }
   pSc->state = IX1A_STATE_DOWN;
   DBG (LOG_INFO, pSc->iUnit, "Board state set to DOWN");
   
   DBG (LOG_TRACE, pSc->iUnit, "Primary PCI board successfully attached");
   
   return (0);
} /* ix1a_primary_pci_attach */





/**
 * Disable a board.
 *
 * This function is called to set the board specified to disabled state. It
 * will not do anything until it is re-enabled by a download operation. All
 * allocated resources remain active. The tasks necessary are determined from
 * the respective board type.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 disable.
 *
 * @return Nothing.
 */

static void ix1a_disable_board
   (Ix1aSc_t *pSc)
{
   pSc->state = IX1A_STATE_INITIALISING;
   DBG (LOG_INFO, pSc->iUnit, "Board state set to INITIALISING");

   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         ix1aisa_disable_board (pSc);
         break;

      case IX1A_CARD_TYPE_BASIC_PCI:
         ix1abpci_disable_board (pSc);
         break;

      case IX1A_CARD_TYPE_PRIMARY_PCI:
         ix1appci_disable_board (pSc);
         break;

      default:
         /* all other board types are not supported and thus need not be
          * disabled
          */
         break;
   }
   
} /* ix1a_disabled_board */





/**
 * Perform general detach operations for all board types.
 *
 * The goal for a call to this function is to release all resources allocated
 * for the board specified. It is expected that the softc structure was
 * correctly initialised before.
 *
 * @param dev                   I: The device entry for the detach operation.
 *
 * @return Nothing.
 */

static void ix1a_common_detach
   (device_t dev)
{
   Ix1aSc_t *pSc;
   
   pSc = device_get_softc (dev);
   if (pSc == NULL)
   {
      device_printf (dev, "%s: Address of softc structure is NULL\n",
                     __FUNCTION__);
      return;
   }

   DBG (LOG_DEBUG, pSc->iUnit, "Start releasing resources");
   ix1amisc_release_resinfo (dev, &(pSc->resInfo));
   DBG (LOG_DEBUG, pSc->iUnit, "Ressources released");

   if (mtx_initialized (&(pSc->mtxAccess)))
   {
      mtx_destroy (&(pSc->mtxAccess));
      cv_destroy (&(pSc->cvNotify));
   }
   
} /* ix1a_common_detach */





/**
 * Initialise the device's softc structure for board operation.
 *
 * Within this function all fields of the board's softc structure are
 * initialised for operation.
 *
 * @pre The board type must already be set 
 *
 * @param dev                   I: The device the softc structure belongs to.
 * @param pSc                   I/O: The softc structure to initialise.
 * @param cardType              I: The card type to fill the softc structure
 *                                 for.
 * @param iJumperSetting        I: The value for the jumper setting of ISA
 *                                 boards (values from 0 to 7). For PCI boards
 *                                 this parameter is not used.
 *
 * @retval 0                    Initialisation successful.
 * @retval Else                 Error occurred, softc structure not
 *                              initialised.
 */

static int ix1a_init_softc
   (device_t        dev,
    Ix1aSc_t       *pSc,
    Ix1aCardType_t  cardType,
    int             iJumperSetting)
{
   Ix1aPortData_t *pPortData;
   int             i;
   
   pSc->iUnit = device_get_unit (dev);

   DBG (LOG_DEBUG, pSc->iUnit,
        "Start initialising softc structure");

   bzero (&(pSc->resInfo), sizeof (pSc->resInfo));
   pSc->cardType = cardType;
   pSc->iJumperSetting = iJumperSetting;
   pSc->iBoardRevision = 0;
   strncpy (pSc->szDriverName, device_get_name (dev), 
            sizeof (pSc->szDriverName) - 1);
   pSc->szDriverName [sizeof (pSc->szDriverName) - 1] = '\0';
   pSc->state = IX1A_STATE_INITIALISING;
   DBG (LOG_INFO, pSc->iUnit, "Board state set to INITIALISING");
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_BASIC_PCI:
         snprintf (pSc->szCardName, sizeof (pSc->szCardName), "IX1-Basic-%d",
                   pSc->iUnit + 1);
         pSc->nNumBChnsPerPort = 2;
         pSc->nPorts = 1;
         break;

      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         snprintf (pSc->szCardName, sizeof (pSc->szCardName), "IX1-Octo-%d",
                   pSc->iUnit + 1);
         pSc->nNumBChnsPerPort = 2;
         pSc->nPorts = 4;
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         snprintf (pSc->szCardName, sizeof (pSc->szCardName), "IX1-MMod-%d",
                   pSc->iUnit + 1);
         /* Note: A multimodem in fact does not have any line port. But to be
          *       usable we must declare one with 8 B-channels.
          */
         pSc->nNumBChnsPerPort = 8;
         pSc->nPorts = 1;
         break;
         
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         snprintf (pSc->szCardName, sizeof (pSc->szCardName), "IX1-Primary-%d",
                   pSc->iUnit + 1);
         pSc->nNumBChnsPerPort = 30;
         pSc->nPorts = 1;
         break;

      default:
         snprintf (pSc->szCardName, sizeof (pSc->szCardName), "IX1-Unknown-%d",
                   pSc->iUnit + 1);
         pSc->nNumBChnsPerPort = 0;
         pSc->nPorts = 1;
         break;
   }
   
   for (i = 0; (size_t) i < ARRAY_COUNT (pSc->aPortData); ++i)
   {
      pPortData = &(pSc->aPortData [i]);

      if (pSc->nPorts > 1)
      {
         snprintf (pPortData->szCtlrPortName,
                   sizeof (pPortData->szCtlrPortName),
                   "%s/%d",
                   pSc->szCardName, i + 1);
      }
      else
      {
         strncpy (pPortData->szCtlrPortName, pSc->szCardName,
                  sizeof (pPortData->szCtlrPortName) - 1);
      }
      pPortData->uUniqueCapiCtlrNum = 0;
      pPortData->uDriverCapiCtlrNum = 0;
      /* CAPI register params are filled later */
   }
   
   strlcpy (pSc->szManufacturer, IX1A_MANUFACTURER_NAME,
            sizeof (pSc->szManufacturer) - 1);
   pSc->szSerialNo [0] = '\0';

   /* The number of B-channels must be copied to the profile. So it is possible
    * to register the controller at i4b before the software is downloaded.
    */
   bzero (&(pSc->abCapiProfile), sizeof (pSc->abCapiProfile));
   pSc->abCapiProfile [2] = (pSc->nNumBChnsPerPort & 0x00FF);
   pSc->abCapiProfile [3] = ((pSc->nNumBChnsPerPort & 0xFF00) >> 8);

   pSc->fIntrActive   = 0;
   pSc->fOpInProgress = 0;
   pSc->fWaitingForRc = 0;
   pSc->uRc           = CAPI_OK;

   mtx_init (&(pSc->mtxAccess), IX1AMISC_ACCESS_MUTEX_MSG, NULL, MTX_DEF);
   cv_init (&(pSc->cvNotify), IX1AMISC_NOTIFY_CONDITION_MSG);

   return (0);
} /* ix1a_init_softc */





/**
 * Handling of module load/unload events.
 *
 * This the handler function of all kernel module related events. On module
 * load some internal data structures are initialized. All board device
 * releated startup operations are triggered by other mechanisms.
 *
 * On module unload all maintained CAPI controllers will be unregistered at the
 * CAPI manager and rendered to disabled state.
 *
 * @param mod                    I: The handle of _this_ module.
 * @param iType                  I: The type of module event that occurred (in
 *                                  fact of type modeventtype_t).
 * @param pUnused                I: Unused address of module specific data.
 */

static int ix1a_modevent
   (module_t  mod,
    int       iType,
    void     *pUnused)
{
   switch (iType)
   {
      case MOD_LOAD:
         if (! g_fLoadEventReceived)
         {
            snprintf (g_szVersion, sizeof (g_szVersion), "%d.%d",
                      IX1A_DRIVER_VERSION_MAJOR, IX1A_DRIVER_VERSION_MINOR);
         }
         g_fLoadEventReceived = 1;
         break;
         
      case MOD_UNLOAD:
      case MOD_SHUTDOWN:
         /* for all hardware perform release with the CAPI manager and do a
          * hardware reset to release possibly active connections
          */
         ix1a_unregister_all_controllers ();
         g_fLoadEventReceived = 0;
         break;
         
      default:
         break;
   }
   
   return (0);
} /* ix1a_modevent */
