/**
 * @file i4bcmgr_l4if.c
 *
 * I4BCMgr-Layer4Interface - I4B layer 4 interface handling.
 *
 * Copyright: 2000-2004 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: i4bcmgr_l4if.c,v 1.1.2.1 2006/02/28 21:35:28 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     i4bcapimgr - The I4B driver for CAPI manager driven controllers $
 * @date        01.01.2000
 * @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/limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <capi20.h>
#include <capi_bsd.h>
#include <i4b_cause.h>
#include <i4b_ioctl.h>

/* import includes */
#include <i4b/include/i4b_l3l4.h>
#include <i4b/layer4/i4b_l4.h>

#define __I4BCMGR_L4IF__

/* local includes */
#include <i4b/i4bcapimgr/i4bcmgr_var.h>
#include <i4b/i4bcapimgr/i4bcmgr_nccism.h>
#include <i4b/i4bcapimgr/i4bcmgr_capiutil.h>





/* === public definitions ================================================ */





/* === private definitions =============================================== */





/* === prototypes for private functions ================================== */





/* register at CAPI manager */
static void RegisterAtCapi (void);

/* release at CAPI manager */
static void ReleaseAtCapi (void);

/* send listen-req to CAPI controller */
static void SendListenReq
   (I4bCapiCtlrData_t *pCtlrData,
    int                fDoListen);





/* === definition of public functions ==================================== */





/*
        start new outgoing connection
        -----------------------------
        For a Connect-Req there is currently no assigned i4bcapimgr connection
        data entry. So this function assignes one first. After this we can send
        a message to the i4bcapimgr worker thread to handle the Connect-Req.
*/

void I4bCmgr_NConnectRequest
   (u_int uCdId)
{
   call_desc_t             *pI4bCd;
   I4bCapiCtlrData_t       *pCtlrData;
   I4bCapiConnectionData_t *pConnData;
   I4bCEventQueueEntry_t   *pMsg;
   int                      i;
   int                      s;
   
   DBG (LOG_INFO, "Cd %u: Got N_CONNECT_REQUEST", uCdId);
   
   s = SPLI4B ();
   
   /* first get the address of the call descriptor data for the cd id */
   pI4bCd = cd_by_cdid (uCdId);
   if (! pI4bCd)
   {
      splx (s);
      printf ("i4bcapimgr: ERROR: Got N_CONNECT_REQUEST with invalid call descriptor %u\n",
              uCdId);
      return;
   }
   DBG (LOG_DEBUG, "Cd %u: Got call descriptor 0x%p", uCdId, pI4bCd);
   
   /* get the controller to use */
   if ((size_t) (pI4bCd->controller) >= ARRAY_COUNT (e_auMapI4bToCapiCtlr) ||
       e_auMapI4bToCapiCtlr [pI4bCd->controller] == 0 ||
       e_auMapI4bToCapiCtlr [pI4bCd->controller] >=
          ARRAY_COUNT (e_aI4bCapiCtlrData))
   {
      printf ("i4bcapimgr: ERROR: Got N_CONNECT_REQUEST with invalid i4b controller no. %d, call descriptor %u\n",
              pI4bCd->controller, pI4bCd->cdid);
      SET_CAUSE_TV (pI4bCd->cause_in, CAUSET_I4B, CAUSE_I4B_NOCHAN);
      i4b_l4_disconnect_ind (pI4bCd);
      freecd_by_cd (pI4bCd);
      splx (s);
      return;
   }
   pCtlrData = &(e_aI4bCapiCtlrData
                    [e_auMapI4bToCapiCtlr [pI4bCd->controller]]);
   if (! pCtlrData->fEntryUsed ||
       pCtlrData->iI4bCtlrNum < 0 ||
       ! pCtlrData->paConnData)
   {
      printf ("i4bcapimgr: ERROR: Got N_CONNECT_REQUEST for unusable controller (i4b %d, CAPI %u), call descriptor %u\n",
              pI4bCd->controller, pCtlrData->uCapiCtlrNum, uCdId);
      SET_CAUSE_TV (pI4bCd->cause_in, CAUSET_I4B, CAUSE_I4B_NOCHAN);
      i4b_l4_disconnect_ind (pI4bCd);
      freecd_by_cd (pI4bCd);
      splx (s);
      return;
   }
   DBG (LOG_DEBUG, "Cd %u: Will use CAPI ctlr. %u, i4b ctlr. %d",
        uCdId, pCtlrData->uCapiCtlrNum, pI4bCd->controller);

   /* now select the B-channel to use */
   if (pI4bCd->channelid == CHAN_ANY)
   {
      /* just select first free channel */
      for (i = 0; (size_t) i < pCtlrData->nNumBChannels; i++)
      {
         if (! pCtlrData->paConnData [i].pI4bCd)
         {
            break;
         }
      }
      /* i now indexes either a valid B-channel or points after the array of
       * B-channels, will be checked soon
       */
      DBG (LOG_DEBUG, "Cd %u: Next free B-channel determined to %d", uCdId, i);
   }
   else
   {
      /* just copy the requested channel number to i, will soon be checked for
       * validity
       */
      i = pI4bCd->channelid;
      DBG (LOG_DEBUG, "Cd %u: B-channel %d requested", uCdId, i);
   }
   if ((size_t) i >= pCtlrData->nNumBChannels ||
       (size_t) i >= ARRAY_COUNT (ctrl_desc [pI4bCd->controller].bch_state) ||
       pCtlrData->paConnData [i].pI4bCd)
   {
      if (pI4bCd->channelid == CHAN_ANY)
      {
         printf ("i4bcapimgr: ERROR: N_CONNECT_REQUEST: All B-channels in use for controller (i4b %d, CAPI %u), call descriptor %u\n",
                 pI4bCd->controller, pCtlrData->uCapiCtlrNum, uCdId);
      }
      else
      {
         printf ("i4bcapimgr: ERROR: N_CONNECT_REQUEST: B-channel %d invalid or already in use for controller (i4b %d, CAPI %u), call descriptor %u\n",
                 pI4bCd->channelid, pI4bCd->controller,
                 pCtlrData->uCapiCtlrNum, uCdId);
      }
      SET_CAUSE_TV (pI4bCd->cause_in, CAUSET_I4B, CAUSE_I4B_NOCHAN);
      i4b_l4_disconnect_ind (pI4bCd);
      freecd_by_cd (pI4bCd);
      splx (s);
      return;
   }
   pConnData = &(pCtlrData->paConnData [i]);
   DBG (LOG_DEBUG, "Cd %u: Will use B-channel %d", uCdId, i);
   
   /* so we have all information to allocate a channel for the connection */
   pConnData->pI4bCd = pI4bCd;
   pConnData->uCdId  = pI4bCd->cdid;
   pI4bCd->channelid = i;
   ctrl_desc [pI4bCd->controller].bch_state [i] = BCH_ST_USED;
   
   splx (s);
   
   /* now a B-channel is assigned for the call descriptor, forward the
    * Connect-Req to the i4bcapimgr worker thread
    */
    
   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Cd %u: ERROR: Out of memory for N_CONNECT_REQUEST",
              uCdId);
      SET_CAUSE_TV (pI4bCd->cause_in, CAUSET_I4B, CAUSE_I4B_NOCHAN);
      i4b_l4_disconnect_ind (pI4bCd);
      freecd_by_cd (pI4bCd);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum = pCtlrData->uCapiCtlrNum;
   pMsg->uBChn        = pConnData->uBChn;
   pMsg->uCdId        = uCdId;
   pMsg->event        = I4BCMGR_EVENT_CONN_REQ;
   
   /* enter the message into the event queue */
   DBG (LOG_DEBUG, "Cd %u: Enqueue N_CONNECT_REQUEST", uCdId);
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DEBUG, "Cd %u: N_CONNECT_REQUEST enqueued", uCdId);
   
} /* I4bCmgr_NConnectRequest */




   
/*
        answer new incoming call
        ------------------------
*/

void I4bCmgr_NConnectResponse
   (u_int uCdId,
    int   iResponse,
    int   iCause)
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   DBG (LOG_TRACE,
        "Cd %u: Got N_CONNECT_RESPONSE, iResponse %d, iCause 0x%02X",
        uCdId, iResponse, iCause);

   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Cd %u: ERROR: Out of memory for N_CONNECT_RESPONSE",
              uCdId);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum            = UINT_MAX;
   pMsg->uBChn                   = UINT_MAX;
   pMsg->uCdId                   = uCdId;
   pMsg->event                   = I4BCMGR_EVENT_CONN_RESP;
   pMsg->data.connResp.iResponse = iResponse;
   pMsg->data.connResp.iCause    = iCause;
   
   /* enter the message into the event queue */
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DEBUG, "Cd %u: N_CONNECT_RESPONSE enqueued", uCdId);
   
} /* I4bCmgr_NConnectResponse */




    
/*
        do an active disconnect for a call
        ----------------------------------
*/

void I4bCmgr_NDisconnectRequest
   (u_int uCdId,
    int   iCause)
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   DBG (LOG_TRACE,
        "Cd %u: Got N_DISCONNECT_REQUEST, iCause 0x%02X",
        uCdId, iCause);

   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Cd %u: ERROR: Out of memory for N_DISCONNECT_REQUEST",
              uCdId);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum        = UINT_MAX;
   pMsg->uBChn               = UINT_MAX;
   pMsg->uCdId               = uCdId;
   pMsg->event               = I4BCMGR_EVENT_DISC_REQ;
   pMsg->data.discReq.iCause = iCause;
   
   /* enter the message into the event queue */
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DEBUG, "Cd %u: N_DISCONNECT_REQUEST enqueued", uCdId);
   
} /* I4bCmgr_NDisconnectRequest */





/*
        tell the switch to wait with expiring new incoming call
        -------------------------------------------------------
*/

void I4bCmgr_NAlertRequest
   (u_int uCdId)
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   DBG (LOG_TRACE, "Cd %u: Got N_ALERT_REQUEST", uCdId);

   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Cd %u: ERROR: Out of memory for N_ALERT_REQUEST",
              uCdId);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum = UINT_MAX;
   pMsg->uBChn        = UINT_MAX;
   pMsg->uCdId        = uCdId;
   pMsg->event        = I4BCMGR_EVENT_ALERT_REQ;
   
   /* enter the message into the event queue */
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DEBUG, "Cd %u: N_ALERT_REQUEST enqueued", uCdId);
   
} /* I4bCmgr_NAlertRequest */




   
/*
        download firmware to controller
        -------------------------------
*/

int I4bCmgr_NDownload
   (int                  iUnit,
    int                  iNumProtocols,
    struct isdn_dr_prot *paProtocols)
{
   I4bCapiCtlrData_t   *pCtlrData;
   CAPICtlrDataBlock_t *paDataBlocks;
   int                  i;
   unsigned             uRes;
   
   DBG (LOG_TRACE,
        "Got N_DOWNLOAD for unit %d (CAPI controller no.), %d protocol data blocks",
        iUnit, iNumProtocols);

   /* check if specified controller is registered */
   if ((size_t) iUnit >= ARRAY_COUNT (e_aI4bCapiCtlrData))
   {
      printf ("i4bcapimgr: WARNING: Unknown unit no. %d (CAPI controller no.) for download request\n",
              iUnit);
      return (ENODEV);
   }
   pCtlrData = &(e_aI4bCapiCtlrData [iUnit]);
   if (! pCtlrData->fEntryUsed ||
       pCtlrData->uCapiCtlrNum != (unsigned) iUnit ||
       pCtlrData->iI4bCtlrNum < 0)
   {
      printf ("i4bcapimgr: WARNING: Unit %d (CAPI controller no.) for download not registered\n",
              iUnit);
      return (ENODEV);
   }
   
   /* perform download as a reset controller CAPI call */
   if (iNumProtocols > 0)
   {
      paDataBlocks = malloc (iNumProtocols * sizeof (paDataBlocks [0]),
                             M_TEMP, M_WAITOK);
      if (! paDataBlocks)
      {
         printf ("i4bcapimgr: ERROR: Out of memory for %d CAPI controlle data blocks\n",
                 iNumProtocols);
         return (ENOMEM);
      }
      for (i = 0; i < iNumProtocols; i++)
      {
         paDataBlocks [i].nLenDataBlock = paProtocols [i].bytecount;
         paDataBlocks [i].paucDataBlock = paProtocols [i].microcode;
      }
   }
   else
   {
      paDataBlocks = 0;
   }
   uRes = kcapi_reset_ctlr ((unsigned) iUnit,
                            (size_t) iNumProtocols, paDataBlocks);
   free (paDataBlocks, M_TEMP);
   if (uRes != CAPI_OK)
   {
      DBG (LOG_ERROR,
           "Error 0x%04X performing download for CAPI controller %d",
           uRes, iUnit);
   }
   DBG (LOG_INFO, "Download for CAPI controller %d successful", iUnit);
   
   /* just return the CAPI result value as an errno value */
   return ((int) uRes);
} /* I4bCmgr_NDownload */





/*
        handle layer 4 management commands
        ----------------------------------
*/

void I4bCmgr_NMgmtCommand
   (int   iUnit,
    int   iCmd,
    void *pParam)
{
   I4bCapiCtlrData_t *pCtlrData;
   
   /* check if the unit no. is a valid CAPI controller number */
   if ((size_t) iUnit >= ARRAY_COUNT (e_aI4bCapiCtlrData) ||
       ! e_aI4bCapiCtlrData [iUnit].fEntryUsed ||
       e_aI4bCapiCtlrData [iUnit].iI4bCtlrNum < 0)
   {
      printf ("i4bcapimgr: ERROR: Got N_MGMT_COMMAND with invalid unit number %d (CAPI controller no.)\n",
              iUnit);
      return;
   }
   pCtlrData = &(e_aI4bCapiCtlrData [iUnit]);
   
   /* action according to command */
   switch (iCmd)
   {
      case CMR_DOPEN:
         /* isdnd has attached, perform CAPI register and send Listen-Req to
          * handle incoming calls
          */
         RegisterAtCapi ();
         if (e_fCapiRegistered)
         {
            DBG (LOG_INFO,
                 "ISDN daemon attached, send Listen-Req to controller %u",
                 pCtlrData->uCapiCtlrNum);
            SendListenReq (pCtlrData, 1);
         }
         break;
         
      case CMR_DCLOSE:
         /* isdnd has detached, perform CAPI release */
         if (e_fCapiRegistered)
         {
            DBG (LOG_INFO,
                 "ISDN daemon detached from controller %u, perform CAPI release",
                 pCtlrData->uCapiCtlrNum);
            ReleaseAtCapi ();
         }
         else
         {
            DBG (LOG_INFO, "ISDN daemon detached from controller %u",
                 pCtlrData->uCapiCtlrNum);
         }
         break;
      
      default:
         /* other management commands are not handled or valid for i4bcapimgr */
         DBG (LOG_DEBUG,
              "N_MGMT_COMMAND with command %d for unit %d, will be ignored",
              iCmd, iUnit);
         break;
   }
   
   /* suppress warning for unused variable */
   (void) pParam;
   
} /* I4bCmgr_NMgmtCommand */





/*
        read the linktab to the i4bcapimgr connection handler
        -----------------------------------------------------
*/

isdn_link_t *I4bCmgr_GetLinkTab         /* the link to i4bcapimgr conn. handler,
                                         * NULL if parameters do not identify a
                                         * valid B-channel connection
                                         */
   (int iUnit,                          /* I: unit no. of the ctlr. driver;
                                         *    for i4bcapimgr it is equal to the
                                         *    CAPI controller number
                                         */
    int iChannel)                       /* I: B-channel no., starting at 0 */
{
   I4bCapiConnectionData_t *pConnData;
   int                      s;

   s = SPLI4B ();
   
   /* determine the connection data entry for controller and channel */
   pConnData = I4bCmgr_GetConnDataByUnitAndChannel (iUnit, iChannel);
   if (! pConnData)
   {
      splx (s);
      printf ("i4bcapimgr: %s: ERROR: Got invalid unit/channel %d/%d",
              __FUNCTION__, iUnit, iChannel);
      return (NULL);
   }
   
   splx (s);
   
   /* return the linktab of the connection */
   /* Note: The initialization of the linktab data (and the referenced data)
    *       takes place in the NCCI layer when performing N_CONNECT_REQUEST or
    *       handling signal for established connection from the PLCI layer.
    */
   return (&(pConnData->isdnLinkTab));
} /* I4bCmgr_GetLinkTab */





/*
        store linktab to the app. driver into i4bcapimgr connection data
        ----------------------------------------------------------------
*/

void I4bCmgr_SetLinkTab
   (int          iUnit,                 /* I: unit no. of the ctlr. driver;
                                         *    for i4bcapimgr it es equal to the
                                         *    CAPI controller number
                                         */
    int          iChannel,              /* I: B-channel no., starting at 0 */
    drvr_link_t *pDrvrLinkTab)          /* I: link to the i4b app. driver */
{
   I4bCapiConnectionData_t *pConnData;
   int                      s;
   
   s = SPLI4B ();
   
   /* determine the connection data entry for controller and channel */
   pConnData = I4bCmgr_GetConnDataByUnitAndChannel (iUnit, iChannel);
   if (! pConnData)
   {
      splx (s);
      printf ("i4bcapimgr: %s: ERROR: Got invalid unit/channel %d/%d",
              __FUNCTION__, iUnit, iChannel);
      return;
   }
   
   /* store the i4b application linktab */
   pConnData->pDrvrLinkTab = pDrvrLinkTab;
   
   splx (s);
   
} /* I4bCmgr_SetLinkTab */





/*
        configure the B-channel settings
        --------------------------------
*/

void I4bCmgr_BChConfig
   (int iUnit,                          /* I: unit no. of the ctlr. driver;
                                         *    for i4bcapimgr it es equal to the
                                         *    CAPI controller number
                                         */
    int iChannel,                       /* I: B-channel no., starting at 0 */
    int iBProt,                         /* I: B-channel protocol, values from
                                         *    BPROT_* (i4b_ioctl.h)
                                         */
    int fUp)                            /* I: 0: switch B-channel off
                                         *    1: switch B-channel on
                                         */
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   DBG (LOG_TRACE, "Unit %d / channel %d: Got bch_config (%s)",
        iUnit, iChannel,
        fUp ? "UP" : "DOWN");

   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Unit %d / channel %d: ERROR: out of memory for bch_config",
              iUnit, iChannel);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum = iUnit;
   pMsg->uBChn        = iChannel;
   pMsg->uCdId        = UINT_MAX;
   pMsg->event        = fUp ? I4BCMGR_EVENT_BCH_CONFIG_UP
                            : I4BCMGR_EVENT_BCH_CONFIG_DOWN;
   pMsg->data.bchConfigUp.iBProt = iBProt;
   
   /* enter the message into the event queue */
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DEBUG, "Unit %d / channel %d: bch_config enqueued",
        iUnit, iChannel);
   
} /* I4bCmgr_BChConfig */





/*
        start transmission of B-channel data
        ------------------------------------
*/

void I4bCmgr_BchTxStart
   (int iUnit,                          /* I: unit no. of the ctlr. driver;
                                         *    for i4bcapimgr it is equal to the
                                         *    CAPI controller number
                                         */
    int iChannel)                       /* I: B-channel no., starting at 0 */
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   DBG (LOG_DATAMSG, "Unit %d / channel %d: got bch_tx_start",
        iUnit, iChannel);

   /* get memory for new message */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      printf ("i4bcapimgr: Unit %d / channel %d: ERROR: out of memory for bch_tx_start",
              iUnit, iChannel);
      return;
   }
   
   /* fill the message data */
   pMsg->uCapiCtlrNum = iUnit;
   pMsg->uBChn        = iChannel;
   pMsg->uCdId        = UINT_MAX;
   pMsg->event        = I4BCMGR_EVENT_BCH_TX_START;
   
   /* enter the message into the event queue */
   s = SPLI4B ();
   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   splx (s);
   
   DBG (LOG_DATAMSG, "Unit %d / channel %d: bch_tx_start enqueued",
        iUnit, iChannel);
   
} /* I4bCmgr_BchTxStart */





/*
        read status of B-channel data transmission
        ------------------------------------------
*/

void I4bCmgr_Stat
   (int                 iUnit,          /* I: unit no. of the ctlr. driver;
                                         *    for i4bcapimgr it es equal to the
                                         *    CAPI controller number
                                         */
    int                 iChannel,       /* I: B-channel no., starting at 0 */
    bchan_statistics_t *pBChnStat)      /* O: requested statistic data (bytes
                                         *    received and bytes sent)
                                         */
{
   I4bCapiConnectionData_t *pConnData;
   int                      s;
   
   /* initialize the return data */
   if (pBChnStat)
   {
      bzero (pBChnStat, sizeof (*pBChnStat));
   }
   
   s = SPLI4B ();
   
   /* determine the connection data entry for controller and channel */
   pConnData = I4bCmgr_GetConnDataByUnitAndChannel (iUnit, iChannel);
   if (! pConnData)
   {
      splx (s);
      printf ("i4bcapimgr: %s: ERROR: Got invalid unit/channel %d/%d",
              __FUNCTION__, iUnit, iChannel);
      return;
   }
   
   /* fill the return data structure */
   if (pBChnStat)
   {
      pBChnStat->outbytes = pConnData->uTxCount;
      pBChnStat->inbytes  = pConnData->uRxCount;
   }
   pConnData->uTxCount = 0;
   pConnData->uRxCount = 0;
   
   splx (s);
   
} /* I4bCmgr_Stat */





/* === definition of private functions =================================== */





/*
        register at CAPI manager
        ------------------------
*/

static void RegisterAtCapi (void)
{
   unsigned uRes;
   
   /* check if already registered */
   if (e_fCapiRegistered)
   {
      return;
   }
   
   /* check if at least one CAPI connection is possible */
   if (e_uMaxCapiConnections <= 0)
   {
      printf ("i4bcapimgr: No CAPI connections possible, do not register at CAPI\n");
      return;
   }
   
   /* limit the possible CAPI connections to the number of available i4b call
    * descriptors
    */
   if (e_uMaxCapiConnections > N_CALL_DESC)
   {
      e_uMaxCapiConnections = N_CALL_DESC;
   }
   
   /* do the CAPI register */
   uRes = kcapi_register (e_uMaxCapiConnections,
                          I4BCMGR_MAX_BDATA_BLOCKS, BCH_MAX_DATALEN,
                          &e_uCapiApplID);
   if (uRes != CAPI_OK)
   {
      printf ("i4bcapimgr: ERROR 0x%04X registering at CAPI, uMaxLogicalConnections %u, uMaxBDataBlocks %u, uMaxBDataLen %u\n",
              uRes, e_uMaxCapiConnections,
              I4BCMGR_MAX_BDATA_BLOCKS, BCH_MAX_DATALEN);
      e_uCapiApplID = 0;
      return;
   }
   e_fCapiRegistered = 1;
   DBG (LOG_INFO, "CAPI registration successful, appl. id %u",
        e_uCapiApplID);

   /* set the CAPI callback function */
   uRes = kcapi_set_signal (e_uCapiApplID, I4bCmgr_CapiCallback, 0);
   if (uRes != CAPI_OK)
   {
      /* without a callback function no CAPI operation is possible */
      printf ("i4bcapimgr: ERROR 0x%04X setting the CAPI callback function\n",
              uRes);
      e_fCapiRegistered = 0;
      (void) kcapi_release (e_uCapiApplID);
      e_uCapiApplID = 0;
      return;
   }
   DBG (LOG_TRACE, "Callback function registered successfully");
   
   /* CAPI register successful, activate all CAPI controllers */
   for (uRes = 0; uRes < ARRAY_COUNT (e_aI4bCapiCtlrData); uRes++)
   {
      if (e_aI4bCapiCtlrData [uRes].fEntryUsed &&
          (size_t) (e_aI4bCapiCtlrData [uRes].iI4bCtlrNum) < MAX_CONTROLLERS)
      {
         ctrl_desc [e_aI4bCapiCtlrData [uRes].iI4bCtlrNum].dl_est = DL_UP;
      }
   }
   
} /* RegisterAtCapi */





/*
        release at CAPI manager
        -----------------------
*/

static void ReleaseAtCapi (void)
{
   unsigned uRes;
   
   /* check if registration is (still) active */
   if (! e_fCapiRegistered)
   {
      return;
   }

   e_fCapiRegistered = 0;
   uRes = kcapi_release (e_uCapiApplID);
   if (uRes != CAPI_OK)
   {
      printf ("i4bcapimgr: ERROR 0x%04X releasing at CAPI\n", uRes);
   }

   DBG (LOG_INFO, "Appl. id %u released at CAPI manager", e_uCapiApplID);
   e_uCapiApplID = 0;
   
   /* CAPI release successful, disable all CAPI controllers */
   for (uRes = 0; uRes < ARRAY_COUNT (e_aI4bCapiCtlrData); uRes++)
   {
      if (e_aI4bCapiCtlrData [uRes].fEntryUsed &&
          (size_t) (e_aI4bCapiCtlrData [uRes].iI4bCtlrNum) < MAX_CONTROLLERS)
      {
         ctrl_desc [e_aI4bCapiCtlrData [uRes].iI4bCtlrNum].dl_est = DL_DOWN;
      }
   }
   
} /* ReleaseAtCapi */





/*
        send listen-req to CAPI controller
        ----------------------------------
        Note: Priviledge level for I4B is assumed.
*/

static void SendListenReq
   (I4bCapiCtlrData_t *pCtlrData,
    int                fDoListen)
{
   struct mbuf   *pmbMsg;
   CAPIMsg_t     *pCapiMsg;
   unsigned char *p;
   unsigned       uRes;
   
   /* allocate an mbuf for the CAPI message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t) +
                            sizeof (CAPIListenReq_t) +
                            2);
   if (! pmbMsg)
   {
      printf ("i4bcapimgr: ERROR: Out of mbufs for Listen-Req to controller %u, message length %u\n",
              pCtlrData->uCapiCtlrNum, (unsigned) sizeof (CAPIMsg_t));
      return;
   }
   pCapiMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   
   /* fill the CAPI message for listen-req */
   C_PUT_WORD (pCapiMsg->head.wApp, e_uCapiApplID);
   C_PUT_WORD (pCapiMsg->head.wCmd, CAPI_REQUEST (C_LISTEN));
   C_PUT_WORD (pCapiMsg->head.wNum, 0);
   C_PUT_DWORD (pCapiMsg->head.dwCid, pCtlrData->uCapiCtlrNum);
   if (fDoListen)
   {
      C_PUT_DWORD (pCapiMsg->info.listen_req.dwInfoMask,
                   CAPI_INFO_MASK_CAUSE |
                      CAPI_INFO_MASK_DATE_TIME |
                      CAPI_INFO_MASK_DISPLAY |
                      CAPI_INFO_MASK_CALL_PROGRESSION |
                      CAPI_INFO_MASK_CHARGING);
                                        /* Unfortunately there is currently no
                                         * method to support extension numbers
                                         * (dial-in numbers) in i4b. So we do
                                         * not need the CallingPartyNumber as a
                                         * separate IE. Just what is delivered
                                         * in the Connect-Ind is forwarded to
                                         * the upper layers.
                                         */
      C_PUT_DWORD (pCapiMsg->info.listen_req.dwCipMask,
                   CAPI_CIP_MASK_SPEECH |
                      CAPI_CIP_MASK_UNRESTRICTED_DATA |
                      CAPI_CIP_MASK_3100Hz_AUDIO |
                      CAPI_CIP_MASK_TELEPHONY);
   }
   else
   {
      C_PUT_DWORD (pCapiMsg->info.listen_req.dwInfoMask, 0);
      C_PUT_DWORD (pCapiMsg->info.listen_req.dwCipMask,
                   CAPI_CIP_MASK_UNLISTEN);
   }
   C_PUT_DWORD (pCapiMsg->info.listen_req.dwCipMask2,
                CAPI_CIP_MASK2_UNLISTEN);
   p = (unsigned char *) &(pCapiMsg->info.listen_req.dwCipMask2) +
       sizeof (pCapiMsg->info.listen_req.dwCipMask2);
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   /* compute the message length */
   pmbMsg->m_len = (int) (p - (unsigned char *) pCapiMsg);
   C_PUT_WORD (pCapiMsg->head.wLen, pmbMsg->m_len);
   
   /* send the message */
   uRes = kcapi_put_message (e_uCapiApplID, pmbMsg);
   if (uRes == CAPI_OK)
   {
      DBG (LOG_TRACE, "Listen-Req sent successfully, %s",
           fDoListen ? "start listening for incoming calls" : "unlisten");
   }
   else
   {
      kcapi_free_mbuf (pmbMsg);
      printf ("i4bcapimgr: WARNING: Error 0x%04X sending Listen-Req, %s",
              uRes,
              fDoListen ? "start listening for incoming calls" : "unlisten");
   }
   
   /* Note: The Listen-Conf is handled by the worker thread. */
   
} /* SendListenReq */
