[PATCH wireless-next 3/5] wifi: cfg80211: Refactor nl80211_dump_station() to prepare for per-link stats
Praneesh P
praneesh.p at oss.qualcomm.com
Mon Jun 8 11:02:41 PDT 2026
On 6/8/2026 1:00 PM, Johannes Berg wrote:
> On Sun, 2026-06-07 at 23:29 +0530, P Praneesh wrote:
>> +++ b/net/wireless/nl80211.c
>> @@ -39,6 +39,39 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev,
>>
>> /* the netlink family */
>> static struct genl_family nl80211_fam;
>> +/**
>> + * enum nl80211_dump_station_phase - station dump fragmentation phases
>> + * @NL80211_DUMP_STA_PHASE_AGGREGATED: send aggregated (MLO-combined) station
>> + * statistics for the station entry
>> + * @NL80211_DUMP_STA_PHASE_PER_LINK: send per-link statistics for each active
>> + * MLO link of the station; only used when dump_link_stats is set
>> + */
>> +enum nl80211_dump_station_phase {
>> + NL80211_DUMP_STA_PHASE_AGGREGATED = 0,
>> + NL80211_DUMP_STA_PHASE_PER_LINK = 1,
>> +};
> [snip]
>
> probably nicer to move this stuff to closer to the dump code that needs
> it; at the very least it's missing a blank line.
>
> Also I think the whole 'phase' introduction etc. seems to more belong to
> patch 4 instead of this one? We can still do the structure allocation
> etc. here.
Will add the missing blank line and move both the enum and struct
definitions to sit just above nl80211_dump_station(). Patch 3 will
only introduce the context struct (sta_idx, mac_addr, sinfo) and
the stateful allocation/free mechanism. The phase field and its
enum will move to patch 4 where they are actually used.
>> +struct nl80211_dump_station_ctx {
>> + int sta_idx;
>> + enum nl80211_dump_station_phase phase;
>> + u8 mac_addr[ETH_ALEN];
>> + struct station_info sinfo;
>> +};
>> +
>> +/**
>> + * struct nl80211_dump_station_cb - state stored in netlink_callback::ctx
>> + * @wiphy_idx: args[0] - wiphy index from nl80211_prepare_wdev_dump
>> + * @wdev_id: args[1] - wdev identifier from nl80211_prepare_wdev_dump
>> + * @ctx: args[2] - dump station context pointer
>> + *
>> + * args[0] and args[1] are reserved by nl80211_prepare_wdev_dump().
>> + * The ctx pointer must live at args[2] to avoid corrupting those fields.
>> + */
>> +struct nl80211_dump_station_cb {
>> + long wiphy_idx;
>> + long wdev_id;
>> + struct nl80211_dump_station_ctx *ctx;
>> +};
> I'm not sure I'm happy with this - better to just use args[2] directly
> for a pointer to struct nl80211_dump_station_ctx?
>
>> 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_cb *cb_data = (void *)cb->ctx;
>> + struct nl80211_dump_station_ctx *ctx = cb_data->ctx;
> This doesn't really seem better than just doing
>
> struct nl80211_dump_station_ctx *ctx = cb->args[2];
>
> given the overlay with generic code.
>
> Alternatively we could change the _whole_ nl80211 code (as a separate
> commit, perhaps even converting other things later) to have
>
> struct nl80211_dump_cb {
> long wiphy_idx;
> long wdev_id;
> union {
> long wiphy_filter;
> int mpath_idx;
> int reg_idx;
> int bss_idx;
> int survey_idx;
> //...
>
> // and later add
> struct nl80211_dump_station_ctx *sta_ctx;
> };
> };
>
> or so, and then we don't have the args[0]/... problem at all.
>
> But I'm not convinced mixing the structs with "must have this type at
> this offset" etc. is a good idea if it's going to stay this way.
Ok, I'll drop nl80211_dump_station_cb entirely and use:
struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
directly, mirroring nl80211_dump_wiphy's pattern of casting
cb->args[0] to its state pointer. The cb->args[0]/[1] contract
is already established by nl80211_prepare_wdev_dump(), no
overlay struct is needed to document it.
>> + /* Phase 0: dump aggregated station info */
>> + if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) {
>> + while (true) {
>> + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
>>
> if you have this memset, why do you need this one:
>
>> +out_err_release:
>> + cfg80211_sinfo_release_content(&ctx->sinfo);
>> + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
> Seems a bit odd?
>
> johannes
The two memsets serve different purposes.
The first one (Phase 0 loop top) is a clean-slate reset before allocating
the per-station sinfo.links[] entries for each iteration.
The second one at out_err_release is a double-free guard. The ctx is
persistent across netlink dump callbacks (stored in cb->ctx), so when the
function returns early via out_err_release, nl80211_dump_station_done()
will eventually be called and it unconditionally invokes
cfg80211_sinfo_release_content() again:
if (ctx) {
cfg80211_sinfo_release_content(&ctx->sinfo);
kfree(ctx);
}
Without the memset at out_err_release, sinfo.links[] would still hold the
already-freed pointers when _done() runs, resulting in a double-free.
The memset nullifies those pointers so the second
cfg80211_sinfo_release_content() call in _done() hits
kfree(NULL), which is harmless.
More information about the ath12k
mailing list