From a9af2eee5cc128f4ebb5a4aa8d2fd856d509b66f Mon Sep 17 00:00:00 2001 From: yangfeng Date: Tue, 19 May 2026 09:55:38 +0800 Subject: [PATCH] fix: Fix missing handling for HCI_ERR_CONNECTION_EXISTS - Closes https://github.com/espressif/esp-idf/issues/18562 --- components/bt/host/bluedroid/Kconfig.in | 8 +++ .../include/common/bluedroid_user_config.h | 6 ++ .../common/include/common/bt_target.h | 2 + .../bt/host/bluedroid/stack/btu/btu_task.c | 1 + .../host/bluedroid/stack/include/stack/btu.h | 3 + .../bluedroid/stack/l2cap/include/l2c_int.h | 17 +++++ .../bt/host/bluedroid/stack/l2cap/l2c_csm.c | 20 ++++-- .../bt/host/bluedroid/stack/l2cap/l2c_link.c | 67 +++++++++++++++++++ .../bt/host/bluedroid/stack/l2cap/l2c_main.c | 6 ++ .../bt/host/bluedroid/stack/l2cap/l2c_utils.c | 8 +++ 10 files changed, 134 insertions(+), 4 deletions(-) diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index acbc0c528ac..9b608ccca18 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -48,6 +48,14 @@ config BT_CLASSIC_ENABLED help For now this option needs "SMP_ENABLE" to be set to yes +config BT_CLASSIC_MAX_RECONNECT_ON_COLLISION + int "Maximum number of reconnection attempts in case of collision" + depends on BT_CLASSIC_ENABLED + default 5 + help + The maximum number of reconnection attempts when encountering rejection of connection + request with error code 0x0B(Connection Already Exists) from peer device + config BT_CLASSIC_ENABLE_POWER_CTRL_VSC bool "Enable Espressif Vendor-specific HCI commands for power control of Classic Bluetooth" depends on BT_CLASSIC_ENABLED diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 04ad1832ca2..627a05bb369 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -32,6 +32,12 @@ #define UC_BT_CLASSIC_ENABLED FALSE #endif +#ifdef CONFIG_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION +#define UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION CONFIG_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION +#else +#define UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION 5 +#endif + //A2DP #ifdef CONFIG_BT_A2DP_ENABLE #define UC_BT_A2DP_ENABLED CONFIG_BT_A2DP_ENABLE diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 42d1d248e05..4d72034e109 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -73,6 +73,8 @@ #define SDP_INCLUDED TRUE #define BTA_DM_QOS_INCLUDED TRUE +#define BR_EDR_MAX_RECONNECT_ON_COLLISION UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION + #define ESP_BT_CLASSIC_ENABLE_POWER_CTRL_VSC UC_BT_CLASSIC_ENABLE_POWER_CTRL_VSC #if (UC_BT_A2DP_ENABLED == TRUE) diff --git a/components/bt/host/bluedroid/stack/btu/btu_task.c b/components/bt/host/bluedroid/stack/btu/btu_task.c index 2fe59a92601..6619a392966 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_task.c +++ b/components/bt/host/bluedroid/stack/btu/btu_task.c @@ -321,6 +321,7 @@ static void btu_general_alarm_process(void *param) break; case BTU_TTYPE_L2CAP_LINK: + case BTU_TTYPE_L2CAP_LINK_RETRY: case BTU_TTYPE_L2CAP_CHNL: case BTU_TTYPE_L2CAP_HOLD: case BTU_TTYPE_L2CAP_INFO: diff --git a/components/bt/host/bluedroid/stack/include/stack/btu.h b/components/bt/host/bluedroid/stack/include/stack/btu.h index c2a80916d37..5009976e3ea 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btu.h +++ b/components/bt/host/bluedroid/stack/include/stack/btu.h @@ -172,6 +172,9 @@ typedef void (*tBTU_EVENT_CALLBACK)(BT_HDR *p_hdr); /* BTU internal timer for BR/EDR power control*/ #define BTU_TTYPE_BTM_BREDR_PWR_CTRL 112 +/* L2CAP host-driven Create_Connection retry back-off timer */ +#define BTU_TTYPE_L2CAP_LINK_RETRY 113 + /* BTU Task Signal */ typedef enum { SIG_BTU_START_UP = 0, diff --git a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h index cfc51636649..705b11048bd 100644 --- a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h +++ b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h @@ -25,6 +25,7 @@ #define L2C_INT_H #include +#include "common/bt_target.h" #include "stack/btm_api.h" #include "stack/l2c_api.h" #include "stack/l2cdefs.h" @@ -75,6 +76,12 @@ #define L2CAP_CACHE_ATT_ACL_NUM 10 +/* Maximum number of host-driven Create_Connection retries before reporting + * connection_exist failure to the application layer. */ +#define L2CAP_MAX_RECONNECT_ON_COLLISION BR_EDR_MAX_RECONNECT_ON_COLLISION +/* Back-off (in seconds) between two host-driven Create_Connection retries. */ +#define L2C_LP_CONN_RETRY_DELAY_TOUT 1 /* 1 second */ + /* Define the possible L2CAP channel states. The names of ** the states may seem a bit strange, but they are taken from ** the Bluetooth specification. @@ -393,6 +400,13 @@ typedef struct t_l2c_linkcb { TIMER_LIST_ENT info_timer_entry; /* Timer entry for info resp timeout evt */ TIMER_LIST_ENT upda_con_timer; /* Timer entry for update connection parameter */ BD_ADDR remote_bd_addr; /* The BD address of the remote */ +#if (CLASSIC_BT_INCLUDED == TRUE) + UINT8 br_edr_create_con_retries; /* Host-driven Create_Connection retry counter, + * incremented for each non-success Connection + * Complete that keeps CCBs on this LCB. */ + TIMER_LIST_ENT retry_timer_entry; /* Back-off timer between host-driven + * Create_Connection retries. */ +#endif UINT8 link_role; /* Master or slave */ UINT8 id; @@ -733,6 +747,9 @@ extern BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_b 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); +#if (CLASSIC_BT_INCLUDED == TRUE) +extern void l2c_link_create_conn_retry (tL2C_LCB *p_lcb); +#endif 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); diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c b/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c index 89d74b998fb..9f6192102f4 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c @@ -165,16 +165,28 @@ static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data) p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); break; - case L2CEVT_LP_CONNECT_CFM_NEG: /* Link failed */ + case L2CEVT_LP_CONNECT_CFM_NEG: { /* Link failed */ tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; - /* 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)) { + BOOLEAN keep_for_collision = FALSE; +#if (CLASSIC_BT_INCLUDED == TRUE) + if (p_ci->status == HCI_ERR_CONNECTION_EXISTS + && p_ccb->p_lcb->br_edr_create_con_retries <= L2CAP_MAX_RECONNECT_ON_COLLISION + && btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + keep_for_collision = TRUE; + } +#else + if (p_ci->status == HCI_ERR_CONNECTION_EXISTS + && btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + keep_for_collision = TRUE; + } +#endif + if (!keep_for_collision) { 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 */ diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c index 347e220a41a..1a74d33dcec 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c @@ -219,6 +219,10 @@ BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) btu_stop_timer (&p_lcb->timer_entry); #if (CLASSIC_BT_INCLUDED == TRUE) + /* Link came up successfully; reset host-driven Create_Connection + * retry counter so a future failure on this BDA starts fresh. */ + p_lcb->br_edr_create_con_retries = 0; + btu_stop_timer(&p_lcb->retry_timer_entry); /* 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); @@ -258,6 +262,33 @@ BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) if (ci.status == HCI_ERR_CONNECTION_EXISTS) { /* we are in collision situation, wait for connection request from controller */ p_lcb->link_state = LST_CONNECTING; +#if (CLASSIC_BT_INCLUDED == TRUE) + if (++p_lcb->br_edr_create_con_retries <= L2CAP_MAX_RECONNECT_ON_COLLISION) { + L2CAP_TRACE_WARNING("L2CAP - Conn Comp status: 0x%02x, retry " + "Create_Connection (%u/%u) in %u sec", + status, + p_lcb->br_edr_create_con_retries, + L2CAP_MAX_RECONNECT_ON_COLLISION, + L2C_LP_CONN_RETRY_DELAY_TOUT); + btu_stop_timer(&p_lcb->timer_entry); + p_lcb->retry_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + btu_start_timer(&p_lcb->retry_timer_entry, + BTU_TTYPE_L2CAP_LINK_RETRY, + L2C_LP_CONN_RETRY_DELAY_TOUT); + } else { + L2CAP_TRACE_WARNING("L2CAP - giving up Create_Connection " + "after %u retries, last status: 0x%02x", + p_lcb->br_edr_create_con_retries, status); + 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; + } + btu_stop_timer(&p_lcb->timer_entry); + btu_stop_timer(&p_lcb->retry_timer_entry); + l2cu_release_lcb(p_lcb); + } +#endif ///CLASSIC_BT_INCLUDED == TRUE } else { l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); } @@ -584,7 +615,43 @@ BOOLEAN l2c_link_hci_qos_violation (UINT16 handle) return (TRUE); } +#if (CLASSIC_BT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2c_link_create_conn_retry +** +** Description Back-off timer between two host-driven Create_Connection +** retries fired. Re-issue the connection attempt now. If +** the LCB has been torn down (no CCBs left, link no longer +** in a connecting state) we silently drop the retry. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_create_conn_retry (tL2C_LCB *p_lcb) +{ + if (p_lcb == NULL || !p_lcb->in_use) { + return; + } + /* If the application/upper layer already tore everything down while we + * were waiting for the back-off, do nothing. */ + if (p_lcb->ccb_queue.p_first_ccb == NULL) { + L2CAP_TRACE_WARNING("L2CAP - retry timer fired but no CCB left, " + "dropping retry"); + return; + } + + L2CAP_TRACE_EVENT("L2CAP - back-off elapsed, re-issuing Create_Connection " + "(retry %u/%u)", + p_lcb->br_edr_create_con_retries, + L2CAP_MAX_RECONNECT_ON_COLLISION); + + /* l2cu_create_conn() will (re)set link_state and arm the 60s + * BTU_TTYPE_L2CAP_LINK timer on p_lcb->timer_entry. */ + l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE /******************************************************************************* ** diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c index 1a58b69277a..b3b5a74d5a6 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c @@ -988,6 +988,12 @@ void l2c_process_timeout (TIMER_LIST_ENT *p_tle) l2c_link_timeout ((tL2C_LCB *)p_tle->param); break; #if (CLASSIC_BT_INCLUDED == TRUE) + case BTU_TTYPE_L2CAP_LINK_RETRY: + /* Back-off between host-driven Create_Connection retries expired: + * re-issue the connection attempt now. */ + l2c_link_create_conn_retry ((tL2C_LCB *)p_tle->param); + break; + case BTU_TTYPE_L2CAP_CHNL: l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_TIMEOUT, NULL); break; diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c index ad996c18da9..cb3eb797563 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c @@ -88,6 +88,9 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR btu_free_timer(&p_lcb->timer_entry); btu_free_timer(&p_lcb->info_timer_entry); btu_free_timer(&p_lcb->upda_con_timer); +#if (CLASSIC_BT_INCLUDED == TRUE) + btu_free_timer(&p_lcb->retry_timer_entry); +#endif memset (p_lcb, 0, sizeof (tL2C_LCB)); memcpy (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN); @@ -114,6 +117,7 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR #endif { #if (CLASSIC_BT_INCLUDED == TRUE) + p_lcb->retry_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; l2cb.num_links_active++; l2c_link_adjust_allocation(); #endif // #if (CLASSIC_BT_INCLUDED == TRUE) @@ -181,6 +185,10 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb) 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)); +#if (CLASSIC_BT_INCLUDED == TRUE) + btu_free_timer(&p_lcb->retry_timer_entry); + memset(&p_lcb->retry_timer_entry, 0, sizeof(TIMER_LIST_ENT)); +#endif /* Release any unfinished L2CAP packet on this link */ if (p_lcb->p_hcit_rcv_acl) {