MT7925 (0e8d:0717): HFP microphone unusable — firm ware delivers zero-filled (e)SCO frames on sentinel handle 0 x0E00
Daniel Minchev <[email protected]>
| Newsgroups | org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <CAPCBbk7t6OKtOGynNhf6oGnxGrSEqkh-SfweO_Yfms5NXDSCZQ@mail.gmail.com> |
Hi,
I am reporting a hands-free (HFP) microphone failure on the MediaTek MT7925
(RZ717) USB Bluetooth controller, with evidence that points at the
controller firmware's synchronous-audio receive path. I can reproduce this
at will and am happy to test patches or capture further traces.
Hardware / software
-------------------
- Controller: MediaTek MT7925 (RZ717), USB 0e8d:0717, on an ASRock X870
Pro RS WiFi desktop board (module is the stock M.2 combo card)
- Kernel: 7.2.0-1-cachyos (happy to reproduce on mainline if needed)
- BT firmware: BT_RAM_CODE_MT7925_1_1_hdr.bin from linux-firmware
20260810 (build 20260605...); 20260622 behaves identically
- BlueZ 5.87, PipeWire 1.6.8 / WirePlumber 0.5.15 (native HFP backend)
- Failing headset: Huawei FreeClip 2 (BT 6.0, LE-Audio-first design,
classic HFP fallback)
- Control headset: Motorola moto buds+ — mSBC HFP microphone works
perfectly on the same adapter and stack
- The failing headset's microphone works fine on macOS (Broadcom
controller) and on phones, so the headset side is functional
Symptom (stock kernel)
----------------------
A2DP works. On switching to the HFP profile, codec negotiation succeeds
(AT+BCS=2), Setup Synchronous Connection completes with Status: Success
(eSCO, Air mode Transparent, 60-byte packets), but no microphone audio
ever reaches userspace. dmesg fills with:
Bluetooth: hci0: corrupted SCO packet
Bluetooth: hci0: SCO packet for unknown connection handle 3584
Bluetooth: hci0: ACL packet for unknown connection handle 3837
Note 3584 = 0x0E00 and 3837 = 0x0EFD. The 0x0EFD ACL traffic matches what
was identified in bugzilla #220564 as firmware-generated diag data on a
pseudo-handle; the SCO flavor appears to be unhandled.
Analysis (btmon capture attached)
---------------------------------
The attached btsnoop capture shows the defect at its origin: after Setup
Synchronous Connection on the BR-ACL link (handle 51), a retried setup is
rejected with Command Status 0x20 (Unsupported LMP Parameter Value), and
then the controller delivers:
> HCI Event: Synchronous Connect Complete
Status: Success (0x00)
Handle: 3584 <-- 0x0E00, invalid/sentinel
Link type: eSCO, Air mode: Transparent, RX/TX packet length: 60
i.e. the firmware reports a successful eSCO connection on the bogus
sentinel handle 0x0E00 in the completion event itself. The host cannot
associate that handle, and all subsequent incoming SCO data (tagged with
the same 0x0E00) is dropped. In earlier captures on the same setup I also
force-delivered those frames to userspace via the attached
handle-rewrite experiment: every frame is exactly 60 bytes of 0x00 — not
valid mSBC (which always contains sync/header structure even for encoded
silence) — so even when accepted, the stream carries no microphone data.
CVSD behaves slightly differently: the link sometimes carries real audio
briefly (a lucky first attempt after a fresh reconnect), then degrades to
the same zero/drop behavior. mSBC (Transparent air mode) has never
delivered a single non-zero frame across several dozen attempts. This
asymmetry suggests the Transparent-mode RX path in firmware is broken
outright while the firmware-decoded CVSD path is merely unreliable.
Ruled out / experiments performed
---------------------------------
- linux-firmware 20260622 vs 20260810: no difference
- bluetooth.disable_esco=1: no improvement
- multipoint/dual-device disabled; headset on latest firmware
- Delaying cleanup of outgoing SCO/eSCO conns pending in BT_CONNECT
(hci_conn_drop currently schedules immediate cleanup for SCO/eSCO;
since there is no HCI command to cancel a pending Setup Synchronous
Connection, an early drop races the controller's completion and orphans
the eSCO handle — separate issue, but it reliably worsens this one):
with a 2s delay the transport becomes stable, but payloads remain zeros.
- Removing the blanket HCI_QUIRK_BROKEN_ENHANCED_SETUP_SYNC_CONN for the
MTK block in btusb_probe() (cf. bugzilla #221722): Enhanced Setup
Synchronous Connection is then used and completes successfully — but
incoming frames are still zero-filled on handle 0x0E00.
- Rewriting the bogus handle in btusb_recv_isoc() to the active eSCO
conn's handle (analogous to "Bluetooth: btrtl: Fixup SCO header"):
frames are then delivered to userspace, confirming the payloads are
zeros end-to-end. Patch attached for reference.
Related reports (this looks like one firmware defect seen from
different angles):
- bugzilla #221722 — MT7925 HFP mSBC produces all-zero samples
- bugzilla #220564 — MTK firmware emits ACL on pseudo-handle 0x0EFD
(confirmed by MediaTek as firmware diag traffic)
- bluez/bluez#2067 — MT7925 LE Audio duplex collapses with "ISO packet
for unknown connection handle 0" (same RX-direction pattern on ISO)
- The unmerged "Bluetooth: btusb: deal with MT7925 invalid ISO RX
packets" thread describes the ISO twin of this behavior
Questions / requests
--------------------
1. To MediaTek: can the (e)SCO RX path (particularly Transparent air
mode) be looked at in the MT7925 BT firmware? I can provide any
captures or run any diagnostics needed.
2. Should btmtk grow a SCO-handle validation/fixup analogous to btrtl's,
or should the 0x0Exx pseudo-handles be routed to hci_recv_diag() on
the isoc path as is already done for some pseudo-handles on the ACL
path?
3. Is the blanket BROKEN_ENHANCED_SETUP_SYNC_CONN quirk for all MediaTek
devices still appropriate given #221722 and the behavior above?
Attachments: btmon btsnoop capture of a failing HFP session, dmesg
excerpt, version details, and the two experiment patches.
Thanks,
Daniel
dmesg-excerpt.txt
(text/plain, 1.9 KB)
[ 4049.312813] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 4837.876048] Bluetooth: hci0: HW/SW Version: 0x00000000, Build Time: 20260605184935 [ 4839.705320] Bluetooth: hci0: Device setup in 1798217 usecs [ 4839.705324] Bluetooth: hci0: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported. [ 4839.873947] Bluetooth: hci0: AOSP extensions version v1.00 [ 4839.873952] Bluetooth: hci0: AOSP quality report is supported [ 4839.874037] Bluetooth: MGMT ver 1.23 [ 5082.161716] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 5607.557576] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 5667.557110] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7709.834421] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7712.989356] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7723.363184] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7752.541768] Bluetooth: hci0: SCO packet for unknown connection handle 3584 [ 7752.541771] Bluetooth: hci0: SCO packet for unknown connection handle 3584 [ 7752.541774] Bluetooth: hci0: SCO packet for unknown connection handle 3584 [ 7752.541780] Bluetooth: hci0: SCO packet for unknown connection handle 3584 [ 7752.859654] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7808.681714] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7897.766743] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 7993.031340] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 8053.030415] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 8113.030349] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [ 8148.535198] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [11702.675999] Bluetooth: hci0: ACL packet for unknown connection handle 3837 [11978.506486] Bluetooth: hci0: ACL packet for unknown connection handle 3837
mt7925-freeclip2-hfp-fail.btsnoop
(application/octet-stream, 52.5 KB) - not displayed
sysinfo.txt
(text/plain, 805 B)
+ uname -a Linux uldixxion 7.2.0-1-cachyos #1 SMP PREEMPT_DYNAMIC Thu, 20 Aug 2026 06:06:30 +0000 x86_64 GNU/Linux + echo + pacman -Q linux-firmware-mediatek bluez pipewire wireplumber linux-firmware-mediatek 1:20260810-2 bluez 5.87-2.1 pipewire 1:1.6.8-1.2 wireplumber 0.5.15-1.1 + lsusb + grep -i mediatek Bus 008 Device 006: ID 0e8d:0717 MediaTek Inc. Wireless_Device + lspci -nn + grep -i mediatek 09:00.0 Network controller [0280]: MEDIATEK Corp. MT7925 (RZ717) Wi-Fi 7 160MHz [14c3:0717] + journalctl -k -b --no-pager + grep -iE 'mt7925.*(Version|Build)' + head -4 авг 24 15:48:30 uldixxion kernel: mt7925e 0000:09:00.0: HW/SW Version: 0x8a108a10, Build Time: 20260605184651a авг 24 15:48:30 uldixxion kernel: mt7925e 0000:09:00.0: WM Firmware Version: ____000000, Build Time: 20260605184805
0002-btusb-mtk-sco-handle-fixup.patch
(text/x-patch, 1.5 KB)
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -1412,6 +1412,36 @@
}
}
+/* bluetooth-mic-fix: MediaTek MT79xx firmware tags incoming (e)SCO frames
+ * with a bogus sentinel handle (e.g. 0x0e00) instead of the real connection
+ * handle, so every microphone frame is dropped as "unknown connection".
+ * Analogous to btrtl's "Fixup SCO header": when there is exactly one
+ * connected SCO/eSCO link, rewrite the handle instead of dropping the frame.
+ */
+static bool btusb_mtk_fixup_sco_handle(struct hci_dev *hdev,
+ struct hci_sco_hdr *hdr)
+{
+ struct hci_conn_hash *h = &hdev->conn_hash;
+ struct hci_conn *c, *sco = NULL;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(c, &h->list, list) {
+ if ((c->type == SCO_LINK || c->type == ESCO_LINK) &&
+ c->state == BT_CONNECTED) {
+ if (sco) {
+ sco = NULL; /* more than one: ambiguous */
+ break;
+ }
+ sco = c;
+ }
+ }
+ if (sco)
+ hdr->handle = __cpu_to_le16(sco->handle);
+ rcu_read_unlock();
+
+ return sco;
+}
+
static int btusb_recv_isoc(struct btusb_data *data, void *buffer, int count)
{
struct sk_buff *skb;
@@ -1449,7 +1479,9 @@
hci_skb_expect(skb) = hdr->dlen;
if (skb_tailroom(skb) < hci_skb_expect(skb) ||
- !btusb_validate_sco_handle(data->hdev, hdr)) {
+ (!btusb_validate_sco_handle(data->hdev, hdr) &&
+ !(data->recv_acl == btmtk_usb_recv_acl &&
+ btusb_mtk_fixup_sco_handle(data->hdev, hdr)))) {
kfree_skb(skb);
skb = NULL;