/**
 * @file daic_hw.c
 *
 * Daic-Hardware - Module for direct hardware communication with the board.
 *
 * Copyright: 2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: daic_hw.c,v 1.1.2.1 2006/02/28 21:35:13 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>
 */

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

/* System includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <machine/bus.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <machine/clock.h>
#include <sys/endian.h>

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

#define __DAIC_HW__

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





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





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





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





/**
 * Interrupt handler function for return codes.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The data for the addressed port of the
 *                                 board.
 * @param uRcVal                I: The return code fetched from the board before
 *                                 calling this function.
 *
 * @return Nothing.
 */
static void daichw_handle_rc
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uRcVal);

/**
 * Interrupt handler function for indications.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The data for the addressed port of the
 *                                 board.
 * @param uIndVal               I: The indication code fetched from the board
 *                                 before calling this function.
 *
 * @return Nothing.
 */
static void daichw_handle_ind
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uIndVal);

/**
 * Send a dummy O.K. return code to the PLCI or NCCI originating a request.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param pReqData              I: The request data the return code shall be
 *                                 sent for.
 *
 * @return Nothing.
 */
static void daichw_send_dummy_rc
   (DaicPortData_t      *pPortData,
    const DaicReqData_t *pReqData);





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





/**
 * Perform a reset operation on a controller.
 *
 * This operation will set the board into a defined but unusable state. The only
 * recovery is to perform a new download operation.
 *
 * This function may be called with every board state, so no assumptions must be
 * made regarding softc structure initialisation. The only things that are
 * required before calling this function are to allocate the memory i/o range
 * and to correctly set the board type. Else nothing will bw donw here.
 *
 * @param pSc                   I: The softc structure of the board to reset.
 *
 * @return Nothing.
 */

void daichw_reset
   (DaicSc_t *pSc)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned long      ulIntAck;
   unsigned long      ulStopCpu;
   size_t             nPorts;
   int                i;
   
   /* check for valid resource allocation */
   if (pSc->resInfo.pResMem == NULL)
   {
      /* resources not allocated, no reset possible */
      DBG (LOG_DEBUG, pSc->iUnit, "Resources not allocated, no reset possible");
      return;
   }
   
   /* address the board type specific hardware registers; assume it is a basic
    * board if board type not set yet
    */
   if (pSc->cardType == DAIC_CARD_TYPE_S2M)
   {
      ulIntAck  = DAIC_SHMEM_OFFSET_INTACK_PRIMARY;
      ulStopCpu = DAIC_SHMEM_OFFSET_STOPCPU_PRIMARY;
   }
   else
   {
      ulIntAck  = DAIC_SHMEM_OFFSET_INTACK_BASIC;
      ulStopCpu = DAIC_SHMEM_OFFSET_STOPCPU_BASIC;
   }
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   
   /* the following operations must be performed on all ports of a board if the
    * board type is already determined (works also with an S2m and port 0)
    */
   nPorts = (pSc->cardType == DAIC_CARD_TYPE_QUADRO) ? 1 : 4;
   for (i = 0; (size_t) i < nPorts; ++i)
   {
      /* clear any pending irq */
      (void) bus_space_read_1 (t, h, i * DAIC_MEMORY_SIZE_BASIC + ulIntAck);

      /* stop the board cpu */
      bus_space_write_1 (t, h, i * DAIC_MEMORY_SIZE_BASIC + ulStopCpu, 0);

      /* clear irq */
      bus_space_write_1 (t, h, i * DAIC_MEMORY_SIZE_BASIC + ulIntAck, 0);
      (void) bus_space_read_1 (t, h, i * DAIC_MEMORY_SIZE_BASIC + ulIntAck);
   }

   DBG (LOG_DEBUG, pSc->iUnit, "Reset operation complete");
   
} /* daichw_reset */





/**
 * Check if a board has a primary or basic rate interface.
 *
 * If all goes well it is possible to do some write operations behind the memory
 * range of the S0 boards (also true for quadro boards). If this succeeds it
 * must be a primary board, because an S0 board would only react on memory
 * access below offset 0x402. And as a quadro board is really four single S0
 * boards, there are memory holes between the memory blocks of the respective
 * ports.
 *
 * @param pSc                   I/O: Softc structure for the board device.
 *
 * @retval 1                    The board was identified as an S2m board.
 * @retval 0                    The board is one of the S0 boards.
 */

int daichw_check_for_primary
   (DaicSc_t *pSc)
{
#if 0

   if (pSc->resInfo.ulMemSize == DAIC_MEMORY_SIZE_PRIMARY)
   {
      return (1);
   }
   return (0);

#else /* 1 */

   int fPrimary = 1;
   
   bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                      DAIC_SHMEM_SIZE_BASIC, 0xAA);
   bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                      DAIC_SHMEM_SIZE_BASIC + 1, 0x55);
   if (bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                         DAIC_SHMEM_SIZE_BASIC) != 0xAA ||
       bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                         DAIC_SHMEM_SIZE_BASIC + 1) != 0x55)
   {
      fPrimary = 0;
   }
   else
   {
      bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                         DAIC_SHMEM_SIZE_BASIC, 0x55);
      bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                         DAIC_SHMEM_SIZE_BASIC + 1, 0xAA);
      if (bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                            DAIC_SHMEM_SIZE_BASIC) != 0x55 ||
          bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                            DAIC_SHMEM_SIZE_BASIC + 1) != 0xAA)
      {
         fPrimary = 0;
      }
      else
      {
         bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                            DAIC_SHMEM_SIZE_BASIC, 0);
         bus_space_write_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                            DAIC_SHMEM_SIZE_BASIC + 1, 0);
         if (bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                               DAIC_SHMEM_SIZE_BASIC) != 0 ||
             bus_space_read_1 (pSc->resInfo.memTag, pSc->resInfo.memHandle,
                               DAIC_SHMEM_SIZE_BASIC + 1) != 0)
         {
            fPrimary = 0;
         }
      }
   }
   
   if (fPrimary)
   {
      DBG (LOG_TRACE, pSc->iUnit,
           "Memory above offset %lu readable, assume primary board",
           (unsigned long) DAIC_SHMEM_SIZE_BASIC);
   }
   else
   {
      DBG (LOG_TRACE, pSc->iUnit,
           "Memory above offset %lu not readable, assume basic board",
           (unsigned long) DAIC_SHMEM_SIZE_BASIC);
   }
   
   return (fPrimary);

#endif /* 1 */
} /* daichw_check_for_primary */





/**
 * Check for valid memory base address assignment for the board type.
 *
 * The valid memory base address varies between the different board types. For S
 * and Sx addresses from 0xC0000 to 0xDE000 are valid, all on a 8KB boundary.
 * For the other boards addresses from 0x80000 to 0xFE800 are valid, they must
 * be on a 2KB boundary.
 *
 * If this function is called with a (yet) unknown board type, the ckeck will
 * succeed for any allowed value for any board type.
 *
 * @param pSc                   I: The device softc structure.
 * @param ulMemBase             I: The memory base address to check for validity.
 *
 * @retval 1                    The irq number specified is valid for the card
 *                              type stored in the structure pointed to by pSc.
 * @retval 0                    The irq number specified is not valid for the
 *                              card type stored in the structure pointed to by
 *                              pSc.
 */

int daichw_check_for_valid_membase
   (DaicSc_t      *pSc,
    unsigned long  ulMemBase)
{
   /* distinguish between specific board types if necessary */
   if (pSc->cardType == DAIC_CARD_TYPE_S ||
       pSc->cardType == DAIC_CARD_TYPE_SX)
   {
      if (ulMemBase < 0x000C000 ||
          ulMemBase > 0x000DE000 ||
          (ulMemBase & 0x0001FFF) != 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Memory base address 0x%06X invalid for board types S and Sx",
              (unsigned int) ulMemBase);
         return (0);
      }
   }
   else
   {
      if (ulMemBase < 0x00080000 ||
          ulMemBase > 0x000FE800 ||
          (ulMemBase & 0x000007FF) != 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Memory base address 0x%06X invalid for all board types",
              (unsigned int) ulMemBase);
         return (0);
      }
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Memory base address 0x%06X valid",
        (unsigned int) ulMemBase);
   
   /* if we reach this point, the memory base address is valid */
   return (1);
} /* daichw_check_for_valid_membase */





/**
 * Check for valid irq assignment for the board type.
 *
 * For the boards S and Sx the interrupts 2, 3, 4, 10, 11 and 12 are valid. All
 * others accept also interrupts 5 and 7. If this function is called with an
 * unknown board type, it will succeed with any interrupt valid for any board
 * type.
 *
 * @param pSc                   I: The device softc structure.
 * @param iIrq                  I: The irq number to check for validity.
 *
 * @retval 1                    The irq number specified is valid for the card
 *                              type stored in the structure pointed to by pSc.
 * @retval 0                    The irq number specified is not valid for the
 *                              card type stored in the structure pointed to by
 *                              pSc.
 */

int daichw_check_for_valid_irq
   (DaicSc_t *pSc,
    int       iIrq)
{
   if (pSc->cardType == DAIC_CARD_TYPE_S ||
       pSc->cardType == DAIC_CARD_TYPE_SX)
   {
      if (((1 << iIrq) & 0x1C1C) == 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Irq number %d invalid for board types S and Sx",
              iIrq);
         return (0);
      }
   }
   else
   {
      if (((1 << iIrq) & 0x1CBC) == 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Irq number %d invalid for all board types",
              iIrq);
         return (0);
      }
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Irq number %d valid", iIrq);
      
   /* if we reach this point, the irq number is valid */
   return (1);
} /* daichw_check_for_valid_irq */





/**
 * Download the bootstrap code and identify the board type.
 *
 * If the board is an S2m board, the card type must be set before calling this
 * function. As the basic boards have the same shared memory interface, in this
 * case the card type may still be set to "unknown".
 *
 * @note This function is only called during probe operation. So it must not
 *       access port specific data, because this memory is only assigned during
 *       the attach operation.
 *
 * @param pSc                   I: The device softc structure.
 * @param pabBootCode           I: The bootstrap code to download, must be
 *                                 exactly 1K large.
 *
 * @retval 0                    Bootstrapping and board identification was
 *                              successful.
 * @retval Else                 Errno value as an error result.
 */

int daichw_bootload_and_identify
   (DaicSc_t *pSc,
    u_int8_t *pabBootCode)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned long      ulBaseOffset;
   unsigned long      ulIntAckOffset;
   unsigned long      ulStopCpuOffset;
   unsigned long      ulStartCpuOffset;
   int                iTimeout;
   unsigned short     uh;
   DaicCardType_t     cardType;
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Start loading primary bootstrap with board identification");
   
   /* first reset the board; assume it is a basic board if card type not set yet
    */
   daichw_reset (pSc);
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;

   /* initialize some board type specific shared memory offsets */
   if (pSc->cardType == DAIC_CARD_TYPE_S2M)
   {
      ulBaseOffset     = 0;
      ulIntAckOffset   = DAIC_SHMEM_OFFSET_INTACK_PRIMARY;
      ulStopCpuOffset  = DAIC_SHMEM_OFFSET_STOPCPU_PRIMARY;
      ulStartCpuOffset = DAIC_SHMEM_OFFSET_STARTCPU_PRIMARY;
   }
   else
   {
      /* Note: For the Quadro these values must also be set for the other three
       *       ports later.
       */
      ulBaseOffset     = 0;
      ulIntAckOffset   = DAIC_SHMEM_OFFSET_INTACK_BASIC;
      ulStopCpuOffset  = DAIC_SHMEM_OFFSET_STOPCPU_BASIC;
      ulStartCpuOffset = DAIC_SHMEM_OFFSET_STARTCPU_BASIC;
   }
   
   /* now copy the bootcode to the shared memory area */
   DBG (LOG_DEBUG, pSc->iUnit, "Copy boot code to shared memory");
   bus_space_write_region_1 (t, h, 0, pabBootCode, 0x0400);
   if (pSc->cardType == DAIC_CARD_TYPE_S2M)
   {
      /* for a primary some of the code bytes must be duplicated into the upper
       * memory area
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Duplicate some bytes for primary");
      bus_space_write_region_1 (t, h, 0x3FF0, pabBootCode + 0x03F0, 0x0C);
   }
   DELAY (DAIC_SEC_DELAY * 2 / 10);
   
   /* check for successfully loaded code */
   if (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != 0 ||
       bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT) != 0 ||
       bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT + 1) != 0)
   {
      printf ("%s%d: Shared memory test failed while loading primary bootstrap\n",
              pSc->szDriverName, pSc->iUnit);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Copying boot code successful");
   
   /* let the board perform its internal memory test */
   DBG (LOG_DEBUG, pSc->iUnit, "Start on-board memory test (timeout 30s)");
   bus_space_write_1 (t, h, DAIC_SHMEM_OFFSET_CTRL, DAIC_TEST_MEMORY);
   bus_space_write_1 (t, h, ulStartCpuOffset, 0);
   DELAY (DAIC_SEC_DELAY * 2 / 10);
   
   /* wait for a positive result for the memory test */
   iTimeout = 30 * hz;
   while (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != DAIC_TEST_READY &&
          iTimeout > 0)
   {
      tsleep (pSc, PZERO, DAICMISC_WAIT_FOR_MEMCHECK, 1);
      --iTimeout;
   }
   if (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != DAIC_TEST_READY)
   {
      printf ("%s%d: On-board processor test failed\n",
              pSc->szDriverName, pSc->iUnit);
      return (EIO);
   }
   uh = bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT);
   uh += bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT + 1) << 8;
   if (uh != 0)
   {
      printf ("%s%d: On-board memory test failed at 0x%08zX, bit %hu\n",
              pSc->szDriverName, pSc->iUnit,
              (size_t) bus_space_read_4 (t, h, DAIC_SHMEM_OFFSET_ELOC), uh);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "On-board memory test successful");
   
   /* get the board type from the board itself */
   cardType = bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_GET_CARDTYPE) + 1;
   if (pSc->cardType != DAIC_CARD_TYPE_UNKNOWN &&
       pSc->cardType != cardType)
   {
      printf ("%s%d: Board delivers type %d (%s) but was identified as %d (%s)\n",
              pSc->szDriverName, pSc->iUnit,
              (int) cardType, daicmisc_get_card_type_name (cardType),
              (int) (pSc->cardType),
              daicmisc_get_card_type_name (pSc->cardType));
      return (EIO);
   }
   if (cardType <= DAIC_CARD_TYPE_UNKNOWN ||
       cardType >= DAIC_NUM_CARD_TYPES)
   {
      printf ("%s%d: Board delivers invalid board type %d\n",
              pSc->szDriverName, pSc->iUnit, (int) cardType);
      return (EIO);
   }
   pSc->cardType = cardType;
   
   /* get the on-board memory size */
   pSc->ulOnBoardMemory =
      bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_MEMSIZE) << 4;
   
   /* set the number of ports for the board; the Quadro has 4, all others have
    * only 1
    */
   pSc->nPorts = (pSc->cardType == DAIC_CARD_TYPE_QUADRO) ? 4 : 1;
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Loading primary bootstrap successful, board type %d (%s), %lu KB on-board memory",
        (int) (pSc->cardType), daicmisc_get_card_type_name (pSc->cardType),
        pSc->ulOnBoardMemory);

   return (0);
} /* daichw_bootload_and_identify */





/**
 * Download the bootstrap code to the board without identifying it.
 *
 * This function is called when the user requests a download operation. At this
 * point the board is already identified and it has passed its memory check. So
 * during this operation the user _may_ only decide to do another memory check.
 * The normal operation would be not to do this.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pabBootCode           I: The bootstrap code to download, must be
 *                                 exactly 1K large.
 * @param fDoMemCheck           I:
 *                              - 0: No memory check is performed after the
 *                                 download operation.
 *                              - Else: After the download the board shall
 *                                 execute a check on its on-board memory.
 *
 * @retval 0                    Bootstrapping and board identification was
 *                              successful.
 * @retval Else                 Errno value as an error result.
 */

int daichw_bootload
   (DaicSc_t *pSc,
    u_int8_t *pabBootCode,
    int       fDoMemCheck)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   int                 iTimeout;
   unsigned short      uh;
   DaicCardType_t      cardType;
   int                 i;
   DaicPortData_t     *pPortData;
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Start loading primary bootstrap with knowledge of the board type");
   
   /* first reset the board */
   daichw_reset (pSc);
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;

   /* initialize some board type specific shared memory offsets */
   /* Note: This function is called after the initialization of the softc
    *       structure. So memory is allocated at least for port 0.
    */
   if (pSc->cardType == DAIC_CARD_TYPE_S2M)
   {
      pSc->apPortData [0]->ulBaseOffset     = 0;
      pSc->apPortData [0]->ulIntAckOffset   = DAIC_SHMEM_OFFSET_INTACK_PRIMARY;
      pSc->apPortData [0]->ulStopCpuOffset  = DAIC_SHMEM_OFFSET_STOPCPU_PRIMARY;
      pSc->apPortData [0]->ulStartCpuOffset = DAIC_SHMEM_OFFSET_STARTCPU_PRIMARY;
   }
   else
   {
      /* Note: For the Quadro these values must also be set for the other three
       *       ports later.
       */
      pSc->apPortData [0]->ulBaseOffset     = 0;
      pSc->apPortData [0]->ulIntAckOffset   = DAIC_SHMEM_OFFSET_INTACK_BASIC;
      pSc->apPortData [0]->ulStopCpuOffset  = DAIC_SHMEM_OFFSET_STOPCPU_BASIC;
      pSc->apPortData [0]->ulStartCpuOffset = DAIC_SHMEM_OFFSET_STARTCPU_BASIC;
   }
   
   /* now copy the bootcode to the shared memory area */
   DBG (LOG_DEBUG, pSc->iUnit, "Copy boot code to shared memory");
   bus_space_write_region_1 (t, h, 0, pabBootCode, 0x0400);
   if (pSc->cardType == DAIC_CARD_TYPE_S2M)
   {
      /* for a primary some of the code bytes must be duplicated into the upper
       * memory area
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Duplicate some bytes for primary");
      bus_space_write_region_1 (t, h, 0x3FF0, pabBootCode + 0x03F0, 0x0C);
   }
   DELAY (DAIC_SEC_DELAY * 2 / 10);
   
   /* check for successfully loaded code */
   if (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != 0 ||
       bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT) != 0 ||
       bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT + 1) != 0)
   {
      printf ("%s%d: Shared memory test failed while loading primary bootstrap\n",
              pSc->szDriverName, pSc->iUnit);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Copying boot code successful");
   
   /* let the board perform its internal memory test if required */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Start cpu %s on-board memory test (timeout 30s)",
        fDoMemCheck ? "with" : "without");
   bus_space_write_1 (t, h, DAIC_SHMEM_OFFSET_CTRL,
                      fDoMemCheck ? DAIC_TEST_MEMORY : DAIC_TEST_SKIP);
   bus_space_write_1 (t, h, pSc->apPortData [0]->ulStartCpuOffset, 0);
   DELAY (DAIC_SEC_DELAY * 2 / 10);
   
   /* wait for a positive result for the start operation (at most 30 sec.) */
   iTimeout = 30 * hz;
   while (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != DAIC_TEST_READY &&
          iTimeout > 0)
   {
      msleep (pSc, &(pSc->mtxAccess), PZERO, DAICMISC_WAIT_FOR_MEMCHECK, 1);
      --iTimeout;
   }
   if (bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_CTRL) != DAIC_TEST_READY)
   {
      printf ("%s%d: On-board processor test failed\n",
              pSc->szDriverName, pSc->iUnit);
      return (EIO);
   }
   uh = bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT);
   uh += bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_EBIT + 1) << 8;
   if (uh != 0)
   {
      printf ("%s%d: On-board memory test failed at 0x%08zX, bit %hu\n",
              pSc->szDriverName, pSc->iUnit,
              (size_t) bus_space_read_4 (t, h, DAIC_SHMEM_OFFSET_ELOC), uh);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "On-board memory test successful");
   
   /* get the board type from the board itself, must match the type stored in
    * the softc structure
    */
   cardType = bus_space_read_1 (t, h, DAIC_SHMEM_OFFSET_GET_CARDTYPE) + 1;
   if (cardType == DAIC_CARD_TYPE_UNKNOWN ||
       pSc->cardType != cardType)
   {
      printf ("%s%d: Board delivers type %s (%d) but was identified as %s (%d)\n",
              pSc->szDriverName, pSc->iUnit,
              daicmisc_get_card_type_name (cardType), (int) cardType,
              daicmisc_get_card_type_name (pSc->cardType),
              (int) (pSc->cardType));
      return (EIO);
   }
   
   /* set the number of ports for the board; the Quadro has 4, all others have
    * only 1
    */
   pSc->nPorts = (pSc->cardType == DAIC_CARD_TYPE_QUADRO) ? 4 : 1;
   for (i = 1; (size_t) i < pSc->nPorts; ++i)
   {
      pPortData = pSc->apPortData [i];
      if (pPortData == NULL)
      {
         printf ("%s%d: Board is known to own at least %d ports, but no memory is allocated (internal error)\n",
                 pSc->szDriverName, pSc->iUnit, i + 1);
         return (EIO);
      }
      pPortData->ulBaseOffset = i * DAIC_MEMORY_SIZE_BASIC;
      pPortData->ulIntAckOffset =
         i * DAIC_MEMORY_SIZE_BASIC + DAIC_SHMEM_OFFSET_INTACK_BASIC;
      pPortData->ulStopCpuOffset =
         i * DAIC_MEMORY_SIZE_BASIC + DAIC_SHMEM_OFFSET_STOPCPU_BASIC;
      pPortData->ulStartCpuOffset =
         i * DAIC_MEMORY_SIZE_BASIC + DAIC_SHMEM_OFFSET_STARTCPU_BASIC;
   }
   
   DBG (LOG_TRACE, pSc->iUnit,
        "Loading primary bootstrap successful, board type %s (%d), %lu KB on-board memory",
        daicmisc_get_card_type_name (pSc->cardType), (int) (pSc->cardType),
        pSc->ulOnBoardMemory);

   return (0);
} /* daichw_bootload */





/**
 * Download the protocol firmware to the board including the protocol
 * configuration.
 *
 * @note Currently all ports of a multiport board will receive the same protocol
 *       firmware. 
 * @param pSc                   I/O: The device softc structure.
 * @param pabFirmware           I: The firmware code to download. The length of
 *                                 this data block is in nLenFirmware.
 * @param nLenFirmware          I: The length of the firmware code block at
 *                                 pabFirmware.
 * @param pConfig               I: The address of the board configuration data.
 *                                 This parameter is optional and maybe NULL.
 *
 * @retval 0                    The firmware download operation was successful.
 * @retval Else                 Errno value as an error result.
 */

int daichw_load_firmware
   (DaicSc_t                    *pSc,
    const u_int8_t              *pabFirmware,
    size_t                       nLenFirmware,
    const DaicBoardConfigData_t *pConfig)
{
   const char         *pszMsg;
   const u_int8_t     *p;
   size_t              n;
   size_t              size;
   u_int32_t           dwSwId;
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   int                 i;
   DaicPortData_t     *pPortData;
   int                 iTimeout;
   int                 iRes;
   u_int16_t           wSignature;
   unsigned            u;
   
   /* determine the readable string of the firmware version for printout */
   pszMsg = (const char *) &(pabFirmware [4]);
   for (p = (const u_int8_t *) pszMsg, n = 4;
        n < nLenFirmware && n < 74 && *p != '\0';
        ++p, ++n)
      ;
   if (n >= nLenFirmware || n >= 74)
   {
      printf ("%s%d: ERROR: Invalid firmware file, does not contain zero-terminated version string\n",
              pSc->szDriverName, pSc->iUnit);
      return (EINVAL);
   }
   
   /* behind the version string there should be a software id dword to be
    * written to the board while finishing the port configuration
    */
   if (&(p [4]) >= &(pabFirmware [nLenFirmware]))
   {
      printf ("%s%d: ERROR: Invalid firmware file, does not contain 4-byte softare id after version string\n",
              pSc->szDriverName, pSc->iUnit);
      return (EINVAL);
   }
   dwSwId = p [1] || (p [2] << 8) || (p [3] << 16) || (p [4] << 24);
   
   /* loop to download all ports of the board specified */
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   iRes = -1;
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Port %d: Start downloading firmware \"%s\", software id 0x%08lX",
           i, pszMsg, (unsigned long) dwSwId);

      pPortData = pSc->apPortData [i];
      
      /* loop to copy the firmware onto the board in 256 byte portions */
      iRes = 0;
      for (n = nLenFirmware, p = pabFirmware; n > 0; )
      {
         /* write the next data portion to board */
         size = (n >= 256) ? 256 : n;
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_FWBUF, p, size);
         
         /* tell the board to accept the written data */
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_CTRL,
             DAIC_TEST_MEMORY);
         
         /* wait for the acknowledge of the board (at most 2 sec.) */
         iTimeout = 2 * hz;
         while (iTimeout > 0)
         {
            if (bus_space_read_1
                   (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_CTRL) ==
                   DAIC_TEST_READY)
            {
               break;
            }
            (void) msleep (pPortData, &(pSc->mtxAccess), PZERO,
                           DAICMISC_WAIT_FOR_FIRMWARE_DATA, 1);
            --iTimeout;
         }
         if (bus_space_read_1
                (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_CTRL) !=
                DAIC_TEST_READY)
         {
            printf ("%s%d: Port %d: ERROR: Board does not accept firmware, current offset %p\n",
                    pSc->szDriverName, pSc->iUnit, i, p);
            iRes = EIO;
            break;
         }
         
         /* advance loop counters */
         n -= size;
         p += size;
      }
      if (iRes != 0)
      {
         break;
      }
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %d: Copying firmware successful, write configuration data",
           i);
      
      /* configure the board firmware */
      bus_space_set_region_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_TEI, 0,
          DAIC_SHMEM_LEN_BASIC_CONFIG);
      bus_space_set_region_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OAD1, 0,
          DAIC_SHMEM_LEN_OAD + DAIC_SHMEM_LEN_OSA + DAIC_SHMEM_LEN_SPID);
      bus_space_set_region_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OAD2, 0,
          DAIC_SHMEM_LEN_OAD + DAIC_SHMEM_LEN_OSA + DAIC_SHMEM_LEN_SPID);
      if (pConfig != NULL)
      {
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_TEI,
             pConfig->ucTei);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_NT2,
             pConfig->ucNt2);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_WATCHDOG,
             pConfig->ucWatchDog);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_PERMANENT,
             pConfig->ucPermanent);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_STABLE_L2,
             pConfig->ucStableL2);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_NOORDERCHECK,
             pConfig->ucNoOrderCheck);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_LOW_CHANNEL,
             pConfig->ucLowChannel);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_CRC4,
             pConfig->ucCrc4);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OAD1,
             pConfig->aucOad1, DAIC_SHMEM_LEN_OAD);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OSA1,
             pConfig->aucOsa1, DAIC_SHMEM_LEN_OSA);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SPID1,
             pConfig->aucSpid1, DAIC_SHMEM_LEN_SPID);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OAD2,
             pConfig->aucOad2, DAIC_SHMEM_LEN_OAD);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_OSA2,
             pConfig->aucOsa2, DAIC_SHMEM_LEN_OSA);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SPID2,
             pConfig->aucSpid2, DAIC_SHMEM_LEN_SPID);
         
         DBG (LOG_DEBUG, pSc->iUnit,
              "Port %d: Wrote provided configuration data:", i);
         DBG (LOG_DEBUG, pSc->iUnit,
              "   TEI value:            %u", (unsigned) (pConfig->ucTei));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   NT2 setting:          %u", (unsigned) (pConfig->ucNt2));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   Watchdog setting:     %u", (unsigned) (pConfig->ucWatchDog));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   Permanent connection: %u", (unsigned) (pConfig->ucPermanent));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   Stable layer 2:       %u", (unsigned) (pConfig->ucStableL2));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   No order check:       %u", (unsigned) (pConfig->ucNoOrderCheck));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   Low channel setting:  %u", (unsigned) (pConfig->ucLowChannel));
         DBG (LOG_DEBUG, pSc->iUnit,
              "   CRC4 setting:         %u", (unsigned) (pConfig->ucCrc4));
      }
      else
      {
         /* default configuration: automatic tei (fixed 0 for S2m), no nt2, no
          * L2 disconnect (permanent for S2m), default CRC4, no OAD, OSA or
          * SPID
          */
         if (pSc->cardType == DAIC_CARD_TYPE_S2M)
         {
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_TEI, 0x00);
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_NT2, 0x01);
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_STABLE_L2, 2);
         }
         else
         {
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_TEI, 0x01);
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_STABLE_L2, 1);
         }
         
         DBG (LOG_DEBUG, pSc->iUnit,
              "Port %d: Wrote default configuration data", i);
      }

      /* start the firmware code */
      DBG (LOG_DEBUG, pSc->iUnit, "Port %d: Start firmware code", i);
      bus_space_write_1 (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_CTRL,
                         DAIC_TEST_SKIP);
      if (i > 0)
      {
         /* Note: For Quadro boards only the CPU of the first board is really
          *       started at this point. So the remaining three must be started
          *       explicitly now.
          */
         /* Note2: Always assumed the Quadro really has four CPUs to start
          *        (unfortunately the author does not have one).
          */
         DBG (LOG_DEBUG, pSc->iUnit, "Port %d: Start CPU", i);
         bus_space_write_1 (t, h, pPortData->ulStartCpuOffset, 0);
      }
      
      /* wait for the firmware to respond with a valid signature (at most 5
       * sec.)
       */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %d: Waiting for firmware acknowledge through signature", i);
      iRes = -1;
      iTimeout = 5 * hz;
      wSignature = 0;
      while (iTimeout > 0)
      {
         wSignature =
            bus_space_read_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SIGNATURE);
         wSignature +=
            bus_space_read_1
               (t, h,
                pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SIGNATURE + 1) << 8;
         if (wSignature == DAIC_SIGNATURE_VALUE)
         {
            iRes = 0;
            break;
         }
         if (wSignature != 0)
         {
            printf ("%s%d: Port %d: ERROR: Got invalid signature value from firmware\n",
                    pSc->szDriverName, pSc->iUnit, i);
            iRes = EIO;
            break;
         }
         (void) msleep (pPortData, &(pSc->mtxAccess), PZERO,
                        DAICMISC_WAIT_FOR_FIRMWARE_START, 1);
         --iTimeout;
      }
      if (iRes == -1)
      {
         printf ("%s%d: Port %d: ERROR: Timeout waiting for firmware start acknowledgement\n",
                 pSc->szDriverName, pSc->iUnit, i);
         iRes = EIO;
         break;
      }
      if (iRes != 0)
      {
         break;
      }
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %d: Got signature of 0x%04X, firmware start successful",
           i, (unsigned int) wSignature);

      /* check if the port delivers the same number of channels as determined
       * during device probing
       */
      u =  bus_space_read_1
              (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_NUM_CHANNELS);
      if (u != pPortData->nBChns)
      {
         printf ("%s%d: Port %d: ERROR: Board delivers different number of B-channels (%u) than detected during device probing (%zu)\n",
                 pSc->szDriverName, pSc->iUnit, i, u, pPortData->nBChns);
         iRes = EIO;
         break;
      }
       
      /* let the board raise an interrupt for testing and wait for the response
       */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %d: Trigger READY_INT interrupt for testing purposes", i);
      (void) bus_space_read_1 (t, h, pPortData->ulIntAckOffset);
      bus_space_write_1 (t, h, pPortData->ulIntAckOffset, 0);
      pPortData->iIrqProbe = 1;
      u = bus_space_read_1
             (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_READY_INT);
      ++u;
      bus_space_write_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_READY_INT, u);
      (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), 2 * hz);
      if (pPortData->iIrqProbe == 1)
      {
         printf ("%s%d: Port %d: ERROR: Interrupt handling does not work\n",
                 pSc->szDriverName, pSc->iUnit, i);
         pPortData->iIrqProbe = 0;
         iRes = EIO;
         break;
      }
      pPortData->iIrqProbe = 0;
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %d: Received test interrupt, port working", i);
      
      /* finish port configuration */
      bus_space_write_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_INTERRUPT,
          pSc->resInfo.iIrq);
      bus_space_write_4
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SWID, dwSwId);
      bus_space_write_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_SET_CARDTYPE,
          (u_int8_t) (pSc->cardType) - 1);
      pPortData->iReadyInt = 0;
      
      pSc->nPortsInitialized = (size_t) (i + 1);
   }

   if (iRes == 0)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Download successful, board with %zu ports operable",
           pSc->nPorts);
   }
   else
   {
      pSc->nPortsInitialized = 0;
      DBG (LOG_ERROR, pSc->iUnit,
           "Download failed, board with %zu ports inoperable",
           pSc->nPorts);
   }
   return (iRes);
} /* daichw_load_firmware */





/**
 * Interrupt handler routine for daic devices.
 */

void daichw_intr
   (DaicSc_t *pSc)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   int                 i;
   DaicPortData_t     *pPortData;
   unsigned            u;
   
   mtx_lock (&(pSc->mtxAccess));
   
   if (pSc->fIntrActive != 0)
   {
      cv_broadcast (&(pSc->cvNotify));
      mtx_unlock (&(pSc->mtxAccess));
      DBG (LOG_ERROR, pSc->iUnit, "Nested interrupt handler call");
      return;
   }
   pSc->fIntrActive = 1;
   
   DBG (LOG_IRQ, pSc->iUnit, "Interrupt handler called");
        
   /* all ports of a board must be checked and handled separately */
   /* Note: As the interrupt routine is also called before a port is fully
    *       initialized, we must use pSc->nPorts, not pSc->nPortsInitialized.
    */
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   for (i = 0; (size_t) i < pSc->nPorts; ++i)
   {
      /* address the data for the current port */
      pPortData = pSc->apPortData [i];
      
      /* check if this board and port caused the interrupt */
      if (bus_space_read_1 (t, h, pPortData->ulIntAckOffset) == 0)
      {
         DBG (LOG_IRQ, pSc->iUnit,
              "Port %d: Interrupt did not originate from here", i);
         continue;
      }
      
      /* check for active board state */
      if (pSc->state < DAIC_BOARD_STATE_RUNNING)
      {
         /* as the board is currently not in normal operation, clear any event
          * possibly set
          */
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ, 0);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RC, 0);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_IND, 0);
         bus_space_write_1 (t, h, pPortData->ulIntAckOffset, 0);

         /* this must be an irq test */
         if (pPortData->iIrqProbe > 0)
         {
            ++(pPortData->iIrqProbe);
         }
         
         DBG (LOG_IRQ, pSc->iUnit, "Port %d: Got test interrupt", i);
         continue;
      }
      
      /* check if there is an rc for a completed (or failed) request */
      u = bus_space_read_1
             (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RC);
      if (u != 0)
      {
         daichw_handle_rc (pSc, pPortData, u);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RC, 0);
      }
      
      /* check if there is an indication */
      u = bus_space_read_1
             (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_IND);
      if (u != 0)
      {
         daichw_handle_ind (pSc, pPortData, u);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_IND, 0);
      }
      
      /* acknowledge and clear the interrupt */
      bus_space_write_1 (t, h, pPortData->ulIntAckOffset, 0);

      /* if there are requests in the request queue, try to send the next one */
      daichw_rq_handle_next (pSc, pPortData);
   }
   
   DBG (LOG_IRQ, pSc->iUnit, "Interrupt handler finished");
   
   pSc->fIntrActive = 0;
   
   cv_broadcast (&(pSc->cvNotify));
   mtx_unlock (&(pSc->mtxAccess));
   
} /* daichw_intr */





/**
 * Try to send the next request pending in the request queue.
 *
 * This function will check if there is at least one request pending in the
 * request queue for the controller specified through its port data pointer. If
 * there is no request, nothing is done.
 *
 * If the queue is not empty, the function first checks if the port may accept
 * another request. If this is the case, the request is sent to the board and
 * the corresponding queue entry is released.
 *
 * Else we must ensure that the board will tell us when it is ready to receive
 * another request. This can be done by forcing the board to raise a ready
 * interrupt. But if there is already a ready interrupt pending, we do not need
 * to do anything now. We just wait for this pending interrupt.
 *
 * @attention This function must be called with C4B privilege level or within
 *            interrupt context.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The port for the desired operation.
 *
 * @return Nothing.
 */

void daichw_rq_handle_next
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   u_int8_t            b;
   struct mbuf        *pmb;
   DaicReqData_t      *pReqData;
   int                 fSuppress;
   unsigned            uRawReq;
   
   /* check for non-empty request queue */
   if (pPortData->reqQueue.ifq_len == 0)
   {
      DBG (LOG_IRQ, pSc->iUnit, "Port %u: No (more) request in queue",
           pPortData->uPortIdx);
      return;
   }
   
   /* check if the controller is able to handle another request */
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   if (bus_space_read_1
          (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ) != 0)
   {
      /* controller is still busy; if not in an assign request and not already
       * done request a ready interrupt to tell us when it is not busy any more
       */
      if (! pPortData->fAssignPending && pPortData->iReadyInt == 0)
      {
         ++(pPortData->iReadyInt);
         b = bus_space_read_1
                (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_READY_INT);
         ++b;
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_READY_INT, b);
         
         DBG (LOG_IRQ, pSc->iUnit, "Port %u: READY_INT requested",
              pPortData->uPortIdx);
      }
      else
      {
         DBG (LOG_IRQ, pSc->iUnit,
              "Port %u: Busy with Assign-Request or READY_INT pending",
              pPortData->uPortIdx);
      }
      return;
   }
   
   /* extract the next request message */
   _IF_DEQUEUE (&(pPortData->reqQueue), pmb);
   if (pmb == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Got no mbuf although the request queue was not empty",
           pPortData->uPortIdx);
      return;
   }
   pReqData = mtod (pmb, DaicReqData_t *);
   fSuppress = 0;
   
   /* check if the message belongs to a signaling instance that is already in
    * disconnected state (if the request is not a Remove-Request that must
    * always be passed to the controller)
    */
   if (daicdisp_got_hangup_for_sig_id (pPortData, pReqData->bReqId) &&
       pReqData->bReq != DAIC_SIG_REMOVE)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: SIG-Request 0x%02X for signalling id %u will be suppressed because of hangup",
           pPortData->uPortIdx,
           (unsigned) (pReqData->bReq), (unsigned) (pReqData->bReqId));
      fSuppress = 1;
   }
   /* check if the message belongs to a network instance that is already in
    * disconnected state (if the request is not a Remove-Request that must
    * always be passed to the controller)
    */
   if (daicdisp_got_disconnect_for_net_channel
          (pPortData, pReqData->bReqId, pReqData->bReqCh) &&
       pReqData->bReq != DAIC_NL_REMOVE &&
       pReqData->bReq != DAIC_NL_DISC_ACK)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: NL-Request 0x%02X for network id %u, channel %u will be suppressed because of disconnect",
           pPortData->uPortIdx,
           (unsigned) (pReqData->bReq), (unsigned) (pReqData->bReqId),
           (unsigned) (pReqData->bReqCh));
      fSuppress = 1;
   }
   /* if the request is an assign request set a port global flag to not direct
    * the board to generate unnecessary ready interrupts (see check above in
    * this function)
    */
   /* Note: All assign requests share the same numerical value. And all global
    *       ids to request a specific id have their lower 5 bits set to zero.
    */
   if (! fSuppress &&
       pReqData->bReq == DAIC_SIG_ASSIGN &&
       (pReqData->bReqId & 0x1F) == 0)
   {
      pPortData->uAssignReqQualification =
         (unsigned) (pReqData->bAssignReqQualification);
      pPortData->uAssignReqPlci =
         (unsigned) (pReqData->wAssignReqPlci);
      pPortData->fAssignPending = 1;
   }

   /* if the request shall be suppressed, there is either a Hangup-Indication or
    * a NL-Disconnect-Indication pending. In order to let these messages be
    * handled, the PLCI or NCCI must receive a dummy return code of O.K. Else
    * the PLCI or NCCI will wait forever and will not handle any further
    * messages. NL-Data-Requests must be handled specially, because they may
    * require more than one return code.
    */
   if (fSuppress)
   {
      daichw_send_dummy_rc (pPortData, pReqData);
   }
   /* transfer all data from the mbuf to the controller registers */
   else
   {
      bus_space_write_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQID,
          pReqData->bReqId);
      bus_space_write_1
         (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQCH,
          pReqData->bReqCh);

      /* if the message is a NL-[E|U|B]Data-Request, the request may lead to
       * several real controller requests
       */
      uRawReq = pReqData->bReq & ~DAIC_NL_CMDBIT_MASK;
      if (pmb->m_next != NULL &&
          (uRawReq == DAIC_NL_DATA ||
           uRawReq == DAIC_NL_EDATA ||
           uRawReq == DAIC_NL_UDATA ||
           uRawReq == DAIC_NL_BDATA))
      {
         size_t nLenTotal;
         size_t nLenNow;
         
         nLenTotal = pmb->m_next->m_len;
         if ((size_t) (pReqData->wDataLength) >= nLenTotal)
         {
            nLenNow = 0;
         }
         else
         {
            nLenNow = nLenTotal - (size_t) (pReqData->wDataLength);
            if (nLenNow > DAIC_SHMEM_LEN_RXBUFFER)
            {
               nLenNow = DAIC_SHMEM_LEN_RXBUFFER;
            }
         }
         
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER,
             nLenNow & 0xFF);
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER + 1,
             (nLenNow & 0xFF00) >> 8);
         bus_space_write_region_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER + 2,
             mtod (pmb->m_next, u_int8_t *) + pReqData->wDataLength, nLenNow);

         pReqData->wDataLength += (u_int16_t) nLenNow;
         
         if ((size_t) (pReqData->wDataLength) < nLenTotal)
         {
            uRawReq = DAIC_NL_MDATA;
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ, uRawReq);

            DBG (LOG_DEBUG, pPortData->iUnit,
                 "Port %u: Request 0x%02X for id %u, channel %u sent to controller, data length %zu, %u/%zu",
                 pPortData->uPortIdx, uRawReq,
                 (unsigned) (pReqData->bReqId), (unsigned) (pReqData->bReqCh),
                 nLenNow, (unsigned) (pReqData->wDataLength), nLenTotal);

            /* still some bytes to send, put mbuf back to front of message queue
             */
            _IF_PREPEND (&(pPortData->reqQueue), pmb);
            return;
         }
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ,
             pReqData->bReq);

         DBG (LOG_DEBUG, pPortData->iUnit,
              "Port %u: Request 0x%02X for id %u, channel %u sent to controller, data length %zu, %u/%zu",
              pPortData->uPortIdx, (unsigned) (pReqData->bReq),
              (unsigned) (pReqData->bReqId), (unsigned) (pReqData->bReqCh),
              nLenNow, (unsigned) (pReqData->wDataLength), nLenTotal);
      }
      else
      {
         if (pReqData->wDataLength == 0)
         {
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER, 0);
            bus_space_write_1
               (t, h,
                pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER + 1, 0);
         }
         else
         {
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER,
                pReqData->wDataLength & 0xFF);
            bus_space_write_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER + 1,
                (pReqData->wDataLength & 0xFF00) >> 8);
            bus_space_write_region_1
               (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_XBUFFER + 2,
                (u_int8_t *) pReqData + sizeof (*pReqData),
                pReqData->wDataLength);
         }
         bus_space_write_1
            (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ,
             pReqData->bReq);

         DBG (LOG_DEBUG, pPortData->iUnit,
              "Port %u: Request 0x%02X for id %u, channel %u, data length %u sent to controller",
              pPortData->uPortIdx, (unsigned) (pReqData->bReq),
              (unsigned) (pReqData->bReqId), (unsigned) (pReqData->bReqCh),
              (unsigned) (pReqData->wDataLength));
      }
   }
   
   /* now finally release the mbuf as it is not needed any more */
   kcapi_free_mbuf (pmb);
   
} /* daichw_rq_handle_next */





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





/**
 * Interrupt handler function for return codes.
 *
 * Return codes are passed to the CAPI manager and are not handled in interrupt
 * context. In this function an mbuf is used to store all return code data. The
 * CAPI manager will return this mbuf back to this driver out of interrupt
 * context.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The data for the addressed port of the
 *                                 board.
 * @param uRcVal                I: The return code fetched from the board before
 *                                 calling this function.
 *
 * @return Nothing.
 */

static void daichw_handle_rc
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uRcVal)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   struct mbuf        *pmb;
   DaicRcData_t       *pRcData;

   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   
   /* the ready interrupt just tells us that the board may accept another
    * request, will be done by the caller
    */
   if (uRcVal == DAIC_RC_READY_INT)
   {
      DBG (LOG_IRQ, pSc->iUnit, "Port %u: Got READY_INT",
           pPortData->uPortIdx);
      return;
   }
   
   /* get a new mbuf to forward the return code data to the CAPI manager */
   pmb = kcapi_get_ctlr_notify_mbuf (sizeof (DaicRcData_t));
   if (pmb == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Out of mbufs for return code 0x%02X, id %u, channel %u",
           pPortData->uPortIdx, uRcVal,
           (unsigned)
              bus_space_read_1
                 (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RCID),
           (unsigned)
              bus_space_read_1
                 (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RCCH));
   }
   else
   {
      /* fill the return code data structure and forward it to the CAPI manager
       */
      pRcData = mtod (pmb, DaicRcData_t *);
      pRcData->bMagic = DAIC_MBUF_MAGIC_RC;
      pRcData->bRc    = (u_int8_t) uRcVal;
      pRcData->bRcId  = 
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RCID);
      pRcData->bRcCh  =
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RCCH);
      pRcData->bReqSuppressed = 0;

      /* if this is an Assign-Request return code, we must restore the setting
       * of the originator that sent the request
       */
      if (uRcVal >= DAIC_RC_ASSIGN_MASK && uRcVal <= DAIC_RC_ASSIGN_OK)
      {
         pRcData->bAssignReqQualification =
            (u_int8_t) (pPortData->uAssignReqQualification);
         pRcData->wAssignReqPlci =
            (u_int16_t) (pPortData->uAssignReqPlci);
      }
      else
      {
         pRcData->bAssignReqQualification = DAIC_ID_QUALIFY_UNUSED;
         pRcData->wAssignReqPlci = 0;
      }

      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: Got return code 0x%02X, id %u, channel %u",
           pPortData->uPortIdx, (unsigned) (pRcData->bRc),
           (unsigned) (pRcData->bRcId), (unsigned) (pRcData->bRcCh));

      mtx_unlock (&(pSc->mtxAccess));
      kcapi_ctlr_receive_notify_message (pPortData->uUniqueCapiCtlrNum, pmb);
      mtx_lock (&(pSc->mtxAccess));
   }
   
   /* an assign request requires to clear the request value after handling */
   if (uRcVal >= DAIC_RC_ASSIGN_MASK && uRcVal <= DAIC_RC_ASSIGN_OK)
   {
      bus_space_write_1 (t, h,
                         pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_REQ, 0);
      pPortData->fAssignPending = 0;
   }
   
} /* daichw_handle_rc */





/**
 * Interrupt handler function for indications.
 *
 * Indications are not handled in interrupt context. All data is collected from
 * the board and stored into an mbuf. This mbuf is then passed to the CAPI
 * manager to let its thread call the daic driver with this message again.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The data for the addressed port of the
 *                                 board.
 * @param uIndVal               I: The indication code fetched from the board
 *                                 before calling this function.
 *
 * @return Nothing.
 */

static void daichw_handle_ind
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uIndVal)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   size_t              nLen;
   struct mbuf        *pmb;
   DaicIndData_t      *pIndData;
   
   t = pSc->resInfo.memTag;
   h = pSc->resInfo.memHandle;
   
   /* determine the length of the data for the indication */
   nLen = bus_space_read_1 (t, h,
                            pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RBUFFER);
   nLen += bus_space_read_1 (t, h,
                             pPortData->ulBaseOffset +
                                DAIC_SHMEM_OFFSET_RBUFFER + 1) << 8;
   if (nLen > 270)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Got indication 0x%02X with %zu bytes in the receive buffer, use only 270 bytes",
           pPortData->uPortIdx, uIndVal, nLen);
      nLen = 270;
   }
   
   /* get a new mbuf to forward the indication data to the CAPI manager */
   pmb = kcapi_get_ctlr_notify_mbuf (sizeof (DaicIndData_t) + nLen);
   if (pmb == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Out of mbufs for indication 0x%02X, id %u, channel %u, buffer length %zu",
           pPortData->uPortIdx, uIndVal,
           (unsigned)
              bus_space_read_1
                 (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_INDID),
           (unsigned)
              bus_space_read_1
                 (t, h, pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_INDCH),
           nLen);
   }
   else
   {
      /* fill the indication data structure */
      pIndData = mtod (pmb, DaicIndData_t *);
      pIndData->bMagic = DAIC_MBUF_MAGIC_IND;
      pIndData->bInd = (u_int8_t) uIndVal;
      pIndData->bIndId = 
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_INDID);
      pIndData->bIndCh =
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_INDCH);
      pIndData->bMInd =
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_MIND);
      pIndData->wMLength =
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_MLENGTH);
      pIndData->wMLength +=
         bus_space_read_1 (t, h,
                           pPortData->ulBaseOffset +
                              DAIC_SHMEM_OFFSET_MLENGTH + 1) << 8;
      pIndData->wDataLength = (u_int16_t) nLen;
      if (nLen > 0)
      {
         bus_space_read_region_1
            (t, h,
             pPortData->ulBaseOffset + DAIC_SHMEM_OFFSET_RBUFFER + 2,
             (u_int8_t *) pIndData + sizeof (*pIndData),
             nLen);
      }
      pmb->m_len = sizeof (DaicIndData_t) + nLen;

      /* A hangup or disconnect indication (signalling or network) requires
       * special treatment. In order to not send any more messages for the now
       * hung up or disconnected id to the board, we must register this fact in
       * the corresponding PLCI or NCCI data structure.
       */
      if (uIndVal == DAIC_SIG_HANGUP && pIndData->bIndCh == 0)
      {
         daicdisp_register_hangup_for_sig_id (pPortData, pIndData->bIndId);
      }
      else if (uIndVal == DAIC_NL_DISC && pIndData->bIndCh != 0)
      {
         daicdisp_register_disconnect_for_net_channel
            (pPortData, pIndData->bIndId, pIndData->bIndCh);
      }
      
      DBG (LOG_DEBUG, pSc->iUnit,
           "Port %u: Got indication 0x%02X, id %u, channel %u, buffer length %zu",
           pPortData->uPortIdx, (unsigned) (pIndData->bInd),
           (unsigned) (pIndData->bIndId), (unsigned) (pIndData->bIndCh), nLen);

      /* forward it to the CAPI manager */
      mtx_unlock (&(pSc->mtxAccess));
      kcapi_ctlr_receive_notify_message (pPortData->uUniqueCapiCtlrNum, pmb);
      mtx_lock (&(pSc->mtxAccess));
   }
   
} /* daichw_handle_ind */





/**
 * Send a dummy O.K. return code to the PLCI or NCCI originating a request.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param pReqData              I: The request data the return code shall be
 *                                 sent for.
 *
 * @return Nothing.
 */

static void daichw_send_dummy_rc
   (DaicPortData_t      *pPortData,
    const DaicReqData_t *pReqData)
{
   struct mbuf  *pmb;
   DaicRcData_t *pRcData;

   /* get a new mbuf for the return code data */
   pmb = kcapi_get_ctlr_notify_mbuf (sizeof (DaicRcData_t));
   if (pmb == NULL)
   {
      DBG (LOG_ERROR, pPortData->iUnit,
           "Port %u: Out of mbufs for dummy return code for request 0x%02X, id %u, channel %u",
           pPortData->uPortIdx, (unsigned) (pReqData->bReq),
           (unsigned) (pReqData->bReqId), (unsigned) (pReqData->bReqCh));
   }
   else
   {
      /* fill the return code data structure and forward it to the CAPI manager
       */
      pRcData = mtod (pmb, DaicRcData_t *);
      pRcData->bMagic                  = DAIC_MBUF_MAGIC_RC;
      if (pReqData->bReq == DAIC_SIG_ASSIGN)
      {
         pRcData->bRc = DAIC_RC_ASSIGN_MASK | DAIC_RC_OUT_OF_RESOURCES;
      }
      else
      {
         pRcData->bRc = DAIC_RC_OK;
      }
      pRcData->bRcId                   = pReqData->bReqId;
      pRcData->bRcCh                   = pReqData->bReqCh;
      pRcData->bReqSuppressed          = 1;
      pRcData->bAssignReqQualification = pReqData->bAssignReqQualification;
      pRcData->wAssignReqPlci          = pReqData->wAssignReqPlci;

      DBG (LOG_DEBUG, pPortData->iUnit,
           "Port %u: Send dummy return code 0x%02X, id %u, channel %u, request 0x%02X",
           pPortData->uPortIdx, (unsigned) (pRcData->bRc),
           (unsigned) (pRcData->bRcId), (unsigned) (pRcData->bRcCh),
           (unsigned) (pReqData->bReq));

      mtx_unlock (&(pPortData->pSc->mtxAccess));
      kcapi_ctlr_receive_notify_message (pPortData->uUniqueCapiCtlrNum, pmb);
      mtx_lock (&(pPortData->pSc->mtxAccess));
   }
   
} /* daichw_send_dummy_rc */
