mac80211: handle no-preauth flag for control port
[linux-block.git] / net / wireless / nl80211.c
index cedf17d4933f4ccdd8387dae144b75a8b43b9a53..6d76162256b41846b624c7698dda2fe788743a7d 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/netlink.h>
 #include <linux/nospec.h>
 #include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
 #include <net/net_namespace.h>
 #include <net/genetlink.h>
 #include <net/cfg80211.h>
@@ -321,6 +322,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 },
@@ -361,7 +385,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 },
@@ -469,6 +493,8 @@ const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED },
        [NL80211_ATTR_STA_PLINK_STATE] =
                NLA_POLICY_MAX(NLA_U8, NUM_NL80211_PLINK_STATES - 1),
+       [NL80211_ATTR_MEASUREMENT_DURATION] = { .type = NLA_U16 },
+       [NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY] = { .type = NLA_FLAG },
        [NL80211_ATTR_MESH_PEER_AID] =
                NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
        [NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
@@ -530,6 +556,8 @@ const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_MDID] = { .type = NLA_U16 },
        [NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
                                  .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_ATTR_CRIT_PROT_ID] = { .type = NLA_U16 },
+       [NL80211_ATTR_MAX_CRIT_PROT_DURATION] = { .type = NLA_U16 },
        [NL80211_ATTR_PEER_AID] =
                NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
        [NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
@@ -560,6 +588,7 @@ const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
                NLA_POLICY_MAX(NLA_U8, IEEE80211_NUM_UPS - 1),
        [NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 },
        [NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 },
+       [NL80211_ATTR_OPER_CLASS] = { .type = NLA_U8 },
        [NL80211_ATTR_MAC_MASK] = {
                .type = NLA_EXACT_LEN_WARN,
                .len = ETH_ALEN
@@ -626,6 +655,10 @@ 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 },
 };
 
 /* policy for the key attributes */
@@ -965,6 +998,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,
@@ -1026,7 +1062,7 @@ struct key_parse {
        struct key_params p;
        int idx;
        int type;
-       bool def, defmgmt;
+       bool def, defmgmt, defbeacon;
        bool def_uni, def_multi;
 };
 
@@ -1042,12 +1078,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])
@@ -1154,14 +1191,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;
                }
        }
@@ -1173,14 +1213,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;
                        }
                }
@@ -1886,6 +1932,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;
@@ -2444,6 +2572,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;
@@ -3475,7 +3609,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;
        }
@@ -3763,8 +3897,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]);
@@ -3840,7 +3980,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;
 
@@ -3887,6 +4027,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)) {
@@ -3924,8 +4082,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]);
@@ -3939,8 +4099,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])
@@ -3951,15 +4113,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;
@@ -4512,6 +4681,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)
 {
@@ -4800,6 +4993,13 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
                err = nl80211_parse_he_obss_pd(
                                        info->attrs[NL80211_ATTR_HE_OBSS_PD],
                                        &params.he_obss_pd);
+               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;
        }
@@ -4823,6 +5023,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
        }
        wdev_unlock(wdev);
 
+out:
        kfree(params.acl);
 
        return err;
@@ -9106,6 +9307,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]) {
@@ -10539,8 +10743,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)
@@ -13802,6 +14007,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
@@ -14756,6 +15096,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 = {