From: "Tarjei Bitustøyl" <tarjeib@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: "Tarjei Bitustøyl" <tarjeib@gmail.com>
Subject: [PATCH BlueZ v2 1/1] adapter: Add configurable default LE PHYs
Date: Mon, 25 May 2026 08:50:44 +0200 [thread overview]
Message-ID: <20260525065045.27375-2-tarjeib@gmail.com> (raw)
In-Reply-To: <20260525065045.27375-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.
v2:
- Check MGMT_SETTING_PHY_CONFIGURATION before sending PHY
configuration commands.
---
src/adapter.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++
src/btd.h | 2 ++
src/main.c | 61 +++++++++++++++++++++++++++++++++++++++
src/main.conf | 8 ++++++
4 files changed, 150 insertions(+)
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..97c64845b 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"
@@ -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
next prev parent reply other threads:[~2026-05-25 6:51 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 ` Tarjei Bitustøyl [this message]
2026-05-25 11:04 ` 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 ` [PATCH BlueZ v4 1/1] adapter: Add configurable default LE PHYs Tarjei Bitustøyl
2026-05-25 16:55 ` Add configurable default LE PHY policy 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=20260525065045.27375-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.