From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: Marcel Holtmann <marcel@holtmann.org>,
Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Subject: [PATCH v2] asha: allow LE 2M PHY on the audio channel
Date: Tue, 13 Oct 2026 22:24:09 +0000 [thread overview]
Message-ID: <20261013222355.511390-1-cochcoder@proton.me> (raw)
In-Reply-To: <20261013034823.519119-1-cochcoder@proton.me>
Allow LE 2M on the CoC socket to increase available throughput for
capable peers. Retain LE 1M fallback and treat BT_PHY as best effort
for older kernels.
Test the PHY mask and continued startup when the preference is rejected.
Assisted-by: OpenCode:gpt-6.1-sol
---
Changes since v1: fix sanitizer-retained daemon references with
fail-fast test stubs.
Refreshed to BlueZ 9dc7e40962f11d1d9a5568f8a7633a695288329c.
Strict non-LTO ASan/UBSan build and make check pass; ordinary LTO
build and make check pass. Strict LTO has an unchanged upstream
peripheral/btsensor warning failure, reproduced without this topic.
No new hardware trial or remote functional-lab result is claimed.
Submission review notes (not part of the commit message):
N8/UB500 completes 2M in the downstream stack. N7/1M-only hardware
is unavailable; fallback has focused unit coverage, not new hardware proof.
.gitignore | 1 +
Makefile.am | 9 ++
profiles/audio/asha.c | 11 ++
unit/test-asha-phy.c | 241 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 262 insertions(+)
create mode 100644 unit/test-asha-phy.c
diff --git a/.gitignore b/.gitignore
index 06b24735df..d214ba0591 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,6 +112,7 @@ unit/test-lib
unit/test-mgmt
unit/test-uhid
unit/test-hfp
+unit/test-asha-phy
unit/test-crypto
unit/test-ecc
unit/test-hog
diff --git a/Makefile.am b/Makefile.am
index f742458d11..2041ac87db 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -706,6 +706,15 @@ unit_test_avrcp_LDADD = lib/libbluetooth-internal.la \
unit_tests += unit/test-hfp
+unit_tests += unit/test-asha-phy
+
+unit_test_asha_phy_SOURCES = unit/test-asha-phy.c
+unit_test_asha_phy_CFLAGS = $(AM_CFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \
+ -ffunction-sections -fdata-sections
+unit_test_asha_phy_LDADD = src/libshared-glib.la \
+ lib/libbluetooth-internal.la $(GLIB_LIBS) $(DBUS_LIBS)
+unit_test_asha_phy_LDFLAGS = -Wl,--gc-sections
+
unit_test_hfp_SOURCES = unit/test-hfp.c
unit_test_hfp_LDADD = src/libshared-glib.la $(GLIB_LIBS)
diff --git a/profiles/audio/asha.c b/profiles/audio/asha.c
index 8e625fdca3..d86224764d 100644
--- a/profiles/audio/asha.c
+++ b/profiles/audio/asha.c
@@ -88,6 +88,8 @@ static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
struct connect_data *conn_data = user_data;
struct bt_asha_device *asha_dev = conn_data->asha_dev;
GError *gerr = NULL;
+ uint32_t phys;
+ int fd;
if (err) {
error("%s", err->message);
@@ -105,6 +107,15 @@ static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
g_error_free(gerr);
}
+ /*
+ * Allow LE 2M with LE 1M fallback. Older kernels may lack BT_PHY.
+ */
+ phys = BT_PHY_LE_1M_TX | BT_PHY_LE_1M_RX |
+ BT_PHY_LE_2M_TX | BT_PHY_LE_2M_RX;
+ fd = g_io_channel_unix_get_fd(io);
+ if (setsockopt(fd, SOL_BLUETOOTH, BT_PHY, &phys, sizeof(phys)) < 0)
+ DBG("Could not set LE 2M PHY preference: %s", strerror(errno));
+
asha_dev->io = io;
bt_asha_start(asha_dev->asha, conn_data->cb,
diff --git a/unit/test-asha-phy.c b/unit/test-asha-phy.c
new file mode 100644
index 0000000000..c58101ebfd
--- /dev/null
+++ b/unit/test-asha-phy.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <assert.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+
+int test_setsockopt(int fd, int level, int option, const void *value,
+ socklen_t length);
+
+/* Source-level mocks remain effective with link-time optimization. */
+#define setsockopt test_setsockopt
+#define bt_io_get test_io_get
+#define bt_asha_start test_start
+#include "profiles/audio/asha.c"
+#undef setsockopt
+#undef bt_io_get
+#undef bt_asha_start
+#include "src/shared/tester.h"
+
+static unsigned int preferences, starts;
+static bool mtu_ok;
+static int preference_error, expected_fd;
+
+/* Sanitizers retain the plugin's globals, including unused daemon callbacks. */
+int btd_profile_register(struct btd_profile *profile)
+{
+ assert(false);
+}
+
+void btd_profile_unregister(struct btd_profile *profile)
+{
+ assert(false);
+}
+
+const char *device_get_path(const struct btd_device *device)
+{
+ assert(false);
+}
+
+const bdaddr_t *device_get_address(struct btd_device *device)
+{
+ assert(false);
+}
+
+struct btd_adapter *device_get_adapter(struct btd_device *device)
+{
+ assert(false);
+}
+
+const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter)
+{
+ assert(false);
+}
+
+void btd_device_set_conn_param(struct btd_device *device,
+ uint16_t min_interval, uint16_t max_interval,
+ uint16_t latency, uint16_t timeout)
+{
+ assert(false);
+}
+
+struct gatt_db *btd_device_get_gatt_db(struct btd_device *device)
+{
+ assert(false);
+}
+
+struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device)
+{
+ assert(false);
+}
+
+struct btd_device *btd_service_get_device(const struct btd_service *service)
+{
+ assert(false);
+}
+
+void *btd_service_get_user_data(const struct btd_service *service)
+{
+ assert(false);
+}
+
+void btd_service_set_user_data(struct btd_service *service, void *user_data)
+{
+ assert(false);
+}
+
+void btd_service_connecting_complete(struct btd_service *service, int err)
+{
+ assert(false);
+}
+
+void btd_service_disconnecting_complete(struct btd_service *service, int err)
+{
+ assert(false);
+}
+
+DBusConnection *btd_get_dbus_connection(void)
+{
+ assert(false);
+}
+
+gboolean g_dbus_register_interface(DBusConnection *connection,
+ const char *path, const char *name,
+ const GDBusMethodTable *methods,
+ const GDBusSignalTable *signals,
+ const GDBusPropertyTable *properties,
+ void *user_data, GDBusDestroyFunction destroy)
+{
+ assert(false);
+}
+
+gboolean g_dbus_unregister_interface(DBusConnection *connection,
+ const char *path, const char *name)
+{
+ assert(false);
+}
+
+const struct media_endpoint *media_endpoint_get_asha(void)
+{
+ assert(false);
+}
+
+struct media_transport *media_transport_create(struct btd_device *device,
+ const char *remote_endpoint,
+ uint8_t *configuration, size_t size,
+ void *data, void *stream)
+{
+ assert(false);
+}
+
+void media_transport_destroy(struct media_transport *transport)
+{
+ assert(false);
+}
+
+const char *media_transport_get_path(struct media_transport *transport)
+{
+ assert(false);
+}
+
+GIOChannel *bt_io_connect(BtIOConnect connect, gpointer user_data,
+ GDestroyNotify destroy, GError **gerr,
+ BtIOOption opt1, ...)
+{
+ assert(false);
+}
+
+void btd_error(uint16_t index, const char *format, ...)
+{
+}
+
+void btd_debug(uint16_t index, const char *format, ...)
+{
+}
+
+int test_setsockopt(int fd, int level, int option, const void *value,
+ socklen_t length)
+{
+ const uint32_t *phys = value;
+
+ assert(fd == expected_fd && level == SOL_BLUETOOTH && option == BT_PHY);
+ assert(length == sizeof(*phys));
+ assert(*phys == (BT_PHY_LE_1M_TX | BT_PHY_LE_1M_RX |
+ BT_PHY_LE_2M_TX | BT_PHY_LE_2M_RX));
+ assert(starts == 0);
+ preferences++;
+ errno = preference_error;
+ return preference_error ? -1 : 0;
+}
+
+gboolean test_io_get(GIOChannel *io, GError **error, BtIOOption first, ...)
+{
+ va_list args;
+ uint16_t *imtu, *omtu;
+
+ if (!mtu_ok) {
+ *error = g_error_new(G_IO_CHANNEL_ERROR,
+ G_IO_CHANNEL_ERROR_FAILED, "MTU failure");
+ return FALSE;
+ }
+
+ assert(first == BT_IO_OPT_IMTU);
+ va_start(args, first);
+ imtu = va_arg(args, uint16_t *);
+ assert(va_arg(args, int) == BT_IO_OPT_OMTU);
+ omtu = va_arg(args, uint16_t *);
+ assert(va_arg(args, int) == BT_IO_OPT_INVALID);
+ va_end(args);
+ *imtu = 512;
+ *omtu = 163;
+ return TRUE;
+}
+
+unsigned int test_start(struct bt_asha *asha, bt_asha_cb_t callback, void *data)
+{
+ assert(preferences == 1);
+ starts++;
+ return 0;
+}
+
+static void test_phy(const void *user_data)
+{
+ const int errors[] = { 0, ENOPROTOOPT, EINVAL };
+ unsigned int i;
+ int fds[2];
+ GIOChannel *io;
+ GError *error;
+ struct bt_asha asha = { 0 };
+ struct bt_asha_device device = { .asha = &asha };
+ struct connect_data data = { .asha_dev = &device };
+
+ assert(!pipe(fds));
+ io = g_io_channel_unix_new(fds[0]);
+ expected_fd = fds[0];
+
+ for (i = 0; i < ARRAY_SIZE(errors); i++) {
+ preferences = starts = 0;
+ preference_error = errors[i];
+ mtu_ok = i != 2;
+ connect_cb(io, NULL, &data);
+ assert(preferences == 1 && starts == 1 && device.io == io);
+ assert(device.imtu == 512 && device.omtu == 163);
+ }
+
+ preferences = starts = 0;
+ error = g_error_new(G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED,
+ "mock connection failure");
+ connect_cb(io, error, &data);
+ assert(preferences == 0 && starts == 0);
+ g_error_free(error);
+ g_io_channel_unref(io);
+ close(fds[0]);
+ close(fds[1]);
+ tester_test_passed();
+}
+
+int main(int argc, char *argv[])
+{
+ tester_init(&argc, &argv);
+ tester_add("ASHA/PHY/preference", NULL, NULL, test_phy, NULL);
+ return tester_run();
+}
base-commit: 9dc7e40962f11d1d9a5568f8a7633a695288329c
--
2.56.0
next prev parent reply other threads:[~2026-10-13 22:24 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 3:48 [PATCH] asha: allow LE 2M PHY on the audio channel Kyle Pepper
2026-10-13 4:50 ` bluez.test.bot
2026-10-13 22:24 ` Kyle Pepper [this message]
2026-10-13 23:58 ` [v2] " 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=20261013222355.511390-1-cochcoder@proton.me \
--to=cochcoder@proton.me \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.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