* [PATCH] policy: Connect profiles for bonded inbound ACL connections
@ 2026-07-16 10:04 Chengyi Zhao
2026-07-16 11:00 ` bluez.test.bot
0 siblings, 1 reply; 2+ messages in thread
From: Chengyi Zhao @ 2026-07-16 10:04 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, Chengyi Zhao
From: Chengyi Zhao <zhaochengyi@uniontech.com>
When a bonded device initiates an inbound ACL connection (e.g. after
resume, the remote wins the page race), BlueZ accepts the ACL link
but does not establish AVDTP/AVCTP profiles. The remote's subsequent
L2CAP connect for PSM 25 fails with "security block" because the link
is not yet encrypted, and the kernel does not retry. The ACL link
stays up with no audio.
Add btd_connect_cb callback infrastructure to the adapter core
mirroring the existing btd_disconnect_cb pattern, invoked from
connected_callback so it only fires on new MGMT_EV_DEVICE_CONNECTED
events. Register a callback in the policy plugin that starts a
2-second grace timer for bonded BR/EDR connections. If an audio
profile connects naturally within the window the timer is cancelled;
otherwise btd_device_connect_services is called as a fallback, but
only if no audio profile is connected yet. The grace timer is also
cancelled on disconnect to avoid racing with device removal.
Outbound L2CAP is queued by the kernel until encryption completes,
surviving the race. The fallback is safe when the host already won
since btd_device_connect_services returns -EBUSY if a connection is
already in progress.
---
plugins/policy.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++
src/adapter.c | 24 +++++++++++
src/adapter.h | 4 ++
3 files changed, 134 insertions(+)
diff --git a/plugins/policy.c b/plugins/policy.c
index 0e1ce5c9a..c0004e743 100644
--- a/plugins/policy.c
+++ b/plugins/policy.c
@@ -45,6 +45,7 @@
#define HS_RETRIES SOURCE_RETRIES
#define CT_RETRIES 1
#define TG_RETRIES CT_RETRIES
+#define GRACE_TIMEOUT 2
struct reconnect_data {
struct btd_device *dev;
@@ -92,6 +93,7 @@ struct policy_data {
uint8_t tg_retries;
unsigned int hs_timer;
uint8_t hs_retries;
+ unsigned int grace_timer;
};
static struct reconnect_data *reconnect_find(struct btd_device *dev)
@@ -190,6 +192,9 @@ static void policy_remove(void *user_data)
if (data->hs_timer > 0)
timeout_remove(data->hs_timer);
+ if (data->grace_timer > 0)
+ timeout_remove(data->grace_timer);
+
g_free(data);
}
@@ -209,6 +214,83 @@ static struct policy_data *policy_get_data(struct btd_device *dev)
return data;
}
+static bool policy_has_audio_profile(struct btd_device *dev)
+{
+ struct btd_service *service;
+
+ service = btd_device_get_service(dev, A2DP_SINK_UUID);
+ if (service && btd_service_get_state(service) ==
+ BTD_SERVICE_STATE_CONNECTED)
+ return true;
+
+ service = btd_device_get_service(dev, A2DP_SOURCE_UUID);
+ if (service && btd_service_get_state(service) ==
+ BTD_SERVICE_STATE_CONNECTED)
+ return true;
+
+ return false;
+}
+
+static void policy_clear_grace_timer(struct policy_data *data)
+{
+ if (data->grace_timer > 0) {
+ timeout_remove(data->grace_timer);
+ data->grace_timer = 0;
+ }
+}
+
+static bool policy_grace_timeout(gpointer user_data)
+{
+ struct policy_data *data = user_data;
+ struct btd_device *dev = data->dev;
+ int err;
+
+ data->grace_timer = 0;
+
+ DBG("Grace period expired for %s", device_get_path(dev));
+
+ if (policy_has_audio_profile(dev))
+ return false;
+
+ err = btd_device_connect_services(dev, NULL);
+ if (err < 0)
+ error("Fallback profile connection failed: %s (%d)",
+ strerror(-err), -err);
+
+ return false;
+}
+
+static void policy_set_grace_timer(struct policy_data *data)
+{
+ policy_clear_grace_timer(data);
+
+ DBG("Starting %d second grace period for %s", GRACE_TIMEOUT,
+ device_get_path(data->dev));
+
+ data->grace_timer = timeout_add_seconds(GRACE_TIMEOUT,
+ policy_grace_timeout, data,
+ NULL);
+}
+
+static void policy_connect_cb(struct btd_device *dev, uint8_t bdaddr_type)
+{
+ struct policy_data *data;
+
+ if (bdaddr_type != BDADDR_BREDR)
+ return;
+
+ if (!device_is_bonded(dev, BDADDR_BREDR))
+ return;
+
+ if (policy_has_audio_profile(dev))
+ return;
+
+ DBG("Bonded BREDR ACL connected for %s", device_get_path(dev));
+
+ data = policy_get_data(dev);
+ policy_set_grace_timer(data);
+}
+
static bool policy_connect_hs(gpointer user_data)
{
struct policy_data *data = user_data;
@@ -716,6 +798,22 @@ static void service_cb(struct btd_service *service,
g_str_equal(profile->remote_uuid, HSP_HS_UUID))
hs_cb(service, old_state, new_state);
+ /* Cancel grace timer if an audio profile connected naturally.
+ * Only A2DP connectivity cancels the timer: a non-audio profile
+ * (HFP, HID, etc.) may connect after the security block window
+ * without resolving the A2DP failure the timer is meant to catch.
+ */
+ if (new_state == BTD_SERVICE_STATE_CONNECTED) {
+ struct btd_device *dev = btd_service_get_device(service);
+ struct policy_data *data = find_data(dev);
+
+ if (data && data->grace_timer > 0 &&
+ policy_has_audio_profile(dev)) {
+ DBG("Audio connected during grace period, cancelling");
+ policy_clear_grace_timer(data);
+ }
+ }
+
/*
* Return if the reconnection feature is not enabled (all
* subsequent code in this function is about that).
@@ -810,9 +908,14 @@ static void reconnect_set_timer(struct reconnect_data *reconnect, int timeout)
static void disconnect_cb(struct btd_device *dev, uint8_t reason)
{
struct reconnect_data *reconnect;
+ struct policy_data *data;
DBG("reason %u", reason);
+ data = find_data(dev);
+ if (data)
+ policy_clear_grace_timer(data);
+
/* Only attempt reconnect for the following reasons */
if (reason != MGMT_DEV_DISCONN_TIMEOUT &&
reason != MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND)
@@ -917,6 +1020,8 @@ static int policy_init(void)
service_id = btd_service_add_state_cb(service_cb, NULL);
+ btd_add_connect_cb(policy_connect_cb);
+
conf = btd_get_main_conf();
if (!conf) {
reconnect_uuids = g_strdupv((char **) default_reconnect);
@@ -988,6 +1093,7 @@ static void policy_exit(void)
{
btd_remove_disconnect_cb(disconnect_cb);
btd_remove_conn_fail_cb(conn_fail_cb);
+ btd_remove_connect_cb(policy_connect_cb);
if (reconnect_uuids)
g_strfreev(reconnect_uuids);
diff --git a/src/adapter.c b/src/adapter.c
index 4ffa32a52..e6e061893 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -167,6 +167,7 @@ static GSList *adapter_drivers = NULL;
static GSList *disconnect_list = NULL;
static GSList *conn_fail_list = NULL;
+static GSList *connect_list = NULL;
struct link_key_info {
bdaddr_t bdaddr;
@@ -8609,6 +8610,17 @@ int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
return 0;
}
+static void connect_notify(struct btd_device *dev, uint8_t bdaddr_type)
+{
+ GSList *l;
+
+ for (l = connect_list; l; l = g_slist_next(l)) {
+ btd_connect_cb connect_cb = l->data;
+
+ connect_cb(dev, bdaddr_type);
+ }
+}
+
static void disconnect_notify(struct btd_device *dev, uint8_t reason)
{
GSList *l;
@@ -8662,6 +8674,16 @@ void btd_remove_disconnect_cb(btd_disconnect_cb func)
disconnect_list = g_slist_remove(disconnect_list, func);
}
+void btd_add_connect_cb(btd_connect_cb func)
+{
+ connect_list = g_slist_append(connect_list, func);
+}
+
+void btd_remove_connect_cb(btd_connect_cb func)
+{
+ connect_list = g_slist_remove(connect_list, func);
+}
+
static void disconnect_complete(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
@@ -9606,6 +9628,8 @@ static void connected_callback(uint16_t index, uint16_t length,
adapter_msd_notify(adapter, device, eir_data.msd_list);
eir_data_free(&eir_data);
+
+ connect_notify(device, ev->addr.type);
}
static void controller_resume_notify(struct btd_adapter *adapter)
diff --git a/src/adapter.h b/src/adapter.h
index 4e07f71ad..a9e1bbf66 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -65,6 +65,10 @@ typedef void (*btd_conn_fail_cb) (struct btd_device *device, uint8_t status);
void btd_add_conn_fail_cb(btd_conn_fail_cb func);
void btd_remove_conn_fail_cb(btd_conn_fail_cb func);
+typedef void (*btd_connect_cb) (struct btd_device *device, uint8_t bdaddr_type);
+void btd_add_connect_cb(btd_connect_cb func);
+void btd_remove_connect_cb(btd_connect_cb func);
+
struct btd_adapter *adapter_find(const bdaddr_t *sba);
struct btd_adapter *adapter_find_by_id(int id);
--
2.50.1
^ permalink raw reply related [flat|nested] 2+ messages in thread* RE: policy: Connect profiles for bonded inbound ACL connections
2026-07-16 10:04 [PATCH] policy: Connect profiles for bonded inbound ACL connections Chengyi Zhao
@ 2026-07-16 11:00 ` bluez.test.bot
0 siblings, 0 replies; 2+ messages in thread
From: bluez.test.bot @ 2026-07-16 11:00 UTC (permalink / raw)
To: linux-bluetooth, chengyi.zhao
[-- Attachment #1: Type: text/plain, Size: 7465 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1128714
---Test result---
Test Summary:
CheckPatch FAIL 0.61 seconds
GitLint PASS 0.33 seconds
BuildEll PASS 20.53 seconds
BluezMake FAIL 518.26 seconds
CheckSmatch PASS 295.56 seconds
bluezmakeextell PASS 95.57 seconds
IncrementalBuild FAIL 490.05 seconds
ScanBuild PASS 897.76 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
policy: Connect profiles for bonded inbound ACL connections
ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#282: FILE: src/adapter.c:170:
+static GSList *connect_list = NULL;
/github/workspace/src/patch/14690647.patch total: 1 errors, 0 warnings, 215 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14690647.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: BluezMake - FAIL
Desc: Build BlueZ
Output:
tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12990:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
12990 | int main(int argc, char *argv[])
| ^~~~
unit/test-avdtp.c: In function ‘main’:
unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
766 | int main(int argc, char *argv[])
| ^~~~
unit/test-avrcp.c: In function ‘main’:
unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
989 | int main(int argc, char *argv[])
| ^~~~
monitor/packet.c: In function ‘print_features_subpage’:
monitor/packet.c:2940:29: error: ‘%u’ directive writing between 1 and 10 bytes into a region of size between 5 and 7 [-Werror=format-overflow=]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~
monitor/packet.c:2940:16: note: directive argument in the range [0, 2147483646]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~~~~~~~~~~~~~~~~
In file included from /usr/include/stdio.h:867,
from monitor/packet.c:18:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 14 and 25 bytes into a destination of size 18
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7060: monitor/packet.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4174: all] Error 2
##############################
Test: IncrementalBuild - FAIL
Desc: Incremental build with the patches in the series
Output:
tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12990:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
12990 | int main(int argc, char *argv[])
| ^~~~
unit/test-avdtp.c: In function ‘main’:
unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
766 | int main(int argc, char *argv[])
| ^~~~
unit/test-avrcp.c: In function ‘main’:
unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
989 | int main(int argc, char *argv[])
| ^~~~
monitor/packet.c: In function ‘print_features_subpage’:
monitor/packet.c:2940:29: error: ‘%u’ directive writing between 1 and 10 bytes into a region of size between 5 and 7 [-Werror=format-overflow=]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~
monitor/packet.c:2940:16: note: directive argument in the range [0, 2147483646]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~~~~~~~~~~~~~~~~
In file included from /usr/include/stdio.h:867,
from monitor/packet.c:18:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 14 and 25 bytes into a destination of size 18
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7060: monitor/packet.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4174: all] Error 2
policy: Connect profiles for bonded inbound ACL connections
tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12990:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
12990 | int main(int argc, char *argv[])
| ^~~~
unit/test-avdtp.c: In function ‘main’:
unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
766 | int main(int argc, char *argv[])
| ^~~~
unit/test-avrcp.c: In function ‘main’:
unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
989 | int main(int argc, char *argv[])
| ^~~~
monitor/packet.c: In function ‘print_features_subpage’:
monitor/packet.c:2940:29: error: ‘%u’ directive writing between 1 and 10 bytes into a region of size between 5 and 7 [-Werror=format-overflow=]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~
monitor/packet.c:2940:16: note: directive argument in the range [0, 2147483646]
2940 | sprintf(str, "Features[%u/%u]", page, i);
| ^~~~~~~~~~~~~~~~~
In file included from /usr/include/stdio.h:867,
from monitor/packet.c:18:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 14 and 25 bytes into a destination of size 18
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7060: monitor/packet.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4174: all] Error 2
https://github.com/bluez/bluez/pull/2319
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-16 11:00 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-16 10:04 [PATCH] policy: Connect profiles for bonded inbound ACL connections Chengyi Zhao
2026-07-16 11:00 ` bluez.test.bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox