]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: mac80211: defer link RX stats percpu free to RCU
authorMaoyi Xie <maoyixie.tju@gmail.com>
Sat, 27 Jun 2026 08:30:28 +0000 (16:30 +0800)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 6 Jul 2026 12:11:07 +0000 (14:11 +0200)
sta_remove_link() frees a removed MLO link's RX stats percpu buffer right
away, but defers only the link container to RCU:

sta_info_free_link(&alloc->info);
kfree_rcu(alloc, rcu_head);

The RX fast path reads link_sta under rcu_read_lock and writes the percpu
stats. A reader that resolved link_sta before the removal keeps the
pointer. The container stays alive from the kfree_rcu, so the read still
works. But the percpu block it points to is already freed. This needs
uses_rss. That is when pcpu_rx_stats exists.

The full STA teardown frees the deflink stats only after
synchronize_net(). The link removal path had no such barrier. The race is
hard to win in practice, but the free should still wait for RCU.

Free the link together with its data from a single RCU callback, so the
percpu block is reclaimed only after readers drain.

Fixes: c71420db653a ("wifi: mac80211: RCU-ify link STA pointers")
Link: https://lore.kernel.org/r/20260626080158.3589711-1-maoyixie.tju@gmail.com
Suggested-by: Johannes Berg <johannes@sipsolutions.net>
Co-developed-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
Signed-off-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
Link: https://patch.msgid.link/20260627083028.3826810-1-maoyixie.tju@gmail.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/sta_info.c

index 02b587ff850461eab8c8e51b9a0ef200eb691dc2..22eba0e6e54c5b1c7fe67f4ce27feecd9d19d8b2 100644 (file)
@@ -355,6 +355,15 @@ static void sta_info_free_link(struct link_sta_info *link_sta)
        free_percpu(link_sta->pcpu_rx_stats);
 }
 
+static void sta_link_free_rcu(struct rcu_head *head)
+{
+       struct sta_link_alloc *alloc =
+               container_of(head, struct sta_link_alloc, rcu_head);
+
+       sta_info_free_link(&alloc->info);
+       kfree(alloc);
+}
+
 static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id)
 {
        struct link_sta_info *link_sta = wiphy_dereference(sta->local->hw.wiphy,
@@ -439,10 +448,8 @@ static void sta_remove_link(struct sta_info *sta, unsigned int link_id,
 
        RCU_INIT_POINTER(sta->link[link_id], NULL);
        RCU_INIT_POINTER(sta->sta.link[link_id], NULL);
-       if (alloc) {
-               sta_info_free_link(&alloc->info);
-               kfree_rcu(alloc, rcu_head);
-       }
+       if (alloc)
+               call_rcu(&alloc->rcu_head, sta_link_free_rcu);
 
        ieee80211_sta_recalc_aggregates(&sta->sta);
 }