|
@@ -173,6 +173,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
|
|
|
[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 },
|
|
|
};
|
|
|
|
|
|
/* policy for the key attributes */
|
|
@@ -194,6 +195,15 @@ nl80211_key_default_policy[NUM_NL80211_KEY_DEFAULT_TYPES] = {
|
|
|
[NL80211_KEY_DEFAULT_TYPE_MULTICAST] = { .type = NLA_FLAG },
|
|
|
};
|
|
|
|
|
|
+/* 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 },
|
|
|
+};
|
|
|
+
|
|
|
/* ifidx get helper */
|
|
|
static int nl80211_get_ifidx(struct netlink_callback *cb)
|
|
|
{
|
|
@@ -821,6 +831,35 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
|
|
|
nla_nest_end(msg, nl_ifs);
|
|
|
}
|
|
|
|
|
|
+ if (dev->wiphy.wowlan.flags || dev->wiphy.wowlan.n_patterns) {
|
|
|
+ struct nlattr *nl_wowlan;
|
|
|
+
|
|
|
+ nl_wowlan = nla_nest_start(msg,
|
|
|
+ NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED);
|
|
|
+ if (!nl_wowlan)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_ANY)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY);
|
|
|
+ if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_DISCONNECT)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT);
|
|
|
+ if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_MAGIC_PKT)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT);
|
|
|
+ if (dev->wiphy.wowlan.n_patterns) {
|
|
|
+ struct nl80211_wowlan_pattern_support pat = {
|
|
|
+ .max_patterns = dev->wiphy.wowlan.n_patterns,
|
|
|
+ .min_pattern_len =
|
|
|
+ dev->wiphy.wowlan.pattern_min_len,
|
|
|
+ .max_pattern_len =
|
|
|
+ dev->wiphy.wowlan.pattern_max_len,
|
|
|
+ };
|
|
|
+ NLA_PUT(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN,
|
|
|
+ sizeof(pat), &pat);
|
|
|
+ }
|
|
|
+
|
|
|
+ nla_nest_end(msg, nl_wowlan);
|
|
|
+ }
|
|
|
+
|
|
|
return genlmsg_end(msg, hdr);
|
|
|
|
|
|
nla_put_failure:
|
|
@@ -4808,6 +4847,194 @@ static int nl80211_leave_mesh(struct sk_buff *skb, struct genl_info *info)
|
|
|
return cfg80211_leave_mesh(rdev, dev);
|
|
|
}
|
|
|
|
|
|
+static int nl80211_get_wowlan(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ struct sk_buff *msg;
|
|
|
+ void *hdr;
|
|
|
+
|
|
|
+ if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
+ if (!msg)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
|
|
|
+ NL80211_CMD_GET_WOWLAN);
|
|
|
+ if (!hdr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (rdev->wowlan) {
|
|
|
+ struct nlattr *nl_wowlan;
|
|
|
+
|
|
|
+ nl_wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
|
|
|
+ if (!nl_wowlan)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (rdev->wowlan->any)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY);
|
|
|
+ if (rdev->wowlan->disconnect)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT);
|
|
|
+ if (rdev->wowlan->magic_pkt)
|
|
|
+ NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT);
|
|
|
+ if (rdev->wowlan->n_patterns) {
|
|
|
+ struct nlattr *nl_pats, *nl_pat;
|
|
|
+ int i, pat_len;
|
|
|
+
|
|
|
+ nl_pats = nla_nest_start(msg,
|
|
|
+ NL80211_WOWLAN_TRIG_PKT_PATTERN);
|
|
|
+ if (!nl_pats)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ for (i = 0; i < rdev->wowlan->n_patterns; i++) {
|
|
|
+ nl_pat = nla_nest_start(msg, i + 1);
|
|
|
+ if (!nl_pat)
|
|
|
+ goto nla_put_failure;
|
|
|
+ pat_len = rdev->wowlan->patterns[i].pattern_len;
|
|
|
+ NLA_PUT(msg, NL80211_WOWLAN_PKTPAT_MASK,
|
|
|
+ DIV_ROUND_UP(pat_len, 8),
|
|
|
+ rdev->wowlan->patterns[i].mask);
|
|
|
+ NLA_PUT(msg, NL80211_WOWLAN_PKTPAT_PATTERN,
|
|
|
+ pat_len,
|
|
|
+ rdev->wowlan->patterns[i].pattern);
|
|
|
+ nla_nest_end(msg, nl_pat);
|
|
|
+ }
|
|
|
+ nla_nest_end(msg, nl_pats);
|
|
|
+ }
|
|
|
+
|
|
|
+ nla_nest_end(msg, nl_wowlan);
|
|
|
+ }
|
|
|
+
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+ return genlmsg_reply(msg, info);
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return -ENOBUFS;
|
|
|
+}
|
|
|
+
|
|
|
+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 no_triggers = {};
|
|
|
+ struct cfg80211_wowlan new_triggers = {};
|
|
|
+ struct wiphy_wowlan_support *wowlan = &rdev->wiphy.wowlan;
|
|
|
+ int err, i;
|
|
|
+
|
|
|
+ if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ if (!info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
|
|
|
+ goto no_triggers;
|
|
|
+
|
|
|
+ err = nla_parse(tb, MAX_NL80211_WOWLAN_TRIG,
|
|
|
+ nla_data(info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
|
|
|
+ nla_len(info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
|
|
|
+ nl80211_wowlan_policy);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_WOWLAN_TRIG_MAGIC_PKT]) {
|
|
|
+ if (!(wowlan->flags & WIPHY_WOWLAN_MAGIC_PKT))
|
|
|
+ return -EINVAL;
|
|
|
+ new_triggers.magic_pkt = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_WOWLAN_TRIG_PKT_PATTERN]) {
|
|
|
+ struct nlattr *pat;
|
|
|
+ int n_patterns = 0;
|
|
|
+ int rem, pat_len, mask_len;
|
|
|
+ struct nlattr *pat_tb[NUM_NL80211_WOWLAN_PKTPAT];
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ nla_parse(pat_tb, MAX_NL80211_WOWLAN_PKTPAT,
|
|
|
+ nla_data(pat), nla_len(pat), NULL);
|
|
|
+ err = -EINVAL;
|
|
|
+ if (!pat_tb[NL80211_WOWLAN_PKTPAT_MASK] ||
|
|
|
+ !pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN])
|
|
|
+ goto error;
|
|
|
+ pat_len = nla_len(pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]);
|
|
|
+ mask_len = DIV_ROUND_UP(pat_len, 8);
|
|
|
+ if (nla_len(pat_tb[NL80211_WOWLAN_PKTPAT_MASK]) !=
|
|
|
+ mask_len)
|
|
|
+ goto error;
|
|
|
+ if (pat_len > wowlan->pattern_max_len ||
|
|
|
+ pat_len < wowlan->pattern_min_len)
|
|
|
+ goto error;
|
|
|
+
|
|
|
+ new_triggers.patterns[i].mask =
|
|
|
+ kmalloc(mask_len + pat_len, GFP_KERNEL);
|
|
|
+ if (!new_triggers.patterns[i].mask) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ new_triggers.patterns[i].pattern =
|
|
|
+ new_triggers.patterns[i].mask + mask_len;
|
|
|
+ memcpy(new_triggers.patterns[i].mask,
|
|
|
+ nla_data(pat_tb[NL80211_WOWLAN_PKTPAT_MASK]),
|
|
|
+ mask_len);
|
|
|
+ new_triggers.patterns[i].pattern_len = pat_len;
|
|
|
+ memcpy(new_triggers.patterns[i].pattern,
|
|
|
+ nla_data(pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]),
|
|
|
+ pat_len);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (memcmp(&new_triggers, &no_triggers, sizeof(new_triggers))) {
|
|
|
+ struct cfg80211_wowlan *ntrig;
|
|
|
+ ntrig = kmemdup(&new_triggers, sizeof(new_triggers),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!ntrig) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ cfg80211_rdev_free_wowlan(rdev);
|
|
|
+ rdev->wowlan = ntrig;
|
|
|
+ } else {
|
|
|
+ no_triggers:
|
|
|
+ cfg80211_rdev_free_wowlan(rdev);
|
|
|
+ rdev->wowlan = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ error:
|
|
|
+ for (i = 0; i < new_triggers.n_patterns; i++)
|
|
|
+ kfree(new_triggers.patterns[i].mask);
|
|
|
+ kfree(new_triggers.patterns);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
#define NL80211_FLAG_NEED_WIPHY 0x01
|
|
|
#define NL80211_FLAG_NEED_NETDEV 0x02
|
|
|
#define NL80211_FLAG_NEED_RTNL 0x04
|
|
@@ -5306,6 +5533,22 @@ static struct genl_ops nl80211_ops[] = {
|
|
|
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
|
|
|
NL80211_FLAG_NEED_RTNL,
|
|
|
},
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_GET_WOWLAN,
|
|
|
+ .doit = nl80211_get_wowlan,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ /* can be retrieved by unprivileged users */
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WIPHY |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_SET_WOWLAN,
|
|
|
+ .doit = nl80211_set_wowlan,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WIPHY |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
static struct genl_multicast_group nl80211_mlme_mcgrp = {
|