From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: luiz.dentz@gmail.com, marcel@holtmann.org,
sean.wang@mediatek.com, linux-mediatek@lists.infradead.org,
linux-kernel@vger.kernel.org
Subject: [PATCH] Bluetooth: request LE data length for affected MT7922 firmware
Date: Fri, 16 Oct 2026 17:01:38 +0000 [thread overview]
Message-ID: <20261016170122.27119-1-cochcoder@proton.me> (raw)
On an ASUS G614PR, the MT7922 USB controller 0489:e0f6 accepts maximum
LE suggested default data lengths, but ASHA playback remains severely
choppy until the host sends an explicit LE Set Data Length request.
With the firmware image dated 2026-07-24 14:38:15, a request on the same
Cochlear Nucleus 8 connection produces a Data Length Change event with
TX 251 octets/2120 us. Delivery rises from 21.5 to 50 audio SDUs/s and
the peak outstanding packet count falls from 32 to 3. Wi-Fi power save
remains enabled. Playback also works with the automatic request in the
local 7.2.9 kernel build.
Keep the existing suggested-default initialization and add a driver-set
quirk for the tested USB ID, chip and firmware image only. After a
successful remote feature exchange, queue a request using the current
host defaults if the central-role link and controller support DLE and
the controller advertises LE Set Data Length.
Hold a connection reference while queued and recheck its membership,
state, role and capabilities before constructing the command. Release
the reference on completion, cancellation or queue failure. Failure of
this optional request must not reject connection setup. Other controller
matches, PHY fallback and suggested defaults are unchanged.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
This is the first submission of this MediaTek-specific fix. It follows
Luiz's suggestion, in review of a separate generic DLE RFC on October 13,
to use a driver-set quirk if a controller needs explicit DLE. It is not
a revision or resend of that generic request.
Differences from the separate generic RFC:
- Require the exact tested USB ID, chip and firmware image build.
- Keep the existing suggested-default write unchanged.
- Require a central-role link, local/peer DLE and command support.
- Use current host defaults, not hard-coded transmit limits.
- Keep referenced queued work, cancellation and locked revalidation.
Tested on ASUS G614PR with MT7922 and a Cochlear Nucleus 8:
- Manual same-connection request: 21.5 -> 50 SDUs/s, peak queue 32 -> 3.
- Automatic request: local 7.2.9-arch1-2-asha-mediatek boots and playback
is reported working; the matching firmware and quirk log are present.
This local kernel/userspace stack has other ASHA patches. This is not
a vanilla upstream-kernel hardware test or a post-boot throughput trace.
- Broader suspend/resume, battery, other-profile and native concurrency
coverage is not claimed. An earlier brief quality drop is not assigned
a cause or declared independently resolved.
Validation on Bluetooth upstream base 185c11232d5f:
- Four native Bluetooth objects compile with W=1 on an x86_64 diagnostic
configuration (Rust disabled); this is not a full upstream kernel build.
- 37 source-extracted ASan/UBSan controls pass, with unpatched baseline
and negative controls. These are not in-kernel concurrency tests.
- Equivalent backports pass the same controls on pinned 7.2.y, 6.18.y
and 6.12.y snapshots. The 6.18.y and 6.12.y variants also compile the
four native objects with W=1. No older-kernel hardware boot is claimed.
The firmware gate uses the selected image header. If setup reuses an
already-resident image, this is not an independent read of that image's
identity; the observed boot log matches the tested image. Please review
this limitation before widening the match to other firmware or devices.
Stable consideration: this fixes a real firmware interoperability issue,
but the change exceeds the usual 100-line-with-context stable guideline.
Please assess stable suitability after upstream review. No introducing
kernel commit has been established, so no speculative Fixes tag is added.
The 6.18.y backport uses the older connection-feature layout. The 6.12.y
backport also uses scalar quirks and checks queued duplicates before
taking a reference, since queue_once() there returns zero for duplicates.
Backport application/API differences and checks are documented locally;
no stable acceptance, older-kernel hardware test or public issue closure
is claimed. Please do not queue an unreviewed backport directly.
drivers/bluetooth/btmtk.c | 29 ++++++++++++++++
include/net/bluetooth/hci.h | 6 ++++
include/net/bluetooth/hci_sync.h | 1 +
net/bluetooth/hci_event.c | 8 +++++
net/bluetooth/hci_sync.c | 58 ++++++++++++++++++++++++++++++++
5 files changed, 102 insertions(+)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index 75483d823..cab5c4c36 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c
@@ -47,6 +47,24 @@ struct btmtk_global_desc {
__le32 section_num;
} __packed;
+static bool btmtk_needs_manual_dle(struct hci_dev *hdev, u32 dev_id,
+ const struct btmtk_patch_header *hdr)
+{
+ static const u8 affected_build[16] = "20260724143815";
+ struct btmtk_data *data;
+
+ if (hdev->bus != HCI_USB || dev_id != 0x7922)
+ return false;
+
+ data = hci_get_priv(hdev);
+ if (!data->udev)
+ return false;
+
+ return le16_to_cpu(data->udev->descriptor.idVendor) == 0x0489 &&
+ le16_to_cpu(data->udev->descriptor.idProduct) == 0xe0f6 &&
+ !memcmp(hdr->datetime, affected_build, sizeof(affected_build));
+}
+
struct btmtk_section_map {
__le32 sectype;
__le32 secoffset;
@@ -219,6 +237,9 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname,
u32 section_num, dl_size, section_offset;
u8 cmd[64];
+ /* Re-evaluate the firmware-specific workaround on every setup. */
+ hci_clear_quirk(hdev, HCI_QUIRK_NEEDS_LE_SET_DATA_LEN);
+
err = request_firmware(&fw, fwname, &hdev->dev);
if (err < 0) {
bt_dev_err(hdev, "Failed to load firmware file (%d)", err);
@@ -347,6 +368,14 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname,
/* Wait a few moments for firmware activation done */
usleep_range(100000, 120000);
+ /* This firmware accepts maximum suggested defaults but needs an
+ * explicit per-link request to negotiate a larger TX data length.
+ */
+ if (btmtk_needs_manual_dle(hdev, dev_id, hdr)) {
+ hci_set_quirk(hdev, HCI_QUIRK_NEEDS_LE_SET_DATA_LEN);
+ bt_dev_info(hdev, "Using explicit LE data length requests");
+ }
+
err_release_fw:
release_firmware(fw);
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 1c992e875..513ca226d 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -340,6 +340,12 @@ enum {
*/
HCI_QUIRK_BROKEN_LE_CODED,
+ /* The controller needs an explicit LE Set Data Length request for
+ * each central-role connection despite accepting suggested defaults.
+ * Set during the vendor setup callback for affected firmware only.
+ */
+ HCI_QUIRK_NEEDS_LE_SET_DATA_LEN,
+
/*
* When this quirk is set, the HCI_OP_READ_ENC_KEY_SIZE command is
* skipped during an HCI_EV_ENCRYPT_CHANGE event. This is required
diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index 59cb918d1..5a37d3e50 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -194,3 +194,4 @@ int hci_le_read_remote_features(struct hci_conn *conn);
int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type);
int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys);
+int hci_le_set_data_len(struct hci_conn *conn);
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 56ad10ab9..d78f330e3 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -6875,6 +6875,14 @@ static void hci_le_remote_feat_complete_evt(struct hci_dev *hdev, void *data,
conn->le_tx_def_phys &= ~HCI_LE_SET_PHY_CODED;
conn->le_rx_def_phys &= ~HCI_LE_SET_PHY_CODED;
}
+
+ if (hci_test_quirk(hdev, HCI_QUIRK_NEEDS_LE_SET_DATA_LEN) &&
+ conn->role == HCI_ROLE_MASTER &&
+ (hdev->commands[33] & BIT(6)) &&
+ (hdev->le_features[0] & HCI_LE_DATA_LEN_EXT) &&
+ (conn->le_features[0] & HCI_LE_DATA_LEN_EXT) &&
+ hdev->le_def_tx_len > 27)
+ hci_le_set_data_len(conn);
}
if (conn->state == BT_CONFIG) {
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 7ac26daba..f919249e9 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -8152,3 +8152,61 @@ int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
return (err == -EEXIST) ? 0 : err;
}
+
+static void le_set_data_len_complete(struct hci_dev *hdev, void *data, int err)
+{
+ struct hci_conn *conn = data;
+
+ bt_dev_dbg(hdev, "err %d", err);
+ if (err && err != -ECANCELED && err != -ENODEV)
+ bt_dev_warn(hdev, "Failed to set LE data length (%d)", err);
+ hci_conn_put(conn);
+}
+
+static int hci_le_set_data_len_sync(struct hci_dev *hdev, void *data)
+{
+ struct hci_conn *conn = data;
+ struct hci_cp_le_set_data_len cp;
+
+ hci_dev_lock(hdev);
+
+ if (!hci_conn_valid(hdev, conn) || conn->type != LE_LINK ||
+ conn->role != HCI_ROLE_MASTER ||
+ (conn->state != BT_CONNECTED && conn->state != BT_CONFIG)) {
+ hci_dev_unlock(hdev);
+ return -ECANCELED;
+ }
+
+ /* Use the current suggested defaults, including later host changes. */
+ if (!hci_test_quirk(hdev, HCI_QUIRK_NEEDS_LE_SET_DATA_LEN) ||
+ !(hdev->commands[33] & BIT(6)) ||
+ !(hdev->le_features[0] & HCI_LE_DATA_LEN_EXT) ||
+ !(conn->le_features[0] & HCI_LE_DATA_LEN_EXT) ||
+ hdev->le_def_tx_len <= 27) {
+ hci_dev_unlock(hdev);
+ return 0;
+ }
+
+ cp.handle = cpu_to_le16(conn->handle);
+ cp.tx_len = cpu_to_le16(hdev->le_def_tx_len);
+ cp.tx_time = cpu_to_le16(hdev->le_def_tx_time);
+
+ hci_dev_unlock(hdev);
+
+ return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DATA_LEN,
+ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+int hci_le_set_data_len(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+ int err;
+
+ err = hci_cmd_sync_queue_once(hdev, hci_le_set_data_len_sync,
+ hci_conn_get(conn),
+ le_set_data_len_complete);
+ if (err)
+ hci_conn_put(conn);
+
+ return (err == -EEXIST) ? 0 : err;
+}
base-commit: 185c11232d5f586489bb3b74e6bcc7a6560ddd40
next reply other threads:[~2026-10-16 17:02 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 17:01 Kyle Pepper [this message]
2026-10-16 18:01 ` [PATCH] Bluetooth: request LE data length for affected MT7922 firmware Luiz Augusto von Dentz
2026-10-16 21:36 ` cochcoder
2026-10-16 18:58 ` 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=20261016170122.27119-1-cochcoder@proton.me \
--to=cochcoder@proton.me \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=sean.wang@mediatek.com \
/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.