static int nl80211_channel_switch(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 wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_csa_settings params;
	/* csa_attrs is defined static to avoid waste of stack size - this
	 * function is called under RTNL lock, so this should not be a problem.
	 */
	static struct nlattr *csa_attrs[NL80211_ATTR_MAX+1];
	int err;
	bool need_new_beacon = false;
	bool need_handle_dfs_flag = true;
	int len, i;
	u32 cs_count;

	if (!rdev->ops->channel_switch ||
	    !(rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH))
		return -EOPNOTSUPP;

	switch (dev->ieee80211_ptr->iftype) {
	case NL80211_IFTYPE_AP:
	case NL80211_IFTYPE_P2P_GO:
		need_new_beacon = true;
		/* For all modes except AP the handle_dfs flag needs to be
		 * supplied to tell the kernel that userspace will handle radar
		 * events when they happen. Otherwise a switch to a channel
		 * requiring DFS will be rejected.
		 */
		need_handle_dfs_flag = false;

		/* useless if AP is not running */
		if (!wdev->beacon_interval)
			return -ENOTCONN;
		break;
	case NL80211_IFTYPE_ADHOC:
		if (!wdev->ssid_len)
			return -ENOTCONN;
		break;
	case NL80211_IFTYPE_MESH_POINT:
		if (!wdev->mesh_id_len)
			return -ENOTCONN;
		break;
	default:
		return -EOPNOTSUPP;
	}

	memset(&params, 0, sizeof(params));
	params.beacon_csa.ftm_responder = -1;

	if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
	    !info->attrs[NL80211_ATTR_CH_SWITCH_COUNT])
		return -EINVAL;

	/* only important for AP, IBSS and mesh create IEs internally */
	if (need_new_beacon && !info->attrs[NL80211_ATTR_CSA_IES])
		return -EINVAL;

	/* Even though the attribute is u32, the specification says
	 * u8, so let's make sure we don't overflow.
	 */
	cs_count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
	if (cs_count > 255)
		return -EINVAL;

	params.count = cs_count;

	if (!need_new_beacon)
		goto skip_beacons;

	err = nl80211_parse_beacon(rdev, info->attrs, &params.beacon_after);
	if (err)
		return err;

	err = nla_parse_nested_deprecated(csa_attrs, NL80211_ATTR_MAX,
					  info->attrs[NL80211_ATTR_CSA_IES],
					  nl80211_policy, info->extack);
	if (err)
		return err;

	err = nl80211_parse_beacon(rdev, csa_attrs, &params.beacon_csa);
	if (err)
		return err;

	if (!csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON])
		return -EINVAL;

	len = nla_len(csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON]);
	if (!len || (len % sizeof(u16)))
		return -EINVAL;

	params.n_counter_offsets_beacon = len / sizeof(u16);
	if (rdev->wiphy.max_num_csa_counters &&
	    (params.n_counter_offsets_beacon >
	     rdev->wiphy.max_num_csa_counters))
		return -EINVAL;

	params.counter_offsets_beacon =
		nla_data(csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON]);

	/* sanity checks - counters should fit and be the same */
	for (i = 0; i < params.n_counter_offsets_beacon; i++) {
		u16 offset = params.counter_offsets_beacon[i];

		if (offset >= params.beacon_csa.tail_len)
			return -EINVAL;

		if (params.beacon_csa.tail[offset] != params.count)
			return -EINVAL;
	}

	if (csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]) {
		len = nla_len(csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]);
		if (!len || (len % sizeof(u16)))
			return -EINVAL;

		params.n_counter_offsets_presp = len / sizeof(u16);
		if (rdev->wiphy.max_num_csa_counters &&
		    (params.n_counter_offsets_presp >
		     rdev->wiphy.max_num_csa_counters))
			return -EINVAL;

		params.counter_offsets_presp =
			nla_data(csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]);

		/* sanity checks - counters should fit and be the same */
		for (i = 0; i < params.n_counter_offsets_presp; i++) {
			u16 offset = params.counter_offsets_presp[i];

			if (offset >= params.beacon_csa.probe_resp_len)
				return -EINVAL;

			if (params.beacon_csa.probe_resp[offset] !=
			    params.count)
				return -EINVAL;
		}
	}

skip_beacons:
	err = nl80211_parse_chandef(rdev, info, &params.chandef);
	if (err)
		return err;

	if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &params.chandef,
					   wdev->iftype))
		return -EINVAL;

	err = cfg80211_chandef_dfs_required(wdev->wiphy,
					    &params.chandef,
					    wdev->iftype);
	if (err < 0)
		return err;

	if (err > 0) {
		params.radar_required = true;
		if (need_handle_dfs_flag &&
		    !nla_get_flag(info->attrs[NL80211_ATTR_HANDLE_DFS])) {
			return -EINVAL;
		}
	}

	if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
		params.block_tx = true;

	wdev_lock(wdev);
	err = rdev_channel_switch(rdev, dev, &params);
	wdev_unlock(wdev);

	return err;
}