From: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
To: Navid Ghahremani <ghahramani.navid@gmail.com>,
Andrew Lunn <andrew+netdev@lunn.ch>
Cc: "David S . Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@kernel.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Junyang Han" <han.junyang@zte.com.cn>,
"Stefan Dösinger" <stefandoesinger@gmail.com>,
"Felix Fietkau" <nbd@nbd.name>,
netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-wireless@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Subject: [RFC PATCH net-next 4/5] net: ethernet: zte: zx279128s: offload flows to and from WLAN ports
Date: Wed, 11 Nov 2026 15:44:25 +0100 [thread overview]
Message-ID: <20261111144426.1321237-5-isasmendi.agus@gmail.com> (raw)
In-Reply-To: <20261111144426.1321237-1-isasmendi.agus@gmail.com>
Accept flows whose egress or ingress is a mac80211 AP interface, bound
to a WLAN logical port on first use:
- egress: the flowtable hands over the AP interface, or the bridge in
front of it (mac80211 fills no forwarding path, so the route stops at
the bridge); one step of the bridge's forwarding path for the next
hop MAC gives the interface. The classifier entry's egress field
takes the WLAN port instead of a bridge port; the fabric then writes
the rewritten frame to the WLAN RX ring, and the ring delivers it to
the interface.
- ingress: the flowtable reports the AP interface, or its bridge; the
flow's pre-NAT source address leads through the bridge's neighbour
entry and forwarding database to the interface. The entry is
installed like a switch port's, and its key is listed for the
interface's ingress hook, which injects the matching frames so that
they reach the entry.
The flows of a port are dropped when its interface is unregistered;
their traffic returns to the software path and the flowtable offers
them again once their hardware statistics stop.
Assisted-by: LLM
Signed-off-by: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
---
drivers/net/ethernet/zte/zx279128s-eth.h | 1 +
drivers/net/ethernet/zte/zx279128s-ppe.c | 92 ++++++++++++++++++-----
drivers/net/ethernet/zte/zx279128s-wlan.c | 9 ++-
3 files changed, 82 insertions(+), 20 deletions(-)
diff --git a/drivers/net/ethernet/zte/zx279128s-eth.h b/drivers/net/ethernet/zte/zx279128s-eth.h
index b5a547eade35..a5c8df1c60c3 100644
--- a/drivers/net/ethernet/zte/zx279128s-eth.h
+++ b/drivers/net/ethernet/zte/zx279128s-eth.h
@@ -480,6 +480,7 @@ void zx_set_trap_dmac(struct zx_eth_adapter *adapter, int idx, const u8 *mac);
extern const struct rhashtable_params zx_ppe_ht_params;
void zx_update_traps(struct zx_eth_adapter *adapter);
void zx_update_mac_tables(struct zx_eth_adapter *adapter);
+void zx_ppe_wlan_flush(struct zx_eth_adapter *adapter, int wlan_port);
int zx_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
void *type_data);
diff --git a/drivers/net/ethernet/zte/zx279128s-ppe.c b/drivers/net/ethernet/zte/zx279128s-ppe.c
index ff19d2cc67bd..e02d6bfbddd9 100644
--- a/drivers/net/ethernet/zte/zx279128s-ppe.c
+++ b/drivers/net/ethernet/zte/zx279128s-ppe.c
@@ -37,6 +37,8 @@ struct zx_ppe_entry {
u16 flow;
u16 nh;
u8 subnet;
+ s8 wlan_in; /* WLAN port of the ingress, or -1 */
+ s8 wlan_out; /* WLAN port of the egress, or -1 */
};
const struct rhashtable_params zx_ppe_ht_params = {
@@ -168,12 +170,13 @@ static void zx_ppe_slots(const struct zx_ppe_tuple *t, u16 slot[2])
}
/*
- * Classifier entry: rewrite index, egress port and forward flag, then the
- * key. Words 1-3 are the same constants in every entry of the stock
- * firmware, and so is the 0x44 in word 0.
+ * Classifier entry: rewrite index, egress port (a bridge port or a WLAN
+ * logical port) and forward flag, then the key. Words 1-3 are the same
+ * constants in every entry of the stock firmware, and so is the 0x44 in
+ * word 0.
*/
-static void zx_ppe_hash_entry(const struct zx_ppe_tuple *t, u16 flow, int port,
- u32 w[9])
+static void zx_ppe_hash_entry(const struct zx_ppe_tuple *t, u16 flow,
+ int egress, u32 w[9])
{
u8 key[20] = {};
u16 hw[7];
@@ -185,7 +188,7 @@ static void zx_ppe_hash_entry(const struct zx_ppe_tuple *t, u16 flow, int port,
for (i = 0; i < 7; i++)
zx_ppe_put_bits(key, 24 + 16 * i, 16, hw[i]);
w[0] = FIELD_PREP(ZX_CLA_W0_FLOW, flow) | ZX_CLA_W0_FWD |
- FIELD_PREP(ZX_CLA_W0_EGRESS, ZX_BRPORT(port)) | ZX_CLA_W0_BITS;
+ FIELD_PREP(ZX_CLA_W0_EGRESS, egress) | ZX_CLA_W0_BITS;
w[1] = 0xfa11c000;
w[2] = 0x00000608;
w[3] = 0x80000000;
@@ -305,22 +308,17 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
__be32 new_saddr, new_daddr;
u16 slot[2];
u32 w[17];
- int i, ret, port, subnet, flow, nh;
+ int i, ret, egress, subnet, flow, nh, ingress_ifindex = 0;
+ int wlan_in = -1, wlan_out = -1;
if (rhashtable_lookup_fast(&adapter->ppe_flows, &f->cookie, zx_ppe_ht_params))
return -EEXIST;
- /* the hardware only sees frames from our switch ports */
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META)) {
struct flow_match_meta match;
flow_rule_match_meta(rule, &match);
- for (i = 0; i < ZX_NUM_PORTS; i++)
- if (adapter->ports[i] &&
- adapter->ports[i]->ifindex == match.key->ingress_ifindex)
- break;
- if (i == ZX_NUM_PORTS)
- return -EOPNOTSUPP;
+ ingress_ifindex = match.key->ingress_ifindex;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
@@ -363,6 +361,23 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
rw.new_sport = t.sport;
rw.new_dport = t.dport;
+ /*
+ * The hardware sees frames from our switch ports, and those a WLAN
+ * port's ingress hook injects for it (zx279128s-wlan.c).
+ */
+ if (ingress_ifindex) {
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ if (adapter->ports[i] &&
+ adapter->ports[i]->ifindex == ingress_ifindex)
+ break;
+ if (i == ZX_NUM_PORTS) {
+ wlan_in = zx_wlan_port_of_ingress(adapter, ingress_ifindex,
+ t.saddr);
+ if (wlan_in < 0)
+ return -EOPNOTSUPP;
+ }
+ }
+
flow_action_for_each(i, act, &rule->action) {
switch (act->id) {
case FLOW_ACTION_MANGLE:
@@ -410,11 +425,19 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
}
}
- if (!odev || !zx_eth_is_port(odev))
+ if (!odev)
return -EOPNOTSUPP;
- port = ((struct zx_eth_priv *)netdev_priv(odev))->port;
if (!is_valid_ether_addr(eth.h_source) || !is_valid_ether_addr(eth.h_dest))
return -EOPNOTSUPP;
+ if (zx_eth_is_port(odev)) {
+ egress = ZX_BRPORT(((struct zx_eth_priv *)netdev_priv(odev))->port);
+ } else {
+ /* an AP interface, or the bridge in front of it */
+ wlan_out = zx_wlan_port_of_egress(adapter, odev, eth.h_dest);
+ if (wlan_out < 0)
+ return -EOPNOTSUPP;
+ egress = ZX_WLAN_PORT(wlan_out);
+ }
rw.sip = new_saddr != t.saddr;
rw.dip = new_daddr != t.daddr;
@@ -462,7 +485,7 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
ret = zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_AGING, slot[1], w, 0);
if (ret)
goto err_nh;
- zx_ppe_hash_entry(&t, flow, port, w);
+ zx_ppe_hash_entry(&t, flow, egress, w);
ret = zx_pp_write(adapter, ZX_CLA_BASE,
slot[1] < ZX_PPE_HASH0_SIZE ? ZX_CLA_HASH0 : ZX_CLA_HASH1,
slot[1] % ZX_PPE_HASH0_SIZE, w, 9);
@@ -474,11 +497,18 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
e->flow = flow;
e->nh = nh;
e->subnet = subnet;
+ e->wlan_in = wlan_in;
+ e->wlan_out = wlan_out;
ret = rhashtable_insert_fast(&adapter->ppe_flows, &e->node, zx_ppe_ht_params);
if (ret)
goto err_entry;
set_bit(e->hash, adapter->ppe_hash_used);
adapter->ppe_count++;
+ /* the frames of a WLAN ingress flow only reach the entry if injected */
+ if (wlan_in >= 0)
+ zx_wlan_flow_set(adapter, flow,
+ zx_wlan_flow_key(t.proto, t.saddr, t.daddr,
+ t.sport, t.dport), wlan_in);
return 0;
err_entry:
@@ -499,6 +529,8 @@ static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
static void zx_ppe_entry_remove(struct zx_eth_adapter *adapter,
struct zx_ppe_entry *e)
{
+ if (e->wlan_in >= 0)
+ zx_wlan_flow_set(adapter, e->flow, 0, 0);
zx_pp_write(adapter, ZX_CLA_BASE,
e->hash < ZX_PPE_HASH0_SIZE ? ZX_CLA_HASH0 : ZX_CLA_HASH1,
e->hash % ZX_PPE_HASH0_SIZE, NULL, 0);
@@ -524,6 +556,32 @@ static int zx_ppe_flow_del(struct zx_eth_adapter *adapter,
return 0;
}
+/*
+ * Drop the flows of a WLAN port whose interface goes away; their traffic
+ * returns to the software path, and the flowtable offers them again once
+ * their statistics stop. Called with ppe_lock held.
+ */
+void zx_ppe_wlan_flush(struct zx_eth_adapter *adapter, int wlan_port)
+{
+ struct rhashtable_iter iter;
+ struct zx_ppe_entry *e;
+
+ rhashtable_walk_enter(&adapter->ppe_flows, &iter);
+ rhashtable_walk_start(&iter);
+ while ((e = rhashtable_walk_next(&iter))) {
+ if (IS_ERR(e))
+ continue;
+ if (e->wlan_in != wlan_port && e->wlan_out != wlan_port)
+ continue;
+ rhashtable_remove_fast(&adapter->ppe_flows, &e->node,
+ zx_ppe_ht_params);
+ zx_ppe_entry_remove(adapter, e);
+ kfree(e);
+ }
+ rhashtable_walk_stop(&iter);
+ rhashtable_walk_exit(&iter);
+}
+
/* No packet counters: report use from the entry's hit flag */
static int zx_ppe_flow_stats(struct zx_eth_adapter *adapter,
struct flow_cls_offload *f)
diff --git a/drivers/net/ethernet/zte/zx279128s-wlan.c b/drivers/net/ethernet/zte/zx279128s-wlan.c
index 17ba32bd993a..6c6f6a36d34f 100644
--- a/drivers/net/ethernet/zte/zx279128s-wlan.c
+++ b/drivers/net/ethernet/zte/zx279128s-wlan.c
@@ -707,9 +707,12 @@ static int zx_wlan_netdev_event(struct notifier_block *nb, unsigned long event,
if (event != NETDEV_UNREGISTER || !zx_wlan_is_ap(dev))
return NOTIFY_DONE;
mutex_lock(&w->adapter->ppe_lock);
- for (i = 0; i < ZX_WLAN_PORTS; i++)
- if (w->ports[i].vif == dev)
- zx_wlan_port_put(w, i);
+ for (i = 0; i < ZX_WLAN_PORTS; i++) {
+ if (w->ports[i].vif != dev)
+ continue;
+ zx_ppe_wlan_flush(w->adapter, i);
+ zx_wlan_port_put(w, i);
+ }
mutex_unlock(&w->adapter->ppe_lock);
return NOTIFY_DONE;
}
--
2.30.2
next prev parent reply other threads:[~2026-11-11 14:44 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 14:44 [RFC PATCH net-next 0/5] net: ethernet: zte: zx279128s: offload flows to and from WLAN interfaces Agustin Santiago Isasmendi
2026-11-11 14:44 ` [RFC PATCH net-next 1/5] dt-bindings: net: zte,zx279128s-gmac: add the WLAN DMA interrupt Agustin Santiago Isasmendi
2026-11-11 14:44 ` [RFC PATCH net-next 2/5] ARM: dts: zte: zx279128s: " Agustin Santiago Isasmendi
2026-11-11 14:44 ` [RFC PATCH net-next 3/5] net: ethernet: zte: zx279128s: WLAN DMA ring and logical ports Agustin Santiago Isasmendi
2026-11-11 14:44 ` Agustin Santiago Isasmendi [this message]
2026-11-11 14:44 ` [RFC PATCH net-next 5/5] net: ethernet: zte: zx279128s: debugfs knobs for the WLAN ring Agustin Santiago Isasmendi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111144426.1321237-5-isasmendi.agus@gmail.com \
--to=isasmendi.agus@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@kernel.org \
--cc=ghahramani.navid@gmail.com \
--cc=han.junyang@zte.com.cn \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
--cc=nbd@nbd.name \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--cc=stefandoesinger@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®