// SPDX-License-Identifier: GPL-2.0-only
/*
 * This is the new netlink-based wireless configuration interface.
 *
 * Copyright 2006-2010	Johannes Berg <johannes@sipsolutions.net>
 * Copyright 2013-2014  Intel Mobile Communications GmbH
 * Copyright 2015-2017	Intel Deutschland GmbH
 * Copyright (C) 2018-2022 Intel Corporation
 */

#include <linux/if.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/if_ether.h>
#include <linux/ieee80211.h>
#include <linux/nl80211.h>
#include <linux/rtnetlink.h>
#include <linux/netlink.h>
#include <linux/nospec.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <net/net_namespace.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include <net/sock.h>
#include <net/inet_connection_sock.h>
#include "core.h"
#include "nl80211.h"
#include "reg.h"
#include "rdev-ops.h"

static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev,
				   struct genl_info *info,
				   struct cfg80211_crypto_settings *settings,
				   int cipher_limit);

/* the netlink family */
static struct genl_family nl80211_fam;

/* multicast groups */
enum nl80211_multicast_groups {
	NL80211_MCGRP_CONFIG,
	NL80211_MCGRP_SCAN,
	NL80211_MCGRP_REGULATORY,
	NL80211_MCGRP_MLME,
	NL80211_MCGRP_VENDOR,
	NL80211_MCGRP_NAN,
	NL80211_MCGRP_TESTMODE /* keep last - ifdef! */
};

static const struct genl_multicast_group nl80211_mcgrps[] = {
	[NL80211_MCGRP_CONFIG] = { .name = NL80211_MULTICAST_GROUP_CONFIG },
	[NL80211_MCGRP_SCAN] = { .name = NL80211_MULTICAST_GROUP_SCAN },
	[NL80211_MCGRP_REGULATORY] = { .name = NL80211_MULTICAST_GROUP_REG },
	[NL80211_MCGRP_MLME] = { .name = NL80211_MULTICAST_GROUP_MLME },
	[NL80211_MCGRP_VENDOR] = { .name = NL80211_MULTICAST_GROUP_VENDOR },
	[NL80211_MCGRP_NAN] = { .name = NL80211_MULTICAST_GROUP_NAN },
#ifdef CONFIG_NL80211_TESTMODE
	[NL80211_MCGRP_TESTMODE] = { .name = NL80211_MULTICAST_GROUP_TESTMODE }
#endif
};

/* returns ERR_PTR values */
static struct wireless_dev *
__cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
			   struct net *netns, struct nlattr **attrs)
{
	struct wireless_dev *result = NULL;
	bool have_ifidx = attrs[NL80211_ATTR_IFINDEX];
	bool have_wdev_id = attrs[NL80211_ATTR_WDEV];
	u64 wdev_id = 0;
	int wiphy_idx = -1;
	int ifidx = -1;

	if (!have_ifidx && !have_wdev_id)
		return ERR_PTR(-EINVAL);

	if (have_ifidx)
		ifidx = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
	if (have_wdev_id) {
		wdev_id = nla_get_u64(attrs[NL80211_ATTR_WDEV]);
		wiphy_idx = wdev_id >> 32;
	}

	if (rdev) {
		struct wireless_dev *wdev;

		lockdep_assert_held(&rdev->wiphy.mtx);

		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
			if (have_ifidx && wdev->netdev &&
			    wdev->netdev->ifindex == ifidx) {
				result = wdev;
				break;
			}
			if (have_wdev_id && wdev->identifier == (u32)wdev_id) {
				result = wdev;
				break;
			}
		}

		return result ?: ERR_PTR(-ENODEV);
	}

	ASSERT_RTNL();

	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
		struct wireless_dev *wdev;

		if (wiphy_net(&rdev->wiphy) != netns)
			continue;

		if (have_wdev_id && rdev->wiphy_idx != wiphy_idx)
			continue;

		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
			if (have_ifidx && wdev->netdev &&
			    wdev->netdev->ifindex == ifidx) {
				result = wdev;
				break;
			}
			if (have_wdev_id && wdev->identifier == (u32)wdev_id) {
				result = wdev;
				break;
			}
		}

		if (result)
			break;
	}

	if (result)
		return result;
	return ERR_PTR(-ENODEV);
}

static struct cfg80211_registered_device *
__cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
{
	struct cfg80211_registered_device *rdev = NULL, *tmp;
	struct net_device *netdev;

	ASSERT_RTNL();

	if (!attrs[NL80211_ATTR_WIPHY] &&
	    !attrs[NL80211_ATTR_IFINDEX] &&
	    !attrs[NL80211_ATTR_WDEV])
		return ERR_PTR(-EINVAL);

	if (attrs[NL80211_ATTR_WIPHY])
		rdev = cfg80211_rdev_by_wiphy_idx(
				nla_get_u32(attrs[NL80211_ATTR_WIPHY]));

	if (attrs[NL80211_ATTR_WDEV]) {
		u64 wdev_id = nla_get_u64(attrs[NL80211_ATTR_WDEV]);
		struct wireless_dev *wdev;
		bool found = false;

		tmp = cfg80211_rdev_by_wiphy_idx(wdev_id >> 32);
		if (tmp) {
			/* make sure wdev exists */
			list_for_each_entry(wdev, &tmp->wiphy.wdev_list, list) {
				if (wdev->identifier != (u32)wdev_id)
					continue;
				found = true;
				break;
			}

			if (!found)
				tmp = NULL;

			if (rdev && tmp != rdev)
				return ERR_PTR(-EINVAL);
			rdev = tmp;
		}
	}

	if (attrs[NL80211_ATTR_IFINDEX]) {
		int ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);

		netdev = __dev_get_by_index(netns, ifindex);
		if (netdev) {
			if (netdev->ieee80211_ptr)
				tmp = wiphy_to_rdev(
					netdev->ieee80211_ptr->wiphy);
			else
				tmp = NULL;

			/* not wireless device -- return error */
			if (!tmp)
				return ERR_PTR(-EINVAL);

			/* mismatch -- return error */
			if (rdev && tmp != rdev)
				return ERR_PTR(-EINVAL);

			rdev = tmp;
		}
	}

	if (!rdev)
		return ERR_PTR(-ENODEV);

	if (netns != wiphy_net(&rdev->wiphy))
		return ERR_PTR(-ENODEV);

	return rdev;
}

/*
 * This function returns a pointer to the driver
 * that the genl_info item that is passed refers to.
 *
 * The result of this can be a PTR_ERR and hence must
 * be checked with IS_ERR() for errors.
 */
static struct cfg80211_registered_device *
cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
{
	return __cfg80211_rdev_from_attrs(netns, info->attrs);
}

static int validate_beacon_head(const struct nlattr *attr,
				struct netlink_ext_ack *extack)
{
	const u8 *data = nla_data(attr);
	unsigned int len = nla_len(attr);
	const struct element *elem;
	const struct ieee80211_mgmt *mgmt = (void *)data;
	unsigned int fixedlen, hdrlen;
	bool s1g_bcn;

	if (len < offsetofend(typeof(*mgmt), frame_control))
		goto err;

	s1g_bcn = ieee80211_is_s1g_beacon(mgmt->frame_control);
	if (s1g_bcn) {
		fixedlen = offsetof(struct ieee80211_ext,
				    u.s1g_beacon.variable);
		hdrlen = offsetof(struct ieee80211_ext, u.s1g_beacon);
	} else {
		fixedlen = offsetof(struct ieee80211_mgmt,
				    u.beacon.variable);
		hdrlen = offsetof(struct ieee80211_mgmt, u.beacon);
	}

	if (len < fixedlen)
		goto err;

	if (ieee80211_hdrlen(mgmt->frame_control) != hdrlen)
		goto err;

	data += fixedlen;
	len -= fixedlen;

	for_each_element(elem, data, len) {
		/* nothing */
	}

	if (for_each_element_completed(elem, data, len))
		return 0;

err:
	NL_SET_ERR_MSG_ATTR(extack, attr, "malformed beacon head");
	return -EINVAL;
}

static int validate_ie_attr(const struct nlattr *attr,
			    struct netlink_ext_ack *extack)
{
	const u8 *data = nla_data(attr);
	unsigned int len = nla_len(attr);
	const struct element *elem;

	for_each_element(elem, data, len) {
		/* nothing */
	}

	if (for_each_element_completed(elem, data, len))
		return 0;

	NL_SET_ERR_MSG_ATTR(extack, attr, "malformed information elements");
	return -EINVAL;
}

static int validate_he_capa(const struct nlattr *attr,
			    struct netlink_ext_ack *extack)
{
	if (!ieee80211_he_capa_size_ok(nla_data(attr), nla_len(attr)))
		return -EINVAL;

	return 0;
}

/* policy for the attributes */
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR];

static const struct nla_policy
nl80211_ftm_responder_policy[NL80211_FTM_RESP_ATTR_MAX + 1] = {
	[NL80211_FTM_RESP_ATTR_ENABLED] = { .type = NLA_FLAG, },
	[NL80211_FTM_RESP_ATTR_LCI] = { .type = NLA_BINARY,
					.len = U8_MAX },
	[NL80211_FTM_RESP_ATTR_CIVICLOC] = { .type = NLA_BINARY,
					     .len = U8_MAX },
};

static const struct nla_policy
nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = {
	[NL80211_PMSR_FTM_REQ_ATTR_ASAP] = { .type = NLA_FLAG },
	[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = { .type = NLA_U32 },
	[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP] =
		NLA_POLICY_MAX(NLA_U8, 15),
	[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD] = { .type = NLA_U16 },
	[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION] =
		NLA_POLICY_MAX(NLA_U8, 15),
	[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST] =
		NLA_POLICY_MAX(NLA_U8, 31),
	[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES] = { .type = NLA_U8 },
	[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI] = { .type = NLA_FLAG },
	[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC] = { .type = NLA_FLAG },
	[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED] = { .type = NLA_FLAG },
	[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED] = { .type = NLA_FLAG },
	[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK] = { .type = NLA_FLAG },
};

static const struct nla_policy
nl80211_pmsr_req_data_policy[NL80211_PMSR_TYPE_MAX + 1] = {
	[NL80211_PMSR_TYPE_FTM] =
		NLA_POLICY_NESTED(nl80211_pmsr_ftm_req_attr_policy),
};

static const struct nla_policy
nl80211_pmsr_req_attr_policy[NL80211_PMSR_REQ_ATTR_MAX + 1] = {
	[NL80211_PMSR_REQ_ATTR_DATA] =
		NLA_POLICY_NESTED(nl80211_pmsr_req_data_policy),
	[NL80211_PMSR_REQ_ATTR_GET_AP_TSF] = { .type = NLA_FLAG },
};

static const struct nla_policy
nl80211_pmsr_peer_attr_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = {
	[NL80211_PMSR_PEER_ATTR_ADDR] = NLA_POLICY_ETH_ADDR,
	[NL80211_PMSR_PEER_ATTR_CHAN] = NLA_POLICY_NESTED(nl80211_policy),
	[NL80211_PMSR_PEER_ATTR_REQ] =
		NLA_POLICY_NESTED(nl80211_pmsr_req_attr_policy),
	[NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_REJECT },
};

static const struct nla_policy
nl80211_pmsr_attr_policy[NL80211_PMSR_ATTR_MAX + 1] = {
	[NL80211_PMSR_ATTR_MAX_PEERS] = { .type = NLA_REJECT },
	[NL80211_PMSR_ATTR_REPORT_AP_TSF] = { .type = NLA_REJECT },
	[NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR] = { .type = NLA_REJECT },
	[NL80211_PMSR_ATTR_TYPE_CAPA] = { .type = NLA_REJECT },
	[NL80211_PMSR_ATTR_PEERS] =
		NLA_POLICY_NESTED_ARRAY(nl80211_pmsr_peer_attr_policy),
};

static const struct nla_policy
he_obss_pd_policy[NL80211_HE_OBSS_PD_ATTR_MAX + 1] = {
	[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET] =
		NLA_POLICY_RANGE(NLA_U8, 1, 20),
	[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET] =
		NLA_POLICY_RANGE(NLA_U8, 1, 20),
	[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET] =
		NLA_POLICY_RANGE(NLA_U8, 1, 20),
	[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP] =
		NLA_POLICY_EXACT_LEN(8),
	[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP] =
		NLA_POLICY_EXACT_LEN(8),
	[NL80211_HE_OBSS_PD_ATTR_SR_CTRL] = { .type = NLA_U8 },
};

static const struct nla_policy
he_bss_color_policy[NL80211_HE_BSS_COLOR_ATTR_MAX + 1] = {
	[NL80211_HE_BSS_COLOR_ATTR_COLOR] = NLA_POLICY_RANGE(NLA_U8, 1, 63),
	[NL80211_HE_BSS_COLOR_ATTR_DISABLED] = { .type = NLA_FLAG },
	[NL80211_HE_BSS_COLOR_ATTR_PARTIAL] = { .type = NLA_FLAG },
};

static const struct nla_policy nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] = {
	[NL80211_TXRATE_LEGACY] = { .type = NLA_BINARY,
				    .len = NL80211_MAX_SUPP_RATES },
	[NL80211_TXRATE_HT] = { .type = NLA_BINARY,
				.len = NL80211_MAX_SUPP_HT_RATES },
	[NL80211_TXRATE_VHT] = NLA_POLICY_EXACT_LEN_WARN(sizeof(struct nl80211_txrate_vht)),
	[NL80211_TXRATE_GI] = { .type = NLA_U8 },
	[NL80211_TXRATE_HE] = NLA_POLICY_EXACT_LEN(sizeof(struct nl80211_txrate_he)),
	[NL80211_TXRATE_HE_GI] =  NLA_POLICY_RANGE(NLA_U8,
						   NL80211_RATE_INFO_HE_GI_0_8,
						   NL80211_RATE_INFO_HE_GI_3_2),
	[NL80211_TXRATE_HE_LTF] = NLA_POLICY_RANGE(NLA_U8,
						   NL80211_RATE_INFO_HE_1XLTF,
						   NL80211_RATE_INFO_HE_4XLTF),
};

static const struct nla_policy
nl80211_tid_config_attr_policy[NL80211_TID_CONFIG_ATTR_MAX + 1] = {
	[NL80211_TID_CONFIG_ATTR_VIF_SUPP] = { .type = NLA_U64 },
	[NL80211_TID_CONFIG_ATTR_PEER_SUPP] = { .type = NLA_U64 },
	[NL80211_TID_CONFIG_ATTR_OVERRIDE] = { .type = NLA_FLAG },
	[NL80211_TID_CONFIG_ATTR_TIDS] = NLA_POLICY_RANGE(NLA_U16, 1, 0xff),
	[NL80211_TID_CONFIG_ATTR_NOACK] =
			NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
	[NL80211_TID_CONFIG_ATTR_RETRY_SHORT] = NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_TID_CONFIG_ATTR_RETRY_LONG] = NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_TID_CONFIG_ATTR_AMPDU_CTRL] =
			NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
	[NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL] =
			NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
	[NL80211_TID_CONFIG_ATTR_AMSDU_CTRL] =
			NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
	[NL80211_TID_CONFIG_ATTR_TX_RATE_TYPE] =
			NLA_POLICY_MAX(NLA_U8, NL80211_TX_RATE_FIXED),
	[NL80211_TID_CONFIG_ATTR_TX_RATE] =
			NLA_POLICY_NESTED(nl80211_txattr_policy),
};

static const struct nla_policy
nl80211_fils_discovery_policy[NL80211_FILS_DISCOVERY_ATTR_MAX + 1] = {
	[NL80211_FILS_DISCOVERY_ATTR_INT_MIN] = NLA_POLICY_MAX(NLA_U32, 10000),
	[NL80211_FILS_DISCOVERY_ATTR_INT_MAX] = NLA_POLICY_MAX(NLA_U32, 10000),
	[NL80211_FILS_DISCOVERY_ATTR_TMPL] =
			NLA_POLICY_RANGE(NLA_BINARY,
					 NL80211_FILS_DISCOVERY_TMPL_MIN_LEN,
					 IEEE80211_MAX_DATA_LEN),
};

static const struct nla_policy
nl80211_unsol_bcast_probe_resp_policy[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX + 1] = {
	[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT] = NLA_POLICY_MAX(NLA_U32, 20),
	[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL] = { .type = NLA_BINARY,
						       .len = IEEE80211_MAX_DATA_LEN }
};

static const struct nla_policy
sar_specs_policy[NL80211_SAR_ATTR_SPECS_MAX + 1] = {
	[NL80211_SAR_ATTR_SPECS_POWER] = { .type = NLA_S32 },
	[NL80211_SAR_ATTR_SPECS_RANGE_INDEX] = {.type = NLA_U32 },
};

static const struct nla_policy
sar_policy[NL80211_SAR_ATTR_MAX + 1] = {
	[NL80211_SAR_ATTR_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_SAR_TYPE),
	[NL80211_SAR_ATTR_SPECS] = NLA_POLICY_NESTED_ARRAY(sar_specs_policy),
};

static const struct nla_policy
nl80211_mbssid_config_policy[NL80211_MBSSID_CONFIG_ATTR_MAX + 1] = {
	[NL80211_MBSSID_CONFIG_ATTR_MAX_INTERFACES] = NLA_POLICY_MIN(NLA_U8, 2),
	[NL80211_MBSSID_CONFIG_ATTR_MAX_EMA_PROFILE_PERIODICITY] =
						NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_MBSSID_CONFIG_ATTR_INDEX] = { .type = NLA_U8 },
	[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX] = { .type = NLA_U32 },
	[NL80211_MBSSID_CONFIG_ATTR_EMA] = { .type = NLA_FLAG },
};

static const struct nla_policy
nl80211_sta_wme_policy[NL80211_STA_WME_MAX + 1] = {
	[NL80211_STA_WME_UAPSD_QUEUES] = { .type = NLA_U8 },
	[NL80211_STA_WME_MAX_SP] = { .type = NLA_U8 },
};

static struct netlink_range_validation nl80211_punct_bitmap_range = {
	.min = 0,
	.max = 0xffff,
};

static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
	[0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD },
	[NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
				      .len = 20-1 },
	[NL80211_ATTR_WIPHY_TXQ_PARAMS] = { .type = NLA_NESTED },

	[NL80211_ATTR_WIPHY_FREQ] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_CHANNEL_TYPE] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_EDMG_CHANNELS] = NLA_POLICY_RANGE(NLA_U8,
						NL80211_EDMG_CHANNELS_MIN,
						NL80211_EDMG_CHANNELS_MAX),
	[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG] = NLA_POLICY_RANGE(NLA_U8,
						NL80211_EDMG_BW_CONFIG_MIN,
						NL80211_EDMG_BW_CONFIG_MAX),

	[NL80211_ATTR_CHANNEL_WIDTH] = { .type = NLA_U32 },
	[NL80211_ATTR_CENTER_FREQ1] = { .type = NLA_U32 },
	[NL80211_ATTR_CENTER_FREQ1_OFFSET] = NLA_POLICY_RANGE(NLA_U32, 0, 999),
	[NL80211_ATTR_CENTER_FREQ2] = { .type = NLA_U32 },

	[NL80211_ATTR_WIPHY_RETRY_SHORT] = NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_ATTR_WIPHY_RETRY_LONG] = NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_ATTR_WIPHY_FRAG_THRESHOLD] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_RTS_THRESHOLD] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_COVERAGE_CLASS] = { .type = NLA_U8 },
	[NL80211_ATTR_WIPHY_DYN_ACK] = { .type = NLA_FLAG },

	[NL80211_ATTR_IFTYPE] = NLA_POLICY_MAX(NLA_U32, NL80211_IFTYPE_MAX),
	[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
	[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },

	[NL80211_ATTR_MAC] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_ATTR_PREV_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),

	[NL80211_ATTR_KEY] = { .type = NLA_NESTED, },
	[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
				    .len = WLAN_MAX_KEY_LEN },
	[NL80211_ATTR_KEY_IDX] = NLA_POLICY_MAX(NLA_U8, 7),
	[NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
	[NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG },
	[NL80211_ATTR_KEY_SEQ] = { .type = NLA_BINARY, .len = 16 },
	[NL80211_ATTR_KEY_TYPE] =
		NLA_POLICY_MAX(NLA_U32, NUM_NL80211_KEYTYPES),

	[NL80211_ATTR_BEACON_INTERVAL] = { .type = NLA_U32 },
	[NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 },
	[NL80211_ATTR_BEACON_HEAD] =
		NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_beacon_head,
				       IEEE80211_MAX_DATA_LEN),
	[NL80211_ATTR_BEACON_TAIL] =
		NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
				       IEEE80211_MAX_DATA_LEN),
	[NL80211_ATTR_STA_AID] =
		NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
	[NL80211_ATTR_STA_FLAGS] = { .type = NLA_NESTED },
	[NL80211_ATTR_STA_LISTEN_INTERVAL] = { .type = NLA_U16 },
	[NL80211_ATTR_STA_SUPPORTED_RATES] = { .type = NLA_BINARY,
					       .len = NL80211_MAX_SUPP_RATES },
	[NL80211_ATTR_STA_PLINK_ACTION] =
		NLA_POLICY_MAX(NLA_U8, NUM_NL80211_PLINK_ACTIONS - 1),
	[NL80211_ATTR_STA_TX_POWER_SETTING] =
		NLA_POLICY_RANGE(NLA_U8,
				 NL80211_TX_POWER_AUTOMATIC,
				 NL80211_TX_POWER_FIXED),
	[NL80211_ATTR_STA_TX_POWER] = { .type = NLA_S16 },
	[NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
	[NL80211_ATTR_MNTR_FLAGS] = { /* NLA_NESTED can't be empty */ },
	[NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY,
				   .len = IEEE80211_MAX_MESH_ID_LEN },
	[NL80211_ATTR_MPATH_NEXT_HOP] = NLA_POLICY_ETH_ADDR_COMPAT,

	/* allow 3 for NUL-termination, we used to declare this NLA_STRING */
	[NL80211_ATTR_REG_ALPHA2] = NLA_POLICY_RANGE(NLA_BINARY, 2, 3),
	[NL80211_ATTR_REG_RULES] = { .type = NLA_NESTED },

	[NL80211_ATTR_BSS_CTS_PROT] = { .type = NLA_U8 },
	[NL80211_ATTR_BSS_SHORT_PREAMBLE] = { .type = NLA_U8 },
	[NL80211_ATTR_BSS_SHORT_SLOT_TIME] = { .type = NLA_U8 },
	[NL80211_ATTR_BSS_BASIC_RATES] = { .type = NLA_BINARY,
					   .len = NL80211_MAX_SUPP_RATES },
	[NL80211_ATTR_BSS_HT_OPMODE] = { .type = NLA_U16 },

	[NL80211_ATTR_MESH_CONFIG] = { .type = NLA_NESTED },
	[NL80211_ATTR_SUPPORT_MESH_AUTH] = { .type = NLA_FLAG },

	[NL80211_ATTR_HT_CAPABILITY] = NLA_POLICY_EXACT_LEN_WARN(NL80211_HT_CAPABILITY_LEN),

	[NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 },
	[NL80211_ATTR_IE] = NLA_POLICY_VALIDATE_FN(NLA_BINARY,
						   validate_ie_attr,
						   IEEE80211_MAX_DATA_LEN),
	[NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED },
	[NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED },

	[NL80211_ATTR_SSID] = { .type = NLA_BINARY,
				.len = IEEE80211_MAX_SSID_LEN },
	[NL80211_ATTR_AUTH_TYPE] = { .type = NLA_U32 },
	[NL80211_ATTR_REASON_CODE] = { .type = NLA_U16 },
	[NL80211_ATTR_FREQ_FIXED] = { .type = NLA_FLAG },
	[NL80211_ATTR_TIMED_OUT] = { .type = NLA_FLAG },
	[NL80211_ATTR_USE_MFP] = NLA_POLICY_RANGE(NLA_U32,
						  NL80211_MFP_NO,
						  NL80211_MFP_OPTIONAL),
	[NL80211_ATTR_STA_FLAGS2] =
		NLA_POLICY_EXACT_LEN_WARN(sizeof(struct nl80211_sta_flag_update)),
	[NL80211_ATTR_CONTROL_PORT] = { .type = NLA_FLAG },
	[NL80211_ATTR_CONTROL_PORT_ETHERTYPE] = { .type = NLA_U16 },
	[NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT] = { .type = NLA_FLAG },
	[NL80211_ATTR_CONTROL_PORT_OVER_NL80211] = { .type = NLA_FLAG },
	[NL80211_ATTR_PRIVACY] = { .type = NLA_FLAG },
	[NL80211_ATTR_STATUS_CODE] = { .type = NLA_U16 },
	[NL80211_ATTR_CIPHER_SUITE_GROUP] = { .type = NLA_U32 },
	[NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 },
	[NL80211_ATTR_PID] = { .type = NLA_U32 },
	[NL80211_ATTR_4ADDR] = { .type = NLA_U8 },
	[NL80211_ATTR_PMKID] = NLA_POLICY_EXACT_LEN_WARN(WLAN_PMKID_LEN),
	[NL80211_ATTR_DURATION] = { .type = NLA_U32 },
	[NL80211_ATTR_COOKIE] = { .type = NLA_U64 },
	[NL80211_ATTR_TX_RATES] = { .type = NLA_NESTED },
	[NL80211_ATTR_FRAME] = { .type = NLA_BINARY,
				 .len = IEEE80211_MAX_DATA_LEN },
	[NL80211_ATTR_FRAME_MATCH] = { .type = NLA_BINARY, },
	[NL80211_ATTR_PS_STATE] = NLA_POLICY_RANGE(NLA_U32,
						   NL80211_PS_DISABLED,
						   NL80211_PS_ENABLED),
	[NL80211_ATTR_CQM] = { .type = NLA_NESTED, },
	[NL80211_ATTR_LOCAL_STATE_CHANGE] = { .type = NLA_FLAG },
	[NL80211_ATTR_AP_ISOLATE] = { .type = NLA_U8 },
	[NL80211_ATTR_WIPHY_TX_POWER_SETTING] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_TX_POWER_LEVEL] = { .type = NLA_U32 },
	[NL80211_ATTR_FRAME_TYPE] = { .type = NLA_U16 },
	[NL80211_ATTR_WIPHY_ANTENNA_TX] = { .type = NLA_U32 },
	[NL80211_ATTR_WIPHY_ANTENNA_RX] = { .type = NLA_U32 },
	[NL80211_ATTR_MCAST_RATE] = { .type = NLA_U32 },
	[NL80211_ATTR_OFFCHANNEL_TX_OK] = { .type = NLA_FLAG },
	[NL80211_ATTR_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED },
	[NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED },
	[NL80211_ATTR_STA_PLINK_STATE] =
		NLA_POLICY_MAX(NLA_U8, NUM_NL80211_PLINK_STATES - 1),
	[NL80211_ATTR_MEASUREMENT_DURATION] = { .type = NLA_U16 },
	[NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY] = { .type = NLA_FLAG },
	[NL80211_ATTR_MESH_PEER_AID] =
		NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
	[NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
	[NL80211_ATTR_REKEY_DATA] = { .type = NLA_NESTED },
	[NL80211_ATTR_SCAN_SUPP_RATES] = { .type = NLA_NESTED },
	[NL80211_ATTR_HIDDEN_SSID] =
		NLA_POLICY_RANGE(NLA_U32,
				 NL80211_HIDDEN_SSID_NOT_IN_USE,
				 NL80211_HIDDEN_SSID_ZERO_CONTENTS),
	[NL80211_ATTR_IE_PROBE_RESP] =
		NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
				       IEEE80211_MAX_DATA_LEN),
	[NL80211_ATTR_IE_ASSOC_RESP] =
		NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
				       IEEE80211_MAX_DATA_LEN),
	[NL80211_ATTR_ROAM_SUPPORT] = { .type = NLA_FLAG },
	[NL80211_ATTR_STA_WME] = NLA_POLICY_NESTED(nl80211_sta_wme_policy),
	[NL80211_ATTR_SCHED_SCAN_MATCH] = { .type = NLA_NESTED },
	[NL80211_ATTR_TX_NO_CCK_RATE] = { .type = NLA_FLAG },
	[NL80211_ATTR_TDLS_ACTION] = { .type = NLA_U8 },
	[NL80211_ATTR_TDLS_DIALOG_TOKEN] = { .type = NLA_U8 },
	[NL80211_ATTR_TDLS_OPERATION] = { .type = NLA_U8 },
	[NL80211_ATTR_TDLS_SUPPORT] = { .type = NLA_FLAG },
	[NL80211_ATTR_TDLS_EXTERNAL_SETUP] = { .type = NLA_FLAG },
	[NL80211_ATTR_TDLS_INITIATOR] = { .type = NLA_FLAG },
	[NL80211_ATTR_DONT_WAIT_FOR_ACK] = { .type = NLA_FLAG },
	[NL80211_ATTR_PROBE_RESP] = { .type = NLA_BINARY,
				      .len = IEEE80211_MAX_DATA_LEN },
	[NL80211_ATTR_DFS_REGION] = { .type = NLA_U8 },
	[NL80211_ATTR_DISABLE_HT] = { .type = NLA_FLAG },
	[NL80211_ATTR_HT_CAPABILITY_MASK] = {
		.len = NL80211_HT_CAPABILITY_LEN
	},
	[NL80211_ATTR_NOACK_MAP] = { .type = NLA_U16 },
	[NL80211_ATTR_INACTIVITY_TIMEOUT] = { .type = NLA_U16 },
	[NL80211_ATTR_BG_SCAN_PERIOD] = { .type = NLA_U16 },
	[NL80211_ATTR_WDEV] = { .type = NLA_U64 },
	[NL80211_ATTR_USER_REG_HINT_TYPE] = { .type = NLA_U32 },

	/* need to include at least Auth Transaction and Status Code */
	[NL80211_ATTR_AUTH_DATA] = NLA_POLICY_MIN_LEN(4),

	[NL80211_ATTR_VHT_CAPABILITY] = NLA_POLICY_EXACT_LEN_WARN(NL80211_VHT_CAPABILITY_LEN),
	[NL80211_ATTR_SCAN_FLAGS] = { .type = NLA_U32 },
	[NL80211_ATTR_P2P_CTWINDOW] = NLA_POLICY_MAX(NLA_U8, 127),
	[NL80211_ATTR_P2P_OPPPS] = NLA_POLICY_MAX(NLA_U8, 1),
	[NL80211_ATTR_LOCAL_MESH_POWER_MODE] =
		NLA_POLICY_RANGE(NLA_U32,
				 NL80211_MESH_POWER_UNKNOWN + 1,
				 NL80211_MESH_POWER_MAX),
	[NL80211_ATTR_ACL_POLICY] = {. type = NLA_U32 },
	[NL80211_ATTR_MAC_ADDRS] = { .type = NLA_NESTED },
	[NL80211_ATTR_STA_CAPABILITY] = { .type = NLA_U16 },
	[NL80211_ATTR_STA_EXT_CAPABILITY] = { .type = NLA_BINARY, },
	[NL80211_ATTR_SPLIT_WIPHY_DUMP] = { .type = NLA_FLAG, },
	[NL80211_ATTR_DISABLE_VHT] = { .type = NLA_FLAG },
	[NL80211_ATTR_VHT_CAPABILITY_MASK] = {
		.len = NL80211_VHT_CAPABILITY_LEN,
	},
	[NL80211_ATTR_MDID] = { .type = NLA_U16 },
	[NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
				  .len = IEEE80211_MAX_DATA_LEN },
	[NL80211_ATTR_CRIT_PROT_ID] = { .type = NLA_U16 },
	[NL80211_ATTR_MAX_CRIT_PROT_DURATION] =
		NLA_POLICY_MAX(NLA_U16, NL80211_CRIT_PROTO_MAX_DURATION),
	[NL80211_ATTR_PEER_AID] =
		NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
	[NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
	[NL80211_ATTR_CH_SWITCH_BLOCK_TX] = { .type = NLA_FLAG },
	[NL80211_ATTR_CSA_IES] = { .type = NLA_NESTED },
	[NL80211_ATTR_CNTDWN_OFFS_BEACON] = { .type = NLA_BINARY },
	[NL80211_ATTR_CNTDWN_OFFS_PRESP] = { .type = NLA_BINARY },
	[NL80211_ATTR_STA_SUPPORTED_CHANNELS] = NLA_POLICY_MIN_LEN(2),
	/*
	 * The value of the Length field of the Supported Operating
	 * Classes element is between 2 and 253.
	 */
	[NL80211_ATTR_STA_SUPPORTED_OPER_CLASSES] =
		NLA_POLICY_RANGE(NLA_BINARY, 2, 253),
	[NL80211_ATTR_HANDLE_DFS] = { .type = NLA_FLAG },
	[NL80211_ATTR_OPMODE_NOTIF] = { .type = NLA_U8 },
	[NL80211_ATTR_VENDOR_ID] = { .type = NLA_U32 },
	[NL80211_ATTR_VENDOR_SUBCMD] = { .type = NLA_U32 },
	[NL80211_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
	[NL80211_ATTR_QOS_MAP] = NLA_POLICY_RANGE(NLA_BINARY,
						  IEEE80211_QOS_MAP_LEN_MIN,
						  IEEE80211_QOS_MAP_LEN_MAX),
	[NL80211_ATTR_MAC_HINT] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 },
	[NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 },
	[NL80211_ATTR_SOCKET_OWNER] = { .type = NLA_FLAG },
	[NL80211_ATTR_CSA_C_OFFSETS_TX] = { .type = NLA_BINARY },
	[NL80211_ATTR_USE_RRM] = { .type = NLA_FLAG },
	[NL80211_ATTR_TSID] = NLA_POLICY_MAX(NLA_U8, IEEE80211_NUM_TIDS - 1),
	[NL80211_ATTR_USER_PRIO] =
		NLA_POLICY_MAX(NLA_U8, IEEE80211_NUM_UPS - 1),
	[NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 },
	[NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 },
	[NL80211_ATTR_OPER_CLASS] = { .type = NLA_U8 },
	[NL80211_ATTR_MAC_MASK] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_ATTR_WIPHY_SELF_MANAGED_REG] = { .type = NLA_FLAG },
	[NL80211_ATTR_NETNS_FD] = { .type = NLA_U32 },
	[NL80211_ATTR_SCHED_SCAN_DELAY] = { .type = NLA_U32 },
	[NL80211_ATTR_REG_INDOOR] = { .type = NLA_FLAG },
	[NL80211_ATTR_PBSS] = { .type = NLA_FLAG },
	[NL80211_ATTR_BSS_SELECT] = { .type = NLA_NESTED },
	[NL80211_ATTR_STA_SUPPORT_P2P_PS] =
		NLA_POLICY_MAX(NLA_U8, NUM_NL80211_P2P_PS_STATUS - 1),
	[NL80211_ATTR_MU_MIMO_GROUP_DATA] = {
		.len = VHT_MUMIMO_GROUPS_DATA_LEN
	},
	[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_ATTR_NAN_MASTER_PREF] = NLA_POLICY_MIN(NLA_U8, 1),
	[NL80211_ATTR_BANDS] = { .type = NLA_U32 },
	[NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
	[NL80211_ATTR_FILS_KEK] = { .type = NLA_BINARY,
				    .len = FILS_MAX_KEK_LEN },
	[NL80211_ATTR_FILS_NONCES] = NLA_POLICY_EXACT_LEN_WARN(2 * FILS_NONCE_LEN),
	[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_FLAG, },
	[NL80211_ATTR_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI] = { .type = NLA_S8 },
	[NL80211_ATTR_SCHED_SCAN_RSSI_ADJUST] = {
		.len = sizeof(struct nl80211_bss_select_rssi_adjust)
	},
	[NL80211_ATTR_TIMEOUT_REASON] = { .type = NLA_U32 },
	[NL80211_ATTR_FILS_ERP_USERNAME] = { .type = NLA_BINARY,
					     .len = FILS_ERP_MAX_USERNAME_LEN },
	[NL80211_ATTR_FILS_ERP_REALM] = { .type = NLA_BINARY,
					  .len = FILS_ERP_MAX_REALM_LEN },
	[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM] = { .type = NLA_U16 },
	[NL80211_ATTR_FILS_ERP_RRK] = { .type = NLA_BINARY,
					.len = FILS_ERP_MAX_RRK_LEN },
	[NL80211_ATTR_FILS_CACHE_ID] = NLA_POLICY_EXACT_LEN_WARN(2),
	[NL80211_ATTR_PMK] = { .type = NLA_BINARY, .len = PMK_MAX_LEN },
	[NL80211_ATTR_PMKR0_NAME] = NLA_POLICY_EXACT_LEN(WLAN_PMK_NAME_LEN),
	[NL80211_ATTR_SCHED_SCAN_MULTI] = { .type = NLA_FLAG },
	[NL80211_ATTR_EXTERNAL_AUTH_SUPPORT] = { .type = NLA_FLAG },

	[NL80211_ATTR_TXQ_LIMIT] = { .type = NLA_U32 },
	[NL80211_ATTR_TXQ_MEMORY_LIMIT] = { .type = NLA_U32 },
	[NL80211_ATTR_TXQ_QUANTUM] = { .type = NLA_U32 },
	[NL80211_ATTR_HE_CAPABILITY] =
		NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_he_capa,
				       NL80211_HE_MAX_CAPABILITY_LEN),
	[NL80211_ATTR_FTM_RESPONDER] =
		NLA_POLICY_NESTED(nl80211_ftm_responder_policy),
	[NL80211_ATTR_TIMEOUT] = NLA_POLICY_MIN(NLA_U32, 1),
	[NL80211_ATTR_PEER_MEASUREMENTS] =
		NLA_POLICY_NESTED(nl80211_pmsr_attr_policy),
	[NL80211_ATTR_AIRTIME_WEIGHT] = NLA_POLICY_MIN(NLA_U16, 1),
	[NL80211_ATTR_SAE_PASSWORD] = { .type = NLA_BINARY,
					.len = SAE_PASSWORD_MAX_LEN },
	[NL80211_ATTR_TWT_RESPONDER] = { .type = NLA_FLAG },
	[NL80211_ATTR_HE_OBSS_PD] = NLA_POLICY_NESTED(he_obss_pd_policy),
	[NL80211_ATTR_VLAN_ID] = NLA_POLICY_RANGE(NLA_U16, 1, VLAN_N_VID - 2),
	[NL80211_ATTR_HE_BSS_COLOR] = NLA_POLICY_NESTED(he_bss_color_policy),
	[NL80211_ATTR_TID_CONFIG] =
		NLA_POLICY_NESTED_ARRAY(nl80211_tid_config_attr_policy),
	[NL80211_ATTR_CONTROL_PORT_NO_PREAUTH] = { .type = NLA_FLAG },
	[NL80211_ATTR_PMK_LIFETIME] = NLA_POLICY_MIN(NLA_U32, 1),
	[NL80211_ATTR_PMK_REAUTH_THRESHOLD] = NLA_POLICY_RANGE(NLA_U8, 1, 100),
	[NL80211_ATTR_RECEIVE_MULTICAST] = { .type = NLA_FLAG },
	[NL80211_ATTR_WIPHY_FREQ_OFFSET] = NLA_POLICY_RANGE(NLA_U32, 0, 999),
	[NL80211_ATTR_SCAN_FREQ_KHZ] = { .type = NLA_NESTED },
	[NL80211_ATTR_HE_6GHZ_CAPABILITY] =
		NLA_POLICY_EXACT_LEN(sizeof(struct ieee80211_he_6ghz_capa)),
	[NL80211_ATTR_FILS_DISCOVERY] =
		NLA_POLICY_NESTED(nl80211_fils_discovery_policy),
	[NL80211_ATTR_UNSOL_BCAST_PROBE_RESP] =
		NLA_POLICY_NESTED(nl80211_unsol_bcast_probe_resp_policy),
	[NL80211_ATTR_S1G_CAPABILITY] =
		NLA_POLICY_EXACT_LEN(IEEE80211_S1G_CAPABILITY_LEN),
	[NL80211_ATTR_S1G_CAPABILITY_MASK] =
		NLA_POLICY_EXACT_LEN(IEEE80211_S1G_CAPABILITY_LEN),
	[NL80211_ATTR_SAE_PWE] =
		NLA_POLICY_RANGE(NLA_U8, NL80211_SAE_PWE_HUNT_AND_PECK,
				 NL80211_SAE_PWE_BOTH),
	[NL80211_ATTR_RECONNECT_REQUESTED] = { .type = NLA_REJECT },
	[NL80211_ATTR_SAR_SPEC] = NLA_POLICY_NESTED(sar_policy),
	[NL80211_ATTR_DISABLE_HE] = { .type = NLA_FLAG },
	[NL80211_ATTR_OBSS_COLOR_BITMAP] = { .type = NLA_U64 },
	[NL80211_ATTR_COLOR_CHANGE_COUNT] = { .type = NLA_U8 },
	[NL80211_ATTR_COLOR_CHANGE_COLOR] = { .type = NLA_U8 },
	[NL80211_ATTR_COLOR_CHANGE_ELEMS] = NLA_POLICY_NESTED(nl80211_policy),
	[NL80211_ATTR_MBSSID_CONFIG] =
			NLA_POLICY_NESTED(nl80211_mbssid_config_policy),
	[NL80211_ATTR_MBSSID_ELEMS] = { .type = NLA_NESTED },
	[NL80211_ATTR_RADAR_BACKGROUND] = { .type = NLA_FLAG },
	[NL80211_ATTR_AP_SETTINGS_FLAGS] = { .type = NLA_U32 },
	[NL80211_ATTR_EHT_CAPABILITY] =
		NLA_POLICY_RANGE(NLA_BINARY,
				 NL80211_EHT_MIN_CAPABILITY_LEN,
				 NL80211_EHT_MAX_CAPABILITY_LEN),
	[NL80211_ATTR_DISABLE_EHT] = { .type = NLA_FLAG },
	[NL80211_ATTR_MLO_LINKS] =
		NLA_POLICY_NESTED_ARRAY(nl80211_policy),
	[NL80211_ATTR_MLO_LINK_ID] =
		NLA_POLICY_RANGE(NLA_U8, 0, IEEE80211_MLD_MAX_NUM_LINKS),
	[NL80211_ATTR_MLD_ADDR] = NLA_POLICY_EXACT_LEN(ETH_ALEN),
	[NL80211_ATTR_MLO_SUPPORT] = { .type = NLA_FLAG },
	[NL80211_ATTR_MAX_NUM_AKM_SUITES] = { .type = NLA_REJECT },
	[NL80211_ATTR_PUNCT_BITMAP] =
		NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
};

/* policy for the key attributes */
static const struct nla_policy nl80211_key_policy[NL80211_KEY_MAX + 1] = {
	[NL80211_KEY_DATA] = { .type = NLA_BINARY, .len = WLAN_MAX_KEY_LEN },
	[NL80211_KEY_IDX] = { .type = NLA_U8 },
	[NL80211_KEY_CIPHER] = { .type = NLA_U32 },
	[NL80211_KEY_SEQ] = { .type = NLA_BINARY, .len = 16 },
	[NL80211_KEY_DEFAULT] = { .type = NLA_FLAG },
	[NL80211_KEY_DEFAULT_MGMT] = { .type = NLA_FLAG },
	[NL80211_KEY_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_KEYTYPES - 1),
	[NL80211_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED },
	[NL80211_KEY_MODE] = NLA_POLICY_RANGE(NLA_U8, 0, NL80211_KEY_SET_TX),
};

/* policy for the key default flags */
static const struct nla_policy
nl80211_key_default_policy[NUM_NL80211_KEY_DEFAULT_TYPES] = {
	[NL80211_KEY_DEFAULT_TYPE_UNICAST] = { .type = NLA_FLAG },
	[NL80211_KEY_DEFAULT_TYPE_MULTICAST] = { .type = NLA_FLAG },
};

#ifdef CONFIG_PM
/* policy for WoWLAN attributes */
static const struct nla_policy
nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = {
	[NL80211_WOWLAN_TRIG_ANY] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_DISCONNECT] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_MAGIC_PKT] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .type = NLA_NESTED },
	[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG },
	[NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED },
	[NL80211_WOWLAN_TRIG_NET_DETECT] = { .type = NLA_NESTED },
};

static const struct nla_policy
nl80211_wowlan_tcp_policy[NUM_NL80211_WOWLAN_TCP] = {
	[NL80211_WOWLAN_TCP_SRC_IPV4] = { .type = NLA_U32 },
	[NL80211_WOWLAN_TCP_DST_IPV4] = { .type = NLA_U32 },
	[NL80211_WOWLAN_TCP_DST_MAC] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_WOWLAN_TCP_SRC_PORT] = { .type = NLA_U16 },
	[NL80211_WOWLAN_TCP_DST_PORT] = { .type = NLA_U16 },
	[NL80211_WOWLAN_TCP_DATA_PAYLOAD] = NLA_POLICY_MIN_LEN(1),
	[NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ] = {
		.len = sizeof(struct nl80211_wowlan_tcp_data_seq)
	},
	[NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN] = {
		.len = sizeof(struct nl80211_wowlan_tcp_data_token)
	},
	[NL80211_WOWLAN_TCP_DATA_INTERVAL] = { .type = NLA_U32 },
	[NL80211_WOWLAN_TCP_WAKE_PAYLOAD] = NLA_POLICY_MIN_LEN(1),
	[NL80211_WOWLAN_TCP_WAKE_MASK] = NLA_POLICY_MIN_LEN(1),
};
#endif /* CONFIG_PM */

/* policy for coalesce rule attributes */
static const struct nla_policy
nl80211_coalesce_policy[NUM_NL80211_ATTR_COALESCE_RULE] = {
	[NL80211_ATTR_COALESCE_RULE_DELAY] = { .type = NLA_U32 },
	[NL80211_ATTR_COALESCE_RULE_CONDITION] =
		NLA_POLICY_RANGE(NLA_U32,
				 NL80211_COALESCE_CONDITION_MATCH,
				 NL80211_COALESCE_CONDITION_NO_MATCH),
	[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN] = { .type = NLA_NESTED },
};

/* policy for GTK rekey offload attributes */
static const struct nla_policy
nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
	[NL80211_REKEY_DATA_KEK] = {
		.type = NLA_BINARY,
		.len = NL80211_KEK_EXT_LEN
	},
	[NL80211_REKEY_DATA_KCK] = {
		.type = NLA_BINARY,
		.len = NL80211_KCK_EXT_LEN_32
	},
	[NL80211_REKEY_DATA_REPLAY_CTR] = NLA_POLICY_EXACT_LEN(NL80211_REPLAY_CTR_LEN),
	[NL80211_REKEY_DATA_AKM] = { .type = NLA_U32 },
};

static const struct nla_policy
nl80211_match_band_rssi_policy[NUM_NL80211_BANDS] = {
	[NL80211_BAND_2GHZ] = { .type = NLA_S32 },
	[NL80211_BAND_5GHZ] = { .type = NLA_S32 },
	[NL80211_BAND_6GHZ] = { .type = NLA_S32 },
	[NL80211_BAND_60GHZ] = { .type = NLA_S32 },
	[NL80211_BAND_LC]    = { .type = NLA_S32 },
};

static const struct nla_policy
nl80211_match_policy[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1] = {
	[NL80211_SCHED_SCAN_MATCH_ATTR_SSID] = { .type = NLA_BINARY,
						 .len = IEEE80211_MAX_SSID_LEN },
	[NL80211_SCHED_SCAN_MATCH_ATTR_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI] = { .type = NLA_U32 },
	[NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI] =
		NLA_POLICY_NESTED(nl80211_match_band_rssi_policy),
};

static const struct nla_policy
nl80211_plan_policy[NL80211_SCHED_SCAN_PLAN_MAX + 1] = {
	[NL80211_SCHED_SCAN_PLAN_INTERVAL] = { .type = NLA_U32 },
	[NL80211_SCHED_SCAN_PLAN_ITERATIONS] = { .type = NLA_U32 },
};

static const struct nla_policy
nl80211_bss_select_policy[NL80211_BSS_SELECT_ATTR_MAX + 1] = {
	[NL80211_BSS_SELECT_ATTR_RSSI] = { .type = NLA_FLAG },
	[NL80211_BSS_SELECT_ATTR_BAND_PREF] = { .type = NLA_U32 },
	[NL80211_BSS_SELECT_ATTR_RSSI_ADJUST] = {
		.len = sizeof(struct nl80211_bss_select_rssi_adjust)
	},
};

/* policy for NAN function attributes */
static const struct nla_policy
nl80211_nan_func_policy[NL80211_NAN_FUNC_ATTR_MAX + 1] = {
	[NL80211_NAN_FUNC_TYPE] =
		NLA_POLICY_MAX(NLA_U8, NL80211_NAN_FUNC_MAX_TYPE),
	[NL80211_NAN_FUNC_SERVICE_ID] = {
				    .len = NL80211_NAN_FUNC_SERVICE_ID_LEN },
	[NL80211_NAN_FUNC_PUBLISH_TYPE] = { .type = NLA_U8 },
	[NL80211_NAN_FUNC_PUBLISH_BCAST] = { .type = NLA_FLAG },
	[NL80211_NAN_FUNC_SUBSCRIBE_ACTIVE] = { .type = NLA_FLAG },
	[NL80211_NAN_FUNC_FOLLOW_UP_ID] = { .type = NLA_U8 },
	[NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID] = { .type = NLA_U8 },
	[NL80211_NAN_FUNC_FOLLOW_UP_DEST] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
	[NL80211_NAN_FUNC_CLOSE_RANGE] = { .type = NLA_FLAG },
	[NL80211_NAN_FUNC_TTL] = { .type = NLA_U32 },
	[NL80211_NAN_FUNC_SERVICE_INFO] = { .type = NLA_BINARY,
			.len = NL80211_NAN_FUNC_SERVICE_SPEC_INFO_MAX_LEN },
	[NL80211_NAN_FUNC_SRF] = { .type = NLA_NESTED },
	[NL80211_NAN_FUNC_RX_MATCH_FILTER] = { .type = NLA_NESTED },
	[NL80211_NAN_FUNC_TX_MATCH_FILTER] = { .type = NLA_NESTED },
	[NL80211_NAN_FUNC_INSTANCE_ID] = { .type = NLA_U8 },
	[NL80211_NAN_FUNC_TERM_REASON] = { .type = NLA_U8 },
};

/* policy for Service Response Filter attributes */
static const struct nla_policy
nl80211_nan_srf_policy[NL80211_NAN_SRF_ATTR_MAX + 1] = {
	[NL80211_NAN_SRF_INCLUDE] = { .type = NLA_FLAG },
	[NL80211_NAN_SRF_BF] = { .type = NLA_BINARY,
				 .len =  NL80211_NAN_FUNC_SRF_MAX_LEN },
	[NL80211_NAN_SRF_BF_IDX] = { .type = NLA_U8 },
	[NL80211_NAN_SRF_MAC_ADDRS] = { .type = NLA_NESTED },
};

/* policy for packet pattern attributes */
static const struct nla_policy
nl80211_packet_pattern_policy[MAX_NL80211_PKTPAT + 1] = {
	[NL80211_PKTPAT_MASK] = { .type = NLA_BINARY, },
	[NL80211_PKTPAT_PATTERN] = { .type = NLA_BINARY, },
	[NL80211_PKTPAT_OFFSET] = { .type = NLA_U32 },
};

static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
				     struct cfg80211_registered_device **rdev,
				     struct wireless_dev **wdev,
				     struct nlattr **attrbuf)
{
	int err;

	if (!cb->args[0]) {
		struct nlattr **attrbuf_free = NULL;

		if (!attrbuf) {
			attrbuf = kcalloc(NUM_NL80211_ATTR, sizeof(*attrbuf),
					  GFP_KERNEL);
			if (!attrbuf)
				return -ENOMEM;
			attrbuf_free = attrbuf;
		}

		err = nlmsg_parse_deprecated(cb->nlh,
					     GENL_HDRLEN + nl80211_fam.hdrsize,
					     attrbuf, nl80211_fam.maxattr,
					     nl80211_policy, NULL);
		if (err) {
			kfree(attrbuf_free);
			return err;
		}

		rtnl_lock();
		*wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb->skb->sk),
						   attrbuf);
		kfree(attrbuf_free);
		if (IS_ERR(*wdev)) {
			rtnl_unlock();
			return PTR_ERR(*wdev);
		}
		*rdev = wiphy_to_rdev((*wdev)->wiphy);
		mutex_lock(&(*rdev)->wiphy.mtx);
		rtnl_unlock();
		/* 0 is the first index - add 1 to parse only once */
		cb->args[0] = (*rdev)->wiphy_idx + 1;
		cb->args[1] = (*wdev)->identifier;
	} else {
		/* subtract the 1 again here */
		struct wiphy *wiphy;
		struct wireless_dev *tmp;

		rtnl_lock();
		wiphy = wiphy_idx_to_wiphy(cb->args[0] - 1);
		if (!wiphy) {
			rtnl_unlock();
			return -ENODEV;
		}
		*rdev = wiphy_to_rdev(wiphy);
		*wdev = NULL;

		list_for_each_entry(tmp, &(*rdev)->wiphy.wdev_list, list) {
			if (tmp->identifier == cb->args[1]) {
				*wdev = tmp;
				break;
			}
		}

		if (!*wdev) {
			rtnl_unlock();
			return -ENODEV;
		}
		mutex_lock(&(*rdev)->wiphy.mtx);
		rtnl_unlock();
	}

	return 0;
}

/* message building helper */
void *nl80211hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
		     int flags, u8 cmd)
{
	/* since there is no private header just add the generic one */
	return genlmsg_put(skb, portid, seq, &nl80211_fam, flags, cmd);
}

static int nl80211_msg_put_wmm_rules(struct sk_buff *msg,
				     const struct ieee80211_reg_rule *rule)
{
	int j;
	struct nlattr *nl_wmm_rules =
		nla_nest_start_noflag(msg, NL80211_FREQUENCY_ATTR_WMM);

	if (!nl_wmm_rules)
		goto nla_put_failure;

	for (j = 0; j < IEEE80211_NUM_ACS; j++) {
		struct nlattr *nl_wmm_rule = nla_nest_start_noflag(msg, j);

		if (!nl_wmm_rule)
			goto nla_put_failure;

		if (nla_put_u16(msg, NL80211_WMMR_CW_MIN,
				rule->wmm_rule.client[j].cw_min) ||
		    nla_put_u16(msg, NL80211_WMMR_CW_MAX,
				rule->wmm_rule.client[j].cw_max) ||
		    nla_put_u8(msg, NL80211_WMMR_AIFSN,
			       rule->wmm_rule.client[j].aifsn) ||
		    nla_put_u16(msg, NL80211_WMMR_TXOP,
			        rule->wmm_rule.client[j].cot))
			goto nla_put_failure;

		nla_nest_end(msg, nl_wmm_rule);
	}
	nla_nest_end(msg, nl_wmm_rules);

	return 0;

nla_put_failure:
	return -ENOBUFS;
}

static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
				   struct ieee80211_channel *chan,
				   bool large)
{
	/* Some channels must be completely excluded from the
	 * list to protect old user-space tools from breaking
	 */
	if (!large && chan->flags &
	    (IEEE80211_CHAN_NO_10MHZ | IEEE80211_CHAN_NO_20MHZ))
		return 0;
	if (!large && chan->freq_offset)
		return 0;

	if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_FREQ,
			chan->center_freq))
		goto nla_put_failure;

	if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_OFFSET, chan->freq_offset))
		goto nla_put_failure;

	if ((chan->flags & IEEE80211_CHAN_DISABLED) &&
	    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_DISABLED))
		goto nla_put_failure;
	if (chan->flags & IEEE80211_CHAN_NO_IR) {
		if (nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_IR))
			goto nla_put_failure;
		if (nla_put_flag(msg, __NL80211_FREQUENCY_ATTR_NO_IBSS))
			goto nla_put_failure;
	}
	if (chan->flags & IEEE80211_CHAN_RADAR) {
		if (nla_put_flag(msg, NL80211_FREQUENCY_ATTR_RADAR))
			goto nla_put_failure;
		if (large) {
			u32 time;

			time = elapsed_jiffies_msecs(chan->dfs_state_entered);

			if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_STATE,
					chan->dfs_state))
				goto nla_put_failure;
			if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_TIME,
					time))
				goto nla_put_failure;
			if (nla_put_u32(msg,
					NL80211_FREQUENCY_ATTR_DFS_CAC_TIME,
					chan->dfs_cac_ms))
				goto nla_put_failure;
		}
	}

	if (large) {
		if ((chan->flags & IEEE80211_CHAN_NO_HT40MINUS) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_MINUS))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_HT40PLUS) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_PLUS))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_80MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_80MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_160MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_160MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_INDOOR_ONLY) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_INDOOR_ONLY))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_IR_CONCURRENT) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_IR_CONCURRENT))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_20MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_20MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_10MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_10MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_HE) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HE))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_1MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_1MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_2MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_2MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_4MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_4MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_8MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_8MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_16MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_16MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_320MHZ) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_320MHZ))
			goto nla_put_failure;
		if ((chan->flags & IEEE80211_CHAN_NO_EHT) &&
		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_EHT))
			goto nla_put_failure;
	}

	if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
			DBM_TO_MBM(chan->max_power)))
		goto nla_put_failure;

	if (large) {
		const struct ieee80211_reg_rule *rule =
			freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));

		if (!IS_ERR_OR_NULL(rule) && rule->has_wmm) {
			if (nl80211_msg_put_wmm_rules(msg, rule))
				goto nla_put_failure;
		}
	}

	return 0;

 nla_put_failure:
	return -ENOBUFS;
}

static bool nl80211_put_txq_stats(struct sk_buff *msg,
				  struct cfg80211_txq_stats *txqstats,
				  int attrtype)
{
	struct nlattr *txqattr;

#define PUT_TXQVAL_U32(attr, memb) do {					  \
	if (txqstats->filled & BIT(NL80211_TXQ_STATS_ ## attr) &&	  \
	    nla_put_u32(msg, NL80211_TXQ_STATS_ ## attr, txqstats->memb)) \
		return false;						  \
	} while (0)

	txqattr = nla_nest_start_noflag(msg, attrtype);
	if (!txqattr)
		return false;

	PUT_TXQVAL_U32(BACKLOG_BYTES, backlog_bytes);
	PUT_TXQVAL_U32(BACKLOG_PACKETS, backlog_packets);
	PUT_TXQVAL_U32(FLOWS, flows);
	PUT_TXQVAL_U32(DROPS, drops);
	PUT_TXQVAL_U32(ECN_MARKS, ecn_marks);
	PUT_TXQVAL_U32(OVERLIMIT, overlimit);
	PUT_TXQVAL_U32(OVERMEMORY, overmemory);
	PUT_TXQVAL_U32(COLLISIONS, collisions);
	PUT_TXQVAL_U32(TX_BYTES, tx_bytes);
	PUT_TXQVAL_U32(TX_PACKETS, tx_packets);
	PUT_TXQVAL_U32(MAX_FLOWS, max_flows);
	nla_nest_end(msg, txqattr);

#undef PUT_TXQVAL_U32
	return true;
}

/* netlink command implementations */

/**
 * nl80211_link_id - return link ID
 * @attrs: attributes to look at
 *
 * Returns: the link ID or 0 if not given
 *
 * Note this function doesn't do any validation of the link
 * ID validity wrt. links that were actually added, so it must
 * be called only from ops with %NL80211_FLAG_MLO_VALID_LINK_ID
 * or if additional validation is done.
 */
static unsigned int nl80211_link_id(struct nlattr **attrs)
{
	struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];

	if (!linkid)
		return 0;

	return nla_get_u8(linkid);
}

static int nl80211_link_id_or_invalid(struct nlattr **attrs)
{
	struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];

	if (!linkid)
		return -1;

	return nla_get_u8(linkid);
}

struct key_parse {
	struct key_params p;
	int idx;
	int type;
	bool def, defmgmt, defbeacon;
	bool def_uni, def_multi;
};

static int nl80211_parse_key_new(struct genl_info *info, struct nlattr *key,
				 struct key_parse *k)
{
	struct nlattr *tb[NL80211_KEY_MAX + 1];
	int err = nla_parse_nested_deprecated(tb, NL80211_KEY_MAX, key,
					      nl80211_key_policy,
					      info->extack);
	if (err)
		return err;

	k->def = !!tb[NL80211_KEY_DEFAULT];
	k->defmgmt = !!tb[NL80211_KEY_DEFAULT_MGMT];
	k->defbeacon = !!tb[NL80211_KEY_DEFAULT_BEACON];

	if (k->def) {
		k->def_uni = true;
		k->def_multi = true;
	}
	if (k->defmgmt || k->defbeacon)
		k->def_multi = true;

	if (tb[NL80211_KEY_IDX])
		k->idx = nla_get_u8(tb[NL80211_KEY_IDX]);

	if (tb[NL80211_KEY_DATA]) {
		k->p.key = nla_data(tb[NL80211_KEY_DATA]);
		k->p.key_len = nla_len(tb[NL80211_KEY_DATA]);
	}

	if (tb[NL80211_KEY_SEQ]) {
		k->p.seq = nla_data(tb[NL80211_KEY_SEQ]);
		k->p.seq_len = nla_len(tb[NL80211_KEY_SEQ]);
	}

	if (tb[NL80211_KEY_CIPHER])
		k->p.cipher = nla_get_u32(tb[NL80211_KEY_CIPHER]);

	if (tb[NL80211_KEY_TYPE])
		k->type = nla_get_u32(tb[NL80211_KEY_TYPE]);

	if (tb[NL80211_KEY_DEFAULT_TYPES]) {
		struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];

		err = nla_parse_nested_deprecated(kdt,
						  NUM_NL80211_KEY_DEFAULT_TYPES - 1,
						  tb[NL80211_KEY_DEFAULT_TYPES],
						  nl80211_key_default_policy,
						  info->extack);
		if (err)
			return err;

		k->def_uni = kdt[NL80211_KEY_DEFAULT_TYPE_UNICAST];
		k->def_multi = kdt[NL80211_KEY_DEFAULT_TYPE_MULTICAST];
	}

	if (tb[NL80211_KEY_MODE])
		k->p.mode = nla_get_u8(tb[NL80211_KEY_MODE]);

	return 0;
}

static int nl80211_parse_key_old(struct genl_info *info, struct key_parse *k)
{
	if (info->attrs[NL80211_ATTR_KEY_DATA]) {
		k->p.key = nla_data(info->attrs[NL80211_ATTR_KEY_DATA]);
		k->p.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]);
	}

	if (info->attrs[NL80211_ATTR_KEY_SEQ]) {
		k->p.seq = nla_data(info->attrs[NL80211_ATTR_KEY_SEQ]);
		k->p.seq_len = nla_len(info->attrs[NL80211_ATTR_KEY_SEQ]);
	}

	if (info->attrs[NL80211_ATTR_KEY_IDX])
		k->idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

	if (info->attrs[NL80211_ATTR_KEY_CIPHER])
		k->p.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]);

	k->def = !!info->attrs[NL80211_ATTR_KEY_DEFAULT];
	k->defmgmt = !!info->attrs[NL80211_ATTR_KEY_DEFAULT_MGMT];

	if (k->def) {
		k->def_uni = true;
		k->def_multi = true;
	}
	if (k->defmgmt)
		k->def_multi = true;

	if (info->attrs[NL80211_ATTR_KEY_TYPE])
		k->type = nla_get_u32(info->attrs[NL80211_ATTR_KEY_TYPE]);

	if (info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES]) {
		struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];
		int err = nla_parse_nested_deprecated(kdt,
						      NUM_NL80211_KEY_DEFAULT_TYPES - 1,
						      info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES],
						      nl80211_key_default_policy,
						      info->extack);
		if (err)
			return err;

		k->def_uni = kdt[NL80211_KEY_DEFAULT_TYPE_UNICAST];
		k->def_multi = kdt[NL80211_KEY_DEFAULT_TYPE_MULTICAST];
	}

	return 0;
}

static int nl80211_parse_key(struct genl_info *info, struct key_parse *k)
{
	int err;

	memset(k, 0, sizeof(*k));
	k->idx = -1;
	k->type = -1;

	if (info->attrs[NL80211_ATTR_KEY])
		err = nl80211_parse_key_new(info, info->attrs[NL80211_ATTR_KEY], k);
	else
		err = nl80211_parse_key_old(info, k);

	if (err)
		return err;

	if ((k->def ? 1 : 0) + (k->defmgmt ? 1 : 0) +
	    (k->defbeacon ? 1 : 0) > 1) {
		GENL_SET_ERR_MSG(info,
				 "key with multiple default flags is invalid");
		return -EINVAL;
	}

	if (k->defmgmt || k->defbeacon) {
		if (k->def_uni || !k->def_multi) {
			GENL_SET_ERR_MSG(info,
					 "defmgmt/defbeacon key must be mcast");
			return -EINVAL;
		}
	}

	if (k->idx != -1) {
		if (k->defmgmt) {
			if (k->idx < 4 || k->idx > 5) {
				GENL_SET_ERR_MSG(info,
						 "defmgmt key idx not 4 or 5");
				return -EINVAL;
			}
		} else if (k->defbeacon) {
			if (k->idx < 6 || k->idx > 7) {
				GENL_SET_ERR_MSG(info,
						 "defbeacon key idx not 6 or 7");
				return -EINVAL;
			}
		} else if (k->def) {
			if (k->idx < 0 || k->idx > 3) {
				GENL_SET_ERR_MSG(info, "def key idx not 0-3");
				return -EINVAL;
			}
		} else {
			if (k->idx < 0 || k->idx > 7) {
				GENL_SET_ERR_MSG(info, "key idx not 0-7");
				return -EINVAL;
			}
		}
	}

	return 0;
}

static struct cfg80211_cached_keys *
nl80211_parse_connkeys(struct cfg80211_registered_device *rdev,
		       struct genl_info *info, bool *no_ht)
{
	struct nlattr *keys = info->attrs[NL80211_ATTR_KEYS];
	struct key_parse parse;
	struct nlattr *key;
	struct cfg80211_cached_keys *result;
	int rem, err, def = 0;
	bool have_key = false;

	nla_for_each_nested(key, keys, rem) {
		have_key = true;
		break;
	}

	if (!have_key)
		return NULL;

	result = kzalloc(sizeof(*result), GFP_KERNEL);
	if (!result)
		return ERR_PTR(-ENOMEM);

	result->def = -1;

	nla_for_each_nested(key, keys, rem) {
		memset(&parse, 0, sizeof(parse));
		parse.idx = -1;

		err = nl80211_parse_key_new(info, key, &parse);
		if (err)
			goto error;
		err = -EINVAL;
		if (!parse.p.key)
			goto error;
		if (parse.idx < 0 || parse.idx > 3) {
			GENL_SET_ERR_MSG(info, "key index out of range [0-3]");
			goto error;
		}
		if (parse.def) {
			if (def) {
				GENL_SET_ERR_MSG(info,
						 "only one key can be default");
				goto error;
			}
			def = 1;
			result->def = parse.idx;
			if (!parse.def_uni || !parse.def_multi)
				goto error;
		} else if (parse.defmgmt)
			goto error;
		err = cfg80211_validate_key_settings(rdev, &parse.p,
						     parse.idx, false, NULL);
		if (err)
			goto error;
		if (parse.p.cipher != WLAN_CIPHER_SUITE_WEP40 &&
		    parse.p.cipher != WLAN_CIPHER_SUITE_WEP104) {
			GENL_SET_ERR_MSG(info, "connect key must be WEP");
			err = -EINVAL;
			goto error;
		}
		result->params[parse.idx].cipher = parse.p.cipher;
		result->params[parse.idx].key_len = parse.p.key_len;
		result->params[parse.idx].key = result->data[parse.idx];
		memcpy(result->data[parse.idx], parse.p.key, parse.p.key_len);

		/* must be WEP key if we got here */
		if (no_ht)
			*no_ht = true;
	}

	if (result->def < 0) {
		err = -EINVAL;
		GENL_SET_ERR_MSG(info, "need a default/TX key");
		goto error;
	}

	return result;
 error:
	kfree(result);
	return ERR_PTR(err);
}

static int nl80211_key_allowed(struct wireless_dev *wdev)
{
	ASSERT_WDEV_LOCK(wdev);

	switch (wdev->iftype) {
	case NL80211_IFTYPE_AP:
	case NL80211_IFTYPE_AP_VLAN:
	case NL80211_IFTYPE_P2P_GO:
	case NL80211_IFTYPE_MESH_POINT:
		break;
	case NL80211_IFTYPE_ADHOC:
		if (wdev->u.ibss.current_bss)
			return 0;
		return -ENOLINK;
	case NL80211_IFTYPE_STATION:
	case NL80211_IFTYPE_P2P_CLIENT:
		if (wdev->connected)
			return 0;
		return -ENOLINK;
	case NL80211_IFTYPE_NAN:
		if (wiphy_ext_feature_isset(wdev->wiphy,
					    NL80211_EXT_FEATURE_SECURE_NAN))
			return 0;
		return -EINVAL;
	case NL80211_IFTYPE_UNSPECIFIED:
	case NL80211_IFTYPE_OCB:
	case NL80211_IFTYPE_MONITOR:
	case NL80211_IFTYPE_P2P_DEVICE:
	case NL80211_IFTYPE_WDS:
	case NUM_NL80211_IFTYPES:
		return -EINVAL;
	}

	return 0;
}

static struct ieee80211_channel *nl80211_get_valid_chan(struct wiphy *wiphy,
							u32 freq)
{
	struct ieee80211_channel *chan;

	chan = ieee80211_get_channel_khz(wiphy, freq);
	if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
		return NULL;
	return chan;
}

static int nl80211_put_iftypes(struct sk_buff *msg, u32 attr, u16 ifmodes)
{
	struct nlattr *nl_modes = nla_nest_start_noflag(msg, attr);
	int i;

	if (!nl_modes)
		goto nla_put_failure;

	i = 0;
	while (ifmodes) {
		if ((ifmodes & 1) && nla_put_flag(msg, i))
			goto nla_put_failure;
		ifmodes >>= 1;
		i++;
	}

	nla_nest_end(msg, nl_modes);
	return 0;

nla_put_failure:
	return -ENOBUFS;
}

static int nl80211_put_iface_combinations(struct wiphy *wiphy,
					  struct sk_buff *msg,
					  bool large)
{
	struct nlattr *nl_combis;
	int i, j;

	nl_combis = nla_nest_start_noflag(msg,
					  NL80211_ATTR_INTERFACE_COMBINATIONS);
	if (!nl_combis)
		goto nla_put_failure;

	for (i = 0; i < wiphy->n_iface_combinations; i++) {
		const struct ieee80211_iface_combination *c;
		struct nlattr *nl_combi, *nl_limits;

		c = &wiphy->iface_combinations[i];

		nl_combi = nla_nest_start_noflag(msg, i + 1);
		if (!nl_combi)
			goto nla_put_failure;

		nl_limits = nla_nest_start_noflag(msg,
						  NL80211_IFACE_COMB_LIMITS);
		if (!nl_limits)
			goto nla_put_failure;

		for (j = 0; j < c->n_limits; j++) {
			struct nlattr *nl_limit;

			nl_limit = nla_nest_start_noflag(msg, j + 1);
			if (!nl_limit)
				goto nla_put_failure;
			if (nla_put_u32(msg, NL80211_IFACE_LIMIT_MAX,
					c->limits[j].max))
				goto nla_put_failure;
			if (nl80211_put_iftypes(msg, NL80211_IFACE_LIMIT_TYPES,
						c->limits[j].types))
				goto nla_put_failure;
			nla_nest_end(msg, nl_limit);
		}

		nla_nest_end(msg, nl_limits);

		if (c->beacon_int_infra_match &&
		    nla_put_flag(msg, NL80211_IFACE_COMB_STA_AP_BI_MATCH))
			goto nla_put_failure;
		if (nla_put_u32(msg, NL80211_IFACE_COMB_NUM_CHANNELS,
				c->num_different_channels) ||
		    nla_put_u32(msg, NL80211_IFACE_COMB_MAXNUM,
				c->max_interfaces))
			goto nla_put_failure;
		if (large &&
		    (nla_put_u32(msg, NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS,
				c->radar_detect_widths) ||
		     nla_put_u32(msg, NL80211_IFACE_COMB_RADAR_DETECT_REGIONS,
				c->radar_detect_regions)))
			goto nla_put_failure;
		if (c->beacon_int_min_gcd &&
		    nla_put_u32(msg, NL80211_IFACE_COMB_BI_MIN_GCD,
				c->beacon_int_min_gcd))
			goto nla_put_failure;

		nla_nest_end(msg, nl_combi);
	}

	nla_nest_end(msg, nl_combis);

	return 0;
nla_put_failure:
	return -ENOBUFS;
}

#ifdef CONFIG_PM
static int nl80211_send_wowlan_tcp_caps(struct cfg80211_registered_device *rdev,
					struct sk_buff *msg)
{
	const struct wiphy_wowlan_tcp_support *tcp = rdev->wiphy.wowlan->tcp;
	struct nlattr *nl_tcp;

	if (!tcp)
		return 0;

	nl_tcp = nla_nest_start_noflag(msg,
				       NL80211_WOWLAN_TRIG_TCP_CONNECTION);
	if (!nl_tcp)
		return -ENOBUFS;

	if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
			tcp->data_payload_max))
		return -ENOBUFS;

	if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
			tcp->data_payload_max))
		return -ENOBUFS;

	if (tcp->seq && nla_put_flag(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ))
		return -ENOBUFS;

	if (tcp->tok && nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
				sizeof(*tcp->tok), tcp->tok))
		return -ENOBUFS;

	if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL,
			tcp->data_interval_max))
		return -ENOBUFS;

	if (nla_put_u32(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD,
			tcp->wake_payload_max))
		return -ENOBUFS;

	nla_nest_end(msg, nl_tcp);
	return 0;
}

static int nl80211_send_wowlan(struct sk_buff *msg,
			       struct cfg80211_registered_device *rdev,
			       bool large)
{
	struct nlattr *nl_wowlan;

	if (!rdev->wiphy.wowlan)
		return 0;

	nl_wowlan = nla_nest_start_noflag(msg,
					  NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED);
	if (!nl_wowlan)
		return -ENOBUFS;

	if (((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_ANY) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_ANY)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_DISCONNECT) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_DISCONNECT)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_MAGIC_PKT) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_SUPPORTS_GTK_REKEY) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_4WAY_HANDSHAKE) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE)) ||
	    ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_RFKILL_RELEASE) &&
	     nla_put_flag(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE)))
		return -ENOBUFS;

	if (rdev->wiphy.wowlan->n_patterns) {
		struct nl80211_pattern_support pat = {
			.max_patterns = rdev->wiphy.wowlan->n_patterns,
			.min_pattern_len = rdev->wiphy.wowlan->pattern_min_len,
			.max_pattern_len = rdev->wiphy.wowlan->pattern_max_len,
			.max_pkt_offset = rdev->wiphy.wowlan->max_pkt_offset,
		};

		if (nla_put(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN,
			    sizeof(pat), &pat))
			return -ENOBUFS;
	}

	if ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_NET_DETECT) &&
	    nla_put_u32(msg, NL80211_WOWLAN_TRIG_NET_DETECT,
			rdev->wiphy.wowlan->max_nd_match_sets))
		return -ENOBUFS;

	if (large && nl80211_send_wowlan_tcp_caps(rdev, msg))
		return -ENOBUFS;

	nla_nest_end(msg, nl_wowlan);

	return 0;
}
#endif

static int nl80211_send_coalesce(struct sk_buff *msg,
				 struct cfg80211_registered_device *rdev)
{
	struct nl80211_coalesce_rule_support rule;

	if (!rdev->wiphy.coalesce)
		return 0;

	rule.max_rules = rdev->wiphy.coalesce->n_rules;
	rule.max_delay = rdev->wiphy.coalesce->max_delay;
	rule.pat.max_patterns = rdev->wiphy.coalesce->n_patterns;
	rule.pat.min_pattern_len = rdev->wiphy.coalesce->pattern_min_len;
	rule.pat.max_pattern_len = rdev->wiphy.coalesce->pattern_max_len;
	rule.pat.max_pkt_offset = rdev->wiphy.coalesce->max_pkt_offset;

	if (nla_put(msg, NL80211_ATTR_COALESCE_RULE, sizeof(rule), &rule))
		return -ENOBUFS;

	return 0;
}

static int
nl80211_send_iftype_data(struct sk_buff *msg,
			 const struct ieee80211_supported_band *sband,
			 const struct ieee80211_sband_iftype_data *iftdata)
{
	const struct ieee80211_sta_he_cap *he_cap = &iftdata->he_cap;
	const struct ieee80211_sta_eht_cap *eht_cap = &iftdata->eht_cap;

	if (nl80211_put_iftypes(msg, NL80211_BAND_IFTYPE_ATTR_IFTYPES,
				iftdata->types_mask))
		return -ENOBUFS;

	if (he_cap->has_he) {
		if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC,
			    sizeof(he_cap->he_cap_elem.mac_cap_info),
			    he_cap->he_cap_elem.mac_cap_info) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY,
			    sizeof(he_cap->he_cap_elem.phy_cap_info),
			    he_cap->he_cap_elem.phy_cap_info) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_MCS_SET,
			    sizeof(he_cap->he_mcs_nss_supp),
			    &he_cap->he_mcs_nss_supp) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE,
			    sizeof(he_cap->ppe_thres), he_cap->ppe_thres))
			return -ENOBUFS;
	}

	if (eht_cap->has_eht && he_cap->has_he) {
		u8 mcs_nss_size, ppe_thresh_size;
		u16 ppe_thres_hdr;
		bool is_ap;

		is_ap = iftdata->types_mask & BIT(NL80211_IFTYPE_AP) ||
			iftdata->types_mask & BIT(NL80211_IFTYPE_P2P_GO);

		mcs_nss_size =
			ieee80211_eht_mcs_nss_size(&he_cap->he_cap_elem,
						   &eht_cap->eht_cap_elem,
						   is_ap);

		ppe_thres_hdr = get_unaligned_le16(&eht_cap->eht_ppe_thres[0]);
		ppe_thresh_size =
			ieee80211_eht_ppe_size(ppe_thres_hdr,
					       eht_cap->eht_cap_elem.phy_cap_info);

		if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC,
			    sizeof(eht_cap->eht_cap_elem.mac_cap_info),
			    eht_cap->eht_cap_elem.mac_cap_info) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PHY,
			    sizeof(eht_cap->eht_cap_elem.phy_cap_info),
			    eht_cap->eht_cap_elem.phy_cap_info) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MCS_SET,
			    mcs_nss_size, &eht_cap->eht_mcs_nss_supp) ||
		    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE,
			    ppe_thresh_size, eht_cap->eht_ppe_thres))
			return -ENOBUFS;
	}

	if (sband->band == NL80211_BAND_6GHZ &&
	    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA,
		    sizeof(iftdata->he_6ghz_capa),
		    &iftdata->he_6ghz_capa))
		return -ENOBUFS;

	if (iftdata->vendor_elems.data && iftdata->vendor_elems.len &&
	    nla_put(msg, NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS,
		    iftdata->vendor_elems.len, iftdata->vendor_elems.data))
		return -ENOBUFS;

	return 0;
}

static int nl80211_send_band_rateinfo(struct sk_buff *msg,
				      struct ieee80211_supported_band *sband,
				      bool large)
{
	struct nlattr *nl_rates, *nl_rate;
	struct ieee80211_rate *rate;
	int i;

	/* add HT info */
	if (sband->ht_cap.ht_supported &&
	    (nla_put(msg, NL80211_BAND_ATTR_HT_MCS_SET,
		     sizeof(sband->ht_cap.mcs),
		     &sband->ht_cap.mcs) ||
	     nla_put_u16(msg, NL80211_BAND_ATTR_HT_CAPA,
			 sband->ht_cap.cap) ||
	     nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_FACTOR,
			sband->ht_cap.ampdu_factor) ||
	     nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_DENSITY,
			sband->ht_cap.ampdu_density)))
		return -ENOBUFS;

	/* add VHT info */
	if (sband->vht_cap.vht_supported &&
	    (nla_put(msg, NL80211_BAND_ATTR_VHT_MCS_SET,
		     sizeof(sband->vht_cap.vht_mcs),
		     &sband->vht_cap.vht_mcs) ||
	     nla_put_u32(msg, NL80211_BAND_ATTR_VHT_CAPA,
			 sband->vht_cap.cap)))
		return -ENOBUFS;

	if (large && sband->n_iftype_data) {
		struct nlattr *nl_iftype_data =
			nla_nest_start_noflag(msg,
					      NL80211_BAND_ATTR_IFTYPE_DATA);
		int err;

		if (!nl_iftype_data)
			return -ENOBUFS;

		for (i = 0; i < sband->n_iftype_data; i++) {
			struct nlattr *iftdata;

			iftdata = nla_nest_start_noflag(msg, i + 1);
			if (!iftdata)
				return -ENOBUFS;

			err = nl80211_send_iftype_data(msg, sband,
						       &sband->iftype_data[i]);
			if (err)
				return err;

			nla_nest_end(msg, iftdata);
		}

		nla_nest_end(msg, nl_iftype_data);
	}

	/* add EDMG info */
	if (large && sband->edmg_cap.channels &&
	    (nla_put_u8(msg, NL80211_BAND_ATTR_EDMG_CHANNELS,
		       sband->edmg_cap.channels) ||
	    nla_put_u8(msg, NL80211_BAND_ATTR_EDMG_BW_CONFIG,
		       sband->edmg_cap.bw_config)))

		return -ENOBUFS;

	/* add bitrates */
	nl_rates = nla_nest_start_noflag(msg, NL80211_BAND_ATTR_RATES);
	if (!nl_rates)
		return -ENOBUFS;

	for (i = 0; i < sband->n_bitrates; i++) {
		nl_rate = nla_nest_start_noflag(msg, i);
		if (!nl_rate)
			return -ENOBUFS;

		rate = &sband->bitrates[i];
		if (nla_put_u32(msg, NL80211_BITRATE_ATTR_RATE,
				rate->bitrate))
			return -ENOBUFS;
		if ((rate->flags & IEEE80211_RATE_SHORT_PREAMBLE) &&
		    nla_put_flag(msg,
				 NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE))
			return -ENOBUFS;

		nla_nest_end(msg, nl_rate);
	}

	nla_nest_end(msg, nl_rates);

	return 0;
}

static int
nl80211_send_mgmt_stypes(struct sk_buff *msg,
			 const struct ieee80211_txrx_stypes *mgmt_stypes)
{
	u16 stypes;
	struct nlattr *nl_ftypes, *nl_ifs;
	enum nl80211_iftype ift;
	int i;

	if (!mgmt_stypes)
		return 0;

	nl_ifs = nla_nest_start_noflag(msg, NL80211_ATTR_TX_FRAME_TYPES);
	if (!nl_ifs)
		return -ENOBUFS;

	for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
		nl_ftypes = nla_nest_start_noflag(msg, ift);
		if (!nl_ftypes)
			return -ENOBUFS;
		i = 0;
		stypes = mgmt_stypes[ift].tx;
		while (stypes) {
			if ((stypes & 1) &&
			    nla_put_u16(msg, NL80211_ATTR_FRAME_TYPE,
					(i << 4) | IEEE80211_FTYPE_MGMT))
				return -ENOBUFS;
			stypes >>= 1;
			i++;
		}
		nla_nest_end(msg, nl_ftypes);
	}

	nla_nest_end(msg, nl_ifs);

	nl_ifs = nla_nest_start_noflag(msg, NL80211_ATTR_RX_FRAME_TYPES);
	if (!nl_ifs)
		return -ENOBUFS;

	for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
		nl_ftypes = nla_nest_start_noflag(msg, ift);
		if (!nl_ftypes)
			return -ENOBUFS;
		i = 0;
		stypes = mgmt_stypes[ift].rx;
		while (stypes) {
			if ((stypes & 1) &&
			    nla_put_u16(msg, NL80211_ATTR_FRAME_TYPE,
					(i << 4) | IEEE80211_FTYPE_MGMT))
				return -ENOBUFS;
			stypes >>= 1;
			i++;
		}
		nla_nest_end(msg, nl_ftypes);
	}
	nla_nest_end(msg, nl_ifs);

	return 0;
}

#define CMD(op, n)							\
	 do {								\
		if (rdev->ops->op) {					\
			i++;						\
			if (nla_put_u32(msg, i, NL80211_CMD_ ## n)) 	\
				goto nla_put_failure;			\
		}							\
	} while (0)

static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev,
					struct sk_buff *msg)
{
	int i = 0;

	/*
	 * do *NOT* add anything into this function, new things need to be
	 * advertised only to new versions of userspace that can deal with
	 * the split (and they can't possibly care about new features...
	 */
	CMD(add_virtual_intf, NEW_INTERFACE);
	CMD(change_virtual_intf, SET_INTERFACE);
	CMD(add_key, NEW_KEY);
	CMD(start_ap, START_AP);
	CMD(add_station, NEW_STATION);
	CMD(add_mpath, NEW_MPATH);
	CMD(update_mesh_config, SET_MESH_CONFIG);
	CMD(change_bss, SET_BSS);
	CMD(auth, AUTHENTICATE);
	CMD(assoc, ASSOCIATE);
	CMD(deauth, DEAUTHENTICATE);
	CMD(disassoc, DISASSOCIATE);
	CMD(join_ibss, JOIN_IBSS);
	CMD(join_mesh, JOIN_MESH);
	CMD(set_pmksa, SET_PMKSA);
	CMD(del_pmksa, DEL_PMKSA);
	CMD(flush_pmksa, FLUSH_PMKSA);
	if (rdev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL)
		CMD(remain_on_channel, REMAIN_ON_CHANNEL);
	CMD(set_bitrate_mask, SET_TX_BITRATE_MASK);
	CMD(mgmt_tx, FRAME);
	CMD(mgmt_tx_cancel_wait, FRAME_WAIT_CANCEL);
	if (rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
		i++;
		if (nla_put_u32(msg, i, NL80211_CMD_SET_WIPHY_NETNS))
			goto nla_put_failure;
	}
	if (rdev->ops->set_monitor_channel || rdev->ops->start_ap ||
	    rdev->ops->join_mesh) {
		i++;
		if (nla_put_u32(msg, i, NL80211_CMD_SET_CHANNEL))
			goto nla_put_failure;
	}
	if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) {
		CMD(tdls_mgmt, TDLS_MGMT);
		CMD(tdls_oper, TDLS_OPER);
	}
	if (rdev->wiphy.max_sched_scan_reqs)
		CMD(sched_scan_start, START_SCHED_SCAN);
	CMD(probe_client, PROBE_CLIENT);
	CMD(set_noack_map, SET_NOACK_MAP);
	if (rdev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) {
		i++;
		if (nla_put_u32(msg, i, NL80211_CMD_REGISTER_BEACONS))
			goto nla_put_failure;
	}
	CMD(start_p2p_device, START_P2P_DEVICE);
	CMD(set_mcast_rate, SET_MCAST_RATE);
#ifdef CONFIG_NL80211_TESTMODE
	CMD(testmode_cmd, TESTMODE);
#endif

	if (rdev->ops->connect || rdev->ops->auth) {
		i++;
		if (nla_put_u32(msg, i, NL80211_CMD_CONNECT))
			goto nla_put_failure;
	}

	if (rdev->ops->disconnect || rdev->ops->deauth) {
		i++;
		if (nla_put_u32(msg, i, NL80211_CMD_DISCONNECT))
			goto nla_put_failure;
	}

	return i;
 nla_put_failure:
	return -ENOBUFS;
}

static int
nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap,
			   struct sk_buff *msg)
{
	struct nlattr *ftm;

	if (!cap->ftm.supported)
		return 0;

	ftm = nla_nest_start_noflag(msg, NL80211_PMSR_TYPE_FTM);
	if (!ftm)
		return -ENOBUFS;

	if (cap->ftm.asap && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_ASAP))
		return -ENOBUFS;
	if (cap->ftm.non_asap &&
	    nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_ASAP))
		return -ENOBUFS;
	if (cap->ftm.request_lci &&
	    nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_REQ_LCI))
		return -ENOBUFS;
	if (cap->ftm.request_civicloc &&
	    nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_REQ_CIVICLOC))
		return -ENOBUFS;
	if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_PREAMBLES,
			cap->ftm.preambles))
		return -ENOBUFS;
	if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_BANDWIDTHS,
			cap->ftm.bandwidths))
		return -ENOBUFS;
	if (cap->ftm.max_bursts_exponent >= 0 &&
	    nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_BURSTS_EXPONENT,
			cap->ftm.max_bursts_exponent))
		return -ENOBUFS;
	if (cap->ftm.max_ftms_per_burst &&
	    nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST,
			cap->ftm.max_ftms_per_burst))
		return -ENOBUFS;
	if (cap->ftm.trigger_based &&
	    nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED))
		return -ENOBUFS;
	if (cap->ftm.non_trigger_based &&
	    nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED))
		return -ENOBUFS;

	nla_nest_end(msg, ftm);
	return 0;
}

static int nl80211_send_pmsr_capa(struct cfg80211_registered_device *rdev,
				  struct sk_buff *msg)
{
	const struct cfg80211_pmsr_capabilities *cap = rdev->wiphy.pmsr_capa;
	struct nlattr *pmsr, *caps;

	if (!cap)
		return 0;

	/*
	 * we don't need to clean up anything here since the caller
	 * will genlmsg_cancel() if we fail
	 */

	pmsr = nla_nest_start_noflag(msg, NL80211_ATTR_PEER_MEASUREMENTS);
	if (!pmsr)
		return -ENOBUFS;

	if (nla_put_u32(msg, NL80211_PMSR_ATTR_MAX_PEERS, cap->max_peers))
		return -ENOBUFS;

	if (cap->report_ap_tsf &&
	    nla_put_flag(msg, NL80211_PMSR_ATTR_REPORT_AP_TSF))
		return -ENOBUFS;

	if (cap->randomize_mac_addr &&
	    nla_put_flag(msg, NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR))
		return -ENOBUFS;

	caps = nla_nest_start_noflag(msg, NL80211_PMSR_ATTR_TYPE_CAPA);
	if (!caps)
		return -ENOBUFS;

	if (nl80211_send_pmsr_ftm_capa(cap, msg))
		return -ENOBUFS;

	nla_nest_end(msg, caps);
	nla_nest_end(msg, pmsr);

	return 0;
}

static int
nl80211_put_iftype_akm_suites(struct cfg80211_registered_device *rdev,
			      struct sk_buff *msg)
{
	int i;
	struct nlattr *nested, *nested_akms;
	const struct wiphy_iftype_akm_suites *iftype_akms;

	if (!rdev->wiphy.num_iftype_akm_suites ||
	    !rdev->wiphy.iftype_akm_suites)
		return 0;

	nested = nla_nest_start(msg, NL80211_ATTR_IFTYPE_AKM_SUITES);
	if (!nested)
		return -ENOBUFS;

	for (i = 0; i < rdev->wiphy.num_iftype_akm_suites; i++) {
		nested_akms = nla_nest_start(msg, i + 1);
		if (!nested_akms)
			return -ENOBUFS;

		iftype_akms = &rdev->wiphy.iftype_akm_suites[i];

		if (nl80211_put_iftypes(msg, NL80211_IFTYPE_AKM_ATTR_IFTYPES,
					iftype_akms->iftypes_mask))
			return -ENOBUFS;

		if (nla_put(msg, NL80211_IFTYPE_AKM_ATTR_SUITES,
			    sizeof(u32) * iftype_akms->n_akm_suites,
			    iftype_akms->akm_suites)) {
			return -ENOBUFS;
		}
		nla_nest_end(msg, nested_akms);
	}

	nla_nest_end(msg, nested);

	return 0;
}

static int
nl80211_put_tid_config_support(struct cfg80211_registered_device *rdev,
			       struct sk_buff *msg)
{
	struct nlattr *supp;

	if (!rdev->wiphy.tid_config_support.vif &&
	    !rdev->wiphy.tid_config_support.peer)
		return 0;

	supp = nla_nest_start(msg, NL80211_ATTR_TID_CONFIG);
	if (!supp)
		return -ENOSPC;

	if (rdev->wiphy.tid_config_support.vif &&
	    nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_VIF_SUPP,
			      rdev->wiphy.tid_config_support.vif,
			      NL80211_TID_CONFIG_ATTR_PAD))
		goto fail;

	if (rdev->wiphy.tid_config_support.peer &&
	    nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_PEER_SUPP,
			      rdev->wiphy.tid_config_support.peer,
			      NL80211_TID_CONFIG_ATTR_PAD))
		goto fail;

	/* for now we just use the same value ... makes more sense */
	if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_SHORT,
		       rdev->wiphy.tid_config_support.max_retry))
		goto fail;
	if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_LONG,
		       rdev->wiphy.tid_config_support.max_retry))
		goto fail;

	nla_nest_end(msg, supp);

	return 0;
fail:
	nla_nest_cancel(msg, supp);
	return -ENOBUFS;
}

static int
nl80211_put_sar_specs(struct cfg80211_registered_device *rdev,
		      struct sk_buff *msg)
{
	struct nlattr *sar_capa, *specs, *sub_freq_range;
	u8 num_freq_ranges;
	int i;

	if (!rdev->wiphy.sar_capa)
		return 0;

	num_freq_ranges = rdev->wiphy.sar_capa->num_freq_ranges;

	sar_capa = nla_nest_start(msg, NL80211_ATTR_SAR_SPEC);
	if (!sar_capa)
		return -ENOSPC;

	if (nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, rdev->wiphy.sar_capa->type))
		goto fail;

	specs = nla_nest_start(msg, NL80211_SAR_ATTR_SPECS);
	if (!specs)
		goto fail;

	/* report supported freq_ranges */
	for (i = 0; i < num_freq_ranges; i++) {
		sub_freq_range = nla_nest_start(msg, i + 1);
		if (!sub_freq_range)
			goto fail;

		if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_START_FREQ,
				rdev->wiphy.sar_capa->freq_ranges[i].start_freq))
			goto fail;

		if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_END_FREQ,
				rdev->wiphy.sar_capa->freq_ranges[i].end_freq))
			goto fail;

		nla_nest_end(msg, sub_freq_range);
	}

	nla_nest_end(msg, specs);
	nla_nest_end(msg, sar_capa);

	return 0;
fail:
	nla_nest_cancel(msg, sar_capa);
	return -ENOBUFS;
}

static int nl80211_put_mbssid_support(struct wiphy *wiphy, struct sk_buff *msg)
{
	struct nlattr *config;

	if (!wiphy->mbssid_max_interfaces)
		return 0;

	config = nla_nest_start(msg, NL80211_ATTR_MBSSID_CONFIG);
	if (!config)
		return -ENOBUFS;

	if (nla_put_u8(msg, NL80211_MBSSID_CONFIG_ATTR_MAX_INTERFACES,
		       wiphy->mbssid_max_interfaces))
		goto fail;

	if (wiphy->ema_max_profile_periodicity &&
	    nla_put_u8(msg,
		       NL80211_MBSSID_CONFIG_ATTR_MAX_EMA_PROFILE_PERIODICITY,
		       wiphy->ema_max_profile_periodicity))
		goto fail;

	nla_nest_end(msg, config);
	return 0;

fail:
	nla_nest_cancel(msg, config);
	return -ENOBUFS;
}

struct nl80211_dump_wiphy_state {
	s64 filter_wiphy;
	long start;
	long split_start, band_start, chan_start, capa_start;
	bool split;
};

static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
			      enum nl80211_commands cmd,
			      struct sk_buff *msg, u32 portid, u32 seq,
			      int flags, struct nl80211_dump_wiphy_state *state)
{
	void *hdr;
	struct nlattr *nl_bands, *nl_band;
	struct nlattr *nl_freqs, *nl_freq;
	struct nlattr *nl_cmds;
	enum nl80211_band band;
	struct ieee80211_channel *chan;
	int i;
	const struct ieee80211_txrx_stypes *mgmt_stypes =
				rdev->wiphy.mgmt_stypes;
	u32 features;

	hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
	if (!hdr)
		return -ENOBUFS;

	if (WARN_ON(!state))
		return -EINVAL;

	if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
	    nla_put_string(msg, NL80211_ATTR_WIPHY_NAME,
			   wiphy_name(&rdev->wiphy)) ||
	    nla_put_u32(msg, NL80211_ATTR_GENERATION,
			cfg80211_rdev_list_generation))
		goto nla_put_failure;

	if (cmd != NL80211_CMD_NEW_WIPHY)
		goto finish;

	switch (state->split_start) {
	case 0:
		if (nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_SHORT,
			       rdev->wiphy.retry_short) ||
		    nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_LONG,
			       rdev->wiphy.retry_long) ||
		    nla_put_u32(msg, NL80211_ATTR_WIPHY_FRAG_THRESHOLD,
				rdev->wiphy.frag_threshold) ||
		    nla_put_u32(msg, NL80211_ATTR_WIPHY_RTS_THRESHOLD,
				rdev->wiphy.rts_threshold) ||
		    nla_put_u8(msg, NL80211_ATTR_WIPHY_COVERAGE_CLASS,
			       rdev->wiphy.coverage_class) ||
		    nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
			       rdev->wiphy.max_scan_ssids) ||
		    nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
			       rdev->wiphy.max_sched_scan_ssids) ||
		    nla_put_u16(msg, NL80211_ATTR_MAX_SCAN_IE_LEN,
				rdev->wiphy.max_scan_ie_len) ||
		    nla_put_u16(msg, NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN,
				rdev->wiphy.max_sched_scan_ie_len) ||
		    nla_put_u8(msg, NL80211_ATTR_MAX_MATCH_SETS,
			       rdev->wiphy.max_match_sets))
			goto nla_put_failure;

		if ((rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN) &&
		    nla_put_flag(msg, NL80211_ATTR_SUPPORT_IBSS_RSN))
			goto nla_put_failure;
		if ((rdev->wiphy.flags & WIPHY_FLAG_MESH_AUTH) &&
		    nla_put_flag(msg, NL80211_ATTR_SUPPORT_MESH_AUTH))
			goto nla_put_failure;
		if ((rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) &&
		    nla_put_flag(msg, NL80211_ATTR_SUPPORT_AP_UAPSD))
			goto nla_put_failure;
		if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_FW_ROAM) &&
		    nla_put_flag(msg, NL80211_ATTR_ROAM_SUPPORT))
			goto nla_put_failure;
		if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
		    nla_put_flag(msg, NL80211_ATTR_TDLS_SUPPORT))
			goto nla_put_failure;
		if ((rdev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP) &&
		    nla_put_flag(msg, NL80211_ATTR_TDLS_EXTERNAL_SETUP))
			goto nla_put_failure;
		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 1:
		if (nla_put(msg, NL80211_ATTR_CIPHER_SUITES,
			    sizeof(u32) * rdev->wiphy.n_cipher_suites,
			    rdev->wiphy.cipher_suites))
			goto nla_put_failure;

		if (nla_put_u8(msg, NL80211_ATTR_MAX_NUM_PMKIDS,
			       rdev->wiphy.max_num_pmkids))
			goto nla_put_failure;

		if ((rdev->wiphy.flags & WIPHY_FLAG_CONTROL_PORT_PROTOCOL) &&
		    nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE))
			goto nla_put_failure;

		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX,
				rdev->wiphy.available_antennas_tx) ||
		    nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX,
				rdev->wiphy.available_antennas_rx))
			goto nla_put_failure;

		if ((rdev->wiphy.flags & WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD) &&
		    nla_put_u32(msg, NL80211_ATTR_PROBE_RESP_OFFLOAD,
				rdev->wiphy.probe_resp_offload))
			goto nla_put_failure;

		if ((rdev->wiphy.available_antennas_tx ||
		     rdev->wiphy.available_antennas_rx) &&
		    rdev->ops->get_antenna) {
			u32 tx_ant = 0, rx_ant = 0;
			int res;

			res = rdev_get_antenna(rdev, &tx_ant, &rx_ant);
			if (!res) {
				if (nla_put_u32(msg,
						NL80211_ATTR_WIPHY_ANTENNA_TX,
						tx_ant) ||
				    nla_put_u32(msg,
						NL80211_ATTR_WIPHY_ANTENNA_RX,
						rx_ant))
					goto nla_put_failure;
			}
		}

		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 2:
		if (nl80211_put_iftypes(msg, NL80211_ATTR_SUPPORTED_IFTYPES,
					rdev->wiphy.interface_modes))
				goto nla_put_failure;
		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 3:
		nl_bands = nla_nest_start_noflag(msg,
						 NL80211_ATTR_WIPHY_BANDS);
		if (!nl_bands)
			goto nla_put_failure;

		for (band = state->band_start;
		     band < (state->split ?
				NUM_NL80211_BANDS :
				NL80211_BAND_60GHZ + 1);
		     band++) {
			struct ieee80211_supported_band *sband;

			/* omit higher bands for ancient software */
			if (band > NL80211_BAND_5GHZ && !state->split)
				break;

			sband = rdev->wiphy.bands[band];

			if (!sband)
				continue;

			nl_band = nla_nest_start_noflag(msg, band);
			if (!nl_band)
				goto nla_put_failure;

			switch (state->chan_start) {
			case 0:
				if (nl80211_send_band_rateinfo(msg, sband,
							       state->split))
					goto nla_put_failure;
				state->chan_start++;
				if (state->split)
					break;
				fallthrough;
			default:
				/* add frequencies */
				nl_freqs = nla_nest_start_noflag(msg,
								 NL80211_BAND_ATTR_FREQS);
				if (!nl_freqs)
					goto nla_put_failure;

				for (i = state->chan_start - 1;
				     i < sband->n_channels;
				     i++) {
					nl_freq = nla_nest_start_noflag(msg,
									i);
					if (!nl_freq)
						goto nla_put_failure;

					chan = &sband->channels[i];

					if (nl80211_msg_put_channel(
							msg, &rdev->wiphy, chan,
							state->split))
						goto nla_put_failure;

					nla_nest_end(msg, nl_freq);
					if (state->split)
						break;
				}
				if (i < sband->n_channels)
					state->chan_start = i + 2;
				else
					state->chan_start = 0;
				nla_nest_end(msg, nl_freqs);
			}

			nla_nest_end(msg, nl_band);

			if (state->split) {
				/* start again here */
				if (state->chan_start)
					band--;
				break;
			}
		}
		nla_nest_end(msg, nl_bands);

		if (band < NUM_NL80211_BANDS)
			state->band_start = band + 1;
		else
			state->band_start = 0;

		/* if bands & channels are done, continue outside */
		if (state->band_start == 0 && state->chan_start == 0)
			state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 4:
		nl_cmds = nla_nest_start_noflag(msg,
						NL80211_ATTR_SUPPORTED_COMMANDS);
		if (!nl_cmds)
			goto nla_put_failure;

		i = nl80211_add_commands_unsplit(rdev, msg);
		if (i < 0)
			goto nla_put_failure;
		if (state->split) {
			CMD(crit_proto_start, CRIT_PROTOCOL_START);
			CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
			if (rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)
				CMD(channel_switch, CHANNEL_SWITCH);
			CMD(set_qos_map, SET_QOS_MAP);
			if (rdev->wiphy.features &
					NL80211_FEATURE_SUPPORTS_WMM_ADMISSION)
				CMD(add_tx_ts, ADD_TX_TS);
			CMD(set_multicast_to_unicast, SET_MULTICAST_TO_UNICAST);
			CMD(update_connect_params, UPDATE_CONNECT_PARAMS);
			CMD(update_ft_ies, UPDATE_FT_IES);
			if (rdev->wiphy.sar_capa)
				CMD(set_sar_specs, SET_SAR_SPECS);
		}
#undef CMD

		nla_nest_end(msg, nl_cmds);
		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 5:
		if (rdev->ops->remain_on_channel &&
		    (rdev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL) &&
		    nla_put_u32(msg,
				NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION,
				rdev->wiphy.max_remain_on_channel_duration))
			goto nla_put_failure;

		if ((rdev->wiphy.flags & WIPHY_FLAG_OFFCHAN_TX) &&
		    nla_put_flag(msg, NL80211_ATTR_OFFCHANNEL_TX_OK))
			goto nla_put_failure;

		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 6:
#ifdef CONFIG_PM
		if (nl80211_send_wowlan(msg, rdev, state->split))
			goto nla_put_failure;
		state->split_start++;
		if (state->split)
			break;
#else
		state->split_start++;
#endif
		fallthrough;
	case 7:
		if (nl80211_put_iftypes(msg, NL80211_ATTR_SOFTWARE_IFTYPES,
					rdev->wiphy.software_iftypes))
			goto nla_put_failure;

		if (nl80211_put_iface_combinations(&rdev->wiphy, msg,
						   state->split))
			goto nla_put_failure;

		state->split_start++;
		if (state->split)
			break;
		fallthrough;
	case 8:
		if ((rdev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME) &&
		    nla_put_u32(msg, NL80211_ATTR_DEVICE_AP_SME,
				rdev->wiphy.ap_sme_capa))
			goto nla_put_failure;

		features = rdev->wiphy.features;
		/*
		 * We can only add the per-channel limit information if the
		 * dump is split, otherwise it makes it too big. Therefore
		 * only advertise it in that case.
		 */
		if (state->split)
			features |= NL80211_FEATURE_ADVERTISE_CHAN_LIMITS;
		if (nla_put_u32(msg, NL80211_ATTR_FEATURE_FLAGS, features))
			goto nla_put_failure;

		if (rdev->wiphy.ht_capa_mod_mask &&
		    nla_put(msg, NL80211_ATTR_HT_CAPABILITY_MASK,
			    sizeof(*rdev->wiphy.ht_capa_mod_mask),
			    rdev->wiphy.ht_capa_mod_mask))
			goto nla_put_failure;

		if (rdev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME &&
		    rdev->wiphy.max_acl_mac_addrs &&
		    nla_put_u32(msg, NL80211_ATTR_MAC_ACL_MAX,
				rdev->wiphy.max_acl_mac_addrs))
			goto nla_put_failure;

		/*
		 * Any information below this point is only available to
		 * applications that can deal with it being split. This
		 * helps ensure that newly added capabilities don't break
		 * older tools by overrunning their buffers.
		 *
		 * We still increment split_start so that in the split
		 * case we'll continue with more data in the next round,
		 * but break unconditionally so unsplit data stops here.
		 */
		if (state->split)
			state->split_start++;
		else
			state->split_start = 0;
		break;
	case 9:
		if (nl80211_send_mgmt_stypes(msg, mgmt_stypes))
			goto nla_put_failure;

		if (nla_put_u32(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS,
				rdev->wiphy.max_sched_scan_plans) ||
		    nla_put_u32(msg, NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL,
				rdev->wiphy.max_sched_scan_plan_interval) ||
		    nla_put_u32(msg, NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS,
				rdev->wiphy.max_sched_scan_plan_iterations))
			goto nla_put_failure;

		if (rdev->wiphy.extended_capabilities &&
		    (nla_put(msg, NL80211_ATTR_EXT_CAPA,
			     rdev->wiphy.extended_capabilities_len,
			     rdev->wiphy.extended_capabilities) ||
		     nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
			     rdev->wiphy.extended_capabilities_len,
			     rdev->wiphy.extended_capabilities_mask)))
			goto nla_put_failure;

		if (rdev->wiphy.vht_capa_mod_mask &&
		    nla_put(msg, NL80211_ATTR_VHT_CAPABILITY_MASK,
			    sizeof(*rdev->wiphy.vht_capa_mod_mask),
			    rdev->wiphy.vht_capa_mod_mask))
			goto nla_put_failure;

		if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
			    rdev->wiphy.perm_addr))
			goto nla_put_failure;

		if (!is_zero_ether_addr(rdev->wiphy.addr_mask) &&
		    nla_put(msg, NL80211_ATTR_MAC_MASK, ETH_ALEN,
			    rdev->wiphy.addr_mask))
			goto nla_put_failure;

		if (rdev->wiphy.n_addresses > 1) {
			void *attr;

			attr = nla_nest_start(msg, NL80211_ATTR_MAC_ADDRS);
			if (!attr)
				goto nla_put_failure;

			for (i = 0; i < rdev->wiphy.n_addresses; i++)
				if (nla_put(msg, i + 1, ETH_ALEN,
					    rdev->wiphy.addresses[i].addr))
					goto nla_put_failure;

			nla_nest_end(msg, attr);
		}

		state->split_start++;
		break;
	case 10:
		if (nl80211_send_coalesce(msg, rdev))
			goto nla_put_failure;

		if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ) &&
		    (nla_put_flag(msg, NL80211_ATTR_SUPPORT_5_MHZ) ||
		     nla_put_flag(msg, NL80211_ATTR_SUPPORT_10_MHZ)))
			goto nla_put_failure;

		if (rdev->wiphy.max_ap_assoc_sta &&
		    nla_put_u32(msg, NL80211_ATTR_MAX_AP_ASSOC_STA,
				rdev->wiphy.max_ap_assoc_sta))
			goto nla_put_failure;

		state->split_start++;
		break;
	case 11:
		if (rdev->wiphy.n_vendor_commands) {
			const struct nl80211_vendor_cmd_info *info;
			struct nlattr *nested;

			nested = nla_nest_start_noflag(msg,
						       NL80211_ATTR_VENDOR_DATA);
			if (!nested)
				goto nla_put_failure;

			for (i = 0; i < rdev->wiphy.n_vendor_commands; i++) {
				info = &rdev->wiphy.vendor_commands[i].info;
				if (nla_put(msg, i + 1, sizeof(*info), info))
					goto nla_put_failure;
			}
			nla_nest_end(msg, nested);
		}

		if (rdev->wiphy.n_vendor_events) {
			const struct nl80211_vendor_cmd_info *info;
			struct nlattr *nested;

			nested = nla_nest_start_noflag(msg,
						       NL80211_ATTR_VENDOR_EVENTS);
			if (!nested)
				goto nla_put_failure;

			for (i = 0; i < rdev->wiphy.n_vendor_events; i++) {
				info = &rdev->wiphy.vendor_events[i];
				if (nla_put(msg, i + 1, sizeof(*info), info))
					goto nla_put_failure;
			}
			nla_nest_end(msg, nested);
		}
		state->split_start++;
		break;
	case 12:
		if (rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH &&
		    nla_put_u8(msg, NL80211_ATTR_MAX_CSA_COUNTERS,
			       rdev->wiphy.max_num_csa_counters))
			goto nla_put_failure;

		if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
		    nla_put_flag(msg, NL80211_ATTR_WIPHY_SELF_MANAGED_REG))
			goto nla_put_failure;

		if (rdev->wiphy.max_sched_scan_reqs &&
		    nla_put_u32(msg, NL80211_ATTR_SCHED_SCAN_MAX_REQS,
				rdev->wiphy.max_sched_scan_reqs))
			goto nla_put_failure;

		if (nla_put(msg, NL80211_ATTR_EXT_FEATURES,
			    sizeof(rdev->wiphy.ext_features),
			    rdev->wiphy.ext_features))
			goto nla_put_failure;

		if (rdev->wiphy.bss_select_support) {
			struct nlattr *nested;
			u32 bss_select_support = rdev->wiphy.bss_select_support;

			nested = nla_nest_start_noflag(msg,
						       NL80211_ATTR_BSS_SELECT);
			if (!nested)
				goto nla_put_failure;

			i = 0;
			while (bss_select_support) {
				if ((bss_select_support & 1) &&
				    nla_put_flag(msg, i))
					goto nla_put_failure;
				i++;
				bss_select_support >>= 1;
			}
			nla_nest_end(msg, nested);
		}

		state->split_start++;
		break;
	case 13:
		if (rdev->wiphy.num_iftype_ext_capab &&
		    rdev->wiphy.iftype_ext_capab) {
			struct nlattr *nested_ext_capab, *nested;

			nested = nla_nest_start_noflag(msg,
						       NL80211_ATTR_IFTYPE_EXT_CAPA);
			if (!nested)
				goto nla_put_failure;

			for (i = state->capa_start;
			     i < rdev->wiphy.num_iftype_ext_capab; i++) {
				const struct wiphy_iftype_ext_capab *capab;

				capab = &rdev->wiphy.iftype_ext_capab[i];

				nested_ext_capab = nla_nest_start_noflag(msg,
									 i);
				if (!nested_ext_capab ||
				    nla_put_u32(msg, NL80211_ATTR_IFTYPE,
						capab->iftype) ||
				    nla_put(msg, NL80211_ATTR_EXT_CAPA,
					    capab->extended_capabilities_len,
					    capab->extended_capabilities) ||
				    nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
					    capab->extended_capabilities_len,
					    capab->extended_capabilities_mask))
					goto nla_put_failure;

				if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO &&
				    (nla_put_u16(msg,
						 NL80211_ATTR_EML_CAPABILITY,
						 capab->eml_capabilities) ||
				     nla_put_u16(msg,
						 NL80211_ATTR_MLD_CAPA_AND_OPS,
						 capab->mld_capa_and_ops)))
					goto nla_put_failure;

				nla_nest_end(msg, nested_ext_capab);
				if (state->split)
					break;
			}
			nla_nest_end(msg, nested);
			if (i < rdev->wiphy.num_iftype_ext_capab) {
				state->capa_start = i + 1;
				break;
			}
		}

		if (nla_put_u32(msg, NL80211_ATTR_BANDS,
				rdev->wiphy.nan_supported_bands))
			goto nla_put_failure;

		if (wiphy_ext_feature_isset(&rdev->wiphy,
					    NL80211_EXT_FEATURE_TXQS)) {
			struct cfg80211_txq_stats txqstats = {};
			int res;

			res = rdev_get_txq_stats(rdev, NULL, &txqstats);
			if (!res &&
			    !nl80211_put_txq_stats(msg, &txqstats,
						   NL80211_ATTR_TXQ_STATS))
				goto nla_put_failure;

			if (nla_put_u32(msg, NL80211_ATTR_TXQ_LIMIT,
					rdev->wiphy.txq_limit))
				goto nla_put_failure;
			if (nla_put_u32(msg, NL80211_ATTR_TXQ_MEMORY_LIMIT,
					rdev->wiphy.txq_memory_limit))
				goto nla_put_failure;
			if (nla_put_u32(msg, NL80211_ATTR_TXQ_QUANTUM,
					rdev->wiphy.txq_quantum))
				goto nla_put_failure;
		}

		state->split_start++;
		break;
	case 14:
		if (nl80211_send_pmsr_capa(rdev, msg))
			goto nla_put_failure;

		state->split_start++;
		break;
	case 15:
		if (rdev->wiphy.akm_suites &&
		    nla_put(msg, NL80211_ATTR_AKM_SUITES,
			    sizeof(u32) * rdev->wiphy.n_akm_suites,
			    rdev->wiphy.akm_suites))
			goto nla_put_failure;

		if (nl80211_put_iftype_akm_suites(rdev, msg))
			goto nla_put_failure;

		if (nl80211_put_tid_config_support(rdev, msg))
			goto nla_put_failure;
		state->split_start++;
		break;
	case 16:
		if (nl80211_put_sar_specs(rdev, msg))
			goto nla_put_failure;

		if (nl80211_put_mbssid_support(&rdev->wiphy, msg))
			goto nla_put_failure;

		if (nla_put_u16(msg, NL80211_ATTR_MAX_NUM_AKM_SUITES,
				rdev->wiphy.max_num_akm_suites))
			goto nla_put_failure;

		if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO)
			nla_put_flag(msg, NL80211_ATTR_MLO_SUPPORT);

		/* done */
		state->split_start = 0;
		break;
	}
 finish:
	genlmsg_end(msg, hdr);
	return 0;

 nla_put_failure:
	genlmsg_cancel(msg, hdr);
	return -EMSGSIZE;
}

static int nl80211_dump_wiphy_parse(struct sk_buff *skb,
				    struct netlink_callback *cb,
				    struct nl80211_dump_wiphy_state *state)
{
	struct nlattr **tb = kcalloc(NUM_NL80211_ATTR, sizeof(*tb), GFP_KERNEL);
	int ret;

	if (!tb)
		return -ENOMEM;

	ret = nlmsg_parse_deprecated(cb->nlh,
				     GENL_HDRLEN + nl80211_fam.hdrsize,
				     tb, nl80211_fam.maxattr,
				     nl80211_policy, NULL);
	/* ignore parse errors for backward compatibility */
	if (ret) {
		ret = 0;
		goto out;
	}

	state->split = tb[NL80211_ATTR_SPLIT_WIPHY_DUMP];
	if (tb[NL80211_ATTR_WIPHY])
		state->filter_wiphy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
	if (tb[NL80211_ATTR_WDEV])
		state->filter_wiphy = nla_get_u64(tb[NL80211_ATTR_WDEV]) >> 32;
	if (tb[NL80211_ATTR_IFINDEX]) {
		struct net_device *netdev;
		struct cfg80211_registered_device *rdev;
		int ifidx = nla_get_u32(tb[NL80211_ATTR_IFINDEX]);

		netdev = __dev_get_by_index(sock_net(skb->sk), ifidx);
		if (!netdev) {
			ret = -ENODEV;
			goto out;
		}
		if (netdev->ieee80211_ptr) {
			rdev = wiphy_to_rdev(
				netdev->ieee80211_ptr->wiphy);
			state->filter_wiphy = rdev->wiphy_idx;
		}
	}

	ret = 0;
out:
	kfree(tb);
	return ret;
}

static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
{
	int idx = 0, ret;
	struct nl80211_dump_wiphy_state *state = (void *)cb->args[0];
	struct cfg80211_registered_device *rdev;

	rtnl_lock();
	if (!state) {
		state = kzalloc(sizeof(*state), GFP_KERNEL);
		if (!state) {
			rtnl_unlock();
			return -ENOMEM;
		}
		state->filter_wiphy = -1;
		ret = nl80211_dump_wiphy_parse(skb, cb, state);
		if (ret) {
			kfree(state);
			rtnl_unlock();
			return ret;
		}
		cb->args[0] = (long)state;
	}

	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
		if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
			continue;
		if (++idx <= state->start)
			continue;
		if (state->filter_wiphy != -1 &&
		    state->filter_wiphy != rdev->wiphy_idx)
			continue;
		/* attempt to fit multiple wiphy data chunks into the skb */
		do {
			ret = nl80211_send_wiphy(rdev, NL80211_CMD_NEW_WIPHY,
						 skb,
						 NETLINK_CB(cb->skb).portid,
						 cb->nlh->nlmsg_seq,
						 NLM_F_MULTI, state);
			if (ret < 0) {
				/*
				 * If sending the wiphy data didn't fit (ENOBUFS
				 * or EMSGSIZE returned), this SKB is still
				 * empty (so it's not too big because another
				 * wiphy dataset is already in the skb) and
				 * we've not tried to adjust the dump allocation
				 * yet ... then adjust the alloc size to be
				 * bigger, and return 1 but with the empty skb.
				 * This results in an empty message being RX'ed
				 * in userspace, but that is ignored.
				 *
				 * We can then retry with the larger buffer.
				 */
				if ((ret == -ENOBUFS || ret == -EMSGSIZE) &&
				    !skb->len && !state->split &&
				    cb->min_dump_alloc < 4096) {
					cb->min_dump_alloc = 4096;
					state->split_start = 0;
					rtnl_unlock();
					return 1;
				}
				idx--;
				break;
			}
		} while (state->split_start > 0);
		break;
	}
	rtnl_unlock();

	state->start = idx;

	return skb->len;
}

static int nl80211_dump_wiphy_done(struct netlink_callback *cb)
{
	kfree((void *)cb->args[0]);
	return 0;
}

static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info)
{
	struct sk_buff *msg;
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct nl80211_dump_wiphy_state state = {};

	msg = nlmsg_new(4096, GFP_KERNEL);
	if (!msg)
		return -ENOMEM;

	if (nl80211_send_wiphy(rdev, NL80211_CMD_NEW_WIPHY, msg,
			       info->snd_portid, info->snd_seq, 0,
			       &state) < 0) {
		nlmsg_free(msg);
		return -ENOBUFS;
	}

	return genlmsg_reply(msg, info);
}

static const struct nla_policy txq_params_policy[NL80211_TXQ_ATTR_MAX + 1] = {
	[NL80211_TXQ_ATTR_QUEUE]		= { .type = NLA_U8 },
	[NL80211_TXQ_ATTR_TXOP]			= { .type = NLA_U16 },
	[NL80211_TXQ_ATTR_CWMIN]		= { .type = NLA_U16 },
	[NL80211_TXQ_ATTR_CWMAX]		= { .type = NLA_U16 },
	[NL80211_TXQ_ATTR_AIFS]			= { .type = NLA_U8 },
};

static int parse_txq_params(struct nlattr *tb[],
			    struct ieee80211_txq_params *txq_params)
{
	u8 ac;

	if (!tb[NL80211_TXQ_ATTR_AC] || !tb[NL80211_TXQ_ATTR_TXOP] ||
	    !tb[NL80211_TXQ_ATTR_CWMIN] || !tb[NL80211_TXQ_ATTR_CWMAX] ||
	    !tb[NL80211_TXQ_ATTR_AIFS])
		return -EINVAL;

	ac = nla_get_u8(tb[NL80211_TXQ_ATTR_AC]);
	txq_params->txop = nla_get_u16(tb[NL80211_TXQ_ATTR_TXOP]);
	txq_params->cwmin = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMIN]);
	txq_params->cwmax = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMAX]);
	txq_params->aifs = nla_get_u8(tb[NL80211_TXQ_ATTR_AIFS]);

	if (ac >= NL80211_NUM_ACS)
		return -EINVAL;
	txq_params->ac = array_index_nospec(ac, NL80211_NUM_ACS);
	return 0;
}

static bool nl80211_can_set_dev_channel(struct wireless_dev *wdev)
{
	/*
	 * You can only set the channel explicitly for some interfaces,
	 * most have their channel managed via their respective
	 * "establish a connection" command (connect, join, ...)
	 *
	 * For AP/GO and mesh mode, the channel can be set with the
	 * channel userspace API, but is only stored and passed to the
	 * low-level driver when the AP starts or the mesh is joined.
	 * This is for backward compatibility, userspace can also give
	 * the channel in the start-ap or join-mesh commands instead.
	 *
	 * Monitors are special as they are normally slaved to
	 * whatever else is going on, so they have their own special
	 * operation to set the monitor channel if possible.
	 */
	return !wdev ||
		wdev->iftype == NL80211_IFTYPE_AP ||
		wdev->iftype == NL80211_IFTYPE_MESH_POINT ||
		wdev->iftype == NL80211_IFTYPE_MONITOR ||
		wdev->iftype == NL80211_IFTYPE_P2P_GO;
}

static int nl80211_parse_punct_bitmap(struct cfg80211_registered_device *rdev,
				      struct genl_info *info,
				      const struct cfg80211_chan_def *chandef,
				      u16 *punct_bitmap)
{
	if (!wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_PUNCT))
		return -EINVAL;

	*punct_bitmap = nla_get_u32(info->attrs[NL80211_ATTR_PUNCT_BITMAP]);
	if (!cfg80211_valid_disable_subchannel_bitmap(punct_bitmap, chandef))
		return -EINVAL;

	return 0;
}

int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
			  struct genl_info *info,
			  struct cfg80211_chan_def *chandef)
{
	struct netlink_ext_ack *extack = info->extack;
	struct nlattr **attrs = info->attrs;
	u32 control_freq;

	if (!attrs[NL80211_ATTR_WIPHY_FREQ]) {
		NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ],
				    "Frequency is missing");
		return -EINVAL;
	}

	control_freq = MHZ_TO_KHZ(
			nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]));
	if (info->attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET])
		control_freq +=
		    nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]);

	memset(chandef, 0, sizeof(*chandef));
	chandef->chan = ieee80211_get_channel_khz(&rdev->wiphy, control_freq);
	chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
	chandef->center_freq1 = KHZ_TO_MHZ(control_freq);
	chandef->freq1_offset = control_freq % 1000;
	chandef->center_freq2 = 0;

	/* Primary channel not allowed */
	if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED) {
		NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ],
				    "Channel is disabled");
		return -EINVAL;
	}

	if (attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
		enum nl80211_channel_type chantype;

		chantype = nla_get_u32(attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);

		switch (chantype) {
		case NL80211_CHAN_NO_HT:
		case NL80211_CHAN_HT20:
		case NL80211_CHAN_HT40PLUS:
		case NL80211_CHAN_HT40MINUS:
			cfg80211_chandef_create(chandef, chandef->chan,
						chantype);
			/* user input for center_freq is incorrect */
			if (attrs[NL80211_ATTR_CENTER_FREQ1] &&
			    chandef->center_freq1 != nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1])) {
				NL_SET_ERR_MSG_ATTR(extack,
						    attrs[NL80211_ATTR_CENTER_FREQ1],
						    "bad center frequency 1");
				return -EINVAL;
			}
			/* center_freq2 must be zero */
			if (attrs[NL80211_ATTR_CENTER_FREQ2] &&
			    nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2])) {
				NL_SET_ERR_MSG_ATTR(extack,
						    attrs[NL80211_ATTR_CENTER_FREQ2],
						    "center frequency 2 can't be used");
				return -EINVAL;
			}
			break;
		default:
			NL_SET_ERR_MSG_ATTR(extack,
					    attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE],
					    "invalid channel type");
			return -EINVAL;
		}
	} else if (attrs[NL80211_ATTR_CHANNEL_WIDTH]) {
		chandef->width =
			nla_get_u32(attrs[NL80211_ATTR_CHANNEL_WIDTH]);
		if (chandef->chan->band == NL80211_BAND_S1GHZ) {
			/* User input error for channel width doesn't match channel  */
			if (chandef->width != ieee80211_s1g_channel_width(chandef->chan)) {
				NL_SET_ERR_MSG_ATTR(extack,
						    attrs[NL80211_ATTR_CHANNEL_WIDTH],
						    "bad channel width");
				return -EINVAL;
			}
		}
		if (attrs[NL80211_ATTR_CENTER_FREQ1]) {
			chandef->center_freq1 =
				nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1]);
			if (attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET])
				chandef->freq1_offset = nla_get_u32(
				      attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET]);
			else
				chandef->freq1_offset = 0;
		}
		if (attrs[NL80211_ATTR_CENTER_FREQ2])
			chandef->center_freq2 =
				nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2]);
	}

	if (info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]) {
		chandef->edmg.channels =
		      nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]);

		if (info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG])
			chandef->edmg.bw_config =
		     nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG]);
	} else {
		chandef->edmg.bw_config = 0;
		chandef->edmg.channels = 0;
	}

	if (!cfg80211_chandef_valid(chandef)) {
		NL_SET_ERR_MSG(extack, "invalid channel definition");
		return -EINVAL;
	}

	if (!cfg80211_chandef_usable(&rdev->wiphy, chandef,
				     IEEE80211_CHAN_DISABLED)) {
		NL_SET_ERR_MSG(extack, "(extension) channel is disabled");
		return -EINVAL;
	}

	if ((chandef->width == NL80211_CHAN_WIDTH_5 ||
	     chandef->width == NL80211_CHAN_WIDTH_10) &&
	    !(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ)) {
		NL_SET_ERR_MSG(extack, "5/10 MHz not supported");
		return -EINVAL;
	}

	return 0;
}

static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
				 struct net_device *dev,
				 struct genl_info *info,
				 int _link_id)
{
	struct cfg80211_chan_def chandef;
	int result;
	enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR;
	struct wireless_dev *wdev = NULL;
	int link_id = _link_id;

	if (dev)
		wdev = dev->ieee80211_ptr;
	if (!nl80211_can_set_dev_channel(wdev))
		return -EOPNOTSUPP;
	if (wdev)
		iftype = wdev->iftype;

	if (link_id < 0) {
		if (wdev && wdev->valid_links)
			return -EINVAL;
		link_id = 0;
	}

	result = nl80211_parse_chandef(rdev, info, &chandef);
	if (result)
		return result;

	switch (iftype) {
	case NL80211_IFTYPE_AP:
	case NL80211_IFTYPE_P2P_GO:
		if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &chandef,
						   iftype))
			return -EINVAL;
		if (wdev->links[link_id].ap.beacon_interval) {
			struct ieee80211_channel *cur_chan;

			if (!dev || !rdev->ops->set_ap_chanwidth ||
			    !(rdev->wiphy.features &
			      NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE))
				return -EBUSY;

			/* Only allow dynamic channel width changes */
			cur_chan = wdev->links[link_id].ap.chandef.chan;
			if (chandef.chan != cur_chan)
				return -EBUSY;

			result = rdev_set_ap_chanwidth(rdev, dev, link_id,
						       &chandef);
			if (result)
				return result;
			wdev->links[link_id].ap.chandef = chandef;
		} else {
			wdev->u.ap.preset_chandef = chandef;
		}
		return 0;
	case NL80211_IFTYPE_MESH_POINT:
		return cfg80211_set_mesh_channel(rdev, wdev, &chandef);
	case NL80211_IFTYPE_MONITOR:
		return cfg80211_set_monitor_channel(rdev, &chandef);
	default:
		break;
	}

	return -EINVAL;
}

static int nl80211_set_channel(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int link_id = nl80211_link_id_or_invalid(info->attrs);
	struct net_device *netdev = info->user_ptr[1];
	int ret;

	wdev_lock(netdev->ieee80211_ptr);
	ret = __nl80211_set_channel(rdev, netdev, info, link_id);
	wdev_unlock(netdev->ieee80211_ptr);

	return ret;
}

static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = NULL;
	struct net_device *netdev = NULL;
	struct wireless_dev *wdev;
	int result = 0, rem_txq_params = 0;
	struct nlattr *nl_txq_params;
	u32 changed;
	u8 retry_short = 0, retry_long = 0;
	u32 frag_threshold = 0, rts_threshold = 0;
	u8 coverage_class = 0;
	u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0;

	rtnl_lock();
	/*
	 * Try to find the wiphy and netdev. Normally this
	 * function shouldn't need the netdev, but this is
	 * done for backward compatibility -- previously
	 * setting the channel was done per wiphy, but now
	 * it is per netdev. Previous userland like hostapd
	 * also passed a netdev to set_wiphy, so that it is
	 * possible to let that go to the right netdev!
	 */

	if (info->attrs[NL80211_ATTR_IFINDEX]) {
		int ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]);

		netdev = __dev_get_by_index(genl_info_net(info), ifindex);
		if (netdev && netdev->ieee80211_ptr)
			rdev = wiphy_to_rdev(netdev->ieee80211_ptr->wiphy);
		else
			netdev = NULL;
	}

	if (!netdev) {
		rdev = __cfg80211_rdev_from_attrs(genl_info_net(info),
						  info->attrs);
		if (IS_ERR(rdev)) {
			rtnl_unlock();
			return PTR_ERR(rdev);
		}
		wdev = NULL;
		netdev = NULL;
		result = 0;
	} else
		wdev = netdev->ieee80211_ptr;

	wiphy_lock(&rdev->wiphy);

	/*
	 * end workaround code, by now the rdev is available
	 * and locked, and wdev may or may not be NULL.
	 */

	if (info->attrs[NL80211_ATTR_WIPHY_NAME])
		result = cfg80211_dev_rename(
			rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
	rtnl_unlock();

	if (result)
		goto out;

	if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) {
		struct ieee80211_txq_params txq_params;
		struct nlattr *tb[NL80211_TXQ_ATTR_MAX + 1];

		if (!rdev->ops->set_txq_params) {
			result = -EOPNOTSUPP;
			goto out;
		}

		if (!netdev) {
			result = -EINVAL;
			goto out;
		}

		if (netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
		    netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
			result = -EINVAL;
			goto out;
		}

		if (!netif_running(netdev)) {
			result = -ENETDOWN;
			goto out;
		}

		nla_for_each_nested(nl_txq_params,
				    info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS],
				    rem_txq_params) {
			result = nla_parse_nested_deprecated(tb,
							     NL80211_TXQ_ATTR_MAX,
							     nl_txq_params,
							     txq_params_policy,
							     info->extack);
			if (result)
				goto out;
			result = parse_txq_params(tb, &txq_params);
			if (result)
				goto out;

			txq_params.link_id =
				nl80211_link_id_or_invalid(info->attrs);

			wdev_lock(netdev->ieee80211_ptr);
			if (txq_params.link_id >= 0 &&
			    !(netdev->ieee80211_ptr->valid_links &
			      BIT(txq_params.link_id)))
				result = -ENOLINK;
			else if (txq_params.link_id >= 0 &&
				 !netdev->ieee80211_ptr->valid_links)
				result = -EINVAL;
			else
				result = rdev_set_txq_params(rdev, netdev,
							     &txq_params);
			wdev_unlock(netdev->ieee80211_ptr);
			if (result)
				goto out;
		}
	}

	if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
		int link_id = nl80211_link_id_or_invalid(info->attrs);

		if (wdev) {
			wdev_lock(wdev);
			result = __nl80211_set_channel(
				rdev,
				nl80211_can_set_dev_channel(wdev) ? netdev : NULL,
				info, link_id);
			wdev_unlock(wdev);
		} else {
			result = __nl80211_set_channel(rdev, netdev, info, link_id);
		}

		if (result)
			goto out;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_TX_POWER_SETTING]) {
		struct wireless_dev *txp_wdev = wdev;
		enum nl80211_tx_power_setting type;
		int idx, mbm = 0;

		if (!(rdev->wiphy.features & NL80211_FEATURE_VIF_TXPOWER))
			txp_wdev = NULL;

		if (!rdev->ops->set_tx_power) {
			result = -EOPNOTSUPP;
			goto out;
		}

		idx = NL80211_ATTR_WIPHY_TX_POWER_SETTING;
		type = nla_get_u32(info->attrs[idx]);

		if (!info->attrs[NL80211_ATTR_WIPHY_TX_POWER_LEVEL] &&
		    (type != NL80211_TX_POWER_AUTOMATIC)) {
			result = -EINVAL;
			goto out;
		}

		if (type != NL80211_TX_POWER_AUTOMATIC) {
			idx = NL80211_ATTR_WIPHY_TX_POWER_LEVEL;
			mbm = nla_get_u32(info->attrs[idx]);
		}

		result = rdev_set_tx_power(rdev, txp_wdev, type, mbm);
		if (result)
			goto out;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX] &&
	    info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]) {
		u32 tx_ant, rx_ant;

		if ((!rdev->wiphy.available_antennas_tx &&
		     !rdev->wiphy.available_antennas_rx) ||
		    !rdev->ops->set_antenna) {
			result = -EOPNOTSUPP;
			goto out;
		}

		tx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX]);
		rx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]);

		/* reject antenna configurations which don't match the
		 * available antenna masks, except for the "all" mask */
		if ((~tx_ant && (tx_ant & ~rdev->wiphy.available_antennas_tx)) ||
		    (~rx_ant && (rx_ant & ~rdev->wiphy.available_antennas_rx))) {
			result = -EINVAL;
			goto out;
		}

		tx_ant = tx_ant & rdev->wiphy.available_antennas_tx;
		rx_ant = rx_ant & rdev->wiphy.available_antennas_rx;

		result = rdev_set_antenna(rdev, tx_ant, rx_ant);
		if (result)
			goto out;
	}

	changed = 0;

	if (info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]) {
		retry_short = nla_get_u8(
			info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]);

		changed |= WIPHY_PARAM_RETRY_SHORT;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]) {
		retry_long = nla_get_u8(
			info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]);

		changed |= WIPHY_PARAM_RETRY_LONG;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]) {
		frag_threshold = nla_get_u32(
			info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]);
		if (frag_threshold < 256) {
			result = -EINVAL;
			goto out;
		}

		if (frag_threshold != (u32) -1) {
			/*
			 * Fragments (apart from the last one) are required to
			 * have even length. Make the fragmentation code
			 * simpler by stripping LSB should someone try to use
			 * odd threshold value.
			 */
			frag_threshold &= ~0x1;
		}
		changed |= WIPHY_PARAM_FRAG_THRESHOLD;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]) {
		rts_threshold = nla_get_u32(
			info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]);
		changed |= WIPHY_PARAM_RTS_THRESHOLD;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]) {
		if (info->attrs[NL80211_ATTR_WIPHY_DYN_ACK]) {
			result = -EINVAL;
			goto out;
		}

		coverage_class = nla_get_u8(
			info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]);
		changed |= WIPHY_PARAM_COVERAGE_CLASS;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_DYN_ACK]) {
		if (!(rdev->wiphy.features & NL80211_FEATURE_ACKTO_ESTIMATION)) {
			result = -EOPNOTSUPP;
			goto out;
		}

		changed |= WIPHY_PARAM_DYN_ACK;
	}

	if (info->attrs[NL80211_ATTR_TXQ_LIMIT]) {
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_TXQS)) {
			result = -EOPNOTSUPP;
			goto out;
		}
		txq_limit = nla_get_u32(
			info->attrs[NL80211_ATTR_TXQ_LIMIT]);
		changed |= WIPHY_PARAM_TXQ_LIMIT;
	}

	if (info->attrs[NL80211_ATTR_TXQ_MEMORY_LIMIT]) {
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_TXQS)) {
			result = -EOPNOTSUPP;
			goto out;
		}
		txq_memory_limit = nla_get_u32(
			info->attrs[NL80211_ATTR_TXQ_MEMORY_LIMIT]);
		changed |= WIPHY_PARAM_TXQ_MEMORY_LIMIT;
	}

	if (info->attrs[NL80211_ATTR_TXQ_QUANTUM]) {
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_TXQS)) {
			result = -EOPNOTSUPP;
			goto out;
		}
		txq_quantum = nla_get_u32(
			info->attrs[NL80211_ATTR_TXQ_QUANTUM]);
		changed |= WIPHY_PARAM_TXQ_QUANTUM;
	}

	if (changed) {
		u8 old_retry_short, old_retry_long;
		u32 old_frag_threshold, old_rts_threshold;
		u8 old_coverage_class;
		u32 old_txq_limit, old_txq_memory_limit, old_txq_quantum;

		if (!rdev->ops->set_wiphy_params) {
			result = -EOPNOTSUPP;
			goto out;
		}

		old_retry_short = rdev->wiphy.retry_short;
		old_retry_long = rdev->wiphy.retry_long;
		old_frag_threshold = rdev->wiphy.frag_threshold;
		old_rts_threshold = rdev->wiphy.rts_threshold;
		old_coverage_class = rdev->wiphy.coverage_class;
		old_txq_limit = rdev->wiphy.txq_limit;
		old_txq_memory_limit = rdev->wiphy.txq_memory_limit;
		old_txq_quantum = rdev->wiphy.txq_quantum;

		if (changed & WIPHY_PARAM_RETRY_SHORT)
			rdev->wiphy.retry_short = retry_short;
		if (changed & WIPHY_PARAM_RETRY_LONG)
			rdev->wiphy.retry_long = retry_long;
		if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
			rdev->wiphy.frag_threshold = frag_threshold;
		if (changed & WIPHY_PARAM_RTS_THRESHOLD)
			rdev->wiphy.rts_threshold = rts_threshold;
		if (changed & WIPHY_PARAM_COVERAGE_CLASS)
			rdev->wiphy.coverage_class = coverage_class;
		if (changed & WIPHY_PARAM_TXQ_LIMIT)
			rdev->wiphy.txq_limit = txq_limit;
		if (changed & WIPHY_PARAM_TXQ_MEMORY_LIMIT)
			rdev->wiphy.txq_memory_limit = txq_memory_limit;
		if (changed & WIPHY_PARAM_TXQ_QUANTUM)
			rdev->wiphy.txq_quantum = txq_quantum;

		result = rdev_set_wiphy_params(rdev, changed);
		if (result) {
			rdev->wiphy.retry_short = old_retry_short;
			rdev->wiphy.retry_long = old_retry_long;
			rdev->wiphy.frag_threshold = old_frag_threshold;
			rdev->wiphy.rts_threshold = old_rts_threshold;
			rdev->wiphy.coverage_class = old_coverage_class;
			rdev->wiphy.txq_limit = old_txq_limit;
			rdev->wiphy.txq_memory_limit = old_txq_memory_limit;
			rdev->wiphy.txq_quantum = old_txq_quantum;
			goto out;
		}
	}

	result = 0;

out:
	wiphy_unlock(&rdev->wiphy);
	return result;
}

static int nl80211_send_chandef(struct sk_buff *msg,
				const struct cfg80211_chan_def *chandef)
{
	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
		return -EINVAL;

	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
			chandef->chan->center_freq))
		return -ENOBUFS;
	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET,
			chandef->chan->freq_offset))
		return -ENOBUFS;
	switch (chandef->width) {
	case NL80211_CHAN_WIDTH_20_NOHT:
	case NL80211_CHAN_WIDTH_20:
	case NL80211_CHAN_WIDTH_40:
		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
				cfg80211_get_chandef_type(chandef)))
			return -ENOBUFS;
		break;
	default:
		break;
	}
	if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, chandef->width))
		return -ENOBUFS;
	if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, chandef->center_freq1))
		return -ENOBUFS;
	if (chandef->center_freq2 &&
	    nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, chandef->center_freq2))
		return -ENOBUFS;
	return 0;
}

static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags,
			      struct cfg80211_registered_device *rdev,
			      struct wireless_dev *wdev,
			      enum nl80211_commands cmd)
{
	struct net_device *dev = wdev->netdev;
	void *hdr;

	WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE &&
		cmd != NL80211_CMD_DEL_INTERFACE &&
		cmd != NL80211_CMD_SET_INTERFACE);

	hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
	if (!hdr)
		return -1;

	if (dev &&
	    (nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
	     nla_put_string(msg, NL80211_ATTR_IFNAME, dev->name)))
		goto nla_put_failure;

	if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
	    nla_put_u32(msg, NL80211_ATTR_IFTYPE, wdev->iftype) ||
	    nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
			      NL80211_ATTR_PAD) ||
	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, wdev_address(wdev)) ||
	    nla_put_u32(msg, NL80211_ATTR_GENERATION,
			rdev->devlist_generation ^
			(cfg80211_rdev_list_generation << 2)) ||
	    nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr))
		goto nla_put_failure;

	if (rdev->ops->get_channel && !wdev->valid_links) {
		struct cfg80211_chan_def chandef = {};
		int ret;

		ret = rdev_get_channel(rdev, wdev, 0, &chandef);
		if (ret == 0 && nl80211_send_chandef(msg, &chandef))
			goto nla_put_failure;
	}

	if (rdev->ops->get_tx_power) {
		int dbm, ret;

		ret = rdev_get_tx_power(rdev, wdev, &dbm);
		if (ret == 0 &&
		    nla_put_u32(msg, NL80211_ATTR_WIPHY_TX_POWER_LEVEL,
				DBM_TO_MBM(dbm)))
			goto nla_put_failure;
	}

	wdev_lock(wdev);
	switch (wdev->iftype) {
	case NL80211_IFTYPE_AP:
	case NL80211_IFTYPE_P2P_GO:
		if (wdev->u.ap.ssid_len &&
		    nla_put(msg, NL80211_ATTR_SSID, wdev->u.ap.ssid_len,
			    wdev->u.ap.ssid))
			goto nla_put_failure_locked;
		break;
	case NL80211_IFTYPE_STATION:
	case NL80211_IFTYPE_P2P_CLIENT:
		if (wdev->u.client.ssid_len &&
		    nla_put(msg, NL80211_ATTR_SSID, wdev->u.client.ssid_len,
			    wdev->u.client.ssid))
			goto nla_put_failure_locked;
		break;
	case NL80211_IFTYPE_ADHOC:
		if (wdev->u.ibss.ssid_len &&
		    nla_put(msg, NL80211_ATTR_SSID, wdev->u.ibss.ssid_len,
			    wdev->u.ibss.ssid))
			goto nla_put_failure_locked;
		break;
	default:
		/* nothing */
		break;
	}
	wdev_unlock(wdev);

	if (rdev->ops->get_txq_stats) {
		struct cfg80211_txq_stats txqstats = {};
		int ret = rdev_get_txq_stats(rdev, wdev, &txqstats);

		if (ret == 0 &&
		    !nl80211_put_txq_stats(msg, &txqstats,
					   NL80211_ATTR_TXQ_STATS))
			goto nla_put_failure;
	}

	if (wdev->valid_links) {
		unsigned int link_id;
		struct nlattr *links = nla_nest_start(msg,
						      NL80211_ATTR_MLO_LINKS);

		if (!links)
			goto nla_put_failure;

		for_each_valid_link(wdev, link_id) {
			struct nlattr *link = nla_nest_start(msg, link_id + 1);
			struct cfg80211_chan_def chandef = {};
			int ret;

			if (!link)
				goto nla_put_failure;

			if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
				goto nla_put_failure;
			if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
				    wdev->links[link_id].addr))
				goto nla_put_failure;

			ret = rdev_get_channel(rdev, wdev, link_id, &chandef);
			if (ret == 0 && nl80211_send_chandef(msg, &chandef))
				goto nla_put_failure;

			nla_nest_end(msg, link);
		}

		nla_nest_end(msg, links);
	}

	genlmsg_end(msg, hdr);
	return 0;

 nla_put_failure_locked:
	wdev_unlock(wdev);
 nla_put_failure:
	genlmsg_cancel(msg, hdr);
	return -EMSGSIZE;
}

static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb)
{
	int wp_idx = 0;
	int if_idx = 0;
	int wp_start = cb->args[0];
	int if_start = cb->args[1];
	int filter_wiphy = -1;
	struct cfg80211_registered_device *rdev;
	struct wireless_dev *wdev;
	int ret;

	rtnl_lock();
	if (!cb->args[2]) {
		struct nl80211_dump_wiphy_state state = {
			.filter_wiphy = -1,
		};

		ret = nl80211_dump_wiphy_parse(skb, cb, &state);
		if (ret)
			goto out_unlock;

		filter_wiphy = state.filter_wiphy;

		/*
		 * if filtering, set cb->args[2] to +1 since 0 is the default
		 * value needed to determine that parsing is necessary.
		 */
		if (filter_wiphy >= 0)
			cb->args[2] = filter_wiphy + 1;
		else
			cb->args[2] = -1;
	} else if (cb->args[2] > 0) {
		filter_wiphy = cb->args[2] - 1;
	}

	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
		if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
			continue;
		if (wp_idx < wp_start) {
			wp_idx++;
			continue;
		}

		if (filter_wiphy >= 0 && filter_wiphy != rdev->wiphy_idx)
			continue;

		if_idx = 0;

		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
			if (if_idx < if_start) {
				if_idx++;
				continue;
			}
			if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).portid,
					       cb->nlh->nlmsg_seq, NLM_F_MULTI,
					       rdev, wdev,
					       NL80211_CMD_NEW_INTERFACE) < 0) {
				goto out;
			}
			if_idx++;
		}

		wp_idx++;
	}
 out:
	cb->args[0] = wp_idx;
	cb->args[1] = if_idx;

	ret = skb->len;
 out_unlock:
	rtnl_unlock();

	return ret;
}

static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
{
	struct sk_buff *msg;
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct wireless_dev *wdev = info->user_ptr[1];

	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!msg)
		return -ENOMEM;

	if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
			       rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
		nlmsg_free(msg);
		return -ENOBUFS;
	}

	return genlmsg_reply(msg, info);
}

static const struct nla_policy mntr_flags_policy[NL80211_MNTR_FLAG_MAX + 1] = {
	[NL80211_MNTR_FLAG_FCSFAIL] = { .type = NLA_FLAG },
	[NL80211_MNTR_FLAG_PLCPFAIL] = { .type = NLA_FLAG },
	[NL80211_MNTR_FLAG_CONTROL] = { .type = NLA_FLAG },
	[NL80211_MNTR_FLAG_OTHER_BSS] = { .type = NLA_FLAG },
	[NL80211_MNTR_FLAG_COOK_FRAMES] = { .type = NLA_FLAG },
	[NL80211_MNTR_FLAG_ACTIVE] = { .type = NLA_FLAG },
};

static int parse_monitor_flags(struct nlattr *nla, u32 *mntrflags)
{
	struct nlattr *flags[NL80211_MNTR_FLAG_MAX + 1];
	int flag;

	*mntrflags = 0;

	if (!nla)
		return -EINVAL;

	if (nla_parse_nested_deprecated(flags, NL80211_MNTR_FLAG_MAX, nla, mntr_flags_policy, NULL))
		return -EINVAL;

	for (flag = 1; flag <= NL80211_MNTR_FLAG_MAX; flag++)
		if (flags[flag])
			*mntrflags |= (1<<flag);

	*mntrflags |= MONITOR_FLAG_CHANGED;

	return 0;
}

static int nl80211_parse_mon_options(struct cfg80211_registered_device *rdev,
				     enum nl80211_iftype type,
				     struct genl_info *info,
				     struct vif_params *params)
{
	bool change = false;
	int err;

	if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
		if (type != NL80211_IFTYPE_MONITOR)
			return -EINVAL;

		err = parse_monitor_flags(info->attrs[NL80211_ATTR_MNTR_FLAGS],
					  &params->flags);
		if (err)
			return err;

		change = true;
	}

	if (params->flags & MONITOR_FLAG_ACTIVE &&
	    !(rdev->wiphy.features & NL80211_FEATURE_ACTIVE_MONITOR))
		return -EOPNOTSUPP;

	if (info->attrs[NL80211_ATTR_MU_MIMO_GROUP_DATA]) {
		const u8 *mumimo_groups;
		u32 cap_flag = NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER;

		if (type != NL80211_IFTYPE_MONITOR)
			return -EINVAL;

		if (!wiphy_ext_feature_isset(&rdev->wiphy, cap_flag))
			return -EOPNOTSUPP;

		mumimo_groups =
			nla_data(info->attrs[NL80211_ATTR_MU_MIMO_GROUP_DATA]);

		/* bits 0 and 63 are reserved and must be zero */
		if ((mumimo_groups[0] & BIT(0)) ||
		    (mumimo_groups[VHT_MUMIMO_GROUPS_DATA_LEN - 1] & BIT(7)))
			return -EINVAL;

		params->vht_mumimo_groups = mumimo_groups;
		change = true;
	}

	if (info->attrs[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR]) {
		u32 cap_flag = NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER;

		if (type != NL80211_IFTYPE_MONITOR)
			return -EINVAL;

		if (!wiphy_ext_feature_isset(&rdev->wiphy, cap_flag))
			return -EOPNOTSUPP;

		params->vht_mumimo_follow_addr =
			nla_data(info->attrs[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR]);
		change = true;
	}

	return change ? 1 : 0;
}

static int nl80211_valid_4addr(struct cfg80211_registered_device *rdev,
			       struct net_device *netdev, u8 use_4addr,
			       enum nl80211_iftype iftype)
{
	if (!use_4addr) {
		if (netdev && netif_is_bridge_port(netdev))
			return -EBUSY;
		return 0;
	}

	switch (iftype) {
	case NL80211_IFTYPE_AP_VLAN:
		if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_AP)
			return 0;
		break;
	case NL80211_IFTYPE_STATION:
		if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_STATION)
			return 0;
		break;
	default:
		break;
	}

	return -EOPNOTSUPP;
}

static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct vif_params params;
	int err;
	enum nl80211_iftype otype, ntype;
	struct net_device *dev = info->user_ptr[1];
	bool change = false;

	memset(&params, 0, sizeof(params));

	otype = ntype = dev->ieee80211_ptr->iftype;

	if (info->attrs[NL80211_ATTR_IFTYPE]) {
		ntype = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
		if (otype != ntype)
			change = true;
	}

	if (info->attrs[NL80211_ATTR_MESH_ID]) {
		struct wireless_dev *wdev = dev->ieee80211_ptr;

		if (ntype != NL80211_IFTYPE_MESH_POINT)
			return -EINVAL;
		if (netif_running(dev))
			return -EBUSY;

		wdev_lock(wdev);
		BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
			     IEEE80211_MAX_MESH_ID_LEN);
		wdev->u.mesh.id_up_len =
			nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
		memcpy(wdev->u.mesh.id,
		       nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
		       wdev->u.mesh.id_up_len);
		wdev_unlock(wdev);
	}

	if (info->attrs[NL80211_ATTR_4ADDR]) {
		params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
		change = true;
		err = nl80211_valid_4addr(rdev, dev, params.use_4addr, ntype);
		if (err)
			return err;
	} else {
		params.use_4addr = -1;
	}

	err = nl80211_parse_mon_options(rdev, ntype, info, &params);
	if (err < 0)
		return err;
	if (err > 0)
		change = true;

	if (change)
		err = cfg80211_change_iface(rdev, dev, ntype, &params);
	else
		err = 0;

	if (!err && params.use_4addr != -1)
		dev->ieee80211_ptr->use_4addr = params.use_4addr;

	if (change && !err) {
		struct wireless_dev *wdev = dev->ieee80211_ptr;

		nl80211_notify_iface(rdev, wdev, NL80211_CMD_SET_INTERFACE);
	}

	return err;
}

static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct vif_params params;
	struct wireless_dev *wdev;
	struct sk_buff *msg;
	int err;
	enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;

	memset(&params, 0, sizeof(params));

	if (!info->attrs[NL80211_ATTR_IFNAME])
		return -EINVAL;

	if (info->attrs[NL80211_ATTR_IFTYPE])
		type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);

	if (!rdev->ops->add_virtual_intf)
		return -EOPNOTSUPP;

	if ((type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN ||
	     rdev->wiphy.features & NL80211_FEATURE_MAC_ON_CREATE) &&
	    info->attrs[NL80211_ATTR_MAC]) {
		nla_memcpy(params.macaddr, info->attrs[NL80211_ATTR_MAC],
			   ETH_ALEN);
		if (!is_valid_ether_addr(params.macaddr))
			return -EADDRNOTAVAIL;
	}

	if (info->attrs[NL80211_ATTR_4ADDR]) {
		params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
		err = nl80211_valid_4addr(rdev, NULL, params.use_4addr, type);
		if (err)
			return err;
	}

	if (!cfg80211_iftype_allowed(&rdev->wiphy, type, params.use_4addr, 0))
		return -EOPNOTSUPP;

	err = nl80211_parse_mon_options(rdev, type, info, &params);
	if (err < 0)
		return err;

	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!msg)
		return -ENOMEM;

	wdev = rdev_add_virtual_intf(rdev,
				nla_data(info->attrs[NL80211_ATTR_IFNAME]),
				NET_NAME_USER, type, &params);
	if (WARN_ON(!wdev)) {
		nlmsg_free(msg);
		return -EPROTO;
	} else if (IS_ERR(wdev)) {
		nlmsg_free(msg);
		return PTR_ERR(wdev);
	}

	if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
		wdev->owner_nlportid = info->snd_portid;

	switch (type) {
	case NL80211_IFTYPE_MESH_POINT:
		if (!info->attrs[NL80211_ATTR_MESH_ID])
			break;
		wdev_lock(wdev);
		BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
			     IEEE80211_MAX_MESH_ID_LEN);
		wdev->u.mesh.id_up_len =
			nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
		memcpy(wdev->u.mesh.id,
		       nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
		       wdev->u.mesh.id_up_len);
		wdev_unlock(wdev);
		break;
	case NL80211_IFTYPE_NAN:
	case NL80211_IFTYPE_P2P_DEVICE:
		/*
		 * P2P Device and NAN do not have a netdev, so don't go
		 * through the netdev notifier and must be added here
		 */
		cfg80211_init_wdev(wdev);
		cfg80211_register_wdev(rdev, wdev);
		break;
	default:
		break;
	}

	if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
			       rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
		nlmsg_free(msg);
		return -ENOBUFS;
	}

	return genlmsg_reply(msg, info);
}

static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int ret;

	/* to avoid failing a new interface creation due to pending removal */
	cfg80211_destroy_ifaces(rdev);

	wiphy_lock(&rdev->wiphy);
	ret = _nl80211_new_interface(skb, info);
	wiphy_unlock(&rdev->wiphy);

	return ret;
}

static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct wireless_dev *wdev = info->user_ptr[1];

	if (!rdev->ops->del_virtual_intf)
		return -EOPNOTSUPP;

	/*
	 * We hold RTNL, so this is safe, without RTNL opencount cannot
	 * reach 0, and thus the rdev cannot be deleted.
	 *
	 * We need to do it for the dev_close(), since that will call
	 * the netdev notifiers, and we need to acquire the mutex there
	 * but don't know if we get there from here or from some other
	 * place (e.g. "ip link set ... down").
	 */
	mutex_unlock(&rdev->wiphy.mtx);

	/*
	 * If we remove a wireless device without a netdev then clear
	 * user_ptr[1] so that nl80211_post_doit won't dereference it
	 * to check if it needs to do dev_put(). Otherwise it crashes
	 * since the wdev has been freed, unlike with a netdev where
	 * we need the dev_put() for the netdev to really be freed.
	 */
	if (!wdev->netdev)
		info->user_ptr[1] = NULL;
	else
		dev_close(wdev->netdev);

	mutex_lock(&rdev->wiphy.mtx);

	return cfg80211_remove_virtual_intf(rdev, wdev);
}

static int nl80211_set_noack_map(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct net_device *dev = info->user_ptr[1];
	u16 noack_map;

	if (!info->attrs[NL80211_ATTR_NOACK_MAP])
		return -EINVAL;

	if (!rdev->ops->set_noack_map)
		return -EOPNOTSUPP;

	noack_map = nla_get_u16(info->attrs[NL80211_ATTR_NOACK_MAP]);

	return rdev_set_noack_map(rdev, dev, noack_map);
}

static int nl80211_validate_key_link_id(struct genl_info *info,
					struct wireless_dev *wdev,
					int link_id, bool pairwise)
{
	if (pairwise) {
		if (link_id != -1) {
			GENL_SET_ERR_MSG(info,
					 "link ID not allowed for pairwise key");
			return -EINVAL;
		}

		return 0;
	}

	if (wdev->valid_links) {
		if (link_id == -1) {
			GENL_SET_ERR_MSG(info,
					 "link ID must for MLO group key");
			return -EINVAL;
		}
		if (!(wdev->valid_links & BIT(link_id))) {
			GENL_SET_ERR_MSG(info, "invalid link ID for MLO group key");
			return -EINVAL;
		}
	} else if (link_id != -1) {
		GENL_SET_ERR_MSG(info, "link ID not allowed for non-MLO group key");
		return -EINVAL;
	}

	return 0;
}

struct get_key_cookie {
	struct sk_buff *msg;
	int error;
	int idx;
};

static void get_key_callback(void *c, struct key_params *params)
{
	struct nlattr *key;
	struct get_key_cookie *cookie = c;

	if ((params->key &&
	     nla_put(cookie->msg, NL80211_ATTR_KEY_DATA,
		     params->key_len, params->key)) ||
	    (params->seq &&
	     nla_put(cookie->msg, NL80211_ATTR_KEY_SEQ,
		     params->seq_len, params->seq)) ||
	    (params->cipher &&
	     nla_put_u32(cookie->msg, NL80211_ATTR_KEY_CIPHER,
			 params->cipher)))
		goto nla_put_failure;

	key = nla_nest_start_noflag(cookie->msg, NL80211_ATTR_KEY);
	if (!key)
		goto nla_put_failure;

	if ((params->key &&
	     nla_put(cookie->msg, NL80211_KEY_DATA,
		     params->key_len, params->key)) ||
	    (params->seq &&
	     nla_put(cookie->msg, NL80211_KEY_SEQ,
		     params->seq_len, params->seq)) ||
	    (params->cipher &&
	     nla_put_u32(cookie->msg, NL80211_KEY_CIPHER,
			 params->cipher)))
		goto nla_put_failure;

	if (nla_put_u8(cookie->msg, NL80211_KEY_IDX, cookie->idx))
		goto nla_put_failure;

	nla_nest_end(cookie->msg, key);

	return;
 nla_put_failure:
	cookie->error = 1;
}

static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int err;
	struct net_device *dev = info->user_ptr[1];
	u8 key_idx = 0;
	const u8 *mac_addr = NULL;
	bool pairwise;
	struct get_key_cookie cookie = {
		.error = 0,
	};
	void *hdr;
	struct sk_buff *msg;
	bool bigtk_support = false;
	int link_id = nl80211_link_id_or_invalid(info->attrs);
	struct wireless_dev *wdev = dev->ieee80211_ptr;

	if (wiphy_ext_feature_isset(&rdev->wiphy,
				    NL80211_EXT_FEATURE_BEACON_PROTECTION))
		bigtk_support = true;

	if ((wdev->iftype == NL80211_IFTYPE_STATION ||
	     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
	    wiphy_ext_feature_isset(&rdev->wiphy,
				    NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT))
		bigtk_support = true;

	if (info->attrs[NL80211_ATTR_KEY_IDX]) {
		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

		if (key_idx >= 6 && key_idx <= 7 && !bigtk_support) {
			GENL_SET_ERR_MSG(info, "BIGTK not supported");
			return -EINVAL;
		}
	}

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	pairwise = !!mac_addr;
	if (info->attrs[NL80211_ATTR_KEY_TYPE]) {
		u32 kt = nla_get_u32(info->attrs[NL80211_ATTR_KEY_TYPE]);

		if (kt != NL80211_KEYTYPE_GROUP &&
		    kt != NL80211_KEYTYPE_PAIRWISE)
			return -EINVAL;
		pairwise = kt == NL80211_KEYTYPE_PAIRWISE;
	}

	if (!rdev->ops->get_key)
		return -EOPNOTSUPP;

	if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
		return -ENOENT;

	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!msg)
		return -ENOMEM;

	hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
			     NL80211_CMD_NEW_KEY);
	if (!hdr)
		goto nla_put_failure;

	cookie.msg = msg;
	cookie.idx = key_idx;

	if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
	    nla_put_u8(msg, NL80211_ATTR_KEY_IDX, key_idx))
		goto nla_put_failure;
	if (mac_addr &&
	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr))
		goto nla_put_failure;

	err = nl80211_validate_key_link_id(info, wdev, link_id, pairwise);
	if (err)
		goto free_msg;

	err = rdev_get_key(rdev, dev, link_id, key_idx, pairwise, mac_addr,
			   &cookie, get_key_callback);

	if (err)
		goto free_msg;

	if (cookie.error)
		goto nla_put_failure;

	genlmsg_end(msg, hdr);
	return genlmsg_reply(msg, info);

 nla_put_failure:
	err = -ENOBUFS;
 free_msg:
	nlmsg_free(msg);
	return err;
}

static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct key_parse key;
	int err;
	struct net_device *dev = info->user_ptr[1];
	int link_id = nl80211_link_id_or_invalid(info->attrs);
	struct wireless_dev *wdev = dev->ieee80211_ptr;

	err = nl80211_parse_key(info, &key);
	if (err)
		return err;

	if (key.idx < 0)
		return -EINVAL;

	/* Only support setting default key and
	 * Extended Key ID action NL80211_KEY_SET_TX.
	 */
	if (!key.def && !key.defmgmt && !key.defbeacon &&
	    !(key.p.mode == NL80211_KEY_SET_TX))
		return -EINVAL;

	wdev_lock(wdev);

	if (key.def) {
		if (!rdev->ops->set_default_key) {
			err = -EOPNOTSUPP;
			goto out;
		}

		err = nl80211_key_allowed(wdev);
		if (err)
			goto out;

		err = nl80211_validate_key_link_id(info, wdev, link_id, false);
		if (err)
			goto out;

		err = rdev_set_default_key(rdev, dev, link_id, key.idx,
					   key.def_uni, key.def_multi);

		if (err)
			goto out;

#ifdef CONFIG_CFG80211_WEXT
		wdev->wext.default_key = key.idx;
#endif
	} else if (key.defmgmt) {
		if (key.def_uni || !key.def_multi) {
			err = -EINVAL;
			goto out;
		}

		if (!rdev->ops->set_default_mgmt_key) {
			err = -EOPNOTSUPP;
			goto out;
		}

		err = nl80211_key_allowed(wdev);
		if (err)
			goto out;

		err = nl80211_validate_key_link_id(info, wdev, link_id, false);
		if (err)
			goto out;

		err = rdev_set_default_mgmt_key(rdev, dev, link_id, key.idx);
		if (err)
			goto out;

#ifdef CONFIG_CFG80211_WEXT
		wdev->wext.default_mgmt_key = key.idx;
#endif
	} else if (key.defbeacon) {
		if (key.def_uni || !key.def_multi) {
			err = -EINVAL;
			goto out;
		}

		if (!rdev->ops->set_default_beacon_key) {
			err = -EOPNOTSUPP;
			goto out;
		}

		err = nl80211_key_allowed(wdev);
		if (err)
			goto out;

		err = nl80211_validate_key_link_id(info, wdev, link_id, false);
		if (err)
			goto out;

		err = rdev_set_default_beacon_key(rdev, dev, link_id, key.idx);
		if (err)
			goto out;
	} else if (key.p.mode == NL80211_KEY_SET_TX &&
		   wiphy_ext_feature_isset(&rdev->wiphy,
					   NL80211_EXT_FEATURE_EXT_KEY_ID)) {
		u8 *mac_addr = NULL;

		if (info->attrs[NL80211_ATTR_MAC])
			mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

		if (!mac_addr || key.idx < 0 || key.idx > 1) {
			err = -EINVAL;
			goto out;
		}

		err = nl80211_validate_key_link_id(info, wdev, link_id, true);
		if (err)
			goto out;

		err = rdev_add_key(rdev, dev, link_id, key.idx,
				   NL80211_KEYTYPE_PAIRWISE,
				   mac_addr, &key.p);
	} else {
		err = -EINVAL;
	}
 out:
	wdev_unlock(wdev);

	return err;
}

static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int err;
	struct net_device *dev = info->user_ptr[1];
	struct key_parse key;
	const u8 *mac_addr = NULL;
	int link_id = nl80211_link_id_or_invalid(info->attrs);
	struct wireless_dev *wdev = dev->ieee80211_ptr;

	err = nl80211_parse_key(info, &key);
	if (err)
		return err;

	if (!key.p.key) {
		GENL_SET_ERR_MSG(info, "no key");
		return -EINVAL;
	}

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	if (key.type == -1) {
		if (mac_addr)
			key.type = NL80211_KEYTYPE_PAIRWISE;
		else
			key.type = NL80211_KEYTYPE_GROUP;
	}

	/* for now */
	if (key.type != NL80211_KEYTYPE_PAIRWISE &&
	    key.type != NL80211_KEYTYPE_GROUP) {
		GENL_SET_ERR_MSG(info, "key type not pairwise or group");
		return -EINVAL;
	}

	if (key.type == NL80211_KEYTYPE_GROUP &&
	    info->attrs[NL80211_ATTR_VLAN_ID])
		key.p.vlan_id = nla_get_u16(info->attrs[NL80211_ATTR_VLAN_ID]);

	if (!rdev->ops->add_key)
		return -EOPNOTSUPP;

	if (cfg80211_validate_key_settings(rdev, &key.p, key.idx,
					   key.type == NL80211_KEYTYPE_PAIRWISE,
					   mac_addr)) {
		GENL_SET_ERR_MSG(info, "key setting validation failed");
		return -EINVAL;
	}

	wdev_lock(wdev);
	err = nl80211_key_allowed(wdev);
	if (err)
		GENL_SET_ERR_MSG(info, "key not allowed");

	if (!err)
		err = nl80211_validate_key_link_id(info, wdev, link_id,
				key.type == NL80211_KEYTYPE_PAIRWISE);

	if (!err) {
		err = rdev_add_key(rdev, dev, link_id, key.idx,
				   key.type == NL80211_KEYTYPE_PAIRWISE,
				    mac_addr, &key.p);
		if (err)
			GENL_SET_ERR_MSG(info, "key addition failed");
	}
	wdev_unlock(wdev);

	return err;
}

static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int err;
	struct net_device *dev = info->user_ptr[1];
	u8 *mac_addr = NULL;
	struct key_parse key;
	int link_id = nl80211_link_id_or_invalid(info->attrs);
	struct wireless_dev *wdev = dev->ieee80211_ptr;

	err = nl80211_parse_key(info, &key);
	if (err)
		return err;

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	if (key.type == -1) {
		if (mac_addr)
			key.type = NL80211_KEYTYPE_PAIRWISE;
		else
			key.type = NL80211_KEYTYPE_GROUP;
	}

	/* for now */
	if (key.type != NL80211_KEYTYPE_PAIRWISE &&
	    key.type != NL80211_KEYTYPE_GROUP)
		return -EINVAL;

	if (!cfg80211_valid_key_idx(rdev, key.idx,
				    key.type == NL80211_KEYTYPE_PAIRWISE))
		return -EINVAL;

	if (!rdev->ops->del_key)
		return -EOPNOTSUPP;

	wdev_lock(wdev);
	err = nl80211_key_allowed(wdev);

	if (key.type == NL80211_KEYTYPE_GROUP && mac_addr &&
	    !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
		err = -ENOENT;

	if (!err)
		err = nl80211_validate_key_link_id(info, wdev, link_id,
				key.type == NL80211_KEYTYPE_PAIRWISE);

	if (!err)
		err = rdev_del_key(rdev, dev, link_id, key.idx,
				   key.type == NL80211_KEYTYPE_PAIRWISE,
				   mac_addr);

#ifdef CONFIG_CFG80211_WEXT
	if (!err) {
		if (key.idx == wdev->wext.default_key)
			wdev->wext.default_key = -1;
		else if (key.idx == wdev->wext.default_mgmt_key)
			wdev->wext.default_mgmt_key = -1;
	}
#endif
	wdev_unlock(wdev);

	return err;
}

/* This function returns an error or the number of nested attributes */
static int validate_acl_mac_addrs(struct nlattr *nl_attr)
{
	struct nlattr *attr;
	int n_entries = 0, tmp;

	nla_for_each_nested(attr, nl_attr, tmp) {
		if (nla_len(attr) != ETH_ALEN)
			return -EINVAL;

		n_entries++;
	}

	return n_entries;
}

/*
 * This function parses ACL information and allocates memory for ACL data.
 * On successful return, the calling function is responsible to free the
 * ACL buffer returned by this function.
 */
static struct cfg80211_acl_data *parse_acl_data(struct wiphy *wiphy,
						struct genl_info *info)
{
	enum nl80211_acl_policy acl_policy;
	struct nlattr *attr;
	struct cfg80211_acl_data *acl;
	int i = 0, n_entries, tmp;

	if (!wiphy->max_acl_mac_addrs)
		return ERR_PTR(-EOPNOTSUPP);

	if (!info->attrs[NL80211_ATTR_ACL_POLICY])
		return ERR_PTR(-EINVAL);

	acl_policy = nla_get_u32(info->attrs[NL80211_ATTR_ACL_POLICY]);
	if (acl_policy != NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED &&
	    acl_policy != NL80211_ACL_POLICY_DENY_UNLESS_LISTED)
		return ERR_PTR(-EINVAL);

	if (!info->attrs[NL80211_ATTR_MAC_ADDRS])
		return ERR_PTR(-EINVAL);

	n_entries = validate_acl_mac_addrs(info->attrs[NL80211_ATTR_MAC_ADDRS]);
	if (n_entries < 0)
		return ERR_PTR(n_entries);

	if (n_entries > wiphy->max_acl_mac_addrs)
		return ERR_PTR(-ENOTSUPP);

	acl = kzalloc(struct_size(acl, mac_addrs, n_entries), GFP_KERNEL);
	if (!acl)
		return ERR_PTR(-ENOMEM);

	nla_for_each_nested(attr, info->attrs[NL80211_ATTR_MAC_ADDRS], tmp) {
		memcpy(acl->mac_addrs[i].addr, nla_data(attr), ETH_ALEN);
		i++;
	}

	acl->n_acl_entries = n_entries;
	acl->acl_policy = acl_policy;

	return acl;
}

static int nl80211_set_mac_acl(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct net_device *dev = info->user_ptr[1];
	struct cfg80211_acl_data *acl;
	int err;

	if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
	    dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
		return -EOPNOTSUPP;

	if (!dev->ieee80211_ptr->links[0].ap.beacon_interval)
		return -EINVAL;

	acl = parse_acl_data(&rdev->wiphy, info);
	if (IS_ERR(acl))
		return PTR_ERR(acl);

	err = rdev_set_mac_acl(rdev, dev, acl);

	kfree(acl);

	return err;
}

static u32 rateset_to_mask(struct ieee80211_supported_band *sband,
			   u8 *rates, u8 rates_len)
{
	u8 i;
	u32 mask = 0;

	for (i = 0; i < rates_len; i++) {
		int rate = (rates[i] & 0x7f) * 5;
		int ridx;

		for (ridx = 0; ridx < sband->n_bitrates; ridx++) {
			struct ieee80211_rate *srate =
				&sband->bitrates[ridx];
			if (rate == srate->bitrate) {
				mask |= 1 << ridx;
				break;
			}
		}
		if (ridx == sband->n_bitrates)
			return 0; /* rate not found */
	}

	return mask;
}

static bool ht_rateset_to_mask(struct ieee80211_supported_band *sband,
			       u8 *rates, u8 rates_len,
			       u8 mcs[IEEE80211_HT_MCS_MASK_LEN])
{
	u8 i;

	memset(mcs, 0, IEEE80211_HT_MCS_MASK_LEN);

	for (i = 0; i < rates_len; i++) {
		int ridx, rbit;

		ridx = rates[i] / 8;
		rbit = BIT(rates[i] % 8);

		/* check validity */
		if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
			return false;

		/* check availability */
		ridx = array_index_nospec(ridx, IEEE80211_HT_MCS_MASK_LEN);
		if (sband->ht_cap.mcs.rx_mask[ridx] & rbit)
			mcs[ridx] |= rbit;
		else
			return false;
	}

	return true;
}

static u16 vht_mcs_map_to_mcs_mask(u8 vht_mcs_map)
{
	u16 mcs_mask = 0;

	switch (vht_mcs_map) {
	case IEEE80211_VHT_MCS_NOT_SUPPORTED:
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_7:
		mcs_mask = 0x00FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_8:
		mcs_mask = 0x01FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_9:
		mcs_mask = 0x03FF;
		break;
	default:
		break;
	}

	return mcs_mask;
}

static void vht_build_mcs_mask(u16 vht_mcs_map,
			       u16 vht_mcs_mask[NL80211_VHT_NSS_MAX])
{
	u8 nss;

	for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) {
		vht_mcs_mask[nss] = vht_mcs_map_to_mcs_mask(vht_mcs_map & 0x03);
		vht_mcs_map >>= 2;
	}
}

static bool vht_set_mcs_mask(struct ieee80211_supported_band *sband,
			     struct nl80211_txrate_vht *txrate,
			     u16 mcs[NL80211_VHT_NSS_MAX])
{
	u16 tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
	u16 tx_mcs_mask[NL80211_VHT_NSS_MAX] = {};
	u8 i;

	if (!sband->vht_cap.vht_supported)
		return false;

	memset(mcs, 0, sizeof(u16) * NL80211_VHT_NSS_MAX);

	/* Build vht_mcs_mask from VHT capabilities */
	vht_build_mcs_mask(tx_mcs_map, tx_mcs_mask);

	for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
		if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
			mcs[i] = txrate->mcs[i];
		else
			return false;
	}

	return true;
}

static u16 he_mcs_map_to_mcs_mask(u8 he_mcs_map)
{
	switch (he_mcs_map) {
	case IEEE80211_HE_MCS_NOT_SUPPORTED:
		return 0;
	case IEEE80211_HE_MCS_SUPPORT_0_7:
		return 0x00FF;
	case IEEE80211_HE_MCS_SUPPORT_0_9:
		return 0x03FF;
	case IEEE80211_HE_MCS_SUPPORT_0_11:
		return 0xFFF;
	default:
		break;
	}
	return 0;
}

static void he_build_mcs_mask(u16 he_mcs_map,
			      u16 he_mcs_mask[NL80211_HE_NSS_MAX])
{
	u8 nss;

	for (nss = 0; nss < NL80211_HE_NSS_MAX; nss++) {
		he_mcs_mask[nss] = he_mcs_map_to_mcs_mask(he_mcs_map & 0x03);
		he_mcs_map >>= 2;
	}
}

static u16 he_get_txmcsmap(struct genl_info *info, unsigned int link_id,
			   const struct ieee80211_sta_he_cap *he_cap)
{
	struct net_device *dev = info->user_ptr[1];
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_chan_def *chandef;
	__le16 tx_mcs;

	chandef = wdev_chandef(wdev, link_id);
	if (!chandef) {
		/*
		 * This is probably broken, but we never maintained
		 * a chandef in these cases, so it always was.
		 */
		return le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_80);
	}

	switch (chandef->width) {
	case NL80211_CHAN_WIDTH_80P80:
		tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80p80;
		break;
	case NL80211_CHAN_WIDTH_160:
		tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_160;
		break;
	default:
		tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80;
		break;
	}

	return le16_to_cpu(tx_mcs);
}

static bool he_set_mcs_mask(struct genl_info *info,
			    struct wireless_dev *wdev,
			    struct ieee80211_supported_band *sband,
			    struct nl80211_txrate_he *txrate,
			    u16 mcs[NL80211_HE_NSS_MAX],
			    unsigned int link_id)
{
	const struct ieee80211_sta_he_cap *he_cap;
	u16 tx_mcs_mask[NL80211_HE_NSS_MAX] = {};
	u16 tx_mcs_map = 0;
	u8 i;

	he_cap = ieee80211_get_he_iftype_cap(sband, wdev->iftype);
	if (!he_cap)
		return false;

	memset(mcs, 0, sizeof(u16) * NL80211_HE_NSS_MAX);

	tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);

	/* Build he_mcs_mask from HE capabilities */
	he_build_mcs_mask(tx_mcs_map, tx_mcs_mask);

	for (i = 0; i < NL80211_HE_NSS_MAX; i++) {
		if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
			mcs[i] = txrate->mcs[i];
		else
			return false;
	}

	return true;
}

static int nl80211_parse_tx_bitrate_mask(struct genl_info *info,
					 struct nlattr *attrs[],
					 enum nl80211_attrs attr,
					 struct cfg80211_bitrate_mask *mask,
					 struct net_device *dev,
					 bool default_all_enabled,
					 unsigned int link_id)
{
	struct nlattr *tb[NL80211_TXRATE_MAX + 1];
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	int rem, i;
	struct nlattr *tx_rates;
	struct ieee80211_supported_band *sband;
	u16 vht_tx_mcs_map, he_tx_mcs_map;

	memset(mask, 0, sizeof(*mask));
	/* Default to all rates enabled */
	for (i = 0; i < NUM_NL80211_BANDS; i++) {
		const struct ieee80211_sta_he_cap *he_cap;

		if (!default_all_enabled)
			break;

		sband = rdev->wiphy.bands[i];

		if (!sband)
			continue;

		mask->control[i].legacy = (1 << sband->n_bitrates) - 1;
		memcpy(mask->control[i].ht_mcs,
		       sband->ht_cap.mcs.rx_mask,
		       sizeof(mask->control[i].ht_mcs));

		if (sband->vht_cap.vht_supported) {
			vht_tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
			vht_build_mcs_mask(vht_tx_mcs_map, mask->control[i].vht_mcs);
		}

		he_cap = ieee80211_get_he_iftype_cap(sband, wdev->iftype);
		if (!he_cap)
			continue;

		he_tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);
		he_build_mcs_mask(he_tx_mcs_map, mask->control[i].he_mcs);

		mask->control[i].he_gi = 0xFF;
		mask->control[i].he_ltf = 0xFF;
	}

	/* if no rates are given set it back to the defaults */
	if (!attrs[attr])
		goto out;

	/* The nested attribute uses enum nl80211_band as the index. This maps
	 * directly to the enum nl80211_band values used in cfg80211.
	 */
	BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
	nla_for_each_nested(tx_rates, attrs[attr], rem) {
		enum nl80211_band band = nla_type(tx_rates);
		int err;

		if (band < 0 || band >= NUM_NL80211_BANDS)
			return -EINVAL;
		sband = rdev->wiphy.bands[band];
		if (sband == NULL)
			return -EINVAL;
		err = nla_parse_nested_deprecated(tb, NL80211_TXRATE_MAX,
						  tx_rates,
						  nl80211_txattr_policy,
						  info->extack);
		if (err)
			return err;
		if (tb[NL80211_TXRATE_LEGACY]) {
			mask->control[band].legacy = rateset_to_mask(
				sband,
				nla_data(tb[NL80211_TXRATE_LEGACY]),
				nla_len(tb[NL80211_TXRATE_LEGACY]));
			if ((mask->control[band].legacy == 0) &&
			    nla_len(tb[NL80211_TXRATE_LEGACY]))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_HT]) {
			if (!ht_rateset_to_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_HT]),
					nla_len(tb[NL80211_TXRATE_HT]),
					mask->control[band].ht_mcs))
				return -EINVAL;
		}

		if (tb[NL80211_TXRATE_VHT]) {
			if (!vht_set_mcs_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_VHT]),
					mask->control[band].vht_mcs))
				return -EINVAL;
		}

		if (tb[NL80211_TXRATE_GI]) {
			mask->control[band].gi =
				nla_get_u8(tb[NL80211_TXRATE_GI]);
			if (mask->control[band].gi > NL80211_TXRATE_FORCE_LGI)
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_HE] &&
		    !he_set_mcs_mask(info, wdev, sband,
				     nla_data(tb[NL80211_TXRATE_HE]),
				     mask->control[band].he_mcs,
				     link_id))
			return -EINVAL;

		if (tb[NL80211_TXRATE_HE_GI])
			mask->control[band].he_gi =
				nla_get_u8(tb[NL80211_TXRATE_HE_GI]);
		if (tb[NL80211_TXRATE_HE_LTF])
			mask->control[band].he_ltf =
				nla_get_u8(tb[NL80211_TXRATE_HE_LTF]);

		if (mask->control[band].legacy == 0) {
			/* don't allow empty legacy rates if HT, VHT or HE
			 * are not even supported.
			 */
			if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
			      rdev->wiphy.bands[band]->vht_cap.vht_supported ||
			      ieee80211_get_he_iftype_cap(sband, wdev->iftype)))
				return -EINVAL;

			for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++)
				if (mask->control[band].ht_mcs[i])
					goto out;

			for (i = 0; i < NL80211_VHT_NSS_MAX; i++)
				if (mask->control[band].vht_mcs[i])
					goto out;

			for (i = 0; i < NL80211_HE_NSS_MAX; i++)
				if (mask->control[band].he_mcs[i])
					goto out;

			/* legacy and mcs rates may not be both empty */
			return -EINVAL;
		}
	}

out:
	return 0;
}

static int validate_beacon_tx_rate(struct cfg80211_registered_device *rdev,
				   enum nl80211_band band,
				   struct cfg80211_bitrate_mask *beacon_rate)
{
	u32 count_ht, count_vht, count_he, i;
	u32 rate = beacon_rate->control[band].legacy;

	/* Allow only one rate */
	if (hweight32(rate) > 1)
		return -EINVAL;

	count_ht = 0;
	for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
		if (hweight8(beacon_rate->control[band].ht_mcs[i]) > 1) {
			return -EINVAL;
		} else if (beacon_rate->control[band].ht_mcs[i]) {
			count_ht++;
			if (count_ht > 1)
				return -EINVAL;
		}
		if (count_ht && rate)
			return -EINVAL;
	}

	count_vht = 0;
	for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
		if (hweight16(beacon_rate->control[band].vht_mcs[i]) > 1) {
			return -EINVAL;
		} else if (beacon_rate->control[band].vht_mcs[i]) {
			count_vht++;
			if (count_vht > 1)
				return -EINVAL;
		}
		if (count_vht && rate)
			return -EINVAL;
	}

	count_he = 0;
	for (i = 0; i < NL80211_HE_NSS_MAX; i++) {
		if (hweight16(beacon_rate->control[band].he_mcs[i]) > 1) {
			return -EINVAL;
		} else if (beacon_rate->control[band].he_mcs[i]) {
			count_he++;
			if (count_he > 1)
				return -EINVAL;
		}
		if (count_he && rate)
			return -EINVAL;
	}

	if ((count_ht && count_vht && count_he) ||
	    (!rate && !count_ht && !count_vht && !count_he))
		return -EINVAL;

	if (rate &&
	    !wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_BEACON_RATE_LEGACY))
		return -EINVAL;
	if (count_ht &&
	    !wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_BEACON_RATE_HT))
		return -EINVAL;
	if (count_vht &&
	    !wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_BEACON_RATE_VHT))
		return -EINVAL;
	if (count_he &&
	    !wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_BEACON_RATE_HE))
		return -EINVAL;

	return 0;
}

static int nl80211_parse_mbssid_config(struct wiphy *wiphy,
				       struct net_device *dev,
				       struct nlattr *attrs,
				       struct cfg80211_mbssid_config *config,
				       u8 num_elems)
{
	struct nlattr *tb[NL80211_MBSSID_CONFIG_ATTR_MAX + 1];

	if (!wiphy->mbssid_max_interfaces)
		return -EOPNOTSUPP;

	if (nla_parse_nested(tb, NL80211_MBSSID_CONFIG_ATTR_MAX, attrs, NULL,
			     NULL) ||
	    !tb[NL80211_MBSSID_CONFIG_ATTR_INDEX])
		return -EINVAL;

	config->ema = nla_get_flag(tb[NL80211_MBSSID_CONFIG_ATTR_EMA]);
	if (config->ema) {
		if (!wiphy->ema_max_profile_periodicity)
			return -EOPNOTSUPP;

		if (num_elems > wiphy->ema_max_profile_periodicity)
			return -EINVAL;
	}

	config->index = nla_get_u8(tb[NL80211_MBSSID_CONFIG_ATTR_INDEX]);
	if (config->index >= wiphy->mbssid_max_interfaces ||
	    (!config->index && !num_elems))
		return -EINVAL;

	if (tb[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX]) {
		u32 tx_ifindex =
			nla_get_u32(tb[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX]);

		if ((!config->index && tx_ifindex != dev->ifindex) ||
		    (config->index && tx_ifindex == dev->ifindex))
			return -EINVAL;

		if (tx_ifindex != dev->ifindex) {
			struct net_device *tx_netdev =
				dev_get_by_index(wiphy_net(wiphy), tx_ifindex);

			if (!tx_netdev || !tx_netdev->ieee80211_ptr ||
			    tx_netdev->ieee80211_ptr->wiphy != wiphy ||
			    tx_netdev->ieee80211_ptr->iftype !=
							NL80211_IFTYPE_AP) {
				dev_put(tx_netdev);
				return -EINVAL;
			}

			config->tx_wdev = tx_netdev->ieee80211_ptr;
		} else {
			config->tx_wdev = dev->ieee80211_ptr;
		}
	} else if (!config->index) {
		config->tx_wdev = dev->ieee80211_ptr;
	} else {
		return -EINVAL;
	}

	return 0;
}

static struct cfg80211_mbssid_elems *
nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs)
{
	struct nlattr *nl_elems;
	struct cfg80211_mbssid_elems *elems;
	int rem_elems;
	u8 i = 0, num_elems = 0;

	if (!wiphy->mbssid_max_interfaces)
		return ERR_PTR(-EINVAL);

	nla_for_each_nested(nl_elems, attrs, rem_elems)
		num_elems++;

	elems = kzalloc(struct_size(elems, elem, num_elems), GFP_KERNEL);
	if (!elems)
		return ERR_PTR(-ENOMEM);

	nla_for_each_nested(nl_elems, attrs, rem_elems) {
		elems->elem[i].data = nla_data(nl_elems);
		elems->elem[i].len = nla_len(nl_elems);
		i++;
	}
	elems->cnt = num_elems;
	return elems;
}

static int nl80211_parse_he_bss_color(struct nlattr *attrs,
				      struct cfg80211_he_bss_color *he_bss_color)
{
	struct nlattr *tb[NL80211_HE_BSS_COLOR_ATTR_MAX + 1];
	int err;

	err = nla_parse_nested(tb, NL80211_HE_BSS_COLOR_ATTR_MAX, attrs,
			       he_bss_color_policy, NULL);
	if (err)
		return err;

	if (!tb[NL80211_HE_BSS_COLOR_ATTR_COLOR])
		return -EINVAL;

	he_bss_color->color =
		nla_get_u8(tb[NL80211_HE_BSS_COLOR_ATTR_COLOR]);
	he_bss_color->enabled =
		!nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_DISABLED]);
	he_bss_color->partial =
		nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_PARTIAL]);

	return 0;
}

static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
				struct nlattr *attrs[],
				struct cfg80211_beacon_data *bcn)
{
	bool haveinfo = false;
	int err;

	memset(bcn, 0, sizeof(*bcn));

	bcn->link_id = nl80211_link_id(attrs);

	if (attrs[NL80211_ATTR_BEACON_HEAD]) {
		bcn->head = nla_data(attrs[NL80211_ATTR_BEACON_HEAD]);
		bcn->head_len = nla_len(attrs[NL80211_ATTR_BEACON_HEAD]);
		if (!bcn->head_len)
			return -EINVAL;
		haveinfo = true;
	}

	if (attrs[NL80211_ATTR_BEACON_TAIL]) {
		bcn->tail = nla_data(attrs[NL80211_ATTR_BEACON_TAIL]);
		bcn->tail_len = nla_len(attrs[NL80211_ATTR_BEACON_TAIL]);
		haveinfo = true;
	}

	if (!haveinfo)
		return -EINVAL;

	if (attrs[NL80211_ATTR_IE]) {
		bcn->beacon_ies = nla_data(attrs[NL80211_ATTR_IE]);
		bcn->beacon_ies_len = nla_len(attrs[NL80211_ATTR_IE]);
	}

	if (attrs[NL80211_ATTR_IE_PROBE_RESP]) {
		bcn->proberesp_ies =
			nla_data(attrs[NL80211_ATTR_IE_PROBE_RESP]);
		bcn->proberesp_ies_len =
			nla_len(attrs[NL80211_ATTR_IE_PROBE_RESP]);
	}

	if (attrs[NL80211_ATTR_IE_ASSOC_RESP]) {
		bcn->assocresp_ies =
			nla_data(attrs[NL80211_ATTR_IE_ASSOC_RESP]);
		bcn->assocresp_ies_len =
			nla_len(attrs[NL80211_ATTR_IE_ASSOC_RESP]);
	}

	if (attrs[NL80211_ATTR_PROBE_RESP]) {
		bcn->probe_resp = nla_data(attrs[NL80211_ATTR_PROBE_RESP]);
		bcn->probe_resp_len = nla_len(attrs[NL80211_ATTR_PROBE_RESP]);
	}

	if (attrs[NL80211_ATTR_FTM_RESPONDER]) {
		struct nlattr *tb[NL80211_FTM_RESP_ATTR_MAX + 1];

		err = nla_parse_nested_deprecated(tb,
						  NL80211_FTM_RESP_ATTR_MAX,
						  attrs[NL80211_ATTR_FTM_RESPONDER],
						  NULL, NULL);
		if (err)
			return err;

		if (tb[NL80211_FTM_RESP_ATTR_ENABLED] &&
		    wiphy_ext_feature_isset(&rdev->wiphy,
					    NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER))
			bcn->ftm_responder = 1;
		else
			return -EOPNOTSUPP;

		if (tb[NL80211_FTM_RESP_ATTR_LCI]) {
			bcn->lci = nla_data(tb[NL80211_FTM_RESP_ATTR_LCI]);
			bcn->lci_len = nla_len(tb[NL80211_FTM_RESP_ATTR_LCI]);
		}

		if (tb[NL80211_FTM_RESP_ATTR_CIVICLOC]) {
			bcn->civicloc = nla_data(tb[NL80211_FTM_RESP_ATTR_CIVICLOC]);
			bcn->civicloc_len = nla_len(tb[NL80211_FTM_RESP_ATTR_CIVICLOC]);
		}
	} else {
		bcn->ftm_responder = -1;
	}

	if (attrs[NL80211_ATTR_HE_BSS_COLOR]) {
		err = nl80211_parse_he_bss_color(attrs[NL80211_ATTR_HE_BSS_COLOR],
						 &bcn->he_bss_color);
		if (err)
			return err;
		bcn->he_bss_color_valid = true;
	}

	if (attrs[NL80211_ATTR_MBSSID_ELEMS]) {
		struct cfg80211_mbssid_elems *mbssid =
			nl80211_parse_mbssid_elems(&rdev->wiphy,
						   attrs[NL80211_ATTR_MBSSID_ELEMS]);

		if (IS_ERR(mbssid))
			return PTR_ERR(mbssid);

		bcn->mbssid_ies = mbssid;
	}

	return 0;
}

static int nl80211_parse_he_obss_pd(struct nlattr *attrs,
				    struct ieee80211_he_obss_pd *he_obss_pd)
{
	struct nlattr *tb[NL80211_HE_OBSS_PD_ATTR_MAX + 1];
	int err;

	err = nla_parse_nested(tb, NL80211_HE_OBSS_PD_ATTR_MAX, attrs,
			       he_obss_pd_policy, NULL);
	if (err)
		return err;

	if (!tb[NL80211_HE_OBSS_PD_ATTR_SR_CTRL])
		return -EINVAL;

	he_obss_pd->sr_ctrl = nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_SR_CTRL]);

	if (tb[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET])
		he_obss_pd->min_offset =
			nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET]);
	if (tb[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET])
		he_obss_pd->max_offset =
			nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET]);
	if (tb[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET])
		he_obss_pd->non_srg_max_offset =
			nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET]);

	if (he_obss_pd->min_offset > he_obss_pd->max_offset)
		return -EINVAL;

	if (tb[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP])
		memcpy(he_obss_pd->bss_color_bitmap,
		       nla_data(tb[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP]),
		       sizeof(he_obss_pd->bss_color_bitmap));

	if (tb[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP])
		memcpy(he_obss_pd->partial_bssid_bitmap,
		       nla_data(tb[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP]),
		       sizeof(he_obss_pd->partial_bssid_bitmap));

	he_obss_pd->enable = true;

	return 0;
}

static int nl80211_parse_fils_discovery(struct cfg80211_registered_device *rdev,
					struct nlattr *attrs,
					struct cfg80211_ap_settings *params)
{
	struct nlattr *tb[NL80211_FILS_DISCOVERY_ATTR_MAX + 1];
	int ret;
	struct cfg80211_fils_discovery *fd = &params->fils_discovery;

	if (!wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_FILS_DISCOVERY))
		return -EINVAL;

	ret = nla_parse_nested(tb, NL80211_FILS_DISCOVERY_ATTR_MAX, attrs,
			       NULL, NULL);
	if (ret)
		return ret;

	if (!tb[NL80211_FILS_DISCOVERY_ATTR_INT_MIN] ||
	    !tb[NL80211_FILS_DISCOVERY_ATTR_INT_MAX] ||
	    !tb[NL80211_FILS_DISCOVERY_ATTR_TMPL])
		return -EINVAL;

	fd->tmpl_len = nla_len(tb[NL80211_FILS_DISCOVERY_ATTR_TMPL]);
	fd->tmpl = nla_data(tb[NL80211_FILS_DISCOVERY_ATTR_TMPL]);
	fd->min_interval = nla_get_u32(tb[NL80211_FILS_DISCOVERY_ATTR_INT_MIN]);
	fd->max_interval = nla_get_u32(tb[NL80211_FILS_DISCOVERY_ATTR_INT_MAX]);

	return 0;
}

static int
nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
				     struct nlattr *attrs,
				     struct cfg80211_ap_settings *params)
{
	struct nlattr *tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX + 1];
	int ret;
	struct cfg80211_unsol_bcast_probe_resp *presp =
					&params->unsol_bcast_probe_resp;

	if (!wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_UNSOL_BCAST_PROBE_RESP))
		return -EINVAL;

	ret = nla_parse_nested(tb, NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX,
			       attrs, NULL, NULL);
	if (ret)
		return ret;

	if (!tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT] ||
	    !tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL])
		return -EINVAL;

	presp->tmpl = nla_data(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL]);
	presp->tmpl_len = nla_len(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL]);
	presp->interval = nla_get_u32(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT]);
	return 0;
}

static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
					    const struct element *rates)
{
	int i;

	if (!rates)
		return;

	for (i = 0; i < rates->datalen; i++) {
		if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
			params->ht_required = true;
		if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
			params->vht_required = true;
		if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY)
			params->he_required = true;
		if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E)
			params->sae_h2e_required = true;
	}
}

/*
 * Since the nl80211 API didn't include, from the beginning, attributes about
 * HT/VHT requirements/capabilities, we parse them out of the IEs for the
 * benefit of drivers that rebuild IEs in the firmware.
 */
static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
{
	const struct cfg80211_beacon_data *bcn = &params->beacon;
	size_t ies_len = bcn->tail_len;
	const u8 *ies = bcn->tail;
	const struct element *rates;
	const struct element *cap;

	rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
	nl80211_check_ap_rate_selectors(params, rates);

	rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
	nl80211_check_ap_rate_selectors(params, rates);

	cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len);
	if (cap && cap->datalen >= sizeof(*params->ht_cap))
		params->ht_cap = (void *)cap->data;
	cap = cfg80211_find_elem(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
	if (cap && cap->datalen >= sizeof(*params->vht_cap))
		params->vht_cap = (void *)cap->data;
	cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
	if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
		params->he_cap = (void *)(cap->data + 1);
	cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
	if (cap && cap->datalen >= sizeof(*params->he_oper) + 1)
		params->he_oper = (void *)(cap->data + 1);
	cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
	if (cap) {
		if (!cap->datalen)
			return -EINVAL;
		params->eht_cap = (void *)(cap->data + 1);
		if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap,
						(const u8 *)params->eht_cap,
						cap->datalen - 1, true))
			return -EINVAL;
	}
	cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
	if (cap) {
		if (!cap->datalen)
			return -EINVAL;
		params->eht_oper = (void *)(cap->data + 1);
		if (!ieee80211_eht_oper_size_ok((const u8 *)params->eht_oper,
						cap->datalen - 1))
			return -EINVAL;
	}
	return 0;
}

static bool nl80211_get_ap_channel(struct cfg80211_registered_device *rdev,
				   struct cfg80211_ap_settings *params)
{
	struct wireless_dev *wdev;

	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
		if (wdev->iftype != NL80211_IFTYPE_AP &&
		    wdev->iftype != NL80211_IFTYPE_P2P_GO)
			continue;

		if (!wdev->u.ap.preset_chandef.chan)
			continue;

		params->chandef = wdev->u.ap.preset_chandef;
		return true;
	}

	return false;
}

static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev,
				    enum nl80211_auth_type auth_type,
				    enum nl80211_commands cmd)
{
	if (auth_type > NL80211_AUTHTYPE_MAX)
		return false;

	switch (cmd) {
	case NL80211_CMD_AUTHENTICATE:
		if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) &&
		    auth_type == NL80211_AUTHTYPE_SAE)
			return false;
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_FILS_STA) &&
		    (auth_type == NL80211_AUTHTYPE_FILS_SK ||
		     auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
		     auth_type == NL80211_AUTHTYPE_FILS_PK))
			return false;
		return true;
	case NL80211_CMD_CONNECT:
		if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) &&
		    !wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_SAE_OFFLOAD) &&
		    auth_type == NL80211_AUTHTYPE_SAE)
			return false;

		/* FILS with SK PFS or PK not supported yet */
		if (auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
		    auth_type == NL80211_AUTHTYPE_FILS_PK)
			return false;
		if (!wiphy_ext_feature_isset(
			    &rdev->wiphy,
			    NL80211_EXT_FEATURE_FILS_SK_OFFLOAD) &&
		    auth_type == NL80211_AUTHTYPE_FILS_SK)
			return false;
		return true;
	case NL80211_CMD_START_AP:
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_SAE_OFFLOAD_AP) &&
		    auth_type == NL80211_AUTHTYPE_SAE)
			return false;
		/* FILS not supported yet */
		if (auth_type == NL80211_AUTHTYPE_FILS_SK ||
		    auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
		    auth_type == NL80211_AUTHTYPE_FILS_PK)
			return false;
		return true;
	default:
		return false;
	}
}

static void nl80211_send_ap_started(struct wireless_dev *wdev,
				    unsigned int link_id)
{
	struct wiphy *wiphy = wdev->wiphy;
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
	struct sk_buff *msg;
	void *hdr;

	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!msg)
		return;

	hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_START_AP);
	if (!hdr)
		goto out;

	if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
	    nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex) ||
	    nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
			      NL80211_ATTR_PAD) ||
	    (wdev->u.ap.ssid_len &&
	     nla_put(msg, NL80211_ATTR_SSID, wdev->u.ap.ssid_len,
		     wdev->u.ap.ssid)) ||
	    (wdev->valid_links &&
	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)))
		goto out;

	genlmsg_end(msg, hdr);

	genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), msg, 0,
				NL80211_MCGRP_MLME, GFP_KERNEL);
	return;
out:
	nlmsg_free(msg);
}

static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	unsigned int link_id = nl80211_link_id(info->attrs);
	struct net_device *dev = info->user_ptr[1];
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_ap_settings *params;
	int err;

	if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
	    dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
		return -EOPNOTSUPP;

	if (!rdev->ops->start_ap)
		return -EOPNOTSUPP;

	if (wdev->links[link_id].ap.beacon_interval)
		return -EALREADY;

	/* these are required for START_AP */
	if (!info->attrs[NL80211_ATTR_BEACON_INTERVAL] ||
	    !info->attrs[NL80211_ATTR_DTIM_PERIOD] ||
	    !info->attrs[NL80211_ATTR_BEACON_HEAD])
		return -EINVAL;

	params = kzalloc(sizeof(*params), GFP_KERNEL);
	if (!params)
		return -ENOMEM;

	err = nl80211_parse_beacon(rdev, info->attrs, &params->beacon);
	if (err)
		goto out;

	params->beacon_interval =
		nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
	params->dtim_period =
		nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);

	err = cfg80211_validate_beacon_int(rdev, dev->ieee80211_ptr->iftype,
					   params->beacon_interval);
	if (err)
		goto out;

	/*
	 * In theory, some of these attributes should be required here
	 * but since they were not used when the command was originally
	 * added, keep them optional for old user space programs to let
	 * them continue to work with drivers that do not need the
	 * additional information -- drivers must check!
	 */
	if (info->attrs[NL80211_ATTR_SSID]) {
		params->ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
		params->ssid_len =
			nla_len(info->attrs[NL80211_ATTR_SSID]);
		if (params->ssid_len == 0) {
			err = -EINVAL;
			goto out;
		}

		if (wdev->u.ap.ssid_len &&
		    (wdev->u.ap.ssid_len != params->ssid_len ||
		     memcmp(wdev->u.ap.ssid, params->ssid, params->ssid_len))) {
			/* require identical SSID for MLO */
			err = -EINVAL;
			goto out;
		}
	} else if (wdev->valid_links) {
		/* require SSID for MLO */
		err = -EINVAL;
		goto out;
	}

	if (info->attrs[NL80211_ATTR_HIDDEN_SSID])
		params->hidden_ssid = nla_get_u32(
			info->attrs[NL80211_ATTR_HIDDEN_SSID]);

	params->privacy = !!info->attrs[NL80211_ATTR_PRIVACY];

	if (info->attrs[NL80211_ATTR_AUTH_TYPE]) {
		params->auth_type = nla_get_u32(
			info->attrs[NL80211_ATTR_AUTH_TYPE]);
		if (!nl80211_valid_auth_type(rdev, params->auth_type,
					     NL80211_CMD_START_AP)) {
			err = -EINVAL;
			goto out;
		}
	} else
		params->auth_type = NL80211_AUTHTYPE_AUTOMATIC;

	err = nl80211_crypto_settings(rdev, info, &params->crypto,
				      NL80211_MAX_NR_CIPHER_SUITES);
	if (err)
		goto out;

	if (info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]) {
		if (!(rdev->wiphy.features & NL80211_FEATURE_INACTIVITY_TIMER)) {
			err = -EOPNOTSUPP;
			goto out;
		}
		params->inactivity_timeout = nla_get_u16(
			info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]);
	}

	if (info->attrs[NL80211_ATTR_P2P_CTWINDOW]) {
		if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
			err = -EINVAL;
			goto out;
		}
		params->p2p_ctwindow =
			nla_get_u8(info->attrs[NL80211_ATTR_P2P_CTWINDOW]);
		if (params->p2p_ctwindow != 0 &&
		    !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_CTWIN)) {
			err = -EINVAL;
			goto out;
		}
	}

	if (info->attrs[NL80211_ATTR_P2P_OPPPS]) {
		u8 tmp;

		if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
			err = -EINVAL;
			goto out;
		}
		tmp = nla_get_u8(info->attrs[NL80211_ATTR_P2P_OPPPS]);
		params->p2p_opp_ps = tmp;
		if (params->p2p_opp_ps != 0 &&
		    !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_OPPPS)) {
			err = -EINVAL;
			goto out;
		}
	}

	if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
		err = nl80211_parse_chandef(rdev, info, &params->chandef);
		if (err)
			goto out;
	} else if (wdev->valid_links) {
		/* with MLD need to specify the channel configuration */
		err = -EINVAL;
		goto out;
	} else if (wdev->u.ap.preset_chandef.chan) {
		params->chandef = wdev->u.ap.preset_chandef;
	} else if (!nl80211_get_ap_channel(rdev, params)) {
		err = -EINVAL;
		goto out;
	}

	if (info->attrs[NL80211_ATTR_PUNCT_BITMAP]) {
		err = nl80211_parse_punct_bitmap(rdev, info,
						 &params->chandef,
						 &params->punct_bitmap);
		if (err)
			goto out;
	}

	if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &params->chandef,
					   wdev->iftype)) {
		err = -EINVAL;
		goto out;
	}

	wdev_lock(wdev);

	if (info->attrs[NL80211_ATTR_TX_RATES]) {
		err = nl80211_parse_tx_bitrate_mask(info, info->attrs,
						    NL80211_ATTR_TX_RATES,
						    &params->beacon_rate,
						    dev, false, link_id);
		if (err)
			goto out_unlock;

		err = validate_beacon_tx_rate(rdev, params->chandef.chan->band,
					      &params->beacon_rate);
		if (err)
			goto out_unlock;
	}

	if (info->attrs[NL80211_ATTR_SMPS_MODE]) {
		params->smps_mode =
			nla_get_u8(info->attrs[NL80211_ATTR_SMPS_MODE]);
		switch (params->smps_mode) {
		case NL80211_SMPS_OFF:
			break;
		case NL80211_SMPS_STATIC:
			if (!(rdev->wiphy.features &
			      NL80211_FEATURE_STATIC_SMPS)) {
				err = -EINVAL;
				goto out_unlock;
			}
			break;
		case NL80211_SMPS_DYNAMIC:
			if (!(rdev->wiphy.features &
			      NL80211_FEATURE_DYNAMIC_SMPS)) {
				err = -EINVAL;
				goto out_unlock;
			}
			break;
		default:
			err = -EINVAL;
			goto out_unlock;
		}
	} else {
		params->smps_mode = NL80211_SMPS_OFF;
	}

	params->pbss = nla_get_flag(info->attrs[NL80211_ATTR_PBSS]);
	if (params->pbss && !rdev->