* [PATCH wireless-next 04/14] wifi: cfg80211: allow representing NPCA in chandef
From: Johannes Berg @ 2026-04-28 9:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428092708.71740-16-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Add the necessary fields to the chandef data structure
to represent NPCA (the NPCA primary channel and NPCA
punctured/disabled subchannels bitmap), and the code
to check these for validity, compatibility, as well as
allowing it to be passed for AP mode for capable
devices.
Compatibility is assumed to only be the case when it's
actually identical, enabling later management of this
in channel contexts in mac80211 for multiple APs, but
requiring userspace to set up the identical chandef on
all AP interfaces that share a channel (and BSS color.)
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 11 +++-
include/uapi/linux/nl80211.h | 6 ++
net/wireless/chan.c | 59 +++++++++++++++--
net/wireless/nl80211.c | 120 ++++++++++++++++++++++++++++++-----
net/wireless/nl80211.h | 5 +-
net/wireless/pmsr.c | 2 +-
net/wireless/trace.h | 16 ++++-
7 files changed, 189 insertions(+), 30 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index a40ab36b8edb..9e66490dab4f 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -844,6 +844,7 @@ struct key_params {
/**
* struct cfg80211_chan_def - channel definition
* @chan: the (control) channel
+ * @npca_chan: the NPCA primary channel
* @width: channel width
* @center_freq1: center frequency of first segment
* @center_freq2: center frequency of second segment
@@ -856,18 +857,22 @@ struct key_params {
* @punctured: mask of the punctured 20 MHz subchannels, with
* bits turned on being disabled (punctured); numbered
* from lower to higher frequency (like in the spec)
+ * @npca_punctured: NPCA puncturing bitmap, like @punctured but for
+ * NPCA transmissions. If NPCA is used (@npca_chan is not %NULL)
+ * this will be a superset of the @punctured bimap.
* @s1g_primary_2mhz: Indicates if the control channel pointed to
* by 'chan' exists as a 1MHz primary subchannel within an
* S1G 2MHz primary channel.
*/
struct cfg80211_chan_def {
struct ieee80211_channel *chan;
+ struct ieee80211_channel *npca_chan;
enum nl80211_chan_width width;
u32 center_freq1;
u32 center_freq2;
struct ieee80211_edmg edmg;
u16 freq1_offset;
- u16 punctured;
+ u16 punctured, npca_punctured;
bool s1g_primary_2mhz;
};
@@ -1014,7 +1019,9 @@ cfg80211_chandef_identical(const struct cfg80211_chan_def *chandef1,
chandef1->freq1_offset == chandef2->freq1_offset &&
chandef1->center_freq2 == chandef2->center_freq2 &&
chandef1->punctured == chandef2->punctured &&
- chandef1->s1g_primary_2mhz == chandef2->s1g_primary_2mhz);
+ chandef1->s1g_primary_2mhz == chandef2->s1g_primary_2mhz &&
+ chandef1->npca_chan == chandef2->npca_chan &&
+ chandef1->npca_punctured == chandef2->npca_punctured);
}
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 072b383d7d3c..2b6957b61b62 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3142,6 +3142,9 @@ enum nl80211_commands {
* association response etc., since it's abridged in the beacon. Used
* for START_AP etc.
*
+ * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32)
+ * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32)
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3735,6 +3738,9 @@ enum nl80211_attrs {
NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME,
NL80211_ATTR_NAN_PEER_MAPS,
+ NL80211_ATTR_NPCA_PRIMARY_FREQ,
+ NL80211_ATTR_NPCA_PUNCT_BITMAP,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index 8b94c0de80ad..fae71f2d13ed 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -138,9 +138,10 @@ static const struct cfg80211_per_bw_puncturing_values per_bw_puncturing[] = {
CFG80211_PER_BW_VALID_PUNCTURING_VALUES(320)
};
-static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef)
+static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef,
+ u32 primary_center, u32 punctured)
{
- u32 idx, i, start_freq, primary_center = chandef->chan->center_freq;
+ u32 idx, i, start_freq;
switch (chandef->width) {
case NL80211_CHAN_WIDTH_80:
@@ -156,18 +157,18 @@ static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef)
start_freq = chandef->center_freq1 - 160;
break;
default:
- return chandef->punctured == 0;
+ return punctured == 0;
}
- if (!chandef->punctured)
+ if (!punctured)
return true;
/* check if primary channel is punctured */
- if (chandef->punctured & (u16)BIT((primary_center - start_freq) / 20))
+ if (punctured & (u16)BIT((primary_center - start_freq) / 20))
return false;
for (i = 0; i < per_bw_puncturing[idx].len; i++) {
- if (per_bw_puncturing[idx].valid_values[i] == chandef->punctured)
+ if (per_bw_puncturing[idx].valid_values[i] == punctured)
return true;
}
@@ -458,6 +459,40 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef)
if (!cfg80211_chandef_valid_control_freq(chandef, control_freq))
return false;
+ if (chandef->npca_chan) {
+ bool pri_upper, npca_upper;
+ u32 cf1;
+
+ switch (chandef->width) {
+ case NL80211_CHAN_WIDTH_80:
+ case NL80211_CHAN_WIDTH_160:
+ case NL80211_CHAN_WIDTH_320:
+ break;
+ default:
+ return false;
+ }
+
+ if (!cfg80211_chandef_valid_control_freq(chandef,
+ chandef->npca_chan->center_freq))
+ return false;
+
+ cf1 = chandef->center_freq1;
+ pri_upper = chandef->chan->center_freq > cf1;
+ npca_upper = chandef->npca_chan->center_freq > cf1;
+
+ if (pri_upper == npca_upper)
+ return false;
+
+ if (!valid_puncturing_bitmap(chandef,
+ chandef->npca_chan->center_freq,
+ chandef->npca_punctured) ||
+ (chandef->punctured & chandef->npca_punctured) !=
+ chandef->punctured)
+ return false;
+ } else if (chandef->npca_punctured) {
+ return false;
+ }
+
if (!cfg80211_valid_center_freq(chandef->center_freq1, chandef->width))
return false;
@@ -477,7 +512,8 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef)
if (!cfg80211_chandef_is_s1g(chandef) && chandef->s1g_primary_2mhz)
return false;
- return valid_puncturing_bitmap(chandef);
+ return valid_puncturing_bitmap(chandef, control_freq,
+ chandef->punctured);
}
EXPORT_SYMBOL(cfg80211_chandef_valid);
@@ -564,6 +600,15 @@ _cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1,
if (c1->width == c2->width)
return NULL;
+ /*
+ * We need NPCA to be compatible for some scenarios such as
+ * multiple APs, but in this case userspace should configure
+ * identical chandefs including NPCA, even if perhaps one of
+ * the AP interfaces doesn't even advertise it.
+ */
+ if (c1->npca_chan || c2->npca_chan)
+ return NULL;
+
/*
* can't be compatible if one of them is 5/10 MHz or S1G
* but they don't have the same width.
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index cf236307cca9..7cb5f39f783d 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1076,6 +1076,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME] = { .type = NLA_U16 },
[NL80211_ATTR_NAN_PEER_MAPS] =
NLA_POLICY_NESTED_ARRAY(nl80211_nan_peer_map_policy),
+ [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 },
+ [NL80211_ATTR_NPCA_PUNCT_BITMAP] =
+ NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
};
/* policy for the key attributes */
@@ -3772,7 +3775,8 @@ static bool nl80211_can_set_dev_channel(struct wireless_dev *wdev)
static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
struct netlink_ext_ack *extack,
struct nlattr **attrs, bool monitor,
- struct cfg80211_chan_def *chandef)
+ struct cfg80211_chan_def *chandef,
+ bool permit_npca)
{
u32 control_freq;
@@ -3886,6 +3890,34 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
}
}
+ if (attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ]) {
+ if (!permit_npca) {
+ NL_SET_ERR_MSG_ATTR(extack,
+ attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ],
+ "NPCA not supported");
+ return -EINVAL;
+ }
+
+ chandef->npca_chan =
+ ieee80211_get_channel(&rdev->wiphy,
+ nla_get_u32(attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ]));
+ if (!chandef->npca_chan) {
+ NL_SET_ERR_MSG_ATTR(extack,
+ attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ],
+ "invalid NPCA primary channel");
+ return -EINVAL;
+ }
+
+ chandef->npca_punctured =
+ nla_get_u32_default(attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP],
+ chandef->punctured);
+ } else if (attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP]) {
+ NL_SET_ERR_MSG_ATTR(extack,
+ attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP],
+ "NPCA puncturing only valid with NPCA");
+ return -EINVAL;
+ }
+
if (!cfg80211_chandef_valid(chandef)) {
NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ],
"invalid channel definition");
@@ -3913,9 +3945,11 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
struct netlink_ext_ack *extack,
struct nlattr **attrs,
- struct cfg80211_chan_def *chandef)
+ struct cfg80211_chan_def *chandef,
+ bool permit_npca)
{
- return _nl80211_parse_chandef(rdev, extack, attrs, false, chandef);
+ return _nl80211_parse_chandef(rdev, extack, attrs, false, chandef,
+ permit_npca);
}
static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
@@ -3928,6 +3962,7 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR;
struct wireless_dev *wdev = NULL;
int link_id = _link_id;
+ bool permit_npca;
if (dev)
wdev = dev->ieee80211_ptr;
@@ -3942,9 +3977,13 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
link_id = 0;
}
+ /* allow parsing it - will check on start_ap or below */
+ permit_npca = iftype == NL80211_IFTYPE_AP ||
+ iftype == NL80211_IFTYPE_P2P_GO;
+
result = _nl80211_parse_chandef(rdev, info->extack, info->attrs,
iftype == NL80211_IFTYPE_MONITOR,
- &chandef);
+ &chandef, permit_npca);
if (result)
return result;
@@ -3963,6 +4002,9 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
return -EBUSY;
/* Only allow dynamic channel width changes */
+ cur_chan = wdev->links[link_id].ap.chandef.npca_chan;
+ if (chandef.npca_chan != cur_chan)
+ return -EBUSY;
cur_chan = wdev->links[link_id].ap.chandef.chan;
if (chandef.chan != cur_chan)
return -EBUSY;
@@ -4447,6 +4489,15 @@ int nl80211_send_chandef(struct sk_buff *msg, const struct cfg80211_chan_def *ch
nla_put_flag(msg, NL80211_ATTR_S1G_PRIMARY_2MHZ))
return -ENOBUFS;
+ if (chandef->npca_chan &&
+ nla_put_u32(msg, NL80211_ATTR_NPCA_PRIMARY_FREQ,
+ chandef->npca_chan->center_freq))
+ return -ENOBUFS;
+ if (chandef->npca_punctured &&
+ nla_put_u32(msg, NL80211_ATTR_NPCA_PUNCT_BITMAP,
+ chandef->npca_punctured))
+ return -ENOBUFS;
+
return 0;
}
EXPORT_SYMBOL(nl80211_send_chandef);
@@ -6949,6 +7000,28 @@ nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev,
return 0;
}
+static int nl80211_check_npca(struct cfg80211_registered_device *rdev,
+ const struct cfg80211_chan_def *chandef,
+ enum nl80211_iftype iftype,
+ struct netlink_ext_ack *extack)
+{
+ const struct ieee80211_supported_band *sband;
+ const struct ieee80211_sta_uhr_cap *uhr_cap;
+
+ if (!chandef->npca_chan)
+ return 0;
+
+ sband = rdev->wiphy.bands[chandef->chan->band];
+ uhr_cap = ieee80211_get_uhr_iftype_cap(sband, iftype);
+
+ if (uhr_cap &&
+ (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP))
+ return 0;
+
+ NL_SET_ERR_MSG(extack, "NPCA not supported");
+ return -EINVAL;
+}
+
static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -7088,7 +7161,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- ¶ms->chandef);
+ ¶ms->chandef, true);
if (err)
goto out;
} else if (wdev->valid_links) {
@@ -7107,6 +7180,11 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (err)
goto out;
+ err = nl80211_check_npca(rdev, ¶ms->chandef, wdev->iftype,
+ info->extack);
+ if (err)
+ goto out;
+
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
beacon_check.reg_power =
@@ -11657,7 +11735,8 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
if (dfs_region == NL80211_DFS_UNSET)
return -EINVAL;
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef);
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef,
+ false);
if (err)
return err;
@@ -11746,7 +11825,8 @@ static int nl80211_notify_radar_detection(struct sk_buff *skb,
return -EINVAL;
}
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef);
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef,
+ false);
if (err) {
GENL_SET_ERR_MSG(info, "Unable to extract chandef info");
return err;
@@ -11829,6 +11909,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
int err;
bool need_new_beacon = false;
bool need_handle_dfs_flag = true;
+ bool permit_npca = false;
u32 cs_count;
if (!rdev->ops->channel_switch ||
@@ -11846,6 +11927,8 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
*/
need_handle_dfs_flag = false;
+ permit_npca = true;
+
/* useless if AP is not running */
if (!wdev->links[link_id].ap.beacon_interval)
return -ENOTCONN;
@@ -11883,7 +11966,12 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
params.count = cs_count;
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- ¶ms.chandef);
+ ¶ms.chandef, permit_npca);
+ if (err)
+ goto free;
+
+ err = nl80211_check_npca(rdev, ¶ms.chandef, wdev->iftype,
+ info->extack);
if (err)
goto free;
@@ -13157,7 +13245,7 @@ static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info)
}
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- &ibss.chandef);
+ &ibss.chandef, false);
if (err)
return err;
@@ -14157,7 +14245,8 @@ static int nl80211_remain_on_channel(struct sk_buff *skb,
duration > rdev->wiphy.max_remain_on_channel_duration)
return -EINVAL;
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef);
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef,
+ false);
if (err)
return err;
@@ -14376,7 +14465,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
chandef.chan = NULL;
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- &chandef);
+ &chandef, false);
if (err)
return err;
}
@@ -14779,7 +14868,7 @@ static int nl80211_join_ocb(struct sk_buff *skb, struct genl_info *info)
int err;
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- &setup.chandef);
+ &setup.chandef, false);
if (err)
return err;
@@ -14855,7 +14944,7 @@ static int nl80211_join_mesh(struct sk_buff *skb, struct genl_info *info)
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- &setup.chandef);
+ &setup.chandef, false);
if (err)
return err;
} else {
@@ -16819,7 +16908,7 @@ static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev,
return ret;
ret = nl80211_parse_chandef(rdev, info->extack, channel_parsed,
- &chandef);
+ &chandef, false);
if (ret)
return ret;
@@ -17818,7 +17907,8 @@ static int nl80211_tdls_channel_switch(struct sk_buff *skb,
!info->attrs[NL80211_ATTR_OPER_CLASS])
return -EINVAL;
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef);
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef,
+ false);
if (err)
return err;
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 048ba92c3e42..bdb065d14054 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Portions of this file
- * Copyright (C) 2018, 2020-2025 Intel Corporation
+ * Copyright (C) 2018, 2020-2026 Intel Corporation
*/
#ifndef __NET_WIRELESS_NL80211_H
#define __NET_WIRELESS_NL80211_H
@@ -25,7 +25,8 @@ static inline u64 wdev_id(struct wireless_dev *wdev)
int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
struct netlink_ext_ack *extack,
struct nlattr **attrs,
- struct cfg80211_chan_def *chandef);
+ struct cfg80211_chan_def *chandef,
+ bool npca_permitted);
int nl80211_parse_random_mac(struct nlattr **attrs,
u8 *mac_addr, u8 *mac_addr_mask);
diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c
index 4c8ea0583f94..93fee1f1aa76 100644
--- a/net/wireless/pmsr.c
+++ b/net/wireless/pmsr.c
@@ -238,7 +238,7 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev,
return err;
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- &out->chandef);
+ &out->chandef, false);
if (err)
return err;
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index eb5bedf9c92a..d03e58c63fd4 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -141,7 +141,9 @@
__field(u32, center_freq1) \
__field(u32, freq1_offset) \
__field(u32, center_freq2) \
- __field(u16, punctured)
+ __field(u16, punctured) \
+ __field(u32, npca_pri_freq) \
+ __field(u16, npca_punctured)
#define CHAN_DEF_ASSIGN(chandef) \
do { \
if ((chandef) && (chandef)->chan) { \
@@ -155,6 +157,11 @@
__entry->freq1_offset = (chandef)->freq1_offset;\
__entry->center_freq2 = (chandef)->center_freq2;\
__entry->punctured = (chandef)->punctured; \
+ __entry->npca_pri_freq = \
+ (chandef)->npca_chan ? \
+ (chandef)->npca_chan->center_freq : 0; \
+ __entry->npca_punctured = \
+ (chandef)->npca_punctured; \
} else { \
__entry->band = 0; \
__entry->control_freq = 0; \
@@ -164,14 +171,17 @@
__entry->freq1_offset = 0; \
__entry->center_freq2 = 0; \
__entry->punctured = 0; \
+ __entry->npca_pri_freq = 0; \
+ __entry->npca_punctured = 0; \
} \
} while (0)
#define CHAN_DEF_PR_FMT \
- "band: %d, control freq: %u.%03u, width: %d, cf1: %u.%03u, cf2: %u, punct: 0x%x"
+ "band: %d, control freq: %u.%03u, width: %d, cf1: %u.%03u, cf2: %u, punct: 0x%x, npca:%u, npca_punct:0x%x"
#define CHAN_DEF_PR_ARG __entry->band, __entry->control_freq, \
__entry->freq_offset, __entry->width, \
__entry->center_freq1, __entry->freq1_offset, \
- __entry->center_freq2, __entry->punctured
+ __entry->center_freq2, __entry->punctured, \
+ __entry->npca_pri_freq, __entry->npca_punctured
#define FILS_AAD_ASSIGN(fa) \
do { \
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 03/14] wifi: mac80211: carry element parsing frame type/from_ap
From: Johannes Berg @ 2026-04-28 9:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428092708.71740-16-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Carry the frame type and from_ap indication in the parse
result, the caller should have it, but we often pass the
resulting data structure around, so this saves passing
more parameters.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 2 ++
net/mac80211/parse.c | 3 +++
2 files changed, 5 insertions(+)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e23e347f870d..10bad5369e9d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1790,6 +1790,8 @@ struct ieee802_11_elems {
const u8 *ie_start;
size_t total_len;
u32 crc;
+ u8 frame_type;
+ bool from_ap;
/* pointers to IEs */
const struct ieee80211_tdls_lnkie *lnk_id;
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 2b3632c6008a..5e61457be0f3 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -1053,6 +1053,9 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
if (!elems_parse)
return NULL;
+ elems_parse->elems.frame_type = params->type;
+ elems_parse->elems.from_ap = params->from_ap;
+
elems_parse->scratch_len = scratch_len;
elems_parse->scratch_pos = elems_parse->scratch;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 02/14] wifi: mac80211: move ieee80211_chandef_usable() up
From: Johannes Berg @ 2026-04-28 9:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428092708.71740-16-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
For UHR DBE this is going to be needed in the AP channel
determination function, move it there.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 25c7427e11c4..0bf51774c155 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -149,6 +149,21 @@ static int ecw2cw(int ecw)
return (1 << ecw) - 1;
}
+static bool ieee80211_chandef_usable(struct ieee80211_sub_if_data *sdata,
+ const struct cfg80211_chan_def *chandef,
+ u32 prohibited_flags)
+{
+ if (!cfg80211_chandef_usable(sdata->local->hw.wiphy,
+ chandef, prohibited_flags))
+ return false;
+
+ if (chandef->punctured &&
+ ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING))
+ return false;
+
+ return true;
+}
+
struct ieee80211_determine_ap_chan_data {
/* input data */
struct ieee80211_channel *channel;
@@ -851,21 +866,6 @@ static void ieee80211_get_rates(struct ieee80211_supported_band *sband,
}
}
-static bool ieee80211_chandef_usable(struct ieee80211_sub_if_data *sdata,
- const struct cfg80211_chan_def *chandef,
- u32 prohibited_flags)
-{
- if (!cfg80211_chandef_usable(sdata->local->hw.wiphy,
- chandef, prohibited_flags))
- return false;
-
- if (chandef->punctured &&
- ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING))
- return false;
-
- return true;
-}
-
static int ieee80211_chandef_num_subchans(const struct cfg80211_chan_def *c)
{
if (c->width == NL80211_CHAN_WIDTH_80P80)
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 01/14] wifi: mac80211: use struct for ieee80211_determine_ap_chan() args
From: Johannes Berg @ 2026-04-28 9:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428092708.71740-16-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There are too many arguments, and we're going to need another one
for DBE. Collect them into a struct instead.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 57 ++++++++++++++++++++++++++++++++-------------
1 file changed, 41 insertions(+), 16 deletions(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 00b4beff0e43..25c7427e11c4 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -149,20 +149,32 @@ static int ecw2cw(int ecw)
return (1 << ecw) - 1;
}
+struct ieee80211_determine_ap_chan_data {
+ /* input data */
+ struct ieee80211_channel *channel;
+ const struct ieee802_11_elems *elems;
+ const struct ieee80211_conn_settings *conn;
+ u32 vht_cap_info;
+ bool ignore_ht_channel_mismatch;
+
+ /* target chandef is filled in */
+ struct cfg80211_chan_def *chandef;
+};
+
static enum ieee80211_conn_mode
ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_channel *channel,
- u32 vht_cap_info,
- const struct ieee802_11_elems *elems,
- bool ignore_ht_channel_mismatch,
- const struct ieee80211_conn_settings *conn,
- struct cfg80211_chan_def *chandef)
+ struct ieee80211_determine_ap_chan_data *data)
{
+ bool ignore_ht_channel_mismatch = data->ignore_ht_channel_mismatch;
+ const struct ieee802_11_elems *elems = data->elems;
const struct ieee80211_ht_operation *ht_oper = elems->ht_operation;
const struct ieee80211_vht_operation *vht_oper = elems->vht_operation;
const struct ieee80211_he_operation *he_oper = elems->he_operation;
const struct ieee80211_eht_operation *eht_oper = elems->eht_operation;
const struct ieee80211_uhr_operation *uhr_oper = elems->uhr_operation;
+ const struct ieee80211_conn_settings *conn = data->conn;
+ struct ieee80211_channel *channel = data->channel;
+ struct cfg80211_chan_def *chandef = data->chandef;
struct ieee80211_supported_band *sband =
sdata->local->hw.wiphy->bands[channel->band];
struct cfg80211_chan_def vht_chandef;
@@ -288,7 +300,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
memcpy(&he_oper_vht_cap, he_oper->optional, 3);
he_oper_vht_cap.basic_mcs_set = cpu_to_le16(0);
- if (!ieee80211_chandef_vht_oper(&sdata->local->hw, vht_cap_info,
+ if (!ieee80211_chandef_vht_oper(&sdata->local->hw, data->vht_cap_info,
&he_oper_vht_cap, ht_oper,
&vht_chandef)) {
sdata_info(sdata,
@@ -303,7 +315,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
} else if (sband->band == NL80211_BAND_2GHZ) {
no_vht = true;
} else if (!ieee80211_chandef_vht_oper(&sdata->local->hw,
- vht_cap_info,
+ data->vht_cap_info,
vht_oper, ht_oper,
&vht_chandef)) {
sdata_info(sdata,
@@ -1085,6 +1097,13 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
enum ieee80211_conn_mode ap_mode;
unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
+ struct ieee80211_determine_ap_chan_data ap_chan_data = {
+ .channel = channel,
+ .vht_cap_info = bss->vht_cap_info,
+ .ignore_ht_channel_mismatch = false,
+ .chandef = ap_chandef,
+ .conn = conn,
+ };
int ret;
again:
@@ -1093,8 +1112,8 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
if (!elems)
return ERR_PTR(-ENOMEM);
- ap_mode = ieee80211_determine_ap_chan(sdata, channel, bss->vht_cap_info,
- elems, false, conn, ap_chandef);
+ ap_chan_data.elems = elems;
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
/* this should be impossible since parsing depends on our mode */
if (WARN_ON(ap_mode > conn->mode)) {
@@ -1322,12 +1341,19 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
bool update, u64 *changed, u16 stype)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
+ struct cfg80211_chan_def ap_chandef;
+ struct ieee80211_determine_ap_chan_data ap_chan_data = {
+ .channel = channel,
+ .vht_cap_info = 0,
+ .ignore_ht_channel_mismatch = true,
+ .chandef = &ap_chandef,
+ .elems = elems,
+ .conn = &link->u.mgd.conn,
+ };
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chan_req chanreq = {};
- struct cfg80211_chan_def ap_chandef;
enum ieee80211_conn_mode ap_mode;
const char *frame;
- u32 vht_cap_info = 0;
u16 ht_opmode;
int ret;
@@ -1355,11 +1381,10 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
return 0;
if (elems->vht_cap_elem)
- vht_cap_info = le32_to_cpu(elems->vht_cap_elem->vht_cap_info);
+ ap_chan_data.vht_cap_info =
+ le32_to_cpu(elems->vht_cap_elem->vht_cap_info);
- ap_mode = ieee80211_determine_ap_chan(sdata, channel, vht_cap_info,
- elems, true, &link->u.mgd.conn,
- &ap_chandef);
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 00/14] wifi: UHR non-primary channel access
From: Johannes Berg @ 2026-04-28 9:25 UTC (permalink / raw)
To: linux-wireless
This series implements UHR NPCA (non-primary channel access) in general
(chandef etc.), for the AP side (just configuration values), and the
client (using it from assoc response.)
A few things to note:
- I know that the 802.11bn draft is likely to change and use an 8-bit
NPCA primary channel field, which changes the parsing. This will need
to be adjusted once that finalizes.
- This change will likely also come together with allowing NPCA to be
used inside the DBE rather than only inside the BSS bandwidth, but
we'll see how this will play out in the spec.
- I have, at least for now, deliberately opted to keep more patches
rather than squashing "don't parse full UHR operation from beacons"
and "separate NPCA validity from chandef validity" as I think it may
be easier to follow, but we can change that. It reflects my thought
process more than anything I guess.
- In addition to this I also have preliminary DBE (dynamic bandwidth
extension) patches, but I've opted to not include them here for now.
- For the AP side, I've only made "fake NPCA" patches that allow
advertising it with specific values in hostapd, rather than handling
the client request to enable it and updating the BSS values. I don't
really know how "real AP" vendors want to handle this, so I'm
deliberately leaving it out.
johannes
^ permalink raw reply
* Re: [PATCH] wireless-regdb: allow 320MHz channel width for Russia
From: Chen-Yu Tsai @ 2026-04-28 9:23 UTC (permalink / raw)
To: Ilya K; +Cc: Louis Kotze, Valeriy Kosikhin, wireless-regdb, linux-wireless
In-Reply-To: <b9d9d6e1-91c4-487f-82af-e53d4148c11b@0upti.me>
On Tue, Apr 28, 2026 at 5:17 PM Ilya K <me@0upti.me> wrote:
>
> On 2026-04-28 10:44, Louis Kotze wrote:
> >
> > Note that the patch base predates the recent RU power update (commit
> > 6873bfe in wens/wireless-regdb), which changed 5925-6425 from 100 mW
> > to 200 mW, so this needs a rebase on current HEAD before it can be
> > applied.
> >
> I can resend a rebased version if necessary.
Yes, please send a new version for the record.
Thanks
ChenYu
^ permalink raw reply
* Re: [PATCH wireless-next 2/2] wifi: cfg80211/mac80211: extend cfg80211_rx_assoc_resp_data() for assoc encryption
From: Kavita Kavita @ 2026-04-28 9:18 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless
In-Reply-To: <e3fa97dc1d0bc69477d3a2d2b2bfec6ff0ddff4e.camel@sipsolutions.net>
On 4/28/2026 1:08 PM, Johannes Berg wrote:
> On Mon, 2026-04-27 at 20:37 +0530, Kavita Kavita wrote:
>> Extend cfg80211_rx_assoc_resp_data with a new assoc_encrypted field to
>> indicate if the (re)association exchange is encrypted.
>>
>> Currently, when epp_peer flag is set, unprotected (Re)Association
>> Request/Response frames are dropped. This ensures that by the time
>> the (Re)Association Response is processed, the entire association
>> exchange is encrypted over the air.
>>
>> Set assoc_encrypted in cfg80211_rx_assoc_resp_data based on epp_peer
>> flag when processing the (Re)Association Response.
>
> I don't quite see how this is necessary, even in nl80211_send_rx_assoc()
> the whole frame, including header and protected bit, is available. Why
> does this need mac80211 involvement? One could ask why it's needed *at
> all* when userspace already gets the frame and should probably process
> the frame RX preferably over the connect result indication...
>
The NL80211_ATTR_ASSOC_ENCRYPTED attribute targets NL80211_CMD_CONNECT, not
NL80211_CMD_ASSOCIATE, both are sent from cfg80211_rx_assoc_resp(), but
NL80211_CMD_CONNECT carries only IEs with no MAC frame headers for either
Request or Response.
The attribute is intended to indicate that the entire exchange was encrypted,
not just the Response. For the Response frame, checking ieee80211_has_protected()
is possible since the full frame is available in data->buf, but for the Request
frame only IEs are stored in ifmgd->assoc_req_ies, the MAC header is not preserved,
so I cannot check the Protected bit for the Request.
While an unencrypted Request paired with an encrypted Response is unlikely in practice,
we did not want to leave that gap, so I used the epp_peer flag. That said, if you think
checking the Protected bit on the Response frame alone is sufficient, we are fine with
that approach too.
> If this is needed for some reason please outline it in the commit
> message, and reshuffle the code to properly split between cfg80211 and
> mac80211 in the commits.
In the wireless-next tip, there are already commits that combine both cfg80211
and mac80211 changes together, so since the assoc_encrypted field addition in
cfg80211 and the mac80211 epp_peer lookup that sets it are tightly dependent on
each other, I kept them in the same commit. If you prefer them split into two
separate commits, I can do that. Will update the commit as well.
>
> johannes
^ permalink raw reply
* Re: [PATCH wireless-next 1/2] wifi: cfg80211: indicate (Re)Association frame encryption to userspace
From: Kavita Kavita @ 2026-04-28 9:17 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless
In-Reply-To: <0fb5e4ef6fca8b099fa0614085dd8b3acb911510.camel@sipsolutions.net>
On 4/28/2026 1:09 PM, Johannes Berg wrote:
> On Mon, 2026-04-27 at 20:37 +0530, Kavita Kavita wrote:
>>
>> Mark NL80211_ATTR_ASSOC_ENCRYPTED with NLA_REJECT in the nl80211
>> policy to reject any attempt by userspace to send this attribute.
>
> This is not necessary, it's the default given
>
> [0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD },
Agreed, I missed it. I will remove it in the next version. Thank you.
>
> johannes
^ permalink raw reply
* [PATCH wireless-next 2/2] wifi: cfg80211/mac80211: set only non-MLO-applicable fields for non-MLO stations
From: Sarika Sharma @ 2026-04-28 9:09 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, Sarika Sharma
In-Reply-To: <20260428090919.1798601-1-sarika.sharma@oss.qualcomm.com>
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
* [PATCH wireless-next 1/2] wifi: cfg80211/mac80211: change memory allocation for link_sinfo structure
From: Sarika Sharma @ 2026-04-28 9:09 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, Sarika Sharma
In-Reply-To: <20260428090919.1798601-1-sarika.sharma@oss.qualcomm.com>
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
* [PATCH wireless-next 0/2] wifi: cfg80211/mac80211: optimize station info handling
From: Sarika Sharma @ 2026-04-28 9:09 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, Sarika Sharma
This series improve memory and logic efficiency in cfg80211 and
mac80211 during NL80211_CMD_GET_STATION. Allocate link_sinfo and
link tidstats objects only for valid links to reduce memory usage.
Avoid setting non-MLO applicable fields for MLO stations to
eliminate redundant operations and simplify the code path.
Sarika Sharma (2):
wifi: cfg80211/mac80211: change memory allocation for link_sinfo
structure
wifi: cfg80211/mac80211: set only non-MLO-applicable fields for
non-MLO stations
include/net/cfg80211.h | 29 ++++++++++++++++---
net/mac80211/ethtool.c | 4 +++
net/mac80211/sta_info.c | 63 ++++++++++++++++++++++++-----------------
net/wireless/nl80211.c | 35 ++++-------------------
net/wireless/util.c | 21 ++++++++++++++
5 files changed, 93 insertions(+), 59 deletions(-)
base-commit: 1f5ffc672165ff851063a5fd044b727ab2517ae3
--
2.34.1
^ permalink raw reply
* [PATCH wireless-next 5/5] wifi: mac80211: mlme: advertise driver's extended MLD capa/ops
From: Johannes Berg @ 2026-04-28 9:07 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428090915.54717-7-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
If the AP has extended MLD capa/ops we may advertise our own
from userspace. Also add the driver's in this case. This is
fine since the only one right now from the driver is UHR ML-PM
and that's only relevant if the AP already has it too.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index d24db2c2cde9..9d7afbf3700e 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -9849,7 +9849,9 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgd_assoc_data *assoc_data;
const struct element *ssid_elem;
struct ieee80211_vif_cfg *vif_cfg = &sdata->vif.cfg;
+ const struct wiphy_iftype_ext_capab *ift_ext_capa;
struct ieee80211_link_data *link;
+ u16 driver_ext_mld_capa_ops = 0;
struct cfg80211_bss *cbss;
bool override, uapsd_supported;
bool match_auth;
@@ -9888,17 +9890,26 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
else
memcpy(assoc_data->ap_addr, cbss->bssid, ETH_ALEN);
+ ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy,
+ ieee80211_vif_type_p2p(&sdata->vif));
+ if (ift_ext_capa)
+ driver_ext_mld_capa_ops = ift_ext_capa->ext_mld_capa_and_ops;
+
/*
* Many APs have broken parsing of the extended MLD capa/ops field,
* dropping (re-)association request frames or replying with association
* response with a failure status if it's present.
* Set our value from the userspace request only in strict mode or if
* the AP also had that field present.
+ * For UHR we may want to advertise ML-PM (per driver_ext_mld_capa_ops)
+ * but if the AP doesn't have it then it's pointless, and if it does
+ * then it has to have the extended MLD capa/ops field.
*/
if (ieee80211_hw_check(&local->hw, STRICT) ||
ieee80211_mgd_assoc_bss_has_mld_ext_capa_ops(req))
assoc_data->ext_mld_capa_ops =
- cpu_to_le16(req->ext_mld_capa_ops);
+ cpu_to_le16(req->ext_mld_capa_ops |
+ driver_ext_mld_capa_ops);
if (ifmgd->associated) {
u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN];
@@ -10892,11 +10903,13 @@ ieee80211_build_ml_reconf_req(struct ieee80211_sub_if_data *sdata,
int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
struct cfg80211_ml_reconf_req *req)
{
+ const struct wiphy_iftype_ext_capab *ift_ext_capa;
struct ieee80211_local *local = sdata->local;
struct ieee80211_mgd_assoc_data *data = NULL;
struct sta_info *sta;
struct sk_buff *skb;
u16 added_links, new_valid_links;
+ u16 driver_ext_mld_capa_ops = 0;
int link_id, err;
if (!ieee80211_vif_is_mld(&sdata->vif) ||
@@ -11054,6 +11067,11 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
}
}
+ ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy,
+ ieee80211_vif_type_p2p(&sdata->vif));
+ if (ift_ext_capa)
+ driver_ext_mld_capa_ops = ift_ext_capa->ext_mld_capa_and_ops;
+
/* Build the SKB before the link removal as the construction of the
* station info for removed links requires the local address.
* Invalidate the removed links, so that the transmission of the ML
@@ -11062,7 +11080,8 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
* on which the request was received.
*/
skb = ieee80211_build_ml_reconf_req(sdata, data, req->rem_links,
- cpu_to_le16(req->ext_mld_capa_ops));
+ cpu_to_le16(req->ext_mld_capa_ops |
+ driver_ext_mld_capa_ops));
if (!skb) {
err = -ENOMEM;
goto err_free;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 4/5] wifi: cfg80211: allow devices to advertise extended MLD capa/ops
From: Johannes Berg @ 2026-04-28 9:06 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428090915.54717-7-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
For UHR, multi-link power-management capability lives there, and
so it's needed that hostapd knows what to advertise, and clients
should have it shown to userspace for information.
Repurpose the existing NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS by
renaming it to NL80211_ATTR_EXT_MLD_CAPA_AND_OPS (with a define
for compatibility) and advertise the capabilities.
We can also later use the value, if needed, to set per-station
capabilities on STAs added to AP interfaces.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 2 ++
include/uapi/linux/nl80211.h | 15 +++++++++------
net/wireless/nl80211.c | 18 ++++++++++++------
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index a40ab36b8edb..7c2ddaf2bcd7 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -5855,6 +5855,7 @@ struct wiphy_vendor_command {
* @extended_capabilities_len: length of the extended capabilities
* @eml_capabilities: EML capabilities (for MLO)
* @mld_capa_and_ops: MLD capabilities and operations (for MLO)
+ * @ext_mld_capa_and_ops: Extended MLD capabilities and operations (for MLO)
*/
struct wiphy_iftype_ext_capab {
enum nl80211_iftype iftype;
@@ -5863,6 +5864,7 @@ struct wiphy_iftype_ext_capab {
u8 extended_capabilities_len;
u16 eml_capabilities;
u16 mld_capa_and_ops;
+ u16 ext_mld_capa_and_ops;
};
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 072b383d7d3c..e104943b7e3d 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2993,11 +2993,13 @@ enum nl80211_commands {
* @NL80211_ATTR_EPCS: Flag attribute indicating that EPCS is enabled for a
* station interface.
*
- * @NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS: Extended MLD capabilities and
- * operations that userspace implements to use during association/ML
- * link reconfig, currently only "BTM MLD Recommendation For Multiple
- * APs Support". Drivers may set additional flags that they support
- * in the kernel or device.
+ * @NL80211_ATTR_EXT_MLD_CAPA_AND_OPS: Extended MLD capabilities and operations.
+ * For association and link reconfiguration, indicates extra capabilities
+ * that userspace implements, currently only "BTM MLD Recommendation For
+ * Multiple APs Support".
+ * For wiphy information, additional flags that drivers will set, but
+ * this is informational only for userspace (it's not expected to set
+ * these.)
*
* @NL80211_ATTR_WIPHY_RADIO_INDEX: (int) Integer attribute denoting the index
* of the radio in interest. Internally a value of -1 is used to
@@ -3697,7 +3699,7 @@ enum nl80211_attrs {
NL80211_ATTR_MLO_RECONF_REM_LINKS,
NL80211_ATTR_EPCS,
- NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS,
+ NL80211_ATTR_EXT_MLD_CAPA_AND_OPS,
NL80211_ATTR_WIPHY_RADIO_INDEX,
@@ -3749,6 +3751,7 @@ enum nl80211_attrs {
#define NL80211_ATTR_SAE_DATA NL80211_ATTR_AUTH_DATA
#define NL80211_ATTR_CSA_C_OFF_BEACON NL80211_ATTR_CNTDWN_OFFS_BEACON
#define NL80211_ATTR_CSA_C_OFF_PRESP NL80211_ATTR_CNTDWN_OFFS_PRESP
+#define NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS NL80211_ATTR_EXT_MLD_CAPA_AND_OPS
/*
* Allow user space programs to use #ifdef on new attributes by defining them
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index cf236307cca9..6e76285c1045 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1047,7 +1047,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
NL80211_MAX_SUPP_SELECTORS),
[NL80211_ATTR_MLO_RECONF_REM_LINKS] = { .type = NLA_U16 },
[NL80211_ATTR_EPCS] = { .type = NLA_FLAG },
- [NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS] = { .type = NLA_U16 },
+ [NL80211_ATTR_EXT_MLD_CAPA_AND_OPS] = { .type = NLA_U16 },
[NL80211_ATTR_WIPHY_RADIO_INDEX] = { .type = NLA_U8 },
[NL80211_ATTR_S1G_LONG_BEACON_PERIOD] = NLA_POLICY_MIN(NLA_U8, 2),
[NL80211_ATTR_S1G_SHORT_BEACON] =
@@ -3461,6 +3461,12 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
NL80211_ATTR_MLD_CAPA_AND_OPS,
capab->mld_capa_and_ops)))
goto nla_put_failure;
+ if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO &&
+ capab->ext_mld_capa_and_ops &&
+ nla_put_u16(msg,
+ NL80211_ATTR_EXT_MLD_CAPA_AND_OPS,
+ capab->ext_mld_capa_and_ops))
+ goto nla_put_failure;
nla_nest_end(msg, nested_ext_capab);
if (state->split)
@@ -12921,9 +12927,9 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
goto free;
}
- if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS])
+ if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS])
req.ext_mld_capa_ops =
- nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]);
+ nla_get_u16(info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]);
} else {
if (req.link_id >= 0)
return -EINVAL;
@@ -12934,7 +12940,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
return PTR_ERR(req.bss);
ap_addr = req.bss->bssid;
- if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS])
+ if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS])
return -EINVAL;
}
@@ -18798,9 +18804,9 @@ static int nl80211_assoc_ml_reconf(struct sk_buff *skb, struct genl_info *info)
goto out;
}
- if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS])
+ if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS])
req.ext_mld_capa_ops =
- nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]);
+ nla_get_u16(info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]);
err = cfg80211_assoc_ml_reconf(rdev, dev, &req);
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 3/5] wifi: cfg80211: ensure UHR ML-PM flag is consistent
From: Johannes Berg @ 2026-04-28 9:06 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428090915.54717-7-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
We check that extended MLD capabilities and operations are
consistent across APs in an AP MLD, but didn't check reserved
fields since they could be defined to differ. Check bit 8 now
since it's defined by UHR to be consistent.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/mlme.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index bd72317c4964..65a39428f508 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -360,17 +360,18 @@ cfg80211_mlme_check_mlo_compat(const struct ieee80211_multi_link_elem *mle_a,
* reserved when included in a unicast Probe Response frame and may
* also change when the AP adds/removes links. The BTM MLD
* Recommendation For Multiple APs Support subfield is reserved when
- * transmitted by an AP. All other bits are currently reserved.
- * See IEEE P802.11be/D7.0, Table 9-417o.
+ * transmitted by an AP.
*/
if ((ieee80211_mle_get_ext_mld_capa_op((const u8 *)mle_a) &
(IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE |
IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE |
- IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK)) !=
+ IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK |
+ IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM)) !=
(ieee80211_mle_get_ext_mld_capa_op((const u8 *)mle_b) &
(IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE |
IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE |
- IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK))) {
+ IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK |
+ IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM))) {
NL_SET_ERR_MSG(extack,
"extended link MLD capabilities/ops mismatch");
return -EINVAL;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 2/5] wifi: mac80211: track AP's extended MLD capa/ops
From: Johannes Berg @ 2026-04-28 9:06 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428090915.54717-7-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
For UHR multi-link power management, the driver/device needs
to know if the AP supports it, to be able to use it. Track
the AP's extended MLD capabilities and operations so it does.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 3 +++
net/mac80211/mlme.c | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 02318a4be0e1..5dc4ac08606f 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2674,6 +2674,8 @@ struct ieee80211_link_sta {
* @epp_peer: indicates that the peer is an EPP peer.
* @nmi: For NDI stations, pointer to the NMI station of the peer.
* @nan_sched: NAN peer schedule for this station. Valid only for NMI stations.
+ * @ext_mld_capa_ops: the MLD's extended MLD capabilities and operations
+ * NOTE: currently only tracked for AP STAs
*/
struct ieee80211_sta {
u8 addr[ETH_ALEN] __aligned(2);
@@ -2698,6 +2700,7 @@ struct ieee80211_sta {
struct ieee80211_txq *txq[IEEE80211_NUM_TIDS + 1];
u16 valid_links;
+ u16 ext_mld_capa_ops;
bool epp_peer;
struct ieee80211_link_sta deflink;
struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 00b4beff0e43..d24db2c2cde9 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -6462,6 +6462,12 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
sta->sta.spp_amsdu = assoc_data->spp_amsdu;
if (ieee80211_vif_is_mld(&sdata->vif)) {
+ if (!elems->ml_basic)
+ goto out_err;
+
+ sta->sta.ext_mld_capa_ops =
+ ieee80211_mle_get_ext_mld_capa_op((const void *)elems->ml_basic);
+
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
if (!assoc_data->link[link_id].bss)
continue;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 1/5] wifi: ieee80211: define UHR ML-PM extended MLD capability
From: Johannes Berg @ 2026-04-28 9:06 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260428090915.54717-7-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
UHR defines bit 8 to mean multi-link power management, add
a definition for it. Also reindent the other definitions to
use tabs, not spaces.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-eht.h | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index 335e78bd4b5d..87d92fb86fab 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -9,7 +9,7 @@
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (c) 2018 - 2025 Intel Corporation
+ * Copyright (c) 2018 - 2026 Intel Corporation
*/
#ifndef LINUX_IEEE80211_EHT_H
@@ -750,11 +750,13 @@ static inline u16 ieee80211_mle_get_mld_capa_op(const u8 *data)
}
/* Defined in Figure 9-1074t in P802.11be_D7.0 */
-#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE 0x0001
-#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_RECO_MAX_LINKS_MASK 0x001e
-#define IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE 0x0020
-#define IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK 0x0040
-#define IEEE80211_EHT_ML_EXT_MLD_CAPA_BTM_MLD_RECO_MULTI_AP 0x0080
+#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE 0x0001
+#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_RECO_MAX_LINKS_MASK 0x001e
+#define IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE 0x0020
+#define IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK 0x0040
+#define IEEE80211_EHT_ML_EXT_MLD_CAPA_BTM_MLD_RECO_MULTI_AP 0x0080
+/* defined by UHR Draft P802.11bn_D1.3 Figure 9-1147 */
+#define IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM 0x0100
/**
* ieee80211_mle_get_ext_mld_capa_op - returns the extended MLD capabilities
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 0/5] wifi: UHR extended MLD capa/ops handling
From: Johannes Berg @ 2026-04-28 9:06 UTC (permalink / raw)
To: linux-wireless
This series adds handling for newly defined extended MLD capabilities
and operations from UHR.
johannes
^ permalink raw reply
* [PATCH wireless-next] wifi: mac80211: update UHR capabilities field order
From: Johannes Berg @ 2026-04-28 8:36 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
Since 802.11bn D1.4 the DBE capabilities are after the
PHY capabilities, not between MAC and PHY, adjust the
code accordingly.
Also add a struct for DBE capabilities and use it for
checking the correct length instead of hard-coding the
lengths.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
---
include/linux/ieee80211-uhr.h | 55 +++++++++++++----------------------
net/mac80211/main.c | 3 +-
net/mac80211/mlme.c | 3 +-
net/mac80211/uhr.c | 5 +---
net/mac80211/util.c | 3 +-
5 files changed, 25 insertions(+), 44 deletions(-)
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index d199f3ebdba0..f4f4bd8256df 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -372,6 +372,12 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper)
#define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES 0x08
#define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES 0x10
+struct ieee80211_uhr_cap_dbe {
+ u8 cap;
+ /* present 0, 1 or 2 times depending on _PRES bits */
+ struct ieee80211_eht_mcs_nss_supp_bw eht_mcs_map[];
+} __packed;
+
/**
* enum ieee80211_uhr_dbe_max_supported_bw - DBE Maximum Supported Bandwidth
*
@@ -394,12 +400,6 @@ struct ieee80211_uhr_cap_mac {
u8 mac_cap[5];
} __packed;
-struct ieee80211_uhr_cap {
- struct ieee80211_uhr_cap_mac mac;
- /* DBE, PHY capabilities */
- u8 variable[];
-} __packed;
-
#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x01
#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x02
#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x04
@@ -413,11 +413,18 @@ struct ieee80211_uhr_cap_phy {
u8 cap;
} __packed;
+struct ieee80211_uhr_cap {
+ struct ieee80211_uhr_cap_mac mac;
+ struct ieee80211_uhr_cap_phy phy;
+ /* optional DBE capabilities */
+ u8 variable[];
+} __packed;
+
static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len,
bool from_ap)
{
const struct ieee80211_uhr_cap *cap = (const void *)data;
- size_t needed = sizeof(*cap) + sizeof(struct ieee80211_uhr_cap_phy);
+ size_t needed = sizeof(*cap);
if (len < needed)
return false;
@@ -427,44 +434,24 @@ static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len,
* in the UHR MAC Capabilities Information field.
*/
if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
- u8 dbe;
+ const struct ieee80211_uhr_cap_dbe *dbe;
- needed += 1;
+ needed += sizeof(struct ieee80211_uhr_cap_dbe);
if (len < needed)
return false;
- dbe = cap->variable[0];
+ dbe = (const void *)cap->variable;
- if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
- needed += 3;
+ if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ needed += sizeof(dbe->eht_mcs_map[0]);
- if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
- needed += 3;
+ if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ needed += sizeof(dbe->eht_mcs_map[0]);
}
return len >= needed;
}
-static inline const struct ieee80211_uhr_cap_phy *
-ieee80211_uhr_phy_cap(const struct ieee80211_uhr_cap *cap, bool from_ap)
-{
- u8 offs = 0;
-
- if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
- u8 dbe = cap->variable[0];
-
- offs += 1;
-
- if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
- offs += 3;
-
- if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
- offs += 3;
- }
-
- return (const void *)&cap->variable[offs];
-}
-
#define IEEE80211_SMD_INFO_CAPA_DL_DATA_FWD 0x01
#define IEEE80211_SMD_INFO_CAPA_MAX_NUM_PREP 0x0E
#define IEEE80211_SMD_INFO_CAPA_TYPE 0x10
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index f47dd58770ad..21f7fa0cb60e 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1459,8 +1459,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (supp_uhr)
local->scan_ies_len +=
- 3 + sizeof(struct ieee80211_uhr_cap) +
- sizeof(struct ieee80211_uhr_cap_phy);
+ 3 + sizeof(struct ieee80211_uhr_cap);
if (!local->ops->hw_scan) {
/* For hw_scan, driver needs to set these up. */
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 05e9644d4917..6693e7e04da2 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -2225,8 +2225,7 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata,
sizeof(struct ieee80211_eht_mcs_nss_supp) +
IEEE80211_EHT_PPE_THRES_MAX_LEN;
- size += 2 + 1 + sizeof(struct ieee80211_uhr_cap) +
- sizeof(struct ieee80211_uhr_cap_phy);
+ size += 2 + 1 + sizeof(struct ieee80211_uhr_cap);
return size;
}
diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c
index 2d8f5e5480ef..ca3b471b7981 100644
--- a/net/mac80211/uhr.c
+++ b/net/mac80211/uhr.c
@@ -15,7 +15,6 @@ ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta)
{
struct ieee80211_sta_uhr_cap *sta_uhr_cap = &link_sta->pub->uhr_cap;
- bool from_ap;
memset(sta_uhr_cap, 0, sizeof(*sta_uhr_cap));
@@ -23,8 +22,6 @@ ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
return;
sta_uhr_cap->has_uhr = true;
-
sta_uhr_cap->mac = uhr_cap->mac;
- from_ap = sdata->vif.type == NL80211_IFTYPE_STATION;
- sta_uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_cap, from_ap);
+ sta_uhr_cap->phy = uhr_cap->phy;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 89e82d34ae48..2cc058766c1c 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -4640,8 +4640,7 @@ int ieee80211_put_uhr_cap(struct sk_buff *skb,
if (!uhr_cap)
return 0;
- len = 2 + 1 + sizeof(struct ieee80211_uhr_cap) +
- sizeof(struct ieee80211_uhr_cap_phy);
+ len = 2 + 1 + sizeof(struct ieee80211_uhr_cap);
if (skb_tailroom(skb) < len)
return -ENOBUFS;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next] wifi: mac80211: move frame RX handling to type files
From: Johannes Berg @ 2026-04-28 8:36 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The interface frame queue processing code currently
differentiates many frame types and below those the
interface types, but then falls through to per-type
processing. Move this code to the per-type functions,
creating a new ap.c file that gets relevant code, and
this way reduce the number of non-static symbols.
This also helps with adding more cases for UHR later.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/Makefile | 2 +-
net/mac80211/ap.c | 207 ++++++++++++++++++++++++
net/mac80211/eht.c | 174 ---------------------
net/mac80211/ieee80211_i.h | 23 +--
net/mac80211/iface.c | 49 +-----
net/mac80211/mlme.c | 312 +++++++++++++++++++++----------------
6 files changed, 394 insertions(+), 373 deletions(-)
create mode 100644 net/mac80211/ap.c
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index 20c3135b73ea..36f1e3e2222b 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -30,7 +30,7 @@ mac80211-y := \
key.o \
util.o parse.o \
wme.o \
- chan.o \
+ chan.o ap.o \
trace.o mlme.o \
tdls.o \
ocb.o \
diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c
new file mode 100644
index 000000000000..6c7d2d51a372
--- /dev/null
+++ b/net/mac80211/ap.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AP handling
+ *
+ * Partially
+ * Copyright (C) 2026 Intel Corporation
+ */
+
+#include "driver-ops.h"
+#include "ieee80211_i.h"
+
+static void
+ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *req, int opt_len)
+{
+ int len = IEEE80211_MIN_ACTION_SIZE(eml_omn);
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+
+ len += opt_len; /* optional len */
+ skb = dev_alloc_skb(local->tx_headroom + len);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, local->tx_headroom);
+ mgmt = skb_put_zero(skb, len);
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, req->sa, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+ mgmt->u.action.action_code =
+ WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF;
+ mgmt->u.action.eml_omn.dialog_token =
+ req->u.action.eml_omn.dialog_token;
+ mgmt->u.action.eml_omn.control = req->u.action.eml_omn.control &
+ ~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
+ IEEE80211_EML_CTRL_INDEV_COEX_ACT);
+ /* Copy optional fields from the received notification frame */
+ memcpy(mgmt->u.action.eml_omn.variable,
+ req->u.action.eml_omn.variable, opt_len);
+
+ ieee80211_tx_skb(sdata, skb);
+}
+
+static void
+ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ int len = IEEE80211_MIN_ACTION_SIZE(eml_omn);
+ enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ const struct wiphy_iftype_ext_capab *ift_ext_capa;
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ struct ieee80211_local *local = sdata->local;
+ u8 control = mgmt->u.action.eml_omn.control;
+ u8 *ptr = mgmt->u.action.eml_omn.variable;
+ struct ieee80211_eml_params eml_params = {
+ .link_id = status->link_id,
+ .control = control,
+ };
+ struct sta_info *sta;
+ int opt_len = 0;
+
+ if (!ieee80211_vif_is_mld(&sdata->vif))
+ return;
+
+ /* eMLSR and eMLMR can't be enabled at the same time */
+ if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
+ (control & IEEE80211_EML_CTRL_EMLMR_MODE))
+ return;
+
+ if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
+ (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
+ return;
+
+ ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
+ if (!ift_ext_capa)
+ return;
+
+ if (!status->link_valid)
+ return;
+
+ sta = sta_info_get_bss(sdata, mgmt->sa);
+ if (!sta)
+ return;
+
+ if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+ u8 emlsr_param_update_len;
+
+ if (!(ift_ext_capa->eml_capabilities &
+ IEEE80211_EML_CAP_EMLSR_SUPP))
+ return;
+
+ opt_len += sizeof(__le16); /* eMLSR link_bitmap */
+ /*
+ * eMLSR param update field is not part of Notification frame
+ * sent by the AP to client so account it separately.
+ */
+ emlsr_param_update_len =
+ !!(control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE);
+
+ if (skb->len < len + opt_len + emlsr_param_update_len)
+ return;
+
+ if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
+ u8 pad_delay, trans_delay;
+
+ pad_delay = u8_get_bits(ptr[2],
+ IEEE80211_EML_EMLSR_PAD_DELAY);
+ if (pad_delay >
+ IEEE80211_EML_CAP_EML_PADDING_DELAY_256US)
+ return;
+
+ trans_delay = u8_get_bits(ptr[2],
+ IEEE80211_EML_EMLSR_TRANS_DELAY);
+ if (trans_delay >
+ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
+ return;
+
+ /* Update sta padding and transition delay */
+ sta->sta.eml_cap =
+ u8_replace_bits(sta->sta.eml_cap,
+ pad_delay,
+ IEEE80211_EML_CAP_EML_PADDING_DELAY);
+ sta->sta.eml_cap =
+ u8_replace_bits(sta->sta.eml_cap,
+ trans_delay,
+ IEEE80211_EML_CAP_EML_TRANSITION_DELAY);
+ }
+ }
+
+ if (control & IEEE80211_EML_CTRL_EMLMR_MODE) {
+ u8 mcs_map_size;
+ int i;
+
+ if (!(ift_ext_capa->eml_capabilities &
+ IEEE80211_EML_CAP_EMLMR_SUPPORT))
+ return;
+
+ opt_len += sizeof(__le16); /* eMLMR link_bitmap */
+ opt_len++; /* eMLMR mcs_map_count */
+ if (skb->len < len + opt_len)
+ return;
+
+ eml_params.emlmr_mcs_map_count = ptr[2];
+ if (eml_params.emlmr_mcs_map_count > 2)
+ return;
+
+ mcs_map_size = 3 * (1 + eml_params.emlmr_mcs_map_count);
+ opt_len += mcs_map_size;
+ if (skb->len < len + opt_len)
+ return;
+
+ for (i = 0; i < mcs_map_size; i++) {
+ u8 rx_mcs, tx_mcs;
+
+ rx_mcs = u8_get_bits(ptr[3 + i],
+ IEEE80211_EML_EMLMR_RX_MCS_MAP);
+ if (rx_mcs > 8)
+ return;
+
+ tx_mcs = u8_get_bits(ptr[3 + i],
+ IEEE80211_EML_EMLMR_TX_MCS_MAP);
+ if (tx_mcs > 8)
+ return;
+ }
+
+ memcpy(eml_params.emlmr_mcs_map_bw, &ptr[3], mcs_map_size);
+ }
+
+ if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) ||
+ (control & IEEE80211_EML_CTRL_EMLMR_MODE)) {
+ eml_params.link_bitmap = get_unaligned_le16(ptr);
+ if ((eml_params.link_bitmap & sdata->vif.active_links) !=
+ eml_params.link_bitmap)
+ return;
+ }
+
+ if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params))
+ return;
+
+ ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
+}
+
+void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+
+ /* rx.c cannot queue any non-action frames to AP interfaces */
+ if (WARN_ON(!ieee80211_is_action(mgmt->frame_control)))
+ return;
+
+ switch (mgmt->u.action.category) {
+ case WLAN_CATEGORY_PROTECTED_EHT:
+ switch (mgmt->u.action.action_code) {
+ case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+ ieee80211_rx_eml_op_mode_notif(sdata, skb);
+ break;
+ }
+ break;
+ }
+}
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index 3fb469ef27b7..23fe82df6abf 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -100,177 +100,3 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
}
-
-static void
-ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *req, int opt_len)
-{
- int len = IEEE80211_MIN_ACTION_SIZE(eml_omn);
- struct ieee80211_local *local = sdata->local;
- struct ieee80211_mgmt *mgmt;
- struct sk_buff *skb;
-
- len += opt_len; /* optional len */
- skb = dev_alloc_skb(local->tx_headroom + len);
- if (!skb)
- return;
-
- skb_reserve(skb, local->tx_headroom);
- mgmt = skb_put_zero(skb, len);
- mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
- IEEE80211_STYPE_ACTION);
- memcpy(mgmt->da, req->sa, ETH_ALEN);
- memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
- memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
-
- mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
- mgmt->u.action.action_code = WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF;
- mgmt->u.action.eml_omn.dialog_token =
- req->u.action.eml_omn.dialog_token;
- mgmt->u.action.eml_omn.control = req->u.action.eml_omn.control &
- ~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
- IEEE80211_EML_CTRL_INDEV_COEX_ACT);
- /* Copy optional fields from the received notification frame */
- memcpy(mgmt->u.action.eml_omn.variable,
- req->u.action.eml_omn.variable, opt_len);
-
- ieee80211_tx_skb(sdata, skb);
-}
-
-void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb)
-{
- int len = IEEE80211_MIN_ACTION_SIZE(eml_omn);
- enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
- const struct wiphy_iftype_ext_capab *ift_ext_capa;
- struct ieee80211_mgmt *mgmt = (void *)skb->data;
- struct ieee80211_local *local = sdata->local;
- u8 control = mgmt->u.action.eml_omn.control;
- u8 *ptr = mgmt->u.action.eml_omn.variable;
- struct ieee80211_eml_params eml_params = {
- .link_id = status->link_id,
- .control = control,
- };
- struct sta_info *sta;
- int opt_len = 0;
-
- if (!ieee80211_vif_is_mld(&sdata->vif))
- return;
-
- /* eMLSR and eMLMR can't be enabled at the same time */
- if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
- (control & IEEE80211_EML_CTRL_EMLMR_MODE))
- return;
-
- if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
- (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
- return;
-
- ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
- if (!ift_ext_capa)
- return;
-
- if (!status->link_valid)
- return;
-
- sta = sta_info_get_bss(sdata, mgmt->sa);
- if (!sta)
- return;
-
- if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
- u8 emlsr_param_update_len;
-
- if (!(ift_ext_capa->eml_capabilities &
- IEEE80211_EML_CAP_EMLSR_SUPP))
- return;
-
- opt_len += sizeof(__le16); /* eMLSR link_bitmap */
- /* eMLSR param update field is not part of Notfication frame
- * sent by the AP to client so account it separately.
- */
- emlsr_param_update_len =
- !!(control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE);
-
- if (skb->len < len + opt_len + emlsr_param_update_len)
- return;
-
- if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
- u8 pad_delay, trans_delay;
-
- pad_delay = u8_get_bits(ptr[2],
- IEEE80211_EML_EMLSR_PAD_DELAY);
- if (pad_delay >
- IEEE80211_EML_CAP_EML_PADDING_DELAY_256US)
- return;
-
- trans_delay = u8_get_bits(ptr[2],
- IEEE80211_EML_EMLSR_TRANS_DELAY);
- if (trans_delay >
- IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
- return;
-
- /* Update sta padding and transition delay */
- sta->sta.eml_cap =
- u8_replace_bits(sta->sta.eml_cap,
- pad_delay,
- IEEE80211_EML_CAP_EML_PADDING_DELAY);
- sta->sta.eml_cap =
- u8_replace_bits(sta->sta.eml_cap,
- trans_delay,
- IEEE80211_EML_CAP_EML_TRANSITION_DELAY);
- }
- }
-
- if (control & IEEE80211_EML_CTRL_EMLMR_MODE) {
- u8 mcs_map_size;
- int i;
-
- if (!(ift_ext_capa->eml_capabilities &
- IEEE80211_EML_CAP_EMLMR_SUPPORT))
- return;
-
- opt_len += sizeof(__le16); /* eMLMR link_bitmap */
- opt_len++; /* eMLMR mcs_map_count */
- if (skb->len < len + opt_len)
- return;
-
- eml_params.emlmr_mcs_map_count = ptr[2];
- if (eml_params.emlmr_mcs_map_count > 2)
- return;
-
- mcs_map_size = 3 * (1 + eml_params.emlmr_mcs_map_count);
- opt_len += mcs_map_size;
- if (skb->len < len + opt_len)
- return;
-
- for (i = 0; i < mcs_map_size; i++) {
- u8 rx_mcs, tx_mcs;
-
- rx_mcs = u8_get_bits(ptr[3 + i],
- IEEE80211_EML_EMLMR_RX_MCS_MAP);
- if (rx_mcs > 8)
- return;
-
- tx_mcs = u8_get_bits(ptr[3 + i],
- IEEE80211_EML_EMLMR_TX_MCS_MAP);
- if (tx_mcs > 8)
- return;
- }
-
- memcpy(eml_params.emlmr_mcs_map_bw, &ptr[3], mcs_map_size);
- }
-
- if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) ||
- (control & IEEE80211_EML_CTRL_EMLMR_MODE)) {
- eml_params.link_bitmap = get_unaligned_le16(ptr);
- if ((eml_params.link_bitmap & sdata->vif.active_links) !=
- eml_params.link_bitmap)
- return;
- }
-
- if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params))
- return;
-
- ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
-}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e23e347f870d..89214b37edf1 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1973,6 +1973,10 @@ void ieee80211_clear_fast_rx(struct sta_info *sta);
bool ieee80211_is_our_addr(struct ieee80211_sub_if_data *sdata,
const u8 *addr, int *out_link_id);
+/* AP code */
+void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
+
/* STA code */
void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata);
int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
@@ -1988,10 +1992,8 @@ void ieee80211_send_pspoll(struct ieee80211_local *local,
void ieee80211_recalc_ps(struct ieee80211_local *local);
void ieee80211_recalc_ps_vif(struct ieee80211_sub_if_data *sdata);
void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata);
-void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb);
-void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb);
+void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
void ieee80211_sta_reset_beacon_monitor(struct ieee80211_sub_if_data *sdata);
void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata);
void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata);
@@ -2877,8 +2879,6 @@ void ieee80211_destroy_frag_cache(struct ieee80211_fragment_cache *cache);
u8 ieee80211_ie_len_eht_cap(struct ieee80211_sub_if_data *sdata);
-void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb);
void
ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
@@ -2886,28 +2886,17 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_eht_cap_elem *eht_cap_ie_elem,
u8 eht_cap_len,
struct link_sta_info *link_sta);
-void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len);
-void ieee80211_process_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len);
int ieee80211_req_neg_ttlm(struct ieee80211_sub_if_data *sdata,
struct cfg80211_ttlm_params *params);
-void ieee80211_process_ttlm_teardown(struct ieee80211_sub_if_data *sdata);
void ieee80211_check_wbrf_support(struct ieee80211_local *local);
void ieee80211_add_wbrf(struct ieee80211_local *local, struct cfg80211_chan_def *chandef);
void ieee80211_remove_wbrf(struct ieee80211_local *local, struct cfg80211_chan_def *chandef);
int ieee80211_mgd_set_epcs(struct ieee80211_sub_if_data *sdata, bool enable);
-void ieee80211_process_epcs_ena_resp(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len);
-void ieee80211_process_epcs_teardown(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len);
int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
struct cfg80211_ml_reconf_req *req);
-void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len);
void ieee80211_stop_mbssid(struct ieee80211_sub_if_data *sdata);
void
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 95b779c4d627..e7131fb86c12 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1713,50 +1713,6 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
default:
break;
}
- } else if (ieee80211_is_action(mgmt->frame_control) &&
- mgmt->u.action.category == WLAN_CATEGORY_PROTECTED_EHT) {
- if (sdata->vif.type == NL80211_IFTYPE_AP) {
- switch (mgmt->u.action.action_code) {
- case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
- ieee80211_rx_eml_op_mode_notif(sdata, skb);
- break;
- default:
- break;
- }
- } else if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- switch (mgmt->u.action.action_code) {
- case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ:
- ieee80211_process_neg_ttlm_req(sdata, mgmt,
- skb->len);
- break;
- case WLAN_PROTECTED_EHT_ACTION_TTLM_RES:
- ieee80211_process_neg_ttlm_res(sdata, mgmt,
- skb->len);
- break;
- case WLAN_PROTECTED_EHT_ACTION_TTLM_TEARDOWN:
- ieee80211_process_ttlm_teardown(sdata);
- break;
- case WLAN_PROTECTED_EHT_ACTION_LINK_RECONFIG_RESP:
- ieee80211_process_ml_reconf_resp(sdata, mgmt,
- skb->len);
- break;
- case WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_RESP:
- ieee80211_process_epcs_ena_resp(sdata, mgmt,
- skb->len);
- break;
- case WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_TEARDOWN:
- ieee80211_process_epcs_teardown(sdata, mgmt,
- skb->len);
- break;
- default:
- break;
- }
- }
- } else if (ieee80211_is_ext(mgmt->frame_control)) {
- if (sdata->vif.type == NL80211_IFTYPE_STATION)
- ieee80211_sta_rx_queued_ext(sdata, skb);
- else
- WARN_ON(1);
} else if (ieee80211_is_data_qos(mgmt->frame_control)) {
struct ieee80211_hdr *hdr = (void *)mgmt;
struct sta_info *sta;
@@ -1788,8 +1744,11 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
true);
}
} else switch (sdata->vif.type) {
+ case NL80211_IFTYPE_AP:
+ ieee80211_ap_rx_queued_frame(sdata, skb);
+ break;
case NL80211_IFTYPE_STATION:
- ieee80211_sta_rx_queued_mgmt(sdata, skb);
+ ieee80211_sta_rx_queued_frame(sdata, skb);
break;
case NL80211_IFTYPE_ADHOC:
ieee80211_ibss_rx_queued_mgmt(sdata, skb);
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 00b4beff0e43..05e9644d4917 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -8172,8 +8172,9 @@ ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata,
return 0;
}
-void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len)
+static void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
{
u8 dialog_token, direction[IEEE80211_TTLM_MAX_CNT] = {}, i;
size_t ies_len;
@@ -8235,8 +8236,9 @@ void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata,
ieee80211_send_neg_ttlm_res(sdata, ttlm_res, dialog_token, &neg_ttlm);
}
-void ieee80211_process_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len)
+static void ieee80211_process_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
{
if (!ieee80211_vif_is_mld(&sdata->vif) ||
mgmt->u.action.ttlm_res.dialog_token != sdata->u.mgd.dialog_token_alloc)
@@ -8257,7 +8259,7 @@ void ieee80211_process_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
__ieee80211_disconnect(sdata);
}
-void ieee80211_process_ttlm_teardown(struct ieee80211_sub_if_data *sdata)
+static void ieee80211_process_ttlm_teardown(struct ieee80211_sub_if_data *sdata)
{
u16 new_dormant_links;
@@ -8315,8 +8317,8 @@ void ieee80211_send_teardown_neg_ttlm(struct ieee80211_vif *vif)
}
EXPORT_SYMBOL(ieee80211_send_teardown_neg_ttlm);
-void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb)
+static void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
{
struct ieee80211_link_data *link = &sdata->deflink;
struct ieee80211_rx_status *rx_status;
@@ -8336,129 +8338,6 @@ void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata,
}
}
-void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb)
-{
- struct ieee80211_link_data *link = &sdata->deflink;
- struct ieee80211_rx_status *rx_status;
- struct ieee802_11_elems *elems;
- struct ieee80211_mgmt *mgmt;
- u16 fc;
- int ies_len;
-
- lockdep_assert_wiphy(sdata->local->hw.wiphy);
-
- rx_status = (struct ieee80211_rx_status *) skb->cb;
- mgmt = (struct ieee80211_mgmt *) skb->data;
- fc = le16_to_cpu(mgmt->frame_control);
-
- if (rx_status->link_valid) {
- link = sdata_dereference(sdata->link[rx_status->link_id],
- sdata);
- if (!link)
- return;
- }
-
- switch (fc & IEEE80211_FCTL_STYPE) {
- case IEEE80211_STYPE_BEACON:
- ieee80211_rx_mgmt_beacon(link, (void *)mgmt,
- skb->len, rx_status);
- break;
- case IEEE80211_STYPE_PROBE_RESP:
- ieee80211_rx_mgmt_probe_resp(link, skb);
- break;
- case IEEE80211_STYPE_AUTH:
- ieee80211_rx_mgmt_auth(sdata, mgmt, skb->len);
- break;
- case IEEE80211_STYPE_DEAUTH:
- ieee80211_rx_mgmt_deauth(sdata, mgmt, skb->len);
- break;
- case IEEE80211_STYPE_DISASSOC:
- ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len);
- break;
- case IEEE80211_STYPE_ASSOC_RESP:
- case IEEE80211_STYPE_REASSOC_RESP:
- ieee80211_rx_mgmt_assoc_resp(sdata, mgmt, skb->len);
- break;
- case IEEE80211_STYPE_ACTION:
- if (!sdata->u.mgd.associated ||
- !ether_addr_equal(mgmt->bssid, sdata->vif.cfg.ap_addr))
- break;
-
- switch (mgmt->u.action.category) {
- case WLAN_CATEGORY_SPECTRUM_MGMT:
- ies_len = skb->len -
- offsetof(struct ieee80211_mgmt,
- u.action.chan_switch.variable);
-
- if (ies_len < 0)
- break;
-
- /* CSA IE cannot be overridden, no need for BSSID */
- elems = ieee802_11_parse_elems(mgmt->u.action.chan_switch.variable,
- ies_len,
- IEEE80211_FTYPE_MGMT |
- IEEE80211_STYPE_ACTION,
- NULL);
-
- if (elems && !elems->parse_error) {
- enum ieee80211_csa_source src =
- IEEE80211_CSA_SOURCE_PROT_ACTION;
-
- ieee80211_sta_process_chanswitch(link,
- rx_status->mactime,
- rx_status->device_timestamp,
- elems, elems,
- src);
- }
- kfree(elems);
- break;
- case WLAN_CATEGORY_PUBLIC:
- case WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION:
- ies_len = skb->len -
- offsetof(struct ieee80211_mgmt,
- u.action.ext_chan_switch.variable);
-
- if (ies_len < 0)
- break;
-
- /*
- * extended CSA IE can't be overridden, no need for
- * BSSID
- */
- elems = ieee802_11_parse_elems(mgmt->u.action.ext_chan_switch.variable,
- ies_len,
- IEEE80211_FTYPE_MGMT |
- IEEE80211_STYPE_ACTION,
- NULL);
-
- if (elems && !elems->parse_error) {
- enum ieee80211_csa_source src;
-
- if (mgmt->u.action.category ==
- WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION)
- src = IEEE80211_CSA_SOURCE_PROT_ACTION;
- else
- src = IEEE80211_CSA_SOURCE_UNPROT_ACTION;
-
- /* for the handling code pretend it was an IE */
- elems->ext_chansw_ie =
- &mgmt->u.action.ext_chan_switch.data;
-
- ieee80211_sta_process_chanswitch(link,
- rx_status->mactime,
- rx_status->device_timestamp,
- elems, elems,
- src);
- }
-
- kfree(elems);
- break;
- }
- break;
- }
-}
-
static void ieee80211_sta_timer(struct timer_list *t)
{
struct ieee80211_sub_if_data *sdata =
@@ -10423,8 +10302,9 @@ void ieee80211_disable_rssi_reports(struct ieee80211_vif *vif)
}
EXPORT_SYMBOL(ieee80211_disable_rssi_reports);
-void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len)
+static void
+ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt, size_t len)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
@@ -11247,8 +11127,9 @@ static void ieee80211_ml_epcs(struct ieee80211_sub_if_data *sdata,
}
}
-void ieee80211_process_epcs_ena_resp(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len)
+static void ieee80211_process_epcs_ena_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
{
struct ieee802_11_elems *elems __free(kfree) = NULL;
size_t ies_len;
@@ -11295,8 +11176,9 @@ void ieee80211_process_epcs_ena_resp(struct ieee80211_sub_if_data *sdata,
ieee80211_epcs_changed(sdata, true);
}
-void ieee80211_process_epcs_teardown(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len)
+static void ieee80211_process_epcs_teardown(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
{
if (!ieee80211_vif_is_mld(&sdata->vif) ||
!sdata->u.mgd.epcs.enabled)
@@ -11305,3 +11187,161 @@ void ieee80211_process_epcs_teardown(struct ieee80211_sub_if_data *sdata,
ieee80211_epcs_teardown(sdata);
ieee80211_epcs_changed(sdata, false);
}
+
+void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_link_data *link = &sdata->deflink;
+ struct ieee80211_rx_status *rx_status;
+ struct ieee802_11_elems *elems;
+ struct ieee80211_mgmt *mgmt;
+ u16 fc;
+ int ies_len;
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ mgmt = (struct ieee80211_mgmt *) skb->data;
+
+ if (ieee80211_is_ext(mgmt->frame_control)) {
+ ieee80211_sta_rx_queued_ext(sdata, skb);
+ return;
+ }
+
+ rx_status = (struct ieee80211_rx_status *) skb->cb;
+ fc = le16_to_cpu(mgmt->frame_control);
+
+ if (rx_status->link_valid) {
+ link = sdata_dereference(sdata->link[rx_status->link_id],
+ sdata);
+ if (!link)
+ return;
+ }
+
+ switch (fc & IEEE80211_FCTL_STYPE) {
+ case IEEE80211_STYPE_BEACON:
+ ieee80211_rx_mgmt_beacon(link, (void *)mgmt,
+ skb->len, rx_status);
+ break;
+ case IEEE80211_STYPE_PROBE_RESP:
+ ieee80211_rx_mgmt_probe_resp(link, skb);
+ break;
+ case IEEE80211_STYPE_AUTH:
+ ieee80211_rx_mgmt_auth(sdata, mgmt, skb->len);
+ break;
+ case IEEE80211_STYPE_DEAUTH:
+ ieee80211_rx_mgmt_deauth(sdata, mgmt, skb->len);
+ break;
+ case IEEE80211_STYPE_DISASSOC:
+ ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len);
+ break;
+ case IEEE80211_STYPE_ASSOC_RESP:
+ case IEEE80211_STYPE_REASSOC_RESP:
+ ieee80211_rx_mgmt_assoc_resp(sdata, mgmt, skb->len);
+ break;
+ case IEEE80211_STYPE_ACTION:
+ if (!sdata->u.mgd.associated ||
+ !ether_addr_equal(mgmt->bssid, sdata->vif.cfg.ap_addr))
+ break;
+
+ switch (mgmt->u.action.category) {
+ case WLAN_CATEGORY_SPECTRUM_MGMT:
+ ies_len = skb->len -
+ offsetof(struct ieee80211_mgmt,
+ u.action.chan_switch.variable);
+
+ if (ies_len < 0)
+ break;
+
+ /* CSA IE cannot be overridden, no need for BSSID */
+ elems = ieee802_11_parse_elems(mgmt->u.action.chan_switch.variable,
+ ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
+
+ if (elems && !elems->parse_error) {
+ enum ieee80211_csa_source src =
+ IEEE80211_CSA_SOURCE_PROT_ACTION;
+
+ ieee80211_sta_process_chanswitch(link,
+ rx_status->mactime,
+ rx_status->device_timestamp,
+ elems, elems,
+ src);
+ }
+ kfree(elems);
+ break;
+ case WLAN_CATEGORY_PUBLIC:
+ case WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION:
+ ies_len = skb->len -
+ offsetof(struct ieee80211_mgmt,
+ u.action.ext_chan_switch.variable);
+
+ if (ies_len < 0)
+ break;
+
+ /*
+ * extended CSA IE can't be overridden, no need for
+ * BSSID
+ */
+ elems = ieee802_11_parse_elems(mgmt->u.action.ext_chan_switch.variable,
+ ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
+
+ if (elems && !elems->parse_error) {
+ enum ieee80211_csa_source src;
+
+ if (mgmt->u.action.category ==
+ WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION)
+ src = IEEE80211_CSA_SOURCE_PROT_ACTION;
+ else
+ src = IEEE80211_CSA_SOURCE_UNPROT_ACTION;
+
+ /* for the handling code pretend it was an IE */
+ elems->ext_chansw_ie =
+ &mgmt->u.action.ext_chan_switch.data;
+
+ ieee80211_sta_process_chanswitch(link,
+ rx_status->mactime,
+ rx_status->device_timestamp,
+ elems, elems,
+ src);
+ }
+
+ kfree(elems);
+ break;
+ case WLAN_CATEGORY_PROTECTED_EHT:
+ switch (mgmt->u.action.action_code) {
+ case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ:
+ ieee80211_process_neg_ttlm_req(sdata, mgmt,
+ skb->len);
+ break;
+ case WLAN_PROTECTED_EHT_ACTION_TTLM_RES:
+ ieee80211_process_neg_ttlm_res(sdata, mgmt,
+ skb->len);
+ break;
+ case WLAN_PROTECTED_EHT_ACTION_TTLM_TEARDOWN:
+ ieee80211_process_ttlm_teardown(sdata);
+ break;
+ case WLAN_PROTECTED_EHT_ACTION_LINK_RECONFIG_RESP:
+ ieee80211_process_ml_reconf_resp(sdata, mgmt,
+ skb->len);
+ break;
+ case WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_RESP:
+ ieee80211_process_epcs_ena_resp(sdata, mgmt,
+ skb->len);
+ break;
+ case WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_TEARDOWN:
+ ieee80211_process_epcs_teardown(sdata, mgmt,
+ skb->len);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ break;
+ }
+}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] wireless-regdb: allow 320MHz channel width for Russia
From: Ilya K @ 2026-04-28 8:16 UTC (permalink / raw)
To: Louis Kotze
Cc: Chen-Yu Tsai, Valeriy Kosikhin, wireless-regdb, linux-wireless
In-Reply-To: <20260428074500.ru-320-review-resend@gmail.com>
On 2026-04-28 10:44, Louis Kotze wrote:
>
> Note that the patch base predates the recent RU power update (commit
> 6873bfe in wens/wireless-regdb), which changed 5925-6425 from 100 mW
> to 200 mW, so this needs a rebase on current HEAD before it can be
> applied.
>
I can resend a rebased version if necessary.
^ permalink raw reply
* Re: [PATCH] wireless-regdb: allow 320MHz channel width for Russia
From: Louis Kotze @ 2026-04-28 7:44 UTC (permalink / raw)
To: K900; +Cc: Chen-Yu Tsai, Valeriy Kosikhin, wireless-regdb, linux-wireless
In-Reply-To: <20250708-russia-320-v1-1-53641e8dd417@0upti.me>
ChenYu, thanks for the ping on this one.
Cross-checked against SCRF 22-65-05 (which added 5925-6425 MHz to the
2007 spectrum allocation, Appendix 2):
Section 2 ("Devices with direct sequence spread spectrum and other
modulation types") for 5925-6425 MHz specifies:
LPI: max EIRP 200 mW, max PSD 10 mW/MHz, indoor only,
with automatic power control
VLP: max EIRP 25 mW, max PSD 1.3 mW/MHz, indoor only
No channel-width restriction is specified for this category. The
only width cap (less than 20 MHz) applies to the FHSS subsection,
not to DSSS / 802.11-style modulation.
SCRF 23-68-03 §3.1 later replaced "automatic power control" with
"LBT mode" globally in that appendix, which the 802.11 CCA mechanism
satisfies.
320 MHz operation at the 200 mW LPI cap gives 0.625 mW/MHz, well under
the 10 mW/MHz PSD ceiling, so the wider channel stays compliant on
both EIRP and PSD limits. Per regdb convention of encoding the
spectrum allocation ceiling and leaving per-standard equipment
certification to drivers, the bandwidth widening is sound.
Reviewed-by: Louis Kotze <loukot@gmail.com>
Note that the patch base predates the recent RU power update (commit
6873bfe in wens/wireless-regdb), which changed 5925-6425 from 100 mW
to 200 mW, so this needs a rebase on current HEAD before it can be
applied.
Regards,
Louis
^ permalink raw reply
* Re: [PATCH wireless-next 1/2] wifi: cfg80211: indicate (Re)Association frame encryption to userspace
From: Johannes Berg @ 2026-04-28 7:39 UTC (permalink / raw)
To: Kavita Kavita; +Cc: linux-wireless
In-Reply-To: <20260427150735.2391680-2-kavita.kavita@oss.qualcomm.com>
On Mon, 2026-04-27 at 20:37 +0530, Kavita Kavita wrote:
>
> Mark NL80211_ATTR_ASSOC_ENCRYPTED with NLA_REJECT in the nl80211
> policy to reject any attempt by userspace to send this attribute.
This is not necessary, it's the default given
[0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD },
johannes
^ permalink raw reply
* Re: [PATCH wireless-next 2/2] wifi: cfg80211/mac80211: extend cfg80211_rx_assoc_resp_data() for assoc encryption
From: Johannes Berg @ 2026-04-28 7:38 UTC (permalink / raw)
To: Kavita Kavita; +Cc: linux-wireless
In-Reply-To: <20260427150735.2391680-3-kavita.kavita@oss.qualcomm.com>
On Mon, 2026-04-27 at 20:37 +0530, Kavita Kavita wrote:
> Extend cfg80211_rx_assoc_resp_data with a new assoc_encrypted field to
> indicate if the (re)association exchange is encrypted.
>
> Currently, when epp_peer flag is set, unprotected (Re)Association
> Request/Response frames are dropped. This ensures that by the time
> the (Re)Association Response is processed, the entire association
> exchange is encrypted over the air.
>
> Set assoc_encrypted in cfg80211_rx_assoc_resp_data based on epp_peer
> flag when processing the (Re)Association Response.
I don't quite see how this is necessary, even in nl80211_send_rx_assoc()
the whole frame, including header and protected bit, is available. Why
does this need mac80211 involvement? One could ask why it's needed *at
all* when userspace already gets the frame and should probably process
the frame RX preferably over the connect result indication...
If this is needed for some reason please outline it in the commit
message, and reshuffle the code to properly split between cfg80211 and
mac80211 in the commits.
johannes
^ permalink raw reply
* Re: [PATCH 2/3] dt-bindings: net: wireless: mt76: remove mediatek,mtd-eeprom
From: Krzysztof Kozlowski @ 2026-04-28 7:33 UTC (permalink / raw)
To: Rosen Penev
Cc: devicetree, Felix Fietkau, Lorenzo Bianconi, Ryder Lee,
Shayne Chen, Sean Wang, Johannes Berg, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Thomas Bogendoerfer,
open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
In-Reply-To: <20260427034427.881389-3-rosenp@gmail.com>
On Sun, Apr 26, 2026 at 08:44:26PM -0700, Rosen Penev wrote:
> mediatek,mtd-eeprom is a widely unused binding that predates and has
I clearly see a user. Please use `git grep`.
> been replaced by NVMEM. As there are no users, remove it.
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> .../bindings/net/wireless/mediatek,mt76.yaml | 19 ++-----------------
> 1 file changed, 2 insertions(+), 17 deletions(-)
>
Wrong order of patches, you have undocumented ABI.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 3/3] wifi: mt76: remove mt76_get_of_data_from_mtd
From: Krzysztof Kozlowski @ 2026-04-28 7:32 UTC (permalink / raw)
To: Rosen Penev
Cc: devicetree, Felix Fietkau, Lorenzo Bianconi, Ryder Lee,
Shayne Chen, Sean Wang, Johannes Berg, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Thomas Bogendoerfer,
open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
In-Reply-To: <20260427034427.881389-4-rosenp@gmail.com>
On Sun, Apr 26, 2026 at 08:44:27PM -0700, Rosen Penev wrote:
> mt76_get_of_data_from_mtd has been replaced by
> mt76_get_of_data_from_nvmem in all usages.
>
> Remove it to prevent people from using the deprecated
> mediatek,mtd-eeprom binding.
Where did you deprecate it? I cannot find it.
Anyway, as Conor pointed out, this is ABI break and really poorly
explained. Cleanup is not a reason to break ABI.
Best regards,
Krzysztof
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox