diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-03-28 14:32:49 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-03-28 14:32:49 +1100 |
| commit | ee29c25b29eaa4fac4e897442634b69ecc8d8125 (patch) | |
| tree | 8c5f1a140463f20f104316fa3492984e191154e9 /lib/bt/host/bluedroid/stack/l2cap | |
| parent | 239e6d89507a24c849385f4bfa93ac4ad58e5de5 (diff) | |
| download | tangara-fw-ee29c25b29eaa4fac4e897442634b69ecc8d8125.tar.gz | |
Fork ESP-IDF's bluetooth component
i want better sbc encoding, and no cla will stop me
Diffstat (limited to 'lib/bt/host/bluedroid/stack/l2cap')
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/include/l2c_int.h | 824 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_api.c | 2456 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_ble.c | 1707 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_csm.c | 1263 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_fcr.c | 2229 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_link.c | 1509 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_main.c | 1065 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_ucd.c | 1079 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2c_utils.c | 3731 | ||||
| -rw-r--r-- | lib/bt/host/bluedroid/stack/l2cap/l2cap_client.c | 462 |
10 files changed, 16325 insertions, 0 deletions
diff --git a/lib/bt/host/bluedroid/stack/l2cap/include/l2c_int.h b/lib/bt/host/bluedroid/stack/l2cap/include/l2c_int.h new file mode 100644 index 00000000..045fb10d --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/include/l2c_int.h @@ -0,0 +1,824 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains L2CAP internal definitions + * + ******************************************************************************/ +#ifndef L2C_INT_H +#define L2C_INT_H + +#include <stdbool.h> +#include "stack/btm_api.h" +#include "stack/l2c_api.h" +#include "stack/l2cdefs.h" +#include "osi/list.h" +#include "osi/fixed_queue.h" + +#define L2CAP_MIN_MTU 48 /* Minimum acceptable MTU is 48 bytes */ + +/* LE credit based L2CAP connection parameters */ +#define L2CAP_LE_MIN_MTU 23 +#define L2CAP_LE_MIN_MPS 23 +#define L2CAP_LE_MAX_MPS 65533 +#define L2CAP_LE_MIN_CREDIT 0 +#define L2CAP_LE_MAX_CREDIT 65535 +#define L2CAP_LE_DEFAULT_MTU 512 +#define L2CAP_LE_DEFAULT_MPS 23 +#define L2CAP_LE_DEFAULT_CREDIT 1 + + +/* Timeouts. Since L2CAP works off a 1-second list, all are in seconds. +*/ +#define L2CAP_LINK_ROLE_SWITCH_TOUT 10 /* 10 seconds */ +#define L2CAP_LINK_CONNECT_TOUT 60 /* 30 seconds */ +#define L2CAP_LINK_CONNECT_TOUT_EXT 120 /* 120 seconds */ +#define L2CAP_ECHO_RSP_TOUT 30 /* 30 seconds */ +#define L2CAP_LINK_FLOW_CONTROL_TOUT 2 /* 2 seconds */ +#define L2CAP_LINK_DISCONNECT_TOUT 45 /* 45 seconds */ + +#ifndef L2CAP_CHNL_CONNECT_TOUT /* BTIF needs to override for internal project needs */ +#define L2CAP_CHNL_CONNECT_TOUT 60 /* 60 seconds */ +#endif + +#define L2CAP_CHNL_CONNECT_TOUT_EXT 120 /* 120 seconds */ +#define L2CAP_CHNL_CFG_TIMEOUT 30 /* 30 seconds */ +#define L2CAP_CHNL_DISCONNECT_TOUT 10 /* 10 seconds */ +#define L2CAP_DELAY_CHECK_SM4 2 /* 2 seconds */ +#define L2CAP_WAIT_INFO_RSP_TOUT 3 /* 3 seconds */ +#define L2CAP_WAIT_UNPARK_TOUT 2 /* 2 seconds */ +#define L2CAP_LINK_INFO_RESP_TOUT 2 /* 2 seconds */ +#define L2CAP_UPDATE_CONN_PARAM_TOUT 6 /* 6 seconds */ +#define L2CAP_BLE_LINK_CONNECT_TOUT BLE_ESTABLISH_LINK_CONNECTION_TIMEOUT // configed in menuconfig +#define L2CAP_BLE_CONN_PARAM_UPD_TOUT 30 /* 30 seconds */ + +/* quick timer uses millisecond unit */ +#define L2CAP_DEFAULT_RETRANS_TOUT 2000 /* 2000 milliseconds */ +#define L2CAP_DEFAULT_MONITOR_TOUT 12000 /* 12000 milliseconds */ +#define L2CAP_FCR_ACK_TOUT 200 /* 200 milliseconds */ + +#define L2CAP_CACHE_ATT_ACL_NUM 10 + +/* Define the possible L2CAP channel states. The names of +** the states may seem a bit strange, but they are taken from +** the Bluetooth specification. +*/ +typedef enum { + CST_CLOSED, /* Channel is in clodes state */ + CST_ORIG_W4_SEC_COMP, /* Originator waits security clearence */ + CST_TERM_W4_SEC_COMP, /* Acceptor waits security clearence */ + CST_W4_L2CAP_CONNECT_RSP, /* Waiting for peer conenct response */ + CST_W4_L2CA_CONNECT_RSP, /* Waiting for upper layer connect rsp */ + CST_CONFIG, /* Negotiating configuration */ + CST_OPEN, /* Data transfer state */ + CST_W4_L2CAP_DISCONNECT_RSP, /* Waiting for peer disconnect rsp */ + CST_W4_L2CA_DISCONNECT_RSP /* Waiting for upper layer disc rsp */ +} tL2C_CHNL_STATE; + +/* Define the possible L2CAP link states +*/ +typedef enum { + LST_DISCONNECTED, + LST_CONNECT_HOLDING, + LST_CONNECTING_WAIT_SWITCH, + LST_CONNECTING, + LST_CONNECTED, + LST_DISCONNECTING +} tL2C_LINK_STATE; + + + +/* Define input events to the L2CAP link and channel state machines. The names +** of the events may seem a bit strange, but they are taken from +** the Bluetooth specification. +*/ +#define L2CEVT_LP_CONNECT_CFM 0 /* Lower layer connect confirm */ +#define L2CEVT_LP_CONNECT_CFM_NEG 1 /* Lower layer connect confirm (failed) */ +#define L2CEVT_LP_CONNECT_IND 2 /* Lower layer connect indication */ +#define L2CEVT_LP_DISCONNECT_IND 3 /* Lower layer disconnect indication */ +#define L2CEVT_LP_QOS_CFM 4 /* Lower layer QOS confirmation */ +#define L2CEVT_LP_QOS_CFM_NEG 5 /* Lower layer QOS confirmation (failed)*/ +#define L2CEVT_LP_QOS_VIOLATION_IND 6 /* Lower layer QOS violation indication */ + +#define L2CEVT_SEC_COMP 7 /* Security cleared successfully */ +#define L2CEVT_SEC_COMP_NEG 8 /* Security procedure failed */ + +#define L2CEVT_L2CAP_CONNECT_REQ 10 /* Peer connection request */ +#define L2CEVT_L2CAP_CONNECT_RSP 11 /* Peer connection response */ +#define L2CEVT_L2CAP_CONNECT_RSP_PND 12 /* Peer connection response pending */ +#define L2CEVT_L2CAP_CONNECT_RSP_NEG 13 /* Peer connection response (failed) */ +#define L2CEVT_L2CAP_CONFIG_REQ 14 /* Peer configuration request */ +#define L2CEVT_L2CAP_CONFIG_RSP 15 /* Peer configuration response */ +#define L2CEVT_L2CAP_CONFIG_RSP_NEG 16 /* Peer configuration response (failed) */ +#define L2CEVT_L2CAP_DISCONNECT_REQ 17 /* Peer disconnect request */ +#define L2CEVT_L2CAP_DISCONNECT_RSP 18 /* Peer disconnect response */ +#define L2CEVT_L2CAP_INFO_RSP 19 /* Peer information response */ +#define L2CEVT_L2CAP_DATA 20 /* Peer data */ + +#define L2CEVT_L2CA_CONNECT_REQ 21 /* Upper layer connect request */ +#define L2CEVT_L2CA_CONNECT_RSP 22 /* Upper layer connect response */ +#define L2CEVT_L2CA_CONNECT_RSP_NEG 23 /* Upper layer connect response (failed)*/ +#define L2CEVT_L2CA_CONFIG_REQ 24 /* Upper layer config request */ +#define L2CEVT_L2CA_CONFIG_RSP 25 /* Upper layer config response */ +#define L2CEVT_L2CA_CONFIG_RSP_NEG 26 /* Upper layer config response (failed) */ +#define L2CEVT_L2CA_DISCONNECT_REQ 27 /* Upper layer disconnect request */ +#define L2CEVT_L2CA_DISCONNECT_RSP 28 /* Upper layer disconnect response */ +#define L2CEVT_L2CA_DATA_READ 29 /* Upper layer data read */ +#define L2CEVT_L2CA_DATA_WRITE 30 /* Upper layer data write */ +#define L2CEVT_L2CA_FLUSH_REQ 31 /* Upper layer flush */ + +#define L2CEVT_TIMEOUT 32 /* Timeout */ +#define L2CEVT_SEC_RE_SEND_CMD 33 /* btm_sec has enough info to proceed */ + +#define L2CEVT_ACK_TIMEOUT 34 /* RR delay timeout */ + + +/* Bitmask to skip over Broadcom feature reserved (ID) to avoid sending two + successive ID values, '0' id only or both */ +#define L2CAP_ADJ_BRCM_ID 0x1 +#define L2CAP_ADJ_ZERO_ID 0x2 +#define L2CAP_ADJ_ID 0x3 + +/* Return values for l2cu_process_peer_cfg_req() */ +#define L2CAP_PEER_CFG_UNACCEPTABLE 0 +#define L2CAP_PEER_CFG_OK 1 +#define L2CAP_PEER_CFG_DISCONNECT 2 + +/* eL2CAP option constants */ +#define L2CAP_MIN_RETRANS_TOUT 2000 /* Min retransmission timeout if no flush timeout or PBF */ +#define L2CAP_MIN_MONITOR_TOUT 12000 /* Min monitor timeout if no flush timeout or PBF */ + +#define L2CAP_MAX_FCR_CFG_TRIES 2 /* Config attempts before disconnecting */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +typedef uint8_t tL2C_BLE_FIXED_CHNLS_MASK; + +typedef struct { + UINT8 next_tx_seq; /* Next sequence number to be Tx'ed */ + UINT8 last_rx_ack; /* Last sequence number ack'ed by the peer */ + UINT8 next_seq_expected; /* Next peer sequence number expected */ + UINT8 last_ack_sent; /* Last peer sequence number ack'ed */ + UINT8 num_tries; /* Number of retries to send a packet */ + UINT8 max_held_acks; /* Max acks we can hold before sending */ + + BOOLEAN remote_busy; /* TRUE if peer has flowed us off */ + BOOLEAN local_busy; /* TRUE if we have flowed off the peer */ + + BOOLEAN rej_sent; /* Reject was sent */ + BOOLEAN srej_sent; /* Selective Reject was sent */ + BOOLEAN wait_ack; /* Transmitter is waiting ack (poll sent) */ + BOOLEAN rej_after_srej; /* Send a REJ when SREJ clears */ + + BOOLEAN send_f_rsp; /* We need to send an F-bit response */ + + UINT16 rx_sdu_len; /* Length of the SDU being received */ + BT_HDR *p_rx_sdu; /* Buffer holding the SDU being received */ + fixed_queue_t *waiting_for_ack_q; /* Buffers sent and waiting for peer to ack */ + fixed_queue_t *srej_rcv_hold_q; /* Buffers rcvd but held pending SREJ rsp */ + fixed_queue_t *retrans_q; /* Buffers being retransmitted */ + + TIMER_LIST_ENT ack_timer; /* Timer delaying RR */ + TIMER_LIST_ENT mon_retrans_timer; /* Timer Monitor or Retransmission */ + +#if (L2CAP_ERTM_STATS == TRUE) + UINT32 connect_tick_count; /* Time channel was established */ + UINT32 ertm_pkt_counts[2]; /* Packets sent and received */ + UINT32 ertm_byte_counts[2]; /* Bytes sent and received */ + UINT32 s_frames_sent[4]; /* S-frames sent (RR, REJ, RNR, SREJ) */ + UINT32 s_frames_rcvd[4]; /* S-frames rcvd (RR, REJ, RNR, SREJ) */ + UINT32 xmit_window_closed; /* # of times the xmit window was closed */ + UINT32 controller_idle; /* # of times less than 2 packets in controller */ + /* when the xmit window was closed */ + UINT32 pkts_retransmitted; /* # of packets that were retransmitted */ + UINT32 retrans_touts; /* # of retransmission timouts */ + UINT32 xmit_ack_touts; /* # of xmit ack timouts */ + +#define L2CAP_ERTM_STATS_NUM_AVG 10 +#define L2CAP_ERTM_STATS_AVG_NUM_SAMPLES 100 + UINT32 ack_delay_avg_count; + UINT32 ack_delay_avg_index; + UINT32 throughput_start; + UINT32 throughput[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_avg[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_min[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_max[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_avg[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_min[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_max[L2CAP_ERTM_STATS_NUM_AVG]; +#endif +} tL2C_FCRB; + + +/* Define a registration control block. Every application (e.g. RFCOMM, SDP, +** TCS etc) that registers with L2CAP is assigned one of these. +*/ +#if (L2CAP_UCD_INCLUDED == TRUE) +#define L2C_UCD_RCB_ID 0x00 +#define L2C_UCD_STATE_UNUSED 0x00 +#define L2C_UCD_STATE_W4_DATA 0x01 +#define L2C_UCD_STATE_W4_RECEPTION 0x02 +#define L2C_UCD_STATE_W4_MTU 0x04 + +typedef struct { + UINT8 state; + tL2CAP_UCD_CB_INFO cb_info; +} tL2C_UCD_REG; +#endif + +typedef struct { + BOOLEAN in_use; + UINT16 psm; + UINT16 real_psm; /* This may be a dummy RCB for an o/b connection but */ + /* this is the real PSM that we need to connect to */ +#if (L2CAP_UCD_INCLUDED == TRUE) + tL2C_UCD_REG ucd; +#endif + + tL2CAP_APPL_INFO api; +} tL2C_RCB; + +typedef void (tL2CAP_SEC_CBACK) (BD_ADDR bd_addr, tBT_TRANSPORT transport, + void *p_ref_data, tBTM_STATUS result); + +typedef struct +{ + UINT16 psm; + tBT_TRANSPORT transport; + BOOLEAN is_originator; + tL2CAP_SEC_CBACK *p_callback; + void *p_ref_data; +}tL2CAP_SEC_DATA; + +#ifndef L2CAP_CBB_DEFAULT_DATA_RATE_BUFF_QUOTA +#define L2CAP_CBB_DEFAULT_DATA_RATE_BUFF_QUOTA 10 +#endif +/* Define a channel control block (CCB). There may be many channel control blocks +** between the same two Bluetooth devices (i.e. on the same link). +** Each CCB has unique local and remote CIDs. All channel control blocks on +** the same physical link and are chained together. +*/ +typedef struct t_l2c_ccb { + BOOLEAN in_use; /* TRUE when in use, FALSE when not */ + tL2C_CHNL_STATE chnl_state; /* Channel state */ + tL2CAP_LE_CFG_INFO local_conn_cfg; /* Our config for ble conn oriented channel */ + tL2CAP_LE_CFG_INFO peer_conn_cfg; /* Peer device config ble conn oriented channel */ + + struct t_l2c_ccb *p_next_ccb; /* Next CCB in the chain */ + struct t_l2c_ccb *p_prev_ccb; /* Previous CCB in the chain */ + struct t_l2c_linkcb *p_lcb; /* Link this CCB is assigned to */ + + UINT16 local_cid; /* Local CID */ + UINT16 remote_cid; /* Remote CID */ + + TIMER_LIST_ENT timer_entry; /* CCB Timer List Entry */ + + tL2C_RCB *p_rcb; /* Registration CB for this Channel */ + bool should_free_rcb; /* True if RCB was allocated on the heap */ + +#define IB_CFG_DONE 0x01 +#define OB_CFG_DONE 0x02 +#define RECONFIG_FLAG 0x04 /* True after initial configuration */ +#define CFG_DONE_MASK (IB_CFG_DONE | OB_CFG_DONE) + + UINT8 config_done; /* Configuration flag word */ + UINT8 local_id; /* Transaction ID for local trans */ + UINT8 remote_id; /* Transaction ID for local */ + +#define CCB_FLAG_NO_RETRY 0x01 /* no more retry */ +#define CCB_FLAG_SENT_PENDING 0x02 /* already sent pending response */ + UINT8 flags; + + tL2CAP_CFG_INFO our_cfg; /* Our saved configuration options */ + tL2CAP_CH_CFG_BITS peer_cfg_bits; /* Store what peer wants to configure */ + tL2CAP_CFG_INFO peer_cfg; /* Peer's saved configuration options */ + + fixed_queue_t *xmit_hold_q; /* Transmit data hold queue */ + BOOLEAN cong_sent; /* Set when congested status sent */ + UINT16 buff_quota; /* Buffer quota before sending congestion */ + + tL2CAP_CHNL_PRIORITY ccb_priority; /* Channel priority */ + tL2CAP_CHNL_DATA_RATE tx_data_rate; /* Channel Tx data rate */ + tL2CAP_CHNL_DATA_RATE rx_data_rate; /* Channel Rx data rate */ + + /* Fields used for eL2CAP */ + tL2CAP_ERTM_INFO ertm_info; + tL2C_FCRB fcrb; + UINT16 tx_mps; /* TX MPS adjusted based on current controller */ + UINT16 max_rx_mtu; + UINT8 fcr_cfg_tries; /* Max number of negotiation attempts */ + BOOLEAN peer_cfg_already_rejected; /* If mode rejected once, set to TRUE */ + BOOLEAN out_cfg_fcr_present; /* TRUE if cfg response shoulkd include fcr options */ + +#define L2CAP_CFG_FCS_OUR 0x01 /* Our desired config FCS option */ +#define L2CAP_CFG_FCS_PEER 0x02 /* Peer's desired config FCS option */ +#define L2CAP_BYPASS_FCS (L2CAP_CFG_FCS_OUR | L2CAP_CFG_FCS_PEER) + UINT8 bypass_fcs; + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + BOOLEAN is_flushable; /* TRUE if channel is flushable */ +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) || (L2CAP_UCD_INCLUDED == TRUE) + UINT16 fixed_chnl_idle_tout; /* Idle timeout to use for the fixed channel */ +#endif + UINT16 tx_data_len; +} tL2C_CCB; + +/*********************************************************************** +** Define a queue of linked CCBs. +*/ +typedef struct { + tL2C_CCB *p_first_ccb; /* The first channel in this queue */ + tL2C_CCB *p_last_ccb; /* The last channel in this queue */ +} tL2C_CCB_Q; + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + +/* Round-Robin service for the same priority channels */ +#define L2CAP_NUM_CHNL_PRIORITY 3 /* Total number of priority group (high, medium, low)*/ +#define L2CAP_CHNL_PRIORITY_WEIGHT 5 /* weight per priority for burst transmission quota */ +#define L2CAP_GET_PRIORITY_QUOTA(pri) ((L2CAP_NUM_CHNL_PRIORITY - (pri)) * L2CAP_CHNL_PRIORITY_WEIGHT) + +/* CCBs within the same LCB are served in round robin with priority */ +/* It will make sure that low priority channel (for example, HF signaling on RFCOMM) */ +/* can be sent to headset even if higher priority channel (for example, AV media channel) */ +/* is congested. */ + +typedef struct { + tL2C_CCB *p_serve_ccb; /* current serving ccb within priority group */ + tL2C_CCB *p_first_ccb; /* first ccb of priority group */ + UINT8 num_ccb; /* number of channels in priority group */ + UINT8 quota; /* burst transmission quota */ +} tL2C_RR_SERV; + +#endif /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + + +/* Define a link control block. There is one link control block between +** this device and any other device (i.e. BD ADDR). +*/ +typedef struct t_l2c_linkcb { + BOOLEAN in_use; /* TRUE when in use, FALSE when not */ + tL2C_LINK_STATE link_state; + BOOLEAN is_aux; /* This variable used for BLE 5.0 or higher version when do auxiliary connection */ + TIMER_LIST_ENT timer_entry; /* Timer list entry for timeout evt */ + UINT16 handle; /* The handle used with LM */ + + tL2C_CCB_Q ccb_queue; /* Queue of CCBs on this LCB */ + + tL2C_CCB *p_pending_ccb; /* ccb of waiting channel during link disconnect */ + TIMER_LIST_ENT info_timer_entry; /* Timer entry for info resp timeout evt */ + TIMER_LIST_ENT upda_con_timer; /* Timer entry for update connection parametr */ + BD_ADDR remote_bd_addr; /* The BD address of the remote */ + + UINT8 link_role; /* Master or slave */ + UINT8 id; + UINT8 cur_echo_id; /* Current id value for echo request */ + tL2CA_ECHO_RSP_CB *p_echo_rsp_cb; /* Echo response callback */ + UINT16 idle_timeout; /* Idle timeout */ + BOOLEAN is_bonding; /* True - link active only for bonding */ + + UINT16 link_flush_tout; /* Flush timeout used */ + + UINT16 link_xmit_quota; /* Num outstanding pkts allowed */ + UINT16 sent_not_acked; /* Num packets sent but not acked */ + + BOOLEAN partial_segment_being_sent; /* Set TRUE when a partial segment */ + /* is being sent. */ + BOOLEAN w4_info_rsp; /* TRUE when info request is active */ + UINT8 info_rx_bits; /* set 1 if received info type */ + UINT32 peer_ext_fea; /* Peer's extended features mask */ + list_t *link_xmit_data_q; /* Link transmit data buffer queue */ + + UINT8 peer_chnl_mask[L2CAP_FIXED_CHNL_ARRAY_SIZE]; +#if (L2CAP_UCD_INCLUDED == TRUE) + UINT16 ucd_mtu; /* peer MTU on UCD */ + fixed_queue_t *ucd_out_sec_pending_q; /* Security pending outgoing UCD packet */ + fixed_queue_t *ucd_in_sec_pending_q; /* Security pending incoming UCD packet */ +#endif + + BT_HDR *p_hcit_rcv_acl; /* Current HCIT ACL buf being rcvd */ + UINT16 idle_timeout_sv; /* Save current Idle timeout */ + UINT8 acl_priority; /* L2C_PRIORITY_NORMAL or L2C_PRIORITY_HIGH */ + tL2CA_NOCP_CB *p_nocp_cb; /* Num Cmpl pkts callback */ + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2C_CCB *p_fixed_ccbs[L2CAP_NUM_FIXED_CHNLS]; + UINT16 disc_reason; +#endif + + tBT_TRANSPORT transport; +#if (BLE_INCLUDED == TRUE) + tBLE_ADDR_TYPE open_addr_type; /* be set by open API */ + tBLE_ADDR_TYPE ble_addr_type; + UINT16 tx_data_len; /* tx data length used in data length extension */ + fixed_queue_t *le_sec_pending_q; /* LE coc channels waiting for security check completion */ + UINT8 sec_act; +#define L2C_BLE_CONN_UPDATE_DISABLE 0x1 /* disable update connection parameters */ +#define L2C_BLE_NEW_CONN_PARAM 0x2 /* new connection parameter to be set */ +#define L2C_BLE_UPDATE_PENDING 0x4 /* waiting for connection update finished */ +#define L2C_BLE_NOT_DEFAULT_PARAM 0x8 /* not using default connection parameters */ +#define L2C_BLE_UPDATE_PARAM_FULL 0x10 /* update connection parameters full, can not update */ + UINT8 conn_update_mask; + /* cache connection parameters that wait to update */ + UINT16 waiting_update_conn_min_interval; + UINT16 waiting_update_conn_max_interval; + UINT16 waiting_update_conn_latency; + UINT16 waiting_update_conn_timeout; + /* cache parameters that is being updated */ + UINT16 updating_conn_min_interval; + UINT16 updating_conn_max_interval; + bool updating_param_flag; + /* current connection parameters that current connection is using */ + UINT16 current_used_conn_interval; + UINT16 current_used_conn_latency; + UINT16 current_used_conn_timeout; + /* connection parameters update order: + waiting_update_conn_xx -> updating_conn_xx -> current_used_conn_xx + */ + /* create connection retry count*/ + UINT8 retry_create_con; + UINT32 start_time_s; +#endif + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* each priority group is limited burst transmission */ + /* round robin service for the same priority channels */ + tL2C_RR_SERV rr_serv[L2CAP_NUM_CHNL_PRIORITY]; + UINT8 rr_pri; /* current serving priority group */ +#endif + +} tL2C_LCB; + + +/* Define the L2CAP control structure +*/ +typedef struct { + UINT8 l2cap_trace_level; + UINT16 controller_xmit_window; /* Total ACL window for all links */ + + UINT16 round_robin_quota; /* Round-robin link quota */ + UINT16 round_robin_unacked; /* Round-robin unacked */ + BOOLEAN check_round_robin; /* Do a round robin check */ + + BOOLEAN is_cong_cback_context; + + list_t *p_lcb_pool; /* Link Control Block pool */ + list_t *p_ccb_pool; /* Channel Control Block pool */ + tL2C_RCB rcb_pool[MAX_L2CAP_CLIENTS]; /* Registration info pool */ + + + UINT8 desire_role; /* desire to be master/slave when accepting a connection */ + BOOLEAN disallow_switch; /* FALSE, to allow switch at create conn */ + UINT16 num_lm_acl_bufs; /* # of ACL buffers on controller */ + UINT16 idle_timeout; /* Idle timeout */ + + list_t *rcv_pending_q; /* Recv pending queue */ + TIMER_LIST_ENT rcv_hold_tle; /* Timer list entry for rcv hold */ + + tL2C_LCB *p_cur_hcit_lcb; /* Current HCI Transport buffer */ + UINT16 num_links_active; /* Number of links active */ + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + UINT16 non_flushable_pbf; /* L2CAP_PKT_START_NON_FLUSHABLE if controller supports */ + /* Otherwise, L2CAP_PKT_START */ + BOOLEAN is_flush_active; /* TRUE if an HCI_Enhanced_Flush has been sent */ +#endif + +#if L2CAP_CONFORMANCE_TESTING == TRUE + UINT32 test_info_resp; /* Conformance testing needs a dynamic response */ +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2CAP_FIXED_CHNL_REG fixed_reg[L2CAP_NUM_FIXED_CHNLS]; /* Reg info for fixed channels */ +#endif + +#if (BLE_INCLUDED == TRUE) + UINT16 num_ble_links_active; /* Number of LE links active */ + BOOLEAN is_ble_connecting; + BD_ADDR ble_connecting_bda; + UINT16 controller_le_xmit_window; /* Total ACL window for all links */ + tL2C_BLE_FIXED_CHNLS_MASK l2c_ble_fixed_chnls_mask; // LE fixed channels mask + UINT16 num_lm_ble_bufs; /* # of ACL buffers on controller */ + UINT16 ble_round_robin_quota; /* Round-robin link quota */ + UINT16 ble_round_robin_unacked; /* Round-robin unacked */ + BOOLEAN ble_check_round_robin; /* Do a round robin check */ + tL2C_RCB ble_rcb_pool[BLE_MAX_L2CAP_CLIENTS]; /* Registration info pool */ +#endif + + tL2CA_ECHO_DATA_CB *p_echo_data_cb; /* Echo data callback */ + +#if (defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) && (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE)) + UINT16 high_pri_min_xmit_quota; /* Minimum number of ACL credit for high priority link */ +#endif /* (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE) */ + + UINT16 dyn_psm; +} tL2C_CB; + + + +/* Define a structure that contains the information about a connection. +** This structure is used to pass between functions, and not all the +** fields will always be filled in. +*/ +typedef struct { + BD_ADDR bd_addr; /* Remote BD address */ + UINT8 status; /* Connection status */ + UINT16 psm; /* PSM of the connection */ + UINT16 l2cap_result; /* L2CAP result */ + UINT16 l2cap_status; /* L2CAP status */ + UINT16 remote_cid; /* Remote CID */ +} tL2C_CONN_INFO; + + +typedef void (tL2C_FCR_MGMT_EVT_HDLR) (UINT8, tL2C_CCB *); + +/* The offset in a buffer that L2CAP will use when building commands. +*/ +#define L2CAP_SEND_CMD_OFFSET 0 + + +/* Number of ACL buffers to use for high priority channel +*/ +#if (!defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) || (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == FALSE)) +#define L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A (L2CAP_HIGH_PRI_MIN_XMIT_QUOTA) +#else +#define L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A (l2cb.high_pri_min_xmit_quota) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* L2CAP global data +************************************ +*/ +#if (!defined L2C_DYNAMIC_MEMORY) || (L2C_DYNAMIC_MEMORY == FALSE) +extern tL2C_CB l2cb; +#else +extern tL2C_CB *l2c_cb_ptr; +#define l2cb (*l2c_cb_ptr) +#endif + + +/* Functions provided by l2c_main.c +************************************ +*/ +void l2c_init(void); +void l2c_free(void); + +extern void l2c_process_timeout (TIMER_LIST_ENT *p_tle); +extern UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flag); +extern void l2c_rcv_acl_data (BT_HDR *p_msg); +extern void l2c_process_held_packets (BOOLEAN timed_out); + +/* Functions provided by l2c_utils.c +************************************ +*/ +extern tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPORT transport); +extern BOOLEAN l2cu_start_post_bond_timer (UINT16 handle); +extern void l2cu_release_lcb (tL2C_LCB *p_lcb); +extern tL2C_LCB *l2cu_find_lcb_by_bd_addr (BD_ADDR p_bd_addr, tBT_TRANSPORT transport); +extern tL2C_LCB *l2cu_find_lcb_by_handle (UINT16 handle); +extern uint8_t l2cu_plcb_active_count(void); +extern void l2cu_update_lcb_4_bonding (BD_ADDR p_bd_addr, BOOLEAN is_bonding); + +extern UINT8 l2cu_get_conn_role (tL2C_LCB *p_this_lcb); +extern BOOLEAN l2cu_set_acl_priority (BD_ADDR bd_addr, UINT8 priority, BOOLEAN reset_after_rs); + +extern void l2cu_enqueue_ccb (tL2C_CCB *p_ccb); +extern void l2cu_dequeue_ccb (tL2C_CCB *p_ccb); +extern void l2cu_change_pri_ccb (tL2C_CCB *p_ccb, tL2CAP_CHNL_PRIORITY priority); + +extern tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid); +extern void l2cu_release_ccb (tL2C_CCB *p_ccb); +extern tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid); +extern tL2C_CCB *l2cu_find_free_ccb(void); +extern tL2C_CCB *l2cu_find_ccb_by_remote_cid (tL2C_LCB *p_lcb, UINT16 remote_cid); +extern void l2cu_adj_id (tL2C_LCB *p_lcb, UINT8 adj_mask); +extern BOOLEAN l2c_is_cmd_rejected (UINT8 cmd_code, UINT8 id, tL2C_LCB *p_lcb); + +extern void l2cu_send_peer_cmd_reject (tL2C_LCB *p_lcb, UINT16 reason, + UINT8 rem_id, UINT16 p1, UINT16 p2); +extern void l2cu_send_peer_connect_req (tL2C_CCB *p_ccb); +extern void l2cu_send_peer_connect_rsp (tL2C_CCB *p_ccb, UINT16 result, UINT16 status); +extern void l2cu_send_peer_config_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_send_peer_config_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, UINT16 rej_len); +extern void l2cu_send_peer_disc_req (tL2C_CCB *p_ccb); +extern void l2cu_send_peer_disc_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 local_cid, UINT16 remote_cid); +extern void l2cu_send_peer_echo_req (tL2C_LCB *p_lcb, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_peer_echo_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_peer_info_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT16 info_type); +extern void l2cu_reject_connection (tL2C_LCB *p_lcb, UINT16 remote_cid, UINT8 rem_id, UINT16 result); +extern void l2cu_send_peer_info_req (tL2C_LCB *p_lcb, UINT16 info_type); +extern void l2cu_set_acl_hci_header (BT_HDR *p_buf, tL2C_CCB *p_ccb); +extern void l2cu_check_channel_congestion (tL2C_CCB *p_ccb); +extern void l2cu_disconnect_chnl (tL2C_CCB *p_ccb); + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) +extern void l2cu_set_non_flushable_pbf(BOOLEAN); +#endif + +#if (BLE_INCLUDED == TRUE) +extern void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout); +extern void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id); +#endif + +extern BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr); +extern void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb); +extern void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb); + +/* Functions provided by l2c_ucd.c +************************************ +*/ +#if (L2CAP_UCD_INCLUDED == TRUE) +void l2c_ucd_delete_sec_pending_q(tL2C_LCB *p_lcb); +void l2c_ucd_enqueue_pending_out_sec_q(tL2C_CCB *p_ccb, void *p_data); +BOOLEAN l2c_ucd_check_pending_info_req(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_pending_out_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_send_pending_out_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_discard_pending_out_sec_q(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_pending_in_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_send_pending_in_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_discard_pending_in_sec_q(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_rx_pkts(tL2C_LCB *p_lcb, BT_HDR *p_msg); +BOOLEAN l2c_ucd_process_event(tL2C_CCB *p_ccb, UINT16 event, void *p_data); +#endif + +#if (BLE_INCLUDED == TRUE) +extern void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout); +extern void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id); +extern void l2cu_reject_ble_connection (tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result); +extern void l2cu_send_peer_ble_credit_based_conn_res (tL2C_CCB *p_ccb, UINT16 result); +extern void l2cu_send_peer_ble_credit_based_conn_req (tL2C_CCB *p_ccb); +extern void l2cu_send_peer_ble_flow_control_credit(tL2C_CCB *p_ccb, UINT16 credit_value); +extern void l2cu_send_peer_ble_credit_based_disconn_req(tL2C_CCB *p_ccb); + +#endif + +extern BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr); +extern void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb); +extern void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb); + + +/* Functions provided for Broadcom Aware +**************************************** +*/ +extern BOOLEAN l2cu_check_feature_req (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_check_feature_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_feature_req (tL2C_CCB *p_ccb); + +extern tL2C_RCB *l2cu_allocate_rcb (UINT16 psm); +extern tL2C_RCB *l2cu_find_rcb_by_psm (UINT16 psm); +extern void l2cu_release_rcb (tL2C_RCB *p_rcb); +extern tL2C_RCB *l2cu_allocate_ble_rcb (UINT16 psm); +extern tL2C_RCB *l2cu_find_ble_rcb_by_psm (UINT16 psm); + +#if (L2CAP_COC_INCLUDED == TRUE) +extern UINT8 l2cu_process_peer_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_peer_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_our_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_our_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +#endif // (L2CAP_COC_INCLUDED == TRUE) + +extern void l2cu_device_reset (void); +extern tL2C_LCB *l2cu_find_lcb_by_state (tL2C_LINK_STATE state); +extern BOOLEAN l2cu_lcb_disconnecting (void); + +extern BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb, tBT_TRANSPORT transport); +extern BOOLEAN l2cu_create_conn_after_switch (tL2C_LCB *p_lcb); +extern BT_HDR *l2cu_get_next_buffer_to_send (tL2C_LCB *p_lcb); +extern void l2cu_resubmit_pending_sec_req (BD_ADDR p_bda); +extern void l2cu_initialize_amp_ccb (tL2C_LCB *p_lcb); +extern void l2cu_adjust_out_mps (tL2C_CCB *p_ccb); + +/* Functions provided by l2c_link.c +************************************ +*/ +extern BOOLEAN l2c_link_hci_conn_req (BD_ADDR bd_addr); +extern BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda); +extern BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason); +extern BOOLEAN l2c_link_hci_qos_violation (UINT16 handle); +extern void l2c_link_timeout (tL2C_LCB *p_lcb); +extern void l2c_info_timeout (tL2C_LCB *p_lcb); +extern void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf); +extern void l2c_link_adjust_allocation (void); +extern void l2c_link_process_num_completed_pkts (UINT8 *p); +extern void l2c_link_process_num_completed_blocks (UINT8 controller_id, UINT8 *p, UINT16 evt_len); +extern void l2c_link_processs_num_bufs (UINT16 num_lm_acl_bufs); +extern UINT8 l2c_link_pkts_rcvd (UINT16 *num_pkts, UINT16 *handles); +extern void l2c_link_role_changed (BD_ADDR bd_addr, UINT8 new_role, UINT8 hci_status); +extern void l2c_link_sec_comp (BD_ADDR p_bda, tBT_TRANSPORT transport, void *p_ref_data, UINT8 status); +extern void l2c_link_segments_xmitted (BT_HDR *p_msg); +extern void l2c_pin_code_request (BD_ADDR bd_addr); +extern void l2c_link_adjust_chnl_allocation (void); + +#if (BLE_INCLUDED == TRUE) +extern void l2c_link_processs_ble_num_bufs (UINT16 num_lm_acl_bufs); +#endif + +#if L2CAP_WAKE_PARKED_LINK == TRUE +extern BOOLEAN l2c_link_check_power_mode ( tL2C_LCB *p_lcb ); +#define L2C_LINK_CHECK_POWER_MODE(x) l2c_link_check_power_mode ((x)) +#else // L2CAP_WAKE_PARKED_LINK +#define L2C_LINK_CHECK_POWER_MODE(x) (FALSE) +#endif // L2CAP_WAKE_PARKED_LINK + +#if L2CAP_CONFORMANCE_TESTING == TRUE +/* Used only for conformance testing */ +extern void l2cu_set_info_rsp_mask (UINT32 mask); +#endif + +/* Functions provided by l2c_csm.c +************************************ +*/ +#if (L2CAP_COC_INCLUDED == TRUE) +extern void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +#endif +extern void l2c_enqueue_peer_data (tL2C_CCB *p_ccb, BT_HDR *p_buf); + +/* Functions provided by l2c_fcr.c +************************************ +*/ +extern void l2c_fcr_cleanup (tL2C_CCB *p_ccb); +extern void l2c_fcr_proc_pdu (tL2C_CCB *p_ccb, BT_HDR *p_buf); +extern void l2c_fcr_proc_tout (tL2C_CCB *p_ccb); +extern void l2c_fcr_proc_ack_tout (tL2C_CCB *p_ccb); +extern void l2c_fcr_send_S_frame (tL2C_CCB *p_ccb, UINT16 function_code, UINT16 pf_bit); +extern BT_HDR *l2c_fcr_clone_buf (BT_HDR *p_buf, UINT16 new_offset, UINT16 no_of_bytes); +extern BOOLEAN l2c_fcr_is_flow_controlled (tL2C_CCB *p_ccb); +extern BT_HDR *l2c_fcr_get_next_xmit_sdu_seg (tL2C_CCB *p_ccb, UINT16 max_packet_length); +extern void l2c_fcr_start_timer (tL2C_CCB *p_ccb); + +/* Configuration negotiation */ +extern UINT8 l2c_fcr_chk_chan_modes (tL2C_CCB *p_ccb); +extern BOOLEAN l2c_fcr_adj_our_req_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2c_fcr_adj_our_rsp_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_peer_cfg); +extern BOOLEAN l2c_fcr_renegotiate_chan(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern UINT8 l2c_fcr_process_peer_cfg_req(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2c_fcr_adj_monitor_retran_timeout (tL2C_CCB *p_ccb); +extern void l2c_fcr_stop_timer (tL2C_CCB *p_ccb); +extern void l2c_fcr_free_timer (tL2C_CCB *p_ccb); +/* Functions provided by l2c_ble.c +************************************ +*/ +#if (BLE_INCLUDED == TRUE) +extern BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb); +extern void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); +extern void l2cble_conn_comp (UINT16 handle, UINT8 role, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout); +extern BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb); +extern void l2cble_notify_le_connection (BD_ADDR bda); +extern void l2c_ble_link_adjust_allocation (void); +extern void l2cble_process_conn_update_evt (UINT16 handle, UINT8 status, UINT16 conn_interval, + UINT16 conn_latency, UINT16 conn_timeout); +extern void l2cble_get_conn_param_format_err_from_contoller(UINT8 status, UINT16 handle); + +extern void l2cble_credit_based_conn_req (tL2C_CCB *p_ccb); +extern void l2cble_credit_based_conn_res (tL2C_CCB *p_ccb, UINT16 result); +extern void l2cble_send_peer_disc_req(tL2C_CCB *p_ccb); +extern void l2cble_send_flow_control_credit(tL2C_CCB *p_ccb, UINT16 credit_value); +extern BOOLEAN l2ble_sec_access_req(BD_ADDR bd_addr, UINT16 psm, BOOLEAN is_originator, tL2CAP_SEC_CBACK *p_callback, void *p_ref_data); + + +#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) +extern void l2cble_process_rc_param_request_evt(UINT16 handle, UINT16 int_min, UINT16 int_max, + UINT16 latency, UINT16 timeout); +#endif + +extern void l2cble_update_data_length(tL2C_LCB *p_lcb); +extern void l2cble_set_fixed_channel_tx_data_length(BD_ADDR remote_bda, UINT16 fix_cid, + UINT16 tx_mtu); +extern void l2c_send_update_conn_params_cb(tL2C_LCB *p_lcb, UINT8 status); +extern void l2cble_process_data_length_change_event(UINT16 handle, UINT16 tx_data_len, + UINT16 rx_data_len); +extern UINT32 CalConnectParamTimeout(tL2C_LCB *p_lcb); + +#endif +extern void l2cu_process_fixed_disc_cback (tL2C_LCB *p_lcb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_api.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_api.c new file mode 100644 index 00000000..fa01790d --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_api.c @@ -0,0 +1,2456 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains the L2CAP API code + * + ******************************************************************************/ + +//#define LOG_TAG "bt_l2cap" + +//#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "common/bt_trace.h" +#include "stack/bt_types.h" +#include "stack/hcidefs.h" +#include "stack/hcimsgs.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/btu.h" +#include "stack/btm_api.h" +#include "osi/allocator.h" +#include "gatt_int.h" +#if (CLASSIC_BT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function L2CA_Register +** +** Description Other layers call this function to register for L2CAP +** services. +** +** Returns PSM to use or zero if error. Typically, the PSM returned +** is the same as was passed in, but for an outgoing-only +** connection to a dynamic PSM, a "virtual" PSM is returned +** and should be used in the calls to L2CA_ConnectReq(), +** L2CA_ErtmConnectReq() and L2CA_Deregister() +** +*******************************************************************************/ +UINT16 L2CA_Register (UINT16 psm, tL2CAP_APPL_INFO *p_cb_info) +{ + tL2C_RCB *p_rcb; + UINT16 vpsm = psm; + + + /* Verify that the required callback info has been filled in + ** Note: Connection callbacks are required but not checked + ** for here because it is possible to be only a client + ** or only a server. + */ + if ((!p_cb_info->pL2CA_ConfigCfm_Cb) + || (!p_cb_info->pL2CA_ConfigInd_Cb) + || (!p_cb_info->pL2CA_DataInd_Cb) + || (!p_cb_info->pL2CA_DisconnectInd_Cb)) { + L2CAP_TRACE_ERROR ("L2CAP - no cb registering PSM: 0x%04x", psm); + return (0); + } + + /* Verify PSM is valid */ + if (L2C_INVALID_PSM(psm)) { + L2CAP_TRACE_ERROR ("L2CAP - invalid PSM value, PSM: 0x%04x", psm); + return (0); + } + + /* Check if this is a registration for an outgoing-only connection to */ + /* a dynamic PSM. If so, allocate a "virtual" PSM for the app to use. */ + if ( (psm >= 0x1001) && (p_cb_info->pL2CA_ConnectInd_Cb == NULL) ) { + for (vpsm = 0x1002; vpsm < 0x8000; vpsm += 2) { + if ((p_rcb = l2cu_find_rcb_by_psm (vpsm)) == NULL) { + break; + } + } + + //L2CAP_TRACE_API ("L2CA_Register - Real PSM: 0x%04x Virtual PSM: 0x%04x", psm, vpsm); + } + + /* If registration block already there, just overwrite it */ + if ((p_rcb = l2cu_find_rcb_by_psm (vpsm)) == NULL) { + if ((p_rcb = l2cu_allocate_rcb (vpsm)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no RCB available, PSM: 0x%04x vPSM: 0x%04x", psm, vpsm); + return (0); + } + } + + p_rcb->api = *p_cb_info; + p_rcb->real_psm = psm; + + return (vpsm); +} + + + +/******************************************************************************* +** +** Function L2CA_Deregister +** +** Description Other layers call this function to de-register for L2CAP +** services. +** +** Returns void +** +*******************************************************************************/ +void L2CA_Deregister (UINT16 psm) +{ + tL2C_RCB *p_rcb; + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + list_node_t *p_node = NULL; + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) != NULL) { + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use) { + if (((p_ccb = p_lcb->ccb_queue.p_first_ccb) == NULL) + || (p_lcb->link_state == LST_DISCONNECTING)) { + continue; + } + + if ((p_ccb->in_use) && + ((p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) || + (p_ccb->chnl_state == CST_W4_L2CA_DISCONNECT_RSP))) { + continue; + } + + if (p_ccb->p_rcb == p_rcb) { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + } + } + } + l2cu_release_rcb (p_rcb); + } else { + L2CAP_TRACE_WARNING ("L2CAP - PSM: 0x%04x not found for deregistration", psm); + } +} + +/******************************************************************************* +** +** Function L2CA_AllocatePSM +** +** Description Other layers call this function to find an unused PSM for L2CAP +** services. +** +** Returns PSM to use. +** +*******************************************************************************/ +UINT16 L2CA_AllocatePSM(void) +{ + BOOLEAN done = FALSE; + UINT16 psm = l2cb.dyn_psm; + + while (!done) { + psm += 2; + if (psm > 0xfeff) { + psm = 0x1001; + } else if (psm & 0x0100) { + /* the upper byte must be even */ + psm += 0x0100; + } + + /* if psm is in range of reserved BRCM Aware features */ + if ((BRCM_RESERVED_PSM_START <= psm) && (psm <= BRCM_RESERVED_PSM_END)) { + continue; + } + + /* make sure the newlly allocated psm is not used right now */ + if ((l2cu_find_rcb_by_psm (psm)) == NULL) { + done = TRUE; + } + } + l2cb.dyn_psm = psm; + + return (psm); +} + +/******************************************************************************* +** +** Function L2CA_ConnectReq +** +** Description Higher layers call this function to create an L2CAP connection. +** Note that the connection is not established at this time, but +** connection establishment gets started. The callback function +** will be invoked when connection establishes or fails. +** +** Returns the CID of the connection, or 0 if it failed to start +** +*******************************************************************************/ +UINT16 L2CA_ConnectReq (UINT16 psm, BD_ADDR p_bd_addr) +{ + return L2CA_ErtmConnectReq (psm, p_bd_addr, NULL); +} + +/******************************************************************************* +** +** Function L2CA_ErtmConnectReq +** +** Description Higher layers call this function to create an L2CAP connection. +** Note that the connection is not established at this time, but +** connection establishment gets started. The callback function +** will be invoked when connection establishes or fails. +** +** Parameters: PSM: L2CAP PSM for the connection +** BD address of the peer +** Enhaced retransmission mode configurations + +** Returns the CID of the connection, or 0 if it failed to start +** +*******************************************************************************/ +UINT16 L2CA_ErtmConnectReq (UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_ERTM_INFO *p_ertm_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + //counter_add("l2cap.conn.req", 1); + L2CAP_TRACE_API ("L2CA_ErtmConnectReq() PSM: 0x%04x BDA: %08x%04x p_ertm_info: %p allowed:0x%x preferred:%d", psm, + (p_bd_addr[0] << 24) + (p_bd_addr[1] << 16) + (p_bd_addr[2] << 8) + p_bd_addr[3], + (p_bd_addr[4] << 8) + p_bd_addr[5], p_ertm_info, + (p_ertm_info) ? p_ertm_info->allowed_modes : 0, + (p_ertm_info) ? p_ertm_info->preferred_mode : 0); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) { + L2CAP_TRACE_WARNING ("L2CAP connect req - BTU not ready"); + return (0); + } + /* Fail if the PSM is not registered */ + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no RCB for L2CA_conn_req, PSM: 0x%04x", psm); + return (0); + } + + /* First, see if we already have a link to the remote */ + /* assume all ERTM l2cap connection is going over BR/EDR for now */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + /* No link. Get an LCB and start link establishment */ + if ( ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE, BT_TRANSPORT_BR_EDR)) == NULL) + /* currently use BR/EDR for ERTM mode l2cap connection */ + || (l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR) == FALSE) ) { + L2CAP_TRACE_WARNING ("L2CAP - conn not started for PSM: 0x%04x p_lcb: %p", psm, p_lcb); + return (0); + } + } + + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_conn_req, PSM: 0x%04x", psm); + return (0); + } + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + if (p_ertm_info) { + p_ccb->ertm_info = *p_ertm_info; + + /* Replace default indicators with the actual default pool */ + if (p_ccb->ertm_info.fcr_rx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.fcr_rx_buf_size = L2CAP_FCR_RX_BUF_SIZE; + } + + if (p_ccb->ertm_info.fcr_tx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.fcr_tx_buf_size = L2CAP_FCR_TX_BUF_SIZE; + } + + if (p_ccb->ertm_info.user_rx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.user_rx_buf_size = L2CAP_USER_RX_BUF_SIZE; + } + + if (p_ccb->ertm_info.user_tx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.user_tx_buf_size = L2CAP_USER_TX_BUF_SIZE; + } + + p_ccb->max_rx_mtu = p_ertm_info->user_rx_buf_size - + (L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET + L2CAP_FCS_LEN); + } + + + + /* If link is up, start the L2CAP connection */ + if (p_lcb->link_state == LST_CONNECTED) { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_REQ, NULL); + } + + /* If link is disconnecting, save link info to retry after disconnect + * Possible Race condition when a reconnect occurs + * on the channel during a disconnect of link. This + * ccb will be automatically retried after link disconnect + * arrives + */ + else if (p_lcb->link_state == LST_DISCONNECTING) { + L2CAP_TRACE_DEBUG ("L2CAP API - link disconnecting: RETRY LATER"); + + /* Save ccb so it can be started after disconnect is finished */ + p_lcb->p_pending_ccb = p_ccb; + } + + L2CAP_TRACE_API ("L2CAP - L2CA_conn_req(psm: 0x%04x) returned CID: 0x%04x", psm, p_ccb->local_cid); + + /* Return the local CID as our handle */ + return (p_ccb->local_cid); +} + +bool L2CA_SetConnectionCallbacks(uint16_t local_cid, const tL2CAP_APPL_INFO *callbacks) +{ + assert(callbacks != NULL); + assert(callbacks->pL2CA_ConnectInd_Cb == NULL); + assert(callbacks->pL2CA_ConnectCfm_Cb != NULL); + assert(callbacks->pL2CA_ConfigInd_Cb != NULL); + assert(callbacks->pL2CA_ConfigCfm_Cb != NULL); + assert(callbacks->pL2CA_DisconnectInd_Cb != NULL); + assert(callbacks->pL2CA_DisconnectCfm_Cb != NULL); + assert(callbacks->pL2CA_CongestionStatus_Cb != NULL); + assert(callbacks->pL2CA_DataInd_Cb != NULL); + assert(callbacks->pL2CA_TxComplete_Cb != NULL); + + tL2C_CCB *channel_control_block = l2cu_find_ccb_by_cid(NULL, local_cid); + if (!channel_control_block) { + L2CAP_TRACE_ERROR("%s no channel control block found for L2CAP LCID=0x%04x.", __func__, local_cid); + return false; + } + + // We're making a connection-specific registration control block so we check if + // we already have a private one allocated to us on the heap. If not, we make a + // new allocation, mark it as heap-allocated, and inherit the fields from the old + // control block. + tL2C_RCB *registration_control_block = channel_control_block->p_rcb; + if (!channel_control_block->should_free_rcb) { + registration_control_block = (tL2C_RCB *)osi_calloc(sizeof(tL2C_RCB)); + if (!registration_control_block) { + L2CAP_TRACE_ERROR("%s unable to allocate registration control block.", __func__); + return false; + } + + *registration_control_block = *channel_control_block->p_rcb; + channel_control_block->p_rcb = registration_control_block; + channel_control_block->should_free_rcb = true; + } + + registration_control_block->api = *callbacks; + return true; +} + +/******************************************************************************* +** +** Function L2CA_ConnectRsp +** +** Description Higher layers call this function to accept an incoming +** L2CAP connection, for which they had gotten an connect +** indication callback. +** +** Returns TRUE for success, FALSE for failure +** +*******************************************************************************/ +BOOLEAN L2CA_ConnectRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, + UINT16 result, UINT16 status) +{ + return L2CA_ErtmConnectRsp (p_bd_addr, id, lcid, result, status, NULL); +} + + +/******************************************************************************* +** +** Function L2CA_ErtmConnectRsp +** +** Description Higher layers call this function to accept an incoming +** L2CAP connection, for which they had gotten an connect +** indication callback. +** +** Returns TRUE for success, FALSE for failure +** +*******************************************************************************/ +BOOLEAN L2CA_ErtmConnectRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result, + UINT16 status, tL2CAP_ERTM_INFO *p_ertm_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + //counter_add("l2cap.conn.rsp", 1); + L2CAP_TRACE_API ("L2CA_ErtmConnectRsp() CID: 0x%04x Result: %d Status: %d BDA: %08x%04x p_ertm_info:%p", + lcid, result, status, + (p_bd_addr[0] << 24) + (p_bd_addr[1] << 16) + (p_bd_addr[2] << 8) + p_bd_addr[3], + (p_bd_addr[4] << 8) + p_bd_addr[5], p_ertm_info); + + /* First, find the link control block */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + /* No link. Get an LCB and start link establishment */ + L2CAP_TRACE_WARNING ("L2CAP - no LCB for L2CA_conn_rsp"); + return (FALSE); + } + /* Now, find the channel control block */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_conn_rsp"); + return (FALSE); + } + + /* The IDs must match */ + if (p_ccb->remote_id != id) { + L2CAP_TRACE_WARNING ("L2CAP - bad id in L2CA_conn_rsp. Exp: %d Got: %d", p_ccb->remote_id, id); + return (FALSE); + } + + if (p_ertm_info) { + p_ccb->ertm_info = *p_ertm_info; + + /* Replace default indicators with the actual default pool */ + if (p_ccb->ertm_info.fcr_rx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.fcr_rx_buf_size = L2CAP_FCR_RX_BUF_SIZE; + } + + if (p_ccb->ertm_info.fcr_tx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.fcr_tx_buf_size = L2CAP_FCR_TX_BUF_SIZE; + } + + if (p_ccb->ertm_info.user_rx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.user_rx_buf_size = L2CAP_USER_RX_BUF_SIZE; + } + + if (p_ccb->ertm_info.user_tx_buf_size == L2CAP_INVALID_ERM_BUF_SIZE) { + p_ccb->ertm_info.user_tx_buf_size = L2CAP_USER_TX_BUF_SIZE; + } + + p_ccb->max_rx_mtu = p_ertm_info->user_rx_buf_size - + (L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET + L2CAP_FCS_LEN); + } + + if (result == L2CAP_CONN_OK) { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); + } else { + tL2C_CONN_INFO conn_info; + + conn_info.l2cap_result = result; + conn_info.l2cap_status = status; + + if (result == L2CAP_CONN_PENDING) { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, &conn_info); + } else { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP_NEG, &conn_info); + } + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_ConfigReq +** +** Description Higher layers call this function to send configuration. +** +** Note: The FCR options of p_cfg are not used. +** +** Returns TRUE if configuration sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_ConfigReq (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_CCB *p_ccb; + + //counter_add("l2cap.cfg.req", 1); + L2CAP_TRACE_API ("L2CA_ConfigReq() CID 0x%04x: fcr_present:%d (mode %d) mtu_present:%d (%d)", + cid, p_cfg->fcr_present, p_cfg->fcr.mode, p_cfg->mtu_present, p_cfg->mtu); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_cfg_req, CID: %d", cid); + return (FALSE); + } + + /* We need to have at least one mode type common with the peer */ + if (!l2c_fcr_adj_our_req_options(p_ccb, p_cfg)) { + return (FALSE); + } + + /* Don't adjust FCR options if not used */ + if ((!p_cfg->fcr_present) || (p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE)) { + /* FCR and FCS options are not used in basic mode */ + p_cfg->fcs_present = FALSE; + p_cfg->ext_flow_spec_present = FALSE; + + if ( (p_cfg->mtu_present) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) { + L2CAP_TRACE_WARNING ("L2CAP - adjust MTU: %u too large", p_cfg->mtu); + p_cfg->mtu = L2CAP_MTU_SIZE; + } + } + + /* Save the adjusted configuration in case it needs to be used for renegotiation */ + p_ccb->our_cfg = *p_cfg; + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_REQ, p_cfg); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_ConfigRsp +** +** Description Higher layers call this function to send a configuration +** response. +** +** Returns TRUE if configuration response sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_ConfigRsp (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_CCB *p_ccb; + + //counter_add("l2cap.cfg.rsp", 1); + L2CAP_TRACE_API ("L2CA_ConfigRsp() CID: 0x%04x Result: %d MTU present:%d Flush TO:%d FCR:%d FCS:%d", + cid, p_cfg->result, p_cfg->mtu_present, p_cfg->flush_to_present, p_cfg->fcr_present, p_cfg->fcs_present); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_cfg_rsp, CID: %d", cid); + return (FALSE); + } + + if ( (p_cfg->result == L2CAP_CFG_OK) || (p_cfg->result == L2CAP_CFG_PENDING) ) { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_RSP, p_cfg); + } else { + p_cfg->fcr_present = FALSE; /* FCR options already negotiated before this point */ + + /* Clear out any cached options that are being returned as an error (excluding FCR) */ + if (p_cfg->mtu_present) { + p_ccb->peer_cfg.mtu_present = FALSE; + } + if (p_cfg->flush_to_present) { + p_ccb->peer_cfg.flush_to_present = FALSE; + } + if (p_cfg->qos_present) { + p_ccb->peer_cfg.qos_present = FALSE; + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_RSP_NEG, p_cfg); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_DisconnectReq +** +** Description Higher layers call this function to disconnect a channel. +** +** Returns TRUE if disconnect sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_DisconnectReq (UINT16 cid) +{ + tL2C_CCB *p_ccb; + + //counter_add("l2cap.disconn.req", 1); + L2CAP_TRACE_API ("L2CA_DisconnectReq() CID: 0x%04x", cid); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_disc_req, CID: %d", cid); + return (FALSE); + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_DisconnectRsp +** +** Description Higher layers call this function to acknowledge the +** disconnection of a channel. +** +** Returns void +** +*******************************************************************************/ +BOOLEAN L2CA_DisconnectRsp (UINT16 cid) +{ + tL2C_CCB *p_ccb; + + //counter_add("l2cap.disconn.rsp", 1); + L2CAP_TRACE_API ("L2CA_DisconnectRsp() CID: 0x%04x", cid); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_disc_rsp, CID: %d", cid); + return (FALSE); + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_RSP, NULL); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_Ping +** +** Description Higher layers call this function to send an echo request. +** +** Returns TRUE if echo request sent, else FALSE. +** +*******************************************************************************/ +BOOLEAN L2CA_Ping (BD_ADDR p_bd_addr, tL2CA_ECHO_RSP_CB *p_callback) +{ + tL2C_LCB *p_lcb; + + L2CAP_TRACE_API ("L2CA_Ping() BDA: %02x-%02x-%02x-%02x-%02x-%02x", + p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) { + return (FALSE); + } + + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + /* No link. Get an LCB and start link establishment */ + if ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no LCB for L2CA_ping"); + return (FALSE); + } + if (l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR) == FALSE) { + return (FALSE); + } + + p_lcb->p_echo_rsp_cb = p_callback; + + return (TRUE); + } + + /* We only allow 1 ping outstanding at a time */ + if (p_lcb->p_echo_rsp_cb != NULL) { + L2CAP_TRACE_WARNING ("L2CAP - rejected second L2CA_ping"); + return (FALSE); + } + + /* Have a link control block. If link is disconnecting, tell user to retry later */ + if (p_lcb->link_state == LST_DISCONNECTING) { + L2CAP_TRACE_WARNING ("L2CAP - L2CA_ping rejected - link disconnecting"); + return (FALSE); + } + + /* Save address of callback */ + p_lcb->p_echo_rsp_cb = p_callback; + + if (p_lcb->link_state == LST_CONNECTED) { + l2cu_adj_id(p_lcb, L2CAP_ADJ_BRCM_ID); /* Make sure not using Broadcom ID */ + l2cu_send_peer_echo_req (p_lcb, NULL, 0); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_ECHO_RSP_TOUT); + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_Echo +** +** Description Higher layers call this function to send an echo request +** with application-specific data. +** +** Returns TRUE if echo request sent, else FALSE. +** +*******************************************************************************/ +BOOLEAN L2CA_Echo (BD_ADDR p_bd_addr, BT_HDR *p_data, tL2CA_ECHO_DATA_CB *p_callback) +{ + tL2C_LCB *p_lcb; + UINT8 *pp; + + L2CAP_TRACE_API ("L2CA_Echo() BDA: %08X%04X", + ((p_bd_addr[0] << 24) + (p_bd_addr[1] << 16) + (p_bd_addr[2] << 8) + (p_bd_addr[3])), + ((p_bd_addr[4] << 8) + (p_bd_addr[5]))); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) { + return (FALSE); + } + + if ((memcmp(BT_BD_ANY, p_bd_addr, BD_ADDR_LEN) == 0) && (p_data == NULL)) { + /* Only register callback without sending message. */ + l2cb.p_echo_data_cb = p_callback; + return TRUE; + } + + /* We assume the upper layer will call this function only when the link is established. */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_ERROR ("L2CA_Echo ERROR : link not established"); + return FALSE; + } + + if (p_lcb->link_state != LST_CONNECTED) { + L2CAP_TRACE_ERROR ("L2CA_Echo ERROR : link is not connected"); + return FALSE; + } + + /* Save address of callback */ + l2cb.p_echo_data_cb = p_callback; + + /* Set the pointer to the beginning of the data */ + pp = (UINT8 *)(p_data + 1) + p_data->offset; + l2cu_adj_id(p_lcb, L2CAP_ADJ_BRCM_ID); /* Make sure not using Broadcom ID */ + l2cu_send_peer_echo_req (p_lcb, pp, p_data->len); + + return (TRUE); + +} + +#endif ///CLASSIC_BT_INCLUDED == TRUE + + +bool L2CA_GetIdentifiers(uint16_t lcid, uint16_t *rcid, uint16_t *handle) +{ + tL2C_CCB *control_block = l2cu_find_ccb_by_cid(NULL, lcid); + if (!control_block) { + return false; + } + + if (rcid) { + *rcid = control_block->remote_cid; + } + if (handle) { + *handle = control_block->p_lcb->handle; + } + + return true; +} + +/******************************************************************************* +** +** Function L2CA_SetIdleTimeout +** +** Description Higher layers call this function to set the idle timeout for +** a connection, or for all future connections. The "idle timeout" +** is the amount of time that a connection can remain up with +** no L2CAP channels on it. A timeout of zero means that the +** connection will be torn down immediately when the last channel +** is removed. A timeout of 0xFFFF means no timeout. Values are +** in seconds. +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This timeout takes effect after at least 1 channel has been +** established and removed. L2CAP maintains its own timer from +** whan a connection is established till the first channel is +** set up. +*******************************************************************************/ +BOOLEAN L2CA_SetIdleTimeout (UINT16 cid, UINT16 timeout, BOOLEAN is_global) +{ + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + + if (is_global) { + l2cb.idle_timeout = timeout; + } else { + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_SetIdleTimeout, CID: %d", cid); + return (FALSE); + } + + p_lcb = p_ccb->p_lcb; + + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { + p_lcb->idle_timeout = timeout; + } else { + return (FALSE); + } + } + + return (TRUE); +} + + + +/******************************************************************************* +** +** Function L2CA_SetIdleTimeoutByBdAddr +** +** Description Higher layers call this function to set the idle timeout for +** a connection. The "idle timeout" is the amount of time that +** a connection can remain up with no L2CAP channels on it. +** A timeout of zero means that the connection will be torn +** down immediately when the last channel is removed. +** A timeout of 0xFFFF means no timeout. Values are in seconds. +** A bd_addr is the remote BD address. If bd_addr = BT_BD_ANY, +** then the idle timeouts for all active l2cap links will be +** changed. +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This timeout applies to all logical channels active on the +** ACL link. +*******************************************************************************/ +BOOLEAN L2CA_SetIdleTimeoutByBdAddr(BD_ADDR bd_addr, UINT16 timeout, tBT_TRANSPORT transport) +{ + tL2C_LCB *p_lcb; + list_node_t *p_node = NULL; + + if (memcmp (BT_BD_ANY, bd_addr, BD_ADDR_LEN)) { + p_lcb = l2cu_find_lcb_by_bd_addr( bd_addr, transport); + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { + p_lcb->idle_timeout = timeout; + + if (!p_lcb->ccb_queue.p_first_ccb) { + l2cu_no_dynamic_ccbs (p_lcb); + } + } else { + return FALSE; + } + } else { + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { + p_lcb->idle_timeout = timeout; + + if (!p_lcb->ccb_queue.p_first_ccb) { + l2cu_no_dynamic_ccbs (p_lcb); + } + } + } + } + + return TRUE; +} + + + +/******************************************************************************* +** +** Function L2CA_SetTraceLevel +** +** Description This function sets the trace level for L2CAP. If called with +** a value of 0xFF, it simply reads the current trace level. +** +** Returns the new (current) trace level +** +*******************************************************************************/ +UINT8 L2CA_SetTraceLevel (UINT8 new_level) +{ + if (new_level != 0xFF) { + l2cb.l2cap_trace_level = new_level; + } + + return (l2cb.l2cap_trace_level); +} + + +/******************************************************************************* +** +** Function L2CA_SetDesireRole +** +** Description This function sets the desire role for L2CAP. +** If the new role is L2CAP_ROLE_ALLOW_SWITCH, allow switch on +** HciCreateConnection. +** If the new role is L2CAP_ROLE_DISALLOW_SWITCH, do not allow switch on +** HciCreateConnection. +** +** If the new role is a valid role (HCI_ROLE_MASTER or HCI_ROLE_SLAVE), +** the desire role is set to the new value. Otherwise, it is not changed. +** +** Returns the new (current) role +** +*******************************************************************************/ +UINT8 L2CA_SetDesireRole (UINT8 new_role) +{ + L2CAP_TRACE_API ("L2CA_SetDesireRole() new:x%x, disallow_switch:%d", + new_role, l2cb.disallow_switch); + + if (L2CAP_ROLE_CHECK_SWITCH != (L2CAP_ROLE_CHECK_SWITCH & new_role)) { + /* do not process the allow_switch when both bits are set */ + if (new_role & L2CAP_ROLE_ALLOW_SWITCH) { + l2cb.disallow_switch = FALSE; + } + if (new_role & L2CAP_ROLE_DISALLOW_SWITCH) { + l2cb.disallow_switch = TRUE; + } + } + + if (new_role == HCI_ROLE_MASTER || new_role == HCI_ROLE_SLAVE) { + l2cb.desire_role = new_role; + } + + return (l2cb.desire_role); +} + +#if (CLASSIC_BT_INCLUDED == TRUE) + +/******************************************************************************* +** +** Function L2CA_LocalLoopbackReq +** +** Description This function sets up a CID for local loopback +** +** Returns CID of 0 if none. +** +*******************************************************************************/ +UINT16 L2CA_LocalLoopbackReq (UINT16 psm, UINT16 handle, BD_ADDR p_bd_addr) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API ("L2CA_LocalLoopbackReq() PSM: %d Handle: 0x%04x", psm, handle); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) { + L2CAP_TRACE_WARNING ("L2CAP loop req - BTU not ready"); + return (0); + } + + /* Fail if the PSM is not registered */ + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no RCB for L2CA_conn_req, PSM: %d", psm); + return (0); + } + + if ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no LCB for L2CA_conn_req"); + return (0); + } + + p_lcb->link_state = LST_CONNECTED; + p_lcb->handle = handle; + + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_conn_req"); + return (0); + } + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + p_ccb->chnl_state = CST_OPEN; + p_ccb->remote_cid = p_ccb->local_cid; + p_ccb->config_done = CFG_DONE_MASK; + + /* Return the local CID as our handle */ + return (p_ccb->local_cid); +} + +/******************************************************************************* +** +** Function L2CA_SetAclPriority +** +** Description Sets the transmission priority for a channel. +** (For initial implementation only two values are valid. +** L2CAP_PRIORITY_NORMAL and L2CAP_PRIORITY_HIGH). +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetAclPriority (BD_ADDR bd_addr, UINT8 priority) +{ + L2CAP_TRACE_API ("L2CA_SetAclPriority() bdaddr: %02x%02x%02x%02x%04x, priority:%d", + bd_addr[0], bd_addr[1], bd_addr[2], + bd_addr[3], (bd_addr[4] << 8) + bd_addr[5], priority); + + return (l2cu_set_acl_priority(bd_addr, priority, FALSE)); +} + +/******************************************************************************* +** +** Function L2CA_FlowControl +** +** Description Higher layers call this function to flow control a channel. +** +** data_enabled - TRUE data flows, FALSE data is stopped +** +** Returns TRUE if valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_FlowControl (UINT16 cid, BOOLEAN data_enabled) +{ + tL2C_CCB *p_ccb; + BOOLEAN on_off = !data_enabled; + + L2CAP_TRACE_API ("L2CA_FlowControl(%d) CID: 0x%04x", on_off, cid); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_FlowControl, CID: 0x%04x data_enabled: %d", cid, data_enabled); + return (FALSE); + } + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) { + L2CAP_TRACE_EVENT ("L2CA_FlowControl() invalid mode:%d", p_ccb->peer_cfg.fcr.mode); + return (FALSE); + } + if (p_ccb->fcrb.local_busy != on_off) { + p_ccb->fcrb.local_busy = on_off; + + if ( (p_ccb->chnl_state == CST_OPEN) && (!p_ccb->fcrb.wait_ack) ) { + if (on_off) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_P_BIT); + } + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SendTestSFrame +** +** Description Higher layers call this function to send a test S-frame. +** +** Returns TRUE if valid Channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SendTestSFrame (UINT16 cid, UINT8 sup_type, UINT8 back_track) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_SendTestSFrame() CID: 0x%04x Type: 0x%02x back_track: %u", cid, sup_type, back_track); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_SendTestSFrame, CID: %d", cid); + return (FALSE); + } + + if ( (p_ccb->chnl_state != CST_OPEN) || (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) ) { + return (FALSE); + } + + p_ccb->fcrb.next_seq_expected -= back_track; + + l2c_fcr_send_S_frame (p_ccb, (UINT16)(sup_type & 3), (UINT16)(sup_type & (L2CAP_FCR_P_BIT | L2CAP_FCR_F_BIT))); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_SetTxPriority +** +** Description Sets the transmission priority for a channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetTxPriority (UINT16 cid, tL2CAP_CHNL_PRIORITY priority) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_SetTxPriority() CID: 0x%04x, priority:%d", cid, priority); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_SetTxPriority, CID: %d", cid); + return (FALSE); + } + + /* it will update the order of CCB in LCB by priority and update round robin service variables */ + l2cu_change_pri_ccb (p_ccb, priority); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetChnlDataRate +** +** Description Sets the tx/rx data rate for a channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetChnlDataRate (UINT16 cid, tL2CAP_CHNL_DATA_RATE tx, tL2CAP_CHNL_DATA_RATE rx) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_SetChnlDataRate() CID: 0x%04x, tx:%d, rx:%d", cid, tx, rx); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_SetChnlDataRate, CID: %d", cid); + return (FALSE); + } + + p_ccb->tx_data_rate = tx; + p_ccb->rx_data_rate = rx; + + /* Adjust channel buffer allocation */ + l2c_link_adjust_chnl_allocation (); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetFlushTimeout +** +** Description This function set the automatic flush time out in Baseband +** for ACL-U packets. +** BdAddr : the remote BD address of ACL link. If it is BT_DB_ANY +** then the flush time out will be applied to all ACL link. +** FlushTimeout: flush time out in ms +** 0x0000 : No automatic flush +** L2CAP_NO_RETRANSMISSION : No retransmission +** 0x0002 - 0xFFFE : flush time out, if (flush_tout*8)+3/5) +** <= HCI_MAX_AUTO_FLUSH_TOUT (in 625us slot). +** Otherwise, return FALSE. +** L2CAP_NO_AUTOMATIC_FLUSH : No automatic flush +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This flush timeout applies to all logical channels active on the +** ACL link. +*******************************************************************************/ +BOOLEAN L2CA_SetFlushTimeout (BD_ADDR bd_addr, UINT16 flush_tout) +{ + tL2C_LCB *p_lcb; + UINT16 hci_flush_to; + UINT32 temp; + + /* no automatic flush (infinite timeout) */ + if (flush_tout == 0x0000) { + hci_flush_to = flush_tout; + flush_tout = L2CAP_NO_AUTOMATIC_FLUSH; + } + /* no retransmission */ + else if (flush_tout == L2CAP_NO_RETRANSMISSION) { + /* not mandatory range for controller */ + /* Packet is flushed before getting any ACK/NACK */ + /* To do this, flush timeout should be 1 baseband slot */ + hci_flush_to = flush_tout; + } + /* no automatic flush (infinite timeout) */ + else if (flush_tout == L2CAP_NO_AUTOMATIC_FLUSH) { + hci_flush_to = 0x0000; + } else { + /* convert L2CAP flush_to to 0.625 ms units, with round */ + temp = (((UINT32)flush_tout * 8) + 3) / 5; + + /* if L2CAP flush_to within range of HCI, set HCI flush timeout */ + if (temp > HCI_MAX_AUTO_FLUSH_TOUT) { + L2CAP_TRACE_WARNING("WARNING L2CA_SetFlushTimeout timeout(0x%x) is out of range", flush_tout); + return FALSE; + } else { + hci_flush_to = (UINT16)temp; + } + } + + if (memcmp (BT_BD_ANY, bd_addr, BD_ADDR_LEN)) { + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_BR_EDR); + + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { + if (p_lcb->link_flush_tout != flush_tout) { + p_lcb->link_flush_tout = flush_tout; + + L2CAP_TRACE_API ("L2CA_SetFlushTimeout 0x%04x ms for bd_addr [...;%02x%02x%02x]", + flush_tout, bd_addr[3], bd_addr[4], bd_addr[5]); + + if (!btsnd_hcic_write_auto_flush_tout (p_lcb->handle, hci_flush_to)) { + return (FALSE); + } + } + } else { + L2CAP_TRACE_WARNING ("WARNING L2CA_SetFlushTimeout No lcb for bd_addr [...;%02x%02x%02x]", + bd_addr[3], bd_addr[4], bd_addr[5]); + return (FALSE); + } + } else { + list_node_t *p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { + if (p_lcb->link_flush_tout != flush_tout) { + p_lcb->link_flush_tout = flush_tout; + + L2CAP_TRACE_API ("L2CA_SetFlushTimeout 0x%04x ms for bd_addr [...;%02x%02x%02x]", + flush_tout, p_lcb->remote_bd_addr[3], + p_lcb->remote_bd_addr[4], p_lcb->remote_bd_addr[5]); + + if (!btsnd_hcic_write_auto_flush_tout(p_lcb->handle, hci_flush_to)) { + return (FALSE); + } + } + } + } + } + + return (TRUE); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + + +/******************************************************************************* +** +** Function L2CA_GetPeerFeatures +** +** Description Get a peers features and fixed channel map +** +** Parameters: BD address of the peer +** Pointers to features and channel mask storage area +** +** Return value: TRUE if peer is connected +** +*******************************************************************************/ +BOOLEAN L2CA_GetPeerFeatures (BD_ADDR bd_addr, UINT32 *p_ext_feat, UINT8 *p_chnl_mask) +{ + tL2C_LCB *p_lcb; + + /* We must already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_WARNING ("L2CA_GetPeerFeatures() No BDA: %08x%04x", + (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3], + (bd_addr[4] << 8) + bd_addr[5]); + return (FALSE); + } + + L2CAP_TRACE_API ("L2CA_GetPeerFeatures() BDA: %08x%04x ExtFea: 0x%08x Chnl_Mask[0]: 0x%02x", + (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3], + (bd_addr[4] << 8) + bd_addr[5], p_lcb->peer_ext_fea, p_lcb->peer_chnl_mask[0]); + + *p_ext_feat = p_lcb->peer_ext_fea; + + memcpy (p_chnl_mask, p_lcb->peer_chnl_mask, L2CAP_FIXED_CHNL_ARRAY_SIZE); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_GetBDAddrbyHandle +** +** Description Get BD address for the given HCI handle +** +** Parameters: HCI handle +** BD address of the peer +** +** Return value: TRUE if found lcb for the given handle, FALSE otherwise +** +*******************************************************************************/ +BOOLEAN L2CA_GetBDAddrbyHandle (UINT16 handle, BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb = NULL; + BOOLEAN found_dev = FALSE; + + p_lcb = l2cu_find_lcb_by_handle (handle); + if (p_lcb) { + found_dev = TRUE; + memcpy (bd_addr, p_lcb->remote_bd_addr, BD_ADDR_LEN); + } + + return found_dev; +} + +#if (CLASSIC_BT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function L2CA_GetChnlFcrMode +** +** Description Get the channel FCR mode +** +** Parameters: Local CID +** +** Return value: Channel mode +** +*******************************************************************************/ +UINT8 L2CA_GetChnlFcrMode (UINT16 lcid) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid (NULL, lcid); + + if (p_ccb) { + L2CAP_TRACE_API ("L2CA_GetChnlFcrMode() returns mode %d", p_ccb->peer_cfg.fcr.mode); + return (p_ccb->peer_cfg.fcr.mode); + } + + L2CAP_TRACE_API ("L2CA_GetChnlFcrMode() returns mode L2CAP_FCR_BASIC_MODE"); + return (L2CAP_FCR_BASIC_MODE); +} + +#endif ///CLASSIC_BT_INCLUDED == TRUE + +#if (BLE_L2CAP_COC_INCLUDED == TRUE) +/******************************************************************************* +** +** Function L2CA_RegisterLECoc +** +** Description Other layers call this function to register for L2CAP +** Connection Oriented Channel. +** +** Returns PSM to use or zero if error. Typically, the PSM returned +** is the same as was passed in, but for an outgoing-only +** connection to a dynamic PSM, a "virtual" PSM is returned +** and should be used in the calls to L2CA_ConnectLECocReq() +** and L2CA_DeregisterLECoc() +** +*******************************************************************************/ +UINT16 L2CA_RegisterLECoc(UINT16 psm, tL2CAP_APPL_INFO *p_cb_info) +{ + L2CAP_TRACE_API("%s called for LE PSM: 0x%04x", __func__, psm); + + /* Verify that the required callback info has been filled in + ** Note: Connection callbacks are required but not checked + ** for here because it is possible to be only a client + ** or only a server. + */ + if ((!p_cb_info->pL2CA_DataInd_Cb) + || (!p_cb_info->pL2CA_DisconnectInd_Cb)) + { + L2CAP_TRACE_ERROR("%s No cb registering BLE PSM: 0x%04x", __func__, psm); + return 0; + } + + /* Verify PSM is valid */ + if (!L2C_IS_VALID_LE_PSM(psm)) + { + L2CAP_TRACE_ERROR("%s Invalid BLE PSM value, PSM: 0x%04x", __func__, psm); + return 0; + } + + tL2C_RCB *p_rcb; + UINT16 vpsm = psm; + + /* Check if this is a registration for an outgoing-only connection to */ + /* a dynamic PSM. If so, allocate a "virtual" PSM for the app to use. */ + if ((psm >= 0x0080) && (p_cb_info->pL2CA_ConnectInd_Cb == NULL)) + { + for (vpsm = 0x0080; vpsm < 0x0100; vpsm++) + { + p_rcb = l2cu_find_ble_rcb_by_psm(vpsm); + if (p_rcb == NULL) { + break; + } + } + + L2CAP_TRACE_API("%s Real PSM: 0x%04x Virtual PSM: 0x%04x", __func__, psm, vpsm); + } + + /* If registration block already there, just overwrite it */ + p_rcb = l2cu_find_ble_rcb_by_psm(vpsm); + if (p_rcb == NULL) + { + p_rcb = l2cu_allocate_ble_rcb(vpsm); + if (p_rcb == NULL) + { + L2CAP_TRACE_WARNING("%s No BLE RCB available, PSM: 0x%04x vPSM: 0x%04x", + __func__, psm, vpsm); + return 0; + } + } + + p_rcb->api = *p_cb_info; + p_rcb->real_psm = psm; + + return vpsm; +} + +/******************************************************************************* +** +** Function L2CA_DeregisterLECoc +** +** Description Other layers call this function to de-register for L2CAP +** Connection Oriented Channel. +** +** Returns void +** +*******************************************************************************/ +void L2CA_DeregisterLECoc(UINT16 psm) +{ + L2CAP_TRACE_API("%s called for PSM: 0x%04x", __func__, psm); + + tL2C_RCB *p_rcb = l2cu_find_ble_rcb_by_psm(psm); + if (p_rcb == NULL) + { + L2CAP_TRACE_WARNING("%s PSM: 0x%04x not found for deregistration", __func__, psm); + return; + } + + tL2C_LCB *p_lcb = NULL; + list_node_t *p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (!p_lcb->in_use || p_lcb->transport != BT_TRANSPORT_LE) { + continue; + } + + tL2C_CCB *p_ccb = p_lcb->ccb_queue.p_first_ccb; + if ((p_ccb == NULL) || (p_lcb->link_state == LST_DISCONNECTING)) { + continue; + } + + if (p_ccb->in_use && + (p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP || + p_ccb->chnl_state == CST_W4_L2CA_DISCONNECT_RSP)) { + continue; + } + + if (p_ccb->p_rcb == p_rcb) { + l2c_csm_execute(p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + } + } + + l2cu_release_rcb (p_rcb); +} + +/******************************************************************************* +** +** Function L2CA_ConnectLECocReq +** +** Description Higher layers call this function to create an L2CAP connection. +** Note that the connection is not established at this time, but +** connection establishment gets started. The callback function +** will be invoked when connection establishes or fails. +** +** Parameters: PSM: L2CAP PSM for the connection +** BD address of the peer +** Local Coc configurations + +** Returns the CID of the connection, or 0 if it failed to start +** +*******************************************************************************/ +UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p_cfg) +{ + L2CAP_TRACE_API("%s PSM: 0x%04x BDA: %02x:%02x:%02x:%02x:%02x:%02x", __func__, psm, + p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING("%s BTU not ready", __func__); + return 0; + } + + /* Fail if the PSM is not registered */ + tL2C_RCB *p_rcb = l2cu_find_ble_rcb_by_psm(psm); + if (p_rcb == NULL) + { + L2CAP_TRACE_WARNING("%s No BLE RCB, PSM: 0x%04x", __func__, psm); + return 0; + } + + /* First, see if we already have a le link to the remote */ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(p_bd_addr, BT_TRANSPORT_LE); + if (p_lcb == NULL) + { + /* No link. Get an LCB and start link establishment */ + p_lcb = l2cu_allocate_lcb(p_bd_addr, FALSE, BT_TRANSPORT_LE); + if ((p_lcb == NULL) + /* currently use BR/EDR for ERTM mode l2cap connection */ + || (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE) == FALSE) ) + { + L2CAP_TRACE_WARNING("%s conn not started for PSM: 0x%04x p_lcb: 0x%p", + __func__, psm, p_lcb); + return 0; + } + } + + /* Allocate a channel control block */ + tL2C_CCB *p_ccb = l2cu_allocate_ccb(p_lcb, 0); + if (p_ccb == NULL) + { + L2CAP_TRACE_WARNING("%s no CCB, PSM: 0x%04x", __func__, psm); + return 0; + } + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + /* Save the configuration */ + if (p_cfg) { + memcpy(&p_ccb->local_conn_cfg, p_cfg, sizeof(tL2CAP_LE_CFG_INFO)); + } + + /* If link is up, start the L2CAP connection */ + if (p_lcb->link_state == LST_CONNECTED) + { + if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) + { + L2CAP_TRACE_DEBUG("%s LE Link is up", __func__); + l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_REQ, NULL); + } + } + + /* If link is disconnecting, save link info to retry after disconnect + * Possible Race condition when a reconnect occurs + * on the channel during a disconnect of link. This + * ccb will be automatically retried after link disconnect + * arrives + */ + else if (p_lcb->link_state == LST_DISCONNECTING) + { + L2CAP_TRACE_DEBUG("%s link disconnecting: RETRY LATER", __func__); + + /* Save ccb so it can be started after disconnect is finished */ + p_lcb->p_pending_ccb = p_ccb; + } + + L2CAP_TRACE_API("%s(psm: 0x%04x) returned CID: 0x%04x", __func__, psm, p_ccb->local_cid); + + /* Return the local CID as our handle */ + return p_ccb->local_cid; +} + +/******************************************************************************* +** +** Function L2CA_ConnectLECocRsp +** +** Description Higher layers call this function to accept an incoming +** L2CAP COC connection, for which they had gotten an connect +** indication callback. +** +** Returns TRUE for success, FALSE for failure +** +*******************************************************************************/ +BOOLEAN L2CA_ConnectLECocRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result, + UINT16 status, tL2CAP_LE_CFG_INFO *p_cfg) +{ + L2CAP_TRACE_API("%s CID: 0x%04x Result: %d Status: %d BDA: %02x:%02x:%02x:%02x:%02x:%02x", + __func__, lcid, result, status, + p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]); + + + /* First, find the link control block */ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(p_bd_addr, BT_TRANSPORT_LE); + if (p_lcb == NULL) + { + /* No link. Get an LCB and start link establishment */ + L2CAP_TRACE_WARNING("%s no LCB", __func__); + return FALSE; + } + + /* Now, find the channel control block */ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid); + if (p_ccb == NULL) + { + L2CAP_TRACE_WARNING("%s no CCB", __func__); + return FALSE; + } + + /* The IDs must match */ + if (p_ccb->remote_id != id) + { + L2CAP_TRACE_WARNING("%s bad id. Expected: %d Got: %d", __func__, p_ccb->remote_id, id); + return FALSE; + } + + if (p_cfg) { + memcpy(&p_ccb->local_conn_cfg, p_cfg, sizeof(tL2CAP_LE_CFG_INFO)); + } + + if (result == L2CAP_CONN_OK) + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); + else + { + tL2C_CONN_INFO conn_info; + memcpy(conn_info.bd_addr, p_bd_addr, BD_ADDR_LEN); + conn_info.l2cap_result = result; + conn_info.l2cap_status = status; + l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP_NEG, &conn_info); + } + + return TRUE; +} + +/******************************************************************************* +** +** Function L2CA_GetPeerLECocConfig +** +** Description Get a peers configuration for LE Connection Oriented Channel. +** +** Parameters: local channel id +** Pointers to peers configuration storage area +** +** Return value: TRUE if peer is connected +** +*******************************************************************************/ +BOOLEAN L2CA_GetPeerLECocConfig (UINT16 lcid, tL2CAP_LE_CFG_INFO* peer_cfg) +{ + L2CAP_TRACE_API ("%s CID: 0x%04x", __func__, lcid); + + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL) + { + L2CAP_TRACE_ERROR("%s No CCB for CID:0x%04x", __func__, lcid); + return FALSE; + } + + if (peer_cfg != NULL) { + memcpy(peer_cfg, &p_ccb->peer_conn_cfg, sizeof(tL2CAP_LE_CFG_INFO)); + } + + return TRUE; +} +#endif // (BLE_L2CAP_COC_INCLUDED == TRUE) + +#if (L2CAP_NUM_FIXED_CHNLS > 0) +/******************************************************************************* +** +** Function L2CA_RegisterFixedChannel +** +** Description Register a fixed channel. +** +** Parameters: Fixed Channel # +** Channel Callbacks and config +** +** Return value: - +** +*******************************************************************************/ +BOOLEAN L2CA_RegisterFixedChannel (UINT16 fixed_cid, tL2CAP_FIXED_CHNL_REG *p_freg) +{ + L2CAP_TRACE_DEBUG ("L2CA_RegisterFixedChannel() CID: 0x%04x, %p", fixed_cid,p_freg); + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) ) { + L2CAP_TRACE_ERROR ("L2CA_RegisterFixedChannel() Invalid CID: 0x%04x", fixed_cid); + + return (FALSE); + } + + l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = *p_freg; + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_ConnectFixedChnl +** +** Description Connect an fixed signalling channel to a remote device. +** +** Parameters: Fixed CID +** BD Address of remote +** BD Address type +** +** Return value: TRUE if connection started +** +*******************************************************************************/ +BOOLEAN L2CA_ConnectFixedChnl (UINT16 fixed_cid, BD_ADDR rem_bda, tBLE_ADDR_TYPE bd_addr_type, BOOLEAN is_aux) +{ + tL2C_LCB *p_lcb; + tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; + + L2CAP_TRACE_API ("%s() CID: 0x%04x BDA: %08x%04x", __func__, fixed_cid, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + + // Check CID is valid and registered + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) { + L2CAP_TRACE_ERROR ("%s() Invalid CID: 0x%04x", __func__, fixed_cid); + return (FALSE); + } + + // Fail if BT is not yet up + if (!BTM_IsDeviceUp()) { + L2CAP_TRACE_WARNING ("%s(0x%04x) - BTU not ready", __func__, fixed_cid); + return (FALSE); + } + +#if BLE_INCLUDED == TRUE + if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID) { + transport = BT_TRANSPORT_LE; + } +#endif + + tL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask; + + // If we already have a link to the remote, check if it supports that CID + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport)) != NULL) { + // Fixed channels are mandatory on LE transports so ignore the received + // channel mask and use the locally cached LE channel mask. + +#if BLE_INCLUDED == TRUE + if (transport == BT_TRANSPORT_LE) { + peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask; + } else +#endif + { + peer_channel_mask = p_lcb->peer_chnl_mask[0]; + } + + // Check for supported channel + if (!(peer_channel_mask & (1 << fixed_cid))) { + L2CAP_TRACE_EVENT ("%s() CID:0x%04x BDA: %08x%04x not supported", __func__, + fixed_cid, (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + return FALSE; + } + + // Get a CCB and link the lcb to it + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, + &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { + L2CAP_TRACE_WARNING ("%s(0x%04x) - LCB but no CCB", __func__, fixed_cid); + return FALSE; + } + + // racing with disconnecting, queue the connection request + if (p_lcb->link_state == LST_DISCONNECTING) { + L2CAP_TRACE_DEBUG ("%s() - link disconnecting: RETRY LATER", __func__); + /* Save ccb so it can be started after disconnect is finished */ + p_lcb->p_pending_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]; + return TRUE; + } + + (*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb) + (fixed_cid, p_lcb->remote_bd_addr, TRUE, 0, transport); + + return TRUE; + } + + // No link. Get an LCB and start link establishment + if ((p_lcb = l2cu_allocate_lcb (rem_bda, FALSE, transport)) == NULL) { + L2CAP_TRACE_WARNING ("%s(0x%04x) - no LCB", __func__, fixed_cid); + return FALSE; + } + + // Get a CCB and link the lcb to it + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, + &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { + p_lcb->disc_reason = L2CAP_CONN_NO_RESOURCES; + L2CAP_TRACE_WARNING ("%s(0x%04x) - no CCB", __func__, fixed_cid); + l2cu_release_lcb (p_lcb); + return FALSE; + } +#if (BLE_INCLUDED == TRUE) + p_lcb->is_aux = is_aux; + p_lcb->open_addr_type = bd_addr_type; +#endif + if (!l2cu_create_conn(p_lcb, transport)) { + L2CAP_TRACE_WARNING ("%s() - create_conn failed", __func__); + l2cu_release_lcb (p_lcb); + return FALSE; + } + return TRUE; +} + +/******************************************************************************* +** +** Function L2CA_SendFixedChnlData +** +** Description Write data on a fixed channel. +** +** Parameters: Fixed CID +** BD Address of remote +** Pointer to buffer of type BT_HDR +** +** Return value L2CAP_DW_SUCCESS, if data accepted +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT16 L2CA_SendFixedChnlData (UINT16 fixed_cid, BD_ADDR rem_bda, BT_HDR *p_buf) +{ + tL2C_LCB *p_lcb; + tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; + + L2CAP_TRACE_API ("L2CA_SendFixedChnlData() CID: 0x%04x BDA: %08x%04x", fixed_cid, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + +#if BLE_INCLUDED == TRUE + if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID) { + transport = BT_TRANSPORT_LE; + } +#endif + + // Check CID is valid and registered + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) { + L2CAP_TRACE_ERROR ("L2CA_SendFixedChnlData() Invalid CID: 0x%04x", fixed_cid); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + // Fail if BT is not yet up + if (!BTM_IsDeviceUp()) { + L2CAP_TRACE_WARNING ("L2CA_SendFixedChnlData(0x%04x) - BTU not ready", fixed_cid); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + // We need to have a link up + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport)) == NULL || + /* if link is disconnecting, also report data sending failure */ + p_lcb->link_state == LST_DISCONNECTING) { + L2CAP_TRACE_WARNING ("L2CA_SendFixedChnlData(0x%04x) - no LCB", fixed_cid); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + tL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask; + + // Select peer channels mask to use depending on transport +#if BLE_INCLUDED == TRUE + if (transport == BT_TRANSPORT_LE) { + peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask; + } else +#endif + { + peer_channel_mask = p_lcb->peer_chnl_mask[0]; + } + + if ((peer_channel_mask & (1 << fixed_cid)) == 0) { + L2CAP_TRACE_WARNING ("L2CA_SendFixedChnlData() - peer does not support fixed chnl: 0x%04x", fixed_cid); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + p_buf->event = 0; + p_buf->layer_specific = L2CAP_FLUSHABLE_CH_BASED; + + if (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) { + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { + L2CAP_TRACE_WARNING ("L2CA_SendFixedChnlData() - no CCB for chnl: 0x%4x", fixed_cid); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + } + + // If already congested, do not accept any more packets + if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent && fixed_cid != L2CAP_SMP_CID) { + L2CAP_TRACE_DEBUG ("L2CAP - CID: 0x%04x cannot send, already congested\ + xmit_hold_q.count: %u buff_quota: %u", fixed_cid, + fixed_queue_length(p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->xmit_hold_q), + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->buff_quota); + osi_free(p_buf); + return (L2CAP_DW_CONGESTED); + } + + l2c_enqueue_peer_data (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL], p_buf); + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + + // If there is no dynamic CCB on the link, restart the idle timer each time something is sent + if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED && !p_lcb->ccb_queue.p_first_ccb) { + l2cu_no_dynamic_ccbs (p_lcb); + } + + if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent) { + return (L2CAP_DW_CONGESTED); + } + + return (L2CAP_DW_SUCCESS); +} + +BOOLEAN L2CA_CheckIsCongest(UINT16 fixed_cid, BD_ADDR addr) +{ + tL2C_LCB *p_lcb; + p_lcb = l2cu_find_lcb_by_bd_addr(addr, BT_TRANSPORT_LE); + + if (p_lcb != NULL && p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] != NULL) { + return p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent; + } + + return TRUE; +} + +#if (BLE_INCLUDED == TRUE) +UINT16 L2CA_GetFreePktBufferNum_LE(void) +{ + return l2cb.controller_le_xmit_window; +} +UINT16 L2CA_GetCurFreePktBufferNum_LE(UINT16 conn_id) +{ + uint16_t num = 0; + tl2c_buff_param_t param; + param.conn_id = conn_id; + param.get_num = # + l2ble_update_att_acl_pkt_num(L2CA_GET_ATT_NUM, ¶m); + return num; +} +#endif + +/******************************************************************************* +** +** Function L2CA_RemoveFixedChnl +** +** Description Remove a fixed channel to a remote device. +** +** Parameters: Fixed CID +** BD Address of remote +** Idle timeout to use (or 0xFFFF if don't care) +** +** Return value: TRUE if channel removed +** +*******************************************************************************/ +BOOLEAN L2CA_RemoveFixedChnl (UINT16 fixed_cid, BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; + + /* Check CID is valid and registered */ + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) { + L2CAP_TRACE_ERROR ("L2CA_RemoveFixedChnl() Invalid CID: 0x%04x", fixed_cid); + return (FALSE); + } + +#if BLE_INCLUDED == TRUE + if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID) { + transport = BT_TRANSPORT_LE; + } +#endif + + /* Is a fixed channel connected to the remote BDA ?*/ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport); + + if ( ((p_lcb) == NULL) || (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) ) { + L2CAP_TRACE_DEBUG ("L2CA_RemoveFixedChnl() CID: 0x%04x BDA: %08x%04x not connected", fixed_cid, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + return (FALSE); + } + + L2CAP_TRACE_API ("L2CA_RemoveFixedChnl() CID: 0x%04x BDA: %08x%04x", fixed_cid, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + + /* Release the CCB, starting an inactivity timeout on the LCB if no other CCBs exist */ + p_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]; + + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = NULL; + p_lcb->disc_reason = HCI_ERR_CONN_CAUSE_LOCAL_HOST; + +#if BLE_INCLUDED == TRUE + // Retain the link for a few more seconds after SMP pairing is done, since the Android + // platform always does service discovery after pairing is complete. This will avoid + // the link down (pairing is complete) and an immediate re-connection for service + // discovery. + // Some devices do not do auto advertising when link is dropped, thus fail the second + // connection and service discovery. + if ((fixed_cid == L2CAP_ATT_CID ) && !p_lcb->ccb_queue.p_first_ccb) { + p_lcb->idle_timeout = 0; + } +#endif + + l2cu_release_ccb (p_ccb); + + return (TRUE); +} + +#if BLE_INCLUDED == TRUE +BOOLEAN L2CA_BleDisconnect (BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; + tGATT_TCB *p_tcb; + + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_LE); + if (p_lcb == NULL) { + return FALSE; + } + + if (p_lcb->link_state != LST_CONNECTED) { + return FALSE; + } + + p_lcb->disc_reason = HCI_ERR_CONN_CAUSE_LOCAL_HOST; + p_lcb->link_state = LST_DISCONNECTING; + btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER); + + p_tcb = gatt_find_tcb_by_addr(rem_bda, BT_TRANSPORT_LE); + if (p_tcb == NULL) { + return FALSE; + } + + gatt_set_ch_state(p_tcb, GATT_CH_CLOSING); + + return TRUE; +} +#endif + +/******************************************************************************* +** +** Function L2CA_SetFixedChannelTout +** +** Description Higher layers call this function to set the idle timeout for +** a fixed channel. The "idle timeout" is the amount of time that +** a connection can remain up with no L2CAP channels on it. +** A timeout of zero means that the connection will be torn +** down immediately when the last channel is removed. +** A timeout of 0xFFFF means no timeout. Values are in seconds. +** A bd_addr is the remote BD address. If bd_addr = BT_BD_ANY, +** then the idle timeouts for all active l2cap links will be +** changed. +** +** Returns TRUE if command succeeded, FALSE if failed +** +*******************************************************************************/ +BOOLEAN L2CA_SetFixedChannelTout (BD_ADDR rem_bda, UINT16 fixed_cid, UINT16 idle_tout) +{ + tL2C_LCB *p_lcb; + tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; + +#if BLE_INCLUDED == TRUE + if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID) { + transport = BT_TRANSPORT_LE; + } +#endif + if (fixed_cid<L2CAP_FIRST_FIXED_CHNL) { + return (FALSE); + } + + /* Is a fixed channel connected to the remote BDA ?*/ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport); + if ( ((p_lcb) == NULL) || (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) ) { + L2CAP_TRACE_WARNING ("L2CA_SetFixedChannelTout() CID: 0x%04x BDA: %08x%04x not connected", fixed_cid, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + return (FALSE); + } + + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->fixed_chnl_idle_tout = idle_tout; + + if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED && !p_lcb->ccb_queue.p_first_ccb) { + /* If there are no dynamic CCBs, (re)start the idle timer in case we changed it */ + l2cu_no_dynamic_ccbs (p_lcb); + } + + return TRUE; +} + +#endif /* #if (L2CAP_NUM_FIXED_CHNLS > 0) */ + +#if (CLASSIC_BT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function L2CA_GetCurrentConfig +** +** Description This function returns configurations of L2CAP channel +** pp_our_cfg : pointer of our saved configuration options +** p_our_cfg_bits : valid config in bitmap +** pp_peer_cfg: pointer of peer's saved configuration options +** p_peer_cfg_bits : valid config in bitmap +** +** Returns TRUE if successful +** +*******************************************************************************/ +BOOLEAN L2CA_GetCurrentConfig (UINT16 lcid, + tL2CAP_CFG_INFO **pp_our_cfg, tL2CAP_CH_CFG_BITS *p_our_cfg_bits, + tL2CAP_CFG_INFO **pp_peer_cfg, tL2CAP_CH_CFG_BITS *p_peer_cfg_bits) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_GetCurrentConfig() CID: 0x%04x", lcid); + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + if (p_ccb) { + *pp_our_cfg = &(p_ccb->our_cfg); + + /* convert valid config items into bitmap */ + *p_our_cfg_bits = 0; + if (p_ccb->our_cfg.mtu_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_MTU; + } + if (p_ccb->our_cfg.qos_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_QOS; + } + if (p_ccb->our_cfg.flush_to_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FLUSH_TO; + } + if (p_ccb->our_cfg.fcr_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FCR; + } + if (p_ccb->our_cfg.fcs_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FCS; + } + if (p_ccb->our_cfg.ext_flow_spec_present) { + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_EXT_FLOW_SPEC; + } + + *pp_peer_cfg = &(p_ccb->peer_cfg); + *p_peer_cfg_bits = p_ccb->peer_cfg_bits; + + return TRUE; + } else { + L2CAP_TRACE_ERROR ("No CCB for CID:0x%04x", lcid); + return FALSE; + } +} + +/******************************************************************************* +** +** Function L2CA_RegForNoCPEvt +** +** Description Register callback for Number of Completed Packets event. +** +** Input Param p_cb - callback for Number of completed packets event +** p_bda - BT address of remote device +** +** Returns TRUE if registered OK, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_RegForNoCPEvt(tL2CA_NOCP_CB *p_cb, BD_ADDR p_bda) +{ + tL2C_LCB *p_lcb; + + /* Find the link that is associated with this remote bdaddr */ + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda, BT_TRANSPORT_BR_EDR); + + /* If no link for this handle, nothing to do. */ + if (!p_lcb) { + return FALSE; + } + + p_lcb->p_nocp_cb = p_cb; + + return TRUE; +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + +/******************************************************************************* +** +** Function L2CA_DataWrite +** +** Description Higher layers call this function to write data. +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data) +{ + L2CAP_TRACE_API ("L2CA_DataWrite() CID: 0x%04x Len: %d", cid, p_data->len); + return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + +/******************************************************************************* +** +** Function l2cap_bqb_write_data +** +** Description Call L2CA_DataWrite and write I-Frame data for BQB test. +** +** Returns None +** +*******************************************************************************/ +#if (BT_CLASSIC_BQB_INCLUDED == TRUE) +void l2cap_bqb_write_data(UINT16 cid) +{ + BT_HDR *p_buf; + uint8_t *p; + + if ((p_buf = (BT_HDR *)osi_malloc(SDP_DATA_BUF_SIZE)) != NULL) { + p_buf->len = 30; + p_buf->offset = L2CAP_MIN_OFFSET; + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + for(int i = 0 ; i < 10; i++) { + UINT8_TO_BE_STREAM(p, 0) + } + L2CA_DataWrite(cid, p_buf); + } +} +#endif /* BT_CLASSIC_BQB_INCLUDED */ + +/******************************************************************************* +** +** Function L2CA_SetChnlFlushability +** +** Description Higher layers call this function to set a channels +** flushability flags +** +** Returns TRUE if CID found, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetChnlFlushability (UINT16 cid, BOOLEAN is_flushable) +{ +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + + tL2C_CCB *p_ccb; + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_SetChnlFlushability, CID: %d", cid); + return (FALSE); + } + + p_ccb->is_flushable = is_flushable; + + L2CAP_TRACE_API ("L2CA_SetChnlFlushability() CID: 0x%04x is_flushable: %d", cid, is_flushable); + +#endif + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_DataWriteEx +** +** Description Higher layers call this function to write data with extended +** flags. +** flags : L2CAP_FLUSHABLE_CH_BASED +** L2CAP_FLUSHABLE_PKT +** L2CAP_NON_FLUSHABLE_PKT +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +UINT8 L2CA_DataWriteEx (UINT16 cid, BT_HDR *p_data, UINT16 flags) +{ + L2CAP_TRACE_API ("L2CA_DataWriteEx() CID: 0x%04x Len: %d Flags:0x%04X", + cid, p_data->len, flags); + return l2c_data_write (cid, p_data, flags); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + +/******************************************************************************* +** +** Function L2CA_FlushChannel +** +** Description This function flushes none, some or all buffers queued up +** for xmission for a particular CID. If called with +** L2CAP_FLUSH_CHANS_GET (0), it simply returns the number +** of buffers queued for that CID L2CAP_FLUSH_CHANS_ALL (0xffff) +** flushes all buffers. All other values specifies the maximum +** buffers to flush. +** +** Returns Number of buffers left queued for that CID +** +*******************************************************************************/ +UINT16 L2CA_FlushChannel (UINT16 lcid, UINT16 num_to_flush) +{ + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + UINT16 num_left = 0, + num_flushed1 = 0, + num_flushed2 = 0; + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + if ( !p_ccb || ((p_lcb = p_ccb->p_lcb) == NULL) ) { + L2CAP_TRACE_WARNING ("L2CA_FlushChannel() abnormally returning 0 CID: 0x%04x", lcid); + return (0); + } + + if (num_to_flush != L2CAP_FLUSH_CHANS_GET) { + L2CAP_TRACE_API ("L2CA_FlushChannel (FLUSH) CID: 0x%04x NumToFlush: %d QC: %u pFirst: %p", + lcid, num_to_flush, + fixed_queue_length(p_ccb->xmit_hold_q), + fixed_queue_try_peek_first(p_ccb->xmit_hold_q)); + } else { + L2CAP_TRACE_API ("L2CA_FlushChannel (QUERY) CID: 0x%04x", lcid); + } + + /* Cannot flush eRTM buffers once they have a sequence number */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) { +#if L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE + if (num_to_flush != L2CAP_FLUSH_CHANS_GET) { + /* If the controller supports enhanced flush, flush the data queued at the controller */ + if ( (HCI_NON_FLUSHABLE_PB_SUPPORTED(BTM_ReadLocalFeatures ())) + && (BTM_GetNumScoLinks() == 0) ) { + if ( l2cb.is_flush_active == FALSE ) { + l2cb.is_flush_active = TRUE; + + /* The only packet type defined - 0 - Automatically-Flushable Only */ + btsnd_hcic_enhanced_flush (p_lcb->handle, 0); + } + } + } +#endif + + // Iterate though list and flush the amount requested from + // the transmit data queue that satisfy the layer and event conditions. + for (const list_node_t *node = list_begin(p_lcb->link_xmit_data_q); + (num_to_flush > 0) && node != list_end(p_lcb->link_xmit_data_q);) { + BT_HDR *p_buf = (BT_HDR *)list_node(node); + node = list_next(node); + if ((p_buf->layer_specific == 0) && (p_buf->event == lcid)) { + num_to_flush--; + num_flushed1++; + + list_remove(p_lcb->link_xmit_data_q, p_buf); + osi_free(p_buf); + } + } + } + + /* If needed, flush buffers in the CCB xmit hold queue */ + while ( (num_to_flush != 0) && (!fixed_queue_is_empty(p_ccb->xmit_hold_q))) { + BT_HDR *p_buf = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0); + if (p_buf) { + osi_free (p_buf); + } + num_to_flush--; + num_flushed2++; + } + + /* If app needs to track all packets, call him */ + if ( (p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_TxComplete_Cb) && (num_flushed2) ) { + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, num_flushed2); + } + + /* Now count how many are left */ + for (const list_node_t *node = list_begin(p_lcb->link_xmit_data_q); + node != list_end(p_lcb->link_xmit_data_q); + node = list_next(node)) { + + BT_HDR *p_buf = (BT_HDR *)list_node(node); + if (p_buf->event == lcid) { + num_left++; + } + } + + /* Add in the number in the CCB xmit queue */ + num_left += fixed_queue_length(p_ccb->xmit_hold_q); + + /* Return the local number of buffers left for the CID */ + L2CAP_TRACE_DEBUG ("L2CA_FlushChannel() flushed: %u + %u, num_left: %u", num_flushed1, num_flushed2, num_left); + + /* If we were congested, and now we are not, tell the app */ + l2cu_check_channel_congestion (p_ccb); + + return (num_left); +} + +/****************************************************************************** +** +** Function update_acl_pkt_num +** +** Description Update the number of att acl packets to be sent in xmit_hold_q. +** +** Returns None +** +*******************************************************************************/ +#if BLE_INCLUDED == TRUE +void l2ble_update_att_acl_pkt_num(UINT8 type, tl2c_buff_param_t *param) +{ + static SemaphoreHandle_t buff_semaphore = NULL ; + static INT16 btc_buf; + static INT16 btu_buf; + + if(buff_semaphore == NULL && type != L2CA_BUFF_INI){ + L2CAP_TRACE_ERROR("%s buff_semaphore not init", __func__); + return; + } + switch (type) + { + case L2CA_ADD_BTC_NUM:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + btc_buf ++; + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_DECREASE_BTC_NUM:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + btc_buf --; + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_ADD_BTU_NUM:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + btu_buf ++; + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_DECREASE_BTU_NUM:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + btu_buf --; + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_GET_ATT_NUM:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + INT16 att_acl_pkt_num = 0; + INT16 att_max_num = 0; + *(param->get_num) = 0; + UINT8 tcb_idx = param->conn_id; + tGATT_TCB * p_tcb = gatt_get_tcb_by_idx(tcb_idx); + if (p_tcb == NULL){ + L2CAP_TRACE_ERROR("%s not found p_tcb", __func__); + xSemaphoreGive(buff_semaphore); + break; + } + + if (!gatt_check_connection_state_by_tcb(p_tcb)) { + L2CAP_TRACE_ERROR("connection not established\n"); + xSemaphoreGive(buff_semaphore); + break; + } + + tL2C_LCB * p_lcb = l2cu_find_lcb_by_bd_addr (p_tcb->peer_bda, BT_TRANSPORT_LE); + if (p_lcb == NULL){ + L2CAP_TRACE_ERROR("%s not found p_lcb", __func__); + xSemaphoreGive(buff_semaphore); + break; + } + + tL2C_CCB *p_ccb = p_lcb->p_fixed_ccbs[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL]; + if(p_ccb == NULL) { + L2CAP_TRACE_ERROR("%s not found p_ccb", __func__); + xSemaphoreGive(buff_semaphore); + break; + } + + fixed_queue_t * queue = p_ccb->xmit_hold_q; + att_max_num = MIN(p_lcb->link_xmit_quota, L2CAP_CACHE_ATT_ACL_NUM); + if (queue == NULL){ + L2CAP_TRACE_ERROR("%s not found queue", __func__); + xSemaphoreGive(buff_semaphore); + break; + } + att_acl_pkt_num = fixed_queue_length(queue); + if(att_acl_pkt_num < att_max_num){ + if(btc_buf + btu_buf < att_max_num - att_acl_pkt_num){ + *(param->get_num) = att_max_num - att_acl_pkt_num - (btc_buf + btu_buf); + } + } + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_BUFF_INI:{ + btc_buf = 0; + btu_buf = 0; + buff_semaphore = xSemaphoreCreateBinary(); + if (buff_semaphore == NULL) { + L2CAP_TRACE_ERROR("%s NO MEMORY", __func__); + break; + } + xSemaphoreGive(buff_semaphore); + break; + } + case L2CA_BUFF_DEINIT:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + btc_buf = 0; + btu_buf = 0; + xSemaphoreGive(buff_semaphore); + vSemaphoreDelete(buff_semaphore); + buff_semaphore = NULL; + break; + } + case L2CA_BUFF_FREE:{ + xSemaphoreTake(buff_semaphore, portMAX_DELAY); + // Do nothing + xSemaphoreGive(buff_semaphore); + break; + } + default: + break; + } +} +#endif diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_ble.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_ble.c new file mode 100644 index 00000000..4e6c8534 --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_ble.c @@ -0,0 +1,1707 @@ +/****************************************************************************** + * + * Copyright (C) 2009-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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * this file contains functions relating to BLE management. + * + ******************************************************************************/ + +#include <string.h> +#include "common/bt_target.h" +//#include "bt_utils.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/btu.h" +#include "btm_int.h" +#include "stack/hcimsgs.h" +#include "device/controller.h" + +#if (BLE_INCLUDED == TRUE) + +#if (BLE_50_FEATURE_SUPPORT == TRUE) +#define EXT_CONN_INT_DEF_1M MAX(((MAX_ACL_CONNECTIONS + 1) * 4), 12) +#define EXT_CONN_INT_DEF_2M MAX(((MAX_ACL_CONNECTIONS + 1) * 2), 12) +#define EXT_CONN_INT_DEF_CODED (320) // 306-> 362Kbps + +const static tHCI_ExtConnParams ext_conn_params_1m_phy = { + .scan_interval = 0x40, + .scan_window = 0x40, + .conn_interval_min = EXT_CONN_INT_DEF_1M, + .conn_interval_max = EXT_CONN_INT_DEF_1M, + .conn_latency = 0, + .sup_timeout = 600, + .min_ce_len = 0, + .max_ce_len = 0, +}; +const static tHCI_ExtConnParams ext_conn_params_2m_phy = { + .scan_interval = 0x40, + .scan_window = 0x40, + .conn_interval_min = EXT_CONN_INT_DEF_2M, + .conn_interval_max = EXT_CONN_INT_DEF_2M, + .conn_latency = 0, + .sup_timeout = 600, + .min_ce_len = 0, + .max_ce_len = 0, +}; +const static tHCI_ExtConnParams ext_conn_params_coded_phy = { + .scan_interval = 0x40, + .scan_window = 0x40, + .conn_interval_min = EXT_CONN_INT_DEF_CODED, + .conn_interval_max = EXT_CONN_INT_DEF_CODED, + .conn_latency = 0, + .sup_timeout = 600, + .min_ce_len = 0, + .max_ce_len = 0, +}; +#define BLE_PHY_NO_PREF 0 +#define BLE_PHY_PREF_MASK ((1 << 2) | (1 << 1) | (1 << 0)) + +#endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) + +static BOOLEAN l2cble_start_conn_update (tL2C_LCB *p_lcb); +extern int64_t esp_system_get_time(void); + +/******************************************************************************* +** +** Function L2CA_CancelBleConnectReq +** +** Description Cancel a pending connection attempt to a BLE device. +** +** Parameters: BD Address of remote +** +** Return value: TRUE if connection was cancelled +** +*******************************************************************************/ +BOOLEAN L2CA_CancelBleConnectReq (BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; + + /* There can be only one BLE connection request outstanding at a time */ + if (btm_ble_get_conn_st() == BLE_CONN_IDLE) { + L2CAP_TRACE_WARNING ("L2CA_CancelBleConnectReq - no connection pending"); + return (FALSE); + } + + if (memcmp (rem_bda, l2cb.ble_connecting_bda, BD_ADDR_LEN)) { + L2CAP_TRACE_WARNING ("L2CA_CancelBleConnectReq - different BDA Connecting: %08x%04x Cancel: %08x%04x", + (l2cb.ble_connecting_bda[0] << 24) + (l2cb.ble_connecting_bda[1] << 16) + (l2cb.ble_connecting_bda[2] << 8) + l2cb.ble_connecting_bda[3], + (l2cb.ble_connecting_bda[4] << 8) + l2cb.ble_connecting_bda[5], + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], (rem_bda[4] << 8) + rem_bda[5]); + + return (FALSE); + } + + if (btsnd_hcic_ble_create_conn_cancel()) { + p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, BT_TRANSPORT_LE); + /* Do not remove lcb if an LE link is already up as a peripheral */ + if (p_lcb != NULL && + !(p_lcb->link_role == HCI_ROLE_SLAVE && BTM_ACL_IS_CONNECTED(rem_bda))) { + p_lcb->disc_reason = L2CAP_CONN_CANCEL; + l2cu_release_lcb (p_lcb); + } + /* update state to be cancel, wait for connection cancel complete */ + btm_ble_set_conn_st (BLE_CONN_CANCEL); + + return (TRUE); + } else { + return (FALSE); + } +} + +/******************************************************************************* +** +** Function L2CA_UpdateBleConnParams +** +** Description Update BLE connection parameters. +** +** Parameters: BD Address of remote +** +** Return value: TRUE if update started +** +*******************************************************************************/ +BOOLEAN L2CA_UpdateBleConnParams (BD_ADDR rem_bda, UINT16 min_int, UINT16 max_int, + UINT16 latency, UINT16 timeout) +{ + tL2C_LCB *p_lcb; + tACL_CONN *p_acl_cb = btm_bda_to_acl(rem_bda, BT_TRANSPORT_LE); + UINT8 status = HCI_SUCCESS; + BOOLEAN need_cb = false; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_LE); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb || !p_acl_cb) { + L2CAP_TRACE_WARNING ("L2CA_UpdateBleConnParams - unknown BD_ADDR %08x%04x", + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + return (FALSE); + } + + if (p_lcb->transport != BT_TRANSPORT_LE) { + L2CAP_TRACE_WARNING ("L2CA_UpdateBleConnParams - BD_ADDR %08x%04x not LE", + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + return (FALSE); + } + + /* Check whether the request conn params is already set */ + if ((max_int == p_lcb->current_used_conn_interval) && (latency == p_lcb->current_used_conn_latency) && + (timeout == p_lcb->current_used_conn_timeout)) { + status = HCI_SUCCESS; + need_cb = true; + L2CAP_TRACE_WARNING("%s connection parameter already set", __func__); + } + + if (p_lcb->conn_update_mask & L2C_BLE_UPDATE_PARAM_FULL){ + status = HCI_ERR_ILLEGAL_COMMAND; + need_cb = true; + L2CAP_TRACE_ERROR("There are two connection parameter requests that are being updated, please try later "); + } + + if ((need_cb == TRUE) && (conn_param_update_cb.update_conn_param_cb != NULL)) { + tBTM_LE_UPDATE_CONN_PRAMS update_param; + update_param.max_conn_int = max_int; + update_param.min_conn_int = min_int; + update_param.conn_int = p_lcb->current_used_conn_interval; + update_param.slave_latency = p_lcb->current_used_conn_latency; + update_param.supervision_tout = p_lcb->current_used_conn_timeout; + (conn_param_update_cb.update_conn_param_cb)(status, p_lcb->remote_bd_addr, &update_param); + return (status == HCI_SUCCESS); + } + + p_lcb->waiting_update_conn_min_interval = min_int; + p_lcb->waiting_update_conn_max_interval = max_int; + p_lcb->waiting_update_conn_latency = latency; + p_lcb->waiting_update_conn_timeout = timeout; + + p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; + + if(l2cble_start_conn_update(p_lcb) == TRUE) { + UINT32 time = CalConnectParamTimeout(p_lcb); + btu_start_timer(&p_lcb->upda_con_timer, BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS, time); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_EnableUpdateBleConnParams +** +** Description Enable or disable update based on the request from the peer +** +** Parameters: BD Address of remote +** +** Return value: TRUE if update started +** +*******************************************************************************/ +BOOLEAN L2CA_EnableUpdateBleConnParams (BD_ADDR rem_bda, BOOLEAN enable) +{ + tL2C_LCB *p_lcb; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_LE); + + if (!p_lcb) { + L2CAP_TRACE_WARNING ("L2CA_EnableUpdateBleConnParams - unknown BD_ADDR %08x%04x", + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + return (FALSE); + } + + L2CAP_TRACE_API ("%s - BD_ADDR %08x%04x enable %d current upd state 0x%02x", __FUNCTION__, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5], enable, p_lcb->conn_update_mask); + + if (p_lcb->transport != BT_TRANSPORT_LE) { + L2CAP_TRACE_WARNING ("%s - BD_ADDR %08x%04x not LE (link role %d)", __FUNCTION__, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5], p_lcb->link_role); + return (FALSE); + } + + if (p_lcb->current_used_conn_interval <= BTM_BLE_CONN_INT_MAX_DEF && (p_lcb->conn_update_mask & L2C_BLE_CONN_UPDATE_DISABLE) == 0){ + return (FALSE); + } + bool is_disable = (p_lcb->conn_update_mask & L2C_BLE_CONN_UPDATE_DISABLE); + if(l2cu_plcb_active_count() >1 && !(enable && is_disable)) { + return FALSE; + } + + if (enable) { + p_lcb->conn_update_mask &= ~L2C_BLE_CONN_UPDATE_DISABLE; + } else { + p_lcb->conn_update_mask |= L2C_BLE_CONN_UPDATE_DISABLE; + } + + if (l2cble_start_conn_update(p_lcb) == TRUE) { + UINT32 time = CalConnectParamTimeout(p_lcb); + btu_start_timer(&p_lcb->upda_con_timer, BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS, time); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_GetBleConnRole +** +** Description This function returns the connection role. +** +** Returns link role. +** +*******************************************************************************/ +UINT8 L2CA_GetBleConnRole (BD_ADDR bd_addr) +{ + UINT8 role = HCI_ROLE_UNKNOWN; + + tL2C_LCB *p_lcb; + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_LE)) != NULL) { + role = p_lcb->link_role; + } + + return role; +} + +/******************************************************************************* +** +** Function l2cble_notify_le_connection +** +** Description This function notifiy the l2cap connection to the app layer +** +** Returns none +** +*******************************************************************************/ +void l2cble_notify_le_connection (BD_ADDR bda) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (bda, BT_TRANSPORT_LE); + tACL_CONN *p_acl = btm_bda_to_acl(bda, BT_TRANSPORT_LE) ; + + if (p_lcb != NULL && p_acl != NULL && p_lcb->link_state != LST_CONNECTED) { + + if(p_acl->link_role == HCI_ROLE_SLAVE) { + //clear p_cb->state, controller will stop adv when ble connected. + tBTM_BLE_INQ_CB *p_cb = &btm_cb.ble_ctr_cb.inq_var; + if(p_cb) { + p_cb->adv_mode = BTM_BLE_ADV_DISABLE; + p_cb->state &= ~BTM_BLE_ADVERTISING; + } + } + /* update link status */ + btm_establish_continue(p_acl); + /* update l2cap link status and send callback */ + p_lcb->link_state = LST_CONNECTED; + l2cu_process_fixed_chnl_resp (p_lcb); + } +} + +/******************************************************************************* +** +** Function l2cble_scanner_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received while we are a scanner (so we are master). +** +** Returns void +** +*******************************************************************************/ +void l2cble_scanner_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + tL2C_LCB *p_lcb; + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (bda); + + L2CAP_TRACE_DEBUG ("l2cble_scanner_conn_comp: HANDLE=%d addr_type=%d conn_interval=%d slave_latency=%d supervision_tout=%d", + handle, type, conn_interval, conn_latency, conn_timeout); + + l2cb.is_ble_connecting = FALSE; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bda, BT_TRANSPORT_LE); + + /* If we don't have one, create one. this is auto connection complete. */ + if (!p_lcb) { + p_lcb = l2cu_allocate_lcb (bda, FALSE, BT_TRANSPORT_LE); + if (!p_lcb) { +#if (SMP_INCLUDED == TRUE) + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_ERROR ("l2cble_scanner_conn_comp - failed to allocate LCB"); +#endif ///SMP_INCLUDED == TRUE + return; + } else { + if (!l2cu_initialize_fixed_ccb (p_lcb, L2CAP_ATT_CID, &l2cb.fixed_reg[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { +#if (SMP_INCLUDED == TRUE) + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_WARNING ("l2cble_scanner_conn_comp - LCB but no CCB"); +#endif ///SMP_INCLUDED == TRUE + return ; + } + } + } else if (p_lcb->link_state != LST_CONNECTING) { + L2CAP_TRACE_ERROR ("L2CAP got BLE scanner conn_comp in bad state: %d", p_lcb->link_state); + return; + } + btu_stop_timer(&p_lcb->timer_entry); + + /* Save the handle */ + p_lcb->handle = handle; + + /* Connected OK. Change state to connected, we were scanning so we are master */ + p_lcb->link_role = HCI_ROLE_MASTER; + p_lcb->transport = BT_TRANSPORT_LE; + + /* update link parameter, set slave link as non-spec default upon link up */ + p_lcb->waiting_update_conn_min_interval = p_lcb->waiting_update_conn_max_interval = p_lcb->current_used_conn_interval = conn_interval; + p_lcb->waiting_update_conn_timeout = p_lcb->current_used_conn_timeout = conn_timeout; + p_lcb->waiting_update_conn_latency = p_lcb->current_used_conn_latency = conn_latency; + p_lcb->conn_update_mask = L2C_BLE_NOT_DEFAULT_PARAM; + p_lcb->updating_param_flag = false; + + /* If there are any preferred connection parameters, set them now */ + if ( (p_dev_rec->conn_params.min_conn_int >= BTM_BLE_CONN_INT_MIN ) && + (p_dev_rec->conn_params.min_conn_int <= BTM_BLE_CONN_INT_MAX ) && + (p_dev_rec->conn_params.max_conn_int >= BTM_BLE_CONN_INT_MIN ) && + (p_dev_rec->conn_params.max_conn_int <= BTM_BLE_CONN_INT_MAX ) && + (p_dev_rec->conn_params.slave_latency <= BTM_BLE_CONN_LATENCY_MAX ) && + (p_dev_rec->conn_params.supervision_tout >= BTM_BLE_CONN_SUP_TOUT_MIN) && + (p_dev_rec->conn_params.supervision_tout <= BTM_BLE_CONN_SUP_TOUT_MAX) && + ((conn_interval < p_dev_rec->conn_params.min_conn_int && + p_dev_rec->conn_params.min_conn_int != BTM_BLE_CONN_PARAM_UNDEF) || + (conn_interval > p_dev_rec->conn_params.max_conn_int) || + (conn_latency > p_dev_rec->conn_params.slave_latency) || + (conn_timeout > p_dev_rec->conn_params.supervision_tout))) { + L2CAP_TRACE_ERROR ("upd_ll_conn_params: HANDLE=%d min_conn_int=%d max_conn_int=%d slave_latency=%d supervision_tout=%d", + handle, p_dev_rec->conn_params.min_conn_int, p_dev_rec->conn_params.max_conn_int, + p_dev_rec->conn_params.slave_latency, p_dev_rec->conn_params.supervision_tout); + + p_lcb->waiting_update_conn_min_interval = p_dev_rec->conn_params.min_conn_int; + p_lcb->waiting_update_conn_max_interval = p_dev_rec->conn_params.max_conn_int; + p_lcb->waiting_update_conn_timeout = p_dev_rec->conn_params.supervision_tout; + p_lcb->waiting_update_conn_latency = p_dev_rec->conn_params.slave_latency; + + btsnd_hcic_ble_upd_ll_conn_params (handle, + p_dev_rec->conn_params.min_conn_int, + p_dev_rec->conn_params.max_conn_int, + p_dev_rec->conn_params.slave_latency, + p_dev_rec->conn_params.supervision_tout, + BLE_CE_LEN_MIN, BLE_CE_LEN_MIN); + } + + /* Tell BTM Acl management about the link */ + btm_acl_created (bda, NULL, p_dev_rec->sec_bd_name, handle, p_lcb->link_role, BT_TRANSPORT_LE); + + p_lcb->peer_chnl_mask[0] = L2CAP_FIXED_CHNL_ATT_BIT | L2CAP_FIXED_CHNL_BLE_SIG_BIT | L2CAP_FIXED_CHNL_SMP_BIT; + + btm_ble_set_conn_st(BLE_CONN_IDLE); + +#if BLE_PRIVACY_SPT == TRUE + btm_ble_disable_resolving_list(BTM_BLE_RL_INIT, TRUE); +#endif +} + + +/******************************************************************************* +** +** Function l2cble_advertiser_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received while we are an advertiser (so we are slave). +** +** Returns void +** +*******************************************************************************/ +void l2cble_advertiser_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + tL2C_LCB *p_lcb; + tBTM_SEC_DEV_REC *p_dev_rec; + UNUSED(type); + UNUSED(conn_interval); + UNUSED(conn_latency); + UNUSED(conn_timeout); + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bda, BT_TRANSPORT_LE); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) { + p_lcb = l2cu_allocate_lcb (bda, FALSE, BT_TRANSPORT_LE); + if (!p_lcb) { +#if (SMP_INCLUDED == TRUE) + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); +#endif ///SMP_INCLUDED == TRUE + L2CAP_TRACE_ERROR ("l2cble_advertiser_conn_comp - failed to allocate LCB"); + return; + } else { + if (!l2cu_initialize_fixed_ccb (p_lcb, L2CAP_ATT_CID, &l2cb.fixed_reg[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { +#if (SMP_INCLUDED == TRUE) + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); +#endif ///SMP_INCLUDED == TRUE + L2CAP_TRACE_WARNING ("l2cble_scanner_conn_comp - LCB but no CCB"); + return ; + } + } + } + + /* Save the handle */ + p_lcb->handle = handle; + + /* Connected OK. Change state to connected, we were advertising, so we are slave */ + p_lcb->link_role = HCI_ROLE_SLAVE; + p_lcb->transport = BT_TRANSPORT_LE; + + /* update link parameter, set slave link as non-spec default upon link up */ + p_lcb->waiting_update_conn_min_interval = p_lcb->waiting_update_conn_max_interval = p_lcb->current_used_conn_interval = conn_interval; + p_lcb->waiting_update_conn_timeout = p_lcb->current_used_conn_timeout = conn_timeout; + p_lcb->waiting_update_conn_latency = p_lcb->current_used_conn_latency = conn_latency; + p_lcb->conn_update_mask = L2C_BLE_NOT_DEFAULT_PARAM; + p_lcb->updating_param_flag = false; + + /* Tell BTM Acl management about the link */ + p_dev_rec = btm_find_or_alloc_dev (bda); + + btm_acl_created (bda, NULL, p_dev_rec->sec_bd_name, handle, p_lcb->link_role, BT_TRANSPORT_LE); + +#if BLE_PRIVACY_SPT == TRUE + btm_ble_disable_resolving_list(BTM_BLE_RL_ADV, TRUE); +#endif + + p_lcb->peer_chnl_mask[0] = L2CAP_FIXED_CHNL_ATT_BIT | L2CAP_FIXED_CHNL_BLE_SIG_BIT | L2CAP_FIXED_CHNL_SMP_BIT; + + if (!HCI_LE_SLAVE_INIT_FEAT_EXC_SUPPORTED(controller_get_interface()->get_features_ble()->as_array)) { + p_lcb->link_state = LST_CONNECTED; + l2cu_process_fixed_chnl_resp (p_lcb); + } + + /* when adv and initiating are both active, cancel the direct connection */ + if (l2cb.is_ble_connecting && memcmp(bda, l2cb.ble_connecting_bda, BD_ADDR_LEN) == 0) { + L2CA_CancelBleConnectReq(bda); + } +} + +/******************************************************************************* +** +** Function l2cble_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received. +** +** Returns void +** +*******************************************************************************/ +void l2cble_conn_comp(UINT16 handle, UINT8 role, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + btm_ble_update_link_topology_mask(role, TRUE); + + if (role == HCI_ROLE_MASTER) { + l2cble_scanner_conn_comp(handle, bda, type, conn_interval, conn_latency, conn_timeout); + } else { + l2cble_advertiser_conn_comp(handle, bda, type, conn_interval, conn_latency, conn_timeout); + } +} + +/******************************************************************************* +** +** Function l2cble_start_conn_update +** +** Description start BLE connection parameter update process based on status +** +** Parameters: lcb : l2cap link control block +** +** Return value: true if successfully sending the request to peer device, else false. +** +*******************************************************************************/ +static BOOLEAN l2cble_start_conn_update (tL2C_LCB *p_lcb) +{ + UINT16 min_conn_int, max_conn_int, slave_latency, supervision_tout; +#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) && (BLE_SLAVE_UPD_CONN_PARAMS == TRUE) + tACL_CONN *p_acl_cb = btm_bda_to_acl(p_lcb->remote_bd_addr, BT_TRANSPORT_LE); +#endif /* defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) && (BLE_SLAVE_UPD_CONN_PARAMS == TRUE */ + + if (p_lcb->conn_update_mask & L2C_BLE_UPDATE_PENDING) { + L2CAP_TRACE_WARNING("%s, the last connection update command still pending.", __func__); + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PARAM_FULL; + return FALSE; + } + + if (p_lcb->conn_update_mask & L2C_BLE_CONN_UPDATE_DISABLE) { + /* application requests to disable parameters update. + If parameters are already updated, lets set them + up to what has been requested during connection establishement */ + if (p_lcb->conn_update_mask & L2C_BLE_NOT_DEFAULT_PARAM && + /* current connection interval is greater than default min */ + p_lcb->current_used_conn_interval > BTM_BLE_CONN_INT_MAX_DEF) { + /* use 6 * 1.25 = 7.5 ms as fast connection parameter, 0 slave latency */ + min_conn_int = max_conn_int = BTM_BLE_CONN_INT_MIN; + slave_latency = BTM_BLE_CONN_SLAVE_LATENCY_DEF; + supervision_tout = BTM_BLE_CONN_TIMEOUT_DEF; + + /* if both side 4.1, or we are master device, send HCI command */ + if (p_lcb->link_role == HCI_ROLE_MASTER +#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) && (BLE_SLAVE_UPD_CONN_PARAMS == TRUE) + || (HCI_LE_CONN_PARAM_REQ_SUPPORTED(controller_get_interface()->get_features_ble()->as_array) && + HCI_LE_CONN_PARAM_REQ_SUPPORTED(p_acl_cb->peer_le_features)) +#endif + ) { + btsnd_hcic_ble_upd_ll_conn_params(p_lcb->handle, min_conn_int, max_conn_int, + slave_latency, supervision_tout, BLE_CE_LEN_MIN, BLE_CE_LEN_MIN); + } else { + l2cu_send_peer_ble_par_req (p_lcb, min_conn_int, max_conn_int, slave_latency, supervision_tout); + } + + //cache save + p_lcb->updating_conn_min_interval = min_conn_int; + p_lcb->updating_conn_max_interval = max_conn_int; + p_lcb->updating_param_flag = true; + + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; + p_lcb->conn_update_mask &= ~L2C_BLE_NOT_DEFAULT_PARAM; + p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; + return TRUE; + }else { + return FALSE; + } + } else { + /* application allows to do update, if we were delaying one do it now */ + if (p_lcb->conn_update_mask & L2C_BLE_NEW_CONN_PARAM) { + /* if both side 4.1, or we are master device, send HCI command */ + if (p_lcb->link_role == HCI_ROLE_MASTER +#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) && (BLE_SLAVE_UPD_CONN_PARAMS == TRUE) + || (HCI_LE_CONN_PARAM_REQ_SUPPORTED(controller_get_interface()->get_features_ble()->as_array) && + HCI_LE_CONN_PARAM_REQ_SUPPORTED(p_acl_cb->peer_le_features)) +#endif + ) { + btsnd_hcic_ble_upd_ll_conn_params(p_lcb->handle, p_lcb->waiting_update_conn_min_interval, + p_lcb->waiting_update_conn_max_interval, p_lcb->waiting_update_conn_latency, p_lcb->waiting_update_conn_timeout, BLE_CE_LEN_MIN, BLE_CE_LEN_MIN); + } else { + l2cu_send_peer_ble_par_req (p_lcb, p_lcb->waiting_update_conn_min_interval, p_lcb->waiting_update_conn_max_interval, + p_lcb->waiting_update_conn_latency, p_lcb->waiting_update_conn_timeout); + } + + //cache save + p_lcb->updating_conn_min_interval = p_lcb->waiting_update_conn_min_interval; + p_lcb->updating_conn_max_interval = p_lcb->waiting_update_conn_max_interval; + p_lcb->updating_param_flag = true; + + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; + p_lcb->conn_update_mask &= ~L2C_BLE_NEW_CONN_PARAM; + p_lcb->conn_update_mask |= L2C_BLE_NOT_DEFAULT_PARAM; + return TRUE; + } else { + return FALSE; + } + } +} + +/******************************************************************************* +** +** Function l2cble_process_conn_update_evt +** +** Description This function enables the connection update request from remote +** after a successful connection update response is received. +** +** Returns void +** +*******************************************************************************/ +void l2cble_process_conn_update_evt (UINT16 handle, UINT8 status, UINT16 conn_interval, + UINT16 conn_latency, UINT16 conn_timeout) +{ + tL2C_LCB *p_lcb; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_handle(handle); + if (!p_lcb) { + L2CAP_TRACE_WARNING("le con upd: inv hdl=%d", handle); + return; + } + if (status == HCI_SUCCESS){ + p_lcb->current_used_conn_interval = conn_interval; + p_lcb->current_used_conn_latency = conn_latency; + p_lcb->current_used_conn_timeout = conn_timeout; + }else{ + L2CAP_TRACE_WARNING("le con upd: err_stat=0x%x", status); + } + + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PENDING; + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PARAM_FULL; + btu_stop_timer(&p_lcb->upda_con_timer); + + if (conn_param_update_cb.update_conn_param_cb != NULL) { + l2c_send_update_conn_params_cb(p_lcb, status); + } + + if (l2cble_start_conn_update(p_lcb) == TRUE) { + UINT32 time = CalConnectParamTimeout(p_lcb); + btu_start_timer(&p_lcb->upda_con_timer, BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS, time); + } + + btu_stop_timer (&p_lcb->timer_entry); + + L2CAP_TRACE_DEBUG("le con upd: conn_update_mask=%d", p_lcb->conn_update_mask); +} + +/******************************************************************************* +** +** Function l2cble_get_conn_param_format_err_from_contoller +** +** Description This function is called when host get illegal connection paramrters +** format status from controller +** +** Returns void +** +*******************************************************************************/ +void l2cble_get_conn_param_format_err_from_contoller (UINT8 status, UINT16 handle) +{ + tL2C_LCB *p_lcb; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_handle(handle); + if (!p_lcb) { + L2CAP_TRACE_ERROR("%s: Invalid handle: %d", __FUNCTION__, handle); + return; + } + + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PENDING; + + btu_stop_timer (&p_lcb->upda_con_timer); + + if (conn_param_update_cb.update_conn_param_cb != NULL) { + l2c_send_update_conn_params_cb(p_lcb, status); + } + if ((p_lcb->conn_update_mask & L2C_BLE_UPDATE_PARAM_FULL) != 0){ + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PARAM_FULL; + if (l2cble_start_conn_update(p_lcb) == TRUE) { + UINT32 time = CalConnectParamTimeout(p_lcb); + btu_start_timer(&p_lcb->upda_con_timer, BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS, time); + } + } + +} + +/******************************************************************************* +** +** Function l2cble_process_sig_cmd +** +** Description This function is called when a signalling packet is received +** on the BLE signalling CID +** +** Returns void +** +*******************************************************************************/ +void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) +{ + UINT8 *p_pkt_end; + UINT8 cmd_code, id; + UINT16 cmd_len; + UINT16 min_interval, max_interval, latency, timeout; + + p_pkt_end = p + pkt_len; + + STREAM_TO_UINT8 (cmd_code, p); + STREAM_TO_UINT8 (id, p); + STREAM_TO_UINT16 (cmd_len, p); + + /* Check command length does not exceed packet length */ + if ((p + cmd_len) > p_pkt_end) { + L2CAP_TRACE_WARNING ("L2CAP - LE - format error, pkt_len: %d cmd_len: %d code: %d", pkt_len, cmd_len, cmd_code); + return; + } + + switch (cmd_code) { + case L2CAP_CMD_REJECT: + case L2CAP_CMD_ECHO_RSP: + case L2CAP_CMD_INFO_RSP: + p += 2; + break; + case L2CAP_CMD_ECHO_REQ: + case L2CAP_CMD_INFO_REQ: + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + break; + + case L2CAP_CMD_BLE_UPDATE_REQ: + STREAM_TO_UINT16 (min_interval, p); /* 0x0006 - 0x0C80 */ + STREAM_TO_UINT16 (max_interval, p); /* 0x0006 - 0x0C80 */ + STREAM_TO_UINT16 (latency, p); /* 0x0000 - 0x03E8 */ + STREAM_TO_UINT16 (timeout, p); /* 0x000A - 0x0C80 */ + /* If we are a master, the slave wants to update the parameters */ + if (p_lcb->link_role == HCI_ROLE_MASTER) { + if (min_interval < BTM_BLE_CONN_INT_MIN || min_interval > BTM_BLE_CONN_INT_MAX || + max_interval < BTM_BLE_CONN_INT_MIN || max_interval > BTM_BLE_CONN_INT_MAX || + latency > BTM_BLE_CONN_LATENCY_MAX || + timeout < BTM_BLE_CONN_SUP_TOUT_MIN || timeout > BTM_BLE_CONN_SUP_TOUT_MAX || + /* The supervision_timeout parameter defines the link supervision timeout for the connection. + The supervision_timeout in milliseconds shall be large than (1 + latency) * max_interval * 2, + where max_interval is given in milliseconds. (See [Vol 6] Part B, Section 4.5.2). + supervision_timeout (mult of 10ms); conn_interval (mult of 1.25ms) + (max_interval * 1.25 * 2) replaced by ((max_interval * 5) >> 1). + */ + ((timeout * 10) < ((1 + latency) *((max_interval * 5) >> 1))) || + max_interval < min_interval) { + l2cu_send_peer_ble_par_rsp (p_lcb, L2CAP_CFG_UNACCEPTABLE_PARAMS, id); + + L2CAP_TRACE_ERROR("slave connection parameters update failed, the parameters are out of range"); + + } else { + + l2cu_send_peer_ble_par_rsp (p_lcb, L2CAP_CFG_OK, id); + p_lcb->waiting_update_conn_min_interval = min_interval; + p_lcb->waiting_update_conn_max_interval = max_interval; + p_lcb->waiting_update_conn_latency = latency; + p_lcb->waiting_update_conn_timeout = timeout; + p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; + + if (l2cble_start_conn_update(p_lcb) == TRUE) { + UINT32 time = CalConnectParamTimeout(p_lcb); + btu_start_timer(&p_lcb->upda_con_timer, BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS, time); + } + } + } else { + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + } + break; + + case L2CAP_CMD_BLE_UPDATE_RSP: { + UINT16 result = 0; + STREAM_TO_UINT16(result, p); //result = 0 connection param accepted, result = 1 connection param rejected. + UINT8 status = (result == 0) ? HCI_SUCCESS : HCI_ERR_PARAM_OUT_OF_RANGE; + if (status != HCI_SUCCESS) { + btu_stop_timer(&p_lcb->upda_con_timer); + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PENDING; + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PARAM_FULL; + l2c_send_update_conn_params_cb(p_lcb, status); + } + break; + } + case L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ: { + tL2C_CCB *p_ccb = NULL; + tL2C_RCB *p_rcb = NULL; + UINT16 spsm; + UINT16 scid; + UINT16 mtu; + UINT16 mps; + UINT16 credits; + STREAM_TO_UINT16(spsm, p); + STREAM_TO_UINT16(scid, p); + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + STREAM_TO_UINT16(credits, p); + L2CAP_TRACE_DEBUG("%s spsm %x, scid %x", __func__, spsm, scid); + UNUSED(spsm); + + p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, scid); + if (p_ccb) { + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED); + break; + } + + #if 0 + p_rcb = l2cu_find_ble_rcb_by_psm(spsm); + if (p_rcb == NULL) { + break; + } + #endif + + p_ccb = l2cu_allocate_ccb(p_lcb, 0); + if (p_ccb == NULL) { + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES); + break; + } + + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = scid; + p_ccb->local_conn_cfg.mtu = mtu; + p_ccb->local_conn_cfg.mps = controller_get_interface()->get_acl_data_size_ble(); + p_ccb->local_conn_cfg.credits = credits; + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + p_ccb->peer_conn_cfg.credits = credits; + + l2cu_send_peer_ble_credit_based_conn_res(p_ccb, L2CAP_LE_RESULT_CONN_OK); + break; + } + case L2CAP_CMD_DISC_REQ: { + tL2C_CCB *p_ccb = NULL; + UINT16 lcid; + UINT16 rcid; + STREAM_TO_UINT16(lcid, p); + STREAM_TO_UINT16(rcid, p); + + p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid); + if (p_ccb) { + p_ccb->remote_id = id; + // TODO + } + + l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid); + break; + } + default: + L2CAP_TRACE_WARNING ("L2CAP - LE - unknown cmd code: %d", cmd_code); + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + return; + } +} + +/******************************************************************************* +** +** Function l2cble_init_direct_conn +** +** Description This function is to initate a direct connection +** +** Returns TRUE connection initiated, FALSE otherwise. +** +*******************************************************************************/ +BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) +{ +#if ( (defined BLE_PRIVACY_SPT) && (BLE_PRIVACY_SPT == TRUE) && (!CONTROLLER_RPA_LIST_ENABLE)) + //check for security device information in the cache + bool dev_rec_exist = true; + tBTM_SEC_DEV_REC *find_dev_rec = btm_find_dev (p_lcb->remote_bd_addr); + if(find_dev_rec == NULL) { + dev_rec_exist = false; + } + +#endif // ( (defined BLE_PRIVACY_SPT) && (BLE_PRIVACY_SPT == TRUE) && (!CONTROLLER_RPA_LIST_ENABLE)) + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (p_lcb->remote_bd_addr); + tBTM_BLE_CB *p_cb = &btm_cb.ble_ctr_cb; + UINT16 scan_int; + UINT16 scan_win; + BD_ADDR peer_addr; + UINT8 peer_addr_type = BLE_ADDR_PUBLIC; + UINT8 own_addr_type = BLE_ADDR_PUBLIC; + + /* There can be only one BLE connection request outstanding at a time */ + if (p_dev_rec == NULL) { + L2CAP_TRACE_WARNING ("unknown device, can not initate connection"); + return (FALSE); + } + + scan_int = (p_cb->scan_int == BTM_BLE_SCAN_PARAM_UNDEF) ? BTM_BLE_SCAN_FAST_INT : p_cb->scan_int; + scan_win = (p_cb->scan_win == BTM_BLE_SCAN_PARAM_UNDEF) ? BTM_BLE_SCAN_FAST_WIN : p_cb->scan_win; + + peer_addr_type = p_lcb->ble_addr_type; + memcpy(peer_addr, p_lcb->remote_bd_addr, BD_ADDR_LEN); + +#if ( (defined BLE_PRIVACY_SPT) && (BLE_PRIVACY_SPT == TRUE)) + own_addr_type = btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type; +#if (!CONTROLLER_RPA_LIST_ENABLE) + if(dev_rec_exist) { + // if the current address information is valid, get the real address information + if(p_dev_rec->ble.current_addr_valid) { + peer_addr_type = p_dev_rec->ble.current_addr_type; + memcpy(peer_addr, p_dev_rec->ble.current_addr, 6); + } else { + /* find security device information but not find the real address information + * This state may be directly open without scanning. In this case, you must + * use the current adv address of the device to open*/ + } + } else { + //not find security device information, We think this is a new device, connect directly + } + + /* It will cause that scanner doesn't send scan request to advertiser + * which has sent IRK to us and we have stored the IRK in controller. + * It is a hardware limitation. The preliminary solution is not to + * send key to the controller, but to resolve the random address in host. + * so we need send the real address information to controller. */ + +#endif // (!CONTROLLER_RPA_LIST_ENABLE) + +#if (CONTROLLER_RPA_LIST_ENABLE) + + if (p_dev_rec->ble.in_controller_list & BTM_RESOLVING_LIST_BIT) { + if (btm_cb.ble_ctr_cb.privacy_mode >= BTM_PRIVACY_1_2) { + own_addr_type |= BLE_ADDR_TYPE_ID_BIT; + } + + //btm_ble_enable_resolving_list(BTM_BLE_RL_INIT); + btm_random_pseudo_to_identity_addr(peer_addr, &peer_addr_type); + } else { + btm_ble_disable_resolving_list(BTM_BLE_RL_INIT, TRUE); + } + +#endif // CONTROLLER_RPA_LIST_ENABLE +#endif // (defined BLE_PRIVACY_SPT) && (BLE_PRIVACY_SPT == TRUE) + + if (!btm_ble_topology_check(BTM_BLE_STATE_INIT)) { + l2cu_release_lcb (p_lcb); + L2CAP_TRACE_ERROR("initate direct connection fail, topology limitation"); + return FALSE; + } + uint32_t link_timeout = L2CAP_BLE_LINK_CONNECT_TOUT; + if(GATTC_CONNECT_RETRY_COUNT) { + if(!p_lcb->retry_create_con) { + p_lcb->start_time_s = (esp_system_get_time()/1000); + } + uint32_t current_time = (esp_system_get_time()/1000); + link_timeout = (L2CAP_BLE_LINK_CONNECT_TOUT*1000 - (current_time - p_lcb->start_time_s))/1000; + + if(link_timeout == 0 || link_timeout > L2CAP_BLE_LINK_CONNECT_TOUT) { + link_timeout = L2CAP_BLE_LINK_CONNECT_TOUT; + } + } + + if (!p_lcb->is_aux) { + if (!btsnd_hcic_ble_create_ll_conn (scan_int,/* UINT16 scan_int */ + scan_win, /* UINT16 scan_win */ + FALSE, /* UINT8 white_list */ + peer_addr_type, /* UINT8 addr_type_peer */ + peer_addr, /* BD_ADDR bda_peer */ + own_addr_type, /* UINT8 addr_type_own */ + (UINT16) ((p_dev_rec->conn_params.min_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? + p_dev_rec->conn_params.min_conn_int : BTM_BLE_CONN_INT_MIN_DEF), /* UINT16 conn_int_min */ + (UINT16) ((p_dev_rec->conn_params.max_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? + p_dev_rec->conn_params.max_conn_int : BTM_BLE_CONN_INT_MAX_DEF), /* UINT16 conn_int_max */ + (UINT16) ((p_dev_rec->conn_params.slave_latency != BTM_BLE_CONN_PARAM_UNDEF) ? + p_dev_rec->conn_params.slave_latency : BTM_BLE_CONN_SLAVE_LATENCY_DEF), /* UINT16 conn_latency */ + (UINT16) ((p_dev_rec->conn_params.supervision_tout != BTM_BLE_CONN_PARAM_UNDEF) ? + p_dev_rec->conn_params.supervision_tout : BTM_BLE_CONN_TIMEOUT_DEF), /* conn_timeout */ + BLE_CE_LEN_MIN, /* UINT16 min_len */ + BLE_CE_LEN_MIN)) { /* UINT16 max_len */ + l2cu_release_lcb (p_lcb); + L2CAP_TRACE_ERROR("initate direct connection fail, no resources"); + return (FALSE); + } else { + p_lcb->link_state = LST_CONNECTING; + l2cb.is_ble_connecting = TRUE; + memcpy (l2cb.ble_connecting_bda, p_lcb->remote_bd_addr, BD_ADDR_LEN); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, link_timeout); + btm_ble_set_conn_st (BLE_DIR_CONN); + + return (TRUE); + } + } else { +#if (BLE_50_FEATURE_SUPPORT == TRUE) + + /* + * 0x00 Public Device Address + * 0x01 Random Device Address + * 0x02 Public Identity Address (corresponds to Resolved Private Address) + * 0x03 Random (static) Identity Address (corresponds to Resolved Private Address) + * 0xFF No address provided (anonymous advertisement) + */ + + if ((peer_addr_type & BLE_ADDR_RANDOM) == BLE_ADDR_RANDOM) { + peer_addr_type = BLE_ADDR_RANDOM; + } else { + peer_addr_type = BLE_ADDR_PUBLIC; + } + + tHCI_CreatExtConn aux_conn = {0}; + aux_conn.filter_policy = FALSE; + aux_conn.own_addr_type = own_addr_type; + aux_conn.peer_addr_type = peer_addr_type; + memcpy(aux_conn.peer_addr, peer_addr, sizeof(BD_ADDR)); + if (p_dev_rec->ext_conn_params.phy_mask == BLE_PHY_NO_PREF) { + L2CAP_TRACE_WARNING("No extend connection parameters set, use default parameters"); + aux_conn.init_phy_mask = BLE_PHY_PREF_MASK; + memcpy(&aux_conn.params[0], &ext_conn_params_1m_phy, sizeof(tHCI_ExtConnParams)); + memcpy(&aux_conn.params[1], &ext_conn_params_2m_phy, sizeof(tHCI_ExtConnParams)); + memcpy(&aux_conn.params[2], &ext_conn_params_coded_phy, sizeof(tHCI_ExtConnParams)); + } else { + aux_conn.init_phy_mask = p_dev_rec->ext_conn_params.phy_mask; + memcpy(&aux_conn.params[0], &p_dev_rec->ext_conn_params.phy_1m_conn_params, sizeof(tHCI_ExtConnParams)); + memcpy(&aux_conn.params[1], &p_dev_rec->ext_conn_params.phy_2m_conn_params, sizeof(tHCI_ExtConnParams)); + memcpy(&aux_conn.params[2], &p_dev_rec->ext_conn_params.phy_coded_conn_params, sizeof(tHCI_ExtConnParams)); + } + p_lcb->link_state = LST_CONNECTING; + l2cb.is_ble_connecting = TRUE; + memcpy (l2cb.ble_connecting_bda, p_lcb->remote_bd_addr, BD_ADDR_LEN); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, link_timeout); + btm_ble_set_conn_st (BLE_DIR_CONN); + if(!btsnd_hcic_ble_create_ext_conn(&aux_conn)) { + l2cu_release_lcb (p_lcb); + L2CAP_TRACE_ERROR("initate Aux connection failed, no resources"); + } +#else + L2CAP_TRACE_ERROR("BLE 5.0 not support!\n"); +#endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) + return (TRUE); + } +} + +/******************************************************************************* +** +** Function l2cble_create_conn +** +** Description This function initiates an acl connection via HCI +** +** Returns TRUE if successful, FALSE if connection not started. +** +*******************************************************************************/ +BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb) +{ + tBTM_BLE_CONN_ST conn_st = btm_ble_get_conn_st(); + BOOLEAN rt = FALSE; + + /* There can be only one BLE connection request outstanding at a time */ + if (conn_st == BLE_CONN_IDLE) { + rt = l2cble_init_direct_conn(p_lcb); + } else { + L2CAP_TRACE_WARNING ("L2CAP - LE - cannot start new connection at conn st: %d", conn_st); + + btm_ble_enqueue_direct_conn_req(p_lcb); + + if (conn_st == BLE_BG_CONN) { + btm_ble_suspend_bg_conn(); + } + + rt = TRUE; + } + return rt; +} + +/******************************************************************************* +** +** Function l2c_link_processs_ble_num_bufs +** +** Description This function is called when a "controller buffer size" +** event is first received from the controller. It updates +** the L2CAP values. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_processs_ble_num_bufs (UINT16 num_lm_ble_bufs) +{ + if (num_lm_ble_bufs == 0) { + num_lm_ble_bufs = L2C_DEF_NUM_BLE_BUF_SHARED; + l2cb.num_lm_acl_bufs -= L2C_DEF_NUM_BLE_BUF_SHARED; + } + L2CAP_TRACE_DEBUG("num_lm_ble_bufs = %d",num_lm_ble_bufs); + l2cb.num_lm_ble_bufs = l2cb.controller_le_xmit_window = num_lm_ble_bufs; +} + +/******************************************************************************* +** +** Function l2c_ble_link_adjust_allocation +** +** Description This function is called when a link is created or removed +** to calculate the amount of packets each link may send to +** the HCI without an ack coming back. +** +** Currently, this is a simple allocation, dividing the +** number of Controller Packets by the number of links. In +** the future, QOS configuration should be examined. +** +** Returns void +** +*******************************************************************************/ +void l2c_ble_link_adjust_allocation (void) +{ + UINT16 qq, qq_remainder; + tL2C_LCB *p_lcb; + UINT16 hi_quota, low_quota; + UINT16 num_lowpri_links = 0; + UINT16 num_hipri_links = 0; + UINT16 controller_xmit_quota = l2cb.num_lm_ble_bufs; + UINT16 high_pri_link_quota = L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A; + list_node_t *p_node = NULL; + + /* If no links active, reset buffer quotas and controller buffers */ + if (l2cb.num_ble_links_active == 0) { + l2cb.controller_le_xmit_window = l2cb.num_lm_ble_bufs; + l2cb.ble_round_robin_quota = l2cb.ble_round_robin_unacked = 0; + return; + } + + /* First, count the links */ + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use && p_lcb->transport == BT_TRANSPORT_LE) { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) { + num_hipri_links++; + } else { + num_lowpri_links++; + } + } + } + + /* now adjust high priority link quota */ + low_quota = num_lowpri_links ? 1 : 0; + while ( (num_hipri_links * high_pri_link_quota + low_quota) > controller_xmit_quota ) { + high_pri_link_quota--; + } + + + /* Work out the xmit quota and buffer quota high and low priorities */ + hi_quota = num_hipri_links * high_pri_link_quota; + low_quota = (hi_quota < controller_xmit_quota) ? controller_xmit_quota - hi_quota : 1; + + /* Work out and save the HCI xmit quota for each low priority link */ + + /* If each low priority link cannot have at least one buffer */ + if (num_lowpri_links > low_quota) { + l2cb.ble_round_robin_quota = low_quota; + qq = qq_remainder = 0; + } + /* If each low priority link can have at least one buffer */ + else if (num_lowpri_links > 0) { + l2cb.ble_round_robin_quota = 0; + l2cb.ble_round_robin_unacked = 0; + qq = low_quota / num_lowpri_links; + qq_remainder = low_quota % num_lowpri_links; + } + /* If no low priority link */ + else { + l2cb.ble_round_robin_quota = 0; + l2cb.ble_round_robin_unacked = 0; + qq = qq_remainder = 0; + } + L2CAP_TRACE_EVENT ("l2c_ble_link_adjust_allocation num_hipri: %u num_lowpri: %u low_quota: %u round_robin_quota: %u qq: %u", + num_hipri_links, num_lowpri_links, low_quota, + l2cb.ble_round_robin_quota, qq); + + /* Now, assign the quotas to each link */ + p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use && p_lcb->transport == BT_TRANSPORT_LE) { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) { + p_lcb->link_xmit_quota = high_pri_link_quota; + } else { + /* Safety check in case we switched to round-robin with something outstanding */ + /* if sent_not_acked is added into round_robin_unacked then don't add it again */ + /* l2cap keeps updating sent_not_acked for exiting from round robin */ + if (( p_lcb->link_xmit_quota > 0 ) && ( qq == 0 )) { + l2cb.ble_round_robin_unacked += p_lcb->sent_not_acked; + } + + p_lcb->link_xmit_quota = qq; + if (qq_remainder > 0) { + p_lcb->link_xmit_quota++; + qq_remainder--; + } + } + + L2CAP_TRACE_EVENT("l2c_ble_link_adjust_allocation Priority: %d XmitQuota: %d", + p_lcb->acl_priority, p_lcb->link_xmit_quota); + + L2CAP_TRACE_EVENT(" SentNotAcked: %d RRUnacked: %d", + p_lcb->sent_not_acked, l2cb.round_robin_unacked); + + /* There is a special case where we have readjusted the link quotas and */ + /* this link may have sent anything but some other link sent packets so */ + /* so we may need a timer to kick off this link's transmissions. */ + if ( (p_lcb->link_state == LST_CONNECTED) + && (!list_is_empty(p_lcb->link_xmit_data_q)) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota) ) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_FLOW_CONTROL_TOUT); + } + } + } +} + +#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cble_process_rc_param_request_evt +** +** Description process LE Remote Connection Parameter Request Event. +** +** Returns void +** +*******************************************************************************/ +void l2cble_process_rc_param_request_evt(UINT16 handle, UINT16 int_min, UINT16 int_max, + UINT16 latency, UINT16 timeout) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_handle (handle); + + if (p_lcb != NULL) { + + /* if update is enabled, always accept connection parameter update */ + if ((p_lcb->conn_update_mask & L2C_BLE_CONN_UPDATE_DISABLE) == 0) { + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; + btsnd_hcic_ble_rc_param_req_reply(handle, int_min, int_max, latency, timeout, BLE_CE_LEN_MIN, BLE_CE_LEN_MIN); + }else { + /* always accept connection parameters request which is sent by itself */ + if (int_max == BTM_BLE_CONN_INT_MIN) { + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; + btsnd_hcic_ble_rc_param_req_reply(handle, int_min, int_max, latency, timeout, BLE_CE_LEN_MIN, BLE_CE_LEN_MIN); + }else { + L2CAP_TRACE_EVENT ("L2CAP - LE - update currently disabled"); + p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; + btsnd_hcic_ble_rc_param_req_neg_reply (handle, HCI_ERR_UNACCEPT_CONN_INTERVAL); + } + } + + } else { + L2CAP_TRACE_WARNING("No link to update connection parameter") + } +} +#endif + +/******************************************************************************* +** +** Function l2cble_update_data_length +** +** Description This function update link tx data length if applicable +** +** Returns void +** +*******************************************************************************/ +void l2cble_update_data_length(tL2C_LCB *p_lcb) +{ + UINT16 tx_mtu = 0; + UINT16 i = 0; + + L2CAP_TRACE_DEBUG("%s", __FUNCTION__); + + /* See if we have a link control block for the connection */ + if (p_lcb == NULL) { + return; + } + + for (i = 0; i < L2CAP_NUM_FIXED_CHNLS; i++) { + if (i + L2CAP_FIRST_FIXED_CHNL != L2CAP_BLE_SIGNALLING_CID) { + if ((p_lcb->p_fixed_ccbs[i] != NULL) && + (tx_mtu < (p_lcb->p_fixed_ccbs[i]->tx_data_len + L2CAP_PKT_OVERHEAD))) { + tx_mtu = p_lcb->p_fixed_ccbs[i]->tx_data_len + L2CAP_PKT_OVERHEAD; + } + } + } + + if (tx_mtu > BTM_BLE_DATA_SIZE_MAX) { + tx_mtu = BTM_BLE_DATA_SIZE_MAX; + } + + /* update TX data length if changed */ + if (p_lcb->tx_data_len != tx_mtu) { + BTM_SetBleDataLength(p_lcb->remote_bd_addr, tx_mtu); + } + +} + +/******************************************************************************* +** +** Function l2cble_process_data_length_change_evt +** +** Description This function process the data length change event +** +** Returns void +** +*******************************************************************************/ +void l2cble_process_data_length_change_event(UINT16 handle, UINT16 tx_data_len, UINT16 rx_data_len) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_handle(handle); + tACL_CONN *p_acl = btm_handle_to_acl(handle); + tBTM_LE_SET_PKT_DATA_LENGTH_PARAMS data_length_params; + + L2CAP_TRACE_DEBUG("%s TX data len = %d", __FUNCTION__, tx_data_len); + if (p_lcb == NULL) { + return; + } + + if (tx_data_len > 0) { + p_lcb->tx_data_len = tx_data_len; + } + + data_length_params.rx_len = rx_data_len; + data_length_params.tx_len = tx_data_len; + + if(p_acl) { + p_acl->data_length_params = data_length_params; + if (p_acl->p_set_pkt_data_cback) { + (*p_acl->p_set_pkt_data_cback)(BTM_SUCCESS, &data_length_params); + } + + p_acl->data_len_updating = false; + if(p_acl->data_len_waiting) { + p_acl->data_len_waiting = false; + p_acl->p_set_pkt_data_cback = p_acl->p_set_data_len_cback_waiting; + p_acl->p_set_data_len_cback_waiting = NULL; + // if value is same, triger callback directly + if(p_acl->tx_len_waiting == p_acl->data_length_params.tx_len) { + if(p_acl->p_set_pkt_data_cback) { + (*p_acl->p_set_pkt_data_cback)(BTM_SUCCESS, &p_acl->data_length_params); + } + return; + } + p_acl->data_len_updating = true; + /* always set the TxTime to be max, as controller does not care for now */ + btsnd_hcic_ble_set_data_length(handle, p_acl->tx_len_waiting, + BTM_BLE_DATA_TX_TIME_MAX); + } + } +} + +/******************************************************************************* +** +** Function l2cble_set_fixed_channel_tx_data_length +** +** Description This function update max fixed channel tx data length if applicable +** +** Returns void +** +*******************************************************************************/ +void l2cble_set_fixed_channel_tx_data_length(BD_ADDR remote_bda, UINT16 fix_cid, UINT16 tx_mtu) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(remote_bda, BT_TRANSPORT_LE); + UINT16 cid = fix_cid - L2CAP_FIRST_FIXED_CHNL; + + L2CAP_TRACE_DEBUG("%s TX MTU = %d", __FUNCTION__, tx_mtu); + + if (!controller_get_interface()->supports_ble_packet_extension()) { + L2CAP_TRACE_WARNING("%s, request not supported", __FUNCTION__); + return; + } + + /* See if we have a link control block for the connection */ + if (p_lcb == NULL) { + return; + } + + if (p_lcb->p_fixed_ccbs[cid] != NULL) { + if (tx_mtu > BTM_BLE_DATA_SIZE_MAX) { + tx_mtu = BTM_BLE_DATA_SIZE_MAX; + } + + p_lcb->p_fixed_ccbs[cid]->tx_data_len = tx_mtu; + } + + l2cble_update_data_length(p_lcb); +} + + +/******************************************************************************* +** +** Function l2c_send_update_conn_params_cb +** +** Description This function send the update connection parameter callback to the uplayer. +** +** Returns void +** +*******************************************************************************/ +void l2c_send_update_conn_params_cb(tL2C_LCB *p_lcb, UINT8 status) +{ + if(conn_param_update_cb.update_conn_param_cb != NULL){ + tBTM_LE_UPDATE_CONN_PRAMS update_param; + //if myself update the connection parameters + if (p_lcb->updating_param_flag){ + update_param.max_conn_int = p_lcb->updating_conn_max_interval; + update_param.min_conn_int = p_lcb->updating_conn_min_interval; + p_lcb->updating_param_flag = false; + }else{ + // remote device update the connection parameters + update_param.max_conn_int = update_param.min_conn_int = 0; + } + // current connection parameters + update_param.conn_int = p_lcb->current_used_conn_interval; + update_param.slave_latency = p_lcb->current_used_conn_latency; + update_param.supervision_tout = p_lcb->current_used_conn_timeout; + + (conn_param_update_cb.update_conn_param_cb)(status, p_lcb->remote_bd_addr, &update_param); + } +} + +/******************************************************************************* +** +** Function CalConnectParamTimeout +** +** Description This function is called to calculate the connection parameter timeout. +** +** Returns timeout +** +*******************************************************************************/ +UINT32 CalConnectParamTimeout(tL2C_LCB *p_lcb) +{ + UINT32 timeout = 6; + if (p_lcb != NULL){ + //1.25 * conn_int *(1+ latency) *32 + timeout = (40 * ( 1 + p_lcb->current_used_conn_latency) * p_lcb->current_used_conn_interval + 1.25 * p_lcb->waiting_update_conn_max_interval + 1000) / 1000; + if (timeout < 1){ + timeout = 1; + }else if (timeout > 120){ + timeout = 120; + } + } + return timeout; +} + +/******************************************************************************* +** +** Function l2cble_credit_based_conn_req +** +** Description This function sends LE Credit Based Connection Request for +** LE connection oriented channels. +** +** Returns void +** +*******************************************************************************/ +void l2cble_credit_based_conn_req (tL2C_CCB *p_ccb) +{ + if (!p_ccb) { + return; + } + + if (p_ccb->p_lcb && p_ccb->p_lcb->transport != BT_TRANSPORT_LE) + { + L2CAP_TRACE_WARNING ("LE link doesn't exist"); + return; + } + + l2cu_send_peer_ble_credit_based_conn_req (p_ccb); + return; +} + +/******************************************************************************* +** +** Function l2cble_credit_based_conn_res +** +** Description This function sends LE Credit Based Connection Response for +** LE connection oriented channels. +** +** Returns void +** +*******************************************************************************/ +void l2cble_credit_based_conn_res (tL2C_CCB *p_ccb, UINT16 result) +{ + if (!p_ccb) { + return; + } + + if (p_ccb->p_lcb && p_ccb->p_lcb->transport != BT_TRANSPORT_LE) + { + L2CAP_TRACE_WARNING ("LE link doesn't exist"); + return; + } + + l2cu_send_peer_ble_credit_based_conn_res (p_ccb, result); + return; +} + +/******************************************************************************* +** +** Function l2cble_send_flow_control_credit +** +** Description This function sends flow control credits for +** LE connection oriented channels. +** +** Returns void +** +*******************************************************************************/ +void l2cble_send_flow_control_credit(tL2C_CCB *p_ccb, UINT16 credit_value) +{ + if (!p_ccb) { + return; + } + + if (p_ccb->p_lcb && p_ccb->p_lcb->transport != BT_TRANSPORT_LE) + { + L2CAP_TRACE_WARNING ("LE link doesn't exist"); + return; + } + + l2cu_send_peer_ble_flow_control_credit(p_ccb, credit_value); + return; + +} + +/******************************************************************************* +** +** Function l2cble_send_peer_disc_req +** +** Description This function sends disconnect request +** to the peer LE device +** +** Returns void +** +*******************************************************************************/ +void l2cble_send_peer_disc_req(tL2C_CCB *p_ccb) +{ + L2CAP_TRACE_DEBUG ("%s",__func__); + if (!p_ccb) { + return; + } + + if (p_ccb->p_lcb && p_ccb->p_lcb->transport != BT_TRANSPORT_LE) + { + L2CAP_TRACE_WARNING ("LE link doesn't exist"); + return; + } + + l2cu_send_peer_ble_credit_based_disconn_req(p_ccb); + return; +} + +#if (SMP_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cble_sec_comp +** +** Description This function is called when security procedure for an LE COC +** link is done +** +** Returns void +** +*******************************************************************************/ +void l2cble_sec_comp(BD_ADDR p_bda, tBT_TRANSPORT transport, void *p_ref_data, UINT8 status) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(p_bda, BT_TRANSPORT_LE); + tL2CAP_SEC_DATA *p_buf = NULL; + UINT8 sec_flag; + UINT8 sec_act; + + if (!p_lcb) + { + L2CAP_TRACE_WARNING ("%s security complete for unknown device", __func__); + return; + } + + sec_act = p_lcb->sec_act; + p_lcb->sec_act = 0; + + if (!fixed_queue_is_empty(p_lcb->le_sec_pending_q)) + { + p_buf = (tL2CAP_SEC_DATA*) fixed_queue_dequeue(p_lcb->le_sec_pending_q, FIXED_QUEUE_MAX_TIMEOUT); + if (!p_buf) + { + L2CAP_TRACE_WARNING ("%s Security complete for request not initiated from L2CAP", + __func__); + return; + } + + if (status != BTM_SUCCESS) + { + (*(p_buf->p_callback))(p_bda, BT_TRANSPORT_LE, p_buf->p_ref_data, status); + } + else + { + if (sec_act == BTM_SEC_ENCRYPT_MITM) + { + BTM_GetSecurityFlagsByTransport(p_bda, &sec_flag, transport); + if (sec_flag & BTM_SEC_FLAG_LKEY_AUTHED) { + (*(p_buf->p_callback))(p_bda, BT_TRANSPORT_LE, p_buf->p_ref_data, status); + } + else + { + L2CAP_TRACE_DEBUG ("%s MITM Protection Not present", __func__); + (*(p_buf->p_callback))(p_bda, BT_TRANSPORT_LE, p_buf->p_ref_data, + BTM_FAILED_ON_SECURITY); + } + } + else + { + L2CAP_TRACE_DEBUG ("%s MITM Protection not required sec_act = %d", + __func__, p_lcb->sec_act); + + (*(p_buf->p_callback))(p_bda, BT_TRANSPORT_LE, p_buf->p_ref_data, status); + } + } + } + else + { + L2CAP_TRACE_WARNING ("%s Security complete for request not initiated from L2CAP", __func__); + return; + } + osi_free(p_buf); + + while (!fixed_queue_is_empty(p_lcb->le_sec_pending_q)) + { + p_buf = (tL2CAP_SEC_DATA*) fixed_queue_dequeue(p_lcb->le_sec_pending_q, FIXED_QUEUE_MAX_TIMEOUT); + + if (status != BTM_SUCCESS) { + (*(p_buf->p_callback))(p_bda, BT_TRANSPORT_LE, p_buf->p_ref_data, status); + } else { + l2ble_sec_access_req(p_bda, p_buf->psm, p_buf->is_originator, + p_buf->p_callback, p_buf->p_ref_data); + } + + osi_free(p_buf); + } +} + +/******************************************************************************* +** +** Function l2ble_sec_access_req +** +** Description This function is called by LE COC link to meet the +** security requirement for the link +** +** Returns TRUE - security procedures are started +** FALSE - failure +** +*******************************************************************************/ +BOOLEAN l2ble_sec_access_req(BD_ADDR bd_addr, UINT16 psm, BOOLEAN is_originator, tL2CAP_SEC_CBACK *p_callback, void *p_ref_data) +{ + L2CAP_TRACE_DEBUG ("%s", __func__); + BOOLEAN status; + tL2C_LCB *p_lcb = NULL; + + if (!p_callback) + { + L2CAP_TRACE_ERROR("%s No callback function", __func__); + return FALSE; + } + + p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE); + + if (!p_lcb) + { + L2CAP_TRACE_ERROR ("%s Security check for unknown device", __func__); + p_callback(bd_addr, BT_TRANSPORT_LE, p_ref_data, BTM_UNKNOWN_ADDR); + return FALSE; + } + + tL2CAP_SEC_DATA *p_buf = (tL2CAP_SEC_DATA*) osi_malloc((UINT16)sizeof(tL2CAP_SEC_DATA)); + if (!p_buf) + { + p_callback(bd_addr, BT_TRANSPORT_LE, p_ref_data, BTM_NO_RESOURCES); + return FALSE; + } + + p_buf->psm = psm; + p_buf->is_originator = is_originator; + p_buf->p_callback = p_callback; + p_buf->p_ref_data = p_ref_data; + fixed_queue_enqueue(p_lcb->le_sec_pending_q, p_buf, FIXED_QUEUE_MAX_TIMEOUT); + status = btm_ble_start_sec_check(bd_addr, psm, is_originator, &l2cble_sec_comp, p_ref_data); + + return status; +} +#endif /* #if (SMP_INCLUDED == TRUE) */ +#endif /* (BLE_INCLUDED == TRUE) */ +/******************************************************************************* +** +** Function L2CA_GetDisconnectReason +** +** Description This function returns the disconnect reason code. +** +** Returns disconnect reason +** +*******************************************************************************/ +UINT16 L2CA_GetDisconnectReason (BD_ADDR remote_bda, tBT_TRANSPORT transport) +{ + tL2C_LCB *p_lcb; + UINT16 reason = 0; + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (remote_bda, transport)) != NULL) { + reason = p_lcb->disc_reason; + } + + L2CAP_TRACE_DEBUG ("L2CA_GetDisconnectReason=%d ", reason); + + return reason; +} diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_csm.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_csm.c new file mode 100644 index 00000000..9abe72e2 --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_csm.c @@ -0,0 +1,1263 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains the L2CAP channel state machine + * + ******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "common/bt_target.h" +#include "stack/hcidefs.h" +#include "stack/hcimsgs.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "btm_int.h" +#include "stack/btu.h" +#include "stack/hcimsgs.h" +#include "osi/allocator.h" + +#if (L2CAP_COC_INCLUDED == TRUE) +/********************************************************************************/ +/* L O C A L F U N C T I O N P R O T O T Y P E S */ +/********************************************************************************/ +static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_orig_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_term_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2cap_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2ca_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_config (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2cap_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2ca_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); + +#if (BT_TRACE_VERBOSE == TRUE) +static char *l2c_csm_get_event_name (UINT16 event); +#endif + +/******************************************************************************* +** +** Function l2c_csm_execute +** +** Description This function executes the state machine. +** +** Returns void +** +*******************************************************************************/ +void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + switch (p_ccb->chnl_state) { + case CST_CLOSED: + l2c_csm_closed (p_ccb, event, p_data); + break; + + case CST_ORIG_W4_SEC_COMP: + l2c_csm_orig_w4_sec_comp (p_ccb, event, p_data); + break; + + case CST_TERM_W4_SEC_COMP: + l2c_csm_term_w4_sec_comp (p_ccb, event, p_data); + break; + + case CST_W4_L2CAP_CONNECT_RSP: + l2c_csm_w4_l2cap_connect_rsp (p_ccb, event, p_data); + break; + + case CST_W4_L2CA_CONNECT_RSP: + l2c_csm_w4_l2ca_connect_rsp (p_ccb, event, p_data); + break; + + case CST_CONFIG: + l2c_csm_config (p_ccb, event, p_data); + break; + + case CST_OPEN: + l2c_csm_open (p_ccb, event, p_data); + break; + + case CST_W4_L2CAP_DISCONNECT_RSP: + l2c_csm_w4_l2cap_disconnect_rsp (p_ccb, event, p_data); + break; + + case CST_W4_L2CA_DISCONNECT_RSP: + l2c_csm_w4_l2ca_disconnect_rsp (p_ccb, event, p_data); + break; + + default: + L2CAP_TRACE_DEBUG("Unhandled event! event = %d", event); + break; + } +} + +/******************************************************************************* +** +** Function l2c_csm_closed +** +** Description This function handles events when the channel is in +** CLOSED state. This state exists only when the link is +** being initially established. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; + UINT16 local_cid = p_ccb->local_cid; + tL2CA_DISCONNECT_IND_CB *disconnect_ind; + tL2CA_CONNECT_CFM_CB *connect_cfm; + + if (p_ccb->p_rcb == NULL) { +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_ERROR ("L2CAP - LCID: 0x%04x st: CLOSED evt: %s p_rcb == NULL", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_ERROR ("L2CAP - LCID: 0x%04x st: CLOSED evt: 0x%04x p_rcb == NULL", p_ccb->local_cid, event); +#endif + return; + } + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: CLOSED evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: CLOSED evt: %d", event); +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); + break; + + case L2CEVT_LP_CONNECT_CFM_NEG: /* Link failed */ + /* Disconnect unless ACL collision and upper layer wants to handle it */ + if (p_ci->status != HCI_ERR_CONNECTION_EXISTS + || !btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + L2CAP_TRACE_API ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, p_ci->status); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, p_ci->status); + } + break; + + case L2CEVT_L2CA_CONNECT_REQ: /* API connect request */ + /* Cancel sniff mode if needed */ + { + tBTM_PM_PWR_MD settings; +// btla-specific ++ + memset((void *)&settings, 0, sizeof(settings)); +// btla-specific -- + settings.mode = BTM_PM_MD_ACTIVE; + /* COVERITY + Event uninit_use_in_call: Using uninitialized value "settings" (field "settings".timeout uninitialized) in call to function "BTM_SetPowerMode" [details] + Event uninit_use_in_call: Using uninitialized value "settings.max" in call to function "BTM_SetPowerMode" [details] + Event uninit_use_in_call: Using uninitialized value "settings.min" in call to function "BTM_SetPowerMode" + // FALSE-POSITIVE error from Coverity test-tool. Please do NOT remove following comment. + // coverity[uninit_use_in_call] False-positive: setting the mode to BTM_PM_MD_ACTIVE only uses settings.mode the other data members of tBTM_PM_PWR_MD are ignored + */ + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } + + /* If sec access does not result in started SEC_COM or COMP_NEG are already processed */ + if (btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb) == BTM_CMD_STARTED) { + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + } + break; + + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + + /* Wait for the info resp in this state before sending connect req (if needed) */ + if (!p_ccb->p_lcb->w4_info_rsp) { + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) { + l2cu_release_ccb (p_ccb); + (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(local_cid, L2CAP_CONN_NO_LINK); + } else { + l2cu_send_peer_connect_req (p_ccb); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + } + } + break; + + case L2CEVT_SEC_COMP_NEG: /* something is really bad with security */ + L2CAP_TRACE_API ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, L2CAP_CONN_TIMEOUT); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_SECURITY_BLOCK); + break; + + case L2CEVT_L2CAP_CONNECT_REQ: /* Peer connect request */ + /* stop link timer to avoid race condition between A2MP, Security, and L2CAP */ + btu_stop_timer (&p_ccb->p_lcb->timer_entry); + + /* Cancel sniff mode if needed */ + { + tBTM_PM_PWR_MD settings; +// btla-specific ++ + memset((void *)&settings, 0, sizeof(settings)); +// btla-specific -- + settings.mode = BTM_PM_MD_ACTIVE; + /* COVERITY + Event uninit_use_in_call: Using uninitialized value "settings" (field "settings".timeout uninitialized) in call to function "BTM_SetPowerMode" [details] + Event uninit_use_in_call: Using uninitialized value "settings.max" in call to function "BTM_SetPowerMode" [details] + Event uninit_use_in_call: Using uninitialized value "settings.min" in call to function "BTM_SetPowerMode" + // FALSE-POSITIVE error from Coverity test-tool. Please do NOT remove following comment. + // coverity[uninit_use_in_call] False-positive: setting the mode to BTM_PM_MD_ACTIVE only uses settings.mode the other data members of tBTM_PM_PWR_MD are ignored + */ + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } + + p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; + if (btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, FALSE, &l2c_link_sec_comp, p_ccb) == BTM_CMD_STARTED) { + /* started the security process, tell the peer to set a longer timer */ + l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0); + } + break; + + case L2CEVT_TIMEOUT: + L2CAP_TRACE_API ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, L2CAP_CONN_TIMEOUT); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_TIMEOUT); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + osi_free (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_release_ccb (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_orig_w4_sec_comp +** +** Description This function handles events when the channel is in +** CST_ORIG_W4_SEC_COMP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_orig_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + tL2CA_CONNECT_CFM_CB *connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: ORIG_W4_SEC_COMP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: ORIG_W4_SEC_COMP evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); + break; + + case L2CEVT_SEC_COMP: /* Security completed success */ + /* Wait for the info resp in this state before sending connect req (if needed) */ + p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + if (!p_ccb->p_lcb->w4_info_rsp) { + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) { + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_NO_LINK); + } else { + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + l2cu_send_peer_connect_req (p_ccb); /* Start Connection */ + } + } + break; + + case L2CEVT_SEC_COMP_NEG: + L2CAP_TRACE_API ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, HCI_ERR_AUTH_FAILURE); + + /* If last channel immediately disconnect the ACL for better security. + Also prevents a race condition between BTM and L2CAP */ + if ( (p_ccb == p_ccb->p_lcb->ccb_queue.p_first_ccb) && (p_ccb == p_ccb->p_lcb->ccb_queue.p_last_ccb) ) { + p_ccb->p_lcb->idle_timeout = 0; + } + + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, HCI_ERR_AUTH_FAILURE); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + osi_free (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_term_w4_sec_comp +** +** Description This function handles events when the channel is in +** CST_TERM_W4_SEC_COMP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_term_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: TERM_W4_SEC_COMP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: TERM_W4_SEC_COMP evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP; + + /* Wait for the info resp in next state before sending connect ind (if needed) */ + if (!p_ccb->p_lcb->w4_info_rsp) { + /* Don't need to get info from peer or already retrieved so continue */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + L2CAP_TRACE_API ("L2CAP - Calling Connect_Ind_Cb(), CID: 0x%04x", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) (p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, + p_ccb->p_rcb->psm, p_ccb->remote_id); + } else { + /* + ** L2CAP Connect Response will be sent out by 3 sec timer expiration + ** because Bluesoleil doesn't respond to L2CAP Information Request. + ** Bluesoleil seems to disconnect ACL link as failure case, because + ** it takes too long (4~7secs) to get response. + ** product version : Bluesoleil 2.1.1.0 EDR Release 060123 + ** stack version : 05.04.11.20060119 + */ + + /* Waiting for the info resp, tell the peer to set a longer timer */ + l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0); + } + break; + + case L2CEVT_SEC_COMP_NEG: + if (((tL2C_CONN_INFO *)p_data)->status == BTM_DELAY_CHECK) { + /* start a timer - encryption change not received before L2CAP connect req */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_DELAY_CHECK_SM4); + } else { + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_SECURITY_BLOCK, 0); + l2cu_release_ccb (p_ccb); + } + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + osi_free (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_TIMEOUT: + /* SM4 related. */ + if (!btsnd_hcic_disconnect (p_ccb->p_lcb->handle, HCI_ERR_AUTH_FAILURE)) { + L2CAP_TRACE_API ("L2CAP - Calling btsnd_hcic_disconnect for handle %i failed", p_ccb->p_lcb->handle); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 1); + } + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, FALSE, &l2c_link_sec_comp, p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2cap_connect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CAP_CONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2cap_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + tL2CA_CONNECT_CFM_CB *connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: W4_L2CAP_CON_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: W4_L2CAP_CON_RSP evt: %d", event); +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + /* Send disc indication unless peer to peer race condition AND normal disconnect */ + /* *((UINT8 *)p_data) != HCI_ERR_PEER_USER happens when peer device try to disconnect for normal reason */ + p_ccb->chnl_state = CST_CLOSED; + if ((p_ccb->flags & CCB_FLAG_NO_RETRY) || !p_data || (*((UINT8 *)p_data) != HCI_ERR_PEER_USER)) { + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", + p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + } + p_ccb->flags |= CCB_FLAG_NO_RETRY; + break; + + case L2CEVT_L2CAP_CONNECT_RSP: /* Got peer connect confirm */ + p_ccb->remote_cid = p_ci->remote_cid; + p_ccb->chnl_state = CST_CONFIG; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + L2CAP_TRACE_API ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Success", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(local_cid, L2CAP_CONN_OK); + break; + + case L2CEVT_L2CAP_CONNECT_RSP_PND: /* Got peer connect pending */ + p_ccb->remote_cid = p_ci->remote_cid; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT_EXT); + if (p_ccb->p_rcb->api.pL2CA_ConnectPnd_Cb) { + L2CAP_TRACE_API ("L2CAP - Calling Connect_Pnd_Cb(), CID: 0x%04x", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_ConnectPnd_Cb)(p_ccb->local_cid); + } + break; + + case L2CEVT_L2CAP_CONNECT_RSP_NEG: /* Peer rejected connection */ + L2CAP_TRACE_API ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Failure Code: %d", p_ccb->local_cid, p_ci->l2cap_result); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, p_ci->l2cap_result); + break; + + case L2CEVT_TIMEOUT: + L2CAP_TRACE_API ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Timeout", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_TIMEOUT); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* If we know peer CID from connect pending, we can send disconnect */ + if (p_ccb->remote_cid != 0) { + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + } else { + l2cu_release_ccb (p_ccb); + } + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + osi_free (p_data); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) { + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_NO_LINK); + } else { + /* We have feature info, so now send peer connect request */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + l2cu_send_peer_connect_req (p_ccb); /* Start Connection */ + } + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2ca_connect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CA_CONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2ca_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: W4_L2CA_CON_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: W4_L2CA_CON_RSP evt: %d", event); +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_CONNECT_RSP: + p_ci = (tL2C_CONN_INFO *)p_data; + + /* Result should be OK or PENDING */ + if ((!p_ci) || (p_ci->l2cap_result == L2CAP_CONN_OK)) { + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_OK, 0); + p_ccb->chnl_state = CST_CONFIG; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + } else { + /* If pending, stay in same state and start extended timer */ + l2cu_send_peer_connect_rsp (p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT_EXT); + } + break; + + case L2CEVT_L2CA_CONNECT_RSP_NEG: + p_ci = (tL2C_CONN_INFO *)p_data; + l2cu_send_peer_connect_rsp (p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_NO_PSM, 0); + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + osi_free (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* We have feature info, so now give the upper layer connect IND */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + L2CAP_TRACE_API ("L2CAP - Calling Connect_Ind_Cb(), CID: 0x%04x", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) (p_ccb->p_lcb->remote_bd_addr, + p_ccb->local_cid, + p_ccb->p_rcb->psm, + p_ccb->remote_id); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_config +** +** Description This function handles events when the channel is in +** CONFIG state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_config (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CAP_CFG_INFO *p_cfg = (tL2CAP_CFG_INFO *)p_data; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + UINT8 cfg_result; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: CONFIG evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: CONFIG evt: %d", event); +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ + + if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK) { + L2CAP_TRACE_EVENT ("L2CAP - Calling Config_Req_Cb(), CID: 0x%04x, C-bit %d", + p_ccb->local_cid, (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT)); + (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); + } else if (cfg_result == L2CAP_PEER_CFG_DISCONNECT) { + /* Disconnect if channels are incompatible */ + L2CAP_TRACE_EVENT ("L2CAP - incompatible configurations disconnect"); + l2cu_disconnect_chnl (p_ccb); + } else { /* Return error to peer so he can renegotiate if possible */ + L2CAP_TRACE_EVENT ("L2CAP - incompatible configurations trying reconfig"); + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + } + break; + + case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */ + l2cu_process_peer_cfg_rsp (p_ccb, p_cfg); + + if (p_cfg->result != L2CAP_CFG_PENDING) { + /* TBD: When config options grow beyong minimum MTU (48 bytes) + * logic needs to be added to handle responses with + * continuation bit set in flags field. + * 1. Send additional config request out until C-bit is cleared in response + */ + p_ccb->config_done |= OB_CFG_DONE; + + if (p_ccb->config_done & IB_CFG_DONE) { + /* Verify two sides are in compatible modes before continuing */ + if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) { + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_WARNING ("L2CAP - Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } + + p_ccb->config_done |= RECONFIG_FLAG; + p_ccb->chnl_state = CST_OPEN; + l2c_link_adjust_chnl_allocation (); + btu_stop_timer (&p_ccb->timer_entry); + + /* If using eRTM and waiting for an ACK, restart the ACK timer */ + if (p_ccb->fcrb.wait_ack) { + l2c_fcr_start_timer(p_ccb); + } + + /* + ** check p_ccb->our_cfg.fcr.mon_tout and p_ccb->our_cfg.fcr.rtrans_tout + ** we may set them to zero when sending config request during renegotiation + */ + if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + && ((p_ccb->our_cfg.fcr.mon_tout == 0) || (p_ccb->our_cfg.fcr.rtrans_tout))) { + l2c_fcr_adj_monitor_retran_timeout (p_ccb); + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.connect_tick_count = osi_time_get_os_boottime_ms(); +#endif + /* See if we can forward anything on the hold queue */ + if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + } + } + + L2CAP_TRACE_API ("L2CAP - Calling Config_Rsp_Cb(), CID: 0x%04x", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_cfg); + break; + + case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer config error rsp */ + /* Disable the Timer */ + btu_stop_timer (&p_ccb->timer_entry); + + /* If failure was channel mode try to renegotiate */ + if (l2c_fcr_renegotiate_chan(p_ccb, p_cfg) == FALSE) { + L2CAP_TRACE_API ("L2CAP - Calling Config_Rsp_Cb(), CID: 0x%04x, Failure: %d", p_ccb->local_cid, p_cfg->result); + (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_cfg); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, TRUE); + break; + + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ + l2cu_process_our_cfg_req (p_ccb, p_cfg); + l2cu_send_peer_config_req (p_ccb, p_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config rsp */ + l2cu_process_our_cfg_rsp (p_ccb, p_cfg); + + /* Not finished if continuation flag is set */ + if ( (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT) || (p_cfg->result == L2CAP_CFG_PENDING) ) { + /* Send intermediate response; remain in cfg state */ + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + break; + } + + /* Local config done; clear cached configuration in case reconfig takes place later */ + p_ccb->peer_cfg.mtu_present = FALSE; + p_ccb->peer_cfg.flush_to_present = FALSE; + p_ccb->peer_cfg.qos_present = FALSE; + + p_ccb->config_done |= IB_CFG_DONE; + + if (p_ccb->config_done & OB_CFG_DONE) { + /* Verify two sides are in compatible modes before continuing */ + if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) { + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_WARNING ("L2CAP - Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } + + p_ccb->config_done |= RECONFIG_FLAG; + p_ccb->chnl_state = CST_OPEN; + l2c_link_adjust_chnl_allocation (); + btu_stop_timer (&p_ccb->timer_entry); + } + + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + + /* If using eRTM and waiting for an ACK, restart the ACK timer */ + if (p_ccb->fcrb.wait_ack) { + l2c_fcr_start_timer(p_ccb); + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.connect_tick_count = osi_time_get_os_boottime_ms(); +#endif + + /* See if we can forward anything on the hold queue */ + if ( (p_ccb->chnl_state == CST_OPEN) && + (!fixed_queue_is_empty(p_ccb->xmit_hold_q))) { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + break; + + case L2CEVT_L2CA_CONFIG_RSP_NEG: /* Upper layer config reject */ + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + L2CAP_TRACE_API ("L2CAP - Calling DataInd_Cb(), CID: 0x%04x", p_ccb->local_cid); +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_ccb->local_cid >= L2CAP_FIRST_FIXED_CHNL && + p_ccb->local_cid <= L2CAP_LAST_FIXED_CHNL) { + if (p_ccb->local_cid < L2CAP_BASE_APPL_CID) { + if (l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) { + (*l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) + (p_ccb->local_cid, p_ccb->p_lcb->remote_bd_addr, (BT_HDR *)p_data); + } else { + osi_free (p_data); + } + break; + } + } +#endif + (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR *)p_data); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + if (p_ccb->config_done & OB_CFG_DONE) { + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data); + } else { + osi_free (p_data); + } + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", + p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_open +** +** Description This function handles events when the channel is in +** OPEN state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + UINT16 local_cid = p_ccb->local_cid; + tL2CAP_CFG_INFO *p_cfg; + tL2C_CHNL_STATE tempstate; + UINT8 tempcfgdone; + UINT8 cfg_result; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: OPEN evt: %s", + p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: OPEN evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", + p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + if (p_ccb->p_rcb) { + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(local_cid, FALSE); + } + break; + + case L2CEVT_LP_QOS_VIOLATION_IND: /* QOS violation */ + /* Tell upper layer. If service guaranteed, then clear the channel */ + if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb) { + (*p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb)(p_ccb->p_lcb->remote_bd_addr); + } + break; + + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ + p_cfg = (tL2CAP_CFG_INFO *)p_data; + + tempstate = p_ccb->chnl_state; + tempcfgdone = p_ccb->config_done; + p_ccb->chnl_state = CST_CONFIG; + p_ccb->config_done &= ~CFG_DONE_MASK; + + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + + if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK) { + (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); + } + + /* Error in config parameters: reset state and config flag */ + else if (cfg_result == L2CAP_PEER_CFG_UNACCEPTABLE) { + btu_stop_timer(&p_ccb->timer_entry); + p_ccb->chnl_state = tempstate; + p_ccb->config_done = tempcfgdone; + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + } else { /* L2CAP_PEER_CFG_DISCONNECT */ + /* Disconnect if channels are incompatible + * Note this should not occur if reconfigure + * since this should have never passed original config. + */ + l2cu_disconnect_chnl (p_ccb); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ +// btla-specific ++ + /* Make sure we are not in sniff mode */ + { + tBTM_PM_PWR_MD settings; + memset((void *)&settings, 0, sizeof(settings)); + settings.mode = BTM_PM_MD_ACTIVE; + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } +// btla-specific -- + + p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, TRUE); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + if ((p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_DataInd_Cb)) { + (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR *)p_data); + } + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* Make sure we are not in sniff mode */ + { + tBTM_PM_PWR_MD settings; + memset((void *)&settings, 0, sizeof(settings)); + settings.mode = BTM_PM_MD_ACTIVE; + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } + + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data); + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + break; + + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ + p_ccb->chnl_state = CST_CONFIG; + p_ccb->config_done &= ~CFG_DONE_MASK; + l2cu_process_our_cfg_req (p_ccb, (tL2CAP_CFG_INFO *)p_data); + l2cu_send_peer_config_req (p_ccb, (tL2CAP_CFG_INFO *)p_data); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_TIMEOUT: + /* Process the monitor/retransmission time-outs in flow control/retrans mode */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { + l2c_fcr_proc_tout (p_ccb); + } + break; + + case L2CEVT_ACK_TIMEOUT: + l2c_fcr_proc_ack_tout (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2cap_disconnect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CAP_DISCONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2cap_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_CFM_CB *disconnect_cfm = p_ccb->p_rcb->api.pL2CA_DisconnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: W4_L2CAP_DISC_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: W4_L2CAP_DISC_RSP evt: %d", event); +#endif + + switch (event) { + case L2CEVT_L2CAP_DISCONNECT_RSP: /* Peer disconnect response */ + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) { + L2CAP_TRACE_API ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnect request */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) { + L2CAP_TRACE_API ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); + } + break; + + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + case L2CEVT_TIMEOUT: /* Timeout */ + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) { + L2CAP_TRACE_API ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_TIMEOUT); + } + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + osi_free (p_data); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2ca_disconnect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CA_DISCONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2ca_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT ("L2CAP - LCID: 0x%04x st: W4_L2CA_DISC_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT ("L2CAP - st: W4_L2CA_DISC_RSP evt: %d", event); +#endif + + switch (event) { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + L2CAP_TRACE_API ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper disconnect request */ + case L2CEVT_L2CA_DISCONNECT_RSP: /* Upper disconnect response */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + osi_free (p_data); + break; + } +} +#endif /// (L2CAP_COC_INCLUDED == TRUE) + +#if (BT_TRACE_VERBOSE == TRUE) +/******************************************************************************* +** +** Function l2c_csm_get_event_name +** +** Description This function returns the event name. +** +** NOTE conditionally compiled to save memory. +** +** Returns pointer to the name +** +*******************************************************************************/ +static char *l2c_csm_get_event_name (UINT16 event) +{ + switch (event) { + case L2CEVT_LP_CONNECT_CFM: /* Lower layer connect confirm */ + return ("LOWER_LAYER_CONNECT_CFM"); + case L2CEVT_LP_CONNECT_CFM_NEG: /* Lower layer connect confirm (failed) */ + return ("LOWER_LAYER_CONNECT_CFM_NEG"); + case L2CEVT_LP_CONNECT_IND: /* Lower layer connect indication */ + return ("LOWER_LAYER_CONNECT_IND"); + case L2CEVT_LP_DISCONNECT_IND: /* Lower layer disconnect indication */ + return ("LOWER_LAYER_DISCONNECT_IND"); + case L2CEVT_LP_QOS_CFM: /* Lower layer QOS confirmation */ + return ("LOWER_LAYER_QOS_CFM"); + case L2CEVT_LP_QOS_CFM_NEG: /* Lower layer QOS confirmation (failed)*/ + return ("LOWER_LAYER_QOS_CFM_NEG"); + case L2CEVT_LP_QOS_VIOLATION_IND: /* Lower layer QOS violation indication */ + return ("LOWER_LAYER_QOS_VIOLATION_IND"); + + case L2CEVT_SEC_COMP: /* Security cleared successfully */ + return ("SECURITY_COMPLETE"); + case L2CEVT_SEC_COMP_NEG: /* Security procedure failed */ + return ("SECURITY_COMPLETE_NEG"); + + case L2CEVT_L2CAP_CONNECT_REQ: /* Peer connection request */ + return ("PEER_CONNECT_REQ"); + case L2CEVT_L2CAP_CONNECT_RSP: /* Peer connection response */ + return ("PEER_CONNECT_RSP"); + case L2CEVT_L2CAP_CONNECT_RSP_PND: /* Peer connection response pending */ + return ("PEER_CONNECT_RSP_PND"); + case L2CEVT_L2CAP_CONNECT_RSP_NEG: /* Peer connection response (failed) */ + return ("PEER_CONNECT_RSP_NEG"); + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer configuration request */ + return ("PEER_CONFIG_REQ"); + case L2CEVT_L2CAP_CONFIG_RSP: /* Peer configuration response */ + return ("PEER_CONFIG_RSP"); + case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer configuration response (failed) */ + return ("PEER_CONFIG_RSP_NEG"); + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnect request */ + return ("PEER_DISCONNECT_REQ"); + case L2CEVT_L2CAP_DISCONNECT_RSP: /* Peer disconnect response */ + return ("PEER_DISCONNECT_RSP"); + case L2CEVT_L2CAP_DATA: /* Peer data */ + return ("PEER_DATA"); + + case L2CEVT_L2CA_CONNECT_REQ: /* Upper layer connect request */ + return ("UPPER_LAYER_CONNECT_REQ"); + case L2CEVT_L2CA_CONNECT_RSP: /* Upper layer connect response */ + return ("UPPER_LAYER_CONNECT_RSP"); + case L2CEVT_L2CA_CONNECT_RSP_NEG: /* Upper layer connect response (failed)*/ + return ("UPPER_LAYER_CONNECT_RSP_NEG"); + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config request */ + return ("UPPER_LAYER_CONFIG_REQ"); + case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config response */ + return ("UPPER_LAYER_CONFIG_RSP"); + case L2CEVT_L2CA_CONFIG_RSP_NEG: /* Upper layer config response (failed) */ + return ("UPPER_LAYER_CONFIG_RSP_NEG"); + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper layer disconnect request */ + return ("UPPER_LAYER_DISCONNECT_REQ"); + case L2CEVT_L2CA_DISCONNECT_RSP: /* Upper layer disconnect response */ + return ("UPPER_LAYER_DISCONNECT_RSP"); + case L2CEVT_L2CA_DATA_READ: /* Upper layer data read */ + return ("UPPER_LAYER_DATA_READ"); + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data write */ + return ("UPPER_LAYER_DATA_WRITE"); + case L2CEVT_TIMEOUT: /* Timeout */ + return ("TIMEOUT"); + case L2CEVT_SEC_RE_SEND_CMD: + return ("SEC_RE_SEND_CMD"); + case L2CEVT_L2CAP_INFO_RSP: /* Peer information response */ + return ("L2CEVT_L2CAP_INFO_RSP"); + case L2CEVT_ACK_TIMEOUT: + return ("L2CEVT_ACK_TIMEOUT"); + + default: + return ("???? UNKNOWN EVENT"); + } +} +#endif /* (BT_TRACE_VERBOSE == TRUE) */ + + +/******************************************************************************* +** +** Function l2c_enqueue_peer_data +** +** Description Enqueues data destined for the peer in the ccb. Handles +** FCR segmentation and checks for congestion. +** +** Returns void +** +*******************************************************************************/ +void l2c_enqueue_peer_data (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + UINT8 *p; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { + p_buf->event = 0; + } else { + /* Save the channel ID for faster counting */ + p_buf->event = p_ccb->local_cid; + + /* Step back to add the L2CAP header */ + p_buf->offset -= L2CAP_PKT_OVERHEAD; + p_buf->len += L2CAP_PKT_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* Now the L2CAP header */ + UINT16_TO_STREAM (p, p_buf->len - L2CAP_PKT_OVERHEAD); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + } + + fixed_queue_enqueue(p_ccb->xmit_hold_q, p_buf, FIXED_QUEUE_MAX_TIMEOUT); + + l2cu_check_channel_congestion (p_ccb); + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* if new packet is higher priority than serving ccb and it is not overrun */ + if (( p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority ) + && ( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0)) { + /* send out higher priority packet */ + p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority; + } +#endif + + /* if we are doing a round robin scheduling, set the flag */ + if (p_ccb->p_lcb->link_xmit_quota == 0) { + l2cb.check_round_robin = TRUE; + } +} diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_fcr.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_fcr.c new file mode 100644 index 00000000..1da2459d --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_fcr.c @@ -0,0 +1,2229 @@ +/****************************************************************************** + * + * Copyright (C) 2004-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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains the L2CAP 1.2 Flow Control and retransmissions + * functions + * + ******************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "common/bt_trace.h" +#include "stack/bt_types.h" +#include "stack/hcimsgs.h" +#include "stack/l2c_api.h" +#include "l2c_int.h" +#include "stack/l2cdefs.h" +#include "stack/btm_api.h" +#include "btm_int.h" +#include "stack/btu.h" +#include "osi/allocator.h" + +#if (L2CAP_COC_INCLUDED == TRUE) + +/* Flag passed to retransmit_i_frames() when all packets should be retransmitted */ +#define L2C_FCR_RETX_ALL_PKTS 0xFF + +#if BT_TRACE_VERBOSE == TRUE +static char *SAR_types[] = { "Unsegmented", "Start", "End", "Continuation" }; +static char *SUP_types[] = { "RR", "REJ", "RNR", "SREJ" }; +#endif + +/* Look-up table for the CRC calculation */ +static const unsigned short crctab[256] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, +}; + + +/******************************************************************************* +** Static local functions +*/ +static BOOLEAN process_reqseq (tL2C_CCB *p_ccb, UINT16 ctrl_word); +static void process_s_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word); +static void process_i_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word, BOOLEAN delay_ack); +static BOOLEAN retransmit_i_frames (tL2C_CCB *p_ccb, UINT8 tx_seq); +static void prepare_I_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, BOOLEAN is_retransmission); +static void process_stream_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf); +static BOOLEAN do_sar_reassembly (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word); + +#if (L2CAP_ERTM_STATS == TRUE) +static void l2c_fcr_collect_ack_delay (tL2C_CCB *p_ccb, UINT8 num_bufs_acked); +#endif + +/******************************************************************************* +** +** Function l2c_fcr_updcrc +** +** Description This function computes the CRC using the look-up table. +** +** Returns CRC +** +*******************************************************************************/ +static unsigned short l2c_fcr_updcrc(unsigned short icrc, unsigned char *icp, int icnt) +{ + register unsigned short crc = icrc; + register unsigned char *cp = icp; + register int cnt = icnt; + + while (cnt--) { + crc = ((crc >> 8) & 0xff) ^ crctab[(crc & 0xff) ^ *cp++]; + } + + return (crc); +} + + +/******************************************************************************* +** +** Function l2c_fcr_tx_get_fcs +** +** Description This function computes the CRC for a frame to be TXed. +** +** Returns CRC +** +*******************************************************************************/ +static UINT16 l2c_fcr_tx_get_fcs (BT_HDR *p_buf) +{ + UINT8 *p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + + return (l2c_fcr_updcrc (L2CAP_FCR_INIT_CRC, p, p_buf->len)); +} + +/******************************************************************************* +** +** Function l2c_fcr_rx_get_fcs +** +** Description This function computes the CRC for a received frame. +** +** Returns CRC +** +*******************************************************************************/ +static UINT16 l2c_fcr_rx_get_fcs (BT_HDR *p_buf) +{ + UINT8 *p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + + /* offset points past the L2CAP header, but the CRC check includes it */ + p -= L2CAP_PKT_OVERHEAD; + + return (l2c_fcr_updcrc (L2CAP_FCR_INIT_CRC, p, p_buf->len + L2CAP_PKT_OVERHEAD)); +} + +/******************************************************************************* +** +** Function l2c_fcr_start_timer +** +** Description This function starts the (monitor or retransmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_start_timer (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + UINT32 tout; + + /* The timers which are in milliseconds */ + if (p_ccb->fcrb.wait_ack) { + tout = (UINT32)p_ccb->our_cfg.fcr.mon_tout; + } else { + tout = (UINT32)p_ccb->our_cfg.fcr.rtrans_tout; + } + + /* Only start a timer that was not started */ + if (p_ccb->fcrb.mon_retrans_timer.in_use == 0) { + btu_start_quick_timer (&p_ccb->fcrb.mon_retrans_timer, BTU_TTYPE_L2CAP_CHNL, tout * QUICK_TIMER_TICKS_PER_SEC / 1000); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_stop_timer +** +** Description This function stops the (monitor or transmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_stop_timer (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + if (p_ccb->fcrb.mon_retrans_timer.in_use) { + btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_free_timer +** +** Description This function releases the (monitor or transmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_free_timer (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + btu_free_quick_timer (&p_ccb->fcrb.mon_retrans_timer); +} + +/******************************************************************************* +** +** Function l2c_fcr_cleanup +** +** Description This function cleans up the variable used for flow-control/retrans. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_cleanup (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + + l2c_fcr_stop_timer (p_ccb); + + if (p_fcrb->p_rx_sdu) { + osi_free(p_fcrb->p_rx_sdu); + p_fcrb->p_rx_sdu = NULL; + } + + + fixed_queue_free(p_fcrb->waiting_for_ack_q, osi_free_func); + p_fcrb->waiting_for_ack_q = NULL; + + fixed_queue_free(p_fcrb->srej_rcv_hold_q, osi_free_func); + p_fcrb->srej_rcv_hold_q = NULL; + + fixed_queue_free(p_fcrb->retrans_q, osi_free_func); + p_fcrb->retrans_q = NULL; + + btu_free_quick_timer (&p_fcrb->ack_timer); + memset(&p_fcrb->ack_timer, 0, sizeof(TIMER_LIST_ENT)); + + btu_free_quick_timer (&p_ccb->fcrb.mon_retrans_timer); + memset(&p_fcrb->mon_retrans_timer, 0, sizeof(TIMER_LIST_ENT)); + +#if (L2CAP_ERTM_STATS == TRUE) + if ( (p_ccb->local_cid >= L2CAP_BASE_APPL_CID) && (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) ) { + UINT32 dur = osi_time_get_os_boottime_ms() - p_ccb->fcrb.connect_tick_count; + char *p_str = (char *)osi_malloc(120); + UINT16 i; + UINT32 throughput_avg, ack_delay_avg, ack_q_count_avg; + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "--- L2CAP ERTM Stats for CID: 0x%04x Duration: %08ums", p_ccb->local_cid, dur); + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "Retransmissions:%08u Times Flow Controlled:%08u Retrans Touts:%08u Ack Touts:%08u", + p_ccb->fcrb.pkts_retransmitted, p_ccb->fcrb.xmit_window_closed, p_ccb->fcrb.retrans_touts, p_ccb->fcrb.xmit_ack_touts); + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "Times there is less than 2 packets in controller when flow controlled:%08u", p_ccb->fcrb.controller_idle); + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "max_held_acks:%08u, in_cfg.fcr.tx_win_sz:%08u", p_ccb->fcrb.max_held_acks, p_ccb->peer_cfg.fcr.tx_win_sz ); + if (p_str) { + sprintf(p_str, "Sent Pkts:%08u Bytes:%10u(%06u/sec) RR:%08u REJ:%08u RNR:%08u SREJ:%08u", + p_ccb->fcrb.ertm_pkt_counts[0], p_ccb->fcrb.ertm_byte_counts[0], + (dur >= 10 ? (p_ccb->fcrb.ertm_byte_counts[0] * 100) / (dur / 10) : 0), + p_ccb->fcrb.s_frames_sent[0], p_ccb->fcrb.s_frames_sent[1], p_ccb->fcrb.s_frames_sent[2], p_ccb->fcrb.s_frames_sent[3]); + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + sprintf(p_str, "Rcvd Pkts:%08u Bytes:%10u(%06u/sec) RR:%08u REJ:%08u RNR:%08u SREJ:%08u", + p_ccb->fcrb.ertm_pkt_counts[1], p_ccb->fcrb.ertm_byte_counts[1], + (dur >= 10 ? (p_ccb->fcrb.ertm_byte_counts[1] * 100) / (dur / 10) : 0), + p_ccb->fcrb.s_frames_rcvd[0], p_ccb->fcrb.s_frames_rcvd[1], p_ccb->fcrb.s_frames_rcvd[2], p_ccb->fcrb.s_frames_rcvd[3]); + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + throughput_avg = 0; + ack_delay_avg = 0; + ack_q_count_avg = 0; + + for (i = 0; i < L2CAP_ERTM_STATS_NUM_AVG; i++ ) { + if (i == p_ccb->fcrb.ack_delay_avg_index ) { + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "[%02u] collecting data ...", i ); + continue; + } + + sprintf(p_str, "[%02u] throughput: %5u, ack_delay avg:%3u, min:%3u, max:%3u, ack_q_count avg:%3u, min:%3u, max:%3u", + i, p_ccb->fcrb.throughput[i], + p_ccb->fcrb.ack_delay_avg[i], p_ccb->fcrb.ack_delay_min[i], p_ccb->fcrb.ack_delay_max[i], + p_ccb->fcrb.ack_q_count_avg[i], p_ccb->fcrb.ack_q_count_min[i], p_ccb->fcrb.ack_q_count_max[i] ); + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + throughput_avg += p_ccb->fcrb.throughput[i]; + ack_delay_avg += p_ccb->fcrb.ack_delay_avg[i]; + ack_q_count_avg += p_ccb->fcrb.ack_q_count_avg[i]; + } + + throughput_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + ack_delay_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + ack_q_count_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "throughput_avg: %8u (kbytes/sec), ack_delay_avg: %8u ms, ack_q_count_avg: %8u", + throughput_avg, ack_delay_avg, ack_q_count_avg ); + + osi_free(p_str); + } + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "---"); + } +#endif + + memset (p_fcrb, 0, sizeof (tL2C_FCRB)); +} + +/******************************************************************************* +** +** Function l2c_fcr_clone_buf +** +** Description This function allocates and copies requested part of a buffer +** at a new-offset. +** +** Returns pointer to new buffer +** +*******************************************************************************/ +BT_HDR *l2c_fcr_clone_buf (BT_HDR *p_buf, UINT16 new_offset, UINT16 no_of_bytes) +{ + assert(p_buf != NULL); + /* + * NOTE: We allocate extra L2CAP_FCS_LEN octets, in case we need to put + * the FCS (Frame Check Sequence) at the end of the buffer. + */ + uint16_t buf_size = no_of_bytes + sizeof(BT_HDR) + new_offset + L2CAP_FCS_LEN; +#if (L2CAP_ERTM_STATS == TRUE) + /* + * NOTE: If L2CAP_ERTM_STATS is enabled, we need 4 extra octets at the + * end for a timestamp at the end of an I-frame. + */ + buf_size += sizeof(uint32_t); +#endif + BT_HDR *p_buf2 = (BT_HDR *)osi_malloc(buf_size); + + p_buf2->offset = new_offset; + p_buf2->len = no_of_bytes; + memcpy(((UINT8 *)(p_buf2 + 1)) + p_buf2->offset, + ((UINT8 *)(p_buf + 1)) + p_buf->offset, + no_of_bytes); + + return (p_buf2); +} + +/******************************************************************************* +** +** Function l2c_fcr_is_flow_controlled +** +** Description This function checks if the CCB is flow controlled by peer. +** +** Returns The control word +** +*******************************************************************************/ +BOOLEAN l2c_fcr_is_flow_controlled (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { + /* Check if remote side flowed us off or the transmit window is full */ + if ( (p_ccb->fcrb.remote_busy == TRUE) + || (fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q) >= p_ccb->peer_cfg.fcr.tx_win_sz) ) { +#if (L2CAP_ERTM_STATS == TRUE) + if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + p_ccb->fcrb.xmit_window_closed++; + + if ((p_ccb->p_lcb->sent_not_acked < 2) && (l2cb.controller_xmit_window > 0)) { + p_ccb->fcrb.controller_idle++; + } + } +#endif + return (TRUE); + } + } + return (FALSE); +} + +/******************************************************************************* +** +** Function prepare_I_frame +** +** Description This function sets the FCR variables in an I-frame that is +** about to be sent to HCI for transmission. This may be the +** first time the I-frame is sent, or a retransmission +** +** Returns - +** +*******************************************************************************/ +static void prepare_I_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, BOOLEAN is_retransmission) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 *p; + UINT16 fcs; + UINT16 ctrl_word; + BOOLEAN set_f_bit = p_fcrb->send_f_rsp; + + p_fcrb->send_f_rsp = FALSE; + + if (is_retransmission) { + /* Get the old control word and clear out the old req_seq and F bits */ + p = ((UINT8 *) (p_buf + 1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + ctrl_word &= ~(L2CAP_FCR_REQ_SEQ_BITS + L2CAP_FCR_F_BIT); + } else { + ctrl_word = p_buf->layer_specific & L2CAP_FCR_SEG_BITS; /* SAR bits */ + ctrl_word |= (p_fcrb->next_tx_seq << L2CAP_FCR_TX_SEQ_BITS_SHIFT); /* Tx Seq */ + + p_fcrb->next_tx_seq = (p_fcrb->next_tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + } + + /* Set the F-bit and reqseq only if using re-transmission mode */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { + if (set_f_bit) { + ctrl_word |= L2CAP_FCR_F_BIT; + } + + ctrl_word |= (p_fcrb->next_seq_expected) << L2CAP_FCR_REQ_SEQ_BITS_SHIFT; + + p_fcrb->last_ack_sent = p_ccb->fcrb.next_seq_expected; + + if (p_ccb->fcrb.ack_timer.in_use) { + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + } + + /* Set the control word */ + p = ((UINT8 *) (p_buf + 1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + UINT16_TO_STREAM (p, ctrl_word); + + /* Compute the FCS and add to the end of the buffer if not bypassed */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + /* length field in l2cap header has to include FCS length */ + p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + UINT16_TO_STREAM (p, p_buf->len + L2CAP_FCS_LEN - L2CAP_PKT_OVERHEAD); + + /* Calculate the FCS */ + fcs = l2c_fcr_tx_get_fcs(p_buf); + + /* Point to the end of the buffer and put the FCS there */ + p = ((UINT8 *) (p_buf + 1)) + p_buf->offset + p_buf->len; + + UINT16_TO_STREAM (p, fcs); + + p_buf->len += L2CAP_FCS_LEN; + } + +#if BT_TRACE_VERBOSE == TRUE + if (is_retransmission) { + L2CAP_TRACE_EVENT ("L2CAP eRTM ReTx I-frame CID: 0x%04x Len: %u SAR: %s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } else { + L2CAP_TRACE_EVENT ("L2CAP eRTM Tx I-frame CID: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } +#endif + + /* Start the retransmission timer if not already running */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { + l2c_fcr_start_timer (p_ccb); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_send_S_frame +** +** Description This function formats and sends an S-frame for transmission. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_send_S_frame (tL2C_CCB *p_ccb, UINT16 function_code, UINT16 pf_bit) +{ + assert(p_ccb != NULL); + BT_HDR *p_buf; + UINT8 *p; + UINT16 ctrl_word; + UINT16 fcs; + + if ((!p_ccb->in_use) || (p_ccb->chnl_state != CST_OPEN)) { + return; + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.s_frames_sent[function_code]++; +#endif + + if (pf_bit == L2CAP_FCR_P_BIT) { + p_ccb->fcrb.wait_ack = TRUE; + + l2c_fcr_stop_timer (p_ccb); /* Restart the monitor timer */ + l2c_fcr_start_timer (p_ccb); + } + + /* Create the control word to use */ + ctrl_word = (function_code << L2CAP_FCR_SUP_SHIFT) | L2CAP_FCR_S_FRAME_BIT; + ctrl_word |= (p_ccb->fcrb.next_seq_expected << L2CAP_FCR_REQ_SEQ_BITS_SHIFT); + ctrl_word |= pf_bit; + + if ((p_buf = (BT_HDR *)osi_malloc(L2CAP_CMD_BUF_SIZE)) != NULL) { + p_buf->offset = HCI_DATA_PREAMBLE_SIZE; + p_buf->len = L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* Put in the L2CAP header */ + UINT16_TO_STREAM (p, L2CAP_FCR_OVERHEAD + L2CAP_FCS_LEN); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, ctrl_word); + + /* Compute the FCS and add to the end of the buffer if not bypassed */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + fcs = l2c_fcr_tx_get_fcs (p_buf); + + UINT16_TO_STREAM (p, fcs); + p_buf->len += L2CAP_FCS_LEN; + } else { /* rewrite the length without FCS length */ + p -= 6; + UINT16_TO_STREAM (p, L2CAP_FCR_OVERHEAD); + } + + /* Now, the HCI transport header */ + p_buf->layer_specific = L2CAP_NON_FLUSHABLE_PKT; + l2cu_set_acl_hci_header (p_buf, p_ccb); + +#if BT_TRACE_VERBOSE == TRUE + if ((((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 1) + || (((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 3)) { + L2CAP_TRACE_WARNING ("L2CAP eRTM Tx S-frame CID: 0x%04x ctrlword: 0x%04x Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, ctrl_word, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + L2CAP_TRACE_WARNING (" Buf Len: %u", p_buf->len); + } else { + L2CAP_TRACE_EVENT ("L2CAP eRTM Tx S-frame CID: 0x%04x ctrlword: 0x%04x Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, ctrl_word, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + L2CAP_TRACE_EVENT (" Buf Len: %u", p_buf->len); + } +#endif /* BT_TRACE_VERBOSE */ + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); + + p_ccb->fcrb.last_ack_sent = p_ccb->fcrb.next_seq_expected; + + if (p_ccb->fcrb.ack_timer.in_use) { + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + } else { + L2CAP_TRACE_ERROR ("l2c_fcr_send_S_frame(No Resources) cid 0x%04x, Type: 0x%4x", + p_ccb->local_cid, function_code); + } +} + + +/******************************************************************************* +** +** Function l2c_fcr_proc_pdu +** +** Description This function is the entry point for processing of a +** received PDU when in flow control and/or retransmission modes. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_pdu (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + UINT8 *p; + UINT16 fcs; + UINT16 min_pdu_len; + UINT16 ctrl_word; + + /* Check the length */ + min_pdu_len = (p_ccb->bypass_fcs == L2CAP_BYPASS_FCS) ? + (UINT16)L2CAP_FCR_OVERHEAD : (UINT16)(L2CAP_FCS_LEN + L2CAP_FCR_OVERHEAD); + + if (p_buf->len < min_pdu_len) { + L2CAP_TRACE_WARNING ("Rx L2CAP PDU: CID: 0x%04x Len too short: %u", p_ccb->local_cid, p_buf->len); + osi_free (p_buf); + return; + } + + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_STREAM_MODE) { + process_stream_frame (p_ccb, p_buf); + return; + } + +#if BT_TRACE_VERBOSE == TRUE + /* Get the control word */ + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset; + STREAM_TO_UINT16 (ctrl_word, p); + + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) { + if ((((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 1) + || (((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 3)) { + /* REJ or SREJ */ + L2CAP_TRACE_WARNING ("L2CAP eRTM Rx S-frame: cid: 0x%04x Len: %u Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, p_buf->len, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } else { + L2CAP_TRACE_EVENT ("L2CAP eRTM Rx S-frame: cid: 0x%04x Len: %u Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, p_buf->len, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + } else { + L2CAP_TRACE_EVENT ("L2CAP eRTM Rx I-frame: cid: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + + L2CAP_TRACE_EVENT (" eRTM Rx Nxt_tx_seq %u, Lst_rx_ack %u, Nxt_seq_exp %u, Lst_ack_snt %u, wt_q.cnt %u, tries %u", + p_ccb->fcrb.next_tx_seq, p_ccb->fcrb.last_rx_ack, + p_ccb->fcrb.next_seq_expected, + p_ccb->fcrb.last_ack_sent, + fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q), + p_ccb->fcrb.num_tries); + +#endif /* BT_TRACE_VERBOSE */ + + /* Verify FCS if using */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset + p_buf->len - L2CAP_FCS_LEN; + + /* Extract and drop the FCS from the packet */ + STREAM_TO_UINT16 (fcs, p); + p_buf->len -= L2CAP_FCS_LEN; + + if (l2c_fcr_rx_get_fcs(p_buf) != fcs) { + L2CAP_TRACE_WARNING ("Rx L2CAP PDU: CID: 0x%04x BAD FCS", p_ccb->local_cid); + osi_free(p_buf); + return; + } + } + + /* Get the control word */ + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset; + + STREAM_TO_UINT16 (ctrl_word, p); + + p_buf->len -= L2CAP_FCR_OVERHEAD; + p_buf->offset += L2CAP_FCR_OVERHEAD; + + /* If we had a poll bit outstanding, check if we got a final response */ + if (p_ccb->fcrb.wait_ack) { + /* If final bit not set, ignore the frame unless it is a polled S-frame */ + if ( !(ctrl_word & L2CAP_FCR_F_BIT) ) { + if ( (ctrl_word & L2CAP_FCR_P_BIT) && (ctrl_word & L2CAP_FCR_S_FRAME_BIT) ) { + if (p_ccb->fcrb.srej_sent) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, L2CAP_FCR_F_BIT); + } else if (p_ccb->fcrb.local_busy) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_F_BIT); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_F_BIT); + } + + /* Got a poll while in wait_ack state, so re-start our timer with 1-second */ + /* This is a small optimization... the monitor timer is 12 secs, but we saw */ + /* that if the other side sends us a poll when we are waiting for a final, */ + /* then it speeds up recovery significantly if we poll him back soon after his poll. */ + btu_start_quick_timer (&p_ccb->fcrb.mon_retrans_timer, BTU_TTYPE_L2CAP_CHNL, QUICK_TIMER_TICKS_PER_SEC); + } + osi_free (p_buf); + return; + } + + p_ccb->fcrb.wait_ack = FALSE; + + /* P and F are mutually exclusive */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) { + ctrl_word &= ~L2CAP_FCR_P_BIT; + } + + if (fixed_queue_is_empty(p_ccb->fcrb.waiting_for_ack_q)) { + p_ccb->fcrb.num_tries = 0; + } + + l2c_fcr_stop_timer (p_ccb); + } else { + /* Otherwise, ensure the final bit is ignored */ + ctrl_word &= ~L2CAP_FCR_F_BIT; + } + + /* Process receive sequence number */ + if (!process_reqseq (p_ccb, ctrl_word)) { + osi_free (p_buf); + return; + } + + /* Process based on whether it is an S-frame or an I-frame */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) { + process_s_frame (p_ccb, p_buf, ctrl_word); + } else { + process_i_frame (p_ccb, p_buf, ctrl_word, FALSE); + } + + /* Return if the channel got disconnected by a bad packet or max retransmissions */ + if ( (!p_ccb->in_use) || (p_ccb->chnl_state != CST_OPEN) ) { + return; + } + + /* If we have some buffers held while doing SREJ, and SREJ has cleared, process them now */ + if ( (!p_ccb->fcrb.local_busy) && (!p_ccb->fcrb.srej_sent) && + (!fixed_queue_is_empty(p_ccb->fcrb.srej_rcv_hold_q))) { + fixed_queue_t *temp_q = p_ccb->fcrb.srej_rcv_hold_q; + p_ccb->fcrb.srej_rcv_hold_q = fixed_queue_new(QUEUE_SIZE_MAX); + + while ((p_buf = (BT_HDR *)fixed_queue_dequeue(temp_q, 0)) != NULL) { + if (p_ccb->in_use && (p_ccb->chnl_state == CST_OPEN)) { + /* Get the control word */ + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset - L2CAP_FCR_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + L2CAP_TRACE_DEBUG ("l2c_fcr_proc_pdu() CID: 0x%04x Process Buffer from SREJ_Hold_Q TxSeq: %u Expected_Seq: %u", + p_ccb->local_cid, (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + p_ccb->fcrb.next_seq_expected); + + /* Process the SREJ held I-frame, but do not send an RR for each individual frame */ + process_i_frame (p_ccb, p_buf, ctrl_word, TRUE); + } else { + osi_free (p_buf); + } + + /* If more frames were lost during SREJ, send a REJ */ + if (p_ccb->fcrb.rej_after_srej) { + p_ccb->fcrb.rej_after_srej = FALSE; + p_ccb->fcrb.rej_sent = TRUE; + + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_REJ, 0); + } + } + fixed_queue_free(temp_q, NULL); + + /* Now, if needed, send one RR for the whole held queue */ + if ( (!p_ccb->fcrb.local_busy) && (!p_ccb->fcrb.rej_sent) && (!p_ccb->fcrb.srej_sent) + && (p_ccb->fcrb.next_seq_expected != p_ccb->fcrb.last_ack_sent) ) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + } else { + L2CAP_TRACE_DEBUG ("l2c_fcr_proc_pdu() not sending RR CID: 0x%04x local_busy:%d rej_sent:%d srej_sent:%d Expected_Seq:%u Last_Ack:%u", + p_ccb->local_cid, p_ccb->fcrb.local_busy, p_ccb->fcrb.rej_sent, p_ccb->fcrb.srej_sent, p_ccb->fcrb.next_seq_expected, + p_ccb->fcrb.last_ack_sent); + } + } + + /* If a window has opened, check if we can send any more packets */ + if ( (!fixed_queue_is_empty(p_ccb->fcrb.retrans_q) || + !fixed_queue_is_empty(p_ccb->xmit_hold_q)) + && (p_ccb->fcrb.wait_ack == FALSE) + && (l2c_fcr_is_flow_controlled (p_ccb) == FALSE) ) { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_proc_tout +** +** Description Handle a timeout. We should be in error recovery state. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_tout (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + L2CAP_TRACE_DEBUG ("l2c_fcr_proc_tout: CID: 0x%04x num_tries: %u (max: %u) wait_ack: %u ack_q_count: %u", + p_ccb->local_cid, p_ccb->fcrb.num_tries, + p_ccb->peer_cfg.fcr.max_transmit, + p_ccb->fcrb.wait_ack, + fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q)); + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.retrans_touts++; +#endif + + if ( (p_ccb->peer_cfg.fcr.max_transmit != 0) && (++p_ccb->fcrb.num_tries > p_ccb->peer_cfg.fcr.max_transmit) ) { + l2cu_disconnect_chnl (p_ccb); + } else { + if (!p_ccb->fcrb.srej_sent && !p_ccb->fcrb.rej_sent) { + if (p_ccb->fcrb.local_busy) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_P_BIT); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_P_BIT); + } + } + } +} + + +/******************************************************************************* +** +** Function l2c_fcr_proc_ack_tout +** +** Description Send RR/RNR if we have not acked I frame +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_ack_tout (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + L2CAP_TRACE_DEBUG ("l2c_fcr_proc_ack_tout: CID: 0x%04x State: %u Wack:%u Rq:%d Acked:%d", p_ccb->local_cid, + p_ccb->chnl_state, p_ccb->fcrb.wait_ack, p_ccb->fcrb.next_seq_expected, p_ccb->fcrb.last_ack_sent); + + if ( (p_ccb->chnl_state == CST_OPEN) && (!p_ccb->fcrb.wait_ack) + && (p_ccb->fcrb.last_ack_sent != p_ccb->fcrb.next_seq_expected) ) { +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.xmit_ack_touts++; +#endif + if (p_ccb->fcrb.local_busy) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + } + } +} + + +/******************************************************************************* +** +** Function process_reqseq +** +** Description Handle receive sequence number +** +** Returns - +** +*******************************************************************************/ +static BOOLEAN process_reqseq (tL2C_CCB *p_ccb, UINT16 ctrl_word) +{ + assert(p_ccb != NULL); + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 req_seq, num_bufs_acked, xx; + UINT16 ls; + UINT16 full_sdus_xmitted; + + /* Receive sequence number does not ack anything for SREJ with P-bit set to zero */ + if ( (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + && ((ctrl_word & L2CAP_FCR_SUP_BITS) == (L2CAP_FCR_SUP_SREJ << L2CAP_FCR_SUP_SHIFT)) + && ((ctrl_word & L2CAP_FCR_P_BIT) == 0) ) { + /* If anything still waiting for ack, restart the timer if it was stopped */ + if (!fixed_queue_is_empty(p_fcrb->waiting_for_ack_q)) { + l2c_fcr_start_timer(p_ccb); + } + + return (TRUE); + } + + /* Extract the receive sequence number from the control word */ + req_seq = (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT; + + num_bufs_acked = (req_seq - p_fcrb->last_rx_ack) & L2CAP_FCR_SEQ_MODULO; + + /* Verify the request sequence is in range before proceeding */ + if (num_bufs_acked > fixed_queue_length(p_fcrb->waiting_for_ack_q)) { + /* The channel is closed if ReqSeq is not in range */ + L2CAP_TRACE_WARNING ("L2CAP eRTM Frame BAD Req_Seq - ctrl_word: 0x%04x req_seq 0x%02x last_rx_ack: 0x%02x QCount: %u", + ctrl_word, req_seq, p_fcrb->last_rx_ack, + fixed_queue_length(p_fcrb->waiting_for_ack_q)); + + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + + p_fcrb->last_rx_ack = req_seq; + + /* Now we can release all acknowledged frames, and restart the retransmission timer if needed */ + if (num_bufs_acked != 0) { + p_fcrb->num_tries = 0; + full_sdus_xmitted = 0; + +#if (L2CAP_ERTM_STATS == TRUE) + l2c_fcr_collect_ack_delay (p_ccb, num_bufs_acked); +#endif + + for (xx = 0; xx < num_bufs_acked; xx++) { + BT_HDR *p_tmp = (BT_HDR *)fixed_queue_dequeue(p_fcrb->waiting_for_ack_q, 0); + ls = p_tmp->layer_specific & L2CAP_FCR_SAR_BITS; + + if ( (ls == L2CAP_FCR_UNSEG_SDU) || (ls == L2CAP_FCR_END_SDU) ) { + full_sdus_xmitted++; + } + osi_free(p_tmp); + if (p_ccb->cong_sent) { + l2cu_check_channel_congestion(p_ccb); + } + } + + /* If we are still in a wait_ack state, do not mess with the timer */ + if (!p_ccb->fcrb.wait_ack) { + l2c_fcr_stop_timer (p_ccb); + } + + /* Check if we need to call the "packet_sent" callback */ + if ( (p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_TxComplete_Cb) && (full_sdus_xmitted) ) { + /* Special case for eRTM, if all packets sent, send 0xFFFF */ + if (fixed_queue_is_empty(p_fcrb->waiting_for_ack_q) && + fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + full_sdus_xmitted = 0xFFFF; + } + + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, full_sdus_xmitted); + } + } + + /* If anything still waiting for ack, restart the timer if it was stopped */ + if (!fixed_queue_is_empty(p_fcrb->waiting_for_ack_q)) { + l2c_fcr_start_timer (p_ccb); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function process_s_frame +** +** Description Process an S frame +** +** Returns - +** +*******************************************************************************/ +static void process_s_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT16 s_frame_type = (ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT; + BOOLEAN remote_was_busy; + BOOLEAN all_ok = TRUE; + + if (p_buf->len != 0) { + L2CAP_TRACE_WARNING ("Incorrect S-frame Length (%d)", p_buf->len); + } + + L2CAP_TRACE_DEBUG ("process_s_frame ctrl_word 0x%04x fcrb_remote_busy:%d", ctrl_word, p_fcrb->remote_busy); + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.s_frames_rcvd[s_frame_type]++; +#endif + + if (ctrl_word & L2CAP_FCR_P_BIT) { + p_fcrb->rej_sent = FALSE; /* After checkpoint, we can send anoher REJ */ + p_fcrb->send_f_rsp = TRUE; /* Set a flag in case an I-frame is pending */ + } + + switch (s_frame_type) { + case L2CAP_FCR_SUP_RR: + remote_was_busy = p_fcrb->remote_busy; + p_fcrb->remote_busy = FALSE; + + if ( (ctrl_word & L2CAP_FCR_F_BIT) || (remote_was_busy) ) { + all_ok = retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS); + } + break; + + case L2CAP_FCR_SUP_REJ: + p_fcrb->remote_busy = FALSE; + all_ok = retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS); + break; + + case L2CAP_FCR_SUP_RNR: + p_fcrb->remote_busy = TRUE; + l2c_fcr_stop_timer (p_ccb); + break; + + case L2CAP_FCR_SUP_SREJ: + p_fcrb->remote_busy = FALSE; + all_ok = retransmit_i_frames (p_ccb, (UINT8)((ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT)); + break; + } + + if (all_ok) { + /* If polled, we need to respond with F-bit. Note, we may have sent a I-frame with the F-bit */ + if (p_fcrb->send_f_rsp) { + if (p_fcrb->srej_sent) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, L2CAP_FCR_F_BIT); + } else if (p_fcrb->local_busy) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_F_BIT); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_F_BIT); + } + + p_fcrb->send_f_rsp = FALSE; + } + } else { + L2CAP_TRACE_DEBUG ("process_s_frame hit_max_retries"); + } + + osi_free (p_buf); +} + + +/******************************************************************************* +** +** Function process_i_frame +** +** Description Process an I frame +** +** Returns - +** +*******************************************************************************/ +static void process_i_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word, BOOLEAN delay_ack) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 tx_seq, num_lost, num_to_ack, next_srej; + + /* If we were doing checkpoint recovery, first retransmit all unacked I-frames */ + if (ctrl_word & L2CAP_FCR_F_BIT) { + if (!retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS)) { + osi_free(p_buf); + return; + } + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.ertm_pkt_counts[1]++; + p_ccb->fcrb.ertm_byte_counts[1] += p_buf->len; +#endif + + /* Extract the sequence number */ + tx_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + /* If we have flow controlled the peer, ignore any bad I-frames from him */ + if ( (tx_seq != p_fcrb->next_seq_expected) && (p_fcrb->local_busy) ) { + L2CAP_TRACE_WARNING ("Dropping bad I-Frame since we flowed off, tx_seq:%u", tx_seq); + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + osi_free(p_buf); + return; + } + + /* Check if tx-sequence is the expected one */ + if (tx_seq != p_fcrb->next_seq_expected) { + num_lost = (tx_seq - p_fcrb->next_seq_expected) & L2CAP_FCR_SEQ_MODULO; + + /* Is the frame a duplicate ? If so, just drop it */ + if (num_lost >= p_ccb->our_cfg.fcr.tx_win_sz) { + /* Duplicate - simply drop it */ + L2CAP_TRACE_WARNING ("process_i_frame() Dropping Duplicate Frame tx_seq:%u ExpectedTxSeq %u", tx_seq, p_fcrb->next_seq_expected); + osi_free(p_buf); + } else { + L2CAP_TRACE_WARNING ("process_i_frame() CID: 0x%04x Lost: %u tx_seq:%u ExpTxSeq %u Rej: %u SRej: %u", + p_ccb->local_cid, num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent, p_fcrb->srej_sent); + + if (p_fcrb->srej_sent) { + /* If SREJ sent, save the frame for later processing as long as it is in sequence */ + next_srej = (((BT_HDR *)fixed_queue_try_peek_last(p_fcrb->srej_rcv_hold_q))->layer_specific + 1) & L2CAP_FCR_SEQ_MODULO; + + if ( (tx_seq == next_srej) && (fixed_queue_length(p_fcrb->srej_rcv_hold_q) < p_ccb->our_cfg.fcr.tx_win_sz) ) { + /* If user gave us a pool for held rx buffers, use that */ + /* TODO: Could that happen? Get rid of this code. */ + if (p_ccb->ertm_info.fcr_rx_buf_size != L2CAP_FCR_RX_BUF_SIZE) { + BT_HDR *p_buf2; + + /* Adjust offset and len so that control word is copied */ + p_buf->offset -= L2CAP_FCR_OVERHEAD; + p_buf->len += L2CAP_FCR_OVERHEAD; + + p_buf2 = l2c_fcr_clone_buf (p_buf, p_buf->offset, p_buf->len); + + if (p_buf2) { + osi_free (p_buf); + p_buf = p_buf2; + } + p_buf->offset += L2CAP_FCR_OVERHEAD; + p_buf->len -= L2CAP_FCR_OVERHEAD; + } + L2CAP_TRACE_DEBUG ("process_i_frame() Lost: %u tx_seq:%u ExpTxSeq %u Rej: %u SRej1", + num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent); + + p_buf->layer_specific = tx_seq; + fixed_queue_enqueue(p_fcrb->srej_rcv_hold_q, p_buf, FIXED_QUEUE_MAX_TIMEOUT); + } else { + L2CAP_TRACE_WARNING ("process_i_frame() CID: 0x%04x frame dropped in Srej Sent next_srej:%u hold_q.count:%u win_sz:%u", + p_ccb->local_cid, next_srej, fixed_queue_length(p_fcrb->srej_rcv_hold_q), p_ccb->our_cfg.fcr.tx_win_sz); + + p_fcrb->rej_after_srej = TRUE; + osi_free (p_buf); + } + } else if (p_fcrb->rej_sent) { + L2CAP_TRACE_WARNING ("process_i_frame() CID: 0x%04x Lost: %u tx_seq:%u ExpTxSeq %u Rej: 1 SRej: %u", + p_ccb->local_cid, num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->srej_sent); + + /* If REJ sent, just drop the frame */ + osi_free (p_buf); + } else { + L2CAP_TRACE_DEBUG ("process_i_frame() CID: 0x%04x tx_seq:%u ExpTxSeq %u Rej: %u", + p_ccb->local_cid, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent); + + /* If only one lost, we will send SREJ, otherwise we will send REJ */ + if (num_lost > 1) { + osi_free (p_buf); + p_fcrb->rej_sent = TRUE; + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_REJ, 0); + } else { + if (!fixed_queue_is_empty(p_fcrb->srej_rcv_hold_q)) { + L2CAP_TRACE_ERROR ("process_i_frame() CID: 0x%04x sending SREJ tx_seq:%d hold_q.count:%u", + p_ccb->local_cid, tx_seq, fixed_queue_length(p_fcrb->srej_rcv_hold_q)); + } + p_buf->layer_specific = tx_seq; + fixed_queue_enqueue(p_fcrb->srej_rcv_hold_q, p_buf, FIXED_QUEUE_MAX_TIMEOUT); + p_fcrb->srej_sent = TRUE; + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, 0); + } + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + } + return; + } + + /* Seq number is the next expected. Clear possible reject exception in case it occured */ + p_fcrb->rej_sent = p_fcrb->srej_sent = FALSE; + + /* Adjust the next_seq, so that if the upper layer sends more data in the callback + context, the received frame is acked by an I-frame. */ + p_fcrb->next_seq_expected = (tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + + /* If any SAR problem in eRTM mode, spec says disconnect. */ + if (!do_sar_reassembly (p_ccb, p_buf, ctrl_word)) { + L2CAP_TRACE_WARNING ("process_i_frame() CID: 0x%04x reassembly failed", p_ccb->local_cid); + l2cu_disconnect_chnl (p_ccb); + return; + } + + /* RR optimization - if peer can still send us more, then start an ACK timer */ + num_to_ack = (p_fcrb->next_seq_expected - p_fcrb->last_ack_sent) & L2CAP_FCR_SEQ_MODULO; + + if ( (num_to_ack < p_ccb->fcrb.max_held_acks) && (!p_fcrb->local_busy) ) { + delay_ack = TRUE; + } + + /* We should neve never ack frame if we are not in OPEN state */ + if ((num_to_ack != 0) && p_ccb->in_use && (p_ccb->chnl_state == CST_OPEN)) { + /* If no frames are awaiting transmission or are held, send an RR or RNR S-frame for ack */ + if (delay_ack) { + /* If it is the first I frame we did not ack, start ack timer */ + if (!p_ccb->fcrb.ack_timer.in_use) { + btu_start_quick_timer (&p_ccb->fcrb.ack_timer, BTU_TTYPE_L2CAP_FCR_ACK, + (L2CAP_FCR_ACK_TOUT * QUICK_TIMER_TICKS_PER_SEC) / 1000); + } + } else if ((fixed_queue_is_empty(p_ccb->xmit_hold_q) || + l2c_fcr_is_flow_controlled(p_ccb)) + && fixed_queue_is_empty(p_ccb->fcrb.srej_rcv_hold_q)) { + if (p_fcrb->local_busy) { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + } else { + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + } + } + } +} + + +/******************************************************************************* +** +** Function process_stream_frame +** +** Description This function processes frames in streaming mode +** +** Returns - +** +*******************************************************************************/ +static void process_stream_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + + UINT16 ctrl_word; + UINT16 fcs; + UINT8 *p; + UINT8 tx_seq; + + /* Verify FCS if using */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset + p_buf->len - L2CAP_FCS_LEN; + + /* Extract and drop the FCS from the packet */ + STREAM_TO_UINT16 (fcs, p); + p_buf->len -= L2CAP_FCS_LEN; + + if (l2c_fcr_rx_get_fcs(p_buf) != fcs) { + L2CAP_TRACE_WARNING ("Rx L2CAP PDU: CID: 0x%04x BAD FCS", p_ccb->local_cid); + osi_free(p_buf); + return; + } + } + + /* Get the control word */ + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset; + + STREAM_TO_UINT16 (ctrl_word, p); + + p_buf->len -= L2CAP_FCR_OVERHEAD; + p_buf->offset += L2CAP_FCR_OVERHEAD; + + /* Make sure it is an I-frame */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) { + L2CAP_TRACE_WARNING ("Rx L2CAP PDU: CID: 0x%04x BAD S-frame in streaming mode ctrl_word: 0x%04x", p_ccb->local_cid, ctrl_word); + osi_free (p_buf); + return; + } + +#if BT_TRACE_VERBOSE == TRUE + L2CAP_TRACE_EVENT ("L2CAP eRTM Rx I-frame: cid: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); +#endif + + /* Extract the sequence number */ + tx_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + /* Check if tx-sequence is the expected one */ + if (tx_seq != p_ccb->fcrb.next_seq_expected) { + L2CAP_TRACE_WARNING ("Rx L2CAP PDU: CID: 0x%04x Lost frames Exp: %u Got: %u p_rx_sdu: %p", + p_ccb->local_cid, p_ccb->fcrb.next_seq_expected, tx_seq, p_ccb->fcrb.p_rx_sdu); + + /* Lost one or more packets, so flush the SAR queue */ + if (p_ccb->fcrb.p_rx_sdu != NULL) { + osi_free(p_ccb->fcrb.p_rx_sdu); + p_ccb->fcrb.p_rx_sdu = NULL; + } + } + + p_ccb->fcrb.next_seq_expected = (tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + + if (!do_sar_reassembly (p_ccb, p_buf, ctrl_word)) { + /* Some sort of SAR error, so flush the SAR queue */ + if (p_ccb->fcrb.p_rx_sdu != NULL) { + osi_free(p_ccb->fcrb.p_rx_sdu); + p_ccb->fcrb.p_rx_sdu = NULL; + } + } +} + + +/******************************************************************************* +** +** Function do_sar_reassembly +** +** Description Process SAR bits and re-assemble frame +** +** Returns TRUE if all OK, else FALSE +** +*******************************************************************************/ +static BOOLEAN do_sar_reassembly (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word) +{ + assert(p_ccb != NULL); + assert(p_buf != NULL); + + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT16 sar_type = ctrl_word & L2CAP_FCR_SEG_BITS; + BOOLEAN packet_ok = TRUE; + UINT8 *p; + + /* Check if the SAR state is correct */ + if ((sar_type == L2CAP_FCR_UNSEG_SDU) || (sar_type == L2CAP_FCR_START_SDU)) { + if (p_fcrb->p_rx_sdu != NULL) { + L2CAP_TRACE_WARNING ("SAR - got unexpected unsegmented or start SDU Expected len: %u Got so far: %u", + p_fcrb->rx_sdu_len, p_fcrb->p_rx_sdu->len); + + packet_ok = FALSE; + } + /* Check the length of the packet */ + if ( (sar_type == L2CAP_FCR_START_SDU) && (p_buf->len < L2CAP_SDU_LEN_OVERHEAD) ) { + L2CAP_TRACE_WARNING ("SAR start packet too short: %u", p_buf->len); + packet_ok = FALSE; + } + } else { + if (p_fcrb->p_rx_sdu == NULL) { + L2CAP_TRACE_WARNING ("SAR - got unexpected cont or end SDU"); + packet_ok = FALSE; + } + } + + if ( (packet_ok) && (sar_type != L2CAP_FCR_UNSEG_SDU) ) { + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset; + + /* For start SDU packet, extract the SDU length */ + if (sar_type == L2CAP_FCR_START_SDU) { + /* Get the SDU length */ + STREAM_TO_UINT16 (p_fcrb->rx_sdu_len, p); + p_buf->offset += 2; + p_buf->len -= 2; + + if (p_fcrb->rx_sdu_len > p_ccb->max_rx_mtu) { + L2CAP_TRACE_WARNING ("SAR - SDU len: %u larger than MTU: %u", p_fcrb->rx_sdu_len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } else if ((p_fcrb->p_rx_sdu = (BT_HDR *)osi_malloc(L2CAP_MAX_BUF_SIZE)) == NULL) { + L2CAP_TRACE_ERROR ("SAR - no buffer for SDU start user_rx_buf_size:%d", p_ccb->ertm_info.user_rx_buf_size); + packet_ok = FALSE; + } else { + p_fcrb->p_rx_sdu->offset = 4; /* this is the minimal offset required by OBX to process incoming packets */ + p_fcrb->p_rx_sdu->len = 0; + } + } + + if (packet_ok) { + if ((p_fcrb->p_rx_sdu->len + p_buf->len) > p_fcrb->rx_sdu_len) { + L2CAP_TRACE_ERROR ("SAR - SDU len exceeded Type: %u Lengths: %u %u %u", + sar_type, p_fcrb->p_rx_sdu->len, p_buf->len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } else if ( (sar_type == L2CAP_FCR_END_SDU) && ((p_fcrb->p_rx_sdu->len + p_buf->len) != p_fcrb->rx_sdu_len) ) { + L2CAP_TRACE_WARNING ("SAR - SDU end rcvd but SDU incomplete: %u %u %u", + p_fcrb->p_rx_sdu->len, p_buf->len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } else { + memcpy (((UINT8 *) (p_fcrb->p_rx_sdu + 1)) + p_fcrb->p_rx_sdu->offset + p_fcrb->p_rx_sdu->len, p, p_buf->len); + + p_fcrb->p_rx_sdu->len += p_buf->len; + + osi_free (p_buf); + p_buf = NULL; + + if (sar_type == L2CAP_FCR_END_SDU) { + p_buf = p_fcrb->p_rx_sdu; + p_fcrb->p_rx_sdu = NULL; + } + } + } + } + + if (packet_ok == FALSE) { + osi_free (p_buf); + } else if (p_buf != NULL) { +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_ccb->local_cid < L2CAP_BASE_APPL_CID && + (p_ccb->local_cid >= L2CAP_FIRST_FIXED_CHNL && p_ccb->local_cid <= L2CAP_LAST_FIXED_CHNL)) { + if (l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) + (*l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) + (p_ccb->local_cid, p_ccb->p_lcb->remote_bd_addr, p_buf); + } else +#endif + { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_buf); + } + } + + return (packet_ok); +} + + +/******************************************************************************* +** +** Function retransmit_i_frames +** +** Description This function retransmits i-frames awaiting acks. +** +** Returns BOOLEAN - TRUE if retransmitted +** +*******************************************************************************/ +static BOOLEAN retransmit_i_frames (tL2C_CCB *p_ccb, UINT8 tx_seq) +{ + assert(p_ccb != NULL); + + BT_HDR *p_buf = NULL; + UINT8 *p; + UINT8 buf_seq; + UINT16 ctrl_word; + + if ( (!fixed_queue_is_empty(p_ccb->fcrb.waiting_for_ack_q)) + && (p_ccb->peer_cfg.fcr.max_transmit != 0) + && (p_ccb->fcrb.num_tries >= p_ccb->peer_cfg.fcr.max_transmit) ) { + L2CAP_TRACE_EVENT ("Max Tries Exceeded: (last_acq: %d CID: 0x%04x num_tries: %u (max: %u) ack_q_count: %u", + p_ccb->fcrb.last_rx_ack, p_ccb->local_cid, p_ccb->fcrb.num_tries, p_ccb->peer_cfg.fcr.max_transmit, + fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q)); + + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + + /* tx_seq indicates whether to retransmit a specific sequence or all (if == L2C_FCR_RETX_ALL_PKTS) */ + list_t *list_ack = NULL; + const list_node_t *node_ack = NULL; + if (! fixed_queue_is_empty(p_ccb->fcrb.waiting_for_ack_q)) { + list_ack = fixed_queue_get_list(p_ccb->fcrb.waiting_for_ack_q); + node_ack = list_begin(list_ack); + } + if (tx_seq != L2C_FCR_RETX_ALL_PKTS) { + /* If sending only one, the sequence number tells us which one. Look for it. + */ + if (list_ack != NULL) { + for ( ; node_ack != list_end(list_ack); node_ack = list_next(node_ack)) { + p_buf = (BT_HDR *)list_node(node_ack); + /* Get the old control word */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + buf_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + L2CAP_TRACE_DEBUG ("retransmit_i_frames() cur seq: %u looking for: %u", buf_seq, tx_seq); + + if (tx_seq == buf_seq) { + break; + } + } + } + + if (!p_buf) { + L2CAP_TRACE_ERROR ("retransmit_i_frames() UNKNOWN seq: %u q_count: %u", + tx_seq, + fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q)); + return (TRUE); + } + } else { + // Iterate though list and flush the amount requested from + // the transmit data queue that satisfy the layer and event conditions. + for (const list_node_t *node = list_begin(p_ccb->p_lcb->link_xmit_data_q); + node != list_end(p_ccb->p_lcb->link_xmit_data_q);) { + BT_HDR *p_buf = (BT_HDR *)list_node(node); + node = list_next(node); + + /* Do not flush other CIDs or partial segments */ + if ((p_buf->layer_specific == 0) && (p_buf->event == p_ccb->local_cid)) { + list_remove(p_ccb->p_lcb->link_xmit_data_q, p_buf); + osi_free(p_buf); + } + } + + /* Also flush our retransmission queue */ + while (!fixed_queue_is_empty(p_ccb->fcrb.retrans_q)) { + osi_free(fixed_queue_dequeue(p_ccb->fcrb.retrans_q, 0)); + } + + if (list_ack != NULL) { + node_ack = list_begin(list_ack); + } + } + + if (list_ack != NULL) { + while (node_ack != list_end(list_ack)) + { + p_buf = (BT_HDR *)list_node(node_ack); + node_ack = list_next(node_ack); + + BT_HDR *p_buf2 = l2c_fcr_clone_buf(p_buf, p_buf->offset, p_buf->len); + if (p_buf2) + { + p_buf2->layer_specific = p_buf->layer_specific; + + fixed_queue_enqueue(p_ccb->fcrb.retrans_q, p_buf2, FIXED_QUEUE_MAX_TIMEOUT); + } + + if ( (tx_seq != L2C_FCR_RETX_ALL_PKTS) || (p_buf2 == NULL) ) { + break; + } + } + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + + if (fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q)) + { + p_ccb->fcrb.num_tries++; + l2c_fcr_start_timer (p_ccb); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_get_next_xmit_sdu_seg +** +** Description Get the next SDU segment to transmit. +** +** Returns pointer to buffer with segment or NULL +** +*******************************************************************************/ +BT_HDR *l2c_fcr_get_next_xmit_sdu_seg (tL2C_CCB *p_ccb, UINT16 max_packet_length) +{ + assert(p_ccb != NULL); + + BOOLEAN first_seg = FALSE, /* The segment is the first part of data */ + mid_seg = FALSE, /* The segment is the middle part of data */ + last_seg = FALSE; /* The segment is the last part of data */ + UINT16 sdu_len = 0; + BT_HDR *p_buf, *p_xmit; + UINT8 *p; + UINT16 max_pdu = p_ccb->tx_mps /* Needed? - L2CAP_MAX_HEADER_FCS*/; + + /* If there is anything in the retransmit queue, that goes first + */ + p_buf = (BT_HDR *)fixed_queue_dequeue(p_ccb->fcrb.retrans_q, 0); + if (p_buf != NULL) { + /* Update Rx Seq and FCS if we acked some packets while this one was queued */ + prepare_I_frame (p_ccb, p_buf, TRUE); + + p_buf->event = p_ccb->local_cid; + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.pkts_retransmitted++; + p_ccb->fcrb.ertm_pkt_counts[0]++; + p_ccb->fcrb.ertm_byte_counts[0] += (p_buf->len - 8); +#endif + return (p_buf); + } + + /* For BD/EDR controller, max_packet_length is set to 0 */ + /* For AMP controller, max_packet_length is set by available blocks */ + if ( (max_packet_length > L2CAP_MAX_HEADER_FCS) + && (max_pdu + L2CAP_MAX_HEADER_FCS > max_packet_length) ) { + max_pdu = max_packet_length - L2CAP_MAX_HEADER_FCS; + } + + p_buf = (BT_HDR *)fixed_queue_try_peek_first(p_ccb->xmit_hold_q); + + /* If there is more data than the MPS, it requires segmentation */ + if (p_buf->len > max_pdu) { + /* We are using the "event" field to tell is if we already started segmentation */ + if (p_buf->event == 0) { + first_seg = TRUE; + sdu_len = p_buf->len; + } else { + mid_seg = TRUE; + } + + /* Get a new buffer and copy the data that can be sent in a PDU */ + p_xmit = l2c_fcr_clone_buf (p_buf, L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET, + max_pdu); + + if (p_xmit != NULL) { + p_buf->event = p_ccb->local_cid; + p_xmit->event = p_ccb->local_cid; + + p_buf->len -= max_pdu; + p_buf->offset += max_pdu; + + /* copy PBF setting */ + p_xmit->layer_specific = p_buf->layer_specific; + } else { /* Should never happen if the application has configured buffers correctly */ + L2CAP_TRACE_ERROR ("L2CAP - cannot get buffer for segmentation, max_pdu: %u", max_pdu); + return (NULL); + } + } else { /* Use the original buffer if no segmentation, or the last segment */ + p_xmit = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0); + + if (p_xmit->event != 0) { + last_seg = TRUE; + } + + p_xmit->event = p_ccb->local_cid; + } + + /* Step back to add the L2CAP headers */ + p_xmit->offset -= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD); + p_xmit->len += L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD; + + if (first_seg) { + p_xmit->offset -= L2CAP_SDU_LEN_OVERHEAD; + p_xmit->len += L2CAP_SDU_LEN_OVERHEAD; + } + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_xmit + 1) + p_xmit->offset; + + /* Now the L2CAP header */ + + /* Note: if FCS has to be included then the length is recalculated later */ + UINT16_TO_STREAM (p, p_xmit->len - L2CAP_PKT_OVERHEAD); + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + + if (first_seg) { + /* Skip control word and add SDU length */ + p += 2; + UINT16_TO_STREAM (p, sdu_len); + + /* We will store the SAR type in layer-specific */ + /* layer_specific is shared with flushable flag(bits 0-1), don't clear it */ + p_xmit->layer_specific |= L2CAP_FCR_START_SDU; + + first_seg = FALSE; + } else if (mid_seg) { + p_xmit->layer_specific |= L2CAP_FCR_CONT_SDU; + } else if (last_seg) { + p_xmit->layer_specific |= L2CAP_FCR_END_SDU; + } else { + p_xmit->layer_specific |= L2CAP_FCR_UNSEG_SDU; + } + + prepare_I_frame (p_ccb, p_xmit, FALSE); + + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { + BT_HDR *p_wack = l2c_fcr_clone_buf (p_xmit, HCI_DATA_PREAMBLE_SIZE, p_xmit->len); + + if (!p_wack) { + L2CAP_TRACE_ERROR("L2CAP - no buffer for xmit cloning, CID: 0x%04x Length: %u", + p_ccb->local_cid, p_xmit->len); + + /* We will not save the FCS in case we reconfigure and change options */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + p_xmit->len -= L2CAP_FCS_LEN; + } + + /* Pretend we sent it and it got lost */ + fixed_queue_enqueue(p_ccb->fcrb.waiting_for_ack_q, p_xmit, FIXED_QUEUE_MAX_TIMEOUT); + return (NULL); + } else { +#if (L2CAP_ERTM_STATS == TRUE) + /* set timestamp at the end of tx I-frame to get acking delay */ + p = ((UINT8 *) (p_wack + 1)) + p_wack->offset + p_wack->len; + UINT32_TO_STREAM (p, osi_time_get_os_boottime_ms()); +#endif + /* We will not save the FCS in case we reconfigure and change options */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + p_wack->len -= L2CAP_FCS_LEN; + } + + p_wack->layer_specific = p_xmit->layer_specific; + fixed_queue_enqueue(p_ccb->fcrb.waiting_for_ack_q, p_wack, FIXED_QUEUE_MAX_TIMEOUT); + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.ertm_pkt_counts[0]++; + p_ccb->fcrb.ertm_byte_counts[0] += (p_xmit->len - 8); +#endif + + } + + return (p_xmit); +} + + +/******************************************************************************* +** Configuration negotiation functions +** +** The following functions are used in negotiating channel modes during +** configuration +********************************************************************************/ + +/******************************************************************************* +** +** Function l2c_fcr_chk_chan_modes +** +** Description Validates and adjusts if necessary, the FCR options +** based on remote EXT features. +** +** Note: This assumes peer EXT Features have been received. +** Basic mode is used if FCR Options have not been received +** +** Returns UINT8 - nonzero if can continue, '0' if no compatible channels +** +*******************************************************************************/ +UINT8 l2c_fcr_chk_chan_modes (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + + /* Remove nonbasic options that the peer does not support */ + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_ENH_RETRANS)) { + p_ccb->ertm_info.allowed_modes &= ~L2CAP_FCR_CHAN_OPT_ERTM; + } + + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_STREAM_MODE)) { + p_ccb->ertm_info.allowed_modes &= ~L2CAP_FCR_CHAN_OPT_STREAM; + } + + /* At least one type needs to be set (Basic, ERTM, STM) to continue */ + if (!p_ccb->ertm_info.allowed_modes) { + L2CAP_TRACE_WARNING ("L2CAP - Peer does not support our desired channel types"); + } + + return (p_ccb->ertm_info.allowed_modes); +} + +/******************************************************************************* +** +** Function l2c_fcr_adj_our_req_options +** +** Description Validates and sets up the FCR options passed in from +** L2CA_ConfigReq based on remote device's features. +** +** Returns TRUE if no errors, Otherwise FALSE +** +*******************************************************************************/ +BOOLEAN l2c_fcr_adj_our_req_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + assert(p_ccb != NULL); + assert(p_cfg != NULL); + + tL2CAP_FCR_OPTS *p_fcr = &p_cfg->fcr; + + if (p_fcr->mode != p_ccb->ertm_info.preferred_mode) { + L2CAP_TRACE_WARNING ("l2c_fcr_adj_our_req_options - preferred_mode (%d), does not match mode (%d)", + p_ccb->ertm_info.preferred_mode, p_fcr->mode); + + /* The preferred mode is passed in through tL2CAP_ERTM_INFO, so override this one */ + p_fcr->mode = p_ccb->ertm_info.preferred_mode; + } + + /* If upper layer did not request eRTM mode, BASIC must be used */ + if (p_ccb->ertm_info.allowed_modes == L2CAP_FCR_CHAN_OPT_BASIC) { + if (p_cfg->fcr_present && p_fcr->mode != L2CAP_FCR_BASIC_MODE) { + L2CAP_TRACE_WARNING ("l2c_fcr_adj_our_req_options (mode %d): ERROR: No FCR options set using BASIC mode", p_fcr->mode); + } + p_fcr->mode = L2CAP_FCR_BASIC_MODE; + } + + /* Process the FCR options if initial channel bring-up (not a reconfig request) + ** Determine initial channel mode to try based on our options and remote's features + */ + if (p_cfg->fcr_present && !(p_ccb->config_done & RECONFIG_FLAG)) { + /* We need to have at least one mode type common with the peer */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) { + /* Two channels have incompatible supported types */ + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + + /* Basic is the only common channel mode between the two devices */ + else if (p_ccb->ertm_info.allowed_modes == L2CAP_FCR_CHAN_OPT_BASIC) { + /* We only want to try Basic, so bypass sending the FCR options entirely */ + p_cfg->fcr_present = FALSE; + p_cfg->fcs_present = FALSE; /* Illegal to use FCS option in basic mode */ + p_cfg->ext_flow_spec_present = FALSE; /* Illegal to use extended flow spec in basic mode */ + } + + /* We have at least one non-basic mode available + * Override mode from available mode options based on preference, if needed + */ + else { + /* If peer does not support STREAMING, try ERTM */ + if (p_fcr->mode == L2CAP_FCR_STREAM_MODE && !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_STREAM)) { + L2CAP_TRACE_DEBUG ("L2C CFG: mode is STREAM, but peer does not support; Try ERTM"); + p_fcr->mode = L2CAP_FCR_ERTM_MODE; + } + + /* If peer does not support ERTM, try BASIC (will support this if made it here in the code) */ + if (p_fcr->mode == L2CAP_FCR_ERTM_MODE && !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM)) { + L2CAP_TRACE_DEBUG ("L2C CFG: mode is ERTM, but peer does not support; Try BASIC"); + p_fcr->mode = L2CAP_FCR_BASIC_MODE; + } + } + + if (p_fcr->mode != L2CAP_FCR_BASIC_MODE) { + /* MTU must be smaller than buffer size */ + if ( (p_cfg->mtu_present) && (p_cfg->mtu > p_ccb->max_rx_mtu) ) { + L2CAP_TRACE_WARNING ("L2CAP - MTU: %u larger than buf size: %u", p_cfg->mtu, p_ccb->max_rx_mtu); + return (FALSE); + } + + /* application want to use the default MPS */ + if (p_fcr->mps == L2CAP_DEFAULT_ERM_MPS) { + p_fcr->mps = L2CAP_MPS_OVER_BR_EDR; + } + /* MPS must be less than MTU */ + else if (p_fcr->mps > p_ccb->max_rx_mtu) { + L2CAP_TRACE_WARNING ("L2CAP - MPS %u invalid MTU: %u", p_fcr->mps, p_ccb->max_rx_mtu); + return (FALSE); + } + + /* We always initially read into the HCI buffer pool, so make sure it fits */ + if (p_fcr->mps > (L2CAP_MTU_SIZE - L2CAP_MAX_HEADER_FCS)) { + p_fcr->mps = L2CAP_MTU_SIZE - L2CAP_MAX_HEADER_FCS; + } + } else { + p_cfg->fcs_present = FALSE; /* Illegal to use FCS option in basic mode */ + p_cfg->ext_flow_spec_present = FALSE; /* Illegal to use extended flow spec in basic mode */ + } + + p_ccb->our_cfg.fcr = *p_fcr; + } else { /* Not sure how to send a reconfiguration(??) should fcr be included? */ + p_ccb->our_cfg.fcr_present = FALSE; + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_adj_monitor_retran_timeout +** +** Description Overrides monitor/retrans timer value based on controller +** +** Returns None +** +*******************************************************************************/ +void l2c_fcr_adj_monitor_retran_timeout (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + + /* adjust our monitor/retran timeout */ + if (p_ccb->out_cfg_fcr_present) { + /* + ** if we requestd ERTM or accepted ERTM + ** We may accept ERTM even if we didn't request ERTM, in case of requesting STREAM + */ + if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + || (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE)) { + /* upper layer setting is ignored */ + p_ccb->our_cfg.fcr.mon_tout = L2CAP_MIN_MONITOR_TOUT; + p_ccb->our_cfg.fcr.rtrans_tout = L2CAP_MIN_RETRANS_TOUT; + } else { + p_ccb->our_cfg.fcr.mon_tout = 0; + p_ccb->our_cfg.fcr.rtrans_tout = 0; + } + + L2CAP_TRACE_DEBUG ("l2c_fcr_adj_monitor_retran_timeout: mon_tout:%d, rtrans_tout:%d", + p_ccb->our_cfg.fcr.mon_tout, p_ccb->our_cfg.fcr.rtrans_tout); + } +} +/******************************************************************************* +** +** Function l2c_fcr_adj_our_rsp_options +** +** Description Overrides any neccesary FCR options passed in from +** L2CA_ConfigRsp based on our FCR options. +** Only makes adjustments if channel is in ERTM mode. +** +** Returns None +** +*******************************************************************************/ +void l2c_fcr_adj_our_rsp_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + assert(p_ccb != NULL); + assert(p_cfg != NULL); + + /* adjust our monitor/retran timeout */ + l2c_fcr_adj_monitor_retran_timeout(p_ccb); + + p_cfg->fcr_present = p_ccb->out_cfg_fcr_present; + + if (p_cfg->fcr_present) { +// btla-specific ++ + /* Temporary - until a better algorithm is implemented */ + /* If peer's tx_wnd_sz requires too many buffers for us to support, then adjust it. For now, respond with our own tx_wnd_sz. */ + /* Note: peer is not guaranteed to obey our adjustment */ + if (p_ccb->peer_cfg.fcr.tx_win_sz > p_ccb->our_cfg.fcr.tx_win_sz) { + L2CAP_TRACE_DEBUG ("%s: adjusting requested tx_win_sz from %i to %i", __FUNCTION__, p_ccb->peer_cfg.fcr.tx_win_sz, p_ccb->our_cfg.fcr.tx_win_sz); + p_ccb->peer_cfg.fcr.tx_win_sz = p_ccb->our_cfg.fcr.tx_win_sz; + } +// btla-specific -- + + p_cfg->fcr.mode = p_ccb->peer_cfg.fcr.mode; + p_cfg->fcr.tx_win_sz = p_ccb->peer_cfg.fcr.tx_win_sz; + p_cfg->fcr.max_transmit = p_ccb->peer_cfg.fcr.max_transmit; + p_cfg->fcr.mps = p_ccb->peer_cfg.fcr.mps; + p_cfg->fcr.rtrans_tout = p_ccb->our_cfg.fcr.rtrans_tout; + p_cfg->fcr.mon_tout = p_ccb->our_cfg.fcr.mon_tout; + } +} + +/******************************************************************************* +** +** Function l2c_fcr_renegotiate_chan +** +** Description Called upon unsuccessful peer response to config request. +** If the error is because of the channel mode, it will try +** to resend using another supported optional channel. +** +** Returns TRUE if resent configuration, False if channel matches or +** cannot match. +** +*******************************************************************************/ +BOOLEAN l2c_fcr_renegotiate_chan(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + assert(p_ccb != NULL); + assert(p_cfg != NULL); + + UINT8 peer_mode = p_ccb->our_cfg.fcr.mode; + BOOLEAN can_renegotiate; + + /* Skip if this is a reconfiguration from OPEN STATE or if FCR is not returned */ + if (!p_cfg->fcr_present || (p_ccb->config_done & RECONFIG_FLAG)) { + return (FALSE); + } + + /* Only retry if there are more channel options to try */ + if (p_cfg->result == L2CAP_CFG_UNACCEPTABLE_PARAMS) { + peer_mode = (p_cfg->fcr_present) ? p_cfg->fcr.mode : L2CAP_FCR_BASIC_MODE; + + if (p_ccb->our_cfg.fcr.mode != peer_mode) { + + if ((--p_ccb->fcr_cfg_tries) == 0) { + p_cfg->result = L2CAP_CFG_FAILED_NO_REASON; + L2CAP_TRACE_WARNING ("l2c_fcr_renegotiate_chan (Max retries exceeded)"); + } + + can_renegotiate = FALSE; + + /* Try another supported mode if available based on our last attempted channel */ + switch (p_ccb->our_cfg.fcr.mode) { + /* Our Streaming mode request was unnacceptable; try ERTM or Basic */ + case L2CAP_FCR_STREAM_MODE: + /* Peer wants ERTM and we support it */ + if ( (peer_mode == L2CAP_FCR_ERTM_MODE) && (p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM) ) { + L2CAP_TRACE_DEBUG ("l2c_fcr_renegotiate_chan(Trying ERTM)"); + p_ccb->our_cfg.fcr.mode = L2CAP_FCR_ERTM_MODE; + can_renegotiate = TRUE; + } else /* Falls through */ + + case L2CAP_FCR_ERTM_MODE: { + /* We can try basic for any other peer mode if we support it */ + if (p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_BASIC) { + L2CAP_TRACE_DEBUG ("l2c_fcr_renegotiate_chan(Trying Basic)"); + can_renegotiate = TRUE; + p_ccb->our_cfg.fcr.mode = L2CAP_FCR_BASIC_MODE; + } + } + break; + + default: + /* All other scenarios cannot be renegotiated */ + break; + } + + if (can_renegotiate) { + p_ccb->our_cfg.fcr_present = TRUE; + + if (p_ccb->our_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) { + p_ccb->our_cfg.fcs_present = FALSE; + p_ccb->our_cfg.ext_flow_spec_present = FALSE; + + /* Basic Mode uses ACL Data Pool, make sure the MTU fits */ + if ( (p_cfg->mtu_present) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) { + L2CAP_TRACE_WARNING ("L2CAP - adjust MTU: %u too large", p_cfg->mtu); + p_cfg->mtu = L2CAP_MTU_SIZE; + } + } + + l2cu_process_our_cfg_req (p_ccb, &p_ccb->our_cfg); + l2cu_send_peer_config_req (p_ccb, &p_ccb->our_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + return (TRUE); + } + } + } + + /* Disconnect if the channels do not match */ + if (p_ccb->our_cfg.fcr.mode != peer_mode) { + L2CAP_TRACE_WARNING ("L2C CFG: Channels incompatible (local %d, peer %d)", + p_ccb->our_cfg.fcr.mode, peer_mode); + l2cu_disconnect_chnl (p_ccb); + } + + return (FALSE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_process_peer_cfg_req +** +** Description This function is called to process the FCR options passed +** in the peer's configuration request. +** +** Returns UINT8 - L2CAP_PEER_CFG_OK, L2CAP_PEER_CFG_UNACCEPTABLE, +** or L2CAP_PEER_CFG_DISCONNECT. +** +*******************************************************************************/ +UINT8 l2c_fcr_process_peer_cfg_req(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + assert(p_ccb != NULL); + assert(p_cfg != NULL); + + UINT16 max_retrans_size; + UINT8 fcr_ok = L2CAP_PEER_CFG_OK; + + p_ccb->p_lcb->w4_info_rsp = FALSE; /* Handles T61x SonyEricsson Bug in Info Request */ + + L2CAP_TRACE_EVENT ("l2c_fcr_process_peer_cfg_req() CFG fcr_present:%d fcr.mode:%d CCB FCR mode:%d preferred: %u allowed:%u", + p_cfg->fcr_present, p_cfg->fcr.mode, p_ccb->our_cfg.fcr.mode, p_ccb->ertm_info.preferred_mode, + p_ccb->ertm_info.allowed_modes); + + /* If Peer wants basic, we are done (accept it or disconnect) */ + if (p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE) { + /* If we do not allow basic, disconnect */ + if ( !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_BASIC) ) { + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + } + } + + /* Need to negotiate if our modes are not the same */ + else if (p_cfg->fcr.mode != p_ccb->ertm_info.preferred_mode) { + /* If peer wants a mode that we don't support then retry our mode (ex. rtx/flc), OR + ** If we want ERTM and they wanted streaming retry our mode. + ** Note: If we have already determined they support our mode previously + ** from their EXF mask. + */ + if ( (((1 << p_cfg->fcr.mode) & L2CAP_FCR_CHAN_OPT_ALL_MASK) == 0) + || (p_ccb->ertm_info.preferred_mode == L2CAP_FCR_ERTM_MODE) ) { + p_cfg->fcr.mode = p_ccb->our_cfg.fcr.mode; + p_cfg->fcr.tx_win_sz = p_ccb->our_cfg.fcr.tx_win_sz; + p_cfg->fcr.max_transmit = p_ccb->our_cfg.fcr.max_transmit; + fcr_ok = L2CAP_PEER_CFG_UNACCEPTABLE; + } + + /* If we wanted basic, then try to renegotiate it */ + else if (p_ccb->ertm_info.preferred_mode == L2CAP_FCR_BASIC_MODE) { + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + p_cfg->fcr.max_transmit = p_cfg->fcr.tx_win_sz = 0; + p_cfg->fcr.rtrans_tout = p_cfg->fcr.mon_tout = p_cfg->fcr.mps = 0; + p_ccb->our_cfg.fcr.rtrans_tout = p_ccb->our_cfg.fcr.mon_tout = p_ccb->our_cfg.fcr.mps = 0; + fcr_ok = L2CAP_PEER_CFG_UNACCEPTABLE; + } + + /* Only other valid case is if they want ERTM and we wanted STM which should be + accepted if we support it; otherwise the channel should be disconnected */ + else if ( (p_cfg->fcr.mode != L2CAP_FCR_ERTM_MODE) + || !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM) ) { + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + } + } + + /* Configuration for FCR channels so make any adjustments and fwd to upper layer */ + if (fcr_ok == L2CAP_PEER_CFG_OK) { + /* by default don't need to send params in the response */ + p_ccb->out_cfg_fcr_present = FALSE; + + /* Make any needed adjustments for the response to the peer */ + if (p_cfg->fcr_present && p_cfg->fcr.mode != L2CAP_FCR_BASIC_MODE) { + /* Peer desires to bypass FCS check, and streaming or ERTM mode */ + if (p_cfg->fcs_present) { + p_ccb->peer_cfg.fcs = p_cfg->fcs; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FCS; + if ( p_cfg->fcs == L2CAP_CFG_FCS_BYPASS) { + p_ccb->bypass_fcs |= L2CAP_CFG_FCS_PEER; + } + } + + max_retrans_size = p_ccb->ertm_info.fcr_tx_buf_size - sizeof(BT_HDR) + - L2CAP_MIN_OFFSET - L2CAP_SDU_LEN_OFFSET - L2CAP_FCS_LEN; + + /* Ensure the MPS is not bigger than the MTU */ + if ( (p_cfg->fcr.mps == 0) || (p_cfg->fcr.mps > p_ccb->peer_cfg.mtu) ) { + p_cfg->fcr.mps = p_ccb->peer_cfg.mtu; + p_ccb->out_cfg_fcr_present = TRUE; + } + + /* Ensure the MPS is not bigger than our retransmission buffer */ + if (p_cfg->fcr.mps > max_retrans_size) { + L2CAP_TRACE_DEBUG("CFG: Overriding MPS to %d (orig %d)", max_retrans_size, p_cfg->fcr.mps); + + p_cfg->fcr.mps = max_retrans_size; + p_ccb->out_cfg_fcr_present = TRUE; + } + + if (p_cfg->fcr.mode == L2CAP_FCR_ERTM_MODE || p_cfg->fcr.mode == L2CAP_FCR_STREAM_MODE) { + /* Always respond with FCR ERTM parameters */ + p_ccb->out_cfg_fcr_present = TRUE; + } + } + + /* Everything ok, so save the peer's adjusted fcr options */ + p_ccb->peer_cfg.fcr = p_cfg->fcr; + + if (p_cfg->fcr_present) { + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FCR; + } + } else if (fcr_ok == L2CAP_PEER_CFG_UNACCEPTABLE) { + /* Allow peer only one retry for mode */ + if (p_ccb->peer_cfg_already_rejected) { + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + } else { + p_ccb->peer_cfg_already_rejected = TRUE; + } + } + + return (fcr_ok); +} + +#if (L2CAP_ERTM_STATS == TRUE) +/******************************************************************************* +** +** Function l2c_fcr_collect_ack_delay +** +** Description collect throughput, delay, queue size of waiting ack +** +** Parameters +** tL2C_CCB +** +** Returns void +** +*******************************************************************************/ +static void l2c_fcr_collect_ack_delay (tL2C_CCB *p_ccb, UINT8 num_bufs_acked) +{ + UINT32 index; + BT_HDR *p_buf; + UINT8 *p; + UINT32 timestamp, delay; + UINT8 xx; + UINT8 str[120]; + + index = p_ccb->fcrb.ack_delay_avg_index; + + /* update sum, max and min of waiting for ack queue size */ + p_ccb->fcrb.ack_q_count_avg[index] += + fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q); + + if (fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q) > p_ccb->fcrb.ack_q_count_max[index]) { + p_ccb->fcrb.ack_q_count_max[index] = fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q); + } + + if (fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q) < p_ccb->fcrb.ack_q_count_min[index]) { + p_ccb->fcrb.ack_q_count_min[index] = fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q); + } + + /* update sum, max and min of round trip delay of acking */ + list_t *list = NULL; + if (! fixed_queue_is_empty(p_ccb->fcrb.waiting_for_ack_q)) { + list = fixed_queue_get_list(p_ccb->fcrb.waiting_for_ack_q); + } + if (list != NULL) { + for (const list_node_t *node = list_begin(list), xx = 0; + (node != list_end(list)) && (xx < num_bufs_acked); + node = list_next(node), xx++) { + p_buf = list_node(node); + /* adding up length of acked I-frames to get throughput */ + p_ccb->fcrb.throughput[index] += p_buf->len - 8; + + if ( xx == num_bufs_acked - 1 ) { + /* get timestamp from tx I-frame that receiver is acking */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + p_buf->len; + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) { + p += L2CAP_FCS_LEN; + } + + STREAM_TO_UINT32(timestamp, p); + delay = osi_time_get_os_boottime_ms() - timestamp; + + p_ccb->fcrb.ack_delay_avg[index] += delay; + if ( delay > p_ccb->fcrb.ack_delay_max[index] ) { + p_ccb->fcrb.ack_delay_max[index] = delay; + } + if ( delay < p_ccb->fcrb.ack_delay_min[index] ) { + p_ccb->fcrb.ack_delay_min[index] = delay; + } + } + } + } + + p_ccb->fcrb.ack_delay_avg_count++; + + /* calculate average and initialize next avg, min and max */ + if (p_ccb->fcrb.ack_delay_avg_count > L2CAP_ERTM_STATS_AVG_NUM_SAMPLES) { + p_ccb->fcrb.ack_delay_avg_count = 0; + + p_ccb->fcrb.ack_q_count_avg[index] /= L2CAP_ERTM_STATS_AVG_NUM_SAMPLES; + p_ccb->fcrb.ack_delay_avg[index] /= L2CAP_ERTM_STATS_AVG_NUM_SAMPLES; + + /* calculate throughput */ + timestamp = osi_time_get_os_boottime_ms(); + if (timestamp - p_ccb->fcrb.throughput_start > 0 ) { + p_ccb->fcrb.throughput[index] /= (timestamp - p_ccb->fcrb.throughput_start); + } + + p_ccb->fcrb.throughput_start = timestamp; + + sprintf(str, "[%02u] throughput: %5u, ack_delay avg:%3u, min:%3u, max:%3u, ack_q_count avg:%3u, min:%3u, max:%3u", + index, p_ccb->fcrb.throughput[index], + p_ccb->fcrb.ack_delay_avg[index], p_ccb->fcrb.ack_delay_min[index], p_ccb->fcrb.ack_delay_max[index], + p_ccb->fcrb.ack_q_count_avg[index], p_ccb->fcrb.ack_q_count_min[index], p_ccb->fcrb.ack_q_count_max[index] ); + + BT_TRACE(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", str); + + index = (index + 1) % L2CAP_ERTM_STATS_NUM_AVG; + p_ccb->fcrb.ack_delay_avg_index = index; + + p_ccb->fcrb.ack_q_count_max[index] = 0; + p_ccb->fcrb.ack_q_count_min[index] = 0xFFFFFFFF; + p_ccb->fcrb.ack_q_count_avg[index] = 0; + + + p_ccb->fcrb.ack_delay_max[index] = 0; + p_ccb->fcrb.ack_delay_min[index] = 0xFFFFFFFF; + p_ccb->fcrb.ack_delay_avg[index] = 0; + + p_ccb->fcrb.throughput[index] = 0; + } +} +#endif +#endif /// (L2CAP_COC_INCLUDED == TRUE) diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_link.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_link.c new file mode 100644 index 00000000..4b81b4b3 --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_link.c @@ -0,0 +1,1509 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * this file contains the functions relating to link management. A "link" + * is a connection between this device and another device. Only ACL links + * are managed. + * + ******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +//#include <stdio.h> + +#include "device/controller.h" +//#include "btcore/include/counter.h" +#include "stack/bt_types.h" +//#include "bt_utils.h" +#include "stack/hcimsgs.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/l2c_api.h" +#include "stack/btu.h" +#include "stack/btm_api.h" +#include "btm_int.h" + +static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf); + +#if (BLE_50_FEATURE_SUPPORT == TRUE) +extern tBTM_STATUS BTM_BleStartExtAdvRestart(uint8_t handle); +#endif// #if (BLE_50_FEATURE_SUPPORT == TRUE) +extern bool btm_ble_inter_get(void); + +/******************************************************************************* +** +** Function l2c_link_hci_conn_req +** +** Description This function is called when an HCI Connection Request +** event is received. +** +** Returns TRUE, if accept conn +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_conn_req (BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb; + tL2C_LCB *p_lcb_cur; + BOOLEAN no_links; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_BR_EDR); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) { + p_lcb = l2cu_allocate_lcb (bd_addr, FALSE, BT_TRANSPORT_BR_EDR); + if (!p_lcb) { + btsnd_hcic_reject_conn (bd_addr, HCI_ERR_HOST_REJECT_RESOURCES); + L2CAP_TRACE_ERROR ("L2CAP failed to allocate LCB"); + return FALSE; + } + + no_links = TRUE; + + /* If we already have connection, accept as a master */ + list_node_t *p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb_cur = list_node(p_node); + if (p_lcb_cur == p_lcb) { + continue; + } + + if (p_lcb_cur->in_use) { + no_links = FALSE; + p_lcb->link_role = HCI_ROLE_MASTER; + break; + } + } + + if (no_links) { + if (!btm_dev_support_switch (bd_addr)) { + p_lcb->link_role = HCI_ROLE_SLAVE; + } else { + p_lcb->link_role = l2cu_get_conn_role(p_lcb); + } + } + + //counter_add("l2cap.conn.accept", 1); + + /* Tell the other side we accept the connection */ + btsnd_hcic_accept_conn (bd_addr, p_lcb->link_role); + + p_lcb->link_state = LST_CONNECTING; + + /* Start a timer waiting for connect complete */ + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_CONNECT_TOUT); + return (TRUE); + } + + /* We already had a link control block to the guy. Check what state it is in */ + if ((p_lcb->link_state == LST_CONNECTING) || (p_lcb->link_state == LST_CONNECT_HOLDING)) { + /* Connection collision. Accept the connection anyways. */ + + if (!btm_dev_support_switch (bd_addr)) { + p_lcb->link_role = HCI_ROLE_SLAVE; + } else { + p_lcb->link_role = l2cu_get_conn_role(p_lcb); + } + + //counter_add("l2cap.conn.accept", 1); + btsnd_hcic_accept_conn (bd_addr, p_lcb->link_role); + + p_lcb->link_state = LST_CONNECTING; + return (TRUE); + } else if (p_lcb->link_state == LST_DISCONNECTING) { + /* In disconnecting state, reject the connection. */ + //counter_add("l2cap.conn.reject.disconn", 1); + btsnd_hcic_reject_conn (bd_addr, HCI_ERR_HOST_REJECT_DEVICE); + } else { + L2CAP_TRACE_ERROR("L2CAP got conn_req while connected (state:%d). Reject it\n", + p_lcb->link_state); + /* Reject the connection with ACL Connection Already exist reason */ + //counter_add("l2cap.conn.reject.exists", 1); + btsnd_hcic_reject_conn (bd_addr, HCI_ERR_CONNECTION_EXISTS); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_link_hci_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received. +** +** Returns void +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) +{ + tL2C_CONN_INFO ci; + tL2C_LCB *p_lcb; +#if (CLASSIC_BT_INCLUDED == TRUE) + tL2C_CCB *p_ccb; +#endif ///CLASSIC_BT_INCLUDED == TRUE + tBTM_SEC_DEV_REC *p_dev_info = NULL; + + btm_acl_update_busy_level (BTM_BLI_PAGE_DONE_EVT); + + /* Save the parameters */ + ci.status = status; + memcpy (ci.bd_addr, p_bda, BD_ADDR_LEN); + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (ci.bd_addr, BT_TRANSPORT_BR_EDR); + + /* If we don't have one, this is an error */ + if (!p_lcb) { + L2CAP_TRACE_WARNING ("L2CAP got conn_comp for unknown BD_ADDR\n"); + return (FALSE); + } + + if (p_lcb->link_state != LST_CONNECTING) { + L2CAP_TRACE_ERROR ("L2CAP got conn_comp in bad state: %d status: 0x%d\n", p_lcb->link_state, status); + + if (status != HCI_SUCCESS) { + l2c_link_hci_disc_comp (p_lcb->handle, status); + } + + return (FALSE); + } + + /* Save the handle */ + p_lcb->handle = handle; + + if (ci.status == HCI_SUCCESS) { + /* Connected OK. Change state to connected */ + p_lcb->link_state = LST_CONNECTED; + //counter_add("l2cap.conn.ok", 1); + + /* Get the peer information if the l2cap flow-control/rtrans is supported */ + l2cu_send_peer_info_req (p_lcb, L2CAP_EXTENDED_FEATURES_INFO_TYPE); + + /* Tell BTM Acl management about the link */ + if ((p_dev_info = btm_find_dev (p_bda)) != NULL) { + btm_acl_created (ci.bd_addr, p_dev_info->dev_class, + p_dev_info->sec_bd_name, handle, + p_lcb->link_role, BT_TRANSPORT_BR_EDR); + } else { + btm_acl_created (ci.bd_addr, NULL, NULL, handle, p_lcb->link_role, BT_TRANSPORT_BR_EDR); + } + + BTM_SetLinkSuperTout (ci.bd_addr, btm_cb.btm_def_link_super_tout); + + /* If dedicated bonding do not process any further */ + if (p_lcb->is_bonding) { + if (l2cu_start_post_bond_timer(handle)) { + return (TRUE); + } + } + + /* Update the timeouts in the hold queue */ + l2c_process_held_packets(FALSE); + + btu_stop_timer (&p_lcb->timer_entry); +#if (CLASSIC_BT_INCLUDED == TRUE) + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM, &ci); + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + if (p_lcb->p_echo_rsp_cb) { + l2cu_send_peer_echo_req (p_lcb, NULL, 0); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_ECHO_RSP_TOUT); + } else if (!p_lcb->ccb_queue.p_first_ccb) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_STARTUP_TOUT); + } + } + /* Max number of acl connections. */ + /* If there's an lcb disconnecting set this one to holding */ + else if ((ci.status == HCI_ERR_MAX_NUM_OF_CONNECTIONS) && l2cu_lcb_disconnecting()) { + p_lcb->link_state = LST_CONNECT_HOLDING; + p_lcb->handle = HCI_INVALID_HANDLE; + } else { + /* Just in case app decides to try again in the callback context */ + p_lcb->link_state = LST_DISCONNECTING; +#if(CLASSIC_BT_INCLUDED == TRUE) + /* Connection failed. For all channels, send the event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM_NEG, &ci); + + p_ccb = pn; + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + p_lcb->disc_reason = status; + /* Release the LCB */ + if (p_lcb->ccb_queue.p_first_ccb == NULL) { + l2cu_release_lcb (p_lcb); + } else { /* there are any CCBs remaining */ + if (ci.status == HCI_ERR_CONNECTION_EXISTS) { + /* we are in collision situation, wait for connection request from controller */ + p_lcb->link_state = LST_CONNECTING; + } else { + l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); + } + } + } + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_link_sec_comp +** +** Description This function is called when required security procedures +** are completed. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_sec_comp (BD_ADDR p_bda, tBT_TRANSPORT transport, void *p_ref_data, UINT8 status) +{ + tL2C_CONN_INFO ci; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_CCB *p_next_ccb; +#if (CLASSIC_BT_INCLUDED == TRUE) + UINT8 event; +#endif ///CLASSIC_BT_INCLUDED == TRUE + UNUSED(transport); + + L2CAP_TRACE_DEBUG ("l2c_link_sec_comp: %d, %p", status, p_ref_data); + + if (status == BTM_SUCCESS_NO_SECURITY) { + status = BTM_SUCCESS; + } + + /* Save the parameters */ + ci.status = status; + memcpy (ci.bd_addr, p_bda, BD_ADDR_LEN); + + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda, BT_TRANSPORT_BR_EDR); + + /* If we don't have one, this is an error */ + if (!p_lcb) { + L2CAP_TRACE_WARNING ("L2CAP got sec_comp for unknown BD_ADDR\n"); + return; + } + + /* Match p_ccb with p_ref_data returned by sec manager */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) { + p_next_ccb = p_ccb->p_next_ccb; + + if (p_ccb == p_ref_data) { + switch (status) { + case BTM_SUCCESS: + L2CAP_TRACE_DEBUG ("ccb timer ticks: %u", p_ccb->timer_entry.ticks); +#if (CLASSIC_BT_INCLUDED == TRUE) + event = L2CEVT_SEC_COMP; +#endif ///CLASSIC_BT_INCLUDED == TRUE + break; + + case BTM_DELAY_CHECK: + /* start a timer - encryption change not received before L2CAP connect req */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_DELAY_CHECK_SM4); + return; + + default: +#if (CLASSIC_BT_INCLUDED == TRUE) + event = L2CEVT_SEC_COMP_NEG; +#endif ///CLASSIC_BT_INCLUDED == TRUE + break; + } +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_csm_execute (p_ccb, event, &ci); +#endif ///CLASSIC_BT_INCLUDED == TRUE + break; + } + } +} + +/******************************************************************************* +** +** Function l2c_link_hci_disc_comp +** +** Description This function is called when an HCI Disconnect Complete +** event is received. +** +** Returns TRUE if the link is known about, else FALSE +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason) +{ + tL2C_LCB *p_lcb; +#if (CLASSIC_BT_INCLUDED == TRUE) + tL2C_CCB *p_ccb; +#endif ///CLASSIC_BT_INCLUDED == TRUE + BOOLEAN status = TRUE; + BOOLEAN lcb_is_free = TRUE; + tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; + + /* See if we have a link control block for the connection */ + p_lcb = l2cu_find_lcb_by_handle (handle); + /* If we don't have one, maybe an SCO link. Send to MM */ + if (!p_lcb) { +#if (BLE_INCLUDED == TRUE) + /* The Directed Advertising Timeout error code indicates that directed advertising completed */ + if (reason != HCI_ERR_DIRECTED_ADVERTISING_TIMEOUT) { + BTM_Recovery_Pre_State(); + } +#endif ///BLE_INCLUDED == TRUE + status = FALSE; + } else { + /* There can be a case when we rejected PIN code authentication */ + /* otherwise save a new reason */ + if (btm_cb.acl_disc_reason != HCI_ERR_HOST_REJECT_SECURITY) { + btm_cb.acl_disc_reason = reason; + } + + p_lcb->disc_reason = btm_cb.acl_disc_reason; + + /* Just in case app decides to try again in the callback context */ + p_lcb->link_state = LST_DISCONNECTING; + +#if (BLE_INCLUDED == TRUE) + /* Check for BLE and handle that differently */ + if (p_lcb->transport == BT_TRANSPORT_LE) { + btm_ble_update_link_topology_mask(p_lcb->link_role, FALSE); + } +#endif +#if (CLASSIC_BT_INCLUDED == TRUE) + /* Link is disconnected. For all channels, send the event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + /* Keep connect pending control block (if exists) + * Possible Race condition when a reconnect occurs + * on the channel during a disconnect of link. This + * ccb will be automatically retried after link disconnect + * arrives + */ + if (p_ccb != p_lcb->p_pending_ccb) { + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, &reason); + } + p_ccb = pn; + } +#endif ///CLASSIC_BT_INCLUDED == TRUE +#if (BTM_SCO_INCLUDED == TRUE) +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_BR_EDR) +#endif + { + /* Tell SCO management to drop any SCOs on this ACL */ + btm_sco_acl_removed (p_lcb->remote_bd_addr); + } +#endif + + /* If waiting for disconnect and reconnect is pending start the reconnect now + race condition where layer above issued connect request on link that was + disconnecting + */ + if (p_lcb->ccb_queue.p_first_ccb != NULL || p_lcb->p_pending_ccb) { + L2CAP_TRACE_DEBUG("l2c_link_hci_disc_comp: Restarting pending ACL request"); + transport = p_lcb->transport; +#if BLE_INCLUDED == TRUE + /* for LE link, always drop and re-open to ensure to get LE remote feature */ + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.is_ble_connecting = FALSE; + btm_acl_removed (p_lcb->remote_bd_addr, p_lcb->transport); + /* Release any held buffers */ + BT_HDR *p_buf; + while (!list_is_empty(p_lcb->link_xmit_data_q)) { + p_buf = list_front(p_lcb->link_xmit_data_q); + list_remove(p_lcb->link_xmit_data_q, p_buf); + osi_free(p_buf); + } + } else +#endif + { +#if (L2CAP_NUM_FIXED_CHNLS > 0) + /* If we are going to re-use the LCB without dropping it, release all fixed channels + here */ + int xx; + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { + if (p_lcb->p_fixed_ccbs[xx] && p_lcb->p_fixed_ccbs[xx] != p_lcb->p_pending_ccb) { +#if BLE_INCLUDED == TRUE + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, p_lcb->transport); +#else + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, BT_TRANSPORT_BR_EDR); +#endif + l2cu_release_ccb (p_lcb->p_fixed_ccbs[xx]); + + p_lcb->p_fixed_ccbs[xx] = NULL; + } + } +#endif + } + if (l2cu_create_conn(p_lcb, transport)) { + lcb_is_free = FALSE; /* still using this lcb */ + } + } + + p_lcb->p_pending_ccb = NULL; +#if (BLE_INCLUDED == TRUE && GATTC_CONNECT_RETRY_EN == TRUE) + if(reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT && p_lcb->transport == BT_TRANSPORT_LE) { + + if(p_lcb->link_role == HCI_ROLE_MASTER && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) { + L2CAP_TRACE_DEBUG("master retry connect, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + p_lcb->retry_create_con ++; + // create connection retry + if (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE)) { + btm_acl_removed (p_lcb->remote_bd_addr, BT_TRANSPORT_LE); + lcb_is_free = FALSE; /* still using this lcb */ + } + } + + #if (BLE_50_FEATURE_SUPPORT == TRUE) + if(btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) { + p_lcb->retry_create_con ++; + L2CAP_TRACE_DEBUG("slave restart extend adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + BTM_BleStartExtAdvRestart(handle); + } + #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) + + #if (BLE_42_FEATURE_SUPPORT == TRUE) + if(!btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) { + p_lcb->retry_create_con ++; + L2CAP_TRACE_DEBUG("slave resatrt adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + btm_ble_start_adv(); + } + #endif // #if (BLE_42_FEATURE_SUPPORT == TRUE) + } + + +#endif // #if (BLE_INCLUDED == TRUE) + /* Release the LCB */ + if (lcb_is_free) { + l2cu_release_lcb (p_lcb); + } + } + + /* Now that we have a free acl connection, see if any lcbs are pending */ + if (lcb_is_free && ((p_lcb = l2cu_find_lcb_by_state(LST_CONNECT_HOLDING)) != NULL)) { + /* we found one-- create a connection */ + l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); + } + + return status; +} + + +/******************************************************************************* +** +** Function l2c_link_hci_qos_violation +** +** Description This function is called when an HCI QOS Violation +** event is received. +** +** Returns TRUE if the link is known about, else FALSE +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_qos_violation (UINT16 handle) +{ + tL2C_LCB *p_lcb; +#if (CLASSIC_BT_INCLUDED == TRUE) + tL2C_CCB *p_ccb; +#endif ///CLASSIC_BT_INCLUDED == TRUE + /* See if we have a link control block for the connection */ + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* If we don't have one, maybe an SCO link. */ + if (!p_lcb) { + return (FALSE); + } +#if (CLASSIC_BT_INCLUDED == TRUE) + /* For all channels, tell the upper layer about it */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb) { + l2c_csm_execute (p_ccb, L2CEVT_LP_QOS_VIOLATION_IND, NULL); + } + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + return (TRUE); +} + + + +/******************************************************************************* +** +** Function l2c_link_timeout +** +** Description This function is called when a link timer expires +** +** Returns void +** +*******************************************************************************/ +void l2c_link_timeout (tL2C_LCB *p_lcb) +{ +#if (CLASSIC_BT_INCLUDED == TRUE) + tL2C_CCB *p_ccb; +#endif ///CLASSIC_BT_INCLUDED == TRUE +#if (SMP_INCLUDED == TRUE) + UINT16 timeout; + tBTM_STATUS rc; +#endif ///SMP_INCLUDED == TRUE + L2CAP_TRACE_EVENT ("L2CAP - l2c_link_timeout() link state %d first CCB %p is_bonding:%d", + p_lcb->link_state, p_lcb->ccb_queue.p_first_ccb, p_lcb->is_bonding); + + /* If link was connecting or disconnecting, clear all channels and drop the LCB */ + if ((p_lcb->link_state == LST_CONNECTING_WAIT_SWITCH) || + (p_lcb->link_state == LST_CONNECTING) || + (p_lcb->link_state == LST_CONNECT_HOLDING) || + (p_lcb->link_state == LST_DISCONNECTING)) { + p_lcb->p_pending_ccb = NULL; +#if (CLASSIC_BT_INCLUDED == TRUE) + /* For all channels, send a disconnect indication event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + + p_ccb = pn; + } +#endif ///CLASSIC_BT_INCLUDED == TRUE +#if (BLE_INCLUDED == TRUE) + if (p_lcb->link_state == LST_CONNECTING && + l2cb.is_ble_connecting == TRUE) { + L2CA_CancelBleConnectReq(l2cb.ble_connecting_bda); + } +#endif + /* Release the LCB */ + l2cu_release_lcb (p_lcb); + } + + /* If link is connected, check for inactivity timeout */ + if (p_lcb->link_state == LST_CONNECTED) { + /* Check for ping outstanding */ + if (p_lcb->p_echo_rsp_cb) { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_NO_RESP); + + L2CAP_TRACE_WARNING ("L2CAP - ping timeout"); +#if (CLASSIC_BT_INCLUDED == TRUE) + /* For all channels, send a disconnect indication event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + + p_ccb = pn; + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + } + +#if (SMP_INCLUDED == TRUE) + /* If no channels in use, drop the link. */ + if (!p_lcb->ccb_queue.p_first_ccb) { + rc = btm_sec_disconnect (p_lcb->handle, HCI_ERR_PEER_USER); + + if (rc == BTM_CMD_STORED) { + /* Security Manager will take care of disconnecting, state will be updated at that time */ + timeout = 0xFFFF; + } else if (rc == BTM_CMD_STARTED) { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else if (rc == BTM_SUCCESS) { + l2cu_process_fixed_disc_cback(p_lcb); + /* BTM SEC will make sure that link is release (probably after pairing is done) */ + p_lcb->link_state = LST_DISCONNECTING; + timeout = 0xFFFF; + } else if (rc == BTM_BUSY) { + /* BTM is still executing security process. Let lcb stay as connected */ + timeout = 0xFFFF; + } else if ((p_lcb->is_bonding) + && (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER))) { + l2cu_process_fixed_disc_cback(p_lcb); + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else { + /* probably no buffer to send disconnect */ + timeout = BT_1SEC_TIMEOUT; + } + + if (timeout != 0xFFFF) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + } + } else { + /* Check in case we were flow controlled */ + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + } +#endif ///SMP_INCLUDED == TRUE + } +} + +/******************************************************************************* +** +** Function l2c_info_timeout +** +** Description This function is called when an info request times out +** +** Returns void +** +*******************************************************************************/ +void l2c_info_timeout (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; +#if (CLASSIC_BT_INCLUDED == TRUE) + tL2C_CONN_INFO ci; +#endif ///CLASSIC_BT_INCLUDED == TRUE + /* If we timed out waiting for info response, just continue using basic if allowed */ + if (p_lcb->w4_info_rsp) { + /* If waiting for security complete, restart the info response timer */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if ( (p_ccb->chnl_state == CST_ORIG_W4_SEC_COMP) || (p_ccb->chnl_state == CST_TERM_W4_SEC_COMP) ) { + btu_start_timer (&p_lcb->info_timer_entry, BTU_TTYPE_L2CAP_INFO, L2CAP_WAIT_INFO_RSP_TOUT); + return; + } + } + + p_lcb->w4_info_rsp = FALSE; +#if (CLASSIC_BT_INCLUDED == TRUE) + /* If link is in process of being brought up */ + if ((p_lcb->link_state != LST_DISCONNECTED) && + (p_lcb->link_state != LST_DISCONNECTING)) { + /* Notify active channels that peer info is finished */ + if (p_lcb->ccb_queue.p_first_ccb) { + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + } + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + } +} + +/******************************************************************************* +** +** Function l2c_link_adjust_allocation +** +** Description This function is called when a link is created or removed +** to calculate the amount of packets each link may send to +** the HCI without an ack coming back. +** +** Currently, this is a simple allocation, dividing the +** number of Controller Packets by the number of links. In +** the future, QOS configuration should be examined. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_adjust_allocation (void) +{ + UINT16 qq, qq_remainder; + tL2C_LCB *p_lcb; + UINT16 hi_quota, low_quota; + UINT16 num_lowpri_links = 0; + UINT16 num_hipri_links = 0; + UINT16 controller_xmit_quota = l2cb.num_lm_acl_bufs; + UINT16 high_pri_link_quota = L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A; + list_node_t *p_node = NULL; + + /* If no links active, reset buffer quotas and controller buffers */ + if (l2cb.num_links_active == 0) { + l2cb.controller_xmit_window = l2cb.num_lm_acl_bufs; + l2cb.round_robin_quota = l2cb.round_robin_unacked = 0; + return; + } + + /* First, count the links */ + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use) { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) { + num_hipri_links++; + } else { + num_lowpri_links++; + } + } + } + + /* now adjust high priority link quota */ + low_quota = num_lowpri_links ? 1 : 0; + while ( (num_hipri_links * high_pri_link_quota + low_quota) > controller_xmit_quota ) { + high_pri_link_quota--; + } + + /* Work out the xmit quota and buffer quota high and low priorities */ + hi_quota = num_hipri_links * high_pri_link_quota; + low_quota = (hi_quota < controller_xmit_quota) ? controller_xmit_quota - hi_quota : 1; + + /* Work out and save the HCI xmit quota for each low priority link */ + + /* If each low priority link cannot have at least one buffer */ + if (num_lowpri_links > low_quota) { + l2cb.round_robin_quota = low_quota; + qq = qq_remainder = 1; + } + /* If each low priority link can have at least one buffer */ + else if (num_lowpri_links > 0) { + l2cb.round_robin_quota = 0; + l2cb.round_robin_unacked = 0; + qq = low_quota / num_lowpri_links; + qq_remainder = low_quota % num_lowpri_links; + } + /* If no low priority link */ + else { + l2cb.round_robin_quota = 0; + l2cb.round_robin_unacked = 0; + qq = qq_remainder = 1; + } + + L2CAP_TRACE_EVENT ("l2c_link_adjust_allocation num_hipri: %u num_lowpri: %u low_quota: %u round_robin_quota: %u qq: %u\n", + num_hipri_links, num_lowpri_links, low_quota, + l2cb.round_robin_quota, qq); + + /* Now, assign the quotas to each link */ + p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use) { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) { + p_lcb->link_xmit_quota = high_pri_link_quota; + } else { + /* Safety check in case we switched to round-robin with something outstanding */ + /* if sent_not_acked is added into round_robin_unacked then don't add it again */ + /* l2cap keeps updating sent_not_acked for exiting from round robin */ + if (( p_lcb->link_xmit_quota > 0 ) && ( qq == 0 )) { + l2cb.round_robin_unacked += p_lcb->sent_not_acked; + } + + p_lcb->link_xmit_quota = qq; + if (qq_remainder > 0) { + p_lcb->link_xmit_quota++; + qq_remainder--; + } + } + + L2CAP_TRACE_EVENT ("l2c_link_adjust_allocation Priority: %d XmitQuota: %d\n", + p_lcb->acl_priority, p_lcb->link_xmit_quota); + + L2CAP_TRACE_EVENT (" SentNotAcked: %d RRUnacked: %d\n", + p_lcb->sent_not_acked, l2cb.round_robin_unacked); + + /* There is a special case where we have readjusted the link quotas and */ + /* this link may have sent anything but some other link sent packets so */ + /* so we may need a timer to kick off this link's transmissions. */ + if ( (p_lcb->link_state == LST_CONNECTED) + && (!list_is_empty(p_lcb->link_xmit_data_q)) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota) ) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_FLOW_CONTROL_TOUT); + } + } + } + +} + +/******************************************************************************* +** +** Function l2c_link_adjust_chnl_allocation +** +** Description This function is called to calculate the amount of packets each +** non-F&EC channel may have outstanding. +** +** Currently, this is a simple allocation, dividing the number +** of packets allocated to the link by the number of channels. In +** the future, QOS configuration should be examined. +** +** Returns void +** +*******************************************************************************/ + +bool l2c_chnl_allocation_in_ccb_list (void *p_ccb_node, void *context) +{ + UNUSED(context); + tL2C_CCB *p_ccb = (tL2C_CCB *)p_ccb_node; + if (p_ccb->in_use) { + tL2CAP_CHNL_DATA_RATE data_rate = p_ccb->tx_data_rate + p_ccb->rx_data_rate; + p_ccb->buff_quota = L2CAP_CBB_DEFAULT_DATA_RATE_BUFF_QUOTA * data_rate; + L2CAP_TRACE_EVENT("CID:0x%04x FCR Mode:%u Priority:%u TxDataRate:%u RxDataRate:%u Quota:%u", + p_ccb->local_cid, p_ccb->peer_cfg.fcr.mode, + p_ccb->ccb_priority, p_ccb->tx_data_rate, + p_ccb->rx_data_rate, p_ccb->buff_quota); + + /* quota may be change so check congestion */ + l2cu_check_channel_congestion (p_ccb); + } + return false; +} +void l2c_link_adjust_chnl_allocation (void) +{ + + L2CAP_TRACE_DEBUG ("l2c_link_adjust_chnl_allocation"); + + /* assign buffer quota to each channel based on its data rate requirement */ + list_foreach(l2cb.p_ccb_pool, l2c_chnl_allocation_in_ccb_list, NULL); +} + +/******************************************************************************* +** +** Function l2c_link_processs_num_bufs +** +** Description This function is called when a "controller buffer size" +** event is first received from the controller. It updates +** the L2CAP values. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_processs_num_bufs (UINT16 num_lm_acl_bufs) +{ + l2cb.num_lm_acl_bufs = l2cb.controller_xmit_window = num_lm_acl_bufs; + +} + +/******************************************************************************* +** +** Function l2c_link_pkts_rcvd +** +** Description This function is called from the HCI transport when it is time +** tto send a "Host ready for packets" command. This is only when +** host to controller flow control is used. If fills in the arrays +** of numbers of packets and handles. +** +** Returns count of number of entries filled in +** +*******************************************************************************/ +UINT8 l2c_link_pkts_rcvd (UINT16 *num_pkts, UINT16 *handles) +{ + UINT8 num_found = 0; + + UNUSED(num_pkts); + UNUSED(handles); + + return (num_found); +} + +/******************************************************************************* +** +** Function l2c_link_role_changed +** +** Description This function is called whan a link's master/slave role change +** event is received. It simply updates the link control block. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_role_changed (BD_ADDR bd_addr, UINT8 new_role, UINT8 hci_status) +{ + tL2C_LCB *p_lcb; + + /* Make sure not called from HCI Command Status (bd_addr and new_role are invalid) */ + if (bd_addr) { + /* If here came form hci role change event */ + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_BR_EDR); + if (p_lcb) { + p_lcb->link_role = new_role; + + /* Reset high priority link if needed */ + if (hci_status == HCI_SUCCESS) { + l2cu_set_acl_priority(bd_addr, p_lcb->acl_priority, TRUE); + } + } + } + + /* Check if any LCB was waiting for switch to be completed */ + list_node_t *p_node = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTING_WAIT_SWITCH)) { + l2cu_create_conn_after_switch (p_lcb); + } + } +} + +/******************************************************************************* +** +** Function l2c_pin_code_request +** +** Description This function is called whan a pin-code request is received +** on a connection. If there are no channels active yet on the +** link, it extends the link first connection timer. Make sure +** that inactivity timer is not extended if PIN code happens +** to be after last ccb released. +** +** Returns void +** +*******************************************************************************/ +void l2c_pin_code_request (BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr, BT_TRANSPORT_BR_EDR); + + if ( (p_lcb) && (!p_lcb->ccb_queue.p_first_ccb) ) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_CONNECT_TOUT_EXT); + } +} + +#if L2CAP_WAKE_PARKED_LINK == TRUE +/******************************************************************************* +** +** Function l2c_link_check_power_mode +** +** Description This function is called to check power mode. +** +** Returns TRUE if link is going to be active from park +** FALSE if nothing to send or not in park mode +** +*******************************************************************************/ +BOOLEAN l2c_link_check_power_mode (tL2C_LCB *p_lcb) +{ + tBTM_PM_MODE mode; + tL2C_CCB *p_ccb; + BOOLEAN need_to_active = FALSE; + + /* + * We only switch park to active only if we have unsent packets + */ + if (list_is_empty(p_lcb->link_xmit_data_q)) { + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + need_to_active = TRUE; + break; + } + } + } else { + need_to_active = TRUE; + } + + /* if we have packets to send */ + if ( need_to_active ) { + /* check power mode */ + if (BTM_ReadPowerMode(p_lcb->remote_bd_addr, &mode) == BTM_SUCCESS) { + if ( mode == BTM_PM_STS_PENDING ) { + L2CAP_TRACE_DEBUG ("LCB(0x%x) is in PM pending state\n", p_lcb->handle); + + return TRUE; + } + } + } + return FALSE; +} +#endif /* L2CAP_WAKE_PARKED_LINK == TRUE) */ + +/******************************************************************************* +** +** Function l2c_link_check_send_pkts +** +** Description This function is called to check if it can send packets +** to the Host Controller. It may be passed the address of +** a packet to send. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + BOOLEAN single_write = FALSE; + L2CAP_TRACE_DEBUG("%s",__func__); + /* Save the channel ID for faster counting */ + if (p_buf) { + if (p_ccb != NULL) { + p_buf->event = p_ccb->local_cid; + single_write = TRUE; + } else { + p_buf->event = 0; + } + + p_buf->layer_specific = 0; + list_append(p_lcb->link_xmit_data_q, p_buf); + + if (p_lcb->link_xmit_quota == 0) { +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.ble_check_round_robin = TRUE; + } else +#endif + { + l2cb.check_round_robin = TRUE; + } + } + } + + /* If this is called from uncongested callback context break recursive calling. + ** This LCB will be served when receiving number of completed packet event. + */ + if (l2cb.is_cong_cback_context) { + L2CAP_TRACE_ERROR("l2cab is_cong_cback_context"); + return; + } + + /* If we are in a scenario where there are not enough buffers for each link to + ** have at least 1, then do a round-robin for all the LCBs + */ + if ( (p_lcb == NULL) || (p_lcb->link_xmit_quota == 0) ) { + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb_cur = NULL; + if (p_lcb == NULL) { + p_node = list_begin(l2cb.p_lcb_pool); + p_lcb = list_node(p_node); + } else if (!single_write) { + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb_cur = list_node(p_node); + if (p_lcb_cur == p_lcb) { + p_node = list_next(p_node); + p_lcb = list_node(p_node); + break; + } + } + } + + /* Loop through, starting at the next */ + for ( ; p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); +#if (BLE_INCLUDED == TRUE) + L2CAP_TRACE_DEBUG("window = %d,robin_unacked = %d,robin_quota=%d",l2cb.controller_le_xmit_window,l2cb.ble_round_robin_unacked,l2cb.ble_round_robin_quota); +#endif ///BLE_INCLUDED == TRUE + /* If controller window is full, nothing to do */ + if (((l2cb.controller_xmit_window == 0 || + (l2cb.round_robin_unacked >= l2cb.round_robin_quota)) +#if (BLE_INCLUDED == TRUE) + && (p_lcb->transport == BT_TRANSPORT_BR_EDR) + ) + || (p_lcb->transport == BT_TRANSPORT_LE && + (l2cb.ble_round_robin_unacked >= l2cb.ble_round_robin_quota || + l2cb.controller_le_xmit_window == 0 ))) +#else + )) +#endif ///BLE_INCLUDED == TRUE + break; + + + /* Check for wraparound */ + if (p_node == list_end(l2cb.p_lcb_pool)) { + p_node = list_begin(l2cb.p_lcb_pool); + p_lcb = list_node(p_node); + } + L2CAP_TRACE_DEBUG("in_use=%d,segment_being_sent=%d,link_state=%d,link_xmit_quota=%d",p_lcb->in_use,p_lcb->partial_segment_being_sent,p_lcb->link_state,p_lcb->link_xmit_quota); + if ( (!p_lcb->in_use) + || (p_lcb->partial_segment_being_sent) + || (p_lcb->link_state != LST_CONNECTED) + || (p_lcb->link_xmit_quota != 0) + || (L2C_LINK_CHECK_POWER_MODE (p_lcb)) ) { + continue; + } + + /* See if we can send anything from the Link Queue */ + if (!list_is_empty(p_lcb->link_xmit_data_q)) { + p_buf = (BT_HDR *)list_front(p_lcb->link_xmit_data_q); + list_remove(p_lcb->link_xmit_data_q, p_buf); + l2c_link_send_to_lower (p_lcb, p_buf); + } else if (single_write) { + /* If only doing one write, break out */ + break; + } + /* If nothing on the link queue, check the channel queue */ + else if ((p_buf = l2cu_get_next_buffer_to_send (p_lcb)) != NULL) { + l2c_link_send_to_lower (p_lcb, p_buf); + } + } + + /* If we finished without using up our quota, no need for a safety check */ + if ( (l2cb.controller_xmit_window > 0) + && (l2cb.round_robin_unacked < l2cb.round_robin_quota) +#if (BLE_INCLUDED == TRUE) + && (p_lcb->transport == BT_TRANSPORT_BR_EDR) +#endif + ) { + l2cb.check_round_robin = FALSE; + } + +#if (BLE_INCLUDED == TRUE) + if ( (l2cb.controller_le_xmit_window > 0) + && (l2cb.ble_round_robin_unacked < l2cb.ble_round_robin_quota) + && (p_lcb->transport == BT_TRANSPORT_LE)) { + l2cb.ble_check_round_robin = FALSE; + } +#endif + } else { /* if this is not round-robin service */ + /* If a partial segment is being sent, can't send anything else */ + L2CAP_TRACE_DEBUG("partial_segment_being_sent=%d,link_state=%d,power_mode=%d",p_lcb->partial_segment_being_sent,p_lcb->link_state,L2C_LINK_CHECK_POWER_MODE (p_lcb)); + if ( (p_lcb->partial_segment_being_sent) + || (p_lcb->link_state != LST_CONNECTED) + || (L2C_LINK_CHECK_POWER_MODE (p_lcb)) ) { + return; + } + + /* See if we can send anything from the link queue */ +#if (BLE_INCLUDED == TRUE) + while ( ((l2cb.controller_xmit_window != 0 && (p_lcb->transport == BT_TRANSPORT_BR_EDR)) || + (l2cb.controller_le_xmit_window != 0 && (p_lcb->transport == BT_TRANSPORT_LE))) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#else + while ( (l2cb.controller_xmit_window != 0) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#endif + { + if (list_is_empty(p_lcb->link_xmit_data_q)) { + break; + } + + p_buf = (BT_HDR *)list_front(p_lcb->link_xmit_data_q); + list_remove(p_lcb->link_xmit_data_q, p_buf); + if (!l2c_link_send_to_lower (p_lcb, p_buf)) { + break; + } + } + + if (!single_write) { + /* See if we can send anything for any channel */ +#if (BLE_INCLUDED == TRUE) + while ( ((l2cb.controller_xmit_window != 0 && (p_lcb->transport == BT_TRANSPORT_BR_EDR)) || + (l2cb.controller_le_xmit_window != 0 && (p_lcb->transport == BT_TRANSPORT_LE))) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#else + while ((l2cb.controller_xmit_window != 0) && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#endif + { + //need check flag: partial_segment_being_sent + if ( (p_lcb->partial_segment_being_sent) + || (p_lcb->link_state != LST_CONNECTED) + || (L2C_LINK_CHECK_POWER_MODE (p_lcb)) ) { + break; + } + //L2CAP_TRACE_DEBUG("l2cu_get_next_buffer_to_send = %p",l2cu_get_next_buffer_to_send(p_lcb)); + if ((p_buf = l2cu_get_next_buffer_to_send (p_lcb)) == NULL) { + break; + } + + if (!l2c_link_send_to_lower (p_lcb, p_buf)) { + break; + } + } + } + + /* There is a special case where we have readjusted the link quotas and */ + /* this link may have sent anything but some other link sent packets so */ + /* so we may need a timer to kick off this link's transmissions. */ + if ( (!list_is_empty(p_lcb->link_xmit_data_q)) && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota) ) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_FLOW_CONTROL_TOUT); + } + } + +} + +/******************************************************************************* +** +** Function l2c_link_send_to_lower +** +** Description This function queues the buffer for HCI transmission +** +** Returns TRUE for success, FALSE for fail +** +*******************************************************************************/ +static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf) +{ + UINT16 num_segs; + UINT16 xmit_window, acl_data_size; + const controller_t *controller = controller_get_interface(); + L2CAP_TRACE_DEBUG("%s",__func__); + if ((p_buf->len <= controller->get_acl_packet_size_classic() +#if (BLE_INCLUDED == TRUE) + && (p_lcb->transport == BT_TRANSPORT_BR_EDR)) || + ((p_lcb->transport == BT_TRANSPORT_LE) && (p_buf->len <= controller->get_acl_packet_size_ble())) +#else + ) +#endif + ) { + if (p_lcb->link_xmit_quota == 0) { +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.ble_round_robin_unacked++; + } else +#endif + l2cb.round_robin_unacked++; + } + p_lcb->sent_not_acked++; + p_buf->layer_specific = 0; + +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.controller_le_xmit_window--; + bte_main_hci_send(p_buf, (UINT16)(BT_EVT_TO_LM_HCI_ACL | LOCAL_BLE_CONTROLLER_ID)); + } else +#endif + { + l2cb.controller_xmit_window--; + bte_main_hci_send(p_buf, BT_EVT_TO_LM_HCI_ACL); + } + } else { +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + acl_data_size = controller->get_acl_data_size_ble(); + xmit_window = l2cb.controller_le_xmit_window; + + } else +#endif + { + acl_data_size = controller->get_acl_data_size_classic(); + xmit_window = l2cb.controller_xmit_window; + } + num_segs = (p_buf->len - HCI_DATA_PREAMBLE_SIZE + acl_data_size - 1) / acl_data_size; + + + /* If doing round-robin, then only 1 segment each time */ + if (p_lcb->link_xmit_quota == 0) { + num_segs = 1; + p_lcb->partial_segment_being_sent = TRUE; + } else { + /* Multi-segment packet. Make sure it can fit */ + if (num_segs > xmit_window) { + num_segs = xmit_window; + p_lcb->partial_segment_being_sent = TRUE; + } + + if (num_segs > (p_lcb->link_xmit_quota - p_lcb->sent_not_acked)) { + num_segs = (p_lcb->link_xmit_quota - p_lcb->sent_not_acked); + p_lcb->partial_segment_being_sent = TRUE; + } + } + + p_buf->layer_specific = num_segs; +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.controller_le_xmit_window -= num_segs; + if (p_lcb->link_xmit_quota == 0) { + l2cb.ble_round_robin_unacked += num_segs; + } + } else +#endif + { + l2cb.controller_xmit_window -= num_segs; + + if (p_lcb->link_xmit_quota == 0) { + l2cb.round_robin_unacked += num_segs; + } + } + + p_lcb->sent_not_acked += num_segs; +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + bte_main_hci_send(p_buf, (UINT16)(BT_EVT_TO_LM_HCI_ACL | LOCAL_BLE_CONTROLLER_ID)); + } else +#endif + { + bte_main_hci_send(p_buf, BT_EVT_TO_LM_HCI_ACL); + } + } + +#if (L2CAP_HCI_FLOW_CONTROL_DEBUG == TRUE) +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + L2CAP_TRACE_DEBUG ("TotalWin=%d,Hndl=0x%x,Quota=%d,Unack=%d,RRQuota=%d,RRUnack=%d", + l2cb.controller_le_xmit_window, + p_lcb->handle, + p_lcb->link_xmit_quota, p_lcb->sent_not_acked, + l2cb.ble_round_robin_quota, l2cb.ble_round_robin_unacked); + } else +#endif + { + L2CAP_TRACE_DEBUG ("TotalWin=%d,Hndl=0x%x,Quota=%d,Unack=%d,RRQuota=%d,RRUnack=%d", + l2cb.controller_xmit_window, + p_lcb->handle, + p_lcb->link_xmit_quota, p_lcb->sent_not_acked, + l2cb.round_robin_quota, l2cb.round_robin_unacked); + } +#endif + + return TRUE; +} + +/******************************************************************************* +** +** Function l2c_link_process_num_completed_pkts +** +** Description This function is called when a "number-of-completed-packets" +** event is received from the controller. It updates all the +** LCB transmit counts. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_process_num_completed_pkts (UINT8 *p) +{ + UINT8 num_handles, xx; + UINT16 handle; + UINT16 num_sent; + tL2C_LCB *p_lcb; + + STREAM_TO_UINT8 (num_handles, p); + + for (xx = 0; xx < num_handles; xx++) { + STREAM_TO_UINT16 (handle, p); + STREAM_TO_UINT16 (num_sent, p); + + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* Callback for number of completed packet event */ + /* Originally designed for [3DSG] */ + if ((p_lcb != NULL) && (p_lcb->p_nocp_cb)) { + L2CAP_TRACE_DEBUG ("L2CAP - calling NoCP callback"); + (*p_lcb->p_nocp_cb)(p_lcb->remote_bd_addr); + } + + if (p_lcb) { +#if (BLE_INCLUDED == TRUE) + if (p_lcb && (p_lcb->transport == BT_TRANSPORT_LE)) { + l2cb.controller_le_xmit_window += num_sent; + } else +#endif + { + /* Maintain the total window to the controller */ + l2cb.controller_xmit_window += num_sent; + } + /* If doing round-robin, adjust communal counts */ + if (p_lcb->link_xmit_quota == 0) { +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + /* Don't go negative */ + if (l2cb.ble_round_robin_unacked > num_sent) { + l2cb.ble_round_robin_unacked -= num_sent; + } else { + l2cb.ble_round_robin_unacked = 0; + } + } else +#endif + { + /* Don't go negative */ + if (l2cb.round_robin_unacked > num_sent) { + l2cb.round_robin_unacked -= num_sent; + } else { + l2cb.round_robin_unacked = 0; + } + } + } + + /* Don't go negative */ + if (p_lcb->sent_not_acked > num_sent) { + p_lcb->sent_not_acked -= num_sent; + } else { + p_lcb->sent_not_acked = 0; + } + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + + /* If we were doing round-robin for low priority links, check 'em */ + if ( (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) + && (l2cb.check_round_robin) + && (l2cb.round_robin_unacked < l2cb.round_robin_quota) ) { + l2c_link_check_send_pkts (NULL, NULL, NULL); + } +#if BLE_INCLUDED == TRUE + if ((p_lcb->transport == BT_TRANSPORT_LE) + && (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) + && ((l2cb.ble_check_round_robin) + && (l2cb.ble_round_robin_unacked < l2cb.ble_round_robin_quota))) { + l2c_link_check_send_pkts (NULL, NULL, NULL); + } +#endif + } + +#if (L2CAP_HCI_FLOW_CONTROL_DEBUG == TRUE) + if (p_lcb) { +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + L2CAP_TRACE_DEBUG ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d\n", + l2cb.controller_le_xmit_window, + p_lcb->handle, p_lcb->sent_not_acked, + l2cb.ble_check_round_robin, l2cb.ble_round_robin_unacked); + } else +#endif + { + L2CAP_TRACE_DEBUG ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d\n", + l2cb.controller_xmit_window, + p_lcb->handle, p_lcb->sent_not_acked, + l2cb.check_round_robin, l2cb.round_robin_unacked); + + } + } else { +#if (BLE_INCLUDED == TRUE) + L2CAP_TRACE_DEBUG ("TotalWin=%d LE_Win: %d, Handle=0x%x, RRCheck=%d, RRUnack=%d\n", + l2cb.controller_xmit_window, + l2cb.controller_le_xmit_window, + handle, + l2cb.ble_check_round_robin, l2cb.ble_round_robin_unacked); +#else + L2CAP_TRACE_DEBUG ("TotalWin=%d Handle=0x%x RRCheck=%d RRUnack=%d\n", + l2cb.controller_xmit_window, + handle, + l2cb.check_round_robin, l2cb.round_robin_unacked); +#endif + } +#endif + } + +#if (defined(HCILP_INCLUDED) && HCILP_INCLUDED == TRUE) + /* only full stack can enable sleep mode */ + btu_check_bt_sleep (); +#endif +} + +/******************************************************************************* +** +** Function l2c_link_segments_xmitted +** +** Description This function is called from the HCI Interface when an ACL +** data packet segment is transmitted. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_segments_xmitted (BT_HDR *p_msg) +{ + UINT8 *p = (UINT8 *)(p_msg + 1) + p_msg->offset; + UINT16 handle; + tL2C_LCB *p_lcb; + + /* Extract the handle */ + STREAM_TO_UINT16 (handle, p); + handle = HCID_GET_HANDLE (handle); + + /* Find the LCB based on the handle */ + if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - rcvd segment complete, unknown handle: %d\n", handle); + osi_free (p_msg); + return; + } + + if (p_lcb->link_state == LST_CONNECTED) { + /* Enqueue the buffer to the head of the transmit queue, and see */ + /* if we can transmit anything more. */ + list_prepend(p_lcb->link_xmit_data_q, p_msg); + + p_lcb->partial_segment_being_sent = FALSE; + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + } else { + osi_free (p_msg); + } +} diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_main.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_main.c new file mode 100644 index 00000000..dfdef99b --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_main.c @@ -0,0 +1,1065 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains the main L2CAP entry points + * + ******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +//#include <stdio.h> + +#include "device/controller.h" +//#include "btcore/include/counter.h" +#include "common/bt_target.h" +#include "btm_int.h" +#include "stack/btu.h" +#include "stack/hcimsgs.h" +#include "stack/l2c_api.h" +#include "l2c_int.h" +#include "stack/l2cdefs.h" +//#include "osi/include/log.h" + +/********************************************************************************/ +/* L O C A L F U N C T I O N P R O T O T Y P E S */ +/********************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); +#endif ///CLASSIC_BT_INCLUDED == TRUE +/********************************************************************************/ +/* G L O B A L L 2 C A P D A T A */ +/********************************************************************************/ +#if L2C_DYNAMIC_MEMORY == FALSE +tL2C_CB l2cb; +#else +tL2C_CB *l2c_cb_ptr; +#endif + +#if BT_CLASSIC_BQB_INCLUDED +static BOOLEAN s_l2cap_bqb_bad_cmd_len_rej_flag = FALSE; +#endif /* BT_CLASSIC_BQB_INCLUDED */ + +#if 0 //Unused +/******************************************************************************* +** +** Function l2c_bcst_msg +** +** Description +** +** Returns void +** +*******************************************************************************/ +void l2c_bcst_msg( BT_HDR *p_buf, UINT16 psm ) +{ + UINT8 *p; + + /* Ensure we have enough space in the buffer for the L2CAP and HCI headers */ + if (p_buf->offset < L2CAP_BCST_MIN_OFFSET) { + L2CAP_TRACE_ERROR ("L2CAP - cannot send buffer, offset: %d", p_buf->offset); + osi_free (p_buf); + return; + } + + /* Step back some bytes to add the headers */ + p_buf->offset -= (HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_BCST_OVERHEAD); + p_buf->len += L2CAP_PKT_OVERHEAD + L2CAP_BCST_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* First, the HCI transport header */ + UINT16_TO_STREAM (p, 0x0050 | (L2CAP_PKT_START << 12) | (2 << 14)); + + uint16_t acl_data_size = controller_get_interface()->get_acl_data_size_classic(); + /* The HCI transport will segment the buffers. */ + if (p_buf->len > acl_data_size) { + UINT16_TO_STREAM (p, acl_data_size); + } else { + UINT16_TO_STREAM (p, p_buf->len); + } + + /* Now the L2CAP header */ + UINT16_TO_STREAM (p, p_buf->len - L2CAP_PKT_OVERHEAD); + UINT16_TO_STREAM (p, L2CAP_CONNECTIONLESS_CID); + UINT16_TO_STREAM (p, psm); + + p_buf->len += HCI_DATA_PREAMBLE_SIZE; + + if (p_buf->len <= controller_get_interface()->get_acl_packet_size_classic()) { + //counter_add("l2cap.ch2.tx.bytes", p_buf->len); + //counter_add("l2cap.ch2.tx.pkts", 1); + + bte_main_hci_send(p_buf, BT_EVT_TO_LM_HCI_ACL); + } +} +#endif + +/******************************************************************************* +** +** Function l2cap_bqb_bad_cmd_len_rej_ctrl +** +** Description Control rejecting L2CAP signaling PDUs with incorrect length +** for BQB test. +** +** Returns void +** +*******************************************************************************/ +#if BT_CLASSIC_BQB_INCLUDED +void l2cap_bqb_bad_cmd_len_rej_ctrl(BOOLEAN enable) +{ + s_l2cap_bqb_bad_cmd_len_rej_flag = enable; +} +#endif /* BT_CLASSIC_BQB_INCLUDED */ + + +/******************************************************************************* +** +** Function l2c_rcv_acl_data +** +** Description This function is called from the HCI Interface when an ACL +** data packet is received. +** +** Returns void +** +*******************************************************************************/ +void l2c_rcv_acl_data (BT_HDR *p_msg) +{ + UINT8 *p = (UINT8 *)(p_msg + 1) + p_msg->offset; + UINT16 handle, hci_len; + UINT8 pkt_type; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb = NULL; + UINT16 l2cap_len, rcv_cid; +#if (!CONFIG_BT_STACK_NO_LOG) + UINT16 psm; +#endif + UINT16 credit; + + /* Extract the handle */ + STREAM_TO_UINT16 (handle, p); + pkt_type = HCID_GET_EVENT (handle); + handle = HCID_GET_HANDLE (handle); + + /* Since the HCI Transport is putting segmented packets back together, we */ + /* should never get a valid packet with the type set to "continuation" */ + if (pkt_type != L2CAP_PKT_CONTINUE) { + /* Find the LCB based on the handle */ + if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL) { + UINT8 cmd_code; + + /* There is a slight possibility (specifically with USB) that we get an */ + /* L2CAP connection request before we get the HCI connection complete. */ + /* So for these types of messages, hold them for up to 2 seconds. */ + STREAM_TO_UINT16 (hci_len, p); + STREAM_TO_UINT16 (l2cap_len, p); + STREAM_TO_UINT16 (rcv_cid, p); + STREAM_TO_UINT8 (cmd_code, p); + + if ((p_msg->layer_specific == 0) && (rcv_cid == L2CAP_SIGNALLING_CID) + && (cmd_code == L2CAP_CMD_INFO_REQ || cmd_code == L2CAP_CMD_CONN_REQ)) { + L2CAP_TRACE_WARNING ("L2CAP - holding ACL for unknown handle:%d ls:%d" + " cid:%d opcode:%d cur count:%d", handle, p_msg->layer_specific, + rcv_cid, cmd_code, list_length(l2cb.rcv_pending_q)); + p_msg->layer_specific = 2; + list_append(l2cb.rcv_pending_q, p_msg); + + if (list_length(l2cb.rcv_pending_q) == 1) { + btu_start_timer (&l2cb.rcv_hold_tle, BTU_TTYPE_L2CAP_HOLD, BT_1SEC_TIMEOUT); + } + + return; + } else { + L2CAP_TRACE_ERROR ("L2CAP - rcvd ACL for unknown handle:%d ls:%d cid:%d" + " opcode:%d cur count:%d", handle, p_msg->layer_specific, rcv_cid, + cmd_code, list_length(l2cb.rcv_pending_q)); + } + osi_free (p_msg); + return; + } + } else { + L2CAP_TRACE_WARNING ("L2CAP - expected pkt start or complete, got: %d", pkt_type); + osi_free (p_msg); + return; + } + + /* Extract the length and update the buffer header */ + STREAM_TO_UINT16 (hci_len, p); + p_msg->offset += 4; + + /* Extract the length and CID */ + STREAM_TO_UINT16 (l2cap_len, p); + STREAM_TO_UINT16 (rcv_cid, p); + +#if BLE_INCLUDED == TRUE + /* for BLE channel, always notify connection when ACL data received on the link */ + if (p_lcb && p_lcb->transport == BT_TRANSPORT_LE && p_lcb->link_state != LST_DISCONNECTING) + /* only process fixed channel data as channel open indication when link is not in disconnecting mode */ + { + l2cble_notify_le_connection(p_lcb->remote_bd_addr); + } +#endif + L2CAP_TRACE_DEBUG ("L2CAP - rcv_cid CID: 0x%04x\n", rcv_cid); + /* Find the CCB for this CID */ + if (rcv_cid >= L2CAP_BASE_APPL_CID) { + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, rcv_cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - unknown CID: 0x%04x", rcv_cid); + osi_free (p_msg); + return; + } + } + + if (hci_len >= L2CAP_PKT_OVERHEAD) { /* Must receive at least the L2CAP length and CID.*/ + p_msg->len = hci_len - L2CAP_PKT_OVERHEAD; + p_msg->offset += L2CAP_PKT_OVERHEAD; + } else { + L2CAP_TRACE_WARNING ("L2CAP - got incorrect hci header" ); + osi_free (p_msg); + return; + } + + if (l2cap_len != p_msg->len) { + L2CAP_TRACE_WARNING ("L2CAP - bad length in pkt. Exp: %d Act: %d", + l2cap_len, p_msg->len); + + osi_free (p_msg); + return; + } + + /* Send the data through the channel state machine */ + if (rcv_cid == L2CAP_SIGNALLING_CID) { + //counter_add("l2cap.sig.rx.bytes", l2cap_len); + //counter_add("l2cap.sig.rx.pkts", 1); +#if (CLASSIC_BT_INCLUDED == TRUE) + process_l2cap_cmd (p_lcb, p, l2cap_len); +#endif ///CLASSIC_BT_INCLUDED == TRUE + osi_free (p_msg); + } else if (rcv_cid == L2CAP_CONNECTIONLESS_CID) { + //counter_add("l2cap.ch2.rx.bytes", l2cap_len); + //counter_add("l2cap.ch2.rx.pkts", 1); + /* process_connectionless_data (p_lcb); */ +#if !CONFIG_BT_STACK_NO_LOG + STREAM_TO_UINT16 (psm, p); +#endif + L2CAP_TRACE_DEBUG( "GOT CONNECTIONLESS DATA PSM:%d", psm ) ; + +#if (L2CAP_UCD_INCLUDED == TRUE) + /* if it is not broadcast, check UCD registration */ + if ( l2c_ucd_check_rx_pkts( p_lcb, p_msg ) ) { + /* nothing to do */ + } else +#endif + { + osi_free (p_msg); + } + } +#if (BLE_INCLUDED == TRUE) + else if (rcv_cid == L2CAP_BLE_SIGNALLING_CID) { + //counter_add("l2cap.ble.rx.bytes", l2cap_len); + //counter_add("l2cap.ble.rx.pkts", 1); + l2cble_process_sig_cmd (p_lcb, p, l2cap_len); + osi_free (p_msg); + } +#endif +#if (L2CAP_NUM_FIXED_CHNLS > 0) + else if ((rcv_cid >= L2CAP_FIRST_FIXED_CHNL) && (rcv_cid <= L2CAP_LAST_FIXED_CHNL) && + (l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb != NULL) ) { + //counter_add("l2cap.fix.rx.bytes", l2cap_len); + //counter_add("l2cap.fix.rx.pkts", 1); + /* If no CCB for this channel, allocate one */ + if (p_lcb && + /* only process fixed channel data when link is open or wait for data indication */ + (p_lcb->link_state != LST_DISCONNECTING) && + l2cu_initialize_fixed_ccb (p_lcb, rcv_cid, &l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { + p_ccb = p_lcb->p_fixed_ccbs[rcv_cid - L2CAP_FIRST_FIXED_CHNL]; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_fcr_proc_pdu (p_ccb, p_msg); +#endif ///CLASSIC_BT_INCLUDED == TRUE + } else { + (*l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) + (rcv_cid, p_lcb->remote_bd_addr, p_msg); + } + } else { + osi_free (p_msg); + } + } +#endif + + else { + //counter_add("l2cap.dyn.rx.bytes", l2cap_len); + //counter_add("l2cap.dyn.rx.pkts", 1); + if (p_ccb == NULL) { + osi_free (p_msg); + } else { + if (p_lcb->transport == BT_TRANSPORT_LE) { + // Got a pkt, valid send out credits to the peer device + credit = L2CAP_LE_DEFAULT_CREDIT; + L2CAP_TRACE_DEBUG("%s Credits received %d",__func__, credit); + if((p_ccb->peer_conn_cfg.credits + credit) > L2CAP_LE_MAX_CREDIT) { + /* we have received credits more than max coc credits, + * so disconnecting the Le Coc Channel + */ +#if (BLE_INCLUDED == TRUE) + l2cble_send_peer_disc_req (p_ccb); +#endif ///BLE_INCLUDED == TRUE + } else { + p_ccb->peer_conn_cfg.credits += credit; + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + } + /* Basic mode packets go straight to the state machine */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) { +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_msg); +#endif ///CLASSIC_BT_INCLUDED == TRUE + } else { + /* eRTM or streaming mode, so we need to validate states first */ + if ((p_ccb->chnl_state == CST_OPEN) || (p_ccb->chnl_state == CST_CONFIG)) { +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_fcr_proc_pdu (p_ccb, p_msg); +#endif ///CLASSIC_BT_INCLUDED == TRUE + } else { + osi_free (p_msg); + } + } + } + } +} + +/******************************************************************************* +** +** Function process_l2cap_cmd +** +** Description This function is called when a packet is received on the +** L2CAP signalling CID +** +** Returns void +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) +{ + UINT8 *p_pkt_end, *p_next_cmd, *p_cfg_end, *p_cfg_start; + UINT8 cmd_code, cfg_code, cfg_len, id; + tL2C_CONN_INFO con_info; + tL2CAP_CFG_INFO cfg_info; + UINT16 rej_reason, rej_mtu, lcid, rcid, info_type; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + BOOLEAN cfg_rej, pkt_size_rej = FALSE; + UINT16 cfg_rej_len, cmd_len; + UINT16 result; + tL2C_CONN_INFO ci; + +#if (defined BLE_INCLUDED && BLE_INCLUDED == TRUE) + /* if l2cap command received in CID 1 on top of an LE link, ignore this command */ + if (p_lcb->transport == BT_TRANSPORT_LE) { + return; + } +#endif + + /* Reject the packet if it exceeds the default Signalling Channel MTU */ + if (pkt_len > L2CAP_DEFAULT_MTU) { + /* Core Spec requires a single response to the first command found in a multi-command + ** L2cap packet. If only responses in the packet, then it will be ignored. + ** Here we simply mark the bad packet and decide which cmd ID to reject later + */ + pkt_size_rej = TRUE; + L2CAP_TRACE_ERROR ("L2CAP SIG MTU Pkt Len Exceeded (672) -> pkt_len: %d", pkt_len); + } + + p_next_cmd = p; + p_pkt_end = p + pkt_len; + + memset (&cfg_info, 0, sizeof(cfg_info)); + + /* An L2CAP packet may contain multiple commands */ + while (TRUE) { + /* Smallest command is 4 bytes */ + if ((p = p_next_cmd) > (p_pkt_end - 4)) { + break; + } + + STREAM_TO_UINT8 (cmd_code, p); + STREAM_TO_UINT8 (id, p); + STREAM_TO_UINT16 (cmd_len, p); + + /* Check command length does not exceed packet length */ + if ((p_next_cmd = p + cmd_len) > p_pkt_end) { + L2CAP_TRACE_WARNING ("Command len bad pkt_len: %d cmd_len: %d code: %d", + pkt_len, cmd_len, cmd_code); + break; + } + + L2CAP_TRACE_DEBUG ("cmd_code: %d, id:%d, cmd_len:%d", cmd_code, id, cmd_len); + + /* Bad L2CAP packet length, look or cmd to reject */ + if (pkt_size_rej) { + /* If command found rejected it and we're done, otherwise keep looking */ + if (l2c_is_cmd_rejected(cmd_code, id, p_lcb)) { + return; + } else { + continue; /* Look for next cmd/response in current packet */ + } + } + + switch (cmd_code) { + case L2CAP_CMD_REJECT: + STREAM_TO_UINT16 (rej_reason, p); + if (rej_reason == L2CAP_CMD_REJ_MTU_EXCEEDED) { + STREAM_TO_UINT16 (rej_mtu, p); + /* What to do with the MTU reject ? We have negotiated an MTU. For now */ + /* we will ignore it and let a higher protocol timeout take care of it */ + L2CAP_TRACE_WARNING ("L2CAP - MTU rej Handle: %d MTU: %d", p_lcb->handle, rej_mtu); + } + if (rej_reason == L2CAP_CMD_REJ_INVALID_CID) { + STREAM_TO_UINT16 (rcid, p); + STREAM_TO_UINT16 (lcid, p); + + L2CAP_TRACE_WARNING ("L2CAP - rej with CID invalid, LCID: 0x%04x RCID: 0x%04x", lcid, rcid); + + /* Remote CID invalid. Treat as a disconnect */ + if (((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + && (p_ccb->remote_cid == rcid)) { + /* Fake link disconnect - no reply is generated */ + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + } + } + + /* SonyEricsson Info request Bug workaround (Continue connection) */ + else if (rej_reason == L2CAP_CMD_REJ_NOT_UNDERSTOOD && p_lcb->w4_info_rsp) { + btu_stop_timer (&p_lcb->info_timer_entry); + + p_lcb->w4_info_rsp = FALSE; + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + } + break; + + case L2CAP_CMD_CONN_REQ: + STREAM_TO_UINT16 (con_info.psm, p); + STREAM_TO_UINT16 (rcid, p); + if ((p_rcb = l2cu_find_rcb_by_psm (con_info.psm)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - rcvd conn req for unknown PSM: %d", con_info.psm); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_PSM); + break; + } else { + if (!p_rcb->api.pL2CA_ConnectInd_Cb) { + L2CAP_TRACE_WARNING ("L2CAP - rcvd conn req for outgoing-only connection PSM: %d", con_info.psm); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_PSM); + break; + } + } + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - unable to allocate CCB"); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_RESOURCES); + break; + } + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = rcid; + + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info); +#if BT_CLASSIC_BQB_INCLUDED + // L2CAP/COS/CED/BI-02-C + if (s_l2cap_bqb_bad_cmd_len_rej_flag) { + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + } +#endif /* BT_CLASSIC_BQB_INCLUDED */ + break; + + case L2CAP_CMD_CONN_RSP: + STREAM_TO_UINT16 (con_info.remote_cid, p); + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (con_info.l2cap_result, p); + STREAM_TO_UINT16 (con_info.l2cap_status, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for conn rsp, LCID: %d RCID: %d", + lcid, con_info.remote_cid); + break; + } + if (p_ccb->local_id != id) { + L2CAP_TRACE_WARNING ("L2CAP - con rsp - bad ID. Exp: %d Got: %d", + p_ccb->local_id, id); + break; + } + + if (con_info.l2cap_result == L2CAP_CONN_OK) { + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP, &con_info); + } else if (con_info.l2cap_result == L2CAP_CONN_PENDING) { + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_PND, &con_info); + } else { + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_NEG, &con_info); + } + + break; + + case L2CAP_CMD_CONFIG_REQ: + p_cfg_end = p + cmd_len; + cfg_rej = FALSE; + cfg_rej_len = 0; + + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (cfg_info.flags, p); + + p_cfg_start = p; + + cfg_info.flush_to_present = cfg_info.mtu_present = cfg_info.qos_present = + cfg_info.fcr_present = cfg_info.fcs_present = FALSE; + + while (p < p_cfg_end) { + STREAM_TO_UINT8 (cfg_code, p); + STREAM_TO_UINT8 (cfg_len, p); + + switch (cfg_code & 0x7F) { + case L2CAP_CFG_TYPE_MTU: + cfg_info.mtu_present = TRUE; + STREAM_TO_UINT16 (cfg_info.mtu, p); + break; + + case L2CAP_CFG_TYPE_FLUSH_TOUT: + cfg_info.flush_to_present = TRUE; + STREAM_TO_UINT16 (cfg_info.flush_to, p); + break; + + case L2CAP_CFG_TYPE_QOS: + cfg_info.qos_present = TRUE; + STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); + STREAM_TO_UINT8 (cfg_info.qos.service_type, p); + STREAM_TO_UINT32 (cfg_info.qos.token_rate, p); + STREAM_TO_UINT32 (cfg_info.qos.token_bucket_size, p); + STREAM_TO_UINT32 (cfg_info.qos.peak_bandwidth, p); + STREAM_TO_UINT32 (cfg_info.qos.latency, p); + STREAM_TO_UINT32 (cfg_info.qos.delay_variation, p); + break; + + case L2CAP_CFG_TYPE_FCR: + cfg_info.fcr_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcr.mode, p); + STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); + STREAM_TO_UINT8 (cfg_info.fcr.max_transmit, p); + STREAM_TO_UINT16 (cfg_info.fcr.rtrans_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mon_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mps, p); + break; + + case L2CAP_CFG_TYPE_FCS: + cfg_info.fcs_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcs, p); + break; + + case L2CAP_CFG_TYPE_EXT_FLOW: + cfg_info.ext_flow_spec_present = TRUE; + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); + STREAM_TO_UINT16 (cfg_info.ext_flow_spec.max_sdu_size, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.sdu_inter_time, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.access_latency, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.flush_timeout, p); + break; + + default: + /* sanity check option length */ + if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= cmd_len) { + p += cfg_len; + if ((cfg_code & 0x80) == 0) { + cfg_rej_len += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + cfg_rej = TRUE; + } + } + /* bad length; force loop exit */ + else { + p = p_cfg_end; + cfg_rej = TRUE; + } + break; + } + } + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) { + p_ccb->remote_id = id; + if (cfg_rej) { + l2cu_send_peer_config_rej (p_ccb, p_cfg_start, (UINT16) (cmd_len - L2CAP_CONFIG_REQ_LEN), cfg_rej_len); + } else { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_REQ, &cfg_info); + } + } else { + /* updated spec says send command reject on invalid cid */ + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_INVALID_CID, id, 0, 0); + } + break; + + case L2CAP_CMD_CONFIG_RSP: + p_cfg_end = p + cmd_len; + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (cfg_info.flags, p); + STREAM_TO_UINT16 (cfg_info.result, p); + + cfg_info.flush_to_present = cfg_info.mtu_present = cfg_info.qos_present = + cfg_info.fcr_present = cfg_info.fcs_present = FALSE; + + while (p < p_cfg_end) { + STREAM_TO_UINT8 (cfg_code, p); + STREAM_TO_UINT8 (cfg_len, p); + + switch (cfg_code & 0x7F) { + case L2CAP_CFG_TYPE_MTU: + cfg_info.mtu_present = TRUE; + STREAM_TO_UINT16 (cfg_info.mtu, p); + break; + + case L2CAP_CFG_TYPE_FLUSH_TOUT: + cfg_info.flush_to_present = TRUE; + STREAM_TO_UINT16 (cfg_info.flush_to, p); + break; + + case L2CAP_CFG_TYPE_QOS: + cfg_info.qos_present = TRUE; + STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); + STREAM_TO_UINT8 (cfg_info.qos.service_type, p); + STREAM_TO_UINT32 (cfg_info.qos.token_rate, p); + STREAM_TO_UINT32 (cfg_info.qos.token_bucket_size, p); + STREAM_TO_UINT32 (cfg_info.qos.peak_bandwidth, p); + STREAM_TO_UINT32 (cfg_info.qos.latency, p); + STREAM_TO_UINT32 (cfg_info.qos.delay_variation, p); + break; + + case L2CAP_CFG_TYPE_FCR: + cfg_info.fcr_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcr.mode, p); + STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); + STREAM_TO_UINT8 (cfg_info.fcr.max_transmit, p); + STREAM_TO_UINT16 (cfg_info.fcr.rtrans_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mon_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mps, p); + break; + + case L2CAP_CFG_TYPE_FCS: + cfg_info.fcs_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcs, p); + break; + + case L2CAP_CFG_TYPE_EXT_FLOW: + cfg_info.ext_flow_spec_present = TRUE; + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); + STREAM_TO_UINT16 (cfg_info.ext_flow_spec.max_sdu_size, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.sdu_inter_time, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.access_latency, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.flush_timeout, p); + break; + } + } + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) { + if (p_ccb->local_id != id) { + L2CAP_TRACE_WARNING ("L2CAP - cfg rsp - bad ID. Exp: %d Got: %d", + p_ccb->local_id, id); + break; + } + if ( (cfg_info.result == L2CAP_CFG_OK) || (cfg_info.result == L2CAP_CFG_PENDING) ) { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_RSP, &cfg_info); + } else { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_RSP_NEG, &cfg_info); + } + } else { + L2CAP_TRACE_WARNING ("L2CAP - rcvd cfg rsp for unknown CID: 0x%04x", lcid); + } + break; + + + case L2CAP_CMD_DISC_REQ: + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (rcid, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) { + if (p_ccb->remote_cid == rcid) { + p_ccb->remote_id = id; + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &con_info); + } + } else { + l2cu_send_peer_disc_rsp (p_lcb, id, lcid, rcid); + } + + break; + + case L2CAP_CMD_DISC_RSP: + STREAM_TO_UINT16 (rcid, p); + STREAM_TO_UINT16 (lcid, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) { + if ((p_ccb->remote_cid == rcid) && (p_ccb->local_id == id)) { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_RSP, &con_info); + } + } + break; + + case L2CAP_CMD_ECHO_REQ: + l2cu_send_peer_echo_rsp (p_lcb, id, NULL, 0); + break; + + case L2CAP_CMD_ECHO_RSP: + if (p_lcb->p_echo_rsp_cb) { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_OK); + } + break; + + case L2CAP_CMD_INFO_REQ: + STREAM_TO_UINT16 (info_type, p); + l2cu_send_peer_info_rsp (p_lcb, id, info_type); + break; + + case L2CAP_CMD_INFO_RSP: + /* Stop the link connect timer if sent before L2CAP connection is up */ + if (p_lcb->w4_info_rsp) { + btu_stop_timer (&p_lcb->info_timer_entry); + p_lcb->w4_info_rsp = FALSE; + } + + STREAM_TO_UINT16 (info_type, p); + STREAM_TO_UINT16 (result, p); + + p_lcb->info_rx_bits |= (1 << info_type); + + if ( (info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (result == L2CAP_INFO_RESP_RESULT_SUCCESS) ) { + STREAM_TO_UINT32( p_lcb->peer_ext_fea, p ); + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_lcb->peer_ext_fea & L2CAP_EXTFEA_FIXED_CHNLS) { + l2cu_send_peer_info_req (p_lcb, L2CAP_FIXED_CHANNELS_INFO_TYPE); + break; + } else { + l2cu_process_fixed_chnl_resp (p_lcb); + } +#endif + } + + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) { + if (result == L2CAP_INFO_RESP_RESULT_SUCCESS) { + memcpy (p_lcb->peer_chnl_mask, p, L2CAP_FIXED_CHNL_ARRAY_SIZE); + } + + l2cu_process_fixed_chnl_resp (p_lcb); + } +#endif +#if (L2CAP_UCD_INCLUDED == TRUE) + else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) { + if (result == L2CAP_INFO_RESP_RESULT_SUCCESS) { + STREAM_TO_UINT16 (p_lcb->ucd_mtu, p); + } + } +#endif + + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + break; + + default: + L2CAP_TRACE_WARNING ("L2CAP - bad cmd code: %d", cmd_code); + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + return; + } + } + + UNUSED(rej_mtu); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + + +/******************************************************************************* +** +** Function l2c_process_held_packets +** +** Description This function processes any L2CAP packets that arrived before +** the HCI connection complete arrived. It is a work around for +** badly behaved controllers. +** +** Returns void +** +*******************************************************************************/ +void l2c_process_held_packets(BOOLEAN timed_out) +{ + if (list_is_empty(l2cb.rcv_pending_q)) { + return; + } + + if (!timed_out) { + btu_stop_timer(&l2cb.rcv_hold_tle); + L2CAP_TRACE_WARNING("L2CAP HOLD CONTINUE"); + } else { + L2CAP_TRACE_WARNING("L2CAP HOLD TIMEOUT"); + } + + for (const list_node_t *node = list_begin(l2cb.rcv_pending_q); + node != list_end(l2cb.rcv_pending_q);) { + BT_HDR *p_buf = list_node(node); + node = list_next(node); + if (!timed_out || (!p_buf->layer_specific) || (--p_buf->layer_specific == 0)) { + list_remove(l2cb.rcv_pending_q, p_buf); + p_buf->layer_specific = 0xFFFF; + l2c_rcv_acl_data(p_buf); + } + } + + /* If anyone still in the queue, restart the timeout */ + if (!list_is_empty(l2cb.rcv_pending_q)) { + btu_start_timer (&l2cb.rcv_hold_tle, BTU_TTYPE_L2CAP_HOLD, BT_1SEC_TIMEOUT); + } +} + + +/******************************************************************************* +** +** Function l2c_init +** +** Description This function is called once at startup to initialize +** all the L2CAP structures +** +** Returns void +** +*******************************************************************************/ +void l2c_init (void) +{ +#if L2C_DYNAMIC_MEMORY + l2c_cb_ptr = (tL2C_CB *)osi_malloc(sizeof(tL2C_CB)); +#endif /* #if L2C_DYNAMIC_MEMORY */ + memset (&l2cb, 0, sizeof (tL2C_CB)); + /* the psm is increased by 2 before being used */ + l2cb.dyn_psm = 0xFFF; + + l2cb.p_ccb_pool = list_new(osi_free_func); + if (l2cb.p_ccb_pool == NULL) { + L2CAP_TRACE_ERROR("%s unable to allocate memory for L2CAP channel control block", __func__); + } + l2cb.p_lcb_pool = list_new(osi_free_func); + if (l2cb.p_lcb_pool == NULL) { + L2CAP_TRACE_ERROR("%s unable to allocate memory for L2CAP Link control block", __func__); + } + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + /* it will be set to L2CAP_PKT_START_NON_FLUSHABLE if controller supports */ + l2cb.non_flushable_pbf = L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT; +#endif + + + +#ifdef L2CAP_DESIRED_LINK_ROLE + l2cb.desire_role = L2CAP_DESIRED_LINK_ROLE; +#else + l2cb.desire_role = HCI_ROLE_SLAVE; +#endif + + /* Set the default idle timeout */ + l2cb.idle_timeout = L2CAP_LINK_INACTIVITY_TOUT; + +#if defined(L2CAP_INITIAL_TRACE_LEVEL) + l2cb.l2cap_trace_level = L2CAP_INITIAL_TRACE_LEVEL; +#else + l2cb.l2cap_trace_level = BT_TRACE_LEVEL_NONE; /* No traces */ +#endif + +#if L2CAP_CONFORMANCE_TESTING == TRUE + /* Conformance testing needs a dynamic response */ + l2cb.test_info_resp = L2CAP_EXTFEA_SUPPORTED_MASK; +#endif + + /* Number of ACL buffers to use for high priority channel */ +#if (defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) && (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE)) + l2cb.high_pri_min_xmit_quota = L2CAP_HIGH_PRI_MIN_XMIT_QUOTA; +#endif + +#if BLE_INCLUDED == TRUE + l2cb.l2c_ble_fixed_chnls_mask = + L2CAP_FIXED_CHNL_ATT_BIT | L2CAP_FIXED_CHNL_BLE_SIG_BIT | L2CAP_FIXED_CHNL_SMP_BIT; +#endif + + l2cb.rcv_pending_q = list_new(NULL); + if (l2cb.rcv_pending_q == NULL) { + L2CAP_TRACE_ERROR("%s unable to allocate memory for link layer control block", __func__); + } +#if BLE_INCLUDED == TRUE + l2ble_update_att_acl_pkt_num(L2CA_BUFF_INI, NULL); +#endif +} +void l2c_free_p_lcb_pool(void) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb) { + l2cu_release_lcb (p_lcb); + } + } + + list_free(l2cb.p_lcb_pool); +} + +void l2c_free_p_ccb_pool(void) +{ + list_node_t *p_node = NULL; + tL2C_CCB *p_ccb = NULL; + for (p_node = list_begin(l2cb.p_ccb_pool); p_node; p_node = list_next(p_node)) { + p_ccb = list_node(p_node); + if (p_ccb) { + l2cu_release_ccb (p_ccb); + } + } + + list_free(l2cb.p_ccb_pool); +} + +void l2c_free(void) +{ + list_free(l2cb.rcv_pending_q); + l2cb.rcv_pending_q = NULL; + l2c_free_p_lcb_pool(); + l2c_free_p_ccb_pool(); +#if L2C_DYNAMIC_MEMORY + FREE_AND_RESET(l2c_cb_ptr); +#endif +#if BLE_INCLUDED == TRUE + l2ble_update_att_acl_pkt_num(L2CA_BUFF_DEINIT, NULL); +#endif +} + +/******************************************************************************* +** +** Function l2c_process_timeout +** +** Description This function is called when an L2CAP-related timeout occurs +** +** Returns void +** +*******************************************************************************/ +void l2c_process_timeout (TIMER_LIST_ENT *p_tle) +{ + /* What type of timeout ? */ + switch (p_tle->event) { + case BTU_TTYPE_L2CAP_LINK: + l2c_link_timeout ((tL2C_LCB *)p_tle->param); + break; +#if (CLASSIC_BT_INCLUDED == TRUE) + case BTU_TTYPE_L2CAP_CHNL: + l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_TIMEOUT, NULL); + break; + + case BTU_TTYPE_L2CAP_FCR_ACK: + l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_ACK_TIMEOUT, NULL); + break; +#endif ///CLASSIC_BT_INCLUDED == TRUE + case BTU_TTYPE_L2CAP_HOLD: + /* Update the timeouts in the hold queue */ + l2c_process_held_packets(TRUE); + break; + + case BTU_TTYPE_L2CAP_INFO: + l2c_info_timeout((tL2C_LCB *)p_tle->param); + break; + case BTU_TTYPE_L2CAP_UPDA_CONN_PARAMS: { +#if (BLE_INCLUDED == TRUE) + UINT8 status = HCI_ERR_HOST_TIMEOUT; + tL2C_LCB *p_lcb = (tL2C_LCB *)p_tle->param; + if (p_lcb){ + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PENDING; + p_lcb->conn_update_mask &= ~L2C_BLE_UPDATE_PARAM_FULL; + l2c_send_update_conn_params_cb(p_lcb, status); + } +#endif ///BLE_INCLUDED == TRUE + break; + } + } +} + +/******************************************************************************* +** +** Function l2c_data_write +** +** Description API functions call this function to write data. +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flags) +{ + tL2C_CCB *p_ccb; + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_DataWrite, CID: %d", cid); + osi_free (p_data); + return (L2CAP_DW_FAILED); + } + +#ifndef TESTER /* Tester may send any amount of data. otherwise sending message + bigger than mtu size of peer is a violation of protocol */ + if (p_data->len > p_ccb->peer_cfg.mtu) { + L2CAP_TRACE_WARNING ("L2CAP - CID: 0x%04x cannot send message bigger than peer's mtu size", cid); + osi_free (p_data); + return (L2CAP_DW_FAILED); + } +#endif + + /* channel based, packet based flushable or non-flushable */ + p_data->layer_specific = flags; + + /* If already congested, do not accept any more packets */ + if (p_ccb->cong_sent) { + L2CAP_TRACE_DEBUG ("L2CAP - CID: 0x%04x cannot send, already congested xmit_hold_q.count: %u buff_quota: %u", + p_ccb->local_cid, + fixed_queue_length(p_ccb->xmit_hold_q), + p_ccb->buff_quota); + + osi_free (p_data); + return (L2CAP_DW_FAILED); + } + + //counter_add("l2cap.dyn.tx.bytes", p_data->len); + //counter_add("l2cap.dyn.tx.pkts", 1); + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data); + + if (p_ccb->cong_sent) { + return (L2CAP_DW_CONGESTED); + } + return (L2CAP_DW_SUCCESS); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_ucd.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_ucd.c new file mode 100644 index 00000000..d2de138b --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_ucd.c @@ -0,0 +1,1079 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains the L2CAP UCD code + * + ******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +//#include <stdio.h> + +#include "stack/bt_types.h" +#include "stack/hcidefs.h" +#include "stack/hcimsgs.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/btu.h" +#include "stack/btm_api.h" +#include "btm_int.h" + +#if (L2CAP_UCD_INCLUDED == TRUE) +static BOOLEAN l2c_ucd_connect ( BD_ADDR rem_bda ); + +/******************************************************************************* +** +** Function l2c_ucd_discover_cback +** +** Description UCD Discover callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_discover_cback (BD_ADDR rem_bda, UINT8 info_type, UINT32 data) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + L2CAP_TRACE_DEBUG ("L2CAP - l2c_ucd_discover_cback"); + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if (p_rcb->in_use) { + /* if this application is waiting UCD reception info */ + if (( info_type == L2CAP_UCD_INFO_TYPE_RECEPTION ) + && ( p_rcb->ucd.state & L2C_UCD_STATE_W4_RECEPTION )) { + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (rem_bda, info_type, data); + p_rcb->ucd.state &= ~(L2C_UCD_STATE_W4_RECEPTION); + } + + /* if this application is waiting UCD MTU info */ + if (( info_type == L2CAP_UCD_INFO_TYPE_MTU ) + && ( p_rcb->ucd.state & L2C_UCD_STATE_W4_MTU )) { + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (rem_bda, info_type, data); + p_rcb->ucd.state &= ~(L2C_UCD_STATE_W4_MTU); + } + } + } +} + +/******************************************************************************* +** +** Function l2c_ucd_data_ind_cback +** +** Description UCD Data callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_data_ind_cback (BD_ADDR rem_bda, BT_HDR *p_buf) +{ + UINT8 *p; + UINT16 psm; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_DEBUG ("L2CAP - l2c_ucd_data_ind_cback"); + + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT16(psm, p) + + p_buf->offset += L2CAP_UCD_OVERHEAD; + p_buf->len -= L2CAP_UCD_OVERHEAD; + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - no RCB for l2c_ucd_data_ind_cback, PSM: 0x%04x", psm); + osi_free (p_buf); + } else { + p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb(rem_bda, p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_congestion_status_cback +** +** Description UCD Congestion Status callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_congestion_status_cback (BD_ADDR rem_bda, BOOLEAN is_congested) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + L2CAP_TRACE_DEBUG ("L2CAP - l2c_ucd_congestion_status_cback"); + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if (( p_rcb->in_use ) + && ( p_rcb->ucd.state != L2C_UCD_STATE_UNUSED )) { + if ( p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) { + L2CAP_TRACE_DEBUG ("L2CAP - Calling UCDCongestionStatus_Cb (%d), PSM=0x%04x, BDA: %08x%04x,", + is_congested, p_rcb->psm, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + + p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ( rem_bda, is_congested ); + } + } + } +} + +/******************************************************************************* +** +** Function l2c_ucd_disconnect_ind_cback +** +** Description UCD disconnect callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_disconnect_ind_cback (UINT16 cid, BOOLEAN result) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function l2c_ucd_config_ind_cback +** +** Description UCD config callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_config_ind_cback (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function l2c_ucd_config_cfm_cback +** +** Description UCD config callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_config_cfm_cback (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function L2CA_UcdRegister +** +** Description Register PSM on UCD. +** +** Parameters: tL2CAP_UCD_CB_INFO +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdRegister ( UINT16 psm, tL2CAP_UCD_CB_INFO *p_cb_info ) +{ + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API ("L2CA_UcdRegister() PSM: 0x%04x", psm); + + if ((!p_cb_info->pL2CA_UCD_Discover_Cb) + || (!p_cb_info->pL2CA_UCD_Data_Cb)) { + L2CAP_TRACE_ERROR ("L2CAP - no callback registering PSM(0x%04x) on UCD", psm); + return (FALSE); + } + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - no RCB for L2CA_UcdRegister, PSM: 0x%04x", psm); + return (FALSE); + } + + p_rcb->ucd.state = L2C_UCD_STATE_W4_DATA; + p_rcb->ucd.cb_info = *p_cb_info; + + /* check if master rcb is created for UCD */ + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) == NULL) { + if ((p_rcb = l2cu_allocate_rcb (L2C_UCD_RCB_ID)) == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - no RCB available for L2CA_UcdRegister"); + return (FALSE); + } else { + /* these callback functions will forward data to each UCD application */ + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb = l2c_ucd_discover_cback; + p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb = l2c_ucd_data_ind_cback; + p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb = l2c_ucd_congestion_status_cback; + + memset (&p_rcb->api, 0, sizeof(tL2CAP_APPL_INFO)); + p_rcb->api.pL2CA_DisconnectInd_Cb = l2c_ucd_disconnect_ind_cback; + + /* This will make L2CAP check UCD congestion callback */ + p_rcb->api.pL2CA_CongestionStatus_Cb = NULL; + + /* do nothing but prevent crash */ + p_rcb->api.pL2CA_ConfigInd_Cb = l2c_ucd_config_ind_cback; + p_rcb->api.pL2CA_ConfigCfm_Cb = l2c_ucd_config_cfm_cback; + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDeregister +** +** Description Deregister PSM on UCD. +** +** Parameters: PSM +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdDeregister_In_CCB_List (void *p_ccb_node, void * context) +{ + p_ccb = (tL2C_CCB *)p_ccb_node; + if (( p_ccb->in_use ) + && ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID )) { + l2cu_release_ccb (p_ccb); + } + return false; +} + +BOOLEAN L2CA_UcdDeregister ( UINT16 psm ) +{ + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + UINT16 xx; + + L2CAP_TRACE_API ("L2CA_UcdDeregister() PSM: 0x%04x", psm); + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - no RCB for L2CA_UcdDeregister, PSM: 0x%04x", psm); + return (FALSE); + } + + p_rcb->ucd.state = L2C_UCD_STATE_UNUSED; + + /* check this was the last UCD registration */ + p_rcb = &l2cb.rcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if ((p_rcb->in_use) && (p_rcb->ucd.state != L2C_UCD_STATE_UNUSED)) { + return (TRUE); + } + } + + /* delete master rcb for UCD */ + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) != NULL) { + l2cu_release_rcb (p_rcb); + } + + /* delete CCB for UCD */ + list_foreach(l2cb.p_ccb_pool, L2CA_UcdDeregister_In_CCB_List, NULL); + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDiscover +** +** Description Discover UCD of remote device. +** +** Parameters: PSM +** BD_ADDR of remote device +** info_type : L2CAP_UCD_INFO_TYPE_RECEPTION +** L2CAP_UCD_INFO_TYPE_MTU +** +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdDiscover ( UINT16 psm, BD_ADDR rem_bda, UINT8 info_type ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API ("L2CA_UcdDiscover() PSM: 0x%04x BDA: %08x%04x, InfoType=0x%02x", psm, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5], info_type); + + /* Fail if the PSM is not registered */ + if (((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + || ( p_rcb->ucd.state == L2C_UCD_STATE_UNUSED )) { + L2CAP_TRACE_WARNING ("L2CAP - no RCB for L2CA_UcdDiscover, PSM: 0x%04x", psm); + return (FALSE); + } + + /* First, see if we already have a link to the remote */ + /* then find the channel control block for UCD. */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) + || ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) { + if ( l2c_ucd_connect (rem_bda) == FALSE ) { + return (FALSE); + } + } + + /* set waiting flags in rcb */ + + if ( info_type & L2CAP_UCD_INFO_TYPE_RECEPTION ) { + p_rcb->ucd.state |= L2C_UCD_STATE_W4_RECEPTION; + } + + if ( info_type & L2CAP_UCD_INFO_TYPE_MTU ) { + p_rcb->ucd.state |= L2C_UCD_STATE_W4_MTU; + } + + /* if link is already established */ + if ((p_lcb) && (p_lcb->link_state == LST_CONNECTED)) { + if (!p_ccb) { + p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID); + } + l2c_ucd_check_pending_info_req(p_ccb); + } + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDataWrite +** +** Description Send UCD to remote device +** +** Parameters: PSM +** BD Address of remote +** Pointer to buffer of type BT_HDR +** flags : L2CAP_FLUSHABLE_CH_BASED +** L2CAP_FLUSHABLE_PKT +** L2CAP_NON_FLUSHABLE_PKT +** +** Return value L2CAP_DW_SUCCESS, if data accepted +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT16 L2CA_UcdDataWrite (UINT16 psm, BD_ADDR rem_bda, BT_HDR *p_buf, UINT16 flags) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + UINT8 *p; + + L2CAP_TRACE_API ("L2CA_UcdDataWrite() PSM: 0x%04x BDA: %08x%04x", psm, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + + /* Fail if the PSM is not registered */ + if (((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + || ( p_rcb->ucd.state == L2C_UCD_STATE_UNUSED )) { + L2CAP_TRACE_WARNING ("L2CAP - no RCB for L2CA_UcdDataWrite, PSM: 0x%04x", psm); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + /* First, see if we already have a link to the remote */ + /* then find the channel control block for UCD */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) + || ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) { + if ( l2c_ucd_connect (rem_bda) == FALSE ) { + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + /* If we still don't have lcb and ccb after connect attempt, then can't proceed */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) + || ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) { + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + } + + /* write PSM */ + p_buf->offset -= L2CAP_UCD_OVERHEAD; + p_buf->len += L2CAP_UCD_OVERHEAD; + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + UINT16_TO_STREAM (p, psm); + + /* UCD MTU check */ + if ((p_lcb->ucd_mtu) && (p_buf->len > p_lcb->ucd_mtu)) { + L2CAP_TRACE_WARNING ("L2CAP - Handle: 0x%04x UCD bigger than peer's UCD mtu size cannot be sent", p_lcb->handle); + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + /* If already congested, do not accept any more packets */ + if (p_ccb->cong_sent) { + L2CAP_TRACE_ERROR ("L2CAP - Handle: 0x%04x UCD cannot be sent, already congested count: %u buff_quota: %u", + p_lcb->handle, + (fixed_queue_length(p_ccb->xmit_hold_q) + + fixed_queue_length(p_lcb->ucd_out_sec_pending_q)), + p_ccb->buff_quota); + + osi_free (p_buf); + return (L2CAP_DW_FAILED); + } + + /* channel based, packet based flushable or non-flushable */ + p_buf->layer_specific = flags; + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_buf); + + if (p_ccb->cong_sent) { + return (L2CAP_DW_CONGESTED); + } else { + return (L2CAP_DW_SUCCESS); + } +} + +/******************************************************************************* +** +** Function L2CA_UcdSetIdleTimeout +** +** Description Set UCD Idle timeout. +** +** Parameters: BD Addr +** Timeout in second +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdSetIdleTimeout ( BD_ADDR rem_bda, UINT16 timeout ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_UcdSetIdleTimeout() Timeout: 0x%04x BDA: %08x%04x", timeout, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + + /* First, see if we already have a link to the remote */ + /* then find the channel control block. */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) + || ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) { + L2CAP_TRACE_WARNING ("L2CAP - no UCD channel"); + return (FALSE); + } else { + p_ccb->fixed_chnl_idle_tout = timeout; + return (TRUE); + } +} + +/******************************************************************************* +** +** Function L2CA_UCDSetTxPriority +** +** Description Sets the transmission priority for a connectionless channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_UCDSetTxPriority ( BD_ADDR rem_bda, tL2CAP_CHNL_PRIORITY priority ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API ("L2CA_UCDSetTxPriority() priority: 0x%02x BDA: %08x%04x", priority, + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no LCB for L2CA_UCDSetTxPriority"); + return (FALSE); + } + + /* Find the channel control block */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for L2CA_UCDSetTxPriority"); + return (FALSE); + } + + /* it will update the order of CCB in LCB by priority and update round robin service variables */ + l2cu_change_pri_ccb (p_ccb, priority); + + return (TRUE); +} + +/******************************************************************************* +** +** Function l2c_ucd_connect +** +** Description Connect UCD to remote device. +** +** Parameters: BD_ADDR of remote device +** +** Return value: TRUE if successs +** +*******************************************************************************/ +static BOOLEAN l2c_ucd_connect ( BD_ADDR rem_bda ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_DEBUG ("l2c_ucd_connect() BDA: %08x%04x", + (rem_bda[0] << 24) + (rem_bda[1] << 16) + (rem_bda[2] << 8) + rem_bda[3], + (rem_bda[4] << 8) + rem_bda[5]); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) { + L2CAP_TRACE_WARNING ("l2c_ucd_connect - BTU not ready"); + return (FALSE); + } + + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_BR_EDR)) == NULL) { + /* No link. Get an LCB and start link establishment */ + if ( ((p_lcb = l2cu_allocate_lcb (rem_bda, FALSE, BT_TRANSPORT_BR_EDR)) == NULL) + || (l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR) == FALSE) ) { + L2CAP_TRACE_WARNING ("L2CAP - conn not started l2c_ucd_connect"); + return (FALSE); + } + } else if ( p_lcb->info_rx_bits & (1 << L2CAP_EXTENDED_FEATURES_INFO_TYPE) ) { + if (!(p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION)) { + L2CAP_TRACE_WARNING ("L2CAP - UCD is not supported by peer, l2c_ucd_connect"); + return (FALSE); + } + } + + /* Find the channel control block. */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL) { + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for l2c_ucd_connect"); + return (FALSE); + } else { + /* Set CID for the connection */ + p_ccb->local_cid = L2CAP_CONNECTIONLESS_CID; + p_ccb->remote_cid = L2CAP_CONNECTIONLESS_CID; + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = L2CAP_UCD_IDLE_TIMEOUT; + + /* Set the default channel priority value to use */ + l2cu_change_pri_ccb (p_ccb, L2CAP_UCD_CH_PRIORITY); + + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no UCD registered, l2c_ucd_connect"); + return (FALSE); + } + /* Save UCD registration info */ + p_ccb->p_rcb = p_rcb; + + /* There is no configuration, so if the link is up, the channel is up */ + if (p_lcb->link_state == LST_CONNECTED) { + p_ccb->chnl_state = CST_OPEN; + } + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function l2c_ucd_delete_sec_pending_q +** +** Description discard all of UCD packets in security pending queue +** +** Returns None +** +*******************************************************************************/ +void l2c_ucd_delete_sec_pending_q(tL2C_LCB *p_lcb) +{ + /* clean up any security pending UCD */ + while (p_lcb->ucd_out_sec_pending_q.p_first) { + osi_free(fixed_queue_dequeue(p_lcb->ucd_out_sec_pending_q, 0)); + } + fixed_queue_free(p_lcb->ucd_out_sec_pending_q, NULL); + p_lcb->ucd_out_sec_pending_q = NULL; + + while (! fixed_queue_is_empty(p_lcb->ucd_in_sec_pending_q)) { + osi_free(fixed_queue_dequeue(p_lcb->ucd_in_sec_pending_q, 0)); + } + fixed_queue_free(p_lcb->ucd_in_sec_pending_q); + p_lcb->ucd_in_sec_pending_q = NULL; +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_info_req +** +** Description check if any application is waiting for UCD information +** +** Return TRUE if any pending UCD info request +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_info_req(tL2C_CCB *p_ccb) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + BOOLEAN pending = FALSE; + + if (p_ccb == NULL) { + L2CAP_TRACE_ERROR ("L2CAP - NULL p_ccb in l2c_ucd_check_pending_info_req"); + return (FALSE); + } + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if (p_rcb->in_use) { + /* if application is waiting UCD reception info */ + if (p_rcb->ucd.state & L2C_UCD_STATE_W4_RECEPTION) { + /* if this information is available */ + if ( p_ccb->p_lcb->info_rx_bits & (1 << L2CAP_EXTENDED_FEATURES_INFO_TYPE) ) { + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION)) { + L2CAP_TRACE_WARNING ("L2CAP - UCD is not supported by peer, l2c_ucd_check_pending_info_req"); + + l2c_ucd_delete_sec_pending_q(p_ccb->p_lcb); + l2cu_release_ccb (p_ccb); + } + + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (p_ccb->p_lcb->remote_bd_addr, + L2CAP_UCD_INFO_TYPE_RECEPTION, + p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION); + } else { + pending = TRUE; + if (p_ccb->p_lcb->w4_info_rsp == FALSE) { + l2cu_send_peer_info_req (p_ccb->p_lcb, L2CAP_EXTENDED_FEATURES_INFO_TYPE); + } + } + } + + /* if application is waiting for UCD MTU */ + if (p_rcb->ucd.state & L2C_UCD_STATE_W4_MTU) { + /* if this information is available */ + if ( p_ccb->p_lcb->info_rx_bits & (1 << L2CAP_CONNLESS_MTU_INFO_TYPE)) { + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (p_ccb->p_lcb->remote_bd_addr, + L2CAP_UCD_INFO_TYPE_MTU, + p_ccb->p_lcb->ucd_mtu); + } else { + pending = TRUE; + if (p_ccb->p_lcb->w4_info_rsp == FALSE) { + l2cu_send_peer_info_req (p_ccb->p_lcb, L2CAP_CONNLESS_MTU_INFO_TYPE); + } + } + } + } + } + return (pending); +} + +/******************************************************************************* +** +** Function l2c_ucd_enqueue_pending_out_sec_q +** +** Description enqueue outgoing UCD packet into security pending queue +** and check congestion +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_enqueue_pending_out_sec_q(tL2C_CCB *p_ccb, void *p_data) +{ + fixed_queue_enqueue(p_ccb->p_lcb->ucd_out_sec_pending_q, p_data, FIXED_QUEUE_MAX_TIMEOUT); + l2cu_check_channel_congestion (p_ccb); +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_out_sec_q +** +** Description check outgoing security +** +** Return TRUE if any UCD packet for security +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_try_peek_first(p_ccb->p_lcb->ucd_out_sec_pending_q); + + if (p_buf != NULL) { + UINT16 psm; + UINT8 *p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + STREAM_TO_UINT16(psm, p) + + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, psm, + p_ccb->p_lcb->handle, CONNLESS_ORIG, &l2c_link_sec_comp, p_ccb); + + return (TRUE); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_ucd_send_pending_out_sec_q +** +** Description dequeue UCD packet from security pending queue and +** enqueue it into CCB +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_send_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_dequeue(p_ccb->p_lcb->ucd_out_sec_pending_q, 0); + + if (p_buf != NULL) { + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_buf); + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_discard_pending_out_sec_q +** +** Description dequeue UCD packet from security pending queue and +** discard it. +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_discard_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_dequeue(p_ccb->p_lcb->ucd_out_sec_pending_q, 0); + + /* we may need to report to application */ + + if (p_buf) { + osi_free (p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_in_sec_q +** +** Description check incoming security +** +** Return TRUE if any UCD packet for security +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_dequeue(p_ccb->p_lcb->ucd_in_sec_pending_q, 0); + + if (p_buf != NULL) { + UINT16 psm; + UINT8 *p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT16(psm, p) + + p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, psm, + p_ccb->p_lcb->handle, CONNLESS_TERM, &l2c_link_sec_comp, p_ccb); + + return (TRUE); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_ucd_send_pending_in_sec_q +** +** Description dequeue UCD packet from security pending queue and +** send it to application +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_send_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_dequeue(p_ccb->p_lcb->ucd_in_sec_pending_q, 0) + + if (p_buf != NULL) { + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb(p_ccb->p_lcb->remote_bd_addr, (BT_HDR *)p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_discard_pending_in_sec_q +** +** Description dequeue UCD packet from security pending queue and +** discard it. +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_discard_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf = (BT_HDR*)fixed_queue_dequeue(p_ccb->p_lcb->ucd_in_sec_pending_q, 0); + + if (p_buf) { + osi_free (p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_check_rx_pkts +** +** Description Check if UCD reception is registered. +** Process received UCD packet if application is expecting. +** +** Return TRUE if UCD reception is registered +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_rx_pkts(tL2C_LCB *p_lcb, BT_HDR *p_msg) +{ + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + if (((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) != NULL) + || ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) != NULL)) { + if (p_ccb == NULL) { + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no CCB for UCD reception"); + osi_free (p_msg); + return TRUE; + } else { + /* Set CID for the connection */ + p_ccb->local_cid = L2CAP_CONNECTIONLESS_CID; + p_ccb->remote_cid = L2CAP_CONNECTIONLESS_CID; + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = L2CAP_UCD_IDLE_TIMEOUT; + + /* Set the default channel priority value to use */ + l2cu_change_pri_ccb (p_ccb, L2CAP_UCD_CH_PRIORITY); + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + p_ccb->chnl_state = CST_OPEN; + } + } + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DATA, p_msg); + return TRUE; + } else { + return FALSE; + } +} + +/******************************************************************************* +** +** Function l2c_ucd_process_event +** +** Description This is called from main state machine when LCID is connectionless +** Process the event if it is for UCD. +** +** Return TRUE if the event is consumed by UCD +** FALSE if the event needs to be processed by main state machine +** +*******************************************************************************/ +BOOLEAN l2c_ucd_process_event(tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + /* if the event is not processed by this function, this variable will be set to FALSE */ + BOOLEAN done = TRUE; + + switch (p_ccb->chnl_state) { + case CST_CLOSED: + switch (event) { + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + /* check if waiting for UCD info */ + if (!l2c_ucd_check_pending_info_req (p_ccb)) { + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) { + p_ccb->chnl_state = CST_OPEN; + } + } + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + fixed_queue_enqueue(p_ccb->p_lcb->ucd_in_sec_pending_q, p_data, FIXED_QUEUE_MAX_TIMEOUT); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + if (!l2c_ucd_check_pending_info_req (p_ccb)) { + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) { + p_ccb->chnl_state = CST_OPEN; + } + } + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + case CST_ORIG_W4_SEC_COMP: + switch (event) { + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) { + p_ccb->chnl_state = CST_OPEN; + } + break; + + case L2CEVT_SEC_COMP: /* Security completed success */ + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_send_pending_out_sec_q(p_ccb); + + if (! fixed_queue_is_empty(p_ccb->p_lcb->ucd_out_sec_pending_q)) + { + /* start a timer to send next UCD packet in OPEN state */ + /* it will prevent stack overflow */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 0); + } else { + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + } + break; + + case L2CEVT_SEC_COMP_NEG: + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_discard_pending_out_sec_q(p_ccb); + + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + fixed_queue_enqueue(p_ccb->p_lcb->ucd_in_sec_pending_q, p_data, FIXED_QUEUE_MAX_TIMEOUT); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + + case CST_TERM_W4_SEC_COMP: + switch (event) { + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_send_pending_in_sec_q (p_ccb); + + if (! fixed_queue_is_empty(p_ccb->p_lcb->ucd_in_sec_pending_q)) { + /* start a timer to check next UCD packet in OPEN state */ + /* it will prevent stack overflow */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 0); + } else { + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + } + break; + + case L2CEVT_SEC_COMP_NEG: + if (((tL2C_CONN_INFO *)p_data)->status == BTM_DELAY_CHECK) { + done = FALSE; + break; + } + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_discard_pending_in_sec_q (p_ccb); + + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + fixed_queue_enqueue(p_ccb->p_lcb->ucd_in_sec_pending_q, p_data, FIXED_QUEUE_MAX_TIMEOUT); + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + /* check if any incoming UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_in_sec_q(p_ccb)) { + p_ccb->chnl_state = CST_OPEN; + } + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + case CST_OPEN: + switch (event) { + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + /* stop idle timer of UCD */ + btu_stop_timer (&p_ccb->timer_entry); + + fixed_queue_enqueue(p_ccb->p_lcb->ucd_in_sec_pending_q, p_data, FIXED_QUEUE_MAX_TIMEOUT); + l2c_ucd_check_pending_in_sec_q (p_ccb); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + /* stop idle timer of UCD */ + btu_stop_timer (&p_ccb->timer_entry); + + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + + /* coverity[check_return] */ /* coverity[unchecked_value] */ + /* success changes state, failure stays in current state */ + l2c_ucd_check_pending_out_sec_q (p_ccb); + break; + + case L2CEVT_TIMEOUT: + /* check if any UCD packet is waiting security check */ + if ((!l2c_ucd_check_pending_in_sec_q(p_ccb)) + && (!l2c_ucd_check_pending_out_sec_q(p_ccb))) { + l2cu_release_ccb (p_ccb); + } + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + + return done; +} +#endif /* (L2CAP_UCD_INCLUDED == TRUE) */ diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2c_utils.c b/lib/bt/host/bluedroid/stack/l2cap/l2c_utils.c new file mode 100644 index 00000000..992d4b68 --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2c_utils.c @@ -0,0 +1,3731 @@ +/****************************************************************************** + * + * 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. + * + ******************************************************************************/ + +/****************************************************************************** + * + * This file contains L2CAP utility functions + * + ******************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "osi/allocator.h" +#include "device/controller.h" +#include "stack/bt_types.h" +#include "stack/hcimsgs.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/hcidefs.h" +#include "stack/btu.h" +#include "stack/btm_api.h" +#include "btm_int.h" +#include "stack/hcidefs.h" +#include "osi/allocator.h" +#include "osi/list.h" + +#if BT_SDP_BQB_INCLUDED +extern BOOLEAN l2cap_bqb_ertm_mode_included_flag; +#endif /* BT_SDP_BQB_INCLUDED */ + +/******************************************************************************* +** +** Function l2cu_allocate_lcb +** +** Description Look for an unused LCB +** +** Returns LCB address or NULL if none found +** +*******************************************************************************/ +tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPORT transport) +{ + tL2C_LCB *p_lcb = NULL; + bool list_ret = false; + extern tL2C_LCB *l2cu_find_free_lcb (void); + // temp solution + p_lcb = l2cu_find_free_lcb(); + if(p_lcb != NULL) { + list_ret = true; + } + +#if (CLASSIC_BT_INCLUDED == TRUE) + /* Check if peer device's and our BD_ADDR is same or not. It + should be different to avoid 'Impersonation in the Pin Pairing + Protocol' (CVE-2020-26555) vulnerability. */ + if (memcmp((uint8_t *)p_bd_addr, (uint8_t *)&controller_get_interface()->get_address()->address, sizeof (BD_ADDR)) == 0) { + L2CAP_TRACE_ERROR ("%s connection rejected due to same BD ADDR", __func__); + return (NULL); + } +#endif + + if(p_lcb == NULL && list_length(l2cb.p_lcb_pool) < MAX_L2CAP_LINKS) { + p_lcb = (tL2C_LCB *)osi_malloc(sizeof(tL2C_LCB)); + if (p_lcb) { + memset (p_lcb, 0, sizeof(tL2C_LCB)); + list_ret = list_append(l2cb.p_lcb_pool, p_lcb); + }else { + L2CAP_TRACE_ERROR("Error in allocating L2CAP Link Control Block"); + } + } + if (list_ret) { + if (p_lcb) { + btu_free_timer(&p_lcb->timer_entry); + btu_free_timer(&p_lcb->info_timer_entry); + btu_free_timer(&p_lcb->upda_con_timer); + + memset (p_lcb, 0, sizeof (tL2C_LCB)); + memcpy (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN); + + p_lcb->in_use = TRUE; + p_lcb->link_state = LST_DISCONNECTED; + p_lcb->handle = HCI_INVALID_HANDLE; + p_lcb->link_flush_tout = 0xFFFF; + p_lcb->timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + p_lcb->info_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + p_lcb->upda_con_timer.param = (TIMER_PARAM_TYPE)p_lcb; + p_lcb->idle_timeout = l2cb.idle_timeout; + p_lcb->id = 1; /* spec does not allow '0' */ + p_lcb->is_bonding = is_bonding; +#if (BLE_INCLUDED == TRUE) + p_lcb->transport = transport; + p_lcb->tx_data_len = controller_get_interface()->get_ble_default_data_packet_length(); + p_lcb->le_sec_pending_q = fixed_queue_new(QUEUE_SIZE_MAX); + + if (transport == BT_TRANSPORT_LE) { + l2cb.num_ble_links_active++; + l2c_ble_link_adjust_allocation(); + } else +#endif + { + l2cb.num_links_active++; + l2c_link_adjust_allocation(); + } + p_lcb->link_xmit_data_q = list_new(NULL); + return (p_lcb); + } + } + + /* If here, no free LCB found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_update_lcb_4_bonding +** +** Description Mark the lcb for bonding. Used when bonding takes place on +** an existing ACL connection. (Pre-Lisbon devices) +** +** Returns Nothing +** +*******************************************************************************/ +void l2cu_update_lcb_4_bonding (BD_ADDR p_bd_addr, BOOLEAN is_bonding) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_BR_EDR); + + if (p_lcb) { + p_lcb->is_bonding = is_bonding; + } +} + +/******************************************************************************* +** +** Function l2cu_release_lcb +** +** Description Release an LCB. All timers will be stopped, channels +** dropped, buffers returned etc. +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_lcb (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + + p_lcb->in_use = FALSE; + p_lcb->is_bonding = FALSE; +#if (BLE_INCLUDED == TRUE) + p_lcb->retry_create_con = 0; + p_lcb->start_time_s = 0; +#endif // #if (BLE_INCLUDED == TRUE) + + /* Stop and release timers */ + btu_free_timer (&p_lcb->timer_entry); + memset(&p_lcb->timer_entry, 0, sizeof(TIMER_LIST_ENT)); + btu_free_timer (&p_lcb->info_timer_entry); + memset(&p_lcb->info_timer_entry, 0, sizeof(TIMER_LIST_ENT)); + btu_free_timer(&p_lcb->upda_con_timer); + memset(&p_lcb->upda_con_timer, 0, sizeof(TIMER_LIST_ENT)); + + /* Release any unfinished L2CAP packet on this link */ + if (p_lcb->p_hcit_rcv_acl) { + osi_free(p_lcb->p_hcit_rcv_acl); + p_lcb->p_hcit_rcv_acl = NULL; + } + +#if BTM_SCO_INCLUDED == TRUE +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_BR_EDR) +#endif + { + /* Release all SCO links */ + btm_remove_sco_links(p_lcb->remote_bd_addr); + } +#endif + + if (p_lcb->sent_not_acked > 0) { +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + l2cb.controller_le_xmit_window += p_lcb->sent_not_acked; + if (l2cb.controller_le_xmit_window > l2cb.num_lm_ble_bufs) { + l2cb.controller_le_xmit_window = l2cb.num_lm_ble_bufs; + } + } else +#endif + { + l2cb.controller_xmit_window += p_lcb->sent_not_acked; + if (l2cb.controller_xmit_window > l2cb.num_lm_acl_bufs) { + l2cb.controller_xmit_window = l2cb.num_lm_acl_bufs; + } + } + } + +#if (BLE_INCLUDED == TRUE) + // Reset BLE connecting flag only if the address matches + if (!memcmp(l2cb.ble_connecting_bda, p_lcb->remote_bd_addr, BD_ADDR_LEN)) { + l2cb.is_ble_connecting = FALSE; + } +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + l2cu_process_fixed_disc_cback(p_lcb); +#endif + + /* Ensure no CCBs left on this LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_lcb->ccb_queue.p_first_ccb) { + l2cu_release_ccb (p_ccb); + } + + /* Tell BTM Acl management the link was removed */ + if ((p_lcb->link_state == LST_CONNECTED) || (p_lcb->link_state == LST_DISCONNECTING)) { +#if (BLE_INCLUDED == TRUE) + btm_acl_removed (p_lcb->remote_bd_addr, p_lcb->transport); +#else + btm_acl_removed (p_lcb->remote_bd_addr, BT_TRANSPORT_BR_EDR); +#endif + } + + /* Release any held buffers */ + if (p_lcb->link_xmit_data_q) { + while (!list_is_empty(p_lcb->link_xmit_data_q)) { + BT_HDR *p_buf = list_front(p_lcb->link_xmit_data_q); + list_remove(p_lcb->link_xmit_data_q, p_buf); + osi_free(p_buf); + } + list_free(p_lcb->link_xmit_data_q); + p_lcb->link_xmit_data_q = NULL; + } + +#if (L2CAP_UCD_INCLUDED == TRUE) + /* clean up any security pending UCD */ + l2c_ucd_delete_sec_pending_q(p_lcb); +#endif + +#if BLE_INCLUDED == TRUE + /* Re-adjust flow control windows make sure it does not go negative */ + if (p_lcb->transport == BT_TRANSPORT_LE) { + if (l2cb.num_ble_links_active >= 1) { + l2cb.num_ble_links_active--; + } + + l2c_ble_link_adjust_allocation(); + } else +#endif + { + if (l2cb.num_links_active >= 1) { + l2cb.num_links_active--; + } + + l2c_link_adjust_allocation(); + } + + /* Check for ping outstanding */ + if (p_lcb->p_echo_rsp_cb) { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_NO_LINK); + } + +#if (BLE_INCLUDED == TRUE) + /* Check and release all the LE COC connections waiting for security */ + if (p_lcb->le_sec_pending_q) + { + while (!fixed_queue_is_empty(p_lcb->le_sec_pending_q)) + { + tL2CAP_SEC_DATA *p_buf = (tL2CAP_SEC_DATA*) fixed_queue_dequeue(p_lcb->le_sec_pending_q, FIXED_QUEUE_MAX_TIMEOUT); + if (p_buf->p_callback) { + p_buf->p_callback(p_lcb->remote_bd_addr, p_lcb->transport, p_buf->p_ref_data, BTM_DEV_RESET); + } + osi_free(p_buf); + } + fixed_queue_free(p_lcb->le_sec_pending_q, NULL); + p_lcb->le_sec_pending_q = NULL; + } +#endif ///BLE_INCLUDED == TRUE +} + + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_bd_addr +** +** Description Look through all active LCBs for a match based on the +** remote BD address. +** +** Returns pointer to matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_bd_addr (BD_ADDR p_bd_addr, tBT_TRANSPORT transport) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && +#if BLE_INCLUDED == TRUE + p_lcb->transport == transport && +#endif + (!memcmp (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN))) { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + +tL2C_LCB *l2cu_find_free_lcb (void) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (!p_lcb->in_use) { + return (p_lcb); + } + } + /* If here, no match found */ + return (NULL); +} + +uint8_t l2cu_plcb_active_count(void) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + uint8_t active_count = 0; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb && p_lcb->in_use) { + active_count ++; + } + } + if (active_count >= MAX_L2CAP_CHANNELS) { + L2CAP_TRACE_ERROR("error active count"); + active_count = 0; + } + L2CAP_TRACE_DEBUG("plcb active count %d", active_count); + return active_count; + +} + +/******************************************************************************* +** +** Function l2cu_get_conn_role +** +** Description Determine the desired role (master or slave) of a link. +** If already got a slave link, this one must be a master. If +** already got at least 1 link where we are the master, make this +** also a master. +** +** Returns HCI_ROLE_MASTER or HCI_ROLE_SLAVE +** +*******************************************************************************/ +UINT8 l2cu_get_conn_role (tL2C_LCB *p_this_lcb) +{ + return l2cb.desire_role; +} + +/******************************************************************************* +** +** Function l2c_is_cmd_rejected +** +** Description Checks if cmd_code is command or response +** If a command it will be rejected per spec. +** This function is used when a illegal packet length is detected +** +** Returns BOOLEAN - TRUE if cmd_code is a command and it is rejected, +** FALSE if response code. (command not rejected) +** +*******************************************************************************/ +BOOLEAN l2c_is_cmd_rejected (UINT8 cmd_code, UINT8 id, tL2C_LCB *p_lcb) +{ + switch (cmd_code) { + case L2CAP_CMD_CONN_REQ: + case L2CAP_CMD_CONFIG_REQ: + case L2CAP_CMD_DISC_REQ: + case L2CAP_CMD_ECHO_REQ: + case L2CAP_CMD_INFO_REQ: + case L2CAP_CMD_AMP_CONN_REQ: + case L2CAP_CMD_AMP_MOVE_REQ: + case L2CAP_CMD_BLE_UPDATE_REQ: + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_MTU_EXCEEDED, id, L2CAP_DEFAULT_MTU, 0); + L2CAP_TRACE_WARNING ("Dumping first Command (%d)", cmd_code); + return TRUE; + + default: /* Otherwise a response */ + return FALSE; + } +} + +/******************************************************************************* +** +** Function l2cu_build_header +** +** Description Builds the L2CAP command packet header +** +** Returns Pointer to allocated packet or NULL if no resources +** +*******************************************************************************/ +BT_HDR *l2cu_build_header (tL2C_LCB *p_lcb, UINT16 len, UINT8 cmd, UINT8 id) +{ + BT_HDR *p_buf = (BT_HDR *)osi_malloc(L2CAP_CMD_BUF_SIZE); + UINT8 *p; + + if (!p_buf) { + return (NULL); + } + + p_buf->offset = L2CAP_SEND_CMD_OFFSET; + p_buf->len = len + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET; + + /* Put in HCI header - handle + pkt boundary */ +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + UINT16_TO_STREAM (p, (p_lcb->handle | (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT))); + } else +#endif + { +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + UINT16_TO_STREAM (p, p_lcb->handle | l2cb.non_flushable_pbf); +#else + UINT16_TO_STREAM (p, (p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT))); +#endif + } + + UINT16_TO_STREAM (p, len + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD); + UINT16_TO_STREAM (p, len + L2CAP_CMD_OVERHEAD); + +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + //counter_add("l2cap.ble.tx.bytes", p_buf->len); + //counter_add("l2cap.ble.tx.pkts", 1); + + UINT16_TO_STREAM (p, L2CAP_BLE_SIGNALLING_CID); + } else +#endif + { + //counter_add("l2cap.sig.tx.bytes", p_buf->len); + //counter_add("l2cap.sig.tx.pkts", 1); + UINT16_TO_STREAM (p, L2CAP_SIGNALLING_CID); + } + + /* Put in L2CAP command header */ + UINT8_TO_STREAM (p, cmd); + UINT8_TO_STREAM (p, id); + UINT16_TO_STREAM (p, len); + + return (p_buf); +} + +/******************************************************************************* +** +** Function l2cu_adj_id +** +** Description Checks for valid ID based on specified mask +** and adjusts the id if invalid. +** +** Returns void +** +*******************************************************************************/ +void l2cu_adj_id (tL2C_LCB *p_lcb, UINT8 adj_mask) +{ + if ((adj_mask & L2CAP_ADJ_ZERO_ID) && !p_lcb->id) { + p_lcb->id++; + } +} + +/******************************************************************************* +** +** Function l2cu_send_peer_cmd_reject +** +** Description Build and send an L2CAP "command reject" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_cmd_reject (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id, + UINT16 p1, UINT16 p2) +{ + UINT16 param_len; + BT_HDR *p_buf; + UINT8 *p; + + /* Put in L2CAP packet header */ + if (reason == L2CAP_CMD_REJ_MTU_EXCEEDED) { + param_len = 2; + } else if (reason == L2CAP_CMD_REJ_INVALID_CID) { + param_len = 4; + } else { + param_len = 0; + } + + if ((p_buf = l2cu_build_header (p_lcb, (UINT16) (L2CAP_CMD_REJECT_LEN + param_len), L2CAP_CMD_REJECT, rem_id)) == NULL ) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer cmd_rej"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, reason); + + if (param_len >= 2) { + UINT16_TO_STREAM (p, p1); + } + + if (param_len >= 4) { + UINT16_TO_STREAM (p, p2); + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_connect_req +** +** Description Build and send an L2CAP "connection request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_connect_req (tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, L2CAP_CONN_REQ_LEN, L2CAP_CMD_CONN_REQ, + p_ccb->local_id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->p_rcb->real_psm); + UINT16_TO_STREAM (p, p_ccb->local_cid); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_connect_rsp +** +** Description Build and send an L2CAP "connection response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_connect_rsp (tL2C_CCB *p_ccb, UINT16 result, UINT16 status) +{ + BT_HDR *p_buf; + UINT8 *p; + + if (result == L2CAP_CONN_PENDING) { + /* if we already sent pending response */ + if (p_ccb->flags & CCB_FLAG_SENT_PENDING) { + return; + } else { + p_ccb->flags |= CCB_FLAG_SENT_PENDING; + } + } + + if ((p_buf = l2cu_build_header(p_ccb->p_lcb, L2CAP_CONN_RSP_LEN, L2CAP_CMD_CONN_RSP, p_ccb->remote_id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for conn_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->local_cid); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, result); + UINT16_TO_STREAM (p, status); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_reject_connection +** +** Description Build and send an L2CAP "connection response neg" message +** to the peer. This function is called when there is no peer +** CCB (non-existant PSM or no resources). +** +** Returns void +** +*******************************************************************************/ +void l2cu_reject_connection (tL2C_LCB *p_lcb, UINT16 remote_cid, UINT8 rem_id, UINT16 result) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf = l2cu_build_header(p_lcb, L2CAP_CONN_RSP_LEN, L2CAP_CMD_CONN_RSP, rem_id)) == NULL ) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, 0); /* Local CID of 0 */ + UINT16_TO_STREAM (p, remote_cid); + UINT16_TO_STREAM (p, result); + UINT16_TO_STREAM (p, 0); /* Status of 0 */ + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_req +** +** Description Build and send an L2CAP "configuration request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BT_HDR *p_buf; + UINT16 cfg_len = 0; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if (p_cfg->mtu_present) { + cfg_len += L2CAP_CFG_MTU_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->flush_to_present) { + cfg_len += L2CAP_CFG_FLUSH_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->qos_present) { + cfg_len += L2CAP_CFG_QOS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->fcr_present) { + cfg_len += L2CAP_CFG_FCR_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->fcs_present) { + cfg_len += L2CAP_CFG_FCS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->ext_flow_spec_present) { + cfg_len += L2CAP_CFG_EXT_FLOW_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, (UINT16) (L2CAP_CONFIG_REQ_LEN + cfg_len), + L2CAP_CMD_CONFIG_REQ, p_ccb->local_id)) == NULL ) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_cfg->flags); /* Flags (continuation) */ + + /* Now, put the options */ + if (p_cfg->mtu_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_MTU); + UINT8_TO_STREAM (p, L2CAP_CFG_MTU_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->mtu); + } + if (p_cfg->flush_to_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FLUSH_TOUT); + UINT8_TO_STREAM (p, L2CAP_CFG_FLUSH_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->flush_to); + } + if (p_cfg->qos_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_QOS); + UINT8_TO_STREAM (p, L2CAP_CFG_QOS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->qos.qos_flags); + UINT8_TO_STREAM (p, p_cfg->qos.service_type); + UINT32_TO_STREAM (p, p_cfg->qos.token_rate); + UINT32_TO_STREAM (p, p_cfg->qos.token_bucket_size); + UINT32_TO_STREAM (p, p_cfg->qos.peak_bandwidth); + UINT32_TO_STREAM (p, p_cfg->qos.latency); + UINT32_TO_STREAM (p, p_cfg->qos.delay_variation); + } + if (p_cfg->fcr_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCR); + UINT8_TO_STREAM (p, L2CAP_CFG_FCR_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcr.mode); + UINT8_TO_STREAM (p, p_cfg->fcr.tx_win_sz); + UINT8_TO_STREAM (p, p_cfg->fcr.max_transmit); + UINT16_TO_STREAM (p, p_cfg->fcr.rtrans_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mon_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mps); + } + + if (p_cfg->fcs_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCS); + UINT8_TO_STREAM (p, L2CAP_CFG_FCS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcs); + } + + if (p_cfg->ext_flow_spec_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_EXT_FLOW); + UINT8_TO_STREAM (p, L2CAP_CFG_EXT_FLOW_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.id); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.stype); + UINT16_TO_STREAM (p, p_cfg->ext_flow_spec.max_sdu_size); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.sdu_inter_time); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.access_latency); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.flush_timeout); + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_rsp +** +** Description Build and send an L2CAP "configuration response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BT_HDR *p_buf; + UINT16 cfg_len = 0; + UINT8 *p; + + /* Create an identifier for this packet */ + if (p_cfg->mtu_present) { + cfg_len += L2CAP_CFG_MTU_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->flush_to_present) { + cfg_len += L2CAP_CFG_FLUSH_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->qos_present) { + cfg_len += L2CAP_CFG_QOS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->fcr_present) { + cfg_len += L2CAP_CFG_FCR_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + if (p_cfg->ext_flow_spec_present) { + cfg_len += L2CAP_CFG_EXT_FLOW_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + } + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, (UINT16)(L2CAP_CONFIG_RSP_LEN + cfg_len), + L2CAP_CMD_CONFIG_RSP, p_ccb->remote_id)) == NULL ) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_cfg->flags); /* Flags (continuation) Must match request */ + UINT16_TO_STREAM (p, p_cfg->result); + + /* Now, put the options */ + if (p_cfg->mtu_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_MTU); + UINT8_TO_STREAM (p, L2CAP_CFG_MTU_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->mtu); + } + if (p_cfg->flush_to_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FLUSH_TOUT); + UINT8_TO_STREAM (p, L2CAP_CFG_FLUSH_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->flush_to); + } + if (p_cfg->qos_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_QOS); + UINT8_TO_STREAM (p, L2CAP_CFG_QOS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->qos.qos_flags); + UINT8_TO_STREAM (p, p_cfg->qos.service_type); + UINT32_TO_STREAM (p, p_cfg->qos.token_rate); + UINT32_TO_STREAM (p, p_cfg->qos.token_bucket_size); + UINT32_TO_STREAM (p, p_cfg->qos.peak_bandwidth); + UINT32_TO_STREAM (p, p_cfg->qos.latency); + UINT32_TO_STREAM (p, p_cfg->qos.delay_variation); + } + if (p_cfg->fcr_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCR); + UINT8_TO_STREAM (p, L2CAP_CFG_FCR_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcr.mode); + UINT8_TO_STREAM (p, p_cfg->fcr.tx_win_sz); + UINT8_TO_STREAM (p, p_cfg->fcr.max_transmit); + UINT16_TO_STREAM (p, p_ccb->our_cfg.fcr.rtrans_tout); + UINT16_TO_STREAM (p, p_ccb->our_cfg.fcr.mon_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mps); + } + + if (p_cfg->ext_flow_spec_present) { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_EXT_FLOW); + UINT8_TO_STREAM (p, L2CAP_CFG_EXT_FLOW_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.id); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.stype); + UINT16_TO_STREAM (p, p_cfg->ext_flow_spec.max_sdu_size); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.sdu_inter_time); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.access_latency); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.flush_timeout); + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_rej +** +** Description Build and send an L2CAP "configuration reject" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, UINT16 rej_len) +{ + BT_HDR *p_buf; + UINT16 len, cfg_len, buf_space, len1; + UINT8 *p, *p_hci_len, *p_data_end; + UINT8 cfg_code; + + L2CAP_TRACE_DEBUG("l2cu_send_peer_config_rej: data_len=%d, rej_len=%d", data_len, rej_len); + + + len = BT_HDR_SIZE + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD + L2CAP_CONFIG_RSP_LEN; + len1 = 0xFFFF - len; + if (rej_len > len1) { + L2CAP_TRACE_ERROR ("L2CAP - cfg_rej pkt size exceeds buffer design max limit."); + return; + } + + p_buf = (BT_HDR *)osi_malloc (len + rej_len); + + if (!p_buf) { + L2CAP_TRACE_ERROR ("L2CAP - no buffer for cfg_rej"); + return; + } + + p_buf->offset = L2CAP_SEND_CMD_OFFSET; + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET; + + /* Put in HCI header - handle + pkt boundary */ +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + if (HCI_NON_FLUSHABLE_PB_SUPPORTED(BTM_ReadLocalFeatures ())) { + UINT16_TO_STREAM (p, (p_ccb->p_lcb->handle | (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT))); + } else +#endif + { + UINT16_TO_STREAM (p, (p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT))); + } + + /* Remember the HCI header length position, and save space for it */ + p_hci_len = p; + p += 2; + + /* Put in L2CAP packet header */ + UINT16_TO_STREAM (p, L2CAP_CMD_OVERHEAD + L2CAP_CONFIG_RSP_LEN + rej_len); + UINT16_TO_STREAM (p, L2CAP_SIGNALLING_CID); + + /* Put in L2CAP command header */ + UINT8_TO_STREAM (p, L2CAP_CMD_CONFIG_RSP); + UINT8_TO_STREAM (p, p_ccb->remote_id); + + UINT16_TO_STREAM (p, L2CAP_CONFIG_RSP_LEN + rej_len); + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, 0); /* Flags = 0 (no continuation) */ + UINT16_TO_STREAM (p, L2CAP_CFG_UNKNOWN_OPTIONS); + + buf_space = rej_len; + + /* Now, put the rejected options */ + p_data_end = p_data + data_len; + while (p_data < p_data_end) { + cfg_code = *p_data; + cfg_len = *(p_data + 1); + + switch (cfg_code & 0x7F) { + /* skip known options */ + case L2CAP_CFG_TYPE_MTU: + case L2CAP_CFG_TYPE_FLUSH_TOUT: + case L2CAP_CFG_TYPE_QOS: + p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + break; + + /* unknown options; copy into rsp if not hints */ + default: + /* sanity check option length */ + if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= data_len) { + if ((cfg_code & 0x80) == 0) { + if (buf_space >= (cfg_len + L2CAP_CFG_OPTION_OVERHEAD)) { + memcpy(p, p_data, cfg_len + L2CAP_CFG_OPTION_OVERHEAD); + p += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + buf_space -= (cfg_len + L2CAP_CFG_OPTION_OVERHEAD); + } else { + L2CAP_TRACE_WARNING("L2CAP - cfg_rej exceeds allocated buffer"); + p_data = p_data_end; /* force loop exit */ + break; + } + } + p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + } + /* bad length; force loop exit */ + else { + p_data = p_data_end; + } + break; + } + } + + len = (UINT16) (p - p_hci_len - 2); + UINT16_TO_STREAM (p_hci_len, len); + + p_buf->len = len + 4; + + L2CAP_TRACE_DEBUG ("L2CAP - cfg_rej pkt hci_len=%d, l2cap_len=%d", + len, (L2CAP_CMD_OVERHEAD + L2CAP_CONFIG_RSP_LEN + rej_len)); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_disc_req +** +** Description Build and send an L2CAP "disconnect request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_disc_req (tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf, *p_buf2; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header(p_ccb->p_lcb, L2CAP_DISC_REQ_LEN, L2CAP_CMD_DISC_REQ, p_ccb->local_id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for disc_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_ccb->local_cid); + + /* Move all queued data packets to the LCB. In FCR mode, assume the higher + layer checks that all buffers are sent before disconnecting. + */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) { + while ((p_buf2 = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0)) != NULL) { + l2cu_set_acl_hci_header (p_buf2, p_ccb); + l2c_link_check_send_pkts (p_ccb->p_lcb, p_ccb, p_buf2); + } + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_disc_rsp +** +** Description Build and send an L2CAP "disconnect response" message +** to the peer. +** +** This function is passed the parameters for the disconnect +** response instead of the CCB address, as it may be called +** to send a disconnect response when there is no CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_disc_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 local_cid, + UINT16 remote_cid) +{ + BT_HDR *p_buf; + UINT8 *p; + + if (!p_lcb) { + L2CAP_TRACE_WARNING("lcb already released\n"); + return; + } + + if ((p_buf = l2cu_build_header(p_lcb, L2CAP_DISC_RSP_LEN, L2CAP_CMD_DISC_RSP, remote_id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for disc_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, local_cid); + UINT16_TO_STREAM (p, remote_cid); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_echo_req +** +** Description Build and send an L2CAP "echo request" message +** to the peer. Note that we do not currently allow +** data in the echo request. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_echo_req (tL2C_LCB *p_lcb, UINT8 *p_data, UINT16 data_len) +{ + BT_HDR *p_buf; + UINT8 *p; + + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ZERO_ID); /* check for wrap to '0' */ + + if ((p_buf = l2cu_build_header(p_lcb, (UINT16) (L2CAP_ECHO_REQ_LEN + data_len), L2CAP_CMD_ECHO_REQ, p_lcb->id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for echo_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + if (data_len) { + ARRAY_TO_STREAM (p, p_data, data_len); + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_echo_rsp +** +** Description Build and send an L2CAP "echo response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_echo_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len) +{ + BT_HDR *p_buf; + UINT8 *p; + UINT16 maxlen; + /* Filter out duplicate IDs or if available buffers are low (intruder checking) */ + if (!id || id == p_lcb->cur_echo_id) { + /* Dump this request since it is illegal */ + L2CAP_TRACE_WARNING ("L2CAP ignoring duplicate echo request (%d)", id); + return; + } else { + p_lcb->cur_echo_id = id; + } + + uint16_t acl_data_size = controller_get_interface()->get_acl_data_size_classic(); + uint16_t acl_packet_size = controller_get_interface()->get_acl_packet_size_classic(); + /* Don't return data if it does not fit in ACL and L2CAP MTU */ + maxlen = (L2CAP_CMD_BUF_SIZE > acl_packet_size) ? + acl_data_size : (UINT16)L2CAP_CMD_BUF_SIZE; + maxlen -= (UINT16)(BT_HDR_SIZE + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + + L2CAP_CMD_OVERHEAD + L2CAP_ECHO_RSP_LEN); + + if (data_len > maxlen) { + data_len = 0; + } + + if ((p_buf = l2cu_build_header (p_lcb, (UINT16)(L2CAP_ECHO_RSP_LEN + data_len), L2CAP_CMD_ECHO_RSP, id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for echo_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + if (data_len) { + ARRAY_TO_STREAM (p, p_data, data_len); + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_info_req +** +** Description Build and send an L2CAP "info request" message +** to the peer. +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_info_req (tL2C_LCB *p_lcb, UINT16 info_type) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* check for wrap and/or BRCM ID */ + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ID); + + if ((p_buf = l2cu_build_header(p_lcb, 2, L2CAP_CMD_INFO_REQ, p_lcb->id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for info_req"); + return; + } + + L2CAP_TRACE_EVENT ("l2cu_send_peer_info_req: type 0x%04x", info_type); + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, info_type); + + p_lcb->w4_info_rsp = TRUE; + btu_start_timer (&p_lcb->info_timer_entry, BTU_TTYPE_L2CAP_INFO, L2CAP_WAIT_INFO_RSP_TOUT); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_info_rsp +** +** Description Build and send an L2CAP "info response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_info_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 info_type) +{ + BT_HDR *p_buf; + UINT8 *p; + UINT16 len = L2CAP_INFO_RSP_LEN; + +#if (L2CAP_CONFORMANCE_TESTING == TRUE) + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (l2cb.test_info_resp & (L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE | + L2CAP_EXTFEA_NO_CRC | L2CAP_EXTFEA_EXT_FLOW_SPEC | + L2CAP_EXTFEA_FIXED_CHNLS | L2CAP_EXTFEA_EXT_WINDOW | + L2CAP_EXTFEA_UCD_RECEPTION )) ) +#else + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (L2CAP_EXTFEA_SUPPORTED_MASK & (L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE | + L2CAP_EXTFEA_NO_CRC | L2CAP_EXTFEA_FIXED_CHNLS | + L2CAP_EXTFEA_UCD_RECEPTION )) ) +#endif + { + len += L2CAP_EXTENDED_FEATURES_ARRAY_SIZE; + } else if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) { + len += L2CAP_FIXED_CHNL_ARRAY_SIZE; + } else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) { + len += L2CAP_CONNLESS_MTU_INFO_SIZE; + } + + if ((p_buf = l2cu_build_header(p_lcb, len, L2CAP_CMD_INFO_RSP, remote_id)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no buffer for info_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, info_type); + +#if (L2CAP_CONFORMANCE_TESTING == TRUE) + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (l2cb.test_info_resp & ( L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE + | L2CAP_EXTFEA_UCD_RECEPTION )) ) +#else + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (L2CAP_EXTFEA_SUPPORTED_MASK & ( L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE + | L2CAP_EXTFEA_UCD_RECEPTION )) ) +#endif + { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_LE) { + /* optional data are not added for now */ + UINT32_TO_STREAM (p, L2CAP_BLE_EXTFEA_MASK); + } else +#endif + { +#if L2CAP_CONFORMANCE_TESTING == TRUE + UINT32_TO_STREAM (p, l2cb.test_info_resp); +#else +#if (L2CAP_NUM_FIXED_CHNLS > 0) + UINT32_TO_STREAM (p, L2CAP_EXTFEA_SUPPORTED_MASK | L2CAP_EXTFEA_FIXED_CHNLS); +#else + UINT32_TO_STREAM (p, L2CAP_EXTFEA_SUPPORTED_MASK); +#endif +#endif + } + } else if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); + memset (p, 0, L2CAP_FIXED_CHNL_ARRAY_SIZE); + + p[0] = L2CAP_FIXED_CHNL_SIG_BIT; + + if ( L2CAP_EXTFEA_SUPPORTED_MASK & L2CAP_EXTFEA_UCD_RECEPTION ) { + p[0] |= L2CAP_FIXED_CHNL_CNCTLESS_BIT; + } + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + { + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + if (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) { + p[(xx + L2CAP_FIRST_FIXED_CHNL) / 8] |= 1 << ((xx + L2CAP_FIRST_FIXED_CHNL) % 8); + } + } +#endif + } else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); + UINT16_TO_STREAM (p, L2CAP_UCD_MTU); + } else { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_NOT_SUPPORTED); /* 'not supported' */ + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/****************************************************************************** +** +** Function l2cu_enqueue_ccb +** +** Description queue CCB by priority. The first CCB is highest priority and +** is served at first. The CCB is queued to an LLCB or an LCB. +** +** Returns None +** +*******************************************************************************/ +void l2cu_enqueue_ccb (tL2C_CCB *p_ccb) +{ + tL2C_CCB *p_ccb1; + tL2C_CCB_Q *p_q = NULL; + + /* Find out which queue the channel is on + */ + if (p_ccb->p_lcb != NULL) { + p_q = &p_ccb->p_lcb->ccb_queue; + } + + if ( (!p_ccb->in_use) || (p_q == NULL) ) { + L2CAP_TRACE_ERROR ("l2cu_enqueue_ccb CID: 0x%04x ERROR in_use: %u p_lcb: %p", + p_ccb->local_cid, p_ccb->in_use, p_ccb->p_lcb); + return; + } + + L2CAP_TRACE_DEBUG ("l2cu_enqueue_ccb CID: 0x%04x priority: %d", + p_ccb->local_cid, p_ccb->ccb_priority); + + /* If the queue is empty, we go at the front */ + if (!p_q->p_first_ccb) { + p_q->p_first_ccb = p_q->p_last_ccb = p_ccb; + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; + } else { + p_ccb1 = p_q->p_first_ccb; + + while (p_ccb1 != NULL) { + /* Insert new ccb at the end of the same priority. Lower number, higher priority */ + if (p_ccb->ccb_priority < p_ccb1->ccb_priority) { + /* Are we at the head of the queue ? */ + if (p_ccb1 == p_q->p_first_ccb) { + p_q->p_first_ccb = p_ccb; + } else { + p_ccb1->p_prev_ccb->p_next_ccb = p_ccb; + } + + p_ccb->p_next_ccb = p_ccb1; + p_ccb->p_prev_ccb = p_ccb1->p_prev_ccb; + p_ccb1->p_prev_ccb = p_ccb; + break; + } + + p_ccb1 = p_ccb1->p_next_ccb; + } + + /* If we are lower then anyone in the list, we go at the end */ + if (!p_ccb1) { + /* add new ccb at the end of the list */ + p_q->p_last_ccb->p_next_ccb = p_ccb; + + p_ccb->p_next_ccb = NULL; + p_ccb->p_prev_ccb = p_q->p_last_ccb; + p_q->p_last_ccb = p_ccb; + } + } + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* Adding CCB into round robin service table of its LCB */ + if (p_ccb->p_lcb != NULL) { + /* if this is the first channel in this priority group */ + if (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb == 0 ) { + /* Set the first channel to this CCB */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb; + /* Set the next serving channel in this group to this CCB */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = p_ccb; + /* Initialize quota of this priority group based on its priority */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota = L2CAP_GET_PRIORITY_QUOTA(p_ccb->ccb_priority); + } + /* increase number of channels in this group */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb++; + } +#endif + +} + +/****************************************************************************** +** +** Function l2cu_dequeue_ccb +** +** Description dequeue CCB from a queue +** +** Returns - +** +*******************************************************************************/ +void l2cu_dequeue_ccb (tL2C_CCB *p_ccb) +{ + tL2C_CCB_Q *p_q = NULL; + + L2CAP_TRACE_DEBUG ("l2cu_dequeue_ccb CID: 0x%04x", p_ccb->local_cid); + + /* Find out which queue the channel is on + */ + if (p_ccb->p_lcb != NULL) { + p_q = &p_ccb->p_lcb->ccb_queue; + } + + if ( (!p_ccb->in_use) || (p_q == NULL) || (p_q->p_first_ccb == NULL) ) { + L2CAP_TRACE_ERROR ("l2cu_dequeue_ccb CID: 0x%04x ERROR in_use: %u p_lcb: %p p_q: %p p_q->p_first_ccb: %p", + p_ccb->local_cid, p_ccb->in_use, p_ccb->p_lcb, p_q, p_q ? p_q->p_first_ccb : 0); + return; + } + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* Removing CCB from round robin service table of its LCB */ + if (p_ccb->p_lcb != NULL) { + /* decrease number of channels in this priority group */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb--; + + /* if it was the last channel in the priority group */ + if (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb == 0 ) { + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = NULL; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = NULL; + } else { + /* if it is the first channel of this group */ + if ( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb == p_ccb ) { + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb->p_next_ccb; + } + /* if it is the next serving channel of this group */ + if ( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb == p_ccb ) { + /* simply, start serving from the first channel */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb + = p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb; + } + } + } +#endif + + if (p_ccb == p_q->p_first_ccb) { + /* We are removing the first in a queue */ + p_q->p_first_ccb = p_ccb->p_next_ccb; + + if (p_q->p_first_ccb) { + p_q->p_first_ccb->p_prev_ccb = NULL; + } else { + p_q->p_last_ccb = NULL; + } + } else if (p_ccb == p_q->p_last_ccb) { + /* We are removing the last in a queue */ + p_q->p_last_ccb = p_ccb->p_prev_ccb; + p_q->p_last_ccb->p_next_ccb = NULL; + } else { + /* In the middle of a chain. */ + p_ccb->p_prev_ccb->p_next_ccb = p_ccb->p_next_ccb; + p_ccb->p_next_ccb->p_prev_ccb = p_ccb->p_prev_ccb; + } + + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; +} + +/****************************************************************************** +** +** Function l2cu_change_pri_ccb +** +** Description +** +** Returns - +** +*******************************************************************************/ +void l2cu_change_pri_ccb (tL2C_CCB *p_ccb, tL2CAP_CHNL_PRIORITY priority) +{ + if (p_ccb->ccb_priority != priority) { + /* If CCB is not the only guy on the queue */ + if ( (p_ccb->p_next_ccb != NULL) || (p_ccb->p_prev_ccb != NULL) ) { + L2CAP_TRACE_DEBUG ("Update CCB list in logical link"); + + /* Remove CCB from queue and re-queue it at new priority */ + l2cu_dequeue_ccb (p_ccb); + + p_ccb->ccb_priority = priority; + l2cu_enqueue_ccb (p_ccb); + } +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + else { + /* If CCB is the only guy on the queue, no need to re-enqueue */ + /* update only round robin service data */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb = 0; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = NULL; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = NULL; + + p_ccb->ccb_priority = priority; + + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = p_ccb; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota = L2CAP_GET_PRIORITY_QUOTA(p_ccb->ccb_priority); + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb = 1; + } +#endif + } +} + +/******************************************************************************* +** +** Function l2cu_allocate_ccb +** +** Description This function allocates a Channel Control Block and +** attaches it to a link control block. The local CID +** is also assigned. +** +** Returns pointer to CCB, or NULL if none +** +*******************************************************************************/ +bool l2cu_find_ccb_in_list(void *p_ccb_node, void *p_local_cid); +tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid) +{ + tL2C_CCB *p_ccb = NULL; + uint16_t tmp_cid = L2CAP_BASE_APPL_CID; + L2CAP_TRACE_DEBUG ("l2cu_allocate_ccb: cid 0x%04x", cid); + + p_ccb = l2cu_find_free_ccb (); + if(p_ccb == NULL) { + if (list_length(l2cb.p_ccb_pool) < MAX_L2CAP_CHANNELS) { + p_ccb = (tL2C_CCB *)osi_malloc(sizeof(tL2C_CCB)); + + if (p_ccb) { + memset (p_ccb, 0, sizeof(tL2C_CCB)); + list_append(l2cb.p_ccb_pool, p_ccb); + } + } + } + if (p_ccb == NULL) { + return (NULL); + } + + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; + + p_ccb->in_use = TRUE; + + /* Get a CID for the connection */ + for (tmp_cid = L2CAP_BASE_APPL_CID; tmp_cid < MAX_L2CAP_CHANNELS + L2CAP_BASE_APPL_CID; tmp_cid++) { + if (list_foreach(l2cb.p_ccb_pool, l2cu_find_ccb_in_list, &tmp_cid) == NULL) { + break; + } + } + assert(tmp_cid != MAX_L2CAP_CHANNELS + L2CAP_BASE_APPL_CID); + p_ccb->local_cid = tmp_cid; + p_ccb->p_lcb = p_lcb; + p_ccb->p_rcb = NULL; + p_ccb->should_free_rcb = false; + + /* Set priority then insert ccb into LCB queue (if we have an LCB) */ + p_ccb->ccb_priority = L2CAP_CHNL_PRIORITY_LOW; + + if (p_lcb) { + l2cu_enqueue_ccb (p_ccb); + } + + /* clear what peer wants to configure */ + p_ccb->peer_cfg_bits = 0; + + /* Put in default values for configuration */ + memset (&p_ccb->our_cfg, 0, sizeof(tL2CAP_CFG_INFO)); + memset (&p_ccb->peer_cfg, 0, sizeof(tL2CAP_CFG_INFO)); + + /* Put in default values for local/peer configurations */ + p_ccb->our_cfg.flush_to = p_ccb->peer_cfg.flush_to = L2CAP_DEFAULT_FLUSH_TO; + p_ccb->our_cfg.mtu = p_ccb->peer_cfg.mtu = L2CAP_DEFAULT_MTU; + p_ccb->our_cfg.qos.service_type = p_ccb->peer_cfg.qos.service_type = L2CAP_DEFAULT_SERV_TYPE; + p_ccb->our_cfg.qos.token_rate = p_ccb->peer_cfg.qos.token_rate = L2CAP_DEFAULT_TOKEN_RATE; + p_ccb->our_cfg.qos.token_bucket_size = p_ccb->peer_cfg.qos.token_bucket_size = L2CAP_DEFAULT_BUCKET_SIZE; + p_ccb->our_cfg.qos.peak_bandwidth = p_ccb->peer_cfg.qos.peak_bandwidth = L2CAP_DEFAULT_PEAK_BANDWIDTH; + p_ccb->our_cfg.qos.latency = p_ccb->peer_cfg.qos.latency = L2CAP_DEFAULT_LATENCY; + p_ccb->our_cfg.qos.delay_variation = p_ccb->peer_cfg.qos.delay_variation = L2CAP_DEFAULT_DELAY; + + p_ccb->bypass_fcs = 0; + memset (&p_ccb->ertm_info, 0, sizeof(tL2CAP_ERTM_INFO)); + p_ccb->peer_cfg_already_rejected = FALSE; + p_ccb->fcr_cfg_tries = L2CAP_MAX_FCR_CFG_TRIES; + + /* stop and release timers */ + btu_free_quick_timer(&p_ccb->fcrb.ack_timer); + memset(&p_ccb->fcrb.ack_timer, 0, sizeof(TIMER_LIST_ENT)); + p_ccb->fcrb.ack_timer.param = (TIMER_PARAM_TYPE)p_ccb; + + btu_free_quick_timer(&p_ccb->fcrb.mon_retrans_timer); + memset(&p_ccb->fcrb.mon_retrans_timer, 0, sizeof(TIMER_LIST_ENT)); + p_ccb->fcrb.mon_retrans_timer.param = (TIMER_PARAM_TYPE)p_ccb; + +// btla-specific ++ + /* CSP408639 Fix: When L2CAP send amp move channel request or receive + * L2CEVT_AMP_MOVE_REQ do following sequence. Send channel move + * request -> Stop retrans/monitor timer -> Change channel state to CST_AMP_MOVING. */ +// btla-specific -- + +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_fcr_free_timer (p_ccb); +#endif ///CLASSIC_BT_INCLUDED == TRUE + +#if BT_SDP_BQB_INCLUDED + if (l2cap_bqb_ertm_mode_included_flag) { + p_ccb->ertm_info.preferred_mode = L2CAP_FCR_ERTM_MODE; + p_ccb->ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM; + } else +#endif /* BT_SDP_BQB_INCLUDED */ + { + p_ccb->ertm_info.preferred_mode = L2CAP_FCR_BASIC_MODE; /* Default mode for channel is basic mode */ + p_ccb->ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_BASIC|L2CAP_FCR_CHAN_OPT_ERTM; + } + p_ccb->ertm_info.fcr_rx_buf_size = L2CAP_FCR_RX_BUF_SIZE; + p_ccb->ertm_info.fcr_tx_buf_size = L2CAP_FCR_TX_BUF_SIZE; + p_ccb->ertm_info.user_rx_buf_size = L2CAP_USER_RX_BUF_SIZE; + p_ccb->ertm_info.user_tx_buf_size = L2CAP_USER_TX_BUF_SIZE; + p_ccb->max_rx_mtu = L2CAP_MTU_SIZE; + p_ccb->tx_mps = L2CAP_FCR_TX_BUF_SIZE - 32; + + p_ccb->xmit_hold_q = fixed_queue_new(QUEUE_SIZE_MAX); +#if (CLASSIC_BT_INCLUDED == TRUE) + p_ccb->fcrb.srej_rcv_hold_q = fixed_queue_new(QUEUE_SIZE_MAX); + p_ccb->fcrb.retrans_q = fixed_queue_new(QUEUE_SIZE_MAX); + p_ccb->fcrb.waiting_for_ack_q = fixed_queue_new(QUEUE_SIZE_MAX); +#endif ///CLASSIC_BT_INCLUDED == TRUE + + p_ccb->cong_sent = FALSE; + p_ccb->buff_quota = 2; /* This gets set after config */ + + /* If CCB was reserved Config_Done can already have some value */ + if (cid == 0) { + p_ccb->config_done = 0; + } else { + L2CAP_TRACE_DEBUG ("l2cu_allocate_ccb: cid 0x%04x config_done:0x%x", cid, p_ccb->config_done); + } + + p_ccb->chnl_state = CST_CLOSED; + p_ccb->flags = 0; + p_ccb->tx_data_rate = L2CAP_CHNL_DATA_RATE_LOW; + p_ccb->rx_data_rate = L2CAP_CHNL_DATA_RATE_LOW; + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + p_ccb->is_flushable = FALSE; +#endif + + btu_free_timer(&p_ccb->timer_entry); + memset(&p_ccb->timer_entry, 0, sizeof(TIMER_LIST_ENT)); + p_ccb->timer_entry.param = (TIMER_PARAM_TYPE)p_ccb; + p_ccb->timer_entry.in_use = 0; + + l2c_link_adjust_chnl_allocation (); + + return (p_ccb); +} + +/******************************************************************************* +** +** Function l2cu_start_post_bond_timer +** +** Description This function starts the ACL Link inactivity timer after +** dedicated bonding +** This timer can be longer than the normal link inactivity +** timer for some platforms. +** +** Returns BOOLEAN - TRUE if idle timer started or disconnect initiated +** FALSE if there's one or more pending CCB's exist +** +*******************************************************************************/ +BOOLEAN l2cu_start_post_bond_timer (UINT16 handle) +{ + UINT16 timeout; + tL2C_LCB *p_lcb = l2cu_find_lcb_by_handle(handle); + + if (!p_lcb) { + return (TRUE); + } + + p_lcb->is_bonding = FALSE; + + /* Only start timer if no control blocks allocated */ + if (p_lcb->ccb_queue.p_first_ccb != NULL) { + return (FALSE); + } + + /* If no channels on the connection, start idle timeout */ + if ( (p_lcb->link_state == LST_CONNECTED) || (p_lcb->link_state == LST_CONNECTING) || (p_lcb->link_state == LST_DISCONNECTING) ) { + if (p_lcb->idle_timeout == 0) { + if (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER)) { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else { + timeout = BT_1SEC_TIMEOUT; + } + } else { + timeout = L2CAP_BONDING_TIMEOUT; + } + + if (timeout != 0xFFFF) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + } + + return (TRUE); + } + + return (FALSE); +} + +/******************************************************************************* +** +** Function l2cu_release_ccb +** +** Description This function releases a Channel Control Block. The timer +** is stopped, any attached buffers freed, and the CCB is removed +** from the link control block. +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_ccb (tL2C_CCB *p_ccb) +{ + tL2C_LCB *p_lcb = p_ccb->p_lcb; + tL2C_RCB *p_rcb = p_ccb->p_rcb; + L2CAP_TRACE_DEBUG ("l2cu_release_ccb: cid 0x%04x in_use: %u", p_ccb->local_cid, p_ccb->in_use); + + /* If already released, could be race condition */ + if (!p_ccb->in_use) { + return; + } +#if BLE_INCLUDED == TRUE + if (p_lcb->transport == BT_TRANSPORT_LE) { + /* Take samephore to avoid race condition */ + l2ble_update_att_acl_pkt_num(L2CA_BUFF_FREE, NULL); + } +#endif +#if (SDP_INCLUDED == TRUE) + if (p_rcb && (p_rcb->psm != p_rcb->real_psm)) { + btm_sec_clr_service_by_psm(p_rcb->psm); + } +#endif ///SMP_INCLUDED == TRUE + if (p_ccb->should_free_rcb) { + osi_free(p_rcb); + p_ccb->p_rcb = NULL; + p_ccb->should_free_rcb = false; + } + if (p_lcb) { + btm_sec_clr_temp_auth_service (p_lcb->remote_bd_addr); + } + + /* Stop and free the timer */ + btu_free_timer (&p_ccb->timer_entry); + + fixed_queue_free(p_ccb->xmit_hold_q, osi_free_func); + p_ccb->xmit_hold_q = NULL; +#if (CLASSIC_BT_INCLUDED == TRUE) + fixed_queue_free(p_ccb->fcrb.srej_rcv_hold_q, osi_free_func); + fixed_queue_free(p_ccb->fcrb.retrans_q, osi_free_func); + fixed_queue_free(p_ccb->fcrb.waiting_for_ack_q, osi_free_func); + p_ccb->fcrb.srej_rcv_hold_q = NULL; + p_ccb->fcrb.retrans_q = NULL; + p_ccb->fcrb.waiting_for_ack_q = NULL; +#endif ///CLASSIC_BT_INCLUDED == TRUE + + +#if (CLASSIC_BT_INCLUDED == TRUE) + l2c_fcr_cleanup (p_ccb); +#endif ///CLASSIC_BT_INCLUDED == TRUE + /* Channel may not be assigned to any LCB if it was just pre-reserved */ + if ( (p_lcb) && + ( (p_ccb->local_cid >= L2CAP_BASE_APPL_CID) +#if (L2CAP_UCD_INCLUDED == TRUE) + || (p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID) +#endif + ) + ) { + l2cu_dequeue_ccb (p_ccb); + + /* Delink the CCB from the LCB */ + p_ccb->p_lcb = NULL; + } + + + /* Flag as not in use */ + p_ccb->in_use = FALSE; + + /* If no channels on the connection, start idle timeout */ + if ((p_lcb) && p_lcb->in_use && (p_lcb->link_state == LST_CONNECTED)) { + if (!p_lcb->ccb_queue.p_first_ccb) { + l2cu_no_dynamic_ccbs (p_lcb); + } else { + /* Link is still active, adjust channel quotas. */ + l2c_link_adjust_chnl_allocation (); + } + } +} + +/******************************************************************************* +** +** Function l2cu_find_ccb_by_remote_cid +** +** Description Look through all active CCBs on a link for a match based +** on the remote CID. +** +** Returns pointer to matched CCB, or NULL if no match +** +*******************************************************************************/ +tL2C_CCB *l2cu_find_ccb_by_remote_cid (tL2C_LCB *p_lcb, UINT16 remote_cid) +{ + tL2C_CCB *p_ccb; + + /* If LCB is NULL, look through all active links */ + if (!p_lcb) { + return NULL; + } else { + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + if ((p_ccb->in_use) && (p_ccb->remote_cid == remote_cid)) { + return (p_ccb); + } + } + + /* If here, no match found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_allocate_rcb +** +** Description Look through the Registration Control Blocks for a free +** one. +** +** Returns Pointer to the RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_allocate_rcb (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if (!p_rcb->in_use) { + p_rcb->in_use = TRUE; + p_rcb->psm = psm; +#if (L2CAP_UCD_INCLUDED == TRUE) + p_rcb->ucd.state = L2C_UCD_STATE_UNUSED; +#endif + return (p_rcb); + } + } + + /* If here, no free RCB found */ + return (NULL); +} + +#if (BLE_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cu_allocate_ble_rcb +** +** Description Look through the BLE Registration Control Blocks for a free +** one. +** +** Returns Pointer to the BLE RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_allocate_ble_rcb (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.ble_rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < BLE_MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if (!p_rcb->in_use) + { + p_rcb->in_use = TRUE; + p_rcb->psm = psm; +#if (L2CAP_UCD_INCLUDED == TRUE) + p_rcb->ucd.state = L2C_UCD_STATE_UNUSED; +#endif + return (p_rcb); + } + } + + /* If here, no free RCB found */ + return (NULL); +} +#endif ///BLE_INCLUDED == TRUE + +/******************************************************************************* +** +** Function l2cu_release_rcb +** +** Description Mark an RCB as no longet in use +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_rcb (tL2C_RCB *p_rcb) +{ + p_rcb->in_use = FALSE; + p_rcb->psm = 0; +} + + +/******************************************************************************* +** +** Function l2cu_disconnect_chnl +** +** Description Disconnect a channel. Typically, this is due to either +** receiving a bad configuration, bad packet or max_retries expiring. +** +*******************************************************************************/ +void l2cu_disconnect_chnl (tL2C_CCB *p_ccb) +{ + UINT16 local_cid = p_ccb->local_cid; + + if (local_cid >= L2CAP_BASE_APPL_CID) { + tL2CA_DISCONNECT_IND_CB *p_disc_cb = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + + L2CAP_TRACE_WARNING ("L2CAP - disconnect_chnl CID: 0x%04x", local_cid); + + l2cu_send_peer_disc_req (p_ccb); + + l2cu_release_ccb (p_ccb); + + (*p_disc_cb)(local_cid, FALSE); + } else { + /* failure on the AMP channel, probably need to disconnect ACL */ + L2CAP_TRACE_ERROR ("L2CAP - disconnect_chnl CID: 0x%04x Ignored", local_cid); + } +} + + +/******************************************************************************* +** +** Function l2cu_find_rcb_by_psm +** +** Description Look through the Registration Control Blocks to see if +** anyone registered to handle the PSM in question +** +** Returns Pointer to the RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_find_rcb_by_psm (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) { + if ((p_rcb->in_use) && (p_rcb->psm == psm)) { + return (p_rcb); + } + } + + /* If here, no match found */ + return (NULL); +} + +#if (BLE_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cu_find_ble_rcb_by_psm +** +** Description Look through the BLE Registration Control Blocks to see if +** anyone registered to handle the PSM in question +** +** Returns Pointer to the BLE RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_find_ble_rcb_by_psm (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.ble_rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < BLE_MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if ((p_rcb->in_use) && (p_rcb->psm == psm)) { + return (p_rcb); + } + } + + /* If here, no match found */ + return (NULL); +} +#endif ///BLE_INCLUDED == TRUE + +#if (L2CAP_COC_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cu_process_peer_cfg_req +** +** Description This function is called when the peer sends us a "config request" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Note: Negotiation of the FCR channel type is handled internally, +** all others are passed to the upper layer. +** +** Returns UINT8 - L2CAP_PEER_CFG_OK if passed to upper layer, +** L2CAP_PEER_CFG_UNACCEPTABLE if automatically responded to +** because parameters are unnacceptable from a specification +** point of view. +** L2CAP_PEER_CFG_DISCONNECT if no compatible channel modes +** between the two devices, and shall be closed. +** +*******************************************************************************/ +UINT8 l2cu_process_peer_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BOOLEAN mtu_ok = TRUE; + BOOLEAN qos_type_ok = TRUE; + BOOLEAN flush_to_ok = TRUE; + BOOLEAN fcr_ok = TRUE; +#if (CLASSIC_BT_INCLUDED == TRUE) + UINT8 fcr_status; +#endif ///CLASSIC_BT_INCLUDED == TRUE + /* Ignore FCR parameters for basic mode */ + if (!p_cfg->fcr_present) { + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + } + + /* Save the MTU that our peer can receive */ + if (p_cfg->mtu_present) { + /* Make sure MTU is at least the minimum */ + if (p_cfg->mtu >= L2CAP_MIN_MTU) { + /* In basic mode, limit the MTU to our buffer size */ + if ( (p_cfg->fcr_present == FALSE) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) { + p_cfg->mtu = L2CAP_MTU_SIZE; + } + + /* Save the accepted value in case of renegotiation */ + p_ccb->peer_cfg.mtu = p_cfg->mtu; + p_ccb->peer_cfg.mtu_present = TRUE; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_MTU; + } else { /* Illegal MTU value */ + p_cfg->mtu = L2CAP_MIN_MTU; + mtu_ok = FALSE; + } + } + /* Reload mtu from a previously accepted config request */ + else if (p_ccb->peer_cfg.mtu_present) { + p_cfg->mtu_present = TRUE; + p_cfg->mtu = p_ccb->peer_cfg.mtu; + } + + /* Verify that the flush timeout is a valid value (0 is illegal) */ + if (p_cfg->flush_to_present) { + if (!p_cfg->flush_to) { + p_cfg->flush_to = 0xFFFF; /* Infinite retransmissions (spec default) */ + flush_to_ok = FALSE; + } else { /* Save the accepted value in case of renegotiation */ + p_ccb->peer_cfg.flush_to_present = TRUE; + p_ccb->peer_cfg.flush_to = p_cfg->flush_to; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FLUSH_TO; + } + } + /* Reload flush_to from a previously accepted config request */ + else if (p_ccb->peer_cfg.flush_to_present) { + p_cfg->flush_to_present = TRUE; + p_cfg->flush_to = p_ccb->peer_cfg.flush_to; + } + + /* Save the QOS settings the the peer is using */ + if (p_cfg->qos_present) { + /* Make sure service type is not a reserved value; otherwise let upper + layer decide if acceptable + */ + if (p_cfg->qos.service_type <= GUARANTEED) { + p_ccb->peer_cfg.qos = p_cfg->qos; + p_ccb->peer_cfg.qos_present = TRUE; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_QOS; + } else { /* Illegal service type value */ + p_cfg->qos.service_type = BEST_EFFORT; + qos_type_ok = FALSE; + } + } + /* Reload QOS from a previously accepted config request */ + else if (p_ccb->peer_cfg.qos_present) { + p_cfg->qos_present = TRUE; + p_cfg->qos = p_ccb->peer_cfg.qos; + } +#if (CLASSIC_BT_INCLUDED == TRUE) + if ((fcr_status = l2c_fcr_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_DISCONNECT) { + /* Notify caller to disconnect the channel (incompatible modes) */ + p_cfg->result = L2CAP_CFG_FAILED_NO_REASON; + p_cfg->mtu_present = p_cfg->qos_present = p_cfg->flush_to_present = 0; + + return (L2CAP_PEER_CFG_DISCONNECT); + } + + fcr_ok = (fcr_status == L2CAP_PEER_CFG_OK); +#endif ///CLASSIC_BT_INCLUDED == TRUE + + /* Return any unacceptable parameters */ + if (mtu_ok && flush_to_ok && qos_type_ok && fcr_ok) { + l2cu_adjust_out_mps (p_ccb); + return (L2CAP_PEER_CFG_OK); + } else { + p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS; + + if (mtu_ok) { + p_cfg->mtu_present = FALSE; + } + if (flush_to_ok) { + p_cfg->flush_to_present = FALSE; + } + if (qos_type_ok) { + p_cfg->qos_present = FALSE; + } + if (fcr_ok) { + p_cfg->fcr_present = FALSE; + } + + return (L2CAP_PEER_CFG_UNACCEPTABLE); + } +} + + +/******************************************************************************* +** +** Function l2cu_process_peer_cfg_rsp +** +** Description This function is called when the peer sends us a "config response" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_peer_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + /* If we wanted QoS and the peer sends us a positive response with QoS, use his values */ + if ( (p_cfg->qos_present) && (p_ccb->our_cfg.qos_present) ) { + p_ccb->our_cfg.qos = p_cfg->qos; + } + + if (p_cfg->fcr_present) { + /* Save the retransmission and monitor timeout values */ + if (p_cfg->fcr.mode == L2CAP_FCR_ERTM_MODE) { + p_ccb->peer_cfg.fcr.rtrans_tout = p_cfg->fcr.rtrans_tout; + p_ccb->peer_cfg.fcr.mon_tout = p_cfg->fcr.mon_tout; + } + + /* Calculate the max number of packets for which we can delay sending an ack */ + if (p_cfg->fcr.tx_win_sz < p_ccb->our_cfg.fcr.tx_win_sz) { + p_ccb->fcrb.max_held_acks = p_cfg->fcr.tx_win_sz / 3; + } else { + p_ccb->fcrb.max_held_acks = p_ccb->our_cfg.fcr.tx_win_sz / 3; + } + + L2CAP_TRACE_DEBUG ("l2cu_process_peer_cfg_rsp(): peer tx_win_sz: %d, our tx_win_sz: %d, max_held_acks: %d", + p_cfg->fcr.tx_win_sz, p_ccb->our_cfg.fcr.tx_win_sz, p_ccb->fcrb.max_held_acks); + } +} + +/******************************************************************************* +** +** Function l2cu_process_our_cfg_req +** +** Description This function is called when we send a "config request" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_our_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_LCB *p_lcb; + UINT16 hci_flush_to; + + /* Save the QOS settings we are using for transmit */ + if (p_cfg->qos_present) { + p_ccb->our_cfg.qos_present = TRUE; + p_ccb->our_cfg.qos = p_cfg->qos; + } + + if (p_cfg->fcr_present) { + /* Override FCR options if attempting streaming or basic */ + if (p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE) { + memset(&p_cfg->fcr, 0, sizeof(tL2CAP_FCR_OPTS)); + } else { + /* On BR/EDR, timer values are zero in config request */ + /* On class 2 AMP, timer value in config request shall be non-0 processing time */ + /* timer value in config response shall be greater than received processing time */ + p_cfg->fcr.mon_tout = p_cfg->fcr.rtrans_tout = 0; + + if (p_cfg->fcr.mode == L2CAP_FCR_STREAM_MODE) { + p_cfg->fcr.max_transmit = p_cfg->fcr.tx_win_sz = 0; + } + } + + /* Set the threshold to send acks (may be updated in the cfg response) */ + p_ccb->fcrb.max_held_acks = p_cfg->fcr.tx_win_sz / 3; + + /* Include FCS option only if peer can handle it */ + if (p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_NO_CRC) { + /* FCS check can be bypassed if peer also desires to bypass */ + if (p_cfg->fcs_present && p_cfg->fcs == L2CAP_CFG_FCS_BYPASS) { + p_ccb->bypass_fcs |= L2CAP_CFG_FCS_OUR; + } + } else { + p_cfg->fcs_present = FALSE; + } + } else { + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + } + + p_ccb->our_cfg.fcr.mode = p_cfg->fcr.mode; + p_ccb->our_cfg.fcr_present = p_cfg->fcr_present; + + /* Check the flush timeout. If it is lower than the current one used */ + /* then we need to adjust the flush timeout sent to the controller */ + if (p_cfg->flush_to_present) { + if ((p_cfg->flush_to == 0) || (p_cfg->flush_to == L2CAP_NO_AUTOMATIC_FLUSH)) { + /* don't send invalid flush timeout */ + /* SPEC: The sender of the Request shall specify its flush timeout value */ + /* if it differs from the default value of 0xFFFF */ + p_cfg->flush_to_present = FALSE; + } else { + p_ccb->our_cfg.flush_to = p_cfg->flush_to; + p_lcb = p_ccb->p_lcb; + + if (p_cfg->flush_to < p_lcb->link_flush_tout) { + p_lcb->link_flush_tout = p_cfg->flush_to; + + /* If the timeout is within range of HCI, set the flush timeout */ + if (p_cfg->flush_to <= ((HCI_MAX_AUTO_FLUSH_TOUT * 5) / 8)) { + /* Convert flush timeout to 0.625 ms units, with round */ + hci_flush_to = ((p_cfg->flush_to * 8) + 3) / 5; + btsnd_hcic_write_auto_flush_tout (p_lcb->handle, hci_flush_to); + } + } + } + } +} + + +/******************************************************************************* +** +** Function l2cu_process_our_cfg_rsp +** +** Description This function is called when we send the peer a "config response" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_our_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + /* If peer wants QoS, we are allowed to change the values in a positive response */ + if ( (p_cfg->qos_present) && (p_ccb->peer_cfg.qos_present) ) { + p_ccb->peer_cfg.qos = p_cfg->qos; + } else { + p_cfg->qos_present = FALSE; + } + + l2c_fcr_adj_our_rsp_options (p_ccb, p_cfg); +} +#endif // (L2CAP_COC_INCLUDED == TRUE) + + +/******************************************************************************* +** +** Function l2cu_device_reset +** +** Description This function is called when reset of the device is +** completed. For all active connection simulate HCI_DISC +** +** Returns void +** +*******************************************************************************/ +void l2cu_device_reset (void) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->handle != HCI_INVALID_HANDLE)) { + l2c_link_hci_disc_comp (p_lcb->handle, (UINT8) - 1); + } + } +#if (BLE_INCLUDED == TRUE) + l2cb.is_ble_connecting = FALSE; +#endif +} + +/******************************************************************************* +** +** Function l2cu_create_conn +** +** Description This function initiates an acl connection via HCI +** +** Returns TRUE if successful, FALSE if gki get buffer fails. +** +*******************************************************************************/ +BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb, tBT_TRANSPORT transport) +{ +#if BTM_SCO_INCLUDED == TRUE + BOOLEAN is_sco_active; +#endif + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb_cur = NULL; + +#if (BLE_INCLUDED == TRUE) + tBT_DEVICE_TYPE dev_type; + tBLE_ADDR_TYPE addr_type = p_lcb->open_addr_type; + if(addr_type == BLE_ADDR_UNKNOWN_TYPE) { + BTM_ReadDevInfo(p_lcb->remote_bd_addr, &dev_type, &addr_type); + } + + if (transport == BT_TRANSPORT_LE) { + if (!controller_get_interface()->supports_ble()) { + return FALSE; + } + if(addr_type > BLE_ADDR_TYPE_MAX) { + addr_type = BLE_ADDR_PUBLIC; + } + p_lcb->ble_addr_type = addr_type; + p_lcb->transport = BT_TRANSPORT_LE; + + return (l2cble_create_conn(p_lcb)); + } +#endif + + /* If there is a connection where we perform as a slave, try to switch roles + for this connection */ + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb_cur = list_node(p_node); + if (p_lcb_cur == p_lcb) { + continue; + } + + if ((p_lcb_cur->in_use) && (p_lcb_cur->link_role == HCI_ROLE_SLAVE)) { + +#if BTM_SCO_INCLUDED == TRUE + /* The LMP_switch_req shall be sent only if the ACL logical transport + is in active mode, when encryption is disabled, and all synchronous + logical transports on the same physical link are disabled." */ + + /* Check if there is any SCO Active on this BD Address */ + is_sco_active = btm_is_sco_active_by_bdaddr(p_lcb_cur->remote_bd_addr); + + L2CAP_TRACE_API ("l2cu_create_conn - btm_is_sco_active_by_bdaddr() is_sco_active = %s", \ + (is_sco_active == TRUE) ? "TRUE" : "FALSE"); + + if (is_sco_active == TRUE) { + continue; /* No Master Slave switch not allowed when SCO Active */ + } +#endif + /*4_1_TODO check if btm_cb.devcb.local_features to be used instead */ + if (HCI_SWITCH_SUPPORTED(BTM_ReadLocalFeatures())) { + /* mark this lcb waiting for switch to be completed and + start switch on the other one */ + p_lcb->link_state = LST_CONNECTING_WAIT_SWITCH; + p_lcb->link_role = HCI_ROLE_MASTER; + + if (BTM_SwitchRole (p_lcb_cur->remote_bd_addr, HCI_ROLE_MASTER, NULL) == BTM_CMD_STARTED) { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_ROLE_SWITCH_TOUT); + return (TRUE); + } + } + } + } + + p_lcb->link_state = LST_CONNECTING; + + return (l2cu_create_conn_after_switch (p_lcb)); +} + +/******************************************************************************* +** +** Function l2cu_get_num_hi_priority +** +** Description Gets the number of high priority channels. +** +** Returns +** +*******************************************************************************/ +UINT8 l2cu_get_num_hi_priority (void) +{ + UINT8 no_hi = 0; + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH)) { + no_hi++; + } + } + return no_hi; +} + + +/******************************************************************************* +** +** Function l2cu_create_conn_after_switch +** +** Description This function initiates an acl connection via HCI +** If switch required to create connection it is already done. +** +** Returns TRUE if successful, FALSE if osi get buffer fails. +** +*******************************************************************************/ + +BOOLEAN l2cu_create_conn_after_switch (tL2C_LCB *p_lcb) +{ + UINT8 allow_switch = HCI_CR_CONN_ALLOW_SWITCH; + tBTM_INQ_INFO *p_inq_info; + UINT8 page_scan_rep_mode; + UINT8 page_scan_mode; + UINT16 clock_offset; + UINT8 *p_features; + UINT16 num_acl = BTM_GetNumAclLinks(); + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_lcb->remote_bd_addr); + UINT8 no_hi_prio_chs = l2cu_get_num_hi_priority(); + + p_features = BTM_ReadLocalFeatures(); + + L2CAP_TRACE_DEBUG ("l2cu_create_conn_after_switch :%d num_acl:%d no_hi: %d is_bonding:%d", + l2cb.disallow_switch, num_acl, no_hi_prio_chs, p_lcb->is_bonding); + /* FW team says that we can participant in 4 piconets + * typically 3 piconet + 1 for scanning. + * We can enhance the code to count the number of piconets later. */ + if ( ((!l2cb.disallow_switch && (num_acl < 3)) || (p_lcb->is_bonding && (no_hi_prio_chs == 0))) + && HCI_SWITCH_SUPPORTED(p_features)) { + allow_switch = HCI_CR_CONN_ALLOW_SWITCH; + } else { + allow_switch = HCI_CR_CONN_NOT_ALLOW_SWITCH; + } + + p_lcb->link_state = LST_CONNECTING; + + /* Check with the BT manager if details about remote device are known */ + if ((p_inq_info = BTM_InqDbRead(p_lcb->remote_bd_addr)) != NULL) { + page_scan_rep_mode = p_inq_info->results.page_scan_rep_mode; + page_scan_mode = p_inq_info->results.page_scan_mode; + clock_offset = (UINT16)(p_inq_info->results.clock_offset); + } else { + /* No info known. Use default settings */ + page_scan_rep_mode = HCI_PAGE_SCAN_REP_MODE_R2; + page_scan_mode = HCI_MANDATARY_PAGE_SCAN_MODE; + + clock_offset = (p_dev_rec) ? p_dev_rec->clock_offset : 0; + } + + if (!btsnd_hcic_create_conn (p_lcb->remote_bd_addr, + ( HCI_PKT_TYPES_MASK_DM1 | HCI_PKT_TYPES_MASK_DH1 + | HCI_PKT_TYPES_MASK_DM3 | HCI_PKT_TYPES_MASK_DH3 + | HCI_PKT_TYPES_MASK_DM5 | HCI_PKT_TYPES_MASK_DH5 ), + page_scan_rep_mode, + page_scan_mode, + clock_offset, + allow_switch)) + + { + L2CAP_TRACE_ERROR ("L2CAP - no buffer for l2cu_create_conn"); + l2cu_release_lcb (p_lcb); + return (FALSE); + } + + btm_acl_update_busy_level (BTM_BLI_PAGE_EVT); + + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, + L2CAP_LINK_CONNECT_TOUT); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_state +** +** Description Look through all active LCBs for a match based on the +** LCB state. +** +** Returns pointer to first matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_state (tL2C_LINK_STATE state) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->link_state == state)) { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + + +/******************************************************************************* +** +** Function l2cu_lcb_disconnecting +** +** Description On each active lcb, check if the lcb is in disconnecting +** state, or if there are no ccb's on the lcb (implying + idle timeout is running), or if last ccb on the link + is in disconnecting state. +** +** Returns TRUE if any of above conditions met, FALSE otherwise +** +*******************************************************************************/ +BOOLEAN l2cu_lcb_disconnecting (void) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + BOOLEAN status = FALSE; + list_node_t *p_node = NULL; + + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use) { + /* no ccbs on lcb, or lcb is in disconnecting state */ + if ((!p_lcb->ccb_queue.p_first_ccb) || (p_lcb->link_state == LST_DISCONNECTING)) { + status = TRUE; + break; + } + /* only one ccb left on lcb */ + else if (p_lcb->ccb_queue.p_first_ccb == p_lcb->ccb_queue.p_last_ccb) { + p_ccb = p_lcb->ccb_queue.p_first_ccb; + + if ((p_ccb->in_use) && + ((p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) || + (p_ccb->chnl_state == CST_W4_L2CA_DISCONNECT_RSP))) { + status = TRUE; + break; + } + } + } + } + return status; +} + + +/******************************************************************************* +** +** Function l2cu_set_acl_priority +** +** Description Sets the transmission priority for a channel. +** (For initial implementation only two values are valid. +** L2CAP_PRIORITY_NORMAL and L2CAP_PRIORITY_HIGH). +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ + +BOOLEAN l2cu_set_acl_priority (BD_ADDR bd_addr, UINT8 priority, BOOLEAN reset_after_rs) +{ + tL2C_LCB *p_lcb; + UINT8 *pp; + UINT8 command[HCI_BRCM_ACL_PRIORITY_PARAM_SIZE]; + UINT8 vs_param; + + //APPL_TRACE_EVENT("SET ACL PRIORITY %d", priority); + + /* Find the link control block for the acl channel */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_BR_EDR)) == NULL) { + L2CAP_TRACE_WARNING ("L2CAP - no LCB for L2CA_SetAclPriority"); + return (FALSE); + } + + if (BTM_IS_BRCM_CONTROLLER()) { + /* Called from above L2CAP through API; send VSC if changed */ + if ((!reset_after_rs && (priority != p_lcb->acl_priority)) || + /* Called because of a master/slave role switch; if high resend VSC */ + ( reset_after_rs && p_lcb->acl_priority == L2CAP_PRIORITY_HIGH)) { + pp = command; + + vs_param = (priority == L2CAP_PRIORITY_HIGH) ? HCI_BRCM_ACL_PRIORITY_HIGH : HCI_BRCM_ACL_PRIORITY_LOW; + + UINT16_TO_STREAM (pp, p_lcb->handle); + UINT8_TO_STREAM (pp, vs_param); + + BTM_VendorSpecificCommand (HCI_BRCM_SET_ACL_PRIORITY, HCI_BRCM_ACL_PRIORITY_PARAM_SIZE, command, NULL); + + /* Adjust lmp buffer allocation for this channel if priority changed */ + if (p_lcb->acl_priority != priority) { + p_lcb->acl_priority = priority; + l2c_link_adjust_allocation(); + } + } + } + return (TRUE); +} + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) +/****************************************************************************** +** +** Function l2cu_set_non_flushable_pbf +** +** Description set L2CAP_PKT_START_NON_FLUSHABLE if controller supoorts +** +** Returns void +** +*******************************************************************************/ +void l2cu_set_non_flushable_pbf (BOOLEAN is_supported) +{ + if (is_supported) { + l2cb.non_flushable_pbf = (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT); + } else { + l2cb.non_flushable_pbf = (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT); + } +} +#endif + +/******************************************************************************* +** +** Function l2cu_resubmit_pending_sec_req +** +** Description This function is called when required security procedures +** are completed and any pending requests can be re-submitted. +** +** Returns void +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +void l2cu_resubmit_pending_sec_req (BD_ADDR p_bda) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_CCB *p_next_ccb; + L2CAP_TRACE_DEBUG ("l2cu_resubmit_pending_sec_req p_bda: %p", p_bda); + list_node_t *p_node = NULL; + + /* If we are called with a BDA, only resubmit for that BDA */ + if (p_bda) { + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda, BT_TRANSPORT_BR_EDR); + /* If we don't have one, this is an error */ + if (p_lcb) { + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) { + p_next_ccb = p_ccb->p_next_ccb; + l2c_csm_execute (p_ccb, L2CEVT_SEC_RE_SEND_CMD, NULL); + } + } else { + L2CAP_TRACE_WARNING ("l2cu_resubmit_pending_sec_req - unknown BD_ADDR"); + } + } else { + /* No BDA pasesed in, so check all links */ + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if (p_lcb->in_use) { + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) { + p_next_ccb = p_ccb->p_next_ccb; + l2c_csm_execute (p_ccb, L2CEVT_SEC_RE_SEND_CMD, NULL); + } + } + } + } +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + +#if L2CAP_CONFORMANCE_TESTING == TRUE +/******************************************************************************* +** +** Function l2cu_set_info_rsp_mask +** +** Description This function allows the script wrapper to change the +** info resp mask for conformance testing. +** +** Returns pointer to CCB, or NULL if none +** +*******************************************************************************/ +void l2cu_set_info_rsp_mask (UINT32 mask) +{ + l2cb.test_info_resp = mask; +} +#endif /* L2CAP_CONFORMANCE_TESTING */ + +/******************************************************************************* +** +** Function l2cu_adjust_out_mps +** +** Description Sets our MPS based on current controller capabilities +** +** Returns void +** +*******************************************************************************/ +void l2cu_adjust_out_mps (tL2C_CCB *p_ccb) +{ + UINT16 packet_size; + + /* on the tx side MTU is selected based on packet size of the controller */ + packet_size = btm_get_max_packet_size (p_ccb->p_lcb->remote_bd_addr); + + if (packet_size <= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD + L2CAP_SDU_LEN_OVERHEAD + L2CAP_FCS_LEN)) { + /* something is very wrong */ + L2CAP_TRACE_DEBUG ("l2cu_adjust_out_mps bad packet size: %u will use MPS: %u", packet_size, p_ccb->peer_cfg.fcr.mps); + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps; + } else { + packet_size -= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD + L2CAP_SDU_LEN_OVERHEAD + L2CAP_FCS_LEN); + + /* 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 L2CAP packet is + 5 * 339 = 1695. Minus 4 bytes L2CAP header 1691. + + 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. */ + if (p_ccb->peer_cfg.fcr.mps >= packet_size) { + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps / packet_size * packet_size; + } else { + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps; + } + + L2CAP_TRACE_DEBUG ("l2cu_adjust_out_mps use %d Based on peer_cfg.fcr.mps: %u packet_size: %u", + p_ccb->tx_mps, p_ccb->peer_cfg.fcr.mps, packet_size); + } +} + + +/******************************************************************************* +** +** Function l2cu_initialize_fixed_ccb +** +** Description Initialize a fixed channel's CCB +** +** Returns TRUE or FALSE +** +*******************************************************************************/ +BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr) +{ +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2C_CCB *p_ccb; + /* If we already have a CCB, then simply return */ + if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] != NULL) { + return (TRUE); + } + + if ((p_ccb = l2cu_allocate_ccb (NULL, 0)) == NULL) { + return (FALSE); + } + + btu_stop_timer(&p_lcb->timer_entry); + + /* Set CID for the connection */ + p_ccb->local_cid = fixed_cid; + p_ccb->remote_cid = fixed_cid; + + p_ccb->is_flushable = FALSE; + + p_ccb->timer_entry.param = (TIMER_PARAM_TYPE)p_ccb; + + + if (p_fcr) { + /* Set the FCR parameters. For now, we will use default pools */ + p_ccb->our_cfg.fcr = p_ccb->peer_cfg.fcr = *p_fcr; + + p_ccb->ertm_info.fcr_rx_buf_size = L2CAP_FCR_RX_BUF_SIZE; + p_ccb->ertm_info.fcr_tx_buf_size = L2CAP_FCR_TX_BUF_SIZE; + p_ccb->ertm_info.user_rx_buf_size = L2CAP_USER_RX_BUF_SIZE; + p_ccb->ertm_info.user_tx_buf_size = L2CAP_USER_TX_BUF_SIZE; + + p_ccb->fcrb.max_held_acks = p_fcr->tx_win_sz / 3; + } + + /* Link ccb to lcb and lcb to ccb */ + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = p_ccb; + p_ccb->p_lcb = p_lcb; + + /* There is no configuration, so if the link is up, the channel is up */ + if (p_lcb->link_state == LST_CONNECTED) { + p_ccb->chnl_state = CST_OPEN; + } + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].default_idle_tout; +#endif + return (TRUE); +} + +/******************************************************************************* +** +** Function l2cu_no_dynamic_ccbs +** +** Description Handles the case when there are no more dynamic CCBs. If there +** are any fixed CCBs, start the longest of the fixed CCB timeouts, +** otherwise start the default link idle timeout or disconnect. +** +** Returns void +** +*******************************************************************************/ +void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb) +{ +#if (SMP_INCLUDED == TRUE) + tBTM_STATUS rc; +#endif ///SMP_INCLUDED == TRUE + UINT16 timeout = p_lcb->idle_timeout; + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { + if ( (p_lcb->p_fixed_ccbs[xx] != NULL) && (p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout > timeout) ) { + timeout = p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout; + } + } +#endif + + /* If the link is pairing, do not mess with the timeouts */ + if (p_lcb->is_bonding) { + return; + } + + if (timeout == 0) { + L2CAP_TRACE_DEBUG ("l2cu_no_dynamic_ccbs() IDLE timer 0, disconnecting link"); +#if (SMP_INCLUDED == TRUE) + rc = btm_sec_disconnect (p_lcb->handle, HCI_ERR_PEER_USER); + if (rc == BTM_CMD_STARTED) { + l2cu_process_fixed_disc_cback(p_lcb); + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else if (rc == BTM_SUCCESS) { + l2cu_process_fixed_disc_cback(p_lcb); + /* BTM SEC will make sure that link is release (probably after pairing is done) */ + p_lcb->link_state = LST_DISCONNECTING; + timeout = 0xFFFF; + } else if ( (p_lcb->is_bonding) + && (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER)) ) { + l2cu_process_fixed_disc_cback(p_lcb); + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else { + /* probably no buffer to send disconnect */ + timeout = BT_1SEC_TIMEOUT; + } +#else + if (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER)) { + l2cu_process_fixed_disc_cback(p_lcb); + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } else { + timeout = BT_1SEC_TIMEOUT; + } +#endif ///SMP_INCLUDED == TRUE + + } + + if (timeout != 0xFFFF) { + L2CAP_TRACE_DEBUG ("l2cu_no_dynamic_ccbs() starting IDLE timeout: %d", timeout); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + } else { + btu_stop_timer(&p_lcb->timer_entry); + } +} + +#if (L2CAP_NUM_FIXED_CHNLS > 0) +/******************************************************************************* +** +** Function l2cu_process_fixed_chnl_resp +** +** Description handle a fixed channel response (or lack thereof) +** if the link failed, or a fixed channel response was +** not received, the bitfield is all zeros. +** +*******************************************************************************/ +void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb) +{ + L2CAP_TRACE_DEBUG("%s",__func__); +#if (BLE_INCLUDED == TRUE) + if (p_lcb->transport == BT_TRANSPORT_BR_EDR) { + /* ignore all not assigned BR/EDR channels */ + p_lcb->peer_chnl_mask[0] &= (L2CAP_FIXED_CHNL_SIG_BIT | \ + L2CAP_FIXED_CHNL_CNCTLESS_BIT | \ + L2CAP_FIXED_CHNL_SMP_BR_BIT); + } else { + p_lcb->peer_chnl_mask[0] = l2cb.l2c_ble_fixed_chnls_mask; + } +#endif + + /* Tell all registered fixed channels about the connection */ + for (int xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { +#if BLE_INCLUDED == TRUE + /* skip sending LE fix channel callbacks on BR/EDR links */ + if (p_lcb->transport == BT_TRANSPORT_BR_EDR && + xx + L2CAP_FIRST_FIXED_CHNL >= L2CAP_ATT_CID && + xx + L2CAP_FIRST_FIXED_CHNL <= L2CAP_SMP_CID) { + continue; + } +#endif + if (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) { + if (p_lcb->peer_chnl_mask[(xx + L2CAP_FIRST_FIXED_CHNL) / 8] + & (1 << ((xx + L2CAP_FIRST_FIXED_CHNL) % 8))) { + if (p_lcb->p_fixed_ccbs[xx]) { + p_lcb->p_fixed_ccbs[xx]->chnl_state = CST_OPEN; + } +#if BLE_INCLUDED == TRUE + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, TRUE, 0, p_lcb->transport); +#else + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, TRUE, 0, BT_TRANSPORT_BR_EDR); +#endif + } else { +#if BLE_INCLUDED == TRUE + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, p_lcb->transport); +#else + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, BT_TRANSPORT_BR_EDR); +#endif + + if (p_lcb->p_fixed_ccbs[xx]) { + l2cu_release_ccb (p_lcb->p_fixed_ccbs[xx]); + p_lcb->p_fixed_ccbs[xx] = NULL; + } + } + } + } +} +#endif + + +/******************************************************************************* +** +** Function l2cu_process_fixed_disc_cback +** +** Description send l2cap fixed channel disconnection callback to application +** +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_fixed_disc_cback (tL2C_LCB *p_lcb) +{ +#if (L2CAP_NUM_FIXED_CHNLS > 0) + + /* Select peer channels mask to use depending on transport */ + UINT8 peer_channel_mask = p_lcb->peer_chnl_mask[0]; + + // For LE, reset the stored peer channel mask + if (p_lcb->transport == BT_TRANSPORT_LE) { + p_lcb->peer_chnl_mask[0] = 0; + } + + for (int xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { + if (p_lcb->p_fixed_ccbs[xx]) { + if (p_lcb->p_fixed_ccbs[xx] != p_lcb->p_pending_ccb) { + tL2C_CCB *p_l2c_chnl_ctrl_block; + p_l2c_chnl_ctrl_block = p_lcb->p_fixed_ccbs[xx]; + p_lcb->p_fixed_ccbs[xx] = NULL; + l2cu_release_ccb(p_l2c_chnl_ctrl_block); +#if BLE_INCLUDED == TRUE + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, p_lcb->transport); +#else + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, BT_TRANSPORT_BR_EDR); +#endif + } + } else if ( (peer_channel_mask & (1 << (xx + L2CAP_FIRST_FIXED_CHNL))) + && (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) ) { +#if BLE_INCLUDED == TRUE + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, p_lcb->transport); +#else + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL, + p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason, BT_TRANSPORT_BR_EDR); +#endif + } + } +#endif +} + +#if (BLE_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cu_send_peer_ble_par_req +** +** Description Build and send a BLE parameter update request message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, + UINT16 latency, UINT16 timeout) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* Create an identifier for this packet */ + p_lcb->id++; + l2cu_adj_id (p_lcb, L2CAP_ADJ_ID); + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_UPD_REQ_LEN, + L2CAP_CMD_BLE_UPDATE_REQ, p_lcb->id)) == NULL ) { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_par_req - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, min_int); + UINT16_TO_STREAM (p, max_int); + UINT16_TO_STREAM (p, latency); + UINT16_TO_STREAM (p, timeout); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_par_rsp +** +** Description Build and send a BLE parameter update response message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_UPD_RSP_LEN, + L2CAP_CMD_BLE_UPDATE_RSP, rem_id)) == NULL ) { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_par_rsp - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, reason); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_credit_based_conn_req +** +** Description Build and send a BLE packet to establish LE connection oriented +** L2CAP channel. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_credit_based_conn_req (tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + UINT8 *p; + tL2C_LCB *p_lcb = NULL; + UINT16 mtu; + UINT16 mps; + UINT16 initial_credit; + + if (!p_ccb) { + return; + } + p_lcb = p_ccb->p_lcb; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ_LEN, + L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ, p_lcb->id)) == NULL ) + { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_credit_based_conn_req - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + mtu = p_ccb->local_conn_cfg.mtu; + mps = p_ccb->local_conn_cfg.mps; + initial_credit = p_ccb->local_conn_cfg.credits; + + L2CAP_TRACE_DEBUG ("l2cu_send_peer_ble_credit_based_conn_req PSM:0x%04x local_cid:%d\ + mtu:%d mps:%d initial_credit:%d", p_ccb->p_rcb->real_psm,\ + p_ccb->local_cid, mtu, mps, initial_credit); + + UINT16_TO_STREAM (p, p_ccb->p_rcb->real_psm); + UINT16_TO_STREAM (p, p_ccb->local_cid); + UINT16_TO_STREAM (p, mtu); + UINT16_TO_STREAM (p, mps); + UINT16_TO_STREAM (p, initial_credit); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_reject_ble_connection +** +** Description Build and send an L2CAP "Credit based connection res" message +** to the peer. This function is called for non-success cases. +** +** Returns void +** +*******************************************************************************/ +void l2cu_reject_ble_connection (tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf = l2cu_build_header(p_lcb, L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES_LEN, + L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES, rem_id)) == NULL ) + { + L2CAP_TRACE_WARNING ("l2cu_reject_ble_connection - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, 0); /* Local CID of 0 */ + UINT16_TO_STREAM (p, 0); /* MTU */ + UINT16_TO_STREAM (p, 0); /* MPS */ + UINT16_TO_STREAM (p, 0); /* initial credit */ + UINT16_TO_STREAM (p, result); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_credit_based_conn_res +** +** Description Build and send an L2CAP "Credit based connection res" message +** to the peer. This function is called in case of success. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_credit_based_conn_res (tL2C_CCB *p_ccb, UINT16 result) +{ + BT_HDR *p_buf; + UINT8 *p; + + L2CAP_TRACE_DEBUG ("l2cu_send_peer_ble_credit_based_conn_res"); + if ((p_buf = l2cu_build_header(p_ccb->p_lcb, L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES_LEN, + L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES, p_ccb->remote_id)) == NULL ) + { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_credit_based_conn_res - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->local_cid); /* Local CID */ + UINT16_TO_STREAM (p, p_ccb->local_conn_cfg.mtu); /* MTU */ + UINT16_TO_STREAM (p, p_ccb->local_conn_cfg.mps); /* MPS */ + UINT16_TO_STREAM (p, p_ccb->local_conn_cfg.credits); /* initial credit */ + UINT16_TO_STREAM (p, result); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_flow_control_credit +** +** Description Build and send a BLE packet to give credits to peer device +** for LE connection oriented L2CAP channel. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_flow_control_credit(tL2C_CCB *p_ccb, UINT16 credit_value) +{ + BT_HDR *p_buf; + UINT8 *p; + tL2C_LCB *p_lcb = NULL; + + if (!p_ccb) { + return; + } + p_lcb = p_ccb->p_lcb; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_FLOW_CTRL_CREDIT_LEN, + L2CAP_CMD_BLE_FLOW_CTRL_CREDIT, p_lcb->id)) == NULL ) + { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_credit_based_conn_req - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->local_cid); + UINT16_TO_STREAM (p, credit_value); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_credit_based_conn_req +** +** Description Build and send a BLE packet to disconnect LE connection oriented +** L2CAP channel. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_credit_based_disconn_req(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + UINT8 *p; + tL2C_LCB *p_lcb = NULL; + L2CAP_TRACE_DEBUG ("%s",__func__); + + if (!p_ccb) { + return; + } + p_lcb = p_ccb->p_lcb; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_DISC_REQ_LEN, + L2CAP_CMD_DISC_REQ, p_lcb->id)) == NULL ) + { + L2CAP_TRACE_WARNING ("l2cu_send_peer_ble_credit_based_disconn_req - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p,p_ccb->local_cid); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +#endif /* BLE_INCLUDED == TRUE */ + +/******************************************************************************* +** Functions used by both Full and Light Stack +********************************************************************************/ + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_handle +** +** Description Look through all active LCBs for a match based on the +** HCI handle. +** +** Returns pointer to matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_handle (UINT16 handle) +{ + list_node_t *p_node = NULL; + tL2C_LCB *p_lcb = NULL; + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + if ((p_lcb->in_use) && (p_lcb->handle == handle)) { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_find_ccb_by_cid +** +** Description Look through all active CCBs on a link for a match based +** on the local CID. If passed the link pointer is NULL, all +** active links are searched. +** +** Returns pointer to matched CCB, or NULL if no match +** +*******************************************************************************/ +bool l2cu_find_ccb_in_list(void *p_ccb_node, void *p_local_cid) +{ + tL2C_CCB *p_ccb = (tL2C_CCB *)p_ccb_node; + uint8_t local_cid = *((uint8_t *)p_local_cid); + + if (p_ccb->local_cid == local_cid && p_ccb->in_use) { + return FALSE; + } + return TRUE; +} + +tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid) +{ + tL2C_CCB *p_ccb = NULL; +#if (L2CAP_UCD_INCLUDED == FALSE) + if (local_cid < L2CAP_BASE_APPL_CID) { + return NULL; + } +#endif //(L2CAP_UCD_INCLUDED == FALSE) + list_node_t *p_node = NULL; + + p_node = (list_foreach(l2cb.p_ccb_pool, l2cu_find_ccb_in_list, &local_cid)); + if (p_node) { + p_ccb = (tL2C_CCB *)list_node(p_node); + + if (p_lcb && p_lcb != p_ccb->p_lcb) { + p_ccb = NULL; + } + } + + return (p_ccb); +} + +tL2C_CCB *l2cu_find_free_ccb (void) +{ + tL2C_CCB *p_ccb = NULL; + + list_node_t *p_node = NULL; + + for (p_node = list_begin(l2cb.p_ccb_pool); p_node; p_node = list_next(p_node)) + { + p_ccb = list_node(p_node); + if(p_ccb && !p_ccb->in_use ) { + return p_ccb; + } + } + + return (NULL); +} + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE && CLASSIC_BT_INCLUDED == TRUE) + +/****************************************************************************** +** +** Function l2cu_get_next_channel_in_rr +** +** Description get the next channel to send on a link. It also adjusts the +** CCB queue to do a basic priority and round-robin scheduling. +** +** Returns pointer to CCB or NULL +** +*******************************************************************************/ +static tL2C_CCB *l2cu_get_next_channel_in_rr(tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_serve_ccb = NULL; + tL2C_CCB *p_ccb; + + int i, j; + + /* scan all of priority until finding a channel to serve */ + for ( i = 0; (i < L2CAP_NUM_CHNL_PRIORITY) && (!p_serve_ccb); i++ ) { + /* scan all channel within serving priority group until finding a channel to serve */ + for ( j = 0; (j < p_lcb->rr_serv[p_lcb->rr_pri].num_ccb) && (!p_serve_ccb); j++) { + /* scaning from next serving channel */ + p_ccb = p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb; + + if (!p_ccb) { + L2CAP_TRACE_ERROR("p_serve_ccb is NULL, rr_pri=%d", p_lcb->rr_pri); + return NULL; + } + + L2CAP_TRACE_DEBUG("RR scan pri=%d, lcid=0x%04x, q_cout=%d", + p_ccb->ccb_priority, p_ccb->local_cid, + fixed_queue_length(p_ccb->xmit_hold_q)); + + /* store the next serving channel */ + /* this channel is the last channel of its priority group */ + if (( p_ccb->p_next_ccb == NULL ) + || ( p_ccb->p_next_ccb->ccb_priority != p_ccb->ccb_priority )) { + /* next serving channel is set to the first channel in the group */ + p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb = p_lcb->rr_serv[p_lcb->rr_pri].p_first_ccb; + } else { + /* next serving channel is set to the next channel in the group */ + p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb = p_ccb->p_next_ccb; + } + + if (p_ccb->chnl_state != CST_OPEN) { + continue; + } + + /* eL2CAP option in use */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) { + continue; + } + + if (fixed_queue_is_empty(p_ccb->fcrb.retrans_q)) { + if (fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + continue; + } + + +#if (CLASSIC_BT_INCLUDED == TRUE) + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) { + continue; + } +#endif ///CLASSIC_BT_INCLUDED == TRUE + } + } else { + if (fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + continue; + } + } + + /* found a channel to serve */ + p_serve_ccb = p_ccb; + /* decrease quota of its priority group */ + p_lcb->rr_serv[p_lcb->rr_pri].quota--; + } + + /* if there is no more quota of the priority group or no channel to have data to send */ + if ((p_lcb->rr_serv[p_lcb->rr_pri].quota == 0) || (!p_serve_ccb)) { + /* serve next priority group */ + p_lcb->rr_pri = (p_lcb->rr_pri + 1) % L2CAP_NUM_CHNL_PRIORITY; + /* initialize its quota */ + p_lcb->rr_serv[p_lcb->rr_pri].quota = L2CAP_GET_PRIORITY_QUOTA(p_lcb->rr_pri); + } + } + + if (p_serve_ccb) { + L2CAP_TRACE_DEBUG("RR service pri=%d, quota=%d, lcid=0x%04x", + p_serve_ccb->ccb_priority, + p_lcb->rr_serv[p_serve_ccb->ccb_priority].quota, + p_serve_ccb->local_cid ); + } + + return p_serve_ccb; +} + +#else /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + +/****************************************************************************** +** +** Function l2cu_get_next_channel +** +** Description get the next channel to send on a link bassed on priority +** scheduling. +** +** Returns pointer to CCB or NULL +** +*******************************************************************************/ +#if (CLASSIC_BT_INCLUDED == TRUE) +static tL2C_CCB *l2cu_get_next_channel(tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + + /* Get the first CCB with data to send. + */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if (p_ccb->chnl_state != CST_OPEN) { + continue; + } + + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) { + continue; + } + + if (!fixed_queue_is_empty(p_ccb->fcrb.retrans_q)) + return p_ccb; + } + + if (fixed_queue_is_empty(p_ccb->xmit_hold_q)) + continue; + } + + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) { + continue; + } + + /* If here, we found someone */ + return p_ccb; + } + + return NULL; +} +#endif ///CLASSIC_BT_INCLUDED == TRUE + +#endif /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + +/****************************************************************************** +** +** Function l2cu_get_next_buffer_to_send +** +** Description get the next buffer to send on a link. It also adjusts the +** CCB queue to do a basic priority and round-robin scheduling. +** +** Returns pointer to buffer or NULL +** +*******************************************************************************/ +BT_HDR *l2cu_get_next_buffer_to_send (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + BT_HDR *p_buf = NULL; + + /* Highest priority are fixed channels */ +#if (L2CAP_NUM_FIXED_CHNLS > 0) + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { + if ((p_ccb = p_lcb->p_fixed_ccbs[xx]) == NULL) { + continue; + } + + /* eL2CAP option in use */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { +#if (CLASSIC_BT_INCLUDED == TRUE) + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) { + continue; + } + + /* No more checks needed if sending from the reatransmit queue */ + if (fixed_queue_is_empty(p_ccb->fcrb.retrans_q)) + { + if (fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + continue; + } + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) { + continue; + } + } + if ((p_buf = l2c_fcr_get_next_xmit_sdu_seg(p_ccb, 0)) != NULL) { + l2cu_check_channel_congestion (p_ccb); + l2cu_set_acl_hci_header (p_buf, p_ccb); + return (p_buf); + } +#else + continue; +#endif ///CLASSIC_BT_INCLUDED == TRUE + + } else { + if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + p_buf = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0); + if (NULL == p_buf) { + L2CAP_TRACE_ERROR("l2cu_get_buffer_to_send: No data to be sent"); + return (NULL); + } + l2cu_check_channel_congestion (p_ccb); + l2cu_set_acl_hci_header (p_buf, p_ccb); + /* send tx complete */ + if (l2cb.fixed_reg[xx].pL2CA_FixedTxComplete_Cb) { + (*l2cb.fixed_reg[xx].pL2CA_FixedTxComplete_Cb)(p_ccb->local_cid, 1); + } + return (p_buf); + } + } + } +#endif +#if (CLASSIC_BT_INCLUDED == TRUE) +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* get next serving channel in round-robin */ + p_ccb = l2cu_get_next_channel_in_rr( p_lcb ); +#else + p_ccb = l2cu_get_next_channel( p_lcb ); +#endif + + /* Return if no buffer */ + if (p_ccb == NULL) { + return (NULL); + } + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { + + if ((p_buf = l2c_fcr_get_next_xmit_sdu_seg(p_ccb, 0)) == NULL) { + return (NULL); + } + + } else { + p_buf = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0); + if (NULL == p_buf) { + L2CAP_TRACE_ERROR("l2cu_get_buffer_to_send() #2: No data to be sent"); + return (NULL); + } + } + + if ( p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_TxComplete_Cb && (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) ) { + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, 1); + } + + + l2cu_check_channel_congestion (p_ccb); + + l2cu_set_acl_hci_header (p_buf, p_ccb); +#endif ///CLASSIC_BT_INCLUDED == TRUE + + return (p_buf); +} + +/****************************************************************************** +** +** Function l2cu_set_acl_hci_header +** +** Description Set HCI handle for ACL packet +** +** Returns None +** +*******************************************************************************/ +void l2cu_set_acl_hci_header (BT_HDR *p_buf, tL2C_CCB *p_ccb) +{ + UINT8 *p; + + /* Set the pointer to the beginning of the data minus 4 bytes for the packet header */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset - HCI_DATA_PREAMBLE_SIZE; + +#if (BLE_INCLUDED == TRUE) + if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT)); + + uint16_t acl_data_size = controller_get_interface()->get_acl_data_size_ble(); + /* The HCI transport will segment the buffers. */ + if (p_buf->len > acl_data_size) { + UINT16_TO_STREAM (p, acl_data_size); + } else { + UINT16_TO_STREAM (p, p_buf->len); + } + } /* (BLE_INCLUDED == TRUE) */ + else +#endif + { +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + if ( (((p_buf->layer_specific & L2CAP_FLUSHABLE_MASK) == L2CAP_FLUSHABLE_CH_BASED) && (p_ccb->is_flushable)) + || ((p_buf->layer_specific & L2CAP_FLUSHABLE_MASK) == L2CAP_FLUSHABLE_PKT) ) { + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT)); + } else { + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | l2cb.non_flushable_pbf); + } +#else + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT)); +#endif + + uint16_t acl_data_size = controller_get_interface()->get_acl_data_size_classic(); + /* The HCI transport will segment the buffers. */ + if (p_buf->len > acl_data_size) { + UINT16_TO_STREAM (p, acl_data_size); + } else { + UINT16_TO_STREAM (p, p_buf->len); + } + } + p_buf->offset -= HCI_DATA_PREAMBLE_SIZE; + p_buf->len += HCI_DATA_PREAMBLE_SIZE; +} + +/****************************************************************************** +** +** Function l2cu_check_channel_congestion +** +** Description check if any change in congestion status +** +** Returns None +** +*******************************************************************************/ +void l2cu_check_channel_congestion (tL2C_CCB *p_ccb) +{ + size_t q_count = fixed_queue_length(p_ccb->xmit_hold_q); +#if (CLASSIC_BT_INCLUDED == TRUE) + size_t q_waiting_ack_count = fixed_queue_length(p_ccb->fcrb.waiting_for_ack_q); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) { + q_count += fixed_queue_length(p_ccb->p_lcb->ucd_out_sec_pending_q); + } +#endif + /* If the CCB queue limit is subject to a quota, check for congestion */ + /* if this channel has outgoing traffic */ + if (p_ccb->buff_quota != 0) { + /* If this channel was congested */ + if ( p_ccb->cong_sent ) { + /* If the channel is not congested now, tell the app */ + if (q_count <= (p_ccb->buff_quota / 2) +#if (CLASSIC_BT_INCLUDED == TRUE) + && (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE || q_waiting_ack_count < p_ccb->our_cfg.fcr.tx_win_sz) +#endif + ) { + p_ccb->cong_sent = FALSE; + if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) { + L2CAP_TRACE_DEBUG ("L2CAP - Calling CongestionStatus_Cb (FALSE), CID: 0x%04x xmit_hold_q.count: %u buff_quota: %u", + p_ccb->local_cid, q_count, p_ccb->buff_quota); + + /* Prevent recursive calling */ + l2cb.is_cong_cback_context = TRUE; + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, FALSE); + l2cb.is_cong_cback_context = FALSE; + } +#if (L2CAP_UCD_INCLUDED == TRUE) + else if ( p_ccb->p_rcb && p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) { + if ( p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) { + L2CAP_TRACE_DEBUG ("L2CAP - Calling UCD CongestionStatus_Cb (FALSE), SecPendingQ:%u,XmitQ:%u,Quota:%u", + fixed_queue_length(p_ccb->p_lcb->ucd_out_sec_pending_q), + fixed_queue_length(p_ccb->xmit_hold_q), + p_ccb->buff_quota); + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb( p_ccb->p_lcb->remote_bd_addr, FALSE ); + } + } +#endif +#if (L2CAP_NUM_FIXED_CHNLS > 0) + else { + UINT8 xx; + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx ++) { + if (p_ccb->p_lcb->p_fixed_ccbs[xx] == p_ccb) { + if (l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb != NULL) { + (* l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb)(p_ccb->p_lcb->remote_bd_addr, FALSE); + } + break; + } + } + } +#endif + } + } else { + tL2C_LCB *p_lcb = p_ccb->p_lcb; + /* If this channel was not congested but it is congested now, tell the app */ + if (q_count > p_ccb->buff_quota || (p_lcb && (p_lcb->link_xmit_data_q) && (list_length(p_lcb->link_xmit_data_q) + q_count) > p_ccb->buff_quota) +#if (CLASSIC_BT_INCLUDED == TRUE) + || (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE && q_waiting_ack_count >= p_ccb->our_cfg.fcr.tx_win_sz) +#endif + ) { + p_ccb->cong_sent = TRUE; + if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) { + L2CAP_TRACE_DEBUG ("L2CAP - Calling CongestionStatus_Cb (TRUE),CID:0x%04x,XmitQ:%u,Quota:%u", + p_ccb->local_cid, q_count, p_ccb->buff_quota); + + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, TRUE); + } +#if (L2CAP_UCD_INCLUDED == TRUE) + else if ( p_ccb->p_rcb && p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) { + if ( p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) { + L2CAP_TRACE_DEBUG ("L2CAP - Calling UCD CongestionStatus_Cb (TRUE), SecPendingQ:%u,XmitQ:%u,Quota:%u", + fixed_queue_length(p_ccb->p_lcb->ucd_out_sec_pending_q), + fixed_queue_length(p_ccb->xmit_hold_q), + p_ccb->buff_quota); + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb( p_ccb->p_lcb->remote_bd_addr, TRUE ); + } + } +#endif +#if (L2CAP_NUM_FIXED_CHNLS > 0) + else { + UINT8 xx; + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx ++) { + if (p_ccb->p_lcb->p_fixed_ccbs[xx] == p_ccb) { + if (l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb != NULL) { + (* l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb)(p_ccb->p_lcb->remote_bd_addr, TRUE); + } + break; + } + } + } +#endif + } + } + } +} diff --git a/lib/bt/host/bluedroid/stack/l2cap/l2cap_client.c b/lib/bt/host/bluedroid/stack/l2cap/l2cap_client.c new file mode 100644 index 00000000..c7314b62 --- /dev/null +++ b/lib/bt/host/bluedroid/stack/l2cap/l2cap_client.c @@ -0,0 +1,462 @@ +/****************************************************************************** + * + * Copyright (C) 2014 Google, Inc. + * + * 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. + * + ******************************************************************************/ +#if (defined(L2CAP_CLIENT_INCLUDED) && L2CAP_CLIENT_INCLUDED == TRUE) +#include <string.h> +#include "common/bt_trace.h" +#include "common/bt_defs.h" +#include "device/bdaddr.h" +#include "osi/allocator.h" +#include "osi/buffer.h" +#include "osi/list.h" +#include "osi/osi.h" +#include "stack/l2cap_client.h" +#include "stack/l2c_api.h" + +struct l2cap_client_t { + l2cap_client_callbacks_t callbacks; + void *context; + + uint16_t local_channel_id; + uint16_t remote_mtu; + bool configured_self; + bool configured_peer; + bool is_congested; + list_t *outbound_fragments; +}; + +static void connect_completed_cb(uint16_t local_channel_id, uint16_t error_code); +static void config_request_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *requested_parameters); +static void config_completed_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *negotiated_parameters); +static void disconnect_request_cb(uint16_t local_channel_id, bool ack_required); +static void disconnect_completed_cb(uint16_t local_channel_id, uint16_t error_code); +static void congestion_cb(uint16_t local_channel_id, bool is_congested); +static void read_ready_cb(uint16_t local_channel_id, BT_HDR *packet); +static void write_completed_cb(uint16_t local_channel_id, uint16_t packets_completed); + +static void fragment_packet(l2cap_client_t *client, buffer_t *packet); +static void dispatch_fragments(l2cap_client_t *client); +static l2cap_client_t *find(uint16_t local_channel_id); + +// From the Bluetooth Core specification. +static const uint16_t L2CAP_MTU_DEFAULT = 672; +static const uint16_t L2CAP_MTU_MINIMUM = 48; + +static const tL2CAP_APPL_INFO l2cap_callbacks = { + .pL2CA_ConnectCfm_Cb = connect_completed_cb, + .pL2CA_ConfigInd_Cb = config_request_cb, + .pL2CA_ConfigCfm_Cb = config_completed_cb, + .pL2CA_DisconnectInd_Cb = disconnect_request_cb, + .pL2CA_DisconnectCfm_Cb = disconnect_completed_cb, + .pL2CA_CongestionStatus_Cb = congestion_cb, + .pL2CA_DataInd_Cb = read_ready_cb, + .pL2CA_TxComplete_Cb = write_completed_cb, +}; + +static list_t *l2cap_clients; // A list of l2cap_client_t. Container does not own objects. + +buffer_t *l2cap_buffer_new(size_t size) +{ + buffer_t *buf = buffer_new(size + L2CAP_MIN_OFFSET); + buffer_t *slice = NULL; + if (buf) { + slice = buffer_new_slice(buf, size); + } + buffer_free(buf); + return slice; +} + +l2cap_client_t *l2cap_client_new(const l2cap_client_callbacks_t *callbacks, void *context) +{ + assert(callbacks != NULL); + assert(callbacks->connected != NULL); + assert(callbacks->disconnected != NULL); + assert(callbacks->read_ready != NULL); + assert(callbacks->write_ready != NULL); + + if (!l2cap_clients) { + l2cap_clients = list_new(NULL); + if (!l2cap_clients) { + L2CAP_TRACE_ERROR("%s unable to allocate space for L2CAP client list.", __func__); + return NULL; + } + } + + l2cap_client_t *ret = (l2cap_client_t *)osi_calloc(sizeof(l2cap_client_t)); + if (!ret) { + L2CAP_TRACE_ERROR("%s unable to allocate L2CAP client.", __func__); + goto error; + } + + ret->callbacks = *callbacks; + ret->context = context; + + ret->remote_mtu = L2CAP_MTU_DEFAULT; + ret->outbound_fragments = list_new(NULL); + if (!ret) { + L2CAP_TRACE_ERROR("%s unable to allocate outbound L2CAP fragment list.", __func__); + goto error; + } + + list_append(l2cap_clients, ret); + + return ret; + +error:; + osi_free(ret); + return NULL; +} + +void l2cap_client_free(l2cap_client_t *client) +{ + if (!client) { + return; + } + + list_remove(l2cap_clients, client); + l2cap_client_disconnect(client); + list_free(client->outbound_fragments); + osi_free(client); +} + +bool l2cap_client_connect(l2cap_client_t *client, const bt_bdaddr_t *remote_bdaddr, uint16_t psm) +{ + assert(client != NULL); + assert(remote_bdaddr != NULL); + assert(psm != 0); + assert(!bdaddr_is_empty(remote_bdaddr)); + assert(client->local_channel_id == 0); + assert(!client->configured_self); + assert(!client->configured_peer); + assert(!L2C_INVALID_PSM(psm)); + + client->local_channel_id = L2CA_ConnectReq(psm, (uint8_t *)remote_bdaddr); + if (!client->local_channel_id) { + L2CAP_TRACE_ERROR("%s unable to create L2CAP connection.", __func__); + return false; + } + + L2CA_SetConnectionCallbacks(client->local_channel_id, &l2cap_callbacks); + return true; +} + +void l2cap_client_disconnect(l2cap_client_t *client) +{ + assert(client != NULL); + + if (client->local_channel_id && !L2CA_DisconnectReq(client->local_channel_id)) { + L2CAP_TRACE_ERROR("%s unable to send disconnect message for LCID 0x%04x.", __func__, client->local_channel_id); + } + + client->local_channel_id = 0; + client->remote_mtu = L2CAP_MTU_DEFAULT; + client->configured_self = false; + client->configured_peer = false; + client->is_congested = false; + + for (const list_node_t *node = list_begin(client->outbound_fragments); node != list_end(client->outbound_fragments); node = list_next(node)) { + osi_free(list_node(node)); + } + + list_clear(client->outbound_fragments); +} + +bool l2cap_client_is_connected(const l2cap_client_t *client) +{ + assert(client != NULL); + + return client->local_channel_id != 0 && client->configured_self && client->configured_peer; +} + +bool l2cap_client_write(l2cap_client_t *client, buffer_t *packet) +{ + assert(client != NULL); + assert(packet != NULL); + assert(l2cap_client_is_connected(client)); + + if (client->is_congested) { + return false; + } + + fragment_packet(client, packet); + dispatch_fragments(client); + return true; +} + +static void connect_completed_cb(uint16_t local_channel_id, uint16_t error_code) +{ + assert(local_channel_id != 0); + + l2cap_client_t *client = find(local_channel_id); + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client for LCID 0x%04x.", __func__, local_channel_id); + return; + } + + if (error_code != L2CAP_CONN_OK) { + L2CAP_TRACE_ERROR("%s error connecting L2CAP channel: %d.", __func__, error_code); + client->callbacks.disconnected(client, client->context); + return; + } + + // Use default L2CAP parameters. + tL2CAP_CFG_INFO desired_parameters = { 0 }; + if (!L2CA_ConfigReq(local_channel_id, &desired_parameters)) { + L2CAP_TRACE_ERROR("%s error sending L2CAP config parameters.", __func__); + client->callbacks.disconnected(client, client->context); + } +} + +static void config_request_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *requested_parameters) +{ + tL2CAP_CFG_INFO response = { 0 }; + l2cap_client_t *client = find(local_channel_id); + + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id); + return; + } + + response.result = L2CAP_CFG_OK; + + if (requested_parameters->mtu_present) { + // Make sure the peer chose an MTU at least as large as the minimum L2CAP MTU defined + // by the Bluetooth Core spec. + if (requested_parameters->mtu < L2CAP_MTU_MINIMUM) { + response.mtu = L2CAP_MTU_MINIMUM; + response.mtu_present = true; + response.result = L2CAP_CFG_UNACCEPTABLE_PARAMS; + } else { + client->remote_mtu = requested_parameters->mtu; + } + } + + if (requested_parameters->fcr_present) { + if (requested_parameters->fcr.mode != L2CAP_FCR_BASIC_MODE) { + response.fcr_present = true; + response.fcr = requested_parameters->fcr; + response.fcr.mode = L2CAP_FCR_BASIC_MODE; + response.result = L2CAP_CFG_UNACCEPTABLE_PARAMS; + } + } + + if (!L2CA_ConfigRsp(local_channel_id, &response)) { + L2CAP_TRACE_ERROR("%s unable to send config response for LCID 0x%04x.", __func__, local_channel_id); + l2cap_client_disconnect(client); + return; + } + + // If we've configured both endpoints, let the listener know we've connected. + client->configured_peer = true; + if (l2cap_client_is_connected(client)) { + client->callbacks.connected(client, client->context); + } +} + +static void config_completed_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *negotiated_parameters) +{ + l2cap_client_t *client = find(local_channel_id); + + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id); + return; + } + + switch (negotiated_parameters->result) { + // We'll get another configuration response later. + case L2CAP_CFG_PENDING: + break; + + case L2CAP_CFG_UNACCEPTABLE_PARAMS: + // TODO: see if we can renegotiate parameters instead of dropping the connection. + L2CAP_TRACE_WARNING("%s dropping L2CAP connection due to unacceptable config parameters.\n", __func__); + l2cap_client_disconnect(client); + break; + + case L2CAP_CFG_OK: + // If we've configured both endpoints, let the listener know we've connected. + client->configured_self = true; + if (l2cap_client_is_connected(client)) { + client->callbacks.connected(client, client->context); + } + break; + + // Failure, no further parameter negotiation possible. + default: + L2CAP_TRACE_WARNING("%s L2CAP parameter negotiation failed with error code %d.\n", __func__, negotiated_parameters->result); + l2cap_client_disconnect(client); + break; + } +} + +static void disconnect_request_cb(uint16_t local_channel_id, bool ack_required) +{ + l2cap_client_t *client = find(local_channel_id); + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client with LCID 0x%04x.\n", __func__, local_channel_id); + return; + } + + if (ack_required) { + L2CA_DisconnectRsp(local_channel_id); + } + + // We already sent a disconnect response so this LCID is now invalid. + client->local_channel_id = 0; + l2cap_client_disconnect(client); + + client->callbacks.disconnected(client, client->context); +} + +static void disconnect_completed_cb(uint16_t local_channel_id, UNUSED_ATTR uint16_t error_code) +{ + assert(local_channel_id != 0); + + l2cap_client_t *client = find(local_channel_id); + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client with LCID 0x%04x.\n", __func__, local_channel_id); + return; + } + + client->local_channel_id = 0; + l2cap_client_disconnect(client); + + client->callbacks.disconnected(client, client->context); +} + +static void congestion_cb(uint16_t local_channel_id, bool is_congested) +{ + assert(local_channel_id != 0); + + l2cap_client_t *client = find(local_channel_id); + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.\n", __func__, local_channel_id); + return; + } + + client->is_congested = is_congested; + + if (!is_congested) { + // If we just decongested, dispatch whatever we have left over in our queue. + // Once that's done, if we're still decongested, notify the listener so it + // can start writing again. + dispatch_fragments(client); + if (!client->is_congested) { + client->callbacks.write_ready(client, client->context); + } + } +} + +static void read_ready_cb(uint16_t local_channel_id, BT_HDR *packet) +{ + assert(local_channel_id != 0); + + l2cap_client_t *client = find(local_channel_id); + if (!client) { + L2CAP_TRACE_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.\n", __func__, local_channel_id); + return; + } + + // TODO(sharvil): eliminate copy from BT_HDR. + buffer_t *buffer = buffer_new(packet->len); + memcpy(buffer_ptr(buffer), packet->data + packet->offset, packet->len); + osi_free(packet); + + client->callbacks.read_ready(client, buffer, client->context); + buffer_free(buffer); +} + +static void write_completed_cb(UNUSED_ATTR uint16_t local_channel_id, UNUSED_ATTR uint16_t packets_completed) +{ + // Do nothing. We update congestion state based on the congestion callback + // and we've already removed items from outbound_fragments list so we don't + // really care how many packets were successfully dispatched. +} + +static void fragment_packet(l2cap_client_t *client, buffer_t *packet) +{ + assert(client != NULL); + assert(packet != NULL); + + // TODO(sharvil): eliminate copy into BT_HDR. + BT_HDR *bt_packet = osi_malloc(buffer_length(packet) + L2CAP_MIN_OFFSET); + bt_packet->offset = L2CAP_MIN_OFFSET; + bt_packet->len = buffer_length(packet); + memcpy(bt_packet->data + bt_packet->offset, buffer_ptr(packet), buffer_length(packet)); + + for (;;) { + if (bt_packet->len <= client->remote_mtu) { + if (bt_packet->len > 0) { + list_append(client->outbound_fragments, bt_packet); + } else { + osi_free(bt_packet); + } + break; + } + + BT_HDR *fragment = osi_malloc(client->remote_mtu + L2CAP_MIN_OFFSET); + fragment->offset = L2CAP_MIN_OFFSET; + fragment->len = client->remote_mtu; + memcpy(fragment->data + fragment->offset, bt_packet->data + bt_packet->offset, client->remote_mtu); + + list_append(client->outbound_fragments, fragment); + + bt_packet->offset += client->remote_mtu; + bt_packet->len -= client->remote_mtu; + } +} + +static void dispatch_fragments(l2cap_client_t *client) +{ + assert(client != NULL); + assert(!client->is_congested); + + while (!list_is_empty(client->outbound_fragments)) { + BT_HDR *packet = (BT_HDR *)list_front(client->outbound_fragments); + list_remove(client->outbound_fragments, packet); + + switch (L2CA_DataWrite(client->local_channel_id, packet)) { + case L2CAP_DW_CONGESTED: + client->is_congested = true; + return; + + case L2CAP_DW_FAILED: + L2CAP_TRACE_ERROR("%s error writing data to L2CAP connection LCID 0x%04x; disconnecting.", __func__, client->local_channel_id); + l2cap_client_disconnect(client); + return; + + case L2CAP_DW_SUCCESS: + break; + } + } +} + +static l2cap_client_t *find(uint16_t local_channel_id) +{ + assert(local_channel_id != 0); + + for (const list_node_t *node = list_begin(l2cap_clients); node != list_end(l2cap_clients); node = list_next(node)) { + l2cap_client_t *client = (l2cap_client_t *)list_node(node); + if (client->local_channel_id == local_channel_id) { + return client; + } + } + + return NULL; +} + +#endif /*L2CAP_CLIENT_INCLUDED*/ |
