I went through the mt7996 patch in this series line by line — here’s a verification and a couple of notes. (I’ll leave the BCM84891 SFP series to someone with that module in hand.)
What it does
Two one-line guards in mt7996_tx_prepare_skb(), both adding a sta NULL-check before the sta->mlo dereference:
- ... && sta->mlo && ...
+ ... && sta && sta->mlo && ...
The function is reachable with sta == NULL — group-addressed / multicast data scheduled from a vif txq in AP mode, and frames on the global wcid, carry no station pointer. The MLO link_id selection added later dereferenced sta->mlo unconditionally, so such a frame faulted:
Unable to handle kernel read from unreadable memory at
virtual address 000000000000001b
pc : mt7996_tx_prepare_skb+0x2a0/0x570 [mt7996e]
(0x1b is NULL plus the offset of the mlo field — a textbook NULL deref.)
Verdict: correct and complete.
I checked every sta / msta dereference in the whole function, not just the two touched lines:
msta is already derived safely — msta = sta ? (struct mt7996_sta *)sta->drv_priv : NULL; — and is only used inside the sta && sta->mlo block, so the guard covers it too.
- The wcid-selection block is gated on
if (msta) / else if (mvif) — fine.
- The strongest argument that
sta == NULL is an expected input: the tail of the same function already had txp->fw.rept_wds_wcid = cpu_to_le16(sta ? wcid->idx : 0xfff);. So the function was always meant to handle a NULL sta — the MLO link_id logic simply broke that invariant, and this patch restores it. Right fix, right place.
Minor, non-blocking: the software MLD-translation block dereferences vif (vif->link_conf[...], vif->addr) without an explicit vif check. In practice a non-NULL sta implies a non-NULL vif, and this predates the patch, so it isn’t a regression — just noting it for completeness.
Which trees this actually applies to
Worth flagging, since the impact is easy to mis-estimate:
- Stock kernel 6.12 (in-tree mt76) doesn’t carry the MLO
link_id selection code at all → not affected; the patch is moot there.
- This 7.1-main mt76 has both blocks unguarded → the patch applies in full (both commits needed).
- The mt76 2026.03.05 snapshot (what current MTK-SDK builds pull) already has the
link_id block guarded (sta && sta->mlo); there, effectively only the second commit (EAPOL / MLD-translation block) is still missing.
Concretely on the MTK-SDK snapshot: the fault actually reported in the commit message is on the link_id path — group-addressed / multicast data in AP mode — and on that snapshot the block is already guarded (sta && sta->mlo, plus the matching id == -EBUSY && sta check). So the observed crash is already covered there. The only block still unguarded is the second commit’s EAPOL / MLD-translation path, and reaching it requires an EAPOL frame with a NULL sta — which doesn’t arise in normal operation, since EAPOL is always unicast to an associated station. So on that snapshot the patch closes a real-but-not-observed gap rather than the crash people actually hit.
One thing worth aligning — the description.
The commit messages are accurate (they describe the NULL-sta deref precisely, with the trace). But the summary posted for the series calls it “the crash caused by the PCI reset on the mt7996, and possibly attempts a restart.” That doesn’t match the code: there’s no PCI reset and no restart here — it’s purely a NULL-pointer guard in the TX path. Worth syncing the wording so nobody reads it as a reset/recovery fix. In particular it’s unrelated to the MT7996 stuck firmware-semaphore issue — that one is a hardware lock inside the MCU that needs a GPIO reset line, a different failure at a different layer entirely.
Net: the mt7996 patch is a clean, correctly-scoped fix and is safe to take. Thanks @meehien for chasing it down, and @frank-w for posting it for review.