* [PATCH RESEND v2] asha: allow LE 2M PHY on the audio channel
@ 2026-10-29 0:21 Kyle Pepper
2026-10-29 1:22 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 2+ messages in thread
From: Kyle Pepper @ 2026-10-29 0:21 UTC (permalink / raw)
To: linux-bluetooth
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
---
Resending unchanged v2 after inactivity closure of the prior CI PR
(closure reported October 28). Original source and attribution retained.
Prior v2 CI passed as reported; not independently reverified.
No new hardware or remote CI result is claimed.
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
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH RESEND v2] asha: allow LE 2M PHY on the audio channel
2026-10-29 0:21 [PATCH RESEND v2] asha: allow LE 2M PHY on the audio channel Kyle Pepper
@ 2026-10-29 1:22 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 2+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-29 1:22 UTC (permalink / raw)
To: Kyle Pepper; +Cc: linux-bluetooth
Hi Kyle,
On Wed, Oct 28, 2026 at 8:21 PM Kyle Pepper <cochcoder@proton.me> wrote:
>
> 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
> ---
> Resending unchanged v2 after inactivity closure of the prior CI PR
> (closure reported October 28). Original source and attribution retained.
> Prior v2 CI passed as reported; not independently reverified.
> No new hardware or remote CI result is claimed.
>
> 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 ++++++++++++++++++++++++++++++++++++++++++
Don't create new test files, just reuse test-asha.
> 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
>
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-29 1:22 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-29 0:21 [PATCH RESEND v2] asha: allow LE 2M PHY on the audio channel Kyle Pepper
2026-10-29 1:22 ` Luiz Augusto von Dentz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox