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

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

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

/* Import includes */

#define __IX1A_SHM__

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





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





/** @name Values for the F/V flag preceeding CAPI messages in the shared memory */
/** @{ */

/** Flag value for a valid CAPI message following the flag word. */
#define IX1A_SHM_FV_FLAG_VALID  ((u_int16_t) 'V')

/** Flag value for a free CAPI message buffer position behind the flag word. */
#define IX1A_SHM_FV_FLAG_FREE   ((u_int16_t) 'F')

/** @} */



/** @name Values for the NCCI flow control table entries */
/** @{ */

/** PLCI/NCCI can accept (at least) one more Data-B3-Request. */
#define IX1A_SHM_PLNC_OK        ((u_int16_t) 'P')

/** PLCI/NCCI cannot accept any more Data-B3-Request, queue full. */
#define IX1A_SHM_PLNC_FULL      ((u_int16_t) 'N')

/** @} */



/** @name Commands for a CAPI call to or delivery from a board */
/** @{ */

/** Command for a CAPI call to a board. */
#define IX1A_CAPI_CMD_TO_BOARD          0x01

/** Command for a CAPI delivery from a board. */
#define IX1A_CAPI_CMD_FROM_BOARD        0x10

/** @} */



/** @name Sub-commands for a CAPI call to or delivery from a board */
/** @{ */

/** Sub-command for CAPI_REGISTER. */
#define IX1A_CAPI_CALL_API_REGISTER             0x01

/** Sub-command for CAPI_RELEASE. */
#define IX1A_CAPI_CALL_API_RELEASE              0x02

/** Sub-command for CAPI_PUT_MESSAGE (only Data-B3-Request). */
#define IX1A_CAPI_CALL_API_PUT_MESSAGE          0x03

/** Sub-command for CAPI_GET_MESSAGE. */
#define IX1A_CAPI_CALL_API_GET_MESSAGE          0x04

/** Sub-command for CAPI_PUT_MESSAGE (all but Data-B3-Request). */
#define IX1A_CAPI_CALL_API_PUT_SMALL_MESSAGE    0x0A

/** @} */



/**
 * Layout of a CAPI function call message to a board.
 *
 * It is used for CAPI_REGISTER and CAPI_RELEASE calls. The two calls use
 * different fields for their parameters, see the comments for the respective
 * fields. Where no comment is made for a specifid call type, the field is not
 * used for it and should be set to null.
 */
typedef struct
{
   u_int8_t bRegAl;             /**<
                                 * CAPI_REGISTER: Controller-specific
                                 * application id.
                                 */
   u_int8_t bCommand;           /**<
                                 * Call type (0x01 for CAPI_REGISTER, 0x02 for
                                 * CAPI_RELEASE).
                                 */
   u_int16_t wRegBX;            /**< Not used, set to null. */
   u_int16_t wRegES;            /**< Not used, set to null. */
   u_int16_t wRegCX;            /**<
                                 * CAPI_REGISTER: Number of messages, should be
                                 * set to 1. Formular is: <Total no. messages>
                                 * = wRegCX + wRegDX.
                                 */
   u_int16_t wRegDX;            /**<
                                 * CAPI_REGISTER: Number of B3-connections.
                                 * CAPI_RELEASE: Controller-specific
                                 * application id.
                                 */
   u_int16_t wRegSI;            /**<
                                 * CAPI_REGISTER: Number of Data-B3 blocks per
                                 * B3-connection.
                                 */
   u_int16_t wRegDI;            /**<
                                 * CAPI_REGISTER: Maximum size of Data-B3
                                 * blocks.
                                 */
} __packed Ix1aCapiRegs_t;

/** Version information for CAPI application registration at a board. */
typedef struct
{
   u_int8_t bLength;            /**<
                                 * Length of the structure excluding length
                                 * byte.
                                 */
   u_int8_t bVerMajor;          /**< Major software version. */
   u_int8_t bVerMinor;          /**< Minor software version. */
   u_int8_t bOsType;            /**< Operating system type. */
} __packed Ix1aCapiRegisterVersionData_t;

/**
 * Layout of a CAPI function call message to or from a board.
 *
 * In all cases this structure is followed by additional data, either of type
 * Ix1aCapiRegs_t (CAPI_REGISTER and CAPI_RELEASE) or a CAPI message (type
 * CAPIMsg_t, only relevant length), possibly followed by a Data-B3 block.
 * All parts must be aligned on word boundaries when written to shared memory.
 */
typedef struct
{
   u_int8_t  bCmd;              /**<
                                 * Command, either IX1A_CAPI_CMD_TO_BOARD (CAPI
                                 * command to board) or
                                 * IX1A_CAPI_CMD_FROM_BOARD (CAPI command from
                                 * board).
                                 */
   u_int8_t  bSubCmd;           /**<
                                 * CAPI function call identifier, one of
                                 * IX1A_CAPI_CALL_*.
                                 */
   u_int16_t wLength;           /**<
                                 * Total length of the message, including
                                 * command, sub-command, CAPI message and
                                 * possibly following Data-B3 block.
                                 */
} __packed Ix1aBoardCapiCallData_t;





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





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





/**
 * Determine firmware version from manufacturer string.
 *
 * This function is called to determine the firmware version number out of the
 * manufacturer string delivered from the board. This string contains the board
 * type the firmware is designed for and the version number thereafter.
 *
 * If the firmware version cannot be determined, a default version of 4.1 will
 * be used.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @return Nothing.
 */
static void ix1ashm_set_firmware_version
   (Ix1aSc_t *pSc);

/**
 * Send a CAPI message or similar message to a board.
 *
 * This function is called to send a CAPI message, a CAPI_REGISTER call or a
 * CAPI_RELEASE call to a board. The resulting message is written to the
 * board's shared memory at the current message write position.
 *
 * Before writing the message the board's status byte (at starting offset of
 * the shared memory) is checked to be sure the board is (still) working. If it
 * is not, an error message is printed on the console and the function call
 * fails with an appropriate errno value.
 *
 * If the board is (still) working, the message is written to the current
 * write position into the shared memory, if is is free. If it is not, the
 * board must be too busy to read the messages from the shared memory. The
 * caller must wait some time to repeat the request.
 *
 * If the message was written successfully, the board is told to accept it and
 * the shared memory write offset is incremented to the next grid position
 * within the write buffer (ring buffer).
 *
 * All message parts (call data and one or two following blocks) must start on
 * word boundaries. So if one of the first blocks ends on an odd address, a
 * dummy byte will be counted. But as the low level write routines know how to
 * write odd length data blocks, the buffers specified as parameters need not
 * be modified and need not be longer than the real number of bytes.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param pCallData             I/O: The discriminator to distinguish the
 *                                 desired operation (CAPI message or other
 *                                 operation). The length member will be
 *                                 modified to reflect the total message
 *                                 length.
 * @param pbMainMsg             I: The main message data, e.g. a CAPI message
 *                                 or application registration data. The length
 *                                 in bytes is stored in nLenMainMsg.
 * @param nLenMainMsg           I: The number of bytes to write for pbMainMsg.
 * @param pbAddData             I: An additional message part to write, e.g.
 *                                 a Data-B3 block. Its length is specified in
 *                                 nLenAddData. If no additional message should
 *                                 be sent, this parameter shall be NULL.
 * @param nLenAddData           I: The number of bytes to write for pbAddData.
 *                                 If no additional message part should be
 *                                 written, this parameter must be null.
 *
 * @retval 0                    The message was sent successfully.
 * @retval EBUSY                The board cannot accept any more message, the
 *                              send buffer is full.
 * @retval Else                 Failure, the message could not be sent. The
 *                              result is an appropriate errno value.
 */
static int ix1ashm_send_request
   (Ix1aSc_t                *pSc,
    Ix1aBoardCapiCallData_t *pCallData,
    u_int8_t                *pbMainMsg,
    size_t                   nLenMainMsg,
    u_int8_t                *pbAddData,
    size_t                   nLenAddData);

/**
 * Read a single board message from the current read position.
 *
 * @pre This function is only called, if there is a message at the current read
 *      position (F/V flag was checked before to be "valid").
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    The message was read successfully.
 * @retval Else                 Failure, the message could not be read from
 *                              shared memory.
 */
static int ix1ashm_read_one_message
   (Ix1aSc_t *pSc);

/**
 * Check if a board can accept another Data-B3-Request for an NCCI.
 *
 * Every IX1 board maintains a table for NCCI flow control in its shared
 * memory. This table must be queried whenever a new Data-B3-Request is to be
 * sent to the board. The message may only be sent, if the table entry for the
 * given NCCI allows it.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uNcci                 I: The NCCI value to query for.
 *
 * @retval 0                    The board allows to send another
 *                              Data-B3-Request for the NCCI.
 * @retval EBUSY                The NCCI is blocked, its send queue is full.
 * @retval Else                 Failure querying the table entry, the result is
 *                              an appropriate errno value.
 */
static int ix1ashm_check_ncci_fc
   (Ix1aSc_t *pSc,
    unsigned  uNcci);

/**
 * Check if a board's state is still set to "working".
 *
 * The purpose of this function is to read the board's state byte and check it
 * for validity. If a crashed board is detected, the only operation performed
 * is to write an error message to the console.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    The board is (still) working.
 * @retval EFAULT               The board is not working (crashed).
 * @retval Else                 Failure to get the board state, maybe it is
 *                              even more crashed than expected.                  
 */
static int ix1ashm_check_board_state
   (Ix1aSc_t *pSc);





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





/**
 * Setup shared memory pointers after successful firmware download.
 *
 * @pre This function only supports Basic, Octo and Multimodem boards. For
 *      Primary PCI boards the function ix1ashm_firmware_started_ppci() must be
 *      called.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    Setup was successful, board is ready for CAPI
 *                              message exchange, CAPI profile data set.
 * @retval Else                 Setup failed, board is not ready for CAPI
 *                              message exchange, CAPI profile data is in
 *                              undefined state. The result is an errno value.
 */

int ix1ashm_firmware_started
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   int                 reg;
   unsigned            u;
   int                 iRes;
   char               *pSrc;
   char               *pDst;
   
   /* check the firmware status byte (low byte of 1st word in shared memory,
    * little endian byte order)
    */
   /* Note: Here no Primary boards need to be handled. */
   iRes = ix1ashm_read_word (pSc, IX1A_SHM_OFS_CAPI_BOARD_STATE, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, error reading board status byte: %d",
           iRes);
      return (iRes);
   }
   if ((u & 0x00FF) != IX1A_FW_STATUS_WORKING)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board status byte is 0x%02X, 0x%02X expected",
           u & 0x00FF, IX1A_FW_STATUS_WORKING);
      return (EIO);
   }

   /* check the host heart beat word, must be null right after firmware
    * startup (interrupt routine will indirectly change this word for every
    * CAPI message read operation)
    */
   iRes = ix1ashm_read_word (pSc, IX1A_SHM_OFS_CAPI_BEAT_BOARD, &u);
   DBG (LOG_DEBUG, pSc->iUnit,
        "Board heart beat is %u after download",
        u);
   iRes = ix1ashm_read_word (pSc, IX1A_SHM_OFS_CAPI_BEAT_PC, &u);
   if (u != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, host heart beat not null after download (%u)",
           u);
      return (EIO);
   }

   /* set the current shared memory offset to the address needed in the next
    * read operation; the following operations read consecutive words from the
    * shared memory
    */
   iRes = ix1ashm_set_offset (pSc, IX1A_SHM_OFS_CAPI_MAX_MSG_LEN);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, error setting shared memory offset to start of parameter table: %d",
           iRes);
      return (iRes);
   }
   t = pSc->resInfo.ioTagData;
   h = pSc->resInfo.ioHandleData;
   reg = (pSc->cardType == IX1A_CARD_TYPE_BASIC_PCI) ? 0 : IX1A_REG_DATA_LOW;

   /* as here only non-Primary boards are handled, the F/V flag length must be
    * set to 2 bytes
    */
   pSc->uShmSizeFVFlag = sizeof (u_int16_t);
   
   /* determine the maximum total length of a CAPI message in the shared memory
    * read or write buffers, including the word for the preceeding F/V flag
    */
   pSc->uShmMsgLen = le16toh (bus_space_read_2 (t, h, reg)) +
                     pSc->uShmSizeFVFlag;

   /* determine the maximum number of PLCI/NCCIs the board supports */
   pSc->nNumNcci = (size_t) le16toh (bus_space_read_2 (t, h, reg));
   if (pSc->nNumNcci == 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board does not accept any NCCIs");
      return (EIO);
   }
   
   /* determine the maximum number of registered applications for the board */
   /* Note: The value read from the board gives the highest application number
    *       possible. We store the maximum number of applications to be
    *       registered, including the unused index 0. So we must use a value
    *       one higher than the value read, limited by the array count for the
    *       application id mapping.
    */
   pSc->nNumAppl = (size_t) le16toh (bus_space_read_2 (t, h, reg));
   if (pSc->nNumAppl < 1)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board does not accept any CAPI application");
      return (EIO);
   }
   ++(pSc->nNumAppl);
   if (pSc->nNumAppl > ARRAY_COUNT (pSc->auApplMap))
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Board can handle %zu applications, will be reduced to %u because of driver limit",
           pSc->nNumAppl, (unsigned) ARRAY_COUNT (pSc->auApplMap));
      pSc->nNumAppl = ARRAY_COUNT (pSc->auApplMap);
   }
   bzero (pSc->auApplMap, sizeof (pSc->auApplMap));
   
   /* determine the first and last address of CAPI messages in the read and
    * write buffers (four shared memory offset words)
    */
   pSc->uShmOfsFirstWriteMsg =
      (unsigned) le16toh (bus_space_read_2 (t, h, reg));
   pSc->uShmOfsLastWriteMsg =
      (unsigned) le16toh (bus_space_read_2 (t, h, reg));
   pSc->uShmOfsFirstReadMsg =
      (unsigned) le16toh (bus_space_read_2 (t, h, reg));
   pSc->uShmOfsLastReadMsg =
      (unsigned) le16toh (bus_space_read_2 (t, h, reg));

   /* initialise the current read pointer to the first CAPI message address */
   pSc->uShmOfsCurrReadMsg = pSc->uShmOfsFirstReadMsg;
   
   /* initialise the current write pointer to the first CAPI message address */
   pSc->uShmOfsCurrWriteMsg = pSc->uShmOfsFirstWriteMsg;
   
   /* read the board type from the shared memory to be sure, the Octo firmware
    * is only loaded to a real Octo board
    */
   iRes = ix1ashm_read_word (pSc, IX1A_SHM_OFS_CAPI_BOARD_TYPE, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, error reading board type from shared memory: %d",
           iRes);
      return (iRes);
   }
#if 0 /* XXX: Seems this at least now is not up-to-date anymore */
   if (u != (unsigned) (pSc->cardType))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, firmware delivers board type \"%s\" (%u), board configured as \"%s\" (%u)",
           ix1amisc_get_card_type_name ((Ix1aCardType_t) u), u,
           ix1amisc_get_card_type_name (pSc->cardType),
           (unsigned) (pSc->cardType));
      return (EIO);
   }
#else /* 0 */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read board type 0x%02X from shared memory position 0x%04X",
        u, IX1A_SHM_OFS_CAPI_BOARD_TYPE);
#endif /* 0 */

   /* set the CAPI manufacturer string from shared memory */
   /* Note: The target memory is larger than the size in the shared memory. */
   (void) ix1ashm_read_block_16 (pSc, IX1A_SHM_OFS_CAPI_MANUFACTURER,
                                 pSc->szManufacturer,
                                 IX1A_SHM_SIZE_CAPI_MANUFACTURER);
   pSc->szManufacturer [IX1A_SHM_SIZE_CAPI_MANUFACTURER] = '\0';
   
   /* determine the firmware version out of the manufacturer string */
   ix1ashm_set_firmware_version (pSc);

   /* read the version string from shared memory */
   /* Note: The target memory is of the same size as the shared memory block. */
   (void) ix1ashm_read_block_16 (pSc, IX1A_SHM_OFS_CAPI_VERSION,
                                 pSc->szVersionString,
                                 IX1A_SHM_SIZE_CAPI_VERSION);
   pSc->szVersionString [IX1A_SHM_SIZE_CAPI_VERSION - 1] = '\0';
   
   /* set the board serial number from shared memory */
   /* Note: The target memory is larger than the size in the shared memory. */
   (void) ix1ashm_read_block_16 (pSc, IX1A_SHM_OFS_CAPI_SERIAL_NUMBER,
                                 pSc->szSerialNo,
                                 IX1A_SHM_SIZE_CAPI_SERIAL_NUMBER);
   pSc->szSerialNo [IX1A_SHM_SIZE_CAPI_SERIAL_NUMBER] = '\0';
   for (pSrc = pSc->szSerialNo, pDst = pSc->szPureSerialNo;
        *pSrc != '\0';
        ++pSrc)
   {
      if (isdigit (*pSrc))
      {
         *(pDst++) = *pSrc;
      }
   }
   *pDst = '\0';
   
   /* set the CAPI profile buffer from shared memory, must be filled up with
    * null bytes
    */
   bzero (&(pSc->abCapiProfile [4]), sizeof (pSc->abCapiProfile) - 4);
   iRes = ix1ashm_read_block_16 (pSc, IX1A_SHM_OFS_CAPI_PROFILE,
                                 &(pSc->abCapiProfile [2]),
                                 IX1A_SHM_SIZE_CAPI_PROFILE);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, error reading CAPI profile buffer: %d",
           iRes);
      return (iRes);
   }
   
   DBG (LOG_INFO, pSc->iUnit, "Board prepared for operation:");
   DBG (LOG_INFO, pSc->iUnit, "   Manufacturer string:   %s",
        pSc->szManufacturer);
   DBG (LOG_INFO, pSc->iUnit, "   Version string:        %s",
        pSc->szVersionString);
   DBG (LOG_INFO, pSc->iUnit, "   Serial number:         %s (%s)",
        pSc->szSerialNo, pSc->szPureSerialNo);
   DBG (LOG_INFO, pSc->iUnit, "   CAPI message length:   0x%04X",
        pSc->uShmMsgLen);
   DBG (LOG_INFO, pSc->iUnit, "   Max. no. NCCIs:        %zu",
        pSc->nNumNcci);
   DBG (LOG_INFO, pSc->iUnit, "   Max. no. applications: %zu",
        pSc->nNumAppl);
   DBG (LOG_INFO, pSc->iUnit,
        "   Message read buffer:   0x%04X-0x%04X (%u messages)",
        pSc->uShmOfsFirstReadMsg, pSc->uShmOfsLastReadMsg,
        (pSc->uShmOfsLastReadMsg -
         pSc->uShmOfsFirstReadMsg) / pSc->uShmMsgLen + 1);
   DBG (LOG_INFO, pSc->iUnit,
        "   Message write buffer:  0x%04X-0x%04X (%u messages)",
        pSc->uShmOfsFirstWriteMsg, pSc->uShmOfsLastWriteMsg,
        (pSc->uShmOfsLastWriteMsg -
         pSc->uShmOfsFirstWriteMsg) / pSc->uShmMsgLen + 1);

   return (0);
} /* ix1ashm_firmware_started */





/**
 * Setup shared memory pointers after successful firmware download for the
 * Primary PCI.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    Setup was successful, board is ready for CAPI
 *                              message exchange, CAPI profile data set.
 * @retval Else                 Setup failed, board is not ready for CAPI
 *                              message exchange, CAPI profile data is in
 *                              undefined state. The result is an errno value.
 */

int ix1ashm_firmware_started_ppci
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t     t;
   bus_space_handle_t  h;
   unsigned            u;
   int                 iRes;
   char               *pSrc;
   char               *pDst;
   
   t = pSc->resInfo.memTagShm;
   h = pSc->resInfo.memHandleShm;

   /* check the firmware status byte (low byte of 1st word in shared memory,
    * little endian byte order)
    */
   iRes = ix1appci_shm_read_word (pSc, IX1A_SHM_OFS_CAPIPRIM_BOARD_STATE, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, error reading board status byte: %d",
           iRes);
      return (iRes);
   }
   if ((u & 0x00FF) != IX1A_FW_STATUS_WORKING)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board status byte is 0x%02X, 0x%02X expected",
           u & 0x00FF, IX1A_FW_STATUS_WORKING);
      return (EIO);
   }

   /* check the host heart beat word, must be null right after firmware
    * startup (interrupt routine will indirectly change this word for every
    * CAPI message read operation)
    */
   u = (unsigned) le32toh (bus_space_read_4
                              (t, h,
                               IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                  IX1A_SHM_OFS_CAPIPRIM_BEAT_BOARD));
   DBG (LOG_DEBUG, pSc->iUnit,
        "Board heart beat is %u after download",
        u);
   u = (unsigned) le32toh (bus_space_read_4
                              (t, h,
                               IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                  IX1A_SHM_OFS_CAPIPRIM_BEAT_PC));
   if (u != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, host heart beat not null after download (%u)",
           u);
      return (EIO);
   }

   /* as here only Primary boards are handled, the F/V flag length must be set
    * to 4 bytes
    */
   pSc->uShmSizeFVFlag = sizeof (u_int32_t);
   
   /* determine the maximum number of PLCI/NCCIs the board supports */
   pSc->nNumNcci =
      (size_t) bus_space_read_1
                  (t, h,
                   IX1A_SHM_OFS_CAPI_SHM_PRIM +
                      IX1A_SHM_OFS_CAPIPRIM_MAX_NUM_PLNC);
   if (pSc->nNumNcci == 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board does not accept any NCCIs");
      return (EIO);
   }
   
   /* determine the maximum number of registered applications for the board */
   /* Note: The value read from the board gives the highest application number
    *       possible. We store the maximum number of applications to be
    *       registered, including the unused index 0. So we must use a value
    *       one higher than the value read, limited by the array count for the
    *       application id mapping.
    */
   pSc->nNumAppl =
      (size_t) bus_space_read_1
                  (t, h,
                   IX1A_SHM_OFS_CAPI_SHM_PRIM +
                      IX1A_SHM_OFS_CAPIPRIM_MAX_APPL_ID);
   if (pSc->nNumAppl < 1)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to prepare board for operation, board does not accept any CAPI application");
      return (EIO);
   }
   ++(pSc->nNumAppl);
   if (pSc->nNumAppl > ARRAY_COUNT (pSc->auApplMap))
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Board can handle %zu applications, will be reduced to %u because of driver limit",
           pSc->nNumAppl, (unsigned) ARRAY_COUNT (pSc->auApplMap));
      pSc->nNumAppl = ARRAY_COUNT (pSc->auApplMap);
   }
   bzero (pSc->auApplMap, sizeof (pSc->auApplMap));
   
   /* determine the maximum total length of a CAPI message in the shared memory
    * read or write buffers, including the word for the preceeding F/V flag
    */
   pSc->uShmMsgLen =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_MAX_MSG_LEN)) +
      pSc->uShmSizeFVFlag;

   /* determine the first and last address of CAPI messages in the read and
    * write buffers (four shared memory offset words)
    */
   pSc->uShmOfsFirstWriteMsg =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_FIRST_WRITE_MSG));
   pSc->uShmOfsLastWriteMsg =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_LAST_WRITE_MSG));
   pSc->uShmOfsFirstReadMsg =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_FIRST_READ_MSG));
   pSc->uShmOfsLastReadMsg =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_LAST_READ_MSG));

   /* initialise the current read pointer to the first CAPI message address */
   pSc->uShmOfsCurrReadMsg = pSc->uShmOfsFirstReadMsg;
   
   /* initialise the current write pointer to the first CAPI message address */
   pSc->uShmOfsCurrWriteMsg = pSc->uShmOfsFirstWriteMsg;
   
   /* determine the offset of the NCCI flow control table within the shared
    * memory
    */
   pSc->uShmOfsPrimNcciFc =
      (unsigned) le32toh (bus_space_read_4
                             (t, h,
                              IX1A_SHM_OFS_CAPI_SHM_PRIM +
                                 IX1A_SHM_OFS_CAPIPRIM_NCCI_FC));
   
   /* set the CAPI manufacturer string from shared memory */
   /* Note: The target memory is larger than the size in the shared memory. */
   bus_space_read_region_1 (t, h,
                            IX1A_SHM_OFS_CAPI_SHM_PRIM +
                               IX1A_SHM_OFS_CAPIPRIM_MANUFACTURER,
                            pSc->szManufacturer,
                            IX1A_SHM_SIZE_CAPI_MANUFACTURER);
   pSc->szManufacturer [IX1A_SHM_SIZE_CAPI_MANUFACTURER] = '\0';
   
   /* determine the firmware version out of the manufacturer string */
   ix1ashm_set_firmware_version (pSc);

   /* read the version string from shared memory */
   /* Note: The target memory is of the same size as the shared memory block. */
   bus_space_read_region_1 (t, h,
                            IX1A_SHM_OFS_CAPI_SHM_PRIM +
                               IX1A_SHM_OFS_CAPIPRIM_VERSION,
                            pSc->szVersionString,
                            IX1A_SHM_SIZE_CAPI_VERSION);
   pSc->szVersionString [IX1A_SHM_SIZE_CAPI_VERSION - 1] = '\0';
   
   /* set the board serial number from shared memory */
   /* Note: The target memory is larger than the size in the shared memory. */
   bus_space_read_region_1 (t, h,
                            IX1A_SHM_OFS_CAPI_SHM_PRIM +
                               IX1A_SHM_OFS_CAPIPRIM_SERIAL_NUMBER,
                            pSc->szSerialNo,
                            IX1A_SHM_SIZE_CAPI_SERIAL_NUMBER);
   pSc->szSerialNo [IX1A_SHM_SIZE_CAPI_SERIAL_NUMBER] = '\0';
   for (pSrc = pSc->szSerialNo, pDst = pSc->szPureSerialNo;
        *pSrc != '\0';
        ++pSrc)
   {
      if (isdigit (*pSrc))
      {
         *(pDst++) = *pSrc;
      }
   }
   *pDst = '\0';
   
   /* set the CAPI profile buffer from shared memory, must be filled up with
    * null bytes
    */
   bzero (&(pSc->abCapiProfile [4]), sizeof (pSc->abCapiProfile) - 4);
   u = (unsigned) le32toh (bus_space_read_4
                           (t, h,
                            IX1A_SHM_OFS_CAPI_SHM_PRIM +
                               IX1A_SHM_OFS_CAPIPRIM_PROFILE_TABLE));
   bus_space_read_region_1 (t, h,
                            IX1A_SHM_OFS_CAPI_SHM_PRIM +
                               u +
                               IX1A_SHM_OFS_CAPIPRIM_PROFILE,
                            &(pSc->abCapiProfile [2]),
                            IX1A_SHM_SIZE_CAPI_PROFILE);
   
   DBG (LOG_INFO, pSc->iUnit, "Board prepared for operation:");
   DBG (LOG_INFO, pSc->iUnit, "   Manufacturer string:   %s",
        pSc->szManufacturer);
   DBG (LOG_INFO, pSc->iUnit, "   Version string:        %s",
        pSc->szVersionString);
   DBG (LOG_INFO, pSc->iUnit, "   Serial number:         %s (%s)",
        pSc->szSerialNo, pSc->szPureSerialNo);
   DBG (LOG_INFO, pSc->iUnit, "   CAPI message length:   0x%04X",
        pSc->uShmMsgLen);
   DBG (LOG_INFO, pSc->iUnit, "   Max. no. NCCIs:        %zu",
        pSc->nNumNcci);
   DBG (LOG_INFO, pSc->iUnit, "   Max. no. applications: %zu",
        pSc->nNumAppl);
   DBG (LOG_INFO, pSc->iUnit,
        "   Message read buffer:   0x%04X-0x%04X (%u messages)",
        pSc->uShmOfsFirstReadMsg, pSc->uShmOfsLastReadMsg,
        (pSc->uShmOfsLastReadMsg -
         pSc->uShmOfsFirstReadMsg) / pSc->uShmMsgLen + 1);
   DBG (LOG_INFO, pSc->iUnit,
        "   Message write buffer:  0x%04X-0x%04X (%u messages)",
        pSc->uShmOfsFirstWriteMsg, pSc->uShmOfsLastWriteMsg,
        (pSc->uShmOfsLastWriteMsg -
         pSc->uShmOfsFirstWriteMsg) / pSc->uShmMsgLen + 1);
   DBG (LOG_INFO, pSc->iUnit,
        "   NCCI flow control table offset: 0x%06X",
        pSc->uShmOfsPrimNcciFc);

   return (0);
} /* ix1ashm_firmware_started_ppci */





/**
 * Send a CAPI-Application-Register message to the board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uMaxLogicalConnections
 *                              I: The maximum number of logical connections
 *                                 the application wishes to handle.
 * @param uMaxBDataBlocks       I: The maximum number of unresponded incoming
 *                                 data blocks to be queued for the
 *                                 application.
 * @param uMaxBDataLen          I: The maximum length of a sent or received
 *                                 data block for a connection.
 * @param puApplID              I/O: On function entrance this parameter
 *                                 contains the CAPI manager assigned unique
 *                                 application id. On successful return the
 *                                 parameter value will be set to the board
 *                                 specific application id.
 *
 * @retval CAPI_OK              The CAPI application registration was
 *                              successful.
 * @retval Else                 CAPI application registration was unsuccessful.
 */

unsigned ix1ashm_app_register
   (Ix1aSc_t *pSc,
    unsigned  uMaxLogicalConnections,
    unsigned  uMaxBDataBlocks,
    unsigned  uMaxBDataLen,
    unsigned *puApplID)
{
   Ix1aBoardCapiCallData_t       callData;
   Ix1aCapiRegs_t                capiRegs;
   Ix1aCapiRegisterVersionData_t versionData;
   unsigned                      uId;
   size_t                        n;
   int                           iRes;
   
   /* determine the controller specific application id to use */
   /* Note: As the board is operable (else we do not get here), at least one
    *       application can be registered.
    */
   for (uId = (pSc->auApplMap [0] + 1) % (unsigned) (pSc->nNumAppl),
           n = pSc->nNumAppl;
        n > 0;
        uId = (uId + 1) % (unsigned) (pSc->nNumAppl), ++n)
   {
      /* entry with index null is not used for an application id */
      if (uId == 0)
      {
         continue;
      }
      
      /* if the value is null, the entry is currently unused --> ready */
      if (pSc->auApplMap [uId] == 0)
      {
         break;
      }
   }
   if (n == 0 || uId == 0)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Unable to register application id %u, already maximum of %u applications registered",
           *puApplID, (unsigned) (pSc->nNumAppl - 1));
      return (CRE_TOO_MANY_APPLICATIONS);
   }
   DBG (LOG_TRACE, pSc->iUnit,
        "Application id %u: Controller specific id %u allocated, perform registration",
        *puApplID, uId);
   
   /* prepare a registration message for the board */
   bzero (&callData, sizeof (callData));
   callData.bCmd    = IX1A_CAPI_CMD_TO_BOARD;
   callData.bSubCmd = IX1A_CAPI_CALL_API_REGISTER;
   bzero (&capiRegs, sizeof (capiRegs));
   capiRegs.bRegAl   = (u_int8_t) uId;
   capiRegs.bCommand = IX1A_CAPI_CALL_API_REGISTER;
   capiRegs.wRegCX   = 1;
   capiRegs.wRegDX   = (u_int16_t) uMaxLogicalConnections;
   capiRegs.wRegSI   = (u_int16_t) uMaxBDataBlocks;
   capiRegs.wRegDI   = (u_int16_t) uMaxBDataLen;
   bzero (&versionData, sizeof (versionData));
   versionData.bLength   =
      (u_int8_t) (sizeof (versionData) - sizeof (versionData.bLength));
   versionData.bVerMajor =
      (u_int8_t) 3 /*(pSc->aPortData [0].regParams.uManufacturerMajor)*/;
   versionData.bVerMinor =
      (u_int8_t) 1 /*(pSc->aPortData [0].regParams.uManufacturerMinor)*/;
   
   /* send the registration message to the board */
   iRes = ix1ashm_send_request
             (pSc, &callData,
              (u_int8_t *) &capiRegs, sizeof (capiRegs),
              (u_int8_t *) &versionData, sizeof (versionData));
   if (iRes != 0)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Error registering application id %u (controller specific id %u) at board: %d",
           *puApplID, uId, iRes);
      return ((iRes == EBUSY) ? CRE_BUSY : CRE_OS_RESOURCE_ERROR);
   }
   
   /* registration successful, mark the board specific application id as used */
   pSc->auApplMap [uId] = *puApplID;
   pSc->auApplMap [0] = uId;
   *puApplID = uId;
   
   DBG (LOG_INFO, pSc->iUnit,
        "Application id %u registered successfully, controller specific id %u",
        pSc->auApplMap [uId], *puApplID);
   return (CAPI_OK);
} /* ix1ashm_app_register */





/**
 * Send a CAPI-Application-Release message to the board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uCtlrApplID           I: The board specific application id for the
 *                                 release operation.
 *
 * @retval CAPI_OK              The CAPI release operation was successful.
 * @retval Else                 CAPI application release was unsuccessful.
 */

unsigned ix1ashm_app_release
   (Ix1aSc_t *pSc,
    unsigned  uCtlrApplID)
{
   Ix1aBoardCapiCallData_t callData;
   Ix1aCapiRegs_t          capiRegs;
   int                     iRes;
   
   /* check that the application id specified is valid */
   if (uCtlrApplID == 0 || (size_t) uCtlrApplID >= pSc->nNumAppl ||
       pSc->auApplMap [uCtlrApplID] == 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to release controller specific application id %u, id invalid or not registered",
           uCtlrApplID);
      return (CME_INVALID_APPLICATION_ID);
   }
   DBG (LOG_DEBUG, pSc->iUnit,
        "Application id %u is registered with controller specific id %u, perform release",
        pSc->auApplMap [uCtlrApplID], uCtlrApplID);
   
   /* prepare a release message for the board */
   bzero (&callData, sizeof (callData));
   callData.bCmd    = IX1A_CAPI_CMD_TO_BOARD;
   callData.bSubCmd = IX1A_CAPI_CALL_API_RELEASE;
   bzero (&capiRegs, sizeof (capiRegs));
   capiRegs.bCommand = IX1A_CAPI_CALL_API_RELEASE;
   capiRegs.wRegDX   = (u_int16_t) uCtlrApplID;
   
   /* send the registration message to the board */
   iRes = ix1ashm_send_request
             (pSc, &callData,
              (u_int8_t *) &capiRegs, sizeof (capiRegs), NULL, 0);
   if (iRes != 0)
   {
      DBG (LOG_INFO, pSc->iUnit,
           "Error releasing application id %u (controller specific id %u) at board: %d",
           pSc->auApplMap [uCtlrApplID], uCtlrApplID, iRes);
      pSc->auApplMap [uCtlrApplID] = 0;
      return ((iRes == EBUSY) ? CRE_BUSY : CRE_OS_RESOURCE_ERROR);
   }
   
   DBG (LOG_INFO, pSc->iUnit,
        "Application id %u released successfully, controller specific id %u",
        pSc->auApplMap [uCtlrApplID], uCtlrApplID);
   pSc->auApplMap [uCtlrApplID] = 0;
   
   return (CAPI_OK);
} /* ix1ashm_app_release */





/**
 * Send a general CAPI message to the board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uCtlrApplID           I: The board specific application ID for the
 *                                 message.
 * @param pmbMsg                I/O: The mbuf with the message to send. On
 *                                 success the mbuf is released before return,
 *                                 on failure the parent is still responsible
 *                                 for the mbuf.
 *
 * @retval CAPI_OK              The message was successfully sent to the
 *                              board, the mbuf is released.
 * @retval Else                 The message could not be sent to the board,
 *                              the mbuf is still allocated.
 */

unsigned ix1ashm_put_message
   (Ix1aSc_t    *pSc,
    unsigned     uCtlrApplID,
    struct mbuf *pmbMsg)
{
   Ix1aBoardCapiCallData_t callData;
   size_t                  nLenBackup;
   size_t                  nLen;
   int                     iRes;
   
   bzero (&callData, sizeof (callData));
   callData.bCmd = IX1A_CAPI_CMD_TO_BOARD;
   
   /* distinguish between Data-B3-Requests and other messages */
   if (CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *)) == CAPI_REQUEST (C_DATA_B3) &&
       pmbMsg->m_next != NULL)
   {
      /* check if the board can still accept another Data-B3-Request for the
       * NCCI of the message
       */
      iRes = ix1ashm_check_ncci_fc
                (pSc,
                 CAPI_GET_NCCI_FROM_CID
                    (CAPI_GET_CID (mtod (pmbMsg, CAPIMsg_t *))));
      if (iRes == EBUSY)
      {
         return (CME_PUT_QUEUE_FULL);
      }
      else if (iRes != 0)
      {
         return (CME_OS_RESOURCE_ERROR);
      }
      
      /* determine the real message length to send to the board; the board does
       * only accept 32bit Data-B3-Request, the qwData pointer must be left out
       */
      nLenBackup = pmbMsg->m_len;
      if (nLenBackup > sizeof (CAPIMsgHead_t) + sizeof (CAPIDataB332Req_t))
      {
         nLen = sizeof (CAPIMsgHead_t) + sizeof (CAPIDataB332Req_t);
      }
      else
      {
         nLen = nLenBackup;
      }
      C_PUT_WORD (mtod (pmbMsg, CAPIMsg_t *)->head.wLen, nLen);
      
      callData.bSubCmd = IX1A_CAPI_CALL_API_PUT_MESSAGE;
      iRes = ix1ashm_send_request
                (pSc, &callData,
                 mtod (pmbMsg, u_int8_t *), nLen,
                 mtod (pmbMsg->m_next, u_int8_t *), pmbMsg->m_next->m_len);

      /* if sending the message failed, we must restore the CAPI message length
       */
      if (iRes != 0)
      {
         C_PUT_WORD (mtod (pmbMsg, CAPIMsg_t *)->head.wLen, nLenBackup);
      }
   }
   else
   {
      callData.bSubCmd = IX1A_CAPI_CALL_API_PUT_SMALL_MESSAGE;
      iRes = ix1ashm_send_request
                (pSc, &callData,
                 mtod (pmbMsg, u_int8_t *), pmbMsg->m_len, NULL, 0);
   }
   
   if (iRes == EBUSY)
   {
      return (CME_BUSY);
   }
   else if (iRes != 0)
   {
      return (CME_OS_RESOURCE_ERROR);
   }
   return (CAPI_OK);
} /* ix1ashm_put_message */





/**
 * Read all available CAPI messages from the shared memory.
 *
 * This function is called within interrupt context to get all pending CAPI
 * messages from a board. The call will only be made in READY state of above.
 *
 * The messages are simply forwarded to the kernel CAPI manager.
 *
 * @param pSc                   I/O: The softc structure for the board to read
 *                                 from.
 *
 * @return Nothing.
 */

void ix1ashm_read_capi_messages
   (Ix1aSc_t *pSc)
{
   int      fNoMsgAvail;
   int      fMessageRead;
   int      iRes;
   unsigned u;
   
   /* update the watchdog to tell the board the driver is alive */
   
   /* loop to read all available messages from the board */
   fNoMsgAvail = 1;
   do
   {
      /* check for valid message at current read position in shared memory */
      iRes = ix1ashm_read_word (pSc, pSc->uShmOfsCurrReadMsg, &u);
      if (iRes != 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Unable to read CAPI message from board, error reading F/V flag: %d",
              iRes);
         break;
      }
      if (u == IX1A_SHM_FV_FLAG_VALID)
      {
         fMessageRead = 1;
         fNoMsgAvail  = 0;
         
         /* there is a message at the current read position, read it */
         iRes = ix1ashm_read_one_message (pSc);
         if (iRes != 0)
         {
            /* error message already printed */
            break;
         }
         
         /* now advance the current read pointer to the next grid position
          * (ring buffer)
          */
         if (pSc->uShmOfsCurrReadMsg >= pSc->uShmOfsLastReadMsg)
         {
            pSc->uShmOfsCurrReadMsg = pSc->uShmOfsFirstReadMsg;
         }
         else
         {
            pSc->uShmOfsCurrReadMsg += pSc->uShmMsgLen;
         }
         DBG (LOG_DEBUG, pSc->iUnit,
              "Shared memory read pointer advanced to 0x%06X",
              pSc->uShmOfsCurrReadMsg);
      }
      else
      {
         fMessageRead = 0;
      }
      
   } while (fMessageRead);
   
   /* if the last operation failed or if we did not get any message, check if
    * the board is still working
    */
   if (fNoMsgAvail || iRes != 0)
   {
      (void) ix1ashm_check_board_state (pSc);
   }
   
} /* ix1ashm_read_capi_messages */





/**
 * Board type independent function to set the board's current offset pointer
 * within the shared memory.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory offset to set.
 *
 * @retval 0                    The offset pointer was read successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_set_offset
   (Ix1aSc_t *pSc,
    unsigned  uAddr)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_set_offset (pSc, uAddr));
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_set_offset (pSc, uAddr));
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         /* the Primary board does not have something like an offset pointer */
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_set_offset */





/**
 * Board type independent function to read a word from a board's shared memory.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param puValue               O: On successful return the word read is
 *                                 delivered as an unsigned integer.
 *
 * @retval 0                    The data word was read successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_read_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned *puValue)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_read_word (pSc, uAddr, puValue));
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_read_word (pSc, uAddr, puValue));
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         return (ix1appci_shm_read_word (pSc, uAddr, puValue));

      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_read_word */





/**
 * Board type independent function to read a block from a board's shared
 * memory.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param pbData                I: The address for returning the data read.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 read.
 *
 * @retval 0                    The data block was read successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_read_block_16
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    u_int8_t *pbData,
    size_t    nLenData)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_read_block_16 (pSc, uAddr, pbData, nLenData));
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_read_block_16 (pSc, uAddr, pbData, nLenData));
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         return (ix1appci_shm_read_block (pSc, uAddr, pbData, nLenData));

      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_read_block_16 */





/**
 * Board type independent function to send a word to a board's shared memory.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param uValue                I: The word to write as an unsigned integer.
 *
 * @retval 0                    The data word was written successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_write_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned  uValue)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_write_word (pSc, uAddr, uValue));
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_write_word (pSc, uAddr, uValue));
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         return (ix1appci_shm_write_word (pSc, uAddr, uValue));

      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_write_word */





/**
 * Board type independent function to send a block to a board's shared memory.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param pbData                I: The address of the data block to write.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 write.
 *
 * @retval 0                    The data block was written successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_write_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_write_block_16 (pSc, uAddr, pbData, nLenData));
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_write_block_16 (pSc, uAddr, pbData, nLenData));
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         return (ix1appci_shm_write_block (pSc, uAddr, pbData, nLenData));

      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_write_block_16 */





/**
 * Board type independent function to compare a block to a board's shared
 * memory content.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param pbData                I: The address of the data block to write.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 write.
 * @param pfResult              O: The compare result (0 - failure, !0 -
 *                                 success).
 *
 * @retval 0                    The data block was compared successfully,
 *                              compare result is stored into *pfResult.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1ashm_compare_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData,
    int            *pfResult)
{
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
         return (ix1aisa_shm_compare_block_16
                    (pSc, uAddr, pbData, nLenData, pfResult));
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         return (ix1abpci_shm_compare_block_16
                    (pSc, uAddr, pbData, nLenData, pfResult));
         break;
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         /* we do not need this operation for the Primary board */
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
} /* ix1ashm_compare_block_16 */





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





/**
 * Determine firmware version from manufacturer string.
 *
 * This function is called to determine the firmware version number out of the
 * manufacturer string delivered from the board. This string contains the board
 * type the firmware is designed for and the version number thereafter.
 *
 * If the firmware version cannot be determined, a default version of 4.1 will
 * be used.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @return Nothing.
 */

static void ix1ashm_set_firmware_version
   (Ix1aSc_t *pSc)
{
   unsigned    uMajor;
   unsigned    uMinor;
   const char *pszBoardName;
   size_t      nLenBoardName;
   const char *p;
   unsigned    u1;
   unsigned    u2;
   size_t      n;
   
   uMajor = 4;
   uMinor = 1;
   
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_BASIC_PCI:
         pszBoardName = "Basic";
         break;
         
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         pszBoardName = "Octo";
         break;
         
      case IX1A_CARD_TYPE_MMOD_ISA:
         pszBoardName = "multi-modem";
         break;
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         pszBoardName = "Primary";
         break;
         
      default:
         pszBoardName = NULL;
         break;
   }
   if (pszBoardName != NULL)
   {
      nLenBoardName = strlen (pszBoardName);
      for (p = pSc->szManufacturer; *p != '\0'; ++p)
      {
         if (strncmp (p, pszBoardName, nLenBoardName) == 0)
         {
            break;
         }
      }
      if (*p != '\0')
      {
         p = index (p, 'V');
         if (p != NULL)
         {
            ++p;
            if (sscanf (p, "%u.%u", &u1, &u2) == 2)
            {
               uMajor = u1;
               uMinor = u2;
            }
         }
      }
   }
   
   /* set the CAPI manufacturer version for every port */
   for (n = 0; n < pSc->nPorts; ++n)
   {
      pSc->aPortData [n].regParams.uManufacturerMajor = uMajor;
      pSc->aPortData [n].regParams.uManufacturerMinor = uMinor;
   }
   
} /* ix1ashm_set_firmware_version */





/**
 * Send a CAPI message or similar message to a board.
 *
 * This function is called to send a CAPI message, a CAPI_REGISTER call or a
 * CAPI_RELEASE call to a board. The resulting message is written to the
 * board's shared memory at the current message write position.
 *
 * Before writing the message the board's status byte (at starting offset of
 * the shared memory) is checked to be sure the board is (still) working. If it
 * is not, an error message is printed on the console and the function call
 * fails with an appropriate errno value.
 *
 * If the board is (still) working, the message is written to the current
 * write position into the shared memory, if is is free. If it is not, the
 * board must be too busy to read the messages from the shared memory. The
 * caller must wait some time to repeat the request.
 *
 * If the message was written successfully, the board is told to accept it and
 * the shared memory write offset is incremented to the next grid position
 * within the write buffer (ring buffer).
 *
 * All message parts (call data and one or two following blocks) must start on
 * word boundaries. So if one of the first blocks ends on an odd address, a
 * dummy byte will be counted. But as the low level write routines know how to
 * write odd length data blocks, the buffers specified as parameters need not
 * be modified and need not be longer than the real number of bytes.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param pCallData             I/O: The discriminator to distinguish the
 *                                 desired operation (CAPI message or other
 *                                 operation). The length member will be
 *                                 modified to reflect the total message
 *                                 length.
 * @param pbMainMsg             I: The main message data, e.g. a CAPI message
 *                                 or application registration data. The length
 *                                 in bytes is stored in nLenMainMsg.
 * @param nLenMainMsg           I: The number of bytes to write for pbMainMsg.
 * @param pbAddData             I: An additional message part to write, e.g.
 *                                 a Data-B3 block. Its length is specified in
 *                                 nLenAddData. If no additional message should
 *                                 be sent, this parameter shall be NULL.
 * @param nLenAddData           I: The number of bytes to write for pbAddData.
 *                                 If no additional message part should be
 *                                 written, this parameter must be null.
 *
 * @retval 0                    The message was sent successfully.
 * @retval EBUSY                The board cannot accept any more message, the
 *                              send buffer is full.
 * @retval Else                 Failure, the message could not be sent. The
 *                              result is an appropriate errno value.
 */

static int ix1ashm_send_request
   (Ix1aSc_t                *pSc,
    Ix1aBoardCapiCallData_t *pCallData,
    u_int8_t                *pbMainMsg,
    size_t                   nLenMainMsg,
    u_int8_t                *pbAddData,
    size_t                   nLenAddData)
{
   size_t             nLenMainMsgReal;
   size_t             nLenAddDataReal;
   unsigned           uOfs;
   unsigned           u;
   int                iRes;
   bus_space_tag_t    t;
   bus_space_handle_t h;
   
   /* check the board's status byte for normal operation */
   if (ix1ashm_check_board_state (pSc) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to send message to board, not working");
      return (EIO);
   }
   
   /* check for the current write position to be free */
   iRes = ix1ashm_read_word (pSc, pSc->uShmOfsCurrWriteMsg, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to send message to board, error reading F/V flag: %d",
           iRes);
      return (iRes);
   }
   if (u != IX1A_SHM_FV_FLAG_FREE)
   {
      /* send queue is full, board must first accept some message, before a new
       * can be sent
       */
      return (EBUSY);
   }
   
   /* everything is fine, determine the aligned message portion's length values
    */
   /* Note: The call-data structure is assumed to be of even size. */
   if ((nLenMainMsg & 0x0001) == 0)
   {
      nLenMainMsgReal = nLenMainMsg;
   }
   else
   {
      nLenMainMsgReal = nLenMainMsg + 1;
   }
   if ((nLenAddData & 0x0001) == 0)
   {
      nLenAddDataReal = nLenAddData;
   }
   else
   {
      nLenAddDataReal = nLenAddData + 1;
   }
   pCallData->wLength = sizeof (*pCallData) +
                        nLenMainMsgReal + nLenAddDataReal;

   /* now write the message in two or three portions */
   uOfs = pSc->uShmOfsCurrWriteMsg + pSc->uShmSizeFVFlag;
   iRes = ix1ashm_write_block_16
             (pSc, uOfs, (u_int8_t *) pCallData, sizeof (*pCallData));
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to send message to board, error sending call data part: %d",
           iRes);
      return (iRes);
   }
   uOfs += sizeof (*pCallData);
   iRes = ix1ashm_write_block_16 (pSc, uOfs, pbMainMsg, nLenMainMsg);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to send message to board, error sending main part: %d",
           iRes);
      return (iRes);
   }
   if (pbAddData != NULL && nLenAddData != 0)
   {
      uOfs += nLenMainMsgReal;
      iRes = ix1ashm_write_block_16 (pSc, uOfs, pbAddData, nLenAddData);
      if (iRes != 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Unable to send message to board, error sending additional part: %d",
              iRes);
         return (iRes);
      }
   }
   
   /* now the complete message is written to the shared memory, mark its grid
    * position as filled
    */
   iRes = ix1ashm_write_word (pSc, pSc->uShmOfsCurrWriteMsg,
                              (unsigned) IX1A_SHM_FV_FLAG_VALID);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to send message to board, error setting F/V flag");
      return (iRes);
   }
   
   /* finally tell the board there is a new message by raising an interrupt */
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         t = pSc->resInfo.ioTagData;
         h = pSc->resInfo.ioHandleData;
         u = bus_space_read_1 (t, h, IX1A_REG_CMD2);
         u &= IX1A_CMD2_EISA_LEVEL_IRQ;
         bus_space_write_1 (t, h, IX1A_REG_CMD2, u | IX1A_CMD2_INT2BOARD);
         bus_space_write_1 (t, h, IX1A_REG_CMD2, u);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         bus_space_write_2 (pSc->resInfo.ioTagHeader,
                            pSc->resInfo.ioHandleHeader,
                            IX1A_REG_AMCC_OMB1,
                            0);
         break;
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         /* don't touch the CPUINTMASK register */
         /* set the interrupt (reset the interrupt bit), but don't touch the
          * other bits
          */
         (void) bus_space_read_4 (pSc->resInfo.memTagGal,
                                  pSc->resInfo.memHandleGal,
                                  IX1A_PPCI_GAL_INT);
         /* set interrupt bit 26 to null */
         bus_space_write_4 (pSc->resInfo.memTagGal,
                            pSc->resInfo.memHandleGal,
                            IX1A_PPCI_GAL_INT,
                            htole32 (~IX1A_PPCI_GAL_CPUINT_BIT));
         break;
         
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   DBG (LOG_DATAMSG, pSc->iUnit,
        "Message successfully sent to board, main part length %zu, additional part length %zu",
        nLenMainMsg, nLenAddData);
        
   /* the current memory grid position is filled, advance the write pointer to
    * the next grid position (ring buffer)
    */
   if (pSc->uShmOfsCurrWriteMsg >= pSc->uShmOfsLastWriteMsg)
   {
      pSc->uShmOfsCurrWriteMsg = pSc->uShmOfsFirstWriteMsg;
   }
   else
   {
      pSc->uShmOfsCurrWriteMsg += pSc->uShmMsgLen;
   }
   DBG (LOG_DATAMSG, pSc->iUnit,
        "Shared memory write pointer advanced to 0x%06X",
        pSc->uShmOfsCurrWriteMsg);

   return (0);
} /* ix1ashm_send_request */





/**
 * Read a single board message from the current read position.
 *
 * @pre This function is only called, if there is a message at the current read
 *      position (F/V flag was checked before to be "valid").
 *
 * To successfully read and handle a message from a board we must first read
 * enough data to determine the total message length and for CAPI messages the
 * message command (for Data-B3-Indications the board delivers only the 32bit
 * version, we always deliver the 64bit version). With this information we can
 * allocate a first mbuf for the CAPI message (the board only delivers CAPI
 * messages, no other message types). Then we read the remaining bytes of the
 * CAPI message into the mbuf.
 *
 * For Data-B3-Indications we must also allocate a second mbuf for the
 * remaining Data-B3 block. The CAPI message contains the length of this block.
 * We must then read the data block into this mbuf and attach it to the CAPI
 * message mbuf.
 *
 * The complete CAPI message is then forwarded to the CAPI manager for delivery
 * to the application.
 *
 * In any case, we must release the message in the shared memory. Otherwise the
 * board will soon lockup.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    The message was read successfully.
 * @retval Else                 Failure, the message could not be read from
 *                              shared memory.
 */

static int ix1ashm_read_one_message
   (Ix1aSc_t *pSc)
{
   u_int8_t                 abBuf [sizeof (Ix1aBoardCapiCallData_t) +
                                   sizeof (CAPIMsgHead_t)];
   Ix1aBoardCapiCallData_t *pCallData;
   CAPIMsg_t               *pCapiMsg;
   struct mbuf             *pmbMsg;
   int                      iResTotal;
   int                      iRes;
   size_t                   nMsgLen;
   size_t                   nMbufLen;
   size_t                   nDataLen;
   unsigned                 uOfs;
   unsigned                 uCtlr;
   unsigned                 uApplId;
   
   /* read the call data structure and the expected CAPI message header into
    * the buffer
    */
   iResTotal = 0;
   uOfs = pSc->uShmOfsCurrReadMsg + pSc->uShmSizeFVFlag;
   iRes = ix1ashm_read_block_16 (pSc, uOfs, abBuf, sizeof (abBuf));
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Unable to read message from board, error reading initial data: %d",
           iRes);
      iResTotal = iRes;
   }
   
   /* check the call data structure for validity */
   nMsgLen = 0;
   if (iResTotal == 0)
   {
      pCallData = (Ix1aBoardCapiCallData_t *) &(abBuf [0]);
/* It seems that the call data structure does neither contain a real command /
 * subcommand pair nor a total message length. The only part that should really
 * be handled is the (hopefully) following CAPI message.
 */
#if 1
      DBG (LOG_DEBUG, pSc->iUnit,
           "Got call data structure with command 0x%02X/0x%02X, length value 0x%04X",
           (unsigned) (pCallData->bCmd), (unsigned) (pCallData->bSubCmd),
           (unsigned) le16toh (pCallData->wLength));
#else /* 1 */
      if (pCallData->bCmd != IX1A_CAPI_CMD_FROM_BOARD)
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Unexpected command 0x%02X read from board, 0x%02X expected",
              (unsigned) (pCallData->bCmd), IX1A_CAPI_CMD_FROM_BOARD);
      }
      if (pCallData->bSubCmd != IX1A_CAPI_CALL_API_GET_MESSAGE)
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Unexpected sub-command 0x%02X read from board, 0x%02X expected",
              (unsigned) (pCallData->bSubCmd), IX1A_CAPI_CALL_API_GET_MESSAGE);
      }
      nMsgLen = (size_t) le16toh (pCallData->wLength);
      if (nMsgLen < sizeof (*pCallData))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Error reading CAPI message from board, call data length (%zu) less then call data structure (%zu)",
              nMsgLen, sizeof (*pCallData));
         iResTotal = EIO;
      }
      nMsgLen -= sizeof (*pCallData);
      if (iResTotal == 0 && nMsgLen < sizeof (CAPIMsgHead_t))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Error reading CAPI message from board, message length (%zu) shorter than message header (%zu)",
              nMsgLen, sizeof (CAPIMsgHead_t));
         iResTotal = EIO;
      }
#endif /* 1 */
   }
   
   /* determine the length for the CAPI message mbuf (consider
    * Data-B3-Indication for 64bit applications)
    */
   pCapiMsg = (CAPIMsg_t *) &(abBuf [sizeof (*pCallData)]);
   nMsgLen = CAPI_GET_LEN (pCapiMsg);
   nMbufLen = 0;
   if (iResTotal == 0 && CAPI_GET_CMD (pCapiMsg) == CAPI_INDICAT (C_DATA_B3))
   {
      if (nMsgLen !=
             sizeof (CAPIMsgHead_t) + sizeof (pCapiMsg->info.data_b3_32_ind))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Invalid message length %zu for Data-B3-Indication, use %zu",
              nMsgLen,
              sizeof (CAPIMsgHead_t) + sizeof (pCapiMsg->info.data_b3_32_ind));
         nMsgLen =
            sizeof (CAPIMsgHead_t) + sizeof (pCapiMsg->info.data_b3_32_ind);
      }
      nMbufLen =
         sizeof (CAPIMsgHead_t) + sizeof (pCapiMsg->info.data_b3_64_ind);
   }
   else
   {
      nMbufLen = nMsgLen;
   }
   DBG (LOG_DEBUG, pSc->iUnit,
        "Got CAPI message of length 0x%04zX (use mbuf length 0x%04zX), appl. id 0x%04X, cmd 0x%04X, num 0x%04X, cid 0x%08X",
        nMsgLen, nMbufLen,
        (unsigned) CAPI_GET_APPL (pCapiMsg),
        (unsigned) CAPI_GET_CMD (pCapiMsg),
        (unsigned) CAPI_GET_MSGNUM (pCapiMsg),
        (unsigned) CAPI_GET_CID (pCapiMsg));
   
   /* allocate a new mbuf for the CAPI message */
   pmbMsg = NULL;
   if (iResTotal == 0 && nMbufLen > 0)
   {
      pmbMsg = kcapi_get_mbuf (nMbufLen);
      if (pmbMsg == NULL)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Error reading CAPI message from board, out of mbufs for %zu bytes",
              nMbufLen);
         iResTotal = ENOMEM;
      }
   }
   
   /* read the CAPI message into the mbuf */
   if (iResTotal == 0 && pmbMsg != NULL)
   {
      bcopy (&(abBuf [sizeof (*pCallData)]), pmbMsg->m_data,
             sizeof (CAPIMsgHead_t));
      uOfs += sizeof (abBuf);
      if (nMsgLen > sizeof (CAPIMsgHead_t))
      {
         iRes = ix1ashm_read_block_16
                   (pSc, uOfs,
                    mtod (pmbMsg, u_int8_t *) + sizeof (CAPIMsgHead_t),
                    nMsgLen - sizeof (CAPIMsgHead_t));
         if (iRes == 0)
         {
            uOfs += nMsgLen - sizeof (CAPIMsgHead_t);
            if ((uOfs & 0x0001) != 0)
            {
               ++uOfs;
            }
         }
         else
         {
            DBG (LOG_ERROR, pSc->iUnit,
                 "Error reading CAPI message from board, error reading remaining message part (%u bytes)",
                 (unsigned) (nMsgLen - sizeof (CAPIMsgHead_t)));
            iResTotal = iRes;
            kcapi_free_mbuf (pmbMsg);
            pmbMsg = NULL;
         }
      }
      if (iResTotal == 0)
      {
         pCapiMsg = mtod (pmbMsg, CAPIMsg_t *);
         pmbMsg->m_len = nMbufLen;
         C_PUT_WORD (pCapiMsg->head.wLen, nMbufLen);
      }
   }
   
   /* if the message is a Data-B3-Indication and there is a non-empty data
    * block after the message, we must now read the remaining data block
    */
   if (iResTotal == 0 && pmbMsg != NULL &&
       CAPI_GET_CMD (pCapiMsg) == CAPI_INDICAT (C_DATA_B3) &&
       C_GET_WORD (pCapiMsg->info.data_b3_32_ind.wLen) != 0)
   {
      /* allocate an mbuf of matching length */
      nDataLen = (size_t) C_GET_WORD (pCapiMsg->info.data_b3_32_ind.wLen);
      C_PUT_QWORD (pCapiMsg->info.data_b3_64_ind.qwData64, 0);
      pmbMsg->m_next = kcapi_get_mbuf (nDataLen);
      if (pmbMsg->m_next == NULL)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Error reading CAPI message from board, out of mbufs for data block of %zu bytes",
              nDataLen);
         /* not marked as error, we nevertheless deliver the CAPI message */
      }
      
      /* read the data block into the mbuf */
      if (pmbMsg->m_next != NULL)
      {
         pmbMsg->m_next->m_len = nDataLen;
         iRes = ix1ashm_read_block_16
                   (pSc, uOfs, mtod (pmbMsg->m_next, u_int8_t *), nDataLen);
         if (iRes != 0)
         {
            DBG (LOG_ERROR, pSc->iUnit,
                 "Error reading CAPI message from board, error reading data block: %d",
                 iRes);
            kcapi_free_mbuf (pmbMsg->m_next);
            pmbMsg->m_next = NULL;
         }
      }
   }
   
   /* if we now have a valid CAPI message, forward it to the CAPI manager */
   /* Note: We must release the board access mutex for this (see the CAPI
    *       manager function documentation). As the interrupt routine is still
    *       active, no user side function call will disturb us.
    */
   if (iResTotal == 0 && pmbMsg != NULL && pCapiMsg != NULL)
   {
      uCtlr = CAPI_GET_CTLR_FROM_CID (CAPI_GET_CID (pCapiMsg));
      uApplId = CAPI_GET_APPL (pCapiMsg);
      if (uCtlr < ARRAY_COUNT (e_apMapDrvCtlrToPortData) &&
          e_apMapDrvCtlrToPortData [uCtlr] != NULL)
      {
         mtx_unlock (&(pSc->mtxAccess));
         kcapi_ctlr_receive_capi_message
            (e_apMapDrvCtlrToPortData [uCtlr]->uUniqueCapiCtlrNum,
             uApplId,
             pmbMsg);
         mtx_lock (&(pSc->mtxAccess));
         DBG (LOG_DATAMSG, pSc->iUnit,
              "CAPI message from controller %u (unique %u) successfully forwarded to application id %u (unique %u)",
              uCtlr, e_apMapDrvCtlrToPortData [uCtlr]->uUniqueCapiCtlrNum,
              uApplId, pSc->auApplMap [uApplId]);
      }
      else
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Error reading CAPI message from board, invalid controller (specific) number %u",
              uCtlr);
      }
   }
   
   /* finally release the message in the shared memory */
   iRes = ix1ashm_write_word (pSc, pSc->uShmOfsCurrReadMsg,
                              (unsigned) IX1A_SHM_FV_FLAG_FREE);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Error reading CAPI message from board, error marking message position as free: %d",
           iRes);
      return (iRes);
   }
   
   return (0);
} /* ix1ashm_read_one_message */





/**
 * Check if a board can accept another Data-B3-Request for an NCCI.
 *
 * Every IX1 board maintains a table for NCCI flow control in its shared
 * memory. This table must be queried whenever a new Data-B3-Request is to be
 * sent to the board. The message may only be sent, if the table entry for the
 * given NCCI allows it.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uNcci                 I: The NCCI value to query for.
 *
 * @retval 0                    The board allows to send another
 *                              Data-B3-Request for the NCCI.
 * @retval EBUSY                The NCCI is blocked, its send queue is full.
 * @retval Else                 Failure querying the table entry, the result is
 *                              an appropriate errno value.
 */

static int ix1ashm_check_ncci_fc
   (Ix1aSc_t *pSc,
    unsigned  uNcci)
{
   unsigned uIdx;
   unsigned uFcVal;
   int      iRes;
   
   /* the NCCI value to check is the high byte of the NCCI word */
#if 1 /* seems that this is not true (any more) */
   uIdx = uNcci;
#else /* 1 */
   uIdx = ((uNcci & 0xFF00) >> 8);
#endif /* 1 */
   
   /* if the NCCI check value is higher than the maximum allowed number of
    * NCCIs there must be an error, no Data-B3-Request message is allowed for
    * such NCCIs
    */
   if ((size_t) uIdx >= pSc->nNumNcci)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Error checking flow control for NCCI 0x%04X, NCCI check value 0x%02X is too large (maximum 0x%02X)",
           uNcci, uIdx, (unsigned) (pSc->nNumNcci));
      return (EINVAL);
   }
   
   /* distinguish between the different board types */
   switch (pSc->cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
      case IX1A_CARD_TYPE_MMOD_ISA:
      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         /* for Basic / Octo / Multimodem boards there is a word per NCCI */
         iRes = ix1aisa_shm_read_word
                   (pSc,
                    IX1A_SHM_OFS_CAPI_NCCI_FC + uIdx * sizeof (u_int16_t),
                    &uFcVal);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         /* for Basic / Octo / Multimodem boards there is a word per NCCI */
         iRes = ix1abpci_shm_read_word
                   (pSc,
                    IX1A_SHM_OFS_CAPI_NCCI_FC + uIdx * sizeof (u_int16_t),
                    &uFcVal);
         break;
      
      case IX1A_CARD_TYPE_PRIMARY_PCI:
         /* for Primary boards there is only one byte per NCCI */
         iRes = ix1appci_shm_read_byte
                   (pSc, pSc->uShmOfsPrimNcciFc + uIdx, &uFcVal);
         break;

      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Error checking flow control for NCCI 0x%04X, error reading flow control value from board: %d",
           uNcci, iRes);
      return (iRes);
   }
   
   if (uFcVal != (unsigned) IX1A_SHM_PLNC_OK)
   {
      DBG (LOG_TRACE, pSc->iUnit,
           "NCCI 0x%04X is busy, send queue full",
           uNcci);
      return (EBUSY);
   }
   
   return (0);
} /* ix1ashm_check_ncci_fc */





/**
 * Check if a board's state is still set to "working".
 *
 * The purpose of this function is to read the board's state byte and check it
 * for validity. If a crashed board is detected, the only operation performed
 * is to write an error message to the console.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 *
 * @retval 0                    The board is (still) working.
 * @retval EFAULT               The board is not working (crashed).
 * @retval Else                 Failure to get the board state, maybe it is
 *                              even more crashed than expected.                  
 */

static int ix1ashm_check_board_state
   (Ix1aSc_t *pSc)
{
   unsigned u;
   int      iRes;
   
   if (pSc->cardType == IX1A_CARD_TYPE_PRIMARY_PCI)
   {
      iRes = ix1appci_shm_read_word
                (pSc, IX1A_SHM_OFS_CAPIPRIM_BOARD_STATE, &u);
   }
   else
   {
      iRes = ix1ashm_read_word (pSc, IX1A_SHM_OFS_CAPI_BOARD_STATE, &u);
   }
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Error checking board state, error reading board status byte: %d",
           iRes);
      return (iRes);
   }
   
   if (u != IX1A_FW_STATUS_WORKING)
   {
      printf ("%s%d: ERROR: Board crashed during operation (board status byte is 0x%02X, should be 0x%02X)\n",
              pSc->szDriverName, pSc->iUnit, u, IX1A_FW_STATUS_WORKING);
      return (EFAULT);
   }
   
   return (0);
} /* ix1ashm_check_board_state */
