net/mac80211/tx.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-)
Commit 0a44dfc07074 ("wifi: mac80211: simplify non-chanctx drivers")
removed the fallback path in ieee80211_monitor_start_xmit() for when
the monitor interface has no channel context assigned. This broke frame
capture and injection for drivers that implement real channel context
ops (as opposed to the ieee80211_emulate_* helpers), such as the mt76
family, when a monitor interface runs alongside another interface
(e.g. managed mode).
In that scenario the (virtual) monitor sdata does not get a chanctx of
its own, even though there is an active one from the other interface.
Before the simplification the code fell back to local->_oper_chandef;
after it, the code goes straight to fail_rcu and silently drops every
injected frame.
Commit d594cc6f2c58 ("wifi: mac80211: restore non-chanctx injection
behaviour") restored the fallback for drivers using emulate_chanctx,
but explicitly left real chanctx drivers unfixed.
Fix this by falling back to the first entry in local->chanctx_list
when the monitor vif has no chanctx and the driver uses real channel
contexts. This is analogous to how ieee80211_hw_conf_chan() already
uses the same pattern.
Tested on MT7921AU (mt76) USB adapter:
- v6.13: managed + monitor coexistence restored (0 -> 37 frames/5s)
- v6.19: managed + monitor coexistence restored (0 -> 39 frames/5s)
- v7.0-rc2: managed + monitor coexistence restored (0 -> 33 frames/5s)
Cc: stable@vger.kernel.org
Fixes: 0a44dfc07074 ("wifi: mac80211: simplify non-chanctx drivers")
Link: https://github.com/morrownr/USB-WiFi/issues/682
Signed-off-by: 傅继晗 <fjhhz1997@gmail.com>
---
net/mac80211/tx.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 8cdbd41..56eaf9a 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2396,12 +2396,28 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
rcu_dereference(tmp_sdata->vif.bss_conf.chanctx_conf);
}
- if (chanctx_conf)
+ if (chanctx_conf) {
chandef = &chanctx_conf->def;
- else if (local->emulate_chanctx)
+ } else if (local->emulate_chanctx) {
chandef = &local->hw.conf.chandef;
- else
- goto fail_rcu;
+ } else {
+ /*
+ * For real chanctx drivers (e.g. mt76), the monitor
+ * interface may not have a chanctx assigned when running
+ * concurrently with another interface. Fall back to any
+ * active chanctx so that injection can still work on the
+ * operating channel.
+ */
+ struct ieee80211_chanctx *ctx;
+
+ ctx = list_first_entry_or_null(&local->chanctx_list,
+ struct ieee80211_chanctx,
+ list);
+ if (ctx)
+ chandef = &ctx->conf.def;
+ else
+ goto fail_rcu;
+ }
/*
* If driver/HW supports IEEE80211_CHAN_CAN_MONITOR we still
--
2.43.0
On Sun, 2026-03-08 at 16:45 +0000, 傅继晗 wrote:
> Commit 0a44dfc07074 ("wifi: mac80211: simplify non-chanctx drivers")
> removed the fallback path in ieee80211_monitor_start_xmit() for when
> the monitor interface has no channel context assigned. This broke frame
> capture and injection for drivers that implement real channel context
> ops (as opposed to the ieee80211_emulate_* helpers), such as the mt76
> family, when a monitor interface runs alongside another interface
> (e.g. managed mode).
It actually broke the others too, as you note later.
> In that scenario the (virtual) monitor sdata does not get a chanctx of
> its own, even though there is an active one from the other interface.
> Before the simplification the code fell back to local->_oper_chandef;
> after it, the code goes straight to fail_rcu and silently drops every
> injected frame.
>
> Commit d594cc6f2c58 ("wifi: mac80211: restore non-chanctx injection
> behaviour") restored the fallback for drivers using emulate_chanctx,
> but explicitly left real chanctx drivers unfixed.
>
> Fix this by falling back to the first entry in local->chanctx_list
> when the monitor vif has no chanctx and the driver uses real channel
> contexts. This is analogous to how ieee80211_hw_conf_chan() already
> uses the same pattern.
I did have pretty much the same attempt at a fix:
https://lore.kernel.org/linux-wireless/20251216111909.25076-2-johannes@sipsolutions.net/
but it was reported to cause crashes on certain devices, so we didn't
think it was very safe at the time.
Is that no longer an issue?
johannes
On Sun, 2026-03-08 at 16:45 +0000, 傅继晗 wrote:
> Fix this by falling back to the first entry in local->chanctx_list
> when the monitor vif has no chanctx and the driver uses real channel
> contexts. This is analogous to how ieee80211_hw_conf_chan() already
> uses the same pattern.
On Mon, 2026-03-09, Johannes Berg wrote:
> I did have pretty much the same attempt at a fix:
> https://lore.kernel.org/linux-wireless/20251216111909.25076-2-johannes@sipsolutions.net/
>
> but it was reported to cause crashes on certain devices, so we didn't
> think it was very safe at the time.
>
> Is that no longer an issue?
Hi Johannes,
Thanks for the quick review and for pointing me to your earlier v2
patch.
I see the key difference between our approaches: your v2 iterates
the chanctx_list and only proceeds when there is exactly one entry
(going to fail_rcu if more than one exists), while mine blindly takes
the first entry via list_first_entry_or_null(). Your approach is
clearly safer -- in a multi-chanctx scenario, there is no way to know
which channel the user intends to inject on, so refusing is the
correct behaviour.
I have tested my patch on an MT7921AU (mt76, USB) adapter across
v6.13, v6.19, and v7.0-rc2 with managed + monitor coexistence, and
have not observed any crashes. However, my testing was limited to a
single-chanctx scenario (one managed interface + one monitor
interface), so it does not rule out crashes in multi-chanctx
configurations.
Could you share some details about the crashes that were reported
with your v2? For example, which devices/drivers were affected and
what the crash signature looked like? That would help me understand
whether the issue was specific to multi-chanctx usage or something
more fundamental with accessing the chanctx_list in this code path.
If you agree, I would like to send a v2 that combines both approaches:
use list_first_entry_or_null() for simplicity, but add a
list_is_singular() guard so we only proceed when there is exactly one
chanctx -- matching the safety constraint from your v2:
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2399,10 +2399,24 @@
- if (chanctx_conf)
+ if (chanctx_conf) {
chandef = &chanctx_conf->def;
- else if (local->emulate_chanctx)
+ } else if (local->emulate_chanctx) {
chandef = &local->hw.conf.chandef;
- else
- goto fail_rcu;
+ } else {
+ struct ieee80211_chanctx *ctx;
+
+ ctx = list_first_entry_or_null(&local->chanctx_list,
+ struct ieee80211_chanctx,
+ list);
+ if (ctx && list_is_singular(&local->chanctx_list))
+ chandef = &ctx->conf.def;
+ else
+ goto fail_rcu;
+ }
This avoids the ambiguity of picking an arbitrary chanctx in
multi-chanctx scenarios while still fixing the common single-chanctx
case (e.g. one managed + one monitor interface).
Alternatively, if you would prefer to revive your own patch, I am
happy to help test it on mt76 hardware.
Thanks,
Jihan
On Mon, 2026-03-09 at 10:45 +0000, 傅继晗 wrote:
>
> I see the key difference between our approaches: your v2 iterates
> the chanctx_list and only proceeds when there is exactly one entry
> (going to fail_rcu if more than one exists), while mine blindly takes
> the first entry via list_first_entry_or_null(). Your approach is
> clearly safer -- in a multi-chanctx scenario, there is no way to know
> which channel the user intends to inject on, so refusing is the
> correct behaviour.
Oh, right, I hadn't even realised that at first.
> I have tested my patch on an MT7921AU (mt76, USB) adapter across
> v6.13, v6.19, and v7.0-rc2 with managed + monitor coexistence, and
> have not observed any crashes. However, my testing was limited to a
> single-chanctx scenario (one managed interface + one monitor
> interface), so it does not rule out crashes in multi-chanctx
> configurations.
Maybe Óscar can comment on which device/version he tested and got the
crash with? I just would like to avoid having crashes because of this,
but generally think that - perhaps optionally - we could have code like
this, since people _do_ want injection to work.
> Could you share some details about the crashes that were reported
> with your v2? For example, which devices/drivers were affected and
> what the crash signature looked like? That would help me understand
> whether the issue was specific to multi-chanctx usage or something
> more fundamental with accessing the chanctx_list in this code path.
No, it was specific to some driver implementation, but I don't have any
more information now.
> If you agree, I would like to send a v2 that combines both approaches:
> use list_first_entry_or_null() for simplicity, but add a
> list_is_singular() guard so we only proceed when there is exactly one
> chanctx -- matching the safety constraint from your v2:
>
> --- a/net/mac80211/tx.c
> +++ b/net/mac80211/tx.c
> @@ -2399,10 +2399,24 @@
> - if (chanctx_conf)
> + if (chanctx_conf) {
> chandef = &chanctx_conf->def;
> - else if (local->emulate_chanctx)
> + } else if (local->emulate_chanctx) {
> chandef = &local->hw.conf.chandef;
> - else
> - goto fail_rcu;
> + } else {
> + struct ieee80211_chanctx *ctx;
> +
> + ctx = list_first_entry_or_null(&local->chanctx_list,
> + struct ieee80211_chanctx,
> + list);
> + if (ctx && list_is_singular(&local->chanctx_list))
> + chandef = &ctx->conf.def;
> + else
> + goto fail_rcu;
> + }
>
> This avoids the ambiguity of picking an arbitrary chanctx in
> multi-chanctx scenarios while still fixing the common single-chanctx
> case (e.g. one managed + one monitor interface).
Seems reasonable, I think we could even drop the "if (emulate) part
(since in that case the list should always be singular). Just like I
said above - would like to understand the issue that had appeared with
it.
johannes
© 2016 - 2026 Red Hat, Inc.