All of lore.kernel.org
 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: 4+ 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
2026-10-13 22:24 ` [PATCH v2] " Kyle Pepper
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=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 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.