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

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

/* System includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <capi20.h>

/* Import includes */

#define __DAIC_DISPATCH__

/* Local includes */
#include <c4b/daic/daic_var.h>
#include <c4b/daic/daic_misc.h>
#include <c4b/daic/daic_globalid.h>
#include <c4b/daic/daic_plci.h>
#include <c4b/daic/daic_ncci.h>
#include <c4b/daic/daic_dispatch.h>





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





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





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





/**
 * Determine if a CAPI message is addressed to an NCCI state machine.
 *
 * NCCI messages are all B3-messages but Connect-B3-Request. The later must be
 * dispatched to the PLCI layer for the assignment of a new network id
 * (allthough after the assignment message execution continues in the NCCI
 * layer).
 *
 * Facility messages are handled either by an NCCI or a PLCI layer according to
 * the facility selector. But for now no facility messages are supported by this
 * driver.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to an NCCI.
 * @retval 1                    The message must be handled by an NCCI layer.
 */
static int daicdisp_is_ncci_message
   (const CAPIMsg_t *pCapiMsg);

/**
 * Determine if a message is addressed to a PLCI state machine.
 *
 * PLCI messages are all messages that are no B3-messages and the
 * Connect-B3-Request, but not the Connect-Request, the Listen-Request or the
 * Info-Request with a CID addressing only the controller itself. The
 * Connect-B3-Request must be dispatched to the PLCI layer for the assignment of
 * a new network id (although after the assignment message execution continues
 * in the NCCI layer). A Connect-Request must be handled by the global id state
 * machine, because signaling ids are only allocated in this context. PLCI
 * messages also include the Select-B-Protocol-Request.
 *
 * Facility messages are handled either by an NCCI or a PLCI layer according to
 * the facility selector. But for now no facility messages are supported by this
 * driver.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to an PLCI.
 * @retval 1                    The message must be handled by an PLCI layer.
 */
static int daicdisp_is_plci_message
   (const CAPIMsg_t *pCapiMsg);

/**
 * Determine if a CAPI message is addressed to the global id state machine.
 *
 * Only Listen-Requests and Info-messages with a CID consisting only of the
 * controller number are addressed to the global id state machine.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to the global id
 *                              state machine.
 * @retval 1                    The message must be handled by the global id
 *                              state machine.
 */
static int daicdisp_is_gid_message
   (const CAPIMsg_t *pCapiMsg);

/**
 * Translate a channel number into an NCCI value for a id translation entry.
 *
 * This function tries to find the specified channel number in the NCCI info
 * array of the translation entry passed as an argument. The first position to
 * look for is the channel number modulo the NCCI info array size. The result is
 * the NCCI value for the channel number if it is found.
 *
 * If the channel number is not found in the array, the result is 0. As an NCCI
 * cannot take this value, this is a unique value for the result "channel not
 * found".
 *
 * @param pEntry                I: The controller id translation entry to
 *                                 search in.
 * @param uChId                 I: The channel number to look for.
 *
 * @retval 0                    The channel number is currently not known for
 *                              the network id specified through the translation
 *                              table entry.
 * @retval Else                 The NCCI value associated with the specified
 *                              channel number.
 */
static unsigned daicdisp_get_ncci_from_channel
   (DaicIdTransTableEntry_t *pEntry,
    unsigned                 uChId);

/**
 * Enter the qualification of a controller id into the translation table.
 *
 * Besides entering the parameters iQualify and uPlci at the table position
 * indexed by uId, the array aNcciInfo is completely cleared. For a newly
 * assigned id this results in a clean startup association array for channel ids
 * and NCCIs and for removed ids this performs a normal cleanup.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bId                   I: The controller id for that the qualification
 *                                 is to be entered.
 * @param iQualify              I: The qualification to enter.
 * @param uPlci                 I: The PLCI value to enter.
 *
 * @return Nothing.
 */
static void daicdisp_set_id_trans_table_entry
   (DaicPortData_t *pPortData,
    u_int8_t        bId,
    int             iQualify,
    unsigned        uPlci);

/**
 * Allocate a new NCCI and enter the association with network id and channel
 * into the id translation table.
 *
 * This function first allocates a new NCCI. Then the association of this NCCI
 * and its network id and channel id are entered into the NCCI info array of
 * the translation table entry specified as a parameter. If the channel id does
 * already exist in this array, the array entry is re-used for the new NCCI (of
 * course with an error message, because this must not happen).
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param pEntry                I/O: The controller id translation table entry,
 *                                 the new assignment shall be reflected in.
 * @param uChId                 I: The newly assigned channel id for the network
 *                                 id and NCCI.
 *
 * @retval 0                    All NCCI info array entries in use, no more NCCI
 *                              allowed for the network id or the associated
 *                              PLCI.
 * @retval Else                 The NCCI value for the newly allocated NCCI.
 */
static unsigned daicdisp_alloc_new_ncci_for_channel
   (DaicPortData_t          *pPortData,
    DaicIdTransTableEntry_t *pEntry,
    unsigned                 uChId);





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





/**
 * Handle a return code from a controller.
 *
 * @param pPortData             I/O: The address of the port data for this
 *                                 operation.
 * @param pRcData               I: The return code data to evaluate.
 *
 * @return Nothing.
 */

void daicdisp_handle_rc
   (DaicPortData_t     *pPortData,
    const DaicRcData_t *pRcData)
{
   DaicIdTransTableEntry_t *pEntry;
   
   /* check for an assign request result */
   if (pRcData->bRc >= DAIC_RC_ASSIGN_MASK &&
       pRcData->bRc <= DAIC_RC_ASSIGN_OK)
   {
      /* enter the newly assigned controller id into the id translation table,
       * if the request was successful
       */
      if (pRcData->bRc == DAIC_RC_ASSIGN_OK)
      {
         daicdisp_set_id_trans_table_entry
            (pPortData,
             pRcData->bRcId,
             (int) (pRcData->bAssignReqQualification),
             (unsigned) (pRcData->wAssignReqPlci));
      }

      /* Assign-Request return codes for the global id state machine are of
       * course dispatched to it
       */
      if (pRcData->bAssignReqQualification == DAIC_ID_QUALIFY_GID)
      {
         daicgid_handle_assign_rc (pPortData, pRcData);
      }
      /* all other Assign-Request originate from PLCI state machines, so
       * dispatch this return code to the PLCI specified in the return code data
       */
      else if (pRcData->bAssignReqQualification == DAIC_ID_QUALIFY_PLCI)
      {
         daicplci_handle_sig_assign_rc
            (pPortData, (unsigned) (pRcData->wAssignReqPlci), pRcData);
      }
      else if (pRcData->bAssignReqQualification == DAIC_ID_QUALIFY_NCCI)
      {
         daicplci_handle_net_assign_rc
            (pPortData, (unsigned) (pRcData->wAssignReqPlci), pRcData);
      }
      /* this should never happen */
      else
      {
         DBG (LOG_ERROR, pPortData->pSc->iUnit,
              "Port %u: Got assign return code 0x%02X with unknown qualification value %u, id %u, channel %u, PLCI %u",
              pPortData->uPortIdx,
              (unsigned) (pRcData->bRc),
              (unsigned) (pRcData->bAssignReqQualification),
              (unsigned) (pRcData->bRcId),
              (unsigned) (pRcData->bRcCh),
              (unsigned) (pRcData->wAssignReqPlci));
      }
      return;
   }
   
   /* translate id and channel into a state machine specification with respect
    * to the qualification of the id
    */
   pEntry = &(pPortData->aIdTransTable [pRcData->bRcId]);
   if (pEntry->iQualify == DAIC_ID_QUALIFY_GID)
   {
      /* global id state machine for D-channel access */
      daicgid_handle_rc (pPortData, pRcData);
   }
   else if (pEntry->iQualify == DAIC_ID_QUALIFY_PLCI ||
            (pEntry->iQualify == DAIC_ID_QUALIFY_NCCI &&
             pRcData->bRcCh == 0))
   {
      /* a qualification of PLCI or NCCI with a channel value of 0 means the
       * request came from a PLCI state machine, for a network id this is
       * normally a return code for a Remove-Request
       */
      daicplci_handle_rc (pPortData, pEntry->uPlci, pRcData);
   }
   else if (pEntry->iQualify == DAIC_ID_QUALIFY_NCCI)
   {
      unsigned uNcci;
      
      /* so this is a return code associated with a network id and a channel
       * number not equal to 0 --> determine the NCCI value through the channel
       * number
       */
      uNcci = daicdisp_get_ncci_from_channel
                 (pEntry, (unsigned) (pRcData->bRcCh));
      /* Note: A result value of 0 means that the channel is currently not
       *       known. So this must be a return code for a NL_CONNECT request
       *       (the only request besides a Remove-Request, for which the channel
       *       number is only assigned through its return code). Such requests
       *       are executed by NCCI 0 (resulting from a CAPI
       *       Connect-B3-Request).
       */
      daicncci_handle_rc (pPortData, uNcci, pRcData);
   }
   else
   {
      /* another qualification means this id is not assigned --> error */
      DBG (LOG_ERROR, pPortData->pSc->iUnit,
           "Port %u: Got return code 0x%02X with unassigned id %u, channel %u",
           pPortData->uPortIdx, (unsigned) (pRcData->bRc),
           (unsigned) (pRcData->bRcId), (unsigned) (pRcData->bRcCh));
   }
   
   return;
} /* daicdisp_handle_rc */





/**
 * Handle an indication from a controller.
 *
 * @param pPortData             I/O: The address of the port data for this
 *                                 operation.
 * @param pmbIndData            I: The indication data to evaluate. The
 *                                 ownership of the mbuf changes to the called
 *                                 function.
 *
 * @return Nothing.
 */

void daicdisp_handle_ind
   (DaicPortData_t *pPortData,
    struct mbuf    *pmbIndData)
{
   DaicIndData_t           *pIndData = mtod (pmbIndData, DaicIndData_t *);
   DaicIdTransTableEntry_t *pEntry;
   
   /* translate id and channel into a state machine specification with respect
    * to the qualification of the id
    */
   pEntry = &(pPortData->aIdTransTable [pIndData->bIndId]);
   if (pEntry->iQualify == DAIC_ID_QUALIFY_GID)
   {
      /* global id state machine for D-channel access */
      daicgid_handle_ind (pPortData, pIndData);
      kcapi_free_mbuf (pmbIndData);
   }
   else if (pEntry->iQualify == DAIC_ID_QUALIFY_PLCI)
   {
      /* a qualification of PLCI means the request came from a PLCI state
       * machine
       */
      daicplci_enqueue_ind (pPortData, pEntry->uPlci, pmbIndData);
   }
   else if (pEntry->iQualify == DAIC_ID_QUALIFY_NCCI)
   {
      unsigned uNcci;
      
      /* So this is an indication associated with a network id; the channel
       * number cannot be 0, there is no such indication defined
       */

      /* if this is a NL_CONNECT indication, the channel number signaled should
       * currently be unknown --> add it to the NCCI info array entry for the
       * associated network id
       */
      if (pIndData->bInd == DAIC_NL_CONNECT)
      {
         uNcci = daicdisp_alloc_new_ncci_for_channel
                    (pPortData, pEntry, pIndData->bIndCh);
         /* Note: If the result is 0, there is no free NCCI entry for this
          *       network id, i.e. the number of NCCIs per PLCI is exhausted. We
          *       simply forward this indication to NCCI 0 that will reject the
          *       new network connection.
          */
      }
      /* otherwise the channel must be known and assigned to an NCCI */
      else
      {
         uNcci = daicdisp_get_ncci_from_channel
                    (pEntry, (unsigned) (pIndData->bIndCh));
      }
      
      /* now finally forward the indication to the associated NCCI state machine
       */
      daicncci_enqueue_ind (pPortData, uNcci, pmbIndData);
   }
   else
   {
      /* the controller id qualification is unknown, i.e. the id is not assigned
       * --> error
       */
      DBG (LOG_ERROR, pPortData->pSc->iUnit,
           "Port %u: Got indication 0x%02X with unassigned id %u, channel %u",
           pPortData->uPortIdx, (unsigned) (pIndData->bInd),
           (unsigned) (pIndData->bIndId), (unsigned) (pIndData->bIndCh));
      kcapi_free_mbuf (pmbIndData);
   }
   
} /* daicdisp_handle_ind */





/**
 * Reset all state machines to Idle state.
 *
 * @param pPortData             I/O: The port data for this operation.
 *
 * @return Nothing.
 */

void daicdisp_reset_all
   (DaicPortData_t *pPortData)
{
   daicncci_reset_all (pPortData);
   daicplci_reset_all (pPortData);
   daicgid_reset (pPortData);
   bzero (pPortData->aIdTransTable, sizeof (pPortData->aIdTransTable));
   
} /* daicdisp_reset_all */





/**
 * Start operation of all state machines of a controller.
 *
 * The only thing to do is to start the allocation of a global id for D-channel
 * access to be informed about incoming calls.
 *
 * @param pPortData             I/O: The port data for this operation.
 *
 * @return CAPI result value, CAPI_OK on success.
 */

unsigned daicdisp_start_all
   (DaicPortData_t *pPortData)
{
   return (daicgid_start (pPortData));
} /* daicdisp_start_all */





/**
 * Clear any pending calls for an application during kcapi_release().
 *
 * If there is still some connection running for the application id specified,
 * the application id in the corresponding state machines must be cleared. This
 * is necessary, because after the release operation the CAPI manager must not
 * receive any messages for this application any more. Without a valid
 * application id the state machines will not send out any CAPI message in the
 * application direction, but abort any connection.
 *
 * This should never happen, because the CAPI manager will perform a
 * Disconnect-Request before releasing the application. But maybe the PLCI state
 * machine only waits for the Disconnect-Response that will never arrive after a
 * release. In this case the state machine will remove any resources for this
 * call and return all allocated ids (signalling and network) to the board
 * firmware.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The port for the desired operation.
 * @param uApplID               I: The application id to be released.
 * @param iApplIdx              I: The index into the application data array for
 *                                 the application id.
 *
 * @return Nothing.
 */

void daicdisp_release_appl
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uApplID,
    int             iApplIdx)
{
   /* tell the NCCI state machines that the application id specified is not
    * valid any more
    */
   daicncci_release_appl (pPortData, uApplID);
   
   /* let the PLCI state machines clear any pending connection for this
    * application
    */
   daicplci_release_appl (pPortData, uApplID, iApplIdx);
   
   /* nothing more to do here */
   
   /* suppress warning */
   (void) pSc;
   
} /* daicdisp_release_appl */





/**
 * Handle a CAPI message for a controller.
 *
 * The mbuf for the message (and a possibly attached data block) is dispatched
 * to the PLCI or NCCI instance or to the global id handler, according to the
 * message content. If the message can be dispatched, the result is CAPI_OK and
 * the ownership of the message (and its data block) changes to the respective
 * handler. If the message cannot be enqueued or directly handled, the result is
 * some other value than CAPI_OK and the caller (in effect the CAPI manager) is
 * still responsible of releasing the mbuf if necessary.
 *
 * @param pSc                   I/O: The device softc structure.
 * @param pPortData             I/O: The port for the desired operation.
 * @param uApplID               I: The application id the CAPI message is from.
 * @param pmbMsg                I: The CAPI message to send. A Data-B3-Request
 *                                 contains the address of another mbuf in its
 *                                 pmbMsg->m_next member.
 *
 * @retval CAPI_OK              The CAPI message could be evaluated or enqueued
 *                              successfully. The mbuf(s) will be released by
 *                              the respective message handler.
 * @retval Else                 CAPI result value to indicate error in message
 *                              handling. The ownership of the message mbuf(s)
 *                              remains at the caller.
 */

unsigned daicdisp_put_message
   (DaicSc_t       *pSc,
    DaicPortData_t *pPortData,
    unsigned        uApplID,
    struct mbuf    *pmbMsg)
{
   CAPIMsg_t *pCapiMsg;
   unsigned   uCmd;
   u_int32_t  dwCid;
   unsigned   uRes;
   
   /* Manufacturer-Messages are (currently) not supported by this driver */
   pCapiMsg = mtod (pmbMsg, CAPIMsg_t *);
   uCmd = CAPI_GET_CMD (pCapiMsg);
   if ((uCmd & CAPI_CMDMASK_COMMAND) == C_MANUFACTURER)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: Got unsupported manufacturer CAPI message 0x%04X",
           pPortData->uPortIdx, uCmd);
      return (CME_ILLEGAL_COMMAND);
   }
   
   /* forward the CAPI message to the addressed state machine / layer */
   dwCid = CAPI_GET_CID (pCapiMsg);
   if (daicdisp_is_ncci_message (pCapiMsg))
   {
      uRes = daicncci_enqueue_capi_msg
                (pPortData, CAPI_GET_NCCI_FROM_CID (dwCid), uApplID, pmbMsg);
   }
   else if (daicdisp_is_plci_message (pCapiMsg))
   {
      /* if the message is a Connect-Request, we must allocate a new PLCI and
       * forward the message to it
       */
      if (uCmd == CAPI_REQUEST (C_CONNECT))
      {
         DaicPlciData_t *pNewPlciData;
         
         pNewPlciData = daicplci_alloc_new_plci (pPortData, 0);
         if (pNewPlciData == NULL)
         {
            DBG (LOG_INFO, pSc->iUnit,
                 "Port %u: Out of PLCIs for CAPI Connect-Request (application id %u)",
                 pPortData->uPortIdx, uApplID);
            return (CCE_NO_PLCI_AVAILABLE);
         }
         uRes = daicplci_enqueue_capi_msg
                   (pPortData, pNewPlciData->uPlci, uApplID, pmbMsg);
      }
      /* all other messages must have a valid PLCI in the CID field, so forward
       * them to the corresponding state machine (the check for a valid PLCI is
       * done in the PLCI layer)
       */
      else
      {
         uRes = daicplci_enqueue_capi_msg
                   (pPortData, CAPI_GET_PLCI_FROM_CID (dwCid), uApplID, pmbMsg);
      }
   }
   else if (daicdisp_is_gid_message (pCapiMsg))
   {
      uRes = daicgid_handle_capi_msg (pPortData, uApplID, pmbMsg);
   }
   else
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Port %u: ERROR: Unable to dispatch CAPI message 0x%04X with CID 0x%08X",
           pPortData->uPortIdx, uCmd, (unsigned) dwCid);
      uRes = CCE_MSG_NOT_ALLOWED_YET;
   }
   
   return (uRes);
} /* daicdisp_put_message */





/**
 * Handle an Indicate-Indication from the global id state machine.
 *
 * This function is called by the global id state machine, when an incoming call
 * is signaled. First a new PLCI must be allocated and the reported signaling id
 * associated with it. The Indicate-Indication is then forwarded to this PLCI.
 * The later is not done by the general indication handling function, but with a
 * special one. So it is possible to direct rejection of the call back to the
 * global id state machine if no application is registered or no registeration
 * data entry matches the call.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bSigId                I: The signaling id of the global id state
 *                                 machine that received the new call.
 * @param pIndData              I: The data for the Indicate-Indication.
 *
 * @retval CAPI_OK              The new call is successfully transfered to the
 *                              PLCI state machine, the global id state machine
 *                              must allocate a new one.
 * @retval Else                 The new call cannot be handled, the global id
 *                              state machine must reject it itself.
 */

unsigned daicdisp_handle_indicate_ind
   (DaicPortData_t      *pPortData,
    u_int8_t             bSigId,
    const DaicIndData_t *pIndData)
{
   DaicPlciData_t *pPlciData;
   unsigned        uRes;
   
   /* allocate a new PLCI */
   DBG (LOG_DEBUG, pPortData->iUnit,
        "Port %u: Got new incoming call for global id 0x%02X, allocate new PLCI",
        pPortData->uPortIdx, (unsigned) bSigId);
   pPlciData = daicplci_alloc_new_plci (pPortData, (unsigned) bSigId);
   if (pPlciData == NULL)
   {
      /* error message already out */
      return (CCE_NO_PLCI_AVAILABLE);
   }
   
   /* send the message to the newly allocated PLCI */
   uRes = daicplci_handle_indicate_ind (pPortData, pPlciData, pIndData);
   if (uRes != CAPI_OK)
   {
      /* error message already out */
      return (uRes);
   }

   /* associate the signaling id with the newly allocated PLCI */
   daicdisp_set_id_trans_table_entry
      (pPortData, bSigId, DAIC_ID_QUALIFY_PLCI, pPlciData->uPlci);

   return (CAPI_OK);
} /* daicdisp_handle_indicate_ind */





/**
 * Enter the qualification of a network id into the translation table.
 *
 * First the qualification of the id is checked if it is really a network id.
 * If true the NCCI data array is searched to find a free entry for the
 * specified channel id / NCCI association. If the channel id is found to
 * already exist in the array, its entry is simply overwritten (of course with
 * an error message).
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param uNetId                I: The network id for that the association is to
 *                                 be added.
 * @param uChId                 I: The channel id to enter.
 * @param uNcci                 I: The NCCI value to enter.
 *
 * @retval 0                    The association was successfully entered into
 *                              the table.
 * @retval -1                   The id is not a network id or the array of
 *                              channel id / NCCI associations is full.
 */

int daicdisp_idtrans_enter_channel_and_ncci
   (DaicPortData_t *pPortData,
    unsigned        uNetId,
    unsigned        uChId,
    unsigned        uNcci)
{
   DaicIdTransTableEntry_t *pEntry;
   size_t                   n;
   int                      i;
   
   /* find the array position to enter the new channel id / NCCI association */
   pEntry = &(pPortData->aIdTransTable [uNetId]);
   for (n = ARRAY_COUNT (pEntry->aNcciInfo),
           i = (int) uChId % (int) ARRAY_COUNT (pEntry->aNcciInfo);
        n > 0;
        --n, i = (i + 1) & (int) ARRAY_COUNT (pEntry->aNcciInfo))
   {
      if (pEntry->aNcciInfo [i].uChId == 0)
      {
         break;
      }
      if (pEntry->aNcciInfo [i].uChId == uChId)
      {
         DBG (LOG_ERROR, pPortData->iUnit,
              "Port %u: Channel id %u for network id %u still registered for NCCI %u, overwrite with new mapping",
              pPortData->uPortIdx, uChId, uNetId,
              pEntry->aNcciInfo [i].uNcci);
         break;
      }
   }
   if (n == 0)
   {
      DBG (LOG_ERROR, pPortData->iUnit,
           "Port %u: Network id %u: Unable to find free entry for NCCI / channel id mapping entry (%u/%u)",
           pPortData->uPortIdx, uNetId, uNcci, uChId);
      return (-1);
   }
   /* i now addresses the array index to enter the association of the channel id
    * and the NCCI with the network id, pEntry points to it
    */
   
   /* enter the channel id / NCCI association at the determined position */
   pEntry->aNcciInfo [i].uChId = uChId;
   pEntry->aNcciInfo [i].uNcci = uNcci;
   
   DBG (LOG_TRACE, pPortData->iUnit,
        "Port %u: Network id %u: Associated NCCI %u with channel id %u",
        pPortData->uPortIdx, uNetId, uNcci, uChId);

   return (0);
} /* daicdisp_idtrans_enter_channel_and_ncci */





/**
 * Handle the removal of a controller id (network or signaling).
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bId                   I: The controller id to mark as removed.
 *
 * @return Nothing.
 */

void daicdisp_id_removed
   (DaicPortData_t *pPortData,
    u_int8_t        bId)
{
   DaicIdTransTableEntry_t *pEntry;

   /* address the entry to the id in the id translation table */
   pEntry = &(pPortData->aIdTransTable [bId]);
   
   /* now mark the entry as free by resetting everything to zero */
   bzero (pEntry, sizeof (*pEntry));
   pEntry->iQualify = DAIC_ID_QUALIFY_UNUSED;
   
   DBG (LOG_DEBUG, pPortData->iUnit,
        "Port %u: Id %u: Translation table entry marked as unused",
        pPortData->uPortIdx, (unsigned) bId);

} /* daicdisp_id_removed */





/**
 * Register a received Hangup-Indication for a signaling id.
 *
 * A Hangup-Indication is normally entered into the message queue for a PLCI.
 * There may be some other messages before it is handled. But we must not send
 * any message but a Remove-Request for the now hung up signaling id to the
 * port. So this fact is registered through a flag for the PLCI. The hardware
 * layer will simply ignore any request other than a Remove-Request for this
 * signaling id.
 *
 * Here we first check if the id specified is really a signaling id assigned to
 * a PLCI. If so, the flag for a received Hangup-Indication is set. Otherwise
 * nothing is done.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bSigId                I: The signaling for which the Hangup-Indication
 *                                 is received.
 *
 * @return Nothing.
 */

void daicdisp_register_hangup_for_sig_id
   (DaicPortData_t *pPortData,
    u_int8_t        bSigId)
{
   DaicIdTransTableEntry_t *pEntry;
   DaicPlciData_t          *pPlciData;

   /* address the entry to the id in the id translation table */
   pEntry = &(pPortData->aIdTransTable [bSigId]);
   
   /* check if the id is really a signaling id associated with a PLCI */
   if (pEntry->iQualify != DAIC_ID_QUALIFY_PLCI)
   {
      return;
   }
   
   /* address the related PLCI data structure */
   pPlciData = &(pPortData->aPlciData [pEntry->uPlci]);
   
   /* now mark the PLCI as hung up */
   pPlciData->ulFlags |= DAIC_PLCI_FLAG_GOT_HANGUP;
   
   DBG (LOG_DEBUG, pPortData->iUnit,
        "Port %u: PLCI %u: State %u: Signaling id 0x%02X marked as hung up",
        pPortData->uPortIdx, pPlciData->uPlci, (int) (pPlciData->state),
        (unsigned) bSigId);

} /* daicdisp_register_hangup_for_sig_id */





/**
 * Check if a signaling id already received a Hangup-Indication.
 *
 * If the id specified is really a signalind id assigned to a PLCI, this
 * function returns, if the flag for a Hangup-Indication is set for the related
 * PLCI. See also daicdisp_register_hangup_for_sig_id().
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bSigId                I: The signaling that is to be checked for an
 *                                 already received Hangup-Indication.
 *
 * @retval 1                    A Hangup-Indication was already received for
 *                              this signaling id.
 * @retval 0                    The id specified is either not a signaling id
 *                              associated with a PLCI or a Hangup-Indication
 *                              was not received, yet.
 */

int daicdisp_got_hangup_for_sig_id
   (DaicPortData_t *pPortData,
    u_int8_t        bSigId)
{
   DaicIdTransTableEntry_t *pEntry;
   DaicPlciData_t          *pPlciData;

   /* address the entry to the id in the id translation table */
   pEntry = &(pPortData->aIdTransTable [bSigId]);
   
   /* check if the id is really a signaling id associated with a PLCI */
   if (pEntry->iQualify != DAIC_ID_QUALIFY_PLCI)
   {
      return (0);
   }
   
   /* address the related PLCI data structure */
   pPlciData = &(pPortData->aPlciData [pEntry->uPlci]);
   
   /* now check the flag for a Hangup-Indication for the PLCI */
   return ((pPlciData->ulFlags & DAIC_PLCI_FLAG_GOT_HANGUP) != 0);
} /* daicdisp_got_hangup_for_sig_id */





/**
 * Register a received Disconnect-Indication for a network channel.
 *
 * A Disconnect-Indication is normally entered into the message queue for the
 * NCCI. There may be some other messages before it is handled. But we must not
 * send any more message to this channel, because it does not exist any more.
 * So the received Disconnect-Indication is registered through a flag for the
 * NCCI. The hardware layer will simply ignore any request for this channel id.
 *
 * Here we first check if the id specified is really a network id and the
 * channel id is really registered for an NCCI. If so, the flag for a received
 * Disconnect-Indication is set. Otherwise nothing is done.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bNetId                I: The network id for the channel id that
 *                                 received the Disconnect-Indication.
 * @param bNetCh                I: The channel id belonging to bNetId that
 *                                 received the Disconnect-Indication.
 *
 * @return Nothing.
 */

void daicdisp_register_disconnect_for_net_channel
   (DaicPortData_t *pPortData,
    u_int8_t        bNetId,
    u_int8_t        bNetCh)
{
   DaicIdTransTableEntry_t *pEntry;
   DaicNcciData_t          *pNcciData;
   size_t                   n;
   int                      i;

   /* address the entry to the id in the id translation table */
   pEntry = &(pPortData->aIdTransTable [bNetId]);
   
   /* check if the id is really a network id */
   if (pEntry->iQualify != DAIC_ID_QUALIFY_NCCI)
   {
      return;
   }
   
   /* find the network channel in the NCCI array of the id entry */
   for (n = ARRAY_COUNT (pEntry->aNcciInfo),
           i = (int) (unsigned) bNetCh % (int) ARRAY_COUNT (pEntry->aNcciInfo);
        n > 0;
        --n, i = (i + 1) & (int) ARRAY_COUNT (pEntry->aNcciInfo))
   {
      if (pEntry->aNcciInfo [i].uChId == 0)
      {
         continue;
      }
      if (pEntry->aNcciInfo [i].uChId == (unsigned) bNetCh)
      {
         break;
      }
   }
   if (n == 0)
   {
      /* network channel is currently not known */
      return;
   }

   /* now mark the NCCI as hung up */
   pNcciData = &(pPortData->aNcciData [pEntry->aNcciInfo [i].uNcci]);
   pNcciData->ulFlags |= DAIC_NCCI_FLAG_GOT_DISCONNECT;
   
   DBG (LOG_DEBUG, pPortData->iUnit,
        "Port %u: NCCI %u: State %u: Network id 0x%02X, channel id 0x%02X marked as disconnected",
        pPortData->uPortIdx, pNcciData->uNcci, (int) (pNcciData->state),
        (unsigned) bNetId, (unsigned) (bNetCh));

} /* daicdisp_register_disconnect_for_net_channel */





/**
 * Check if a network channel already received a Disconnect-Indication.
 *
 * If the id specified is really a network id and the channel id is associated
 * with an NCCI, this function returns, if the flag for a Disconnect-Indication
 * is set for the related NCCI. See also
 * daicdisp_register_disconnect_for_net_channel().
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param bNetId                I: The network id for the context to theck the
 *                                 channel id for.
 * @param bNetCh                I: The channel id for which to check for a
 *                                 received Disconnect-Indication.
 *
 * @retval 1                    The network channel specified already received
 *                              a Disconnect-Indication.
 * @retval 0                    The network channel specified either is not
 *                              associated with an NCCI or it did not receive a
 *                              Disconnect-Indication, yet.
 */

int daicdisp_got_disconnect_for_net_channel
   (DaicPortData_t *pPortData,
    u_int8_t        bNetId,
    u_int8_t        bNetCh)
{
   DaicIdTransTableEntry_t *pEntry;
   DaicNcciData_t          *pNcciData;
   size_t                   n;
   int                      i;

   /* address the entry to the id in the id translation table */
   pEntry = &(pPortData->aIdTransTable [bNetId]);
   
   /* check if the id is really a network id */
   if (pEntry->iQualify != DAIC_ID_QUALIFY_NCCI)
   {
      return (0);
   }
   
   /* find the network channel in the NCCI array of the id entry */
   for (n = ARRAY_COUNT (pEntry->aNcciInfo),
           i = (int) (unsigned) bNetCh % (int) ARRAY_COUNT (pEntry->aNcciInfo);
        n > 0;
        --n, i = (i + 1) & (int) ARRAY_COUNT (pEntry->aNcciInfo))
   {
      if (pEntry->aNcciInfo [i].uChId == 0)
      {
         continue;
      }
      if (pEntry->aNcciInfo [i].uChId == (unsigned) bNetCh)
      {
         break;
      }
   }
   if (n == 0)
   {
      /* network channel is currently not known --> not disconnected */
      return (0);
   }

   /* now check the flag for a received Disconnect-Indication for the NCCI */
   pNcciData = &(pPortData->aNcciData [pEntry->aNcciInfo [i].uNcci]);
   return ((pNcciData->ulFlags & DAIC_NCCI_FLAG_GOT_DISCONNECT) != 0);
} /* daicdisp_got_disconnect_for_net_channel */





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





/**
 * Determine if a message is addressed to an NCCI state machine.
 *
 * NCCI messages are all B3-messages. But a Connect-B3-Request must be
 * dispatched to the NCCI state machine with id 0 for the assignment of a new
 * network id.
 *
 * Facility messages are handled either by an NCCI or a PLCI layer according to
 * the facility selector. But for now no facility messages are supported by this
 * driver.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to an NCCI.
 * @retval 1                    The message must be handled by an NCCI layer.
 */

int daicdisp_is_ncci_message
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned uBaseCmd;
   
   uBaseCmd = (CAPI_GET_CMD (pCapiMsg) & CAPI_CMDMASK_COMMAND);
   if (uBaseCmd < 0x80 || uBaseCmd == C_FACILITY)
   {
      return (0);
   }
   if (uBaseCmd > 0x8F)
   {
      return (0);
   }
   return (1);
} /* daicdisp_is_ncci_message */





/**
 * Determine if a message is addressed to a PLCI state machine.
 *
 * PLCI messages are all messages that are no B3-messages. The Listen-Request
 * and an Info-Request with a CID addressing only the controller itself are not
 * PLCI messages. A Connect-Request must be handled by the PLCI state machine
 * with id 0, that is only used to obtain new signalling ids. Further processing
 * of the Connect-Request will be done in the PLCI state machine for the newly
 * assigned signalling id. PLCI messages also include the
 * Select-B-Protocol-Request.
 *
 * Facility messages are handled either by an NCCI or a PLCI layer according to
 * the facility selector. But for now no facility messages are supported by this
 * driver.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to an PLCI.
 * @retval 1                    The message must be handled by an PLCI layer.
 */

int daicdisp_is_plci_message
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned uBaseCmd;
   
   uBaseCmd = (CAPI_GET_CMD (pCapiMsg) & CAPI_CMDMASK_COMMAND);
   if (uBaseCmd == C_INFO_20)
   {
      if (CAPI_GET_PLCI_FROM_CID (CAPI_GET_CID (pCapiMsg)) == 0)
      {
         return (0);
      }
      return (1);
   }
   if (uBaseCmd == C_LISTEN)
   {
      return (0);
   }
   if (uBaseCmd < 0x80)
   {
      return (1);
   }
   return (0);
} /* daicdisp_is_plci_message */





/**
 * Determine if a CAPI message is addressed to the global id state machine.
 *
 * Only Listen-Requests and Info-messages with a CID consisting only of the
 * controller number are addressed to the global id state machine.
 *
 * @param pCapiMsg              I: The CAPI message to characterize.
 *
 * @retval 0                    The message is not addressed to the global id
 *                              state machine.
 * @retval 1                    The message must be handled by the global id
 *                              state machine.
 */

int daicdisp_is_gid_message
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned uBaseCmd;
   
   uBaseCmd = (CAPI_GET_CMD (pCapiMsg) & CAPI_CMDMASK_COMMAND);
   if (uBaseCmd == C_LISTEN)
   {
      return (1);
   }
   if (uBaseCmd == C_INFO_20 &&
       CAPI_GET_PLCI_FROM_CID (CAPI_GET_CID (pCapiMsg)) == 0)
   {
      return (1);
   }
   return (0);
} /* daicdisp_is_gid_message */





/**
 * Translate a channel number into an NCCI value for a id translation entry.
 *
 * This function tries to find the specified channel number in the NCCI info
 * array of the translation entry passed as an argument. The first position to
 * look for is the channel number modulo the NCCI info array size. The result is
 * the NCCI value for the channel number if it is found.
 *
 * If the channel number is not found in the array, the result is 0. As an NCCI
 * cannot take this value, this is a unique value for the result "channel not
 * found".
 *
 * @param pEntry                I: The controller id translation entry to
 *                                 search in.
 * @param uChId                 I: The channel number to look for.
 *
 * @retval 0                    The channel number is currently not known for
 *                              the network id specified through the translation
 *                              table entry.
 * @retval Else                 The NCCI value associated with the specified
 *                              channel number.
 */

static unsigned daicdisp_get_ncci_from_channel
   (DaicIdTransTableEntry_t *pEntry,
    unsigned                 uChId)
{
   size_t n;
   int    i;
   
   if (uChId == 0)
   {
      return (0);
   }
   
   for (n = ARRAY_COUNT (pEntry->aNcciInfo),
           i = (int) uChId % (int) ARRAY_COUNT (pEntry->aNcciInfo);
        n > 0;
        --n, i = (i + 1) & (int) ARRAY_COUNT (pEntry->aNcciInfo))
   {
      if (pEntry->aNcciInfo [i].uChId == uChId)
      {
         return (pEntry->aNcciInfo [i].uNcci);
      }
   }

   return (0);
} /* daicdisp_get_ncci_from_channel */





/**
 * Enter the qualification of a controller id into the translation table.
 *
 * Besides entering the parameters iQualify and uPlci at the table position
 * indexed by uId, the array aNcciInfo is completely cleared. For a newly
 * assigned id this results in a clean startup association array for channel ids
 * and NCCIs and for removed ids this performs a normal cleanup.
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param uId                   I: The controller id for that the qualification
 *                                 is to be entered.
 * @param iQualify              I: The qualification to enter.
 * @param uPlci                 I: The PLCI value to enter.
 *
 * @return Nothing.
 */

static void daicdisp_set_id_trans_table_entry
   (DaicPortData_t *pPortData,
    u_int8_t        bId,
    int             iQualify,
    unsigned        uPlci)
{
   DaicIdTransTableEntry_t *pEntry;
   
   pEntry = &(pPortData->aIdTransTable [bId]);
   pEntry->iQualify = iQualify;
   pEntry->uPlci    = uPlci;
   bzero (pEntry->aNcciInfo, sizeof (pEntry->aNcciInfo));
   
   DBG (LOG_TRACE, pPortData->iUnit,
        "Port %u: Set qualification for controller id %u to %d, PLCI %u",
        pPortData->uPortIdx, (unsigned) bId, iQualify, uPlci);

} /* daicdisp_set_id_trans_table_entry */





/**
 * Allocate a new NCCI and enter the association with network id and channel
 * into the id translation table.
 *
 * This function first allocates a new NCCI. Then the association of this NCCI
 * and its network id and channel id are entered into the NCCI info array of
 * the translation table entry specified as a parameter. If the channel id does
 * already exist in this array, the array entry is re-used for the new NCCI (of
 * course with an error message, because this must not happen).
 *
 * @param pPortData             I/O: The port data as the operation context.
 * @param pEntry                I/O: The controller id translation table entry,
 *                                 the new assignment shall be reflected in.
 * @param uChId                 I: The newly assigned channel id for the network
 *                                 id and NCCI.
 *
 * @retval 0                    All NCCI info array entries in use, no more NCCI
 *                              allowed for the network id or the associated
 *                              PLCI.
 * @retval Else                 The NCCI value for the newly allocated NCCI.
 */

static unsigned daicdisp_alloc_new_ncci_for_channel
   (DaicPortData_t          *pPortData,
    DaicIdTransTableEntry_t *pEntry,
    unsigned                 uChId)
{
   DaicPlciData_t *pPlciData;
   DaicNcciData_t *pNcciData;
   size_t          n;
   int             i;
   
   /* check for exhausted number of NCCIs for the current PLCI */
   pPlciData = &(pPortData->aPlciData [pEntry->uPlci]);
   if (pPlciData->nNumNcci >= DAIC_MAX_NCCI_PER_PLCI)
   {
      DBG (LOG_INFO, pPortData->iUnit,
           "Port %u: PLCI %u: Max. number of NCCIs %zu exhausted, unable to assign new NCCI for new channel id %u, network id %u",
           pPortData->uPortIdx, pPlciData->uPlci, pPlciData->nNumNcci,
           uChId, pPlciData->uNetId);
      return (0);
   }
   
   /* find the array position to enter the new channel id / NCCI association */
   for (n = ARRAY_COUNT (pEntry->aNcciInfo),
           i = (int) uChId % (int) ARRAY_COUNT (pEntry->aNcciInfo);
        n > 0;
        --n, i = (i + 1) & (int) ARRAY_COUNT (pEntry->aNcciInfo))
   {
      if (pEntry->aNcciInfo [i].uChId == 0)
      {
         break;
      }
      if (pEntry->aNcciInfo [i].uChId == uChId)
      {
         DBG (LOG_ERROR, pPortData->iUnit,
              "Port %u: Channel id %u for network id %u still registered for NCCI %u, overwrite with new mapping",
              pPortData->uPortIdx, uChId, pPlciData->uNetId,
              pEntry->aNcciInfo [i].uNcci);
         break;
      }
   }
   if (n == 0)
   {
      DBG (LOG_ERROR, pPortData->iUnit,
           "Port %u: PLCI %u: Max. number of NCCIs %zu not exhausted, but no NCCI / channel id mapping entry available",
           pPortData->uPortIdx, pPlciData->uPlci, pPlciData->nNumNcci);
      return (0);
   }
   /* i now addresses the array index to enter the association of the channel id
    * and the newly assigned NCCI with the network id
    */
   
   /* now assign a new NCCI */
   pNcciData = daicncci_alloc_new_ncci (pPortData, pPlciData, uChId);
   if (pNcciData == NULL)
   {
      /* error message is already printed */
      return (0);
   }
   
   /* enter the new channel id / NCCI association at the determined position */
   pEntry->aNcciInfo [i].uChId = uChId;
   pEntry->aNcciInfo [i].uNcci = pNcciData->uNcci;
   
   DBG (LOG_TRACE, pPortData->iUnit,
        "Port %u: Network id %u: Associated NCCI %u with channel id %u",
        pPortData->uPortIdx, pNcciData->uNetId, pNcciData->uNcci, uChId);

   return (pNcciData->uNcci);
} /* daicdisp_alloc_new_ncci_for_channel */
