mac80211: rework mesh fast xmit implementation
authorFelix Fietkau <nbd@nbd.name>
Sun, 26 Feb 2023 12:36:58 +0000 (13:36 +0100)
committerFelix Fietkau <nbd@nbd.name>
Sun, 26 Feb 2023 22:45:07 +0000 (23:45 +0100)
Refactor in order to make use of generic fast xmit functions
Fix issues with mesh SA/DA addressing

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/kernel/mac80211/patches/subsys/319-wifi-mac80211-mesh-fast-xmit-support.patch
package/kernel/mac80211/patches/subsys/320-wifi-mac80211-use-mesh-header-cache-to-speed-up-mesh.patch
package/kernel/mac80211/patches/subsys/321-mac80211-fix-mesh-forwarding.patch
package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch

index f18e1bb377bf141ca045a0ed613c2b6e6a719bb3..b802960103a3405ded222d9db040e0c267d0e5b1 100644 (file)
@@ -1,33 +1,32 @@
-From: Sriram R <quic_srirrama@quicinc.com>
-Date: Thu, 18 Aug 2022 12:35:42 +0530
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 26 Feb 2023 13:53:08 +0100
 Subject: [PATCH] wifi: mac80211: mesh fast xmit support
 
-Currently fast xmit is supported in AP, STA and other device types where
-the destination doesn't change for the lifetime of its association by
-caching the static parts of the header that can be reused directly for
-every Tx such as addresses and updates only mutable header fields such as
-PN.
-This technique is not directly applicable for a Mesh device type due
-to the dynamic nature of the topology and protocol. The header is built
-based on the destination mesh device which is proxying a certain external
-device and based on the Mesh destination the next hop changes.
-And the RA/A1 which is the next hop for reaching the destination can
-vary during runtime as per the best route based on airtime.  To accommodate
-these changes and to come up with a solution to avoid overhead during header
-generation, the headers comprising the MAC, Mesh and LLC part are cached
-whenever data for a certain external destination is sent.
-This cached header is reused every time a data is sent to that external
-destination.
+Previously, fast xmit only worked on interface types where initially a
+sta lookup is performed, and a cached header can be attached to the sta,
+requiring only some fields to be updated at runtime.
+
+This technique is not directly applicable for a mesh device type due
+to the dynamic nature of the topology and protocol. There are more
+addresses that need to be filled, and there is an extra header with a
+dynamic length based on the addressing mode.
+
+Change the code to cache entries contain a copy of the mesh subframe header +
+bridge tunnel header, as well as an embedded struct ieee80211_fast_tx, which
+contains the information for building the 802.11 header.
+
+Add a mesh specific early fast xmit call, which looks up a cached entry and
+adds only the mesh subframe header, before passing it over to the generic
+fast xmit code.
 
 To ensure the changes in network are reflected in these cached headers,
 flush affected cached entries on path changes, as well as other conditions
 that currently trigger a fast xmit check in other modes (key changes etc.)
 
-In order to keep the cache small, use a short timeout for expiring cache
-entries.
+This code is loosely based on a previous implementation by:
+Sriram R <quic_srirrama@quicinc.com>
 
-Co-developed-by: Felix Fietkau <nbd@nbd.name>
-Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
+Signed-off-by: Ryder Lee <ryder.lee@mediatek.com>
 Signed-off-by: Felix Fietkau <nbd@nbd.name>
 ---
 
@@ -37,24 +36,23 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
  extern const struct cfg80211_ops mac80211_config_ops;
  
  struct ieee80211_local;
-+struct mhdr_cache_entry;
++struct ieee80211_mesh_fast_tx;
  
  /* Maximum number of broadcast/multicast frames to buffer when some of the
   * associated stations are using power saving. */
-@@ -655,6 +656,20 @@ struct mesh_table {
+@@ -655,6 +656,19 @@ struct mesh_table {
        atomic_t entries;               /* Up to MAX_MESH_NEIGHBOURS */
  };
  
 +/**
-+ * struct mesh_hdr_cache - mesh fast xmit header cache
++ * struct mesh_tx_cache - mesh fast xmit header cache
 + *
-+ * @rhead: hash table containing struct mhdr_cache_entry, using skb DA as key
-+ * @walk_head: linked list containing all mhdr_cache_entry objects
-+ * @walk_lock: lock protecting walk_head and rhead
-+ * @enabled: indicates if header cache is initialized
++ * @rht: hash table containing struct ieee80211_mesh_fast_tx, using skb DA as key
++ * @walk_head: linked list containing all ieee80211_mesh_fast_tx objects
++ * @walk_lock: lock protecting walk_head and rht
 + */
-+struct mesh_hdr_cache {
-+      struct rhashtable rhead;
++struct mesh_tx_cache {
++      struct rhashtable rht;
 +      struct hlist_head walk_head;
 +      spinlock_t walk_lock;
 +};
@@ -62,102 +60,209 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
  struct ieee80211_if_mesh {
        struct timer_list housekeeping_timer;
        struct timer_list mesh_path_timer;
-@@ -733,6 +748,7 @@ struct ieee80211_if_mesh {
+@@ -733,6 +747,7 @@ struct ieee80211_if_mesh {
        struct mesh_table mpp_paths; /* Store paths for MPP&MAP */
        int mesh_paths_generation;
        int mpp_paths_generation;
-+      struct mesh_hdr_cache hdr_cache;
++      struct mesh_tx_cache tx_cache;
  };
  
  #ifdef CPTCFG_MAC80211_MESH
-@@ -1998,6 +2014,9 @@ int ieee80211_tx_control_port(struct wip
+@@ -1998,6 +2013,11 @@ int ieee80211_tx_control_port(struct wip
                              int link_id, u64 *cookie);
  int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev,
                              const u8 *buf, size_t len);
-+void __ieee80211_mesh_xmit_fast(struct ieee80211_sub_if_data *sdata,
-+                              struct mhdr_cache_entry *entry,
-+                              struct sk_buff *skb);
++void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
++                         struct sta_info *sta,
++                         struct ieee80211_fast_tx *fast_tx,
++                         struct sk_buff *skb, bool ampdu,
++                         const u8 *da, const u8 *sa);
  
  /* HT */
  void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
 --- a/net/mac80211/mesh.c
 +++ b/net/mac80211/mesh.c
-@@ -780,6 +780,8 @@ static void ieee80211_mesh_housekeeping(
+@@ -10,6 +10,7 @@
+ #include <asm/unaligned.h>
+ #include "ieee80211_i.h"
+ #include "mesh.h"
++#include "wme.h"
+ #include "driver-ops.h"
+ static int mesh_allocated;
+@@ -698,6 +699,102 @@ ieee80211_mesh_update_bss_params(struct
+                       __le32_to_cpu(he_oper->he_oper_params);
+ }
++bool ieee80211_mesh_xmit_fast(struct ieee80211_sub_if_data *sdata,
++                            struct sk_buff *skb, u32 ctrl_flags)
++{
++      struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
++      struct ieee80211_mesh_fast_tx *entry;
++      struct ieee80211s_hdr *meshhdr;
++      u8 sa[ETH_ALEN] __aligned(2);
++      struct tid_ampdu_tx *tid_tx;
++      struct sta_info *sta;
++      bool copy_sa = false;
++      u16 ethertype;
++      u8 tid;
++
++      if (ctrl_flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP)
++              return false;
++
++      if (ifmsh->mshcfg.dot11MeshNolearn)
++              return false;
++
++      /* Add support for these cases later */
++      if (ifmsh->ps_peers_light_sleep || ifmsh->ps_peers_deep_sleep)
++              return false;
++
++      if (is_multicast_ether_addr(skb->data))
++              return false;
++
++      ethertype = (skb->data[12] << 8) | skb->data[13];
++      if (ethertype < ETH_P_802_3_MIN)
++              return false;
++
++      if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)
++              return false;
++
++      if (skb->ip_summed == CHECKSUM_PARTIAL) {
++              skb_set_transport_header(skb, skb_checksum_start_offset(skb));
++              if (skb_checksum_help(skb))
++                      return false;
++      }
++
++      entry = mesh_fast_tx_get(sdata, skb->data);
++      if (!entry)
++              return false;
++
++      if (skb_headroom(skb) + 2 * ETH_ALEN < entry->hdrlen +
++                                             entry->fast_tx.hdr_len)
++              return false;
++
++      sta = rcu_dereference(entry->mpath->next_hop);
++      if (!sta)
++              return false;
++
++      tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
++      tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
++      if (tid_tx) {
++              if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state))
++                      return false;
++              if (tid_tx->timeout)
++                      tid_tx->last_tx = jiffies;
++      }
++
++      /* If the skb is shared we need to obtain our own copy */
++      if (skb_shared(skb)) {
++              struct sk_buff *oskb = skb;
++
++              skb = skb_clone(skb, GFP_ATOMIC);
++              if (!skb)
++                      return false;
++
++              kfree_skb(oskb);
++      }
++
++      skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb));
++
++      meshhdr = (struct ieee80211s_hdr *)entry->hdr;
++      if ((meshhdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6) {
++              /* preserve SA from eth header for 6-addr frames */
++              ether_addr_copy(sa, skb->data + ETH_ALEN);
++              copy_sa = true;
++      }
++
++      memcpy(skb_push(skb, entry->hdrlen - 2 * ETH_ALEN), entry->hdr,
++             entry->hdrlen);
++
++      meshhdr = (struct ieee80211s_hdr *)skb->data;
++      put_unaligned_le32(atomic_inc_return(&sdata->u.mesh.mesh_seqnum),
++                         &meshhdr->seqnum);
++      meshhdr->ttl = sdata->u.mesh.mshcfg.dot11MeshTTL;
++      if (copy_sa)
++          ether_addr_copy(meshhdr->eaddr2, sa);
++
++      __ieee80211_xmit_fast(sdata, sta, &entry->fast_tx, skb, tid_tx,
++                            entry->mpath->dst, sdata->vif.addr);
++
++      return true;
++}
++
+ /**
+  * ieee80211_fill_mesh_addresses - fill addresses of a locally originated mesh frame
+  * @hdr:      802.11 frame header
+@@ -780,6 +877,8 @@ static void ieee80211_mesh_housekeeping(
        changed = mesh_accept_plinks_update(sdata);
        ieee80211_mbss_info_change_notify(sdata, changed);
  
-+      mesh_hdr_cache_gc(sdata);
++      mesh_fast_tx_gc(sdata);
 +
        mod_timer(&ifmsh->housekeeping_timer,
                  round_jiffies(jiffies +
                                IEEE80211_MESH_HOUSEKEEPING_INTERVAL));
 --- a/net/mac80211/mesh.h
 +++ b/net/mac80211/mesh.h
-@@ -122,11 +122,49 @@ struct mesh_path {
+@@ -122,11 +122,41 @@ struct mesh_path {
        u8 rann_snd_addr[ETH_ALEN];
        u32 rann_metric;
        unsigned long last_preq_to_root;
-+      unsigned long fast_xmit_check;
++      unsigned long fast_tx_check;
        bool is_root;
        bool is_gate;
        u32 path_change_count;
  };
  
-+#define MESH_HEADER_CACHE_MAX_SIZE            512
-+#define MESH_HEADER_CACHE_THRESHOLD_SIZE      384
-+#define MESH_HEADER_CACHE_TIMEOUT             8000 /* msecs */
-+#define MESH_HEADER_MAX_LEN                   68   /* mac+mesh+rfc1042 hdr */
++#define MESH_FAST_TX_CACHE_MAX_SIZE           512
++#define MESH_FAST_TX_CACHE_THRESHOLD_SIZE     384
++#define MESH_FAST_TX_CACHE_TIMEOUT            8000 /* msecs */
 +
 +/**
-+ * struct mhdr_cache_entry - Cached Mesh header entry
-+ * @addr_key: The Ethernet DA which is the key for this entry
-+ * @hdr: The cached header
-+ * @machdr_len: Total length of the mac header
-+ * @hdrlen: Length of this header entry
-+ * @key: Key corresponding to the nexthop stored in the header
-+ * @pn_offs: Offset to PN which is updated for every xmit
-+ * @band:  band used for tx
-+ * @walk_list: list containing all the cached header entries
++ * struct ieee80211_mesh_fast_tx - cached mesh fast tx entry
 + * @rhash: rhashtable pointer
-+ * @mpath: The Mesh path corresponding to the Mesh DA
-+ * @mppath: The MPP entry corresponding to this DA
++ * @addr_key: The Ethernet DA which is the key for this entry
++ * @fast_tx: base fast_tx data
++ * @hdr: cached mesh and rfc1042 headers
++ * @hdrlen: length of mesh + rfc1042
++ * @walk_list: list containing all the fast tx entries
++ * @mpath: mesh path corresponding to the Mesh DA
++ * @mppath: MPP entry corresponding to this DA
 + * @timestamp: Last used time of this entry
-+ * @rcu: rcu to free this entry
-+ * @path_change_count: Stored path change value corresponding to the mpath
 + */
-+struct mhdr_cache_entry {
++struct ieee80211_mesh_fast_tx {
++      struct rhash_head rhash;
 +      u8 addr_key[ETH_ALEN] __aligned(2);
-+      u8 hdr[MESH_HEADER_MAX_LEN];
-+      u16 machdr_len;
++
++      struct ieee80211_fast_tx fast_tx;
++      u8 hdr[sizeof(struct ieee80211s_hdr) + sizeof(rfc1042_header)];
 +      u16 hdrlen;
-+      u8 pn_offs;
-+      u8 band;
-+      struct ieee80211_key *key;
-+      struct hlist_node walk_list;
-+      struct rhash_head rhash;
++
 +      struct mesh_path *mpath, *mppath;
++      struct hlist_node walk_list;
 +      unsigned long timestamp;
-+      struct rcu_head rcu;
 +};
 +
  /* Recent multicast cache */
  /* RMC_BUCKETS must be a power of 2, maximum 256 */
  #define RMC_BUCKETS           256
-@@ -298,6 +336,18 @@ void mesh_path_discard_frame(struct ieee
+@@ -298,6 +328,20 @@ void mesh_path_discard_frame(struct ieee
  void mesh_path_tx_root_frame(struct ieee80211_sub_if_data *sdata);
  
  bool mesh_action_is_path_sel(struct ieee80211_mgmt *mgmt);
-+struct mhdr_cache_entry *
-+mesh_get_cached_hdr(struct ieee80211_sub_if_data *sdata, const u8 *addr);
-+void mesh_cache_hdr(struct ieee80211_sub_if_data *sdata,
-+                  struct sk_buff *skb, struct mesh_path *mpath);
-+void mesh_hdr_cache_gc(struct ieee80211_sub_if_data *sdata);
-+void mesh_hdr_cache_flush_mpp(struct ieee80211_sub_if_data *sdata,
-+                            const u8 *addr);
-+void mesh_hdr_cache_flush_mpath(struct mesh_path *mpath);
-+void mesh_hdr_cache_flush_sta(struct ieee80211_sub_if_data *sdata,
-+                            struct sta_info *sta);
-+void mesh_refresh_path(struct ieee80211_sub_if_data *sdata,
++struct ieee80211_mesh_fast_tx *
++mesh_fast_tx_get(struct ieee80211_sub_if_data *sdata, const u8 *addr);
++bool ieee80211_mesh_xmit_fast(struct ieee80211_sub_if_data *sdata,
++                            struct sk_buff *skb, u32 ctrl_flags);
++void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata,
++                      struct sk_buff *skb, struct mesh_path *mpath);
++void mesh_fast_tx_gc(struct ieee80211_sub_if_data *sdata);
++void mesh_fast_tx_flush_addr(struct ieee80211_sub_if_data *sdata,
++                           const u8 *addr);
++void mesh_fast_tx_flush_mpath(struct mesh_path *mpath);
++void mesh_fast_tx_flush_sta(struct ieee80211_sub_if_data *sdata,
++                          struct sta_info *sta);
++void mesh_path_refresh(struct ieee80211_sub_if_data *sdata,
 +                     struct mesh_path *mpath, const u8 *addr);
  
  #ifdef CPTCFG_MAC80211_MESH
@@ -189,7 +294,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
                        mesh_path_activate(mpath);
                        spin_unlock_bh(&mpath->state_lock);
 +                      if (flush_mpath)
-+                              mesh_hdr_cache_flush_mpath(mpath);
++                              mesh_fast_tx_flush_mpath(mpath);
                        ewma_mesh_fail_avg_init(&sta->mesh->fail_avg);
                        /* init it at a low value - 0 start is tricky */
                        ewma_mesh_fail_avg_add(&sta->mesh->fail_avg, 1);
@@ -210,24 +315,15 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
                        mesh_path_activate(mpath);
                        spin_unlock_bh(&mpath->state_lock);
 +                      if (flush_mpath)
-+                              mesh_hdr_cache_flush_mpath(mpath);
++                              mesh_fast_tx_flush_mpath(mpath);
                        ewma_mesh_fail_avg_init(&sta->mesh->fail_avg);
                        /* init it at a low value - 0 start is tricky */
                        ewma_mesh_fail_avg_add(&sta->mesh->fail_avg, 1);
-@@ -977,7 +986,7 @@ free:
-  * Locking: the function must be called from within a rcu read lock block.
-  *
-  */
--static void mesh_queue_preq(struct mesh_path *mpath, u8 flags)
-+void mesh_queue_preq(struct mesh_path *mpath, u8 flags)
- {
-       struct ieee80211_sub_if_data *sdata = mpath->sdata;
-       struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
 @@ -1215,6 +1224,20 @@ static int mesh_nexthop_lookup_nolearn(s
        return 0;
  }
  
-+void mesh_refresh_path(struct ieee80211_sub_if_data *sdata,
++void mesh_path_refresh(struct ieee80211_sub_if_data *sdata,
 +                     struct mesh_path *mpath, const u8 *addr)
 +{
 +      if (mpath->flags & (MESH_PATH_REQ_QUEUED | MESH_PATH_FIXED |
@@ -244,7 +340,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
  /**
   * mesh_nexthop_lookup - put the appropriate next hop on a mesh frame. Calling
   * this function is considered "using" the associated mpath, so preempt a path
-@@ -1242,19 +1265,18 @@ int mesh_nexthop_lookup(struct ieee80211
+@@ -1242,19 +1265,15 @@ int mesh_nexthop_lookup(struct ieee80211
        if (!mpath || !(mpath->flags & MESH_PATH_ACTIVE))
                return -ENOENT;
  
@@ -255,18 +351,15 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 -          !(mpath->flags & MESH_PATH_RESOLVING) &&
 -          !(mpath->flags & MESH_PATH_FIXED))
 -              mesh_queue_preq(mpath, PREQ_Q_F_START | PREQ_Q_F_REFRESH);
-+      mesh_refresh_path(sdata, mpath, hdr->addr4);
++      mesh_path_refresh(sdata, mpath, hdr->addr4);
  
        next_hop = rcu_dereference(mpath->next_hop);
        if (next_hop) {
                memcpy(hdr->addr1, next_hop->sta.addr, ETH_ALEN);
                memcpy(hdr->addr2, sdata->vif.addr, ETH_ALEN);
                ieee80211_mps_set_frame_flags(sdata, next_hop, hdr);
-+              /* Cache the whole header so as to use next time rather than resolving
-+               * and building it every time
-+               */
 +              if (ieee80211_hw_check(&sdata->local->hw, SUPPORT_FAST_XMIT))
-+                      mesh_cache_hdr(sdata, skb, mpath);
++                      mesh_fast_tx_cache(sdata, skb, mpath);
                return 0;
        }
  
@@ -284,37 +377,37 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        .hashfn = mesh_table_hash,
  };
  
-+static const struct rhashtable_params mesh_hdr_rht_params = {
++static const struct rhashtable_params fast_tx_rht_params = {
 +      .nelem_hint = 10,
 +      .automatic_shrinking = true,
-+      .key_len =  ETH_ALEN,
-+      .key_offset = offsetof(struct mhdr_cache_entry, addr_key),
-+      .head_offset = offsetof(struct mhdr_cache_entry, rhash),
++      .key_len = ETH_ALEN,
++      .key_offset = offsetof(struct ieee80211_mesh_fast_tx, addr_key),
++      .head_offset = offsetof(struct ieee80211_mesh_fast_tx, rhash),
 +      .hashfn = mesh_table_hash,
 +};
 +
-+static void __mesh_hdr_cache_entry_free(void *ptr, void *tblptr)
++static void __mesh_fast_tx_entry_free(void *ptr, void *tblptr)
 +{
-+      struct mhdr_cache_entry *mhdr = ptr;
++      struct ieee80211_mesh_fast_tx *entry = ptr;
 +
-+      kfree_rcu(mhdr, rcu);
++      kfree_rcu(entry, fast_tx.rcu_head);
 +}
 +
-+static void mesh_hdr_cache_deinit(struct ieee80211_sub_if_data *sdata)
++static void mesh_fast_tx_deinit(struct ieee80211_sub_if_data *sdata)
 +{
-+      struct mesh_hdr_cache *cache;
++      struct mesh_tx_cache *cache;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
-+      rhashtable_free_and_destroy(&cache->rhead,
-+                                  __mesh_hdr_cache_entry_free, NULL);
++      cache = &sdata->u.mesh.tx_cache;
++      rhashtable_free_and_destroy(&cache->rht,
++                                  __mesh_fast_tx_entry_free, NULL);
 +}
 +
-+static void mesh_hdr_cache_init(struct ieee80211_sub_if_data *sdata)
++static void mesh_fast_tx_init(struct ieee80211_sub_if_data *sdata)
 +{
-+      struct mesh_hdr_cache *cache;
++      struct mesh_tx_cache *cache;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
-+      rhashtable_init(&cache->rhead, &mesh_hdr_rht_params);
++      cache = &sdata->u.mesh.tx_cache;
++      rhashtable_init(&cache->rht, &fast_tx_rht_params);
 +      INIT_HLIST_HEAD(&cache->walk_head);
 +      spin_lock_init(&cache->walk_lock);
 +}
@@ -322,40 +415,40 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
  static inline bool mpath_expired(struct mesh_path *mpath)
  {
        return (mpath->flags & MESH_PATH_ACTIVE) &&
-@@ -381,6 +417,254 @@ struct mesh_path *mesh_path_new(struct i
+@@ -381,6 +417,243 @@ struct mesh_path *mesh_path_new(struct i
        return new_mpath;
  }
  
-+static void mesh_hdr_cache_entry_free(struct mesh_hdr_cache *cache,
-+                                    struct mhdr_cache_entry *entry)
++static void mesh_fast_tx_entry_free(struct mesh_tx_cache *cache,
++                                  struct ieee80211_mesh_fast_tx *entry)
 +{
 +      hlist_del_rcu(&entry->walk_list);
-+      rhashtable_remove_fast(&cache->rhead, &entry->rhash, mesh_hdr_rht_params);
-+      kfree_rcu(entry, rcu);
++      rhashtable_remove_fast(&cache->rht, &entry->rhash, fast_tx_rht_params);
++      kfree_rcu(entry, fast_tx.rcu_head);
 +}
 +
-+struct mhdr_cache_entry *
-+mesh_get_cached_hdr(struct ieee80211_sub_if_data *sdata, const u8 *addr)
++struct ieee80211_mesh_fast_tx *
++mesh_fast_tx_get(struct ieee80211_sub_if_data *sdata, const u8 *addr)
 +{
-+      struct mhdr_cache_entry *entry;
-+      struct mesh_hdr_cache *cache;
++      struct ieee80211_mesh_fast_tx *entry;
++      struct mesh_tx_cache *cache;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
-+      entry = rhashtable_lookup(&cache->rhead, addr, mesh_hdr_rht_params);
++      cache = &sdata->u.mesh.tx_cache;
++      entry = rhashtable_lookup(&cache->rht, addr, fast_tx_rht_params);
 +      if (!entry)
 +              return NULL;
 +
 +      if (!(entry->mpath->flags & MESH_PATH_ACTIVE) ||
 +          mpath_expired(entry->mpath)) {
 +              spin_lock_bh(&cache->walk_lock);
-+              entry = rhashtable_lookup(&cache->rhead, addr, mesh_hdr_rht_params);
++              entry = rhashtable_lookup(&cache->rht, addr, fast_tx_rht_params);
 +              if (entry)
-+                  mesh_hdr_cache_entry_free(cache, entry);
++                  mesh_fast_tx_entry_free(cache, entry);
 +              spin_unlock_bh(&cache->walk_lock);
 +              return NULL;
 +      }
 +
-+      mesh_refresh_path(sdata, entry->mpath, NULL);
++      mesh_path_refresh(sdata, entry->mpath, NULL);
 +      if (entry->mppath)
 +              entry->mppath->exp_time = jiffies;
 +      entry->timestamp = jiffies;
@@ -363,33 +456,30 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +      return entry;
 +}
 +
-+void mesh_cache_hdr(struct ieee80211_sub_if_data *sdata,
-+                  struct sk_buff *skb, struct mesh_path *mpath)
++void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata,
++                      struct sk_buff *skb, struct mesh_path *mpath)
 +{
 +      struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
 +      struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
-+      struct mesh_hdr_cache *cache;
-+      struct mhdr_cache_entry *mhdr, *old_mhdr;
++      struct ieee80211_mesh_fast_tx *entry, *prev;
++      struct ieee80211_mesh_fast_tx build = {};
 +      struct ieee80211s_hdr *meshhdr;
-+      struct sta_info *sta;
++      struct mesh_tx_cache *cache;
 +      struct ieee80211_key *key;
 +      struct mesh_path *mppath;
-+      u16 meshhdr_len;
-+      u8 pn_offs = 0;
-+      int hdrlen;
-+
-+      if (sdata->noack_map)
-+              return;
++      struct sta_info *sta;
++      u8 *qc;
 +
-+      if (!ieee80211_is_data_qos(hdr->frame_control))
++      if (sdata->noack_map ||
++          !ieee80211_is_data_qos(hdr->frame_control))
 +              return;
 +
-+      hdrlen = ieee80211_hdrlen(hdr->frame_control);
-+      meshhdr = (struct ieee80211s_hdr *)(skb->data + hdrlen);
-+      meshhdr_len = ieee80211_get_mesh_hdrlen(meshhdr);
++      build.fast_tx.hdr_len = ieee80211_hdrlen(hdr->frame_control);
++      meshhdr = (struct ieee80211s_hdr *)(skb->data + build.fast_tx.hdr_len);
++      build.hdrlen = ieee80211_get_mesh_hdrlen(meshhdr);
 +
-+      cache = &sdata->u.mesh.hdr_cache;
-+      if (atomic_read(&cache->rhead.nelems) >= MESH_HEADER_CACHE_MAX_SIZE)
++      cache = &sdata->u.mesh.tx_cache;
++      if (atomic_read(&cache->rht.nelems) >= MESH_FAST_TX_CACHE_MAX_SIZE)
 +              return;
 +
 +      sta = rcu_dereference(mpath->next_hop);
@@ -401,6 +491,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +              mppath = mpp_path_lookup(sdata, meshhdr->eaddr1);
 +              if (!mppath)
 +                      return;
++              build.mppath = mppath;
 +      } else if (ieee80211_has_a4(hdr->frame_control)) {
 +              mppath = mpath;
 +      } else {
@@ -408,10 +499,10 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +      }
 +
 +      /* rate limit, in case fast xmit can't be enabled */
-+      if (mppath->fast_xmit_check == jiffies)
++      if (mppath->fast_tx_check == jiffies)
 +              return;
 +
-+      mppath->fast_xmit_check = jiffies;
++      mppath->fast_tx_check = jiffies;
 +
 +      /*
 +       * Same use of the sta lock as in ieee80211_check_fast_xmit, in order
@@ -421,6 +512,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +      key = rcu_access_pointer(sta->ptk[sta->ptk_idx]);
 +      if (!key)
 +              key = rcu_access_pointer(sdata->default_unicast_key);
++      build.fast_tx.key = key;
 +
 +      if (key) {
 +              bool gen_iv, iv_spc;
@@ -436,60 +528,50 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +              case WLAN_CIPHER_SUITE_CCMP:
 +              case WLAN_CIPHER_SUITE_CCMP_256:
 +                      if (gen_iv)
-+                              pn_offs = hdrlen;
++                              build.fast_tx.pn_offs = build.fast_tx.hdr_len;
 +                      if (gen_iv || iv_spc)
-+                              hdrlen += IEEE80211_CCMP_HDR_LEN;
++                              build.fast_tx.hdr_len += IEEE80211_CCMP_HDR_LEN;
 +                      break;
 +              case WLAN_CIPHER_SUITE_GCMP:
 +              case WLAN_CIPHER_SUITE_GCMP_256:
 +                      if (gen_iv)
-+                              pn_offs = hdrlen;
++                              build.fast_tx.pn_offs = build.fast_tx.hdr_len;
 +                      if (gen_iv || iv_spc)
-+                              hdrlen += IEEE80211_GCMP_HDR_LEN;
++                              build.fast_tx.hdr_len += IEEE80211_GCMP_HDR_LEN;
 +                      break;
 +              default:
 +                      goto unlock_sta;
 +              }
 +      }
 +
-+      if (WARN_ON_ONCE(hdrlen + meshhdr_len + sizeof(rfc1042_header) >
-+                       MESH_HEADER_MAX_LEN))
-+              goto unlock_sta;
-+
-+      mhdr = kzalloc(sizeof(*mhdr), GFP_ATOMIC);
-+      if (!mhdr)
-+              goto unlock_sta;
++      memcpy(build.addr_key, mppath->dst, ETH_ALEN);
++      build.timestamp = jiffies;
++      build.fast_tx.band = info->band;
++      build.fast_tx.da_offs = offsetof(struct ieee80211_hdr, addr3);
++      build.fast_tx.sa_offs = offsetof(struct ieee80211_hdr, addr4);
++      build.mpath = mpath;
++      memcpy(build.hdr, meshhdr, build.hdrlen);
++      memcpy(build.hdr + build.hdrlen, rfc1042_header, sizeof(rfc1042_header));
++      build.hdrlen += sizeof(rfc1042_header);
++      memcpy(build.fast_tx.hdr, hdr, build.fast_tx.hdr_len);
++
++      hdr = (struct ieee80211_hdr *)build.fast_tx.hdr;
++      if (build.fast_tx.key)
++              hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
 +
-+      memcpy(mhdr->addr_key, mppath->dst, ETH_ALEN);
-+      mhdr->machdr_len = hdrlen;
-+      mhdr->hdrlen = mhdr->machdr_len + meshhdr_len + sizeof(rfc1042_header);
-+      mhdr->mpath = mpath;
-+      if (meshhdr->flags & MESH_FLAGS_AE)
-+              mhdr->mppath = mppath;
-+      mhdr->key = key;
-+      mhdr->timestamp = jiffies;
-+      mhdr->band = info->band;
-+      mhdr->pn_offs = pn_offs;
-+
-+      if (pn_offs) {
-+              memcpy(mhdr->hdr, skb->data, pn_offs);
-+              memcpy(mhdr->hdr + mhdr->machdr_len, skb->data + pn_offs,
-+                     mhdr->hdrlen - mhdr->machdr_len);
-+      } else {
-+              memcpy(mhdr->hdr, skb->data, mhdr->hdrlen);
-+      }
++      qc = ieee80211_get_qos_ctl(hdr);
++      qc[1] |= IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT >> 8;
 +
-+      if (key) {
-+              hdr = (struct ieee80211_hdr *)mhdr->hdr;
-+              hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
-+      }
++      entry = kmemdup(&build, sizeof(build), GFP_ATOMIC);
++      if (!entry)
++              goto unlock_sta;
 +
 +      spin_lock_bh(&cache->walk_lock);
-+      old_mhdr = rhashtable_lookup_get_insert_fast(&cache->rhead,
-+                                                   &mhdr->rhash,
-+                                                   mesh_hdr_rht_params);
-+      if (unlikely(IS_ERR(old_mhdr))) {
-+              kfree(mhdr);
++      prev = rhashtable_lookup_get_insert_fast(&cache->rht,
++                                               &entry->rhash,
++                                               fast_tx_rht_params);
++      if (unlikely(IS_ERR(prev))) {
++              kfree(entry);
 +              goto unlock_cache;
 +      }
 +
@@ -497,14 +579,14 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +       * replace any previous entry in the hash table, in case we're
 +       * replacing it with a different type (e.g. mpath -> mpp)
 +       */
-+      if (unlikely(old_mhdr)) {
-+              rhashtable_replace_fast(&cache->rhead, &old_mhdr->rhash,
-+                                      &mhdr->rhash, mesh_hdr_rht_params);
-+              hlist_del_rcu(&old_mhdr->walk_list);
-+              kfree_rcu(old_mhdr, rcu);
++      if (unlikely(prev)) {
++              rhashtable_replace_fast(&cache->rht, &prev->rhash,
++                                      &entry->rhash, fast_tx_rht_params);
++              hlist_del_rcu(&prev->walk_list);
++              kfree_rcu(prev, fast_tx.rcu_head);
 +      }
 +
-+      hlist_add_head(&mhdr->walk_list, &cache->walk_head);
++      hlist_add_head(&entry->walk_list, &cache->walk_head);
 +
 +unlock_cache:
 +      spin_unlock_bh(&cache->walk_lock);
@@ -512,112 +594,112 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +      spin_unlock_bh(&sta->lock);
 +}
 +
-+void mesh_hdr_cache_gc(struct ieee80211_sub_if_data *sdata)
++void mesh_fast_tx_gc(struct ieee80211_sub_if_data *sdata)
 +{
-+      unsigned long timeout = msecs_to_jiffies(MESH_HEADER_CACHE_TIMEOUT);
-+      struct mesh_hdr_cache *cache;
-+      struct mhdr_cache_entry *entry;
++      unsigned long timeout = msecs_to_jiffies(MESH_FAST_TX_CACHE_TIMEOUT);
++      struct mesh_tx_cache *cache;
++      struct ieee80211_mesh_fast_tx *entry;
 +      struct hlist_node *n;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
-+      if (atomic_read(&cache->rhead.nelems) < MESH_HEADER_CACHE_THRESHOLD_SIZE)
++      cache = &sdata->u.mesh.tx_cache;
++      if (atomic_read(&cache->rht.nelems) < MESH_FAST_TX_CACHE_THRESHOLD_SIZE)
 +              return;
 +
 +      spin_lock_bh(&cache->walk_lock);
 +      hlist_for_each_entry_safe(entry, n, &cache->walk_head, walk_list)
 +              if (!time_is_after_jiffies(entry->timestamp + timeout))
-+                      mesh_hdr_cache_entry_free(cache, entry);
++                      mesh_fast_tx_entry_free(cache, entry);
 +      spin_unlock_bh(&cache->walk_lock);
 +}
 +
-+void mesh_hdr_cache_flush_mpath(struct mesh_path *mpath)
++void mesh_fast_tx_flush_mpath(struct mesh_path *mpath)
 +{
 +      struct ieee80211_sub_if_data *sdata = mpath->sdata;
-+      struct mesh_hdr_cache *cache = &sdata->u.mesh.hdr_cache;
-+      struct mhdr_cache_entry *entry;
++      struct mesh_tx_cache *cache = &sdata->u.mesh.tx_cache;
++      struct ieee80211_mesh_fast_tx *entry;
 +      struct hlist_node *n;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
++      cache = &sdata->u.mesh.tx_cache;
 +      spin_lock_bh(&cache->walk_lock);
 +      hlist_for_each_entry_safe(entry, n, &cache->walk_head, walk_list)
 +              if (entry->mpath == mpath)
-+                      mesh_hdr_cache_entry_free(cache, entry);
++                      mesh_fast_tx_entry_free(cache, entry);
 +      spin_unlock_bh(&cache->walk_lock);
 +}
 +
-+void mesh_hdr_cache_flush_sta(struct ieee80211_sub_if_data *sdata,
-+                            struct sta_info *sta)
++void mesh_fast_tx_flush_sta(struct ieee80211_sub_if_data *sdata,
++                          struct sta_info *sta)
 +{
-+      struct mesh_hdr_cache *cache = &sdata->u.mesh.hdr_cache;
-+      struct mhdr_cache_entry *entry;
++      struct mesh_tx_cache *cache = &sdata->u.mesh.tx_cache;
++      struct ieee80211_mesh_fast_tx *entry;
 +      struct hlist_node *n;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
++      cache = &sdata->u.mesh.tx_cache;
 +      spin_lock_bh(&cache->walk_lock);
 +      hlist_for_each_entry_safe(entry, n, &cache->walk_head, walk_list)
 +              if (rcu_access_pointer(entry->mpath->next_hop) == sta)
-+                      mesh_hdr_cache_entry_free(cache, entry);
++                      mesh_fast_tx_entry_free(cache, entry);
 +      spin_unlock_bh(&cache->walk_lock);
 +}
 +
-+void mesh_hdr_cache_flush_mpp(struct ieee80211_sub_if_data *sdata,
-+                            const u8 *addr)
++void mesh_fast_tx_flush_addr(struct ieee80211_sub_if_data *sdata,
++                           const u8 *addr)
 +{
-+      struct mesh_hdr_cache *cache = &sdata->u.mesh.hdr_cache;
-+      struct mhdr_cache_entry *entry;
++      struct mesh_tx_cache *cache = &sdata->u.mesh.tx_cache;
++      struct ieee80211_mesh_fast_tx *entry;
 +
-+      cache = &sdata->u.mesh.hdr_cache;
++      cache = &sdata->u.mesh.tx_cache;
 +      spin_lock_bh(&cache->walk_lock);
-+      entry = rhashtable_lookup(&cache->rhead, addr, mesh_hdr_rht_params);
++      entry = rhashtable_lookup(&cache->rht, addr, fast_tx_rht_params);
 +      if (entry)
-+              mesh_hdr_cache_entry_free(cache, entry);
++              mesh_fast_tx_entry_free(cache, entry);
 +      spin_unlock_bh(&cache->walk_lock);
 +}
 +
  /**
   * mesh_path_add - allocate and add a new path to the mesh path table
   * @dst: destination address of the path (ETH_ALEN length)
-@@ -464,6 +748,8 @@ int mpp_path_add(struct ieee80211_sub_if
+@@ -464,6 +737,8 @@ int mpp_path_add(struct ieee80211_sub_if
  
        if (ret)
                kfree(new_mpath);
 +      else
-+              mesh_hdr_cache_flush_mpp(sdata, dst);
++              mesh_fast_tx_flush_addr(sdata, dst);
  
        sdata->u.mesh.mpp_paths_generation++;
        return ret;
-@@ -523,6 +809,10 @@ static void __mesh_path_del(struct mesh_
+@@ -523,6 +798,10 @@ static void __mesh_path_del(struct mesh_
  {
        hlist_del_rcu(&mpath->walk_list);
        rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params);
 +      if (tbl == &mpath->sdata->u.mesh.mpp_paths)
-+              mesh_hdr_cache_flush_mpp(mpath->sdata, mpath->dst);
++              mesh_fast_tx_flush_addr(mpath->sdata, mpath->dst);
 +      else
-+              mesh_hdr_cache_flush_mpath(mpath);
++              mesh_fast_tx_flush_mpath(mpath);
        mesh_path_free_rcu(tbl, mpath);
  }
  
-@@ -747,6 +1037,7 @@ void mesh_path_fix_nexthop(struct mesh_p
+@@ -747,6 +1026,7 @@ void mesh_path_fix_nexthop(struct mesh_p
        mpath->exp_time = 0;
        mpath->flags = MESH_PATH_FIXED | MESH_PATH_SN_VALID;
        mesh_path_activate(mpath);
-+      mesh_hdr_cache_flush_mpath(mpath);
++      mesh_fast_tx_flush_mpath(mpath);
        spin_unlock_bh(&mpath->state_lock);
        ewma_mesh_fail_avg_init(&next_hop->mesh->fail_avg);
        /* init it at a low value - 0 start is tricky */
-@@ -758,6 +1049,7 @@ void mesh_pathtbl_init(struct ieee80211_
+@@ -758,6 +1038,7 @@ void mesh_pathtbl_init(struct ieee80211_
  {
        mesh_table_init(&sdata->u.mesh.mesh_paths);
        mesh_table_init(&sdata->u.mesh.mpp_paths);
-+      mesh_hdr_cache_init(sdata);
++      mesh_fast_tx_init(sdata);
  }
  
  static
-@@ -785,6 +1077,7 @@ void mesh_path_expire(struct ieee80211_s
+@@ -785,6 +1066,7 @@ void mesh_path_expire(struct ieee80211_s
  
  void mesh_pathtbl_unregister(struct ieee80211_sub_if_data *sdata)
  {
-+      mesh_hdr_cache_deinit(sdata);
++      mesh_fast_tx_deinit(sdata);
        mesh_table_free(&sdata->u.mesh.mesh_paths);
        mesh_table_free(&sdata->u.mesh.mpp_paths);
  }
@@ -646,7 +728,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +
 +              /* flush fast xmit cache if the address path changed */
 +              if (update)
-+                      mesh_hdr_cache_flush_mpp(sdata, proxied_addr);
++                      mesh_fast_tx_flush_addr(sdata, proxied_addr);
 +
                rcu_read_unlock();
        }
@@ -658,175 +740,105 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
                return;
  
 +      if (ieee80211_vif_is_mesh(&sdata->vif))
-+              mesh_hdr_cache_flush_sta(sdata, sta);
++              mesh_fast_tx_flush_sta(sdata, sta);
 +
        /* Locking here protects both the pointer itself, and against concurrent
         * invocations winning data access races to, e.g., the key pointer that
         * is used.
-@@ -3723,6 +3726,162 @@ free:
-       kfree_skb(skb);
- }
+@@ -3402,6 +3405,9 @@ static bool ieee80211_amsdu_aggregate(st
+       if (sdata->vif.offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED)
+               return false;
  
-+void __ieee80211_mesh_xmit_fast(struct ieee80211_sub_if_data *sdata,
-+                              struct mhdr_cache_entry *entry,
-+                              struct sk_buff *skb)
-+{
-+      struct ieee80211_local *local = sdata->local;
-+      struct ieee80211_tx_data tx = {};
-+      struct ieee80211_tx_info *info;
-+      struct tid_ampdu_tx *tid_tx;
-+      struct ieee80211_key *key;
-+      struct ieee80211_hdr *hdr;
-+      struct mesh_path *mpath;
-+      ieee80211_tx_result r;
-+      struct sta_info *sta;
-+      u8 tid;
-+
-+      if (!IS_ENABLED(CPTCFG_MAC80211_MESH))
-+              return;
-+
-+      info = IEEE80211_SKB_CB(skb);
-+      memset(info, 0, sizeof(*info));
-+      info->band = entry->band;
-+      info->control.vif = &sdata->vif;
-+      info->flags = IEEE80211_TX_CTL_FIRST_FRAGMENT |
-+                    IEEE80211_TX_CTL_DONTFRAG;
-+
-+      info->control.flags = IEEE80211_TX_CTRL_FAST_XMIT;
-+
-+#ifdef CONFIG_MAC80211_DEBUGFS
-+      if (local->force_tx_status)
-+              info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
-+#endif
-+
-+      mpath = entry->mpath;
-+      key = entry->key;
-+      sta = rcu_dereference(mpath->next_hop);
-+
-+      __skb_queue_head_init(&tx.skbs);
-+
-+      tx.flags = IEEE80211_TX_UNICAST;
-+      tx.local = local;
-+      tx.sdata = sdata;
-+      tx.sta = sta;
-+      tx.key = key;
-+      tx.skb = skb;
-+
-+      hdr = (struct ieee80211_hdr *)skb->data;
-+      tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
-+      *ieee80211_get_qos_ctl(hdr) = tid;
-+      tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
-+      if (tid_tx) {
-+              if (tid_tx->timeout)
-+                      tid_tx->last_tx = jiffies;
-+              info->flags |= IEEE80211_TX_CTL_AMPDU;
-+      }
-+
-+      ieee80211_aggr_check(sdata, sta, skb);
-+
-+      if (ieee80211_queue_skb(local, sdata, sta, skb))
-+              return;
-+
-+      r = ieee80211_xmit_fast_finish(sdata, sta, entry->pn_offs, key, &tx);
-+      if (r == TX_DROP) {
-+              kfree_skb(skb);
-+              return;
-+      }
-+
-+      __skb_queue_tail(&tx.skbs, skb);
-+      ieee80211_tx_frags(local, &sdata->vif, sta, &tx.skbs, false);
-+}
-+
-+
-+static bool ieee80211_mesh_xmit_fast(struct ieee80211_sub_if_data *sdata,
-+                                   struct sk_buff *skb, u32 ctrl_flags)
-+{
-+      struct ieee80211_local *local = sdata->local;
-+      struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
-+      struct mhdr_cache_entry *entry;
-+      struct ieee80211s_hdr *meshhdr;
-+      u8 sa[ETH_ALEN] __aligned(2);
-+      struct sta_info *sta;
-+      bool copy_sa = false;
-+      u16 ethertype;
-+
-+      if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
-+              return false;
-+
-+      if (ctrl_flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP)
-+              return false;
-+
-+      if (ifmsh->mshcfg.dot11MeshNolearn)
-+              return false;
-+
-+      /* Add support for these cases later */
-+      if (ifmsh->ps_peers_light_sleep || ifmsh->ps_peers_deep_sleep)
-+              return false;
-+
-+      if (is_multicast_ether_addr(skb->data))
-+              return false;
-+
-+      ethertype = (skb->data[12] << 8) | skb->data[13];
-+      if (ethertype < ETH_P_802_3_MIN)
-+              return false;
-+
-+      if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)
-+              return false;
-+
-+      if (skb->ip_summed == CHECKSUM_PARTIAL) {
-+              skb_set_transport_header(skb, skb_checksum_start_offset(skb));
-+              if (skb_checksum_help(skb))
-+                      return false;
-+      }
-+
-+      entry = mesh_get_cached_hdr(sdata, skb->data);
-+      if (!entry)
-+              return false;
-+
-+      /* Avoid extra work in this path */
-+      if (skb_headroom(skb) < (entry->hdrlen - ETH_HLEN + 2))
++      if (ieee80211_vif_is_mesh(&sdata->vif))
 +              return false;
 +
-+      /* If the skb is shared we need to obtain our own copy */
-+      if (skb_shared(skb)) {
-+              struct sk_buff *oskb = skb;
-+
-+              skb = skb_clone(skb, GFP_ATOMIC);
-+              if (!skb)
-+                      return false;
-+
-+              kfree_skb(oskb);
-+      }
-+
-+      sta = rcu_dereference(entry->mpath->next_hop);
-+      skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb));
-+
-+      meshhdr = (struct ieee80211s_hdr *)(entry->hdr + entry->machdr_len);
-+      if ((meshhdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6) {
-+              /* preserve SA from eth header for 6-addr frames */
-+              ether_addr_copy(sa, skb->data + ETH_ALEN);
-+              copy_sa = true;
-+      }
-+
-+      memcpy(skb_push(skb, entry->hdrlen - 2 * ETH_ALEN), entry->hdr,
-+             entry->hdrlen);
-+
-+      meshhdr = (struct ieee80211s_hdr *)(skb->data + entry->machdr_len);
-+      put_unaligned_le32(atomic_inc_return(&sdata->u.mesh.mesh_seqnum),
-+                         &meshhdr->seqnum);
-+      meshhdr->ttl = sdata->u.mesh.mshcfg.dot11MeshTTL;
-+      if (copy_sa)
-+          ether_addr_copy(meshhdr->eaddr2, sa);
-+
-+      __ieee80211_mesh_xmit_fast(sdata, entry, skb);
-+
-+      return true;
-+}
+       if (skb_is_gso(skb))
+               return false;
+@@ -3634,10 +3640,11 @@ free:
+       return NULL;
+ }
+-static void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
+-                                struct sta_info *sta,
+-                                struct ieee80211_fast_tx *fast_tx,
+-                                struct sk_buff *skb, u8 tid, bool ampdu)
++void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
++                         struct sta_info *sta,
++                         struct ieee80211_fast_tx *fast_tx,
++                         struct sk_buff *skb, bool ampdu,
++                         const u8 *da, const u8 *sa)
+ {
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
+@@ -3645,8 +3652,6 @@ static void __ieee80211_xmit_fast(struct
+       struct ieee80211_tx_data tx;
+       ieee80211_tx_result r;
+       int hw_headroom = sdata->local->hw.extra_tx_headroom;
+-      int extra_head = fast_tx->hdr_len - (ETH_HLEN - 2);
+-      struct ethhdr eth;
+       skb = skb_share_check(skb, GFP_ATOMIC);
+       if (unlikely(!skb))
+@@ -3661,16 +3666,15 @@ static void __ieee80211_xmit_fast(struct
+        * more room than we already have in 'extra_head'
+        */
+       if (unlikely(ieee80211_skb_resize(sdata, skb,
+-                                        max_t(int, extra_head + hw_headroom -
++                                        max_t(int, fast_tx->hdr_len + hw_headroom -
+                                                    skb_headroom(skb), 0),
+                                         ENCRYPT_NO)))
+               goto free;
+-      memcpy(&eth, skb->data, ETH_HLEN - 2);
+-      hdr = skb_push(skb, extra_head);
++      hdr = skb_push(skb, fast_tx->hdr_len);
+       memcpy(skb->data, fast_tx->hdr, fast_tx->hdr_len);
+-      memcpy(skb->data + fast_tx->da_offs, eth.h_dest, ETH_ALEN);
+-      memcpy(skb->data + fast_tx->sa_offs, eth.h_source, ETH_ALEN);
++      memcpy(skb->data + fast_tx->da_offs, da, ETH_ALEN);
++      memcpy(skb->data + fast_tx->sa_offs, sa, ETH_ALEN);
+       info = IEEE80211_SKB_CB(skb);
+       memset(info, 0, sizeof(*info));
+@@ -3689,7 +3693,7 @@ static void __ieee80211_xmit_fast(struct
+ #endif
+       if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) {
+-              tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
++              u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
+               *ieee80211_get_qos_ctl(hdr) = tid;
+       }
+@@ -3732,6 +3736,7 @@ static bool ieee80211_xmit_fast(struct i
+       struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
+       struct tid_ampdu_tx *tid_tx = NULL;
+       struct sk_buff *next;
++      struct ethhdr eth;
+       u8 tid = IEEE80211_NUM_TIDS;
+       /* control port protocol needs a lot of special handling */
+@@ -3757,14 +3762,18 @@ static bool ieee80211_xmit_fast(struct i
+               }
+       }
++      memcpy(&eth, skb->data, ETH_HLEN - 2);
 +
- static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
-                               struct sta_info *sta,
-                               struct ieee80211_fast_tx *fast_tx,
-@@ -4244,8 +4403,14 @@ void __ieee80211_subif_start_xmit(struct
+       /* after this point (skb is modified) we cannot return false */
++      skb_pull(skb, ETH_HLEN - 2);
+       skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
+       if (!skb)
+               return true;
+       skb_list_walk_safe(skb, skb, next) {
+               skb_mark_not_on_list(skb);
+-              __ieee80211_xmit_fast(sdata, sta, fast_tx, skb, tid, tid_tx);
++              __ieee80211_xmit_fast(sdata, sta, fast_tx, skb, tid_tx,
++                                    eth.h_dest, eth.h_source);
+       }
+       return true;
+@@ -4244,8 +4253,15 @@ void __ieee80211_subif_start_xmit(struct
                return;
        }
  
@@ -835,13 +847,14 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        rcu_read_lock();
  
 +      if (ieee80211_vif_is_mesh(&sdata->vif) &&
++          ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT) &&
 +          ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags))
 +              goto out;
 +
        if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
                goto out_free;
  
-@@ -4255,8 +4420,6 @@ void __ieee80211_subif_start_xmit(struct
+@@ -4255,8 +4271,6 @@ void __ieee80211_subif_start_xmit(struct
        skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb));
        ieee80211_aggr_check(sdata, sta, skb);
  
index e0d4e60ed74008a1c7cd4163c39aabd5a5bdc9b1..3b0bae67a5f176307ec0dd2851115d102ab5d097 100644 (file)
@@ -3,40 +3,92 @@ Date: Thu, 16 Feb 2023 11:07:30 +0100
 Subject: [PATCH] wifi: mac80211: use mesh header cache to speed up mesh
  forwarding
 
-Use it to look up the next hop address + sta pointer + key and call
-__ieee80211_mesh_xmit_fast to queue the tx frame.
-
 Significantly reduces mesh forwarding path CPU usage and enables the
-use of iTXQ.
+direct use of iTXQ.
 
 Signed-off-by: Felix Fietkau <nbd@nbd.name>
 ---
 
 --- a/net/mac80211/rx.c
 +++ b/net/mac80211/rx.c
-@@ -2731,6 +2731,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
-       struct ieee80211_hdr hdr = {
-               .frame_control = cpu_to_le16(fc)
-       };
-+      struct mhdr_cache_entry *entry = NULL;
-       struct ieee80211_hdr *fwd_hdr;
-       struct ieee80211s_hdr *mesh_hdr;
-       struct ieee80211_tx_info *info;
-@@ -2788,7 +2789,12 @@ ieee80211_rx_mesh_data(struct ieee80211_
-               return RX_DROP_MONITOR;
+@@ -2720,6 +2720,65 @@ ieee80211_deliver_skb(struct ieee80211_r
        }
+ }
  
--      if (mesh_hdr->flags & MESH_FLAGS_AE) {
++#ifdef CPTCFG_MAC80211_MESH
++static bool
++ieee80211_rx_mesh_fast_forward(struct ieee80211_sub_if_data *sdata,
++                             struct sk_buff *skb, int hdrlen)
++{
++      struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
++      struct ieee80211_mesh_fast_tx *entry = NULL;
++      struct ieee80211s_hdr *mesh_hdr;
++      struct tid_ampdu_tx *tid_tx;
++      struct sta_info *sta;
++      struct ethhdr eth;
++      u8 tid;
++
++      mesh_hdr = (struct ieee80211s_hdr *)(skb->data + sizeof(eth));
 +      if ((mesh_hdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6)
-+              entry = mesh_get_cached_hdr(sdata, mesh_hdr->eaddr1);
++              entry = mesh_fast_tx_get(sdata, mesh_hdr->eaddr1);
 +      else if (!(mesh_hdr->flags & MESH_FLAGS_AE))
-+              entry = mesh_get_cached_hdr(sdata, eth->h_dest);
++              entry = mesh_fast_tx_get(sdata, skb->data);
++      if (!entry)
++              return false;
++
++      sta = rcu_dereference(entry->mpath->next_hop);
++      if (!sta)
++              return false;
++
++      if (skb_linearize(skb))
++              return false;
++
++      tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
++      tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
++      if (tid_tx) {
++              if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state))
++                      return false;
++
++              if (tid_tx->timeout)
++                      tid_tx->last_tx = jiffies;
++      }
++
++      ieee80211_aggr_check(sdata, sta, skb);
++
++      if (ieee80211_get_8023_tunnel_proto(skb->data + hdrlen,
++                                          &skb->protocol))
++              hdrlen += ETH_ALEN;
++      else
++              skb->protocol = htons(skb->len - hdrlen);
++      skb_set_network_header(skb, hdrlen + 2);
++
++      skb->dev = sdata->dev;
++      memcpy(&eth, skb->data, ETH_HLEN - 2);
++      skb_pull(skb, sizeof(eth));
++      __ieee80211_xmit_fast(sdata, sta, &entry->fast_tx, skb, tid_tx,
++                            eth.h_dest, eth.h_source);
++      IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
++      IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
 +
-+      if (!entry && (mesh_hdr->flags & MESH_FLAGS_AE)) {
-               struct mesh_path *mppath;
-               char *proxied_addr;
-               bool update = false;
-@@ -2862,11 +2868,23 @@ ieee80211_rx_mesh_data(struct ieee80211_
++      return true;
++}
++#endif
++
+ static ieee80211_rx_result
+ ieee80211_rx_mesh_data(struct ieee80211_sub_if_data *sdata, struct sta_info *sta,
+                      struct sk_buff *skb)
+@@ -2824,6 +2883,10 @@ ieee80211_rx_mesh_data(struct ieee80211_
+       skb_set_queue_mapping(skb, ieee802_1d_to_ac[skb->priority]);
++      if (!multicast &&
++          ieee80211_rx_mesh_fast_forward(sdata, skb, mesh_hdrlen))
++              return RX_QUEUED;
++
+       ieee80211_fill_mesh_addresses(&hdr, &hdr.frame_control,
+                                     eth->h_dest, eth->h_source);
+       hdrlen = ieee80211_hdrlen(hdr.frame_control);
+@@ -2862,6 +2925,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
        info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
        info->control.vif = &sdata->vif;
        info->control.jiffies = jiffies;
@@ -44,23 +96,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        if (multicast) {
                IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_mcast);
                memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
-               /* update power mode indication when forwarding */
-               ieee80211_mps_set_frame_flags(sdata, NULL, fwd_hdr);
-+      } else if (entry) {
-+              struct ieee80211_hdr *ehdr = (struct ieee80211_hdr *)entry->hdr;
-+
-+              ether_addr_copy(fwd_hdr->addr1, ehdr->addr1);
-+              ether_addr_copy(fwd_hdr->addr2, sdata->vif.addr);
-+              IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
-+              IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
-+              qos[0] = fwd_skb->priority;
-+              qos[1] = ieee80211_get_qos_ctl(ehdr)[1];
-+              __ieee80211_mesh_xmit_fast(sdata, entry, fwd_skb);
-+              return RX_QUEUED;
-       } else if (!mesh_nexthop_lookup(sdata, fwd_skb)) {
-               /* mesh power mode flags updated in mesh_nexthop_lookup */
-               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
-@@ -2883,7 +2901,6 @@ ieee80211_rx_mesh_data(struct ieee80211_
+@@ -2883,7 +2947,6 @@ ieee80211_rx_mesh_data(struct ieee80211_
        }
  
        IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
@@ -68,3 +104,29 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        ieee80211_add_pending_skb(local, fwd_skb);
  
  rx_accept:
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -2018,6 +2018,8 @@ void __ieee80211_xmit_fast(struct ieee80
+                          struct ieee80211_fast_tx *fast_tx,
+                          struct sk_buff *skb, bool ampdu,
+                          const u8 *da, const u8 *sa);
++void ieee80211_aggr_check(struct ieee80211_sub_if_data *sdata,
++                        struct sta_info *sta, struct sk_buff *skb);
+ /* HT */
+ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -1191,10 +1191,8 @@ static bool ieee80211_tx_prep_agg(struct
+       return queued;
+ }
+-static void
+-ieee80211_aggr_check(struct ieee80211_sub_if_data *sdata,
+-                   struct sta_info *sta,
+-                   struct sk_buff *skb)
++void ieee80211_aggr_check(struct ieee80211_sub_if_data *sdata,
++                        struct sta_info *sta, struct sk_buff *skb)
+ {
+       struct rate_control_ref *ref = sdata->local->rate_ctrl;
+       u16 tid;
index d9af8c79291b38e8ab7e6747cec5ea350c07ae62..e2b268ae4c0e4fe6060d698fd9cebd312d9667b4 100644 (file)
@@ -11,7 +11,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 
 --- a/net/mac80211/rx.c
 +++ b/net/mac80211/rx.c
-@@ -2847,6 +2847,9 @@ ieee80211_rx_mesh_data(struct ieee80211_
+@@ -2904,6 +2904,9 @@ ieee80211_rx_mesh_data(struct ieee80211_
  
                if (skb_cow_head(fwd_skb, hdrlen - sizeof(struct ethhdr)))
                        return RX_DROP_UNUSABLE;
@@ -21,7 +21,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        }
  
        fwd_hdr = skb_push(fwd_skb, hdrlen - sizeof(struct ethhdr));
-@@ -2861,7 +2864,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
+@@ -2918,7 +2921,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
                hdrlen += ETH_ALEN;
        else
                fwd_skb->protocol = htons(fwd_skb->len - hdrlen);
index 817be9e4d2359ffc942ec993fc4e68b49bc60f82..80ffb493b8930c5700d53a2af1f0ef75da6bbb21 100644 (file)
@@ -87,7 +87,7 @@
        CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
 --- a/net/mac80211/ieee80211_i.h
 +++ b/net/mac80211/ieee80211_i.h
-@@ -1536,6 +1536,7 @@ struct ieee80211_local {
+@@ -1535,6 +1535,7 @@ struct ieee80211_local {
        int dynamic_ps_forced_timeout;
  
        int user_power_level; /* in dBm, for all interfaces */