cfg80211/nl80211: add support for scheduled scans
[GitHub/exynos8895/android_kernel_samsung_universal8895.git] / net / wireless / nl80211.c
index 95dd5832e719c582cce256436bda810fc8a88735..4fac370284c047d331bf2255aba505cf7364840e 100644 (file)
@@ -761,6 +761,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
        }
        CMD(set_channel, SET_CHANNEL);
        CMD(set_wds_peer, SET_WDS_PEER);
+       if (dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
+               CMD(sched_scan_start, START_SCHED_SCAN);
 
 #undef CMD
 
@@ -3357,6 +3359,179 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_start_sched_scan(struct sk_buff *skb,
+                                   struct genl_info *info)
+{
+       struct cfg80211_sched_scan_request *request;
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct cfg80211_ssid *ssid;
+       struct ieee80211_channel *channel;
+       struct nlattr *attr;
+       struct wiphy *wiphy;
+       int err, tmp, n_ssids = 0, n_channels, i;
+       enum ieee80211_band band;
+       size_t ie_len;
+
+       if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
+           !rdev->ops->sched_scan_start)
+               return -EOPNOTSUPP;
+
+       if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE]))
+               return -EINVAL;
+
+       if (rdev->sched_scan_req)
+               return -EINPROGRESS;
+
+       wiphy = &rdev->wiphy;
+
+       if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+               n_channels = validate_scan_freqs(
+                               info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]);
+               if (!n_channels)
+                       return -EINVAL;
+       } else {
+               n_channels = 0;
+
+               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)
+               return -EINVAL;
+
+       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)
+               return -EINVAL;
+
+       request = kzalloc(sizeof(*request)
+                       + sizeof(*ssid) * n_ssids
+                       + sizeof(channel) * n_channels
+                       + ie_len, GFP_KERNEL);
+       if (!request)
+               return -ENOMEM;
+
+       if (n_ssids)
+               request->ssids = (void *)&request->channels[n_channels];
+       request->n_ssids = n_ssids;
+       if (ie_len) {
+               if (request->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 {
+               /* all channels */
+               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++) {
+                               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;
+
+       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++;
+               }
+       }
+
+       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);
+       }
+
+       request->dev = dev;
+       request->wiphy = &rdev->wiphy;
+
+       err = rdev->ops->sched_scan_start(&rdev->wiphy, dev, request);
+       if (!err) {
+               rdev->sched_scan_req = request;
+               nl80211_send_sched_scan(rdev, dev,
+                                       NL80211_CMD_START_SCHED_SCAN);
+               goto out;
+       }
+
+out_free:
+       kfree(request);
+out:
+       return err;
+}
+
+static int nl80211_stop_sched_scan(struct sk_buff *skb,
+                                  struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+
+       if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
+           !rdev->ops->sched_scan_stop)
+               return -EOPNOTSUPP;
+
+       return __cfg80211_stop_sched_scan(rdev, false);
+}
+
 static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                            struct cfg80211_registered_device *rdev,
                            struct wireless_dev *wdev,
@@ -5326,6 +5501,22 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .dumpit = nl80211_dump_scan,
        },
+       {
+               .cmd = NL80211_CMD_START_SCHED_SCAN,
+               .doit = nl80211_start_sched_scan,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL80211_CMD_STOP_SCHED_SCAN,
+               .doit = nl80211_stop_sched_scan,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
        {
                .cmd = NL80211_CMD_AUTHENTICATE,
                .doit = nl80211_authenticate,
@@ -5652,6 +5843,28 @@ static int nl80211_send_scan_msg(struct sk_buff *msg,
        return -EMSGSIZE;
 }
 
+static int
+nl80211_send_sched_scan_msg(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->wiphy_idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
 void nl80211_send_scan_start(struct cfg80211_registered_device *rdev,
                             struct net_device *netdev)
 {
@@ -5709,6 +5922,43 @@ void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev,
                                nl80211_scan_mcgrp.id, GFP_KERNEL);
 }
 
+void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
+                                    struct net_device *netdev)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0,
+                                       NL80211_CMD_SCHED_SCAN_RESULTS) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+                               nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
+void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
+                            struct net_device *netdev, u32 cmd)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0, cmd) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+                               nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
 /*
  * This can happen on global regulatory changes or device specific settings
  * based on custom world regulatory domains.