]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
wifi: mac80211: handle WLAN_HT_ACTION_NOTIFY_CHANWIDTH async
authorMiri Korenblit <miriam.rachel.korenblit@intel.com>
Wed, 9 Jul 2025 20:38:02 +0000 (23:38 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 20 Aug 2025 16:41:08 +0000 (18:41 +0200)
[ Upstream commit 93370f2d37f50757a810da409efc0223c342527e ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/mac80211/ht.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/rx.c

index 32390d8a9d753e50b60873ef8da6a71ea957a137..1c82a28b03de4b090f83a02a6d4ae4c0c9ab836f 100644 (file)
@@ -9,7 +9,7 @@
  * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
  * Copyright 2007-2010, Intel Corporation
  * Copyright 2017      Intel Deutschland GmbH
- * Copyright(c) 2020-2024 Intel Corporation
+ * Copyright(c) 2020-2025 Intel Corporation
  */
 
 #include <linux/ieee80211.h>
@@ -603,3 +603,41 @@ out:
 }
 /* this might change ... don't want non-open drivers using it */
 EXPORT_SYMBOL_GPL(ieee80211_request_smps);
+
+void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
+                                        struct ieee80211_sub_if_data *sdata,
+                                        struct sta_info *sta,
+                                        struct link_sta_info *link_sta,
+                                        u8 chanwidth, enum nl80211_band band)
+{
+       enum ieee80211_sta_rx_bandwidth max_bw, new_bw;
+       struct ieee80211_supported_band *sband;
+       struct sta_opmode_info sta_opmode = {};
+
+       lockdep_assert_wiphy(local->hw.wiphy);
+
+       if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
+               max_bw = IEEE80211_STA_RX_BW_20;
+       else
+               max_bw = ieee80211_sta_cap_rx_bw(link_sta);
+
+       /* set cur_max_bandwidth and recalc sta bw */
+       link_sta->cur_max_bandwidth = max_bw;
+       new_bw = ieee80211_sta_cur_vht_bw(link_sta);
+
+       if (link_sta->pub->bandwidth == new_bw)
+               return;
+
+       link_sta->pub->bandwidth = new_bw;
+       sband = local->hw.wiphy->bands[band];
+       sta_opmode.bw =
+               ieee80211_sta_rx_bw_to_chan_width(link_sta);
+       sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED;
+
+       rate_control_rate_update(local, sband, link_sta,
+                                IEEE80211_RC_BW_CHANGED);
+       cfg80211_sta_opmode_change_notify(sdata->dev,
+                                         sta->addr,
+                                         &sta_opmode,
+                                         GFP_KERNEL);
+}
index f71d9eeb8abc80014768b4951ed5a388a89001be..61439e6efdb74f91c749fa4250677daac43ab145 100644 (file)
@@ -2220,6 +2220,12 @@ u8 ieee80211_mcs_to_chains(const struct ieee80211_mcs_info *mcs);
 enum nl80211_smps_mode
 ieee80211_smps_mode_to_smps_mode(enum ieee80211_smps_mode smps);
 
+void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
+                                        struct ieee80211_sub_if_data *sdata,
+                                        struct sta_info *sta,
+                                        struct link_sta_info *link_sta,
+                                        u8 chanwidth, enum nl80211_band band);
+
 /* VHT */
 void
 ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
index c01634fdba789a86dfacf1a2fb34df4461ec33fc..851d399fca135092a819fbfd700a000ed82bce15 100644 (file)
@@ -1556,6 +1556,35 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
                                break;
                        }
                }
+       } else if (ieee80211_is_action(mgmt->frame_control) &&
+                  mgmt->u.action.category == WLAN_CATEGORY_HT) {
+               switch (mgmt->u.action.u.ht_smps.action) {
+               case WLAN_HT_ACTION_NOTIFY_CHANWIDTH: {
+                       u8 chanwidth = mgmt->u.action.u.ht_notify_cw.chanwidth;
+                       struct ieee80211_rx_status *status;
+                       struct link_sta_info *link_sta;
+                       struct sta_info *sta;
+
+                       sta = sta_info_get_bss(sdata, mgmt->sa);
+                       if (!sta)
+                               break;
+
+                       status = IEEE80211_SKB_RXCB(skb);
+                       if (!status->link_valid)
+                               link_sta = &sta->deflink;
+                       else
+                               link_sta = rcu_dereference_protected(sta->link[status->link_id],
+                                                       lockdep_is_held(&local->hw.wiphy->mtx));
+                       if (link_sta)
+                               ieee80211_ht_handle_chanwidth_notif(local, sdata, sta,
+                                                                   link_sta, chanwidth,
+                                                                   status->band);
+                       break;
+               }
+               default:
+                       WARN_ON(1);
+                       break;
+               }
        } else if (ieee80211_is_action(mgmt->frame_control) &&
                   mgmt->u.action.category == WLAN_CATEGORY_VHT) {
                switch (mgmt->u.action.u.vht_group_notif.action_code) {
index e73431549ce77e29346ee8f4d1273a18bef81991..8ec06cf0a9f0112c445f54f006b5a446111628e4 100644 (file)
@@ -3576,41 +3576,18 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
                        goto handled;
                }
                case WLAN_HT_ACTION_NOTIFY_CHANWIDTH: {
-                       struct ieee80211_supported_band *sband;
                        u8 chanwidth = mgmt->u.action.u.ht_notify_cw.chanwidth;
-                       enum ieee80211_sta_rx_bandwidth max_bw, new_bw;
-                       struct sta_opmode_info sta_opmode = {};
+
+                       if (chanwidth != IEEE80211_HT_CHANWIDTH_20MHZ &&
+                           chanwidth != IEEE80211_HT_CHANWIDTH_ANY)
+                               goto invalid;
 
                        /* If it doesn't support 40 MHz it can't change ... */
                        if (!(rx->link_sta->pub->ht_cap.cap &
-                                       IEEE80211_HT_CAP_SUP_WIDTH_20_40))
-                               goto handled;
-
-                       if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
-                               max_bw = IEEE80211_STA_RX_BW_20;
-                       else
-                               max_bw = ieee80211_sta_cap_rx_bw(rx->link_sta);
-
-                       /* set cur_max_bandwidth and recalc sta bw */
-                       rx->link_sta->cur_max_bandwidth = max_bw;
-                       new_bw = ieee80211_sta_cur_vht_bw(rx->link_sta);
-
-                       if (rx->link_sta->pub->bandwidth == new_bw)
+                               IEEE80211_HT_CAP_SUP_WIDTH_20_40))
                                goto handled;
 
-                       rx->link_sta->pub->bandwidth = new_bw;
-                       sband = rx->local->hw.wiphy->bands[status->band];
-                       sta_opmode.bw =
-                               ieee80211_sta_rx_bw_to_chan_width(rx->link_sta);
-                       sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED;
-
-                       rate_control_rate_update(local, sband, rx->link_sta,
-                                                IEEE80211_RC_BW_CHANGED);
-                       cfg80211_sta_opmode_change_notify(sdata->dev,
-                                                         rx->sta->addr,
-                                                         &sta_opmode,
-                                                         GFP_ATOMIC);
-                       goto handled;
+                       goto queue;
                }
                default:
                        goto invalid;