xref: /linux/drivers/net/ethernet/airoha/airoha_ppe.c (revision 78bd03ee1f20a267d2c218884b66041b3508ac9c)
100a76783SLorenzo Bianconi // SPDX-License-Identifier: GPL-2.0-only
200a76783SLorenzo Bianconi /*
300a76783SLorenzo Bianconi  * Copyright (c) 2025 AIROHA Inc
400a76783SLorenzo Bianconi  * Author: Lorenzo Bianconi <lorenzo@kernel.org>
500a76783SLorenzo Bianconi  */
600a76783SLorenzo Bianconi 
700a76783SLorenzo Bianconi #include <linux/ip.h>
800a76783SLorenzo Bianconi #include <linux/ipv6.h>
900a76783SLorenzo Bianconi #include <linux/rhashtable.h>
1000a76783SLorenzo Bianconi #include <net/ipv6.h>
1100a76783SLorenzo Bianconi #include <net/pkt_cls.h>
1200a76783SLorenzo Bianconi 
1300a76783SLorenzo Bianconi #include "airoha_npu.h"
1400a76783SLorenzo Bianconi #include "airoha_regs.h"
1500a76783SLorenzo Bianconi #include "airoha_eth.h"
1600a76783SLorenzo Bianconi 
1700a76783SLorenzo Bianconi static DEFINE_MUTEX(flow_offload_mutex);
1800a76783SLorenzo Bianconi static DEFINE_SPINLOCK(ppe_lock);
1900a76783SLorenzo Bianconi 
2000a76783SLorenzo Bianconi static const struct rhashtable_params airoha_flow_table_params = {
2100a76783SLorenzo Bianconi 	.head_offset = offsetof(struct airoha_flow_table_entry, node),
2200a76783SLorenzo Bianconi 	.key_offset = offsetof(struct airoha_flow_table_entry, cookie),
2300a76783SLorenzo Bianconi 	.key_len = sizeof(unsigned long),
2400a76783SLorenzo Bianconi 	.automatic_shrinking = true,
2500a76783SLorenzo Bianconi };
2600a76783SLorenzo Bianconi 
27b4916f67SLorenzo Bianconi static const struct rhashtable_params airoha_l2_flow_table_params = {
28b4916f67SLorenzo Bianconi 	.head_offset = offsetof(struct airoha_flow_table_entry, l2_node),
29b4916f67SLorenzo Bianconi 	.key_offset = offsetof(struct airoha_flow_table_entry, data.bridge),
30b4916f67SLorenzo Bianconi 	.key_len = 2 * ETH_ALEN,
31b4916f67SLorenzo Bianconi 	.automatic_shrinking = true,
32b4916f67SLorenzo Bianconi };
33b4916f67SLorenzo Bianconi 
3400a76783SLorenzo Bianconi static bool airoha_ppe2_is_enabled(struct airoha_eth *eth)
3500a76783SLorenzo Bianconi {
3600a76783SLorenzo Bianconi 	return airoha_fe_rr(eth, REG_PPE_GLO_CFG(1)) & PPE_GLO_CFG_EN_MASK;
3700a76783SLorenzo Bianconi }
3800a76783SLorenzo Bianconi 
3900a76783SLorenzo Bianconi static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
4000a76783SLorenzo Bianconi {
4100a76783SLorenzo Bianconi 	u16 timestamp = airoha_fe_rr(ppe->eth, REG_FE_FOE_TS);
4200a76783SLorenzo Bianconi 
4300a76783SLorenzo Bianconi 	return FIELD_GET(AIROHA_FOE_IB1_BIND_TIMESTAMP, timestamp);
4400a76783SLorenzo Bianconi }
4500a76783SLorenzo Bianconi 
4600a76783SLorenzo Bianconi static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
4700a76783SLorenzo Bianconi {
4800a76783SLorenzo Bianconi 	u32 sram_tb_size, sram_num_entries, dram_num_entries;
4900a76783SLorenzo Bianconi 	struct airoha_eth *eth = ppe->eth;
5000a76783SLorenzo Bianconi 	int i;
5100a76783SLorenzo Bianconi 
5200a76783SLorenzo Bianconi 	sram_tb_size = PPE_SRAM_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
5300a76783SLorenzo Bianconi 	dram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(PPE_DRAM_NUM_ENTRIES);
5400a76783SLorenzo Bianconi 
5500a76783SLorenzo Bianconi 	for (i = 0; i < PPE_NUM; i++) {
5600a76783SLorenzo Bianconi 		int p;
5700a76783SLorenzo Bianconi 
5800a76783SLorenzo Bianconi 		airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
5900a76783SLorenzo Bianconi 			     ppe->foe_dma + sram_tb_size);
6000a76783SLorenzo Bianconi 
6100a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_BND_AGE0(i),
6200a76783SLorenzo Bianconi 			      PPE_BIND_AGE0_DELTA_NON_L4 |
6300a76783SLorenzo Bianconi 			      PPE_BIND_AGE0_DELTA_UDP,
6400a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_BIND_AGE0_DELTA_NON_L4, 1) |
6500a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_BIND_AGE0_DELTA_UDP, 12));
6600a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_BND_AGE1(i),
6700a76783SLorenzo Bianconi 			      PPE_BIND_AGE1_DELTA_TCP_FIN |
6800a76783SLorenzo Bianconi 			      PPE_BIND_AGE1_DELTA_TCP,
6900a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_BIND_AGE1_DELTA_TCP_FIN, 1) |
7000a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_BIND_AGE1_DELTA_TCP, 7));
7100a76783SLorenzo Bianconi 
7200a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_TB_HASH_CFG(i),
7300a76783SLorenzo Bianconi 			      PPE_SRAM_TABLE_EN_MASK |
7400a76783SLorenzo Bianconi 			      PPE_SRAM_HASH1_EN_MASK |
7500a76783SLorenzo Bianconi 			      PPE_DRAM_TABLE_EN_MASK |
7600a76783SLorenzo Bianconi 			      PPE_SRAM_HASH0_MODE_MASK |
7700a76783SLorenzo Bianconi 			      PPE_SRAM_HASH1_MODE_MASK |
7800a76783SLorenzo Bianconi 			      PPE_DRAM_HASH0_MODE_MASK |
7900a76783SLorenzo Bianconi 			      PPE_DRAM_HASH1_MODE_MASK,
8000a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_TABLE_EN_MASK, 1) |
8100a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_HASH1_EN_MASK, 1) |
8200a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_HASH1_MODE_MASK, 1) |
8300a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_DRAM_HASH1_MODE_MASK, 3));
8400a76783SLorenzo Bianconi 
8500a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_TB_CFG(i),
8600a76783SLorenzo Bianconi 			      PPE_TB_CFG_SEARCH_MISS_MASK |
87a98326c1SLorenzo Bianconi 			      PPE_TB_CFG_KEEPALIVE_MASK |
8800a76783SLorenzo Bianconi 			      PPE_TB_ENTRY_SIZE_MASK,
8900a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_TB_CFG_SEARCH_MISS_MASK, 3) |
9000a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_TB_ENTRY_SIZE_MASK, 0));
9100a76783SLorenzo Bianconi 
9200a76783SLorenzo Bianconi 		airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
9300a76783SLorenzo Bianconi 
9400a76783SLorenzo Bianconi 		for (p = 0; p < ARRAY_SIZE(eth->ports); p++)
9500a76783SLorenzo Bianconi 			airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
9600a76783SLorenzo Bianconi 				      FP0_EGRESS_MTU_MASK |
9700a76783SLorenzo Bianconi 				      FP1_EGRESS_MTU_MASK,
9800a76783SLorenzo Bianconi 				      FIELD_PREP(FP0_EGRESS_MTU_MASK,
9900a76783SLorenzo Bianconi 						 AIROHA_MAX_MTU) |
10000a76783SLorenzo Bianconi 				      FIELD_PREP(FP1_EGRESS_MTU_MASK,
10100a76783SLorenzo Bianconi 						 AIROHA_MAX_MTU));
10200a76783SLorenzo Bianconi 	}
10300a76783SLorenzo Bianconi 
10400a76783SLorenzo Bianconi 	if (airoha_ppe2_is_enabled(eth)) {
10500a76783SLorenzo Bianconi 		sram_num_entries =
106b81e0f2bSLorenzo Bianconi 			PPE_RAM_NUM_ENTRIES_SHIFT(PPE1_SRAM_NUM_DATA_ENTRIES);
10700a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
10800a76783SLorenzo Bianconi 			      PPE_SRAM_TB_NUM_ENTRY_MASK |
10900a76783SLorenzo Bianconi 			      PPE_DRAM_TB_NUM_ENTRY_MASK,
11000a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
11100a76783SLorenzo Bianconi 					 sram_num_entries) |
11200a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
11300a76783SLorenzo Bianconi 					 dram_num_entries));
11400a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_TB_CFG(1),
11500a76783SLorenzo Bianconi 			      PPE_SRAM_TB_NUM_ENTRY_MASK |
11600a76783SLorenzo Bianconi 			      PPE_DRAM_TB_NUM_ENTRY_MASK,
11700a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
11800a76783SLorenzo Bianconi 					 sram_num_entries) |
11900a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
12000a76783SLorenzo Bianconi 					 dram_num_entries));
12100a76783SLorenzo Bianconi 	} else {
12200a76783SLorenzo Bianconi 		sram_num_entries =
123b81e0f2bSLorenzo Bianconi 			PPE_RAM_NUM_ENTRIES_SHIFT(PPE_SRAM_NUM_DATA_ENTRIES);
12400a76783SLorenzo Bianconi 		airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
12500a76783SLorenzo Bianconi 			      PPE_SRAM_TB_NUM_ENTRY_MASK |
12600a76783SLorenzo Bianconi 			      PPE_DRAM_TB_NUM_ENTRY_MASK,
12700a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
12800a76783SLorenzo Bianconi 					 sram_num_entries) |
12900a76783SLorenzo Bianconi 			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
13000a76783SLorenzo Bianconi 					 dram_num_entries));
13100a76783SLorenzo Bianconi 	}
13200a76783SLorenzo Bianconi }
13300a76783SLorenzo Bianconi 
13400a76783SLorenzo Bianconi static void airoha_ppe_flow_mangle_eth(const struct flow_action_entry *act, void *eth)
13500a76783SLorenzo Bianconi {
13600a76783SLorenzo Bianconi 	void *dest = eth + act->mangle.offset;
13700a76783SLorenzo Bianconi 	const void *src = &act->mangle.val;
13800a76783SLorenzo Bianconi 
13900a76783SLorenzo Bianconi 	if (act->mangle.offset > 8)
14000a76783SLorenzo Bianconi 		return;
14100a76783SLorenzo Bianconi 
14200a76783SLorenzo Bianconi 	if (act->mangle.mask == 0xffff) {
14300a76783SLorenzo Bianconi 		src += 2;
14400a76783SLorenzo Bianconi 		dest += 2;
14500a76783SLorenzo Bianconi 	}
14600a76783SLorenzo Bianconi 
14700a76783SLorenzo Bianconi 	memcpy(dest, src, act->mangle.mask ? 2 : 4);
14800a76783SLorenzo Bianconi }
14900a76783SLorenzo Bianconi 
15000a76783SLorenzo Bianconi static int airoha_ppe_flow_mangle_ports(const struct flow_action_entry *act,
15100a76783SLorenzo Bianconi 					struct airoha_flow_data *data)
15200a76783SLorenzo Bianconi {
15300a76783SLorenzo Bianconi 	u32 val = be32_to_cpu((__force __be32)act->mangle.val);
15400a76783SLorenzo Bianconi 
15500a76783SLorenzo Bianconi 	switch (act->mangle.offset) {
15600a76783SLorenzo Bianconi 	case 0:
15700a76783SLorenzo Bianconi 		if ((__force __be32)act->mangle.mask == ~cpu_to_be32(0xffff))
15800a76783SLorenzo Bianconi 			data->dst_port = cpu_to_be16(val);
15900a76783SLorenzo Bianconi 		else
16000a76783SLorenzo Bianconi 			data->src_port = cpu_to_be16(val >> 16);
16100a76783SLorenzo Bianconi 		break;
16200a76783SLorenzo Bianconi 	case 2:
16300a76783SLorenzo Bianconi 		data->dst_port = cpu_to_be16(val);
16400a76783SLorenzo Bianconi 		break;
16500a76783SLorenzo Bianconi 	default:
16600a76783SLorenzo Bianconi 		return -EINVAL;
16700a76783SLorenzo Bianconi 	}
16800a76783SLorenzo Bianconi 
16900a76783SLorenzo Bianconi 	return 0;
17000a76783SLorenzo Bianconi }
17100a76783SLorenzo Bianconi 
17200a76783SLorenzo Bianconi static int airoha_ppe_flow_mangle_ipv4(const struct flow_action_entry *act,
17300a76783SLorenzo Bianconi 				       struct airoha_flow_data *data)
17400a76783SLorenzo Bianconi {
17500a76783SLorenzo Bianconi 	__be32 *dest;
17600a76783SLorenzo Bianconi 
17700a76783SLorenzo Bianconi 	switch (act->mangle.offset) {
17800a76783SLorenzo Bianconi 	case offsetof(struct iphdr, saddr):
17900a76783SLorenzo Bianconi 		dest = &data->v4.src_addr;
18000a76783SLorenzo Bianconi 		break;
18100a76783SLorenzo Bianconi 	case offsetof(struct iphdr, daddr):
18200a76783SLorenzo Bianconi 		dest = &data->v4.dst_addr;
18300a76783SLorenzo Bianconi 		break;
18400a76783SLorenzo Bianconi 	default:
18500a76783SLorenzo Bianconi 		return -EINVAL;
18600a76783SLorenzo Bianconi 	}
18700a76783SLorenzo Bianconi 
18800a76783SLorenzo Bianconi 	memcpy(dest, &act->mangle.val, sizeof(u32));
18900a76783SLorenzo Bianconi 
19000a76783SLorenzo Bianconi 	return 0;
19100a76783SLorenzo Bianconi }
19200a76783SLorenzo Bianconi 
19300a76783SLorenzo Bianconi static int airoha_get_dsa_port(struct net_device **dev)
19400a76783SLorenzo Bianconi {
19500a76783SLorenzo Bianconi #if IS_ENABLED(CONFIG_NET_DSA)
19600a76783SLorenzo Bianconi 	struct dsa_port *dp = dsa_port_from_netdev(*dev);
19700a76783SLorenzo Bianconi 
19800a76783SLorenzo Bianconi 	if (IS_ERR(dp))
19900a76783SLorenzo Bianconi 		return -ENODEV;
20000a76783SLorenzo Bianconi 
20100a76783SLorenzo Bianconi 	*dev = dsa_port_to_conduit(dp);
20200a76783SLorenzo Bianconi 	return dp->index;
20300a76783SLorenzo Bianconi #else
20400a76783SLorenzo Bianconi 	return -ENODEV;
20500a76783SLorenzo Bianconi #endif
20600a76783SLorenzo Bianconi }
20700a76783SLorenzo Bianconi 
208cd53f622SLorenzo Bianconi static void airoha_ppe_foe_set_bridge_addrs(struct airoha_foe_bridge *br,
209cd53f622SLorenzo Bianconi 					    struct ethhdr *eh)
210cd53f622SLorenzo Bianconi {
211cd53f622SLorenzo Bianconi 	br->dest_mac_hi = get_unaligned_be32(eh->h_dest);
212cd53f622SLorenzo Bianconi 	br->dest_mac_lo = get_unaligned_be16(eh->h_dest + 4);
213cd53f622SLorenzo Bianconi 	br->src_mac_hi = get_unaligned_be16(eh->h_source);
214cd53f622SLorenzo Bianconi 	br->src_mac_lo = get_unaligned_be32(eh->h_source + 2);
215cd53f622SLorenzo Bianconi }
216cd53f622SLorenzo Bianconi 
21709bccf56SLorenzo Bianconi static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
21809bccf56SLorenzo Bianconi 					struct airoha_foe_entry *hwe,
21900a76783SLorenzo Bianconi 					struct net_device *dev, int type,
22000a76783SLorenzo Bianconi 					struct airoha_flow_data *data,
22100a76783SLorenzo Bianconi 					int l4proto)
22200a76783SLorenzo Bianconi {
22300a76783SLorenzo Bianconi 	int dsa_port = airoha_get_dsa_port(&dev);
22400a76783SLorenzo Bianconi 	struct airoha_foe_mac_info_common *l2;
22500a76783SLorenzo Bianconi 	u32 qdata, ports_pad, val;
226a869d3a5SLorenzo Bianconi 	u8 smac_id = 0xf;
22700a76783SLorenzo Bianconi 
22800a76783SLorenzo Bianconi 	memset(hwe, 0, sizeof(*hwe));
22900a76783SLorenzo Bianconi 
23000a76783SLorenzo Bianconi 	val = FIELD_PREP(AIROHA_FOE_IB1_BIND_STATE, AIROHA_FOE_STATE_BIND) |
23100a76783SLorenzo Bianconi 	      FIELD_PREP(AIROHA_FOE_IB1_BIND_PACKET_TYPE, type) |
23200a76783SLorenzo Bianconi 	      FIELD_PREP(AIROHA_FOE_IB1_BIND_UDP, l4proto == IPPROTO_UDP) |
23300a76783SLorenzo Bianconi 	      FIELD_PREP(AIROHA_FOE_IB1_BIND_VLAN_LAYER, data->vlan.num) |
23400a76783SLorenzo Bianconi 	      FIELD_PREP(AIROHA_FOE_IB1_BIND_VPM, data->vlan.num) |
23500a76783SLorenzo Bianconi 	      AIROHA_FOE_IB1_BIND_TTL;
23600a76783SLorenzo Bianconi 	hwe->ib1 = val;
23700a76783SLorenzo Bianconi 
2389cd451d4SLorenzo Bianconi 	val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f) |
2399cd451d4SLorenzo Bianconi 	      AIROHA_FOE_IB2_PSE_QOS;
24000a76783SLorenzo Bianconi 	if (dsa_port >= 0)
24100a76783SLorenzo Bianconi 		val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, dsa_port);
24200a76783SLorenzo Bianconi 
24300a76783SLorenzo Bianconi 	if (dev) {
24400a76783SLorenzo Bianconi 		struct airoha_gdm_port *port = netdev_priv(dev);
24500a76783SLorenzo Bianconi 		u8 pse_port;
24600a76783SLorenzo Bianconi 
24709bccf56SLorenzo Bianconi 		if (!airoha_is_valid_gdm_port(eth, port))
24809bccf56SLorenzo Bianconi 			return -EINVAL;
24909bccf56SLorenzo Bianconi 
2509cd451d4SLorenzo Bianconi 		if (dsa_port >= 0)
25100a76783SLorenzo Bianconi 			pse_port = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
2529cd451d4SLorenzo Bianconi 		else
2539cd451d4SLorenzo Bianconi 			pse_port = 2; /* uplink relies on GDM2 loopback */
25400a76783SLorenzo Bianconi 		val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port);
255c683e378SLorenzo Bianconi 
256c683e378SLorenzo Bianconi 		/* For downlink traffic consume SRAM memory for hw forwarding
257c683e378SLorenzo Bianconi 		 * descriptors queue.
258c683e378SLorenzo Bianconi 		 */
259c683e378SLorenzo Bianconi 		if (airhoa_is_lan_gdm_port(port))
260c683e378SLorenzo Bianconi 			val |= AIROHA_FOE_IB2_FAST_PATH;
261a869d3a5SLorenzo Bianconi 
262a869d3a5SLorenzo Bianconi 		smac_id = port->id;
26300a76783SLorenzo Bianconi 	}
26400a76783SLorenzo Bianconi 
26500a76783SLorenzo Bianconi 	if (is_multicast_ether_addr(data->eth.h_dest))
26600a76783SLorenzo Bianconi 		val |= AIROHA_FOE_IB2_MULTICAST;
26700a76783SLorenzo Bianconi 
26800a76783SLorenzo Bianconi 	ports_pad = 0xa5a5a500 | (l4proto & 0xff);
26900a76783SLorenzo Bianconi 	if (type == PPE_PKT_TYPE_IPV4_ROUTE)
27000a76783SLorenzo Bianconi 		hwe->ipv4.orig_tuple.ports = ports_pad;
27100a76783SLorenzo Bianconi 	if (type == PPE_PKT_TYPE_IPV6_ROUTE_3T)
27200a76783SLorenzo Bianconi 		hwe->ipv6.ports = ports_pad;
27300a76783SLorenzo Bianconi 
27400a76783SLorenzo Bianconi 	qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f);
27500a76783SLorenzo Bianconi 	if (type == PPE_PKT_TYPE_BRIDGE) {
276cd53f622SLorenzo Bianconi 		airoha_ppe_foe_set_bridge_addrs(&hwe->bridge, &data->eth);
27700a76783SLorenzo Bianconi 		hwe->bridge.data = qdata;
27800a76783SLorenzo Bianconi 		hwe->bridge.ib2 = val;
27900a76783SLorenzo Bianconi 		l2 = &hwe->bridge.l2.common;
28000a76783SLorenzo Bianconi 	} else if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
28100a76783SLorenzo Bianconi 		hwe->ipv6.data = qdata;
28200a76783SLorenzo Bianconi 		hwe->ipv6.ib2 = val;
28300a76783SLorenzo Bianconi 		l2 = &hwe->ipv6.l2;
28400a76783SLorenzo Bianconi 	} else {
28500a76783SLorenzo Bianconi 		hwe->ipv4.data = qdata;
28600a76783SLorenzo Bianconi 		hwe->ipv4.ib2 = val;
28700a76783SLorenzo Bianconi 		l2 = &hwe->ipv4.l2.common;
28800a76783SLorenzo Bianconi 	}
28900a76783SLorenzo Bianconi 
29000a76783SLorenzo Bianconi 	l2->dest_mac_hi = get_unaligned_be32(data->eth.h_dest);
29100a76783SLorenzo Bianconi 	l2->dest_mac_lo = get_unaligned_be16(data->eth.h_dest + 4);
29200a76783SLorenzo Bianconi 	if (type <= PPE_PKT_TYPE_IPV4_DSLITE) {
29300a76783SLorenzo Bianconi 		l2->src_mac_hi = get_unaligned_be32(data->eth.h_source);
29400a76783SLorenzo Bianconi 		hwe->ipv4.l2.src_mac_lo =
29500a76783SLorenzo Bianconi 			get_unaligned_be16(data->eth.h_source + 4);
29600a76783SLorenzo Bianconi 	} else {
297a869d3a5SLorenzo Bianconi 		l2->src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID, smac_id);
29800a76783SLorenzo Bianconi 	}
29900a76783SLorenzo Bianconi 
30000a76783SLorenzo Bianconi 	if (data->vlan.num) {
30100a76783SLorenzo Bianconi 		l2->etype = dsa_port >= 0 ? BIT(dsa_port) : 0;
30200a76783SLorenzo Bianconi 		l2->vlan1 = data->vlan.hdr[0].id;
30300a76783SLorenzo Bianconi 		if (data->vlan.num == 2)
30400a76783SLorenzo Bianconi 			l2->vlan2 = data->vlan.hdr[1].id;
30500a76783SLorenzo Bianconi 	} else if (dsa_port >= 0) {
30600a76783SLorenzo Bianconi 		l2->etype = BIT(15) | BIT(dsa_port);
30700a76783SLorenzo Bianconi 	} else if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
30800a76783SLorenzo Bianconi 		l2->etype = ETH_P_IPV6;
30900a76783SLorenzo Bianconi 	} else {
31000a76783SLorenzo Bianconi 		l2->etype = ETH_P_IP;
31100a76783SLorenzo Bianconi 	}
31200a76783SLorenzo Bianconi 
31300a76783SLorenzo Bianconi 	return 0;
31400a76783SLorenzo Bianconi }
31500a76783SLorenzo Bianconi 
31600a76783SLorenzo Bianconi static int airoha_ppe_foe_entry_set_ipv4_tuple(struct airoha_foe_entry *hwe,
31700a76783SLorenzo Bianconi 					       struct airoha_flow_data *data,
31800a76783SLorenzo Bianconi 					       bool egress)
31900a76783SLorenzo Bianconi {
32000a76783SLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
32100a76783SLorenzo Bianconi 	struct airoha_foe_ipv4_tuple *t;
32200a76783SLorenzo Bianconi 
32300a76783SLorenzo Bianconi 	switch (type) {
32400a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_HNAPT:
32500a76783SLorenzo Bianconi 		if (egress) {
32600a76783SLorenzo Bianconi 			t = &hwe->ipv4.new_tuple;
32700a76783SLorenzo Bianconi 			break;
32800a76783SLorenzo Bianconi 		}
32900a76783SLorenzo Bianconi 		fallthrough;
33000a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_DSLITE:
33100a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_ROUTE:
33200a76783SLorenzo Bianconi 		t = &hwe->ipv4.orig_tuple;
33300a76783SLorenzo Bianconi 		break;
33400a76783SLorenzo Bianconi 	default:
33500a76783SLorenzo Bianconi 		WARN_ON_ONCE(1);
33600a76783SLorenzo Bianconi 		return -EINVAL;
33700a76783SLorenzo Bianconi 	}
33800a76783SLorenzo Bianconi 
33900a76783SLorenzo Bianconi 	t->src_ip = be32_to_cpu(data->v4.src_addr);
34000a76783SLorenzo Bianconi 	t->dest_ip = be32_to_cpu(data->v4.dst_addr);
34100a76783SLorenzo Bianconi 
34200a76783SLorenzo Bianconi 	if (type != PPE_PKT_TYPE_IPV4_ROUTE) {
34300a76783SLorenzo Bianconi 		t->src_port = be16_to_cpu(data->src_port);
34400a76783SLorenzo Bianconi 		t->dest_port = be16_to_cpu(data->dst_port);
34500a76783SLorenzo Bianconi 	}
34600a76783SLorenzo Bianconi 
34700a76783SLorenzo Bianconi 	return 0;
34800a76783SLorenzo Bianconi }
34900a76783SLorenzo Bianconi 
35000a76783SLorenzo Bianconi static int airoha_ppe_foe_entry_set_ipv6_tuple(struct airoha_foe_entry *hwe,
35100a76783SLorenzo Bianconi 					       struct airoha_flow_data *data)
35200a76783SLorenzo Bianconi 
35300a76783SLorenzo Bianconi {
35400a76783SLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
35500a76783SLorenzo Bianconi 	u32 *src, *dest;
35600a76783SLorenzo Bianconi 
35700a76783SLorenzo Bianconi 	switch (type) {
35800a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_ROUTE_5T:
35900a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_6RD:
36000a76783SLorenzo Bianconi 		hwe->ipv6.src_port = be16_to_cpu(data->src_port);
36100a76783SLorenzo Bianconi 		hwe->ipv6.dest_port = be16_to_cpu(data->dst_port);
36200a76783SLorenzo Bianconi 		fallthrough;
36300a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_ROUTE_3T:
36400a76783SLorenzo Bianconi 		src = hwe->ipv6.src_ip;
36500a76783SLorenzo Bianconi 		dest = hwe->ipv6.dest_ip;
36600a76783SLorenzo Bianconi 		break;
36700a76783SLorenzo Bianconi 	default:
36800a76783SLorenzo Bianconi 		WARN_ON_ONCE(1);
36900a76783SLorenzo Bianconi 		return -EINVAL;
37000a76783SLorenzo Bianconi 	}
37100a76783SLorenzo Bianconi 
37200a76783SLorenzo Bianconi 	ipv6_addr_be32_to_cpu(src, data->v6.src_addr.s6_addr32);
37300a76783SLorenzo Bianconi 	ipv6_addr_be32_to_cpu(dest, data->v6.dst_addr.s6_addr32);
37400a76783SLorenzo Bianconi 
37500a76783SLorenzo Bianconi 	return 0;
37600a76783SLorenzo Bianconi }
37700a76783SLorenzo Bianconi 
37800a76783SLorenzo Bianconi static u32 airoha_ppe_foe_get_entry_hash(struct airoha_foe_entry *hwe)
37900a76783SLorenzo Bianconi {
38000a76783SLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
38100a76783SLorenzo Bianconi 	u32 hash, hv1, hv2, hv3;
38200a76783SLorenzo Bianconi 
38300a76783SLorenzo Bianconi 	switch (type) {
38400a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_ROUTE:
38500a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_HNAPT:
38600a76783SLorenzo Bianconi 		hv1 = hwe->ipv4.orig_tuple.ports;
38700a76783SLorenzo Bianconi 		hv2 = hwe->ipv4.orig_tuple.dest_ip;
38800a76783SLorenzo Bianconi 		hv3 = hwe->ipv4.orig_tuple.src_ip;
38900a76783SLorenzo Bianconi 		break;
39000a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_ROUTE_3T:
39100a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_ROUTE_5T:
39200a76783SLorenzo Bianconi 		hv1 = hwe->ipv6.src_ip[3] ^ hwe->ipv6.dest_ip[3];
39300a76783SLorenzo Bianconi 		hv1 ^= hwe->ipv6.ports;
39400a76783SLorenzo Bianconi 
39500a76783SLorenzo Bianconi 		hv2 = hwe->ipv6.src_ip[2] ^ hwe->ipv6.dest_ip[2];
39600a76783SLorenzo Bianconi 		hv2 ^= hwe->ipv6.dest_ip[0];
39700a76783SLorenzo Bianconi 
39800a76783SLorenzo Bianconi 		hv3 = hwe->ipv6.src_ip[1] ^ hwe->ipv6.dest_ip[1];
39900a76783SLorenzo Bianconi 		hv3 ^= hwe->ipv6.src_ip[0];
40000a76783SLorenzo Bianconi 		break;
401cd53f622SLorenzo Bianconi 	case PPE_PKT_TYPE_BRIDGE: {
402cd53f622SLorenzo Bianconi 		struct airoha_foe_mac_info *l2 = &hwe->bridge.l2;
403cd53f622SLorenzo Bianconi 
404cd53f622SLorenzo Bianconi 		hv1 = l2->common.src_mac_hi & 0xffff;
405cd53f622SLorenzo Bianconi 		hv1 = hv1 << 16 | l2->src_mac_lo;
406cd53f622SLorenzo Bianconi 
407cd53f622SLorenzo Bianconi 		hv2 = l2->common.dest_mac_lo;
408cd53f622SLorenzo Bianconi 		hv2 = hv2 << 16;
409cd53f622SLorenzo Bianconi 		hv2 = hv2 | ((l2->common.src_mac_hi & 0xffff0000) >> 16);
410cd53f622SLorenzo Bianconi 
411cd53f622SLorenzo Bianconi 		hv3 = l2->common.dest_mac_hi;
412cd53f622SLorenzo Bianconi 		break;
413cd53f622SLorenzo Bianconi 	}
41400a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV4_DSLITE:
41500a76783SLorenzo Bianconi 	case PPE_PKT_TYPE_IPV6_6RD:
41600a76783SLorenzo Bianconi 	default:
41700a76783SLorenzo Bianconi 		WARN_ON_ONCE(1);
41800a76783SLorenzo Bianconi 		return PPE_HASH_MASK;
41900a76783SLorenzo Bianconi 	}
42000a76783SLorenzo Bianconi 
42100a76783SLorenzo Bianconi 	hash = (hv1 & hv2) | ((~hv1) & hv3);
42200a76783SLorenzo Bianconi 	hash = (hash >> 24) | ((hash & 0xffffff) << 8);
42300a76783SLorenzo Bianconi 	hash ^= hv1 ^ hv2 ^ hv3;
42400a76783SLorenzo Bianconi 	hash ^= hash >> 16;
42500a76783SLorenzo Bianconi 	hash &= PPE_NUM_ENTRIES - 1;
42600a76783SLorenzo Bianconi 
42700a76783SLorenzo Bianconi 	return hash;
42800a76783SLorenzo Bianconi }
42900a76783SLorenzo Bianconi 
430b81e0f2bSLorenzo Bianconi static u32 airoha_ppe_foe_get_flow_stats_index(struct airoha_ppe *ppe, u32 hash)
431b81e0f2bSLorenzo Bianconi {
432b81e0f2bSLorenzo Bianconi 	if (!airoha_ppe2_is_enabled(ppe->eth))
433b81e0f2bSLorenzo Bianconi 		return hash;
434b81e0f2bSLorenzo Bianconi 
435b81e0f2bSLorenzo Bianconi 	return hash >= PPE_STATS_NUM_ENTRIES ? hash - PPE1_STATS_NUM_ENTRIES
436b81e0f2bSLorenzo Bianconi 					     : hash;
437b81e0f2bSLorenzo Bianconi }
438b81e0f2bSLorenzo Bianconi 
439b81e0f2bSLorenzo Bianconi static void airoha_ppe_foe_flow_stat_entry_reset(struct airoha_ppe *ppe,
440b81e0f2bSLorenzo Bianconi 						 struct airoha_npu *npu,
441b81e0f2bSLorenzo Bianconi 						 int index)
442b81e0f2bSLorenzo Bianconi {
443b81e0f2bSLorenzo Bianconi 	memset_io(&npu->stats[index], 0, sizeof(*npu->stats));
444b81e0f2bSLorenzo Bianconi 	memset(&ppe->foe_stats[index], 0, sizeof(*ppe->foe_stats));
445b81e0f2bSLorenzo Bianconi }
446b81e0f2bSLorenzo Bianconi 
447b81e0f2bSLorenzo Bianconi static void airoha_ppe_foe_flow_stats_reset(struct airoha_ppe *ppe,
448b81e0f2bSLorenzo Bianconi 					    struct airoha_npu *npu)
449b81e0f2bSLorenzo Bianconi {
450b81e0f2bSLorenzo Bianconi 	int i;
451b81e0f2bSLorenzo Bianconi 
452b81e0f2bSLorenzo Bianconi 	for (i = 0; i < PPE_STATS_NUM_ENTRIES; i++)
453b81e0f2bSLorenzo Bianconi 		airoha_ppe_foe_flow_stat_entry_reset(ppe, npu, i);
454b81e0f2bSLorenzo Bianconi }
455b81e0f2bSLorenzo Bianconi 
456b81e0f2bSLorenzo Bianconi static void airoha_ppe_foe_flow_stats_update(struct airoha_ppe *ppe,
457b81e0f2bSLorenzo Bianconi 					     struct airoha_npu *npu,
458b81e0f2bSLorenzo Bianconi 					     struct airoha_foe_entry *hwe,
459b81e0f2bSLorenzo Bianconi 					     u32 hash)
460b81e0f2bSLorenzo Bianconi {
461b81e0f2bSLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
462b81e0f2bSLorenzo Bianconi 	u32 index, pse_port, val, *data, *ib2, *meter;
463b81e0f2bSLorenzo Bianconi 	u8 nbq;
464b81e0f2bSLorenzo Bianconi 
465b81e0f2bSLorenzo Bianconi 	index = airoha_ppe_foe_get_flow_stats_index(ppe, hash);
466b81e0f2bSLorenzo Bianconi 	if (index >= PPE_STATS_NUM_ENTRIES)
467b81e0f2bSLorenzo Bianconi 		return;
468b81e0f2bSLorenzo Bianconi 
469b81e0f2bSLorenzo Bianconi 	if (type == PPE_PKT_TYPE_BRIDGE) {
470b81e0f2bSLorenzo Bianconi 		data = &hwe->bridge.data;
471b81e0f2bSLorenzo Bianconi 		ib2 = &hwe->bridge.ib2;
472b81e0f2bSLorenzo Bianconi 		meter = &hwe->bridge.l2.meter;
473b81e0f2bSLorenzo Bianconi 	} else if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
474b81e0f2bSLorenzo Bianconi 		data = &hwe->ipv6.data;
475b81e0f2bSLorenzo Bianconi 		ib2 = &hwe->ipv6.ib2;
476b81e0f2bSLorenzo Bianconi 		meter = &hwe->ipv6.meter;
477b81e0f2bSLorenzo Bianconi 	} else {
478b81e0f2bSLorenzo Bianconi 		data = &hwe->ipv4.data;
479b81e0f2bSLorenzo Bianconi 		ib2 = &hwe->ipv4.ib2;
480b81e0f2bSLorenzo Bianconi 		meter = &hwe->ipv4.l2.meter;
481b81e0f2bSLorenzo Bianconi 	}
482b81e0f2bSLorenzo Bianconi 
483b81e0f2bSLorenzo Bianconi 	airoha_ppe_foe_flow_stat_entry_reset(ppe, npu, index);
484b81e0f2bSLorenzo Bianconi 
485b81e0f2bSLorenzo Bianconi 	val = FIELD_GET(AIROHA_FOE_CHANNEL | AIROHA_FOE_QID, *data);
486b81e0f2bSLorenzo Bianconi 	*data = (*data & ~AIROHA_FOE_ACTDP) |
487b81e0f2bSLorenzo Bianconi 		FIELD_PREP(AIROHA_FOE_ACTDP, val);
488b81e0f2bSLorenzo Bianconi 
489b81e0f2bSLorenzo Bianconi 	val = *ib2 & (AIROHA_FOE_IB2_NBQ | AIROHA_FOE_IB2_PSE_PORT |
490b81e0f2bSLorenzo Bianconi 		      AIROHA_FOE_IB2_PSE_QOS | AIROHA_FOE_IB2_FAST_PATH);
491b81e0f2bSLorenzo Bianconi 	*meter |= FIELD_PREP(AIROHA_FOE_TUNNEL_MTU, val);
492b81e0f2bSLorenzo Bianconi 
493b81e0f2bSLorenzo Bianconi 	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, *ib2);
494b81e0f2bSLorenzo Bianconi 	nbq = pse_port == 1 ? 6 : 5;
495b81e0f2bSLorenzo Bianconi 	*ib2 &= ~(AIROHA_FOE_IB2_NBQ | AIROHA_FOE_IB2_PSE_PORT |
496b81e0f2bSLorenzo Bianconi 		  AIROHA_FOE_IB2_PSE_QOS);
497b81e0f2bSLorenzo Bianconi 	*ib2 |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, 6) |
498b81e0f2bSLorenzo Bianconi 		FIELD_PREP(AIROHA_FOE_IB2_NBQ, nbq);
499b81e0f2bSLorenzo Bianconi }
500b81e0f2bSLorenzo Bianconi 
5013fe15c64SLorenzo Bianconi struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
5023fe15c64SLorenzo Bianconi 						  u32 hash)
50300a76783SLorenzo Bianconi {
50400a76783SLorenzo Bianconi 	if (hash < PPE_SRAM_NUM_ENTRIES) {
50500a76783SLorenzo Bianconi 		u32 *hwe = ppe->foe + hash * sizeof(struct airoha_foe_entry);
50600a76783SLorenzo Bianconi 		struct airoha_eth *eth = ppe->eth;
50700a76783SLorenzo Bianconi 		bool ppe2;
50800a76783SLorenzo Bianconi 		u32 val;
50900a76783SLorenzo Bianconi 		int i;
51000a76783SLorenzo Bianconi 
51100a76783SLorenzo Bianconi 		ppe2 = airoha_ppe2_is_enabled(ppe->eth) &&
51200a76783SLorenzo Bianconi 		       hash >= PPE1_SRAM_NUM_ENTRIES;
51300a76783SLorenzo Bianconi 		airoha_fe_wr(ppe->eth, REG_PPE_RAM_CTRL(ppe2),
51400a76783SLorenzo Bianconi 			     FIELD_PREP(PPE_SRAM_CTRL_ENTRY_MASK, hash) |
51500a76783SLorenzo Bianconi 			     PPE_SRAM_CTRL_REQ_MASK);
51600a76783SLorenzo Bianconi 		if (read_poll_timeout_atomic(airoha_fe_rr, val,
51700a76783SLorenzo Bianconi 					     val & PPE_SRAM_CTRL_ACK_MASK,
51800a76783SLorenzo Bianconi 					     10, 100, false, eth,
51900a76783SLorenzo Bianconi 					     REG_PPE_RAM_CTRL(ppe2)))
52000a76783SLorenzo Bianconi 			return NULL;
52100a76783SLorenzo Bianconi 
52200a76783SLorenzo Bianconi 		for (i = 0; i < sizeof(struct airoha_foe_entry) / 4; i++)
52300a76783SLorenzo Bianconi 			hwe[i] = airoha_fe_rr(eth,
52400a76783SLorenzo Bianconi 					      REG_PPE_RAM_ENTRY(ppe2, i));
52500a76783SLorenzo Bianconi 	}
52600a76783SLorenzo Bianconi 
52700a76783SLorenzo Bianconi 	return ppe->foe + hash * sizeof(struct airoha_foe_entry);
52800a76783SLorenzo Bianconi }
52900a76783SLorenzo Bianconi 
53000a76783SLorenzo Bianconi static bool airoha_ppe_foe_compare_entry(struct airoha_flow_table_entry *e,
53100a76783SLorenzo Bianconi 					 struct airoha_foe_entry *hwe)
53200a76783SLorenzo Bianconi {
53300a76783SLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, e->data.ib1);
53400a76783SLorenzo Bianconi 	int len;
53500a76783SLorenzo Bianconi 
53600a76783SLorenzo Bianconi 	if ((hwe->ib1 ^ e->data.ib1) & AIROHA_FOE_IB1_BIND_UDP)
53700a76783SLorenzo Bianconi 		return false;
53800a76783SLorenzo Bianconi 
53900a76783SLorenzo Bianconi 	if (type > PPE_PKT_TYPE_IPV4_DSLITE)
54000a76783SLorenzo Bianconi 		len = offsetof(struct airoha_foe_entry, ipv6.data);
54100a76783SLorenzo Bianconi 	else
54200a76783SLorenzo Bianconi 		len = offsetof(struct airoha_foe_entry, ipv4.ib2);
54300a76783SLorenzo Bianconi 
54400a76783SLorenzo Bianconi 	return !memcmp(&e->data.d, &hwe->d, len - sizeof(hwe->ib1));
54500a76783SLorenzo Bianconi }
54600a76783SLorenzo Bianconi 
54700a76783SLorenzo Bianconi static int airoha_ppe_foe_commit_entry(struct airoha_ppe *ppe,
54800a76783SLorenzo Bianconi 				       struct airoha_foe_entry *e,
54900a76783SLorenzo Bianconi 				       u32 hash)
55000a76783SLorenzo Bianconi {
55100a76783SLorenzo Bianconi 	struct airoha_foe_entry *hwe = ppe->foe + hash * sizeof(*hwe);
55200a76783SLorenzo Bianconi 	u32 ts = airoha_ppe_get_timestamp(ppe);
55300a76783SLorenzo Bianconi 	struct airoha_eth *eth = ppe->eth;
554b81e0f2bSLorenzo Bianconi 	struct airoha_npu *npu;
555b81e0f2bSLorenzo Bianconi 	int err = 0;
55600a76783SLorenzo Bianconi 
55700a76783SLorenzo Bianconi 	memcpy(&hwe->d, &e->d, sizeof(*hwe) - sizeof(hwe->ib1));
55800a76783SLorenzo Bianconi 	wmb();
55900a76783SLorenzo Bianconi 
56000a76783SLorenzo Bianconi 	e->ib1 &= ~AIROHA_FOE_IB1_BIND_TIMESTAMP;
56100a76783SLorenzo Bianconi 	e->ib1 |= FIELD_PREP(AIROHA_FOE_IB1_BIND_TIMESTAMP, ts);
56200a76783SLorenzo Bianconi 	hwe->ib1 = e->ib1;
56300a76783SLorenzo Bianconi 
564b81e0f2bSLorenzo Bianconi 	rcu_read_lock();
565b81e0f2bSLorenzo Bianconi 
566b81e0f2bSLorenzo Bianconi 	npu = rcu_dereference(eth->npu);
567b81e0f2bSLorenzo Bianconi 	if (!npu) {
568b81e0f2bSLorenzo Bianconi 		err = -ENODEV;
569b81e0f2bSLorenzo Bianconi 		goto unlock;
570b81e0f2bSLorenzo Bianconi 	}
571b81e0f2bSLorenzo Bianconi 
572b81e0f2bSLorenzo Bianconi 	airoha_ppe_foe_flow_stats_update(ppe, npu, hwe, hash);
573b81e0f2bSLorenzo Bianconi 
57400a76783SLorenzo Bianconi 	if (hash < PPE_SRAM_NUM_ENTRIES) {
57500a76783SLorenzo Bianconi 		dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
57600a76783SLorenzo Bianconi 		bool ppe2 = airoha_ppe2_is_enabled(eth) &&
57700a76783SLorenzo Bianconi 			    hash >= PPE1_SRAM_NUM_ENTRIES;
57800a76783SLorenzo Bianconi 
579b81e0f2bSLorenzo Bianconi 		err = npu->ops.ppe_foe_commit_entry(npu, addr, sizeof(*hwe),
580b81e0f2bSLorenzo Bianconi 						    hash, ppe2);
581b81e0f2bSLorenzo Bianconi 	}
582b81e0f2bSLorenzo Bianconi unlock:
58300a76783SLorenzo Bianconi 	rcu_read_unlock();
58400a76783SLorenzo Bianconi 
58500a76783SLorenzo Bianconi 	return err;
58600a76783SLorenzo Bianconi }
58700a76783SLorenzo Bianconi 
588b4916f67SLorenzo Bianconi static void airoha_ppe_foe_remove_flow(struct airoha_ppe *ppe,
589b4916f67SLorenzo Bianconi 				       struct airoha_flow_table_entry *e)
590b4916f67SLorenzo Bianconi {
591b4916f67SLorenzo Bianconi 	lockdep_assert_held(&ppe_lock);
592b4916f67SLorenzo Bianconi 
593b4916f67SLorenzo Bianconi 	hlist_del_init(&e->list);
594b4916f67SLorenzo Bianconi 	if (e->hash != 0xffff) {
595b4916f67SLorenzo Bianconi 		e->data.ib1 &= ~AIROHA_FOE_IB1_BIND_STATE;
596b4916f67SLorenzo Bianconi 		e->data.ib1 |= FIELD_PREP(AIROHA_FOE_IB1_BIND_STATE,
597b4916f67SLorenzo Bianconi 					  AIROHA_FOE_STATE_INVALID);
598b4916f67SLorenzo Bianconi 		airoha_ppe_foe_commit_entry(ppe, &e->data, e->hash);
599b4916f67SLorenzo Bianconi 		e->hash = 0xffff;
600b4916f67SLorenzo Bianconi 	}
601cd53f622SLorenzo Bianconi 	if (e->type == FLOW_TYPE_L2_SUBFLOW) {
602cd53f622SLorenzo Bianconi 		hlist_del_init(&e->l2_subflow_node);
603cd53f622SLorenzo Bianconi 		kfree(e);
604cd53f622SLorenzo Bianconi 	}
605b4916f67SLorenzo Bianconi }
606b4916f67SLorenzo Bianconi 
607b4916f67SLorenzo Bianconi static void airoha_ppe_foe_remove_l2_flow(struct airoha_ppe *ppe,
608b4916f67SLorenzo Bianconi 					  struct airoha_flow_table_entry *e)
609b4916f67SLorenzo Bianconi {
610cd53f622SLorenzo Bianconi 	struct hlist_head *head = &e->l2_flows;
611cd53f622SLorenzo Bianconi 	struct hlist_node *n;
612cd53f622SLorenzo Bianconi 
613b4916f67SLorenzo Bianconi 	lockdep_assert_held(&ppe_lock);
614b4916f67SLorenzo Bianconi 
615b4916f67SLorenzo Bianconi 	rhashtable_remove_fast(&ppe->l2_flows, &e->l2_node,
616b4916f67SLorenzo Bianconi 			       airoha_l2_flow_table_params);
617cd53f622SLorenzo Bianconi 	hlist_for_each_entry_safe(e, n, head, l2_subflow_node)
618cd53f622SLorenzo Bianconi 		airoha_ppe_foe_remove_flow(ppe, e);
619b4916f67SLorenzo Bianconi }
620b4916f67SLorenzo Bianconi 
621b4916f67SLorenzo Bianconi static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
622b4916f67SLorenzo Bianconi 					     struct airoha_flow_table_entry *e)
623b4916f67SLorenzo Bianconi {
624b4916f67SLorenzo Bianconi 	spin_lock_bh(&ppe_lock);
625b4916f67SLorenzo Bianconi 
626b4916f67SLorenzo Bianconi 	if (e->type == FLOW_TYPE_L2)
627b4916f67SLorenzo Bianconi 		airoha_ppe_foe_remove_l2_flow(ppe, e);
628b4916f67SLorenzo Bianconi 	else
629b4916f67SLorenzo Bianconi 		airoha_ppe_foe_remove_flow(ppe, e);
630b4916f67SLorenzo Bianconi 
631b4916f67SLorenzo Bianconi 	spin_unlock_bh(&ppe_lock);
632b4916f67SLorenzo Bianconi }
633b4916f67SLorenzo Bianconi 
634cd53f622SLorenzo Bianconi static int
635cd53f622SLorenzo Bianconi airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
636cd53f622SLorenzo Bianconi 				    struct airoha_flow_table_entry *e,
637cd53f622SLorenzo Bianconi 				    u32 hash)
638cd53f622SLorenzo Bianconi {
639cd53f622SLorenzo Bianconi 	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
640cd53f622SLorenzo Bianconi 	struct airoha_foe_entry *hwe_p, hwe;
641cd53f622SLorenzo Bianconi 	struct airoha_flow_table_entry *f;
642cd53f622SLorenzo Bianconi 	int type;
643cd53f622SLorenzo Bianconi 
644cd53f622SLorenzo Bianconi 	hwe_p = airoha_ppe_foe_get_entry(ppe, hash);
645cd53f622SLorenzo Bianconi 	if (!hwe_p)
646cd53f622SLorenzo Bianconi 		return -EINVAL;
647cd53f622SLorenzo Bianconi 
648cd53f622SLorenzo Bianconi 	f = kzalloc(sizeof(*f), GFP_ATOMIC);
649cd53f622SLorenzo Bianconi 	if (!f)
650cd53f622SLorenzo Bianconi 		return -ENOMEM;
651cd53f622SLorenzo Bianconi 
652cd53f622SLorenzo Bianconi 	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
653cd53f622SLorenzo Bianconi 	f->type = FLOW_TYPE_L2_SUBFLOW;
654cd53f622SLorenzo Bianconi 	f->hash = hash;
655cd53f622SLorenzo Bianconi 
656cd53f622SLorenzo Bianconi 	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
657cd53f622SLorenzo Bianconi 	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
658cd53f622SLorenzo Bianconi 
659cd53f622SLorenzo Bianconi 	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
660504a577cSLorenzo Bianconi 	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
661504a577cSLorenzo Bianconi 		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
662504a577cSLorenzo Bianconi 		hwe.ipv6.ib2 = e->data.bridge.ib2;
663c86fac53SLorenzo Bianconi 		/* setting smac_id to 0xf instruct the hw to keep original
664c86fac53SLorenzo Bianconi 		 * source mac address
665c86fac53SLorenzo Bianconi 		 */
666c86fac53SLorenzo Bianconi 		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
667c86fac53SLorenzo Bianconi 						    0xf);
668504a577cSLorenzo Bianconi 	} else {
669504a577cSLorenzo Bianconi 		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
670504a577cSLorenzo Bianconi 		       sizeof(hwe.bridge.l2));
671504a577cSLorenzo Bianconi 		hwe.bridge.ib2 = e->data.bridge.ib2;
672cd53f622SLorenzo Bianconi 		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
673cd53f622SLorenzo Bianconi 			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
674cd53f622SLorenzo Bianconi 			       sizeof(hwe.ipv4.new_tuple));
675504a577cSLorenzo Bianconi 	}
676cd53f622SLorenzo Bianconi 
677b81e0f2bSLorenzo Bianconi 	hwe.bridge.data = e->data.bridge.data;
678cd53f622SLorenzo Bianconi 	airoha_ppe_foe_commit_entry(ppe, &hwe, hash);
679cd53f622SLorenzo Bianconi 
680cd53f622SLorenzo Bianconi 	return 0;
681cd53f622SLorenzo Bianconi }
682cd53f622SLorenzo Bianconi 
683cd53f622SLorenzo Bianconi static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
684cd53f622SLorenzo Bianconi 					struct sk_buff *skb,
685cd53f622SLorenzo Bianconi 					u32 hash)
68600a76783SLorenzo Bianconi {
68700a76783SLorenzo Bianconi 	struct airoha_flow_table_entry *e;
688cd53f622SLorenzo Bianconi 	struct airoha_foe_bridge br = {};
68900a76783SLorenzo Bianconi 	struct airoha_foe_entry *hwe;
690cd53f622SLorenzo Bianconi 	bool commit_done = false;
69100a76783SLorenzo Bianconi 	struct hlist_node *n;
69200a76783SLorenzo Bianconi 	u32 index, state;
69300a76783SLorenzo Bianconi 
69400a76783SLorenzo Bianconi 	spin_lock_bh(&ppe_lock);
69500a76783SLorenzo Bianconi 
69600a76783SLorenzo Bianconi 	hwe = airoha_ppe_foe_get_entry(ppe, hash);
69700a76783SLorenzo Bianconi 	if (!hwe)
69800a76783SLorenzo Bianconi 		goto unlock;
69900a76783SLorenzo Bianconi 
70000a76783SLorenzo Bianconi 	state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, hwe->ib1);
70100a76783SLorenzo Bianconi 	if (state == AIROHA_FOE_STATE_BIND)
70200a76783SLorenzo Bianconi 		goto unlock;
70300a76783SLorenzo Bianconi 
70400a76783SLorenzo Bianconi 	index = airoha_ppe_foe_get_entry_hash(hwe);
70500a76783SLorenzo Bianconi 	hlist_for_each_entry_safe(e, n, &ppe->foe_flow[index], list) {
706cd53f622SLorenzo Bianconi 		if (e->type == FLOW_TYPE_L2_SUBFLOW) {
707cd53f622SLorenzo Bianconi 			state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, hwe->ib1);
708cd53f622SLorenzo Bianconi 			if (state != AIROHA_FOE_STATE_BIND) {
709cd53f622SLorenzo Bianconi 				e->hash = 0xffff;
710cd53f622SLorenzo Bianconi 				airoha_ppe_foe_remove_flow(ppe, e);
711cd53f622SLorenzo Bianconi 			}
712cd53f622SLorenzo Bianconi 			continue;
713cd53f622SLorenzo Bianconi 		}
714cd53f622SLorenzo Bianconi 
715cd53f622SLorenzo Bianconi 		if (commit_done || !airoha_ppe_foe_compare_entry(e, hwe)) {
716cd53f622SLorenzo Bianconi 			e->hash = 0xffff;
717cd53f622SLorenzo Bianconi 			continue;
718cd53f622SLorenzo Bianconi 		}
719cd53f622SLorenzo Bianconi 
72000a76783SLorenzo Bianconi 		airoha_ppe_foe_commit_entry(ppe, &e->data, hash);
721cd53f622SLorenzo Bianconi 		commit_done = true;
72200a76783SLorenzo Bianconi 		e->hash = hash;
72300a76783SLorenzo Bianconi 	}
724cd53f622SLorenzo Bianconi 
725cd53f622SLorenzo Bianconi 	if (commit_done)
726cd53f622SLorenzo Bianconi 		goto unlock;
727cd53f622SLorenzo Bianconi 
728cd53f622SLorenzo Bianconi 	airoha_ppe_foe_set_bridge_addrs(&br, eth_hdr(skb));
729cd53f622SLorenzo Bianconi 	e = rhashtable_lookup_fast(&ppe->l2_flows, &br,
730cd53f622SLorenzo Bianconi 				   airoha_l2_flow_table_params);
731cd53f622SLorenzo Bianconi 	if (e)
732cd53f622SLorenzo Bianconi 		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash);
73300a76783SLorenzo Bianconi unlock:
73400a76783SLorenzo Bianconi 	spin_unlock_bh(&ppe_lock);
73500a76783SLorenzo Bianconi }
73600a76783SLorenzo Bianconi 
737b4916f67SLorenzo Bianconi static int
738b4916f67SLorenzo Bianconi airoha_ppe_foe_l2_flow_commit_entry(struct airoha_ppe *ppe,
739b4916f67SLorenzo Bianconi 				    struct airoha_flow_table_entry *e)
740b4916f67SLorenzo Bianconi {
741b4916f67SLorenzo Bianconi 	struct airoha_flow_table_entry *prev;
742b4916f67SLorenzo Bianconi 
743b4916f67SLorenzo Bianconi 	e->type = FLOW_TYPE_L2;
744b4916f67SLorenzo Bianconi 	prev = rhashtable_lookup_get_insert_fast(&ppe->l2_flows, &e->l2_node,
745b4916f67SLorenzo Bianconi 						 airoha_l2_flow_table_params);
746b4916f67SLorenzo Bianconi 	if (!prev)
747b4916f67SLorenzo Bianconi 		return 0;
748b4916f67SLorenzo Bianconi 
749b4916f67SLorenzo Bianconi 	if (IS_ERR(prev))
750b4916f67SLorenzo Bianconi 		return PTR_ERR(prev);
751b4916f67SLorenzo Bianconi 
752b4916f67SLorenzo Bianconi 	return rhashtable_replace_fast(&ppe->l2_flows, &prev->l2_node,
753b4916f67SLorenzo Bianconi 				       &e->l2_node,
754b4916f67SLorenzo Bianconi 				       airoha_l2_flow_table_params);
755b4916f67SLorenzo Bianconi }
756b4916f67SLorenzo Bianconi 
75700a76783SLorenzo Bianconi static int airoha_ppe_foe_flow_commit_entry(struct airoha_ppe *ppe,
75800a76783SLorenzo Bianconi 					    struct airoha_flow_table_entry *e)
75900a76783SLorenzo Bianconi {
760b4916f67SLorenzo Bianconi 	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, e->data.ib1);
761b4916f67SLorenzo Bianconi 	u32 hash;
76200a76783SLorenzo Bianconi 
763b4916f67SLorenzo Bianconi 	if (type == PPE_PKT_TYPE_BRIDGE)
764b4916f67SLorenzo Bianconi 		return airoha_ppe_foe_l2_flow_commit_entry(ppe, e);
765b4916f67SLorenzo Bianconi 
766b4916f67SLorenzo Bianconi 	hash = airoha_ppe_foe_get_entry_hash(&e->data);
767b4916f67SLorenzo Bianconi 	e->type = FLOW_TYPE_L4;
76800a76783SLorenzo Bianconi 	e->hash = 0xffff;
76900a76783SLorenzo Bianconi 
77000a76783SLorenzo Bianconi 	spin_lock_bh(&ppe_lock);
77100a76783SLorenzo Bianconi 	hlist_add_head(&e->list, &ppe->foe_flow[hash]);
77200a76783SLorenzo Bianconi 	spin_unlock_bh(&ppe_lock);
77300a76783SLorenzo Bianconi 
77400a76783SLorenzo Bianconi 	return 0;
77500a76783SLorenzo Bianconi }
77600a76783SLorenzo Bianconi 
777b81e0f2bSLorenzo Bianconi static int airoha_ppe_get_entry_idle_time(struct airoha_ppe *ppe, u32 ib1)
778b81e0f2bSLorenzo Bianconi {
779b81e0f2bSLorenzo Bianconi 	u32 state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, ib1);
780b81e0f2bSLorenzo Bianconi 	u32 ts, ts_mask, now = airoha_ppe_get_timestamp(ppe);
781b81e0f2bSLorenzo Bianconi 	int idle;
782b81e0f2bSLorenzo Bianconi 
783b81e0f2bSLorenzo Bianconi 	if (state == AIROHA_FOE_STATE_BIND) {
784b81e0f2bSLorenzo Bianconi 		ts = FIELD_GET(AIROHA_FOE_IB1_BIND_TIMESTAMP, ib1);
785b81e0f2bSLorenzo Bianconi 		ts_mask = AIROHA_FOE_IB1_BIND_TIMESTAMP;
786b81e0f2bSLorenzo Bianconi 	} else {
787b81e0f2bSLorenzo Bianconi 		ts = FIELD_GET(AIROHA_FOE_IB1_UNBIND_TIMESTAMP, ib1);
788b81e0f2bSLorenzo Bianconi 		now = FIELD_GET(AIROHA_FOE_IB1_UNBIND_TIMESTAMP, now);
789b81e0f2bSLorenzo Bianconi 		ts_mask = AIROHA_FOE_IB1_UNBIND_TIMESTAMP;
790b81e0f2bSLorenzo Bianconi 	}
791b81e0f2bSLorenzo Bianconi 	idle = now - ts;
792b81e0f2bSLorenzo Bianconi 
793b81e0f2bSLorenzo Bianconi 	return idle < 0 ? idle + ts_mask + 1 : idle;
794b81e0f2bSLorenzo Bianconi }
795b81e0f2bSLorenzo Bianconi 
796b81e0f2bSLorenzo Bianconi static void
797b81e0f2bSLorenzo Bianconi airoha_ppe_foe_flow_l2_entry_update(struct airoha_ppe *ppe,
798b81e0f2bSLorenzo Bianconi 				    struct airoha_flow_table_entry *e)
799b81e0f2bSLorenzo Bianconi {
800b81e0f2bSLorenzo Bianconi 	int min_idle = airoha_ppe_get_entry_idle_time(ppe, e->data.ib1);
801b81e0f2bSLorenzo Bianconi 	struct airoha_flow_table_entry *iter;
802b81e0f2bSLorenzo Bianconi 	struct hlist_node *n;
803b81e0f2bSLorenzo Bianconi 
804b81e0f2bSLorenzo Bianconi 	lockdep_assert_held(&ppe_lock);
805b81e0f2bSLorenzo Bianconi 
806b81e0f2bSLorenzo Bianconi 	hlist_for_each_entry_safe(iter, n, &e->l2_flows, l2_subflow_node) {
807b81e0f2bSLorenzo Bianconi 		struct airoha_foe_entry *hwe;
808b81e0f2bSLorenzo Bianconi 		u32 ib1, state;
809b81e0f2bSLorenzo Bianconi 		int idle;
810b81e0f2bSLorenzo Bianconi 
811b81e0f2bSLorenzo Bianconi 		hwe = airoha_ppe_foe_get_entry(ppe, iter->hash);
812*78bd03eeSLorenzo Bianconi 		if (!hwe)
813*78bd03eeSLorenzo Bianconi 			continue;
814b81e0f2bSLorenzo Bianconi 
815*78bd03eeSLorenzo Bianconi 		ib1 = READ_ONCE(hwe->ib1);
816b81e0f2bSLorenzo Bianconi 		state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, ib1);
817b81e0f2bSLorenzo Bianconi 		if (state != AIROHA_FOE_STATE_BIND) {
818b81e0f2bSLorenzo Bianconi 			iter->hash = 0xffff;
819b81e0f2bSLorenzo Bianconi 			airoha_ppe_foe_remove_flow(ppe, iter);
820b81e0f2bSLorenzo Bianconi 			continue;
821b81e0f2bSLorenzo Bianconi 		}
822b81e0f2bSLorenzo Bianconi 
823b81e0f2bSLorenzo Bianconi 		idle = airoha_ppe_get_entry_idle_time(ppe, ib1);
824b81e0f2bSLorenzo Bianconi 		if (idle >= min_idle)
825b81e0f2bSLorenzo Bianconi 			continue;
826b81e0f2bSLorenzo Bianconi 
827b81e0f2bSLorenzo Bianconi 		min_idle = idle;
828b81e0f2bSLorenzo Bianconi 		e->data.ib1 &= ~AIROHA_FOE_IB1_BIND_TIMESTAMP;
829b81e0f2bSLorenzo Bianconi 		e->data.ib1 |= ib1 & AIROHA_FOE_IB1_BIND_TIMESTAMP;
830b81e0f2bSLorenzo Bianconi 	}
831b81e0f2bSLorenzo Bianconi }
832b81e0f2bSLorenzo Bianconi 
833b81e0f2bSLorenzo Bianconi static void airoha_ppe_foe_flow_entry_update(struct airoha_ppe *ppe,
834b81e0f2bSLorenzo Bianconi 					     struct airoha_flow_table_entry *e)
835b81e0f2bSLorenzo Bianconi {
836b81e0f2bSLorenzo Bianconi 	struct airoha_foe_entry *hwe_p, hwe = {};
837b81e0f2bSLorenzo Bianconi 
838b81e0f2bSLorenzo Bianconi 	spin_lock_bh(&ppe_lock);
839b81e0f2bSLorenzo Bianconi 
840b81e0f2bSLorenzo Bianconi 	if (e->type == FLOW_TYPE_L2) {
841b81e0f2bSLorenzo Bianconi 		airoha_ppe_foe_flow_l2_entry_update(ppe, e);
842b81e0f2bSLorenzo Bianconi 		goto unlock;
843b81e0f2bSLorenzo Bianconi 	}
844b81e0f2bSLorenzo Bianconi 
845b81e0f2bSLorenzo Bianconi 	if (e->hash == 0xffff)
846b81e0f2bSLorenzo Bianconi 		goto unlock;
847b81e0f2bSLorenzo Bianconi 
848b81e0f2bSLorenzo Bianconi 	hwe_p = airoha_ppe_foe_get_entry(ppe, e->hash);
849b81e0f2bSLorenzo Bianconi 	if (!hwe_p)
850b81e0f2bSLorenzo Bianconi 		goto unlock;
851b81e0f2bSLorenzo Bianconi 
852b81e0f2bSLorenzo Bianconi 	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
853b81e0f2bSLorenzo Bianconi 	if (!airoha_ppe_foe_compare_entry(e, &hwe)) {
854b81e0f2bSLorenzo Bianconi 		e->hash = 0xffff;
855b81e0f2bSLorenzo Bianconi 		goto unlock;
856b81e0f2bSLorenzo Bianconi 	}
857b81e0f2bSLorenzo Bianconi 
858b81e0f2bSLorenzo Bianconi 	e->data.ib1 = hwe.ib1;
859b81e0f2bSLorenzo Bianconi unlock:
860b81e0f2bSLorenzo Bianconi 	spin_unlock_bh(&ppe_lock);
861b81e0f2bSLorenzo Bianconi }
862b81e0f2bSLorenzo Bianconi 
863b81e0f2bSLorenzo Bianconi static int airoha_ppe_entry_idle_time(struct airoha_ppe *ppe,
864b81e0f2bSLorenzo Bianconi 				      struct airoha_flow_table_entry *e)
865b81e0f2bSLorenzo Bianconi {
866b81e0f2bSLorenzo Bianconi 	airoha_ppe_foe_flow_entry_update(ppe, e);
867b81e0f2bSLorenzo Bianconi 
868b81e0f2bSLorenzo Bianconi 	return airoha_ppe_get_entry_idle_time(ppe, e->data.ib1);
869b81e0f2bSLorenzo Bianconi }
870b81e0f2bSLorenzo Bianconi 
87100a76783SLorenzo Bianconi static int airoha_ppe_flow_offload_replace(struct airoha_gdm_port *port,
87200a76783SLorenzo Bianconi 					   struct flow_cls_offload *f)
87300a76783SLorenzo Bianconi {
87400a76783SLorenzo Bianconi 	struct flow_rule *rule = flow_cls_offload_flow_rule(f);
87500a76783SLorenzo Bianconi 	struct airoha_eth *eth = port->qdma->eth;
87600a76783SLorenzo Bianconi 	struct airoha_flow_table_entry *e;
87700a76783SLorenzo Bianconi 	struct airoha_flow_data data = {};
87800a76783SLorenzo Bianconi 	struct net_device *odev = NULL;
87900a76783SLorenzo Bianconi 	struct flow_action_entry *act;
88000a76783SLorenzo Bianconi 	struct airoha_foe_entry hwe;
88100a76783SLorenzo Bianconi 	int err, i, offload_type;
88200a76783SLorenzo Bianconi 	u16 addr_type = 0;
88300a76783SLorenzo Bianconi 	u8 l4proto = 0;
88400a76783SLorenzo Bianconi 
88500a76783SLorenzo Bianconi 	if (rhashtable_lookup(&eth->flow_table, &f->cookie,
88600a76783SLorenzo Bianconi 			      airoha_flow_table_params))
88700a76783SLorenzo Bianconi 		return -EEXIST;
88800a76783SLorenzo Bianconi 
88900a76783SLorenzo Bianconi 	if (!flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META))
89000a76783SLorenzo Bianconi 		return -EOPNOTSUPP;
89100a76783SLorenzo Bianconi 
89200a76783SLorenzo Bianconi 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
89300a76783SLorenzo Bianconi 		struct flow_match_control match;
89400a76783SLorenzo Bianconi 
89500a76783SLorenzo Bianconi 		flow_rule_match_control(rule, &match);
89600a76783SLorenzo Bianconi 		addr_type = match.key->addr_type;
89700a76783SLorenzo Bianconi 		if (flow_rule_has_control_flags(match.mask->flags,
89800a76783SLorenzo Bianconi 						f->common.extack))
89900a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
90000a76783SLorenzo Bianconi 	} else {
90100a76783SLorenzo Bianconi 		return -EOPNOTSUPP;
90200a76783SLorenzo Bianconi 	}
90300a76783SLorenzo Bianconi 
90400a76783SLorenzo Bianconi 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
90500a76783SLorenzo Bianconi 		struct flow_match_basic match;
90600a76783SLorenzo Bianconi 
90700a76783SLorenzo Bianconi 		flow_rule_match_basic(rule, &match);
90800a76783SLorenzo Bianconi 		l4proto = match.key->ip_proto;
90900a76783SLorenzo Bianconi 	} else {
91000a76783SLorenzo Bianconi 		return -EOPNOTSUPP;
91100a76783SLorenzo Bianconi 	}
91200a76783SLorenzo Bianconi 
91300a76783SLorenzo Bianconi 	switch (addr_type) {
91400a76783SLorenzo Bianconi 	case 0:
91500a76783SLorenzo Bianconi 		offload_type = PPE_PKT_TYPE_BRIDGE;
91600a76783SLorenzo Bianconi 		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
91700a76783SLorenzo Bianconi 			struct flow_match_eth_addrs match;
91800a76783SLorenzo Bianconi 
91900a76783SLorenzo Bianconi 			flow_rule_match_eth_addrs(rule, &match);
92000a76783SLorenzo Bianconi 			memcpy(data.eth.h_dest, match.key->dst, ETH_ALEN);
92100a76783SLorenzo Bianconi 			memcpy(data.eth.h_source, match.key->src, ETH_ALEN);
92200a76783SLorenzo Bianconi 		} else {
92300a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
92400a76783SLorenzo Bianconi 		}
92500a76783SLorenzo Bianconi 		break;
92600a76783SLorenzo Bianconi 	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
92700a76783SLorenzo Bianconi 		offload_type = PPE_PKT_TYPE_IPV4_HNAPT;
92800a76783SLorenzo Bianconi 		break;
92900a76783SLorenzo Bianconi 	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
93000a76783SLorenzo Bianconi 		offload_type = PPE_PKT_TYPE_IPV6_ROUTE_5T;
93100a76783SLorenzo Bianconi 		break;
93200a76783SLorenzo Bianconi 	default:
93300a76783SLorenzo Bianconi 		return -EOPNOTSUPP;
93400a76783SLorenzo Bianconi 	}
93500a76783SLorenzo Bianconi 
93600a76783SLorenzo Bianconi 	flow_action_for_each(i, act, &rule->action) {
93700a76783SLorenzo Bianconi 		switch (act->id) {
93800a76783SLorenzo Bianconi 		case FLOW_ACTION_MANGLE:
93900a76783SLorenzo Bianconi 			if (offload_type == PPE_PKT_TYPE_BRIDGE)
94000a76783SLorenzo Bianconi 				return -EOPNOTSUPP;
94100a76783SLorenzo Bianconi 
94200a76783SLorenzo Bianconi 			if (act->mangle.htype == FLOW_ACT_MANGLE_HDR_TYPE_ETH)
94300a76783SLorenzo Bianconi 				airoha_ppe_flow_mangle_eth(act, &data.eth);
94400a76783SLorenzo Bianconi 			break;
94500a76783SLorenzo Bianconi 		case FLOW_ACTION_REDIRECT:
94600a76783SLorenzo Bianconi 			odev = act->dev;
94700a76783SLorenzo Bianconi 			break;
94800a76783SLorenzo Bianconi 		case FLOW_ACTION_CSUM:
94900a76783SLorenzo Bianconi 			break;
95000a76783SLorenzo Bianconi 		case FLOW_ACTION_VLAN_PUSH:
95100a76783SLorenzo Bianconi 			if (data.vlan.num == 2 ||
95200a76783SLorenzo Bianconi 			    act->vlan.proto != htons(ETH_P_8021Q))
95300a76783SLorenzo Bianconi 				return -EOPNOTSUPP;
95400a76783SLorenzo Bianconi 
95500a76783SLorenzo Bianconi 			data.vlan.hdr[data.vlan.num].id = act->vlan.vid;
95600a76783SLorenzo Bianconi 			data.vlan.hdr[data.vlan.num].proto = act->vlan.proto;
95700a76783SLorenzo Bianconi 			data.vlan.num++;
95800a76783SLorenzo Bianconi 			break;
95900a76783SLorenzo Bianconi 		case FLOW_ACTION_VLAN_POP:
96000a76783SLorenzo Bianconi 			break;
96100a76783SLorenzo Bianconi 		case FLOW_ACTION_PPPOE_PUSH:
96200a76783SLorenzo Bianconi 			break;
96300a76783SLorenzo Bianconi 		default:
96400a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
96500a76783SLorenzo Bianconi 		}
96600a76783SLorenzo Bianconi 	}
96700a76783SLorenzo Bianconi 
96800a76783SLorenzo Bianconi 	if (!is_valid_ether_addr(data.eth.h_source) ||
96900a76783SLorenzo Bianconi 	    !is_valid_ether_addr(data.eth.h_dest))
97000a76783SLorenzo Bianconi 		return -EINVAL;
97100a76783SLorenzo Bianconi 
97209bccf56SLorenzo Bianconi 	err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type,
97300a76783SLorenzo Bianconi 					   &data, l4proto);
97400a76783SLorenzo Bianconi 	if (err)
97500a76783SLorenzo Bianconi 		return err;
97600a76783SLorenzo Bianconi 
97700a76783SLorenzo Bianconi 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
97800a76783SLorenzo Bianconi 		struct flow_match_ports ports;
97900a76783SLorenzo Bianconi 
98000a76783SLorenzo Bianconi 		if (offload_type == PPE_PKT_TYPE_BRIDGE)
98100a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
98200a76783SLorenzo Bianconi 
98300a76783SLorenzo Bianconi 		flow_rule_match_ports(rule, &ports);
98400a76783SLorenzo Bianconi 		data.src_port = ports.key->src;
98500a76783SLorenzo Bianconi 		data.dst_port = ports.key->dst;
98600a76783SLorenzo Bianconi 	} else if (offload_type != PPE_PKT_TYPE_BRIDGE) {
98700a76783SLorenzo Bianconi 		return -EOPNOTSUPP;
98800a76783SLorenzo Bianconi 	}
98900a76783SLorenzo Bianconi 
99000a76783SLorenzo Bianconi 	if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
99100a76783SLorenzo Bianconi 		struct flow_match_ipv4_addrs addrs;
99200a76783SLorenzo Bianconi 
99300a76783SLorenzo Bianconi 		flow_rule_match_ipv4_addrs(rule, &addrs);
99400a76783SLorenzo Bianconi 		data.v4.src_addr = addrs.key->src;
99500a76783SLorenzo Bianconi 		data.v4.dst_addr = addrs.key->dst;
99600a76783SLorenzo Bianconi 		airoha_ppe_foe_entry_set_ipv4_tuple(&hwe, &data, false);
99700a76783SLorenzo Bianconi 	}
99800a76783SLorenzo Bianconi 
99900a76783SLorenzo Bianconi 	if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
100000a76783SLorenzo Bianconi 		struct flow_match_ipv6_addrs addrs;
100100a76783SLorenzo Bianconi 
100200a76783SLorenzo Bianconi 		flow_rule_match_ipv6_addrs(rule, &addrs);
100300a76783SLorenzo Bianconi 
100400a76783SLorenzo Bianconi 		data.v6.src_addr = addrs.key->src;
100500a76783SLorenzo Bianconi 		data.v6.dst_addr = addrs.key->dst;
100600a76783SLorenzo Bianconi 		airoha_ppe_foe_entry_set_ipv6_tuple(&hwe, &data);
100700a76783SLorenzo Bianconi 	}
100800a76783SLorenzo Bianconi 
100900a76783SLorenzo Bianconi 	flow_action_for_each(i, act, &rule->action) {
101000a76783SLorenzo Bianconi 		if (act->id != FLOW_ACTION_MANGLE)
101100a76783SLorenzo Bianconi 			continue;
101200a76783SLorenzo Bianconi 
101300a76783SLorenzo Bianconi 		if (offload_type == PPE_PKT_TYPE_BRIDGE)
101400a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
101500a76783SLorenzo Bianconi 
101600a76783SLorenzo Bianconi 		switch (act->mangle.htype) {
101700a76783SLorenzo Bianconi 		case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
101800a76783SLorenzo Bianconi 		case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
101900a76783SLorenzo Bianconi 			err = airoha_ppe_flow_mangle_ports(act, &data);
102000a76783SLorenzo Bianconi 			break;
102100a76783SLorenzo Bianconi 		case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
102200a76783SLorenzo Bianconi 			err = airoha_ppe_flow_mangle_ipv4(act, &data);
102300a76783SLorenzo Bianconi 			break;
102400a76783SLorenzo Bianconi 		case FLOW_ACT_MANGLE_HDR_TYPE_ETH:
102500a76783SLorenzo Bianconi 			/* handled earlier */
102600a76783SLorenzo Bianconi 			break;
102700a76783SLorenzo Bianconi 		default:
102800a76783SLorenzo Bianconi 			return -EOPNOTSUPP;
102900a76783SLorenzo Bianconi 		}
103000a76783SLorenzo Bianconi 
103100a76783SLorenzo Bianconi 		if (err)
103200a76783SLorenzo Bianconi 			return err;
103300a76783SLorenzo Bianconi 	}
103400a76783SLorenzo Bianconi 
103500a76783SLorenzo Bianconi 	if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
103600a76783SLorenzo Bianconi 		err = airoha_ppe_foe_entry_set_ipv4_tuple(&hwe, &data, true);
103700a76783SLorenzo Bianconi 		if (err)
103800a76783SLorenzo Bianconi 			return err;
103900a76783SLorenzo Bianconi 	}
104000a76783SLorenzo Bianconi 
104100a76783SLorenzo Bianconi 	e = kzalloc(sizeof(*e), GFP_KERNEL);
104200a76783SLorenzo Bianconi 	if (!e)
104300a76783SLorenzo Bianconi 		return -ENOMEM;
104400a76783SLorenzo Bianconi 
104500a76783SLorenzo Bianconi 	e->cookie = f->cookie;
104600a76783SLorenzo Bianconi 	memcpy(&e->data, &hwe, sizeof(e->data));
104700a76783SLorenzo Bianconi 
104800a76783SLorenzo Bianconi 	err = airoha_ppe_foe_flow_commit_entry(eth->ppe, e);
104900a76783SLorenzo Bianconi 	if (err)
105000a76783SLorenzo Bianconi 		goto free_entry;
105100a76783SLorenzo Bianconi 
105200a76783SLorenzo Bianconi 	err = rhashtable_insert_fast(&eth->flow_table, &e->node,
105300a76783SLorenzo Bianconi 				     airoha_flow_table_params);
105400a76783SLorenzo Bianconi 	if (err < 0)
105500a76783SLorenzo Bianconi 		goto remove_foe_entry;
105600a76783SLorenzo Bianconi 
105700a76783SLorenzo Bianconi 	return 0;
105800a76783SLorenzo Bianconi 
105900a76783SLorenzo Bianconi remove_foe_entry:
106000a76783SLorenzo Bianconi 	airoha_ppe_foe_flow_remove_entry(eth->ppe, e);
106100a76783SLorenzo Bianconi free_entry:
106200a76783SLorenzo Bianconi 	kfree(e);
106300a76783SLorenzo Bianconi 
106400a76783SLorenzo Bianconi 	return err;
106500a76783SLorenzo Bianconi }
106600a76783SLorenzo Bianconi 
106700a76783SLorenzo Bianconi static int airoha_ppe_flow_offload_destroy(struct airoha_gdm_port *port,
106800a76783SLorenzo Bianconi 					   struct flow_cls_offload *f)
106900a76783SLorenzo Bianconi {
107000a76783SLorenzo Bianconi 	struct airoha_eth *eth = port->qdma->eth;
107100a76783SLorenzo Bianconi 	struct airoha_flow_table_entry *e;
107200a76783SLorenzo Bianconi 
107300a76783SLorenzo Bianconi 	e = rhashtable_lookup(&eth->flow_table, &f->cookie,
107400a76783SLorenzo Bianconi 			      airoha_flow_table_params);
107500a76783SLorenzo Bianconi 	if (!e)
107600a76783SLorenzo Bianconi 		return -ENOENT;
107700a76783SLorenzo Bianconi 
107800a76783SLorenzo Bianconi 	airoha_ppe_foe_flow_remove_entry(eth->ppe, e);
107900a76783SLorenzo Bianconi 	rhashtable_remove_fast(&eth->flow_table, &e->node,
108000a76783SLorenzo Bianconi 			       airoha_flow_table_params);
108100a76783SLorenzo Bianconi 	kfree(e);
108200a76783SLorenzo Bianconi 
108300a76783SLorenzo Bianconi 	return 0;
108400a76783SLorenzo Bianconi }
108500a76783SLorenzo Bianconi 
1086b81e0f2bSLorenzo Bianconi void airoha_ppe_foe_entry_get_stats(struct airoha_ppe *ppe, u32 hash,
1087b81e0f2bSLorenzo Bianconi 				    struct airoha_foe_stats64 *stats)
1088b81e0f2bSLorenzo Bianconi {
1089b81e0f2bSLorenzo Bianconi 	u32 index = airoha_ppe_foe_get_flow_stats_index(ppe, hash);
1090b81e0f2bSLorenzo Bianconi 	struct airoha_eth *eth = ppe->eth;
1091b81e0f2bSLorenzo Bianconi 	struct airoha_npu *npu;
1092b81e0f2bSLorenzo Bianconi 
1093b81e0f2bSLorenzo Bianconi 	if (index >= PPE_STATS_NUM_ENTRIES)
1094b81e0f2bSLorenzo Bianconi 		return;
1095b81e0f2bSLorenzo Bianconi 
1096b81e0f2bSLorenzo Bianconi 	rcu_read_lock();
1097b81e0f2bSLorenzo Bianconi 
1098b81e0f2bSLorenzo Bianconi 	npu = rcu_dereference(eth->npu);
1099b81e0f2bSLorenzo Bianconi 	if (npu) {
1100b81e0f2bSLorenzo Bianconi 		u64 packets = ppe->foe_stats[index].packets;
1101b81e0f2bSLorenzo Bianconi 		u64 bytes = ppe->foe_stats[index].bytes;
1102b81e0f2bSLorenzo Bianconi 		struct airoha_foe_stats npu_stats;
1103b81e0f2bSLorenzo Bianconi 
1104b81e0f2bSLorenzo Bianconi 		memcpy_fromio(&npu_stats, &npu->stats[index],
1105b81e0f2bSLorenzo Bianconi 			      sizeof(*npu->stats));
1106b81e0f2bSLorenzo Bianconi 		stats->packets = packets << 32 | npu_stats.packets;
1107b81e0f2bSLorenzo Bianconi 		stats->bytes = bytes << 32 | npu_stats.bytes;
1108b81e0f2bSLorenzo Bianconi 	}
1109b81e0f2bSLorenzo Bianconi 
1110b81e0f2bSLorenzo Bianconi 	rcu_read_unlock();
1111b81e0f2bSLorenzo Bianconi }
1112b81e0f2bSLorenzo Bianconi 
1113b81e0f2bSLorenzo Bianconi static int airoha_ppe_flow_offload_stats(struct airoha_gdm_port *port,
1114b81e0f2bSLorenzo Bianconi 					 struct flow_cls_offload *f)
1115b81e0f2bSLorenzo Bianconi {
1116b81e0f2bSLorenzo Bianconi 	struct airoha_eth *eth = port->qdma->eth;
1117b81e0f2bSLorenzo Bianconi 	struct airoha_flow_table_entry *e;
1118b81e0f2bSLorenzo Bianconi 	u32 idle;
1119b81e0f2bSLorenzo Bianconi 
1120b81e0f2bSLorenzo Bianconi 	e = rhashtable_lookup(&eth->flow_table, &f->cookie,
1121b81e0f2bSLorenzo Bianconi 			      airoha_flow_table_params);
1122b81e0f2bSLorenzo Bianconi 	if (!e)
1123b81e0f2bSLorenzo Bianconi 		return -ENOENT;
1124b81e0f2bSLorenzo Bianconi 
1125b81e0f2bSLorenzo Bianconi 	idle = airoha_ppe_entry_idle_time(eth->ppe, e);
1126b81e0f2bSLorenzo Bianconi 	f->stats.lastused = jiffies - idle * HZ;
1127b81e0f2bSLorenzo Bianconi 
1128b81e0f2bSLorenzo Bianconi 	if (e->hash != 0xffff) {
1129b81e0f2bSLorenzo Bianconi 		struct airoha_foe_stats64 stats = {};
1130b81e0f2bSLorenzo Bianconi 
1131b81e0f2bSLorenzo Bianconi 		airoha_ppe_foe_entry_get_stats(eth->ppe, e->hash, &stats);
1132b81e0f2bSLorenzo Bianconi 		f->stats.pkts += (stats.packets - e->stats.packets);
1133b81e0f2bSLorenzo Bianconi 		f->stats.bytes += (stats.bytes - e->stats.bytes);
1134b81e0f2bSLorenzo Bianconi 		e->stats = stats;
1135b81e0f2bSLorenzo Bianconi 	}
1136b81e0f2bSLorenzo Bianconi 
1137b81e0f2bSLorenzo Bianconi 	return 0;
1138b81e0f2bSLorenzo Bianconi }
1139b81e0f2bSLorenzo Bianconi 
114000a76783SLorenzo Bianconi static int airoha_ppe_flow_offload_cmd(struct airoha_gdm_port *port,
114100a76783SLorenzo Bianconi 				       struct flow_cls_offload *f)
114200a76783SLorenzo Bianconi {
114300a76783SLorenzo Bianconi 	switch (f->command) {
114400a76783SLorenzo Bianconi 	case FLOW_CLS_REPLACE:
114500a76783SLorenzo Bianconi 		return airoha_ppe_flow_offload_replace(port, f);
114600a76783SLorenzo Bianconi 	case FLOW_CLS_DESTROY:
114700a76783SLorenzo Bianconi 		return airoha_ppe_flow_offload_destroy(port, f);
1148b81e0f2bSLorenzo Bianconi 	case FLOW_CLS_STATS:
1149b81e0f2bSLorenzo Bianconi 		return airoha_ppe_flow_offload_stats(port, f);
115000a76783SLorenzo Bianconi 	default:
115100a76783SLorenzo Bianconi 		break;
115200a76783SLorenzo Bianconi 	}
115300a76783SLorenzo Bianconi 
115400a76783SLorenzo Bianconi 	return -EOPNOTSUPP;
115500a76783SLorenzo Bianconi }
115600a76783SLorenzo Bianconi 
115700a76783SLorenzo Bianconi static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe,
115800a76783SLorenzo Bianconi 					 struct airoha_npu *npu)
115900a76783SLorenzo Bianconi {
116000a76783SLorenzo Bianconi 	int i, sram_num_entries = PPE_SRAM_NUM_ENTRIES;
116100a76783SLorenzo Bianconi 	struct airoha_foe_entry *hwe = ppe->foe;
116200a76783SLorenzo Bianconi 
116300a76783SLorenzo Bianconi 	if (airoha_ppe2_is_enabled(ppe->eth))
116400a76783SLorenzo Bianconi 		sram_num_entries = sram_num_entries / 2;
116500a76783SLorenzo Bianconi 
116600a76783SLorenzo Bianconi 	for (i = 0; i < sram_num_entries; i++)
116700a76783SLorenzo Bianconi 		memset(&hwe[i], 0, sizeof(*hwe));
116800a76783SLorenzo Bianconi 
116900a76783SLorenzo Bianconi 	return npu->ops.ppe_flush_sram_entries(npu, ppe->foe_dma,
117000a76783SLorenzo Bianconi 					       PPE_SRAM_NUM_ENTRIES);
117100a76783SLorenzo Bianconi }
117200a76783SLorenzo Bianconi 
117300a76783SLorenzo Bianconi static struct airoha_npu *airoha_ppe_npu_get(struct airoha_eth *eth)
117400a76783SLorenzo Bianconi {
1175b81e0f2bSLorenzo Bianconi 	struct airoha_npu *npu = airoha_npu_get(eth->dev,
1176b81e0f2bSLorenzo Bianconi 						&eth->ppe->foe_stats_dma);
117700a76783SLorenzo Bianconi 
117800a76783SLorenzo Bianconi 	if (IS_ERR(npu)) {
117900a76783SLorenzo Bianconi 		request_module("airoha-npu");
1180b81e0f2bSLorenzo Bianconi 		npu = airoha_npu_get(eth->dev, &eth->ppe->foe_stats_dma);
118100a76783SLorenzo Bianconi 	}
118200a76783SLorenzo Bianconi 
118300a76783SLorenzo Bianconi 	return npu;
118400a76783SLorenzo Bianconi }
118500a76783SLorenzo Bianconi 
118600a76783SLorenzo Bianconi static int airoha_ppe_offload_setup(struct airoha_eth *eth)
118700a76783SLorenzo Bianconi {
118800a76783SLorenzo Bianconi 	struct airoha_npu *npu = airoha_ppe_npu_get(eth);
118900a76783SLorenzo Bianconi 	int err;
119000a76783SLorenzo Bianconi 
119100a76783SLorenzo Bianconi 	if (IS_ERR(npu))
119200a76783SLorenzo Bianconi 		return PTR_ERR(npu);
119300a76783SLorenzo Bianconi 
119400a76783SLorenzo Bianconi 	err = npu->ops.ppe_init(npu);
119500a76783SLorenzo Bianconi 	if (err)
119600a76783SLorenzo Bianconi 		goto error_npu_put;
119700a76783SLorenzo Bianconi 
119800a76783SLorenzo Bianconi 	airoha_ppe_hw_init(eth->ppe);
119900a76783SLorenzo Bianconi 	err = airoha_ppe_flush_sram_entries(eth->ppe, npu);
120000a76783SLorenzo Bianconi 	if (err)
120100a76783SLorenzo Bianconi 		goto error_npu_put;
120200a76783SLorenzo Bianconi 
1203b81e0f2bSLorenzo Bianconi 	airoha_ppe_foe_flow_stats_reset(eth->ppe, npu);
1204b81e0f2bSLorenzo Bianconi 
120500a76783SLorenzo Bianconi 	rcu_assign_pointer(eth->npu, npu);
120600a76783SLorenzo Bianconi 	synchronize_rcu();
120700a76783SLorenzo Bianconi 
120800a76783SLorenzo Bianconi 	return 0;
120900a76783SLorenzo Bianconi 
121000a76783SLorenzo Bianconi error_npu_put:
121100a76783SLorenzo Bianconi 	airoha_npu_put(npu);
121200a76783SLorenzo Bianconi 
121300a76783SLorenzo Bianconi 	return err;
121400a76783SLorenzo Bianconi }
121500a76783SLorenzo Bianconi 
1216df8398fbSLorenzo Bianconi int airoha_ppe_setup_tc_block_cb(struct net_device *dev, void *type_data)
121700a76783SLorenzo Bianconi {
121800a76783SLorenzo Bianconi 	struct airoha_gdm_port *port = netdev_priv(dev);
1219df8398fbSLorenzo Bianconi 	struct flow_cls_offload *cls = type_data;
122000a76783SLorenzo Bianconi 	struct airoha_eth *eth = port->qdma->eth;
122100a76783SLorenzo Bianconi 	int err = 0;
122200a76783SLorenzo Bianconi 
122300a76783SLorenzo Bianconi 	mutex_lock(&flow_offload_mutex);
122400a76783SLorenzo Bianconi 
122500a76783SLorenzo Bianconi 	if (!eth->npu)
122600a76783SLorenzo Bianconi 		err = airoha_ppe_offload_setup(eth);
122700a76783SLorenzo Bianconi 	if (!err)
122800a76783SLorenzo Bianconi 		err = airoha_ppe_flow_offload_cmd(port, cls);
122900a76783SLorenzo Bianconi 
123000a76783SLorenzo Bianconi 	mutex_unlock(&flow_offload_mutex);
123100a76783SLorenzo Bianconi 
123200a76783SLorenzo Bianconi 	return err;
123300a76783SLorenzo Bianconi }
123400a76783SLorenzo Bianconi 
1235cd53f622SLorenzo Bianconi void airoha_ppe_check_skb(struct airoha_ppe *ppe, struct sk_buff *skb,
1236cd53f622SLorenzo Bianconi 			  u16 hash)
123700a76783SLorenzo Bianconi {
123800a76783SLorenzo Bianconi 	u16 now, diff;
123900a76783SLorenzo Bianconi 
124000a76783SLorenzo Bianconi 	if (hash > PPE_HASH_MASK)
124100a76783SLorenzo Bianconi 		return;
124200a76783SLorenzo Bianconi 
124300a76783SLorenzo Bianconi 	now = (u16)jiffies;
124400a76783SLorenzo Bianconi 	diff = now - ppe->foe_check_time[hash];
124500a76783SLorenzo Bianconi 	if (diff < HZ / 10)
124600a76783SLorenzo Bianconi 		return;
124700a76783SLorenzo Bianconi 
124800a76783SLorenzo Bianconi 	ppe->foe_check_time[hash] = now;
1249cd53f622SLorenzo Bianconi 	airoha_ppe_foe_insert_entry(ppe, skb, hash);
125000a76783SLorenzo Bianconi }
125100a76783SLorenzo Bianconi 
1252a869d3a5SLorenzo Bianconi void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
1253a869d3a5SLorenzo Bianconi {
1254a869d3a5SLorenzo Bianconi 	struct airoha_eth *eth = port->qdma->eth;
1255a869d3a5SLorenzo Bianconi 	struct net_device *dev = port->dev;
1256a869d3a5SLorenzo Bianconi 	const u8 *addr = dev->dev_addr;
1257a869d3a5SLorenzo Bianconi 	u32 val;
1258a869d3a5SLorenzo Bianconi 
1259a869d3a5SLorenzo Bianconi 	val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
1260a869d3a5SLorenzo Bianconi 	airoha_fe_wr(eth, REG_UPDMEM_DATA(0), val);
1261a869d3a5SLorenzo Bianconi 	airoha_fe_wr(eth, REG_UPDMEM_CTRL(0),
1262a869d3a5SLorenzo Bianconi 		     FIELD_PREP(PPE_UPDMEM_ADDR_MASK, port->id) |
1263a869d3a5SLorenzo Bianconi 		     PPE_UPDMEM_WR_MASK | PPE_UPDMEM_REQ_MASK);
1264a869d3a5SLorenzo Bianconi 
1265a869d3a5SLorenzo Bianconi 	val = (addr[0] << 8) | addr[1];
1266a869d3a5SLorenzo Bianconi 	airoha_fe_wr(eth, REG_UPDMEM_DATA(0), val);
1267a869d3a5SLorenzo Bianconi 	airoha_fe_wr(eth, REG_UPDMEM_CTRL(0),
1268a869d3a5SLorenzo Bianconi 		     FIELD_PREP(PPE_UPDMEM_ADDR_MASK, port->id) |
1269a869d3a5SLorenzo Bianconi 		     FIELD_PREP(PPE_UPDMEM_OFFSET_MASK, 1) |
1270a869d3a5SLorenzo Bianconi 		     PPE_UPDMEM_WR_MASK | PPE_UPDMEM_REQ_MASK);
1271a869d3a5SLorenzo Bianconi }
1272a869d3a5SLorenzo Bianconi 
127300a76783SLorenzo Bianconi int airoha_ppe_init(struct airoha_eth *eth)
127400a76783SLorenzo Bianconi {
127500a76783SLorenzo Bianconi 	struct airoha_ppe *ppe;
12763fe15c64SLorenzo Bianconi 	int foe_size, err;
127700a76783SLorenzo Bianconi 
127800a76783SLorenzo Bianconi 	ppe = devm_kzalloc(eth->dev, sizeof(*ppe), GFP_KERNEL);
127900a76783SLorenzo Bianconi 	if (!ppe)
128000a76783SLorenzo Bianconi 		return -ENOMEM;
128100a76783SLorenzo Bianconi 
128200a76783SLorenzo Bianconi 	foe_size = PPE_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
128300a76783SLorenzo Bianconi 	ppe->foe = dmam_alloc_coherent(eth->dev, foe_size, &ppe->foe_dma,
128400a76783SLorenzo Bianconi 				       GFP_KERNEL);
128500a76783SLorenzo Bianconi 	if (!ppe->foe)
128600a76783SLorenzo Bianconi 		return -ENOMEM;
128700a76783SLorenzo Bianconi 
128800a76783SLorenzo Bianconi 	ppe->eth = eth;
128900a76783SLorenzo Bianconi 	eth->ppe = ppe;
129000a76783SLorenzo Bianconi 
129100a76783SLorenzo Bianconi 	ppe->foe_flow = devm_kzalloc(eth->dev,
129200a76783SLorenzo Bianconi 				     PPE_NUM_ENTRIES * sizeof(*ppe->foe_flow),
129300a76783SLorenzo Bianconi 				     GFP_KERNEL);
129400a76783SLorenzo Bianconi 	if (!ppe->foe_flow)
129500a76783SLorenzo Bianconi 		return -ENOMEM;
129600a76783SLorenzo Bianconi 
1297b81e0f2bSLorenzo Bianconi 	foe_size = PPE_STATS_NUM_ENTRIES * sizeof(*ppe->foe_stats);
1298b81e0f2bSLorenzo Bianconi 	if (foe_size) {
1299b81e0f2bSLorenzo Bianconi 		ppe->foe_stats = dmam_alloc_coherent(eth->dev, foe_size,
1300b81e0f2bSLorenzo Bianconi 						     &ppe->foe_stats_dma,
1301b81e0f2bSLorenzo Bianconi 						     GFP_KERNEL);
1302b81e0f2bSLorenzo Bianconi 		if (!ppe->foe_stats)
1303b81e0f2bSLorenzo Bianconi 			return -ENOMEM;
1304b81e0f2bSLorenzo Bianconi 	}
1305b81e0f2bSLorenzo Bianconi 
13063fe15c64SLorenzo Bianconi 	err = rhashtable_init(&eth->flow_table, &airoha_flow_table_params);
13073fe15c64SLorenzo Bianconi 	if (err)
13083fe15c64SLorenzo Bianconi 		return err;
13093fe15c64SLorenzo Bianconi 
1310b4916f67SLorenzo Bianconi 	err = rhashtable_init(&ppe->l2_flows, &airoha_l2_flow_table_params);
1311b4916f67SLorenzo Bianconi 	if (err)
1312b4916f67SLorenzo Bianconi 		goto error_flow_table_destroy;
1313b4916f67SLorenzo Bianconi 
13143fe15c64SLorenzo Bianconi 	err = airoha_ppe_debugfs_init(ppe);
13153fe15c64SLorenzo Bianconi 	if (err)
1316b4916f67SLorenzo Bianconi 		goto error_l2_flow_table_destroy;
1317b4916f67SLorenzo Bianconi 
1318b4916f67SLorenzo Bianconi 	return 0;
1319b4916f67SLorenzo Bianconi 
1320b4916f67SLorenzo Bianconi error_l2_flow_table_destroy:
1321b4916f67SLorenzo Bianconi 	rhashtable_destroy(&ppe->l2_flows);
1322b4916f67SLorenzo Bianconi error_flow_table_destroy:
13233fe15c64SLorenzo Bianconi 	rhashtable_destroy(&eth->flow_table);
13243fe15c64SLorenzo Bianconi 
13253fe15c64SLorenzo Bianconi 	return err;
132600a76783SLorenzo Bianconi }
132700a76783SLorenzo Bianconi 
132800a76783SLorenzo Bianconi void airoha_ppe_deinit(struct airoha_eth *eth)
132900a76783SLorenzo Bianconi {
133000a76783SLorenzo Bianconi 	struct airoha_npu *npu;
133100a76783SLorenzo Bianconi 
133200a76783SLorenzo Bianconi 	rcu_read_lock();
133300a76783SLorenzo Bianconi 	npu = rcu_dereference(eth->npu);
133400a76783SLorenzo Bianconi 	if (npu) {
133500a76783SLorenzo Bianconi 		npu->ops.ppe_deinit(npu);
133600a76783SLorenzo Bianconi 		airoha_npu_put(npu);
133700a76783SLorenzo Bianconi 	}
133800a76783SLorenzo Bianconi 	rcu_read_unlock();
133900a76783SLorenzo Bianconi 
1340b4916f67SLorenzo Bianconi 	rhashtable_destroy(&eth->ppe->l2_flows);
134100a76783SLorenzo Bianconi 	rhashtable_destroy(&eth->flow_table);
13423fe15c64SLorenzo Bianconi 	debugfs_remove(eth->ppe->debugfs_dir);
134300a76783SLorenzo Bianconi }
1344