From: "Tarjei Bitustøyl" <tarjeib@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: "Tarjei Bitustøyl" <tarjeib@gmail.com>
Subject: [PATCH BlueZ v3 1/1] adapter: Add configurable default LE PHYs
Date: Mon, 25 May 2026 13:37:09 +0200 [thread overview]
Message-ID: <20260525113709.115020-2-tarjeib@gmail.com> (raw)
In-Reply-To: <20260525113709.115020-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.
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
next prev parent reply other threads:[~2026-05-25 11:37 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 ` Tarjei Bitustøyl [this message]
2026-05-25 14:33 ` 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=20260525113709.115020-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