Linux bluetooth development
 help / color / mirror / Atom feed
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] asha: allow LE 2M PHY on the audio channel
Date: Tue, 13 Oct 2026 03:48:41 +0000	[thread overview]
Message-ID: <20261013034823.519119-1-cochcoder@proton.me> (raw)

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
---
Submission review notes (not part of the commit message):
Local validation is for base 7428ca2df9356363079db0f48412c20cbdb9471f.
Compatibility with newer upstream tips has not been verified locally.
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  | 117 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 138 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..9c9d7b9a98
--- /dev/null
+++ b/unit/test-asha-phy.c
@@ -0,0 +1,117 @@
+// 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;
+
+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: 7428ca2df9356363079db0f48412c20cbdb9471f


             reply	other threads:[~2026-10-13  3:48 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13  3:48 Kyle Pepper [this message]
2026-10-13  4:50 ` asha: allow LE 2M PHY on the audio channel 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=20261013034823.519119-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