/**
 * @file ix1a_isa.c
 *
 * IX1a-ISA - Routines and specifications for IX1 ISA boards.
 *
 * Copyright: 2004 Thomas Wintergerst. All rights reserved.
 *
 * $Id: ix1a_isa.c,v 1.1.2.1 2006/02/28 21:35:21 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     ix1a - CAPI manager driver for IX1 active ISDN controllers $
 * @date        14.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/errno.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_ISA__

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





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





/** Low byte to send to an ISA board to enable it. */
#define IX1AISA_ENABLE_CMD_LOW     0xE6

/** High byte to send to an ISA board to enable it. */
#define IX1AISA_ENABLE_CMD_HIGH    0x70

/** Byte to send to an ISA board to disable it. */
#define IX1AISA_DISABLE_CMD        0xA5





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





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





/**
 * Interrupt routine for ISA boards.
 *
 * @param pSc                   I/O: The softc structure for the board raising
 *                                 the interrupt.
 *
 * @return Nothing.
 */
static void ix1aisa_interrupt_handler
   (Ix1aSc_t *pSc);

/**
 * Check if a specified board is already initialised and working.
 *
 * @param pSc                   I/O: The softc structure for the board to check.
 *
 * @retval 0                    The board is not working and must be
 *                              initialised before use.
 * @retval 1                    The board is ready for operation.
 */
static int ix1aisa_check_board_enabled
   (Ix1aSc_t *pSc);

/**
 * Determine the scratch value to assign an i/o address to a board.
 *
 * @param pszDriverName         I: The driver name for error output.
 * @param iUnit                 I: The driver unit number for error output.
 * @param cardType              I: The board type for the device.
 * @param iIoBase               I: The i/o port base address, will be coded
 *                                 into the scratch value.
 * @param iJumperSetting        I: The jumper setting of the board.
 *
 * @return The scratch value determined from the i/o base address and the
 *         jumper setting. This value is always positive and less than 0x100,
 *         so it fits into a byte. If the parameters are not valid for the
 *         specific board type are invalid, a negative value is returned.
 */
static int ix1aisa_get_scratch_value
   (const char     *pszDriverName,
    int             iUnit,
    Ix1aCardType_t  cardType,
    int             iIoBase,
    int             iJumperSetting);

/**
 * Translate an IRQ number into an index value to tell the board.
 *
 * Active IX1 ISA boards can only use a limited number of IRQ numbers. Basic
 * (and Octo) boards can only use 4, Multimodems can use 7 different ones. The
 * IRQ number to use must be translated to an index into the array of useable
 * interrupts for the respective board type.
 *
 * @param pszDriverName         I: The driver name for error output.
 * @param iUnit                 I: The driver unit number for error output.
 * @param cardType              I: The card type, determines the interrupt
 *                                 table to use.
 * @param iIrq                  I: The IRQ number to assign to the board.
 *
 * @return The IRQ index to send to the board. If the IRQ number specified is
 *         not valid for the board, the result is 0xFFFFFFFF, i.e. lies outside
 *         of any valid IRQ index.
 */
static unsigned ix1aisa_get_irq_index
   (const char     *pszDriverName,
    int             iUnit,
    Ix1aCardType_t  cardType,
    int             iIrq);

/**
 * Check if an interrupt is pending for a board.
 *
 * This function is called to check if the board specified has raised an
 * interrupt and is waiting for an acknowledge.
 *
 * All revision 1 boards are missing a flag for this information in their
 * status registers, so for these boards we always must assume a pending
 * interrupt.
 *
 * Boards of revision 2 and above have an INT-PENDING bit in their status
 * register. So for these boards the status bit is checked and the result is
 * delivered as the function result.
 *
 * @param pSc                   I/O: The board to check for a pending
 *                                 interrupt.
 *
 * @retval 0                    No interrupt is pending from the board.
 * @retval 1                    The board raised an interrupt and is waiting
 *                              for an acknowledge.
 */
static int ix1aisa_is_int_pending
   (Ix1aSc_t *pSc);





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





/**
 *  Initialise and fill the resource info structure for an ISA board.
 *
 * This function will initialise the resource info structure for ISA boards
 * and acquire the needed resources.
 *
 * @param dev                   I: The device entry for the board to register.
 * @param pResInfo              I/O: The resource info structure to fill.
 *
 * @retval 0                    All resources could be acquired.
 * @retval Else                 At least one resource could not be acquired.
 *                              In fact no resource is required in this case.
 */

int ix1aisa_init_resinfo
   (device_t            dev,
    Ix1aResourceInfo_t *pResInfo)
{
   unsigned long ulBase;
   unsigned long ulCount;
   int           iRes;

   pResInfo->iRidIoData = 0;
   iRes = bus_get_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoData,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Unable to get i/o port range (error code %d)\n",
                      iRes);
      return (ENXIO);
   }
   pResInfo->iIoBaseData = (int) ulBase;

   (void) bus_set_resource (dev, SYS_RES_IOPORT,
                            pResInfo->iRidIoData,
                            (u_long) (pResInfo->iIoBaseData),
                            IX1A_ISA_IO_PORT_RANGE);
   pResInfo->pResIoData = bus_alloc_resource
                             (dev, SYS_RES_IOPORT,
                              &(pResInfo->iRidIoData),
                              0UL, ~0UL, 1, RF_ACTIVE);
   if (pResInfo->pResIoData == NULL)
   {
      device_printf (dev,
                     "Unable to allocate i/o port range 0x%03X, 0x%02X\n",
                     pResInfo->iIoBaseData, IX1A_ISA_IO_PORT_RANGE);
      return (ENXIO);
   }

   pResInfo->ioTagData    = rman_get_bustag (pResInfo->pResIoData);
   pResInfo->ioHandleData = rman_get_bushandle (pResInfo->pResIoData);
   
   pResInfo->iRidIrq = 0;
   iRes = bus_get_resource (dev, SYS_RES_IRQ, pResInfo->iRidIrq,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Unable to get irq number (error code %d)\n",
                     iRes);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   pResInfo->iIrq = (int) ulBase;
   
   (void) bus_set_resource (dev, SYS_RES_IRQ,
                            pResInfo->iRidIrq, (u_long) (pResInfo->iIrq), 1);
   pResInfo->pResIrq = bus_alloc_resource
                          (dev, SYS_RES_IRQ, &(pResInfo->iRidIrq),
                           0UL, ~0UL, 1, RF_ACTIVE | RF_SHAREABLE);
   if (pResInfo->pResIrq == NULL)
   {
      device_printf (dev, "Unable to allocate irq %d\n",
                     pResInfo->iIrq);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   /* Note: The interrupt routine will be setup by the caller when apropriate
    */
                            
   return (0);       
} /* ix1aisa_init_resinfo */






/**
 * Initialise ISA board for operation.
 *
 * This function is called to initialise an ISA board. The first task is to
 * tell the board its i/o address. Before that the board is completely disabled
 * and does not react on any i/o operation. If the board was initialised
 * before, this step must be left out.
 *
 * Other steps are also necessary to render the board operational. The last one
 * is to turn the board into reset mode. In this state it does nothing and
 * waits for the bootstrap code to be downloaded.
 *
 * @param dev                   I: The device representing the board.
 * @param pSc                   I/O: The softc structure for the board.
 * @param cardType              I: The expected card type. It is used to handle
 *                                 the slight differences between the ISA board
 *                                 types and is checked against the card type
 *                                 delivered by the board itself.
 * @param fFullInit             I:
 *                              - 0
 *                                 Only basic tests are performed. This is used
 *                                 during the probe phase. It will only be
 *                                 checked that there is a board and that it is
 *                                 responding to i/o operations.
 *                              - 1
 *                                 All tests are performed. This includes the
 *                                 on-board shared memory tests in addition to
 *                                 the basic tests. And finally the interrupt
 *                                 routine is enabled.
 *
 * @retval 0                    The tests were successful, board exists and is
 *                              working.
 * @retval Else                 Error occurred, board does not exist or is not
 *                              working.
 */

int ix1aisa_init_board
   (device_t        dev,
    Ix1aSc_t       *pSc,
    Ix1aCardType_t  cardType,
    int             fFullInit)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   unsigned           uIrqIdx;
   unsigned           uScratchVal;
   unsigned           uScratchBackup;
   unsigned           u;
   int                iFlags;
   int                iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start board initialisation");
   
   /* check if the card has already its i/o port assigned */
   uIrqIdx = 0;
   if (! ix1aisa_check_board_enabled (pSc))
   {
      /* the board is currently completely passive, must assign its i/o port
       * base address
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Board passive, perform initialisation");
      
      /* determine the scratch value to address the current board */
      uScratchVal = ix1aisa_get_scratch_value
                       (device_get_name (dev), device_get_unit (dev), cardType,
                        pSc->resInfo.iIoBaseData, pSc->iJumperSetting);
      if (uScratchVal < 0)
      {
         /* Note: Error message already printed. */
         return (ENXIO);
      }
      DBG (LOG_DEBUG, pSc->iUnit,
           "Scratch value for i/o base 0x%03X, jumper setting %d determined to 0x%02X",
           pSc->resInfo.iIoBaseData, pSc->iJumperSetting, uScratchVal);
      
      /* determine the irq index for the irq number to assign */
      uIrqIdx = ix1aisa_get_irq_index
                   (device_get_name (dev), device_get_unit (dev),
                    cardType, pSc->resInfo.iIrq);
      if ((uIrqIdx & ~IX1A_CMD1_IRQMASK) != 0)
      {
         /* Note: Error message already printed. */
         return (ENXIO);
      }
      DBG (LOG_DEBUG, pSc->iUnit, "Index for irq %d determined to 0x%02X",
           pSc->resInfo.iIrq, uIrqIdx);
      
      /* save current value of scratch register */
      /* HACK: We need to address i/o port 0x3FF directly (not assigned to any
       *       real device). We avoid the overhead to obtain "official" access
       *       to this port and use the address directly as the handle.
       */
      uScratchBackup = (unsigned) bus_space_read_1
                                     (t, (bus_space_handle_t) 0x3FF, 0);
      
      /* write scratch value into the scratch register; only the board with the
       * jumper setting leading to this scratch value will react and take the
       * i/o port base also coded into the scratch value
       */
      bus_space_write_1 (t, (bus_space_handle_t) 0x3FF, 0, uScratchVal);
      
      /* clear any pending irq from the board */
      bus_space_write_1 (t, h, IX1A_REG_CMD2, IX1A_CMD2_INT2PC);
      bus_space_write_1 (t, h, IX1A_REG_CMD2, 0);
      
      /* set the ENABLE bit and tell the board the irq index to use */
      u = (unsigned) bus_space_read_1 (t, h, IX1A_REG_CMD1);
      bus_space_write_1
         (t, h, IX1A_REG_CMD1,
          (u & ~IX1A_CMD1_IRQMASK) | IX1A_CMD1_ENABLE | uIrqIdx);
      
      /* now perform the real activation by writing some magic values to the
       * offset register
       */
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_ENABLE_CMD_LOW);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_ENABLE_CMD_HIGH);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_ENABLE_CMD_LOW);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_ENABLE_CMD_HIGH);

      /* restore the scratch register value */
      bus_space_write_1 (t, (bus_space_handle_t) 0x3FF, 0, uScratchBackup);

      /* now again check if there is a board at the current i/o port */
      if (! ix1aisa_check_board_enabled (pSc))
      {
         device_printf (dev,
                        "ERROR: Board not working after initialisation attempt, no device with jumper setting %d\n",
                        pSc->iJumperSetting);
         ix1aisa_disable_board (pSc);
         return (ENXIO);
      }
   }
   else
   {
      /* the board is already working, only set to defined state */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Board already enabled, only (re-)assign irq");
      
      /* clear any pending irq from the board */
      bus_space_write_1 (t, h, IX1A_REG_CMD2, IX1A_CMD2_INT2PC);
      bus_space_write_1 (t, h, IX1A_REG_CMD2, 0);
      
      /* tell the board the (maybe new) irq index to use; keep the BOOT, RESET
       * and 16Bit flags, set ENABLE
       */
      uIrqIdx = ix1aisa_get_irq_index
                   (device_get_name (dev), device_get_unit (dev),
                    cardType, pSc->resInfo.iIrq);
      if ((uIrqIdx & ~IX1A_CMD1_IRQMASK) != 0)
      {
         /* Note: Error message already printed. */
         return (ENXIO);
      }
      DBG (LOG_DEBUG, pSc->iUnit, "Index for irq %d determined to 0x%02X",
           pSc->resInfo.iIrq, uIrqIdx);
      u = (unsigned) bus_space_read_1 (t, h, IX1A_REG_CMD1);
      u &= IX1A_CMD1_BOOT | IX1A_CMD1_RESET | IX1A_CMD1_16BIT;
      u |= IX1A_CMD1_ENABLE | uIrqIdx;
      bus_space_write_1 (t, h, IX1A_REG_CMD1, u);
   }
   
   /* reset the shared memory pointer */
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, 0);
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, 0);
   
   /* get hardware type and version from the board; must fit to the card type
    * specified as the function argument
    */
   u = (unsigned) bus_space_read_1 (t, h, IX1A_REG_HWTYPE);
   switch (cardType)
   {
      case IX1A_CARD_TYPE_BASIC_UP0_ISA:
      case IX1A_CARD_TYPE_BASIC_S0_ISA:
         if (u >> 2 != IX1A_CARD_TYPE_BASIC_UP0_ISA &&
             u >> 2 != IX1A_CARD_TYPE_BASIC_S0_ISA)
         {
            device_printf (dev,
                           "ERROR: Board is configured to be \"%s\" but claims to be \"%s\", unable to configure device\n",
                           ix1amisc_get_card_type_name (cardType),
                           ix1amisc_get_card_type_name (u >> 2));
            ix1aisa_disable_board (pSc);
            return (ENXIO);
         }
         pSc->cardType = u >> 2;
         pSc->iBoardRevision = u & 0x03;
         break;

      case IX1A_CARD_TYPE_MMOD_ISA:
         if (u >> 2 != IX1A_CARD_TYPE_MMOD_ISA)
         {
            device_printf (dev,
                           "ERROR: Board is configured to be \"%s\" but claims to be \"%s\", unable to configure device\n",
                           ix1amisc_get_card_type_name (cardType),
                           ix1amisc_get_card_type_name (u >> 2));
            ix1aisa_disable_board (pSc);
            return (ENXIO);
         }
         pSc->cardType = u >> 2;
         pSc->iBoardRevision = u & 0x03;
         break;

      case IX1A_CARD_TYPE_OCTO_UP0_ISA:
      case IX1A_CARD_TYPE_OCTO_S0_ISA:
         if (u >> 2 != IX1A_CARD_TYPE_BASIC_UP0_ISA &&
             u >> 2 != IX1A_CARD_TYPE_BASIC_S0_ISA)
         {
            device_printf (dev,
                           "ERROR: Board is configured to be \"%s\" but claims to be \"%s\", unable to configure device\n",
                           ix1amisc_get_card_type_name (cardType),
                           ix1amisc_get_card_type_name (u >> 2));
            ix1aisa_disable_board (pSc);
            return (ENXIO);
         }
         if (u >> 2 == IX1A_CARD_TYPE_BASIC_UP0_ISA)
         {
            pSc->cardType = IX1A_CARD_TYPE_OCTO_UP0_ISA;
         }
         else
         {
            pSc->cardType = IX1A_CARD_TYPE_OCTO_S0_ISA;
         }
         pSc->iBoardRevision = u & 0x03;
         break;

      default:
         DBG (LOG_ERROR, pSc->iUnit,
              "Unsupported ISA board type \"%s\" (%d)",
              ix1amisc_get_card_type_name (cardType), (int) cardType);
         return (ENXIO);
   }
   DBG (LOG_INFO, pSc->iUnit,
        "Board type determined to \"%s\", revision 1.%d",
        ix1amisc_get_card_type_name (pSc->cardType), pSc->iBoardRevision);

   /* set board to 16bit mode, set RESET bit, keep ENABLE and the irq index,
    * unset BOOT
    */
   if (cardType == IX1A_CARD_TYPE_MMOD_ISA)
   {
      bus_space_write_1 (t, h, IX1A_REG_CMD1,
                         IX1A_CMD1_RESET | IX1A_CMD1_ENABLE | uIrqIdx);
   }
   else
   {
      bus_space_write_1 (t, h, IX1A_REG_CMD1,
                         IX1A_CMD1_RESET | IX1A_CMD1_16BIT | IX1A_CMD1_ENABLE |
                            uIrqIdx);
   }
   
   /* if only basic initialisation is requested, we are ready now */
   if (! fFullInit)
   {
      DBG (LOG_DEBUG, pSc->iUnit, "Basic initialisation successful");
      return (0);
   }
   
   /* for full initialisation now check the shared memory */
   DBG (LOG_DEBUG, pSc->iUnit, "Start checking shared memory");
   ix1aisa_shm_write_word (pSc, 0x0, IX1A_SHM_TEST_WORD_LOW);
   ix1aisa_shm_write_word (pSc, IX1A_SHM_SIZE_ISA - 2,
                              IX1A_SHM_TEST_WORD_HIGH);
   iRes = ix1aisa_shm_read_word (pSc, 0x0, &u);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Shared memory error, unable to read from address 0x%04X\n",
                     0);
      ix1aisa_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_LOW)
   {
      device_printf (dev,
                     "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
                     IX1A_SHM_TEST_WORD_LOW, 0x0, u);
      ix1aisa_disable_board (pSc);
      return (EIO);
   }
   iRes = ix1aisa_shm_read_word (pSc, IX1A_SHM_SIZE_ISA - 2, &u);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Shared memory error, unable to read from address 0x%04X\n",
                     IX1A_SHM_SIZE_ISA - 2);
      ix1aisa_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_HIGH)
   {
      device_printf (dev,
                     "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
                     IX1A_SHM_TEST_WORD_HIGH, IX1A_SHM_SIZE_ISA - 2, u);
      ix1aisa_disable_board (pSc);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Shared memory test successful");
   
   /* finally enable the interrupt routine to handle interrupts from the board
    */
   if (! pSc->resInfo.fIrqSetup)
   {
      iFlags = INTR_TYPE_NET | INTR_MPSAFE;
      if (pSc->iBoardRevision < 2)
      {
         /* boards with revision 1 cannot share interrupts because of the
          * missing INT-PENDING status bit
          */
         iFlags |= INTR_EXCL;
      }
      iRes = bus_setup_intr (dev, pSc->resInfo.pResIrq, iFlags,
                             (driver_intr_t *) ix1aisa_interrupt_handler, pSc,
                             &(pSc->resInfo.hIrqCookie));
      if (iRes != 0)
      {
         device_printf (dev, "Error setting up interrupt handler: %d\n",
                        iRes);
         ix1aisa_disable_board (pSc);
         return (iRes);
      }
      pSc->resInfo.fIrqSetup = 1;
      DBG (LOG_DEBUG, pSc->iUnit, "Interrupt handler set up");
   }

   DBG (LOG_TRACE, pSc->iUnit, "Board initialised successfully");
   return (0);
} /* ix1aisa_init_board */





/**
 * Enable an ISA board after disabling it.
 *
 * @note This routine expects the board to have been correctly initialised
 *       before disabling. So no attempt is made to determine the correctness
 *       of the board type.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 enable.
 *
 * @retval 0                    The board was successfully enabled.
 * @retval Else                 Enabling the board failed, the result is an
 *                              errno value.
 */

int ix1aisa_enable_board
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   unsigned           uIrqIdx;
   unsigned           uScratchVal;
   unsigned           uScratchBackup;
   unsigned           u;
   int                iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start enabling board");
   
   /* check if the card has already / still its i/o port assigned */
   uIrqIdx = 0;
   if (! ix1aisa_check_board_enabled (pSc))
   {
      /* the board is currently completely passive, must assign its i/o port
       * base address
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Board passive, perform initialisation");
      
      /* determine the scratch value to address the current board */
      uScratchVal = ix1aisa_get_scratch_value
                       (pSc->szDriverName, pSc->iUnit, pSc->cardType,
                        pSc->resInfo.iIoBaseData, pSc->iJumperSetting);
      if (uScratchVal < 0)
      {
         /* Note: Error message already printed. */
         return (ENXIO);
      }
      DBG (LOG_DEBUG, pSc->iUnit,
           "Scratch value for i/o base 0x%03X, jumper setting %d determined to 0x%02X",
           pSc->resInfo.iIoBaseData, pSc->iJumperSetting, uScratchVal);
      
      /* determine the irq index for the irq number to assign */
      uIrqIdx = ix1aisa_get_irq_index
                   (pSc->szDriverName, pSc->iUnit,
                    pSc->cardType, pSc->resInfo.iIrq);
      if ((uIrqIdx & ~IX1A_CMD1_IRQMASK) != 0)
      {
         /* Note: Error message already printed. */
         return (ENXIO);
      }
      DBG (LOG_DEBUG, pSc->iUnit, "Index for irq %d determined to 0x%02X",
           pSc->resInfo.iIrq, uIrqIdx);
      
      /* save current value of scratch register */
      /* HACK: We need to address i/o port 0x3FF directly (not assigned to any
       *       real device). We avoid the overhead to obtain "official" access
       *       to this port and use the address directly as the handle.
       */
      uScratchBackup = (unsigned) bus_space_read_1
                                     (t, (bus_space_handle_t) 0x3FF, 0);
      
      /* write scratch value into the scratch register; only the board with the
       * jumper setting leading to this scratch value will react and take the
       * i/o port base also coded into the scratch value
       */
      bus_space_write_1 (t, (bus_space_handle_t) 0x3FF, 0, uScratchVal);
      
      /* clear any pending irq from the board */
      bus_space_write_1 (t, h, IX1A_REG_CMD2, IX1A_CMD2_INT2PC);
      bus_space_write_1 (t, h, IX1A_REG_CMD2, 0);
      
      /* set the ENABLE bit and tell the board the irq index to use */
      u = (unsigned) bus_space_read_1 (t, h, IX1A_REG_CMD1);
      bus_space_write_1
         (t, h, IX1A_REG_CMD1,
          (u & ~IX1A_CMD1_IRQMASK) | IX1A_CMD1_ENABLE | uIrqIdx);
      
      /* now perform the real activation by writing some magic values to the
       * offset register
       */
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_ENABLE_CMD_LOW);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_ENABLE_CMD_HIGH);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_ENABLE_CMD_LOW);
      bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_ENABLE_CMD_HIGH);

      /* restore the scratch register value */
      bus_space_write_1 (t, (bus_space_handle_t) 0x3FF, 0, uScratchBackup);

      /* now again check if there is a board at the current i/o port */
      if (! ix1aisa_check_board_enabled (pSc))
      {
         DBG (LOG_ERROR, pSc->iUnit,
              "Board not working after initialisation attempt, no device with jumper setting %d\n",
              pSc->iJumperSetting);
         ix1aisa_disable_board (pSc);
         return (ENXIO);
      }
   }
   else
   {
      /* the board is already working, only set to defined state */
      DBG (LOG_DEBUG, pSc->iUnit,
           "Board already enabled, only set to defined state");
      
      /* clear any pending irq from the board */
      bus_space_write_1 (t, h, IX1A_REG_CMD2, IX1A_CMD2_INT2PC);
      bus_space_write_1 (t, h, IX1A_REG_CMD2, 0);
      
      /* command register 1 will be set soon */
   }
   
   /* reset the shared memory pointer */
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, 0);
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, 0);
   
   /* set board to 16bit mode, set RESET bit, keep ENABLE and the irq index,
    * unset BOOT
    */
   u = bus_space_read_1 (t, h, IX1A_REG_CMD1);
   u &= ~IX1A_CMD1_BOOT;
   u |= IX1A_CMD1_RESET | IX1A_CMD1_ENABLE;
   if (pSc->cardType != IX1A_CARD_TYPE_MMOD_ISA)
   {
      u |= IX1A_CMD1_16BIT;
   }
   bus_space_write_1 (t, h, IX1A_REG_CMD1, u);
   
   /* now check the shared memory */
   DBG (LOG_DEBUG, pSc->iUnit, "Start checking shared memory");
   ix1aisa_shm_write_word (pSc, 0x0, IX1A_SHM_TEST_WORD_LOW);
   ix1aisa_shm_write_word (pSc, IX1A_SHM_SIZE_ISA - 2,
                              IX1A_SHM_TEST_WORD_HIGH);
   iRes = ix1aisa_shm_read_word (pSc, 0x0, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, unable to read from address 0x%04X\n",
           IX1A_SHM_SIZE_ISA - 2);
      ix1aisa_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_LOW)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
           IX1A_SHM_TEST_WORD_LOW, 0x0, u);
      ix1aisa_disable_board (pSc);
      return (EIO);
   }
   iRes = ix1aisa_shm_read_word (pSc, IX1A_SHM_SIZE_ISA - 2, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, unable to read from address 0x%04X\n",
           IX1A_SHM_SIZE_ISA - 2);
      ix1aisa_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_HIGH)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
           IX1A_SHM_TEST_WORD_HIGH, IX1A_SHM_SIZE_ISA - 2, u);
      ix1aisa_disable_board (pSc);
      return (EIO);
   }
   
   DBG (LOG_TRACE, pSc->iUnit, "Board enabled successfully");
   return (0);
} /* ix1aisa_enable_board */





/**
 * Disable an ISA board in general.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 disable.
 *
 * @return Nothing.
 */

void ix1aisa_disable_board
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;
   unsigned           uCmd1;

   /* if the resource structure is not initialised, the board should already
    * or still be disabled, no action needed here
    */
   if (pSc->resInfo.pResIoData == NULL)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Resources not initialised, board is expected to already be disabled");
      return;
   }
   t = pSc->resInfo.ioTagData;
   h = pSc->resInfo.ioHandleData;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start disabling board");
   
   /* read the first status register, ENABLE will be unmasked later */
   uCmd1 = (unsigned) bus_space_read_1 (t, h, IX1A_REG_CMD1);
   
   /* request a test interrupt from the board */
   bus_space_write_1 (t, h, IX1A_REG_CMD2, IX1A_CMD2_INT2PC);
   bus_space_write_1 (t, h, IX1A_REG_CMD2, ~IX1A_CMD2_INT2PC);
   
   /* reset the ENABLE bit, i.e. disable the board */
   bus_space_write_1 (t, h, IX1A_REG_CMD1, uCmd1 & ~IX1A_CMD1_ENABLE);
   
   /* now do the real disabling by writing some magic values to the offset
    * register
    */
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_DISABLE_CMD);
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_DISABLE_CMD);
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_LOW, IX1AISA_DISABLE_CMD);
   bus_space_write_1 (t, h, IX1A_REG_OFFSET_HIGH, IX1AISA_DISABLE_CMD);

   DBG (LOG_DEBUG, pSc->iUnit, "Board disabled");
   
} /* ix1aisa_disable_board */





/**
 * Acknowledge a pending interrupt to a board.
 *
 * This function is called to acknowledge a pending interrupt to the board
 * specified. The acknowledge operation is to toggle the correct bit in the
 * status register 2 shortly from 0 to 1 and back to 0.
 *
 * @param pSc                   I/O: The board to acknowledge the interrupt to.
 *
 * @return Nothing.
 */

void ix1aisa_int_ack
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   unsigned           u;
   
   u = bus_space_read_1 (t, h, IX1A_REG_CMD2);
   u &= IX1A_CMD2_EISA_LEVEL_IRQ;
   u |= IX1A_CMD2_CLEAR_INT2PC;
   bus_space_write_1 (t, h, IX1A_REG_CMD2, u);
   u &= ~IX1A_CMD2_CLEAR_INT2PC;
   bus_space_write_1 (t, h, IX1A_REG_CMD2, u);
   
} /* ix1aisa_int_ack */





/**
 * Set the offset pointer within an ISA board's shared memory.
 *
 * @param pSc                   I/O: The softc structure for the board to set
 *                                 the offset register.
 * @param uAddr                 I: The address offset to set.
 *
 * @retval 0                    The offset register was set successfully.
 * @retval Else                 The offset register could not be set, board
 *                              failure.
 */

int ix1aisa_shm_set_offset
   (Ix1aSc_t *pSc,
    unsigned  uAddr)
{
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid address 0x%04X to set shared memory offset to (must be even)",
           uAddr);
      return (EINVAL);
   }
   
   bus_space_write_2 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData,
                      IX1A_REG_OFFSET_LOW, htole16 ((u_int16_t) (uAddr >> 1)));

   return (0);
} /* ix1aisa_shm_set_offset */





/**
 * Read a byte from a shared memory address of an ISA board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param puValue               O: The byte read from the board as an unsigned
 *                                 integer, only valid if the result is 0.
 *
 * @retval 0                    The value was read successfully and stored into
 *                              *puValue.
 * @retval Else                 An errno value on failure.
 */

int ix1aisa_shm_read_byte
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned *puValue)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   u_int16_t          w;
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now read the value at the current offset */
   w = bus_space_read_2 (t, h, IX1A_REG_DATA_LOW);
   
   /* for even addresses we must return the low byte, else the high byte of the
    * 16bit word read (the board works with little endian byte order)
    */
   if ((uAddr & 0x01) == 0)
   {
      *puValue = (htole16 (w) & 0xFF);
   }
   else
   {
      *puValue = (htole16 (w) >> 8);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read byte 0x%02X from shared memory address 0x%04X",
        *puValue, uAddr);
   return (0);
} /* ix1aisa_shm_read_byte */





/**
 * Read a 16bit word from a shared memory address of an ISA board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param puValue               O: The word read from the board, only valid if
 *                                 the result is 0.
 *
 * @retval 0                    The value was read successfully and stored into
 *                              *puValue.
 * @retval Else                 An errno value on failure.
 */

int ix1aisa_shm_read_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned *puValue)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0 || puValue == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to read a word from",
           uAddr);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now read the value at the current offset */
   *puValue = le16toh (bus_space_read_2 (t, h, IX1A_REG_DATA_LOW));
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read word 0x%04X from shared memory address 0x%04X",
        *puValue, uAddr);
   return (0);
} /* ix1aisa_shm_read_word */





/**
 * Read a block of data from a shared memory address of an ISA board assuming
 * 16bit mode.
 *
 * @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 returing 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 ix1aisa_shm_read_block_16
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    u_int8_t *pbData,
    size_t    nLenData)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   u_int16_t          w;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to read block of length %zu from",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now read the block from the current offset; maybe the last byte is
    * missing if the length is odd
    */
   bus_space_read_multi_2 (t, h, IX1A_REG_DATA_LOW,
                           (u_int16_t *) pbData, nLenData >> 1);
   
   /* If the length is odd, there is still a byte to read. As we are in 16bit
    * mode we must read a word and extract the low byte from it (in little
    * endian byte order).
    */
   if ((nLenData & 0x01) != 0)
   {
      w = le16toh (bus_space_read_2 (t, h, IX1A_REG_DATA_LOW));
      pbData [nLenData - 1] = (w & 0xFF);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read %zu bytes from shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1aisa_shm_read_block_16 */





/**
 * Write a 16bit word to a shared memory address of an ISA board.
 *
 * @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.
 *
 * @retval 0                    The value was successfully written.
 * @retval Else                 An errno value on failure.
 */

int ix1aisa_shm_write_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned  uValue)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to write word 0x%04X to",
           uAddr, uValue);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now write the value to the current offset */
   bus_space_write_2 (t, h, IX1A_REG_DATA_LOW, htole16 ((u_int16_t) uValue));
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote word 0x%04X to shared memory address 0x%04X",
        uValue, uAddr);
   return (0);
} /* ix1aisa_shm_write_word */





/**
 * Write a block of data to a shared memory address of an ISA board assuming
 * 8bit mode.
 *
 * @pre This function may only be called for Basic and Octo boards, Multimodems
 *      do not support 8bit mode.
 *
 * @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 ix1aisa_shm_write_block_8
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW, htole16 ((u_int16_t) uAddr));
   
   /* now write the block to the current offset */
   bus_space_write_multi_1 (t, h, IX1A_REG_DATA_LOW, pbData, nLenData);
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote %zu bytes to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1aisa_shm_write_block_8 */





/**
 * Write a block of data to a shared memory address of an ISA board assuming
 * 16bit mode.
 *
 * @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 ix1aisa_shm_write_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   u_int16_t          w;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to write block of length %zu to",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now write the block to the current offset; maybe the last byte is missing
    * if the length is odd
    */
   bus_space_write_multi_2 (t, h, IX1A_REG_DATA_LOW,
                            (const u_int16_t *) pbData, nLenData >> 1);
   
   /* If the length is odd, there is still a byte to write. As we are in 16bit
    * mode we must construct a word to write it.
    */
   if ((nLenData & 0x01) != 0)
   {
      w = htole16 ((u_int16_t) (pbData [nLenData - 1]));
      bus_space_write_2 (t, h, IX1A_REG_DATA_LOW, w);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote %zu bytes to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1aisa_shm_write_block_16 */





/**
 * Compare a block of data to a shared memory address of an ISA board assuming
 * 16bit mode.
 *
 * @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 ix1aisa_shm_compare_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData,
    int            *pfResult)
{
   bus_space_tag_t     t = pSc->resInfo.ioTagData;
   bus_space_handle_t  h = pSc->resInfo.ioHandleData;
   const u_int16_t    *p = (const u_int16_t *) pbData;
   size_t              nNumWords = nLenData >> 2;
   size_t              nCurrPos;
   u_int16_t           w;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to compare block of length %zu to",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                      htole16 ((u_int16_t) (uAddr >> 1)));
   
   /* now read the necessare words and compare them to the data specified */
   for (nCurrPos = 0; nCurrPos < nNumWords; ++nCurrPos)
   {
      w = le16toh (bus_space_read_2 (t, h, IX1A_REG_DATA_LOW));
      if (w != p [nCurrPos])
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Compare failed at position 0x%04X (read 0x%04X, expected 0x%04X)",
              (unsigned) (nCurrPos * 2),
              (unsigned) w, (unsigned) p [nCurrPos]);
         *pfResult = 0;
         return (0);
      }
   }
   
   /* If the length is odd, there is still a byte to compare. As we are in
    * 16bit mode we must read it as a word.
    */
   if ((nLenData & 0x01) != 0)
   {
      w = le16toh (bus_space_read_2 (t, h, IX1A_REG_DATA_LOW)) & 0xFF;
      if (w != (u_int16_t) pbData [nLenData - 1])
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Compare failed at position 0x%04X (read 0x%04X, expected 0x%04X)",
              (unsigned) (nLenData - 1),
              (unsigned) w, (unsigned) pbData [nLenData - 1]);
         *pfResult = 0;
         return (0);
      }
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Compared %zu bytes to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   *pfResult = 1;
   return (0);
} /* ix1aisa_shm_compare_block_16 */





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





/**
 * Interrupt routine for ISA boards.
 *
 * @param pSc                   I/O: The softc structure for the board raising
 *                                 the interrupt.
 *
 * @return Nothing.
 */

static void ix1aisa_interrupt_handler
   (Ix1aSc_t *pSc)
{
   DBG (LOG_IRQ, pSc->iUnit, "Interrupt handler called");
        
   mtx_lock (&(pSc->mtxAccess));
   if (pSc->fOpInProgress != 0 && ! pSc->fWaitingForRc)
   {
      /* Note: This may happen because for each interrupt we loop to get
       *       several messages if available. But the board raises an interrupt
       *       for every message delivered. So after fetching more than one
       *       message the next interrupt may occur before a result waiting
       *       function call took its result and so this operation is still in
       *       progress, although its return code is already stored to be
       *       fetched.
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Operation still in progress");
   }
   if (pSc->fIntrActive != 0)
   {
      mtx_unlock (&(pSc->mtxAccess));
      DBG (LOG_ERROR, pSc->iUnit, "Nested interrupt handler call");
      return;
   }
   pSc->fIntrActive = 1;
   
   /* check if this board really delivered the interrupt */
   if (! ix1aisa_is_int_pending (pSc))
   {
      pSc->fIntrActive = 0;
      mtx_unlock (&(pSc->mtxAccess));
      DBG (LOG_IRQ, pSc->iUnit, "No interrupt pending");
      return;
   }
   
   /* acknowledge the pending interrupt to the board */
   ix1aisa_int_ack (pSc);
   
   /* if there is someone waiting for the board to deliver an interrupt, tell
    * him it has happened
    */
   if (pSc->fOpInProgress && pSc->fWaitingForRc)
   {
      pSc->uRc = CAPI_OK;
      pSc->fWaitingForRc = 0;
      cv_broadcast (&(pSc->cvNotify));

      DBG (LOG_DEBUG, pSc->iUnit, "Woken up waiting thread");
      
      /* we proceed nevertheless with handling CAPI messages from the board */
   }
   
   /* if the board is in READY state or above, we now must read all available
    * CAPI message out of the shared memory; otherwise we must skip this step,
    * because the board cannot deliver any CAPI messages in other states
    */
   if (pSc->state >= IX1A_STATE_READY)
   {
      bus_space_tag_t    t;
      bus_space_handle_t h;
      u_int16_t          w;
      
      /* update the watchdog word */
      t = pSc->resInfo.ioTagData;
      h = pSc->resInfo.ioHandleData;
      bus_space_write_2 (t, h, IX1A_REG_OFFSET_LOW,
                         htole16
                            ((u_int16_t) (IX1A_SHM_OFS_CAPI_BEAT_BOARD >> 1)));
      w = bus_space_read_2 (t, h, IX1A_REG_DATA_LOW);
      bus_space_write_2 (t, h, IX1A_REG_DATA_LOW, w);
      DBG (LOG_IRQ, pSc->iUnit,
           "Watchdog word updated, value 0x%04X",
           (unsigned) w);

      ix1ashm_read_capi_messages (pSc);
   }

   pSc->fIntrActive = 0;
   mtx_unlock (&(pSc->mtxAccess));
   
   DBG (LOG_IRQ, pSc->iUnit, "Left interrupt handler");
        
} /* ix1aisa_interrupt_handler */





/**
 * Check if a specified board is already initialised and working.
 *
 * This test is based on the auto-increment feature of the shared memory offset
 * register. If one reads a byte through the data register, the offset pointer
 * will increment by one. If this does not happen, the board cannot be expected
 * to be initialised and running.
 *
 * @param pSc                   I/O: The softc structure for the board to check.
 *
 * @retval 0                    The board is not working and must be
 *                              initialised before use.
 * @retval 1                    The board is ready for operation.
 */

static int ix1aisa_check_board_enabled
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagData;
   bus_space_handle_t h = pSc->resInfo.ioHandleData;
   unsigned           uOff1;
   unsigned           uOff2;
   unsigned           uOff3;
   
   /* read the current lower half of the offset pointer */
   uOff1 = bus_space_read_1 (t, h, IX1A_REG_OFFSET_LOW);
   
   /* if the offset is near a wrap-around, just read some bytes to start with
    * a low offset value
    */
   if (uOff1 > 252)
   {
      (void) bus_space_read_1 (t, h, IX1A_REG_DATA_LOW);
      (void) bus_space_read_1 (t, h, IX1A_REG_DATA_LOW);
      (void) bus_space_read_1 (t, h, IX1A_REG_DATA_LOW);
      uOff1 = bus_space_read_1 (t, h, IX1A_REG_OFFSET_LOW);
   }
   
   /* now read a data byte and the resulting offset two times */
   (void) bus_space_read_1 (t, h, IX1A_REG_DATA_LOW);
   uOff2 = bus_space_read_1 (t, h, IX1A_REG_OFFSET_LOW);
   (void) bus_space_read_1 (t, h, IX1A_REG_DATA_LOW);
   uOff3 = bus_space_read_1 (t, h, IX1A_REG_OFFSET_LOW);
   
   /* if all offset values are consequtive, the board is working */
   if (uOff1 + 1 == uOff2 && uOff2 + 1 == uOff3)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Offset values are consequitive (%u, %u, %u), board is operationable",
           uOff1, uOff2, uOff3);
      return (1);
   }
   
   /* if we reach this point, the board does not work */
   DBG (LOG_DEBUG, pSc->iUnit,
        "Offset values are not consequtive (%u, %u, %u), board is not active",
        uOff1, uOff2, uOff3);
   return (0);
} /* ix1aisa_check_board_enabled */





/**
 * Determine the scratch value to assign an i/o address to a board.
 *
 * The scratch value encodes the i/o base address of the board. It is created
 * through a table lookup using the i/o base address to assign and the jumper
 * setting of the board. When sending the scratch value to the board, only the
 * board with the same jumper setting will react on writing to the scratch
 * register and take over the i/o address coded into the scratch value.
 *
 * To not work with unsupported values the i/o base address and the jumper
 * setting is verified. If their values are not supported by the card type or
 * the card type is invalid (i.e. no ISA board), no valid scratch value will be
 * returned.
 *
 * @param pszDriverName         I: The driver name for error output.
 * @param iUnit                 I: The driver unit number for error output.
 * @param cardType              I: The board type for the device.
 * @param iIoBase               I: The i/o port base address, will be coded
 *                                 into the scratch value.
 * @param iJumperSetting        I: The jumper setting of the board.
 *
 * @return The scratch value determined from the i/o base address and the
 *         jumper setting. This value is always positive and less than 0x100,
 *         so it fits into a byte. If the parameters are not valid for the
 *         specific board type are invalid, a negative value is returned.
 */

static int ix1aisa_get_scratch_value
   (const char     *pszDriverName,
    int             iUnit,
    Ix1aCardType_t  cardType,
    int             iIoBase,
    int             iJumperSetting)
{
   int aaiScratchTable [9] [8] =
      {
         {  7,  6,  5,  4,  3,  2,  1,  0 }, /* i/o 0x368, only Multimodem */
         {  6,  5,  4,  7,  2,  1,  0,  3 }, /* EISA, Basic 1.2 */
         { -1, -1, -1, -1, -1, -1, -1, -1 }, /* invalid i/o base address */
         {  0,  3,  2,  1,  4,  7,  6,  5 }, /* i/o 0x380 */
         {  1,  0,  3,  2,  5,  4,  7,  6 }, /* i/o 0x388 */
         {  2,  1,  0,  3,  6,  5,  4,  7 }, /* i/o 0x390 */
         {  3,  2,  1,  0,  7,  6,  5,  4 }, /* i/o 0x398 */
         {  4,  7,  6,  5,  0,  3,  2,  1 }, /* i/o 0x3A0, Basic 1.2 */
         {  5,  4,  7,  6,  1,  0,  3,  2 }  /* i/o 0x3A8, Basic 1.2 */
      };
   int i;

   /* The i/o base address must be a multiple of 8 and lie between 0x368 and
    * 0x3A8
    */
   if (iIoBase < 0x368 || iIoBase > 0x3A8 || (iIoBase & 0x07) != 0)
   {
      printf ("%s%d: Invalid i/o base address 0x%04X\n",
              pszDriverName, iUnit, iIoBase);
      return (-1);
   }
   
   /* Some boards have specific requirements */
   switch (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:
         if (iJumperSetting < 0 || iJumperSetting >= 4)
         {
            printf ("%s%d: Invalid jumper setting %d for Basic board\n",
                    pszDriverName, iUnit, iJumperSetting);
            return (-1);
         }
         if (iIoBase < 0x380)
         {
            printf ("%s%d: I/o base address 0x%03X not supported by Basic board\n",
                    pszDriverName, iUnit, iIoBase);
            return (-1);
         }
         /* Note: A Basic v1.1 cannot use i/o base address 0x3A0 and above. But
          *       this cannot be checked here, because we cannot ask the board
          *       for its revision before it is assigned an i/o address.
          */
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         if (iJumperSetting < 0 || iJumperSetting >= 8)
         {
            printf ("%s%d: Invalid jumper setting %d for Multimodem board\n",
                    pszDriverName, iUnit, iJumperSetting);
            return (-1);
         }
         if (iIoBase > 0x368 && iIoBase < 0x380)
         {
            printf ("%s%d: I/o base address 0x%03X not supported by Multimodem board\n",
                    pszDriverName, iUnit, iIoBase);
            return (-1);
         }
         break;
      
      default:
         printf ("%s%d: Invalid card type \"%s\" (%d) to determine scratch value for\n",
                 pszDriverName, iUnit,
                 ix1amisc_get_card_type_name (cardType), (int) cardType);
         return (-1);
   }
   
   /* read the scratch table value for the i/o base and the jumper setting */
   i = aaiScratchTable [(iIoBase - 0x368) / 8] [iJumperSetting];
   if (i < 0)
   {
      printf ("%s%d: Invalid combination of i/o base address 0x%03X and jumper setting %d\n",
              pszDriverName, iUnit, iIoBase, iJumperSetting);
      return (-1);
   }
   return (i);
} /* ix1aisa_get_scratch_value */





/**
 * Translate an IRQ number into an index value to tell the board.
 *
 * Active IX1 ISA boards can only use a limited number of IRQ numbers. Basic
 * (and Octo) boards can only use 4, Multimodems can use 6 different ones. The
 * IRQ number to use must be translated to an index into the array of useable
 * interrupts for the respective board type.
 *
 * @param pszDriverName         I: The driver name for error output.
 * @param iUnit                 I: The driver unit number for error output.
 * @param cardType              I: The card type, determines the interrupt
 *                                 table to use.
 * @param iIrq                  I: The IRQ number to assign to the board.
 *
 * @return The IRQ index to send to the board. If the IRQ number specified is
 *         not valid for the board, the result is 0xFFFFFFFF, i.e. lies outside
 *         of any valid IRQ index.
 */

static unsigned ix1aisa_get_irq_index
   (const char     *pszDriverName,
    int             iUnit,
    Ix1aCardType_t  cardType,
    int             iIrq)
{
   unsigned uBasicIrqTable [] =
      {
         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF,
         0xFF, 0x00, 0x02, 0x03, 0xFF, 0xFF, 0xFF, 0xFF
      };
   unsigned uMModIrqTable [] =
      {
         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x02,
         0xFF, 0x03, 0x04, 0x05, 0x06, 0xFF, 0xFF, 0x07
      };
   unsigned uResult;

   /* check if the IRQ value is a valid value in general between 0 and 15 */
   if (iIrq < 0 || iIrq > 15)
   {
      printf ("%s%d: Invalid irq number %d for ISA board\n",
              pszDriverName, iUnit, iIrq);
      return (0xFFFFFFFF);
   }
   
   /* now perform a table lookup for the IRQ number according to the board type
    */
   switch (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:
         uResult = uBasicIrqTable [iIrq];
         if (uResult >= 0x04)
         {
            printf ("%s%d: Unsupported irq number %d for Basic board\n",
                    pszDriverName, iUnit, iIrq);
            return (0xFFFFFFFF);
         }
         break;
      
      case IX1A_CARD_TYPE_MMOD_ISA:
         uResult = uMModIrqTable [iIrq];
         if (uResult >= 0x08)
         {
            printf ("%s%d: Unsupported irq number %d for Multimodem board\n",
                    pszDriverName, iUnit, iIrq);
            return (0xFFFFFFFF);
         }
         break;
      
      default:
         printf ("%s%d: Invalid card type \"%s\" (%d) to determine irq index for\n",
                 pszDriverName, iUnit,
                 ix1amisc_get_card_type_name (cardType), (int) cardType);
         return (0xFFFFFFFF);
   }
   
   return (uResult);
} /* ix1aisa_get_irq_index */





/**
 * Check if an interrupt is pending for a board.
 *
 * This function is called to check if the board specified has raised an
 * interrupt and is waiting for an acknowledge.
 *
 * All revision 1 boards are missing a flag for this information in their
 * status registers, so for these boards we always must assume a pending
 * interrupt.
 *
 * Boards of revision 2 and above have an INT-PENDING bit in their status
 * register. So for these boards the status bit is checked and the result is
 * delivered as the function result.
 *
 * @param pSc                   I/O: The board to check for a pending
 *                                 interrupt.
 *
 * @retval 0                    No interrupt is pending from the board.
 * @retval 1                    The board raised an interrupt and is waiting
 *                              for an acknowledge.
 */

static int ix1aisa_is_int_pending
   (Ix1aSc_t *pSc)
{
   unsigned u;
   
   /* all boards of revision 1 or below are missing an INT-PENDIG status bit */
   if (pSc->iBoardRevision < 2)
   {
      return (1);
   }
   
   /* check status register 2 for the INT-PENDING bit */
   u = bus_space_read_1 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData,
                         IX1A_REG_CMD2);
   return ((u & IX1A_CMD2_INT2PC) != 0);
} /* ix1aisa_is_int_pending */
