Merge tag 'v3.10.68' into update
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / wireless / nl80211.c
index dc200bf831ae4eedbb50b45c3355708cdeadabbf..a3aa0406f5b4559ac8fffdf3c96ec9916e80d95f 100644 (file)
@@ -378,6 +378,12 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
        [NL80211_ATTR_MDID] = { .type = NLA_U16 },
        [NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
                                  .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_ATTR_PEER_AID] = { .type = NLA_U16 },
+       [NL80211_ATTR_STA_SUPPORTED_CHANNELS] = { .type = NLA_BINARY },
+       [NL80211_ATTR_STA_SUPPORTED_OPER_CLASSES] = { .type = NLA_BINARY },
+       [NL80211_ATTR_VENDOR_ID] = { .type = NLA_U32 },
+       [NL80211_ATTR_VENDOR_SUBCMD] = { .type = NLA_U32 },
+       [NL80211_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
 };
 
 /* policy for the key attributes */
@@ -1520,6 +1526,39 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
                                dev->wiphy.max_acl_mac_addrs))
                        goto nla_put_failure;
 
+               if (dev->wiphy.n_vendor_commands) {
+                       const struct nl80211_vendor_cmd_info *info;
+                       struct nlattr *nested;
+
+                       nested = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
+                       if (!nested)
+                               goto nla_put_failure;
+
+                       for (i = 0; i < dev->wiphy.n_vendor_commands; i++) {
+                               info = &dev->wiphy.vendor_commands[i].info;
+                               if (nla_put(msg, i + 1, sizeof(*info), info))
+                                       goto nla_put_failure;
+                       }
+                               nla_nest_end(msg, nested);
+               }
+
+               if (dev->wiphy.n_vendor_events) {
+                       const struct nl80211_vendor_cmd_info *info;
+                       struct nlattr *nested;
+
+                       nested = nla_nest_start(msg,
+                               NL80211_ATTR_VENDOR_EVENTS);
+                       if (!nested)
+                               goto nla_put_failure;
+
+                       for (i = 0; i < dev->wiphy.n_vendor_events; i++) {
+                               info = &dev->wiphy.vendor_events[i];
+                               if (nla_put(msg, i + 1, sizeof(*info), info))
+                                       goto nla_put_failure;
+                       }
+                       nla_nest_end(msg, nested);
+               }
+
                /*
                 * Any information below this point is only available to
                 * applications that can deal with it being split. This
@@ -2638,8 +2677,8 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
 
        hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
                             NL80211_CMD_NEW_KEY);
-       if (IS_ERR(hdr))
-               return PTR_ERR(hdr);
+       if (!hdr)
+               goto nla_put_failure;
 
        cookie.msg = msg;
        cookie.idx = key_idx;
@@ -6392,12 +6431,62 @@ static int nl80211_set_mcast_rate(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static struct sk_buff *
+__cfg80211_alloc_vendor_skb(struct cfg80211_registered_device *rdev,
+                           int approxlen, u32 portid, u32 seq,
+                           enum nl80211_commands cmd,
+                           enum nl80211_attrs attr,
+                           const struct nl80211_vendor_cmd_info *info,
+                           gfp_t gfp)
+{
+       struct sk_buff *skb;
+       void *hdr;
+       struct nlattr *data;
+
+       skb = nlmsg_new(approxlen + 100, gfp);
+       if (!skb)
+               return NULL;
+
+       hdr = nl80211hdr_put(skb, portid, seq, 0, cmd);
+       if (!hdr) {
+               kfree_skb(skb);
+               return NULL;
+       }
+
+       if (nla_put_u32(skb, NL80211_ATTR_WIPHY, rdev->wiphy_idx))
+               goto nla_put_failure;
+
+       if (info) {
+               if (nla_put_u32(skb, NL80211_ATTR_VENDOR_ID,
+                               info->vendor_id))
+                       goto nla_put_failure;
+               if (nla_put_u32(skb, NL80211_ATTR_VENDOR_SUBCMD,
+                               info->subcmd))
+                       goto nla_put_failure;
+       }
+
+       data = nla_nest_start(skb, attr);
+
+       ((void **)skb->cb)[0] = rdev;
+       ((void **)skb->cb)[1] = hdr;
+       ((void **)skb->cb)[2] = data;
+
+       return skb;
+
+ nla_put_failure:
+       kfree_skb(skb);
+       return NULL;
+}
 
 #ifdef CONFIG_NL80211_TESTMODE
 static struct genl_multicast_group nl80211_testmode_mcgrp = {
        .name = "testmode",
 };
 
+static struct genl_multicast_group nl80211_vendor_mcgrp = {
+       .name = "vendor",
+};
+
 static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -6408,11 +6497,11 @@ static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info)
 
        err = -EOPNOTSUPP;
        if (rdev->ops->testmode_cmd) {
-               rdev->testmode_info = info;
+               rdev->cur_cmd_info = info;
                err = rdev_testmode_cmd(rdev,
                                nla_data(info->attrs[NL80211_ATTR_TESTDATA]),
                                nla_len(info->attrs[NL80211_ATTR_TESTDATA]));
-               rdev->testmode_info = NULL;
+               rdev->cur_cmd_info = NULL;
        }
 
        return err;
@@ -6481,6 +6570,9 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
                                           NL80211_CMD_TESTMODE);
                struct nlattr *tmdata;
 
+               if (!hdr)
+                       break;
+
                if (nla_put_u32(skb, NL80211_ATTR_WIPHY, phy_idx)) {
                        genlmsg_cancel(skb, hdr);
                        break;
@@ -6513,84 +6605,37 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
        return err;
 }
 
-static struct sk_buff *
-__cfg80211_testmode_alloc_skb(struct cfg80211_registered_device *rdev,
-                             int approxlen, u32 portid, u32 seq, gfp_t gfp)
-{
-       struct sk_buff *skb;
-       void *hdr;
-       struct nlattr *data;
-
-       skb = nlmsg_new(approxlen + 100, gfp);
-       if (!skb)
-               return NULL;
-
-       hdr = nl80211hdr_put(skb, portid, seq, 0, NL80211_CMD_TESTMODE);
-       if (!hdr) {
-               kfree_skb(skb);
-               return NULL;
-       }
-
-       if (nla_put_u32(skb, NL80211_ATTR_WIPHY, rdev->wiphy_idx))
-               goto nla_put_failure;
-       data = nla_nest_start(skb, NL80211_ATTR_TESTDATA);
-
-       ((void **)skb->cb)[0] = rdev;
-       ((void **)skb->cb)[1] = hdr;
-       ((void **)skb->cb)[2] = data;
-
-       return skb;
-
- nla_put_failure:
-       kfree_skb(skb);
-       return NULL;
-}
-
-struct sk_buff *cfg80211_testmode_alloc_reply_skb(struct wiphy *wiphy,
-                                                 int approxlen)
+struct sk_buff *__cfg80211_alloc_event_skb(struct wiphy *wiphy,
+                                          enum nl80211_commands cmd,
+                                          enum nl80211_attrs attr,
+                                          int vendor_event_idx,
+                                          int approxlen, gfp_t gfp)
 {
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
+       const struct nl80211_vendor_cmd_info *info;
 
-       if (WARN_ON(!rdev->testmode_info))
+       switch (cmd) {
+       case NL80211_CMD_TESTMODE:
+               if (WARN_ON(vendor_event_idx != -1))
+                       return NULL;
+               info = NULL;
+               break;
+       case NL80211_CMD_VENDOR:
+               if (WARN_ON(vendor_event_idx < 0 ||
+                           vendor_event_idx >= wiphy->n_vendor_events))
+                       return NULL;
+               info = &wiphy->vendor_events[vendor_event_idx];
+               break;
+       default:
+               WARN_ON(1);
                return NULL;
-
-       return __cfg80211_testmode_alloc_skb(rdev, approxlen,
-                               rdev->testmode_info->snd_portid,
-                               rdev->testmode_info->snd_seq,
-                               GFP_KERNEL);
-}
-EXPORT_SYMBOL(cfg80211_testmode_alloc_reply_skb);
-
-int cfg80211_testmode_reply(struct sk_buff *skb)
-{
-       struct cfg80211_registered_device *rdev = ((void **)skb->cb)[0];
-       void *hdr = ((void **)skb->cb)[1];
-       struct nlattr *data = ((void **)skb->cb)[2];
-
-       /* clear CB data for netlink core to own from now on */
-       memset(skb->cb, 0, sizeof(skb->cb));
-
-       if (WARN_ON(!rdev->testmode_info)) {
-               kfree_skb(skb);
-               return -EINVAL;
        }
-
-       nla_nest_end(skb, data);
-       genlmsg_end(skb, hdr);
-       return genlmsg_reply(skb, rdev->testmode_info);
-}
-EXPORT_SYMBOL(cfg80211_testmode_reply);
-
-struct sk_buff *cfg80211_testmode_alloc_event_skb(struct wiphy *wiphy,
-                                                 int approxlen, gfp_t gfp)
-{
-       struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
-
-       return __cfg80211_testmode_alloc_skb(rdev, approxlen, 0, 0, gfp);
+       return __cfg80211_alloc_vendor_skb(rdev, approxlen, 0, 0,
+                                          cmd, attr, info, gfp);
 }
-EXPORT_SYMBOL(cfg80211_testmode_alloc_event_skb);
+EXPORT_SYMBOL(__cfg80211_alloc_event_skb);
 
-void cfg80211_testmode_event(struct sk_buff *skb, gfp_t gfp)
+void __cfg80211_send_event_skb(struct sk_buff *skb, gfp_t gfp)
 {
        struct cfg80211_registered_device *rdev = ((void **)skb->cb)[0];
        void *hdr = ((void **)skb->cb)[1];
@@ -6601,10 +6646,15 @@ void cfg80211_testmode_event(struct sk_buff *skb, gfp_t gfp)
 
        nla_nest_end(skb, data);
        genlmsg_end(skb, hdr);
-       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), skb, 0,
-                               nl80211_testmode_mcgrp.id, gfp);
+
+       if (data->nla_type == NL80211_ATTR_VENDOR_DATA)
+               genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), skb, 0,
+                       nl80211_vendor_mcgrp.id, gfp);
+       else
+               genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), skb, 0,
+                       nl80211_testmode_mcgrp.id, gfp);
 }
-EXPORT_SYMBOL(cfg80211_testmode_event);
+EXPORT_SYMBOL(__cfg80211_send_event_skb);
 #endif
 
 static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
@@ -6925,9 +6975,8 @@ static int nl80211_remain_on_channel(struct sk_buff *skb,
 
        hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
                             NL80211_CMD_REMAIN_ON_CHANNEL);
-
-       if (IS_ERR(hdr)) {
-               err = PTR_ERR(hdr);
+       if (!hdr) {
+               err = -ENOBUFS;
                goto free_msg;
        }
 
@@ -7214,9 +7263,8 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
 
                hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
                                     NL80211_CMD_FRAME);
-
-               if (IS_ERR(hdr)) {
-                       err = PTR_ERR(hdr);
+               if (!hdr) {
+                       err = -ENOBUFS;
                        goto free_msg;
                }
        }
@@ -8077,9 +8125,8 @@ static int nl80211_probe_client(struct sk_buff *skb,
 
        hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
                             NL80211_CMD_PROBE_CLIENT);
-
-       if (IS_ERR(hdr)) {
-               err = PTR_ERR(hdr);
+       if (!hdr) {
+               err = -ENOBUFS;
                goto free_msg;
        }
 
@@ -8296,6 +8343,108 @@ static int nl80211_crit_protocol_stop(struct sk_buff *skb,
        return 0;
 }
 
+static int nl80211_vendor_cmd(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct wireless_dev *wdev =
+               __cfg80211_wdev_from_attrs(genl_info_net(info), info->attrs);
+       int i, err;
+       u32 vid, subcmd;
+
+       if (!rdev || !rdev->wiphy.vendor_commands)
+               return -EOPNOTSUPP;
+
+       if (IS_ERR(wdev)) {
+               err = PTR_ERR(wdev);
+               if (err != -EINVAL)
+                       return err;
+               wdev = NULL;
+       } else if (wdev->wiphy != &rdev->wiphy) {
+               return -EINVAL;
+       }
+
+       if (!info->attrs[NL80211_ATTR_VENDOR_ID] ||
+           !info->attrs[NL80211_ATTR_VENDOR_SUBCMD])
+               return -EINVAL;
+
+       vid = nla_get_u32(info->attrs[NL80211_ATTR_VENDOR_ID]);
+       subcmd = nla_get_u32(info->attrs[NL80211_ATTR_VENDOR_SUBCMD]);
+       for (i = 0; i < rdev->wiphy.n_vendor_commands; i++) {
+               const struct wiphy_vendor_command *vcmd;
+               void *data = NULL;
+               int len = 0;
+
+               vcmd = &rdev->wiphy.vendor_commands[i];
+
+               if (vcmd->info.vendor_id != vid || vcmd->info.subcmd != subcmd)
+                       continue;
+
+               if (vcmd->flags & (WIPHY_VENDOR_CMD_NEED_WDEV |
+                                  WIPHY_VENDOR_CMD_NEED_NETDEV)) {
+                       if (!wdev)
+                               return -EINVAL;
+                       if (vcmd->flags & WIPHY_VENDOR_CMD_NEED_NETDEV &&
+                           !wdev->netdev)
+                               return -EINVAL;
+
+                       if (vcmd->flags & WIPHY_VENDOR_CMD_NEED_RUNNING) {
+                               if (!wdev->netdev ||
+                                   !netif_running(wdev->netdev))
+                                       return -ENETDOWN;
+                       }
+               } else {
+                       wdev = NULL;
+               }
+
+               if (info->attrs[NL80211_ATTR_VENDOR_DATA]) {
+                       data = nla_data(info->attrs[NL80211_ATTR_VENDOR_DATA]);
+                       len = nla_len(info->attrs[NL80211_ATTR_VENDOR_DATA]);
+               }
+
+               rdev->cur_cmd_info = info;
+               err = rdev->wiphy.vendor_commands[i].doit(&rdev->wiphy, wdev,
+                                                               data, len);
+               rdev->cur_cmd_info = NULL;
+               return err;
+       }
+
+       return -EOPNOTSUPP;
+}
+
+struct sk_buff *__cfg80211_alloc_reply_skb(struct wiphy *wiphy,
+                                          enum nl80211_commands cmd,
+                                          enum nl80211_attrs attr,
+                                          int approxlen)
+{
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
+
+       if (WARN_ON(!rdev->cur_cmd_info))
+               return NULL;
+
+       return __cfg80211_alloc_vendor_skb(rdev, approxlen,
+                                          0,
+                                          0,
+                                          cmd, attr, NULL, GFP_KERNEL);
+}
+EXPORT_SYMBOL(__cfg80211_alloc_reply_skb);
+
+int cfg80211_vendor_cmd_reply(struct sk_buff *skb)
+{
+       struct cfg80211_registered_device *rdev = ((void **)skb->cb)[0];
+       void *hdr = ((void **)skb->cb)[1];
+       struct nlattr *data = ((void **)skb->cb)[2];
+
+       if (WARN_ON(!rdev->cur_cmd_info)) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+
+       nla_nest_end(skb, data);
+       genlmsg_end(skb, hdr);
+       return genlmsg_reply(skb, rdev->cur_cmd_info);
+}
+EXPORT_SYMBOL(cfg80211_vendor_cmd_reply);
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -9000,7 +9149,15 @@ static struct genl_ops nl80211_ops[] = {
                .flags = GENL_ADMIN_PERM,
                .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
                                  NL80211_FLAG_NEED_RTNL,
-       }
+       },
+       {
+               .cmd = NL80211_CMD_VENDOR,
+               .doit = nl80211_vendor_cmd,
+               .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 = {
@@ -10819,6 +10976,10 @@ int nl80211_init(void)
        err = genl_register_mc_group(&nl80211_fam, &nl80211_testmode_mcgrp);
        if (err)
                goto err_out;
+
+       err = genl_register_mc_group(&nl80211_fam, &nl80211_vendor_mcgrp);
+       if (err)
+               goto err_out;
 #endif
 
        err = netlink_register_notifier(&nl80211_netlink_notifier);