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@nbd.name>
This commit is contained in:
Felix Fietkau 2023-02-15 19:40:02 +01:00
parent 7ae4716243
commit 57db2280a2
6 changed files with 939 additions and 1 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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:

View File

@ -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));

View 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 */