Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: request LE data length for affected MT7922 firmware
@ 2026-10-16 17:01 Kyle Pepper
  2026-10-16 18:01 ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 3+ messages in thread
From: Kyle Pepper @ 2026-10-16 17:01 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: luiz.dentz, marcel, sean.wang, linux-mediatek, linux-kernel

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



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] Bluetooth: request LE data length for affected MT7922 firmware
  2026-10-16 17:01 [PATCH] Bluetooth: request LE data length for affected MT7922 firmware Kyle Pepper
@ 2026-10-16 18:01 ` Luiz Augusto von Dentz
  2026-10-16 21:36   ` cochcoder
  0 siblings, 1 reply; 3+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-16 18:01 UTC (permalink / raw)
  To: Kyle Pepper
  Cc: linux-bluetooth, marcel, sean.wang, linux-mediatek, linux-kernel

Hi Kyle,

On Fri, Oct 16, 2026 at 1:01 PM Kyle Pepper <cochcoder@proton.me> wrote:
>
> 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

Perhaps before we go into the whole trouble of adding a quirk we
should check with MediaTek folks if there is a fix planned for this,
since this is actually a firmware bug. If there isn't one and a fix
would take time to be validated, I'd proceed with the quirk but I
don't think gating it on a specific build is a good idea; there might
be other firmware releases affected by this, unless MediaTek can
confirm this is exclusive to a single image in which case we should
treat it as a regression.

-- 
Luiz Augusto von Dentz


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] Bluetooth: request LE data length for affected MT7922 firmware
  2026-10-16 18:01 ` Luiz Augusto von Dentz
@ 2026-10-16 21:36   ` cochcoder
  0 siblings, 0 replies; 3+ messages in thread
From: cochcoder @ 2026-10-16 21:36 UTC (permalink / raw)
  To: sean.wang@mediatek.com
  Cc: linux-bluetooth, marcel, linux-mediatek, linux-kernel,
	luiz.dentz@gmail.com

Hi Luiz, Sean and the MediaTek team,

Thanks for your review Luiz. Sean/MediaTek team, could you help confirm whether a firmware fix is planned for this MT7922 behavior?

On my ASUS G614PR, with the Bluetooth USB ID 0489:e0f6 and firmware build 20260724143815, the controller accepts the maximum LE suggested default data lengths, but ASHA playback remains severely choppy. To the point that it's unusable due to only hearing a half second of audio every second or so. 

Sending an explicit LE Set Data Length request on the same connection produces a Data Length Change event reporting TX 251 octets/2120 µs. Audio delivery then rises from about 21.5 to 50 SDUs/s, and peak outstanding packets fall from 32 to 3.

Is this a known firmware issue? Do you happen to know which firmware versions and devices are affected? And does there happen to be an expected timeline for a fix?

I haven't established if other versions are affected as well. I'll wait for your guidance before revising the quirk scope.

Thanks,
Kyle




On Friday, October 16th, 2026 at 2:02 PM, Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote:

> Hi Kyle,
> 
> On Fri, Oct 16, 2026 at 1:01 PM Kyle Pepper <cochcoder@proton.me> wrote:
> >
> > 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
> 
> Perhaps before we go into the whole trouble of adding a quirk we
> should check with MediaTek folks if there is a fix planned for this,
> since this is actually a firmware bug. If there isn't one and a fix
> would take time to be validated, I'd proceed with the quirk but I
> don't think gating it on a specific build is a good idea; there might
> be other firmware releases affected by this, unless MediaTek can
> confirm this is exclusive to a single image in which case we should
> treat it as a regression.
> 
> --
> Luiz Augusto von Dentz
>


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-16 21:36 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 17:01 [PATCH] Bluetooth: request LE data length for affected MT7922 firmware Kyle Pepper
2026-10-16 18:01 ` Luiz Augusto von Dentz
2026-10-16 21:36   ` cochcoder

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox