[openwrt/openwrt] mac80211: fix mesh issues and improve performance

LEDE Commits lede-commits at lists.infradead.org
Mon Feb 20 04:00:18 PST 2023


nbd pushed a commit to openwrt/openwrt.git, branch master:
https://git.openwrt.org/57db2280a2155c39f545ac712766a849cf45f62c

commit 57db2280a2155c39f545ac712766a849cf45f62c
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Wed Feb 15 19:40:02 2023 +0100

    mac80211: fix mesh issues and improve performance
    
    fix forwarding received mesh a-msdu packets
    add fast xmit support for mesh to improve performance
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 ...-wifi-mac80211-fix-qos-on-mesh-interfaces.patch |  35 +
 ...11-fix-race-in-mesh-sequence-number-assig.patch |  37 +
 .../319-wifi-mac80211-mesh-fast-xmit-support.patch | 764 +++++++++++++++++++++
 ...11-use-mesh-header-cache-to-speed-up-mesh.patch |  70 ++
 .../subsys/321-mac80211-fix-mesh-forwarding.patch  |  32 +
 .../500-mac80211_configure_antenna_gain.patch      |   2 +-
 6 files changed, 939 insertions(+), 1 deletion(-)

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
index 0000000000..c60a88d2a6
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/317-wifi-mac80211-fix-qos-on-mesh-interfaces.patch
@@ -0,0 +1,35 @@
+From: Felix Fietkau <nbd at 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 at 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
index 0000000000..05e368cd2e
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/318-wifi-mac80211-fix-race-in-mesh-sequence-number-assig.patch
@@ -0,0 +1,37 @@
+From: Felix Fietkau <nbd at 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 at 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
index 0000000000..4bd3d4c092
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/319-wifi-mac80211-mesh-fast-xmit-support.patch
@@ -0,0 +1,764 @@
+From: Sriram R <quic_srirrama at 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 at nbd.name>
+Signed-off-by: Sriram R <quic_srirrama at quicinc.com>
+Signed-off-by: Felix Fietkau <nbd at 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
index 0000000000..e0d4e60ed7
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/320-wifi-mac80211-use-mesh-header-cache-to-speed-up-mesh.patch
@@ -0,0 +1,70 @@
+From: Felix Fietkau <nbd at 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 at 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
index 0000000000..d9af8c7929
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/321-mac80211-fix-mesh-forwarding.patch
@@ -0,0 +1,32 @@
+From: Felix Fietkau <nbd at 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 at 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));
diff --git a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
index 70d4e89c90..817be9e4d2 100644
--- a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
+++ b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
@@ -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 */




More information about the lede-commits mailing list