mac80211: fix mesh issues and improve performance
authorFelix Fietkau <nbd@nbd.name>
Wed, 15 Feb 2023 18:40:02 +0000 (19:40 +0100)
committerFelix Fietkau <nbd@nbd.name>
Mon, 20 Feb 2023 11:59:51 +0000 (12:59 +0100)
fix forwarding received mesh a-msdu packets
add fast xmit support for mesh to improve performance

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/kernel/mac80211/patches/subsys/317-wifi-mac80211-fix-qos-on-mesh-interfaces.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/318-wifi-mac80211-fix-race-in-mesh-sequence-number-assig.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/319-wifi-mac80211-mesh-fast-xmit-support.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/320-wifi-mac80211-use-mesh-header-cache-to-speed-up-mesh.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/321-mac80211-fix-mesh-forwarding.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch

diff --git a/package/kernel/mac80211/patches/subsys/317-wifi-mac80211-fix-qos-on-mesh-interfaces.patch b/package/kernel/mac80211/patches/subsys/317-wifi-mac80211-fix-qos-on-mesh-interfaces.patch
new file mode 100644 (file)
index 0000000..c60a88d
--- /dev/null
@@ -0,0 +1,35 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Wed, 15 Feb 2023 15:11:54 +0100
+Subject: [PATCH] wifi: mac80211: fix qos on mesh interfaces
+
+When ieee80211_select_queue is called for mesh, the sta pointer is usually
+NULL, since the nexthop is looked up much later in the tx path.
+Explicitly check for unicast address in that case in order to make qos work
+again.
+
+Fixes: 50e2ab392919 ("wifi: mac80211: fix queue selection for mesh/OCB interfaces")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/mac80211/wme.c
++++ b/net/mac80211/wme.c
+@@ -147,6 +147,7 @@ u16 ieee80211_select_queue_80211(struct
+ u16 ieee80211_select_queue(struct ieee80211_sub_if_data *sdata,
+                          struct sta_info *sta, struct sk_buff *skb)
+ {
++      const struct ethhdr *eth = (void *)skb->data;
+       struct mac80211_qos_map *qos_map;
+       bool qos;
+@@ -154,8 +155,9 @@ u16 ieee80211_select_queue(struct ieee80
+       skb_get_hash(skb);
+       /* all mesh/ocb stations are required to support WME */
+-      if (sta && (sdata->vif.type == NL80211_IFTYPE_MESH_POINT ||
+-                  sdata->vif.type == NL80211_IFTYPE_OCB))
++      if ((sdata->vif.type == NL80211_IFTYPE_MESH_POINT &&
++          !is_multicast_ether_addr(eth->h_dest)) ||
++          (sdata->vif.type == NL80211_IFTYPE_OCB && sta))
+               qos = true;
+       else if (sta)
+               qos = sta->sta.wme;
diff --git a/package/kernel/mac80211/patches/subsys/318-wifi-mac80211-fix-race-in-mesh-sequence-number-assig.patch b/package/kernel/mac80211/patches/subsys/318-wifi-mac80211-fix-race-in-mesh-sequence-number-assig.patch
new file mode 100644 (file)
index 0000000..05e368c
--- /dev/null
@@ -0,0 +1,37 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Wed, 15 Feb 2023 15:21:37 +0100
+Subject: [PATCH] wifi: mac80211: fix race in mesh sequence number
+ assignment
+
+Since the sequence number is shared across different tx queues, it needs
+to be atomic in order to avoid accidental duplicate assignment
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -695,7 +695,7 @@ struct ieee80211_if_mesh {
+       struct mesh_stats mshstats;
+       struct mesh_config mshcfg;
+       atomic_t estab_plinks;
+-      u32 mesh_seqnum;
++      atomic_t mesh_seqnum;
+       bool accepting_plinks;
+       int num_gates;
+       struct beacon_data __rcu *beacon;
+--- a/net/mac80211/mesh.c
++++ b/net/mac80211/mesh.c
+@@ -752,10 +752,8 @@ unsigned int ieee80211_new_mesh_header(s
+       meshhdr->ttl = sdata->u.mesh.mshcfg.dot11MeshTTL;
+-      /* FIXME: racy -- TX on multiple queues can be concurrent */
+-      put_unaligned(cpu_to_le32(sdata->u.mesh.mesh_seqnum), &meshhdr->seqnum);
+-      sdata->u.mesh.mesh_seqnum++;
+-
++      put_unaligned_le32(atomic_inc_return(&sdata->u.mesh.mesh_seqnum),
++                         &meshhdr->seqnum);
+       if (addr4or5 && !addr6) {
+               meshhdr->flags |= MESH_FLAGS_AE_A4;
+               memcpy(meshhdr->eaddr1, addr4or5, ETH_ALEN);
diff --git a/package/kernel/mac80211/patches/subsys/319-wifi-mac80211-mesh-fast-xmit-support.patch b/package/kernel/mac80211/patches/subsys/319-wifi-mac80211-mesh-fast-xmit-support.patch
new file mode 100644 (file)
index 0000000..4bd3d4c
--- /dev/null
@@ -0,0 +1,764 @@
+From: Sriram R <quic_srirrama@quicinc.com>
+Date: Thu, 18 Aug 2022 12:35:42 +0530
+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.
+
+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.
+
+Co-developed-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -37,6 +37,7 @@
+ extern const struct cfg80211_ops mac80211_config_ops;
+ struct ieee80211_local;
++struct mhdr_cache_entry;
+ /* 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 {
+       atomic_t entries;               /* Up to MAX_MESH_NEIGHBOURS */
+ };
++/**
++ * struct mesh_hdr_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
++ */
++struct mesh_hdr_cache {
++      struct rhashtable rhead;
++      struct hlist_head walk_head;
++      spinlock_t walk_lock;
++};
++
+ struct ieee80211_if_mesh {
+       struct timer_list housekeeping_timer;
+       struct timer_list mesh_path_timer;
+@@ -733,6 +748,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;
+ };
+ #ifdef CPTCFG_MAC80211_MESH
+@@ -1998,6 +2014,9 @@ 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);
+ /* 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(
+       changed = mesh_accept_plinks_update(sdata);
+       ieee80211_mbss_info_change_notify(sdata, changed);
++      mesh_hdr_cache_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 {
+       u8 rann_snd_addr[ETH_ALEN];
+       u32 rann_metric;
+       unsigned long last_preq_to_root;
++      unsigned long fast_xmit_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 */
++
++/**
++ * 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
++ * @rhash: rhashtable pointer
++ * @mpath: The Mesh path corresponding to the Mesh DA
++ * @mppath: The 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 {
++      u8 addr_key[ETH_ALEN] __aligned(2);
++      u8 hdr[MESH_HEADER_MAX_LEN];
++      u16 machdr_len;
++      u16 hdrlen;
++      u8 pn_offs;
++      u8 band;
++      struct ieee80211_key __rcu *key;
++      struct hlist_node walk_list;
++      struct rhash_head rhash;
++      struct mesh_path *mpath, *mppath;
++      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,15 @@ 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(struct ieee80211_sub_if_data *sdata, const u8 *addr,
++                        bool is_mpp);
++void mesh_refresh_path(struct ieee80211_sub_if_data *sdata,
++                     struct mesh_path *mpath, const u8 *addr);
+ #ifdef CPTCFG_MAC80211_MESH
+ static inline
+--- a/net/mac80211/mesh_hwmp.c
++++ b/net/mac80211/mesh_hwmp.c
+@@ -491,8 +491,11 @@ static u32 hwmp_route_info_get(struct ie
+               }
+               if (fresh_info) {
+-                      if (rcu_access_pointer(mpath->next_hop) != sta)
++                      if (rcu_access_pointer(mpath->next_hop) != sta) {
+                               mpath->path_change_count++;
++                              mesh_hdr_cache_flush(mpath->sdata, mpath->dst,
++                                                   false);
++                      }
+                       mesh_path_assign_nexthop(mpath, sta);
+                       mpath->flags |= MESH_PATH_SN_VALID;
+                       mpath->metric = new_metric;
+@@ -539,8 +542,11 @@ static u32 hwmp_route_info_get(struct ie
+               }
+               if (fresh_info) {
+-                      if (rcu_access_pointer(mpath->next_hop) != sta)
++                      if (rcu_access_pointer(mpath->next_hop) != sta) {
+                               mpath->path_change_count++;
++                              mesh_hdr_cache_flush(mpath->sdata, mpath->dst,
++                                                   false);
++                      }
+                       mesh_path_assign_nexthop(mpath, sta);
+                       mpath->metric = last_hop_metric;
+                       mpath->exp_time = time_after(mpath->exp_time, exp_time)
+@@ -977,7 +983,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 +1221,20 @@ static int mesh_nexthop_lookup_nolearn(s
+       return 0;
+ }
++void mesh_refresh_path(struct ieee80211_sub_if_data *sdata,
++                     struct mesh_path *mpath, const u8 *addr)
++{
++      if (mpath->flags & (MESH_PATH_REQ_QUEUED | MESH_PATH_FIXED |
++                          MESH_PATH_RESOLVING))
++              return;
++
++      if (time_after(jiffies,
++                     mpath->exp_time -
++                     msecs_to_jiffies(sdata->u.mesh.mshcfg.path_refresh_time)) &&
++          (!addr || ether_addr_equal(sdata->vif.addr, addr)))
++              mesh_queue_preq(mpath, PREQ_Q_F_START | PREQ_Q_F_REFRESH);
++}
++
+ /**
+  * 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 +1262,18 @@ int mesh_nexthop_lookup(struct ieee80211
+       if (!mpath || !(mpath->flags & MESH_PATH_ACTIVE))
+               return -ENOENT;
+-      if (time_after(jiffies,
+-                     mpath->exp_time -
+-                     msecs_to_jiffies(sdata->u.mesh.mshcfg.path_refresh_time)) &&
+-          ether_addr_equal(sdata->vif.addr, hdr->addr4) &&
+-          !(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);
+       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);
+               return 0;
+       }
+--- a/net/mac80211/mesh_pathtbl.c
++++ b/net/mac80211/mesh_pathtbl.c
+@@ -14,6 +14,7 @@
+ #include "wme.h"
+ #include "ieee80211_i.h"
+ #include "mesh.h"
++#include <linux/rhashtable.h>
+ static void mesh_path_free_rcu(struct mesh_table *tbl, struct mesh_path *mpath);
+@@ -32,6 +33,41 @@ static const struct rhashtable_params me
+       .hashfn = mesh_table_hash,
+ };
++static const struct rhashtable_params mesh_hdr_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),
++      .hashfn = mesh_table_hash,
++};
++
++static void __mesh_hdr_cache_entry_free(void *ptr, void *tblptr)
++{
++      struct mhdr_cache_entry *mhdr = ptr;
++
++      kfree_rcu(mhdr, rcu);
++}
++
++static void mesh_hdr_cache_deinit(struct ieee80211_sub_if_data *sdata)
++{
++      struct mesh_hdr_cache *cache;
++
++      cache = &sdata->u.mesh.hdr_cache;
++      rhashtable_free_and_destroy(&cache->rhead,
++                                  __mesh_hdr_cache_entry_free, NULL);
++}
++
++static void mesh_hdr_cache_init(struct ieee80211_sub_if_data *sdata)
++{
++      struct mesh_hdr_cache *cache;
++
++      cache = &sdata->u.mesh.hdr_cache;
++      rhashtable_init(&cache->rhead, &mesh_hdr_rht_params);
++      INIT_HLIST_HEAD(&cache->walk_head);
++      spin_lock_init(&cache->walk_lock);
++}
++
+ static inline bool mpath_expired(struct mesh_path *mpath)
+ {
+       return (mpath->flags & MESH_PATH_ACTIVE) &&
+@@ -381,6 +417,211 @@ struct mesh_path *mesh_path_new(struct i
+       return new_mpath;
+ }
++struct mhdr_cache_entry *
++mesh_get_cached_hdr(struct ieee80211_sub_if_data *sdata, const u8 *addr)
++{
++      struct mesh_path *mpath, *mppath;
++      struct mhdr_cache_entry *entry;
++      struct mesh_hdr_cache *cache;
++
++      cache = &sdata->u.mesh.hdr_cache;
++      entry = rhashtable_lookup(&cache->rhead, addr, mesh_hdr_rht_params);
++      if (!entry)
++              return NULL;
++
++      mpath = rcu_dereference(entry->mpath);
++      mppath = rcu_dereference(entry->mppath);
++      if (!(mpath->flags & MESH_PATH_ACTIVE) || mpath_expired(mpath))
++              return NULL;
++
++      mesh_refresh_path(sdata, mpath, NULL);
++      if (mppath)
++              mppath->exp_time = jiffies;
++      entry->timestamp = jiffies;
++
++      return entry;
++}
++
++void mesh_cache_hdr(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 ieee80211s_hdr *meshhdr;
++      struct sta_info *next_hop;
++      struct ieee80211_key *key;
++      struct mesh_path *mppath;
++      u16 meshhdr_len;
++      u8 pn_offs = 0;
++      int hdrlen;
++
++      if (sdata->noack_map)
++              return;
++
++      if (!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);
++
++      cache = &sdata->u.mesh.hdr_cache;
++      if (atomic_read(&cache->rhead.nelems) >= MESH_HEADER_CACHE_MAX_SIZE)
++              return;
++
++      next_hop = rcu_dereference(mpath->next_hop);
++      if (!next_hop)
++              return;
++
++      if ((meshhdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6) {
++              /* This is required to keep the mppath alive */
++              mppath = mpp_path_lookup(sdata, meshhdr->eaddr1);
++              if (!mppath)
++                      return;
++      } else if (ieee80211_has_a4(hdr->frame_control)) {
++              mppath = mpath;
++      } else {
++              return;
++      }
++
++      /* rate limit, in case fast xmit can't be enabled */
++      if (mppath->fast_xmit_check == jiffies)
++              return;
++
++      mppath->fast_xmit_check = jiffies;
++
++      key = rcu_access_pointer(next_hop->ptk[next_hop->ptk_idx]);
++      if (!key)
++              key = rcu_access_pointer(sdata->default_unicast_key);
++
++      if (key) {
++              bool gen_iv, iv_spc;
++
++              gen_iv = key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV;
++              iv_spc = key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE;
++
++              if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) ||
++                  (key->flags & KEY_FLAG_TAINTED))
++                      return;
++
++              switch (key->conf.cipher) {
++              case WLAN_CIPHER_SUITE_CCMP:
++              case WLAN_CIPHER_SUITE_CCMP_256:
++                      if (gen_iv)
++                              pn_offs = hdrlen;
++                      if (gen_iv || iv_spc)
++                              hdrlen += IEEE80211_CCMP_HDR_LEN;
++                      break;
++              case WLAN_CIPHER_SUITE_GCMP:
++              case WLAN_CIPHER_SUITE_GCMP_256:
++                      if (gen_iv)
++                              pn_offs = hdrlen;
++                      if (gen_iv || iv_spc)
++                              hdrlen += IEEE80211_GCMP_HDR_LEN;
++                      break;
++              default:
++                      return;
++              }
++      }
++
++      if (WARN_ON_ONCE(hdrlen + meshhdr_len + sizeof(rfc1042_header) >
++                       MESH_HEADER_MAX_LEN))
++              return;
++
++      mhdr = kzalloc(sizeof(*mhdr), GFP_ATOMIC);
++      if (!mhdr)
++              return;
++
++      memcpy(mhdr->addr_key, mppath->dst, ETH_ALEN);
++      mhdr->machdr_len = hdrlen;
++      mhdr->hdrlen = mhdr->machdr_len + meshhdr_len + sizeof(rfc1042_header);
++      rcu_assign_pointer(mhdr->mpath, mpath);
++      if (meshhdr->flags & MESH_FLAGS_AE)
++              rcu_assign_pointer(mhdr->mppath, mppath);
++      rcu_assign_pointer(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);
++      }
++
++      if (key) {
++              hdr = (struct ieee80211_hdr *)mhdr->hdr;
++              hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
++      }
++
++      spin_lock_bh(&cache->walk_lock);
++      old_mhdr = rhashtable_lookup_get_insert_fast(&cache->rhead,
++                                                   &mhdr->rhash,
++                                                   mesh_hdr_rht_params);
++      if (likely(!old_mhdr))
++              hlist_add_head(&mhdr->walk_list, &cache->walk_head);
++      else
++              kfree(mhdr);
++      spin_unlock_bh(&cache->walk_lock);
++}
++
++static void mesh_hdr_cache_entry_free(struct mesh_hdr_cache *cache,
++                                    struct mhdr_cache_entry *entry)
++{
++      hlist_del_rcu(&entry->walk_list);
++      rhashtable_remove_fast(&cache->rhead, &entry->rhash, mesh_hdr_rht_params);
++      kfree_rcu(entry, rcu);
++}
++
++void mesh_hdr_cache_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;
++      struct hlist_node *n;
++
++      cache = &sdata->u.mesh.hdr_cache;
++      if (atomic_read(&cache->rhead.nelems) < MESH_HEADER_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);
++      spin_unlock_bh(&cache->walk_lock);
++}
++
++void mesh_hdr_cache_flush(struct ieee80211_sub_if_data *sdata, const u8 *addr,
++                        bool is_mpp)
++{
++      struct mesh_hdr_cache *cache = &sdata->u.mesh.hdr_cache;
++      struct mhdr_cache_entry *entry;
++      struct hlist_node *n;
++
++      cache = &sdata->u.mesh.hdr_cache;
++      spin_lock_bh(&cache->walk_lock);
++
++      /* Only one header per mpp address is expected in the header cache */
++      if (is_mpp) {
++              entry = rhashtable_lookup(&cache->rhead, addr,
++                                        mesh_hdr_rht_params);
++              if (entry)
++                      mesh_hdr_cache_entry_free(cache, entry);
++              goto out;
++      }
++
++      hlist_for_each_entry_safe(entry, n, &cache->walk_head, walk_list)
++              if (ether_addr_equal(entry->mpath->dst, addr))
++                      mesh_hdr_cache_entry_free(cache, entry);
++
++out:
++      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)
+@@ -521,6 +762,8 @@ static void mesh_path_free_rcu(struct me
+ static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath)
+ {
++      mesh_hdr_cache_flush(mpath->sdata, mpath->dst,
++                           tbl == &mpath->sdata->u.mesh.mpp_paths);
+       hlist_del_rcu(&mpath->walk_list);
+       rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params);
+       mesh_path_free_rcu(tbl, mpath);
+@@ -747,6 +990,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->sdata, mpath->dst, false);
+       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 +1002,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);
+ }
+ static
+@@ -785,6 +1030,7 @@ void mesh_path_expire(struct ieee80211_s
+ void mesh_pathtbl_unregister(struct ieee80211_sub_if_data *sdata)
+ {
++      mesh_hdr_cache_deinit(sdata);
+       mesh_table_free(&sdata->u.mesh.mesh_paths);
+       mesh_table_free(&sdata->u.mesh.mpp_paths);
+ }
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -2791,6 +2791,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
+       if (mesh_hdr->flags & MESH_FLAGS_AE) {
+               struct mesh_path *mppath;
+               char *proxied_addr;
++              bool update = false;
+               if (multicast)
+                       proxied_addr = mesh_hdr->eaddr1;
+@@ -2806,11 +2807,18 @@ ieee80211_rx_mesh_data(struct ieee80211_
+                       mpp_path_add(sdata, proxied_addr, eth->h_source);
+               } else {
+                       spin_lock_bh(&mppath->state_lock);
+-                      if (!ether_addr_equal(mppath->mpp, eth->h_source))
++                      if (!ether_addr_equal(mppath->mpp, eth->h_source)) {
+                               memcpy(mppath->mpp, eth->h_source, ETH_ALEN);
++                              update = true;
++                      }
+                       mppath->exp_time = jiffies;
+                       spin_unlock_bh(&mppath->state_lock);
+               }
++
++              /* flush fast xmit cache if the address path changed */
++              if (update)
++                      mesh_hdr_cache_flush(sdata, proxied_addr, true);
++
+               rcu_read_unlock();
+       }
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -3021,6 +3021,9 @@ void ieee80211_check_fast_xmit(struct st
+       if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
+               return;
++      if (ieee80211_vif_is_mesh(&sdata->vif))
++              mesh_hdr_cache_flush(sdata, sta->addr, false);
++
+       /* 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,155 @@ free:
+       kfree_skb(skb);
+ }
++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 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;
++
++      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 (ctrl_flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP)
++              return false;
++
++      if (ifmsh->mshcfg.dot11MeshNolearn)
++              return false;
++
++      if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
++              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))
++              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;
++}
++
+ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
+                               struct sta_info *sta,
+                               struct ieee80211_fast_tx *fast_tx,
+@@ -4244,8 +4396,14 @@ void __ieee80211_subif_start_xmit(struct
+               return;
+       }
++      sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
++
+       rcu_read_lock();
++      if (ieee80211_vif_is_mesh(&sdata->vif) &&
++          ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags))
++              goto out;
++
+       if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
+               goto out_free;
+@@ -4255,8 +4413,6 @@ void __ieee80211_subif_start_xmit(struct
+       skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb));
+       ieee80211_aggr_check(sdata, sta, skb);
+-      sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
+-
+       if (sta) {
+               struct ieee80211_fast_tx *fast_tx;
diff --git a/package/kernel/mac80211/patches/subsys/320-wifi-mac80211-use-mesh-header-cache-to-speed-up-mesh.patch b/package/kernel/mac80211/patches/subsys/320-wifi-mac80211-use-mesh-header-cache-to-speed-up-mesh.patch
new file mode 100644 (file)
index 0000000..e0d4e60
--- /dev/null
@@ -0,0 +1,70 @@
+From: Felix Fietkau <nbd@nbd.name>
+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.
+
+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;
+       }
+-      if (mesh_hdr->flags & MESH_FLAGS_AE) {
++      if ((mesh_hdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6)
++              entry = mesh_get_cached_hdr(sdata, mesh_hdr->eaddr1);
++      else if (!(mesh_hdr->flags & MESH_FLAGS_AE))
++              entry = mesh_get_cached_hdr(sdata, eth->h_dest);
++
++      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_
+       info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
+       info->control.vif = &sdata->vif;
+       info->control.jiffies = jiffies;
++      fwd_skb->dev = sdata->dev;
+       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_
+       }
+       IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
+-      fwd_skb->dev = sdata->dev;
+       ieee80211_add_pending_skb(local, fwd_skb);
+ rx_accept:
diff --git a/package/kernel/mac80211/patches/subsys/321-mac80211-fix-mesh-forwarding.patch b/package/kernel/mac80211/patches/subsys/321-mac80211-fix-mesh-forwarding.patch
new file mode 100644 (file)
index 0000000..d9af8c7
--- /dev/null
@@ -0,0 +1,32 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 20 Feb 2023 12:50:50 +0100
+Subject: [PATCH] mac80211: fix mesh forwarding
+
+Linearize packets (needed for forwarding A-MSDU subframes).
+Fix network header offset to fix flow dissector (and fair queueing).
+
+Fixes: 986e43b19ae9 ("wifi: mac80211: fix receiving A-MSDU frames on mesh interfaces")
+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_
+               if (skb_cow_head(fwd_skb, hdrlen - sizeof(struct ethhdr)))
+                       return RX_DROP_UNUSABLE;
++
++              if (skb_linearize(fwd_skb))
++                      return RX_DROP_UNUSABLE;
+       }
+       fwd_hdr = skb_push(fwd_skb, hdrlen - sizeof(struct ethhdr));
+@@ -2861,7 +2864,7 @@ ieee80211_rx_mesh_data(struct ieee80211_
+               hdrlen += ETH_ALEN;
+       else
+               fwd_skb->protocol = htons(fwd_skb->len - hdrlen);
+-      skb_set_network_header(fwd_skb, hdrlen);
++      skb_set_network_header(fwd_skb, hdrlen + 2);
+       info = IEEE80211_SKB_CB(fwd_skb);
+       memset(info, 0, sizeof(*info));
index 70d4e89c9094b1235371315199686b4ee8d4919e..817be9e4d2359ffc942ec993fc4e68b49bc60f82 100644 (file)
@@ -87,7 +87,7 @@
        CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
 --- a/net/mac80211/ieee80211_i.h
 +++ b/net/mac80211/ieee80211_i.h
-@@ -1520,6 +1520,7 @@ struct ieee80211_local {
+@@ -1536,6 +1536,7 @@ struct ieee80211_local {
        int dynamic_ps_forced_timeout;
  
        int user_power_level; /* in dBm, for all interfaces */