diff options
Diffstat (limited to 'stack/rfcomm/port_utils.c')
-rw-r--r-- | stack/rfcomm/port_utils.c | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/stack/rfcomm/port_utils.c b/stack/rfcomm/port_utils.c new file mode 100644 index 0000000..ae41682 --- /dev/null +++ b/stack/rfcomm/port_utils.c @@ -0,0 +1,582 @@ +/****************************************************************************** + * + * 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 "bt_target.h" +#include "gki.h" +#include "rfcdefs.h" +#include "port_api.h" +#include "port_int.h" +#include "rfc_int.h" +#include "l2cdefs.h" +#include "btm_int.h" +#include "btu.h" + +#include <cutils/log.h> +#define info(fmt, ...) ALOGI ("%s: " fmt,__FUNCTION__, ## __VA_ARGS__) +#define debug(fmt, ...) ALOGD ("%s: " fmt,__FUNCTION__, ## __VA_ARGS__) +#define error(fmt, ...) ALOGE ("## ERROR : %s: " fmt "##",__FUNCTION__, ## __VA_ARGS__) +#define asrt(s) if(!(s)) ALOGE ("## %s assert %s failed at line:%d ##",__FUNCTION__, #s, __LINE__) + + +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) + { + 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; + debug("rfc_cb.port.port[%d] allocated, last_port:%d", yy, rfc_cb.rfc.last_port); + 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) +{ + 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)); +} + +/******************************************************************************* +** +** 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_WARNING0 ("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_DEBUG1 ("port_select_mtu selected %d based on connection speed", p_port->mtu); + } + else + { + p_port->mtu = L2CAP_MTU_SIZE - RFCOMM_DATA_OVERHEAD; + RFCOMM_TRACE_DEBUG1 ("port_select_mtu selected %d based on l2cap PDU size", p_port->mtu); + } + } + } + else + { + RFCOMM_TRACE_DEBUG1 ("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_DEBUG3 ("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; + + PORT_SCHEDULE_LOCK; + debug("port_release_port, p_port:%p", p_port); + while ((p_buf = (BT_HDR *)GKI_dequeue (&p_port->rx.queue)) != NULL) + GKI_freebuf (p_buf); + + p_port->rx.queue_size = 0; + + while ((p_buf = (BT_HDR *)GKI_dequeue (&p_port->tx.queue)) != NULL) + GKI_freebuf (p_buf); + + p_port->tx.queue_size = 0; + + PORT_SCHEDULE_UNLOCK; + + p_port->state = PORT_STATE_CLOSED; + + if (p_port->rfc.state == RFC_STATE_CLOSED) + { + RFCOMM_TRACE_DEBUG0 ("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); + + if( p_port->keep_port_handle ) + { + RFCOMM_TRACE_DEBUG1 ("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_DEBUG1 ("port_release_port:Clean-up handle:%d", p_port->inx); + memset (p_port, 0, sizeof (tPORT)); + } + } +} + + +/******************************************************************************* +** +** 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 */ + return (&rfc_cb.port.rfc_mcb[i]); + } + } + 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) + 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) + || (p_port->tx.queue.count > 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 (p_port->rx.queue.count >= 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) + && (p_port->rx.queue.count < 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) + || (p_port->rx.queue.count > PORT_RX_BUF_HIGH_WM)) + && !p_port->rx.peer_fc) + { + RFCOMM_TRACE_EVENT0 ("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); + } + } + } +} + |