/**
 * @file ix1a_misc.c
 *
 * Ix1a-Misc - Miscellaneous helper functions for the ix1a device driver.
 *
 * Copyright: 2004 Thomas Wintergerst. All rights reserved.
 *
 * $Id: ix1a_misc.c,v 1.1.2.1 2006/02/28 21:35:22 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     ix1a - CAPI manager driver for IX1 active ISDN controllers $
 * @date        17.08.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>          /* size_t */
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/endian.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <sys/bus.h>
#include <machine/resource.h>

/* Import includes */

#define __IX1A_MISC__

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





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





/** The timeout in hz units for obtaining controller access. */
#define IX1AMISC_WAIT_FOR_CTLR_ACCESS_TIMEOUT   (5 * hz)

/** The table of strings for the translation of board types into strings. */
static const char *g_apszCardTypeNames [] =
   {
      "Digi Datafire Basic Up0 ISA",    /**< IX1A_CARD_TYPE_BASIC_UP0_ISA. */
      "Digi Datafire Basic S0 ISA",     /**< IX1A_CARD_TYPE_BASIC_S0_ISA. */
      "Digi Datafire Basic Up0 MCA",    /**< IX1A_CARD_TYPE_BASIC_UP0_MCA. */
      "Digi Datafire Basic S0 MCA",     /**< IX1A_CARD_TYPE_BASIC_S0_MCA. */
      "Digi Datafire Primary EISA",     /**< IX1A_CARD_TYPE_PRIMARY_EISA. */
      "Digi Datafire Primary ISA",      /**< IX1A_CARD_TYPE_PRIMARY_ISA. */
      "Digi Datafire Multimodem ISA",   /**< IX1A_CARD_TYPE_MMOD_ISA. */
      "Digi Datafire Octo Up0",         /**< IX1A_CARD_TYPE_OCTO_UP0_ISA. */
      "Digi Datafire Octo S0",          /**< IX1A_CARD_TYPE_OCTO_S0_ISA. */
      "Digi Datafire Basic PCI",        /**< IX1A_CARD_TYPE_BASIC_PCI. */
      "Digi Datafire Primary PCI",      /**< IX1A_CARD_TYPE_PRIMARY_PCI. */
      "Digi Datafire Digital Modem ISA" /**< IX1A_CARD_TYPE_GRANITE_ISA. */
   };

/** The string a boot code file starts with. */
#define IX1A_BOOT_FILE_START_STRING     "Bootfile"

/** The string a firmware code file starts with. */
#define IX1A_FIRMWARE_FILE_START_STRING "Loadfile"

/** The block size used for firmware download of all boards but Multimodems. */
#define IX1A_FW_BLOCK_SIZE_LARGE        0xF800

/** The block size used for firmware download of Multimodem boards. */
#define IX1A_FW_BLOCK_SIZE_SMALL        0x7C00





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





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





/**
 * Check a data block for valid boot code.
 *
 * A valid boot code data block starts with a Ctrl-Z terminated string,
 * followed by a single byte (value not relevant). After this exactly 32KB of
 * binary data must follow. The string at the beginning must start with the
 * fixed text "Bootfile".
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbDataBlock           I: The boot code block to check.
 * @param nLenDataBlock         I: The length of the boot code block at
 *                                 pbDataBlock.
 * @param ppbBootCode           O: On successful return the address of the real
 *                                 boot code within the data block is returned.
 *
 * @retval 0                    The boot code data block is valid, *ppbBootCode
 *                              points to the location of the boot code.
 * @retval Else                 The boot code data block is invalid, the result
 *                              is an appropriate errno value.
 */
static int ix1amisc_check_boot_code
   (Ix1aSc_t        *pSc,
    const u_int8_t  *pbDataBlock,
    size_t           nLenDataBlock,
    const u_int8_t **ppbBootCode);

/**
 * Set the board to boot mode.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @return Nothing.
 */
static void ix1amisc_set_boot_mode
   (Ix1aSc_t *pSc);

/**
 * Request a test interrupt from the board and wait for it.
 *
 * A test interrupt can only be performed for ISA boards. For PCI boards this
 * task will silently be ignored, the function returns at once with success.
 *
 * For other boards all currently pending interrupts are cleared and a test
 * interrupt is requested. We wait for it for some time. If we get it, we are
 * done. If not, the function fails.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @retval 0                    The test interrupt was successful.
 * @retval Else                 We did not get any interrupt, board failure.
 *                              The result is an appropriate errno value.
 */
static int ix1amisc_do_test_int
   (Ix1aSc_t *pSc);

/**
 * Send the boot code to a board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbBootCode            I: The boot code to send. There must be exactly
 *                                 32KB of binary data at the specified
 *                                 location.
 *
 * @retval 0                    Sending the boot code was successful.
 * @retval Else                 Failure, the board did not respond to the
 *                              loading operation.
 */
static int ix1amisc_send_boot_code
   (Ix1aSc_t       *pSc,
    const u_int8_t *pbBootCode);

/**
 * Read the boot status code from a board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param puBootStatus          O: On successful return the boot status code
 *                                 byte is delivered here as an unsigned
 *                                 integer.
 *
 * @retval 0                    The boot status code was read successfully,
 *                              *puBootStatus is set accordingly.
 * @retval Else                 Failed to read boot status byte from the board.
 */
static int ix1amisc_read_boot_status
   (Ix1aSc_t *pSc,
    unsigned *puBootStatus);

/**
 * Send a boot command and interrupt to a board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uBootCmd              I: The boot command code to send.
 *
 * @retval 0                    The boot command was successfully send to the
 *                              board.
 * @retval Else                 Failure, the command could not be sent to the
 *                              board.
 */
static int ix1amisc_int_to_board
   (Ix1aSc_t *pSc,
    unsigned  uBootCmd);

/**
 * Prepare the board for sending the firmware code.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @return Nothing.
 */
static void ix1amisc_prepare_for_firmware
   (Ix1aSc_t *pSc);

/**
 * Send the firmware parameter block to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pParamBlock           I/O: The parameter block to send. It will be
 *                                 modified to contain the correct block size
 *                                 for the firmware portions to send.
 *
 * @retval 0                    Sending the firmware parameter block was
 *                              successful.
 * @retval Else                 Failure, the board did not respond to the
 *                              loading operation.
 */
static int ix1amisc_send_param_block
   (Ix1aSc_t                 *pSc,
    Ix1aFirmwareParamBlock_t *pParamBlock);

/**
 * Send the firmware code to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbFirmwareCode        I: The address of the firmware code to send.
 * @param nLenFirmwareCode      I: The number of firmware code bytes to send.
 * @param nBlockSize            I: The size of a single firmware code portion
 *                                 to send to the board.
 *
 * @retval 0                    Sending the firmware code was successful.
 * @retval Else                 Failure, the board did not respone to the
 *                              loading operations.
 */
static int ix1amisc_send_firmware
   (Ix1aSc_t       *pSc,
    const u_int8_t *pbFirmwareCode,
    size_t          nLenFirmwareCode,
    size_t          nBlockSize);

/**
 * Send line settings to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbSettings            I: The address of the settings data to send.
 * @param nLenSettings          I: The number of settings bytes to send.
 *
 * @retval 0                    Sending the line settings was successful.
 * @retval Else                 Failure, the board did not respone to the
 *                              sending operation.
 */
static int ix1amisc_send_port_params
   (Ix1aSc_t *pSc,
    u_int8_t *pbSettings,
    size_t    nLenSettings);

/**
 * Start the board firmware after a successful download operation.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @retval 0                    The board is now ready for operation.
 * @retval Else                 Starting the firmware failed, the board is in
 *                              undefined state.
 */
static int ix1amisc_start_firmware
   (Ix1aSc_t *pSc);





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





/**
 * Get access to a controller for an operation.
 *
 * @note This function is called by all CAPI manager entry functions. The
 *       interrupt routine must not call it, but must directly obtain the board
 *       access mutex.
 *
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    Unable to obtain board access, timeout.
 * @retval 1                    Exclusive board access gained.
 */

int ix1amisc_get_ctlr_access
   (Ix1aSc_t *pSc)
{
   /* The mutex might not be initialized when calling this function. But in this
    * case no real operation will be pending, but the controller device will be
    * detached from its bus. So it does not matter, but a lock operation must
    * not be performed.
    */
   if (! mtx_initialized (&(pSc->mtxAccess)))
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Allow direct controller access because of uninitialized mutex");
      pSc->fOpInProgress = 1;
      return (1);
   }
   
   DBG (LOG_IRQ, pSc->iUnit, "Gain controller access");
   mtx_lock (&(pSc->mtxAccess));
   DBG (LOG_IRQ, pSc->iUnit,
        "Got preliminary controller access, wait for no other operation in progress");
   while (pSc->fOpInProgress || pSc->fIntrActive)
   {
      if (cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess),
                        IX1AMISC_WAIT_FOR_CTLR_ACCESS_TIMEOUT) != 0)
      {
         if (mtx_owned (&(pSc->mtxAccess)) &&
             ! pSc->fOpInProgress && ! pSc->fIntrActive)
         {
            /* the wait operation failed but we got mutex access and no other
             * operation is pending, so just proceed and return success
             */
            DBG (LOG_ERROR, pSc->iUnit,
                 "Timeout waiting for controller access (%lums) but no operation in progess",
                 (unsigned long)
                    IX1AMISC_WAIT_FOR_CTLR_ACCESS_TIMEOUT * 1000 / hz);
            break;
         }
         
         DBG (LOG_ERROR, pSc->iUnit,
              "Timeout waiting for controller access (%lums), unable to obtain controller access",
              (unsigned long) IX1AMISC_WAIT_FOR_CTLR_ACCESS_TIMEOUT * 1000 / hz);
         if (pSc->fOpInProgress != 0)
         {
            DBG (LOG_ERROR, pSc->iUnit, "Still operation in progress");
         }
         if (pSc->fIntrActive != 0)
         {
            DBG (LOG_ERROR, pSc->iUnit, "Interrupt handler still active");
         }
         if (mtx_owned (&(pSc->mtxAccess)))
         {
            mtx_unlock (&(pSc->mtxAccess));
         }
         return (0);
      }
   }
   
   pSc->fOpInProgress = 1;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Got controller access");
   
   return (1);
} /* ix1amisc_get_ctlr_access */





/**
 * Release access to a controller after an operation.
 */

void ix1amisc_release_ctlr_access
   (Ix1aSc_t *pSc)
{
   /* The mutex might not be initialized when calling this function. But in this
    * case no real operation will be pending, but the controller device will be
    * detached from its bus. So it does not matter, but an unlock operation must
    * not be performed.
    */
   if (! mtx_initialized (&(pSc->mtxAccess)) ||
       ! mtx_owned (&(pSc->mtxAccess)))
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Directly release controller access because of uninitialized mutex");
      pSc->fOpInProgress = 0;
      return;
   }
   
   pSc->fOpInProgress = 0;
   cv_broadcast (&(pSc->cvNotify));
   mtx_unlock (&(pSc->mtxAccess));
   
   DBG (LOG_DEBUG, pSc->iUnit, "Released controller access");

} /* ix1amisc_release_ctlr_access */





/**
 * Release all resources allocated within a resource info structure.
 *
 * This function will release all resources still allocated within the
 * structure given as an argument.
 *
 * @param dev                   I: The device entry for the board to register.
 * @param pResInfo              I/O: The resource info with the resources to
 *                                 release.
 *
 * @retval 0                    All resources are release successfully.
 * @retval Else                 An error occurred.
 */

int ix1amisc_release_resinfo
   (device_t            dev,
    Ix1aResourceInfo_t *pResInfo)
{
   if (pResInfo->pResIrq != NULL)
   {
      if (pResInfo->fIrqSetup)
      {
         (void) bus_teardown_intr
                   (dev, pResInfo->pResIrq, pResInfo->hIrqCookie);
         pResInfo->hIrqCookie = NULL;
         pResInfo->fIrqSetup = 0;
      }
      (void) bus_release_resource (dev, SYS_RES_IRQ, pResInfo->iRidIrq,
                                   pResInfo->pResIrq);
      pResInfo->pResIrq = NULL;
   }

   if (pResInfo->pResIoHeader != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoHeader,
                                   pResInfo->pResIoHeader);
      pResInfo->pResIoHeader = NULL;
   }
   if (pResInfo->pResIoData != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoData,
                                   pResInfo->pResIoData);
      pResInfo->pResIoData = NULL;
   }
   if (pResInfo->pResIoCmd != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoCmd,
                                   pResInfo->pResIoCmd);
      pResInfo->pResIoCmd = NULL;
   }
   if (pResInfo->pResMemShm != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_MEMORY, pResInfo->iRidMemShm,
                                   pResInfo->pResMemShm);
      pResInfo->pResMemShm = NULL;
   }
   if (pResInfo->pResMemGal != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_MEMORY, pResInfo->iRidMemGal,
                                   pResInfo->pResMemGal);
      pResInfo->pResMemGal = NULL;
   }
   if (pResInfo->pResMemMmr != NULL)
   {
      (void) bus_release_resource (dev, SYS_RES_MEMORY, pResInfo->iRidMemMmr,
                                   pResInfo->pResMemMmr);
      pResInfo->pResMemMmr = NULL;
   }

   return (0);
} /* ix1amisc_release_resinfo */





/**
 * Perform a download operation for a board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param nNumDataBlocks        I: The number of data blocks for the download
 *                                 operation. If not only a board reset is
 *                                 requested, there must be three data blocks
 *                                 at paDataBlocks.
 * @param paDataBlocks          I: The array of data blocks for the download
 *                                 operation. For a pure reset operation no
 *                                 data block is specified. Else there must be
 *                                 three data blocks: Primary bootstrap,
 *                                 firmware and line configuration.
 *
 * @retval 0                    The reset or download operation was successful.
 * @retval Else                 The reset or download operation failed, result
 *                              is an errno value.
 */

int ix1amisc_download_board
   (Ix1aSc_t                  *pSc,
    size_t                     nNumDataBlocks,
    const CAPICtlrDataBlock_t *paDataBlocks)
{
   const u_int8_t           *pBootCode;
   const u_int8_t           *pFirmwareCode;
   size_t                    nLenFirmwareCode;
   Ix1aFirmwareParamBlock_t  paramBlock;
   int                       iRes;
   
   /* the download operation for a Primary PCI is so different from the other
    * boards, that we use a specific function for it
    */
   if (pSc->cardType == IX1A_CARD_TYPE_PRIMARY_PCI)
   {
      return (ix1appci_download_board (pSc, nNumDataBlocks, paDataBlocks));
   }
   
   /* the following code is common for Basic / Octo and Multimodem boards, ISA
    * and PCI
    */
   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:
      case IX1A_CARD_TYPE_BASIC_PCI:
         break;

      default:
         DBG (LOG_ERROR, pSc->iUnit,
              "Invalid board type %d, unable to perform download",
              (int) (pSc->cardType));
         return (EINVAL);
   }

   /* check the parameters, three data blocks are expected but the third is
    * optional
    */
   if (nNumDataBlocks < 2 || paDataBlocks == NULL ||
       paDataBlocks [0].nLenDataBlock == 0 ||
       paDataBlocks [0].paucDataBlock == NULL ||
       paDataBlocks [1].nLenDataBlock == 0 ||
       paDataBlocks [1].paucDataBlock == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid data block parameters for download operation");
      return (EINVAL);
   }

   /* check the boot file (1st block), must start with two strings, followed by
    * exactly 32KB of boot code (only for Basic / Octo and Multimodem boards)
    */
   iRes = ix1amisc_check_boot_code (pSc,
                                    paDataBlocks [0].paucDataBlock,
                                    paDataBlocks [0].nLenDataBlock,
                                    &pBootCode);
   if (iRes != 0)
   {
      return (iRes);
   }
    
   /* check the firmware file (2nd block), must start with two strings,
    * followed by a parameter block of fixed size
    */
   iRes = ix1amisc_check_firmware_code (pSc,
                                        paDataBlocks [1].paucDataBlock,
                                        paDataBlocks [1].nLenDataBlock,
                                        &paramBlock,
                                        &pFirmwareCode,
                                        &nLenFirmwareCode);
   if (iRes != 0)
   {
      return (iRes);
   }

   /* if there is a third data block, it must be the board configuration and of
    * correct length
    */
   if (nNumDataBlocks >= 3 &&
       paDataBlocks [2].nLenDataBlock != 0 &&
       paDataBlocks [2].paucDataBlock != NULL)
   {
      size_t nNumLinesExpected;
      size_t nNumLinesProvided;
      
      switch (pSc->cardType)
      {
         case IX1A_CARD_TYPE_BASIC_UP0_ISA:
         case IX1A_CARD_TYPE_BASIC_S0_ISA:
         case IX1A_CARD_TYPE_BASIC_PCI:
            nNumLinesExpected = 1;
            break;

         case IX1A_CARD_TYPE_MMOD_ISA:
            nNumLinesExpected = 0;
            break;

         case IX1A_CARD_TYPE_OCTO_UP0_ISA:
         case IX1A_CARD_TYPE_OCTO_S0_ISA:
            nNumLinesExpected = 4;
            break;
   
         default:
            nNumLinesExpected = (size_t) -1;
            break;
      }
      
      if (paDataBlocks [2].nLenDataBlock != sizeof (Ix1aBoardParameters_t))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Invalid board parameters data block for download operation (length %zu bytes, %zu expected)",
              paDataBlocks [2].nLenDataBlock, sizeof (Ix1aBoardParameters_t));
         return (EINVAL);
      }
      nNumLinesProvided =
         (size_t) le32toh (((Ix1aBoardParameters_t *)
                               (paDataBlocks [2].paucDataBlock))->dwNumLines);
      if (nNumLinesExpected != nNumLinesProvided)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Invalid board parameters data block for download operation (number of lines is %zu, must be %zu)",
              nNumLinesProvided, nNumLinesExpected);
         return (EINVAL);
      }
   }
   
   /* enable the board in case it is in disabled state */
   if (pSc->state <= IX1A_STATE_INITIALISING)
   {
      if (pSc->cardType == IX1A_CARD_TYPE_BASIC_PCI)
      {
         iRes = ix1abpci_enable_board (pSc);
      }
      else
      {
         iRes = ix1aisa_enable_board (pSc);
      }
      if (iRes != 0)
      {
         return (iRes);
      }
      pSc->state = IX1A_STATE_DOWN;
      DBG (LOG_INFO, pSc->iUnit, "Board state set to DOWN");
   }
   
   /* stop the board and set it to boot mode */
   ix1amisc_set_boot_mode (pSc);
   
   /* request a test interrupt and wait for it (not supported for PCI boards)
    */
   iRes = ix1amisc_do_test_int (pSc);
   if (iRes != 0)
   {
      return (iRes);
   }

   /* send boot code (leads to an interrupt) */
   iRes = ix1amisc_send_boot_code (pSc, pBootCode);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* prepare board for firmware download */
   ix1amisc_prepare_for_firmware (pSc);
   
   /* send the parameter block determined earlier for the following firmware
    * download (leads to an interrupt)
    */
   iRes = ix1amisc_send_param_block (pSc, &paramBlock);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* send the firmware in relatively small blocks to the board, each block is
    * signaled through an interrupt to the board and acknowledged by an
    * interrupt from the board
    */
   iRes = ix1amisc_send_firmware
             (pSc, pFirmwareCode, nLenFirmwareCode,
              (size_t) le32toh (paramBlock.dwBinBlockSize));
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* write the adapter parameters (3rd data block) to the shared memory */
   if (nNumDataBlocks >= 3 &&
       paDataBlocks [2].nLenDataBlock != 0 &&
       paDataBlocks [2].paucDataBlock != NULL)
   {
      iRes = ix1amisc_send_port_params
                (pSc,
                 paDataBlocks [2].paucDataBlock,
                 paDataBlocks [2].nLenDataBlock);
      if (iRes != 0)
      {
         return (iRes);
      }
   }
   
   /* now start the firmware on the board */
   iRes = ix1amisc_start_firmware (pSc);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* after the firmware startup we must setup some memory locations for later
    * CAPI message exchange
    */
   iRes = ix1ashm_firmware_started (pSc);
   if (iRes != 0)
   {
      return (iRes);
   }
   
   /* now the board is ready for operation */
   pSc->state = IX1A_STATE_READY;
   DBG (LOG_INFO, pSc->iUnit, "Board ready for (CAPI) operation");
   return (0);
} /* ix1amisc_download_board */





/**
 * Check a data block for valid board firmware.
 *
 * A valid firmware data block starts with two Ctrl-Z terminated strings, the
 * first must start with the text "Loadfile". This is followed by a parameter
 * block of six dwords. The real length is prepended to the parameter block as
 * a single byte right after the 2nd Ctrl-Z. Behind the parameter block the
 * real firmware follows. Normally it continues to the end of the data block.
 * But the first dword of the parameter block determines the number of bytes to
 * send to the board. There may be some bytes left behind the firmware.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbDataBlock           I: The firmware code block to check.
 * @param nLenDataBlock         I: The length of the firmware data block at
 *                                 pbDataBlock.
 * @param pParamBlock           O: On successful return the content of the
 *                                 parameter block found is stored here.
 * @param ppbFirmwareCode       O: On successful return the address of the real
 *                                 firmware code is returned here.
 * @param pnLenFirmwareCode     O: On successful return the real length of the
 *                                 firmware to send to the board in bytes is
 *                                 returned here.
 *
 * @retval 0                    The firmware code data block is valid,
 *                              *pParamBlock, *ppFirmwareCode and
 *                              *pnLenFirmwareCode are filled with the values
 *                              determined.
 * @retval Else                 The firmware code data block is invalid, the
 *                              result is an appropriate errno value.
 */

int ix1amisc_check_firmware_code
   (Ix1aSc_t                  *pSc,
    const u_int8_t            *pbDataBlock,
    size_t                     nLenDataBlock,
    Ix1aFirmwareParamBlock_t  *pParamBlock,
    const u_int8_t           **ppbFirmwareCode,
    size_t                    *pnLenFirmwareCode)
{
   const u_int8_t *p;
   size_t          n;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Checking firmware code data block");
   
   /* an empty block is always an error */
   if (pbDataBlock == NULL || nLenDataBlock == 0)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Empty boot code block, check failed");
      return (EINVAL);
   }
   if (pParamBlock == NULL ||
       ppbFirmwareCode == NULL || pnLenFirmwareCode == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Invalid return parameters");
      return (EINVAL);
   }
   
   /* check for the string "Loadfile" at the beginning of the block */
   if (nLenDataBlock < sizeof (IX1A_FIRMWARE_FILE_START_STRING) ||
       strncmp ((const char *) pbDataBlock, IX1A_FIRMWARE_FILE_START_STRING,
                sizeof (IX1A_FIRMWARE_FILE_START_STRING) - 1) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid firmware code data block, does not start with \"%s\"",
           IX1A_FIRMWARE_FILE_START_STRING);
      return (EINVAL);
   }
   
   /* find the Ctrl-Z behind the text part */
   for (p = pbDataBlock, n = 0; n < nLenDataBlock && *p != '\x1A'; ++p, ++n)
      ;
   for (++p, ++n; n < nLenDataBlock && *p != '\x1A'; ++p, ++n)
      ;
   if (n >= nLenDataBlock)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid firmware code data block, unable to find two Ctrl-Z");
      return (EINVAL);
   }

   /* there is a length byte after the Ctrl-Z for the length of the parameter
    * block
    */
   ++p;
   ++n;
   if (n >= nLenDataBlock || *p != sizeof (*pParamBlock))
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid firmware code data block, parameter block missing or not of correct size");
      return (EINVAL);
   }
   
   /* so there is a parameter block, copy it */
   ++p;
   ++n;
   bcopy (p, pParamBlock, sizeof (*pParamBlock));
   
   /* the real firmware code starts after the parameter block */
   p += sizeof (*pParamBlock);
   n += sizeof (*pParamBlock);
   *ppbFirmwareCode = p;
   
   /* the length of the firmware code stands in the first dword of the
    * parameter block
    */
   *pnLenFirmwareCode = (size_t) le32toh (pParamBlock->dwBinSize);
   if (n + *pnLenFirmwareCode > nLenDataBlock)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid firmware code data block, found %u firmware code bytes at offset %u, %u expected",
           (unsigned) (nLenDataBlock - n), (unsigned) n,
           (unsigned) *pnLenFirmwareCode);
      return (EINVAL);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Firmware code data block valid, firmware code starts at offset %u, length %u",
        (unsigned) n, (unsigned) *pnLenFirmwareCode);
   DBG (LOG_DEBUG, pSc->iUnit,
        "Parameter block for firmware loading: dwBinSize=0x%08X, dwBinBlockSize=0x%08X, dwBinDestAddr=0x%08X, dwStartAddr=0x%08X, dwCheckSum=0x%08X, dwFlags=0x%08X",
        (unsigned) le32toh (pParamBlock->dwBinSize),
        (unsigned) le32toh (pParamBlock->dwBinBlockSize),
        (unsigned) le32toh (pParamBlock->dwBinDestAddr),
        (unsigned) le32toh (pParamBlock->dwStartAddr),
        (unsigned) le32toh (pParamBlock->dwCheckSum),
        (unsigned) le32toh (pParamBlock->dwFlags));
   return (0);
} /* ix1amisc_check_firmware_code */





/**
 * Translate a card type into a string.
 */

const char *ix1amisc_get_card_type_name
   (Ix1aCardType_t cardType)
{
   if (cardType == IX1A_CARD_TYPE_UNKNOWN)
   {
      return ("Unknown");
   }
   else if ((size_t) cardType >= ARRAY_COUNT (g_apszCardTypeNames))
   {
      return ("Invalid");
   }
   return (g_apszCardTypeNames [(size_t) cardType]);
} /* ix1amisc_get_card_type_name */





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





/**
 * Check a data block for valid boot code.
 *
 * A valid boot code data block starts with a Ctrl-Z terminated string,
 * followed by a single byte (value not relevant). After this exactly 32KB of
 * binary data must follow. The string at the beginning must start with the
 * fixed text "Bootfile".
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbDataBlock           I: The boot code block to check.
 * @param nLenDataBlock         I: The length of the boot code block at
 *                                 pbDataBlock.
 * @param ppbBootCode           O: On successful return the address of the real
 *                                 boot code within the data block is returned.
 *
 * @retval 0                    The boot code data block is valid, *ppbBootCode
 *                              points to the location of the boot code.
 * @retval Else                 The boot code data block is invalid, the result
 *                              is an appropriate errno value.
 */

static int ix1amisc_check_boot_code
   (Ix1aSc_t        *pSc,
    const u_int8_t  *pbDataBlock,
    size_t           nLenDataBlock,
    const u_int8_t **ppbBootCode)
{
   const u_int8_t *p;
   size_t          n;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Checking boot code data block");
   
   /* an empty block is always an error */
   if (pbDataBlock == NULL || nLenDataBlock == 0 || ppbBootCode == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Empty boot code block, check failed");
      return (EINVAL);
   }
   
   /* check for the string "Bootfile" at the beginning of the block */
   if (nLenDataBlock < sizeof (IX1A_BOOT_FILE_START_STRING) ||
       strncmp ((const char *) pbDataBlock, IX1A_BOOT_FILE_START_STRING,
                sizeof (IX1A_BOOT_FILE_START_STRING) - 1) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid boot code data block, does not start with \"%s\"",
           IX1A_BOOT_FILE_START_STRING);
      return (EINVAL);
   }
   
   /* find the Ctrl-Z behind the text part */
   for (p = pbDataBlock, n = 0; n < nLenDataBlock && *p != '\x1A'; ++p, ++n)
      ;
   if (n >= nLenDataBlock)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid boot code data block, no Ctrl-Z found");
      return (EINVAL);
   }

   /* there is one dummy byte after the Ctrl-Z and we need the position behind
    * this byte
    */
   p += 2;
   n += 2;
   
   /* there must be exactly 32KB behind the current position */
   if (n + 32768 != nLenDataBlock)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid boot code data block, found %u bytes after Ctrl-Z and dummy byte, %u expected",
           (unsigned) (nLenDataBlock - n), 32768);
      return (EINVAL);
   }

   /* boot code is valid, return the boot code position */
   *ppbBootCode = p;
   DBG (LOG_DEBUG, pSc->iUnit,
        "Boot code data block valid, boot code starts at offset %u",
        (unsigned) n);
   return (0);
} /* ix1amisc_check_boot_code */





/**
 * Set the board to boot mode.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @return Nothing.
 */

static void ix1amisc_set_boot_mode
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned           u;

   pSc->state = IX1A_STATE_SET_BOOT_MODE;
   DBG (LOG_DEBUG, pSc->iUnit, "Set board state to SET-BOOT-MODE");
   
   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:
         /* set BOOT, RESET, ENABLE, 8bit mode, keep irq index */
         t = pSc->resInfo.ioTagData;
         h = pSc->resInfo.ioHandleData;
         u = bus_space_read_1 (t, h, IX1A_REG_CMD1);
         u &= IX1A_CMD1_IRQMASK;
         u |= IX1A_CMD1_BOOT | IX1A_CMD1_RESET | IX1A_CMD1_ENABLE;
         bus_space_write_1 (t, h, IX1A_REG_CMD1, u);
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         /* set RESET, ENABLE, keep irq index; board does not have BOOT bit and
          * supports only 16bit mode
          */
         t = pSc->resInfo.ioTagData;
         h = pSc->resInfo.ioHandleData;
         u = bus_space_read_1 (t, h, IX1A_REG_CMD1);
         u &= IX1A_CMD1_IRQMASK;
         u |= IX1A_CMD1_RESET | IX1A_CMD1_ENABLE;
         bus_space_write_1 (t, h, IX1A_REG_CMD1, u);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         /* set BOOT, RESET; board does not support 8bit mode and has no ENABLE
          * bit or irq index settings
          */
         t = pSc->resInfo.ioTagCmd;
         h = pSc->resInfo.ioHandleCmd;
         bus_space_write_1 (t, h, IX1A_REG_PCICMD_CMD1,
                            IX1A_CMD1_BOOT | IX1A_CMD1_RESET);
         break;
      
      default:
         /* other board types are not supported by this function */
         DBG (LOG_INFO, pSc->iUnit, "Unsupported board type, nothing to do");
         break;
   }
   
} /* ix1amisc_set_boot_mode */





/**
 * Request a test interrupt from the board and wait for it.
 *
 * A test interrupt can only be performed for ISA boards. For PCI boards this
 * task will silently be ignored, the function returns at once with success.
 *
 * For other boards all currently pending interrupts are cleared and a test
 * interrupt is requested. We wait for it for some time. If we get it, we are
 * done. If not, the function fails.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @retval 0                    The test interrupt was successful.
 * @retval Else                 We did not get any interrupt, board failure.
 *                              The result is an appropriate errno value.
 */

static int ix1amisc_do_test_int
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned           u;

   pSc->state = IX1A_STATE_TEST_INT_WAITING;
   DBG (LOG_DEBUG, pSc->iUnit, "Set board state to TEST-INT-WAITING");
   
   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:
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         /* this board does not support test interrupts, simply assume success
          */
         DBG (LOG_DEBUG, pSc->iUnit,
              "Board does not support test interrupt, assume success");
         return (0);
      
      default:
         /* other board types are not supported by this function */
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   /* first acknowledge any pending interrupt from the board (only ISA boards
    * possible if this point is reached)
    */
   ix1aisa_int_ack (pSc);
   
   /* prepare to wait for the test interrupt and to receive the signal about it
    */
   pSc->uRc = 0xFFFFFFFF;
   pSc->fWaitingForRc = 1;
   
   /* now toggle the TEST-INT bit, keep EISA interrupt flag for safety */
   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_INT2PC);
   bus_space_write_1 (t, h, IX1A_REG_CMD2, u);
   
   /* wait for the test interrupt to come */
   DBG (LOG_DEBUG, pSc->iUnit, "Test interrupt requested, wait for arrival");
   (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), hz * 5);
   if (pSc->fWaitingForRc || pSc->uRc != CAPI_OK)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Timeout waiting for test interrupt, board does not work");
      pSc->fWaitingForRc = 0;
      return (EIO);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit, "Got test interrupt");
   return (0);
} /* ix1amisc_do_test_int */





/**
 * Send the boot code to a board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbBootCode            I: The boot code to send. There must be exactly
 *                                 32KB of binary data at the specified
 *                                 location.
 *
 * @retval 0                    Sending the boot code was successful.
 * @retval Else                 Failure, the board did not respond to the
 *                              loading operation.
 */

static int ix1amisc_send_boot_code
   (Ix1aSc_t       *pSc,
    const u_int8_t *pbBootCode)
{
   unsigned u;
   int      iRes;
   
   pSc->state = IX1A_STATE_BOOTSTRAP_LOADING;
   DBG (LOG_DEBUG, pSc->iUnit, "Set board state to BOOTSTRAP-LOADING");
   
   /* prepare to wait for an interrupt as the board response to a successful
    * boot code download
    */
   pSc->uRc = 0xFFFFFFFF;
   pSc->fWaitingForRc = 1;
   
   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:
         /* write the boot code to shared memory offset 0, assume board is set
          * to 8bit mode
          */
         (void) ix1aisa_shm_write_block_8 (pSc, 0, pbBootCode, 32768);
         /* take back the RESET bit to boot the board */
         u = bus_space_read_1 (pSc->resInfo.ioTagData,
                               pSc->resInfo.ioHandleData,
                               IX1A_REG_CMD1);
         u &= ~IX1A_CMD1_RESET;
         u |= IX1A_CMD1_BOOT | IX1A_CMD1_ENABLE;
         bus_space_write_1 (pSc->resInfo.ioTagData,
                            pSc->resInfo.ioHandleData,
                            IX1A_REG_CMD1, u);
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         /* write the boot code to shared memory offset 0; board does not
          * support 8bit mode and thus is in 16bit mode
          */
         (void) ix1aisa_shm_write_block_16 (pSc, 0, pbBootCode, 32768);
         /* take back the RESET bit to boot the board */
         u = bus_space_read_1 (pSc->resInfo.ioTagData,
                               pSc->resInfo.ioHandleData,
                               IX1A_REG_CMD1);
         u &= ~IX1A_CMD1_RESET;
         u |= IX1A_CMD1_ENABLE;
         bus_space_write_1 (pSc->resInfo.ioTagData,
                            pSc->resInfo.ioHandleData,
                            IX1A_REG_CMD1, u);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         /* write the boot code to shared memory offset 0 using 16bit mode for
          * writing each single byte; board does not support 8bit mode and thus
          * is in 16bit mode
          */
         (void) ix1abpci_shm_write_block_8 (pSc, 0, pbBootCode, 32768);
         /* take back the RESET bit to boot the board; the board does not have
          * any other bits than BOOT and RESET
          */
         bus_space_write_1 (pSc->resInfo.ioTagCmd,
                            pSc->resInfo.ioHandleCmd,
                            IX1A_REG_PCICMD_CMD1, IX1A_CMD1_BOOT);
         break;
      
      default:
         /* other board types are not supported by this function */
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         pSc->fWaitingForRc = 0;
         return (EINVAL);
   }
   
   /* now wait for the board to generate an interrupt as an acknowledge for the
    * boot code download
    */
   (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), hz * 5);
   if (pSc->fWaitingForRc || pSc->uRc != CAPI_OK)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Timeout waiting for interrupt to acknowledge boot code download, board is not working");
      pSc->fWaitingForRc = 0;
      return (EIO);
   }
   
   /* we got an interrupt, what is still left is to read the boot status byte
    * to see if the boot code really works
    */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Got interrupt, reading boot status byte from board");
   iRes = ix1amisc_read_boot_status (pSc, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Boot code download failed, error reading boot status byte: %d",
           iRes);
      return (iRes);
   }
   if (u != IX1A_BOOT_STATUS_BOOT_CODE_OK)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Boot code download failed, board delivers boot status byte 0x%02X, 0x%02X expected",
           u, IX1A_BOOT_STATUS_BOOT_CODE_OK);
      return (EIO);
   }
   
   DBG (LOG_INFO, pSc->iUnit, "Boot code is active");
   return (0);
} /* ix1amisc_send_boot_code */





/**
 * Read the boot status code from a board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param puBootStatus          O: On successful return the boot status code
 *                                 byte is delivered here as an unsigned
 *                                 integer.
 *
 * @retval 0                    The boot status code was read successfully,
 *                              *puBootStatus is set accordingly.
 * @retval Else                 Failed to read boot status byte from the board.
 */

static int ix1amisc_read_boot_status
   (Ix1aSc_t *pSc,
    unsigned *puBootStatus)
{
   int iRes;
   
   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:
         iRes = ix1aisa_shm_read_byte
                   (pSc, IX1A_SHM_OFS_BOOT_STATUS_BYTE_BASIC, puBootStatus);
         break;

      case IX1A_CARD_TYPE_MMOD_ISA:
         iRes = ix1aisa_shm_read_byte
                   (pSc, IX1A_SHM_OFS_BOOT_STATUS_BYTE_PRIM, puBootStatus);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         *puBootStatus = bus_space_read_2 (pSc->resInfo.ioTagHeader,
                                           pSc->resInfo.ioHandleHeader,
                                           IX1A_REG_AMCC_IMB1);
         iRes = 0;
         break;
      
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   return (iRes);
} /* ix1amisc_read_boot_status */





/**
 * Send a boot command and interrupt to a board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uBootCmd              I: The boot command code to send.
 *
 * @retval 0                    The boot command was successfully send to the
 *                              board.
 * @retval Else                 Failure, the command could not be sent to the
 *                              board.
 */

static int ix1amisc_int_to_board
   (Ix1aSc_t *pSc,
    unsigned  uBootCmd)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned           u;
   int                iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Send boot command 0x%02X to board",
        uBootCmd);

   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:
         iRes = ix1aisa_shm_write_word
                   (pSc, IX1A_SHM_OFS_BOOT_COMMAND_BYTE_BASIC, uBootCmd);
         if (iRes != 0)
         {
            return (iRes);
         }
         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_MMOD_ISA:
         iRes = ix1aisa_shm_write_word
                   (pSc, IX1A_SHM_OFS_BOOT_COMMAND_BYTE_PRIM, uBootCmd);
         if (iRes != 0)
         {
            return (iRes);
         }
         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,
                            htole16 (uBootCmd));
         break;
      
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Boot command 0x%02X successfully sent to board",
        uBootCmd);
   return (0);
} /* ix1amisc_int_to_board */





/**
 * Prepare the board for sending the firmware code.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @return Nothing.
 */

static void ix1amisc_prepare_for_firmware
   (Ix1aSc_t *pSc)
{
   unsigned u;
   
   /* set 16bit mode, reset BOOT for Basic / Octo ISA boards (other boards do
    * not require any operation)
    */
   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:
         u = bus_space_read_1 (pSc->resInfo.ioTagData,
                               pSc->resInfo.ioHandleData,
                               IX1A_REG_CMD1);
         u &= IX1A_CMD1_IRQMASK;
         u |= IX1A_CMD1_16BIT | IX1A_CMD1_ENABLE;
         bus_space_write_1 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData,
                            IX1A_REG_CMD1, u);
         break;
      
      default:
         break;
   }
   
   DBG (LOG_DEBUG, pSc->iUnit, "Board prepared for firmware download");
   
} /* ix1amisc_prepare_for_firmware */





/**
 * Send the firmware parameter block to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pParamBlock           I/O: The parameter block to send. It will be
 *                                 modified to contain the correct block size
 *                                 for the firmware portions to send.
 *
 * @retval 0                    Sending the firmware parameter block was
 *                              successful.
 * @retval Else                 Failure, the board did not respond to the
 *                              loading operation.
 */

static int ix1amisc_send_param_block
   (Ix1aSc_t                 *pSc,
    Ix1aFirmwareParamBlock_t *pParamBlock)
{
   u_int16_t  aw [2];
   unsigned   u;
   u_int8_t  *p;
   size_t     n;
   int        iRes;
   
   pSc->state = IX1A_STATE_BOOTPARAM_LOADING;
   DBG (LOG_DEBUG, pSc->iUnit, "Set board state to BOOTPARAM-LOADING");
   
   /* determine the block size to use for the different board types */
   if (pSc->cardType == IX1A_CARD_TYPE_MMOD_ISA)
   {
      pParamBlock->dwBinBlockSize = htole32 (IX1A_FW_BLOCK_SIZE_SMALL);
   }
   else
   {
      pParamBlock->dwBinBlockSize = htole32 (IX1A_FW_BLOCK_SIZE_LARGE);
   }
   
   /* distinguish between the different hardware types supported by this
    * function
    */
   aw [0] = 0;
   aw [1] = 0;
   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:
         /* first reset the boot command (two consequtive words), will be set
          * later
          */
         (void) ix1aisa_shm_write_block_16
                   (pSc, IX1A_SHM_OFS_BOOT_COMMAND_BYTE_BASIC,
                    (u_int8_t *) aw, sizeof (aw));
         /* now write the parameter block using 8bit i/o even in 16bit mode;
          * the shared memory pointer is at the right position after the last
          * write operation
          */
         bus_space_write_multi_1 (pSc->resInfo.ioTagData,
                                  pSc->resInfo.ioHandleData,
                                  IX1A_REG_DATA_LOW,
                                  (u_int8_t *) pParamBlock,
                                  sizeof (*pParamBlock));
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         /* first reset the boot command (one word), will be set later */
         (void) ix1aisa_shm_write_word
                   (pSc, IX1A_SHM_OFS_BOOT_COMMAND_BYTE_PRIM, 0);
         /* now write the parameter block using 16bit i/o; the shared memory
          * pointer is at the right position after the last write operation
          */
         bus_space_write_multi_2 (pSc->resInfo.ioTagData,
                                  pSc->resInfo.ioHandleData,
                                  IX1A_REG_DATA_LOW,
                                  (u_int16_t *) pParamBlock,
                                  sizeof (*pParamBlock) / 2);
         break;
      
      case IX1A_CARD_TYPE_BASIC_PCI:
         /* first reset the boot command (two consequtive words), will be set
          * later
          */
         (void) ix1abpci_shm_write_block_16
                   (pSc, IX1A_SHM_OFS_BOOT_COMMAND_BYTE_BASIC,
                    (u_int8_t *) aw, sizeof (aw));
         /* now we must write the parameter block using 16bit i/o for each
          * single byte; the shared memory pointer is at the right position
          * after the last write operation
          */
         for (p = (u_int8_t *) pParamBlock, n = 0;
              n < sizeof (*pParamBlock);
              ++p, ++n)
         {
            bus_space_write_2 (pSc->resInfo.ioTagData,
                               pSc->resInfo.ioHandleData,
                               0, htole16 ((u_int16_t) *p));
         }
         break;
      
      default:
         DBG (LOG_ERROR, pSc->iUnit, "Unsupported board type");
         return (EINVAL);
   }
   
   /* prepare to wait for a board interrupt after sending an interrupt to the
    * board
    */
   pSc->uRc = 0xFFFFFFFF;
   pSc->fWaitingForRc = 1;
   
   /* after writing the parameter block to the shared memory we must tell the
    * board to accept it by setting the boot command and sending an interrupt
    */
   iRes = ix1amisc_int_to_board (pSc, IX1A_BOOT_COMMAND_PARAM_READY);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Sending boot parameter block failed, error generating interrupt to board: %d",
           iRes);
      pSc->fWaitingForRc = 0;
      return (iRes);
   }
   
   /* the board must respond with an interrupt, wait for it */
   (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), hz * 5);
   if (pSc->fWaitingForRc || pSc->uRc != CAPI_OK)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Sending boot parameter block failed, timeout waiting for interrupt as an acknowledge");
      pSc->fWaitingForRc = 0;
      return (EIO);
   }
   
   /* with the interrupt the board must deliver a boot status code, check it */
   iRes = ix1amisc_read_boot_status (pSc, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Sending boot parameter block failed, error reading boot status code: %d",
           iRes);
      return (iRes);
   }
   if (u != IX1A_BOOT_STATUS_PARAM_DONE)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Sending boot parameter block failed, board delivers boot status byte 0x%02X, 0x%02X expected",
           u, IX1A_BOOT_STATUS_PARAM_DONE);
      return (EIO);
   }

   DBG (LOG_DEBUG, pSc->iUnit,
        "Boot parameter block successfully transfered to board");
   return (0);
} /* ix1amisc_send_param_block */





/**
 * Send the firmware code to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbFirmwareCode        I: The address of the firmware code to send.
 * @param nLenFirmwareCode      I: The number of firmware code bytes to send.
 * @param nBlockSize            I: The size of a single firmware code portion
 *                                 to send to the board.
 *
 * @retval 0                    Sending the firmware code was successful.
 * @retval Else                 Failure, the board did not respone to the
 *                              loading operations.
 */

static int ix1amisc_send_firmware
   (Ix1aSc_t       *pSc,
    const u_int8_t *pbFirmwareCode,
    size_t          nLenFirmwareCode,
    size_t          nBlockSize)
{
   size_t   nCurrPos;
   size_t   nCurrSize;
   unsigned u;
   int      iRes;
   int      fRes;
   
   pSc->state = IX1A_STATE_FIRMWARE_LOADING;
   DBG (LOG_DEBUG, pSc->iUnit,
        "Set board state to FIRMWARE-LOADING, send 0x%08zX firmware bytes in blocks of 0x%08zX bytes",
        nLenFirmwareCode, nBlockSize);
   
   /* loop to send the whole firmware code in multiple blocks */
   nCurrPos = 0;
   do
   {
      /* determine the current block size */
      if (nCurrPos + nBlockSize > nLenFirmwareCode)
      {
         nCurrSize = nLenFirmwareCode - nCurrPos;
      }
      else
      {
         nCurrSize = nBlockSize;
      }
      
      /* send the current block to the board's shared memory */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Send 0x%08zX bytes from position 0x%08zX to board",
           nCurrSize, nCurrPos);
      iRes = ix1ashm_write_block_16
                (pSc, 0, pbFirmwareCode + nCurrPos, nCurrSize);
      if (iRes != 0)
      {
         return (iRes);
      }
      
      /* check that the shared memory is filled correctly */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Compare 0x%08zX bytes from position 0x%08zX with data written to board",
           nCurrSize, nCurrPos);
      iRes = ix1ashm_compare_block_16
                (pSc, 0, pbFirmwareCode + nCurrPos, nCurrSize, &fRes);
      if (iRes != 0)
      {
         return (iRes);
      }
      if (! fRes)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Firmware download failed, data written from position 0x%08zX, length %zu is different from data read for check",
              nCurrPos, nCurrSize);
         return (EIO);
      }
      
      /* prepare to wait for the board to acknowledge the current block */
      pSc->uRc = 0xFFFFFFFF;
      pSc->fWaitingForRc = 1;
      
      /* tell the board to accept the current block */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Firmware block of size %zu from position 0x%08zX written, tell board to accept",
           nCurrSize, nCurrPos);
      iRes = ix1amisc_int_to_board (pSc, IX1A_BOOT_COMMAND_BLOCK_2LR_READY);
      if (iRes != 0)
      {
         pSc->fWaitingForRc = 0;
         return (iRes);
      }
      
      /* wait for the board to acknowledge the current block */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Wait for board to accept current firmware data block");
      (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), hz * 2);
      if (pSc->fWaitingForRc || pSc->uRc != CAPI_OK)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Firmware download failed, timeout waiting for acknowledge of firmware block from position 0x%08zX, length 0x%08zX",
              nCurrPos, nCurrSize);
         pSc->fWaitingForRc = 0;
         return (EIO);
      }
      
      /* check the boot status code for success */
      DBG (LOG_DEBUG, pSc->iUnit, "Got interrupt, read boot status code");
      iRes = ix1amisc_read_boot_status (pSc, &u);
      if (iRes != 0)
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Firmware download failed, error reading boot status code after written firmware block: %d",
              iRes);
         return (iRes);
      }
      if (u == IX1A_BOOT_STATUS_BLOCK_2LR_DONE &&
          nCurrPos + nCurrSize < nLenFirmwareCode)
      {
         DBG (LOG_DEBUG, pSc->iUnit,
              "Board accepted current firmware block, send next one");
      }
      else if (u == IX1A_BOOT_STATUS_TRANSMIT_OK &&
               nCurrPos + nCurrSize >= nLenFirmwareCode)
      {
         DBG (LOG_DEBUG, pSc->iUnit,
              "Board accepted last firmware block");
      }
      else
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Board rejected current firmware block with status code 0x%02X, expected 0x%02X",
              u,
              (nCurrPos + nCurrSize >= nLenFirmwareCode)
                 ? IX1A_BOOT_STATUS_TRANSMIT_OK
                 : IX1A_BOOT_STATUS_BLOCK_2LR_DONE);
      }
      
      /* increment current source position */
      nCurrPos += nCurrSize;
      
   } while (nCurrPos < nLenFirmwareCode);
   
   DBG (LOG_INFO, pSc->iUnit, "Firmware download complete");
   return (0);
} /* ix1amisc_send_firmware */





/**
 * Send line settings to the board.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 * @param pbSettings            I: The address of the settings data to send.
 * @param nLenSettings          I: The number of settings bytes to send.
 *
 * @retval 0                    Sending the line settings was successful.
 * @retval Else                 Failure, the board did not respone to the
 *                              sending operation.
 */

static int ix1amisc_send_port_params
   (Ix1aSc_t *pSc,
    u_int8_t *pbSettings,
    size_t    nLenSettings)
{
   int iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Write %zu bytes for adapter settings to shared memory",
        nLenSettings);

   /* print out excerpt from the line parameters */
   if (e_iIx1aLogLevel >= LOG_TRACE)
   {
      Ix1aBoardParameters_t           *p =
         (Ix1aBoardParameters_t *) pbSettings;
      Ix1aBoardLineConfigParameters_t *pl;
      int                              i;
      
      DBG (LOG_TRACE, pSc->iUnit, "Line port parameters version: 0x%02X",
           (unsigned) le32toh (p->dwVersion));
      DBG (LOG_TRACE, pSc->iUnit, "Line port parameters size:    0x%04X",
           (unsigned) le32toh (p->dwSize));
      DBG (LOG_TRACE, pSc->iUnit, "Board settings:");
      DBG (LOG_TRACE, pSc->iUnit,
           "   MVIP type:    %lu",
           (unsigned long) le32toh (p->general.dwMvipType));
      DBG (LOG_TRACE, pSc->iUnit,
           "   Coding law:   %lu",
           (unsigned long) le32toh (p->general.dwCodingLaw));
      DBG (LOG_TRACE, pSc->iUnit,
           "   Country code: %lu",
           (unsigned long) le32toh (p->general.dwMultimodemCountryCode));
      DBG (LOG_TRACE, pSc->iUnit,
           "   Compression:  %lu",
           (unsigned long) le32toh (p->comp.dwCompMode));

      for (i = 0; (size_t) i < (size_t) le32toh (p->dwNumLines); ++i)
      {
         pl = &(p->aLineParams [i]);
         
         DBG (LOG_TRACE, pSc->iUnit, "Settings for port %d:", i);
         
         DBG (LOG_TRACE, pSc->iUnit,
              "   Line access:        %lu",
              (unsigned long) le32toh (pl->interface.dwLineAccess));
         DBG (LOG_TRACE, pSc->iUnit,
              "   Line type:          %lu",
              (unsigned long) le32toh (pl->interface.dwLineType));
         DBG (LOG_TRACE, pSc->iUnit,
              "   D-channel protocol: %lu",
              (unsigned long) le32toh (pl->dchan.dwDProtocol));
         DBG (LOG_TRACE, pSc->iUnit,
              "   TEI type:           %lu",
              (unsigned long) le32toh (pl->dchan.dwTeiType));
         DBG (LOG_TRACE, pSc->iUnit,
              "   TEI value:          %lu",
              (unsigned long) le32toh (pl->dchan.dwTeiValue));
         DBG (LOG_TRACE, pSc->iUnit,
              "   NT/TE mode:         %lu",
              (unsigned long) le32toh (pl->interface.dwNtTeSide));
      }
   }
   
   iRes = ix1ashm_write_block_16
             (pSc, IX1A_SHM_OFS_ADAPTER_PARAMS,
              IX1A_SHM_ADAPTER_PARAMS_MAGIC_STRING,
              sizeof (IX1A_SHM_ADAPTER_PARAMS_MAGIC_STRING));
   if (iRes != 0)
   {
      return (iRes);
   }
   iRes = ix1ashm_write_block_16
             (pSc,
              (IX1A_SHM_OFS_ADAPTER_PARAMS +
                  sizeof (IX1A_SHM_ADAPTER_PARAMS_MAGIC_STRING)) & ~1,
              pbSettings, nLenSettings);
   if (iRes != 0)
   {
      return (iRes);
   }

   DBG (LOG_DEBUG, pSc->iUnit, "Adapter settings written successfully");
   return (0);
} /* ix1amisc_send_port_params */





/**
 * Start the board firmware after a successful download operation.
 *
 * @param pSc                   I/O: The softc structure for the board context.
 *
 * @retval 0                    The board is now ready for operation.
 * @retval Else                 Starting the firmware failed, the board is in
 *                              undefined state.
 */

static int ix1amisc_start_firmware
   (Ix1aSc_t *pSc)
{
   u_int16_t aw [4];
   int       iCount;
   u_int8_t  bFwStatus;
   int       iRes;
   
   pSc->state = IX1A_STATE_FIRMWARE_STARTING;
   DBG (LOG_DEBUG, pSc->iUnit, "Set board state to FIRMWARE-STARTING");
   
   /* clear the firmware ready flag; will be set by the board if firmware was
    * successfully started
    */
   bzero (aw, sizeof (aw));
   iRes = ix1ashm_write_block_16 (pSc, 0, (u_int8_t *) aw, sizeof (aw));
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Starting firmware failed, error clearing firmware ready flag: %d",
           iRes);
      return (iRes);
   }

   /* start the firmware */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Firmware ready flag cleared, send run command");
   iRes = ix1amisc_int_to_board (pSc, IX1A_BOOT_COMMAND_LR_RUN);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Starting firmware failed, error sending run command to board: %d",
           iRes);
      return (iRes);
   }
   
   /* check every 100ms if the board is running, total timeout is about 30s */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Run command sent successfully, poll for firmware ready");
   iCount = 300;
   do
   {
      /* wait for 100ms */
      (void) msleep (pSc, &(pSc->mtxAccess),
                     PZERO, IX1AMISC_FW_READY_WAIT_STRING, hz / 10);
      --iCount;
      
      /* read the firmware ready flag */
      iRes = ix1ashm_read_block_16
                (pSc, 0, (u_int8_t *) aw, sizeof (aw [0]) * 3);
      if (iRes != 0)
      {
         /* This is normally an unrecoverable error. But maybe it goes away
          * during the timeout. So we just try again.
          */
         continue;
      }
      
      /* check the just read board status value (first byte, low byte of the
       * first word in little endian byte order)
       */
      bFwStatus = le16toh (aw [0]) & 0xFF;
      if (bFwStatus == IX1A_FW_STATUS_STARTUP_IN_PROGRESS)
      {
         /* board is still starting, continue waiting */
         continue;
      }
      else if (bFwStatus == IX1A_FW_STATUS_WORKING)
      {
         /* board is running, perform last check after the loop */
         break;
      }
      else if (bFwStatus == IX1A_FW_STATUS_WARNING_CONFIGURATION)
      {
         /* board is working but rejected the line configuration, will
          * nevertheless assume working board (if last check succeeds)
          */
         printf ("%s%d: WARNING: Board did not accept line configuration\n",
                 pSc->szDriverName, pSc->iUnit);
         break;
      }
      else
      {
         /* any other value denotes firmware start failure, no need to wait
          * further
          */
         DBG (LOG_ERROR, pSc->iUnit,
              "Starting firmware failed, board delivered firmware status 0x%02X",
              (unsigned) bFwStatus);
         return (EIO);
      }
      
   } while (iCount > 0);
   
   /* check for the string "ITK" starting at the 2nd word of the block read
    * from shared memory during the loop
    */
   if (bcmp (IX1A_FW_READY_STRING, (const u_int8_t *) &(aw [1]),
             sizeof (IX1A_FW_READY_STRING) - 1) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Starting firmware failed, board did not deliver string \"%s\", found \"%.*s\"",
           IX1A_FW_READY_STRING, (int) sizeof (IX1A_FW_READY_STRING) - 1,
           (const char *) &(aw [1]));
      return (EIO);
   }

   /* if we reach this point, the board is ready and working (maybe with
    * configuration warnings)
    */
   DBG (LOG_INFO, pSc->iUnit, "Board successfully started");
   return (0);
} /* ix1amisc_start_firmware */
