[PATCH wireless-next v2 3/5] wifi: cfg80211: Refactor nl80211_dump_station() to prepare for per-link stats
P Praneesh
praneesh.p at oss.qualcomm.com
Sat Jun 13 22:17:33 PDT 2026
Currently, nl80211_dump_station() relies on the netlink callback's generic
args array (cb->args[2]) to track the station index during dumps. It also
processes the entire sinfo structure and transmits it to userspace
immediately in a single pass.
This approach creates a bottleneck for MLO. When an MLD station has
multiple active links, the aggregated station information, combined
with the individual per-link statistics, can easily exceed the
maximum netlink message size limits. The current monolithic dump
iteration cannot pause and resume mid-station to fragment these large
per-link statistics across multiple netlink messages.
Introduce a stateful context structure (struct nl80211_dump_station_ctx)
allocated during the dump to track the iteration state. Store the context
pointer directly at cb->args[2], following the same pattern as
nl80211_dump_wiphy which stores its state pointer at cb->args[0].
Move the station index (sta_idx) tracking and the sinfo payload into this
context. The per-station netlink message is built inline in the loop:
common header attributes are assembled directly, then
nl80211_put_sta_info_common() adds the STA_INFO payload.
Furthermore, move the NL80211_CMD_GET_STATION command definition from
genl_small_ops to genl_ops to natively support the .done callback.
Implement nl80211_dump_station_done() to ensure the newly allocated state
context and its deeply allocated sinfo payload are safely freed when the
dump concludes or is aborted prematurely by userspace.
Note that the previous dump path used nl80211_send_station(), which
included NL80211_ATTR_IE and NL80211_ATTR_RESP_IE. These attributes are
not carried forward in this implementation. As documented, association
response IEs (assoc_resp_ies) are only relevant at station creation time
(e.g. via cfg80211_new_sta()) to notify userspace about association
details, and are not expected to be part of get_station()/dump_station()
callbacks. Aligning with this expectation, these IEs are intentionally
omitted here.
This refactoring maintains the existing netlink batching performance while
laying the stateful foundation required for per-link statistics
fragmentation in subsequent patches.
At out_err_release, cfg80211_sinfo_release_content() frees any
dynamically allocated sub-fields inside ctx->sinfo (including per-link
pointers in sinfo.links[]). Without the subsequent memset, those
pointers remain non-NULL in the embedded sinfo. When the dump concludes
or is aborted, nl80211_dump_station_done() calls
cfg80211_sinfo_release_content() a second time on the same ctx->sinfo,
which would free the already-released link memory. The
memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)) zeroes all pointers so the
second release call hits kfree(NULL), which is a harmless no-op.
Signed-off-by: P Praneesh <praneesh.p at oss.qualcomm.com>
---
net/wireless/nl80211.c | 129 +++++++++++++++++++++++++++--------------
1 file changed, 85 insertions(+), 44 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 66f19bfc030a..b4c4406d77bf 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8464,16 +8464,19 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo)
sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
}
+struct nl80211_dump_station_ctx {
+ int sta_idx;
+ u8 mac_addr[ETH_ALEN];
+ struct station_info sinfo;
+};
+
static int nl80211_dump_station(struct sk_buff *skb,
struct netlink_callback *cb)
{
- struct station_info sinfo;
struct cfg80211_registered_device *rdev;
struct wireless_dev *wdev;
- u8 mac_addr[ETH_ALEN];
- int sta_idx = cb->args[2];
- bool sinfo_alloc = false;
- int err, i;
+ struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
+ int err;
err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL);
if (err)
@@ -8481,6 +8484,15 @@ static int nl80211_dump_station(struct sk_buff *skb,
/* nl80211_prepare_wdev_dump acquired it in the successful case */
__acquire(&rdev->wiphy.mtx);
+ if (!ctx) {
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+ cb->args[2] = (long)ctx;
+ }
+
if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) {
err = -EINVAL;
goto out_err;
@@ -8491,55 +8503,83 @@ static int nl80211_dump_station(struct sk_buff *skb,
goto out_err;
}
- while (1) {
- memset(&sinfo, 0, sizeof(sinfo));
+ while (true) {
+ void *hdr;
- for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
- sinfo.links[i] =
- kzalloc_obj(*sinfo.links[0]);
- if (!sinfo.links[i]) {
+ memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
+ for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
+ ctx->sinfo.links[i] =
+ kzalloc_obj(*ctx->sinfo.links[0]);
+ if (!ctx->sinfo.links[i]) {
err = -ENOMEM;
- goto out_err;
+ goto out_err_release;
}
- sinfo_alloc = true;
}
- err = rdev_dump_station(rdev, wdev, sta_idx,
- mac_addr, &sinfo);
- if (err == -ENOENT)
- break;
+ err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
+ ctx->mac_addr, &ctx->sinfo);
+ if (err == -ENOENT) {
+ err = skb->len;
+ goto out_err_release;
+ }
if (err)
- goto out_err;
+ goto out_err_release;
- if (sinfo.valid_links)
- cfg80211_sta_set_mld_sinfo(&sinfo);
+ if (ctx->sinfo.valid_links)
+ cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
- /* reset the sinfo_alloc flag as nl80211_send_station()
- * always releases sinfo
- */
- sinfo_alloc = false;
+ hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ NL80211_CMD_NEW_STATION);
+ if (!hdr) {
+ err = skb->len;
+ goto out_err_release;
+ }
- if (nl80211_send_station(skb, NL80211_CMD_NEW_STATION,
- NETLINK_CB(cb->skb).portid,
- cb->nlh->nlmsg_seq, NLM_F_MULTI,
- rdev, wdev, mac_addr,
- &sinfo) < 0)
- goto out;
+ if ((wdev->netdev &&
+ nla_put_u32(skb, NL80211_ATTR_IFINDEX,
+ wdev->netdev->ifindex)) ||
+ nla_put_u64_64bit(skb, NL80211_ATTR_WDEV,
+ wdev_id(wdev), NL80211_ATTR_PAD) ||
+ nla_put(skb, NL80211_ATTR_MAC, ETH_ALEN, ctx->mac_addr) ||
+ nla_put_u32(skb, NL80211_ATTR_GENERATION,
+ ctx->sinfo.generation)) {
+ genlmsg_cancel(skb, hdr);
+ err = skb->len;
+ goto out_err_release;
+ }
- sta_idx++;
+ if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) {
+ genlmsg_cancel(skb, hdr);
+ err = skb->len;
+ goto out_err_release;
+ }
+
+ genlmsg_end(skb, hdr);
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ ctx->sta_idx++;
}
- out:
- cb->args[2] = sta_idx;
- err = skb->len;
- out_err:
- if (sinfo_alloc)
- cfg80211_sinfo_release_content(&sinfo);
+out_err_release:
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
+out_err:
wiphy_unlock(&rdev->wiphy);
return err;
}
+static int nl80211_dump_station_done(struct netlink_callback *cb)
+{
+ struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
+
+ if (ctx) {
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ kfree(ctx);
+ }
+ return 0;
+}
+
static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -19504,6 +19544,14 @@ static const struct genl_ops nl80211_ops[] = {
/* can be retrieved by unprivileged users */
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY),
},
+ {
+ .cmd = NL80211_CMD_GET_STATION,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = nl80211_get_station,
+ .dumpit = nl80211_dump_station,
+ .done = nl80211_dump_station_done,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV),
+ },
};
static const struct genl_small_ops nl80211_small_ops[] = {
@@ -19603,13 +19651,6 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_MLO_VALID_LINK_ID),
},
- {
- .cmd = NL80211_CMD_GET_STATION,
- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
- .doit = nl80211_get_station,
- .dumpit = nl80211_dump_station,
- .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV),
- },
{
.cmd = NL80211_CMD_SET_STATION,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
--
2.43.0
More information about the ath12k
mailing list