wifi: cfg80211: update channel DFS data under wiphy mutex

We check the DFS data for a channel under wiphy mutex, but
the code sometimes writes it under RTNL only. Fix that to
avoid data races and to prepare things for later removal
of the RTNL here.

cfg80211_dfs_channels_update_work() must change a bit more
to do this, since it locks other wiphys and nesting locks
is, while possible, too difficult: track the changes in an
array and update everything afterwards.

Link: https://patch.msgid.link/20261006120655.e90b467f7739.I9074ec4427736f66fd5d9d47a53ccd94a1f260bf@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Johannes Berg 2026-10-06 12:04:06 +02:00
parent 379d1a3471
commit 18b54e7928
2 changed files with 79 additions and 17 deletions

View File

@ -1070,18 +1070,49 @@ void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev)
queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, 0);
}
struct cfg80211_dfs_chan_expiry {
struct ieee80211_channel *chan;
unsigned long entered;
enum nl80211_dfs_state state;
enum nl80211_radar_event event;
};
static bool cfg80211_dfs_chan_expire(struct cfg80211_registered_device *rdev,
struct cfg80211_dfs_chan_expiry *exp,
struct cfg80211_chan_def *chandef)
{
struct ieee80211_channel *c = exp->chan;
guard(wiphy)(&rdev->wiphy);
/* the state may have changed while the wiphy wasn't locked */
if (c->dfs_state != exp->state || c->dfs_state_entered != exp->entered)
return false;
c->dfs_state = NL80211_DFS_USABLE;
c->dfs_state_entered = jiffies;
cfg80211_chandef_create(chandef, c, NL80211_CHAN_NO_HT);
nl80211_radar_notify(rdev, chandef, exp->event, NULL, GFP_KERNEL);
return true;
}
void cfg80211_dfs_channels_update_work(struct work_struct *work)
{
struct delayed_work *delayed_work = to_delayed_work(work);
struct cfg80211_dfs_chan_expiry *expired;
struct cfg80211_registered_device *rdev;
struct cfg80211_chan_def chandef;
struct ieee80211_supported_band *sband;
struct ieee80211_channel *c;
struct wiphy *wiphy;
bool check_again = false;
bool check_again = false, pre_cac_allowed;
unsigned long timeout, next_time = 0;
unsigned long time_dfs_update;
enum nl80211_radar_event radar_event;
unsigned int n_chans = 0, n_expired = 0;
int bandid, i;
rdev = container_of(delayed_work, struct cfg80211_registered_device,
@ -1089,6 +1120,25 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
wiphy = &rdev->wiphy;
rtnl_lock();
wiphy_lock(wiphy);
for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
if (wiphy->bands[bandid])
n_chans += wiphy->bands[bandid]->n_channels;
}
expired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL);
if (!expired) {
/* hmm - retry later */
queue_delayed_work(cfg80211_wq,
&rdev->dfs_update_channels_wk,
HZ);
wiphy_unlock(wiphy);
rtnl_unlock();
return;
}
pre_cac_allowed = regulatory_pre_cac_allowed(wiphy);
for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
sband = wiphy->bands[bandid];
if (!sband)
@ -1108,8 +1158,7 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
time_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS;
radar_event = NL80211_RADAR_NOP_FINISHED;
} else {
if (regulatory_pre_cac_allowed(wiphy) ||
cfg80211_any_wiphy_oper_chan(wiphy, c))
if (pre_cac_allowed)
continue;
time_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS;
@ -1120,19 +1169,11 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
msecs_to_jiffies(time_dfs_update);
if (time_after_eq(jiffies, timeout)) {
c->dfs_state = NL80211_DFS_USABLE;
c->dfs_state_entered = jiffies;
cfg80211_chandef_create(&chandef, c,
NL80211_CHAN_NO_HT);
nl80211_radar_notify(rdev, &chandef,
radar_event, NULL,
GFP_ATOMIC);
regulatory_propagate_dfs_state(wiphy, &chandef,
c->dfs_state,
radar_event);
expired[n_expired].chan = c;
expired[n_expired].state = c->dfs_state;
expired[n_expired].entered = c->dfs_state_entered;
expired[n_expired].event = radar_event;
n_expired++;
continue;
}
@ -1143,8 +1184,25 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
check_again = true;
}
}
wiphy_unlock(wiphy);
/* these lock the wiphys, so must be done without holding ours */
for (i = 0; i < n_expired; i++) {
if (expired[i].event == NL80211_RADAR_PRE_CAC_EXPIRED &&
cfg80211_any_wiphy_oper_chan(wiphy, expired[i].chan))
continue;
if (!cfg80211_dfs_chan_expire(rdev, &expired[i], &chandef))
continue;
regulatory_propagate_dfs_state(wiphy, &chandef,
NL80211_DFS_USABLE,
expired[i].event);
}
rtnl_unlock();
kvfree(expired);
/* reschedule if there are other channels waiting to be cleared again */
if (check_again)
queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk,

View File

@ -3060,6 +3060,8 @@ static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)
ASSERT_RTNL();
guard(wiphy)(wiphy);
for_each_rdev(rdev) {
if (wiphy == &rdev->wiphy)
continue;
@ -4276,7 +4278,7 @@ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
struct wireless_dev *wdev;
unsigned int link_id;
guard(wiphy)(&rdev->wiphy);
lockdep_assert_wiphy(&rdev->wiphy);
/* If we finished CAC or received radar, we should end any
* CAC running on the same channels.
@ -4327,6 +4329,8 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,
chandef->chan->center_freq))
continue;
guard(wiphy)(&rdev->wiphy);
cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state);
if (event == NL80211_RADAR_DETECTED ||