/******************************************************************************
 *
 *  Copyright (C) 1999-2012 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  Port Emulation entity utilities
 *
 ******************************************************************************/
#include <string.h>

#include "osi/include/mutex.h"

#include "bt_target.h"
#include "bt_common.h"
#include "btm_int.h"
#include "btu.h"
#include "l2cdefs.h"
#include "port_api.h"
#include "port_int.h"
#include "rfc_int.h"
#include "rfcdefs.h"

static const tPORT_STATE default_port_pars =
{
    PORT_BAUD_RATE_9600,
    PORT_8_BITS,
    PORT_ONESTOPBIT,
    PORT_PARITY_NO,
    PORT_ODD_PARITY,
    PORT_FC_OFF,
    0,                      /* No rx_char */
    PORT_XON_DC1,
    PORT_XOFF_DC3,
};



/*******************************************************************************
**
** Function         port_allocate_port
**
** Description      Look through the Port Control Blocks for a free one.  Note
**                  that one server can open several ports with the same SCN
**                  if it can support simulteneous requests from different
**                  clients.
**
** Returns          Pointer to the PORT or NULL if not found
**
*******************************************************************************/
tPORT *port_allocate_port (UINT8 dlci, BD_ADDR bd_addr)
{
    tPORT  *p_port = &rfc_cb.port.port[0];
    UINT8  xx, yy;

    for (xx = 0, yy = rfc_cb.rfc.last_port + 1; xx < MAX_RFC_PORTS; xx++, yy++)
    {
        if (yy >= MAX_RFC_PORTS)
            yy = 0;

        p_port = &rfc_cb.port.port[yy];
        if (!p_port->in_use)
        {
            fixed_queue_free(p_port->tx.queue, NULL);
            fixed_queue_free(p_port->rx.queue, NULL);
            alarm_free(p_port->rfc.port_timer);
            memset(p_port, 0, sizeof (tPORT));

            p_port->in_use = TRUE;
            p_port->inx    = yy + 1;

            p_port->dlci   = dlci;
            memcpy (p_port->bd_addr, bd_addr, BD_ADDR_LEN);

            /* During the open set default state for the port connection */
            port_set_defaults (p_port);

            rfc_cb.rfc.last_port = yy;
            RFCOMM_TRACE_DEBUG("rfc_cb.port.port[%d]:%p allocated, last_port:%d", yy, p_port, rfc_cb.rfc.last_port);
            RFCOMM_TRACE_DEBUG("port_allocate_port:bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
                                bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);
            return (p_port);
        }
    }

    /* If here, no free PORT found */
    return (NULL);
}


/*******************************************************************************
**
** Function         port_set_defaults
**
** Description      Set defualt port parameters
**
**
*******************************************************************************/
void port_set_defaults (tPORT *p_port)
{
    fixed_queue_free(p_port->tx.queue, NULL);
    fixed_queue_free(p_port->rx.queue, NULL);
    alarm_free(p_port->rfc.port_timer);

    p_port->ev_mask        = 0;
    p_port->p_callback     = NULL;
    p_port->port_ctrl      = 0;
    p_port->error          = 0;
    p_port->line_status    = 0;
    p_port->rx_flag_ev_pending = FALSE;
    p_port->peer_mtu       = RFCOMM_DEFAULT_MTU;

    p_port->user_port_pars = default_port_pars;
    p_port->peer_port_pars = default_port_pars;

    p_port->credit_tx      = 0;
    p_port->credit_rx      = 0;
/*  p_port->credit_rx_max  = PORT_CREDIT_RX_MAX;            Determined later */
/*  p_port->credit_rx_low  = PORT_CREDIT_RX_LOW;            Determined later */

    memset (&p_port->local_ctrl, 0, sizeof (p_port->local_ctrl));
    memset (&p_port->peer_ctrl, 0, sizeof (p_port->peer_ctrl));
    memset (&p_port->rx, 0, sizeof (p_port->rx));
    memset (&p_port->tx, 0, sizeof (p_port->tx));
    p_port->rx.queue = fixed_queue_new(SIZE_MAX);
    p_port->tx.queue = fixed_queue_new(SIZE_MAX);
    p_port->rfc.port_timer = alarm_new("rfcomm_port.port_timer");
}

/*******************************************************************************
**
** Function         port_select_mtu
**
** Description      Select MTU which will best serve connection from our
**                  point of view.
**                  If our device is 1.2 or lower we calculate how many DH5s
**                  fit into 1 RFCOMM buffer.
**
**
*******************************************************************************/
void port_select_mtu (tPORT *p_port)
{
    UINT16 packet_size;

    /* Will select MTU only if application did not setup something */
    if (p_port->mtu == 0)
    {
        /* find packet size which connection supports */
        packet_size = btm_get_max_packet_size (p_port->bd_addr);
        if (packet_size == 0)
        {
            /* something is very wrong */
            RFCOMM_TRACE_WARNING ("port_select_mtu bad packet size");
            p_port->mtu = RFCOMM_DEFAULT_MTU;
        }
        else
        {
            /* We try to negotiate MTU that each packet can be split into whole
            number of max packets.  For example if link is 1.2 max packet size is 339 bytes.
            At first calculate how many whole packets it is.  MAX L2CAP is 1691 + 4 overhead.
            1695, that will be 5 Dh5 packets.  Now maximum RFCOMM packet is
            5 * 339 = 1695. Minus 4 bytes L2CAP header 1691.  Minus RFCOMM 6 bytes header overhead 1685

            For EDR 2.0 packet size is 1027.  So we better send RFCOMM packet as 1 3DH5 packet
            1 * 1027 = 1027.  Minus 4 bytes L2CAP header 1023.  Minus RFCOMM 6 bytes header overhead 1017 */
            if ((L2CAP_MTU_SIZE + L2CAP_PKT_OVERHEAD) >= packet_size)
            {
                p_port->mtu = ((L2CAP_MTU_SIZE + L2CAP_PKT_OVERHEAD) / packet_size * packet_size) - RFCOMM_DATA_OVERHEAD - L2CAP_PKT_OVERHEAD;
                RFCOMM_TRACE_DEBUG ("port_select_mtu selected %d based on connection speed", p_port->mtu);
            }
            else
            {
                p_port->mtu = L2CAP_MTU_SIZE - RFCOMM_DATA_OVERHEAD;
                RFCOMM_TRACE_DEBUG ("port_select_mtu selected %d based on l2cap PDU size", p_port->mtu);
            }
        }
    }
    else
    {
        RFCOMM_TRACE_DEBUG ("port_select_mtu application selected %d", p_port->mtu);
    }
    p_port->credit_rx_max  = (PORT_RX_HIGH_WM / p_port->mtu);
    if( p_port->credit_rx_max > PORT_RX_BUF_HIGH_WM )
        p_port->credit_rx_max = PORT_RX_BUF_HIGH_WM;
    p_port->credit_rx_low  = (PORT_RX_LOW_WM / p_port->mtu);
    if( p_port->credit_rx_low > PORT_RX_BUF_LOW_WM )
        p_port->credit_rx_low = PORT_RX_BUF_LOW_WM;
    p_port->rx_buf_critical = (PORT_RX_CRITICAL_WM / p_port->mtu);
    if( p_port->rx_buf_critical > PORT_RX_BUF_CRITICAL_WM )
        p_port->rx_buf_critical = PORT_RX_BUF_CRITICAL_WM;
    RFCOMM_TRACE_DEBUG ("port_select_mtu credit_rx_max %d, credit_rx_low %d, rx_buf_critical %d",
                          p_port->credit_rx_max, p_port->credit_rx_low, p_port->rx_buf_critical);
}


/*******************************************************************************
**
** Function         port_release_port
**
** Description      Release port infor control block.
**
** Returns          Pointer to the PORT or NULL if not found
**
*******************************************************************************/
void port_release_port (tPORT *p_port)
{
    BT_HDR *p_buf;
    UINT32 mask;
    tPORT_CALLBACK *p_port_cb;
    tPORT_STATE user_port_pars;

    mutex_global_lock();
    RFCOMM_TRACE_DEBUG("port_release_port, p_port:%p", p_port);
    while ((p_buf = (BT_HDR *)fixed_queue_try_dequeue(p_port->rx.queue)) != NULL)
        osi_free(p_buf);

    p_port->rx.queue_size = 0;

    while ((p_buf = (BT_HDR *)fixed_queue_try_dequeue(p_port->tx.queue)) != NULL)
        osi_free(p_buf);

    p_port->tx.queue_size = 0;

    mutex_global_unlock();

    alarm_cancel(p_port->rfc.port_timer);

    p_port->state = PORT_STATE_CLOSED;

    if (p_port->rfc.state == RFC_STATE_CLOSED)
    {
        RFCOMM_TRACE_DEBUG ("rfc_port_closed DONE");
        if (p_port->rfc.p_mcb)
        {
            p_port->rfc.p_mcb->port_inx[p_port->dlci] = 0;

            /* If there are no more ports opened on this MCB release it */
            rfc_check_mcb_active (p_port->rfc.p_mcb);
        }
        rfc_port_timer_stop (p_port);
        RFCOMM_TRACE_DEBUG ("port_release_port:p_port->keep_port_handle:%d", p_port->keep_port_handle);
        if( p_port->keep_port_handle )
        {
            RFCOMM_TRACE_DEBUG ("port_release_port:Initialize handle:%d", p_port->inx);
            /* save event mask and callback */
            mask = p_port->ev_mask;
            p_port_cb = p_port->p_callback;
            user_port_pars = p_port->user_port_pars;

            port_set_defaults(p_port);
            /* restore */
            p_port->ev_mask         = mask;
            p_port->p_callback      = p_port_cb;
            p_port->user_port_pars  = user_port_pars;
            p_port->mtu             = p_port->keep_mtu;

            p_port->state           = PORT_STATE_OPENING;
            p_port->rfc.p_mcb       = NULL;
            if(p_port->is_server)
                p_port->dlci       &= 0xfe;

            p_port->local_ctrl.modem_signal = p_port->default_signal_state;
            memcpy (p_port->bd_addr, BT_BD_ANY, BD_ADDR_LEN);
        }
        else
        {
            RFCOMM_TRACE_DEBUG ("port_release_port:Clean-up handle:%d", p_port->inx);
            fixed_queue_free(p_port->tx.queue, NULL);
            fixed_queue_free(p_port->rx.queue, NULL);
            alarm_free(p_port->rfc.port_timer);
            memset (p_port, 0, sizeof (tPORT));
            p_port->rx.queue = fixed_queue_new(SIZE_MAX);
            p_port->tx.queue = fixed_queue_new(SIZE_MAX);
            p_port->rfc.port_timer = alarm_new("rfcomm_port.port_timer");
        }
    }
}


/*******************************************************************************
**
** Function         port_find_mcb
**
** Description      This function checks if connection exists to device with
**                  the BD_ADDR.
**
*******************************************************************************/
tRFC_MCB *port_find_mcb (BD_ADDR bd_addr)
{
    int      i;

    for (i = 0; i < MAX_BD_CONNECTIONS; i++)
    {
        if ((rfc_cb.port.rfc_mcb[i].state != RFC_MX_STATE_IDLE)
         && !memcmp (rfc_cb.port.rfc_mcb[i].bd_addr, bd_addr, BD_ADDR_LEN))
        {
            /* Multiplexer channel found do not change anything */
            RFCOMM_TRACE_DEBUG("port_find_mcb: found  bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
                                bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);
            RFCOMM_TRACE_DEBUG("port_find_mcb: rfc_cb.port.rfc_mcb:index:%d, %p, lcid:%d",
                                i, &rfc_cb.port.rfc_mcb[i], rfc_cb.port.rfc_mcb[i].lcid);
            return (&rfc_cb.port.rfc_mcb[i]);
        }
    }
    RFCOMM_TRACE_DEBUG("port_find_mcb: not found, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
                         bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);
    return (NULL);
}


/*******************************************************************************
**
** Function         port_find_mcb_dlci_port
**
** Description      Find port on the multiplexer channel based on DLCI.  If
**                  this port with DLCI not found try to use even DLCI.  This
**                  is for the case when client is establishing connection on
**                  none-initiator MCB.
**
** Returns          Pointer to the PORT or NULL if not found
**
*******************************************************************************/
tPORT *port_find_mcb_dlci_port (tRFC_MCB *p_mcb, UINT8 dlci)
{
    UINT8 inx;

    if (!p_mcb)
        return (NULL);

    if (dlci > RFCOMM_MAX_DLCI)
        return (NULL);

    inx = p_mcb->port_inx[dlci];
    if (inx == 0)
    {
        RFCOMM_TRACE_DEBUG("port_find_mcb_dlci_port: p_mcb:%p, port_inx[dlci:%d] is 0", p_mcb, dlci);
        return (NULL);
    }
    else
        return (&rfc_cb.port.port[inx - 1]);
}


/*******************************************************************************
**
** Function         port_find_dlci_port
**
** Description      Find port with DLCI not assigned to multiplexer channel
**
** Returns          Pointer to the PORT or NULL if not found
**
*******************************************************************************/
tPORT *port_find_dlci_port (UINT8 dlci)
{
    UINT16 i;
    tPORT  *p_port;

    for (i = 0; i < MAX_RFC_PORTS; i++)
    {
        p_port = &rfc_cb.port.port[i];

        if (p_port->in_use && (p_port->rfc.p_mcb == NULL))
        {
            if (p_port->dlci == dlci)
            {
                return (p_port);
            }
            else if ((dlci & 0x01) && (p_port->dlci == (dlci - 1)))
            {
                p_port->dlci++;
                return (p_port);
            }
        }
    }
    return (NULL);
}


/*******************************************************************************
**
** Function         port_find_port
**
** Description      Find port with DLCI, BD_ADDR
**
** Returns          Pointer to the PORT or NULL if not found
**
*******************************************************************************/
tPORT *port_find_port (UINT8 dlci, BD_ADDR bd_addr)
{
    UINT16 i;
    tPORT  *p_port;

    for (i = 0; i < MAX_RFC_PORTS; i++)
    {
        p_port = &rfc_cb.port.port[i];
        if (p_port->in_use
         && (p_port->dlci == dlci)
         && !memcmp (p_port->bd_addr, bd_addr, BD_ADDR_LEN))
        {
            return (p_port);
        }
    }
    return (NULL);
}


/*******************************************************************************
**
** Function         port_flow_control_user
**
** Description      Check the current user flow control and if necessary return
**                  events to be send to the user based on the user's specified
**                  flow control type.
**
** Returns          event mask to be returned to the application
**
*******************************************************************************/
UINT32 port_flow_control_user (tPORT *p_port)
{
    UINT32 event = 0;

    /* Flow control to the user can be caused by flow controlling by the peer */
    /* (FlowInd, or flow control by the peer RFCOMM (Fcon) or internally if */
    /* tx_queue is full */
    BOOLEAN fc = p_port->tx.peer_fc
              || !p_port->rfc.p_mcb
              || !p_port->rfc.p_mcb->peer_ready
              || (p_port->tx.queue_size > PORT_TX_HIGH_WM)
              || (fixed_queue_length(p_port->tx.queue) > PORT_TX_BUF_HIGH_WM);

    if (p_port->tx.user_fc == fc)
        return (0);

    p_port->tx.user_fc = fc;

    if (fc)
        event = PORT_EV_FC;
    else
        event = PORT_EV_FC | PORT_EV_FCS;

    return (event);
}


/*******************************************************************************
**
** Function         port_get_signal_changes
**
** Description      Check modem signals that has been changed
**
** Returns          event mask to be returned to the application
**
*******************************************************************************/
UINT32 port_get_signal_changes (tPORT *p_port, UINT8 old_signals, UINT8 signal)
{
    UINT8  changed_signals = (signal ^ old_signals);
    UINT32 events = 0;

    if (changed_signals & PORT_DTRDSR_ON)
    {
        events |= PORT_EV_DSR;

        if (signal & PORT_DTRDSR_ON)
            events |= PORT_EV_DSRS;
    }

    if (changed_signals & PORT_CTSRTS_ON)
    {
        events |= PORT_EV_CTS;

        if (signal & PORT_CTSRTS_ON)
            events |= PORT_EV_CTSS;
    }

    if (changed_signals & PORT_RING_ON)
        events |= PORT_EV_RING;

    if (changed_signals & PORT_DCD_ON)
    {
        events |= PORT_EV_RLSD;

        if (signal & PORT_DCD_ON)
            events |= PORT_EV_RLSDS;
    }

    return (p_port->ev_mask & events);
}

/*******************************************************************************
**
** Function         port_flow_control_peer
**
** Description      Send flow control messages to the peer for both enabling
**                  and disabling flow control, for both credit-based and
**                  TS 07.10 flow control mechanisms.
**
** Returns          nothing
**
*******************************************************************************/
void port_flow_control_peer(tPORT *p_port, BOOLEAN enable, UINT16 count)
{
    if (!p_port->rfc.p_mcb)
        return;

    /* If using credit based flow control */
    if (p_port->rfc.p_mcb->flow == PORT_FC_CREDIT)
    {
        /* if want to enable flow from peer */
        if (enable)
        {
            /* update rx credits */
            if (count > p_port->credit_rx)
            {
                p_port->credit_rx = 0;
            }
            else
            {
                p_port->credit_rx -= count;
            }

            /* If credit count is less than low credit watermark, and user */
            /* did not force flow control, send a credit update */
            /* There might be a special case when we just adjusted rx_max */
            if ((p_port->credit_rx <= p_port->credit_rx_low)
             && !p_port->rx.user_fc
             && (p_port->credit_rx_max > p_port->credit_rx))
            {
                rfc_send_credit(p_port->rfc.p_mcb, p_port->dlci,
                                (UINT8) (p_port->credit_rx_max - p_port->credit_rx));

                p_port->credit_rx = p_port->credit_rx_max;

                p_port->rx.peer_fc = FALSE;
            }
        }
        /* else want to disable flow from peer */
        else
        {
            /* if client registered data callback, just do what they want */
            if (p_port->p_data_callback || p_port->p_data_co_callback)
            {
                p_port->rx.peer_fc = TRUE;
            }
            /* if queue count reached credit rx max, set peer fc */
            else if (fixed_queue_length(p_port->rx.queue) >= p_port->credit_rx_max)
            {
                p_port->rx.peer_fc = TRUE;
            }
        }
    }
    /* else using TS 07.10 flow control */
    else
    {
        /* if want to enable flow from peer */
        if (enable)
        {
            /* If rfcomm suspended traffic from the peer based on the rx_queue_size */
            /* check if it can be resumed now */
            if (p_port->rx.peer_fc
             && (p_port->rx.queue_size < PORT_RX_LOW_WM)
             && (fixed_queue_length(p_port->rx.queue) < PORT_RX_BUF_LOW_WM))
            {
                p_port->rx.peer_fc = FALSE;

                /* If user did not force flow control allow traffic now */
                if (!p_port->rx.user_fc)
                    RFCOMM_FlowReq (p_port->rfc.p_mcb, p_port->dlci, TRUE);
            }
        }
        /* else want to disable flow from peer */
        else
        {
            /* if client registered data callback, just do what they want */
            if (p_port->p_data_callback || p_port->p_data_co_callback)
            {
                p_port->rx.peer_fc = TRUE;
                RFCOMM_FlowReq (p_port->rfc.p_mcb, p_port->dlci, FALSE);
            }
            /* Check the size of the rx queue.  If it exceeds certain */
            /* level and flow control has not been sent to the peer do it now */
            else if ( ((p_port->rx.queue_size > PORT_RX_HIGH_WM)
                     || (fixed_queue_length(p_port->rx.queue) > PORT_RX_BUF_HIGH_WM))
                     && !p_port->rx.peer_fc)
            {
                RFCOMM_TRACE_EVENT ("PORT_DataInd Data reached HW. Sending FC set.");

                p_port->rx.peer_fc = TRUE;
                RFCOMM_FlowReq (p_port->rfc.p_mcb, p_port->dlci, FALSE);
            }
        }
    }
}

