Merge tag 'metag-v3.9-rc1-v4' of git://git.kernel.org/pub/scm/linux/kernel/git/jhogan...
[firefly-linux-kernel-4.4.55.git] / net / wireless / nl80211.c
index cc0fad30b8c9de64b9967bb28751f7b2534b53ed..35545ccc30fd0079acb1e848c4ecaff0cdc8b371 100644 (file)
@@ -19,6 +19,7 @@
 #include <net/genetlink.h>
 #include <net/cfg80211.h>
 #include <net/sock.h>
+#include <net/inet_connection_sock.h>
 #include "core.h"
 #include "nl80211.h"
 #include "reg.h"
@@ -367,6 +368,8 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
        [NL80211_ATTR_P2P_OPPPS] = { .type = NLA_U8 },
        [NL80211_ATTR_ACL_POLICY] = {. type = NLA_U32 },
        [NL80211_ATTR_MAC_ADDRS] = { .type = NLA_NESTED },
+       [NL80211_ATTR_STA_CAPABILITY] = { .type = NLA_U16 },
+       [NL80211_ATTR_STA_EXT_CAPABILITY] = { .type = NLA_BINARY, },
 };
 
 /* policy for the key attributes */
@@ -399,6 +402,26 @@ nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = {
        [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 },
+       [NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+nl80211_wowlan_tcp_policy[NUM_NL80211_WOWLAN_TCP] = {
+       [NL80211_WOWLAN_TCP_SRC_IPV4] = { .type = NLA_U32 },
+       [NL80211_WOWLAN_TCP_DST_IPV4] = { .type = NLA_U32 },
+       [NL80211_WOWLAN_TCP_DST_MAC] = { .len = ETH_ALEN },
+       [NL80211_WOWLAN_TCP_SRC_PORT] = { .type = NLA_U16 },
+       [NL80211_WOWLAN_TCP_DST_PORT] = { .type = NLA_U16 },
+       [NL80211_WOWLAN_TCP_DATA_PAYLOAD] = { .len = 1 },
+       [NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ] = {
+               .len = sizeof(struct nl80211_wowlan_tcp_data_seq)
+       },
+       [NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN] = {
+               .len = sizeof(struct nl80211_wowlan_tcp_data_token)
+       },
+       [NL80211_WOWLAN_TCP_DATA_INTERVAL] = { .type = NLA_U32 },
+       [NL80211_WOWLAN_TCP_WAKE_PAYLOAD] = { .len = 1 },
+       [NL80211_WOWLAN_TCP_WAKE_MASK] = { .len = 1 },
 };
 
 /* policy for GTK rekey offload attributes */
@@ -531,8 +554,27 @@ static int nl80211_msg_put_channel(struct sk_buff *msg,
        if ((chan->flags & IEEE80211_CHAN_NO_IBSS) &&
            nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_IBSS))
                goto nla_put_failure;
-       if ((chan->flags & IEEE80211_CHAN_RADAR) &&
-           nla_put_flag(msg, NL80211_FREQUENCY_ATTR_RADAR))
+       if (chan->flags & IEEE80211_CHAN_RADAR) {
+               u32 time = elapsed_jiffies_msecs(chan->dfs_state_entered);
+               if (nla_put_flag(msg, NL80211_FREQUENCY_ATTR_RADAR))
+                       goto nla_put_failure;
+               if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_STATE,
+                               chan->dfs_state))
+                       goto nla_put_failure;
+               if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_TIME, time))
+                       goto nla_put_failure;
+       }
+       if ((chan->flags & IEEE80211_CHAN_NO_HT40MINUS) &&
+           nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_MINUS))
+               goto nla_put_failure;
+       if ((chan->flags & IEEE80211_CHAN_NO_HT40PLUS) &&
+           nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_PLUS))
+               goto nla_put_failure;
+       if ((chan->flags & IEEE80211_CHAN_NO_80MHZ) &&
+           nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_80MHZ))
+               goto nla_put_failure;
+       if ((chan->flags & IEEE80211_CHAN_NO_160MHZ) &&
+           nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_160MHZ))
                goto nla_put_failure;
 
        if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
@@ -872,6 +914,48 @@ nla_put_failure:
        return -ENOBUFS;
 }
 
+#ifdef CONFIG_PM
+static int nl80211_send_wowlan_tcp_caps(struct cfg80211_registered_device *rdev,
+                                       struct sk_buff *msg)
+{
+       const struct wiphy_wowlan_tcp_support *tcp = rdev->wiphy.wowlan.tcp;
+       struct nlattr *nl_tcp;
+
+       if (!tcp)
+               return 0;
+
+       nl_tcp = nla_nest_start(msg, NL80211_WOWLAN_TRIG_TCP_CONNECTION);
+       if (!nl_tcp)
+               return -ENOBUFS;
+
+       if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
+                       tcp->data_payload_max))
+               return -ENOBUFS;
+
+       if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
+                       tcp->data_payload_max))
+               return -ENOBUFS;
+
+       if (tcp->seq && nla_put_flag(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ))
+               return -ENOBUFS;
+
+       if (tcp->tok && nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
+                               sizeof(*tcp->tok), tcp->tok))
+               return -ENOBUFS;
+
+       if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL,
+                       tcp->data_interval_max))
+               return -ENOBUFS;
+
+       if (nla_put_u32(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD,
+                       tcp->wake_payload_max))
+               return -ENOBUFS;
+
+       nla_nest_end(msg, nl_tcp);
+       return 0;
+}
+#endif
+
 static int nl80211_send_wiphy(struct sk_buff *msg, u32 portid, u32 seq, int flags,
                              struct cfg80211_registered_device *dev)
 {
@@ -1246,6 +1330,9 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 portid, u32 seq, int flag
                                goto nla_put_failure;
                }
 
+               if (nl80211_send_wowlan_tcp_caps(dev, msg))
+                       goto nla_put_failure;
+
                nla_nest_end(msg, nl_wowlan);
        }
 #endif
@@ -1278,6 +1365,15 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 portid, u32 seq, int flag
                        dev->wiphy.max_acl_mac_addrs))
                goto nla_put_failure;
 
+       if (dev->wiphy.extended_capabilities &&
+           (nla_put(msg, NL80211_ATTR_EXT_CAPA,
+                    dev->wiphy.extended_capabilities_len,
+                    dev->wiphy.extended_capabilities) ||
+            nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
+                    dev->wiphy.extended_capabilities_len,
+                    dev->wiphy.extended_capabilities_mask)))
+               goto nla_put_failure;
+
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
@@ -2709,6 +2805,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_ap_settings params;
        int err;
+       u8 radar_detect_width = 0;
 
        if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
@@ -2827,9 +2924,19 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
        if (!cfg80211_reg_can_beacon(&rdev->wiphy, &params.chandef))
                return -EINVAL;
 
+       err = cfg80211_chandef_dfs_required(wdev->wiphy, &params.chandef);
+       if (err < 0)
+               return err;
+       if (err) {
+               radar_detect_width = BIT(params.chandef.width);
+               params.radar_required = true;
+       }
+
        mutex_lock(&rdev->devlist_mtx);
-       err = cfg80211_can_use_chan(rdev, wdev, params.chandef.chan,
-                                   CHAN_MODE_SHARED);
+       err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
+                                          params.chandef.chan,
+                                          CHAN_MODE_SHARED,
+                                          radar_detect_width);
        mutex_unlock(&rdev->devlist_mtx);
 
        if (err)
@@ -3302,6 +3409,54 @@ static struct net_device *get_vlan(struct genl_info *info,
        return ERR_PTR(ret);
 }
 
+static struct nla_policy
+nl80211_sta_wme_policy[NL80211_STA_WME_MAX + 1] __read_mostly = {
+       [NL80211_STA_WME_UAPSD_QUEUES] = { .type = NLA_U8 },
+       [NL80211_STA_WME_MAX_SP] = { .type = NLA_U8 },
+};
+
+static int nl80211_set_station_tdls(struct genl_info *info,
+                                   struct station_parameters *params)
+{
+       struct nlattr *tb[NL80211_STA_WME_MAX + 1];
+       struct nlattr *nla;
+       int err;
+
+       /* Dummy STA entry gets updated once the peer capabilities are known */
+       if (info->attrs[NL80211_ATTR_HT_CAPABILITY])
+               params->ht_capa =
+                       nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
+       if (info->attrs[NL80211_ATTR_VHT_CAPABILITY])
+               params->vht_capa =
+                       nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY]);
+
+       /* parse WME attributes if present */
+       if (!info->attrs[NL80211_ATTR_STA_WME])
+               return 0;
+
+       nla = info->attrs[NL80211_ATTR_STA_WME];
+       err = nla_parse_nested(tb, NL80211_STA_WME_MAX, nla,
+                              nl80211_sta_wme_policy);
+       if (err)
+               return err;
+
+       if (tb[NL80211_STA_WME_UAPSD_QUEUES])
+               params->uapsd_queues = nla_get_u8(
+                       tb[NL80211_STA_WME_UAPSD_QUEUES]);
+       if (params->uapsd_queues & ~IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
+               return -EINVAL;
+
+       if (tb[NL80211_STA_WME_MAX_SP])
+               params->max_sp = nla_get_u8(tb[NL80211_STA_WME_MAX_SP]);
+
+       if (params->max_sp & ~IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK)
+               return -EINVAL;
+
+       params->sta_modify_mask |= STATION_PARAM_APPLY_UAPSD;
+
+       return 0;
+}
+
 static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -3330,8 +3485,20 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                        nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
        }
 
-       if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL] ||
-           info->attrs[NL80211_ATTR_HT_CAPABILITY])
+       if (info->attrs[NL80211_ATTR_STA_CAPABILITY]) {
+               params.capability =
+                       nla_get_u16(info->attrs[NL80211_ATTR_STA_CAPABILITY]);
+               params.sta_modify_mask |= STATION_PARAM_APPLY_CAPABILITY;
+       }
+
+       if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]) {
+               params.ext_capab =
+                       nla_data(info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]);
+               params.ext_capab_len =
+                       nla_len(info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]);
+       }
+
+       if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
                return -EINVAL;
 
        if (!rdev->ops->change_station)
@@ -3400,6 +3567,13 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                /* reject other things that can't change */
                if (params.supported_rates)
                        return -EINVAL;
+               if (info->attrs[NL80211_ATTR_STA_CAPABILITY])
+                       return -EINVAL;
+               if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY])
+                       return -EINVAL;
+               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
+                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
+                       return -EINVAL;
 
                /* must be last in here for error handling */
                params.vlan = get_vlan(info, rdev);
@@ -3415,13 +3589,29 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                 * to change the flag.
                 */
                params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
-               /* fall through */
+               /* Include parameters for TDLS peer (driver will check) */
+               err = nl80211_set_station_tdls(info, &params);
+               if (err)
+                       return err;
+               /* disallow things sta doesn't support */
+               if (params.plink_action)
+                       return -EINVAL;
+               if (params.local_pm)
+                       return -EINVAL;
+               /* reject any changes other than AUTHORIZED or WME (for TDLS) */
+               if (params.sta_flags_mask & ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+                                             BIT(NL80211_STA_FLAG_WME)))
+                       return -EINVAL;
+               break;
        case NL80211_IFTYPE_ADHOC:
                /* disallow things sta doesn't support */
                if (params.plink_action)
                        return -EINVAL;
                if (params.local_pm)
                        return -EINVAL;
+               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
+                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
+                       return -EINVAL;
                /* reject any changes other than AUTHORIZED */
                if (params.sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
                        return -EINVAL;
@@ -3432,6 +3622,13 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                        return -EINVAL;
                if (params.supported_rates)
                        return -EINVAL;
+               if (info->attrs[NL80211_ATTR_STA_CAPABILITY])
+                       return -EINVAL;
+               if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY])
+                       return -EINVAL;
+               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
+                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
+                       return -EINVAL;
                /*
                 * No special handling for TDLS here -- the userspace
                 * mesh code doesn't have this bug.
@@ -3456,12 +3653,6 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
-static struct nla_policy
-nl80211_sta_wme_policy[NL80211_STA_WME_MAX + 1] __read_mostly = {
-       [NL80211_STA_WME_UAPSD_QUEUES] = { .type = NLA_U8 },
-       [NL80211_STA_WME_MAX_SP] = { .type = NLA_U8 },
-};
-
 static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -3496,6 +3687,19 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
        if (!params.aid || params.aid > IEEE80211_MAX_AID)
                return -EINVAL;
 
+       if (info->attrs[NL80211_ATTR_STA_CAPABILITY]) {
+               params.capability =
+                       nla_get_u16(info->attrs[NL80211_ATTR_STA_CAPABILITY]);
+               params.sta_modify_mask |= STATION_PARAM_APPLY_CAPABILITY;
+       }
+
+       if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]) {
+               params.ext_capab =
+                       nla_data(info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]);
+               params.ext_capab_len =
+                       nla_len(info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY]);
+       }
+
        if (info->attrs[NL80211_ATTR_HT_CAPABILITY])
                params.ht_capa =
                        nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
@@ -4989,6 +5193,54 @@ static int nl80211_stop_sched_scan(struct sk_buff *skb,
        return err;
 }
 
+static int nl80211_start_radar_detection(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 cfg80211_chan_def chandef;
+       int err;
+
+       err = nl80211_parse_chandef(rdev, info, &chandef);
+       if (err)
+               return err;
+
+       if (wdev->cac_started)
+               return -EBUSY;
+
+       err = cfg80211_chandef_dfs_required(wdev->wiphy, &chandef);
+       if (err < 0)
+               return err;
+
+       if (err == 0)
+               return -EINVAL;
+
+       if (chandef.chan->dfs_state != NL80211_DFS_USABLE)
+               return -EINVAL;
+
+       if (!rdev->ops->start_radar_detection)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&rdev->devlist_mtx);
+       err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
+                                          chandef.chan, CHAN_MODE_SHARED,
+                                          BIT(chandef.width));
+       if (err)
+               goto err_locked;
+
+       err = rdev->ops->start_radar_detection(&rdev->wiphy, dev, &chandef);
+       if (!err) {
+               wdev->channel = chandef.chan;
+               wdev->cac_started = true;
+               wdev->cac_start_time = jiffies;
+       }
+err_locked:
+       mutex_unlock(&rdev->devlist_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,
@@ -6930,16 +7182,67 @@ static int nl80211_send_wowlan_patterns(struct sk_buff *msg,
        return 0;
 }
 
+static int nl80211_send_wowlan_tcp(struct sk_buff *msg,
+                                  struct cfg80211_wowlan_tcp *tcp)
+{
+       struct nlattr *nl_tcp;
+
+       if (!tcp)
+               return 0;
+
+       nl_tcp = nla_nest_start(msg, NL80211_WOWLAN_TRIG_TCP_CONNECTION);
+       if (!nl_tcp)
+               return -ENOBUFS;
+
+       if (nla_put_be32(msg, NL80211_WOWLAN_TCP_SRC_IPV4, tcp->src) ||
+           nla_put_be32(msg, NL80211_WOWLAN_TCP_DST_IPV4, tcp->dst) ||
+           nla_put(msg, NL80211_WOWLAN_TCP_DST_MAC, ETH_ALEN, tcp->dst_mac) ||
+           nla_put_u16(msg, NL80211_WOWLAN_TCP_SRC_PORT, tcp->src_port) ||
+           nla_put_u16(msg, NL80211_WOWLAN_TCP_DST_PORT, tcp->dst_port) ||
+           nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
+                   tcp->payload_len, tcp->payload) ||
+           nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL,
+                       tcp->data_interval) ||
+           nla_put(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD,
+                   tcp->wake_len, tcp->wake_data) ||
+           nla_put(msg, NL80211_WOWLAN_TCP_WAKE_MASK,
+                   DIV_ROUND_UP(tcp->wake_len, 8), tcp->wake_mask))
+               return -ENOBUFS;
+
+       if (tcp->payload_seq.len &&
+           nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ,
+                   sizeof(tcp->payload_seq), &tcp->payload_seq))
+               return -ENOBUFS;
+
+       if (tcp->payload_tok.len &&
+           nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
+                   sizeof(tcp->payload_tok) + tcp->tokens_size,
+                   &tcp->payload_tok))
+               return -ENOBUFS;
+
+       return 0;
+}
+
 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;
+       u32 size = NLMSG_DEFAULT_SIZE;
 
-       if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns)
+       if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns &&
+           !rdev->wiphy.wowlan.tcp)
                return -EOPNOTSUPP;
 
-       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (rdev->wowlan && rdev->wowlan->tcp) {
+               /* adjust size to have room for all the data */
+               size += rdev->wowlan->tcp->tokens_size +
+                       rdev->wowlan->tcp->payload_len +
+                       rdev->wowlan->tcp->wake_len +
+                       rdev->wowlan->tcp->wake_len / 8;
+       }
+
+       msg = nlmsg_new(size, GFP_KERNEL);
        if (!msg)
                return -ENOMEM;
 
@@ -6970,8 +7273,13 @@ static int nl80211_get_wowlan(struct sk_buff *skb, struct genl_info *info)
                    (rdev->wowlan->rfkill_release &&
                     nla_put_flag(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE)))
                        goto nla_put_failure;
+
                if (nl80211_send_wowlan_patterns(msg, rdev))
                        goto nla_put_failure;
+
+               if (nl80211_send_wowlan_tcp(msg, rdev->wowlan->tcp))
+                       goto nla_put_failure;
+
                nla_nest_end(msg, nl_wowlan);
        }
 
@@ -6983,6 +7291,150 @@ nla_put_failure:
        return -ENOBUFS;
 }
 
+static int nl80211_parse_wowlan_tcp(struct cfg80211_registered_device *rdev,
+                                   struct nlattr *attr,
+                                   struct cfg80211_wowlan *trig)
+{
+       struct nlattr *tb[NUM_NL80211_WOWLAN_TCP];
+       struct cfg80211_wowlan_tcp *cfg;
+       struct nl80211_wowlan_tcp_data_token *tok = NULL;
+       struct nl80211_wowlan_tcp_data_seq *seq = NULL;
+       u32 size;
+       u32 data_size, wake_size, tokens_size = 0, wake_mask_size;
+       int err, port;
+
+       if (!rdev->wiphy.wowlan.tcp)
+               return -EINVAL;
+
+       err = nla_parse(tb, MAX_NL80211_WOWLAN_TCP,
+                       nla_data(attr), nla_len(attr),
+                       nl80211_wowlan_tcp_policy);
+       if (err)
+               return err;
+
+       if (!tb[NL80211_WOWLAN_TCP_SRC_IPV4] ||
+           !tb[NL80211_WOWLAN_TCP_DST_IPV4] ||
+           !tb[NL80211_WOWLAN_TCP_DST_MAC] ||
+           !tb[NL80211_WOWLAN_TCP_DST_PORT] ||
+           !tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD] ||
+           !tb[NL80211_WOWLAN_TCP_DATA_INTERVAL] ||
+           !tb[NL80211_WOWLAN_TCP_WAKE_PAYLOAD] ||
+           !tb[NL80211_WOWLAN_TCP_WAKE_MASK])
+               return -EINVAL;
+
+       data_size = nla_len(tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD]);
+       if (data_size > rdev->wiphy.wowlan.tcp->data_payload_max)
+               return -EINVAL;
+
+       if (nla_get_u32(tb[NL80211_WOWLAN_TCP_DATA_INTERVAL]) >
+                       rdev->wiphy.wowlan.tcp->data_interval_max)
+               return -EINVAL;
+
+       wake_size = nla_len(tb[NL80211_WOWLAN_TCP_WAKE_PAYLOAD]);
+       if (wake_size > rdev->wiphy.wowlan.tcp->wake_payload_max)
+               return -EINVAL;
+
+       wake_mask_size = nla_len(tb[NL80211_WOWLAN_TCP_WAKE_MASK]);
+       if (wake_mask_size != DIV_ROUND_UP(wake_size, 8))
+               return -EINVAL;
+
+       if (tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN]) {
+               u32 tokln = nla_len(tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN]);
+
+               tok = nla_data(tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN]);
+               tokens_size = tokln - sizeof(*tok);
+
+               if (!tok->len || tokens_size % tok->len)
+                       return -EINVAL;
+               if (!rdev->wiphy.wowlan.tcp->tok)
+                       return -EINVAL;
+               if (tok->len > rdev->wiphy.wowlan.tcp->tok->max_len)
+                       return -EINVAL;
+               if (tok->len < rdev->wiphy.wowlan.tcp->tok->min_len)
+                       return -EINVAL;
+               if (tokens_size > rdev->wiphy.wowlan.tcp->tok->bufsize)
+                       return -EINVAL;
+               if (tok->offset + tok->len > data_size)
+                       return -EINVAL;
+       }
+
+       if (tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ]) {
+               seq = nla_data(tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ]);
+               if (!rdev->wiphy.wowlan.tcp->seq)
+                       return -EINVAL;
+               if (seq->len == 0 || seq->len > 4)
+                       return -EINVAL;
+               if (seq->len + seq->offset > data_size)
+                       return -EINVAL;
+       }
+
+       size = sizeof(*cfg);
+       size += data_size;
+       size += wake_size + wake_mask_size;
+       size += tokens_size;
+
+       cfg = kzalloc(size, GFP_KERNEL);
+       if (!cfg)
+               return -ENOMEM;
+       cfg->src = nla_get_be32(tb[NL80211_WOWLAN_TCP_SRC_IPV4]);
+       cfg->dst = nla_get_be32(tb[NL80211_WOWLAN_TCP_DST_IPV4]);
+       memcpy(cfg->dst_mac, nla_data(tb[NL80211_WOWLAN_TCP_DST_MAC]),
+              ETH_ALEN);
+       if (tb[NL80211_WOWLAN_TCP_SRC_PORT])
+               port = nla_get_u16(tb[NL80211_WOWLAN_TCP_SRC_PORT]);
+       else
+               port = 0;
+#ifdef CONFIG_INET
+       /* allocate a socket and port for it and use it */
+       err = __sock_create(wiphy_net(&rdev->wiphy), PF_INET, SOCK_STREAM,
+                           IPPROTO_TCP, &cfg->sock, 1);
+       if (err) {
+               kfree(cfg);
+               return err;
+       }
+       if (inet_csk_get_port(cfg->sock->sk, port)) {
+               sock_release(cfg->sock);
+               kfree(cfg);
+               return -EADDRINUSE;
+       }
+       cfg->src_port = inet_sk(cfg->sock->sk)->inet_num;
+#else
+       if (!port) {
+               kfree(cfg);
+               return -EINVAL;
+       }
+       cfg->src_port = port;
+#endif
+
+       cfg->dst_port = nla_get_u16(tb[NL80211_WOWLAN_TCP_DST_PORT]);
+       cfg->payload_len = data_size;
+       cfg->payload = (u8 *)cfg + sizeof(*cfg) + tokens_size;
+       memcpy((void *)cfg->payload,
+              nla_data(tb[NL80211_WOWLAN_TCP_DATA_PAYLOAD]),
+              data_size);
+       if (seq)
+               cfg->payload_seq = *seq;
+       cfg->data_interval = nla_get_u32(tb[NL80211_WOWLAN_TCP_DATA_INTERVAL]);
+       cfg->wake_len = wake_size;
+       cfg->wake_data = (u8 *)cfg + sizeof(*cfg) + tokens_size + data_size;
+       memcpy((void *)cfg->wake_data,
+              nla_data(tb[NL80211_WOWLAN_TCP_WAKE_PAYLOAD]),
+              wake_size);
+       cfg->wake_mask = (u8 *)cfg + sizeof(*cfg) + tokens_size +
+                        data_size + wake_size;
+       memcpy((void *)cfg->wake_mask,
+              nla_data(tb[NL80211_WOWLAN_TCP_WAKE_MASK]),
+              wake_mask_size);
+       if (tok) {
+               cfg->tokens_size = tokens_size;
+               memcpy(&cfg->payload_tok, tok, sizeof(*tok) + tokens_size);
+       }
+
+       trig->tcp = cfg;
+
+       return 0;
+}
+
 static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -6993,7 +7445,8 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
        int err, i;
        bool prev_enabled = rdev->wowlan;
 
-       if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns)
+       if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns &&
+           !rdev->wiphy.wowlan.tcp)
                return -EOPNOTSUPP;
 
        if (!info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) {
@@ -7120,6 +7573,14 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
                }
        }
 
+       if (tb[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) {
+               err = nl80211_parse_wowlan_tcp(
+                       rdev, tb[NL80211_WOWLAN_TRIG_TCP_CONNECTION],
+                       &new_triggers);
+               if (err)
+                       goto error;
+       }
+
        ntrig = kmemdup(&new_triggers, sizeof(new_triggers), GFP_KERNEL);
        if (!ntrig) {
                err = -ENOMEM;
@@ -7137,6 +7598,9 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
        for (i = 0; i < new_triggers.n_patterns; i++)
                kfree(new_triggers.patterns[i].mask);
        kfree(new_triggers.patterns);
+       if (new_triggers.tcp && new_triggers.tcp->sock)
+               sock_release(new_triggers.tcp->sock);
+       kfree(new_triggers.tcp);
        return err;
 }
 #endif
@@ -8027,6 +8491,14 @@ static struct genl_ops nl80211_ops[] = {
                .internal_flags = NL80211_FLAG_NEED_NETDEV |
                                  NL80211_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL80211_CMD_RADAR_DETECT,
+               .doit = nl80211_start_radar_detection,
+               .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 = {
@@ -9223,6 +9695,57 @@ nl80211_send_cqm_txe_notify(struct cfg80211_registered_device *rdev,
        nlmsg_free(msg);
 }
 
+void
+nl80211_radar_notify(struct cfg80211_registered_device *rdev,
+                    struct cfg80211_chan_def *chandef,
+                    enum nl80211_radar_event event,
+                    struct net_device *netdev, gfp_t gfp)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_RADAR_DETECT);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx))
+               goto nla_put_failure;
+
+       /* NOP and radar events don't need a netdev parameter */
+       if (netdev) {
+               struct wireless_dev *wdev = netdev->ieee80211_ptr;
+
+               if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex) ||
+                   nla_put_u64(msg, NL80211_ATTR_WDEV, wdev_id(wdev)))
+                       goto nla_put_failure;
+       }
+
+       if (nla_put_u32(msg, NL80211_ATTR_RADAR_EVENT, event))
+               goto nla_put_failure;
+
+       if (nl80211_send_chandef(msg, chandef))
+               goto nla_put_failure;
+
+       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,
@@ -9418,6 +9941,17 @@ void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev,
                                wakeup->pattern_idx))
                        goto free_msg;
 
+               if (wakeup->tcp_match)
+                       nla_put_flag(msg, NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH);
+
+               if (wakeup->tcp_connlost)
+                       nla_put_flag(msg,
+                                    NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST);
+
+               if (wakeup->tcp_nomoretokens)
+                       nla_put_flag(msg,
+                               NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS);
+
                if (wakeup->packet) {
                        u32 pkt_attr = NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211;
                        u32 len_attr = NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211_LEN;