summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2011-05-04 15:37:28 +0200
committerJohn W. Linville <linville@tuxdriver.com>2011-05-05 14:59:19 -0400
commitff1b6e69ad4f31fb3c9c6da2665655f2e798dd70 (patch)
tree6fc049fd0389ffb382ea401096d7bd665642af5c /net
parent8f7f3b2fcc4ccbba0be776049df41a2f96c986ac (diff)
downloadlinux-ff1b6e69ad4f31fb3c9c6da2665655f2e798dd70.tar.gz
linux-ff1b6e69ad4f31fb3c9c6da2665655f2e798dd70.tar.bz2
linux-ff1b6e69ad4f31fb3c9c6da2665655f2e798dd70.zip
nl80211/cfg80211: WoWLAN support
This is based on (but now quite far from) the original work from Luis and Eliad. Add support for configuring WoWLAN triggers, and getting the configuration out again. Changes from the original patchset are too numerous to list, but one important change needs highlighting: the suspend() callback is passed NULL for the trigger configuration if userspace has not configured WoWLAN at all. Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com> Signed-off-by: Eliad Peller <eliad@wizery.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/cfg.c3
-rw-r--r--net/wireless/core.c8
-rw-r--r--net/wireless/core.h14
-rw-r--r--net/wireless/nl80211.c243
-rw-r--r--net/wireless/sysfs.c2
5 files changed, 268 insertions, 2 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 12d52cec9515..321d598eb8cb 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1297,7 +1297,8 @@ static int ieee80211_set_channel(struct wiphy *wiphy,
}
#ifdef CONFIG_PM
-static int ieee80211_suspend(struct wiphy *wiphy)
+static int ieee80211_suspend(struct wiphy *wiphy,
+ struct cfg80211_wowlan *wowlan)
{
return __ieee80211_suspend(wiphy_priv(wiphy));
}
diff --git a/net/wireless/core.c b/net/wireless/core.c
index bbf1fa11107a..bea0d80710c8 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -493,6 +493,13 @@ int wiphy_register(struct wiphy *wiphy)
return -EINVAL;
}
+ if (rdev->wiphy.wowlan.n_patterns) {
+ if (WARN_ON(!rdev->wiphy.wowlan.pattern_min_len ||
+ rdev->wiphy.wowlan.pattern_min_len >
+ rdev->wiphy.wowlan.pattern_max_len))
+ return -EINVAL;
+ }
+
/* check and set up bitrates */
ieee80211_set_bitrate_flags(wiphy);
@@ -631,6 +638,7 @@ void cfg80211_dev_free(struct cfg80211_registered_device *rdev)
mutex_destroy(&rdev->devlist_mtx);
list_for_each_entry_safe(scan, tmp, &rdev->bss_list, list)
cfg80211_put_bss(&scan->pub);
+ cfg80211_rdev_free_wowlan(rdev);
kfree(rdev);
}
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 26a0a084e16b..7a18c10a7fb6 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -70,6 +70,8 @@ struct cfg80211_registered_device {
struct work_struct conn_work;
struct work_struct event_work;
+ struct cfg80211_wowlan *wowlan;
+
/* must be last because of the way we do wiphy_priv(),
* and it should at least be aligned to NETDEV_ALIGN */
struct wiphy wiphy __attribute__((__aligned__(NETDEV_ALIGN)));
@@ -89,6 +91,18 @@ bool wiphy_idx_valid(int wiphy_idx)
return wiphy_idx >= 0;
}
+static inline void
+cfg80211_rdev_free_wowlan(struct cfg80211_registered_device *rdev)
+{
+ int i;
+
+ if (!rdev->wowlan)
+ return;
+ for (i = 0; i < rdev->wowlan->n_patterns; i++)
+ kfree(rdev->wowlan->patterns[i].mask);
+ kfree(rdev->wowlan->patterns);
+ kfree(rdev->wowlan);
+}
extern struct workqueue_struct *cfg80211_wq;
extern struct mutex cfg80211_mutex;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ab77f943dc04..0a199a1ca9b6 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -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 = {
diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c
index 4294fa22bb2d..c6e4ca6a7d2e 100644
--- a/net/wireless/sysfs.c
+++ b/net/wireless/sysfs.c
@@ -93,7 +93,7 @@ static int wiphy_suspend(struct device *dev, pm_message_t state)
if (rdev->ops->suspend) {
rtnl_lock();
- ret = rdev->ops->suspend(&rdev->wiphy);
+ ret = rdev->ops->suspend(&rdev->wiphy, rdev->wowlan);
rtnl_unlock();
}