Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-microblaze.git] / net / wireless / nl80211.c
index f0af23c..5fa4021 100644 (file)
@@ -5,7 +5,7 @@
  * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
  * Copyright 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2019 Intel Corporation
+ * Copyright (C) 2018-2020 Intel Corporation
  */
 
 #include <linux/if.h>
@@ -276,6 +276,8 @@ nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = {
        [NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES] = { .type = NLA_U8 },
        [NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI] = { .type = NLA_FLAG },
        [NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED] = { .type = NLA_FLAG },
 };
 
 static const struct nla_policy
@@ -322,6 +324,29 @@ he_obss_pd_policy[NL80211_HE_OBSS_PD_ATTR_MAX + 1] = {
                NLA_POLICY_RANGE(NLA_U8, 1, 20),
 };
 
+static const struct nla_policy
+he_bss_color_policy[NL80211_HE_BSS_COLOR_ATTR_MAX + 1] = {
+       [NL80211_HE_BSS_COLOR_ATTR_COLOR] = NLA_POLICY_RANGE(NLA_U8, 1, 63),
+       [NL80211_HE_BSS_COLOR_ATTR_DISABLED] = { .type = NLA_FLAG },
+       [NL80211_HE_BSS_COLOR_ATTR_PARTIAL] = { .type = NLA_FLAG },
+};
+
+static const struct nla_policy
+nl80211_tid_config_attr_policy[NL80211_TID_CONFIG_ATTR_MAX + 1] = {
+       [NL80211_TID_CONFIG_ATTR_VIF_SUPP] = { .type = NLA_U64 },
+       [NL80211_TID_CONFIG_ATTR_PEER_SUPP] = { .type = NLA_U64 },
+       [NL80211_TID_CONFIG_ATTR_OVERRIDE] = { .type = NLA_FLAG },
+       [NL80211_TID_CONFIG_ATTR_TIDS] = NLA_POLICY_RANGE(NLA_U16, 1, 0xff),
+       [NL80211_TID_CONFIG_ATTR_NOACK] =
+                       NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
+       [NL80211_TID_CONFIG_ATTR_RETRY_SHORT] = NLA_POLICY_MIN(NLA_U8, 1),
+       [NL80211_TID_CONFIG_ATTR_RETRY_LONG] = NLA_POLICY_MIN(NLA_U8, 1),
+       [NL80211_TID_CONFIG_ATTR_AMPDU_CTRL] =
+                       NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
+       [NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL] =
+                       NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
+};
+
 const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD },
        [NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
@@ -362,7 +387,7 @@ const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_KEY] = { .type = NLA_NESTED, },
        [NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
                                    .len = WLAN_MAX_KEY_LEN },
-       [NL80211_ATTR_KEY_IDX] = NLA_POLICY_MAX(NLA_U8, 5),
+       [NL80211_ATTR_KEY_IDX] = NLA_POLICY_MAX(NLA_U8, 7),
        [NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
        [NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG },
        [NL80211_ATTR_KEY_SEQ] = { .type = NLA_BINARY, .len = 16 },
@@ -632,6 +657,12 @@ const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_TWT_RESPONDER] = { .type = NLA_FLAG },
        [NL80211_ATTR_HE_OBSS_PD] = NLA_POLICY_NESTED(he_obss_pd_policy),
        [NL80211_ATTR_VLAN_ID] = NLA_POLICY_RANGE(NLA_U16, 1, VLAN_N_VID - 2),
+       [NL80211_ATTR_HE_BSS_COLOR] = NLA_POLICY_NESTED(he_bss_color_policy),
+       [NL80211_ATTR_TID_CONFIG] =
+               NLA_POLICY_NESTED_ARRAY(nl80211_tid_config_attr_policy),
+       [NL80211_ATTR_CONTROL_PORT_NO_PREAUTH] = { .type = NLA_FLAG },
+       [NL80211_ATTR_PMK_LIFETIME] = NLA_POLICY_MIN(NLA_U32, 1),
+       [NL80211_ATTR_PMK_REAUTH_THRESHOLD] = NLA_POLICY_RANGE(NLA_U8, 1, 100),
 };
 
 /* policy for the key attributes */
@@ -971,6 +1002,9 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
                if ((chan->flags & IEEE80211_CHAN_NO_10MHZ) &&
                    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_10MHZ))
                        goto nla_put_failure;
+               if ((chan->flags & IEEE80211_CHAN_NO_HE) &&
+                   nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HE))
+                       goto nla_put_failure;
        }
 
        if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
@@ -1032,7 +1066,7 @@ struct key_parse {
        struct key_params p;
        int idx;
        int type;
-       bool def, defmgmt;
+       bool def, defmgmt, defbeacon;
        bool def_uni, def_multi;
 };
 
@@ -1048,12 +1082,13 @@ static int nl80211_parse_key_new(struct genl_info *info, struct nlattr *key,
 
        k->def = !!tb[NL80211_KEY_DEFAULT];
        k->defmgmt = !!tb[NL80211_KEY_DEFAULT_MGMT];
+       k->defbeacon = !!tb[NL80211_KEY_DEFAULT_BEACON];
 
        if (k->def) {
                k->def_uni = true;
                k->def_multi = true;
        }
-       if (k->defmgmt)
+       if (k->defmgmt || k->defbeacon)
                k->def_multi = true;
 
        if (tb[NL80211_KEY_IDX])
@@ -1160,14 +1195,17 @@ static int nl80211_parse_key(struct genl_info *info, struct key_parse *k)
        if (err)
                return err;
 
-       if (k->def && k->defmgmt) {
-               GENL_SET_ERR_MSG(info, "key with def && defmgmt is invalid");
+       if ((k->def ? 1 : 0) + (k->defmgmt ? 1 : 0) +
+           (k->defbeacon ? 1 : 0) > 1) {
+               GENL_SET_ERR_MSG(info,
+                                "key with multiple default flags is invalid");
                return -EINVAL;
        }
 
-       if (k->defmgmt) {
+       if (k->defmgmt || k->defbeacon) {
                if (k->def_uni || !k->def_multi) {
-                       GENL_SET_ERR_MSG(info, "defmgmt key must be mcast");
+                       GENL_SET_ERR_MSG(info,
+                                        "defmgmt/defbeacon key must be mcast");
                        return -EINVAL;
                }
        }
@@ -1179,14 +1217,20 @@ static int nl80211_parse_key(struct genl_info *info, struct key_parse *k)
                                                 "defmgmt key idx not 4 or 5");
                                return -EINVAL;
                        }
+               } else if (k->defbeacon) {
+                       if (k->idx < 6 || k->idx > 7) {
+                               GENL_SET_ERR_MSG(info,
+                                                "defbeacon key idx not 6 or 7");
+                               return -EINVAL;
+                       }
                } else if (k->def) {
                        if (k->idx < 0 || k->idx > 3) {
                                GENL_SET_ERR_MSG(info, "def key idx not 0-3");
                                return -EINVAL;
                        }
                } else {
-                       if (k->idx < 0 || k->idx > 5) {
-                               GENL_SET_ERR_MSG(info, "key idx not 0-5");
+                       if (k->idx < 0 || k->idx > 7) {
+                               GENL_SET_ERR_MSG(info, "key idx not 0-7");
                                return -EINVAL;
                        }
                }
@@ -1845,6 +1889,12 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap,
            nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST,
                        cap->ftm.max_ftms_per_burst))
                return -ENOBUFS;
+       if (cap->ftm.trigger_based &&
+           nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED))
+               return -ENOBUFS;
+       if (cap->ftm.non_trigger_based &&
+           nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED))
+               return -ENOBUFS;
 
        nla_nest_end(msg, ftm);
        return 0;
@@ -1892,6 +1942,88 @@ static int nl80211_send_pmsr_capa(struct cfg80211_registered_device *rdev,
        return 0;
 }
 
+static int
+nl80211_put_iftype_akm_suites(struct cfg80211_registered_device *rdev,
+                             struct sk_buff *msg)
+{
+       int i;
+       struct nlattr *nested, *nested_akms;
+       const struct wiphy_iftype_akm_suites *iftype_akms;
+
+       if (!rdev->wiphy.num_iftype_akm_suites ||
+           !rdev->wiphy.iftype_akm_suites)
+               return 0;
+
+       nested = nla_nest_start(msg, NL80211_ATTR_IFTYPE_AKM_SUITES);
+       if (!nested)
+               return -ENOBUFS;
+
+       for (i = 0; i < rdev->wiphy.num_iftype_akm_suites; i++) {
+               nested_akms = nla_nest_start(msg, i + 1);
+               if (!nested_akms)
+                       return -ENOBUFS;
+
+               iftype_akms = &rdev->wiphy.iftype_akm_suites[i];
+
+               if (nl80211_put_iftypes(msg, NL80211_IFTYPE_AKM_ATTR_IFTYPES,
+                                       iftype_akms->iftypes_mask))
+                       return -ENOBUFS;
+
+               if (nla_put(msg, NL80211_IFTYPE_AKM_ATTR_SUITES,
+                           sizeof(u32) * iftype_akms->n_akm_suites,
+                           iftype_akms->akm_suites)) {
+                       return -ENOBUFS;
+               }
+               nla_nest_end(msg, nested_akms);
+       }
+
+       nla_nest_end(msg, nested);
+
+       return 0;
+}
+
+static int
+nl80211_put_tid_config_support(struct cfg80211_registered_device *rdev,
+                              struct sk_buff *msg)
+{
+       struct nlattr *supp;
+
+       if (!rdev->wiphy.tid_config_support.vif &&
+           !rdev->wiphy.tid_config_support.peer)
+               return 0;
+
+       supp = nla_nest_start(msg, NL80211_ATTR_TID_CONFIG);
+       if (!supp)
+               return -ENOSPC;
+
+       if (rdev->wiphy.tid_config_support.vif &&
+           nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_VIF_SUPP,
+                             rdev->wiphy.tid_config_support.vif,
+                             NL80211_TID_CONFIG_ATTR_PAD))
+               goto fail;
+
+       if (rdev->wiphy.tid_config_support.peer &&
+           nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_PEER_SUPP,
+                             rdev->wiphy.tid_config_support.peer,
+                             NL80211_TID_CONFIG_ATTR_PAD))
+               goto fail;
+
+       /* for now we just use the same value ... makes more sense */
+       if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_SHORT,
+                      rdev->wiphy.tid_config_support.max_retry))
+               goto fail;
+       if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_LONG,
+                      rdev->wiphy.tid_config_support.max_retry))
+               goto fail;
+
+       nla_nest_end(msg, supp);
+
+       return 0;
+fail:
+       nla_nest_cancel(msg, supp);
+       return -ENOBUFS;
+}
+
 struct nl80211_dump_wiphy_state {
        s64 filter_wiphy;
        long start;
@@ -2450,6 +2582,12 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
                            rdev->wiphy.akm_suites))
                        goto nla_put_failure;
 
+               if (nl80211_put_iftype_akm_suites(rdev, msg))
+                       goto nla_put_failure;
+
+               if (nl80211_put_tid_config_support(rdev, msg))
+                       goto nla_put_failure;
+
                /* done */
                state->split_start = 0;
                break;
@@ -3481,7 +3619,7 @@ static int nl80211_valid_4addr(struct cfg80211_registered_device *rdev,
                               enum nl80211_iftype iftype)
 {
        if (!use_4addr) {
-               if (netdev && (netdev->priv_flags & IFF_BRIDGE_PORT))
+               if (netdev && netif_is_bridge_port(netdev))
                        return -EBUSY;
                return 0;
        }
@@ -3769,8 +3907,14 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
        void *hdr;
        struct sk_buff *msg;
 
-       if (info->attrs[NL80211_ATTR_KEY_IDX])
+       if (info->attrs[NL80211_ATTR_KEY_IDX]) {
                key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
+               if (key_idx > 5 &&
+                   !wiphy_ext_feature_isset(
+                           &rdev->wiphy,
+                           NL80211_EXT_FEATURE_BEACON_PROTECTION))
+                       return -EINVAL;
+       }
 
        if (info->attrs[NL80211_ATTR_MAC])
                mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
@@ -3846,7 +3990,7 @@ static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
        /* Only support setting default key and
         * Extended Key ID action NL80211_KEY_SET_TX.
         */
-       if (!key.def && !key.defmgmt &&
+       if (!key.def && !key.defmgmt && !key.defbeacon &&
            !(key.p.mode == NL80211_KEY_SET_TX))
                return -EINVAL;
 
@@ -3893,6 +4037,24 @@ static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
 #ifdef CONFIG_CFG80211_WEXT
                dev->ieee80211_ptr->wext.default_mgmt_key = key.idx;
 #endif
+       } else if (key.defbeacon) {
+               if (key.def_uni || !key.def_multi) {
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (!rdev->ops->set_default_beacon_key) {
+                       err = -EOPNOTSUPP;
+                       goto out;
+               }
+
+               err = nl80211_key_allowed(dev->ieee80211_ptr);
+               if (err)
+                       goto out;
+
+               err = rdev_set_default_beacon_key(rdev, dev, key.idx);
+               if (err)
+                       goto out;
        } else if (key.p.mode == NL80211_KEY_SET_TX &&
                   wiphy_ext_feature_isset(&rdev->wiphy,
                                           NL80211_EXT_FEATURE_EXT_KEY_ID)) {
@@ -3930,8 +4092,10 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
        if (err)
                return err;
 
-       if (!key.p.key)
+       if (!key.p.key) {
+               GENL_SET_ERR_MSG(info, "no key");
                return -EINVAL;
+       }
 
        if (info->attrs[NL80211_ATTR_MAC])
                mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
@@ -3945,8 +4109,10 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
 
        /* for now */
        if (key.type != NL80211_KEYTYPE_PAIRWISE &&
-           key.type != NL80211_KEYTYPE_GROUP)
+           key.type != NL80211_KEYTYPE_GROUP) {
+               GENL_SET_ERR_MSG(info, "key type not pairwise or group");
                return -EINVAL;
+       }
 
        if (key.type == NL80211_KEYTYPE_GROUP &&
            info->attrs[NL80211_ATTR_VLAN_ID])
@@ -3957,15 +4123,22 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
 
        if (cfg80211_validate_key_settings(rdev, &key.p, key.idx,
                                           key.type == NL80211_KEYTYPE_PAIRWISE,
-                                          mac_addr))
+                                          mac_addr)) {
+               GENL_SET_ERR_MSG(info, "key setting validation failed");
                return -EINVAL;
+       }
 
        wdev_lock(dev->ieee80211_ptr);
        err = nl80211_key_allowed(dev->ieee80211_ptr);
-       if (!err)
+       if (err)
+               GENL_SET_ERR_MSG(info, "key not allowed");
+       if (!err) {
                err = rdev_add_key(rdev, dev, key.idx,
                                   key.type == NL80211_KEYTYPE_PAIRWISE,
                                    mac_addr, &key.p);
+               if (err)
+                       GENL_SET_ERR_MSG(info, "key addition failed");
+       }
        wdev_unlock(dev->ieee80211_ptr);
 
        return err;
@@ -4518,6 +4691,30 @@ static int nl80211_parse_he_obss_pd(struct nlattr *attrs,
        return 0;
 }
 
+static int nl80211_parse_he_bss_color(struct nlattr *attrs,
+                                     struct cfg80211_he_bss_color *he_bss_color)
+{
+       struct nlattr *tb[NL80211_HE_BSS_COLOR_ATTR_MAX + 1];
+       int err;
+
+       err = nla_parse_nested(tb, NL80211_HE_BSS_COLOR_ATTR_MAX, attrs,
+                              he_bss_color_policy, NULL);
+       if (err)
+               return err;
+
+       if (!tb[NL80211_HE_BSS_COLOR_ATTR_COLOR])
+               return -EINVAL;
+
+       he_bss_color->color =
+               nla_get_u8(tb[NL80211_HE_BSS_COLOR_ATTR_COLOR]);
+       he_bss_color->disabled =
+               nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_DISABLED]);
+       he_bss_color->partial =
+               nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_PARTIAL]);
+
+       return 0;
+}
+
 static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
                                            const u8 *rates)
 {
@@ -4562,6 +4759,9 @@ static void nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
        cap = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
        if (cap && cap[1] >= sizeof(*params->he_cap) + 1)
                params->he_cap = (void *)(cap + 3);
+       cap = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
+       if (cap && cap[1] >= sizeof(*params->he_oper) + 1)
+               params->he_oper = (void *)(cap + 3);
 }
 
 static bool nl80211_get_ap_channel(struct cfg80211_registered_device *rdev,
@@ -4809,6 +5009,14 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
                goto out;
        }
 
+       if (info->attrs[NL80211_ATTR_HE_BSS_COLOR]) {
+               err = nl80211_parse_he_bss_color(
+                                       info->attrs[NL80211_ATTR_HE_BSS_COLOR],
+                                       &params.he_bss_color);
+               if (err)
+                       return err;
+       }
+
        nl80211_calculate_ap_params(&params);
 
        if (info->attrs[NL80211_ATTR_EXTERNAL_AUTH_SUPPORT])
@@ -6077,11 +6285,22 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
        if (info->attrs[NL80211_ATTR_MAC])
                params.mac = nla_data(info->attrs[NL80211_ATTR_MAC]);
 
-       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
-           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN &&
-           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_MESH_POINT &&
-           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
+       switch (dev->ieee80211_ptr->iftype) {
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_AP_VLAN:
+       case NL80211_IFTYPE_MESH_POINT:
+       case NL80211_IFTYPE_P2P_GO:
+               /* always accept these */
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               /* conditionally accept */
+               if (wiphy_ext_feature_isset(&rdev->wiphy,
+                                           NL80211_EXT_FEATURE_DEL_IBSS_STA))
+                       break;
                return -EINVAL;
+       default:
+               return -EINVAL;
+       }
 
        if (!rdev->ops->del_station)
                return -EOPNOTSUPP;
@@ -9112,6 +9331,9 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev,
                        return r;
 
                settings->control_port_over_nl80211 = true;
+
+               if (info->attrs[NL80211_ATTR_CONTROL_PORT_NO_PREAUTH])
+                       settings->control_port_no_preauth = true;
        }
 
        if (info->attrs[NL80211_ATTR_CIPHER_SUITES_PAIRWISE]) {
@@ -10300,6 +10522,15 @@ static int nl80211_setdel_pmksa(struct sk_buff *skb, struct genl_info *info)
                pmksa.pmk_len = nla_len(info->attrs[NL80211_ATTR_PMK]);
        }
 
+       if (info->attrs[NL80211_ATTR_PMK_LIFETIME])
+               pmksa.pmk_lifetime =
+                       nla_get_u32(info->attrs[NL80211_ATTR_PMK_LIFETIME]);
+
+       if (info->attrs[NL80211_ATTR_PMK_REAUTH_THRESHOLD])
+               pmksa.pmk_reauth_threshold =
+                       nla_get_u8(
+                               info->attrs[NL80211_ATTR_PMK_REAUTH_THRESHOLD]);
+
        if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION &&
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_CLIENT &&
            !(dev->ieee80211_ptr->iftype == NL80211_IFTYPE_AP &&
@@ -10545,8 +10776,9 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info)
                return -EOPNOTSUPP;
 
        return cfg80211_mlme_register_mgmt(wdev, info->snd_portid, frame_type,
-                       nla_data(info->attrs[NL80211_ATTR_FRAME_MATCH]),
-                       nla_len(info->attrs[NL80211_ATTR_FRAME_MATCH]));
+                                          nla_data(info->attrs[NL80211_ATTR_FRAME_MATCH]),
+                                          nla_len(info->attrs[NL80211_ATTR_FRAME_MATCH]),
+                                          info->extack);
 }
 
 static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
@@ -13808,6 +14040,141 @@ static int nl80211_probe_mesh_link(struct sk_buff *skb, struct genl_info *info)
        return rdev_probe_mesh_link(rdev, dev, dest, buf, len);
 }
 
+static int parse_tid_conf(struct cfg80211_registered_device *rdev,
+                         struct nlattr *attrs[], struct net_device *dev,
+                         struct cfg80211_tid_cfg *tid_conf,
+                         struct genl_info *info, const u8 *peer)
+{
+       struct netlink_ext_ack *extack = info->extack;
+       u64 mask;
+       int err;
+
+       if (!attrs[NL80211_TID_CONFIG_ATTR_TIDS])
+               return -EINVAL;
+
+       tid_conf->config_override =
+                       nla_get_flag(attrs[NL80211_TID_CONFIG_ATTR_OVERRIDE]);
+       tid_conf->tids = nla_get_u16(attrs[NL80211_TID_CONFIG_ATTR_TIDS]);
+
+       if (tid_conf->config_override) {
+               if (rdev->ops->reset_tid_config) {
+                       err = rdev_reset_tid_config(rdev, dev, peer,
+                                                   tid_conf->tids);
+                       /* If peer is there no other configuration will be
+                        * allowed
+                        */
+                       if (err || peer)
+                               return err;
+               } else {
+                       return -EINVAL;
+               }
+       }
+
+       if (attrs[NL80211_TID_CONFIG_ATTR_NOACK]) {
+               tid_conf->mask |= BIT(NL80211_TID_CONFIG_ATTR_NOACK);
+               tid_conf->noack =
+                       nla_get_u8(attrs[NL80211_TID_CONFIG_ATTR_NOACK]);
+       }
+
+       if (attrs[NL80211_TID_CONFIG_ATTR_RETRY_SHORT]) {
+               tid_conf->mask |= BIT(NL80211_TID_CONFIG_ATTR_RETRY_SHORT);
+               tid_conf->retry_short =
+                       nla_get_u8(attrs[NL80211_TID_CONFIG_ATTR_RETRY_SHORT]);
+
+               if (tid_conf->retry_short > rdev->wiphy.max_data_retry_count)
+                       return -EINVAL;
+       }
+
+       if (attrs[NL80211_TID_CONFIG_ATTR_RETRY_LONG]) {
+               tid_conf->mask |= BIT(NL80211_TID_CONFIG_ATTR_RETRY_LONG);
+               tid_conf->retry_long =
+                       nla_get_u8(attrs[NL80211_TID_CONFIG_ATTR_RETRY_LONG]);
+
+               if (tid_conf->retry_long > rdev->wiphy.max_data_retry_count)
+                       return -EINVAL;
+       }
+
+       if (attrs[NL80211_TID_CONFIG_ATTR_AMPDU_CTRL]) {
+               tid_conf->mask |= BIT(NL80211_TID_CONFIG_ATTR_AMPDU_CTRL);
+               tid_conf->ampdu =
+                       nla_get_u8(attrs[NL80211_TID_CONFIG_ATTR_AMPDU_CTRL]);
+       }
+
+       if (attrs[NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL]) {
+               tid_conf->mask |= BIT(NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL);
+               tid_conf->rtscts =
+                       nla_get_u8(attrs[NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL]);
+       }
+
+       if (peer)
+               mask = rdev->wiphy.tid_config_support.peer;
+       else
+               mask = rdev->wiphy.tid_config_support.vif;
+
+       if (tid_conf->mask & ~mask) {
+               NL_SET_ERR_MSG(extack, "unsupported TID configuration");
+               return -ENOTSUPP;
+       }
+
+       return 0;
+}
+
+static int nl80211_set_tid_config(struct sk_buff *skb,
+                                 struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct nlattr *attrs[NL80211_TID_CONFIG_ATTR_MAX + 1];
+       struct net_device *dev = info->user_ptr[1];
+       struct cfg80211_tid_config *tid_config;
+       struct nlattr *tid;
+       int conf_idx = 0, rem_conf;
+       int ret = -EINVAL;
+       u32 num_conf = 0;
+
+       if (!info->attrs[NL80211_ATTR_TID_CONFIG])
+               return -EINVAL;
+
+       if (!rdev->ops->set_tid_config)
+               return -EOPNOTSUPP;
+
+       nla_for_each_nested(tid, info->attrs[NL80211_ATTR_TID_CONFIG],
+                           rem_conf)
+               num_conf++;
+
+       tid_config = kzalloc(struct_size(tid_config, tid_conf, num_conf),
+                            GFP_KERNEL);
+       if (!tid_config)
+               return -ENOMEM;
+
+       tid_config->n_tid_conf = num_conf;
+
+       if (info->attrs[NL80211_ATTR_MAC])
+               tid_config->peer = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+       nla_for_each_nested(tid, info->attrs[NL80211_ATTR_TID_CONFIG],
+                           rem_conf) {
+               ret = nla_parse_nested(attrs, NL80211_TID_CONFIG_ATTR_MAX,
+                                      tid, NULL, NULL);
+
+               if (ret)
+                       goto bad_tid_conf;
+
+               ret = parse_tid_conf(rdev, attrs, dev,
+                                    &tid_config->tid_conf[conf_idx],
+                                    info, tid_config->peer);
+               if (ret)
+                       goto bad_tid_conf;
+
+               conf_idx++;
+       }
+
+       ret = rdev_set_tid_config(rdev, dev, tid_config);
+
+bad_tid_conf:
+       kfree(tid_config);
+       return ret;
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -14762,6 +15129,13 @@ static const struct genl_ops nl80211_ops[] = {
                .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
                                  NL80211_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL80211_CMD_SET_TID_CONFIG,
+               .doit = nl80211_set_tid_config,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
 };
 
 static struct genl_family nl80211_fam __ro_after_init = {