Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

wifi: mac80211: handle WLAN_HT_ACTION_NOTIFY_CHANWIDTH async

If this action frame, with the value of IEEE80211_HT_CHANWIDTH_ANY,
arrives right after a beacon that changed the operational bandwidth from
20 MHz to 40 MHz, then updating the rate control bandwidth to 40 can
race with updating the chanctx width (that happens in the beacon
proccesing) back to 40 MHz:

cpu0 cpu1

ieee80211_rx_mgmt_beacon
ieee80211_config_bw
ieee80211_link_change_chanreq
(*)ieee80211_link_update_chanreq
ieee80211_rx_h_action
(**)ieee80211_sta_cur_vht_bw
(***) ieee80211_recalc_chanctx_chantype

in (**), the maximum between the capability width and the bss width is
returned. But the bss width was just updated to 40 in (*),
so the action frame handling code will increase the width of the rate
control before the chanctx was increased (in ***), leading to a FW error
(at least in iwlwifi driver. But this is wrong regardless).

Fix this by simply handling the action frame async, so it won't race
with the beacon proccessing.

Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218632
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250709233537.bb9dc6f36c35.I39782d6077424e075974c3bee4277761494a1527@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Miri Korenblit and committed by
Johannes Berg
93370f2d 6ee152b0

+80 -30
+39 -1
net/mac80211/ht.c
··· 9 9 * Copyright 2007, Michael Wu <flamingice@sourmilk.net> 10 10 * Copyright 2007-2010, Intel Corporation 11 11 * Copyright 2017 Intel Deutschland GmbH 12 - * Copyright(c) 2020-2024 Intel Corporation 12 + * Copyright(c) 2020-2025 Intel Corporation 13 13 */ 14 14 15 15 #include <linux/ieee80211.h> ··· 603 603 } 604 604 /* this might change ... don't want non-open drivers using it */ 605 605 EXPORT_SYMBOL_GPL(ieee80211_request_smps); 606 + 607 + void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local, 608 + struct ieee80211_sub_if_data *sdata, 609 + struct sta_info *sta, 610 + struct link_sta_info *link_sta, 611 + u8 chanwidth, enum nl80211_band band) 612 + { 613 + enum ieee80211_sta_rx_bandwidth max_bw, new_bw; 614 + struct ieee80211_supported_band *sband; 615 + struct sta_opmode_info sta_opmode = {}; 616 + 617 + lockdep_assert_wiphy(local->hw.wiphy); 618 + 619 + if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ) 620 + max_bw = IEEE80211_STA_RX_BW_20; 621 + else 622 + max_bw = ieee80211_sta_cap_rx_bw(link_sta); 623 + 624 + /* set cur_max_bandwidth and recalc sta bw */ 625 + link_sta->cur_max_bandwidth = max_bw; 626 + new_bw = ieee80211_sta_cur_vht_bw(link_sta); 627 + 628 + if (link_sta->pub->bandwidth == new_bw) 629 + return; 630 + 631 + link_sta->pub->bandwidth = new_bw; 632 + sband = local->hw.wiphy->bands[band]; 633 + sta_opmode.bw = 634 + ieee80211_sta_rx_bw_to_chan_width(link_sta); 635 + sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED; 636 + 637 + rate_control_rate_update(local, sband, link_sta, 638 + IEEE80211_RC_BW_CHANGED); 639 + cfg80211_sta_opmode_change_notify(sdata->dev, 640 + sta->addr, 641 + &sta_opmode, 642 + GFP_KERNEL); 643 + }
+6
net/mac80211/ieee80211_i.h
··· 2204 2204 enum nl80211_smps_mode 2205 2205 ieee80211_smps_mode_to_smps_mode(enum ieee80211_smps_mode smps); 2206 2206 2207 + void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local, 2208 + struct ieee80211_sub_if_data *sdata, 2209 + struct sta_info *sta, 2210 + struct link_sta_info *link_sta, 2211 + u8 chanwidth, enum nl80211_band band); 2212 + 2207 2213 /* VHT */ 2208 2214 void 2209 2215 ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
+29
net/mac80211/iface.c
··· 1557 1557 } 1558 1558 } 1559 1559 } else if (ieee80211_is_action(mgmt->frame_control) && 1560 + mgmt->u.action.category == WLAN_CATEGORY_HT) { 1561 + switch (mgmt->u.action.u.ht_smps.action) { 1562 + case WLAN_HT_ACTION_NOTIFY_CHANWIDTH: { 1563 + u8 chanwidth = mgmt->u.action.u.ht_notify_cw.chanwidth; 1564 + struct ieee80211_rx_status *status; 1565 + struct link_sta_info *link_sta; 1566 + struct sta_info *sta; 1567 + 1568 + sta = sta_info_get_bss(sdata, mgmt->sa); 1569 + if (!sta) 1570 + break; 1571 + 1572 + status = IEEE80211_SKB_RXCB(skb); 1573 + if (!status->link_valid) 1574 + link_sta = &sta->deflink; 1575 + else 1576 + link_sta = rcu_dereference_protected(sta->link[status->link_id], 1577 + lockdep_is_held(&local->hw.wiphy->mtx)); 1578 + if (link_sta) 1579 + ieee80211_ht_handle_chanwidth_notif(local, sdata, sta, 1580 + link_sta, chanwidth, 1581 + status->band); 1582 + break; 1583 + } 1584 + default: 1585 + WARN_ON(1); 1586 + break; 1587 + } 1588 + } else if (ieee80211_is_action(mgmt->frame_control) && 1560 1589 mgmt->u.action.category == WLAN_CATEGORY_VHT) { 1561 1590 switch (mgmt->u.action.u.vht_group_notif.action_code) { 1562 1591 case WLAN_VHT_ACTION_OPMODE_NOTIF: {
+6 -29
net/mac80211/rx.c
··· 3580 3580 goto handled; 3581 3581 } 3582 3582 case WLAN_HT_ACTION_NOTIFY_CHANWIDTH: { 3583 - struct ieee80211_supported_band *sband; 3584 3583 u8 chanwidth = mgmt->u.action.u.ht_notify_cw.chanwidth; 3585 - enum ieee80211_sta_rx_bandwidth max_bw, new_bw; 3586 - struct sta_opmode_info sta_opmode = {}; 3584 + 3585 + if (chanwidth != IEEE80211_HT_CHANWIDTH_20MHZ && 3586 + chanwidth != IEEE80211_HT_CHANWIDTH_ANY) 3587 + goto invalid; 3587 3588 3588 3589 /* If it doesn't support 40 MHz it can't change ... */ 3589 3590 if (!(rx->link_sta->pub->ht_cap.cap & 3590 - IEEE80211_HT_CAP_SUP_WIDTH_20_40)) 3591 + IEEE80211_HT_CAP_SUP_WIDTH_20_40)) 3591 3592 goto handled; 3592 3593 3593 - if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ) 3594 - max_bw = IEEE80211_STA_RX_BW_20; 3595 - else 3596 - max_bw = ieee80211_sta_cap_rx_bw(rx->link_sta); 3597 - 3598 - /* set cur_max_bandwidth and recalc sta bw */ 3599 - rx->link_sta->cur_max_bandwidth = max_bw; 3600 - new_bw = ieee80211_sta_cur_vht_bw(rx->link_sta); 3601 - 3602 - if (rx->link_sta->pub->bandwidth == new_bw) 3603 - goto handled; 3604 - 3605 - rx->link_sta->pub->bandwidth = new_bw; 3606 - sband = rx->local->hw.wiphy->bands[status->band]; 3607 - sta_opmode.bw = 3608 - ieee80211_sta_rx_bw_to_chan_width(rx->link_sta); 3609 - sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED; 3610 - 3611 - rate_control_rate_update(local, sband, rx->link_sta, 3612 - IEEE80211_RC_BW_CHANGED); 3613 - cfg80211_sta_opmode_change_notify(sdata->dev, 3614 - rx->sta->addr, 3615 - &sta_opmode, 3616 - GFP_ATOMIC); 3617 - goto handled; 3594 + goto queue; 3618 3595 } 3619 3596 default: 3620 3597 goto invalid;