wifi: cfg80211: add API to add/modify/remove a link station
authorShaul Triebitz <shaul.triebitz@intel.com>
Wed, 8 Jun 2022 09:01:12 +0000 (12:01 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 15 Jul 2022 09:43:13 +0000 (11:43 +0200)
Add an API for adding/modifying/removing a link of a station.

Signed-off-by: Shaul Triebitz <shaul.triebitz@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/wireless/nl80211.c
net/wireless/rdev-ops.h
net/wireless/trace.h

index 3bcdd20ace66bfaaa07a4b556371f25270023166..42276488126957d253607a27ee48e434cc08a83d 100644 (file)
@@ -1456,6 +1456,61 @@ struct sta_txpwr {
        enum nl80211_tx_power_setting type;
 };
 
+/**
+ * struct link_station_parameters - link station parameters
+ *
+ * Used to change and create a new link station.
+ *
+ * @mld_mac: MAC address of the station
+ * @link_id: the link id (-1 for non-MLD station)
+ * @link_mac: MAC address of the link
+ * @supported_rates: supported rates in IEEE 802.11 format
+ *     (or NULL for no change)
+ * @supported_rates_len: number of supported rates
+ * @ht_capa: HT capabilities of station
+ * @vht_capa: VHT capabilities of station
+ * @opmode_notif: operating mode field from Operating Mode Notification
+ * @opmode_notif_used: information if operating mode field is used
+ * @he_capa: HE capabilities of station
+ * @he_capa_len: the length of the HE capabilities
+ * @txpwr: transmit power for an associated station
+ * @txpwr_set: txpwr field is set
+ * @he_6ghz_capa: HE 6 GHz Band capabilities of station
+ * @eht_capa: EHT capabilities of station
+ * @eht_capa_len: the length of the EHT capabilities
+ */
+struct link_station_parameters {
+       const u8 *mld_mac;
+       int link_id;
+       const u8 *link_mac;
+       const u8 *supported_rates;
+       u8 supported_rates_len;
+       const struct ieee80211_ht_cap *ht_capa;
+       const struct ieee80211_vht_cap *vht_capa;
+       u8 opmode_notif;
+       bool opmode_notif_used;
+       const struct ieee80211_he_cap_elem *he_capa;
+       u8 he_capa_len;
+       struct sta_txpwr txpwr;
+       bool txpwr_set;
+       const struct ieee80211_he_6ghz_capa *he_6ghz_capa;
+       const struct ieee80211_eht_cap_elem *eht_capa;
+       u8 eht_capa_len;
+};
+
+/**
+ * struct link_station_del_parameters - link station deletion parameters
+ *
+ * Used to delete a link station entry (or all stations).
+ *
+ * @mld_mac: MAC address of the station
+ * @link_id: the link id
+ */
+struct link_station_del_parameters {
+       const u8 *mld_mac;
+       u32 link_id;
+};
+
 /**
  * struct station_parameters - station parameters
  *
@@ -4215,6 +4270,9 @@ struct mgmt_frame_regs {
  *     radar channel.
  *     The caller is expected to set chandef pointer to NULL in order to
  *     disable background CAC/radar detection.
+ * @add_link_station: Add a link to a station.
+ * @mod_link_station: Modify a link of a station.
+ * @del_link_station: Remove a link of a station.
  */
 struct cfg80211_ops {
        int     (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -4560,6 +4618,12 @@ struct cfg80211_ops {
                                struct cfg80211_fils_aad *fils_aad);
        int     (*set_radar_background)(struct wiphy *wiphy,
                                        struct cfg80211_chan_def *chandef);
+       int     (*add_link_station)(struct wiphy *wiphy, struct net_device *dev,
+                                   struct link_station_parameters *params);
+       int     (*mod_link_station)(struct wiphy *wiphy, struct net_device *dev,
+                                   struct link_station_parameters *params);
+       int     (*del_link_station)(struct wiphy *wiphy, struct net_device *dev,
+                                   struct link_station_del_parameters *params);
 };
 
 /*
index 7bb1ae59f3a557861d8d33f384acfb2d74bafd53..37bfc934325ab1af78e8b18252fab856cf942dcf 100644 (file)
  *     without %NL80211_ATTR_MLO_LINK_ID as an easy way to remove all links
  *     in preparation for e.g. roaming to a regular (non-MLO) AP.
  *
+ * @NL80211_CMD_ADD_LINK_STA: Add a link to an MLD station
+ * @NL80211_CMD_MODIFY_LINK_STA: Modify a link of an MLD station
+ * @NL80211_CMD_REMOVE_LINK_STA: Remove a link of an MLD station
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -1501,6 +1505,10 @@ enum nl80211_commands {
        NL80211_CMD_ADD_LINK,
        NL80211_CMD_REMOVE_LINK,
 
+       NL80211_CMD_ADD_LINK_STA,
+       NL80211_CMD_MODIFY_LINK_STA,
+       NL80211_CMD_REMOVE_LINK_STA,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
index 22c4cf6fbb57e43dd2ec6a636133445f94ba22a6..3cf8e01e3f7efc0ca9f7913da93c5b4c98fd76ad 100644 (file)
@@ -6801,7 +6801,8 @@ static int nl80211_set_station_tdls(struct genl_info *info,
 }
 
 static int nl80211_parse_sta_txpower_setting(struct genl_info *info,
-                                            struct station_parameters *params)
+                                            struct sta_txpwr *txpwr,
+                                            bool *txpwr_set)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        int idx;
@@ -6813,18 +6814,20 @@ static int nl80211_parse_sta_txpower_setting(struct genl_info *info,
                        return -EOPNOTSUPP;
 
                idx = NL80211_ATTR_STA_TX_POWER_SETTING;
-               params->txpwr.type = nla_get_u8(info->attrs[idx]);
+               txpwr->type = nla_get_u8(info->attrs[idx]);
 
-               if (params->txpwr.type == NL80211_TX_POWER_LIMITED) {
+               if (txpwr->type == NL80211_TX_POWER_LIMITED) {
                        idx = NL80211_ATTR_STA_TX_POWER;
 
                        if (info->attrs[idx])
-                               params->txpwr.power =
-                                       nla_get_s16(info->attrs[idx]);
+                               txpwr->power = nla_get_s16(info->attrs[idx]);
                        else
                                return -EINVAL;
                }
-               params->sta_modify_mask |= STATION_PARAM_APPLY_STA_TXPOWER;
+
+               *txpwr_set = true;
+       } else {
+               *txpwr_set = false;
        }
 
        return 0;
@@ -6837,6 +6840,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
        struct station_parameters params;
        u8 *mac_addr;
        int err;
+       bool txpwr_set;
 
        memset(&params, 0, sizeof(params));
 
@@ -6930,9 +6934,11 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                                     NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
                return -EOPNOTSUPP;
 
-       err = nl80211_parse_sta_txpower_setting(info, &params);
+       err = nl80211_parse_sta_txpower_setting(info, &params.txpwr, &txpwr_set);
        if (err)
                return err;
+       if (txpwr_set)
+               params.sta_modify_mask |= STATION_PARAM_APPLY_STA_TXPOWER;
 
        /* Include parameters for TDLS peer (will check later) */
        err = nl80211_set_station_tdls(info, &params);
@@ -6975,6 +6981,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
        u8 *mac_addr = NULL;
        u32 auth_assoc = BIT(NL80211_STA_FLAG_AUTHENTICATED) |
                         BIT(NL80211_STA_FLAG_ASSOCIATED);
+       bool txpwr_set;
 
        memset(&params, 0, sizeof(params));
 
@@ -7085,9 +7092,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
                                     NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
                return -EOPNOTSUPP;
 
-       err = nl80211_parse_sta_txpower_setting(info, &params);
+       err = nl80211_parse_sta_txpower_setting(info, &params.txpwr, &txpwr_set);
        if (err)
                return err;
+       if (txpwr_set)
+               params.sta_modify_mask |= STATION_PARAM_APPLY_STA_TXPOWER;
 
        err = nl80211_parse_sta_channel_info(info, &params);
        if (err)
@@ -15682,6 +15691,128 @@ static int nl80211_remove_link(struct sk_buff *skb, struct genl_info *info)
        return 0;
 }
 
+static int
+nl80211_add_mod_link_station(struct sk_buff *skb, struct genl_info *info,
+                            bool add)
+{
+       struct link_station_parameters params = {};
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       int err;
+
+       if ((add && !rdev->ops->add_link_station) ||
+           (!add && !rdev->ops->mod_link_station))
+               return -EOPNOTSUPP;
+
+       if (add && !info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       if (add && !info->attrs[NL80211_ATTR_MLD_ADDR])
+               return -EINVAL;
+
+       if (add && !info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+               return -EINVAL;
+
+       if (info->attrs[NL80211_ATTR_MLD_ADDR])
+               params.mld_mac = nla_data(info->attrs[NL80211_ATTR_MLD_ADDR]);
+
+       if (info->attrs[NL80211_ATTR_MAC]) {
+               params.link_mac = nla_data(info->attrs[NL80211_ATTR_MAC]);
+               if (!is_valid_ether_addr(params.link_mac))
+                       return -EINVAL;
+       }
+
+       if (!info->attrs[NL80211_ATTR_MLO_LINK_ID])
+               return -EINVAL;
+
+       params.link_id = nla_get_u8(info->attrs[NL80211_ATTR_MLO_LINK_ID]);
+
+       if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) {
+               params.supported_rates =
+                       nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+               params.supported_rates_len =
+                       nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+       }
+
+       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]);
+
+       if (info->attrs[NL80211_ATTR_HE_CAPABILITY]) {
+               params.he_capa =
+                       nla_data(info->attrs[NL80211_ATTR_HE_CAPABILITY]);
+               params.he_capa_len =
+                       nla_len(info->attrs[NL80211_ATTR_HE_CAPABILITY]);
+
+               if (info->attrs[NL80211_ATTR_EHT_CAPABILITY]) {
+                       params.eht_capa =
+                               nla_data(info->attrs[NL80211_ATTR_EHT_CAPABILITY]);
+                       params.eht_capa_len =
+                               nla_len(info->attrs[NL80211_ATTR_EHT_CAPABILITY]);
+
+                       if (!ieee80211_eht_capa_size_ok((const u8 *)params.he_capa,
+                                                       (const u8 *)params.eht_capa,
+                                                       params.eht_capa_len))
+                               return -EINVAL;
+               }
+       }
+
+       if (info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY])
+               params.he_6ghz_capa =
+                       nla_data(info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY]);
+
+       if (info->attrs[NL80211_ATTR_OPMODE_NOTIF]) {
+               params.opmode_notif_used = true;
+               params.opmode_notif =
+                       nla_get_u8(info->attrs[NL80211_ATTR_OPMODE_NOTIF]);
+       }
+
+       err = nl80211_parse_sta_txpower_setting(info, &params.txpwr,
+                                               &params.txpwr_set);
+       if (err)
+               return err;
+
+       if (add)
+               return rdev_add_link_station(rdev, dev, &params);
+       return rdev_mod_link_station(rdev, dev, &params);
+}
+
+static int
+nl80211_add_link_station(struct sk_buff *skb, struct genl_info *info)
+{
+       return nl80211_add_mod_link_station(skb, info, true);
+}
+
+static int
+nl80211_modify_link_station(struct sk_buff *skb, struct genl_info *info)
+{
+       return nl80211_add_mod_link_station(skb, info, false);
+}
+
+static int
+nl80211_remove_link_station(struct sk_buff *skb, struct genl_info *info)
+{
+       struct link_station_del_parameters params = {};
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+
+       if (!rdev->ops->del_link_station)
+               return -EOPNOTSUPP;
+
+       if (!info->attrs[NL80211_ATTR_MLD_ADDR] ||
+           !info->attrs[NL80211_ATTR_MLO_LINK_ID])
+               return -EINVAL;
+
+       params.mld_mac = nla_data(info->attrs[NL80211_ATTR_MLD_ADDR]);
+       params.link_id = nla_get_u8(info->attrs[NL80211_ATTR_MLO_LINK_ID]);
+
+       return rdev_del_link_station(rdev, dev, &params);
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -16832,6 +16963,27 @@ static const struct genl_small_ops nl80211_small_ops[] = {
                .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
                                         NL80211_FLAG_MLO_VALID_LINK_ID),
        },
+       {
+               .cmd = NL80211_CMD_ADD_LINK_STA,
+               .doit = nl80211_add_link_station,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
+       },
+       {
+               .cmd = NL80211_CMD_MODIFY_LINK_STA,
+               .doit = nl80211_modify_link_station,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
+       },
+       {
+               .cmd = NL80211_CMD_REMOVE_LINK_STA,
+               .doit = nl80211_remove_link_station,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
+       },
 };
 
 static struct genl_family nl80211_fam __ro_after_init = {
index a329ba036989fad440ba7b68738424b67d1cf418..6221a996c19fa2673910e9ac88b74628c453abd2 100644 (file)
@@ -1448,4 +1448,52 @@ rdev_del_intf_link(struct cfg80211_registered_device *rdev,
        trace_rdev_return_void(&rdev->wiphy);
 }
 
+static inline int
+rdev_add_link_station(struct cfg80211_registered_device *rdev,
+                     struct net_device *dev,
+                     struct link_station_parameters *params)
+{
+       int ret;
+
+       if (!rdev->ops->add_link_station)
+               return -EOPNOTSUPP;
+
+       trace_rdev_add_link_station(&rdev->wiphy, dev, params);
+       ret = rdev->ops->add_link_station(&rdev->wiphy, dev, params);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
+
+static inline int
+rdev_mod_link_station(struct cfg80211_registered_device *rdev,
+                     struct net_device *dev,
+                     struct link_station_parameters *params)
+{
+       int ret;
+
+       if (!rdev->ops->mod_link_station)
+               return -EOPNOTSUPP;
+
+       trace_rdev_mod_link_station(&rdev->wiphy, dev, params);
+       ret = rdev->ops->mod_link_station(&rdev->wiphy, dev, params);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
+
+static inline int
+rdev_del_link_station(struct cfg80211_registered_device *rdev,
+                     struct net_device *dev,
+                     struct link_station_del_parameters *params)
+{
+       int ret;
+
+       if (!rdev->ops->del_link_station)
+               return -EOPNOTSUPP;
+
+       trace_rdev_del_link_station(&rdev->wiphy, dev, params);
+       ret = rdev->ops->del_link_station(&rdev->wiphy, dev, params);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
+
 #endif /* __CFG80211_RDEV_OPS */
index 65f8b814ecd0c65916921a3f53fb1be9eea843a0..16d0fe53fcf26499921ba39339d08f7e381b24fb 100644 (file)
@@ -3775,6 +3775,103 @@ TRACE_EVENT(cfg80211_assoc_comeback,
                  WDEV_PR_ARG, MAC_PR_ARG(bssid), __entry->timeout)
 );
 
+DECLARE_EVENT_CLASS(link_station_add_mod,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct link_station_parameters *params),
+       TP_ARGS(wiphy, netdev, params),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               NETDEV_ENTRY
+               __array(u8, mld_mac, 6)
+               __array(u8, link_mac, 6)
+               __field(u32, link_id)
+               __dynamic_array(u8, supported_rates,
+                               params->supported_rates_len)
+               __array(u8, ht_capa, (int)sizeof(struct ieee80211_ht_cap))
+               __array(u8, vht_capa, (int)sizeof(struct ieee80211_vht_cap))
+               __field(u8, opmode_notif)
+               __field(bool, opmode_notif_used)
+               __dynamic_array(u8, he_capa, params->he_capa_len)
+               __array(u8, he_6ghz_capa, (int)sizeof(struct ieee80211_he_6ghz_capa))
+               __dynamic_array(u8, eht_capa, params->eht_capa_len)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               NETDEV_ASSIGN;
+               memset(__entry->mld_mac, 0, 6);
+               memset(__entry->link_mac, 0, 6);
+               if (params->mld_mac)
+                       memcpy(__entry->mld_mac, params->mld_mac, 6);
+               if (params->link_mac)
+                       memcpy(__entry->link_mac, params->link_mac, 6);
+               __entry->link_id = params->link_id;
+               if (params->supported_rates && params->supported_rates_len)
+                       memcpy(__get_dynamic_array(supported_rates),
+                              params->supported_rates,
+                              params->supported_rates_len);
+               memset(__entry->ht_capa, 0, sizeof(struct ieee80211_ht_cap));
+               if (params->ht_capa)
+                       memcpy(__entry->ht_capa, params->ht_capa,
+                              sizeof(struct ieee80211_ht_cap));
+               memset(__entry->vht_capa, 0, sizeof(struct ieee80211_vht_cap));
+               if (params->vht_capa)
+                       memcpy(__entry->vht_capa, params->vht_capa,
+                              sizeof(struct ieee80211_vht_cap));
+               __entry->opmode_notif = params->opmode_notif;
+               __entry->opmode_notif_used = params->opmode_notif_used;
+               if (params->he_capa && params->he_capa_len)
+                       memcpy(__get_dynamic_array(he_capa), params->he_capa,
+                              params->he_capa_len);
+               memset(__entry->he_6ghz_capa, 0, sizeof(struct ieee80211_he_6ghz_capa));
+               if (params->he_6ghz_capa)
+                       memcpy(__entry->he_6ghz_capa, params->he_6ghz_capa,
+                              sizeof(struct ieee80211_he_6ghz_capa));
+               if (params->eht_capa && params->eht_capa_len)
+                       memcpy(__get_dynamic_array(eht_capa), params->eht_capa,
+                              params->eht_capa_len);
+       ),
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", station mac: " MAC_PR_FMT
+                 ", link mac: " MAC_PR_FMT ", link id: %u",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(mld_mac),
+                 MAC_PR_ARG(link_mac), __entry->link_id)
+);
+
+DEFINE_EVENT(link_station_add_mod, rdev_add_link_station,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct link_station_parameters *params),
+       TP_ARGS(wiphy, netdev, params)
+);
+
+DEFINE_EVENT(link_station_add_mod, rdev_mod_link_station,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct link_station_parameters *params),
+       TP_ARGS(wiphy, netdev, params)
+);
+
+TRACE_EVENT(rdev_del_link_station,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct link_station_del_parameters *params),
+       TP_ARGS(wiphy, netdev, params),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               NETDEV_ENTRY
+               __array(u8, mld_mac, 6)
+               __field(u32, link_id)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               NETDEV_ASSIGN;
+               memset(__entry->mld_mac, 0, 6);
+               if (params->mld_mac)
+                       memcpy(__entry->mld_mac, params->mld_mac, 6);
+               __entry->link_id = params->link_id;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", station mac: " MAC_PR_FMT
+                 ", link id: %u",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(mld_mac),
+                 __entry->link_id)
+);
+
 #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH