mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
wifi: mac80211: implement lockless beacon updates
Implement the new update_beacon() method to update beacon and related templates without the wiphy mutex. If anything else were to change return -EAGAIN, same if there are any active countdowns, and during HW restart. Eventually, the idea is to replace countdowns by beacon update requests so hostapd can handle everything instead. This doesn't enable it for any drivers since it depends on the driver behaviour, if it fetches each beacon and doesn't use the other templates it can just set the feature flag (NL80211_EXT_FEATURE_UPDATE_BEACON), otherwise it must implement the new update_beacon() mac80211 method. The regular beacon update may need to take head, tail and MBSSID/RNR elements from the old beacon, so while that's going on mark "bss_update_in_progress" and make lockless updates return -EAGAIN during that time, so that the old data remains valid/identical. For MBSSID, the lockless path also can't copy old data, so that also has to return -EAGAIN. All those -EAGAIN cases fall back to cfg80211 and do the update with the lock held, where it can then be handled. Userspace should ideally ensure it doesn't hit that path. Assisted-by: LLM Link: https://patch.msgid.link/20261009201417.a9433ea65d8e.Icfbcd157ac2bdeccfcc74a9f4c3997d3010ec0a9@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
dfa7d4ec73
commit
9b790d94d7
|
|
@ -4586,6 +4586,9 @@ struct ieee80211_prep_tx_info {
|
|||
* just "paused" for scanning/ROC, which is indicated by the beacon being
|
||||
* disabled/enabled via @bss_info_changed.
|
||||
* @stop_ap: Stop operation on the AP interface.
|
||||
* @update_beacon: Update the templates indicated by @changed (beacon, probe
|
||||
* response, FILS discovery and/or unsolicitated bcast probe response)
|
||||
* without the wiphy mutex held. This callback must not sleep.
|
||||
*
|
||||
* @reconfig_complete: Called after a call to ieee80211_restart_hw() and
|
||||
* during resume, when the reconfiguration has completed.
|
||||
|
|
@ -4783,6 +4786,10 @@ struct ieee80211_ops {
|
|||
struct ieee80211_bss_conf *link_conf);
|
||||
void (*stop_ap)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link_conf);
|
||||
void (*update_beacon)(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link_conf,
|
||||
u64 changed);
|
||||
|
||||
u64 (*prepare_multicast)(struct ieee80211_hw *hw,
|
||||
struct netdev_hw_addr_list *mc_list);
|
||||
|
|
|
|||
|
|
@ -1561,17 +1561,23 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
|
|||
{
|
||||
struct cfg80211_mbssid_elems *mbssid = params->mbssid_ies;
|
||||
struct cfg80211_rnr_elems *rnr = mbssid ? params->rnr_ies : NULL;
|
||||
struct beacon_data *new, *old;
|
||||
struct beacon_data *new = NULL, *old;
|
||||
int new_head_len, new_tail_len;
|
||||
int err;
|
||||
u64 _changed = BSS_CHANGED_BEACON;
|
||||
struct ieee80211_bss_conf *link_conf = link->conf;
|
||||
|
||||
old = sdata_dereference(link->u.ap.beacon, sdata);
|
||||
/* grab old and make lockless updates return -EAGAIN */
|
||||
spin_lock(&link->ap_tmpl_lock);
|
||||
old = ap_tmpl_dereference(link, link->u.ap.beacon);
|
||||
link->u.ap.bss_update_in_progress = true;
|
||||
spin_unlock(&link->ap_tmpl_lock);
|
||||
|
||||
/* Need to have a beacon head if we don't have one yet */
|
||||
if (!params->head && !old)
|
||||
return -EINVAL;
|
||||
if (!params->head && !old) {
|
||||
err = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* new or old head? */
|
||||
if (params->head)
|
||||
|
|
@ -1593,8 +1599,10 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
|
|||
}
|
||||
|
||||
new = ieee80211_alloc_beacon(mbssid, rnr, new_head_len, new_tail_len);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
if (!new) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (new->mbssid_ies)
|
||||
link_conf->bssid_indicator =
|
||||
|
|
@ -1625,10 +1633,8 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
|
|||
|
||||
err = ieee80211_set_probe_resp(sdata, params->probe_resp,
|
||||
params->probe_resp_len, csa, cca, link);
|
||||
if (err < 0) {
|
||||
kfree(new);
|
||||
return err;
|
||||
}
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (err == 0)
|
||||
_changed |= BSS_CHANGED_AP_PROBE_RESP;
|
||||
|
||||
|
|
@ -1641,21 +1647,31 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
|
|||
params->civicloc_len,
|
||||
link_conf);
|
||||
|
||||
if (err < 0) {
|
||||
kfree(new);
|
||||
return err;
|
||||
}
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
_changed |= BSS_CHANGED_FTM_RESPONDER;
|
||||
}
|
||||
|
||||
ap_tmpl_replace(link, link->u.ap.beacon, new);
|
||||
spin_lock(&link->ap_tmpl_lock);
|
||||
rcu_assign_pointer(link->u.ap.beacon, new);
|
||||
link->u.ap.bss_update_in_progress = false;
|
||||
spin_unlock(&link->ap_tmpl_lock);
|
||||
sdata->u.ap.active = true;
|
||||
|
||||
if (old)
|
||||
kfree_rcu(old, rcu_head);
|
||||
|
||||
ieee80211_update_ap_bandwidth(link, params);
|
||||
|
||||
*changed |= _changed;
|
||||
return 0;
|
||||
error:
|
||||
spin_lock(&link->ap_tmpl_lock);
|
||||
link->u.ap.bss_update_in_progress = false;
|
||||
spin_unlock(&link->ap_tmpl_lock);
|
||||
kfree(new);
|
||||
return err;
|
||||
}
|
||||
|
||||
static u8 ieee80211_num_beaconing_links(struct ieee80211_sub_if_data *sdata)
|
||||
|
|
@ -2050,6 +2066,184 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ieee80211_update_beacon(struct wiphy *wiphy, struct net_device *dev,
|
||||
struct cfg80211_ap_update *params)
|
||||
{
|
||||
struct cfg80211_unsol_bcast_probe_resp *ubpr =
|
||||
¶ms->unsol_bcast_probe_resp;
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
||||
struct cfg80211_fils_discovery *fd = ¶ms->fils_discovery;
|
||||
struct unsol_bcast_probe_resp_data *new_ubpr = NULL, *old_ubpr = NULL;
|
||||
struct cfg80211_beacon_data *beacon = ¶ms->beacon;
|
||||
struct fils_discovery_data *new_fd = NULL, *old_fd = NULL;
|
||||
struct probe_resp *new_presp = NULL, *old_presp = NULL;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
struct beacon_data *new, *old = NULL;
|
||||
struct ieee80211_bss_conf *link_conf;
|
||||
struct ieee80211_link_data *link;
|
||||
struct ieee80211_channel *chan;
|
||||
u64 changed = BSS_CHANGED_BEACON;
|
||||
int err = 0;
|
||||
|
||||
if (READ_ONCE(local->in_reconfig) || READ_ONCE(local->quiescing))
|
||||
return -EAGAIN;
|
||||
|
||||
new = ieee80211_alloc_beacon(beacon->mbssid_ies,
|
||||
beacon->mbssid_ies ? beacon->rnr_ies : NULL,
|
||||
beacon->head_len, beacon->tail_len);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
memcpy(new->head, beacon->head, beacon->head_len);
|
||||
memcpy(new->tail, beacon->tail, beacon->tail_len);
|
||||
|
||||
if (beacon->probe_resp && beacon->probe_resp_len) {
|
||||
new_presp = kzalloc(sizeof(*new_presp) + beacon->probe_resp_len,
|
||||
GFP_KERNEL);
|
||||
if (!new_presp) {
|
||||
err = -ENOMEM;
|
||||
goto free;
|
||||
}
|
||||
new_presp->len = beacon->probe_resp_len;
|
||||
memcpy(new_presp->data, beacon->probe_resp,
|
||||
beacon->probe_resp_len);
|
||||
changed |= BSS_CHANGED_AP_PROBE_RESP;
|
||||
}
|
||||
|
||||
if (fd->update && fd->tmpl && fd->tmpl_len) {
|
||||
new_fd = kzalloc(sizeof(*new_fd) + fd->tmpl_len, GFP_KERNEL);
|
||||
if (!new_fd) {
|
||||
err = -ENOMEM;
|
||||
goto free;
|
||||
}
|
||||
new_fd->len = fd->tmpl_len;
|
||||
memcpy(new_fd->data, fd->tmpl, fd->tmpl_len);
|
||||
}
|
||||
|
||||
if (ubpr->update && ubpr->tmpl && ubpr->tmpl_len) {
|
||||
new_ubpr = kzalloc(sizeof(*new_ubpr) + ubpr->tmpl_len,
|
||||
GFP_KERNEL);
|
||||
if (!new_ubpr) {
|
||||
err = -ENOMEM;
|
||||
goto free;
|
||||
}
|
||||
new_ubpr->len = ubpr->tmpl_len;
|
||||
memcpy(new_ubpr->data, ubpr->tmpl, ubpr->tmpl_len);
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
/* do_stop() synchronizes RCU before the interface type can change */
|
||||
if (!ieee80211_sdata_running(sdata) ||
|
||||
(sdata->vif.type != NL80211_IFTYPE_AP &&
|
||||
sdata->vif.type != NL80211_IFTYPE_P2P_GO)) {
|
||||
err = -ENETDOWN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
link = rcu_dereference(sdata->link[beacon->link_id]);
|
||||
if (!link) {
|
||||
err = -ENOLINK;
|
||||
goto unlock;
|
||||
}
|
||||
link_conf = link->conf;
|
||||
|
||||
/* configuration changes need the wiphy mutex, let cfg80211 retry */
|
||||
err = -EAGAIN;
|
||||
|
||||
if (READ_ONCE(link_conf->csa_active) ||
|
||||
READ_ONCE(link_conf->color_change_active))
|
||||
goto unlock;
|
||||
|
||||
if (new->mbssid_ies &&
|
||||
ieee80211_beacon_bssid_indicator(new) !=
|
||||
READ_ONCE(link_conf->bssid_indicator))
|
||||
goto unlock;
|
||||
|
||||
if (fd->update &&
|
||||
(fd->min_interval != READ_ONCE(link_conf->fils_discovery.min_interval) ||
|
||||
fd->max_interval != READ_ONCE(link_conf->fils_discovery.max_interval)))
|
||||
goto unlock;
|
||||
|
||||
if (ubpr->update &&
|
||||
ubpr->interval !=
|
||||
READ_ONCE(link_conf->unsol_bcast_probe_resp_interval))
|
||||
goto unlock;
|
||||
|
||||
chan = READ_ONCE(link_conf->chanreq.oper.chan);
|
||||
if (!chan)
|
||||
goto unlock;
|
||||
|
||||
if (chan->band != NL80211_BAND_S1GHZ) {
|
||||
enum ieee80211_sta_rx_bandwidth he_and_lower;
|
||||
|
||||
he_and_lower = ieee80211_calc_ap_he_and_lower(beacon);
|
||||
if (he_and_lower != READ_ONCE(link->bss_bw.he_and_lower) ||
|
||||
ieee80211_calc_ap_eht_bw(beacon, he_and_lower) !=
|
||||
READ_ONCE(link->bss_bw.eht))
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
spin_lock(&link->ap_tmpl_lock);
|
||||
old = ap_tmpl_dereference(link, link->u.ap.beacon);
|
||||
if (!old) {
|
||||
err = -ENOENT;
|
||||
} else if (link->u.ap.bss_update_in_progress) {
|
||||
/* a locked update is in progress */
|
||||
old = NULL;
|
||||
} else if (READ_ONCE(old->cntdwn_current_counter)) {
|
||||
/* a countdown just started, must keep the offsets */
|
||||
old = NULL;
|
||||
} else if (old->mbssid_ies && !new->mbssid_ies) {
|
||||
/* only locked path copies old MBSSID data */
|
||||
old = NULL;
|
||||
} else {
|
||||
rcu_assign_pointer(link->u.ap.beacon, new);
|
||||
new = NULL;
|
||||
|
||||
if (new_presp)
|
||||
old_presp = rcu_replace_pointer(link->u.ap.probe_resp,
|
||||
new_presp,
|
||||
lockdep_is_held(&link->ap_tmpl_lock));
|
||||
new_presp = NULL;
|
||||
|
||||
if (fd->update) {
|
||||
old_fd = rcu_replace_pointer(link->u.ap.fils_discovery,
|
||||
new_fd,
|
||||
lockdep_is_held(&link->ap_tmpl_lock));
|
||||
changed |= BSS_CHANGED_FILS_DISCOVERY;
|
||||
}
|
||||
new_fd = NULL;
|
||||
|
||||
if (ubpr->update) {
|
||||
old_ubpr = rcu_replace_pointer(link->u.ap.unsol_bcast_probe_resp,
|
||||
new_ubpr,
|
||||
lockdep_is_held(&link->ap_tmpl_lock));
|
||||
changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP;
|
||||
}
|
||||
new_ubpr = NULL;
|
||||
|
||||
drv_update_beacon(local, sdata, link_conf, changed);
|
||||
err = 0;
|
||||
}
|
||||
spin_unlock(&link->ap_tmpl_lock);
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
free:
|
||||
kfree(new);
|
||||
kfree(new_presp);
|
||||
kfree(new_fd);
|
||||
kfree(new_ubpr);
|
||||
if (old)
|
||||
kfree_rcu(old, rcu_head);
|
||||
if (old_presp)
|
||||
kfree_rcu(old_presp, rcu_head);
|
||||
if (old_fd)
|
||||
kfree_rcu(old_fd, rcu_head);
|
||||
if (old_ubpr)
|
||||
kfree_rcu(old_ubpr, rcu_head);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ieee80211_free_next_beacon(struct ieee80211_link_data *link)
|
||||
{
|
||||
if (!link->u.ap.next_beacon)
|
||||
|
|
@ -6071,6 +6265,7 @@ const struct cfg80211_ops mac80211_config_ops = {
|
|||
.set_default_beacon_key = ieee80211_config_default_beacon_key,
|
||||
.start_ap = ieee80211_start_ap,
|
||||
.change_beacon = ieee80211_change_beacon,
|
||||
.update_beacon = ieee80211_update_beacon,
|
||||
.stop_ap = ieee80211_stop_ap,
|
||||
.add_station = ieee80211_add_station,
|
||||
.del_station = ieee80211_del_station,
|
||||
|
|
|
|||
|
|
@ -1107,6 +1107,22 @@ static inline void drv_stop_ap(struct ieee80211_local *local,
|
|||
trace_drv_return_void(local);
|
||||
}
|
||||
|
||||
static inline void drv_update_beacon(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata,
|
||||
struct ieee80211_bss_conf *link_conf,
|
||||
u64 changed)
|
||||
{
|
||||
/* can race with HW restart, so don't warn */
|
||||
if (!(sdata->flags & IEEE80211_SDATA_IN_DRIVER))
|
||||
return;
|
||||
|
||||
trace_drv_update_beacon(local, sdata, link_conf, changed);
|
||||
if (local->ops->update_beacon)
|
||||
local->ops->update_beacon(&local->hw, &sdata->vif, link_conf,
|
||||
changed);
|
||||
trace_drv_return_void(local);
|
||||
}
|
||||
|
||||
static inline void
|
||||
drv_reconfig_complete(struct ieee80211_local *local,
|
||||
enum ieee80211_reconfig_type reconfig_type)
|
||||
|
|
|
|||
|
|
@ -1103,6 +1103,8 @@ struct ieee80211_link_data_ap {
|
|||
struct unsol_bcast_probe_resp_data __rcu *unsol_bcast_probe_resp;
|
||||
struct s1g_short_beacon_data __rcu *s1g_short_beacon;
|
||||
|
||||
bool bss_update_in_progress;
|
||||
|
||||
/* to be used after channel switch. */
|
||||
struct cfg80211_beacon_data *next_beacon;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1924,6 +1924,34 @@ TRACE_EVENT(drv_stop_ap,
|
|||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(drv_update_beacon,
|
||||
TP_PROTO(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata,
|
||||
struct ieee80211_bss_conf *link_conf,
|
||||
u64 changed),
|
||||
|
||||
TP_ARGS(local, sdata, link_conf, changed),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
VIF_ENTRY
|
||||
__field(u32, link_id)
|
||||
__field(u64, changed)
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
VIF_ASSIGN;
|
||||
__entry->link_id = link_conf->link_id;
|
||||
__entry->changed = changed;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT VIF_PR_FMT " link id %u changed:%#llx",
|
||||
LOCAL_PR_ARG, VIF_PR_ARG, __entry->link_id, __entry->changed
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(drv_reconfig_complete,
|
||||
TP_PROTO(struct ieee80211_local *local,
|
||||
enum ieee80211_reconfig_type reconfig_type),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user