Linux bluetooth development
 help / color / mirror / Atom feed
From: "Tarjei Bitustøyl" <tarjeib@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: "Tarjei Bitustøyl" <tarjeib@gmail.com>
Subject: [PATCH BlueZ v4 1/1] adapter: Add configurable default LE PHYs
Date: Mon, 25 May 2026 16:50:10 +0200	[thread overview]
Message-ID: <20260525145010.154170-2-tarjeib@gmail.com> (raw)
In-Reply-To: <20260525145010.154170-1-tarjeib@gmail.com>

Some controllers mis-handle LE procedures on specific PHYs with
certain peers. On an Intel AX210-class controller, connecting to a
Frostbay BLE device can fail during early ATT/GATT setup unless the
adapter is limited to LE 1M TX/RX.

Add an opt-in [LE] DefaultPHYs setting to bluetoothd and apply it at
adapter startup using MGMT_OP_GET/SET_PHY_CONFIGURATION while
preserving non-configurable PHY bits.

This provides a generic, adapter-wide workaround for controller-
specific LE PHY interoperability problems affecting scanning and
connection establishment, without adding device-specific quirks.

v4:
- Replace the hard tabs in the changelog continuation lines with spaces
  to satisfy GitLint.

v3:
- Match the queue header include spelling in main.c with device.h to
  avoid the Sparse redefinition warning seen in CI.

v2:
- Check MGMT_SETTING_PHY_CONFIGURATION before sending PHY
  configuration commands.
---
 src/adapter.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/btd.h     |  2 ++
 src/main.c    | 63 +++++++++++++++++++++++++++++++++++++++-
 src/main.conf |  8 ++++++
 4 files changed, 151 insertions(+), 1 deletion(-)

diff --git a/src/adapter.c b/src/adapter.c
index 20f7c3e03..46df362c5 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -4972,6 +4972,84 @@ done:
 	mgmt_tlv_list_free(list);
 }
 
+static void set_default_le_phys_complete(uint8_t status, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct btd_adapter *adapter = user_data;
+
+	if (status != MGMT_STATUS_SUCCESS)
+		btd_error(adapter->dev_id,
+				"Failed to set default LE PHYs for hci%u: %s (0x%02x)",
+				adapter->dev_id, mgmt_errstr(status), status);
+}
+
+static void get_default_le_phys_complete(uint8_t status, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct btd_adapter *adapter = user_data;
+	const struct mgmt_rp_get_phy_confguration *rp = param;
+	struct mgmt_cp_set_phy_confguration cp;
+	uint32_t configurable_phys;
+	uint32_t selected_phys;
+	uint32_t next_phys;
+
+	if (status != MGMT_STATUS_SUCCESS) {
+		btd_error(adapter->dev_id,
+				"Failed to read PHY configuration for hci%u: %s (0x%02x)",
+				adapter->dev_id, mgmt_errstr(status), status);
+		return;
+	}
+
+	if (length < sizeof(*rp)) {
+		btd_error(adapter->dev_id,
+				"Too small get PHY configuration response for hci%u",
+				adapter->dev_id);
+		return;
+	}
+
+	configurable_phys = btohl(rp->configurable_phys);
+	selected_phys = btohl(rp->selected_phys);
+
+	configurable_phys &= MGMT_PHY_LE_TX_MASK | MGMT_PHY_LE_RX_MASK;
+	next_phys = selected_phys & ~configurable_phys;
+	next_phys |= btd_opts.default_le_phys & configurable_phys;
+
+	if (next_phys == selected_phys)
+		return;
+
+	cp.selected_phys = cpu_to_le32(next_phys);
+
+	if (mgmt_send(adapter->mgmt, MGMT_OP_SET_PHY_CONFIGURATION,
+			adapter->dev_id, sizeof(cp), &cp,
+			set_default_le_phys_complete, adapter, NULL) > 0)
+		return;
+
+	btd_error(adapter->dev_id,
+			"Failed to set default LE PHYs for hci%u",
+			adapter->dev_id);
+}
+
+static void load_default_le_phys(struct btd_adapter *adapter)
+{
+	if (!btd_opts.default_le_phys_configured)
+		return;
+
+	if (!(adapter->supported_settings & MGMT_SETTING_LE))
+		return;
+
+	if (!(adapter->supported_settings & MGMT_SETTING_PHY_CONFIGURATION))
+		return;
+
+	if (mgmt_send(adapter->mgmt, MGMT_OP_GET_PHY_CONFIGURATION,
+			adapter->dev_id, 0, NULL,
+			get_default_le_phys_complete, adapter, NULL) > 0)
+		return;
+
+	btd_error(adapter->dev_id,
+			"Failed to read PHY configuration for hci%u",
+			adapter->dev_id);
+}
+
 static void load_devices(struct btd_adapter *adapter)
 {
 	char dirname[PATH_MAX];
@@ -9455,6 +9533,7 @@ load:
 	btd_profile_foreach(probe_profile, adapter);
 	clear_blocked(adapter);
 	load_defaults(adapter);
+	load_default_le_phys(adapter);
 	load_devices(adapter);
 
 	/* restore Service Changed CCC value for bonded devices */
diff --git a/src/btd.h b/src/btd.h
index db2e81239..59f44dc8c 100644
--- a/src/btd.h
+++ b/src/btd.h
@@ -140,6 +140,8 @@ struct btd_opts {
 	bool		device_privacy;
 	uint32_t	name_request_retry_delay;
 	uint8_t		secure_conn;
+	uint32_t	default_le_phys;
+	bool		default_le_phys_configured;
 
 	struct btd_defaults defaults;
 
diff --git a/src/main.c b/src/main.c
index 8aa19a3e3..83be19be3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -32,6 +32,7 @@
 #include <dbus/dbus.h>
 
 #include "bluetooth/bluetooth.h"
+#include "bluetooth/mgmt.h"
 #include "bluetooth/sdp.h"
 
 #include "gdbus/gdbus.h"
@@ -43,7 +44,7 @@
 #include "shared/att-types.h"
 #include "shared/mainloop.h"
 #include "shared/timeout.h"
-#include "shared/queue.h"
+#include "src/shared/queue.h"
 #include "shared/crypto.h"
 #include "bluetooth/uuid.h"
 #include "shared/util.h"
@@ -132,6 +133,7 @@ static const char *le_options[] = {
 	"Autoconnecttimeout",
 	"AdvMonAllowlistScanDuration",
 	"AdvMonNoFilterScanDuration",
+	"DefaultPHYs",
 	"EnableAdvMonInterleaveScan",
 	NULL
 };
@@ -145,6 +147,8 @@ static const char *policy_options[] = {
 	NULL
 };
 
+static void parse_default_le_phys(GKeyFile *config);
+
 static const char *gatt_options[] = {
 	"Cache",
 	"KeySize",
@@ -751,6 +755,7 @@ static void parse_le_config(GKeyFile *config)
 		return;
 
 	parse_mode_config(config, "LE", params, ARRAY_SIZE(params));
+	parse_default_le_phys(config);
 }
 
 static bool match_experimental(const void *data, const void *match_data)
@@ -966,6 +971,62 @@ static void parse_repairing(GKeyFile *config)
 	g_free(str);
 }
 
+struct phy_config_entry {
+	const char *name;
+	uint32_t bit;
+};
+
+static const struct phy_config_entry le_phy_configs[] = {
+	{ "LE1MTX", MGMT_PHY_LE_1M_TX },
+	{ "LE1MRX", MGMT_PHY_LE_1M_RX },
+	{ "LE2MTX", MGMT_PHY_LE_2M_TX },
+	{ "LE2MRX", MGMT_PHY_LE_2M_RX },
+	{ "LECODEDTX", MGMT_PHY_LE_CODED_TX },
+	{ "LECODEDRX", MGMT_PHY_LE_CODED_RX },
+};
+
+static void parse_default_le_phys(GKeyFile *config)
+{
+	char *str = NULL;
+	char **tokens;
+	uint32_t phys = 0;
+	bool valid = false;
+	int i;
+
+	if (!parse_config_string(config, "LE", "DefaultPHYs", &str))
+		return;
+
+	tokens = g_strsplit_set(str, ", \t", -1);
+
+	for (i = 0; tokens[i]; i++) {
+		const char *token = tokens[i];
+		size_t j;
+
+		if (!token[0])
+			continue;
+
+		for (j = 0; j < ARRAY_SIZE(le_phy_configs); j++) {
+			if (strcasecmp(le_phy_configs[j].name, token) != 0)
+				continue;
+
+			phys |= le_phy_configs[j].bit;
+			valid = true;
+			break;
+		}
+
+		if (j == ARRAY_SIZE(le_phy_configs))
+			warn("Invalid DefaultPHYs token: %s", token);
+	}
+
+	if (valid) {
+		btd_opts.default_le_phys = phys;
+		btd_opts.default_le_phys_configured = true;
+	}
+
+	g_strfreev(tokens);
+	g_free(str);
+}
+
 static bool parse_config_hex(GKeyFile *config, char *group,
 					const char *key, uint32_t *val)
 {
diff --git a/src/main.conf b/src/main.conf
index 5846ef92d..ed955897e 100644
--- a/src/main.conf
+++ b/src/main.conf
@@ -247,6 +247,14 @@
 # Default: 500
 #AdvMonNoFilterScanDuration=
 
+# Configure the controller's default LE PHY policy used for scanning and
+# connection establishment. Only configurable LE PHYs are changed; mandatory
+# PHYs remain selected automatically.
+# Possible values: comma or space separated list of LE1MTX, LE1MRX, LE2MTX,
+# LE2MRX, LECODEDTX, LECODEDRX.
+# Example: keep LE on 1M only.
+#DefaultPHYs = LE1MTX LE1MRX
+
 # Enable/Disable Advertisement Monitor interleave scan for power saving.
 # 0: disable
 # 1: enable
-- 
2.43.0


  reply	other threads:[~2026-05-25 14:50 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-24 22:14 [PATCH BlueZ 0/1] Add configurable default LE PHY policy Tarjei Bitustøyl
2026-05-24 22:14 ` [PATCH BlueZ 1/1] adapter: Add configurable default LE PHYs Tarjei Bitustøyl
2026-05-24 23:07   ` Add configurable default LE PHY policy bluez.test.bot
2026-05-26 14:27   ` [PATCH BlueZ 1/1] adapter: Add configurable default LE PHYs Luiz Augusto von Dentz
2026-05-26 18:58     ` Tarjei Bitustøyl
2026-05-25  6:50 ` [PATCH BlueZ v2 0/1] Add configurable default LE PHY policy Tarjei Bitustøyl
2026-05-25  6:50   ` [PATCH BlueZ v2 1/1] adapter: Add configurable default LE PHYs Tarjei Bitustøyl
2026-05-25 11:04     ` Add configurable default LE PHY policy bluez.test.bot
2026-05-25 11:37 ` [PATCH BlueZ v3 0/1] " Tarjei Bitustøyl
2026-05-25 11:37   ` [PATCH BlueZ v3 1/1] adapter: Add configurable default LE PHYs Tarjei Bitustøyl
2026-05-25 14:33     ` Add configurable default LE PHY policy bluez.test.bot
2026-05-25 14:50 ` [PATCH BlueZ v4 0/1] " Tarjei Bitustøyl
2026-05-25 14:50   ` Tarjei Bitustøyl [this message]
2026-05-25 16:55     ` bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260525145010.154170-2-tarjeib@gmail.com \
    --to=tarjeib@gmail.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox