static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct nlattr *tb[NUM_NL80211_WOWLAN_TRIG];
	struct cfg80211_wowlan new_triggers = {};
	struct cfg80211_wowlan *ntrig;
	const struct wiphy_wowlan_support *wowlan = rdev->wiphy.wowlan;
	int err, i;
	bool prev_enabled = rdev->wiphy.wowlan_config;
	bool regular = false;

	if (!wowlan)
		return -EOPNOTSUPP;

	if (!info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) {
		cfg80211_rdev_free_wowlan(rdev);
		rdev->wiphy.wowlan_config = NULL;
		goto set_wakeup;
	}

	err = nla_parse_nested_deprecated(tb, MAX_NL80211_WOWLAN_TRIG,
					  info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS],
					  nl80211_wowlan_policy, info->extack);
	if (err)
		return err;

	if (tb[NL80211_WOWLAN_TRIG_ANY]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_ANY))
			return -EINVAL;
		new_triggers.any = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_DISCONNECT]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_DISCONNECT))
			return -EINVAL;
		new_triggers.disconnect = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_MAGIC_PKT]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_MAGIC_PKT))
			return -EINVAL;
		new_triggers.magic_pkt = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED])
		return -EINVAL;

	if (tb[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE))
			return -EINVAL;
		new_triggers.gtk_rekey_failure = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ))
			return -EINVAL;
		new_triggers.eap_identity_req = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_4WAY_HANDSHAKE))
			return -EINVAL;
		new_triggers.four_way_handshake = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) {
		if (!(wowlan->flags & WIPHY_WOWLAN_RFKILL_RELEASE))
			return -EINVAL;
		new_triggers.rfkill_release = true;
		regular = true;
	}

	if (tb[NL80211_WOWLAN_TRIG_PKT_PATTERN]) {
		struct nlattr *pat;
		int n_patterns = 0;
		int rem, pat_len, mask_len, pkt_offset;
		struct nlattr *pat_tb[NUM_NL80211_PKTPAT];

		regular = true;

		nla_for_each_nested(pat, tb[NL80211_WOWLAN_TRIG_PKT_PATTERN],
				    rem)
			n_patterns++;
		if (n_patterns > wowlan->n_patterns)
			return -EINVAL;

		new_triggers.patterns = kcalloc(n_patterns,
						sizeof(new_triggers.patterns[0]),
						GFP_KERNEL);
		if (!new_triggers.patterns)
			return -ENOMEM;

		new_triggers.n_patterns = n_patterns;
		i = 0;

		nla_for_each_nested(pat, tb[NL80211_WOWLAN_TRIG_PKT_PATTERN],
				    rem) {
			u8 *mask_pat;

			err = nla_parse_nested_deprecated(pat_tb,
							  MAX_NL80211_PKTPAT,
							  pat,
							  nl80211_packet_pattern_policy,
							  info->extack);
			if (err)
				goto error;

			err = -EINVAL;
			if (!pat_tb[NL80211_PKTPAT_MASK] ||
			    !pat_tb[NL80211_PKTPAT_PATTERN])
				goto error;
			pat_len = nla_len(pat_tb[NL80211_PKTPAT_PATTERN]);
			mask_len = DIV_ROUND_UP(pat_len, 8);
			if (nla_len(pat_tb[NL80211_PKTPAT_MASK]) != mask_len)
				goto error;
			if (pat_len > wowlan->pattern_max_len ||
			    pat_len < wowlan->pattern_min_len)
				goto error;

			if (!pat_tb[NL80211_PKTPAT_OFFSET])
				pkt_offset = 0;
			else
				pkt_offset = nla_get_u32(
					pat_tb[NL80211_PKTPAT_OFFSET]);
			if (pkt_offset > wowlan->max_pkt_offset)
				goto error;
			new_triggers.patterns[i].pkt_offset = pkt_offset;

			mask_pat = kmalloc(mask_len + pat_len, GFP_KERNEL);
			if (!mask_pat) {
				err = -ENOMEM;
				goto error;
			}
			new_triggers.patterns[i].mask = mask_pat;
			memcpy(mask_pat, nla_data(pat_tb[NL80211_PKTPAT_MASK]),
			       mask_len);
			mask_pat += mask_len;
			new_triggers.patterns[i].pattern = mask_pat;
			new_triggers.patterns[i].pattern_len = pat_len;
			memcpy(mask_pat,
			       nla_data(pat_tb[NL80211_PKTPAT_PATTERN]),
			       pat_len);
			i++;
		}
	}

	if (tb[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) {
		regular = true;
		err = nl80211_parse_wowlan_tcp(
			rdev, tb[NL80211_WOWLAN_TRIG_TCP_CONNECTION],
			&new_triggers);
		if (err)
			goto error;
	}

	if (tb[NL80211_WOWLAN_TRIG_NET_DETECT]) {
		regular = true;
		err = nl80211_parse_wowlan_nd(
			rdev, wowlan, tb[NL80211_WOWLAN_TRIG_NET_DETECT],
			&new_triggers);
		if (err)
			goto error;
	}

	/* The 'any' trigger means the device continues operating more or less
	 * as in its normal operation mode and wakes up the host on most of the
	 * normal interrupts (like packet RX, ...)
	 * It therefore makes little sense to combine with the more constrained
	 * wakeup trigger modes.
	 */
	if (new_triggers.any && regular) {
		err = -EINVAL;
		goto error;
	}

	ntrig = kmemdup(&new_triggers, sizeof(new_triggers), GFP_KERNEL);
	if (!ntrig) {
		err = -ENOMEM;
		goto error;
	}
	cfg80211_rdev_free_wowlan(rdev);
	rdev->wiphy.wowlan_config = ntrig;

 set_wakeup:
	if (rdev->ops->set_wakeup &&
	    prev_enabled != !!rdev->wiphy.wowlan_config)
		rdev_set_wakeup(rdev, rdev->wiphy.wowlan_config);

	return 0;
 error:
	for (i = 0; i < new_triggers.n_patterns; i++)
		kfree(new_triggers.patterns[i].mask);
	kfree(new_triggers.patterns);
	if (new_triggers.tcp && new_triggers.tcp->sock)
		sock_release(new_triggers.tcp->sock);
	kfree(new_triggers.tcp);
	kfree(new_triggers.nd_config);
	return err;
}