nl80211: advertise GTK rekey support, new triggers
[deliverable/linux.git] / net / wireless / nl80211.c
index 0efa7fd01150fef0a9a18030a53ff1b10d7ece14..a2e1e49c9196848f07befdb97720dd665d173e87 100644 (file)
@@ -173,6 +173,10 @@ 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 },
+       [NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 },
+       [NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
+       [NL80211_ATTR_REKEY_DATA] = { .type = NLA_NESTED },
 };
 
 /* policy for the key attributes */
@@ -194,6 +198,27 @@ 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 },
+       [NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE] = { .type = NLA_FLAG },
+       [NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST] = { .type = NLA_FLAG },
+       [NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG },
+       [NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG },
+};
+
+/* policy for GTK rekey offload attributes */
+static const struct nla_policy
+nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
+       [NL80211_REKEY_DATA_KEK] = { .len = NL80211_KEK_LEN },
+       [NL80211_REKEY_DATA_KCK] = { .len = NL80211_KCK_LEN },
+       [NL80211_REKEY_DATA_REPLAY_CTR] = { .len = NL80211_REPLAY_CTR_LEN },
+};
+
 /* ifidx get helper */
 static int nl80211_get_ifidx(struct netlink_callback *cb)
 {
@@ -534,6 +559,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_AP_VLAN:
        case NL80211_IFTYPE_P2P_GO:
+       case NL80211_IFTYPE_MESH_POINT:
                break;
        case NL80211_IFTYPE_ADHOC:
                if (!wdev->current_bss)
@@ -551,6 +577,88 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
        return 0;
 }
 
+static int nl80211_put_iftypes(struct sk_buff *msg, u32 attr, u16 ifmodes)
+{
+       struct nlattr *nl_modes = nla_nest_start(msg, attr);
+       int i;
+
+       if (!nl_modes)
+               goto nla_put_failure;
+
+       i = 0;
+       while (ifmodes) {
+               if (ifmodes & 1)
+                       NLA_PUT_FLAG(msg, i);
+               ifmodes >>= 1;
+               i++;
+       }
+
+       nla_nest_end(msg, nl_modes);
+       return 0;
+
+nla_put_failure:
+       return -ENOBUFS;
+}
+
+static int nl80211_put_iface_combinations(struct wiphy *wiphy,
+                                         struct sk_buff *msg)
+{
+       struct nlattr *nl_combis;
+       int i, j;
+
+       nl_combis = nla_nest_start(msg,
+                               NL80211_ATTR_INTERFACE_COMBINATIONS);
+       if (!nl_combis)
+               goto nla_put_failure;
+
+       for (i = 0; i < wiphy->n_iface_combinations; i++) {
+               const struct ieee80211_iface_combination *c;
+               struct nlattr *nl_combi, *nl_limits;
+
+               c = &wiphy->iface_combinations[i];
+
+               nl_combi = nla_nest_start(msg, i + 1);
+               if (!nl_combi)
+                       goto nla_put_failure;
+
+               nl_limits = nla_nest_start(msg, NL80211_IFACE_COMB_LIMITS);
+               if (!nl_limits)
+                       goto nla_put_failure;
+
+               for (j = 0; j < c->n_limits; j++) {
+                       struct nlattr *nl_limit;
+
+                       nl_limit = nla_nest_start(msg, j + 1);
+                       if (!nl_limit)
+                               goto nla_put_failure;
+                       NLA_PUT_U32(msg, NL80211_IFACE_LIMIT_MAX,
+                                   c->limits[j].max);
+                       if (nl80211_put_iftypes(msg, NL80211_IFACE_LIMIT_TYPES,
+                                               c->limits[j].types))
+                               goto nla_put_failure;
+                       nla_nest_end(msg, nl_limit);
+               }
+
+               nla_nest_end(msg, nl_limits);
+
+               if (c->beacon_int_infra_match)
+                       NLA_PUT_FLAG(msg,
+                               NL80211_IFACE_COMB_STA_AP_BI_MATCH);
+               NLA_PUT_U32(msg, NL80211_IFACE_COMB_NUM_CHANNELS,
+                           c->num_different_channels);
+               NLA_PUT_U32(msg, NL80211_IFACE_COMB_MAXNUM,
+                           c->max_interfaces);
+
+               nla_nest_end(msg, nl_combi);
+       }
+
+       nla_nest_end(msg, nl_combis);
+
+       return 0;
+nla_put_failure:
+       return -ENOBUFS;
+}
+
 static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                              struct cfg80211_registered_device *dev)
 {
@@ -558,13 +666,11 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
        struct nlattr *nl_bands, *nl_band;
        struct nlattr *nl_freqs, *nl_freq;
        struct nlattr *nl_rates, *nl_rate;
-       struct nlattr *nl_modes;
        struct nlattr *nl_cmds;
        enum ieee80211_band band;
        struct ieee80211_channel *chan;
        struct ieee80211_rate *rate;
        int i;
-       u16 ifmodes = dev->wiphy.interface_modes;
        const struct ieee80211_txrx_stypes *mgmt_stypes =
                                dev->wiphy.mgmt_stypes;
 
@@ -624,20 +730,10 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                }
        }
 
-       nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES);
-       if (!nl_modes)
+       if (nl80211_put_iftypes(msg, NL80211_ATTR_SUPPORTED_IFTYPES,
+                               dev->wiphy.interface_modes))
                goto nla_put_failure;
 
-       i = 0;
-       while (ifmodes) {
-               if (ifmodes & 1)
-                       NLA_PUT_FLAG(msg, i);
-               ifmodes >>= 1;
-               i++;
-       }
-
-       nla_nest_end(msg, nl_modes);
-
        nl_bands = nla_nest_start(msg, NL80211_ATTR_WIPHY_BANDS);
        if (!nl_bands)
                goto nla_put_failure;
@@ -749,6 +845,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
 
@@ -821,6 +919,52 @@ 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.flags & WIPHY_WOWLAN_SUPPORTS_GTK_REKEY)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED);
+               if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE);
+               if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST);
+               if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_4WAY_HANDSHAKE)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE);
+               if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_RFKILL_RELEASE)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE);
+               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);
+       }
+
+       if (nl80211_put_iftypes(msg, NL80211_ATTR_SOFTWARE_IFTYPES,
+                               dev->wiphy.software_iftypes))
+               goto nla_put_failure;
+
+       if (nl80211_put_iface_combinations(&dev->wiphy, msg))
+               goto nla_put_failure;
+
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
@@ -1682,14 +1826,6 @@ static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
                if (err)
                        goto out;
 
-               if (!(rdev->wiphy.flags &
-                               WIPHY_FLAG_SUPPORTS_SEPARATE_DEFAULT_KEYS)) {
-                       if (!key.def_uni || !key.def_multi) {
-                               err = -EOPNOTSUPP;
-                               goto out;
-                       }
-               }
-
                err = rdev->ops->set_default_key(&rdev->wiphy, dev, key.idx,
                                                 key.def_uni, key.def_multi);
 
@@ -1840,8 +1976,9 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
                    struct beacon_parameters *info);
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct beacon_parameters params;
-       int haveinfo = 0;
+       int haveinfo = 0, err;
 
        if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_BEACON_TAIL]))
                return -EINVAL;
@@ -1850,6 +1987,8 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
                return -EOPNOTSUPP;
 
+       memset(&params, 0, sizeof(params));
+
        switch (info->genlhdr->cmd) {
        case NL80211_CMD_NEW_BEACON:
                /* these are required for NEW_BEACON */
@@ -1858,6 +1997,15 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
                    !info->attrs[NL80211_ATTR_BEACON_HEAD])
                        return -EINVAL;
 
+               params.interval =
+                       nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
+               params.dtim_period =
+                       nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);
+
+               err = cfg80211_validate_beacon_int(rdev, params.interval);
+               if (err)
+                       return err;
+
                call = rdev->ops->add_beacon;
                break;
        case NL80211_CMD_SET_BEACON:
@@ -1871,20 +2019,6 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
        if (!call)
                return -EOPNOTSUPP;
 
-       memset(&params, 0, sizeof(params));
-
-       if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) {
-               params.interval =
-                   nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
-               haveinfo = 1;
-       }
-
-       if (info->attrs[NL80211_ATTR_DTIM_PERIOD]) {
-               params.dtim_period =
-                   nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);
-               haveinfo = 1;
-       }
-
        if (info->attrs[NL80211_ATTR_BEACON_HEAD]) {
                params.head = nla_data(info->attrs[NL80211_ATTR_BEACON_HEAD]);
                params.head_len =
@@ -1902,13 +2036,18 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
        if (!haveinfo)
                return -EINVAL;
 
-       return call(&rdev->wiphy, dev, &params);
+       err = call(&rdev->wiphy, dev, &params);
+       if (!err && params.interval)
+               wdev->beacon_interval = params.interval;
+       return err;
 }
 
 static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       int err;
 
        if (!rdev->ops->del_beacon)
                return -EOPNOTSUPP;
@@ -1917,7 +2056,10 @@ static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info)
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
                return -EOPNOTSUPP;
 
-       return rdev->ops->del_beacon(&rdev->wiphy, dev);
+       err = rdev->ops->del_beacon(&rdev->wiphy, dev);
+       if (!err)
+               wdev->beacon_interval = 0;
+       return err;
 }
 
 static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
@@ -2216,6 +2358,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
        memset(&params, 0, sizeof(params));
 
        params.listen_interval = -1;
+       params.plink_state = -1;
 
        if (info->attrs[NL80211_ATTR_STA_AID])
                return -EINVAL;
@@ -2247,6 +2390,10 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                params.plink_action =
                    nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_ACTION]);
 
+       if (info->attrs[NL80211_ATTR_STA_PLINK_STATE])
+               params.plink_state =
+                   nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_STATE]);
+
        err = get_vlan(info, rdev, &params.vlan);
        if (err)
                goto out;
@@ -2286,10 +2433,9 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                        err = -EINVAL;
                if (params.listen_interval >= 0)
                        err = -EINVAL;
-               if (params.supported_rates)
-                       err = -EINVAL;
                if (params.sta_flags_mask &
                                ~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                 BIT(NL80211_STA_FLAG_MFP) |
                                  BIT(NL80211_STA_FLAG_AUTHORIZED)))
                        err = -EINVAL;
                break;
@@ -2840,6 +2986,7 @@ static const struct nla_policy
        [NL80211_MESH_SETUP_USERSPACE_AUTH] = { .type = NLA_FLAG },
        [NL80211_MESH_SETUP_IE] = { .type = NLA_BINARY,
                .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_MESH_SETUP_USERSPACE_AMPE] = { .type = NLA_FLAG },
 };
 
 static int nl80211_parse_mesh_config(struct genl_info *info,
@@ -2949,7 +3096,8 @@ static int nl80211_parse_mesh_setup(struct genl_info *info,
                setup->ie = nla_data(ieattr);
                setup->ie_len = nla_len(ieattr);
        }
-       setup->is_secure = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AUTH]);
+       setup->is_authenticated = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AUTH]);
+       setup->is_secure = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AMPE]);
 
        return 0;
 }
@@ -3169,8 +3317,6 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct net_device *dev = info->user_ptr[1];
        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, i;
@@ -3217,8 +3363,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
                return -EINVAL;
 
        request = kzalloc(sizeof(*request)
-                       + sizeof(*ssid) * n_ssids
-                       + sizeof(channel) * n_channels
+                       + sizeof(*request->ssids) * n_ssids
+                       + sizeof(*request->channels) * n_channels
                        + ie_len, GFP_KERNEL);
        if (!request)
                return -ENOMEM;
@@ -3283,12 +3429,12 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
        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) {
+                       if (nla_len(attr) > 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);
+                       memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr));
                        i++;
                }
        }
@@ -3318,121 +3464,316 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
-static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
-                           struct cfg80211_registered_device *rdev,
-                           struct wireless_dev *wdev,
-                           struct cfg80211_internal_bss *intbss)
+static int nl80211_start_sched_scan(struct sk_buff *skb,
+                                   struct genl_info *info)
 {
-       struct cfg80211_bss *res = &intbss->pub;
-       void *hdr;
-       struct nlattr *bss;
-       int i;
+       struct cfg80211_sched_scan_request *request;
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct nlattr *attr;
+       struct wiphy *wiphy;
+       int err, tmp, n_ssids = 0, n_channels, i;
+       u32 interval;
+       enum ieee80211_band band;
+       size_t ie_len;
 
-       ASSERT_WDEV_LOCK(wdev);
+       if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
+           !rdev->ops->sched_scan_start)
+               return -EOPNOTSUPP;
 
-       hdr = nl80211hdr_put(msg, pid, seq, flags,
-                            NL80211_CMD_NEW_SCAN_RESULTS);
-       if (!hdr)
-               return -1;
+       if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE]))
+               return -EINVAL;
 
-       NLA_PUT_U32(msg, NL80211_ATTR_GENERATION, rdev->bss_generation);
-       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex);
+       if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL])
+               return -EINVAL;
 
-       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->beacon_ies && res->len_beacon_ies &&
-           res->beacon_ies != res->information_elements)
-               NLA_PUT(msg, NL80211_BSS_BEACON_IES,
-                       res->len_beacon_ies, res->beacon_ies);
-       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);
-       NLA_PUT_U32(msg, NL80211_BSS_SEEN_MS_AGO,
-               jiffies_to_msecs(jiffies - intbss->ts));
+       interval = nla_get_u32(info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]);
+       if (interval == 0)
+               return -EINVAL;
 
-       switch (rdev->wiphy.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;
-       }
+       wiphy = &rdev->wiphy;
 
-       switch (wdev->iftype) {
-       case NL80211_IFTYPE_P2P_CLIENT:
-       case NL80211_IFTYPE_STATION:
-               if (intbss == wdev->current_bss)
-                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
-                                   NL80211_BSS_STATUS_ASSOCIATED);
-               else for (i = 0; i < MAX_AUTH_BSSES; i++) {
-                       if (intbss != wdev->auth_bsses[i])
-                               continue;
-                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
-                                   NL80211_BSS_STATUS_AUTHENTICATED);
-                       break;
-               }
-               break;
-       case NL80211_IFTYPE_ADHOC:
-               if (intbss == wdev->current_bss)
-                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
-                                   NL80211_BSS_STATUS_IBSS_JOINED);
-               break;
-       default:
-               break;
+       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;
        }
 
-       nla_nest_end(msg, bss);
+       if (info->attrs[NL80211_ATTR_SCAN_SSIDS])
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS],
+                                   tmp)
+                       n_ssids++;
 
-       return genlmsg_end(msg, hdr);
+       if (n_ssids > wiphy->max_scan_ssids)
+               return -EINVAL;
 
- nla_put_failure:
-       genlmsg_cancel(msg, hdr);
-       return -EMSGSIZE;
-}
+       if (info->attrs[NL80211_ATTR_IE])
+               ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
+       else
+               ie_len = 0;
 
-static int nl80211_dump_scan(struct sk_buff *skb,
-                            struct netlink_callback *cb)
-{
-       struct cfg80211_registered_device *rdev;
-       struct net_device *dev;
-       struct cfg80211_internal_bss *scan;
-       struct wireless_dev *wdev;
-       int start = cb->args[1], idx = 0;
-       int err;
+       if (ie_len > wiphy->max_scan_ie_len)
+               return -EINVAL;
 
-       err = nl80211_prepare_netdev_dump(skb, cb, &rdev, &dev);
-       if (err)
-               return err;
+       mutex_lock(&rdev->sched_scan_mtx);
 
-       wdev = dev->ieee80211_ptr;
+       if (rdev->sched_scan_req) {
+               err = -EINPROGRESS;
+               goto out;
+       }
 
-       wdev_lock(wdev);
-       spin_lock_bh(&rdev->bss_lock);
-       cfg80211_bss_expire(rdev);
+       request = kzalloc(sizeof(*request)
+                       + sizeof(*request->ssids) * n_ssids
+                       + sizeof(*request->channels) * n_channels
+                       + ie_len, GFP_KERNEL);
+       if (!request) {
+               err = -ENOMEM;
+               goto out;
+       }
 
-       list_for_each_entry(scan, &rdev->bss_list, list) {
-               if (++idx <= start)
-                       continue;
-               if (nl80211_send_bss(skb,
-                               NETLINK_CB(cb->skb).pid,
-                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
-                               rdev, wdev, scan) < 0) {
-                       idx--;
-                       break;
-               }
+       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 (nla_len(attr) > IEEE80211_MAX_SSID_LEN) {
+                               err = -EINVAL;
+                               goto out_free;
+                       }
+                       request->ssids[i].ssid_len = nla_len(attr);
+                       memcpy(request->ssids[i].ssid, nla_data(attr),
+                              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;
+       request->interval = interval;
+
+       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:
+       mutex_unlock(&rdev->sched_scan_mtx);
+       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];
+       int err;
+
+       if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
+           !rdev->ops->sched_scan_stop)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&rdev->sched_scan_mtx);
+       err = __cfg80211_stop_sched_scan(rdev, false);
+       mutex_unlock(&rdev->sched_scan_mtx);
+
+       return err;
+}
+
+static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb,
+                           u32 seq, int flags,
+                           struct cfg80211_registered_device *rdev,
+                           struct wireless_dev *wdev,
+                           struct cfg80211_internal_bss *intbss)
+{
+       struct cfg80211_bss *res = &intbss->pub;
+       void *hdr;
+       struct nlattr *bss;
+       int i;
+
+       ASSERT_WDEV_LOCK(wdev);
+
+       hdr = nl80211hdr_put(msg, NETLINK_CB(cb->skb).pid, seq, flags,
+                            NL80211_CMD_NEW_SCAN_RESULTS);
+       if (!hdr)
+               return -1;
+
+       genl_dump_check_consistent(cb, hdr, &nl80211_fam);
+
+       NLA_PUT_U32(msg, NL80211_ATTR_GENERATION, rdev->bss_generation);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->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->beacon_ies && res->len_beacon_ies &&
+           res->beacon_ies != res->information_elements)
+               NLA_PUT(msg, NL80211_BSS_BEACON_IES,
+                       res->len_beacon_ies, res->beacon_ies);
+       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);
+       NLA_PUT_U32(msg, NL80211_BSS_SEEN_MS_AGO,
+               jiffies_to_msecs(jiffies - intbss->ts));
+
+       switch (rdev->wiphy.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;
+       }
+
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_P2P_CLIENT:
+       case NL80211_IFTYPE_STATION:
+               if (intbss == wdev->current_bss)
+                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
+                                   NL80211_BSS_STATUS_ASSOCIATED);
+               else for (i = 0; i < MAX_AUTH_BSSES; i++) {
+                       if (intbss != wdev->auth_bsses[i])
+                               continue;
+                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
+                                   NL80211_BSS_STATUS_AUTHENTICATED);
+                       break;
+               }
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               if (intbss == wdev->current_bss)
+                       NLA_PUT_U32(msg, NL80211_BSS_STATUS,
+                                   NL80211_BSS_STATUS_IBSS_JOINED);
+               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 *rdev;
+       struct net_device *dev;
+       struct cfg80211_internal_bss *scan;
+       struct wireless_dev *wdev;
+       int start = cb->args[1], idx = 0;
+       int err;
+
+       err = nl80211_prepare_netdev_dump(skb, cb, &rdev, &dev);
+       if (err)
+               return err;
+
+       wdev = dev->ieee80211_ptr;
+
+       wdev_lock(wdev);
+       spin_lock_bh(&rdev->bss_lock);
+       cfg80211_bss_expire(rdev);
+
+       cb->seq = rdev->bss_generation;
+
+       list_for_each_entry(scan, &rdev->bss_list, list) {
+               if (++idx <= start)
+                       continue;
+               if (nl80211_send_bss(skb, cb,
+                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                               rdev, wdev, scan) < 0) {
+                       idx--;
+                       break;
+               }
        }
 
        spin_unlock_bh(&rdev->bss_lock);
@@ -3451,10 +3792,6 @@ static int nl80211_send_survey(struct sk_buff *msg, u32 pid, u32 seq,
        void *hdr;
        struct nlattr *infoattr;
 
-       /* Survey without a channel doesn't make sense */
-       if (!survey->channel)
-               return -EINVAL;
-
        hdr = nl80211hdr_put(msg, pid, seq, flags,
                             NL80211_CMD_NEW_SURVEY_RESULTS);
        if (!hdr)
@@ -3517,6 +3854,8 @@ static int nl80211_dump_survey(struct sk_buff *skb,
        }
 
        while (1) {
+               struct ieee80211_channel *chan;
+
                res = dev->ops->dump_survey(&dev->wiphy, netdev, survey_idx,
                                            &survey);
                if (res == -ENOENT)
@@ -3524,6 +3863,19 @@ static int nl80211_dump_survey(struct sk_buff *skb,
                if (res)
                        goto out_err;
 
+               /* Survey without a channel doesn't make sense */
+               if (!survey.channel) {
+                       res = -EINVAL;
+                       goto out;
+               }
+
+               chan = ieee80211_get_channel(&dev->wiphy,
+                                            survey.channel->center_freq);
+               if (!chan || chan->flags & IEEE80211_CHAN_DISABLED) {
+                       survey_idx++;
+                       continue;
+               }
+
                if (nl80211_send_survey(skb,
                                NETLINK_CB(cb->skb).pid,
                                cb->nlh->nlmsg_seq, NLM_F_MULTI,
@@ -4058,6 +4410,93 @@ static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_testmode_dump(struct sk_buff *skb,
+                                struct netlink_callback *cb)
+{
+       struct cfg80211_registered_device *dev;
+       int err;
+       long phy_idx;
+       void *data = NULL;
+       int data_len = 0;
+
+       if (cb->args[0]) {
+               /*
+                * 0 is a valid index, but not valid for args[0],
+                * so we need to offset by 1.
+                */
+               phy_idx = cb->args[0] - 1;
+       } else {
+               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_WIPHY])
+                       return -EINVAL;
+               phy_idx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]);
+               if (nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA])
+                       cb->args[1] =
+                               (long)nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA];
+       }
+
+       if (cb->args[1]) {
+               data = nla_data((void *)cb->args[1]);
+               data_len = nla_len((void *)cb->args[1]);
+       }
+
+       mutex_lock(&cfg80211_mutex);
+       dev = cfg80211_rdev_by_wiphy_idx(phy_idx);
+       if (!dev) {
+               mutex_unlock(&cfg80211_mutex);
+               return -ENOENT;
+       }
+       cfg80211_lock_rdev(dev);
+       mutex_unlock(&cfg80211_mutex);
+
+       if (!dev->ops->testmode_dump) {
+               err = -EOPNOTSUPP;
+               goto out_err;
+       }
+
+       while (1) {
+               void *hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).pid,
+                                          cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                          NL80211_CMD_TESTMODE);
+               struct nlattr *tmdata;
+
+               if (nla_put_u32(skb, NL80211_ATTR_WIPHY, dev->wiphy_idx) < 0) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               }
+
+               tmdata = nla_nest_start(skb, NL80211_ATTR_TESTDATA);
+               if (!tmdata) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               }
+               err = dev->ops->testmode_dump(&dev->wiphy, skb, cb,
+                                             data, data_len);
+               nla_nest_end(skb, tmdata);
+
+               if (err == -ENOBUFS || err == -ENOENT) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               } else if (err) {
+                       genlmsg_cancel(skb, hdr);
+                       goto out_err;
+               }
+
+               genlmsg_end(skb, hdr);
+       }
+
+       err = skb->len;
+       /* see above */
+       cb->args[0] = phy_idx + 1;
+ out_err:
+       cfg80211_unlock_rdev(dev);
+       return err;
+}
+
 static struct sk_buff *
 __cfg80211_testmode_alloc_skb(struct cfg80211_registered_device *rdev,
                              int approxlen, u32 pid, u32 seq, gfp_t gfp)
@@ -4816,6 +5255,280 @@ 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->gtk_rekey_failure)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE);
+               if (rdev->wowlan->eap_identity_req)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST);
+               if (rdev->wowlan->four_way_handshake)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE);
+               if (rdev->wowlan->rfkill_release)
+                       NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE);
+               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_GTK_REKEY_SUPPORTED])
+               return -EINVAL;
+
+       if (tb[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) {
+               if (!(wowlan->flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE))
+                       return -EINVAL;
+               new_triggers.gtk_rekey_failure = true;
+       }
+
+       if (tb[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) {
+               if (!(wowlan->flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ))
+                       return -EINVAL;
+               new_triggers.eap_identity_req = true;
+       }
+
+       if (tb[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) {
+               if (!(wowlan->flags & WIPHY_WOWLAN_4WAY_HANDSHAKE))
+                       return -EINVAL;
+               new_triggers.four_way_handshake = true;
+       }
+
+       if (tb[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) {
+               if (!(wowlan->flags & WIPHY_WOWLAN_RFKILL_RELEASE))
+                       return -EINVAL;
+               new_triggers.rfkill_release = 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;
+}
+
+static int nl80211_set_rekey_data(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct nlattr *tb[NUM_NL80211_REKEY_DATA];
+       struct cfg80211_gtk_rekey_data rekey_data;
+       int err;
+
+       if (!info->attrs[NL80211_ATTR_REKEY_DATA])
+               return -EINVAL;
+
+       err = nla_parse(tb, MAX_NL80211_REKEY_DATA,
+                       nla_data(info->attrs[NL80211_ATTR_REKEY_DATA]),
+                       nla_len(info->attrs[NL80211_ATTR_REKEY_DATA]),
+                       nl80211_rekey_policy);
+       if (err)
+               return err;
+
+       if (nla_len(tb[NL80211_REKEY_DATA_REPLAY_CTR]) != NL80211_REPLAY_CTR_LEN)
+               return -ERANGE;
+       if (nla_len(tb[NL80211_REKEY_DATA_KEK]) != NL80211_KEK_LEN)
+               return -ERANGE;
+       if (nla_len(tb[NL80211_REKEY_DATA_KCK]) != NL80211_KCK_LEN)
+               return -ERANGE;
+
+       memcpy(rekey_data.kek, nla_data(tb[NL80211_REKEY_DATA_KEK]),
+              NL80211_KEK_LEN);
+       memcpy(rekey_data.kck, nla_data(tb[NL80211_REKEY_DATA_KCK]),
+              NL80211_KCK_LEN);
+       memcpy(rekey_data.replay_ctr,
+              nla_data(tb[NL80211_REKEY_DATA_REPLAY_CTR]),
+              NL80211_REPLAY_CTR_LEN);
+
+       wdev_lock(wdev);
+       if (!wdev->current_bss) {
+               err = -ENOTCONN;
+               goto out;
+       }
+
+       if (!rdev->ops->set_rekey_data) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       err = rdev->ops->set_rekey_data(&rdev->wiphy, dev, &rekey_data);
+ out:
+       wdev_unlock(wdev);
+       return err;
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -5099,6 +5812,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,
@@ -5151,6 +5880,7 @@ static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_TESTMODE,
                .doit = nl80211_testmode_do,
+               .dumpit = nl80211_testmode_dump,
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
                .internal_flags = NL80211_FLAG_NEED_WIPHY |
@@ -5314,6 +6044,30 @@ 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,
+       },
+       {
+               .cmd = NL80211_CMD_SET_REKEY_OFFLOAD,
+               .doit = nl80211_set_rekey_data,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
 };
 
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
@@ -5409,6 +6163,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)
 {
@@ -5466,6 +6242,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.
@@ -5882,7 +6695,8 @@ void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
        if (addr)
                NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, addr);
        NLA_PUT_U32(msg, NL80211_ATTR_KEY_TYPE, key_type);
-       NLA_PUT_U8(msg, NL80211_ATTR_KEY_IDX, key_id);
+       if (key_id != -1)
+               NLA_PUT_U8(msg, NL80211_ATTR_KEY_IDX, key_id);
        if (tsc)
                NLA_PUT(msg, NL80211_ATTR_KEY_SEQ, 6, tsc);
 
@@ -6198,6 +7012,51 @@ nl80211_send_cqm_rssi_notify(struct cfg80211_registered_device *rdev,
        nlmsg_free(msg);
 }
 
+void nl80211_gtk_rekey_notify(struct cfg80211_registered_device *rdev,
+                             struct net_device *netdev, const u8 *bssid,
+                             const u8 *replay_ctr, gfp_t gfp)
+{
+       struct sk_buff *msg;
+       struct nlattr *rekey_attr;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_SET_REKEY_OFFLOAD);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+       NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, bssid);
+
+       rekey_attr = nla_nest_start(msg, NL80211_ATTR_REKEY_DATA);
+       if (!rekey_attr)
+               goto nla_put_failure;
+
+       NLA_PUT(msg, NL80211_REKEY_DATA_REPLAY_CTR,
+               NL80211_REPLAY_CTR_LEN, replay_ctr);
+
+       nla_nest_end(msg, rekey_attr);
+
+       if (genlmsg_end(msg, hdr) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+                               nl80211_mlme_mcgrp.id, gfp);
+       return;
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       nlmsg_free(msg);
+}
+
 void
 nl80211_send_cqm_pktloss_notify(struct cfg80211_registered_device *rdev,
                                struct net_device *netdev, const u8 *peer,
This page took 0.039829 seconds and 5 git commands to generate.