* [PATCH wireless-next 1/2] wifi: cfg80211/mac80211: change memory allocation for link_sinfo structure
2026-04-28 9:09 [PATCH wireless-next 0/2] wifi: cfg80211/mac80211: optimize station info handling Sarika Sharma
@ 2026-04-28 9:09 ` Sarika Sharma
2026-04-28 9:31 ` Johannes Berg
2026-04-28 9:09 ` [PATCH wireless-next 2/2] wifi: cfg80211/mac80211: set only non-MLO-applicable fields for non-MLO stations Sarika Sharma
1 sibling, 1 reply; 7+ messages in thread
From: Sarika Sharma @ 2026-04-28 9:09 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, Sarika Sharma
Currently, during the NL80211_CMD_GET_STATION call, cfg80211 allocates
memory for link_sinfo objects for all possible links, regardless
of whether they are valid for the station. However, mac80211 only
fills in link_sinfo for valid links, leading to unnecessary memory
consumption.
To optimize memory usage, introduce an API in cfg80211 to dynamically
allocate link_sinfo and the corresponding link tidstats objects.
Memory is allocated only for valid links during link_sinfo population
in mac80211.
Also, refactor cfg80211_sinfo_release_content() so that link_sinfo is
freed separately, keeping allocation and free paths symmetric.
Signed-off-by: Sarika Sharma <sarika.sharma@oss.qualcomm.com>
---
include/net/cfg80211.h | 29 +++++++++++++++++++++++++----
net/mac80211/ethtool.c | 4 ++++
net/mac80211/sta_info.c | 14 +++++++++-----
net/wireless/nl80211.c | 29 ++++++-----------------------
net/wireless/util.c | 21 +++++++++++++++++++++
5 files changed, 65 insertions(+), 32 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 9d3639ff9c28..7dc12c2877b1 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -9146,6 +9146,29 @@ int cfg80211_sinfo_alloc_tid_stats(struct station_info *sinfo, gfp_t gfp);
int cfg80211_link_sinfo_alloc_tid_stats(struct link_station_info *link_sinfo,
gfp_t gfp);
+/**
+ * cfg80211_alloc_link_sinfo_stats - allocate link_sinfo and per tid-statistics.
+ * @link_sinfo: the station link information
+ * @tidstats: indicate if per-tid stats are required
+ * @gfp: allocation flags
+ *
+ * Return: 0 on success. Non-zero on error.
+ */
+int cfg80211_alloc_link_sinfo_stats(struct link_station_info **link_sinfo,
+ bool tidstats, gfp_t gfp);
+
+/**
+ * cfg80211_free_link_sinfo - free the content and memory allocated for
+ * link_sinfo
+ * @link_sinfo: the link_station information
+ */
+static inline void
+cfg80211_free_link_sinfo(struct link_station_info *link_sinfo)
+{
+ kfree(link_sinfo->pertid);
+ kfree(link_sinfo);
+}
+
/**
* cfg80211_sinfo_release_content - release contents of station info
* @sinfo: the station information
@@ -9159,10 +9182,8 @@ static inline void cfg80211_sinfo_release_content(struct station_info *sinfo)
kfree(sinfo->pertid);
for (int link_id = 0; link_id < ARRAY_SIZE(sinfo->links); link_id++) {
- if (sinfo->links[link_id]) {
- kfree(sinfo->links[link_id]->pertid);
- kfree(sinfo->links[link_id]);
- }
+ if (sinfo->links[link_id])
+ cfg80211_free_link_sinfo(sinfo->links[link_id]);
}
}
diff --git a/net/mac80211/ethtool.c b/net/mac80211/ethtool.c
index 3d365626faa4..780229e6bc6d 100644
--- a/net/mac80211/ethtool.c
+++ b/net/mac80211/ethtool.c
@@ -136,6 +136,8 @@ static void ieee80211_get_stats(struct net_device *dev,
if (sinfo.filled & BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG))
data[i] = (u8)sinfo.signal_avg;
i++;
+ if (sinfo.valid_links)
+ cfg80211_sinfo_release_content(&sinfo);
} else {
list_for_each_entry(sta, &local->sta_list, list) {
/* Make sure this station belongs to the proper dev */
@@ -147,6 +149,8 @@ static void ieee80211_get_stats(struct net_device *dev,
i = 0;
ADD_STA_STATS(&sta->deflink);
data[i++] = sdata->tx_handlers_drop;
+ if (sinfo.valid_links)
+ cfg80211_sinfo_release_content(&sinfo);
}
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 4c31ef8817ce..6c6fc7641a53 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2963,8 +2963,7 @@ static void sta_set_link_sinfo(struct sta_info *sta,
BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
}
- if (tidstats && !cfg80211_link_sinfo_alloc_tid_stats(link_sinfo,
- GFP_KERNEL)) {
+ if (tidstats) {
for (i = 0; i < IEEE80211_NUM_TIDS + 1; i++)
sta_set_tidstats(sta, &link_sinfo->pertid[i], i,
link_id);
@@ -3252,6 +3251,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
}
if (sta->sta.valid_links) {
+ struct link_station_info *link_sinfo;
struct ieee80211_link_data *link;
struct link_sta_info *link_sta;
int link_id;
@@ -3267,12 +3267,16 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
link = wiphy_dereference(sdata->local->hw.wiphy,
sdata->link[link_id]);
- if (!link_sta || !sinfo->links[link_id] || !link) {
+ if (!link_sta || !link ||
+ cfg80211_alloc_link_sinfo_stats(&link_sinfo,
+ tidstats,
+ GFP_KERNEL)) {
sinfo->valid_links &= ~BIT(link_id);
continue;
}
- sta_set_link_sinfo(sta, sinfo->links[link_id],
- link, tidstats);
+
+ sta_set_link_sinfo(sta, link_sinfo, link, tidstats);
+ sinfo->links[link_id] = link_sinfo;
}
}
}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f334cdef8958..108583fb2cd2 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8199,7 +8199,7 @@ static int nl80211_dump_station(struct sk_buff *skb,
u8 mac_addr[ETH_ALEN];
int sta_idx = cb->args[2];
bool sinfo_alloc = false;
- int err, i;
+ int err;
err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL);
if (err)
@@ -8220,20 +8220,13 @@ static int nl80211_dump_station(struct sk_buff *skb,
while (1) {
memset(&sinfo, 0, sizeof(sinfo));
- for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
- sinfo.links[i] =
- kzalloc_obj(*sinfo.links[0]);
- if (!sinfo.links[i]) {
- err = -ENOMEM;
- goto out_err;
- }
- sinfo_alloc = true;
- }
-
err = rdev_dump_station(rdev, wdev, sta_idx,
mac_addr, &sinfo);
if (err == -ENOENT)
break;
+
+ sinfo_alloc = true;
+
if (err)
goto out_err;
@@ -8273,7 +8266,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
struct station_info sinfo;
struct sk_buff *msg;
u8 *mac_addr = NULL;
- int err, i;
+ int err;
memset(&sinfo, 0, sizeof(sinfo));
@@ -8288,19 +8281,9 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->get_station)
return -EOPNOTSUPP;
- for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
- sinfo.links[i] = kzalloc_obj(*sinfo.links[0]);
- if (!sinfo.links[i]) {
- cfg80211_sinfo_release_content(&sinfo);
- return -ENOMEM;
- }
- }
-
err = rdev_get_station(rdev, wdev, mac_addr, &sinfo);
- if (err) {
- cfg80211_sinfo_release_content(&sinfo);
+ if (err)
return err;
- }
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg) {
diff --git a/net/wireless/util.c b/net/wireless/util.c
index cff5a1bd95cc..1e1ae2dab7ad 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -2738,6 +2738,27 @@ int cfg80211_link_sinfo_alloc_tid_stats(struct link_station_info *link_sinfo,
}
EXPORT_SYMBOL(cfg80211_link_sinfo_alloc_tid_stats);
+int cfg80211_alloc_link_sinfo_stats(struct link_station_info **link_sinfo,
+ bool tidstats, gfp_t gfp)
+{
+ int ret;
+
+ *link_sinfo = kzalloc_obj(**link_sinfo, gfp);
+ if (!*link_sinfo)
+ return -ENOMEM;
+
+ if (tidstats) {
+ ret = cfg80211_link_sinfo_alloc_tid_stats(*link_sinfo, gfp);
+ if (ret) {
+ kfree(*link_sinfo);
+ *link_sinfo = NULL;
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(cfg80211_alloc_link_sinfo_stats);
+
int cfg80211_sinfo_alloc_tid_stats(struct station_info *sinfo, gfp_t gfp)
{
sinfo->pertid = kzalloc_objs(*(sinfo->pertid), IEEE80211_NUM_TIDS + 1,
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH wireless-next 2/2] wifi: cfg80211/mac80211: set only non-MLO-applicable fields for non-MLO stations
2026-04-28 9:09 [PATCH wireless-next 0/2] wifi: cfg80211/mac80211: optimize station info handling Sarika Sharma
2026-04-28 9:09 ` [PATCH wireless-next 1/2] wifi: cfg80211/mac80211: change memory allocation for link_sinfo structure Sarika Sharma
@ 2026-04-28 9:09 ` Sarika Sharma
1 sibling, 0 replies; 7+ messages in thread
From: Sarika Sharma @ 2026-04-28 9:09 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, Sarika Sharma
Currently, in sta_set_sinfo() during the NL80211_CMD_GET_STATION call,
mac80211 sets certain non-MLO applicable fields with default values
even for MLO stations. These fields are later cleared in cfg80211
before the data is sent to userspace, resulting in unnecessary operations.
Hence, avoid setting these fields for MLO stations to simplify the
code and eliminate redundant processing.
Signed-off-by: Sarika Sharma <sarika.sharma@oss.qualcomm.com>
---
net/mac80211/sta_info.c | 49 +++++++++++++++++++++++------------------
net/wireless/nl80211.c | 6 -----
2 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 6c6fc7641a53..5307dc30595a 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3150,27 +3150,6 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
}
}
- /* for the average - if pcpu_rx_stats isn't set - rxstats must point to
- * the sta->rx_stats struct, so the check here is fine with and without
- * pcpu statistics
- */
- if (last_rxstats->chains &&
- !(sinfo->filled & (BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL) |
- BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG)))) {
- sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL);
- if (!sta->deflink.pcpu_rx_stats)
- sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
-
- sinfo->chains = last_rxstats->chains;
-
- for (i = 0; i < ARRAY_SIZE(sinfo->chain_signal); i++) {
- sinfo->chain_signal[i] =
- last_rxstats->chain_signal_last[i];
- sinfo->chain_signal_avg[i] =
- -ewma_signal_read(&sta->deflink.rx_stats_avg.chain_signal[i]);
- }
- }
-
if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE)) &&
!sta->sta.valid_links &&
ieee80211_rate_valid(&sta->deflink.tx_stats.last_rate)) {
@@ -3278,6 +3257,34 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
sta_set_link_sinfo(sta, link_sinfo, link, tidstats);
sinfo->links[link_id] = link_sinfo;
}
+ } else {
+ /*
+ * Set non-MLO applicable fields.
+ * For the average: if pcpu_rx_stats isn't set, rxstats must
+ * point to the sta->rx_stats struct, so the check here is fine
+ * with and without per-CPU statistics.
+ */
+ if (last_rxstats->chains &&
+ !(sinfo->filled &
+ (BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL) |
+ BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG)))) {
+ sinfo->filled |=
+ BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL);
+ if (!sta->deflink.pcpu_rx_stats)
+ sinfo->filled |=
+ BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
+
+ sinfo->chains = last_rxstats->chains;
+
+ for (i = 0; i < ARRAY_SIZE(sinfo->chain_signal); i++) {
+ struct ewma_signal chain_signal =
+ sta->deflink.rx_stats_avg.chain_signal[i];
+ sinfo->chain_signal[i] =
+ last_rxstats->chain_signal_last[i];
+ sinfo->chain_signal_avg[i] =
+ -ewma_signal_read(&chain_signal);
+ }
+ }
}
}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 108583fb2cd2..2a62be4f574a 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8182,12 +8182,6 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo)
BIT(NL80211_TID_STATS_TX_MSDU_FAILED);
}
}
-
- /* Reset sinfo->filled bits to exclude fields which don't make
- * much sense at the MLO level.
- */
- sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL);
- sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
}
static int nl80211_dump_station(struct sk_buff *skb,
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread