static int nl80211_trigger_scan(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];
	struct cfg80211_scan_request *request;
	struct nlattr *attr;
	struct wiphy *wiphy;
	int err, tmp, n_ssids = 0, n_channels, i;
	size_t ie_len;

	wiphy = &rdev->wiphy;

	if (wdev->iftype == NL80211_IFTYPE_NAN)
		return -EOPNOTSUPP;

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

	if (rdev->scan_req || rdev->scan_msg) {
		err = -EBUSY;
		goto unlock;
	}

	if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
		n_channels = validate_scan_freqs(
				info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]);
		if (!n_channels) {
			err = -EINVAL;
			goto unlock;
		}
	} else {
		n_channels = ieee80211_get_num_supported_channels(wiphy);
	}

	if (info->attrs[NL80211_ATTR_SCAN_SSIDS])
		nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp)
			n_ssids++;

	if (n_ssids > wiphy->max_scan_ssids) {
		err = -EINVAL;
		goto unlock;
	}

	if (info->attrs[NL80211_ATTR_IE])
		ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
	else
		ie_len = 0;

	if (ie_len > wiphy->max_scan_ie_len) {
		err = -EINVAL;
		goto unlock;
	}

	request = kzalloc(sizeof(*request)
			+ sizeof(*request->ssids) * n_ssids
			+ sizeof(*request->channels) * n_channels
			+ ie_len, GFP_KERNEL);
	if (!request) {
		err = -ENOMEM;
		goto unlock;
	}

	if (n_ssids)
		request->ssids = (void *)&request->channels[n_channels];
	request->n_ssids = n_ssids;
	if (ie_len) {
		if (n_ssids)
			request->ie = (void *)(request->ssids + n_ssids);
		else
			request->ie = (void *)(request->channels + n_channels);
	}

	i = 0;
	if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
		/* user specified, bail out if channel not found */
		nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) {
			struct ieee80211_channel *chan;

			chan = ieee80211_get_channel(wiphy, nla_get_u32(attr));

			if (!chan) {
				err = -EINVAL;
				goto out_free;
			}

			/* ignore disabled channels */
			if (chan->flags & IEEE80211_CHAN_DISABLED)
				continue;

			request->channels[i] = chan;
			i++;
		}
	} else {
		enum nl80211_band band;

		/* all channels */
		for (band = 0; band < NUM_NL80211_BANDS; band++) {
			int j;

			if (!wiphy->bands[band])
				continue;
			for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
				struct ieee80211_channel *chan;

				chan = &wiphy->bands[band]->channels[j];

				if (chan->flags & IEEE80211_CHAN_DISABLED)
					continue;

				request->channels[i] = chan;
				i++;
			}
		}
	}

	if (!i) {
		err = -EINVAL;
		goto out_free;
	}

	request->n_channels = i;

	wdev_lock(wdev);
	if (!cfg80211_off_channel_oper_allowed(wdev)) {
		struct ieee80211_channel *chan;

		if (request->n_channels != 1) {
			wdev_unlock(wdev);
			err = -EBUSY;
			goto out_free;
		}

		chan = request->channels[0];
		if (chan->center_freq != wdev->chandef.chan->center_freq) {
			wdev_unlock(wdev);
			err = -EBUSY;
			goto out_free;
		}
	}
	wdev_unlock(wdev);

	i = 0;
	if (n_ssids) {
		nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) {
			if (nla_len(attr) > IEEE80211_MAX_SSID_LEN) {
				err = -EINVAL;
				goto out_free;
			}
			request->ssids[i].ssid_len = nla_len(attr);
			memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr));
			i++;
		}
	}

	if (info->attrs[NL80211_ATTR_IE]) {
		request->ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
		memcpy((void *)request->ie,
		       nla_data(info->attrs[NL80211_ATTR_IE]),
		       request->ie_len);
	}

	for (i = 0; i < NUM_NL80211_BANDS; i++)
		if (wiphy->bands[i])
			request->rates[i] =
				(1 << wiphy->bands[i]->n_bitrates) - 1;

	if (info->attrs[NL80211_ATTR_SCAN_SUPP_RATES]) {
		nla_for_each_nested(attr,
				    info->attrs[NL80211_ATTR_SCAN_SUPP_RATES],
				    tmp) {
			enum nl80211_band band = nla_type(attr);

			if (band < 0 || band >= NUM_NL80211_BANDS) {
				err = -EINVAL;
				goto out_free;
			}

			if (!wiphy->bands[band])
				continue;

			err = ieee80211_get_ratemask(wiphy->bands[band],
						     nla_data(attr),
						     nla_len(attr),
						     &request->rates[band]);
			if (err)
				goto out_free;
		}
	}

	if (info->attrs[NL80211_ATTR_MEASUREMENT_DURATION]) {
		if (!wiphy_ext_feature_isset(wiphy,
					NL80211_EXT_FEATURE_SET_SCAN_DWELL)) {
			err = -EOPNOTSUPP;
			goto out_free;
		}

		request->duration =
			nla_get_u16(info->attrs[NL80211_ATTR_MEASUREMENT_DURATION]);
		request->duration_mandatory =
			nla_get_flag(info->attrs[NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY]);
	}

	err = nl80211_check_scan_flags(wiphy, wdev, request, info->attrs,
				       false);
	if (err)
		goto out_free;

	request->no_cck =
		nla_get_flag(info->attrs[NL80211_ATTR_TX_NO_CCK_RATE]);

	/* Initial implementation used NL80211_ATTR_MAC to set the specific
	 * BSSID to scan for. This was problematic because that same attribute
	 * was already used for another purpose (local random MAC address). The
	 * NL80211_ATTR_BSSID attribute was added to fix this. For backwards
	 * compatibility with older userspace components, also use the
	 * NL80211_ATTR_MAC value here if it can be determined to be used for
	 * the specific BSSID use case instead of the random MAC address
	 * (NL80211_ATTR_SCAN_FLAGS is used to enable random MAC address use).
	 */
	if (info->attrs[NL80211_ATTR_BSSID])
		memcpy(request->bssid,
		       nla_data(info->attrs[NL80211_ATTR_BSSID]), ETH_ALEN);
	else if (!(request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) &&
		 info->attrs[NL80211_ATTR_MAC])
		memcpy(request->bssid, nla_data(info->attrs[NL80211_ATTR_MAC]),
		       ETH_ALEN);
	else
		eth_broadcast_addr(request->bssid);

	request->wdev = wdev;
	request->wiphy = &rdev->wiphy;
	request->scan_start = jiffies;

	rdev->scan_req = request;
	err = rdev_scan(rdev, request);

	if (!err) {
		nl80211_send_scan_start(rdev, wdev);
		if (wdev->netdev)
			dev_hold(wdev->netdev);
	} else {
 out_free:
		rdev->scan_req = NULL;
		kfree(request);
	}

 unlock:
	return err;
}