diff options
Diffstat (limited to 'net/mac80211/cfg.c')
| -rw-r--r-- | net/mac80211/cfg.c | 2848 |
1 files changed, 1982 insertions, 866 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 87a208089caf..b51c2c8584ae 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -5,7 +5,7 @@ * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2021 Intel Corporation + * Copyright (C) 2018-2025 Intel Corporation */ #include <linux/ieee80211.h> @@ -23,6 +23,30 @@ #include "mesh.h" #include "wme.h" +static struct ieee80211_link_data * +ieee80211_link_or_deflink(struct ieee80211_sub_if_data *sdata, int link_id, + bool require_valid) +{ + struct ieee80211_link_data *link; + + if (link_id < 0) { + /* + * For keys, if sdata is not an MLD, we might not use + * the return value at all (if it's not a pairwise key), + * so in that case (require_valid==false) don't error. + */ + if (require_valid && ieee80211_vif_is_mld(&sdata->vif)) + return ERR_PTR(-EINVAL); + + return &sdata->deflink; + } + + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + return ERR_PTR(-ENOLINK); + return link; +} + static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata, struct vif_params *params) { @@ -39,11 +63,14 @@ static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata, memcpy(sdata->vif.bss_conf.mu_group.position, params->vht_mumimo_groups + WLAN_MEMBERSHIP_LEN, WLAN_USER_POSITION_LEN); - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_MU_GROUPS); + /* don't care about endianness - just check for 0 */ memcpy(&membership, params->vht_mumimo_groups, WLAN_MEMBERSHIP_LEN); mu_mimo_groups = membership != 0; + + /* Unset following if configured explicitly */ + eth_broadcast_addr(sdata->u.mntr.mu_follow_addr); } if (params->vht_mumimo_follow_addr) { @@ -51,46 +78,65 @@ static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata, is_valid_ether_addr(params->vht_mumimo_follow_addr); ether_addr_copy(sdata->u.mntr.mu_follow_addr, params->vht_mumimo_follow_addr); + + /* Unset current membership until a management frame is RXed */ + memset(sdata->vif.bss_conf.mu_group.membership, 0, + WLAN_MEMBERSHIP_LEN); } - sdata->vif.mu_mimo_owner = mu_mimo_groups || mu_mimo_follow; + sdata->vif.bss_conf.mu_mimo_owner = mu_mimo_groups || mu_mimo_follow; + + /* Notify only after setting mu_mimo_owner */ + if (sdata->vif.bss_conf.mu_mimo_owner && + sdata->flags & IEEE80211_SDATA_IN_DRIVER) + ieee80211_link_info_change_notify(sdata, &sdata->deflink, + BSS_CHANGED_MU_GROUPS); } static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata, struct vif_params *params) { struct ieee80211_local *local = sdata->local; - struct ieee80211_sub_if_data *monitor_sdata; + struct ieee80211_sub_if_data *monitor_sdata = NULL; /* check flags first */ if (params->flags && ieee80211_sdata_running(sdata)) { - u32 mask = MONITOR_FLAG_COOK_FRAMES | MONITOR_FLAG_ACTIVE; + u32 mask = MONITOR_FLAG_ACTIVE; /* - * Prohibit MONITOR_FLAG_COOK_FRAMES and - * MONITOR_FLAG_ACTIVE to be changed while the - * interface is up. + * Prohibit MONITOR_FLAG_ACTIVE to be changed + * while the interface is up. * Else we would need to add a lot of cruft * to update everything: - * cooked_mntrs, monitor and all fif_* counters + * monitor and all fif_* counters * reconfigure hardware */ if ((params->flags & mask) != (sdata->u.mntr.flags & mask)) return -EBUSY; } - /* also validate MU-MIMO change */ - monitor_sdata = wiphy_dereference(local->hw.wiphy, - local->monitor_sdata); - - if (!monitor_sdata && + /* validate whether MU-MIMO can be configured */ + if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) && + !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) && (params->vht_mumimo_groups || params->vht_mumimo_follow_addr)) return -EOPNOTSUPP; + /* Also update dependent monitor_sdata if required */ + if (test_bit(SDATA_STATE_RUNNING, &sdata->state) && + !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) + monitor_sdata = wiphy_dereference(local->hw.wiphy, + local->monitor_sdata); + /* apply all changes now - no failures allowed */ - if (monitor_sdata) - ieee80211_set_mu_mimo_follow(monitor_sdata, params); + if (ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) || + ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) { + /* This is copied in when the VIF is activated */ + ieee80211_set_mu_mimo_follow(sdata, params); + + if (monitor_sdata) + ieee80211_set_mu_mimo_follow(monitor_sdata, params); + } if (params->flags) { if (ieee80211_sdata_running(sdata)) { @@ -113,31 +159,46 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata, } static int ieee80211_set_ap_mbssid_options(struct ieee80211_sub_if_data *sdata, - struct cfg80211_mbssid_config params) + struct cfg80211_mbssid_config *params, + struct ieee80211_bss_conf *link_conf) { struct ieee80211_sub_if_data *tx_sdata; + struct ieee80211_bss_conf *old; - sdata->vif.mbssid_tx_vif = NULL; - sdata->vif.bss_conf.bssid_index = 0; - sdata->vif.bss_conf.nontransmitted = false; - sdata->vif.bss_conf.ema_ap = false; + link_conf->bssid_index = 0; + link_conf->nontransmitted = false; + link_conf->ema_ap = false; + link_conf->bssid_indicator = 0; - if (sdata->vif.type != NL80211_IFTYPE_AP || !params.tx_wdev) + if (sdata->vif.type != NL80211_IFTYPE_AP || !params->tx_wdev) return -EINVAL; - tx_sdata = IEEE80211_WDEV_TO_SUB_IF(params.tx_wdev); + old = sdata_dereference(link_conf->tx_bss_conf, sdata); + if (old) + return -EALREADY; + + tx_sdata = IEEE80211_WDEV_TO_SUB_IF(params->tx_wdev); if (!tx_sdata) return -EINVAL; if (tx_sdata == sdata) { - sdata->vif.mbssid_tx_vif = &sdata->vif; + rcu_assign_pointer(link_conf->tx_bss_conf, link_conf); } else { - sdata->vif.mbssid_tx_vif = &tx_sdata->vif; - sdata->vif.bss_conf.nontransmitted = true; - sdata->vif.bss_conf.bssid_index = params.index; + struct ieee80211_bss_conf *tx_bss_conf; + + tx_bss_conf = sdata_dereference(tx_sdata->vif.link_conf[params->tx_link_id], + sdata); + if (rcu_access_pointer(tx_bss_conf->tx_bss_conf) != tx_bss_conf) + return -EINVAL; + + rcu_assign_pointer(link_conf->tx_bss_conf, tx_bss_conf); + + link_conf->nontransmitted = true; + link_conf->bssid_index = params->index; + link_conf->bssid_indicator = tx_bss_conf->bssid_indicator; } - if (params.ema) - sdata->vif.bss_conf.ema_ap = true; + if (params->ema) + link_conf->ema_ap = true; return 0; } @@ -167,6 +228,24 @@ static struct wireless_dev *ieee80211_add_iface(struct wiphy *wiphy, } } + /* Let the driver know that an interface is going to be added. + * Indicate so only for interface types that will be added to the + * driver. + */ + switch (type) { + case NL80211_IFTYPE_AP_VLAN: + break; + case NL80211_IFTYPE_MONITOR: + if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) || + !(params->flags & MONITOR_FLAG_ACTIVE)) + break; + fallthrough; + default: + drv_prep_add_interface(local, + ieee80211_vif_type_p2p(&sdata->vif)); + break; + } + return wdev; } @@ -187,6 +266,8 @@ static int ieee80211_change_iface(struct wiphy *wiphy, struct sta_info *sta; int ret; + lockdep_assert_wiphy(local->hw.wiphy); + ret = ieee80211_if_change_type(sdata, type); if (ret) return ret; @@ -200,16 +281,18 @@ static int ieee80211_change_iface(struct wiphy *wiphy, if (params->use_4addr == ifmgd->use_4addr) return 0; + /* FIXME: no support for 4-addr MLO yet */ + if (ieee80211_vif_is_mld(&sdata->vif)) + return -EOPNOTSUPP; + sdata->u.mgd.use_4addr = params->use_4addr; if (!ifmgd->associated) return 0; - mutex_lock(&local->sta_mtx); - sta = sta_info_get(sdata, ifmgd->bssid); + sta = sta_info_get(sdata, sdata->deflink.u.mgd.bssid); if (sta) drv_sta_set_4addr(local, sdata, &sta->sta, params->use_4addr); - mutex_unlock(&local->sta_mtx); if (params->use_4addr) ieee80211_send_4addr_nullfunc(local, sdata); @@ -230,9 +313,9 @@ static int ieee80211_start_p2p_device(struct wiphy *wiphy, struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); int ret; - mutex_lock(&sdata->local->chanctx_mtx); - ret = ieee80211_check_combinations(sdata, NULL, 0, 0); - mutex_unlock(&sdata->local->chanctx_mtx); + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + ret = ieee80211_check_combinations(sdata, NULL, 0, 0, -1); if (ret < 0) return ret; @@ -245,6 +328,96 @@ static void ieee80211_stop_p2p_device(struct wiphy *wiphy, ieee80211_sdata_stop(IEEE80211_WDEV_TO_SUB_IF(wdev)); } +static void ieee80211_nan_conf_free(struct cfg80211_nan_conf *conf) +{ + kfree(conf->cluster_id); + kfree(conf->extra_nan_attrs); + kfree(conf->vendor_elems); + memset(conf, 0, sizeof(*conf)); +} + +static void ieee80211_stop_nan(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + + if (!sdata->u.nan.started) + return; + + drv_stop_nan(sdata->local, sdata); + sdata->u.nan.started = false; + + ieee80211_nan_conf_free(&sdata->u.nan.conf); + + ieee80211_sdata_stop(sdata); + ieee80211_recalc_idle(sdata->local); +} + +static int ieee80211_nan_conf_copy(struct cfg80211_nan_conf *dst, + struct cfg80211_nan_conf *src, + u32 changes) +{ + if (changes & CFG80211_NAN_CONF_CHANGED_PREF) + dst->master_pref = src->master_pref; + + if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) + dst->bands = src->bands; + + if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG) { + dst->scan_period = src->scan_period; + dst->scan_dwell_time = src->scan_dwell_time; + dst->discovery_beacon_interval = + src->discovery_beacon_interval; + dst->enable_dw_notification = src->enable_dw_notification; + memcpy(&dst->band_cfgs, &src->band_cfgs, + sizeof(dst->band_cfgs)); + + kfree(dst->cluster_id); + dst->cluster_id = NULL; + + kfree(dst->extra_nan_attrs); + dst->extra_nan_attrs = NULL; + dst->extra_nan_attrs_len = 0; + + kfree(dst->vendor_elems); + dst->vendor_elems = NULL; + dst->vendor_elems_len = 0; + + if (src->cluster_id) { + dst->cluster_id = kmemdup(src->cluster_id, ETH_ALEN, + GFP_KERNEL); + if (!dst->cluster_id) + goto no_mem; + } + + if (src->extra_nan_attrs && src->extra_nan_attrs_len) { + dst->extra_nan_attrs = kmemdup(src->extra_nan_attrs, + src->extra_nan_attrs_len, + GFP_KERNEL); + if (!dst->extra_nan_attrs) + goto no_mem; + + dst->extra_nan_attrs_len = src->extra_nan_attrs_len; + } + + if (src->vendor_elems && src->vendor_elems_len) { + dst->vendor_elems = kmemdup(src->vendor_elems, + src->vendor_elems_len, + GFP_KERNEL); + if (!dst->vendor_elems) + goto no_mem; + + dst->vendor_elems_len = src->vendor_elems_len; + } + } + + return 0; + +no_mem: + ieee80211_nan_conf_free(dst); + return -ENOMEM; +} + static int ieee80211_start_nan(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_nan_conf *conf) @@ -252,9 +425,12 @@ static int ieee80211_start_nan(struct wiphy *wiphy, struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); int ret; - mutex_lock(&sdata->local->chanctx_mtx); - ret = ieee80211_check_combinations(sdata, NULL, 0, 0); - mutex_unlock(&sdata->local->chanctx_mtx); + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + if (sdata->u.nan.started) + return -EALREADY; + + ret = ieee80211_check_combinations(sdata, NULL, 0, 0, -1); if (ret < 0) return ret; @@ -263,21 +439,21 @@ static int ieee80211_start_nan(struct wiphy *wiphy, return ret; ret = drv_start_nan(sdata->local, sdata, conf); - if (ret) + if (ret) { ieee80211_sdata_stop(sdata); + return ret; + } - sdata->u.nan.conf = *conf; + sdata->u.nan.started = true; + ieee80211_recalc_idle(sdata->local); - return ret; -} - -static void ieee80211_stop_nan(struct wiphy *wiphy, - struct wireless_dev *wdev) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + ret = ieee80211_nan_conf_copy(&sdata->u.nan.conf, conf, 0xFFFFFFFF); + if (ret) { + ieee80211_stop_nan(wiphy, wdev); + return ret; + } - drv_stop_nan(sdata->local, sdata); - ieee80211_sdata_stop(sdata); + return 0; } static int ieee80211_nan_change_conf(struct wiphy *wiphy, @@ -286,7 +462,7 @@ static int ieee80211_nan_change_conf(struct wiphy *wiphy, u32 changes) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); - struct cfg80211_nan_conf new_conf; + struct cfg80211_nan_conf new_conf = {}; int ret = 0; if (sdata->vif.type != NL80211_IFTYPE_NAN) @@ -295,17 +471,28 @@ static int ieee80211_nan_change_conf(struct wiphy *wiphy, if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; - new_conf = sdata->u.nan.conf; + if (!changes) + return 0; - if (changes & CFG80211_NAN_CONF_CHANGED_PREF) - new_conf.master_pref = conf->master_pref; + /* First make a full copy of the previous configuration and then apply + * the changes. This might be a little wasteful, but it is simpler. + */ + ret = ieee80211_nan_conf_copy(&new_conf, &sdata->u.nan.conf, + 0xFFFFFFFF); + if (ret < 0) + return ret; - if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) - new_conf.bands = conf->bands; + ret = ieee80211_nan_conf_copy(&new_conf, conf, changes); + if (ret < 0) + return ret; ret = drv_nan_change_conf(sdata->local, sdata, &new_conf, changes); - if (!ret) + if (ret) { + ieee80211_nan_conf_free(&new_conf); + } else { + ieee80211_nan_conf_free(&sdata->u.nan.conf); sdata->u.nan.conf = new_conf; + } return ret; } @@ -421,30 +608,37 @@ static int ieee80211_set_tx(struct ieee80211_sub_if_data *sdata, if (sta->ptk_idx == key_idx) return 0; - mutex_lock(&local->key_mtx); - key = key_mtx_dereference(local, sta->ptk[key_idx]); + key = wiphy_dereference(local->hw.wiphy, sta->ptk[key_idx]); if (key && key->conf.flags & IEEE80211_KEY_FLAG_NO_AUTO_TX) ret = ieee80211_set_tx_key(key); - mutex_unlock(&local->key_mtx); return ret; } static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx, bool pairwise, const u8 *mac_addr, - struct key_params *params) + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr, struct key_params *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link = + ieee80211_link_or_deflink(sdata, link_id, false); struct ieee80211_local *local = sdata->local; struct sta_info *sta = NULL; - const struct ieee80211_cipher_scheme *cs = NULL; struct ieee80211_key *key; int err; + lockdep_assert_wiphy(local->hw.wiphy); + if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; + if (IS_ERR(link)) + return PTR_ERR(link); + + if (WARN_ON(pairwise && link_id >= 0)) + return -EINVAL; + if (pairwise && params->mode == NL80211_KEY_SET_TX) return ieee80211_set_tx(sdata, mac_addr, key_idx); @@ -453,37 +647,30 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_TKIP: case WLAN_CIPHER_SUITE_WEP104: + if (link_id >= 0) + return -EINVAL; if (WARN_ON_ONCE(fips_enabled)) return -EINVAL; break; - case WLAN_CIPHER_SUITE_CCMP: - case WLAN_CIPHER_SUITE_CCMP_256: - case WLAN_CIPHER_SUITE_AES_CMAC: - case WLAN_CIPHER_SUITE_BIP_CMAC_256: - case WLAN_CIPHER_SUITE_BIP_GMAC_128: - case WLAN_CIPHER_SUITE_BIP_GMAC_256: - case WLAN_CIPHER_SUITE_GCMP: - case WLAN_CIPHER_SUITE_GCMP_256: - break; default: - cs = ieee80211_cs_get(local, params->cipher, sdata->vif.type); break; } key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len, - params->key, params->seq_len, params->seq, - cs); + params->key, params->seq_len, params->seq); if (IS_ERR(key)) return PTR_ERR(key); - if (pairwise) + if (pairwise) { key->conf.flags |= IEEE80211_KEY_FLAG_PAIRWISE; + key->conf.link_id = -1; + } else { + key->conf.link_id = link->link_id; + } if (params->mode == NL80211_KEY_NO_TX) key->conf.flags |= IEEE80211_KEY_FLAG_NO_AUTO_TX; - mutex_lock(&local->sta_mtx); - if (mac_addr) { sta = sta_info_get_bss(sdata, mac_addr); /* @@ -498,8 +685,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, */ if (!sta || !test_sta_flag(sta, WLAN_STA_ASSOC)) { ieee80211_key_free_unused(key); - err = -ENOENT; - goto out_unlock; + return -ENOENT; } } @@ -537,69 +723,102 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, break; } - if (sta) - sta->cipher_scheme = cs; - - err = ieee80211_key_link(key, sdata, sta); - - out_unlock: - mutex_unlock(&local->sta_mtx); + err = ieee80211_key_link(key, link, sta); + /* KRACK protection, shouldn't happen but just silently accept key */ + if (err == -EALREADY) + err = 0; return err; } -static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx, bool pairwise, const u8 *mac_addr) +static struct ieee80211_key * +ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id, + u8 key_idx, bool pairwise, const u8 *mac_addr) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - struct ieee80211_local *local = sdata->local; - struct sta_info *sta; - struct ieee80211_key *key = NULL; - int ret; + struct ieee80211_local *local __maybe_unused = sdata->local; + struct ieee80211_link_data *link = &sdata->deflink; + struct ieee80211_key *key; - mutex_lock(&local->sta_mtx); - mutex_lock(&local->key_mtx); + if (link_id >= 0) { + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + return NULL; + } if (mac_addr) { - ret = -ENOENT; + struct sta_info *sta; + struct link_sta_info *link_sta; sta = sta_info_get_bss(sdata, mac_addr); if (!sta) - goto out_unlock; + return NULL; - if (pairwise) - key = key_mtx_dereference(local, sta->ptk[key_idx]); - else - key = key_mtx_dereference(local, sta->gtk[key_idx]); - } else - key = key_mtx_dereference(local, sdata->keys[key_idx]); + if (link_id >= 0) { + link_sta = rcu_dereference_check(sta->link[link_id], + lockdep_is_held(&local->hw.wiphy->mtx)); + if (!link_sta) + return NULL; + } else { + link_sta = &sta->deflink; + } - if (!key) { - ret = -ENOENT; - goto out_unlock; + if (pairwise && key_idx < NUM_DEFAULT_KEYS) + return wiphy_dereference(local->hw.wiphy, + sta->ptk[key_idx]); + + if (!pairwise && + key_idx < NUM_DEFAULT_KEYS + + NUM_DEFAULT_MGMT_KEYS + + NUM_DEFAULT_BEACON_KEYS) + return wiphy_dereference(local->hw.wiphy, + link_sta->gtk[key_idx]); + + return NULL; } - ieee80211_key_free(key, sdata->vif.type == NL80211_IFTYPE_STATION); + if (pairwise && key_idx < NUM_DEFAULT_KEYS) + return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]); - ret = 0; - out_unlock: - mutex_unlock(&local->key_mtx); - mutex_unlock(&local->sta_mtx); + key = wiphy_dereference(local->hw.wiphy, link->gtk[key_idx]); + if (key) + return key; - return ret; + /* or maybe it was a WEP key */ + if (key_idx < NUM_DEFAULT_KEYS) + return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]); + + return NULL; +} + +static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + struct ieee80211_key *key; + + lockdep_assert_wiphy(local->hw.wiphy); + + key = ieee80211_lookup_key(sdata, link_id, key_idx, pairwise, mac_addr); + if (!key) + return -ENOENT; + + ieee80211_key_free(key, sdata->vif.type == NL80211_IFTYPE_STATION); + + return 0; } static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx, bool pairwise, const u8 *mac_addr, - void *cookie, + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr, void *cookie, void (*callback)(void *cookie, struct key_params *params)) { struct ieee80211_sub_if_data *sdata; - struct sta_info *sta = NULL; u8 seq[6] = {0}; struct key_params params; - struct ieee80211_key *key = NULL; + struct ieee80211_key *key; u64 pn64; u32 iv32; u16 iv16; @@ -610,20 +829,7 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, rcu_read_lock(); - if (mac_addr) { - sta = sta_info_get_bss(sdata, mac_addr); - if (!sta) - goto out; - - if (pairwise && key_idx < NUM_DEFAULT_KEYS) - key = rcu_dereference(sta->ptk[key_idx]); - else if (!pairwise && - key_idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS + - NUM_DEFAULT_BEACON_KEYS) - key = rcu_dereference(sta->gtk[key_idx]); - } else - key = rcu_dereference(sdata->keys[key_idx]); - + key = ieee80211_lookup_key(sdata, link_id, key_idx, pairwise, mac_addr); if (!key) goto out; @@ -697,9 +903,6 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, break; } - params.key = key->conf.key; - params.key_len = key->conf.keylen; - callback(cookie, ¶ms); err = 0; @@ -710,34 +913,49 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, static int ieee80211_config_default_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx, bool uni, + int link_id, u8 key_idx, bool uni, bool multi) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link = + ieee80211_link_or_deflink(sdata, link_id, false); + + if (IS_ERR(link)) + return PTR_ERR(link); - ieee80211_set_default_key(sdata, key_idx, uni, multi); + ieee80211_set_default_key(link, key_idx, uni, multi); return 0; } static int ieee80211_config_default_mgmt_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx) + int link_id, u8 key_idx) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link = + ieee80211_link_or_deflink(sdata, link_id, true); - ieee80211_set_default_mgmt_key(sdata, key_idx); + if (IS_ERR(link)) + return PTR_ERR(link); + + ieee80211_set_default_mgmt_key(link, key_idx); return 0; } static int ieee80211_config_default_beacon_key(struct wiphy *wiphy, struct net_device *dev, - u8 key_idx) + int link_id, u8 key_idx) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link = + ieee80211_link_or_deflink(sdata, link_id, true); + + if (IS_ERR(link)) + return PTR_ERR(link); - ieee80211_set_default_beacon_key(sdata, key_idx); + ieee80211_set_default_beacon_key(link, key_idx); return 0; } @@ -756,15 +974,11 @@ void sta_set_rate_info_tx(struct sta_info *sta, rinfo->nss = ieee80211_rate_get_vht_nss(rate); } else { struct ieee80211_supported_band *sband; - int shift = ieee80211_vif_get_shift(&sta->sdata->vif); - u16 brate; sband = ieee80211_get_sband(sta->sdata); WARN_ON_ONCE(sband && !sband->bitrates); - if (sband && sband->bitrates) { - brate = sband->bitrates[rate->idx].bitrate; - rinfo->legacy = DIV_ROUND_UP(brate, 1 << shift); - } + if (sband && sband->bitrates) + rinfo->legacy = sband->bitrates[rate->idx].bitrate; } if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) rinfo->bw = RATE_INFO_BW_40; @@ -786,16 +1000,21 @@ static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev, struct sta_info *sta; int ret = -ENOENT; - mutex_lock(&local->sta_mtx); + lockdep_assert_wiphy(local->hw.wiphy); sta = sta_info_get_by_idx(sdata, idx); if (sta) { ret = 0; memcpy(mac, sta->sta.addr, ETH_ALEN); sta_set_sinfo(sta, sinfo, true); - } - mutex_unlock(&local->sta_mtx); + /* Add accumulated removed link data to sinfo data for + * consistency for MLO + */ + if (sinfo->valid_links) + sta_set_accumulated_removed_links_sinfo(sta, sinfo); + + } return ret; } @@ -816,62 +1035,73 @@ static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev, struct sta_info *sta; int ret = -ENOENT; - mutex_lock(&local->sta_mtx); + lockdep_assert_wiphy(local->hw.wiphy); sta = sta_info_get_bss(sdata, mac); if (sta) { ret = 0; sta_set_sinfo(sta, sinfo, true); - } - mutex_unlock(&local->sta_mtx); + /* Add accumulated removed link data to sinfo data for + * consistency for MLO + */ + if (sinfo->valid_links) + sta_set_accumulated_removed_links_sinfo(sta, sinfo); + } return ret; } static int ieee80211_set_monitor_channel(struct wiphy *wiphy, + struct net_device *dev, struct cfg80211_chan_def *chandef) { struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_sub_if_data *sdata; - int ret = 0; + struct ieee80211_chan_req chanreq = { .oper = *chandef }; + int ret; - if (cfg80211_chandef_identical(&local->monitor_chandef, chandef)) - return 0; + lockdep_assert_wiphy(local->hw.wiphy); - mutex_lock(&local->mtx); - if (local->use_chanctx) { - sdata = wiphy_dereference(local->hw.wiphy, - local->monitor_sdata); - if (sdata) { - ieee80211_vif_release_channel(sdata); - ret = ieee80211_vif_use_channel(sdata, chandef, - IEEE80211_CHANCTX_EXCLUSIVE); - } - } else if (local->open_count == local->monitors) { - local->_oper_chandef = *chandef; - ieee80211_hw_config(local, 0); + sdata = IEEE80211_DEV_TO_SUB_IF(dev); + if (!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) { + if (cfg80211_chandef_identical(&local->monitor_chanreq.oper, + &chanreq.oper)) + return 0; + + sdata = wiphy_dereference(wiphy, local->monitor_sdata); + if (!sdata) + goto done; } - if (ret == 0) - local->monitor_chandef = *chandef; - mutex_unlock(&local->mtx); + if (rcu_access_pointer(sdata->deflink.conf->chanctx_conf) && + cfg80211_chandef_identical(&sdata->vif.bss_conf.chanreq.oper, + &chanreq.oper)) + return 0; - return ret; + ieee80211_link_release_channel(&sdata->deflink); + ret = ieee80211_link_use_channel(&sdata->deflink, &chanreq, + IEEE80211_CHANCTX_SHARED); + if (ret) + return ret; +done: + local->monitor_chanreq = chanreq; + return 0; } static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, const u8 *resp, size_t resp_len, const struct ieee80211_csa_settings *csa, - const struct ieee80211_color_change_settings *cca) + const struct ieee80211_color_change_settings *cca, + struct ieee80211_link_data *link) { struct probe_resp *new, *old; if (!resp || !resp_len) return 1; - old = sdata_dereference(sdata->u.ap.probe_resp, sdata); + old = sdata_dereference(link->u.ap.probe_resp, sdata); new = kzalloc(sizeof(struct probe_resp) + resp_len, GFP_KERNEL); if (!new) @@ -887,7 +1117,7 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, else if (cca) new->cntdwn_counter_offsets[0] = cca->counter_offset_presp; - rcu_assign_pointer(sdata->u.ap.probe_resp, new); + rcu_assign_pointer(link->u.ap.probe_resp, new); if (old) kfree_rcu(old, rcu_head); @@ -895,73 +1125,128 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, } static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, - struct cfg80211_fils_discovery *params) + struct cfg80211_fils_discovery *params, + struct ieee80211_link_data *link, + struct ieee80211_bss_conf *link_conf, + u64 *changed) { struct fils_discovery_data *new, *old = NULL; struct ieee80211_fils_discovery *fd; - if (!params->tmpl || !params->tmpl_len) - return -EINVAL; + if (!params->update) + return 0; - fd = &sdata->vif.bss_conf.fils_discovery; + fd = &link_conf->fils_discovery; fd->min_interval = params->min_interval; fd->max_interval = params->max_interval; - old = sdata_dereference(sdata->u.ap.fils_discovery, sdata); - new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); - if (!new) - return -ENOMEM; - new->len = params->tmpl_len; - memcpy(new->data, params->tmpl, params->tmpl_len); - rcu_assign_pointer(sdata->u.ap.fils_discovery, new); - + old = sdata_dereference(link->u.ap.fils_discovery, sdata); if (old) kfree_rcu(old, rcu_head); + if (params->tmpl && params->tmpl_len) { + new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); + if (!new) + return -ENOMEM; + new->len = params->tmpl_len; + memcpy(new->data, params->tmpl, params->tmpl_len); + rcu_assign_pointer(link->u.ap.fils_discovery, new); + } else { + RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); + } + + *changed |= BSS_CHANGED_FILS_DISCOVERY; return 0; } static int ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, - struct cfg80211_unsol_bcast_probe_resp *params) + struct cfg80211_unsol_bcast_probe_resp *params, + struct ieee80211_link_data *link, + struct ieee80211_bss_conf *link_conf, + u64 *changed) { struct unsol_bcast_probe_resp_data *new, *old = NULL; - if (!params->tmpl || !params->tmpl_len) + if (!params->update) + return 0; + + link_conf->unsol_bcast_probe_resp_interval = params->interval; + + old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata); + if (old) + kfree_rcu(old, rcu_head); + + if (params->tmpl && params->tmpl_len) { + new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); + if (!new) + return -ENOMEM; + new->len = params->tmpl_len; + memcpy(new->data, params->tmpl, params->tmpl_len); + rcu_assign_pointer(link->u.ap.unsol_bcast_probe_resp, new); + } else { + RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); + } + + *changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP; + return 0; +} + +static int +ieee80211_set_s1g_short_beacon(struct ieee80211_sub_if_data *sdata, + struct ieee80211_link_data *link, + struct cfg80211_s1g_short_beacon *params) +{ + struct s1g_short_beacon_data *new; + struct s1g_short_beacon_data *old = + sdata_dereference(link->u.ap.s1g_short_beacon, sdata); + size_t new_len = + sizeof(*new) + params->short_head_len + params->short_tail_len; + + if (!params->update) + return 0; + + if (!params->short_head) return -EINVAL; - old = sdata_dereference(sdata->u.ap.unsol_bcast_probe_resp, sdata); - new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); + new = kzalloc(new_len, GFP_KERNEL); if (!new) return -ENOMEM; - new->len = params->tmpl_len; - memcpy(new->data, params->tmpl, params->tmpl_len); - rcu_assign_pointer(sdata->u.ap.unsol_bcast_probe_resp, new); + + /* Memory layout: | struct | head | tail | */ + new->short_head = (u8 *)new + sizeof(*new); + new->short_head_len = params->short_head_len; + memcpy(new->short_head, params->short_head, params->short_head_len); + + if (params->short_tail) { + new->short_tail = new->short_head + params->short_head_len; + new->short_tail_len = params->short_tail_len; + memcpy(new->short_tail, params->short_tail, + params->short_tail_len); + } + + rcu_assign_pointer(link->u.ap.s1g_short_beacon, new); if (old) kfree_rcu(old, rcu_head); - sdata->vif.bss_conf.unsol_bcast_probe_resp_interval = - params->interval; - return 0; } static int ieee80211_set_ftm_responder_params( struct ieee80211_sub_if_data *sdata, const u8 *lci, size_t lci_len, - const u8 *civicloc, size_t civicloc_len) + const u8 *civicloc, size_t civicloc_len, + struct ieee80211_bss_conf *link_conf) { struct ieee80211_ftm_responder_params *new, *old; - struct ieee80211_bss_conf *bss_conf; u8 *pos; int len; if (!lci_len && !civicloc_len) return 0; - bss_conf = &sdata->vif.bss_conf; - old = bss_conf->ftmr_params; + old = link_conf->ftmr_params; len = lci_len + civicloc_len; new = kzalloc(sizeof(*new) + len, GFP_KERNEL); @@ -983,24 +1268,63 @@ static int ieee80211_set_ftm_responder_params( pos += civicloc_len; } - bss_conf->ftmr_params = new; + link_conf->ftmr_params = new; kfree(old); return 0; } -static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, - struct cfg80211_beacon_data *params, - const struct ieee80211_csa_settings *csa, - const struct ieee80211_color_change_settings *cca) +static int +ieee80211_copy_mbssid_beacon(u8 *pos, struct cfg80211_mbssid_elems *dst, + struct cfg80211_mbssid_elems *src) +{ + int i, offset = 0; + + dst->cnt = src->cnt; + for (i = 0; i < src->cnt; i++) { + memcpy(pos + offset, src->elem[i].data, src->elem[i].len); + dst->elem[i].len = src->elem[i].len; + dst->elem[i].data = pos + offset; + offset += dst->elem[i].len; + } + + return offset; +} + +static int +ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst, + struct cfg80211_rnr_elems *src) { + int i, offset = 0; + + dst->cnt = src->cnt; + for (i = 0; i < src->cnt; i++) { + memcpy(pos + offset, src->elem[i].data, src->elem[i].len); + dst->elem[i].len = src->elem[i].len; + dst->elem[i].data = pos + offset; + offset += dst->elem[i].len; + } + + return offset; +} + +static int +ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, + struct ieee80211_link_data *link, + struct cfg80211_beacon_data *params, + const struct ieee80211_csa_settings *csa, + const struct ieee80211_color_change_settings *cca, + u64 *changed) +{ + struct cfg80211_mbssid_elems *mbssid = NULL; + struct cfg80211_rnr_elems *rnr = NULL; struct beacon_data *new, *old; int new_head_len, new_tail_len; int size, err; - u32 changed = BSS_CHANGED_BEACON; - - old = sdata_dereference(sdata->u.ap.beacon, sdata); + u64 _changed = BSS_CHANGED_BEACON; + struct ieee80211_bss_conf *link_conf = link->conf; + old = sdata_dereference(link->u.ap.beacon, sdata); /* Need to have a beacon head if we don't have one yet */ if (!params->head && !old) @@ -1021,6 +1345,27 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, size = sizeof(*new) + new_head_len + new_tail_len; + /* new or old multiple BSSID elements? */ + if (params->mbssid_ies) { + mbssid = params->mbssid_ies; + size += struct_size(new->mbssid_ies, elem, mbssid->cnt); + if (params->rnr_ies) { + rnr = params->rnr_ies; + size += struct_size(new->rnr_ies, elem, rnr->cnt); + } + size += ieee80211_get_mbssid_beacon_len(mbssid, rnr, + mbssid->cnt); + } else if (old && old->mbssid_ies) { + mbssid = old->mbssid_ies; + size += struct_size(new->mbssid_ies, elem, mbssid->cnt); + if (old && old->rnr_ies) { + rnr = old->rnr_ies; + size += struct_size(new->rnr_ies, elem, rnr->cnt); + } + size += ieee80211_get_mbssid_beacon_len(mbssid, rnr, + mbssid->cnt); + } + new = kzalloc(size, GFP_KERNEL); if (!new) return -ENOMEM; @@ -1029,12 +1374,32 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, /* * pointers go into the block we allocated, - * memory is | beacon_data | head | tail | + * memory is | beacon_data | head | tail | mbssid_ies | rnr_ies */ new->head = ((u8 *) new) + sizeof(*new); new->tail = new->head + new_head_len; new->head_len = new_head_len; new->tail_len = new_tail_len; + /* copy in optional mbssid_ies */ + if (mbssid) { + u8 *pos = new->tail + new->tail_len; + + new->mbssid_ies = (void *)pos; + pos += struct_size(new->mbssid_ies, elem, mbssid->cnt); + pos += ieee80211_copy_mbssid_beacon(pos, new->mbssid_ies, + mbssid); + if (rnr) { + new->rnr_ies = (void *)pos; + pos += struct_size(new->rnr_ies, elem, rnr->cnt); + ieee80211_copy_rnr_beacon(pos, new->rnr_ies, rnr); + } + /* update bssid_indicator */ + if (new->mbssid_ies->cnt && new->mbssid_ies->elem[0].len > 2) + link_conf->bssid_indicator = + *(new->mbssid_ies->elem[0].data + 2); + else + link_conf->bssid_indicator = 0; + } if (csa) { new->cntdwn_current_counter = csa->count; @@ -1060,36 +1425,63 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, memcpy(new->tail, old->tail, new_tail_len); err = ieee80211_set_probe_resp(sdata, params->probe_resp, - params->probe_resp_len, csa, cca); + params->probe_resp_len, csa, cca, link); if (err < 0) { kfree(new); return err; } if (err == 0) - changed |= BSS_CHANGED_AP_PROBE_RESP; + _changed |= BSS_CHANGED_AP_PROBE_RESP; if (params->ftm_responder != -1) { - sdata->vif.bss_conf.ftm_responder = params->ftm_responder; + link_conf->ftm_responder = params->ftm_responder; err = ieee80211_set_ftm_responder_params(sdata, params->lci, params->lci_len, params->civicloc, - params->civicloc_len); + params->civicloc_len, + link_conf); if (err < 0) { kfree(new); return err; } - changed |= BSS_CHANGED_FTM_RESPONDER; + _changed |= BSS_CHANGED_FTM_RESPONDER; } - rcu_assign_pointer(sdata->u.ap.beacon, new); + rcu_assign_pointer(link->u.ap.beacon, new); + sdata->u.ap.active = true; if (old) kfree_rcu(old, rcu_head); - return changed; + *changed |= _changed; + return 0; +} + +static u8 ieee80211_num_beaconing_links(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_link_data *link; + u8 link_id, num = 0; + + if (sdata->vif.type != NL80211_IFTYPE_AP && + sdata->vif.type != NL80211_IFTYPE_P2P_GO) + return num; + + /* non-MLO mode of operation also uses link_id 0 in sdata so it is + * safe to directly proceed with the below loop + */ + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + continue; + + if (sdata_dereference(link->u.ap.beacon, sdata)) + num++; + } + + return num; } static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, @@ -1099,60 +1491,140 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; struct beacon_data *old; struct ieee80211_sub_if_data *vlan; - u32 changed = BSS_CHANGED_BEACON_INT | + u64 changed = BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON_ENABLED | BSS_CHANGED_BEACON | - BSS_CHANGED_SSID | BSS_CHANGED_P2P_PS | BSS_CHANGED_TXPOWER | BSS_CHANGED_TWT; int i, err; int prev_beacon_int; + unsigned int link_id = params->beacon.link_id; + struct ieee80211_link_data *link; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_chan_req chanreq = { .oper = params->chandef }; + u64 tsf; + + lockdep_assert_wiphy(local->hw.wiphy); + + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + return -ENOLINK; + + link_conf = link->conf; - old = sdata_dereference(sdata->u.ap.beacon, sdata); + old = sdata_dereference(link->u.ap.beacon, sdata); if (old) return -EALREADY; - if (params->smps_mode != NL80211_SMPS_OFF) - return -ENOTSUPP; + link->smps_mode = IEEE80211_SMPS_OFF; - sdata->smps_mode = IEEE80211_SMPS_OFF; + link->needed_rx_chains = sdata->local->rx_chains; - sdata->needed_rx_chains = sdata->local->rx_chains; + prev_beacon_int = link_conf->beacon_int; + link_conf->beacon_int = params->beacon_interval; - prev_beacon_int = sdata->vif.bss_conf.beacon_int; - sdata->vif.bss_conf.beacon_int = params->beacon_interval; + if (params->ht_cap) + link_conf->ht_ldpc = + params->ht_cap->cap_info & + cpu_to_le16(IEEE80211_HT_CAP_LDPC_CODING); + + if (params->vht_cap) { + link_conf->vht_ldpc = + params->vht_cap->vht_cap_info & + cpu_to_le32(IEEE80211_VHT_CAP_RXLDPC); + link_conf->vht_su_beamformer = + params->vht_cap->vht_cap_info & + cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE); + link_conf->vht_su_beamformee = + params->vht_cap->vht_cap_info & + cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE); + link_conf->vht_mu_beamformer = + params->vht_cap->vht_cap_info & + cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE); + link_conf->vht_mu_beamformee = + params->vht_cap->vht_cap_info & + cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE); + } if (params->he_cap && params->he_oper) { - sdata->vif.bss_conf.he_support = true; - sdata->vif.bss_conf.htc_trig_based_pkt_ext = + link_conf->he_support = true; + link_conf->htc_trig_based_pkt_ext = le32_get_bits(params->he_oper->he_oper_params, IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK); - sdata->vif.bss_conf.frame_time_rts_th = + link_conf->frame_time_rts_th = le32_get_bits(params->he_oper->he_oper_params, IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK); changed |= BSS_CHANGED_HE_OBSS_PD; - if (params->he_bss_color.enabled) + if (params->beacon.he_bss_color.enabled) changed |= BSS_CHANGED_HE_BSS_COLOR; } + if (params->he_cap) { + link_conf->he_ldpc = + params->he_cap->phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD; + link_conf->he_su_beamformer = + params->he_cap->phy_cap_info[3] & + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER; + link_conf->he_su_beamformee = + params->he_cap->phy_cap_info[4] & + IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE; + link_conf->he_mu_beamformer = + params->he_cap->phy_cap_info[4] & + IEEE80211_HE_PHY_CAP4_MU_BEAMFORMER; + link_conf->he_full_ul_mumimo = + params->he_cap->phy_cap_info[2] & + IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO; + } + + if (params->eht_cap) { + if (!link_conf->he_support) + return -EOPNOTSUPP; + + link_conf->eht_support = true; + + link_conf->eht_su_beamformer = + params->eht_cap->fixed.phy_cap_info[0] & + IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER; + link_conf->eht_su_beamformee = + params->eht_cap->fixed.phy_cap_info[0] & + IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE; + link_conf->eht_mu_beamformer = + params->eht_cap->fixed.phy_cap_info[7] & + (IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_80MHZ | + IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_160MHZ | + IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ); + link_conf->eht_80mhz_full_bw_ul_mumimo = + params->eht_cap->fixed.phy_cap_info[7] & + (IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_80MHZ | + IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_160MHZ | + IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ); + link_conf->eht_disable_mcs15 = + u8_get_bits(params->eht_oper->params, + IEEE80211_EHT_OPER_MCS15_DISABLE); + } else { + link_conf->eht_su_beamformer = false; + link_conf->eht_su_beamformee = false; + link_conf->eht_mu_beamformer = false; + } + if (sdata->vif.type == NL80211_IFTYPE_AP && params->mbssid_config.tx_wdev) { err = ieee80211_set_ap_mbssid_options(sdata, - params->mbssid_config); + ¶ms->mbssid_config, + link_conf); if (err) return err; } - mutex_lock(&local->mtx); - err = ieee80211_vif_use_channel(sdata, ¶ms->chandef, - IEEE80211_CHANCTX_SHARED); + err = ieee80211_link_use_channel(link, &chanreq, + IEEE80211_CHANCTX_SHARED); if (!err) - ieee80211_vif_copy_chanctx_to_vlans(sdata, false); - mutex_unlock(&local->mtx); + ieee80211_link_copy_chanctx_to_vlans(link, false); if (err) { - sdata->vif.bss_conf.beacon_int = prev_beacon_int; + link_conf->beacon_int = prev_beacon_int; return err; } @@ -1166,9 +1638,6 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, params->crypto.control_port_over_nl80211; sdata->control_port_no_preauth = params->crypto.control_port_no_preauth; - sdata->encrypt_headroom = ieee80211_cs_headroom(sdata->local, - ¶ms->crypto, - sdata->vif.type); list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) { vlan->control_port_protocol = @@ -1179,34 +1648,30 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, params->crypto.control_port_over_nl80211; vlan->control_port_no_preauth = params->crypto.control_port_no_preauth; - vlan->encrypt_headroom = - ieee80211_cs_headroom(sdata->local, - ¶ms->crypto, - vlan->vif.type); - } - - sdata->vif.bss_conf.dtim_period = params->dtim_period; - sdata->vif.bss_conf.enable_beacon = true; - sdata->vif.bss_conf.allow_p2p_go_ps = sdata->vif.p2p; - sdata->vif.bss_conf.twt_responder = params->twt_responder; - sdata->vif.bss_conf.he_obss_pd = params->he_obss_pd; - sdata->vif.bss_conf.he_bss_color = params->he_bss_color; - sdata->vif.bss_conf.s1g = params->chandef.chan->band == - NL80211_BAND_S1GHZ; - - sdata->vif.bss_conf.ssid_len = params->ssid_len; + } + + link_conf->dtim_period = params->dtim_period; + link_conf->enable_beacon = true; + link_conf->allow_p2p_go_ps = sdata->vif.p2p; + link_conf->twt_responder = params->twt_responder; + link_conf->he_obss_pd = params->he_obss_pd; + link_conf->he_bss_color = params->beacon.he_bss_color; + link_conf->s1g_long_beacon_period = params->s1g_long_beacon_period; + sdata->vif.cfg.s1g = params->chandef.chan->band == NL80211_BAND_S1GHZ; + + sdata->vif.cfg.ssid_len = params->ssid_len; if (params->ssid_len) - memcpy(sdata->vif.bss_conf.ssid, params->ssid, + memcpy(sdata->vif.cfg.ssid, params->ssid, params->ssid_len); - sdata->vif.bss_conf.hidden_ssid = + link_conf->hidden_ssid = (params->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE); - memset(&sdata->vif.bss_conf.p2p_noa_attr, 0, - sizeof(sdata->vif.bss_conf.p2p_noa_attr)); - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow = + memset(&link_conf->p2p_noa_attr, 0, + sizeof(link_conf->p2p_noa_attr)); + link_conf->p2p_noa_attr.oppps_ctwindow = params->p2p_ctwindow & IEEE80211_P2P_OPPPS_CTWINDOW_MASK; if (params->p2p_opp_ps) - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow |= + link_conf->p2p_noa_attr.oppps_ctwindow |= IEEE80211_P2P_OPPPS_ENABLE_BIT; sdata->beacon_rate_set = false; @@ -1221,84 +1686,144 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, } if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL)) - sdata->vif.bss_conf.beacon_tx_rate = params->beacon_rate; + link_conf->beacon_tx_rate = params->beacon_rate; - err = ieee80211_assign_beacon(sdata, ¶ms->beacon, NULL, NULL); + err = ieee80211_assign_beacon(sdata, link, ¶ms->beacon, NULL, NULL, + &changed); if (err < 0) goto error; - changed |= err; - if (params->fils_discovery.max_interval) { - err = ieee80211_set_fils_discovery(sdata, - ¶ms->fils_discovery); - if (err < 0) - goto error; - changed |= BSS_CHANGED_FILS_DISCOVERY; - } + err = ieee80211_set_fils_discovery(sdata, ¶ms->fils_discovery, + link, link_conf, &changed); + if (err < 0) + goto error; + + err = ieee80211_set_unsol_bcast_probe_resp(sdata, + ¶ms->unsol_bcast_probe_resp, + link, link_conf, &changed); + if (err < 0) + goto error; - if (params->unsol_bcast_probe_resp.interval) { - err = ieee80211_set_unsol_bcast_probe_resp(sdata, - ¶ms->unsol_bcast_probe_resp); + if (sdata->vif.cfg.s1g) { + err = ieee80211_set_s1g_short_beacon(sdata, link, + ¶ms->s1g_short_beacon); if (err < 0) goto error; - changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP; } - err = drv_start_ap(sdata->local, sdata); + err = drv_start_ap(sdata->local, sdata, link_conf); if (err) { - old = sdata_dereference(sdata->u.ap.beacon, sdata); + old = sdata_dereference(link->u.ap.beacon, sdata); if (old) kfree_rcu(old, rcu_head); - RCU_INIT_POINTER(sdata->u.ap.beacon, NULL); + RCU_INIT_POINTER(link->u.ap.beacon, NULL); + + if (ieee80211_num_beaconing_links(sdata) == 0) + sdata->u.ap.active = false; + goto error; } - ieee80211_recalc_dtim(local, sdata); - ieee80211_bss_info_change_notify(sdata, changed); + tsf = drv_get_tsf(local, sdata); + ieee80211_recalc_dtim(sdata, tsf); + + if (link->u.ap.s1g_short_beacon) + ieee80211_recalc_sb_count(sdata, tsf); + + ieee80211_vif_cfg_change_notify(sdata, BSS_CHANGED_SSID); + ieee80211_link_info_change_notify(sdata, link, changed); + + if (ieee80211_num_beaconing_links(sdata) <= 1) + netif_carrier_on(dev); - netif_carrier_on(dev); list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) netif_carrier_on(vlan->dev); return 0; error: - mutex_lock(&local->mtx); - ieee80211_vif_release_channel(sdata); - mutex_unlock(&local->mtx); + ieee80211_link_release_channel(link); return err; } static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, - struct cfg80211_beacon_data *params) + struct cfg80211_ap_update *params) + { - struct ieee80211_sub_if_data *sdata; + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link; + struct cfg80211_beacon_data *beacon = ¶ms->beacon; struct beacon_data *old; int err; + struct ieee80211_bss_conf *link_conf; + u64 changed = 0; - sdata = IEEE80211_DEV_TO_SUB_IF(dev); - sdata_assert_lock(sdata); + lockdep_assert_wiphy(wiphy); + + link = sdata_dereference(sdata->link[beacon->link_id], sdata); + if (!link) + return -ENOLINK; + + link_conf = link->conf; /* don't allow changing the beacon while a countdown is in place - offset * of channel switch counter may change */ - if (sdata->vif.csa_active || sdata->vif.color_change_active) + if (link_conf->csa_active || link_conf->color_change_active) return -EBUSY; - old = sdata_dereference(sdata->u.ap.beacon, sdata); + old = sdata_dereference(link->u.ap.beacon, sdata); if (!old) return -ENOENT; - err = ieee80211_assign_beacon(sdata, params, NULL, NULL); + err = ieee80211_assign_beacon(sdata, link, beacon, NULL, NULL, + &changed); + if (err < 0) + return err; + + err = ieee80211_set_fils_discovery(sdata, ¶ms->fils_discovery, + link, link_conf, &changed); if (err < 0) return err; - ieee80211_bss_info_change_notify(sdata, err); + + err = ieee80211_set_unsol_bcast_probe_resp(sdata, + ¶ms->unsol_bcast_probe_resp, + link, link_conf, &changed); + if (err < 0) + return err; + + if (link->u.ap.s1g_short_beacon) { + err = ieee80211_set_s1g_short_beacon(sdata, link, + ¶ms->s1g_short_beacon); + if (err < 0) + return err; + } + + if (beacon->he_bss_color_valid && + beacon->he_bss_color.enabled != link_conf->he_bss_color.enabled) { + link_conf->he_bss_color.enabled = beacon->he_bss_color.enabled; + changed |= BSS_CHANGED_HE_BSS_COLOR; + } + + ieee80211_link_info_change_notify(sdata, link, changed); return 0; } -static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) +static void ieee80211_free_next_beacon(struct ieee80211_link_data *link) +{ + if (!link->u.ap.next_beacon) + return; + + kfree(link->u.ap.next_beacon->mbssid_ies); + kfree(link->u.ap.next_beacon->rnr_ies); + kfree(link->u.ap.next_beacon); + link->u.ap.next_beacon = NULL; +} + +static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_sub_if_data *vlan; @@ -1307,44 +1832,50 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) struct probe_resp *old_probe_resp; struct fils_discovery_data *old_fils_discovery; struct unsol_bcast_probe_resp_data *old_unsol_bcast_probe_resp; + struct s1g_short_beacon_data *old_s1g_short_beacon; struct cfg80211_chan_def chandef; + struct ieee80211_link_data *link = + sdata_dereference(sdata->link[link_id], sdata); + struct ieee80211_bss_conf *link_conf = link->conf; + LIST_HEAD(keys); - sdata_assert_lock(sdata); + lockdep_assert_wiphy(local->hw.wiphy); - old_beacon = sdata_dereference(sdata->u.ap.beacon, sdata); + old_beacon = sdata_dereference(link->u.ap.beacon, sdata); if (!old_beacon) return -ENOENT; - old_probe_resp = sdata_dereference(sdata->u.ap.probe_resp, sdata); - old_fils_discovery = sdata_dereference(sdata->u.ap.fils_discovery, + old_probe_resp = sdata_dereference(link->u.ap.probe_resp, + sdata); + old_fils_discovery = sdata_dereference(link->u.ap.fils_discovery, sdata); old_unsol_bcast_probe_resp = - sdata_dereference(sdata->u.ap.unsol_bcast_probe_resp, + sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata); + old_s1g_short_beacon = + sdata_dereference(link->u.ap.s1g_short_beacon, sdata); - /* abort any running channel switch */ - mutex_lock(&local->mtx); - sdata->vif.csa_active = false; - if (sdata->csa_block_tx) { - ieee80211_wake_vif_queues(local, sdata, - IEEE80211_QUEUE_STOP_REASON_CSA); - sdata->csa_block_tx = false; - } - - mutex_unlock(&local->mtx); + /* abort any running channel switch or color change */ + link_conf->csa_active = false; + link_conf->color_change_active = false; + ieee80211_vif_unblock_queues_csa(sdata); - kfree(sdata->u.ap.next_beacon); - sdata->u.ap.next_beacon = NULL; + ieee80211_free_next_beacon(link); /* turn off carrier for this interface and dependent VLANs */ list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) netif_carrier_off(vlan->dev); - netif_carrier_off(dev); + + if (ieee80211_num_beaconing_links(sdata) <= 1) { + netif_carrier_off(dev); + sdata->u.ap.active = false; + } /* remove beacon and probe response */ - RCU_INIT_POINTER(sdata->u.ap.beacon, NULL); - RCU_INIT_POINTER(sdata->u.ap.probe_resp, NULL); - RCU_INIT_POINTER(sdata->u.ap.fils_discovery, NULL); - RCU_INIT_POINTER(sdata->u.ap.unsol_bcast_probe_resp, NULL); + RCU_INIT_POINTER(link->u.ap.beacon, NULL); + RCU_INIT_POINTER(link->u.ap.probe_resp, NULL); + RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); + RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); + RCU_INIT_POINTER(link->u.ap.s1g_short_beacon, NULL); kfree_rcu(old_beacon, rcu_head); if (old_probe_resp) kfree_rcu(old_probe_resp, rcu_head); @@ -1352,37 +1883,55 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) kfree_rcu(old_fils_discovery, rcu_head); if (old_unsol_bcast_probe_resp) kfree_rcu(old_unsol_bcast_probe_resp, rcu_head); + if (old_s1g_short_beacon) + kfree_rcu(old_s1g_short_beacon, rcu_head); + + kfree(link_conf->ftmr_params); + link_conf->ftmr_params = NULL; - kfree(sdata->vif.bss_conf.ftmr_params); - sdata->vif.bss_conf.ftmr_params = NULL; + link_conf->bssid_index = 0; + link_conf->nontransmitted = false; + link_conf->ema_ap = false; + link_conf->bssid_indicator = 0; + link_conf->fils_discovery.min_interval = 0; + link_conf->fils_discovery.max_interval = 0; + link_conf->unsol_bcast_probe_resp_interval = 0; - __sta_info_flush(sdata, true); - ieee80211_free_keys(sdata, true); + __sta_info_flush(sdata, true, link_id, NULL); - sdata->vif.bss_conf.enable_beacon = false; + ieee80211_remove_link_keys(link, &keys); + if (!list_empty(&keys)) { + synchronize_net(); + ieee80211_free_key_list(local, &keys); + } + + ieee80211_stop_mbssid(sdata); + RCU_INIT_POINTER(link_conf->tx_bss_conf, NULL); + + link_conf->enable_beacon = false; sdata->beacon_rate_set = false; - sdata->vif.bss_conf.ssid_len = 0; + sdata->vif.cfg.ssid_len = 0; + sdata->vif.cfg.s1g = false; clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, &sdata->state); - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED); + ieee80211_link_info_change_notify(sdata, link, + BSS_CHANGED_BEACON_ENABLED); - if (sdata->wdev.cac_started) { - chandef = sdata->vif.bss_conf.chandef; - cancel_delayed_work_sync(&sdata->dfs_cac_timer_work); + if (sdata->wdev.links[link_id].cac_started) { + chandef = link_conf->chanreq.oper; + wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work); cfg80211_cac_event(sdata->dev, &chandef, NL80211_RADAR_CAC_ABORTED, - GFP_KERNEL); + GFP_KERNEL, link_id); } - drv_stop_ap(sdata->local, sdata); + drv_stop_ap(sdata->local, sdata, link_conf); /* free all potentially still buffered bcast frames */ local->total_ps_buffered -= skb_queue_len(&sdata->u.ap.ps.bc_buf); ieee80211_purge_tx_queue(&local->hw, &sdata->u.ap.ps.bc_buf); - mutex_lock(&local->mtx); - ieee80211_vif_copy_chanctx_to_vlans(sdata, true); - ieee80211_vif_release_channel(sdata); - mutex_unlock(&local->mtx); + ieee80211_link_copy_chanctx_to_vlans(link, true); + ieee80211_link_release_channel(link); return 0; } @@ -1410,7 +1959,7 @@ static int sta_apply_auth_flags(struct ieee80211_local *local, * before drv_sta_state() is called. */ if (!test_sta_flag(sta, WLAN_STA_RATE_CONTROL)) - rate_control_rate_init(sta); + rate_control_rate_init_all_links(sta); ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC); if (ret) @@ -1453,7 +2002,7 @@ static void sta_apply_mesh_params(struct ieee80211_local *local, { #ifdef CONFIG_MAC80211_MESH struct ieee80211_sub_if_data *sdata = sta->sdata; - u32 changed = 0; + u64 changed = 0; if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) { switch (params->plink_state) { @@ -1512,50 +2061,150 @@ static void sta_apply_mesh_params(struct ieee80211_local *local, #endif } -static void sta_apply_airtime_params(struct ieee80211_local *local, +enum sta_link_apply_mode { + STA_LINK_MODE_NEW, + STA_LINK_MODE_STA_MODIFY, + STA_LINK_MODE_LINK_MODIFY, +}; + +static int sta_link_apply_parameters(struct ieee80211_local *local, struct sta_info *sta, - struct station_parameters *params) + enum sta_link_apply_mode mode, + struct link_station_parameters *params) { - u8 ac; + struct ieee80211_supported_band *sband; + struct ieee80211_sub_if_data *sdata = sta->sdata; + u32 link_id = params->link_id < 0 ? 0 : params->link_id; + struct ieee80211_link_data *link = + sdata_dereference(sdata->link[link_id], sdata); + struct link_sta_info *link_sta = + rcu_dereference_protected(sta->link[link_id], + lockdep_is_held(&local->hw.wiphy->mtx)); + bool changes = params->link_mac || + params->txpwr_set || + params->supported_rates_len || + params->ht_capa || + params->vht_capa || + params->he_capa || + params->eht_capa || + params->s1g_capa || + params->opmode_notif_used; + + switch (mode) { + case STA_LINK_MODE_NEW: + if (!params->link_mac) + return -EINVAL; + break; + case STA_LINK_MODE_LINK_MODIFY: + break; + case STA_LINK_MODE_STA_MODIFY: + if (params->link_id >= 0) + break; + if (!changes) + return 0; + break; + } - for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { - struct airtime_sched_info *air_sched = &local->airtime[ac]; - struct airtime_info *air_info = &sta->airtime[ac]; - struct txq_info *txqi; - u8 tid; - - spin_lock_bh(&air_sched->lock); - for (tid = 0; tid < IEEE80211_NUM_TIDS + 1; tid++) { - if (air_info->weight == params->airtime_weight || - !sta->sta.txq[tid] || - ac != ieee80211_ac_from_tid(tid)) - continue; + if (!link || !link_sta) + return -EINVAL; + + sband = ieee80211_get_link_sband(link); + if (!sband) + return -EINVAL; - airtime_weight_set(air_info, params->airtime_weight); + if (params->link_mac) { + if (mode == STA_LINK_MODE_NEW) { + memcpy(link_sta->addr, params->link_mac, ETH_ALEN); + memcpy(link_sta->pub->addr, params->link_mac, ETH_ALEN); + } else if (!ether_addr_equal(link_sta->addr, + params->link_mac)) { + return -EINVAL; + } + } - txqi = to_txq_info(sta->sta.txq[tid]); - if (RB_EMPTY_NODE(&txqi->schedule_order)) - continue; + if (params->txpwr_set) { + int ret; + + link_sta->pub->txpwr.type = params->txpwr.type; + if (params->txpwr.type == NL80211_TX_POWER_LIMITED) + link_sta->pub->txpwr.power = params->txpwr.power; + ret = drv_sta_set_txpwr(local, sdata, sta); + if (ret) + return ret; + } + + if (params->supported_rates && + params->supported_rates_len && + !ieee80211_parse_bitrates(link->conf->chanreq.oper.width, + sband, params->supported_rates, + params->supported_rates_len, + &link_sta->pub->supp_rates[sband->band])) + return -EINVAL; + + if (params->ht_capa) + ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband, + params->ht_capa, link_sta); + + /* VHT can override some HT caps such as the A-MSDU max length */ + if (params->vht_capa) + ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband, + params->vht_capa, NULL, + link_sta); + + if (params->he_capa) + ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, + (void *)params->he_capa, + params->he_capa_len, + (void *)params->he_6ghz_capa, + link_sta); + + if (params->he_capa && params->eht_capa) + ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, + (u8 *)params->he_capa, + params->he_capa_len, + params->eht_capa, + params->eht_capa_len, + link_sta); + + if (params->s1g_capa) + ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa, + link_sta); + + ieee80211_sta_init_nss(link_sta); + + if (params->opmode_notif_used) { + enum nl80211_chan_width width = link->conf->chanreq.oper.width; - ieee80211_update_airtime_weight(local, air_sched, - 0, true); + switch (width) { + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_40: + case NL80211_CHAN_WIDTH_80: + case NL80211_CHAN_WIDTH_160: + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_320: /* not VHT, allowed for HE/EHT */ + break; + default: + return -EINVAL; } - spin_unlock_bh(&air_sched->lock); + + /* returned value is only needed for rc update, but the + * rc isn't initialized here yet, so ignore it + */ + __ieee80211_vht_handle_opmode(sdata, link_sta, + params->opmode_notif, + sband->band); } + + return 0; } static int sta_apply_parameters(struct ieee80211_local *local, struct sta_info *sta, struct station_parameters *params) { - int ret = 0; - struct ieee80211_supported_band *sband; struct ieee80211_sub_if_data *sdata = sta->sdata; u32 mask, set; - - sband = ieee80211_get_sband(sdata); - if (!sband) - return -EINVAL; + int ret = 0; mask = params->sta_flags_mask; set = params->sta_flags_set; @@ -1619,9 +2268,12 @@ static int sta_apply_parameters(struct ieee80211_local *local, clear_sta_flag(sta, WLAN_STA_TDLS_PEER); } + if (mask & BIT(NL80211_STA_FLAG_SPP_AMSDU)) + sta->sta.spp_amsdu = set & BIT(NL80211_STA_FLAG_SPP_AMSDU); + /* mark TDLS channel switch support, if the AP allows it */ if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) && - !sdata->u.mgd.tdls_chan_switch_prohibited && + !sdata->deflink.u.mgd.tdls_chan_switch_prohibited && params->ext_capab_len >= 4 && params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) set_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH); @@ -1638,40 +2290,21 @@ static int sta_apply_parameters(struct ieee80211_local *local, sta->sta.max_sp = params->max_sp; } - /* The sender might not have sent the last bit, consider it to be 0 */ - if (params->ext_capab_len >= 8) { - u8 val = (params->ext_capab[7] & - WLAN_EXT_CAPA8_MAX_MSDU_IN_AMSDU_LSB) >> 7; - - /* we did get all the bits, take the MSB as well */ - if (params->ext_capab_len >= 9) { - u8 val_msb = params->ext_capab[8] & - WLAN_EXT_CAPA9_MAX_MSDU_IN_AMSDU_MSB; - val_msb <<= 1; - val |= val_msb; - } - - switch (val) { - case 1: - sta->sta.max_amsdu_subframes = 32; - break; - case 2: - sta->sta.max_amsdu_subframes = 16; - break; - case 3: - sta->sta.max_amsdu_subframes = 8; - break; - default: - sta->sta.max_amsdu_subframes = 0; - } - } + ieee80211_sta_set_max_amsdu_subframes(sta, params->ext_capab, + params->ext_capab_len); /* * cfg80211 validates this (1-2007) and allows setting the AID - * only when creating a new station entry + * only when creating a new station entry. For S1G APs, the current + * implementation supports a maximum of 1600 AIDs. */ - if (params->aid) + if (params->aid) { + if (sdata->vif.cfg.s1g && + params->aid > IEEE80211_MAX_SUPPORTED_S1G_AID) + return -EINVAL; + sta->sta.aid = params->aid; + } /* * Some of the following updates would be racy if called on an @@ -1684,45 +2317,13 @@ static int sta_apply_parameters(struct ieee80211_local *local, if (params->listen_interval >= 0) sta->listen_interval = params->listen_interval; - if (params->sta_modify_mask & STATION_PARAM_APPLY_STA_TXPOWER) { - sta->sta.txpwr.type = params->txpwr.type; - if (params->txpwr.type == NL80211_TX_POWER_LIMITED) - sta->sta.txpwr.power = params->txpwr.power; - ret = drv_sta_set_txpwr(local, sdata, sta); - if (ret) - return ret; - } - - if (params->supported_rates && params->supported_rates_len) { - ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef, - sband, params->supported_rates, - params->supported_rates_len, - &sta->sta.supp_rates[sband->band]); - } - - if (params->ht_capa) - ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband, - params->ht_capa, sta); - - /* VHT can override some HT caps such as the A-MSDU max length */ - if (params->vht_capa) - ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband, - params->vht_capa, sta); + if (params->eml_cap_present) + sta->sta.eml_cap = params->eml_cap; - if (params->he_capa) - ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, - (void *)params->he_capa, - params->he_capa_len, - (void *)params->he_6ghz_capa, - sta); - - if (params->opmode_notif_used) { - /* returned value is only needed for rc update, but the - * rc isn't initialized here yet, so ignore it - */ - __ieee80211_vht_handle_opmode(sdata, sta, params->opmode_notif, - sband->band); - } + ret = sta_link_apply_parameters(local, sta, STA_LINK_MODE_STA_MODIFY, + ¶ms->link_sta_params); + if (ret) + return ret; if (params->support_p2p_ps >= 0) sta->sta.support_p2p_ps = params->support_p2p_ps; @@ -1731,8 +2332,7 @@ static int sta_apply_parameters(struct ieee80211_local *local, sta_apply_mesh_params(local, sta, params); if (params->airtime_weight) - sta_apply_airtime_params(local, sta, params); - + sta->airtime_weight = params->airtime_weight; /* set the STA state after all sta info from usermode has been set */ if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) || @@ -1742,6 +2342,10 @@ static int sta_apply_parameters(struct ieee80211_local *local, return ret; } + /* Mark the STA as MLO if MLD MAC address is available */ + if (params->link_sta_params.mld_mac) + sta->sta.mlo = true; + return 0; } @@ -1754,6 +2358,8 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_sub_if_data *sdata; int err; + lockdep_assert_wiphy(local->hw.wiphy); + if (params->vlan) { sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan); @@ -1774,13 +2380,29 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, !sdata->u.mgd.associated) return -EINVAL; - sta = sta_info_alloc(sdata, mac, GFP_KERNEL); + /* + * If we have a link ID, it can be a non-MLO station on an AP MLD, + * but we need to have a link_mac in that case as well, so use the + * STA's MAC address in that case. + */ + if (params->link_sta_params.link_id >= 0) + sta = sta_info_alloc_with_link(sdata, mac, + params->link_sta_params.link_id, + params->link_sta_params.link_mac ?: mac, + GFP_KERNEL); + else + sta = sta_info_alloc(sdata, mac, GFP_KERNEL); + if (!sta) return -ENOMEM; if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) sta->sta.tdls = true; + /* Though the mutex is not needed here (since the station is not + * visible yet), sta_apply_parameters (and inner functions) require + * the mutex due to other paths. + */ err = sta_apply_parameters(local, sta, params); if (err) { sta_info_free(local, sta); @@ -1794,7 +2416,7 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, */ if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER) && test_sta_flag(sta, WLAN_STA_ASSOC)) - rate_control_rate_init(sta); + rate_control_rate_init_all_links(sta); return sta_info_insert(sta); } @@ -1809,7 +2431,7 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, if (params->mac) return sta_info_destroy_addr_bss(sdata, params->mac); - sta_info_flush(sdata); + sta_info_flush(sdata, params->link_id); return 0; } @@ -1824,13 +2446,11 @@ static int ieee80211_change_station(struct wiphy *wiphy, enum cfg80211_station_type statype; int err; - mutex_lock(&local->sta_mtx); + lockdep_assert_wiphy(local->hw.wiphy); sta = sta_info_get_bss(sdata, mac); - if (!sta) { - err = -ENOENT; - goto out_err; - } + if (!sta) + return -ENOENT; switch (sdata->vif.type) { case NL80211_IFTYPE_MESH_POINT: @@ -1860,22 +2480,19 @@ static int ieee80211_change_station(struct wiphy *wiphy, statype = CFG80211_STA_AP_CLIENT_UNASSOC; break; default: - err = -EOPNOTSUPP; - goto out_err; + return -EOPNOTSUPP; } err = cfg80211_check_station_change(wiphy, params, statype); if (err) - goto out_err; + return err; if (params->vlan && params->vlan != sta->sdata->dev) { vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan); if (params->vlan->ieee80211_ptr->use_4addr) { - if (vlansdata->u.vlan.sta) { - err = -EBUSY; - goto out_err; - } + if (vlansdata->u.vlan.sta) + return -EBUSY; rcu_assign_pointer(vlansdata->u.vlan.sta, sta); __ieee80211_check_fast_rx_iface(vlansdata); @@ -1883,15 +2500,14 @@ static int ieee80211_change_station(struct wiphy *wiphy, } if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN && - sta->sdata->u.vlan.sta) { - ieee80211_clear_fast_rx(sta); + sta->sdata->u.vlan.sta) RCU_INIT_POINTER(sta->sdata->u.vlan.sta, NULL); - } if (test_sta_flag(sta, WLAN_STA_AUTHORIZED)) ieee80211_vif_dec_num_mcast(sta->sdata); sta->sdata = vlansdata; + ieee80211_check_fast_rx(sta); ieee80211_check_fast_xmit(sta); if (test_sta_flag(sta, WLAN_STA_AUTHORIZED)) { @@ -1903,9 +2519,7 @@ static int ieee80211_change_station(struct wiphy *wiphy, err = sta_apply_parameters(local, sta, params); if (err) - goto out_err; - - mutex_unlock(&local->sta_mtx); + return err; if (sdata->vif.type == NL80211_IFTYPE_STATION && params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) { @@ -1914,9 +2528,6 @@ static int ieee80211_change_station(struct wiphy *wiphy, } return 0; -out_err: - mutex_unlock(&local->sta_mtx); - return err; } #ifdef CONFIG_MAC80211_MESH @@ -2148,14 +2759,12 @@ static int copy_mesh_setup(struct ieee80211_if_mesh *ifmsh, const struct mesh_setup *setup) { u8 *new_ie; - const u8 *old_ie; struct ieee80211_sub_if_data *sdata = container_of(ifmsh, struct ieee80211_sub_if_data, u.mesh); int i; /* allocate information elements */ new_ie = NULL; - old_ie = ifmsh->ie; if (setup->ie_len) { new_ie = kmemdup(setup->ie, setup->ie_len, @@ -2165,7 +2774,6 @@ static int copy_mesh_setup(struct ieee80211_if_mesh *ifmsh, } ifmsh->ie_len = setup->ie_len; ifmsh->ie = new_ie; - kfree(old_ie); /* now copy the rest of the setup parameters */ ifmsh->mesh_id_len = setup->mesh_id_len; @@ -2285,13 +2893,14 @@ static int ieee80211_update_mesh_config(struct wiphy *wiphy, * devices that report signal in dBm. */ if (!ieee80211_hw_check(&sdata->local->hw, SIGNAL_DBM)) - return -ENOTSUPP; + return -EOPNOTSUPP; conf->rssi_threshold = nconf->rssi_threshold; } if (_chg_mesh_attr(NL80211_MESHCONF_HT_OPMODE, mask)) { conf->ht_opmode = nconf->ht_opmode; sdata->vif.bss_conf.ht_operation_mode = nconf->ht_opmode; - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_HT); + ieee80211_link_info_change_notify(sdata, &sdata->deflink, + BSS_CHANGED_HT); } if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT, mask)) conf->dot11MeshHWMPactivePathToRootTimeout = @@ -2328,9 +2937,12 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, const struct mesh_setup *setup) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_chan_req chanreq = { .oper = setup->chandef }; struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; int err; + lockdep_assert_wiphy(sdata->local->hw.wiphy); + memcpy(&ifmsh->mshcfg, conf, sizeof(struct mesh_config)); err = copy_mesh_setup(ifmsh, setup); if (err) @@ -2339,13 +2951,11 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, sdata->control_port_over_nl80211 = setup->control_port_over_nl80211; /* can mesh use other SMPS modes? */ - sdata->smps_mode = IEEE80211_SMPS_OFF; - sdata->needed_rx_chains = sdata->local->rx_chains; + sdata->deflink.smps_mode = IEEE80211_SMPS_OFF; + sdata->deflink.needed_rx_chains = sdata->local->rx_chains; - mutex_lock(&sdata->local->mtx); - err = ieee80211_vif_use_channel(sdata, &setup->chandef, - IEEE80211_CHANCTX_SHARED); - mutex_unlock(&sdata->local->mtx); + err = ieee80211_link_use_channel(&sdata->deflink, &chanreq, + IEEE80211_CHANCTX_SHARED); if (err) return err; @@ -2356,11 +2966,11 @@ static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + lockdep_assert_wiphy(sdata->local->hw.wiphy); + ieee80211_stop_mesh(sdata); - mutex_lock(&sdata->local->mtx); - ieee80211_vif_release_channel(sdata); + ieee80211_link_release_channel(&sdata->deflink); kfree(sdata->u.mesh.ie); - mutex_unlock(&sdata->local->mtx); return 0; } @@ -2371,49 +2981,53 @@ static int ieee80211_change_bss(struct wiphy *wiphy, struct bss_parameters *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link; struct ieee80211_supported_band *sband; - u32 changed = 0; + u64 changed = 0; + + link = ieee80211_link_or_deflink(sdata, params->link_id, true); + if (IS_ERR(link)) + return PTR_ERR(link); - if (!sdata_dereference(sdata->u.ap.beacon, sdata)) + if (!sdata_dereference(link->u.ap.beacon, sdata)) return -ENOENT; - sband = ieee80211_get_sband(sdata); + sband = ieee80211_get_link_sband(link); if (!sband) return -EINVAL; + if (params->basic_rates) { + if (!ieee80211_parse_bitrates(link->conf->chanreq.oper.width, + wiphy->bands[sband->band], + params->basic_rates, + params->basic_rates_len, + &link->conf->basic_rates)) + return -EINVAL; + changed |= BSS_CHANGED_BASIC_RATES; + ieee80211_check_rate_mask(link); + } + if (params->use_cts_prot >= 0) { - sdata->vif.bss_conf.use_cts_prot = params->use_cts_prot; + link->conf->use_cts_prot = params->use_cts_prot; changed |= BSS_CHANGED_ERP_CTS_PROT; } if (params->use_short_preamble >= 0) { - sdata->vif.bss_conf.use_short_preamble = - params->use_short_preamble; + link->conf->use_short_preamble = params->use_short_preamble; changed |= BSS_CHANGED_ERP_PREAMBLE; } - if (!sdata->vif.bss_conf.use_short_slot && + if (!link->conf->use_short_slot && (sband->band == NL80211_BAND_5GHZ || sband->band == NL80211_BAND_6GHZ)) { - sdata->vif.bss_conf.use_short_slot = true; + link->conf->use_short_slot = true; changed |= BSS_CHANGED_ERP_SLOT; } if (params->use_short_slot_time >= 0) { - sdata->vif.bss_conf.use_short_slot = - params->use_short_slot_time; + link->conf->use_short_slot = params->use_short_slot_time; changed |= BSS_CHANGED_ERP_SLOT; } - if (params->basic_rates) { - ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef, - wiphy->bands[sband->band], - params->basic_rates, - params->basic_rates_len, - &sdata->vif.bss_conf.basic_rates); - changed |= BSS_CHANGED_BASIC_RATES; - ieee80211_check_rate_mask(sdata); - } - if (params->ap_isolate >= 0) { if (params->ap_isolate) sdata->flags |= IEEE80211_SDATA_DONT_BRIDGE_PACKETS; @@ -2423,30 +3037,29 @@ static int ieee80211_change_bss(struct wiphy *wiphy, } if (params->ht_opmode >= 0) { - sdata->vif.bss_conf.ht_operation_mode = - (u16) params->ht_opmode; + link->conf->ht_operation_mode = (u16)params->ht_opmode; changed |= BSS_CHANGED_HT; } if (params->p2p_ctwindow >= 0) { - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow &= + link->conf->p2p_noa_attr.oppps_ctwindow &= ~IEEE80211_P2P_OPPPS_CTWINDOW_MASK; - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow |= + link->conf->p2p_noa_attr.oppps_ctwindow |= params->p2p_ctwindow & IEEE80211_P2P_OPPPS_CTWINDOW_MASK; changed |= BSS_CHANGED_P2P_PS; } if (params->p2p_opp_ps > 0) { - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow |= + link->conf->p2p_noa_attr.oppps_ctwindow |= IEEE80211_P2P_OPPPS_ENABLE_BIT; changed |= BSS_CHANGED_P2P_PS; } else if (params->p2p_opp_ps == 0) { - sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow &= + link->conf->p2p_noa_attr.oppps_ctwindow &= ~IEEE80211_P2P_OPPPS_ENABLE_BIT; changed |= BSS_CHANGED_P2P_PS; } - ieee80211_bss_info_change_notify(sdata, changed); + ieee80211_link_info_change_notify(sdata, link, changed); return 0; } @@ -2457,6 +3070,8 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, { struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link = + ieee80211_link_or_deflink(sdata, params->link_id, true); struct ieee80211_tx_queue_params p; if (!local->ops->conf_tx) @@ -2465,6 +3080,9 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, if (local->hw.queues < IEEE80211_NUM_ACS) return -EOPNOTSUPP; + if (IS_ERR(link)) + return PTR_ERR(link); + memset(&p, 0, sizeof(p)); p.aifs = params->aifs; p.cw_max = params->cwmax; @@ -2479,15 +3097,16 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, ieee80211_regulatory_limit_wmm_params(sdata, &p, params->ac); - sdata->tx_conf[params->ac] = p; - if (drv_conf_tx(local, sdata, params->ac, &p)) { + link->tx_conf[params->ac] = p; + if (drv_conf_tx(local, link, params->ac, &p)) { wiphy_debug(local->hw.wiphy, "failed to set TX queue parameters for AC %d\n", params->ac); return -EINVAL; } - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_QOS); + ieee80211_link_info_change_notify(sdata, link, + BSS_CHANGED_QOS); return 0; } @@ -2512,6 +3131,9 @@ static int ieee80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *req) { struct ieee80211_sub_if_data *sdata; + struct ieee80211_link_data *link; + struct ieee80211_channel *chan; + int radio_idx; sdata = IEEE80211_WDEV_TO_SUB_IF(req->wdev); @@ -2536,13 +3158,23 @@ static int ieee80211_scan(struct wiphy *wiphy, * If the scan has been forced (and the driver supports * forcing), don't care about being beaconing already. * This will create problems to the attached stations (e.g. all - * the frames sent while scanning on other channel will be + * the frames sent while scanning on other channel will be * lost) */ - if (sdata->u.ap.beacon && - (!(wiphy->features & NL80211_FEATURE_AP_SCAN) || - !(req->flags & NL80211_SCAN_FLAG_AP))) - return -EOPNOTSUPP; + for_each_link_data(sdata, link) { + /* if the link is not beaconing, ignore it */ + if (!sdata_dereference(link->u.ap.beacon, sdata)) + continue; + + chan = link->conf->chanreq.oper.chan; + radio_idx = cfg80211_get_radio_idx_by_chan(wiphy, chan); + + if (ieee80211_is_radio_idx_in_scan_req(wiphy, req, + radio_idx) && + (!(wiphy->features & NL80211_FEATURE_AP_SCAN) || + !(req->flags & NL80211_SCAN_FLAG_AP))) + return -EOPNOTSUPP; + } break; case NL80211_IFTYPE_NAN: default: @@ -2636,12 +3268,15 @@ static int ieee80211_set_mcast_rate(struct wiphy *wiphy, struct net_device *dev, memcpy(sdata->vif.bss_conf.mcast_rate, rate, sizeof(int) * NUM_NL80211_BANDS); - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_MCAST_RATE); + if (ieee80211_sdata_running(sdata)) + ieee80211_link_info_change_notify(sdata, &sdata->deflink, + BSS_CHANGED_MCAST_RATE); return 0; } -static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +static int ieee80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) { struct ieee80211_local *local = wiphy_priv(wiphy); int err; @@ -2649,7 +3284,8 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { ieee80211_check_fast_xmit_all(local); - err = drv_set_frag_threshold(local, wiphy->frag_threshold); + err = drv_set_frag_threshold(local, radio_idx, + wiphy->frag_threshold); if (err) { ieee80211_check_fast_xmit_all(local); @@ -2663,14 +3299,23 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) coverage_class = changed & WIPHY_PARAM_COVERAGE_CLASS ? wiphy->coverage_class : -1; - err = drv_set_coverage_class(local, coverage_class); + err = drv_set_coverage_class(local, radio_idx, + coverage_class); if (err) return err; } if (changed & WIPHY_PARAM_RTS_THRESHOLD) { - err = drv_set_rts_threshold(local, wiphy->rts_threshold); + u32 rts_threshold; + + if ((radio_idx == -1) || (radio_idx >= wiphy->n_radio)) + rts_threshold = wiphy->rts_threshold; + else + rts_threshold = + wiphy->radio_cfg[radio_idx].rts_threshold; + + err = drv_set_rts_threshold(local, radio_idx, rts_threshold); if (err) return err; @@ -2688,18 +3333,19 @@ static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) } if (changed & (WIPHY_PARAM_RETRY_SHORT | WIPHY_PARAM_RETRY_LONG)) - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_RETRY_LIMITS); + ieee80211_hw_config(local, radio_idx, + IEEE80211_CONF_CHANGE_RETRY_LIMITS); if (changed & (WIPHY_PARAM_TXQ_LIMIT | WIPHY_PARAM_TXQ_MEMORY_LIMIT | WIPHY_PARAM_TXQ_QUANTUM)) - ieee80211_txq_set_params(local); + ieee80211_txq_set_params(local, radio_idx); return 0; } static int ieee80211_set_tx_power(struct wiphy *wiphy, - struct wireless_dev *wdev, + struct wireless_dev *wdev, int radio_idx, enum nl80211_tx_power_setting type, int mbm) { struct ieee80211_local *local = wiphy_priv(wiphy); @@ -2707,101 +3353,152 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy, enum nl80211_tx_power_setting txp_type = type; bool update_txp_type = false; bool has_monitor = false; + int user_power_level; + int old_power = local->user_power_level; + + lockdep_assert_wiphy(local->hw.wiphy); + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + user_power_level = IEEE80211_UNSET_POWER_LEVEL; + txp_type = NL80211_TX_POWER_LIMITED; + break; + case NL80211_TX_POWER_LIMITED: + case NL80211_TX_POWER_FIXED: + if (mbm < 0 || (mbm % 100)) + return -EOPNOTSUPP; + user_power_level = MBM_TO_DBM(mbm); + break; + default: + return -EINVAL; + } if (wdev) { sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); - if (sdata->vif.type == NL80211_IFTYPE_MONITOR) { + if (sdata->vif.type == NL80211_IFTYPE_MONITOR && + !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) { + if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF)) + return -EOPNOTSUPP; + sdata = wiphy_dereference(local->hw.wiphy, local->monitor_sdata); if (!sdata) return -EOPNOTSUPP; } - switch (type) { - case NL80211_TX_POWER_AUTOMATIC: - sdata->user_power_level = IEEE80211_UNSET_POWER_LEVEL; - txp_type = NL80211_TX_POWER_LIMITED; - break; - case NL80211_TX_POWER_LIMITED: - case NL80211_TX_POWER_FIXED: - if (mbm < 0 || (mbm % 100)) - return -EOPNOTSUPP; - sdata->user_power_level = MBM_TO_DBM(mbm); - break; - } + for (int link_id = 0; + link_id < ARRAY_SIZE(sdata->link); + link_id++) { + struct ieee80211_link_data *link = + wiphy_dereference(wiphy, sdata->link[link_id]); - if (txp_type != sdata->vif.bss_conf.txpower_type) { - update_txp_type = true; - sdata->vif.bss_conf.txpower_type = txp_type; - } + if (!link) + continue; - ieee80211_recalc_txpower(sdata, update_txp_type); + link->user_power_level = user_power_level; + if (txp_type != link->conf->txpower_type) { + update_txp_type = true; + link->conf->txpower_type = txp_type; + } + + ieee80211_recalc_txpower(link, update_txp_type); + } return 0; } - switch (type) { - case NL80211_TX_POWER_AUTOMATIC: - local->user_power_level = IEEE80211_UNSET_POWER_LEVEL; - txp_type = NL80211_TX_POWER_LIMITED; - break; - case NL80211_TX_POWER_LIMITED: - case NL80211_TX_POWER_FIXED: - if (mbm < 0 || (mbm % 100)) - return -EOPNOTSUPP; - local->user_power_level = MBM_TO_DBM(mbm); - break; - } + local->user_power_level = user_power_level; - mutex_lock(&local->iflist_mtx); list_for_each_entry(sdata, &local->interfaces, list) { - if (sdata->vif.type == NL80211_IFTYPE_MONITOR) { + if (sdata->vif.type == NL80211_IFTYPE_MONITOR && + !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) { has_monitor = true; continue; } - sdata->user_power_level = local->user_power_level; - if (txp_type != sdata->vif.bss_conf.txpower_type) - update_txp_type = true; - sdata->vif.bss_conf.txpower_type = txp_type; + + for (int link_id = 0; + link_id < ARRAY_SIZE(sdata->link); + link_id++) { + struct ieee80211_link_data *link = + wiphy_dereference(wiphy, sdata->link[link_id]); + + if (!link) + continue; + + link->user_power_level = local->user_power_level; + if (txp_type != link->conf->txpower_type) + update_txp_type = true; + link->conf->txpower_type = txp_type; + } } list_for_each_entry(sdata, &local->interfaces, list) { - if (sdata->vif.type == NL80211_IFTYPE_MONITOR) + if (sdata->vif.type == NL80211_IFTYPE_MONITOR && + !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) continue; - ieee80211_recalc_txpower(sdata, update_txp_type); + + for (int link_id = 0; + link_id < ARRAY_SIZE(sdata->link); + link_id++) { + struct ieee80211_link_data *link = + wiphy_dereference(wiphy, sdata->link[link_id]); + + if (!link) + continue; + + ieee80211_recalc_txpower(link, update_txp_type); + } } - mutex_unlock(&local->iflist_mtx); if (has_monitor) { sdata = wiphy_dereference(local->hw.wiphy, local->monitor_sdata); - if (sdata) { - sdata->user_power_level = local->user_power_level; + if (sdata && ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF)) { + sdata->deflink.user_power_level = local->user_power_level; if (txp_type != sdata->vif.bss_conf.txpower_type) update_txp_type = true; sdata->vif.bss_conf.txpower_type = txp_type; - ieee80211_recalc_txpower(sdata, update_txp_type); + ieee80211_recalc_txpower(&sdata->deflink, + update_txp_type); } } + if (local->emulate_chanctx && + (old_power != local->user_power_level)) + ieee80211_hw_conf_chan(local); + return 0; } static int ieee80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, + unsigned int link_id, int *dbm) { struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + struct ieee80211_link_data *link_data; - if (local->ops->get_txpower) - return drv_get_txpower(local, sdata, dbm); + if (local->ops->get_txpower && + (sdata->flags & IEEE80211_SDATA_IN_DRIVER)) + return drv_get_txpower(local, sdata, link_id, dbm); - if (!local->use_chanctx) + if (local->emulate_chanctx) { *dbm = local->hw.conf.power_level; - else - *dbm = sdata->vif.bss_conf.txpower; + } else { + link_data = wiphy_dereference(wiphy, sdata->link[link_id]); + + if (link_data) + *dbm = link_data->conf->txpower; + else + return -ENOLINK; + } + + /* INT_MIN indicates no power level was set yet */ + if (*dbm == INT_MIN) + return -EINVAL; return 0; } @@ -2850,6 +3547,7 @@ static int ieee80211_testmode_dump(struct wiphy *wiphy, #endif int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, + struct ieee80211_link_data *link, enum ieee80211_smps_mode smps_mode) { const u8 *ap; @@ -2858,13 +3556,22 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, struct sta_info *sta; bool tdls_peer_found = false; - lockdep_assert_held(&sdata->wdev.mtx); + lockdep_assert_wiphy(sdata->local->hw.wiphy); if (WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_STATION)) return -EINVAL; - old_req = sdata->u.mgd.req_smps; - sdata->u.mgd.req_smps = smps_mode; + if (!ieee80211_vif_link_active(&sdata->vif, link->link_id)) + return 0; + + old_req = link->u.mgd.req_smps; + link->u.mgd.req_smps = smps_mode; + + /* The driver indicated that EML is enabled for the interface, which + * implies that SMPS flows towards the AP should be stopped. + */ + if (sdata->vif.driver_flags & IEEE80211_VIF_EML_ACTIVE) + return 0; if (old_req == smps_mode && smps_mode != IEEE80211_SMPS_AUTOMATIC) @@ -2876,10 +3583,10 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, * the new value until we associate. */ if (!sdata->u.mgd.associated || - sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT) + link->conf->chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT) return 0; - ap = sdata->u.mgd.associated->bssid; + ap = sdata->vif.cfg.ap_addr; rcu_read_lock(); list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) { @@ -2901,11 +3608,13 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, /* send SM PS frame to AP */ err = ieee80211_send_smps_action(sdata, smps_mode, - ap, ap); + ap, ap, + ieee80211_vif_is_mld(&sdata->vif) ? + link->link_id : -1); if (err) - sdata->u.mgd.req_smps = old_req; + link->u.mgd.req_smps = old_req; else if (smps_mode != IEEE80211_SMPS_OFF && tdls_peer_found) - ieee80211_teardown_tdls_peers(sdata); + ieee80211_teardown_tdls_peers(link); return err; } @@ -2915,6 +3624,7 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + unsigned int link_id; if (sdata->vif.type != NL80211_IFTYPE_STATION) return -EOPNOTSUPP; @@ -2930,12 +3640,19 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, local->dynamic_ps_forced_timeout = timeout; /* no change, but if automatic follow powersave */ - sdata_lock(sdata); - __ieee80211_request_smps_mgd(sdata, sdata->u.mgd.req_smps); - sdata_unlock(sdata); + for (link_id = 0; link_id < ARRAY_SIZE(sdata->link); link_id++) { + struct ieee80211_link_data *link; + + link = sdata_dereference(sdata->link[link_id], sdata); + + if (!link) + continue; + __ieee80211_request_smps_mgd(sdata, link, + link->u.mgd.req_smps); + } if (ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS)) - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS); ieee80211_recalc_ps(local); ieee80211_recalc_ps_vif(sdata); @@ -2944,32 +3661,57 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, return 0; } +static void ieee80211_set_cqm_rssi_link(struct ieee80211_sub_if_data *sdata, + struct ieee80211_link_data *link, + s32 rssi_thold, u32 rssi_hyst, + s32 rssi_low, s32 rssi_high) +{ + struct ieee80211_bss_conf *conf; + + if (!link || !link->conf) + return; + + conf = link->conf; + + if (rssi_thold && rssi_hyst && + rssi_thold == conf->cqm_rssi_thold && + rssi_hyst == conf->cqm_rssi_hyst) + return; + + conf->cqm_rssi_thold = rssi_thold; + conf->cqm_rssi_hyst = rssi_hyst; + conf->cqm_rssi_low = rssi_low; + conf->cqm_rssi_high = rssi_high; + link->u.mgd.last_cqm_event_signal = 0; + + if (!ieee80211_vif_link_active(&sdata->vif, link->link_id)) + return; + + if (sdata->u.mgd.associated && + (sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) + ieee80211_link_info_change_notify(sdata, link, BSS_CHANGED_CQM); +} + static int ieee80211_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *dev, s32 rssi_thold, u32 rssi_hyst) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_vif *vif = &sdata->vif; - struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + int link_id; - if (rssi_thold == bss_conf->cqm_rssi_thold && - rssi_hyst == bss_conf->cqm_rssi_hyst) - return 0; - - if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER && - !(sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) + if (vif->driver_flags & IEEE80211_VIF_BEACON_FILTER && + !(vif->driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) return -EOPNOTSUPP; - bss_conf->cqm_rssi_thold = rssi_thold; - bss_conf->cqm_rssi_hyst = rssi_hyst; - bss_conf->cqm_rssi_low = 0; - bss_conf->cqm_rssi_high = 0; - sdata->u.mgd.last_cqm_event_signal = 0; + /* For MLD, handle CQM change on all the active links */ + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + struct ieee80211_link_data *link = + sdata_dereference(sdata->link[link_id], sdata); - /* tell the driver upon association, unless already associated */ - if (sdata->u.mgd.associated && - sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI) - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_CQM); + ieee80211_set_cqm_rssi_link(sdata, link, rssi_thold, rssi_hyst, + 0, 0); + } return 0; } @@ -2980,27 +3722,26 @@ static int ieee80211_set_cqm_rssi_range_config(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_vif *vif = &sdata->vif; - struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + int link_id; - if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER) + if (vif->driver_flags & IEEE80211_VIF_BEACON_FILTER) return -EOPNOTSUPP; - bss_conf->cqm_rssi_low = rssi_low; - bss_conf->cqm_rssi_high = rssi_high; - bss_conf->cqm_rssi_thold = 0; - bss_conf->cqm_rssi_hyst = 0; - sdata->u.mgd.last_cqm_event_signal = 0; + /* For MLD, handle CQM change on all the active links */ + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + struct ieee80211_link_data *link = + sdata_dereference(sdata->link[link_id], sdata); - /* tell the driver upon association, unless already associated */ - if (sdata->u.mgd.associated && - sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI) - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_CQM); + ieee80211_set_cqm_rssi_link(sdata, link, 0, 0, + rssi_low, rssi_high); + } return 0; } static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id, const u8 *addr, const struct cfg80211_bitrate_mask *mask) { @@ -3017,10 +3758,12 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, * to send something, and if we're an AP we have to be able to do * so at a basic rate so that all clients can receive it. */ - if (rcu_access_pointer(sdata->vif.chanctx_conf) && - sdata->vif.bss_conf.chandef.chan) { + if (rcu_access_pointer(sdata->vif.bss_conf.chanctx_conf) && + sdata->vif.bss_conf.chanreq.oper.chan) { u32 basic_rates = sdata->vif.bss_conf.basic_rates; - enum nl80211_band band = sdata->vif.bss_conf.chandef.chan->band; + enum nl80211_band band; + + band = sdata->vif.bss_conf.chanreq.oper.chan->band; if (!(mask->control[band].legacy & basic_rates)) return -EINVAL; @@ -3066,59 +3809,100 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, return 0; } +static bool ieee80211_is_scan_ongoing(struct wiphy *wiphy, + struct ieee80211_local *local, + struct cfg80211_chan_def *chandef) +{ + struct cfg80211_scan_request *scan_req; + int chan_radio_idx, req_radio_idx; + struct ieee80211_roc_work *roc; + + if (list_empty(&local->roc_list) && !local->scanning) + return false; + + req_radio_idx = cfg80211_get_radio_idx_by_chan(wiphy, chandef->chan); + + if (local->scanning) { + scan_req = wiphy_dereference(wiphy, local->scan_req); + /* + * Scan is going on but info is not there. Should not happen + * but if it does, let's not take risk and assume we can't use + * the hw hence return true + */ + if (WARN_ON_ONCE(!scan_req)) + return true; + + return ieee80211_is_radio_idx_in_scan_req(wiphy, scan_req, + req_radio_idx); + } + + list_for_each_entry(roc, &local->roc_list, list) { + chan_radio_idx = cfg80211_get_radio_idx_by_chan(wiphy, + roc->chan); + if (chan_radio_idx == req_radio_idx) + return true; + } + + return false; +} + static int ieee80211_start_radar_detection(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_chan_def *chandef, - u32 cac_time_ms) + u32 cac_time_ms, int link_id) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_chan_req chanreq = { .oper = *chandef }; struct ieee80211_local *local = sdata->local; + struct ieee80211_link_data *link_data; int err; - mutex_lock(&local->mtx); - if (!list_empty(&local->roc_list) || local->scanning) { - err = -EBUSY; - goto out_unlock; - } + lockdep_assert_wiphy(local->hw.wiphy); + + if (ieee80211_is_scan_ongoing(wiphy, local, chandef)) + return -EBUSY; + + link_data = sdata_dereference(sdata->link[link_id], sdata); + if (!link_data) + return -ENOLINK; /* whatever, but channel contexts should not complain about that one */ - sdata->smps_mode = IEEE80211_SMPS_OFF; - sdata->needed_rx_chains = local->rx_chains; + link_data->smps_mode = IEEE80211_SMPS_OFF; + link_data->needed_rx_chains = local->rx_chains; - err = ieee80211_vif_use_channel(sdata, chandef, - IEEE80211_CHANCTX_SHARED); + err = ieee80211_link_use_channel(link_data, &chanreq, + IEEE80211_CHANCTX_SHARED); if (err) - goto out_unlock; + return err; - ieee80211_queue_delayed_work(&sdata->local->hw, - &sdata->dfs_cac_timer_work, - msecs_to_jiffies(cac_time_ms)); + wiphy_delayed_work_queue(wiphy, &link_data->dfs_cac_timer_work, + msecs_to_jiffies(cac_time_ms)); - out_unlock: - mutex_unlock(&local->mtx); - return err; + return 0; } static void ieee80211_end_cac(struct wiphy *wiphy, - struct net_device *dev) + struct net_device *dev, unsigned int link_id) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + struct ieee80211_link_data *link_data; + + lockdep_assert_wiphy(local->hw.wiphy); - mutex_lock(&local->mtx); list_for_each_entry(sdata, &local->interfaces, list) { - /* it might be waiting for the local->mtx, but then - * by the time it gets it, sdata->wdev.cac_started - * will no longer be true - */ - cancel_delayed_work(&sdata->dfs_cac_timer_work); + link_data = sdata_dereference(sdata->link[link_id], sdata); + if (!link_data) + continue; + + wiphy_delayed_work_cancel(wiphy, + &link_data->dfs_cac_timer_work); - if (sdata->wdev.cac_started) { - ieee80211_vif_release_channel(sdata); - sdata->wdev.cac_started = false; + if (sdata->wdev.links[link_id].cac_started) { + ieee80211_link_release_channel(link_data); + sdata->wdev.links[link_id].cac_started = false; } } - mutex_unlock(&local->mtx); } static struct cfg80211_beacon_data * @@ -3132,10 +3916,38 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon) beacon->proberesp_ies_len + beacon->assocresp_ies_len + beacon->probe_resp_len + beacon->lci_len + beacon->civicloc_len; + if (beacon->mbssid_ies) + len += ieee80211_get_mbssid_beacon_len(beacon->mbssid_ies, + beacon->rnr_ies, + beacon->mbssid_ies->cnt); + new_beacon = kzalloc(sizeof(*new_beacon) + len, GFP_KERNEL); if (!new_beacon) return NULL; + if (beacon->mbssid_ies && beacon->mbssid_ies->cnt) { + new_beacon->mbssid_ies = + kzalloc(struct_size(new_beacon->mbssid_ies, + elem, beacon->mbssid_ies->cnt), + GFP_KERNEL); + if (!new_beacon->mbssid_ies) { + kfree(new_beacon); + return NULL; + } + + if (beacon->rnr_ies && beacon->rnr_ies->cnt) { + new_beacon->rnr_ies = + kzalloc(struct_size(new_beacon->rnr_ies, + elem, beacon->rnr_ies->cnt), + GFP_KERNEL); + if (!new_beacon->rnr_ies) { + kfree(new_beacon->mbssid_ies); + kfree(new_beacon); + return NULL; + } + } + } + pos = (u8 *)(new_beacon + 1); if (beacon->head_len) { new_beacon->head_len = beacon->head_len; @@ -3173,6 +3985,15 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon) memcpy(pos, beacon->probe_resp, beacon->probe_resp_len); pos += beacon->probe_resp_len; } + if (beacon->mbssid_ies && beacon->mbssid_ies->cnt) { + pos += ieee80211_copy_mbssid_beacon(pos, + new_beacon->mbssid_ies, + beacon->mbssid_ies); + if (beacon->rnr_ies && beacon->rnr_ies->cnt) + pos += ieee80211_copy_rnr_beacon(pos, + new_beacon->rnr_ies, + beacon->rnr_ies); + } /* might copy -1, meaning no changes requested */ new_beacon->ftm_responder = beacon->ftm_responder; @@ -3192,55 +4013,88 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon) return new_beacon; } -void ieee80211_csa_finish(struct ieee80211_vif *vif) +void ieee80211_csa_finish(struct ieee80211_vif *vif, unsigned int link_id) { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + struct ieee80211_local *local = sdata->local; + struct ieee80211_bss_conf *tx_bss_conf; + struct ieee80211_link_data *link_data; + + if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS)) + return; - ieee80211_queue_work(&sdata->local->hw, - &sdata->csa_finalize_work); + rcu_read_lock(); + + link_data = rcu_dereference(sdata->link[link_id]); + if (WARN_ON(!link_data)) { + rcu_read_unlock(); + return; + } + + tx_bss_conf = rcu_dereference(link_data->conf->tx_bss_conf); + if (tx_bss_conf == link_data->conf) { + /* Trigger ieee80211_csa_finish() on the non-transmitting + * interfaces when channel switch is received on + * transmitting interface + */ + struct ieee80211_link_data *iter; + + for_each_sdata_link_rcu(local, iter) { + if (iter->sdata == sdata || + rcu_access_pointer(iter->conf->tx_bss_conf) != tx_bss_conf) + continue; + + wiphy_work_queue(iter->sdata->local->hw.wiphy, + &iter->csa.finalize_work); + } + } + + wiphy_work_queue(local->hw.wiphy, &link_data->csa.finalize_work); + + rcu_read_unlock(); } EXPORT_SYMBOL(ieee80211_csa_finish); -void ieee80211_channel_switch_disconnect(struct ieee80211_vif *vif, bool block_tx) +void ieee80211_channel_switch_disconnect(struct ieee80211_vif *vif) { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_local *local = sdata->local; - sdata->csa_block_tx = block_tx; sdata_info(sdata, "channel switch failed, disconnecting\n"); - ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work); + wiphy_work_queue(local->hw.wiphy, &ifmgd->csa_connection_drop_work); } EXPORT_SYMBOL(ieee80211_channel_switch_disconnect); -static int ieee80211_set_after_csa_beacon(struct ieee80211_sub_if_data *sdata, - u32 *changed) +static int ieee80211_set_after_csa_beacon(struct ieee80211_link_data *link_data, + u64 *changed) { + struct ieee80211_sub_if_data *sdata = link_data->sdata; int err; switch (sdata->vif.type) { case NL80211_IFTYPE_AP: - err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon, - NULL, NULL); - kfree(sdata->u.ap.next_beacon); - sdata->u.ap.next_beacon = NULL; + if (!link_data->u.ap.next_beacon) + return -EINVAL; + + err = ieee80211_assign_beacon(sdata, link_data, + link_data->u.ap.next_beacon, + NULL, NULL, changed); + ieee80211_free_next_beacon(link_data); if (err < 0) return err; - *changed |= err; break; case NL80211_IFTYPE_ADHOC: - err = ieee80211_ibss_finish_csa(sdata); + err = ieee80211_ibss_finish_csa(sdata, changed); if (err < 0) return err; - *changed |= err; break; #ifdef CONFIG_MAC80211_MESH case NL80211_IFTYPE_MESH_POINT: - err = ieee80211_mesh_finish_csa(sdata); + err = ieee80211_mesh_finish_csa(sdata, changed); if (err < 0) return err; - *changed |= err; break; #endif default: @@ -3251,15 +4105,15 @@ static int ieee80211_set_after_csa_beacon(struct ieee80211_sub_if_data *sdata, return 0; } -static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata) +static int __ieee80211_csa_finalize(struct ieee80211_link_data *link_data) { + struct ieee80211_sub_if_data *sdata = link_data->sdata; struct ieee80211_local *local = sdata->local; - u32 changed = 0; + struct ieee80211_bss_conf *link_conf = link_data->conf; + u64 changed = 0; int err; - sdata_assert_lock(sdata); - lockdep_assert_held(&local->mtx); - lockdep_assert_held(&local->chanctx_mtx); + lockdep_assert_wiphy(local->hw.wiphy); /* * using reservation isn't immediate as it may be deferred until later @@ -3268,92 +4122,86 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata) * completed successfully */ - if (sdata->reserved_chanctx) { + if (link_data->reserved_chanctx) { /* * with multi-vif csa driver may call ieee80211_csa_finish() * many times while waiting for other interfaces to use their * reservations */ - if (sdata->reserved_ready) + if (link_data->reserved_ready) return 0; - return ieee80211_vif_use_reserved_context(sdata); + return ieee80211_link_use_reserved_context(link_data); } - if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef, - &sdata->csa_chandef)) + if (!cfg80211_chandef_identical(&link_conf->chanreq.oper, + &link_data->csa.chanreq.oper)) return -EINVAL; - sdata->vif.csa_active = false; + link_conf->csa_active = false; - err = ieee80211_set_after_csa_beacon(sdata, &changed); + err = ieee80211_set_after_csa_beacon(link_data, &changed); if (err) return err; - ieee80211_bss_info_change_notify(sdata, changed); + ieee80211_link_info_change_notify(sdata, link_data, changed); - if (sdata->csa_block_tx) { - ieee80211_wake_vif_queues(local, sdata, - IEEE80211_QUEUE_STOP_REASON_CSA); - sdata->csa_block_tx = false; - } + ieee80211_vif_unblock_queues_csa(sdata); - err = drv_post_channel_switch(sdata); + err = drv_post_channel_switch(link_data); if (err) return err; - cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef); + cfg80211_ch_switch_notify(sdata->dev, &link_data->csa.chanreq.oper, + link_data->link_id); return 0; } -static void ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata) +static void ieee80211_csa_finalize(struct ieee80211_link_data *link_data) { - if (__ieee80211_csa_finalize(sdata)) { - sdata_info(sdata, "failed to finalize CSA, disconnecting\n"); + struct ieee80211_sub_if_data *sdata = link_data->sdata; + + if (__ieee80211_csa_finalize(link_data)) { + sdata_info(sdata, "failed to finalize CSA on link %d, disconnecting\n", + link_data->link_id); cfg80211_stop_iface(sdata->local->hw.wiphy, &sdata->wdev, GFP_KERNEL); } } -void ieee80211_csa_finalize_work(struct work_struct *work) +void ieee80211_csa_finalize_work(struct wiphy *wiphy, struct wiphy_work *work) { - struct ieee80211_sub_if_data *sdata = - container_of(work, struct ieee80211_sub_if_data, - csa_finalize_work); + struct ieee80211_link_data *link = + container_of(work, struct ieee80211_link_data, csa.finalize_work); + struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_local *local = sdata->local; - sdata_lock(sdata); - mutex_lock(&local->mtx); - mutex_lock(&local->chanctx_mtx); + lockdep_assert_wiphy(local->hw.wiphy); /* AP might have been stopped while waiting for the lock. */ - if (!sdata->vif.csa_active) - goto unlock; + if (!link->conf->csa_active) + return; if (!ieee80211_sdata_running(sdata)) - goto unlock; - - ieee80211_csa_finalize(sdata); + return; -unlock: - mutex_unlock(&local->chanctx_mtx); - mutex_unlock(&local->mtx); - sdata_unlock(sdata); + ieee80211_csa_finalize(link); } -static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, +static int ieee80211_set_csa_beacon(struct ieee80211_link_data *link_data, struct cfg80211_csa_settings *params, - u32 *changed) + u64 *changed) { + struct ieee80211_sub_if_data *sdata = link_data->sdata; struct ieee80211_csa_settings csa = {}; int err; switch (sdata->vif.type) { case NL80211_IFTYPE_AP: - sdata->u.ap.next_beacon = + link_data->u.ap.next_beacon = cfg80211_beacon_dup(¶ms->beacon_after); - if (!sdata->u.ap.next_beacon) + if (!link_data->u.ap.next_beacon) return -ENOMEM; /* @@ -3378,8 +4226,10 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, if ((params->n_counter_offsets_beacon > IEEE80211_MAX_CNTDWN_COUNTERS_NUM) || (params->n_counter_offsets_presp > - IEEE80211_MAX_CNTDWN_COUNTERS_NUM)) + IEEE80211_MAX_CNTDWN_COUNTERS_NUM)) { + ieee80211_free_next_beacon(link_data); return -EINVAL; + } csa.counter_offsets_beacon = params->counter_offsets_beacon; csa.counter_offsets_presp = params->counter_offsets_presp; @@ -3387,16 +4237,17 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, csa.n_counter_offsets_presp = params->n_counter_offsets_presp; csa.count = params->count; - err = ieee80211_assign_beacon(sdata, ¶ms->beacon_csa, &csa, NULL); + err = ieee80211_assign_beacon(sdata, link_data, + ¶ms->beacon_csa, &csa, + NULL, changed); if (err < 0) { - kfree(sdata->u.ap.next_beacon); + ieee80211_free_next_beacon(link_data); return err; } - *changed |= err; break; case NL80211_IFTYPE_ADHOC: - if (!sdata->vif.bss_conf.ibss_joined) + if (!sdata->vif.cfg.ibss_joined) return -EINVAL; if (params->chandef.width != sdata->u.ibss.chandef.width) @@ -3424,10 +4275,9 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, /* see comments in the NL80211_IFTYPE_AP block */ if (params->count > 1) { - err = ieee80211_ibss_csa_beacon(sdata, params); + err = ieee80211_ibss_csa_beacon(sdata, params, changed); if (err < 0) return err; - *changed |= err; } ieee80211_send_action_csa(sdata, params); @@ -3437,11 +4287,8 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, case NL80211_IFTYPE_MESH_POINT: { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; - if (params->chandef.width != sdata->vif.bss_conf.chandef.width) - return -EINVAL; - /* changes into another band are not supported */ - if (sdata->vif.bss_conf.chandef.chan->band != + if (sdata->vif.bss_conf.chanreq.oper.chan->band != params->chandef.chan->band) return -EINVAL; @@ -3455,12 +4302,11 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, /* see comments in the NL80211_IFTYPE_AP block */ if (params->count > 1) { - err = ieee80211_mesh_csa_beacon(sdata, params); + err = ieee80211_mesh_csa_beacon(sdata, params, changed); if (err < 0) { ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; return err; } - *changed |= err; } if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT) @@ -3476,13 +4322,13 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, return 0; } -static void ieee80211_color_change_abort(struct ieee80211_sub_if_data *sdata) +static void ieee80211_color_change_abort(struct ieee80211_link_data *link) { - sdata->vif.color_change_active = false; - kfree(sdata->u.ap.next_beacon); - sdata->u.ap.next_beacon = NULL; + link->conf->color_change_active = false; - cfg80211_color_change_aborted_notify(sdata->dev); + ieee80211_free_next_beacon(link); + + cfg80211_color_change_aborted_notify(link->sdata->dev, link->link_id); } static int @@ -3490,33 +4336,44 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_csa_settings *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_chan_req chanreq = { .oper = params->chandef }; struct ieee80211_local *local = sdata->local; - struct ieee80211_channel_switch ch_switch; + struct ieee80211_channel_switch ch_switch = { + .link_id = params->link_id, + }; struct ieee80211_chanctx_conf *conf; struct ieee80211_chanctx *chanctx; - u32 changed = 0; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_link_data *link_data; + u64 changed = 0; + u8 link_id = params->link_id; int err; - sdata_assert_lock(sdata); - lockdep_assert_held(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); - if (!list_empty(&local->roc_list) || local->scanning) + if (ieee80211_is_scan_ongoing(wiphy, local, ¶ms->chandef)) return -EBUSY; - if (sdata->wdev.cac_started) + if (sdata->wdev.links[link_id].cac_started) return -EBUSY; - if (cfg80211_chandef_identical(¶ms->chandef, - &sdata->vif.bss_conf.chandef)) + if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS)) + return -EINVAL; + + link_data = wiphy_dereference(wiphy, sdata->link[link_id]); + if (!link_data) + return -ENOLINK; + + link_conf = link_data->conf; + + if (chanreq.oper.punctured && !link_conf->eht_support) return -EINVAL; /* don't allow another channel switch if one is already active. */ - if (sdata->vif.csa_active) + if (link_conf->csa_active) return -EBUSY; - mutex_lock(&local->chanctx_mtx); - conf = rcu_dereference_protected(sdata->vif.chanctx_conf, - lockdep_is_held(&local->chanctx_mtx)); + conf = wiphy_dereference(wiphy, link_conf->chanctx_conf); if (!conf) { err = -EBUSY; goto out; @@ -3528,62 +4385,66 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, goto out; } + err = ieee80211_set_unsol_bcast_probe_resp(sdata, + ¶ms->unsol_bcast_probe_resp, + link_data, link_conf, &changed); + if (err) + goto out; + chanctx = container_of(conf, struct ieee80211_chanctx, conf); ch_switch.timestamp = 0; ch_switch.device_timestamp = 0; ch_switch.block_tx = params->block_tx; - ch_switch.chandef = params->chandef; + ch_switch.chandef = chanreq.oper; ch_switch.count = params->count; err = drv_pre_channel_switch(sdata, &ch_switch); if (err) goto out; - err = ieee80211_vif_reserve_chanctx(sdata, ¶ms->chandef, - chanctx->mode, - params->radar_required); + err = ieee80211_link_reserve_chanctx(link_data, &chanreq, + chanctx->mode, + params->radar_required); if (err) goto out; /* if reservation is invalid then this will fail */ - err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0); + err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0, -1); if (err) { - ieee80211_vif_unreserve_chanctx(sdata); + ieee80211_link_unreserve_chanctx(link_data); goto out; } /* if there is a color change in progress, abort it */ - if (sdata->vif.color_change_active) - ieee80211_color_change_abort(sdata); + if (link_conf->color_change_active) + ieee80211_color_change_abort(link_data); - err = ieee80211_set_csa_beacon(sdata, params, &changed); + err = ieee80211_set_csa_beacon(link_data, params, &changed); if (err) { - ieee80211_vif_unreserve_chanctx(sdata); + ieee80211_link_unreserve_chanctx(link_data); goto out; } - sdata->csa_chandef = params->chandef; - sdata->csa_block_tx = params->block_tx; - sdata->vif.csa_active = true; + link_data->csa.chanreq = chanreq; + link_conf->csa_active = true; - if (sdata->csa_block_tx) - ieee80211_stop_vif_queues(local, sdata, - IEEE80211_QUEUE_STOP_REASON_CSA); + if (params->block_tx) + ieee80211_vif_block_queues_csa(sdata); - cfg80211_ch_switch_started_notify(sdata->dev, &sdata->csa_chandef, + cfg80211_ch_switch_started_notify(sdata->dev, + &link_data->csa.chanreq.oper, link_id, params->count, params->block_tx); if (changed) { - ieee80211_bss_info_change_notify(sdata, changed); - drv_channel_switch_beacon(sdata, ¶ms->chandef); + ieee80211_link_info_change_notify(sdata, link_data, changed); + drv_channel_switch_beacon(sdata, &link_data->csa.chanreq.oper); } else { /* if the beacon didn't change, we can finalize immediately */ - ieee80211_csa_finalize(sdata); + ieee80211_csa_finalize(link_data); } out: - mutex_unlock(&local->chanctx_mtx); return err; } @@ -3592,18 +4453,15 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - int err; - mutex_lock(&local->mtx); - err = __ieee80211_channel_switch(wiphy, dev, params); - mutex_unlock(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); - return err; + return __ieee80211_channel_switch(wiphy, dev, params); } u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local) { - lockdep_assert_held(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); local->roc_cookie_counter++; @@ -3635,7 +4493,8 @@ int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, return -ENOMEM; } - IEEE80211_SKB_CB(skb)->ack_frame_id = id; + IEEE80211_SKB_CB(skb)->status_data_idr = 1; + IEEE80211_SKB_CB(skb)->status_data = id; *cookie = ieee80211_mgmt_tx_cookie(local); IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; @@ -3682,21 +4541,29 @@ ieee80211_update_mgmt_frame_registrations(struct wiphy *wiphy, ieee80211_configure_filter(local); } -static int ieee80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) +static int ieee80211_set_antenna(struct wiphy *wiphy, int radio_idx, + u32 tx_ant, u32 rx_ant) { struct ieee80211_local *local = wiphy_priv(wiphy); + int ret; if (local->started) return -EOPNOTSUPP; - return drv_set_antenna(local, tx_ant, rx_ant); + ret = drv_set_antenna(local, tx_ant, rx_ant); + if (ret) + return ret; + + local->rx_chains = hweight8(rx_ant); + return 0; } -static int ieee80211_get_antenna(struct wiphy *wiphy, u32 *tx_ant, u32 *rx_ant) +static int ieee80211_get_antenna(struct wiphy *wiphy, int radio_idx, + u32 *tx_ant, u32 *rx_ant) { struct ieee80211_local *local = wiphy_priv(wiphy); - return drv_get_antenna(local, tx_ant, rx_ant); + return drv_get_antenna(local, radio_idx, tx_ant, rx_ant); } static int ieee80211_set_rekey_data(struct wiphy *wiphy, @@ -3731,22 +4598,23 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, int ret; /* the lock is needed to assign the cookie later */ - mutex_lock(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); rcu_read_lock(); - chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); + sta = sta_info_get_bss(sdata, peer); + if (!sta) { + ret = -ENOLINK; + goto unlock; + } + + qos = sta->sta.wme; + + chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); if (WARN_ON(!chanctx_conf)) { ret = -EINVAL; goto unlock; } band = chanctx_conf->def.chan->band; - sta = sta_info_get_bss(sdata, peer); - if (sta) { - qos = sta->sta.wme; - } else { - ret = -ENOLINK; - goto unlock; - } if (qos) { fc = cpu_to_le16(IEEE80211_FTYPE_DATA | @@ -3801,34 +4669,39 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, ret = 0; unlock: rcu_read_unlock(); - mutex_unlock(&local->mtx); return ret; } static int ieee80211_cfg_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev, + unsigned int link_id, struct cfg80211_chan_def *chandef) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_link_data *link; int ret = -ENODATA; rcu_read_lock(); - chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); + link = rcu_dereference(sdata->link[link_id]); + if (!link) { + ret = -ENOLINK; + goto out; + } + + chanctx_conf = rcu_dereference(link->conf->chanctx_conf); if (chanctx_conf) { - *chandef = sdata->vif.bss_conf.chandef; + *chandef = link->conf->chanreq.oper; ret = 0; } else if (local->open_count > 0 && - local->open_count == local->monitors && + local->open_count == local->virt_monitors && sdata->vif.type == NL80211_IFTYPE_MONITOR) { - if (local->use_chanctx) - *chandef = local->monitor_chandef; - else - *chandef = local->_oper_chandef; + *chandef = local->monitor_chanreq.oper; ret = 0; } +out: rcu_read_unlock(); return ret; @@ -3868,15 +4741,20 @@ static int ieee80211_set_qos_map(struct wiphy *wiphy, static int ieee80211_set_ap_chanwidth(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id, struct cfg80211_chan_def *chandef) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_link_data *link; + struct ieee80211_chan_req chanreq = { .oper = *chandef }; int ret; - u32 changed = 0; + u64 changed = 0; - ret = ieee80211_vif_change_bandwidth(sdata, chandef, &changed); + link = sdata_dereference(sdata->link[link_id], sdata); + + ret = ieee80211_link_change_chanreq(link, &chanreq, &changed); if (ret == 0) - ieee80211_bss_info_change_notify(sdata, changed); + ieee80211_link_info_change_notify(sdata, link, changed); return ret; } @@ -4076,11 +4954,7 @@ static int ieee80211_get_txq_stats(struct wiphy *wiphy, struct ieee80211_sub_if_data *sdata; int ret = 0; - if (!local->ops->wake_tx_queue) - return 1; - spin_lock_bh(&local->fq.lock); - rcu_read_lock(); if (wdev) { sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); @@ -4106,7 +4980,6 @@ static int ieee80211_get_txq_stats(struct wiphy *wiphy, } out: - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); return ret; @@ -4149,7 +5022,8 @@ static int ieee80211_set_tid_config(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct sta_info *sta; - int ret; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); if (!sdata->local->ops->set_tid_config) return -EOPNOTSUPP; @@ -4157,17 +5031,11 @@ static int ieee80211_set_tid_config(struct wiphy *wiphy, if (!tid_conf->peer) return drv_set_tid_config(sdata->local, sdata, NULL, tid_conf); - mutex_lock(&sdata->local->sta_mtx); sta = sta_info_get_bss(sdata, tid_conf->peer); - if (!sta) { - mutex_unlock(&sdata->local->sta_mtx); + if (!sta) return -ENOENT; - } - ret = drv_set_tid_config(sdata->local, sdata, &sta->sta, tid_conf); - mutex_unlock(&sdata->local->sta_mtx); - - return ret; + return drv_set_tid_config(sdata->local, sdata, &sta->sta, tid_conf); } static int ieee80211_reset_tid_config(struct wiphy *wiphy, @@ -4176,7 +5044,8 @@ static int ieee80211_reset_tid_config(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct sta_info *sta; - int ret; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); if (!sdata->local->ops->reset_tid_config) return -EOPNOTSUPP; @@ -4184,17 +5053,11 @@ static int ieee80211_reset_tid_config(struct wiphy *wiphy, if (!peer) return drv_reset_tid_config(sdata->local, sdata, NULL, tids); - mutex_lock(&sdata->local->sta_mtx); sta = sta_info_get_bss(sdata, peer); - if (!sta) { - mutex_unlock(&sdata->local->sta_mtx); + if (!sta) return -ENOENT; - } - - ret = drv_reset_tid_config(sdata->local, sdata, &sta->sta, tids); - mutex_unlock(&sdata->local->sta_mtx); - return ret; + return drv_reset_tid_config(sdata->local, sdata, &sta->sta, tids); } static int ieee80211_set_sar_specs(struct wiphy *wiphy, @@ -4209,22 +5072,26 @@ static int ieee80211_set_sar_specs(struct wiphy *wiphy, } static int -ieee80211_set_after_color_change_beacon(struct ieee80211_sub_if_data *sdata, - u32 *changed) +ieee80211_set_after_color_change_beacon(struct ieee80211_link_data *link, + u64 *changed) { + struct ieee80211_sub_if_data *sdata = link->sdata; + switch (sdata->vif.type) { case NL80211_IFTYPE_AP: { int ret; - ret = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon, - NULL, NULL); - kfree(sdata->u.ap.next_beacon); - sdata->u.ap.next_beacon = NULL; + if (!link->u.ap.next_beacon) + return -EINVAL; + + ret = ieee80211_assign_beacon(sdata, link, + link->u.ap.next_beacon, + NULL, NULL, changed); + ieee80211_free_next_beacon(link); if (ret < 0) return ret; - *changed |= ret; break; } default: @@ -4236,18 +5103,19 @@ ieee80211_set_after_color_change_beacon(struct ieee80211_sub_if_data *sdata, } static int -ieee80211_set_color_change_beacon(struct ieee80211_sub_if_data *sdata, +ieee80211_set_color_change_beacon(struct ieee80211_link_data *link, struct cfg80211_color_change_settings *params, - u32 *changed) + u64 *changed) { + struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_color_change_settings color_change = {}; int err; switch (sdata->vif.type) { case NL80211_IFTYPE_AP: - sdata->u.ap.next_beacon = + link->u.ap.next_beacon = cfg80211_beacon_dup(¶ms->beacon_next); - if (!sdata->u.ap.next_beacon) + if (!link->u.ap.next_beacon) return -ENOMEM; if (params->count <= 1) @@ -4259,13 +5127,13 @@ ieee80211_set_color_change_beacon(struct ieee80211_sub_if_data *sdata, params->counter_offset_presp; color_change.count = params->count; - err = ieee80211_assign_beacon(sdata, ¶ms->beacon_color_change, - NULL, &color_change); + err = ieee80211_assign_beacon(sdata, link, + ¶ms->beacon_color_change, + NULL, &color_change, changed); if (err < 0) { - kfree(sdata->u.ap.next_beacon); + ieee80211_free_next_beacon(link); return err; } - *changed |= err; break; default: return -EOPNOTSUPP; @@ -4275,101 +5143,158 @@ ieee80211_set_color_change_beacon(struct ieee80211_sub_if_data *sdata, } static void -ieee80211_color_change_bss_config_notify(struct ieee80211_sub_if_data *sdata, - u8 color, int enable, u32 changed) +ieee80211_color_change_bss_config_notify(struct ieee80211_link_data *link, + u8 color, int enable, u64 changed) { - sdata->vif.bss_conf.he_bss_color.color = color; - sdata->vif.bss_conf.he_bss_color.enabled = enable; + struct ieee80211_sub_if_data *sdata = link->sdata; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + link->conf->he_bss_color.color = color; + link->conf->he_bss_color.enabled = enable; changed |= BSS_CHANGED_HE_BSS_COLOR; - ieee80211_bss_info_change_notify(sdata, changed); + ieee80211_link_info_change_notify(sdata, link, changed); - if (!sdata->vif.bss_conf.nontransmitted && sdata->vif.mbssid_tx_vif) { - struct ieee80211_sub_if_data *child; + if (!link->conf->nontransmitted && + rcu_access_pointer(link->conf->tx_bss_conf)) { + struct ieee80211_link_data *tmp; - mutex_lock(&sdata->local->iflist_mtx); - list_for_each_entry(child, &sdata->local->interfaces, list) { - if (child != sdata && child->vif.mbssid_tx_vif == &sdata->vif) { - child->vif.bss_conf.he_bss_color.color = color; - child->vif.bss_conf.he_bss_color.enabled = enable; - ieee80211_bss_info_change_notify(child, - BSS_CHANGED_HE_BSS_COLOR); - } + for_each_sdata_link(sdata->local, tmp) { + if (tmp->sdata == sdata || + rcu_access_pointer(tmp->conf->tx_bss_conf) != link->conf) + continue; + + tmp->conf->he_bss_color.color = color; + tmp->conf->he_bss_color.enabled = enable; + ieee80211_link_info_change_notify(tmp->sdata, tmp, + BSS_CHANGED_HE_BSS_COLOR); } - mutex_unlock(&sdata->local->iflist_mtx); } } -static int ieee80211_color_change_finalize(struct ieee80211_sub_if_data *sdata) +static int ieee80211_color_change_finalize(struct ieee80211_link_data *link) { + struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_local *local = sdata->local; - u32 changed = 0; + u64 changed = 0; int err; - sdata_assert_lock(sdata); - lockdep_assert_held(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); - sdata->vif.color_change_active = false; + link->conf->color_change_active = false; - err = ieee80211_set_after_color_change_beacon(sdata, &changed); + err = ieee80211_set_after_color_change_beacon(link, &changed); if (err) { - cfg80211_color_change_aborted_notify(sdata->dev); + cfg80211_color_change_aborted_notify(sdata->dev, link->link_id); return err; } - ieee80211_color_change_bss_config_notify(sdata, - sdata->vif.color_change_color, + ieee80211_color_change_bss_config_notify(link, + link->conf->color_change_color, 1, changed); - cfg80211_color_change_notify(sdata->dev); + cfg80211_color_change_notify(sdata->dev, link->link_id); return 0; } -void ieee80211_color_change_finalize_work(struct work_struct *work) +void ieee80211_color_change_finalize_work(struct wiphy *wiphy, + struct wiphy_work *work) { - struct ieee80211_sub_if_data *sdata = - container_of(work, struct ieee80211_sub_if_data, + struct ieee80211_link_data *link = + container_of(work, struct ieee80211_link_data, color_change_finalize_work); + struct ieee80211_sub_if_data *sdata = link->sdata; + struct ieee80211_bss_conf *link_conf = link->conf; struct ieee80211_local *local = sdata->local; - sdata_lock(sdata); - mutex_lock(&local->mtx); + lockdep_assert_wiphy(local->hw.wiphy); /* AP might have been stopped while waiting for the lock. */ - if (!sdata->vif.color_change_active) - goto unlock; + if (!link_conf->color_change_active) + return; if (!ieee80211_sdata_running(sdata)) - goto unlock; + return; - ieee80211_color_change_finalize(sdata); + ieee80211_color_change_finalize(link); +} -unlock: - mutex_unlock(&local->mtx); - sdata_unlock(sdata); +void ieee80211_color_collision_detection_work(struct wiphy *wiphy, + struct wiphy_work *work) +{ + struct ieee80211_link_data *link = + container_of(work, struct ieee80211_link_data, + color_collision_detect_work.work); + struct ieee80211_sub_if_data *sdata = link->sdata; + + cfg80211_obss_color_collision_notify(sdata->dev, link->color_bitmap, + link->link_id); } -void ieee80211_color_change_finish(struct ieee80211_vif *vif) +void ieee80211_color_change_finish(struct ieee80211_vif *vif, u8 link_id) { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + struct ieee80211_link_data *link; + + if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS)) + return; + + rcu_read_lock(); + + link = rcu_dereference(sdata->link[link_id]); + if (WARN_ON(!link)) { + rcu_read_unlock(); + return; + } + + wiphy_work_queue(sdata->local->hw.wiphy, + &link->color_change_finalize_work); - ieee80211_queue_work(&sdata->local->hw, - &sdata->color_change_finalize_work); + rcu_read_unlock(); } EXPORT_SYMBOL_GPL(ieee80211_color_change_finish); void -ieeee80211_obss_color_collision_notify(struct ieee80211_vif *vif, - u64 color_bitmap) +ieee80211_obss_color_collision_notify(struct ieee80211_vif *vif, + u64 color_bitmap, u8 link_id) { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + struct ieee80211_link_data *link; + + if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS)) + return; + + rcu_read_lock(); + + link = rcu_dereference(sdata->link[link_id]); + if (WARN_ON(!link)) { + rcu_read_unlock(); + return; + } + + if (link->conf->color_change_active || link->conf->csa_active) { + rcu_read_unlock(); + return; + } - if (sdata->vif.color_change_active || sdata->vif.csa_active) + if (wiphy_delayed_work_pending(sdata->local->hw.wiphy, + &link->color_collision_detect_work)) { + rcu_read_unlock(); return; + } - cfg80211_obss_color_collision_notify(sdata->dev, color_bitmap); + link->color_bitmap = color_bitmap; + /* queue the color collision detection event every 500 ms in order to + * avoid sending too much netlink messages to userspace. + */ + wiphy_delayed_work_queue(sdata->local->hw.wiphy, + &link->color_collision_detect_work, + msecs_to_jiffies(500)); + + rcu_read_unlock(); } -EXPORT_SYMBOL_GPL(ieeee80211_obss_color_collision_notify); +EXPORT_SYMBOL_GPL(ieee80211_obss_color_collision_notify); static int ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev, @@ -4377,41 +5302,56 @@ ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - u32 changed = 0; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_link_data *link; + u8 link_id = params->link_id; + u64 changed = 0; int err; - sdata_assert_lock(sdata); + lockdep_assert_wiphy(local->hw.wiphy); - if (sdata->vif.bss_conf.nontransmitted) + if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS)) return -EINVAL; - mutex_lock(&local->mtx); + link = wiphy_dereference(wiphy, sdata->link[link_id]); + if (!link) + return -ENOLINK; + + link_conf = link->conf; + + if (link_conf->nontransmitted) + return -EINVAL; /* don't allow another color change if one is already active or if csa * is active */ - if (sdata->vif.color_change_active || sdata->vif.csa_active) { + if (link_conf->color_change_active || link_conf->csa_active) { err = -EBUSY; goto out; } - err = ieee80211_set_color_change_beacon(sdata, params, &changed); + err = ieee80211_set_unsol_bcast_probe_resp(sdata, + ¶ms->unsol_bcast_probe_resp, + link, link_conf, &changed); + if (err) + goto out; + + err = ieee80211_set_color_change_beacon(link, params, &changed); if (err) goto out; - sdata->vif.color_change_active = true; - sdata->vif.color_change_color = params->color; + link_conf->color_change_active = true; + link_conf->color_change_color = params->color; - cfg80211_color_change_started_notify(sdata->dev, params->count); + cfg80211_color_change_started_notify(sdata->dev, params->count, link_id); if (changed) - ieee80211_color_change_bss_config_notify(sdata, 0, 0, changed); + ieee80211_color_change_bss_config_notify(link, 0, 0, changed); else /* if the beacon didn't change, we can finalize immediately */ - ieee80211_color_change_finalize(sdata); + ieee80211_color_change_finalize(link); out: - mutex_unlock(&local->mtx); return err; } @@ -4428,6 +5368,171 @@ ieee80211_set_radar_background(struct wiphy *wiphy, return local->ops->set_radar_background(&local->hw, chandef); } +static int ieee80211_add_intf_link(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + if (wdev->use_4addr) + return -EOPNOTSUPP; + + return ieee80211_vif_set_links(sdata, wdev->valid_links, 0); +} + +static void ieee80211_del_intf_link(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + u16 new_links = wdev->valid_links & ~BIT(link_id); + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + /* During the link teardown process, certain functions require the + * link_id to remain in the valid_links bitmap. Therefore, instead + * of removing the link_id from the bitmap, pass a masked value to + * simulate as if link_id does not exist anymore. + */ + ieee80211_vif_set_links(sdata, new_links, 0); +} + +static int +ieee80211_add_link_station(struct wiphy *wiphy, struct net_device *dev, + struct link_station_parameters *params) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = wiphy_priv(wiphy); + struct sta_info *sta; + int ret; + + lockdep_assert_wiphy(local->hw.wiphy); + + sta = sta_info_get_bss(sdata, params->mld_mac); + if (!sta) + return -ENOENT; + + if (!sta->sta.valid_links) + return -EINVAL; + + if (sta->sta.valid_links & BIT(params->link_id)) + return -EALREADY; + + ret = ieee80211_sta_allocate_link(sta, params->link_id); + if (ret) + return ret; + + ret = sta_link_apply_parameters(local, sta, STA_LINK_MODE_NEW, params); + if (ret) { + ieee80211_sta_free_link(sta, params->link_id); + return ret; + } + + if (test_sta_flag(sta, WLAN_STA_ASSOC)) { + struct link_sta_info *link_sta; + + link_sta = sdata_dereference(sta->link[params->link_id], sdata); + rate_control_rate_init(link_sta); + } + + /* ieee80211_sta_activate_link frees the link upon failure */ + return ieee80211_sta_activate_link(sta, params->link_id); +} + +static int +ieee80211_mod_link_station(struct wiphy *wiphy, struct net_device *dev, + struct link_station_parameters *params) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = wiphy_priv(wiphy); + struct sta_info *sta; + + lockdep_assert_wiphy(local->hw.wiphy); + + sta = sta_info_get_bss(sdata, params->mld_mac); + if (!sta) + return -ENOENT; + + if (!(sta->sta.valid_links & BIT(params->link_id))) + return -EINVAL; + + return sta_link_apply_parameters(local, sta, STA_LINK_MODE_LINK_MODIFY, + params); +} + +static int +ieee80211_del_link_station(struct wiphy *wiphy, struct net_device *dev, + struct link_station_del_parameters *params) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct sta_info *sta; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + sta = sta_info_get_bss(sdata, params->mld_mac); + if (!sta) + return -ENOENT; + + if (!(sta->sta.valid_links & BIT(params->link_id))) + return -EINVAL; + + /* must not create a STA without links */ + if (sta->sta.valid_links == BIT(params->link_id)) + return -EINVAL; + + ieee80211_sta_remove_link(sta, params->link_id); + + return 0; +} + +static int ieee80211_set_hw_timestamp(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_set_hw_timestamp *hwts) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + + if (!local->ops->set_hw_timestamp) + return -EOPNOTSUPP; + + if (!check_sdata_in_driver(sdata)) + return -EIO; + + return local->ops->set_hw_timestamp(&local->hw, &sdata->vif, hwts); +} + +static int +ieee80211_set_ttlm(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ttlm_params *params) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + return ieee80211_req_neg_ttlm(sdata, params); +} + +static int +ieee80211_assoc_ml_reconf(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ml_reconf_req *req) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + return ieee80211_mgd_assoc_ml_reconf(sdata, req); +} + +static int +ieee80211_set_epcs(struct wiphy *wiphy, struct net_device *dev, bool enable) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + return ieee80211_mgd_set_epcs(sdata, enable); +} + const struct cfg80211_ops mac80211_config_ops = { .add_virtual_intf = ieee80211_add_iface, .del_virtual_intf = ieee80211_del_iface, @@ -4465,6 +5570,7 @@ const struct cfg80211_ops mac80211_config_ops = { .join_ocb = ieee80211_join_ocb, .leave_ocb = ieee80211_leave_ocb, .change_bss = ieee80211_change_bss, + .inform_bss = ieee80211_inform_bss, .set_txq_params = ieee80211_set_txq_params, .set_monitor_channel = ieee80211_set_monitor_channel, .suspend = ieee80211_suspend, @@ -4533,4 +5639,14 @@ const struct cfg80211_ops mac80211_config_ops = { .set_sar_specs = ieee80211_set_sar_specs, .color_change = ieee80211_color_change, .set_radar_background = ieee80211_set_radar_background, + .add_intf_link = ieee80211_add_intf_link, + .del_intf_link = ieee80211_del_intf_link, + .add_link_station = ieee80211_add_link_station, + .mod_link_station = ieee80211_mod_link_station, + .del_link_station = ieee80211_del_link_station, + .set_hw_timestamp = ieee80211_set_hw_timestamp, + .set_ttlm = ieee80211_set_ttlm, + .get_radio_mask = ieee80211_get_radio_mask, + .assoc_ml_reconf = ieee80211_assoc_ml_reconf, + .set_epcs = ieee80211_set_epcs, }; |
