wifi: mac80211: optionally implement MLO multicast TX
authorJohannes Berg <johannes.berg@intel.com>
Thu, 21 Jul 2022 08:56:48 +0000 (10:56 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 22 Jul 2022 12:28:36 +0000 (14:28 +0200)
For drivers using software encryption for multicast TX, such
as mac80211_hwsim, mac80211 needs to duplicate the multicast
frames on each link, if MLO is enabled. Do this, but don't
just make it dependent on the key but provide a separate flag
for drivers to opt out of this.

This is not very efficient, I expect that drivers will do it
in firmware/hardware or at least with DMA engine assistence,
so this is mostly for hwsim.

To make this work, also implement the SNS11 sequence number
space that an AP MLD shall have, and modify the API to the
__ieee80211_subif_start_xmit() function to always require the
link ID bits to be set.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/debugfs.c
net/mac80211/ieee80211_i.h
net/mac80211/tdls.c
net/mac80211/tx.c

index 1afd452..1e04961 100644 (file)
@@ -882,6 +882,8 @@ enum mac80211_tx_info_flags {
  * @IEEE80211_TX_CTRL_DONT_REORDER: This frame should not be reordered
  *     relative to other frames that have this flag set, independent
  *     of their QoS TID or other priority field values.
+ * @IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX: first MLO TX, used mostly internally
+ *     for sequence number assignment
  * @IEEE80211_TX_CTRL_MLO_LINK: If not @IEEE80211_LINK_UNSPECIFIED, this
  *     frame should be transmitted on the specific link. This really is
  *     only relevant for frames that do not have data present, and is
@@ -901,10 +903,14 @@ enum mac80211_tx_control_flags {
        IEEE80211_TX_INTCFL_NEED_TXPROCESSING   = BIT(6),
        IEEE80211_TX_CTRL_NO_SEQNO              = BIT(7),
        IEEE80211_TX_CTRL_DONT_REORDER          = BIT(8),
+       IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX    = BIT(9),
        IEEE80211_TX_CTRL_MLO_LINK              = 0xf0000000,
 };
 
 #define IEEE80211_LINK_UNSPECIFIED     0xf
+#define IEEE80211_TX_CTRL_MLO_LINK_UNSPEC      \
+       u32_encode_bits(IEEE80211_LINK_UNSPECIFIED, \
+                       IEEE80211_TX_CTRL_MLO_LINK)
 
 /**
  * enum mac80211_tx_status_flags - flags to describe transmit status
@@ -2524,6 +2530,9 @@ struct ieee80211_txq {
  * @IEEE80211_HW_DETECTS_COLOR_COLLISION: HW/driver has support for BSS color
  *     collision detection and doesn't need it in software.
  *
+ * @IEEE80211_HW_MLO_MCAST_MULTI_LINK_TX: Hardware/driver handles transmitting
+ *     multicast frames on all links, mac80211 should not do that.
+ *
  * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
  */
 enum ieee80211_hw_flags {
@@ -2580,6 +2589,7 @@ enum ieee80211_hw_flags {
        IEEE80211_HW_SUPPORTS_RX_DECAP_OFFLOAD,
        IEEE80211_HW_SUPPORTS_CONC_MON_RX_DECAP,
        IEEE80211_HW_DETECTS_COLOR_COLLISION,
+       IEEE80211_HW_MLO_MCAST_MULTI_LINK_TX,
 
        /* keep last, obviously */
        NUM_IEEE80211_HW_FLAGS
index 4d43412..78c7d60 100644 (file)
@@ -4,7 +4,7 @@
  *
  * Copyright 2007      Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
- * Copyright (C) 2018 - 2019, 2021 Intel Corporation
+ * Copyright (C) 2018 - 2019, 2021-2022 Intel Corporation
  */
 
 #include <linux/debugfs.h>
@@ -495,6 +495,7 @@ static const char *hw_flag_names[] = {
        FLAG(SUPPORTS_RX_DECAP_OFFLOAD),
        FLAG(SUPPORTS_CONC_MON_RX_DECAP),
        FLAG(DETECTS_COLOR_COLLISION),
+       FLAG(MLO_MCAST_MULTI_LINK_TX),
 #undef FLAG
 };
 
index 4ec6fb9..f93b577 100644 (file)
@@ -1029,6 +1029,7 @@ struct ieee80211_sub_if_data {
        struct ieee80211_key __rcu *default_unicast_key;
 
        u16 sequence_number;
+       u16 mld_mcast_seq;
        __be16 control_port_protocol;
        bool control_port_no_encrypt;
        bool control_port_no_preauth;
index 36bfc54..f4b4d25 100644 (file)
@@ -1054,7 +1054,8 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
 
        /* disable bottom halves when entering the Tx path */
        local_bh_disable();
-       __ieee80211_subif_start_xmit(skb, dev, flags, 0, NULL);
+       __ieee80211_subif_start_xmit(skb, dev, flags,
+                                    IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
        local_bh_enable();
 
        return ret;
index f245650..45df993 100644 (file)
@@ -822,6 +822,16 @@ ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx)
        if (info->control.flags & IEEE80211_TX_CTRL_NO_SEQNO)
                return TX_CONTINUE;
 
+       /* SNS11 from 802.11be 10.3.2.14 */
+       if (unlikely(is_multicast_ether_addr(hdr->addr1) &&
+                    info->control.vif->valid_links &&
+                    info->control.vif->type == NL80211_IFTYPE_AP)) {
+               if (info->control.flags & IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX)
+                       tx->sdata->mld_mcast_seq += 0x10;
+               hdr->seq_ctrl = cpu_to_le16(tx->sdata->mld_mcast_seq);
+               return TX_CONTINUE;
+       }
+
        /*
         * Anything but QoS data that has a sequence number field
         * (is long enough) gets a sequence number from the global
@@ -2570,7 +2580,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_chanctx_conf *chanctx_conf = NULL;
        enum nl80211_band band;
        int ret;
-       u8 link_id = IEEE80211_LINK_UNSPECIFIED;
+       u8 link_id = u32_get_bits(ctrl_flags, IEEE80211_TX_CTRL_MLO_LINK);
 
        if (IS_ERR(sta))
                sta = NULL;
@@ -2630,8 +2640,18 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
                                goto free;
                        }
                        memcpy(hdr.addr2, link->conf->addr, ETH_ALEN);
-               } else {
+               } else if (link_id == IEEE80211_LINK_UNSPECIFIED) {
                        memcpy(hdr.addr2, sdata->vif.addr, ETH_ALEN);
+               } else {
+                       struct ieee80211_bss_conf *conf;
+
+                       conf = rcu_dereference(sdata->vif.link_conf[link_id]);
+                       if (unlikely(!conf)) {
+                               ret = -ENOLINK;
+                               goto free;
+                       }
+
+                       memcpy(hdr.addr2, conf->addr, ETH_ALEN);
                }
 
                memcpy(hdr.addr3, skb->data + ETH_ALEN, ETH_ALEN);
@@ -4222,9 +4242,6 @@ static bool ieee80211_multicast_to_unicast(struct sk_buff *skb,
        const struct vlan_ethhdr *ethvlan = (void *)skb->data;
        __be16 ethertype;
 
-       if (likely(!is_multicast_ether_addr(eth->h_dest)))
-               return false;
-
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_AP_VLAN:
                if (sdata->u.vlan.sta)
@@ -4308,6 +4325,44 @@ out:
        rcu_read_unlock();
 }
 
+static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata,
+                                          struct sk_buff *skb, u32 ctrl_flags,
+                                          unsigned int link_id)
+{
+       struct sk_buff *out;
+
+       out = skb_copy(skb, GFP_ATOMIC);
+       if (!out)
+               return;
+
+       ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK);
+       __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, NULL);
+}
+
+static void ieee80211_mlo_multicast_tx(struct net_device *dev,
+                                      struct sk_buff *skb)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       unsigned long links = sdata->vif.valid_links;
+       unsigned int link;
+       u32 ctrl_flags = IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX;
+
+       if (hweight16(links) == 1) {
+               ctrl_flags |= u32_encode_bits(ffs(links) - 1,
+                                             IEEE80211_TX_CTRL_MLO_LINK);
+
+               __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags,
+                                            NULL);
+               return;
+       }
+
+       for_each_set_bit(link, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
+               ieee80211_mlo_multicast_tx_one(sdata, skb, ctrl_flags, link);
+               ctrl_flags = 0;
+       }
+       kfree_skb(skb);
+}
+
 /**
  * ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs
  * @skb: packet to be sent
@@ -4318,15 +4373,30 @@ out:
 netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
                                       struct net_device *dev)
 {
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       const struct ethhdr *eth = (void *)skb->data;
+
+       if (likely(!is_multicast_ether_addr(eth->h_dest)))
+               goto normal;
+
        if (unlikely(ieee80211_multicast_to_unicast(skb, dev))) {
                struct sk_buff_head queue;
 
                __skb_queue_head_init(&queue);
                ieee80211_convert_to_unicast(skb, dev, &queue);
                while ((skb = __skb_dequeue(&queue)))
-                       __ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
+                       __ieee80211_subif_start_xmit(skb, dev, 0,
+                                                    IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
+                                                    NULL);
+       } else if (sdata->vif.valid_links &&
+                  sdata->vif.type == NL80211_IFTYPE_AP &&
+                  !ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) {
+               ieee80211_mlo_multicast_tx(dev, skb);
        } else {
-               __ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
+normal:
+               __ieee80211_subif_start_xmit(skb, dev, 0,
+                                            IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
+                                            NULL);
        }
 
        return NETDEV_TX_OK;
@@ -4410,7 +4480,9 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
        if (tid_tx) {
                if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) {
                        /* fall back to non-offload slow path */
-                       __ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
+                       __ieee80211_subif_start_xmit(skb, dev, 0,
+                                                    IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
+                                                    NULL);
                        return;
                }
 
@@ -4512,7 +4584,8 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
                goto out;
        }
 
-       skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, 0, NULL);
+       skb = ieee80211_build_hdr(sdata, skb, info_flags, sta,
+                                 IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
        if (IS_ERR(skb))
                goto out;