diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 5755aa288912..16e11c5718b5 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -848,6 +848,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 @@ -860,18 +861,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; }; @@ -1018,7 +1023,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 235d7ae72e60..9998f6c0a665 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3162,6 +3162,9 @@ enum nl80211_commands { * encrypted over the air. Enhanced Privacy Protection (EPP), as defined * in IEEE P802.11bi/D4.0, mandates this encryption. * + * @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 @@ -3757,6 +3760,9 @@ enum nl80211_attrs { NL80211_ATTR_ASSOC_ENCRYPTED, + 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 8954ac1659c1..38b83b44b5dc 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 f935b18112e8..cd72f187a606 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1090,6 +1090,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 */ @@ -3913,7 +3916,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; @@ -4027,6 +4031,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"); @@ -4054,9 +4086,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, @@ -4069,6 +4103,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; @@ -4083,9 +4118,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; @@ -4104,6 +4143,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; @@ -4588,6 +4630,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); @@ -7092,6 +7143,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]; @@ -7231,7 +7304,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) { @@ -7250,6 +7323,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 = @@ -11801,7 +11879,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; @@ -11890,7 +11969,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; @@ -11973,6 +12053,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 || @@ -11990,6 +12071,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; @@ -12027,7 +12110,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; @@ -13301,7 +13389,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; @@ -14310,7 +14398,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; @@ -14539,7 +14628,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; } @@ -14947,7 +15036,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; @@ -15023,7 +15112,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 { @@ -17027,7 +17116,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; @@ -18026,7 +18115,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 f77658b6fccc..e45fc1fb65c2 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -349,7 +349,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 a68d356fe127..94944f2a39a4 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 { \