]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - net/wireless/nl80211.c
Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[linux-2.6-omap-h63xx.git] / net / wireless / nl80211.c
index e69da8d204740ecb0f51789f0f385f1fe35a9c9a..298a4de5994833b39380d7936e75dbf66e98a85b 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/nl80211.h>
 #include <linux/rtnetlink.h>
 #include <linux/netlink.h>
+#include <linux/etherdevice.h>
 #include <net/genetlink.h>
 #include <net/cfg80211.h>
 #include "core.h"
@@ -109,6 +110,8 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 },
        [NL80211_ATTR_IE] = { .type = NLA_BINARY,
                              .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED },
+       [NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED },
 };
 
 /* message building helper */
@@ -141,6 +144,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
 
        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx);
        NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy));
+       NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
+                  dev->wiphy.max_scan_ssids);
 
        nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES);
        if (!nl_modes)
@@ -2093,6 +2098,81 @@ static int nl80211_set_mesh_params(struct sk_buff *skb, struct genl_info *info)
 
 #undef FILL_IN_MESH_PARAM_IF_SET
 
+static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
+{
+       struct sk_buff *msg;
+       void *hdr = NULL;
+       struct nlattr *nl_reg_rules;
+       unsigned int i;
+       int err = -EINVAL;
+
+       mutex_lock(&cfg80211_drv_mutex);
+
+       if (!cfg80211_regdomain)
+               goto out;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg) {
+               err = -ENOBUFS;
+               goto out;
+       }
+
+       hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
+                            NL80211_CMD_GET_REG);
+       if (!hdr)
+               goto nla_put_failure;
+
+       NLA_PUT_STRING(msg, NL80211_ATTR_REG_ALPHA2,
+               cfg80211_regdomain->alpha2);
+
+       nl_reg_rules = nla_nest_start(msg, NL80211_ATTR_REG_RULES);
+       if (!nl_reg_rules)
+               goto nla_put_failure;
+
+       for (i = 0; i < cfg80211_regdomain->n_reg_rules; i++) {
+               struct nlattr *nl_reg_rule;
+               const struct ieee80211_reg_rule *reg_rule;
+               const struct ieee80211_freq_range *freq_range;
+               const struct ieee80211_power_rule *power_rule;
+
+               reg_rule = &cfg80211_regdomain->reg_rules[i];
+               freq_range = &reg_rule->freq_range;
+               power_rule = &reg_rule->power_rule;
+
+               nl_reg_rule = nla_nest_start(msg, i);
+               if (!nl_reg_rule)
+                       goto nla_put_failure;
+
+               NLA_PUT_U32(msg, NL80211_ATTR_REG_RULE_FLAGS,
+                       reg_rule->flags);
+               NLA_PUT_U32(msg, NL80211_ATTR_FREQ_RANGE_START,
+                       freq_range->start_freq_khz);
+               NLA_PUT_U32(msg, NL80211_ATTR_FREQ_RANGE_END,
+                       freq_range->end_freq_khz);
+               NLA_PUT_U32(msg, NL80211_ATTR_FREQ_RANGE_MAX_BW,
+                       freq_range->max_bandwidth_khz);
+               NLA_PUT_U32(msg, NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN,
+                       power_rule->max_antenna_gain);
+               NLA_PUT_U32(msg, NL80211_ATTR_POWER_RULE_MAX_EIRP,
+                       power_rule->max_eirp);
+
+               nla_nest_end(msg, nl_reg_rule);
+       }
+
+       nla_nest_end(msg, nl_reg_rules);
+
+       genlmsg_end(msg, hdr);
+       err = genlmsg_unicast(msg, info->snd_pid);
+       goto out;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       err = -EMSGSIZE;
+out:
+       mutex_unlock(&cfg80211_drv_mutex);
+       return err;
+}
+
 static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
 {
        struct nlattr *tb[NL80211_REG_RULE_ATTR_MAX + 1];
@@ -2195,6 +2275,246 @@ static int nl80211_set_mgmt_extra_ie(struct sk_buff *skb,
        return err;
 }
 
+static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       struct net_device *dev;
+       struct cfg80211_scan_request *request;
+       struct cfg80211_ssid *ssid;
+       struct ieee80211_channel *channel;
+       struct nlattr *attr;
+       struct wiphy *wiphy;
+       int err, tmp, n_ssids = 0, n_channels = 0, i;
+       enum ieee80211_band band;
+
+       err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+       if (err)
+               return err;
+
+       wiphy = &drv->wiphy;
+
+       if (!drv->ops->scan) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+
+       if (drv->scan_req) {
+               err = -EBUSY;
+               goto out_unlock;
+       }
+
+       if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp)
+                       n_channels++;
+               if (!n_channels) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+       } else {
+               for (band = 0; band < IEEE80211_NUM_BANDS; band++)
+                       if (wiphy->bands[band])
+                               n_channels += wiphy->bands[band]->n_channels;
+       }
+
+       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 out_unlock;
+       }
+
+       request = kzalloc(sizeof(*request)
+                       + sizeof(*ssid) * n_ssids
+                       + sizeof(channel) * n_channels, GFP_KERNEL);
+       if (!request) {
+               err = -ENOMEM;
+               goto out_unlock;
+       }
+
+       request->channels = (void *)((char *)request + sizeof(*request));
+       request->n_channels = n_channels;
+       if (n_ssids)
+               request->ssids = (void *)(request->channels + n_channels);
+       request->n_ssids = n_ssids;
+
+       if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+               /* user specified, bail out if channel not found */
+               request->n_channels = n_channels;
+               i = 0;
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) {
+                       request->channels[i] = ieee80211_get_channel(wiphy, nla_get_u32(attr));
+                       if (!request->channels[i]) {
+                               err = -EINVAL;
+                               goto out_free;
+                       }
+                       i++;
+               }
+       } else {
+               /* all channels */
+               i = 0;
+               for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+                       int j;
+                       if (!wiphy->bands[band])
+                               continue;
+                       for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
+                               request->channels[i] = &wiphy->bands[band]->channels[j];
+                               i++;
+                       }
+               }
+       }
+
+       i = 0;
+       if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) {
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) {
+                       if (request->ssids[i].ssid_len > IEEE80211_MAX_SSID_LEN) {
+                               err = -EINVAL;
+                               goto out_free;
+                       }
+                       memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr));
+                       request->ssids[i].ssid_len = nla_len(attr);
+                       i++;
+               }
+       }
+
+       request->ifidx = dev->ifindex;
+       request->wiphy = &drv->wiphy;
+
+       drv->scan_req = request;
+       err = drv->ops->scan(&drv->wiphy, dev, request);
+
+ out_free:
+       if (err) {
+               drv->scan_req = NULL;
+               kfree(request);
+       }
+ out_unlock:
+       rtnl_unlock();
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
+                           struct cfg80211_registered_device *rdev,
+                           struct net_device *dev,
+                           struct cfg80211_bss *res)
+{
+       void *hdr;
+       struct nlattr *bss;
+
+       hdr = nl80211hdr_put(msg, pid, seq, flags,
+                            NL80211_CMD_NEW_SCAN_RESULTS);
+       if (!hdr)
+               return -1;
+
+       NLA_PUT_U32(msg, NL80211_ATTR_SCAN_GENERATION,
+                   rdev->bss_generation);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+
+       bss = nla_nest_start(msg, NL80211_ATTR_BSS);
+       if (!bss)
+               goto nla_put_failure;
+       if (!is_zero_ether_addr(res->bssid))
+               NLA_PUT(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid);
+       if (res->information_elements && res->len_information_elements)
+               NLA_PUT(msg, NL80211_BSS_INFORMATION_ELEMENTS,
+                       res->len_information_elements,
+                       res->information_elements);
+       if (res->tsf)
+               NLA_PUT_U64(msg, NL80211_BSS_TSF, res->tsf);
+       if (res->beacon_interval)
+               NLA_PUT_U16(msg, NL80211_BSS_BEACON_INTERVAL, res->beacon_interval);
+       NLA_PUT_U16(msg, NL80211_BSS_CAPABILITY, res->capability);
+       NLA_PUT_U32(msg, NL80211_BSS_FREQUENCY, res->channel->center_freq);
+
+       switch (res->signal_type) {
+       case CFG80211_SIGNAL_TYPE_MBM:
+               NLA_PUT_U32(msg, NL80211_BSS_SIGNAL_MBM, res->signal);
+               break;
+       case CFG80211_SIGNAL_TYPE_UNSPEC:
+               NLA_PUT_U8(msg, NL80211_BSS_SIGNAL_UNSPEC, res->signal);
+               break;
+       default:
+               break;
+       }
+
+       nla_nest_end(msg, bss);
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nl80211_dump_scan(struct sk_buff *skb,
+                            struct netlink_callback *cb)
+{
+       struct cfg80211_registered_device *dev;
+       struct net_device *netdev;
+       struct cfg80211_internal_bss *scan;
+       int ifidx = cb->args[0];
+       int start = cb->args[1], idx = 0;
+       int err;
+
+       if (!ifidx) {
+               err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
+                                 nl80211_fam.attrbuf, nl80211_fam.maxattr,
+                                 nl80211_policy);
+               if (err)
+                       return err;
+
+               if (!nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX])
+                       return -EINVAL;
+
+               ifidx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]);
+               if (!ifidx)
+                       return -EINVAL;
+               cb->args[0] = ifidx;
+       }
+
+       netdev = dev_get_by_index(&init_net, ifidx);
+       if (!netdev)
+               return -ENODEV;
+
+       dev = cfg80211_get_dev_from_ifindex(ifidx);
+       if (IS_ERR(dev)) {
+               err = PTR_ERR(dev);
+               goto out_put_netdev;
+       }
+
+       spin_lock_bh(&dev->bss_lock);
+       cfg80211_bss_expire(dev);
+
+       list_for_each_entry(scan, &dev->bss_list, list) {
+               if (++idx <= start)
+                       continue;
+               if (nl80211_send_bss(skb,
+                               NETLINK_CB(cb->skb).pid,
+                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                               dev, netdev, &scan->pub) < 0) {
+                       idx--;
+                       goto out;
+               }
+       }
+
+ out:
+       spin_unlock_bh(&dev->bss_lock);
+
+       cb->args[1] = idx;
+       err = skb->len;
+       cfg80211_put_dev(dev);
+ out_put_netdev:
+       dev_put(netdev);
+
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -2332,6 +2652,12 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NL80211_CMD_GET_REG,
+               .doit = nl80211_get_reg,
+               .policy = nl80211_policy,
+               /* can be retrieved by unprivileged users */
+       },
        {
                .cmd = NL80211_CMD_SET_REG,
                .doit = nl80211_set_reg,
@@ -2362,12 +2688,26 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NL80211_CMD_TRIGGER_SCAN,
+               .doit = nl80211_trigger_scan,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_GET_SCAN,
+               .policy = nl80211_policy,
+               .dumpit = nl80211_dump_scan,
+       },
 };
 
 /* multicast groups */
 static struct genl_multicast_group nl80211_config_mcgrp = {
        .name = "config",
 };
+static struct genl_multicast_group nl80211_scan_mcgrp = {
+       .name = "scan",
+};
 
 /* notification functions */
 
@@ -2387,6 +2727,66 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
        genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL);
 }
 
+static int nl80211_send_scan_donemsg(struct sk_buff *msg,
+                                   struct cfg80211_registered_device *rdev,
+                                   struct net_device *netdev,
+                                   u32 pid, u32 seq, int flags,
+                                   u32 cmd)
+{
+       void *hdr;
+
+       hdr = nl80211hdr_put(msg, pid, seq, flags, cmd);
+       if (!hdr)
+               return -1;
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+
+       /* XXX: we should probably bounce back the request? */
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+void nl80211_send_scan_done(struct cfg80211_registered_device *rdev,
+                           struct net_device *netdev)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0,
+                                     NL80211_CMD_NEW_SCAN_RESULTS) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
+void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev,
+                              struct net_device *netdev)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0,
+                                     NL80211_CMD_SCAN_ABORTED) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
 /* initialisation/exit functions */
 
 int nl80211_init(void)
@@ -2407,6 +2807,10 @@ int nl80211_init(void)
        if (err)
                goto err_out;
 
+       err = genl_register_mc_group(&nl80211_fam, &nl80211_scan_mcgrp);
+       if (err)
+               goto err_out;
+
        return 0;
  err_out:
        genl_unregister_family(&nl80211_fam);