Commit cb71f1d1 authored by Johannes Berg's avatar Johannes Berg

wifi: mac80211: add sta link addition/removal

Add the necessary infrastructure, including a new driver
method, to add/remove links to/from a station. To do this,
refactor the link alloc/free a bit, splitting that so we
can do it without linking them, to handle failures better.

Note that a station entry must be created representing an
MLD or a non-MLD STA, it cannot change between the two.
When representing an MLD, the 'deflink' is used for the
first link, which might be removed later, in which case
the memory isn't reused.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 69d41b5a
......@@ -2150,7 +2150,6 @@ struct ieee80211_link_sta {
* @max_tid_amsdu_len: Maximum A-MSDU size in bytes for this TID
* @txq: per-TID data TX queues (if driver uses the TXQ abstraction); note that
* the last entry (%IEEE80211_NUM_TIDS) is used for non-data frames
* @multi_link_sta: Identifies if this sta is a MLD STA
* @deflink: This holds the default link STA information, for non MLO STA all link
* specific STA information is accessed through @deflink or through
* link[0] which points to address of @deflink. For MLO Link STA
......@@ -2162,6 +2161,7 @@ struct ieee80211_link_sta {
* @deflink address and remaining would be allocated and the address
* would be assigned to link[link_id] where link_id is the id assigned
* by the AP.
* @valid_links: bitmap of valid links, or 0 for non-MLO
*/
struct ieee80211_sta {
u8 addr[ETH_ALEN];
......@@ -2199,7 +2199,7 @@ struct ieee80211_sta {
struct ieee80211_txq *txq[IEEE80211_NUM_TIDS + 1];
bool multi_link_sta;
u16 valid_links;
struct ieee80211_link_sta deflink;
struct ieee80211_link_sta *link[IEEE80211_MLD_MAX_NUM_LINKS];
......@@ -4048,6 +4048,11 @@ struct ieee80211_prep_tx_info {
* The @old[] array contains pointers to the old bss_conf structures
* that were already removed, in case they're needed.
* This callback can sleep.
* @change_sta_links: Change the valid links of a station, similar to
* @change_vif_links. This callback can sleep.
* Note that a sta can also be inserted or removed with valid links,
* i.e. passed to @sta_add/@sta_state with sta->valid_links not zero.
* In fact, cannot change from having valid_links and not having them.
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
......@@ -4395,6 +4400,10 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
u16 old_links, u16 new_links,
struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS]);
int (*change_sta_links)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
u16 old_links, u16 new_links);
};
/**
......
......@@ -1839,7 +1839,7 @@ 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);
sta = sta_info_alloc(sdata, mac, -1, GFP_KERNEL);
if (!sta)
return -ENOMEM;
......
......@@ -1554,4 +1554,25 @@ static inline int drv_change_vif_links(struct ieee80211_local *local,
return ret;
}
static inline int drv_change_sta_links(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
struct ieee80211_sta *sta,
u16 old_links, u16 new_links)
{
int ret = -EOPNOTSUPP;
might_sleep();
if (!check_sdata_in_driver(sdata))
return -EIO;
trace_drv_change_sta_links(local, sdata, sta, old_links, new_links);
if (local->ops->change_sta_links)
ret = local->ops->change_sta_links(&local->hw, &sdata->vif, sta,
old_links, new_links);
trace_drv_return_int(local, ret);
return ret;
}
#endif /* __MAC80211_DRIVER_OPS */
......@@ -629,7 +629,7 @@ ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata, const u8 *bssid,
scan_width = cfg80211_chandef_to_scan_width(&chanctx_conf->def);
rcu_read_unlock();
sta = sta_info_alloc(sdata, addr, GFP_KERNEL);
sta = sta_info_alloc(sdata, addr, -1, GFP_KERNEL);
if (!sta) {
rcu_read_lock();
return NULL;
......@@ -1229,7 +1229,7 @@ void ieee80211_ibss_rx_no_sta(struct ieee80211_sub_if_data *sdata,
scan_width = cfg80211_chandef_to_scan_width(&chanctx_conf->def);
rcu_read_unlock();
sta = sta_info_alloc(sdata, addr, GFP_ATOMIC);
sta = sta_info_alloc(sdata, addr, -1, GFP_ATOMIC);
if (!sta)
return;
......
......@@ -510,7 +510,7 @@ __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
if (aid < 0)
return NULL;
sta = sta_info_alloc(sdata, hw_addr, GFP_KERNEL);
sta = sta_info_alloc(sdata, hw_addr, -1, GFP_KERNEL);
if (!sta)
return NULL;
......
......@@ -5579,7 +5579,7 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
}
if (!have_sta) {
new_sta = sta_info_alloc(sdata, cbss->bssid, GFP_KERNEL);
new_sta = sta_info_alloc(sdata, cbss->bssid, -1, GFP_KERNEL);
if (!new_sta)
return -ENOMEM;
}
......
......@@ -69,7 +69,7 @@ void ieee80211_ocb_rx_no_sta(struct ieee80211_sub_if_data *sdata,
scan_width = cfg80211_chandef_to_scan_width(&chanctx_conf->def);
rcu_read_unlock();
sta = sta_info_alloc(sdata, addr, GFP_ATOMIC);
sta = sta_info_alloc(sdata, addr, -1, GFP_ATOMIC);
if (!sta)
return;
......
......@@ -64,6 +64,11 @@
* freed before they are done using it.
*/
struct sta_link_alloc {
struct link_sta_info info;
struct ieee80211_link_sta sta;
};
static const struct rhashtable_params sta_rht_params = {
.nelem_hint = 3, /* start small */
.automatic_shrinking = true,
......@@ -245,17 +250,27 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
return NULL;
}
static void sta_info_free_links(struct sta_info *sta)
static void sta_info_free_link(struct link_sta_info *link_sta)
{
unsigned int link_id;
free_percpu(link_sta->pcpu_rx_stats);
}
for (link_id = 0; link_id < ARRAY_SIZE(sta->link); link_id++) {
if (!sta->link[link_id])
continue;
free_percpu(sta->link[link_id]->pcpu_rx_stats);
static void sta_remove_link(struct sta_info *sta, unsigned int link_id)
{
struct sta_link_alloc *alloc = NULL;
if (sta->link[link_id] != &sta->deflink)
kfree(sta->link[link_id]);
if (WARN_ON(!sta->link[link_id]))
return;
if (sta->link[link_id] != &sta->deflink)
alloc = container_of(sta->link[link_id], typeof(*alloc), info);
sta->sta.valid_links &= ~BIT(link_id);
sta->link[link_id] = NULL;
sta->sta.link[link_id] = NULL;
if (alloc) {
sta_info_free_link(&alloc->info);
kfree(alloc);
}
}
......@@ -272,6 +287,15 @@ static void sta_info_free_links(struct sta_info *sta)
*/
void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
{
int i;
for (i = 0; i < ARRAY_SIZE(sta->link); i++) {
if (!(sta->sta.valid_links & BIT(i)))
continue;
sta_remove_link(sta, i);
}
/*
* If we had used sta_info_pre_move_state() then we might not
* have gone through the state transitions down again, so do
......@@ -302,7 +326,7 @@ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
kfree(sta->mesh);
#endif
sta_info_free_links(sta);
sta_info_free_link(&sta->deflink);
kfree(sta);
}
......@@ -348,19 +372,13 @@ static int sta_prepare_rate_control(struct ieee80211_local *local,
return 0;
}
static int sta_info_init_link(struct sta_info *sta,
unsigned int link_id,
struct link_sta_info *link_info,
struct ieee80211_link_sta *link_sta,
gfp_t gfp)
static int sta_info_alloc_link(struct ieee80211_local *local,
struct link_sta_info *link_info,
gfp_t gfp)
{
struct ieee80211_local *local = sta->local;
struct ieee80211_hw *hw = &local->hw;
int i;
link_info->sta = sta;
link_info->link_id = link_id;
if (ieee80211_hw_check(hw, USES_RSS)) {
link_info->pcpu_rx_stats =
alloc_percpu_gfp(struct ieee80211_sta_rx_stats, gfp);
......@@ -368,9 +386,6 @@ static int sta_info_init_link(struct sta_info *sta,
return -ENOMEM;
}
sta->link[link_id] = link_info;
sta->sta.link[link_id] = link_sta;
link_info->rx_stats.last_rx = jiffies;
u64_stats_init(&link_info->rx_stats.syncp);
......@@ -382,8 +397,19 @@ static int sta_info_init_link(struct sta_info *sta,
return 0;
}
static void sta_info_add_link(struct sta_info *sta,
unsigned int link_id,
struct link_sta_info *link_info,
struct ieee80211_link_sta *link_sta)
{
link_info->sta = sta;
link_info->link_id = link_id;
sta->link[link_id] = link_info;
sta->sta.link[link_id] = link_sta;
}
struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
const u8 *addr, gfp_t gfp)
const u8 *addr, int link_id, gfp_t gfp)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_hw *hw = &local->hw;
......@@ -397,9 +423,17 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
sta->local = local;
sta->sdata = sdata;
if (sta_info_init_link(sta, 0, &sta->deflink, &sta->sta.deflink, gfp))
if (sta_info_alloc_link(local, &sta->deflink, gfp))
return NULL;
if (link_id >= 0) {
sta_info_add_link(sta, link_id, &sta->deflink,
&sta->sta.deflink);
sta->sta.valid_links = BIT(link_id);
} else {
sta_info_add_link(sta, 0, &sta->deflink, &sta->sta.deflink);
}
spin_lock_init(&sta->lock);
spin_lock_init(&sta->ps_lock);
INIT_WORK(&sta->drv_deliver_wk, sta_deliver_ps_frames);
......@@ -565,7 +599,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
if (sta->sta.txq[0])
kfree(to_txq_info(sta->sta.txq[0]));
free:
sta_info_free_links(sta);
sta_info_free_link(&sta->deflink);
#ifdef CONFIG_MAC80211_MESH
kfree(sta->mesh);
#endif
......@@ -2613,3 +2647,77 @@ void ieee80211_sta_set_expected_throughput(struct ieee80211_sta *pubsta,
sta_update_codel_params(sta, thr);
}
int ieee80211_sta_allocate_link(struct sta_info *sta, unsigned int link_id)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
struct sta_link_alloc *alloc;
int ret;
lockdep_assert_held(&sdata->local->sta_mtx);
/* must represent an MLD from the start */
if (WARN_ON(!sta->sta.valid_links))
return -EINVAL;
if (WARN_ON(sta->sta.valid_links & BIT(link_id) ||
sta->link[link_id]))
return -EBUSY;
alloc = kzalloc(sizeof(*alloc), GFP_KERNEL);
if (!alloc)
return -ENOMEM;
ret = sta_info_alloc_link(sdata->local, &alloc->info, GFP_KERNEL);
if (ret) {
kfree(alloc);
return ret;
}
sta_info_add_link(sta, link_id, &alloc->info, &alloc->sta);
return 0;
}
int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
u16 old_links = sta->sta.valid_links;
u16 new_links = old_links | BIT(link_id);
int ret;
lockdep_assert_held(&sdata->local->sta_mtx);
if (WARN_ON(old_links == new_links || !sta->link[link_id]))
return -EINVAL;
sta->sta.valid_links = new_links;
if (!test_sta_flag(sta, WLAN_STA_INSERTED))
return 0;
ret = drv_change_sta_links(sdata->local, sdata, &sta->sta,
old_links, new_links);
if (ret) {
sta->sta.valid_links = old_links;
sta_remove_link(sta, link_id);
}
return ret;
}
void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
lockdep_assert_held(&sdata->local->sta_mtx);
sta->sta.valid_links &= ~BIT(link_id);
if (test_sta_flag(sta, WLAN_STA_INSERTED))
drv_change_sta_links(sdata->local, sdata, &sta->sta,
sta->sta.valid_links,
sta->sta.valid_links & ~BIT(link_id));
sta_remove_link(sta, link_id);
}
......@@ -623,7 +623,6 @@ struct link_sta_info {
* @tdls_chandef: a TDLS peer can have a wider chandef that is compatible to
* the BSS one.
* @frags: fragment cache
* @multi_link_sta: Identifies if this sta is a MLD STA or regular STA
* @deflink: This is the default link STA information, for non MLO STA all link
* specific STA information is accessed through @deflink or through
* link[0] which points to address of @deflink. For MLO Link STA
......@@ -708,7 +707,6 @@ struct sta_info {
struct ieee80211_fragment_cache frags;
bool multi_link_sta;
struct link_sta_info deflink;
struct link_sta_info *link[IEEE80211_MLD_MAX_NUM_LINKS];
......@@ -833,7 +831,7 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
* until sta_info_insert().
*/
struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
const u8 *addr, gfp_t gfp);
const u8 *addr, int link_id, gfp_t gfp);
void sta_info_free(struct ieee80211_local *local, struct sta_info *sta);
......@@ -892,6 +890,10 @@ u32 sta_get_expected_throughput(struct sta_info *sta);
void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata,
unsigned long exp_time);
int ieee80211_sta_allocate_link(struct sta_info *sta, unsigned int link_id);
int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id);
void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id);
void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta);
void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta);
void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);
......
......@@ -2482,6 +2482,37 @@ TRACE_EVENT(drv_change_vif_links,
)
);
TRACE_EVENT(drv_change_sta_links,
TP_PROTO(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
struct ieee80211_sta *sta,
u16 old_links, u16 new_links),
TP_ARGS(local, sdata, sta, old_links, new_links),
TP_STRUCT__entry(
LOCAL_ENTRY
VIF_ENTRY
STA_ENTRY
__field(u16, old_links)
__field(u16, new_links)
),
TP_fast_assign(
LOCAL_ASSIGN;
VIF_ASSIGN;
STA_ASSIGN;
__entry->old_links = old_links;
__entry->new_links = new_links;
),
TP_printk(
LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT " old_links:0x%04x, new_links:0x%04x\n",
LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG,
__entry->old_links, __entry->new_links
)
);
/*
* Tracing for API calls that drivers call.
*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment