/**
 * @file daic.c
 *
 * Daic - The daic CAPI manager driver module for *BSD.
 *
 * Copyright: 2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: daic.c,v 1.1.2.1 2006/02/28 21:35:11 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target      daic - CAPI manager driver for Diehl active ISDN controllers $
 * @date        02.02.2003
 * @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>
 *
 * The daic driver provides CAPI support for the legacy ISDN controllers from
 * Eicon, formerly known as Diehl Elektronik. The controllers supported are
 * called:
 *    ISDN S
 *    ISDN SX
 *    ISDN SXn
 *    ISDN SCOM
 *    ISDN Quattro
 *    ISDN S2m (not yet)
 *
 * All of the above ISDN boards are active ISA boards with a similar hardware
 * interface. Only the S2m board has a slightly different interface and holds
 * more shared memory space for communication. Because of this fact it must be
 * loaded with a different bootstrap than the S0 boards. A compile time tunable
 * default number of four boards may be installed concurrently in one machine.
 *
 * To distinguish the S2m (or primary) board from the S0 (or basic) boards, it
 * is possible to detect if there is more memory than the basic boards use in
 * their 2KB window. Even the Quattro uses four 2KB memory windows, in which
 * only 1026 byte addresses have real memory behind. So if there is real memory
 * at the position 1027 and up, this must be a S2m board.
 *
 * This module, as part of the daic driver, implements the device driver
 * functions necessary for the communication with the *BSD kernel and the CAPI
 * manager. All calls to the daic driver enter in this module, except for
 * interrupt handler calls that are handled in daic_hw.c.
 */

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

/* System includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ctype.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <machine/bus.h> /* needed before sys/rman.h */
#include <sys/rman.h>
#include <machine/resource.h>
#include <sys/sysctl.h>
#include <isa/isavar.h>

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

#define __DAIC__

/* Local includes */
#include <c4b/daic/daic_var.h>
#include <c4b/daic/daic_misc.h>
#include <c4b/daic/daic_hw.h>
#include <c4b/daic/daic_bootstrap.h>
#include <c4b/daic/daic_dispatch.h>
#include <c4b/daic/daic_board_params.h>





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





/* --- Support for sysctl variables --- */

/** The node for the daic device driver under "capi". */
SYSCTL_NODE (_capi, OID_AUTO, daic, CTLFLAG_RW, 0,
             "Legacy Diehl 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_daic, OID_AUTO, version, CTLFLAG_RD,
               g_szVersion, 0, "Version number");

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



/* --- Support for public tunable 'constants' in the kernel environment --- */

/** The logging level as a tunable kernel parameter. */
TUNABLE_INT (DAIC_TUNABLE_LOGLEVEL, &e_iDaicLogLevel);



/* -- definitions for dynamic memory allocation --- */

/* the following tag will be used when allocating memory for use in this device
 * driver
 */
MALLOC_DECLARE (M_DAICDRVBUF);
MALLOC_DEFINE (M_DAICDRVBUF,
               "daicdriverbuffer", "dynamic memory for the daic device driver");





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





/* --- Data maintained for installed boards --- */

/**
 * Mapping from CAPI controller number to the port data.
 *
 * @note CAPI defines a maximum of 127 controller number from 1 to 127.
 */
DaicPortData_t *e_apDaicMapCapiCtlrToPortData [128] = { NULL };



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

/** The current logging level. */
#ifdef DAIC_LOG_LEVEL
int e_iDaicLogLevel = DAIC_LOG_LEVEL;
#else /* DAIC_LOG_LEVEL */
int e_iDaicLogLevel = LOG_ERROR;
#endif /* DAIC_LOG_LEVEL */





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





/* --- Functions needed for the device driver interface --- */

/**
 * Probe for an installed controller.
 *
 * This function is called by the kernel to let the driver validate the correct
 * resource specifications of device lines in the kernel configuration file. The
 * daic driver will check the settings for the given device and will verify the
 * existence of a board of a known type.
 *
 * @param dev                   I: Device data for the card to probe.
 *
 * @retval 0                    The device data was successfully verified, a
 *                              board of the specified type was found.
 * @retval Else                 An errno value reporting the result of the probe
 *                              operation.
 */
static int daic_probe
   (device_t dev);

/**
 * Attach an installed controller to the (ISA) bus.
 *
 * The attach function is called to initialize the board for operation. On
 * successful return the device must be left in a defined and operational state.
 * For an active ISDN adapter this means the board is identified, initialized
 * and ready to have its firmware downloaded. The download operation will be
 * performed later by a user space application.
 *
 * A specific task for ISDN controllers intended for CAPI operation is to
 * register the board at the CAPI manager. If a board has more than one ISDN
 * port (namely the Diehl Quattro adapter), each port will be registered as a
 * separate CAPI controller.
 *
 * @param dev                   I: Device data for the board to attach.
 *
 * @retval 0                    The device was successfully attached.
 * @retval Else                 An errno value reporting the result of the
 *                              attach operation.
 */
static int daic_attach
   (device_t dev);

/**
 * Detach an installed controller from the (ISA) bus.
 *
 * This function is called when the driver module (compiled as a separate kernel
 * module) is removed from kernel space. All resources allocated to the device
 * must be released and the board must be switched to disabled state.
 *
 * As a specific task for ISDN controllers intended for CAPI operation the board
 * will be unregistered at the CAPI manager.
 *
 * @param dev                   I: Device data for the board to detach.
 *
 * @retval 0                    The device was detached successfully.
 * @retval Else                 An errno value reporting the result of the
 *                              detach operation.
 */
static int daic_detach
   (device_t dev);

/**
 * Shut down an installed controller.
 *
 * This function is called when the entire system is shut down. The daic driver
 * will abort all currently running connections and put the board into the
 * disabled state.
 *
 * @param dev                   I: Device data for the board to shut down.
 *
 * @retval 0                    The board was shut down successfully.
 * @retval Else                 An errno value reporting the result of the shut
 *                              down operation.
 */
static int daic_shutdown
   (device_t dev);



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

/**
 * Register an application at a controller.
 */
static CAPIDrv_RegisterFct_t daic_capidrv_register;

/**
 * Release an application from a controller.
 */
static CAPIDrv_ReleaseFct_t daic_capidrv_release;

/**
 * Send a CAPI message to a controller.
 */
static CAPIDrv_PutMessageFct_t daic_capidrv_put_message;

/**
 * Reset and/or download firmware to a controller.
 */
static CAPIDrv_ResetCtlrFct_t daic_capidrv_reset_ctlr;

/**
 * Send a notification message to a controller.
 */
static CAPIDrv_PutNotifyMsgFct_t daic_capidrv_put_notify_message;



/* --- Some high level functions for board operations --- */

/**
 * Perform a board reset operation.
 *
 * @param pSc                   I/O: The device softc structure to operate on.
 *
 * @retval CAPI_OK              The operation was successful.
 * @retval Else                 Some CAPI error value to indicate the occurrence
 *                              of an error.
 */
static unsigned daic_reset_ctlr
   (DaicSc_t *pSc);

/**
 * Perform a board download operation.
 *
 * @note The data block parameters will not be checked here. This must be done
 *       by the caller.
 *
 * @param pSc                   I/O: The device softc structure to operate on.
 * @param nNumDataBlocks        I: The number of data blocks in the array
 *                                 paDataBlocks.
 * @param paDataBlocks          I: The array of data blocks for the download
 *                                 operation. The caller must ensure that there
 *                                 are 2 or 3 valid blocks.
 *
 * @retval CAPI_OK              The operation was successful.
 * @retval Else                 Some CAPI error value to indicate the occurrence
 *                              of an error.
 */
static unsigned daic_download_ctlr
   (DaicSc_t            *pSc,
    size_t               nNumDataBlocks,
    CAPICtlrDataBlock_t *paDataBlocks);



/* --- Miscellaneous helper functions --- */

/**
 * Initialize the softc structure for a device.
 *
 * @param dev                   I: The device to initialize.
 * @param pSc                   I: The softc structure of the device to
 *                                 initialize.
 *
 * @retval 0                    Structure initialized successfully.
 * @retval Else                 Errno value, failed to initialize softc
 *                              structure. This happens mainly because of memory
 *                              shortage or programming errors.
 */
static int daic_init_softc
   (device_t  dev,
    DaicSc_t *pSc);

/**
 * Register a board at the CAPI manager.
 */
static int daic_register_at_capi_mgr
   (device_t  dev, 
    DaicSc_t *pSc);

/**
 * Unregister all controllers at the CAPI manager.
 */
static void daic_unregister_all_controllers (void);



/* --- 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 daic_modevent
   (module_t  mod,
    int       iType,
    void     *pUnused);



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

/** Flag set on the receipt of the first MOD_LOAD event. */
static int g_fLoadEventReceived = 0;

/** The method list needed for the daic device driver. */
static device_method_t g_daicMethods [] =
   {
      DEVMETHOD (device_probe,    daic_probe),
      DEVMETHOD (device_attach,   daic_attach),
      DEVMETHOD (device_detach,   daic_detach),
      DEVMETHOD (device_shutdown, daic_shutdown),
      /*DEVMETHOD (device_suspend,  daic_suspend),*/
      /*DEVMETHOD (device_resume,   daic_resume),*/
      { NULL, NULL }
   };

/** The daic device driver. */
static driver_t g_daicDriver =
   {
      DAIC_DEVICE_CLASS_NAME, g_daicMethods, sizeof (DaicSc_t)
   };

/** Isa pnp ids for the devices - currently there is none. */
static struct isa_pnp_id g_daicPnpIds [] =
   {
      { 0x0, NULL }
   };

/** The device class for the daic device driver. */
static devclass_t g_daicDevClass;

/** Declaration of the daic device driver as a kernel module. */
DRIVER_MODULE (daic, isa, g_daicDriver, g_daicDevClass, daic_modevent, NULL);

MODULE_VERSION (daic,
                (DAIC_DRIVER_VERSION_MAJOR << 16) |
                   DAIC_DRIVER_VERSION_MINOR);

/**
 * @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 (daic, 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 an installed controller.
 *
 * This function is called by the kernel to let the driver validate the correct
 * resource specifications of device lines in the kernel configuration file. The
 * daic driver will check the settings for the given device and will verify the
 * existence of a board of a known type.
 *
 * @param dev                   I: Device data for the card to probe.
 *
 * @retval 0                    The device data was successfully verified, a
 *                              board of the specified type was found.
 * @retval Else                 An errno value reporting the result of the probe
 *                              operation.
 */

static int daic_probe
   (device_t dev)
{
   DaicSc_t      *pSc;
   int            iRes;
   unsigned long  ulBase;
   unsigned long  ulCount;

   DBG (LOG_TRACE, device_get_unit (dev), "start probing for device");
   
   /* old Diehl active controllers are pure isa boards, no isa-PnP devices */
   iRes = ISA_PNP_PROBE (device_get_parent (dev), dev, g_daicPnpIds);
   if (iRes == ENXIO)
   {
      DBG (LOG_TRACE, device_get_unit (dev),
           "Probe for PNP devices delivers \"device not configured\"");
      return (ENXIO);
   }
   
   /* initialize the softc structure for probe operation */
   pSc = device_get_softc (dev);
   bzero (pSc, sizeof (*pSc));
   pSc->iUnit = device_get_unit (dev);
   pSc->cardType = DAIC_CARD_TYPE_UNKNOWN;
   pSc->state = DAIC_BOARD_STATE_UNKNOWN;
   strncpy (pSc->szDriverName, device_get_driver (dev)->name,
            sizeof (pSc->szDriverName) - 1);
   
   /* check for valid address for memory mapped i/o */
   pSc->resInfo.iRidMem = 0;
   iRes = bus_get_resource (dev, SYS_RES_MEMORY, pSc->resInfo.iRidMem,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Probe failed, unable to get memory address range (error code %d)\n",
                     iRes);
      return (ENXIO);
   }
   pSc->resInfo.ulMemBase = ulBase;
   pSc->resInfo.ulMemSize = ulCount;
   if (pSc->resInfo.ulMemSize == 0)
   {
      pSc->resInfo.ulMemSize = DAIC_MEMORY_SIZE_BASIC;
   }
   if (daichw_check_for_valid_membase (pSc, pSc->resInfo.ulMemBase) == 0)
   {
      device_printf (dev, "Probe failed, invalid memory base address 0x%06X\n",
                     (unsigned) (pSc->resInfo.ulMemBase));
      return (ENXIO);
   }
   
   /* check for valid assigned irq number */
   pSc->resInfo.iRidIrq = 0;
   iRes = bus_get_resource (dev, SYS_RES_IRQ, pSc->resInfo.iRidIrq,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Probe failed, unable to get assigned irq number (error code %d)\n",
                     iRes);
      return (ENXIO);
   }
   pSc->resInfo.iIrq = (int) ulBase;
   /* at this point no validity check for the irq number is possible, must be
    * done later after card type detection
    */
   
   /* allocate the memory resources for further operation */
   /* Note: The memory size may have been altered from zero to 2KB. */
   (void) bus_set_resource (dev, SYS_RES_MEMORY,
                            pSc->resInfo.iRidMem, pSc->resInfo.ulMemBase,
                            pSc->resInfo.ulMemSize);
   pSc->resInfo.pResMem = bus_alloc_resource
                             (dev, SYS_RES_MEMORY, &(pSc->resInfo.iRidMem),
                              0UL, ~0UL, 1, RF_ACTIVE);
   if (pSc->resInfo.pResMem == NULL)
   {
      device_printf (dev,
                     "Probe failed, unable to allocate memory range 0x%06X-0x%06X\n",
                     (unsigned) (pSc->resInfo.ulMemBase),
                     (unsigned) (pSc->resInfo.ulMemBase +
                                 pSc->resInfo.ulMemSize));
      return (ENXIO);
   }
   pSc->resInfo.memTag    = rman_get_bustag (pSc->resInfo.pResMem);
   pSc->resInfo.memHandle = rman_get_bushandle (pSc->resInfo.pResMem);
   if (daichw_check_for_primary (pSc))
   {
      pSc->cardType = DAIC_CARD_TYPE_S2M;
      iRes = daichw_bootload_and_identify (pSc, e_abDaicBootCodePrimary);
   }
   else
   {
      iRes = daichw_bootload_and_identify (pSc, e_abDaicBootCodeBasic);
   }
   /* now the board should be identified */
   if (pSc->cardType <= DAIC_CARD_TYPE_UNKNOWN ||
       pSc->cardType >= DAIC_NUM_CARD_TYPES)
   {
      device_printf (dev, "Probe failed, unable to identify card type\n");
      (void) bus_release_resource (dev, SYS_RES_MEMORY, pSc->resInfo.iRidMem,
                                   pSc->resInfo.pResMem);
      pSc->resInfo.pResMem = NULL;
      return (ENXIO);
   }
   device_set_desc (dev, daicmisc_get_card_type_name (pSc->cardType));
   
   /* now the card type is determined, we can check the assigned irq is valid
    * for the card type
    */
   if (! daichw_check_for_valid_irq (pSc, pSc->resInfo.iIrq))
   {
      device_printf (dev, "Probe failed, irq %d not valid for board type %s\n",
                     pSc->resInfo.iIrq,
                     daicmisc_get_card_type_name (pSc->cardType));
      (void) bus_release_resource (dev, SYS_RES_MEMORY, pSc->resInfo.iRidMem,
                                   pSc->resInfo.pResMem);
      pSc->resInfo.pResMem = NULL;
      return (EINVAL);
   }
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Probe succeeded, card type %s at memory base address 0x%06X, %lu KB on-board memory",
        daicmisc_get_card_type_name (pSc->cardType),
        (unsigned int) (pSc->resInfo.ulMemBase), pSc->ulOnBoardMemory);

   /* and finally all resources must be released */
   (void) bus_release_resource (dev, SYS_RES_MEMORY,
                                pSc->resInfo.iRidMem, pSc->resInfo.pResMem);
   pSc->resInfo.pResMem = NULL;
   
   /* as a last action adjust the memory size for a Quadro */
   pSc->resInfo.ulMemSize = pSc->resInfo.ulMemSize * pSc->nPorts;
   (void) bus_set_resource (dev, SYS_RES_MEMORY, pSc->resInfo.iRidMem,
                            pSc->resInfo.ulMemBase, pSc->resInfo.ulMemSize);
                            
   return (0);
} /* daic_probe */





/**
 * Attach an installed controller to the (ISA) bus.
 *
 * The attach function is called to initialize the board for operation. On
 * successful return the device must be left in a defined and operational state.
 * For an active ISDN adapter this means the board is identified, initialized
 * and ready to have its firmware downloaded. The download operation will be
 * performed later by a user space application.
 *
 * A specific task for ISDN controllers intended for CAPI operation is to
 * register the board at the CAPI manager. If a board has more than one ISDN
 * port (namely the Diehl Quattro adapter), each port will be registered as a
 * separate CAPI controller.
 *
 * @param dev                   I: Device data for the board to attach.
 *
 * @retval 0                    The device was successfully attached.
 * @retval Else                 An errno value reporting the result of the
 *                              attach operation.
 */

static int daic_attach
   (device_t dev)
{
   DaicSc_t *pSc;
   int       iRes;
   
   device_printf (dev, "Attaching \"%s\"\n", device_get_desc (dev));
   
   /* check if the probe successfully idenified the board type */
   pSc = device_get_softc (dev);
   if (pSc->cardType <= DAIC_CARD_TYPE_UNKNOWN ||
       pSc->cardType >= DAIC_NUM_CARD_TYPES)
   {
      device_printf (dev,
                     "ERROR: Board type not identified during probe operation\n");
      return (ENXIO);
   }
   
   /* fill the softc structure */
   iRes = daic_init_softc (dev, pSc);
   if (iRes != 0)
   {
      (void) daic_detach (dev);
      return (iRes);
   }

   /* allocate required memory range */
   pSc->resInfo.pResMem = bus_alloc_resource
                             (dev, SYS_RES_MEMORY, &(pSc->resInfo.iRidMem),
                              0UL, ~0UL, 1, RF_ACTIVE);
   if (! pSc->resInfo.pResMem)
   {
      device_printf (dev,
                     "Attach failed, unable to allocate memory range 0x%06X-0x%06X\n",
                     (unsigned int) (pSc->resInfo.ulMemBase),
                     (unsigned int) rman_get_end (pSc->resInfo.pResMem));
      (void) daic_detach (dev);
      return (ENXIO);
   }
   pSc->resInfo.memTag    = rman_get_bustag (pSc->resInfo.pResMem);
   pSc->resInfo.memHandle = rman_get_bushandle (pSc->resInfo.pResMem);
   
   /* allocate required irq */
   pSc->resInfo.pResIrq = bus_alloc_resource
                             (dev, SYS_RES_IRQ, &(pSc->resInfo.iRidIrq),
                              0UL, ~0UL, 1, RF_ACTIVE);
   if (! pSc->resInfo.pResIrq)
   {
      device_printf (dev, "Attach failed, unable to allocate irq %d\n",
                     pSc->resInfo.iIrq);
      (void) daic_detach (dev);
      return (ENXIO);
   }

   /* register interrupt routine */
   (void) bus_setup_intr (dev, pSc->resInfo.pResIrq,
                          INTR_TYPE_NET | INTR_MPSAFE,
                          (driver_intr_t *) daichw_intr, pSc,
                          &(pSc->resInfo.hIrqCookie));

   DBG (LOG_INFO, pSc->iUnit,
        "ISDN controller \"%s\" identified %lu KB on-board memory",
        pSc->szCardName, pSc->ulOnBoardMemory);

   /* register the board at the CAPI manager */
   iRes = daic_register_at_capi_mgr (dev, pSc);
   if (iRes != 0)
   {
      daic_detach (dev);
      return (iRes);
   }
   pSc->state = DAIC_BOARD_STATE_DOWN;
   
   return (0);
} /* daic_attach */





/**
 * Detach an installed controller from the (ISA) bus.
 *
 * This function is called when the driver module (compiled as a separate kernel
 * module) is removed from kernel space. All resources allocated to the device
 * must be released and the board must be switched to disabled state.
 *
 * As a specific task for ISDN controllers intended for CAPI operation the board
 * will be unregistered at the CAPI manager.
 *
 * @param dev                   I: Device data for the board to detach.
 *
 * @retval 0                    The device was detached successfully.
 * @retval Else                 An errno value reporting the result of the
 *                              detach operation.
 */

static int daic_detach
   (device_t dev)
{
   DaicSc_t           *pSc;
   DaicResourceInfo_t *pRes;
   int                 i;
   
   DBG (LOG_TRACE, device_get_unit (dev), "Starting detach operation");
   
   /* CAPI release and controller reset is performed by the shutdown call */
   (void) daic_shutdown (dev);
   
   /* finally release all isa bus resources */
   pSc = device_get_softc (dev);
   pRes = &(pSc->resInfo);
   pSc->state = DAIC_BOARD_STATE_UNKNOWN;
   if (pRes->pResIrq != NULL)
   {
      (void) bus_teardown_intr (dev, pRes->pResIrq, pRes->hIrqCookie);
      (void) bus_release_resource (dev, SYS_RES_IRQ,
                                   pRes->iRidIrq, pRes->pResIrq);
      pRes->pResIrq = NULL;
   }
   if (pRes->pResMem != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_MEMORY,
                                   pRes->iRidMem, pRes->pResMem);
      pRes->pResMem = NULL;
   }
   
   /* allocated memory for the port data must also be released */
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      if (pSc->apPortData [i] != NULL)
      {
         FREE (pSc->apPortData [i], M_DAICDRVBUF);
         pSc->apPortData [i] = NULL;
      }
   }
   
   if (mtx_initialized (&(pSc->mtxAccess)))
   {
      cv_destroy (&(pSc->cvNotify));
      mtx_destroy (&(pSc->mtxAccess));
   }

   DBG (LOG_TRACE, pSc->iUnit, "Detach operation complete");
   
   return (0);
} /* daic_detach */





/**
 * Shut down an installed controller.
 *
 * This function is called when the entire system is shut down. The daic driver
 * will abort all currently running connections and put the board into the
 * disabled state.
 *
 * @param dev                   I: Device data for the board to shut down.
 *
 * @retval 0                    The board was shut down successfully.
 * @retval Else                 An errno value reporting the result of the shut
 *                              down operation.
 */

static int daic_shutdown
   (device_t dev)
{
   DaicSc_t       *pSc;
   DaicPortData_t *pPort;
   int             i;

   DBG (LOG_TRACE, device_get_unit (dev), "Starting shutdown operation");
   
   /* perform CAPI release */
   pSc = (DaicSc_t *) device_get_softc (dev);
   pSc->state = DAIC_BOARD_STATE_UNKNOWN;
   for (i = pSc->nPorts - 1; i >= 0; --i)
   {
      pPort = pSc->apPortData [i];
      if (pPort == NULL)
      {
         continue;
      }
      if (pPort->uUniqueCapiCtlrNum != 0)
      {
         (void) kcapi_ctlr_release (pPort->uUniqueCapiCtlrNum);
         e_apDaicMapCapiCtlrToPortData [pPort->uUniqueCapiCtlrNum] = NULL;
         pPort->uUniqueCapiCtlrNum = 0;
      }
   }

   /* perform generic controller reset */
   daichw_reset (pSc);

   DBG (LOG_TRACE, pSc->iUnit, "Shutdown operation complete");
   
   return (0);
} /* daic_shutdown */





/**
 * Register an application at a controller.
 *
 * @note It is assumed, that the CAPI manager has already checked all function
 *       parameters for validity and plausibility.
 *
 * @note This driver does not need or register controller number or application
 *       id mapping.
 *
 * @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.
 * @param puApplID              I/O: The CAPI manager provides its assigned
 *                                 application id, the controller must return
 *                                 its assigned application id if successful and
 *                                 if it needs application id mapping. If no
 *                                 mapping is needed, the controller must not
 *                                 change this value.
 *
 * @return CAPI result value, CAPI_OK if successful.
 */

static unsigned daic_capidrv_register
   (unsigned  uDrvCtlrNum,
    unsigned  uMaxLogicalConnections,
    unsigned  uMaxBDataBlocks,
    unsigned  uMaxBDataLen,
    unsigned *puApplID)
{
   DaicSc_t       *pSc;
   DaicPortData_t *pPortData;
   DaicApplData_t *pApplData;
   int             i;
   int             iFreeIdx;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData) ||
       e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pPortData = e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum];
   pSc = pPortData->pSc;
   
   /* check parameters */
   if (! puApplID)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Invalid app.id pointer %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);
   }

   /* get exclusive controller access */
   if (! daicmisc_get_ctlr_access (pSc))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to obtain exclusive board access");
      return (CRE_BUSY);
   }
   
   /* check for valid unit and port state */
   if (pSc->state < DAIC_BOARD_STATE_RUNNING)
   {
      daicmisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded",
           uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }

   /* check if the application id specified is already registered at the
    * controller or find the next free entry in the application array of the
    * controller
    */
   for (iFreeIdx = -1, i = 0;
        (size_t) i < ARRAY_COUNT (pPortData->aApplData);
        ++i)
   {
      pApplData = &(pPortData->aApplData [i]);
      if (iFreeIdx == -1 && pApplData->uApplID == 0)
      {
         iFreeIdx = i;
      }
      if (pApplData->uApplID == *puApplID)
      {
         break;
      }
   }
   if (i >= ARRAY_COUNT (pPortData->aApplData))
   {
      if (iFreeIdx < 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Port %u: Maximum number of applications (%zu) exhausted for controller %u",
              pPortData->uPortIdx, ARRAY_COUNT (pPortData->aApplData),
              pPortData->uUniqueCapiCtlrNum);
         daicmisc_release_ctlr_access (pSc);
         return (CRE_TOO_MANY_APPLICATIONS);
      }
      pApplData = &(pPortData->aApplData [iFreeIdx]);
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: Register new application id %u at index %d for controller %u",
           pPortData->uPortIdx, *puApplID, iFreeIdx,
           pPortData->uUniqueCapiCtlrNum);
   }
   else
   {
      pApplData = &(pPortData->aApplData [i]);
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: Re-register application id %u at index %d for controller %u",
           pPortData->uPortIdx, *puApplID, i, pPortData->uUniqueCapiCtlrNum);
   }
   /* pApplData now points to the application array entry to use */
   
   /* take the necessary registration parameters; if the application was already
    * registered, it will simply be re-registered without notice
    */
   bzero (pApplData, sizeof (*pApplData));
   pApplData->uApplID      = *puApplID;
   pApplData->uMaxBDataLen = uMaxBDataLen;
   
   /* suppress warning */
   (void) uMaxLogicalConnections;
   (void) uMaxBDataBlocks;
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Port %u: Appl. id %u registered successfully at controller %u",
        pPortData->uPortIdx, *puApplID, pPortData->uUniqueCapiCtlrNum);

   /* release exclusive board access */
   daicmisc_release_ctlr_access (pSc);
        
   return (CAPI_OK);
} /* daic_capidrv_register */





/**
 * Release an application from a controller.
 *
 * @param uDrvCtlrNum           I: Driver specific controller number.
 * @param uCtlrApplID           I: Controller specific application id from
 *                                 registration. If no mapping is needed, this
 *                                 is the public CAPI manager assigned id.
 *
 * @return CAPI result value, CAPI_OK if successful.
 */

static unsigned daic_capidrv_release
   (unsigned uDrvCtlrNum,
    unsigned uCtlrApplID)
{
   DaicSc_t       *pSc;
   DaicPortData_t *pPortData;
   DaicApplData_t *pApplData;
   int             i;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData) ||
       e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pPortData = e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum];
   pSc = pPortData->pSc;
   
   /* get exclusive controller access */
   if (! daicmisc_get_ctlr_access (pSc))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to obtain exclusive board access");
      return (CRE_BUSY);
   }
   
   /* check for valid unit and port state */
   if (pSc->state < DAIC_BOARD_STATE_RUNNING)
   {
      daicmisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded",
           uDrvCtlrNum);
      return (CRE_CAPI_NOT_INSTALLED);
   }

   /* check if the application id specified is really registered at the
    * controller
    */
   for (i = 0; (size_t) i < ARRAY_COUNT (pPortData->aApplData); ++i)
   {
      pApplData = &(pPortData->aApplData [i]);
      if (pApplData->uApplID == uCtlrApplID)
      {
         break;
      }
   }
   if (i >= ARRAY_COUNT (pPortData->aApplData))
   {
      /* application id not registered --> ready */
      DBG (LOG_TRACE, pSc->iUnit,
           "Port %u: Appl. id %u not registered at controller %u",
           pPortData->uPortIdx, uCtlrApplID, pPortData->uUniqueCapiCtlrNum);
   }
   else
   {
      /* application registered --> clear any reference to this id */
      
      /* clear any pending calls for this application id (will not wait until
       * completion and removal of all resources)
       */
      daicdisp_release_appl (pSc, pPortData, uCtlrApplID, i);
      
      /* now release the entry in the application data array */
      bzero (pApplData, sizeof (*pApplData));
      
      DBG (LOG_TRACE, pSc->iUnit,
           "Port %u: Appl. id %u released at index %d for controller %u",
           pPortData->uPortIdx, uCtlrApplID, i, pPortData->uUniqueCapiCtlrNum);
   }
   
   /* if there are requests in the request queue, try to send the next one */
   daichw_rq_handle_next (pSc, pPortData);
   
   /* release exclusive board access */
   daicmisc_release_ctlr_access (pSc);
        
   return (CAPI_OK);
} /* daic_capidrv_release */





/**
 * Send a CAPI message to a controller.
 *
 * @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 another mbuf in its
 *                                 pmbMsg->m_pkthdr.aux member. The mbuf(s) are
 *                                 only valid until return of the function call.
 *
 * @return CAPI result value, CAPI_OK if successful.
 */

static unsigned daic_capidrv_put_message
   (unsigned     uDrvCtlrNum,
    unsigned     uCtlrApplID,
    struct mbuf *pmbMsg)
{
   DaicSc_t       *pSc;
   DaicPortData_t *pPortData;
   unsigned        uRes;
   
   /* get softc structure and port data for the controller specified */
   if (uDrvCtlrNum >= ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData) ||
       e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pPortData = e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum];
   pSc = pPortData->pSc;
   
   /* get exclusive controller access */
   if (! daicmisc_get_ctlr_access (pSc))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to obtain exclusive board access");
      return (CME_BUSY);
   }
   
   /* check for valid unit and port state */
   if (pSc->state < DAIC_BOARD_STATE_RUNNING)
   {
      daicmisc_release_ctlr_access (pSc);
      DBG (LOG_ERROR, pSc->iUnit, "Controller %u not loaded",
           uDrvCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }

   /* forward the message to the dispatcher module for evaluation */
   uRes = daicdisp_put_message (pSc, pPortData, uCtlrApplID, pmbMsg);
   /* Note: The mbuf for the message is released by the message handler if the
    *       message could be accepted.
    */
   
   /* if there are requests in the request queue, try to send the next one */
   daichw_rq_handle_next (pSc, pPortData);
   
   /* release exclusive board access */
   daicmisc_release_ctlr_access (pSc);
        
   return (uRes);
} /* daic_capidrv_put_message */





/**
 * Reset and/or download firmware to a controller.
 *
 * @param uUniqueCtlrNum        I: Unique controller number to reset or download.
 * @param nNumDataBlocks        I: Number of data blocks to be sent to the
 *                                 controller. This is the array length of
 *                                 paDataBlocks.
 * @param paDataBlocks          I: Array of data blocks to be sent to the
 *                                 controller.
 *
 * @return CAPI result value, CAPI_OK if successful.
 */

static unsigned daic_capidrv_reset_ctlr
   (unsigned             uUniqueCtlrNum,
    size_t               nNumDataBlocks,
    CAPICtlrDataBlock_t *paDataBlocks)
{
   DaicSc_t            *pSc;
   DaicPortData_t      *pPortData;
   CAPIProfileBuffer_t *pProfile;
   int                  i;
   unsigned             uRes;
   unsigned             uRes2;
   
   /* get softc structure and port data for the controller specified */
   if (uUniqueCtlrNum >= ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData) ||
       e_apDaicMapCapiCtlrToPortData [uUniqueCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uUniqueCtlrNum);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pPortData = e_apDaicMapCapiCtlrToPortData [uUniqueCtlrNum];
   pSc = pPortData->pSc;
   
   /* download is only allowed for the first port of a board */
   if (pPortData->uPortIdx != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Download only allowed on port 0",
           pPortData->uPortIdx);
      return (CRE_RESET_NOT_SUPPORTED);
   }
   
   /* check for either no block or at least two blocks (boot code and protocol
    * firmware)
    */
   if (nNumDataBlocks == 0)
   {
      DBG (LOG_INFO, pSc->iUnit, "Unloading controller %u",
           pPortData->uUniqueCapiCtlrNum);
   }
   else if (nNumDataBlocks == 2)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Load bootcode (%zu bytes) and firmware (%zu bytes) to controller %u, default configuration",
           paDataBlocks [0].nLenDataBlock, paDataBlocks [1].nLenDataBlock,
           pPortData->uUniqueCapiCtlrNum);
   }
   else if (nNumDataBlocks == 3)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Load bootcode (%zu bytes) and firmware (%zu bytes) to controller %u, specific configuration data (%zu bytes)",
           paDataBlocks [0].nLenDataBlock, paDataBlocks [1].nLenDataBlock,
           pPortData->uUniqueCapiCtlrNum,
           paDataBlocks [2].nLenDataBlock);
   }
   else
   {
      printf ("%s%d: ERROR: Invalid number of data blocks (%zu) for reset operation\n",
              pSc->szDriverName, pSc->iUnit, nNumDataBlocks);
      return (CME_INVALID_PARAM);
   }
   
   /* check the bootcode (first block, if any) for validity if provided */
   if (nNumDataBlocks >= 1)
   {
      /* there must be a valid bootcode block address */
      if (paDataBlocks [0].paucDataBlock == NULL)
      {
         printf ("%s%d: ERROR: Got NULL pointer for the bootcode address\n",
                 pSc->szDriverName, pSc->iUnit);
         return (CME_INVALID_PARAM);
      }
      
      /* the boot code must be exactly 1K in size */
      if (paDataBlocks [0].nLenDataBlock != 1024)
      {
         printf ("%s%d: ERROR: Invalid length of %zu bytes for bootcode, must be 1024 bytes\n",
                 pSc->szDriverName, pSc->iUnit, paDataBlocks [0].nLenDataBlock);
         return (CME_INVALID_PARAM);
      }
   }
   
   /* check the firmware block if provided */
   if (nNumDataBlocks >= 2)
   {
      /* the only possible check is for the block to be of non-zero size */
      if (paDataBlocks [1].paucDataBlock == NULL ||
          paDataBlocks [1].nLenDataBlock == 0)
      {
         printf ("%s%d: ERROR: Got invalid firmware data block (NULL pointer for block or size of zero)\n",
                 pSc->szDriverName, pSc->iUnit);
         return (CME_INVALID_PARAM);
      }
   }
   
   /* if there is a third block, it must be of the correct size for the
    * configuration data
    */
   if (nNumDataBlocks == 3 &&
       paDataBlocks [2].nLenDataBlock != sizeof (DaicBoardConfigData_t))
   {
      printf ("%s%d: ERROR: Invalid length of %zu bytes for configuration data, must be %zu\n",
              pSc->szDriverName, pSc->iUnit,
              paDataBlocks [2].nLenDataBlock, sizeof (DaicBoardConfigData_t));
      return (CME_INVALID_PARAM);
   }

   /* the following operations must not be disturbed, even not by interrupt
    * calls
    */
   if (! daicmisc_get_ctlr_access (pSc))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to obtain exclusive board access");
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* now perfom the desired operation */
   if (nNumDataBlocks == 0)
   {
      /* no data blocks --> only reset */
      uRes = daic_reset_ctlr (pSc);
   }
   else
   {
      /* 2 or 3 data blocks --> download */
      uRes = daic_download_ctlr (pSc, nNumDataBlocks, paDataBlocks);
   }
   
   /* now (re-)enable all ports of the board at the CAPI manager */
   if (pSc->state >= DAIC_BOARD_STATE_RUNNING)
   {
      DBG (LOG_TRACE, pSc->iUnit, "Board \"%s\" successfully downloaded",
           pSc->szCardName);
           
      for (i = 0; i < pSc->nPorts; i++)
      {
         pPortData = pSc->apPortData [i];
         if (pPortData == NULL)
         {
            DBG (LOG_ERROR, pSc->iUnit,
                 "Port %u: No memory is allocated for this port",
                 (unsigned) i);
            continue;
         }
         
         /* there will be requests in the request queue (especially an
          * Assign-Request for the global id), try to send the next one
          */
         daichw_rq_handle_next (pSc, pPortData);

         /* prepare the controller register params, including profile data */
         pProfile = &(pPortData->regParams.profile);
         bzero (pProfile, sizeof (*pProfile));
         pProfile->wCtlr = pPortData->uUniqueCapiCtlrNum;
         pProfile->wNumBChannels = pPortData->nBChns;
         pProfile->dwGlobalOptions = CAPI_PROFILE_INTERNAL_CTLR_SUPPORT;
         pProfile->dwB1ProtocolSupport = (1 << CAPI_B1_HDLC_64) |
                                         (1 << CAPI_B1_TRANSPARENT_64);
         pProfile->dwB2ProtocolSupport = (1 << CAPI_B2_ISO7776_X75_SLP) |
                                         (1 << CAPI_B2_TRANSPARENT) |
                                         (1 << CAPI_B2_SDLC);
         pProfile->dwB3ProtocolSupport = (1 << CAPI_B3_TRANSPARENT) |
                                         (1 << CAPI_B3_T90NL) |
                                         (1 << CAPI_B3_ISO8208);
         
         /* temporarily release the mutex, not allowed to hold it when calling
          * into the CAPI manager
          */
         mtx_unlock (&(pSc->mtxAccess));
         
         /* now perform the enable operation */
         uRes2 = kcapi_ctlr_enable (pPortData->uUniqueCapiCtlrNum,
                                    pPortData->uUniqueCapiCtlrNum);
         mtx_lock (&(pSc->mtxAccess));
         if (uRes2 == CAPI_OK)
         {
            DBG (LOG_TRACE, pSc->iUnit,
                 "Port %u: Controller %u successfully enabled at the CAPI manager",
                 pPortData->uPortIdx, uUniqueCtlrNum);
         }
         else
         {
            DBG (LOG_TRACE, pSc->iUnit,
                 "Port %u: Failed to enable controller %u at the CAPI manager",
                 pPortData->uPortIdx, uUniqueCtlrNum);
         }
         if (uRes == CAPI_OK)
         {
            uRes = uRes2;
         }
      }
   }
   
   /* release exclusive controller access */
   daicmisc_release_ctlr_access (pSc);
   
   return (uRes);
} /* daic_capidrv_reset_ctlr */





/**
 * Send a notification message to a controller.
 *
 * This function will receive two kinds of notification messages: RC and IND
 * messages. RC messages are return codes for requests sent to the controller
 * earlier, IND messages are indications from the controller.
 *
 * This function will only forward the messages to their respective handler in
 * the dispatcher module.
 *
 * @param uDrvCtlrNum           I: Driver specific controller number.
 * @param pmbMsg                I: The notification message to send. The content
 *                                 of this message is defined exclusively by the
 *                                 driver. 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 daic_capidrv_put_notify_message
   (unsigned     uDrvCtlrNum,
    struct mbuf *pmbMsg)
{
   DaicSc_t       *pSc;
   DaicPortData_t *pPortData;
   DaicRcData_t   *pRcData;
   DaicIndData_t  *pIndData;

   /* determine the softc and port data adresses for the given controller */
   if (uDrvCtlrNum >= ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData) ||
       e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum] == NULL)
   {
      DBG0 (LOG_ERROR, "Invalid controller no. %u", uDrvCtlrNum);
      kcapi_free_mbuf (pmbMsg);
      return (CME_CAPI_NOT_INSTALLED);
   }
   pPortData = e_apDaicMapCapiCtlrToPortData [uDrvCtlrNum];
   pSc = pPortData->pSc;
   
   /* the following operations must not be disturbed by interrupt calls */
   if (! daicmisc_get_ctlr_access (pSc))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to obtain exclusive board access");
      kcapi_free_mbuf (pmbMsg);
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* check for valid unit and port state */
   if (pSc->state != DAIC_BOARD_STATE_RUNNING)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Port %u: Controller %u not loaded",
           pPortData->uPortIdx, uDrvCtlrNum);
      daicmisc_release_ctlr_access (pSc);
      kcapi_free_mbuf (pmbMsg);
      return (CME_CAPI_NOT_INSTALLED);
   }
   
   /* check for valid mbuf address */
   if (pmbMsg == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Got invalid notification message (NULL mbuf pointer)",
           pPortData->uPortIdx);
      daicmisc_release_ctlr_access (pSc);
      kcapi_free_mbuf (pmbMsg);
      return (CME_INVALID_PARAM);
   }
   
   /* now forward the message to the corresponding handler in the dispatcher
    * module
    */
   pRcData = mtod (pmbMsg, DaicRcData_t *);
   if (pRcData->bMagic == DAIC_MBUF_MAGIC_RC)
   {
      if (pmbMsg->m_len != sizeof (DaicRcData_t))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Port %u: Got RC message with invalid length %u, %zu expected",
              pPortData->uPortIdx, pmbMsg->m_len, sizeof (DaicRcData_t));
      }
      else
      {
         daicdisp_handle_rc (pPortData, pRcData);
         DBG (LOG_DEBUG, pSc->iUnit,
              "Port %u: Notification message (rc) successfully handled",
              pPortData->uPortIdx);
      }
      kcapi_free_mbuf (pmbMsg);
   }
   else
   {
      pIndData = mtod (pmbMsg, DaicIndData_t *);
      if (pmbMsg->m_len < sizeof (DaicIndData_t) ||
          pmbMsg->m_len != sizeof (DaicIndData_t) + pIndData->wDataLength)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Port %u: Got IND message with invalid length %u, at least %zu required plus possible data block",
              pPortData->uPortIdx, pmbMsg->m_len, sizeof (DaicIndData_t));
         kcapi_free_mbuf (pmbMsg);
      }
      else
      {
         daicdisp_handle_ind (pPortData, pmbMsg);
         DBG (LOG_DEBUG, pSc->iUnit,
              "Port %u: Notification message (ind) successfully handled",
              pPortData->uPortIdx);
      }
   }
   
   /* if there are requests in the request queue, try to send the next one */
   daichw_rq_handle_next (pSc, pPortData);
   
   /* release exclusive board access */
   daicmisc_release_ctlr_access (pSc);
   
   return (CAPI_OK);
} /* daic_capidrv_put_notify_message */





/**
 * Perform a board reset operation.
 *
 * @param pSc                   I/O: The device softc structure to operate on.
 *
 * @retval CAPI_OK              The operation was successful.
 * @retval Else                 Some CAPI error value to indicate the occurrence
 *                              of an error.
 */

static unsigned daic_reset_ctlr
   (DaicSc_t *pSc)
{
   DaicPortData_t *pPortData;
   int             i;
   int             j;
   
   /* first declare the board to be down */
   pSc->state = DAIC_BOARD_STATE_DOWN;
   
   /* let all state machines go to idle state with releasing all resources */
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      /* if there is no memory assigned, leave this port out (maybe the
       * initialization failed due to low memory)
       */
      pPortData = pSc->apPortData [i];
      if (pPortData == NULL)
      {
         continue;
      }
      
      /* reset all state machines */
      daicdisp_reset_all (pPortData);

      /* clear all application registrations */
      for (j = 0; (size_t) j < ARRAY_COUNT (pPortData->aApplData); ++j)
      {
         bzero (&(pPortData->aApplData [j]),
                sizeof (pPortData->aApplData [j]));
      }

      /* clear the CAPI profile for the controller, only the controller number
       * and the number of B-channels will remain valid
       */
      bzero (&(pPortData->regParams.profile),
             sizeof (pPortData->regParams.profile));
      pPortData->regParams.profile.wCtlr = pPortData->uUniqueCapiCtlrNum;
      pPortData->regParams.profile.wNumBChannels = pPortData->nBChns;
   }
   
   /* now perform a hardware reset operation */
   daichw_reset (pSc);
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Board reset operation successful, board not operational");
        
   return (CAPI_OK);
} /* daic_reset_ctlr */





/**
 * Perform a board download operation.
 *
 * @note The data block parameters will not be checked here. This must be done
 *       by the caller.
 *
 * @param pSc                   I/O: The device softc structure to operate on.
 * @param nNumDataBlocks        I: The number of data blocks in the array
 *                                 paDataBlocks.
 * @param paDataBlocks          I: The array of data blocks for the download
 *                                 operation. The caller must ensure that there
 *                                 are 2 or 3 valid blocks.
 *
 * @retval CAPI_OK              The operation was successful.
 * @retval Else                 Some CAPI error value to indicate the occurrence
 *                              of an error.
 */

static unsigned daic_download_ctlr
   (DaicSc_t            *pSc,
    size_t               nNumDataBlocks,
    CAPICtlrDataBlock_t *paDataBlocks)
{
   DaicBoardConfigData_t *pConfig;
   unsigned               uRes;
   int                    iRes;
   int                    i;
   DaicPortData_t        *pPortData;
   
   /* first perform a normal reset operation */
   uRes = daic_reset_ctlr (pSc);
   if (uRes != CAPI_OK)
   {
      /* an error message is already printed */
      return (uRes);
   }
   
   /* if there is a configuration data block, we need its correctly typed
    * address
    */
   if (nNumDataBlocks == 3)
   {
      pConfig = (DaicBoardConfigData_t *) (paDataBlocks [2].paucDataBlock);
   }
   else
   {
      pConfig = NULL;
   }
   
   /* load the boot code onto the board */
   pSc->state = DAIC_BOARD_STATE_BOOTSTRAP_LOADING;
   iRes = daichw_bootload (pSc, paDataBlocks [0].paucDataBlock,
                           (pConfig != NULL)
                              ? (int) (pConfig->ucMemBootOpt)
                              : 0);
   if (iRes != 0)
   {
      printf ("%s%d: ERROR: download operation not successful, unable to load primary bootstrap\n",
              pSc->szDriverName, pSc->iUnit);
      pSc->state = DAIC_BOARD_STATE_DOWN;
      daichw_reset (pSc);
      return (CRE_OS_RESOURCE_ERROR);
   }
   
   /* load the firmware onto the board including optional configuration data */
   pSc->state = DAIC_BOARD_STATE_FIRMWARE_LOADING;
   iRes = daichw_load_firmware (pSc,
                                paDataBlocks [1].paucDataBlock,
                                paDataBlocks [1].nLenDataBlock,
                                pConfig);
   if (iRes != 0)
   {
      printf ("%s%d: ERROR: download operation not successful, unable to download firmware\n",
              pSc->szDriverName, pSc->iUnit);
      pSc->state = DAIC_BOARD_STATE_DOWN;
      daichw_reset (pSc);
      return (CRE_OS_RESOURCE_ERROR);
   }
   pSc->state = DAIC_BOARD_STATE_RUNNING;
   
   /* start the state machine for the global id to be informed about incoming
    * calls
    */
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      pPortData = pSc->apPortData [i];
      if (pPortData == NULL)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Port %u: No memory is allocated for this port",
              (unsigned) i);
         continue;
      }
      
      uRes = daicdisp_start_all (pPortData);
      {
         if (uRes != CAPI_OK)
         {
            printf ("%s%d: ERROR: download operation not successful, unable to obtain global id (D-channel access) for port %d\n",
                    pSc->szDriverName, pSc->iUnit, i);
            pSc->state = DAIC_BOARD_STATE_DOWN;
            daichw_reset (pSc);
            return (uRes);
         }
      }
   }
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Board download operation successful, board now operational");
        
   return (CAPI_OK);
} /* daic_download_ctlr */





/**
 * Initialize the softc structure for a device.
 *
 * @param dev                   I: The device to initialize.
 * @param pSc                   I: The softc structure of the device to
 *                                 initialize.
 *
 * @retval 0                    Structure initialized successfully.
 * @retval Else                 Errno value, failed to initialize softc
 *                              structure. This happens mainly because of memory
 *                              shortage or programming errors.
 */

static int daic_init_softc
   (device_t  dev,
    DaicSc_t *pSc)
{
   DaicPortData_t *pPortData;
   int             i;

   pSc->iUnit = device_get_unit (dev);

   snprintf (pSc->szCardName, sizeof (pSc->szCardName), "%s-%d",
             daicmisc_get_card_type_name (pSc->cardType), pSc->iUnit + 1);
   strncpy (pSc->szDriverName, device_get_driver (dev)->name,
            sizeof (pSc->szDriverName) - 1);
   pSc->state = DAIC_BOARD_STATE_UNKNOWN;

   pSc->nPortsInitialized = 0;
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      MALLOC (pSc->apPortData [i],
              DaicPortData_t *, sizeof (*(pSc->apPortData [i])),
              M_DAICDRVBUF, M_WAITOK);
      if (pSc->apPortData [i] == NULL)
      {
         device_printf (dev,
                        "Out of memory while allocating data for port %d (%zu bytes)\n",
                        i, sizeof (*(pSc->apPortData [i])));
         return (ENOMEM);
      }
      pPortData = pSc->apPortData [0];
      bzero (pPortData, sizeof (*pPortData));
      pPortData->pSc                 = pSc;
      pPortData->iUnit               = pSc->iUnit;
      pPortData->uPortIdx            = 0;
      strncpy (pPortData->szCtlrPortName, pSc->szCardName,
               sizeof (pPortData->szCtlrPortName) - 1);
      pPortData->uUniqueCapiCtlrNum  = 0;
      pPortData->nBChns              =
         (pSc->cardType == DAIC_CARD_TYPE_S2M) ? 30 : 2;
      pPortData->nNumPlci            = 0;
      pPortData->fAssignPending      = 0;
      pPortData->iReadyInt           = 0;
      pPortData->iIrqProbe           = 0;
      pPortData->reqQueue.ifq_len    = 0;
      pPortData->reqQueue.ifq_maxlen = IFQ_MAXLEN;

      /* 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.
       */
      pPortData->regParams.profile.wCtlr = 0;
      pPortData->regParams.profile.wNumBChannels = pPortData->nBChns;
   }

   pSc->fIntrActive   = 0;
   pSc->fOpInProgress = 0;
   
   mtx_init (&(pSc->mtxAccess), DAICMISC_ACCESS_MUTEX_MSG, NULL, MTX_DEF);
   cv_init (&(pSc->cvNotify), DAICMISC_NOTIFY_CONDITION_MSG);
   
   return (0);
} /* daic_init_softc */





/**
 * Register a board at the CAPI manager.
 */

static int daic_register_at_capi_mgr
   (device_t  dev, 
    DaicSc_t *pSc)
{
   DaicPortData_t           *pPortData;
   CAPICtlrRegisterParams_t *pRegParams;
   int                       i;
   unsigned                  uRes;

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

   /* register all ports of the board at the CAPI manager */
   for (i = 0; i < pSc->nPorts; i++)
   {
      /* fill the registration data for the port */
      /* Note: At this point the board is not functional because it is not
       *       downloaded with its firmware yet.
       */
      pPortData = pSc->apPortData [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               = pSc->nPorts;
      pRegParams->uPortIdx                = pPortData->uPortIdx;
      if (pSc->nPorts > 1)
      {
         pPortData->regParams.ulFlags = CAPI_CTLRFLAG_RESET_FIRST_PORT;
      }
      else
      {
         pPortData->regParams.ulFlags = 0;
      }
      pRegParams->pszManufacturer         = DAIC_MANUFACTURER_NAME;
      pRegParams->uDriverMajor            = DAIC_DRIVER_VERSION_MAJOR;
      pRegParams->uDriverMinor            = DAIC_DRIVER_VERSION_MINOR;
      pRegParams->uManufacturerMajor      = 0;
      pRegParams->uManufacturerMinor      = 0;
      bzero (pRegParams->szSerialNumber, sizeof (pRegParams->szSerialNumber));
      bzero (&(pRegParams->profile), sizeof (pRegParams->profile));
      pRegParams->profile.wNumBChannels   = pPortData->nBChns;
      pRegParams->pfnRegister             = daic_capidrv_register;
      pRegParams->pfnRelease              = daic_capidrv_release;
      pRegParams->pfnPutMessage           = daic_capidrv_put_message;
      pRegParams->pfnResetCtlr            = daic_capidrv_reset_ctlr;
      pRegParams->pfnPutNotifyMsg         = daic_capidrv_put_notify_message;

      /* 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);
         return (ENXIO);
      }
      if (pPortData->uUniqueCapiCtlrNum <= 0 ||
          pPortData->uUniqueCapiCtlrNum >
             ARRAY_COUNT (e_apDaicMapCapiCtlrToPortData))
      {
         device_printf (dev,
                        "Port %u: Attach failed, got invalid controller number %u registering at the CAPI manager",
                        (unsigned) i, pPortData->uUniqueCapiCtlrNum);
         return (ENXIO);
      }
      pRegParams->profile.wCtlr = pPortData->uUniqueCapiCtlrNum;

      /* set the mapping from the controller number to the softc structure */
      e_apDaicMapCapiCtlrToPortData [pPortData->uUniqueCapiCtlrNum] = pPortData;

      device_printf (dev,
                     "\"%s\" successfully attached as CAPI controller %u\n",
                     pPortData->szCtlrPortName, pPortData->uUniqueCapiCtlrNum);
   }
   
   return (0);
} /* daic_register_at_capi_mgr */





/**
 * Unregister all controllers at the CAPI manager.
 */

static void daic_unregister_all_controllers (void)
{
   devclass_t  dc;
   device_t    dev;
   DaicSc_t   *pSc;
   int         i;
   
   /* check if our device class was really created */
   dc = devclass_find (DAIC_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 = (DaicSc_t *) device_get_softc (dev);
         if (pSc)
         {
            (void) daic_shutdown (dev);
         }
      }
   }
   
} /* daic_unregister_all_controllers */





/**
 * 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 daic_modevent
   (module_t  mod,
    int       iType,
    void     *pUnused)
{
   switch (iType)
   {
      case MOD_LOAD:
         if (! g_fLoadEventReceived)
         {
            snprintf (g_szVersion, sizeof (g_szVersion), "%d.%d",
                      DAIC_DRIVER_VERSION_MAJOR, DAIC_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
          */
         daic_unregister_all_controllers ();
         g_fLoadEventReceived = 0;
         break;
         
      default:
         break;
   }
   
   return (0);
} /* daic_modevent */
