From: Navid Ghahremani <ghahramani.navid@gmail.com>
To: linux-arm-kernel@lists.infradead.org
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
arnd@arndb.de, linux@armlinux.org.uk, stefandoesinger@gmail.com,
robh@kernel.org, krzk@kernel.org,
Navid Ghahremani <ghahramani.navid@gmail.com>
Subject: [PATCH 18/24] net: ethernet: zte: Add ZX279128S Ethernet switch driver
Date: Sat, 10 Oct 2026 13:59:56 +0100 [thread overview]
Message-ID: <20261010130024.177503-19-ghahramani.navid@gmail.com> (raw)
In-Reply-To: <20261010130024.177503-1-ghahramani.navid@gmail.com>
Provide per-port interfaces, phylink, IRQ NAPI, switchdev bridge
forwarding, hardware IPv4 flow offload and verified standard MAC
statistics. Use software fallback for unsupported VLAN and routing
cases.
Assisted-by: LLM
Signed-off-by: Navid Ghahremani <ghahramani.navid@gmail.com>
---
drivers/net/ethernet/zte/Kconfig | 13 +
drivers/net/ethernet/zte/Makefile | 3 +
drivers/net/ethernet/zte/zx279128s-eth.h | 385 +++++
drivers/net/ethernet/zte/zx279128s-main.c | 1485 +++++++++++++++++++
drivers/net/ethernet/zte/zx279128s-ppe.c | 803 ++++++++++
drivers/net/ethernet/zte/zx279128s-switch.c | 339 +++++
6 files changed, 3028 insertions(+)
create mode 100644 drivers/net/ethernet/zte/zx279128s-eth.h
create mode 100644 drivers/net/ethernet/zte/zx279128s-main.c
create mode 100644 drivers/net/ethernet/zte/zx279128s-ppe.c
create mode 100644 drivers/net/ethernet/zte/zx279128s-switch.c
diff --git a/drivers/net/ethernet/zte/Kconfig b/drivers/net/ethernet/zte/Kconfig
index b95c2fc7db..20df75486f 100644
--- a/drivers/net/ethernet/zte/Kconfig
+++ b/drivers/net/ethernet/zte/Kconfig
@@ -17,4 +17,17 @@ if NET_VENDOR_ZTE
source "drivers/net/ethernet/zte/dinghai/Kconfig"
+config ZX279128S_ETH
+ bool "ZTE zx279128s Ethernet switch support"
+ depends on ARCH_ZTE || COMPILE_TEST
+ depends on OF
+ depends on BRIDGE=y || BRIDGE=n
+ select NET_SWITCHDEV
+ select PHYLINK
+ help
+ This driver supports the five-port gigabit switch of the ZTE
+ zx279128s SoC and the DMA engine that connects it to the CPU.
+ Each switch port becomes its own network interface, such as the
+ four LAN ports and the WAN port of the ZTE ZXHN H3600.
+
endif # NET_VENDOR_ZTE
diff --git a/drivers/net/ethernet/zte/Makefile b/drivers/net/ethernet/zte/Makefile
index cd9929b615..09b4f6558d 100644
--- a/drivers/net/ethernet/zte/Makefile
+++ b/drivers/net/ethernet/zte/Makefile
@@ -4,3 +4,6 @@
#
obj-$(CONFIG_DINGHAI) += dinghai/
+
+obj-$(CONFIG_ZX279128S_ETH) += zx279128s-eth.o
+zx279128s-eth-y := zx279128s-main.o zx279128s-ppe.o zx279128s-switch.o
diff --git a/drivers/net/ethernet/zte/zx279128s-eth.h b/drivers/net/ethernet/zte/zx279128s-eth.h
new file mode 100644
index 0000000000..4c41b6c5ed
--- /dev/null
+++ b/drivers/net/ethernet/zte/zx279128s-eth.h
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ZTE zx279128s Ethernet switch and DMA driver: registers and shared state
+ *
+ * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid@gmail.com>
+ */
+
+#ifndef ZX279128S_ETH_H
+#define ZX279128S_ETH_H
+
+#include <linux/bitmap.h>
+#include <linux/hrtimer.h>
+#include <linux/if_ether.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/notifier.h>
+#include <linux/phylink.h>
+#include <linux/rhashtable.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#define DRV_NAME "zx279128s-eth"
+
+/*
+ * Register offsets from the start of the block (0x92000000). The names follow
+ * the vendor modules where they are known. Registers that only appear in the
+ * vendor's init sequences, with no known meaning, keep their offsets.
+ */
+
+/* Global control */
+#define ZX_GLB_RESET 0x000008 /* write 0, then all ones */
+#define ZX_GLB_CLK_CTRL 0x040018
+#define ZX_GLB_CLK_CTRL_BITS GENMASK(1, 0) /* cleared by the vendor code */
+#define ZX_GLB_PORT_CLK_EN 0x04001c /* bit n: clock of MAC port n */
+
+/* Switch core */
+#define ZX_SW_PORT_FILTER(p) (0x1c0060 + 4 * (p))
+#define ZX_SW_PORT_FILTER_BITS GENMASK(25, 23) /* cleared for every port */
+#define ZX_SW_FWD_CTRL 0x1cc000
+#define ZX_SW_FWD_CTRL_ON 17
+
+/* Switch port attributes (stock tm.ko "spa") */
+#define ZX_SPA_UP_REG_PKT_EN 0x1d4000
+#define ZX_SPA_DN_REG_PKT_EN 0x1d4040
+#define ZX_SPA_TRAP_DMAC 0x1d41a0 /* 8 bytes per entry */
+#define ZX_SPA_TRAP_DMAC_NUM 2 /* entries the driver uses */
+#define ZX_SPA_TRAP_PROTO 0x1d41c0
+#define ZX_SPA_TRAP_ETH_TYPE 0x1d41c4 /* 4 x 16 bits, big end first */
+#define ZX_SPA_PORT_EN(i) (0x1d428c + 4 * (i))
+#define ZX_SPA_ONU_MAC 0x1d4120 /* 16 x 8 bytes: our MACs, routed */
+#define ZX_SPA_ONU_MAC_NUM 16
+#define ZX_SPA_REG_PKT_TCP_ACK BIT(4) /* word 2: TCP without payload */
+
+/* MAC ports 0-4 */
+#define ZX_MAC_BASE 0x200000
+#define ZX_PORT_STRIDE 0x040000
+#define ZX_MAC_PORT(p) (ZX_MAC_BASE + (p) * ZX_PORT_STRIDE)
+#define ZX_MAC_CTRL 0x000
+#define ZX_MAC_CTRL_EN GENMASK(1, 0) /* receive and transmit */
+#define ZX_MAC_CTRL_FD BIT(13) /* full duplex */
+#define ZX_MAC_CTRL_100 BIT(14) /* 100 Mbit/s, with _MII */
+#define ZX_MAC_CTRL_MII BIT(15) /* 10 or 100 Mbit/s */
+
+/* MAC port counters: 32 bits, not cleared by reading */
+#define ZX_MAC_TX_HIST(i) (0x724 + 4 * (i)) /* frame size bins */
+#define ZX_MAC_TX_MCAST 0x740
+#define ZX_MAC_TX_BCAST 0x744
+#define ZX_MAC_TX_OCTETS 0x764
+#define ZX_MAC_TX_FRAMES 0x768
+#define ZX_MAC_RX_OCTETS 0x784
+#define ZX_MAC_RX_BCAST 0x78c
+#define ZX_MAC_RX_MCAST 0x790
+#define ZX_MAC_RX_HIST(i) (0x7ac + 4 * (i))
+#define ZX_MAC_RX_UCAST 0x7c4
+#define ZX_MAC_HIST_BINS 6 /* 64, 65-127, ... 1024 bytes and up */
+
+/* Traffic manager */
+#define ZX_TM_BASE 0x340000
+#define ZX_TM_CTRL (ZX_TM_BASE + 0x00)
+#define ZX_TM_CTRL_BIT6 BIT(6) /* set by the vendor code */
+#define ZX_TM_CONFIG_START (ZX_TM_BASE + 0x04)
+#define ZX_TM_TAB0_ADDR (ZX_TM_BASE + 0xe8)
+#define ZX_TM_TAB1_ADDR (ZX_TM_BASE + 0xec)
+#define ZX_TM_BUF_F0 (ZX_TM_BASE + 0xf0)
+#define ZX_TM_BUF_START (ZX_TM_BASE + 0xf4)
+#define ZX_TM_BUF_END (ZX_TM_BASE + 0xf8)
+#define ZX_TM_TAB_LIMIT (ZX_TM_BASE + 0xfc)
+#define ZX_TM_INT_STATUS (ZX_TM_BASE + 0x100)
+#define ZX_TM_INT_MASK (ZX_TM_BASE + 0x104) /* 1: masked */
+#define ZX_TM_INT_RX GENMASK_U32(1, 0) /* frames for the CPU */
+
+/* Traffic manager tables, reached through an indirect access window */
+#define ZX_TM_IND_CMD (ZX_TM_BASE + 0x4014)
+#define ZX_TM_IND_INDEX GENMASK(21, 0)
+#define ZX_TM_IND_TABLE GENMASK(26, 22)
+#define ZX_TM_IND_READ BIT(27)
+#define ZX_TM_IND_STATUS (ZX_TM_BASE + 0x4018)
+#define ZX_TM_IND_READY BIT(0)
+#define ZX_TM_IND_DATA(i) (ZX_TM_BASE + 0x401c + 4 * (i))
+#define ZX_TM_TABLE_QUEUE 0
+#define ZX_TM_TABLE_QUEUE_USE 1
+#define ZX_TM_TABLE_RED 2
+#define ZX_TM_TABLE_SCHED 4
+
+/* Release of consumed RX slots */
+#define ZX_RX_REL_CTRL (ZX_TM_BASE + 0x4064)
+#define ZX_RX_REL_BUSY BIT(0)
+#define ZX_RX_REL_DATA (ZX_TM_BASE + 0x4068)
+#define ZX_RX_REL_QUEUE GENMASK(2, 0)
+#define ZX_RX_REL_CLASS BIT(3)
+#define ZX_RX_REL_COUNT GENMASK(13, 4)
+
+/* Buffer management unit */
+#define ZX_BMU_BASE (ZX_TM_BASE + 0x8000)
+#define ZX_BMU_CTRL (ZX_BMU_BASE + 0x00)
+#define ZX_BMU_CTRL_EN BIT(0)
+#define ZX_BMU_CFG1 (ZX_BMU_BASE + 0x04)
+#define ZX_BMU_CFG2 (ZX_BMU_BASE + 0x08)
+#define ZX_BMU_BUF_ID (ZX_BMU_BASE + 0x0c)
+#define ZX_BMU_BUF_ID_VALID BIT(31)
+#define ZX_BMU_BUF_ID_BP GENMASK(15, 0)
+#define ZX_BMU_BUF_FREE (ZX_BMU_BASE + 0x10)
+#define ZX_BMU_BUF_ALLOC (ZX_BMU_BASE + 0x14)
+#define ZX_BMU_BUF_ALLOC_REQ BIT(0)
+#define ZX_BMU_BUF_ALLOC_BUSY GENMASK(1, 0)
+#define ZX_BMU_FREE_STATUS (ZX_BMU_BASE + 0xdc)
+#define ZX_BMU_FREE_SLOTS GENMASK(8, 3)
+#define ZX_BMU_THRES1 (ZX_BMU_BASE + 0x48)
+#define ZX_BMU_THRES2 (ZX_BMU_BASE + 0x4c)
+#define ZX_BMU_LIMIT1 (ZX_BMU_BASE + 0x58)
+#define ZX_BMU_LIMIT2 (ZX_BMU_BASE + 0x5c)
+
+/* DMA engine between the switch and the CPU */
+#define ZX_DMA_BASE (ZX_TM_BASE + 0x10000)
+#define ZX_DMA_CTRL (ZX_DMA_BASE + 0x00)
+#define ZX_DMA_CTRL_EN GENMASK(19, 16)
+#define ZX_DMA_CTRL_BIT21 BIT(21) /* always set, as in the vendor code */
+#define ZX_DMA_RX_IRQ_TIMER (ZX_DMA_BASE + 0x30) /* in 20 ns ticks */
+#define ZX_DMA_RX_IRQ_FRAMES (ZX_DMA_BASE + 0x34)
+#define ZX_DMA_RING_CFG (ZX_DMA_BASE + 0x3c)
+#define ZX_DMA_AREA50 (ZX_DMA_BASE + 0x50) /* 1 MiB area, use unknown */
+#define ZX_DMA_TX_RING (ZX_DMA_BASE + 0x60) /* ring of frames to send */
+#define ZX_DMA_TX_START (ZX_DMA_BASE + 0x64)
+#define ZX_DMA_RX_QUEUE_CNT(q) (ZX_DMA_BASE + 0x100 + ((q) * 4))
+
+/* Packet processor */
+#define ZX_PP_RESET 0x380000
+
+/* Bridge block (sbrg), ports numbered with ZX_BRPORT() */
+#define ZX_SBRG_PORT_CTRL 0x388004
+#define ZX_SBRG_FLUSH_PORTS GENMASK(15, 8) /* drop the entries of these */
+#define ZX_SBRG_FLUSH BIT(16)
+#define ZX_SBRG_IND_CMD 0x388014 /* 27: read, 26-22: memory, 11-0: entry */
+#define ZX_SBRG_IND_READ BIT(27)
+#define ZX_SBRG_IND_DONE 0x388018
+#define ZX_SBRG_IND_DATA(i) (0x38801c + 4 * (i))
+#define ZX_SBRG_TABLE_SEL 0x388184 /* source table: 4 RAMs of 1024/256/512 */
+#define ZX_SBRG_LEARN 0x3881c0 /* ports that learn source MACs */
+#define ZX_SBRG_EGRESS(bp) (0x3883c0 + 4 * (bp)) /* ports bp may send to */
+#define ZX_SBRG_MEM_VLAN 4 /* 1 word per VID */
+#define ZX_SBRG_VLAN_VALID BIT(0)
+#define ZX_SBRG_VLAN_UNTAG(bp) (1 << (2 * (bp) + 1))
+
+/* Packet processor tables, reached through an indirect access window:
+ * command (index | table << 22 | read << 27), status bit 0 = idle, data.
+ * The classifier (CLA) has 17 data words, the packet modifier (PM) 8.
+ */
+#define ZX_CLA_BASE 0x38c000
+#define ZX_PM_BASE 0x39c000
+#define ZX_PP_IND_CMD 0x14
+#define ZX_PP_IND_STATUS 0x18
+#define ZX_PP_IND_DATA 0x1c
+#define ZX_PM_IND_DATA_HI 0x100 /* PM data words 4-7 */
+#define ZX_PP_IND_READ BIT(27)
+
+#define ZX_CLA_EXTRA_INDEX 0 /* header fields each packet type extracts */
+#define ZX_CLA_EXTRA_RULE 1
+#define ZX_CLA_HASH0 2 /* 256 flow entries, CRC-32 slot */
+#define ZX_CLA_HASH1 3 /* 128 flow entries, CRC-32C slot */
+#define ZX_CLA_AGING 8 /* hit flags, bank 0 then bank 1 */
+#define ZX_PM_FLOW 0 /* rewrite of a flow */
+#define ZX_PM_NEXT_HOP 1 /* new destination IP and MAC */
+#define ZX_PM_CMD 3
+#define ZX_PM_SUB 6
+#define ZX_PM_SRC_MAC 12 /* source MAC of each subnet */
+#define ZX_PP_SNAT_IP(s) (0x3a0400 + (s) * 4) /* source IP of each subnet */
+
+#define ZX_PPE_HASH0_SIZE 256
+#define ZX_PPE_HASH_SIZE (256 + 128)
+/*
+ * The rewrite index in a classifier entry has 7 bits (ZX_CLA_W0_FLOW), and
+ * the stock firmware never used more than 125, so only 127 flows (1-127) can
+ * be offloaded at a time. More would reuse the rewrite of another flow.
+ */
+#define ZX_PPE_FLOWS 128
+#define ZX_PPE_NEXT_HOPS 512
+#define ZX_PPE_SUBNETS 16
+/* IPv4 TCP/UDP: extract index 9 and its rule; hashed with this rule id */
+#define ZX_PPE_V4_INDEX 9
+#define ZX_PPE_V4_RULE 0x98
+
+/* Word 0 of a classifier hash entry */
+#define ZX_CLA_W0_FLOW GENMASK(31, 25) /* packet modifier rewrite index */
+#define ZX_CLA_W0_FWD BIT(24) /* forward to the egress port */
+#define ZX_CLA_W0_EGRESS GENMASK(15, 12) /* bridge port, ZX_BRPORT() */
+#define ZX_CLA_W0_BITS 0x44 /* as in every stock entry */
+
+/* Bridge ports: 0 is the PON side, 1-5 are the switch ports 0-4 */
+#define ZX_BRPORT(p) ((p) + 1)
+
+/* DMA buffer pool: 4096 buffers x 2048 bytes = 8 MiB */
+#define ZX_NUM_BUFFERS 4096
+#define ZX_BUF_SIZE 2048
+#define ZX_BUF_POOL_SIZE (ZX_NUM_BUFFERS * ZX_BUF_SIZE)
+
+/* Free-index tables (written big-endian u16 per U-Boot convention) */
+#define ZX_TAB0_ENTRIES ZX_NUM_BUFFERS
+#define ZX_TAB1_ENTRIES 512
+
+/* DMA descriptor rings */
+#define ZX_NUM_TX_DESC 1024
+#define ZX_TX_QUEUE_LIMIT 256
+#define ZX_TX_POLL_US 250 /* see zx_tx_kick() */
+
+/*
+ * The RX interrupt comes after this many frames, or this long after the
+ * first one. Larger batches cost less per frame: 500 us receive faster
+ * than polling every 1 ms did and still answer sooner.
+ */
+#define ZX_RX_IRQ_FRAMES 64
+#define ZX_RX_IRQ_USECS 500
+#define ZX_NUM_RX_DESC 1024
+#define ZX_DESC_SIZE 16 /* bytes per descriptor */
+
+/* Layout of the 16 MiB DMA arena */
+#define ZX_DMA_ARENA_SIZE 0x1000000
+#define ZX_TAB0_OFFSET 0x800000
+#define ZX_TAB1_OFFSET 0x808000
+#define ZX_RX_OFFSET 0xa00000
+#define ZX_AREA50_OFFSET 0xb00000
+#define ZX_TX_OFFSET 0xc00000
+/* RX ring of queue q at ZX_RX_OFFSET + q * 16 KiB (1024 descriptors) */
+#define ZX_RX_QUEUE_STRIDE 0x4000
+#define ZX_RX_QUEUES 8
+#define ZX_DMA_TX_DONE (ZX_DMA_BASE + 0x68)
+
+struct zx_eth_adapter;
+
+/* One net_device per switch port: ports 0-3 LAN, port 4 WAN */
+#define ZX_NUM_PORTS 5
+/* TM egress queue of switch port p (as in the vendor driver) */
+#define ZX_PORT_TX_QUEUE(p) (40 + (p))
+/* MACs the Linux bridge moved away from a switch port, queued for removal */
+#define ZX_SW_FDB_QUEUE 16
+
+/* The MAC counters the driver keeps, see zx_mib_regs[] */
+enum zx_mib {
+ ZX_MIB_TX_OCTETS,
+ ZX_MIB_TX_FRAMES,
+ ZX_MIB_TX_MCAST,
+ ZX_MIB_TX_BCAST,
+ ZX_MIB_TX_HIST,
+ ZX_MIB_RX_OCTETS = ZX_MIB_TX_HIST + ZX_MAC_HIST_BINS,
+ ZX_MIB_RX_UCAST,
+ ZX_MIB_RX_MCAST,
+ ZX_MIB_RX_BCAST,
+ ZX_MIB_RX_HIST,
+ ZX_MIB_NUM = ZX_MIB_RX_HIST + ZX_MAC_HIST_BINS,
+};
+
+struct zx_eth_priv {
+ struct net_device *netdev;
+ struct zx_eth_adapter *adapter;
+ int port;
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
+
+ /* MAC counters, summed up into 64 bits (under adapter->mib_lock) */
+ u64 mib[ZX_MIB_NUM];
+ u32 mib_last[ZX_MIB_NUM]; /* last register values */
+};
+
+struct zx_eth_adapter {
+ struct device *dev;
+ void __iomem *base;
+ struct napi_struct napi;
+ struct hrtimer tx_timer;
+
+ struct net_device *ports[ZX_NUM_PORTS];
+ int open_count;
+ int irq;
+
+ /* DMA arena remains allocated across down/up */
+ void *buffer_pool;
+ dma_addr_t buf_pool_dma;
+ void *tx_ring;
+ dma_addr_t tx_ring_dma;
+ void *rx_ring;
+ dma_addr_t rx_ring_dma;
+ void *tab0;
+ dma_addr_t tab0_dma;
+ void *tab1;
+ dma_addr_t tab1_dma;
+
+ u32 tx_cur_idx;
+ u32 tx_pending;
+ u32 tx_complete_cnt;
+ u32 tx_done_excess;
+ u32 bmu_free_slots;
+ u32 bmu_alloc_fail;
+ u32 bmu_alloc_late; /* buffers of timed-out requests */
+ bool bmu_alloc_pending;
+ u32 bmu_free_fail;
+ u32 rx_cur_idx[ZX_RX_QUEUES];
+ bool rx_synced[ZX_RX_QUEUES];
+ u32 rx_resync_cnt;
+ u32 rx_alias_cnt;
+ u32 rx_nobuf_cnt;
+ u32 rx_rel_fail;
+ u32 rx_bad_cnt;
+
+ /* TX ring, BMU buffer allocation and the TX counters */
+ spinlock_t tx_lock;
+ /* BMU free register and bmu_free_slots */
+ spinlock_t bmu_free_lock;
+
+ /* MAC counters of all ports, read before their 32 bits wrap */
+ struct mutex mib_lock;
+ struct delayed_work mib_work;
+
+ /* Hardware flow offload (packet processor NAT) */
+ struct mutex ppe_lock;
+ bool ppe_active;
+ int ppe_users; /* bound flowtables */
+ struct rhashtable ppe_flows;
+ DECLARE_BITMAP(ppe_hash_used, ZX_PPE_HASH_SIZE);
+ DECLARE_BITMAP(ppe_flow_used, ZX_PPE_FLOWS);
+ DECLARE_BITMAP(ppe_nh_used, ZX_PPE_NEXT_HOPS);
+ struct {
+ u8 mac[ETH_ALEN];
+ __be32 ip;
+ int refs;
+ } ppe_subnet[ZX_PPE_SUBNETS];
+ u32 ppe_saved[4];
+ u32 ppe_count;
+ u32 ppe_add_skip; /* flows the hardware cannot take */
+ u32 ppe_add_fail; /* no free entry, access timeout */
+
+ /* Hardware LAN switching between the ports of one Linux bridge */
+ spinlock_t trap_lock; /* zx_update_traps() */
+ struct mutex sw_lock;
+ bool sw_ready; /* bridge notifications registered */
+ u8 sw_members; /* bridge ports that switch */
+ struct work_struct sw_work;
+ spinlock_t sw_fdb_lock; /* the fields below */
+ struct net_device *sw_bridge[ZX_NUM_PORTS];
+ bool sw_recompute;
+ u8 sw_flush; /* bridge ports to flush */
+ bool sw_fdb_overflow;
+ unsigned int sw_fdb_count;
+ u8 sw_fdb_mac[ZX_SW_FDB_QUEUE][ETH_ALEN];
+ u32 sw_fdb_deleted;
+ struct notifier_block sw_netdev_nb;
+ struct notifier_block sw_switchdev_nb;
+ struct notifier_block sw_switchdev_blocking_nb;
+};
+
+/* zx279128s-main.c */
+bool zx_eth_is_port(const struct net_device *dev);
+void zx_set_trap_dmac(struct zx_eth_adapter *adapter, int idx, const u8 *mac);
+
+/* zx279128s-ppe.c */
+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);
+int zx_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
+ void *type_data);
+
+/* zx279128s-switch.c */
+void zx_sw_init(struct zx_eth_adapter *adapter);
+void zx_sw_work(struct work_struct *work);
+void zx_sw_port_down(struct zx_eth_adapter *adapter, int port);
+
+#endif
diff --git a/drivers/net/ethernet/zte/zx279128s-main.c b/drivers/net/ethernet/zte/zx279128s-main.c
new file mode 100644
index 0000000000..3b9834d63f
--- /dev/null
+++ b/drivers/net/ethernet/zte/zx279128s-main.c
@@ -0,0 +1,1485 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE zx279128s Ethernet switch and DMA driver
+ *
+ * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid@gmail.com>
+ *
+ * The zx279128s has a five-port gigabit switch with a traffic manager (TM),
+ * a buffer management unit (BMU) and a DMA engine that moves frames between
+ * the switch and the CPU. Every port is its own net_device (lan1-lan4 and wan
+ * on the ZTE ZXHN H3600). Ports in the same Linux bridge switch known unicast
+ * in hardware (zx279128s-switch.c), and established flows can be offloaded to
+ * the packet processor (zx279128s-ppe.c).
+ *
+ * All frames pass through buffers of the BMU pool, which lives in a 16 MiB
+ * DMA arena that is allocated once at probe and kept. Received frames are
+ * copied out of their buffer into an skb, frames to send are copied into a
+ * buffer. The ingress port of a received frame is in its descriptor, and a
+ * frame to send goes to the TM queue of its port. The hardware tables are
+ * programmed from scratch, with the values of the vendor firmware.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/rtnetlink.h>
+#include <linux/seq_file.h>
+
+#include "zx279128s-eth.h"
+
+/* ---- BMU helpers --------------------------------------------------- */
+
+/*
+ * Allocate a buffer from the BMU (caller holds tx_lock). A request that
+ * does not complete in time stays with the BMU, which hands out its
+ * buffer later: keep it pending and collect that buffer on the next call,
+ * as the vendor driver does. Starting a new request instead lost one
+ * buffer for good with every timeout.
+ */
+static int zx_bmu_alloc_bp(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+ u32 val;
+ int ret;
+
+ if (adapter->bmu_alloc_pending) {
+ if (readl(base + ZX_BMU_BUF_ALLOC) & ZX_BMU_BUF_ALLOC_REQ) {
+ adapter->bmu_alloc_fail++;
+ return -EBUSY;
+ }
+ adapter->bmu_alloc_pending = false;
+ val = readl(base + ZX_BMU_BUF_ID);
+ if (val & ZX_BMU_BUF_ID_VALID) {
+ adapter->bmu_alloc_late++;
+ return FIELD_GET(ZX_BMU_BUF_ID_BP, val);
+ }
+ }
+
+ val = readl(base + ZX_BMU_BUF_ALLOC);
+ writel(val | ZX_BMU_BUF_ALLOC_REQ, base + ZX_BMU_BUF_ALLOC);
+ ret = readl_poll_timeout_atomic(base + ZX_BMU_BUF_ALLOC, val,
+ !(val & ZX_BMU_BUF_ALLOC_BUSY), 1, 1000);
+ if (!ret) {
+ val = readl(base + ZX_BMU_BUF_ID);
+ if (val & ZX_BMU_BUF_ID_VALID)
+ return FIELD_GET(ZX_BMU_BUF_ID_BP, val);
+ ret = -ENOSPC;
+ }
+
+ adapter->bmu_alloc_pending = true;
+ adapter->bmu_alloc_fail++;
+ return ret;
+}
+
+static int zx_bmu_free_bp(struct zx_eth_adapter *adapter, u16 bp)
+{
+ u32 val;
+ int ret = 0;
+
+ spin_lock_bh(&adapter->bmu_free_lock);
+ if (!adapter->bmu_free_slots) {
+ ret = readl_poll_timeout_atomic(adapter->base + ZX_BMU_FREE_STATUS,
+ val, val & ZX_BMU_FREE_SLOTS, 1, 5000);
+ if (ret) {
+ adapter->bmu_free_fail++;
+ goto out;
+ }
+ adapter->bmu_free_slots = FIELD_GET(ZX_BMU_FREE_SLOTS, val);
+ }
+
+ adapter->bmu_free_slots--;
+ writel(bp, adapter->base + ZX_BMU_BUF_FREE);
+out:
+ spin_unlock_bh(&adapter->bmu_free_lock);
+ return ret;
+}
+
+/* ---- RX descriptor release ----------------------------------------- */
+
+/*
+ * Hand @n consumed slots of RX queue @q back to the hardware with one
+ * command, as the vendor driver does (count in bits 13:4, ring B), instead
+ * of two register polls and two writes per frame.
+ *
+ * The traffic manager counts each frame it hands to the CPU against the
+ * class in descriptor byte 6 bit 0, and a release only returns slots of the
+ * class it names. Releasing a class-1 slot as class 0 leaves the frame
+ * counted in the CPU queue for good: after 1024 frames the queue's reserved
+ * space is used up. So count the slots per class and release each class
+ * separately, as the vendor driver does.
+ */
+static int zx_rx_release(struct zx_eth_adapter *adapter, int q, int class,
+ u32 n)
+{
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(adapter->base + ZX_RX_REL_CTRL,
+ val, !(val & ZX_RX_REL_BUSY), 1, 5000);
+ if (ret)
+ return ret;
+ writel(FIELD_PREP(ZX_RX_REL_COUNT, n) | FIELD_PREP(ZX_RX_REL_CLASS, class) |
+ FIELD_PREP(ZX_RX_REL_QUEUE, q), adapter->base + ZX_RX_REL_DATA);
+ writel(ZX_RX_REL_BUSY, adapter->base + ZX_RX_REL_CTRL);
+ return 0;
+}
+
+/*
+ * Clear a consumed descriptor. All cleared descriptors must be visible
+ * before their slots are released: with a full ring the hardware refills a
+ * slot as soon as it is released, and clearing it afterwards would wipe
+ * the new descriptor.
+ */
+static void zx_rx_clear_desc(__le32 *desc)
+{
+ desc[0] = 0; desc[1] = 0; desc[2] = 0; desc[3] = 0;
+}
+
+/* Count a consumed slot for its release class, then clear it. */
+static void zx_rx_consume(__le32 *desc, u32 rel[2])
+{
+ rel[((u8 *)desc)[6] & 1]++;
+ zx_rx_clear_desc(desc);
+}
+
+/* ---- MAC port helpers ---------------------------------------------- */
+
+static void zx_port_set_speed(struct zx_eth_adapter *adapter, int port,
+ int speed, int duplex)
+{
+ void __iomem *reg = adapter->base + ZX_MAC_PORT(port) + ZX_MAC_CTRL;
+ u32 val = readl(reg);
+
+ if (speed == SPEED_1000) {
+ val &= ~(ZX_MAC_CTRL_MII | ZX_MAC_CTRL_100);
+ val |= ZX_MAC_CTRL_FD;
+ } else {
+ if (duplex == DUPLEX_FULL) {
+ val |= ZX_MAC_CTRL_MII | ZX_MAC_CTRL_FD;
+ } else {
+ val &= ~ZX_MAC_CTRL_FD;
+ val |= ZX_MAC_CTRL_MII;
+ }
+ if (speed == SPEED_100)
+ val |= ZX_MAC_CTRL_100;
+ else
+ val &= ~ZX_MAC_CTRL_100;
+ }
+ writel(val, reg);
+}
+
+static void zx_port_enable(struct zx_eth_adapter *adapter, int port, bool enable)
+{
+ void __iomem *reg = adapter->base + ZX_MAC_PORT(port) + ZX_MAC_CTRL;
+ u32 val = readl(reg);
+
+ if (enable)
+ val |= ZX_MAC_CTRL_EN;
+ else
+ val &= ~ZX_MAC_CTRL_EN;
+ writel(val, reg);
+}
+
+/* ---- PHY link callbacks -------------------------------------------- */
+
+static struct zx_eth_priv *zx_config_to_priv(struct phylink_config *config)
+{
+ return netdev_priv(to_net_dev(config->dev));
+}
+
+static void zx_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ /* the MAC follows the link in zx_mac_link_up() only */
+}
+
+static void zx_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct zx_eth_priv *priv = zx_config_to_priv(config);
+
+ zx_port_enable(priv->adapter, priv->port, false);
+ zx_sw_port_down(priv->adapter, priv->port);
+}
+
+static void zx_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct zx_eth_priv *priv = zx_config_to_priv(config);
+
+ zx_port_set_speed(priv->adapter, priv->port, speed, duplex);
+ zx_port_enable(priv->adapter, priv->port, true);
+}
+
+static const struct phylink_mac_ops zx_phylink_mac_ops = {
+ .mac_config = zx_mac_config,
+ .mac_link_down = zx_mac_link_down,
+ .mac_link_up = zx_mac_link_up,
+};
+
+/*
+ * Frames addressed to one of our MAC addresses go to the CPU, whatever
+ * their type (the ethertype traps below cover the common ones).
+ */
+void zx_set_trap_dmac(struct zx_eth_adapter *adapter, int idx, const u8 *mac)
+{
+ writel(mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5],
+ adapter->base + ZX_SPA_TRAP_DMAC + idx * 8);
+ writel(mac[0] << 8 | mac[1], adapter->base + ZX_SPA_TRAP_DMAC + idx * 8 + 4);
+}
+
+static void zx_dma_enable(struct zx_eth_adapter *adapter, bool enable)
+{
+ u32 val = readl(adapter->base + ZX_DMA_CTRL);
+
+ val &= ~ZX_DMA_CTRL_EN;
+ val |= ZX_DMA_CTRL_BIT21 | (enable ? ZX_DMA_CTRL_EN : 0);
+ writel(val, adapter->base + ZX_DMA_CTRL);
+ readl(adapter->base + ZX_DMA_CTRL);
+}
+
+static int zx_tm_write(struct zx_eth_adapter *adapter, u32 table, u32 index,
+ const u32 data[4])
+{
+ void __iomem *base = adapter->base;
+ u32 val;
+ int i, ret;
+
+ ret = readl_poll_timeout(base + ZX_TM_IND_STATUS, val,
+ val & ZX_TM_IND_READY, 1, 1000);
+ if (ret)
+ return ret;
+ writel(FIELD_PREP(ZX_TM_IND_INDEX, index) |
+ FIELD_PREP(ZX_TM_IND_TABLE, table), base + ZX_TM_IND_CMD);
+ for (i = 3; i >= 0; i--)
+ writel(data[i], base + ZX_TM_IND_DATA(i));
+ return 0;
+}
+
+static int zx_tm_read0(struct zx_eth_adapter *adapter, u32 table, u32 index,
+ u32 *data)
+{
+ void __iomem *base = adapter->base;
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout(base + ZX_TM_IND_STATUS, val,
+ val & ZX_TM_IND_READY, 1, 1000);
+ if (ret)
+ return ret;
+ writel(FIELD_PREP(ZX_TM_IND_INDEX, index) |
+ FIELD_PREP(ZX_TM_IND_TABLE, table) | ZX_TM_IND_READ,
+ base + ZX_TM_IND_CMD);
+ ret = readl_poll_timeout(base + ZX_TM_IND_STATUS, val,
+ val & ZX_TM_IND_READY, 1, 1000);
+ if (ret)
+ return ret;
+ *data = readl(base + ZX_TM_IND_DATA(0));
+ return 0;
+}
+
+/* Queue, RED and scheduler tables. The RED profiles use the vendor
+ * pon_tm_red_init() value.
+ *
+ * Queue word 0 is the reserved space (bits 10:0) plus the shared space
+ * (bits 25:11), in buffers. Queues 0-15 feed the CPU. They get 1023
+ * reserved and no shared buffers, as in the stock firmware: a frame stays
+ * counted in its queue until the driver releases its RX slot, so the
+ * traffic manager never has more than 1023 frames in the 1024-slot RX
+ * ring and drops the excess itself. With more, a burst faster than the
+ * driver drains the ring makes the DMA overwrite slots that were not read
+ * yet, and those frames and their buffers are lost.
+ *
+ * The queue usage in table 1 survives the reset. U-Boot receives a TFTP
+ * download on CPU queue 0 and never returns the slots, so after a TFTP
+ * boot the queue starts with up to 1024 frames counted that will never be
+ * released, and a 1023 limit would drop every frame. Raise the limit of
+ * each CPU queue by what is left counted in it.
+ */
+static int zx_tm_init(struct zx_eth_adapter *adapter)
+{
+ static const u32 queue[4] = { 0x00800400, 0, 0, 0 };
+ static const u32 red[4] = { 0x00200020, 0, 0, 0 };
+ static const u32 scheduler[4] = {
+ 0xff803fff, 0x0100ff80, 0x00100200, 32
+ };
+ u32 cpu_queue[4] = { 0, 0, 0, 0 };
+ u32 used, limit;
+ int i, ret;
+
+ for (i = 0; i < 16; i++) {
+ ret = zx_tm_read0(adapter, ZX_TM_TABLE_QUEUE_USE, i, &used);
+ if (ret)
+ return ret;
+ used = (used & 0x7ff) + ((used >> 11) & 0x7fff);
+ if (used)
+ dev_dbg(adapter->dev,
+ "CPU queue %d: %u frames left counted by the boot loader\n",
+ i, used);
+ limit = 1023 + used;
+ cpu_queue[0] = min(limit, 0x7ffU) | (limit - min(limit, 0x7ffU)) << 11;
+ ret = zx_tm_write(adapter, ZX_TM_TABLE_QUEUE, i, cpu_queue);
+ if (ret)
+ return ret;
+ }
+ for (i = 16; i < 400; i++) {
+ ret = zx_tm_write(adapter, ZX_TM_TABLE_QUEUE, i, queue);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < 384; i++) {
+ ret = zx_tm_write(adapter, ZX_TM_TABLE_RED, i, red);
+ if (ret)
+ return ret;
+ ret = zx_tm_write(adapter, ZX_TM_TABLE_SCHED, i, scheduler);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* ---- Hardware initialisation --------------------------------------- */
+
+static void zx_pp_init(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+
+ /* Mode-2 TX goes through the packet processor bridge. The bootloader
+ * initializes it for TFTP, but not when booting directly from NAND.
+ * Initialize it ourselves, while DMA and the MACs are quiesced. These
+ * are the H3600 bootloader's PP reset, bridge and classifier settings.
+ */
+ writel(2, base + ZX_PP_RESET);
+ usleep_range(1000, 2000);
+
+ writel(0x000200ff, base + ZX_SBRG_PORT_CTRL);
+ /* Stock firmware value. With the reset value 0x00010000 the bridge
+ * steered IPv6 unicast arriving on WAN to a queue the driver never
+ * services, and each frame kept a BMU buffer: about one leaked
+ * buffer per background WAN frame on an IPv6 network.
+ */
+ writel(0x0000dfdf, base + 0x388008);
+ /* The bridge would forward a frame between ports only through VLAN
+ * table entries, which the driver does not program, so it drops what
+ * the traps leave. Frames between two LAN ports must reach the Linux
+ * bridge instead. Turn off source address learning, so that every
+ * unicast destination is unknown, and send unknown unicast arriving on
+ * a switch port (bridge ports 1-5) to the CPU. Without this, lifting
+ * the IPv4 trap for flow offload loses IPv4 between two LAN hosts.
+ */
+ writel(0x00000000, base + ZX_SBRG_LEARN);
+ writel(0xff5ffdff, base + 0x388340);
+ writel(0x0000003e, base + 0x388344);
+ writel(0x0000003f, base + 0x388380);
+ writel(0xaaaaaaaa, base + 0x38863c);
+ writel(0x00005555, base + 0x3881c4);
+ writel(0x000bf874, base + 0x388188);
+ writel(0x000000ff, base + 0x3882c0);
+ writel(0x0000ffff, base + 0x388300);
+ writel(0x0000003e, base + 0x388304);
+
+ writel(0x0000309a, base + 0x38c080);
+ writel(0, base + 0x38c088);
+ writel(1, base + 0x38c0cc);
+ writel(0, base + 0x3a0010);
+ writel(0, base + 0x3a0014);
+}
+
+/* Reset the switch and leave its ports and DMA stopped */
+static void zx_hw_reset(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+ int i;
+
+ /* Master reset pulse */
+ writel(0, base + ZX_GLB_RESET);
+ fsleep(10);
+ writel(0xffffffff, base + ZX_GLB_RESET);
+
+ /* Quiesce all 5 ports */
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ zx_port_enable(adapter, i, false);
+ zx_dma_enable(adapter, false);
+ writel(0, base + ZX_BMU_CTRL);
+}
+
+static int zx_hw_init(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+ static const int port_to_sw_idx[8] = { 0, 1, 2, 3, 4, -1, 5, 6 };
+ __be16 *tab;
+ u32 val;
+ int i, ret;
+
+ /* 0. Reset, interrupts off until a port is opened */
+ zx_hw_reset(adapter);
+ writel(~0, base + ZX_TM_INT_MASK);
+ msleep(20);
+
+ /* 1. Clock gating: enable the clocks of MAC ports 0-4 */
+ writel(readl(base + ZX_GLB_CLK_CTRL) & ~ZX_GLB_CLK_CTRL_BITS,
+ base + ZX_GLB_CLK_CTRL);
+ writel(GENMASK(ZX_NUM_PORTS - 1, 0), base + ZX_GLB_PORT_CLK_EN);
+ zx_pp_init(adapter);
+
+ /* 2. Free-buffer index tables */
+ tab = adapter->tab0;
+ for (i = 0; i < ZX_TAB0_ENTRIES; i++)
+ tab[i] = cpu_to_be16(i);
+
+ tab = adapter->tab1;
+ for (i = 0; i < ZX_TAB1_ENTRIES; i++)
+ tab[i] = cpu_to_be16(i);
+
+ dma_wmb();
+ ret = zx_tm_init(adapter);
+ if (ret)
+ return dev_err_probe(adapter->dev, ret, "TM table setup timed out\n");
+
+ /* 3. Traffic Manager buffer pool */
+ writel((u32)adapter->tab0_dma, base + ZX_TM_TAB0_ADDR);
+ writel((u32)adapter->tab1_dma, base + ZX_TM_TAB1_ADDR);
+ writel(0x08000800, base + ZX_TM_TAB_LIMIT);
+ writel((u32)adapter->buf_pool_dma, base + ZX_TM_BUF_START);
+ writel((u32)adapter->buf_pool_dma + ZX_BUF_POOL_SIZE, base + ZX_TM_BUF_END);
+ writel((u32)adapter->rx_ring_dma, base + ZX_TM_BUF_F0);
+ writel(16, base + ZX_TM_CONFIG_START);
+
+ /* DMA and TM control values from the vendor pon_tm_dma_init() and
+ * the stock firmware, programmed before the BMU is configured.
+ */
+ writel(1, base + ZX_DMA_BASE + 0x28);
+ writel(1, base + ZX_DMA_BASE + 0x2c);
+ writel(ZX_RX_IRQ_USECS * 50, base + ZX_DMA_RX_IRQ_TIMER);
+ writel(ZX_RX_IRQ_FRAMES, base + ZX_DMA_RX_IRQ_FRAMES);
+ writel(0x7f, base + ZX_DMA_BASE + 0x04);
+ writel(32, base + ZX_DMA_BASE + 0x20);
+ writel(32, base + ZX_DMA_BASE + 0x24);
+ /* The bootloader leaves 0x00131213 here, the stock firmware runs with
+ * 0x00131217.
+ */
+ writel(0x00131217, base + ZX_DMA_BASE + 0x388);
+ writel(readl(base + ZX_TM_CTRL) | ZX_TM_CTRL_BIT6, base + ZX_TM_CTRL);
+
+ /* 4. Buffer Management Unit */
+ writel(0, base + ZX_BMU_CTRL);
+ writel(0x0104c040, base + ZX_BMU_CFG1);
+ writel(0x0104c040, base + ZX_BMU_CFG2);
+ /* Pool size in units of 32 buffers. With a 2048-buffer pool the BMU
+ * never fills its external free list (BPPE) after a cold NAND boot and
+ * runs on its ~64-entry internal cache only. 4096 works.
+ */
+ writel(ZX_NUM_BUFFERS / 32 - 1, base + ZX_BMU_LIMIT1);
+ writel(15, base + ZX_BMU_LIMIT2);
+ writel(ZX_NUM_BUFFERS << 16, base + ZX_BMU_THRES1);
+ writel(0x2000000, base + ZX_BMU_THRES2);
+ writel(0, base + ZX_BMU_BUF_ALLOC);
+ writel(ZX_BMU_CTRL_EN, base + ZX_BMU_CTRL);
+
+ writel(17, base + 0x3a00e0);
+
+ /* 5. DMA descriptor rings */
+ memset(adapter->tx_ring, 0, ZX_NUM_TX_DESC * ZX_DESC_SIZE);
+ memset(adapter->rx_ring, 0, ZX_RX_QUEUES * ZX_RX_QUEUE_STRIDE);
+ adapter->tx_cur_idx = 0;
+ for (i = 0; i < ZX_RX_QUEUES; i++) {
+ adapter->rx_cur_idx[i] = 0;
+ adapter->rx_synced[i] = false;
+ }
+ adapter->tx_pending = 0;
+ adapter->tx_complete_cnt = 0;
+ adapter->tx_done_excess = 0;
+ adapter->bmu_free_slots = 0;
+
+ writel((u32)adapter->buf_pool_dma + ZX_AREA50_OFFSET, base + ZX_DMA_AREA50);
+ writel((u32)adapter->tx_ring_dma, base + ZX_DMA_TX_RING);
+ writel(0x00400040, base + ZX_DMA_RING_CFG);
+ readl(base + ZX_DMA_TX_DONE);
+
+ dma_wmb();
+
+ /* 6. Switch: enable forwarding */
+ writel(ZX_SW_FWD_CTRL_ON, base + ZX_SW_FWD_CTRL);
+
+ for (i = 0; i < 8; i++) {
+ int idx = port_to_sw_idx[i];
+
+ if (idx >= 0)
+ writel(1, base + ZX_SPA_PORT_EN(idx));
+ }
+
+ /*
+ * Enable every class of packets the switch can hand to the CPU, up
+ * and down direction, as the stock firmware does. With only a few
+ * classes enabled, IPv6 neighbour discovery never reached the CPU.
+ */
+ writel(0xffffffff, base + ZX_SPA_UP_REG_PKT_EN);
+ writel(0xffffffff, base + ZX_SPA_UP_REG_PKT_EN + 4);
+ writel(0xffffffff, base + ZX_SPA_DN_REG_PKT_EN);
+ writel(0xffffffff, base + ZX_SPA_DN_REG_PKT_EN + 4);
+
+ /*
+ * The switch never forwards from port to port and hands the CPU only
+ * the frames a trap matches; the Linux bridge forwards between the
+ * port netdevs. So trap the common ethertypes whatever their
+ * destination (IPv4, IPv6, ARP, VLAN tagged). This also covers what
+ * the IPv4 protocol trap (TCP, UDP, ICMP) did, so clear that one.
+ */
+ writel(ETH_P_IP << 16 | ETH_P_IPV6, base + ZX_SPA_TRAP_ETH_TYPE);
+ writel(ETH_P_ARP << 16 | ETH_P_8021Q, base + ZX_SPA_TRAP_ETH_TYPE + 4);
+ writel(0, base + ZX_SPA_TRAP_PROTO);
+
+ /* Clear port filter bits for ports 0-4 */
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ val = readl(base + ZX_SW_PORT_FILTER(i));
+ val &= ~ZX_SW_PORT_FILTER_BITS;
+ writel(val, base + ZX_SW_PORT_FILTER(i));
+ }
+
+ /* 7. MAC ports 0-4 */
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ void __iomem *pb = base + ZX_MAC_PORT(i);
+
+ writel(0x00bbe000, pb + ZX_MAC_CTRL);
+ writel(0x80000001, pb + 0x08);
+ writel(0x0000fffe, pb + 0x04);
+ writel(0x00011200, pb + 0xe0);
+ writel(50, pb + 0xd00);
+ writel(168, pb + 0xd30);
+ writel(0x00300002, pb + 0x70);
+ writel(0x4000, pb + 0xb4);
+ writel(0x0010ff11, pb + 0xb00);
+
+ zx_port_set_speed(adapter, i, SPEED_1000, DUPLEX_FULL);
+ zx_port_enable(adapter, i, false);
+ }
+
+ return 0;
+}
+
+/*
+ * No interrupt reports sent frames. They are reclaimed on every transmit
+ * and in the NAPI poll; while a full ring keeps the queues stopped, a timer
+ * runs the poll until they can be woken again. Called with tx_lock held.
+ */
+static void zx_tx_kick(struct zx_eth_adapter *adapter)
+{
+ if (!hrtimer_active(&adapter->tx_timer))
+ hrtimer_start(&adapter->tx_timer, us_to_ktime(ZX_TX_POLL_US),
+ HRTIMER_MODE_REL);
+}
+
+static void zx_tx_reclaim(struct zx_eth_adapter *adapter)
+{
+ u32 done = readl(adapter->base + ZX_DMA_TX_DONE) & 0xffff;
+ u32 n = min(done, adapter->tx_pending);
+
+ if (unlikely(done > adapter->tx_pending))
+ adapter->tx_done_excess += done - adapter->tx_pending;
+ adapter->tx_complete_cnt += n;
+ adapter->tx_pending -= n;
+ if (adapter->tx_pending < ZX_TX_QUEUE_LIMIT) {
+ int p;
+
+ for (p = 0; p < ZX_NUM_PORTS; p++)
+ if (adapter->ports[p] && netif_queue_stopped(adapter->ports[p]))
+ netif_wake_queue(adapter->ports[p]);
+ }
+}
+
+/* ---- TX path ------------------------------------------------------- */
+
+static netdev_tx_t zx_eth_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ struct zx_eth_adapter *adapter = priv->adapter;
+ void __iomem *base = adapter->base;
+ unsigned int len = skb->len;
+ __le32 *desc;
+ u32 tx_idx;
+ u8 *buf_ptr;
+ int bp;
+
+ if (len > 1536) {
+ dev_kfree_skb_any(skb);
+ dev_dstats_tx_dropped(netdev);
+ return NETDEV_TX_OK;
+ }
+
+ spin_lock_bh(&adapter->tx_lock);
+ zx_tx_reclaim(adapter);
+ /*
+ * The ports share the ring. The queues are stopped as soon as it is
+ * full, but another port's transmit may already be waiting for the
+ * lock: give its frame back to the stack.
+ */
+ if (adapter->tx_pending >= ZX_TX_QUEUE_LIMIT) {
+ netif_stop_queue(netdev);
+ zx_tx_kick(adapter);
+ spin_unlock_bh(&adapter->tx_lock);
+ return NETDEV_TX_BUSY;
+ }
+ if (skb_put_padto(skb, ETH_ZLEN)) {
+ spin_unlock_bh(&adapter->tx_lock);
+ dev_dstats_tx_dropped(netdev);
+ return NETDEV_TX_OK;
+ }
+ len = skb->len;
+ bp = zx_bmu_alloc_bp(adapter);
+ if (bp < 0 || bp >= ZX_NUM_BUFFERS) {
+ spin_unlock_bh(&adapter->tx_lock);
+ dev_dstats_tx_dropped(netdev);
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ buf_ptr = (u8 *)adapter->buffer_pool + ((u32)bp * ZX_BUF_SIZE) + 16;
+ if (skb_copy_bits(skb, 0, buf_ptr, len)) {
+ zx_bmu_free_bp(adapter, bp);
+ spin_unlock_bh(&adapter->tx_lock);
+ dev_dstats_tx_dropped(netdev);
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ tx_idx = adapter->tx_cur_idx % ZX_NUM_TX_DESC;
+ desc = (__le32 *)adapter->tx_ring + tx_idx * (ZX_DESC_SIZE / sizeof(*desc));
+
+ /*
+ * Directed egress to the port's TM queue. The switch does not
+ * forward between ports: the Linux bridge does, one netdev per port.
+ */
+ desc[0] = cpu_to_le32(0x80 | (ZX_PORT_TX_QUEUE(priv->port) << 20));
+ desc[3] = cpu_to_le32(0x3 | ((len & 0x3fff) << 2));
+ desc[1] = cpu_to_le32(0x10000);
+ desc[2] = cpu_to_le32(0x21000000 | ((len & 0x3fff) << 9));
+
+ ((u8 *)desc)[7] = (u8)((bp & 0x7f) << 1);
+ ((u8 *)desc)[8] = (u8)(bp >> 7);
+
+ dma_wmb();
+ writel(1, base + ZX_DMA_TX_START);
+
+ adapter->tx_cur_idx++;
+ adapter->tx_pending++;
+ if (adapter->tx_pending >= ZX_TX_QUEUE_LIMIT) {
+ int p;
+
+ for (p = 0; p < ZX_NUM_PORTS; p++)
+ if (adapter->ports[p])
+ netif_stop_queue(adapter->ports[p]);
+ zx_tx_kick(adapter);
+ }
+ spin_unlock_bh(&adapter->tx_lock);
+ dev_dstats_tx_add(netdev, len);
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+}
+
+/* ---- RX path ------------------------------------------------------- */
+
+static bool zx_is_own_addr(struct zx_eth_adapter *adapter, const u8 *addr)
+{
+ int i;
+
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ if (adapter->ports[i] &&
+ ether_addr_equal(adapter->ports[i]->dev_addr, addr))
+ return true;
+ return false;
+}
+
+static int zx_eth_rx(struct zx_eth_adapter *adapter, int budget)
+{
+ int work_done = 0, q;
+
+ for (q = ZX_RX_QUEUES - 1; q >= 0 && work_done < budget; q--) {
+ u32 avail = readl(adapter->base + ZX_DMA_RX_QUEUE_CNT(q)) & 0xffff;
+ u32 rel[2] = { 0, 0 };
+
+ while (avail && work_done < budget) {
+ u32 idx = adapter->rx_cur_idx[q] % ZX_NUM_RX_DESC;
+ __le32 *desc = adapter->rx_ring + q * ZX_RX_QUEUE_STRIDE +
+ idx * ZX_DESC_SIZE;
+ u16 bp, len;
+ int port;
+ struct sk_buff *skb;
+ struct net_device *dest_dev;
+
+ dma_rmb();
+ bp = (((u8 *)desc)[7] >> 1) | ((u16)((u8 *)desc)[8] << 7);
+ len = (le32_to_cpu(desc[3]) >> 2) & 0x3fff;
+ port = (((u8 *)desc)[6] >> 3) ? ((((u8 *)desc)[6] >> 3) - 1) : 0;
+
+ if (bp >= ZX_NUM_BUFFERS || len < ETH_HLEN || len > 1536) {
+ u32 scan, idx2, found = 0;
+
+ if (adapter->rx_synced[q]) {
+ /*
+ * The hardware counted this slot but it
+ * holds no frame: it can leave a slot
+ * empty (e.g. when it had no buffer).
+ * Consume it as the vendor driver does.
+ * Waiting for it would leave our index
+ * behind the hardware's for good.
+ */
+ adapter->rx_bad_cnt++;
+ if (net_ratelimit())
+ dev_warn(adapter->dev,
+ "rxq%d: skipping slot %u (%08x %08x %08x %08x), %u pending\n",
+ q, idx, le32_to_cpu(desc[0]),
+ le32_to_cpu(desc[1]),
+ le32_to_cpu(desc[2]),
+ le32_to_cpu(desc[3]), avail);
+ zx_rx_consume(desc, rel);
+ adapter->rx_cur_idx[q]++;
+ work_done++;
+ avail--;
+ if (bp && bp < ZX_NUM_BUFFERS &&
+ zx_bmu_free_bp(adapter, bp))
+ adapter->bmu_free_fail++;
+ continue;
+ }
+
+ for (scan = 1; scan < ZX_NUM_RX_DESC; scan++) {
+ __le32 *d2;
+ u16 bp2, len2;
+
+ idx2 = (idx + scan) % ZX_NUM_RX_DESC;
+ d2 = adapter->rx_ring + q * ZX_RX_QUEUE_STRIDE +
+ idx2 * ZX_DESC_SIZE;
+ dma_rmb();
+ bp2 = (((u8 *)d2)[7] >> 1) |
+ ((u16)((u8 *)d2)[8] << 7);
+ len2 = (le32_to_cpu(d2[3]) >> 2) & 0x3fff;
+ if (bp2 < ZX_NUM_BUFFERS &&
+ len2 >= ETH_HLEN && len2 <= 1536) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ break;
+ adapter->rx_cur_idx[q] = idx2;
+ adapter->rx_resync_cnt++;
+ continue;
+ }
+ adapter->rx_synced[q] = true;
+
+ if (bp == 0) {
+ adapter->rx_nobuf_cnt++;
+ zx_rx_consume(desc, rel);
+ adapter->rx_cur_idx[q]++;
+ work_done++;
+ avail--;
+ continue;
+ }
+
+ /* Aliasing detector: frames from one of our own addresses */
+ if (len >= ETH_HLEN) {
+ u8 *d = adapter->buffer_pool + bp * ZX_BUF_SIZE + 16;
+
+ if (zx_is_own_addr(adapter, d + ETH_ALEN)) {
+ adapter->rx_alias_cnt++;
+ zx_rx_consume(desc, rel);
+ adapter->rx_cur_idx[q]++;
+ work_done++;
+ avail--;
+ /*
+ * A frame of our own that the switch
+ * sent back still has an RX buffer of
+ * its own: return it, or every such
+ * frame leaks one BMU buffer.
+ */
+ if (zx_bmu_free_bp(adapter, bp))
+ adapter->bmu_free_fail++;
+ continue;
+ }
+ }
+
+ /* Deliver to the netdev of the ingress port */
+ dest_dev = port < ZX_NUM_PORTS ? adapter->ports[port] : NULL;
+
+ skb = napi_alloc_skb(&adapter->napi, len);
+ if (skb && dest_dev && (dest_dev->flags & IFF_UP)) {
+ dma_rmb();
+ memcpy(skb_put(skb, len), adapter->buffer_pool +
+ bp * ZX_BUF_SIZE + 16, len);
+ skb->dev = dest_dev;
+ skb->protocol = eth_type_trans(skb, dest_dev);
+ napi_gro_receive(&adapter->napi, skb);
+ dev_dstats_rx_add(dest_dev, len);
+ } else {
+ if (skb)
+ dev_kfree_skb_any(skb);
+ if (dest_dev)
+ dev_dstats_rx_dropped(dest_dev);
+ }
+
+ zx_rx_consume(desc, rel);
+ adapter->rx_cur_idx[q]++;
+ work_done++;
+ avail--;
+ if (zx_bmu_free_bp(adapter, bp))
+ adapter->bmu_free_fail++;
+ }
+ if (rel[0] || rel[1])
+ dma_wmb();
+ if (rel[0] && zx_rx_release(adapter, q, 0, rel[0]))
+ adapter->rx_rel_fail++;
+ if (rel[1] && zx_rx_release(adapter, q, 1, rel[1]))
+ adapter->rx_rel_fail++;
+ }
+ return work_done;
+}
+
+/* ---- interrupt and NAPI poll -------------------------------------- */
+
+static irqreturn_t zx_eth_isr(int irq, void *dev_id)
+{
+ struct zx_eth_adapter *adapter = dev_id;
+
+ if (!(readl(adapter->base + ZX_TM_INT_STATUS) & ZX_TM_INT_RX))
+ return IRQ_NONE;
+
+ writel(~0, adapter->base + ZX_TM_INT_MASK);
+ napi_schedule(&adapter->napi);
+ return IRQ_HANDLED;
+}
+
+static int zx_eth_poll(struct napi_struct *napi, int budget)
+{
+ struct zx_eth_adapter *adapter =
+ container_of(napi, struct zx_eth_adapter, napi);
+ int work_done;
+
+ spin_lock_bh(&adapter->tx_lock);
+ zx_tx_reclaim(adapter);
+ if (adapter->tx_pending >= ZX_TX_QUEUE_LIMIT)
+ zx_tx_kick(adapter);
+ spin_unlock_bh(&adapter->tx_lock);
+ work_done = budget ? zx_eth_rx(adapter, budget) : 0;
+
+ if (work_done < budget && napi_complete_done(napi, work_done))
+ writel(~ZX_TM_INT_RX, adapter->base + ZX_TM_INT_MASK);
+
+ return work_done;
+}
+
+static enum hrtimer_restart zx_eth_tx_timer(struct hrtimer *timer)
+{
+ struct zx_eth_adapter *adapter =
+ container_of(timer, struct zx_eth_adapter, tx_timer);
+
+ napi_schedule(&adapter->napi);
+ return HRTIMER_NORESTART;
+}
+
+/* ---- ndo_open / ndo_stop ------------------------------------------- */
+
+static int zx_eth_open(struct net_device *netdev)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ struct zx_eth_adapter *adapter = priv->adapter;
+
+ if (adapter->open_count == 0) {
+ napi_enable(&adapter->napi);
+ writel(~ZX_TM_INT_RX, adapter->base + ZX_TM_INT_MASK);
+ }
+ adapter->open_count++;
+
+ phylink_start(priv->phylink);
+ netif_start_queue(netdev);
+ return 0;
+}
+
+static int zx_eth_stop(struct net_device *netdev)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ struct zx_eth_adapter *adapter = priv->adapter;
+
+ netif_tx_disable(netdev);
+ phylink_stop(priv->phylink);
+ adapter->open_count--;
+
+ if (adapter->open_count == 0) {
+ writel(~0, adapter->base + ZX_TM_INT_MASK);
+ hrtimer_cancel(&adapter->tx_timer);
+ napi_disable(&adapter->napi);
+ /* a last poll may have unmasked it again */
+ writel(~0, adapter->base + ZX_TM_INT_MASK);
+ synchronize_irq(adapter->irq);
+ }
+
+ return 0;
+}
+
+/* ---- net_device_ops ------------------------------------------------ */
+
+static int zx_eth_set_mac_address(struct net_device *netdev, void *p)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ struct zx_eth_adapter *adapter = priv->adapter;
+ int ret;
+
+ ret = eth_mac_addr(netdev, p);
+ if (ret)
+ return ret;
+
+ mutex_lock(&adapter->ppe_lock);
+ zx_update_mac_tables(adapter);
+ mutex_unlock(&adapter->ppe_lock);
+ return 0;
+}
+
+static int zx_eth_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+
+ return phylink_mii_ioctl(priv->phylink, ifr, cmd);
+}
+
+static const struct net_device_ops zx_netdev_ops = {
+ .ndo_open = zx_eth_open,
+ .ndo_stop = zx_eth_stop,
+ .ndo_start_xmit = zx_eth_xmit,
+ .ndo_set_mac_address = zx_eth_set_mac_address,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_eth_ioctl = zx_eth_ioctl,
+ .ndo_setup_tc = zx_eth_setup_tc,
+};
+
+bool zx_eth_is_port(const struct net_device *dev)
+{
+ return dev->netdev_ops == &zx_netdev_ops;
+}
+
+/* ---- MAC counters -------------------------------------------------- */
+
+/*
+ * The offsets are from the vendor's statistics table, and the vendor driver
+ * picks the same counters. The MACs have more (errors, collisions, pause
+ * frames), but these are the ones checked against known traffic.
+ */
+static const u16 zx_mib_regs[ZX_MIB_NUM] = {
+ [ZX_MIB_TX_OCTETS] = ZX_MAC_TX_OCTETS,
+ [ZX_MIB_TX_FRAMES] = ZX_MAC_TX_FRAMES,
+ [ZX_MIB_TX_MCAST] = ZX_MAC_TX_MCAST,
+ [ZX_MIB_TX_BCAST] = ZX_MAC_TX_BCAST,
+ [ZX_MIB_TX_HIST + 0] = ZX_MAC_TX_HIST(0),
+ [ZX_MIB_TX_HIST + 1] = ZX_MAC_TX_HIST(1),
+ [ZX_MIB_TX_HIST + 2] = ZX_MAC_TX_HIST(2),
+ [ZX_MIB_TX_HIST + 3] = ZX_MAC_TX_HIST(3),
+ [ZX_MIB_TX_HIST + 4] = ZX_MAC_TX_HIST(4),
+ [ZX_MIB_TX_HIST + 5] = ZX_MAC_TX_HIST(5),
+ [ZX_MIB_RX_OCTETS] = ZX_MAC_RX_OCTETS,
+ [ZX_MIB_RX_UCAST] = ZX_MAC_RX_UCAST,
+ [ZX_MIB_RX_MCAST] = ZX_MAC_RX_MCAST,
+ [ZX_MIB_RX_BCAST] = ZX_MAC_RX_BCAST,
+ [ZX_MIB_RX_HIST + 0] = ZX_MAC_RX_HIST(0),
+ [ZX_MIB_RX_HIST + 1] = ZX_MAC_RX_HIST(1),
+ [ZX_MIB_RX_HIST + 2] = ZX_MAC_RX_HIST(2),
+ [ZX_MIB_RX_HIST + 3] = ZX_MAC_RX_HIST(3),
+ [ZX_MIB_RX_HIST + 4] = ZX_MAC_RX_HIST(4),
+ [ZX_MIB_RX_HIST + 5] = ZX_MAC_RX_HIST(5),
+};
+
+/*
+ * The counters are 32 bits wide and keep running. At 1 Gbit/s the byte
+ * counters wrap after 34 s, so they are read every few seconds, and what
+ * they grew by since the last read is added to 64-bit sums.
+ */
+#define ZX_MIB_INTERVAL (5 * HZ)
+
+static void zx_mib_update(struct zx_eth_priv *priv)
+{
+ void __iomem *base = priv->adapter->base + ZX_MAC_PORT(priv->port);
+ int i;
+
+ lockdep_assert_held(&priv->adapter->mib_lock);
+
+ for (i = 0; i < ZX_MIB_NUM; i++) {
+ u32 val = readl(base + zx_mib_regs[i]);
+
+ priv->mib[i] += val - priv->mib_last[i]; /* modulo 2^32 */
+ priv->mib_last[i] = val;
+ }
+}
+
+static void zx_mib_work(struct work_struct *work)
+{
+ struct zx_eth_adapter *adapter =
+ container_of(to_delayed_work(work), struct zx_eth_adapter,
+ mib_work);
+ int i;
+
+ mutex_lock(&adapter->mib_lock);
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ if (adapter->ports[i])
+ zx_mib_update(netdev_priv(adapter->ports[i]));
+ mutex_unlock(&adapter->mib_lock);
+
+ schedule_delayed_work(&adapter->mib_work, ZX_MIB_INTERVAL);
+}
+
+/* ---- ethtool_ops --------------------------------------------------- */
+
+static int zx_eth_get_link_ksettings(struct net_device *netdev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+
+ return phylink_ethtool_ksettings_get(priv->phylink, cmd);
+}
+
+static int zx_eth_set_link_ksettings(struct net_device *netdev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+
+ return phylink_ethtool_ksettings_set(priv->phylink, cmd);
+}
+
+static int zx_eth_nway_reset(struct net_device *netdev)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+
+ return phylink_ethtool_nway_reset(priv->phylink);
+}
+
+static void zx_eth_get_eth_mac_stats(struct net_device *netdev,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ const u64 *mib = priv->mib;
+
+ mutex_lock(&priv->adapter->mib_lock);
+ zx_mib_update(priv);
+
+ mac_stats->FramesTransmittedOK = mib[ZX_MIB_TX_FRAMES];
+ mac_stats->OctetsTransmittedOK = mib[ZX_MIB_TX_OCTETS];
+ mac_stats->MulticastFramesXmittedOK = mib[ZX_MIB_TX_MCAST];
+ mac_stats->BroadcastFramesXmittedOK = mib[ZX_MIB_TX_BCAST];
+ /* there is a total, but the vendor driver adds up these three */
+ mac_stats->FramesReceivedOK = mib[ZX_MIB_RX_UCAST] +
+ mib[ZX_MIB_RX_MCAST] +
+ mib[ZX_MIB_RX_BCAST];
+ mac_stats->OctetsReceivedOK = mib[ZX_MIB_RX_OCTETS];
+ mac_stats->MulticastFramesReceivedOK = mib[ZX_MIB_RX_MCAST];
+ mac_stats->BroadcastFramesReceivedOK = mib[ZX_MIB_RX_BCAST];
+
+ mutex_unlock(&priv->adapter->mib_lock);
+}
+
+/* The last bin also counts 1522-byte (VLAN-tagged) frames */
+static const struct ethtool_rmon_hist_range zx_rmon_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1522 },
+ {}
+};
+
+static void zx_eth_get_rmon_stats(struct net_device *netdev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct zx_eth_priv *priv = netdev_priv(netdev);
+ int i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(zx_rmon_ranges) != ZX_MAC_HIST_BINS + 1);
+
+ mutex_lock(&priv->adapter->mib_lock);
+ zx_mib_update(priv);
+ for (i = 0; i < ZX_MAC_HIST_BINS; i++) {
+ rmon_stats->hist[i] = priv->mib[ZX_MIB_RX_HIST + i];
+ rmon_stats->hist_tx[i] = priv->mib[ZX_MIB_TX_HIST + i];
+ }
+ mutex_unlock(&priv->adapter->mib_lock);
+
+ *ranges = zx_rmon_ranges;
+}
+
+static const struct ethtool_ops zx_ethtool_ops = {
+ .get_link = ethtool_op_get_link,
+ .get_link_ksettings = zx_eth_get_link_ksettings,
+ .set_link_ksettings = zx_eth_set_link_ksettings,
+ .nway_reset = zx_eth_nway_reset,
+ .get_eth_mac_stats = zx_eth_get_eth_mac_stats,
+ .get_rmon_stats = zx_eth_get_rmon_stats,
+};
+
+/* ---- debugfs ------------------------------------------------------- */
+
+/* Driver counters and the main DMA, BMU and switch registers */
+static int zx_eth_dma_state_show(struct seq_file *s, void *data)
+{
+ struct zx_eth_adapter *adapter = dev_get_drvdata(s->private);
+ static const u32 regs[] = {
+ ZX_TM_TAB0_ADDR, ZX_TM_TAB1_ADDR, ZX_TM_BUF_F0,
+ ZX_TM_BUF_START, ZX_TM_BUF_END, ZX_BMU_CTRL,
+ ZX_BMU_FREE_STATUS,
+ ZX_DMA_CTRL, ZX_DMA_RING_CFG,
+ ZX_DMA_AREA50, ZX_DMA_TX_RING,
+ ZX_DMA_TX_START,
+ ZX_RX_REL_CTRL, ZX_RX_REL_DATA,
+ ZX_MAC_PORT(0), ZX_MAC_PORT(1), ZX_MAC_PORT(2),
+ ZX_MAC_PORT(3), ZX_MAC_PORT(4),
+ ZX_SW_FWD_CTRL,
+ ZX_SPA_UP_REG_PKT_EN, ZX_SPA_UP_REG_PKT_EN + 4,
+ ZX_SPA_DN_REG_PKT_EN, ZX_SPA_DN_REG_PKT_EN + 4,
+ 0x1d4080, 0x1d4084,
+ 0x1d40c0, 0x1d40c4,
+ 0x1d4100, 0x1d4104,
+ ZX_SPA_TRAP_PROTO,
+ ZX_SPA_PORT_EN(0), ZX_SPA_PORT_EN(1), ZX_SPA_PORT_EN(2),
+ ZX_SPA_PORT_EN(3), ZX_SPA_PORT_EN(4), ZX_SPA_PORT_EN(6),
+ };
+ int i;
+
+ rtnl_lock();
+ seq_printf(s, "arena=%pad open_count=%d\n",
+ &adapter->buf_pool_dma, adapter->open_count);
+ seq_printf(s, "tx_submitted=%u tx_completed=%u tx_pending=%u tx_done_excess=%u rx_resync=%u\n",
+ adapter->tx_cur_idx, adapter->tx_complete_cnt,
+ adapter->tx_pending, adapter->tx_done_excess,
+ adapter->rx_resync_cnt);
+ seq_printf(s, "rx_alias=%u rx_nobuf=%u rx_rel_fail=%u rx_bad=%u\n",
+ adapter->rx_alias_cnt, adapter->rx_nobuf_cnt,
+ adapter->rx_rel_fail, adapter->rx_bad_cnt);
+ seq_printf(s, "ppe_active=%u ppe_flows=%u ppe_add_skip=%u ppe_add_fail=%u\n",
+ adapter->ppe_active, adapter->ppe_count,
+ adapter->ppe_add_skip, adapter->ppe_add_fail);
+ seq_printf(s, "sw_members=%#x sw_fdb_deleted=%u\n",
+ adapter->sw_members,
+ adapter->sw_fdb_deleted);
+ seq_printf(s, "bmu_alloc_fail=%u bmu_alloc_late=%u bmu_free_fail=%u bmu_free_slots=%u\n",
+ adapter->bmu_alloc_fail, adapter->bmu_alloc_late,
+ adapter->bmu_free_fail, adapter->bmu_free_slots);
+
+ for (i = 0; i < 0x100; i += 4)
+ seq_printf(s, "bmu+%03x=%08x\n", i,
+ readl(adapter->base + ZX_BMU_BASE + i));
+ /* Reading 0x68 (TX done) would clear the count of sent frames */
+ for (i = 0; i < 0x120; i += 4) {
+ if (i == 0x68)
+ continue;
+ seq_printf(s, "dma+%03x=%08x\n", i,
+ readl(adapter->base + ZX_DMA_BASE + i));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++)
+ seq_printf(s, "%06x=%08x\n", regs[i],
+ readl(adapter->base + regs[i]));
+
+ for (i = 0; i < ZX_RX_QUEUES; i++)
+ seq_printf(s, "rxq%d count=%u consumed=%u\n", i,
+ readl(adapter->base + ZX_DMA_RX_QUEUE_CNT(i)) & 0xffff,
+ READ_ONCE(adapter->rx_cur_idx[i]));
+ rtnl_unlock();
+
+ return 0;
+}
+
+/*
+ * The driver cannot be unbound (suppress_bind_attrs) and has no remove
+ * function, so the directory stays until reboot.
+ */
+static void zx_eth_debugfs_init(struct zx_eth_adapter *adapter)
+{
+ struct dentry *dir = debugfs_create_dir(dev_name(adapter->dev), NULL);
+
+ debugfs_create_devm_seqfile(adapter->dev, "dma_state", dir,
+ zx_eth_dma_state_show);
+}
+
+static int zx_eth_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct zx_eth_adapter *adapter;
+ struct device_node *np;
+ u8 addrs[ZX_NUM_PORTS][ETH_ALEN];
+ unsigned long have_addr = 0;
+ int i, ret;
+
+ struct device_node *ports_np __free(device_node) =
+ of_get_child_by_name(dev->of_node, "ethernet-ports");
+ if (!ports_np)
+ return dev_err_probe(dev, -ENODEV, "no ethernet-ports node\n");
+
+ /*
+ * The MAC addresses can come from nvmem cells on a flash partition
+ * that is not registered yet. Get them before the hardware is set
+ * up, so that deferring the probe leaves no DMA running.
+ */
+ for_each_available_child_of_node_scoped(ports_np, port_np) {
+ u32 port;
+
+ if (of_property_read_u32(port_np, "reg", &port) ||
+ port >= ZX_NUM_PORTS)
+ continue;
+ ret = of_get_mac_address(port_np, addrs[port]);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ if (!ret)
+ __set_bit(port, &have_addr);
+ }
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set the DMA mask\n");
+
+ adapter = devm_kzalloc(dev, sizeof(*adapter), GFP_KERNEL);
+ if (!adapter)
+ return -ENOMEM;
+
+ adapter->irq = platform_get_irq(pdev, 0);
+ if (adapter->irq < 0)
+ return adapter->irq;
+
+ adapter->dev = dev;
+ spin_lock_init(&adapter->tx_lock);
+ spin_lock_init(&adapter->bmu_free_lock);
+ mutex_init(&adapter->ppe_lock);
+ spin_lock_init(&adapter->trap_lock);
+ mutex_init(&adapter->sw_lock);
+ spin_lock_init(&adapter->sw_fdb_lock);
+ INIT_WORK(&adapter->sw_work, zx_sw_work);
+ mutex_init(&adapter->mib_lock);
+ INIT_DELAYED_WORK(&adapter->mib_work, zx_mib_work);
+ ret = rhashtable_init(&adapter->ppe_flows, &zx_ppe_ht_params);
+ if (ret)
+ return ret;
+
+ adapter->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(adapter->base))
+ return PTR_ERR(adapter->base);
+
+ adapter->buffer_pool = dma_alloc_coherent(dev, ZX_DMA_ARENA_SIZE,
+ &adapter->buf_pool_dma, GFP_KERNEL);
+ if (!adapter->buffer_pool)
+ return -ENOMEM;
+ adapter->tab0 = adapter->buffer_pool + ZX_TAB0_OFFSET;
+ adapter->tab0_dma = adapter->buf_pool_dma + ZX_TAB0_OFFSET;
+ adapter->tab1 = adapter->buffer_pool + ZX_TAB1_OFFSET;
+ adapter->tab1_dma = adapter->buf_pool_dma + ZX_TAB1_OFFSET;
+ adapter->rx_ring = adapter->buffer_pool + ZX_RX_OFFSET;
+ adapter->rx_ring_dma = adapter->buf_pool_dma + ZX_RX_OFFSET;
+ adapter->tx_ring = adapter->buffer_pool + ZX_TX_OFFSET;
+ adapter->tx_ring_dma = adapter->buf_pool_dma + ZX_TX_OFFSET;
+
+ ret = zx_hw_init(adapter);
+ if (ret)
+ return ret;
+ zx_dma_enable(adapter, true);
+
+ /* One netdev per port, named by the port's label */
+ for_each_available_child_of_node(ports_np, np) {
+ struct net_device *netdev;
+ struct zx_eth_priv *priv;
+ phy_interface_t interface;
+ const char *label;
+ u32 port;
+
+ if (of_property_read_u32(np, "reg", &port) || port >= ZX_NUM_PORTS ||
+ adapter->ports[port]) {
+ dev_warn(dev, "%pOF: bad or duplicate reg\n", np);
+ continue;
+ }
+
+ netdev = devm_alloc_etherdev(dev, sizeof(struct zx_eth_priv));
+ if (!netdev) {
+ ret = -ENOMEM;
+ of_node_put(np);
+ goto err_disconnect;
+ }
+ SET_NETDEV_DEV(netdev, dev);
+ if (!of_property_read_string(np, "label", &label)) {
+ strscpy(netdev->name, label, IFNAMSIZ);
+ netdev->name_assign_type = NET_NAME_PREDICTABLE;
+ }
+ if (test_bit(port, &have_addr))
+ eth_hw_addr_set(netdev, addrs[port]);
+ else
+ eth_hw_addr_random(netdev);
+ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+ netdev->netdev_ops = &zx_netdev_ops;
+ netdev->ethtool_ops = &zx_ethtool_ops;
+ netdev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
+ netdev->hw_features |= NETIF_F_HW_TC;
+ netdev->features |= NETIF_F_HW_TC;
+ netdev->min_mtu = 68;
+ netdev->max_mtu = 1500;
+ priv = netdev_priv(netdev);
+ priv->netdev = netdev;
+ priv->adapter = adapter;
+ priv->port = port;
+ adapter->ports[port] = netdev;
+
+ ret = of_get_phy_mode(np, &interface);
+ if (ret) {
+ dev_err_probe(dev, ret, "%pOF: no valid phy-mode\n", np);
+ of_node_put(np);
+ goto err_disconnect;
+ }
+ priv->phylink_config.dev = &netdev->dev;
+ priv->phylink_config.type = PHYLINK_NETDEV;
+ priv->phylink_config.mac_capabilities = MAC_10 | MAC_100 |
+ MAC_1000FD;
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ priv->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_GMII,
+ priv->phylink_config.supported_interfaces);
+ priv->phylink = phylink_create(&priv->phylink_config,
+ of_fwnode_handle(np), interface,
+ &zx_phylink_mac_ops);
+ if (IS_ERR(priv->phylink)) {
+ ret = dev_err_probe(dev, PTR_ERR(priv->phylink),
+ "port %u: phylink\n", port);
+ priv->phylink = NULL;
+ of_node_put(np);
+ goto err_disconnect;
+ }
+ ret = phylink_of_phy_connect(priv->phylink, np, 0);
+ if (ret)
+ dev_warn(dev, "port %u: PHY connect failed: %d\n", port, ret);
+ }
+
+ /* The MAC counters start from zero, whatever the registers hold */
+ mutex_lock(&adapter->mib_lock);
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ struct zx_eth_priv *priv;
+
+ if (!adapter->ports[i])
+ continue;
+ priv = netdev_priv(adapter->ports[i]);
+ zx_mib_update(priv);
+ memset(priv->mib, 0, sizeof(priv->mib));
+ }
+ mutex_unlock(&adapter->mib_lock);
+
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ if (adapter->ports[i])
+ break;
+ if (i == ZX_NUM_PORTS) {
+ ret = dev_err_probe(dev, -ENODEV, "no usable port\n");
+ goto err_disconnect;
+ }
+
+ mutex_lock(&adapter->ppe_lock);
+ zx_update_mac_tables(adapter);
+ mutex_unlock(&adapter->ppe_lock);
+
+ /* One NAPI context serves all ports */
+ netif_napi_add(adapter->ports[i], &adapter->napi, zx_eth_poll);
+ hrtimer_setup(&adapter->tx_timer, zx_eth_tx_timer,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ /* zx_hw_init() masked the interrupt, a port's open enables it */
+ ret = devm_request_irq(dev, adapter->irq, zx_eth_isr, 0, dev_name(dev),
+ adapter);
+ if (ret)
+ goto err_napi;
+
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ if (!adapter->ports[i])
+ continue;
+ ret = register_netdev(adapter->ports[i]);
+ if (ret) {
+ dev_err(dev, "port %d: register_netdev failed: %d\n", i, ret);
+ while (--i >= 0)
+ if (adapter->ports[i])
+ unregister_netdev(adapter->ports[i]);
+ goto err_napi;
+ }
+ }
+
+ platform_set_drvdata(pdev, adapter);
+
+ zx_sw_init(adapter);
+ schedule_delayed_work(&adapter->mib_work, ZX_MIB_INTERVAL);
+
+ zx_eth_debugfs_init(adapter);
+ return 0;
+
+err_napi:
+ netif_napi_del(&adapter->napi);
+err_disconnect:
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ struct zx_eth_priv *priv;
+
+ if (!adapter->ports[i])
+ continue;
+ priv = netdev_priv(adapter->ports[i]);
+ if (priv->phylink) {
+ phylink_disconnect_phy(priv->phylink);
+ phylink_destroy(priv->phylink);
+ }
+ }
+ return ret;
+}
+
+/* ---- module glue --------------------------------------------------- */
+
+/*
+ * The switch writes received frames, and frames of offloaded flows, into
+ * the buffer pool without the CPU. Stop it before a reboot or kexec: after
+ * kexec, that memory belongs to the next kernel.
+ */
+static void zx_eth_shutdown(struct platform_device *pdev)
+{
+ struct zx_eth_adapter *adapter = platform_get_drvdata(pdev);
+ int i;
+
+ if (!adapter)
+ return;
+
+ rtnl_lock();
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ if (!adapter->ports[i])
+ continue;
+ netif_device_detach(adapter->ports[i]);
+ dev_close(adapter->ports[i]);
+ }
+ rtnl_unlock();
+ cancel_work_sync(&adapter->sw_work);
+ cancel_delayed_work_sync(&adapter->mib_work);
+
+ zx_hw_reset(adapter);
+}
+
+static const struct of_device_id zx_eth_dt_ids[] = {
+ { .compatible = "zte,zx279128s-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, zx_eth_dt_ids);
+
+static struct platform_driver zx_eth_driver = {
+ .probe = zx_eth_probe,
+ .shutdown = zx_eth_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = zx_eth_dt_ids,
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(zx_eth_driver);
+
+MODULE_AUTHOR("Navid Ghahremani <ghahramani.navid@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx279128s Ethernet switch and DMA driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/zte/zx279128s-ppe.c b/drivers/net/ethernet/zte/zx279128s-ppe.c
new file mode 100644
index 0000000000..d9c6c1d249
--- /dev/null
+++ b/drivers/net/ethernet/zte/zx279128s-ppe.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE zx279128s Ethernet switch: hardware flow offload
+ *
+ * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/etherdevice.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ip.h>
+#include <linux/rhashtable.h>
+#include <linux/slab.h>
+#include <net/flow_offload.h>
+
+#include "zx279128s-eth.h"
+
+/* ---- Hardware flow offload ------------------------------------------ */
+
+/*
+ * The packet processor forwards established IPv4 TCP/UDP flows without the
+ * CPU: the classifier looks the 5-tuple up in two hash banks, the packet
+ * modifier rewrites MACs, addresses, ports and TTL, and the entry sends
+ * the frame straight to the egress port. A miss goes to the CPU as usual.
+ * Formats reverse engineered from the stock firmware's NPU tables.
+ *
+ * The WAN is a plain switch port here, so the hardware treats both
+ * directions of a connection as upstream: both use the upstream key
+ * header, and each direction has its own flow (rewrite) index.
+ */
+
+struct zx_ppe_entry {
+ struct rhash_head node;
+ unsigned long cookie;
+ u16 hash; /* 0-255 bank 0, 256-383 bank 1 */
+ u16 flow;
+ u16 nh;
+ u8 subnet;
+};
+
+const struct rhashtable_params zx_ppe_ht_params = {
+ .head_offset = offsetof(struct zx_ppe_entry, node),
+ .key_offset = offsetof(struct zx_ppe_entry, cookie),
+ .key_len = sizeof(unsigned long),
+ .automatic_shrinking = true,
+};
+
+static u32 zx_pp_data_reg(u32 blk, int i)
+{
+ if (blk == ZX_PM_BASE && i >= 4)
+ return ZX_PM_IND_DATA_HI + (i - 4) * 4;
+ return ZX_PP_IND_DATA + i * 4;
+}
+
+static int zx_pp_wait(struct zx_eth_adapter *adapter, u32 blk)
+{
+ u32 val;
+
+ return readl_poll_timeout(adapter->base + blk + ZX_PP_IND_STATUS, val,
+ val & 1, 0, 1000);
+}
+
+/* Write @n data words of an entry; the hardware takes them when word 0 is
+ * written, so it goes last (as in the stock driver).
+ */
+static int zx_pp_write(struct zx_eth_adapter *adapter, u32 blk, u32 table,
+ u32 index, const u32 *data, int n)
+{
+ int words = blk == ZX_CLA_BASE ? 17 : 8;
+ int i, ret;
+
+ ret = zx_pp_wait(adapter, blk);
+ if (ret)
+ return ret;
+ writel(index | table << 22, adapter->base + blk + ZX_PP_IND_CMD);
+ for (i = words - 1; i >= 0; i--)
+ writel(i < n ? data[i] : 0, adapter->base + blk + zx_pp_data_reg(blk, i));
+ /* the status can still read idle right after the command */
+ for (i = 0; i < 64 && (readl(adapter->base + blk + ZX_PP_IND_STATUS) & 1); i++)
+ ;
+ return zx_pp_wait(adapter, blk);
+}
+
+static int zx_pp_read_word0(struct zx_eth_adapter *adapter, u32 blk, u32 table,
+ u32 index, u32 *val)
+{
+ int i, ret;
+
+ ret = zx_pp_wait(adapter, blk);
+ if (ret)
+ return ret;
+ writel(index | table << 22 | ZX_PP_IND_READ, adapter->base + blk + ZX_PP_IND_CMD);
+ for (i = 0; i < 64 && (readl(adapter->base + blk + ZX_PP_IND_STATUS) & 1); i++)
+ ;
+ ret = zx_pp_wait(adapter, blk);
+ if (!ret)
+ *val = readl(adapter->base + blk + ZX_PP_IND_DATA);
+ return ret;
+}
+
+/* MAC in the layout of the switch's MAC registers: bytes 2-5, then 0-1 */
+static void zx_mac_words(const u8 *mac, u32 *w)
+{
+ w[0] = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5];
+ w[1] = mac[0] << 8 | mac[1];
+}
+
+/* Set @width bits at bit @pos of a little-endian bit string */
+static void zx_ppe_put_bits(u8 *buf, int pos, int width, u32 val)
+{
+ int i;
+
+ for (i = 0; i < width; i++, pos++)
+ if (val & BIT(i))
+ buf[pos / 8] |= BIT(pos % 8);
+}
+
+/* MSB-first CRC over the key, last byte first, no init or final xor */
+static u32 zx_ppe_crc(u32 poly, const u8 *key, int len)
+{
+ u32 crc = 0;
+ int i, b;
+
+ for (i = len - 1; i >= 0; i--) {
+ crc ^= key[i] << 24;
+ for (b = 0; b < 8; b++)
+ crc = crc & BIT(31) ? crc << 1 ^ poly : crc << 1;
+ }
+ return crc;
+}
+
+struct zx_ppe_tuple {
+ u8 proto;
+ __be32 saddr, daddr;
+ __be16 sport, dport;
+};
+
+static void zx_ppe_halfwords(const struct zx_ppe_tuple *t, u16 hw[7])
+{
+ u32 s = be32_to_cpu(t->saddr), d = be32_to_cpu(t->daddr);
+
+ hw[0] = t->proto;
+ hw[1] = s >> 16;
+ hw[2] = s & 0xffff;
+ hw[3] = d >> 16;
+ hw[4] = d & 0xffff;
+ hw[5] = be16_to_cpu(t->sport);
+ hw[6] = be16_to_cpu(t->dport);
+}
+
+/* Hash slot of a tuple in bank 0 (0-255) and bank 1 (256-383). The key is
+ * 45 bytes: header (extract rule id at bit 23, direction bit 32 = 0) and
+ * the extracted halfwords from bit 33.
+ */
+static void zx_ppe_slots(const struct zx_ppe_tuple *t, u16 slot[2])
+{
+ u8 key[45] = {};
+ u16 hw[7];
+ int i;
+
+ zx_ppe_halfwords(t, hw);
+ zx_ppe_put_bits(key, 23, 8, ZX_PPE_V4_RULE);
+ for (i = 0; i < 7; i++)
+ zx_ppe_put_bits(key, 33 + 16 * i, 16, hw[i]);
+ slot[0] = zx_ppe_crc(0x04c11db7, key, sizeof(key)) & 0xff;
+ slot[1] = ZX_PPE_HASH0_SIZE + (zx_ppe_crc(0x1edc6f41, key, sizeof(key)) & 0x7f);
+}
+
+/*
+ * 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.
+ */
+static void zx_ppe_hash_entry(const struct zx_ppe_tuple *t, u16 flow, int port,
+ u32 w[9])
+{
+ u8 key[20] = {};
+ u16 hw[7];
+ int i;
+
+ zx_ppe_halfwords(t, hw);
+ /* valid, extract index 9, upstream */
+ zx_ppe_put_bits(key, 0, 8, 0x40 | ZX_PPE_V4_INDEX);
+ 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;
+ w[1] = 0xfa11c000;
+ w[2] = 0x00000608;
+ w[3] = 0x80000000;
+ for (i = 0; i < 5; i++)
+ w[4 + i] = get_unaligned_le32(key + 4 * i);
+}
+
+struct zx_ppe_rewrite {
+ bool sip, dip, sport, dport;
+ __be16 new_sport, new_dport;
+ u8 subnet;
+ u16 nh;
+};
+
+/* Packet modifier flow entry (12 bytes, little-endian bit fields) */
+static void zx_ppe_flow_entry(const struct zx_ppe_rewrite *r, u32 w[3])
+{
+ u8 b[12] = {};
+
+ zx_ppe_put_bits(b, 0, 1, 1); /* replace destination MAC */
+ zx_ppe_put_bits(b, 1, 1, 1); /* replace source MAC */
+ if (r->dport)
+ zx_ppe_put_bits(b, 2, 16, be16_to_cpu(r->new_dport));
+ if (r->sport)
+ zx_ppe_put_bits(b, 18, 16, be16_to_cpu(r->new_sport));
+ zx_ppe_put_bits(b, 34, 1, 1); /* decrement TTL */
+ zx_ppe_put_bits(b, 35, 1, 1); /* update L4 checksum */
+ zx_ppe_put_bits(b, 36, 1, 1); /* update IP checksum */
+ zx_ppe_put_bits(b, 37, 1, r->dport);
+ zx_ppe_put_bits(b, 38, 1, r->sport);
+ zx_ppe_put_bits(b, 39, 1, r->dip);
+ zx_ppe_put_bits(b, 40, 1, r->sip);
+ zx_ppe_put_bits(b, 41, 4, r->subnet);
+ zx_ppe_put_bits(b, 50, 9, r->nh);
+ w[0] = get_unaligned_le32(b);
+ w[1] = get_unaligned_le32(b + 4);
+ w[2] = get_unaligned_le32(b + 8);
+}
+
+/* Subnet: source MAC and (for SNAT) source IP of rewritten frames */
+static int zx_ppe_subnet_get(struct zx_eth_adapter *adapter, const u8 *mac,
+ __be32 ip)
+{
+ int i, free = -1;
+ u32 w[2];
+
+ for (i = 0; i < ZX_PPE_SUBNETS; i++) {
+ if (adapter->ppe_subnet[i].refs &&
+ ether_addr_equal(adapter->ppe_subnet[i].mac, mac) &&
+ adapter->ppe_subnet[i].ip == ip) {
+ adapter->ppe_subnet[i].refs++;
+ return i;
+ }
+ if (!adapter->ppe_subnet[i].refs && free < 0)
+ free = i;
+ }
+ if (free < 0)
+ return -ENOSPC;
+
+ zx_mac_words(mac, w);
+ if (zx_pp_write(adapter, ZX_PM_BASE, ZX_PM_SRC_MAC, free, w, 2))
+ return -ETIMEDOUT;
+ writel(be32_to_cpu(ip), adapter->base + ZX_PP_SNAT_IP(free));
+ ether_addr_copy(adapter->ppe_subnet[free].mac, mac);
+ adapter->ppe_subnet[free].ip = ip;
+ adapter->ppe_subnet[free].refs = 1;
+ return free;
+}
+
+static void zx_ppe_subnet_put(struct zx_eth_adapter *adapter, int i)
+{
+ adapter->ppe_subnet[i].refs--;
+}
+
+static int zx_ppe_alloc(unsigned long *map, int size, int first)
+{
+ int i = find_next_zero_bit(map, size, first);
+
+ if (i >= size)
+ return -ENOSPC;
+ set_bit(i, map);
+ return i;
+}
+
+/*
+ * Apply one of the MAC rewrites that nf_flow_table_offload.c builds: the
+ * destination MAC as 4 bytes at offset 0 and 2 bytes at offset 4 (mask
+ * 0xffff0000), the source MAC as 2 bytes at offset 4 (mask 0x0000ffff, value
+ * in the upper half) and 4 bytes at offset 8. The masks and values are in
+ * CPU order, so the byte positions below are those of a little-endian CPU,
+ * which the zx279128s is.
+ */
+static void zx_ppe_mangle_eth(const struct flow_action_entry *act, void *eth)
+{
+ void *dest = eth + act->mangle.offset;
+ const void *src = &act->mangle.val;
+
+ if (act->mangle.offset > 8)
+ return;
+ if (act->mangle.mask == 0xffff) {
+ src += 2;
+ dest += 2;
+ }
+ memcpy(dest, src, act->mangle.mask ? 2 : 4);
+}
+
+static int zx_ppe_flow_add(struct zx_eth_adapter *adapter,
+ struct flow_cls_offload *f)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(f);
+ struct flow_action_entry *act;
+ struct zx_ppe_rewrite rw = {};
+ struct zx_ppe_tuple t = {};
+ struct net_device *odev = NULL;
+ struct zx_ppe_entry *e;
+ struct ethhdr eth = {};
+ __be32 new_saddr, new_daddr;
+ u16 slot[2];
+ u32 w[17];
+ int i, ret, port, subnet, flow, nh;
+
+ 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;
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+ struct flow_match_control match;
+
+ flow_rule_match_control(rule, &match);
+ if (match.key->addr_type != FLOW_DISSECTOR_KEY_IPV4_ADDRS)
+ return -EOPNOTSUPP;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+ struct flow_match_basic match;
+
+ flow_rule_match_basic(rule, &match);
+ if (match.key->n_proto != htons(ETH_P_IP))
+ return -EOPNOTSUPP;
+ t.proto = match.key->ip_proto;
+ }
+ if (t.proto != IPPROTO_TCP && t.proto != IPPROTO_UDP)
+ return -EOPNOTSUPP;
+
+ if (!flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) ||
+ !flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS))
+ return -EOPNOTSUPP;
+ {
+ struct flow_match_ipv4_addrs addrs;
+ struct flow_match_ports ports;
+
+ flow_rule_match_ipv4_addrs(rule, &addrs);
+ flow_rule_match_ports(rule, &ports);
+ t.saddr = addrs.key->src;
+ t.daddr = addrs.key->dst;
+ t.sport = ports.key->src;
+ t.dport = ports.key->dst;
+ }
+ new_saddr = t.saddr;
+ new_daddr = t.daddr;
+ rw.new_sport = t.sport;
+ rw.new_dport = t.dport;
+
+ flow_action_for_each(i, act, &rule->action) {
+ switch (act->id) {
+ case FLOW_ACTION_MANGLE:
+ switch (act->mangle.htype) {
+ case FLOW_ACT_MANGLE_HDR_TYPE_ETH:
+ zx_ppe_mangle_eth(act, ð);
+ break;
+ case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
+ /* the value holds the address in network order */
+ if (act->mangle.offset == offsetof(struct iphdr, saddr))
+ new_saddr = (__force __be32)act->mangle.val;
+ else if (act->mangle.offset == offsetof(struct iphdr, daddr))
+ new_daddr = (__force __be32)act->mangle.val;
+ else
+ return -EOPNOTSUPP;
+ break;
+ case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
+ case FLOW_ACT_MANGLE_HDR_TYPE_UDP: {
+ u32 val = be32_to_cpu((__force __be32)act->mangle.val);
+
+ if (act->mangle.offset == 0) {
+ if (act->mangle.mask == ~(__force u32)cpu_to_be32(0xffff))
+ rw.new_dport = cpu_to_be16(val);
+ else
+ rw.new_sport = cpu_to_be16(val >> 16);
+ } else if (act->mangle.offset == 2) {
+ rw.new_dport = cpu_to_be16(val);
+ } else {
+ return -EOPNOTSUPP;
+ }
+ break;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case FLOW_ACTION_CSUM:
+ break;
+ case FLOW_ACTION_REDIRECT:
+ odev = act->dev;
+ break;
+ default:
+ /* VLAN, PPPoE and tunnel encapsulation are not supported */
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (!odev || !zx_eth_is_port(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;
+
+ rw.sip = new_saddr != t.saddr;
+ rw.dip = new_daddr != t.daddr;
+ rw.sport = rw.new_sport != t.sport;
+ rw.dport = rw.new_dport != t.dport;
+
+ zx_ppe_slots(&t, slot);
+ if (!test_bit(slot[0], adapter->ppe_hash_used))
+ slot[1] = slot[0];
+ else if (test_bit(slot[1], adapter->ppe_hash_used))
+ return -EBUSY;
+
+ e = kzalloc_obj(*e);
+ if (!e)
+ return -ENOMEM;
+
+ subnet = zx_ppe_subnet_get(adapter, eth.h_source, rw.sip ? new_saddr : 0);
+ if (subnet < 0) {
+ ret = subnet;
+ goto err_free;
+ }
+ flow = zx_ppe_alloc(adapter->ppe_flow_used, ZX_PPE_FLOWS, 1);
+ if (flow < 0) {
+ ret = flow;
+ goto err_subnet;
+ }
+ nh = zx_ppe_alloc(adapter->ppe_nh_used, ZX_PPE_NEXT_HOPS, 1);
+ if (nh < 0) {
+ ret = nh;
+ goto err_flow;
+ }
+ rw.subnet = subnet;
+ rw.nh = nh;
+
+ /* next hop: new destination IP (used for DNAT) and MAC */
+ w[0] = rw.dip ? be32_to_cpu(new_daddr) : 0;
+ zx_mac_words(eth.h_dest, w + 1);
+ ret = zx_pp_write(adapter, ZX_PM_BASE, ZX_PM_NEXT_HOP, nh, w, 3);
+ if (ret)
+ goto err_nh;
+ zx_ppe_flow_entry(&rw, w);
+ ret = zx_pp_write(adapter, ZX_PM_BASE, ZX_PM_FLOW, flow, w, 3);
+ if (ret)
+ goto err_nh;
+ 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);
+ 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);
+ if (ret)
+ goto err_nh;
+
+ e->cookie = f->cookie;
+ e->hash = slot[1];
+ e->flow = flow;
+ e->nh = nh;
+ e->subnet = subnet;
+ 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++;
+ return 0;
+
+err_entry:
+ 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, 0);
+err_nh:
+ clear_bit(nh, adapter->ppe_nh_used);
+err_flow:
+ clear_bit(flow, adapter->ppe_flow_used);
+err_subnet:
+ zx_ppe_subnet_put(adapter, subnet);
+err_free:
+ kfree(e);
+ return ret;
+}
+
+static void zx_ppe_entry_remove(struct zx_eth_adapter *adapter,
+ struct zx_ppe_entry *e)
+{
+ 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);
+ zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_AGING, e->hash, NULL, 0);
+ clear_bit(e->hash, adapter->ppe_hash_used);
+ clear_bit(e->flow, adapter->ppe_flow_used);
+ clear_bit(e->nh, adapter->ppe_nh_used);
+ zx_ppe_subnet_put(adapter, e->subnet);
+ adapter->ppe_count--;
+}
+
+static int zx_ppe_flow_del(struct zx_eth_adapter *adapter,
+ struct flow_cls_offload *f)
+{
+ struct zx_ppe_entry *e;
+
+ e = rhashtable_lookup_fast(&adapter->ppe_flows, &f->cookie, zx_ppe_ht_params);
+ if (!e)
+ return -ENOENT;
+ rhashtable_remove_fast(&adapter->ppe_flows, &e->node, zx_ppe_ht_params);
+ zx_ppe_entry_remove(adapter, e);
+ kfree(e);
+ return 0;
+}
+
+/* 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)
+{
+ struct zx_ppe_entry *e;
+ u32 hit;
+
+ e = rhashtable_lookup_fast(&adapter->ppe_flows, &f->cookie, zx_ppe_ht_params);
+ if (!e)
+ return -ENOENT;
+ if (zx_pp_read_word0(adapter, ZX_CLA_BASE, ZX_CLA_AGING, e->hash, &hit))
+ return -ETIMEDOUT;
+ if (hit & 1) {
+ f->stats.lastused = jiffies;
+ zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_AGING, e->hash, NULL, 0);
+ }
+ return 0;
+}
+
+/* Values from the stock firmware */
+static const u32 zx_ppe_v4_index[5] = {
+ 0x93929190, 0x97969594, 0x9b9a9998, 0x9f9e9d9c,
+ 0x00150151, /* bit 8: look up the hash banks */
+};
+
+static const u32 zx_ppe_v4_rule[16] = {
+ 0x22038608, 0x000058a1, 0, 0, 0xf00ff000, 0xffffffff, 0xffffffff,
+ 0x0fffffff, 0, 0, 0, 0, 0, 0, 0x00700000, 0x00092492,
+};
+
+static const u32 zx_ppe_sub[2] = { 0xfc000000, 0x00001fff };
+
+/* classifier L3 MTU and actions, packet modifier padding of short frames.
+ * The "other L3 packet" action (0x38c0cc) keeps the value 1 of the base
+ * driver: with the stock value 0, IPv6 addressed to our MACs was dropped
+ * once its trap was lifted (hardware LAN switching).
+ */
+static const u32 zx_ppe_regs[4] = { 0x38c088, 0x38c094, 0x38c098, 0x39c034 };
+static const u32 zx_ppe_reg_vals[4] = { 0x7fff, 4, 0x7fff7fff, 0x3d };
+
+/*
+ * Traps that send frames to the CPU before the flow lookup: the IPv4
+ * ethertype trap, and the registered packet class for TCP segments without
+ * payload (trap reason 0x2c), which would send every pure ACK of an
+ * offloaded connection through the software path. Both are off while
+ * offload is active. SYN, FIN and RST still reach the CPU, so conntrack
+ * sees connections open and close. Hardware LAN switching also needs the
+ * IPv6 trap off. Frames for the router, broadcast, multicast and unknown
+ * unicast still reach the CPU through the other traps.
+ */
+void zx_update_traps(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+ bool sw, fwd;
+ u32 val;
+
+ spin_lock(&adapter->trap_lock);
+ sw = READ_ONCE(adapter->sw_members);
+ fwd = READ_ONCE(adapter->ppe_active) || sw;
+ writel((fwd ? 0 : ETH_P_IP << 16) | (sw ? 0 : ETH_P_IPV6),
+ base + ZX_SPA_TRAP_ETH_TYPE);
+ val = readl(base + ZX_SPA_UP_REG_PKT_EN + 8);
+ if (fwd)
+ val &= ~ZX_SPA_REG_PKT_TCP_ACK;
+ else
+ val |= ZX_SPA_REG_PKT_TCP_ACK;
+ writel(val, base + ZX_SPA_UP_REG_PKT_EN + 8);
+ spin_unlock(&adapter->trap_lock);
+}
+
+/*
+ * Frames to the ports' own addresses reach the CPU through the trap table,
+ * or with flow offload active, through the ONU MAC table, which marks them
+ * as routed so that the classifier sees them first. Addresses beyond the
+ * trap table still reach the CPU as unknown unicast. Called with ppe_lock
+ * held, after an address or the offload state changed.
+ */
+void zx_update_mac_tables(struct zx_eth_adapter *adapter)
+{
+ static const u8 none[ETH_ALEN];
+ void __iomem *base = adapter->base;
+ const u8 *addrs[ZX_NUM_PORTS];
+ int i, j, n = 0;
+ u32 w[2];
+
+ lockdep_assert_held(&adapter->ppe_lock);
+
+ /* each distinct port address once */
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ struct net_device *dev = adapter->ports[i];
+
+ if (!dev)
+ continue;
+ for (j = 0; j < n; j++)
+ if (ether_addr_equal(addrs[j], dev->dev_addr))
+ break;
+ if (j == n)
+ addrs[n++] = dev->dev_addr;
+ }
+
+ for (i = 0; i < ZX_SPA_ONU_MAC_NUM; i++) {
+ zx_mac_words(adapter->ppe_active && i < n ? addrs[i] : none, w);
+ writel(w[0], base + ZX_SPA_ONU_MAC + i * 8);
+ writel(w[1], base + ZX_SPA_ONU_MAC + i * 8 + 4);
+ }
+ for (i = 0; i < ZX_SPA_TRAP_DMAC_NUM; i++)
+ zx_set_trap_dmac(adapter, i,
+ !adapter->ppe_active && i < n ? addrs[i] : none);
+}
+
+/*
+ * Routed frames must reach the classifier: mark our MACs as routed (ONU
+ * MAC table) and stop trapping IPv4 and frames to our MACs straight to the
+ * CPU. IPv6, ARP and VLAN tagged frames stay trapped. Frames the
+ * classifier does not know still go to the CPU.
+ */
+static int zx_ppe_start(struct zx_eth_adapter *adapter)
+{
+ static const u32 cmd = 0x00010000;
+ void __iomem *base = adapter->base;
+ int i, ret;
+
+ for (i = 0; i < ZX_PPE_SUBNETS; i++)
+ adapter->ppe_subnet[i].refs = 0;
+ bitmap_zero(adapter->ppe_hash_used, ZX_PPE_HASH_SIZE);
+ bitmap_zero(adapter->ppe_flow_used, ZX_PPE_FLOWS);
+ bitmap_zero(adapter->ppe_nh_used, ZX_PPE_NEXT_HOPS);
+
+ for (i = 0; i < ARRAY_SIZE(zx_ppe_regs); i++) {
+ adapter->ppe_saved[i] = readl(base + zx_ppe_regs[i]);
+ writel(zx_ppe_reg_vals[i], base + zx_ppe_regs[i]);
+ }
+
+ ret = zx_pp_write(adapter, ZX_PM_BASE, ZX_PM_CMD, 0, &cmd, 1);
+ if (!ret)
+ ret = zx_pp_write(adapter, ZX_PM_BASE, ZX_PM_SUB, 0, zx_ppe_sub, 2);
+ if (!ret)
+ ret = zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_EXTRA_RULE,
+ ZX_PPE_V4_RULE, zx_ppe_v4_rule, 16);
+ if (!ret)
+ ret = zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_EXTRA_INDEX,
+ ZX_PPE_V4_INDEX, zx_ppe_v4_index, 5);
+ if (ret) {
+ for (i = 0; i < ARRAY_SIZE(zx_ppe_regs); i++)
+ writel(adapter->ppe_saved[i], base + zx_ppe_regs[i]);
+ return ret;
+ }
+
+ adapter->ppe_active = true;
+ zx_update_mac_tables(adapter);
+ zx_update_traps(adapter);
+ dev_info(adapter->dev, "hardware flow offload enabled\n");
+ return 0;
+}
+
+static void zx_ppe_flush(void *ptr, void *arg)
+{
+ struct zx_ppe_entry *e = ptr;
+
+ zx_ppe_entry_remove(arg, e);
+ kfree(e);
+}
+
+static void zx_ppe_stop(struct zx_eth_adapter *adapter)
+{
+ void __iomem *base = adapter->base;
+ int i;
+
+ /* back to trapping everything, then drop the tables */
+ adapter->ppe_active = false;
+ zx_update_traps(adapter);
+ zx_update_mac_tables(adapter);
+
+ rhashtable_free_and_destroy(&adapter->ppe_flows, zx_ppe_flush, adapter);
+ rhashtable_init(&adapter->ppe_flows, &zx_ppe_ht_params);
+ zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_EXTRA_INDEX, ZX_PPE_V4_INDEX, NULL, 0);
+ zx_pp_write(adapter, ZX_CLA_BASE, ZX_CLA_EXTRA_RULE, ZX_PPE_V4_RULE, NULL, 0);
+ for (i = 0; i < ARRAY_SIZE(zx_ppe_regs); i++)
+ writel(adapter->ppe_saved[i], base + zx_ppe_regs[i]);
+ dev_info(adapter->dev, "hardware flow offload disabled\n");
+}
+
+static int zx_ppe_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
+{
+ struct zx_eth_adapter *adapter = cb_priv;
+ struct flow_cls_offload *f = type_data;
+ int ret;
+
+ if (type != TC_SETUP_CLSFLOWER)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&adapter->ppe_lock);
+ switch (f->command) {
+ case FLOW_CLS_REPLACE:
+ ret = zx_ppe_flow_add(adapter, f);
+ if (ret == -EOPNOTSUPP)
+ adapter->ppe_add_skip++;
+ else if (ret && ret != -EEXIST)
+ adapter->ppe_add_fail++;
+ break;
+ case FLOW_CLS_DESTROY:
+ ret = zx_ppe_flow_del(adapter, f);
+ break;
+ case FLOW_CLS_STATS:
+ ret = zx_ppe_flow_stats(adapter, f);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ }
+ mutex_unlock(&adapter->ppe_lock);
+ return ret;
+}
+
+static LIST_HEAD(zx_ppe_block_cb_list);
+
+/* One callback for the flowtable of all port netdevs, so each flow is
+ * offered once.
+ */
+int zx_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
+ void *type_data)
+{
+ struct zx_eth_priv *priv = netdev_priv(dev);
+ struct zx_eth_adapter *adapter = priv->adapter;
+ struct flow_block_offload *f = type_data;
+ struct flow_block_cb *block_cb;
+ int ret = 0;
+
+ if (type != TC_SETUP_FT)
+ return -EOPNOTSUPP;
+ if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
+ return -EOPNOTSUPP;
+
+ f->driver_block_list = &zx_ppe_block_cb_list;
+ block_cb = flow_block_cb_lookup(f->block, zx_ppe_block_cb, adapter);
+
+ switch (f->command) {
+ case FLOW_BLOCK_BIND:
+ if (block_cb) {
+ flow_block_cb_incref(block_cb);
+ return 0;
+ }
+ block_cb = flow_block_cb_alloc(zx_ppe_block_cb, adapter, adapter, NULL);
+ if (IS_ERR(block_cb))
+ return PTR_ERR(block_cb);
+ /* A ruleset reload binds the new flowtable before it unbinds the
+ * old one, so keep the hardware on while any flowtable is bound.
+ */
+ mutex_lock(&adapter->ppe_lock);
+ if (!adapter->ppe_users)
+ ret = zx_ppe_start(adapter);
+ if (!ret)
+ adapter->ppe_users++;
+ mutex_unlock(&adapter->ppe_lock);
+ if (ret) {
+ flow_block_cb_free(block_cb);
+ return ret;
+ }
+ flow_block_cb_incref(block_cb);
+ flow_block_cb_add(block_cb, f);
+ list_add_tail(&block_cb->driver_list, &zx_ppe_block_cb_list);
+ return 0;
+ case FLOW_BLOCK_UNBIND:
+ if (!block_cb)
+ return -ENOENT;
+ if (!flow_block_cb_decref(block_cb)) {
+ flow_block_cb_remove(block_cb, f);
+ list_del(&block_cb->driver_list);
+ mutex_lock(&adapter->ppe_lock);
+ if (!--adapter->ppe_users)
+ zx_ppe_stop(adapter);
+ mutex_unlock(&adapter->ppe_lock);
+ }
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
diff --git a/drivers/net/ethernet/zte/zx279128s-switch.c b/drivers/net/ethernet/zte/zx279128s-switch.c
new file mode 100644
index 0000000000..91df8de455
--- /dev/null
+++ b/drivers/net/ethernet/zte/zx279128s-switch.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE zx279128s Ethernet switch: hardware LAN switching
+ *
+ * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/rtnetlink.h>
+#include <net/switchdev.h>
+
+#include "zx279128s-eth.h"
+
+/* ---- Hardware LAN switching ---------------------------------------- */
+
+/*
+ * The bridge block forwards a unicast frame from one switch port to another
+ * when its source table knows the destination and both ports are members
+ * of the port VLAN (1). Normally learning is off and the VLAN table empty,
+ * so every frame goes to the Linux bridge. Ports of the same Linux bridge
+ * switch between each other in hardware, as long as the bridge does not
+ * filter VLANs and the port forwards, learns, and is neither isolated nor
+ * locked (zx_sw_bridge()); any other port stays on the CPU path:
+ * - VLAN 1 has them as untagged members, each may send only to the other
+ * ports of its bridge (egress mask 0 for all other ports, so nothing is
+ * switched to or from a port outside the bridge), and only they learn;
+ * - the IPv4 and IPv6 ethertype traps are off (zx_update_traps()).
+ * Only known unicast is switched. Frames for the router, broadcast,
+ * multicast and unknown unicast reach the CPU alone, and the Linux bridge
+ * floods them, also to the hosts on its other ports (Wi-Fi). So received
+ * frames are never marked as forwarded already (offload_fwd_mark).
+ * A host that moves loses its hardware entry: the entries of a port are
+ * dropped when its link goes down, and a MAC is dropped as soon as the
+ * Linux bridge learns it on a port that is not a switch port.
+ */
+static int zx_sbrg_ind(struct zx_eth_adapter *adapter, u32 cmd)
+{
+ u32 val;
+
+ writel(cmd, adapter->base + ZX_SBRG_IND_CMD);
+ return readl_poll_timeout_atomic(adapter->base + ZX_SBRG_IND_DONE, val,
+ val & 1, 0, 1000);
+}
+
+static void zx_sbrg_flush(struct zx_eth_adapter *adapter, u8 brports)
+{
+ void __iomem *reg = adapter->base + ZX_SBRG_PORT_CTRL;
+ u32 val = readl(reg) & ~(ZX_SBRG_FLUSH_PORTS | ZX_SBRG_FLUSH);
+
+ writel(val | FIELD_PREP(ZX_SBRG_FLUSH_PORTS, brports) | ZX_SBRG_FLUSH, reg);
+ usleep_range(1000, 2000);
+ writel(val, reg);
+}
+
+/* Remove one MAC from the source table: scan the four RAMs */
+static void zx_sbrg_fdb_del(struct zx_eth_adapter *adapter, const u8 *mac)
+{
+ static const int sizes[4] = { 1024, 256, 512, 512 };
+ void __iomem *base = adapter->base;
+ u32 hi = mac[0] << 16 | mac[1] << 8 | mac[2];
+ u32 lo = mac[3] << 24 | mac[4] << 16 | mac[5] << 8;
+ int n = sizes[readl(base + ZX_SBRG_TABLE_SEL) & 3];
+ int ram, i;
+
+ for (ram = 0; ram < 4; ram++) {
+ for (i = 0; i < n; i++) {
+ if (zx_sbrg_ind(adapter, ZX_SBRG_IND_READ | ram << 22 | i))
+ return;
+ if (!(readl(base + ZX_SBRG_IND_DATA(2)) & 0xf0) ||
+ (readl(base + ZX_SBRG_IND_DATA(1)) & 0xffffff) != hi ||
+ (readl(base + ZX_SBRG_IND_DATA(0)) & 0xffffff00) != lo)
+ continue;
+ if (zx_sbrg_ind(adapter, ram << 22 | i))
+ return;
+ writel(0, base + ZX_SBRG_IND_DATA(2));
+ writel(0, base + ZX_SBRG_IND_DATA(1));
+ writel(0, base + ZX_SBRG_IND_DATA(0));
+ adapter->sw_fdb_deleted++;
+ }
+ }
+}
+
+/* the bridge a port switches in, or NULL if it stays on the CPU path */
+static struct net_device *zx_sw_bridge(struct net_device *dev)
+{
+ struct net_device *br;
+
+ ASSERT_RTNL();
+ if (!dev || !netif_is_bridge_port(dev))
+ return NULL;
+ br = netdev_master_upper_dev_get(dev);
+ if (!br || br_vlan_enabled(br) ||
+ br_port_get_stp_state(dev) != BR_STATE_FORWARDING ||
+ !br_port_flag_is_set(dev, BR_LEARNING) ||
+ br_port_flag_is_set(dev, BR_ISOLATED) ||
+ br_port_flag_is_set(dev, BR_PORT_LOCKED))
+ return NULL;
+ return br;
+}
+
+static void zx_sw_apply(struct zx_eth_adapter *adapter)
+{
+ struct net_device *br[ZX_NUM_PORTS] = {};
+ void __iomem *base = adapter->base;
+ u8 egress[ZX_NUM_PORTS] = {};
+ u8 members = 0;
+ u32 vlan = 0;
+ int i, j;
+
+ lockdep_assert_held(&adapter->sw_lock);
+
+ rtnl_lock();
+ for (i = 0; adapter->sw_ready && i < ZX_NUM_PORTS; i++)
+ br[i] = zx_sw_bridge(adapter->ports[i]);
+ rtnl_unlock();
+
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ for (j = 0; j < ZX_NUM_PORTS; j++)
+ if (j != i && br[i] && br[j] == br[i])
+ egress[i] |= BIT(ZX_BRPORT(j));
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ if (!egress[i]) {
+ br[i] = NULL;
+ continue;
+ }
+ members |= BIT(ZX_BRPORT(i));
+ vlan |= ZX_SBRG_VLAN_UNTAG(ZX_BRPORT(i));
+ }
+
+ spin_lock_bh(&adapter->sw_fdb_lock);
+ memcpy(adapter->sw_bridge, br, sizeof(br));
+ spin_unlock_bh(&adapter->sw_fdb_lock);
+
+ /* traps back on before the tables go, off once they are in place */
+ if (!members) {
+ WRITE_ONCE(adapter->sw_members, 0);
+ zx_update_traps(adapter);
+ }
+
+ /* no learning while the tables change, then start empty */
+ writel(0, base + ZX_SBRG_LEARN);
+ if (zx_sbrg_ind(adapter, ZX_SBRG_MEM_VLAN << 22 | 1))
+ dev_warn(adapter->dev, "bridge VLAN table access timed out\n");
+ else
+ writel(members ? vlan | ZX_SBRG_VLAN_VALID : 0,
+ base + ZX_SBRG_IND_DATA(0));
+ /* the reset value lets a port send to every other port */
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ writel(members ? egress[i] : (u8)~BIT(ZX_BRPORT(i)),
+ base + ZX_SBRG_EGRESS(ZX_BRPORT(i)));
+ zx_sbrg_flush(adapter, 0xff);
+ writel(members, base + ZX_SBRG_LEARN);
+ if (members) {
+ WRITE_ONCE(adapter->sw_members, members);
+ zx_update_traps(adapter);
+ }
+ dev_dbg(adapter->dev, "hardware LAN switching %s (bridge ports %#x)\n",
+ members ? "on" : "off", members);
+}
+
+void zx_sw_work(struct work_struct *work)
+{
+ struct zx_eth_adapter *adapter = container_of(work, struct zx_eth_adapter,
+ sw_work);
+ u8 macs[ZX_SW_FDB_QUEUE][ETH_ALEN];
+ bool recompute, overflow;
+ unsigned int n, i;
+ u8 flush;
+
+ spin_lock_bh(&adapter->sw_fdb_lock);
+ recompute = adapter->sw_recompute;
+ overflow = adapter->sw_fdb_overflow;
+ flush = adapter->sw_flush;
+ n = adapter->sw_fdb_count;
+ memcpy(macs, adapter->sw_fdb_mac, n * ETH_ALEN);
+ adapter->sw_recompute = false;
+ adapter->sw_fdb_overflow = false;
+ adapter->sw_flush = 0;
+ adapter->sw_fdb_count = 0;
+ spin_unlock_bh(&adapter->sw_fdb_lock);
+
+ mutex_lock(&adapter->sw_lock);
+ if (recompute) {
+ zx_sw_apply(adapter);
+ } else if (READ_ONCE(adapter->sw_members)) {
+ if (overflow)
+ flush = 0xff;
+ if (flush)
+ zx_sbrg_flush(adapter, flush);
+ for (i = 0; !overflow && i < n; i++)
+ zx_sbrg_fdb_del(adapter, macs[i]);
+ }
+ mutex_unlock(&adapter->sw_lock);
+}
+
+void zx_sw_port_down(struct zx_eth_adapter *adapter, int port)
+{
+ if (!READ_ONCE(adapter->sw_members))
+ return;
+ spin_lock_bh(&adapter->sw_fdb_lock);
+ adapter->sw_flush |= BIT(ZX_BRPORT(port));
+ spin_unlock_bh(&adapter->sw_fdb_lock);
+ schedule_work(&adapter->sw_work);
+}
+
+static bool zx_sw_is_port(struct zx_eth_adapter *adapter,
+ const struct net_device *dev)
+{
+ int i;
+
+ for (i = 0; i < ZX_NUM_PORTS; i++)
+ if (adapter->ports[i] == dev)
+ return true;
+ return false;
+}
+
+static void zx_sw_recompute(struct zx_eth_adapter *adapter)
+{
+ spin_lock_bh(&adapter->sw_fdb_lock);
+ adapter->sw_recompute = true;
+ spin_unlock_bh(&adapter->sw_fdb_lock);
+ schedule_work(&adapter->sw_work);
+}
+
+/* a switch port joined or left a bridge */
+static int zx_sw_netdev_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct zx_eth_adapter *adapter = container_of(nb, struct zx_eth_adapter,
+ sw_netdev_nb);
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+ if (event == NETDEV_CHANGEUPPER && zx_sw_is_port(adapter, dev))
+ zx_sw_recompute(adapter);
+ return NOTIFY_DONE;
+}
+
+static bool zx_sw_dev_check(const struct net_device *dev)
+{
+ return zx_eth_is_port(dev);
+}
+
+/* something that decides whether the port switches (zx_sw_bridge()) */
+static int zx_sw_port_attr_set(struct net_device *dev, const void *ctx,
+ const struct switchdev_attr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct zx_eth_priv *priv = netdev_priv(dev);
+
+ switch (attr->id) {
+ case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+ /* the CPU path handles what the switch does not */
+ return 0;
+ case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
+ case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+ case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
+ zx_sw_recompute(priv->adapter);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int zx_sw_switchdev_blocking_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ int err;
+
+ if (event != SWITCHDEV_PORT_ATTR_SET)
+ return NOTIFY_DONE;
+ err = switchdev_handle_port_attr_set(dev, ptr, zx_sw_dev_check,
+ zx_sw_port_attr_set);
+ return notifier_from_errno(err);
+}
+
+/* the Linux bridge learned a MAC; if it is on a non-switch port of a
+ * switching bridge (Wi-Fi), the host left its switch port
+ */
+static int zx_sw_switchdev_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct zx_eth_adapter *adapter = container_of(nb, struct zx_eth_adapter,
+ sw_switchdev_nb);
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_fdb_info *fdb;
+ struct net_device *br;
+ bool queued = false;
+ int i;
+
+ if (event != SWITCHDEV_FDB_ADD_TO_DEVICE || zx_sw_is_port(adapter, dev))
+ return NOTIFY_DONE;
+ fdb = container_of(ptr, struct switchdev_notifier_fdb_info, info);
+ if (fdb->is_local)
+ return NOTIFY_DONE;
+
+ rcu_read_lock();
+ br = netdev_master_upper_dev_get_rcu(dev);
+ rcu_read_unlock();
+ if (!br)
+ return NOTIFY_DONE;
+
+ spin_lock_bh(&adapter->sw_fdb_lock);
+ for (i = 0; i < ZX_NUM_PORTS; i++) {
+ if (adapter->sw_bridge[i] != br)
+ continue;
+ if (adapter->sw_fdb_count < ZX_SW_FDB_QUEUE)
+ memcpy(adapter->sw_fdb_mac[adapter->sw_fdb_count++],
+ fdb->addr, ETH_ALEN);
+ else
+ adapter->sw_fdb_overflow = true;
+ queued = true;
+ break;
+ }
+ spin_unlock_bh(&adapter->sw_fdb_lock);
+ if (queued)
+ schedule_work(&adapter->sw_work);
+ return NOTIFY_DONE;
+}
+
+/* Follow the bridges the ports are in; called at the end of probe */
+void zx_sw_init(struct zx_eth_adapter *adapter)
+{
+ adapter->sw_netdev_nb.notifier_call = zx_sw_netdev_event;
+ adapter->sw_switchdev_nb.notifier_call = zx_sw_switchdev_event;
+ adapter->sw_switchdev_blocking_nb.notifier_call =
+ zx_sw_switchdev_blocking_event;
+ adapter->sw_ready =
+ !register_netdevice_notifier(&adapter->sw_netdev_nb) &&
+ !register_switchdev_notifier(&adapter->sw_switchdev_nb) &&
+ !register_switchdev_blocking_notifier(&adapter->sw_switchdev_blocking_nb);
+ if (!adapter->sw_ready)
+ dev_warn(adapter->dev,
+ "no bridge notifications: hardware LAN switching unavailable\n");
+}
--
2.55.0
next prev parent reply other threads:[~2026-10-10 13:00 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-10 12:59 [PATCH RFC 00/24] ARM: zte: Add support for Sanechips ZX279128S and ZTE ZXHN H3600 Navid Ghahremani
2026-10-10 12:59 ` [PATCH 01/24] dt-bindings: cache: l2c2x0: Describe ZTE device-read serialization Navid Ghahremani
2026-10-12 9:43 ` Krzysztof Kozlowski
2026-10-10 12:59 ` [PATCH 02/24] ARM: l2c: Serialize device reads with cache maintenance when requested Navid Ghahremani
2026-10-10 13:10 ` sashiko-bot
2026-10-10 14:28 ` Arnd Bergmann
2026-10-10 21:41 ` Navid Ghahremani
2026-10-11 1:02 ` Navid Ghahremani
2026-10-11 14:48 ` Arnd Bergmann
2026-10-10 12:59 ` [PATCH 03/24] wifi: mt76: Serialize MMIO reads with outer cache maintenance Navid Ghahremani
2026-10-10 12:59 ` [PATCH 04/24] dt-bindings: arm: Add ZTE ZX279128S platform descriptions Navid Ghahremani
2026-10-10 12:59 ` [PATCH 05/24] ARM: zte: Add ZX279128S platform and CPU hotplug support Navid Ghahremani
2026-10-10 14:13 ` Arnd Bergmann
2026-10-10 17:34 ` Stefan Dösinger
2026-10-10 21:26 ` Navid Ghahremani
2026-10-10 12:59 ` [PATCH 06/24] dt-bindings: clock: Add ZTE ZX279128S CRM clocks and resets Navid Ghahremani
2026-10-10 13:08 ` sashiko-bot
2026-10-11 9:13 ` Stefan Dösinger
2026-10-10 12:59 ` [PATCH 07/24] clk: zte: Add ZX279128S CRM clock and reset driver Navid Ghahremani
2026-10-10 13:15 ` sashiko-bot
2026-10-11 11:10 ` Stefan Dösinger
2026-10-10 12:59 ` [PATCH 08/24] dt-bindings: gpio: Add ZTE ZX279128S GPIO controller Navid Ghahremani
2026-10-10 12:59 ` [PATCH 09/24] gpio: Add ZTE ZX279128S GPIO driver Navid Ghahremani
2026-10-10 13:14 ` sashiko-bot
2026-10-11 11:19 ` Stefan Dösinger
2026-10-12 10:59 ` Uwe Kleine-König
2026-10-10 12:59 ` [PATCH 10/24] dt-bindings: pinctrl: pinctrl-single: Add ZTE ZX279128S pin mux Navid Ghahremani
2026-10-10 12:59 ` [PATCH 11/24] dt-bindings: mfd: syscon: Add ZX279128S system controller Navid Ghahremani
2026-10-10 13:08 ` sashiko-bot
2026-10-10 12:59 ` [PATCH 12/24] dt-bindings: PCI: Add ZTE ZX279128S host controller Navid Ghahremani
2026-10-10 13:12 ` sashiko-bot
2026-10-10 12:59 ` [PATCH 13/24] PCI: dwc: Add ZTE ZX279128S host controller driver Navid Ghahremani
2026-10-10 13:14 ` sashiko-bot
2026-10-11 13:03 ` Stefan Dösinger
2026-10-10 12:59 ` [PATCH 14/24] dt-bindings: net: Add ZTE ZX279128S MDIO controller Navid Ghahremani
2026-10-10 13:06 ` sashiko-bot
2026-10-10 12:59 ` [PATCH 15/24] net: mdio: " Navid Ghahremani
2026-10-10 13:11 ` sashiko-bot
2026-10-10 12:59 ` [PATCH 16/24] net: phy: Add Sanechips ZX5201 PHY support Navid Ghahremani
2026-10-10 12:59 ` [PATCH 17/24] dt-bindings: net: Add ZTE ZX279128S Ethernet switch Navid Ghahremani
2026-10-10 13:08 ` sashiko-bot
2026-10-10 12:59 ` Navid Ghahremani [this message]
2026-10-10 13:19 ` [PATCH 18/24] net: ethernet: zte: Add ZX279128S Ethernet switch driver sashiko-bot
2026-10-10 12:59 ` [PATCH 19/24] dt-bindings: spi: Add ZTE ZX279128S SPI flash controller Navid Ghahremani
2026-10-10 12:59 ` [PATCH 20/24] " Navid Ghahremani
2026-10-10 13:11 ` sashiko-bot
2026-10-11 16:32 ` Stefan Dösinger
2026-10-10 12:59 ` [PATCH 21/24] dt-bindings: usb: Add ZTE ZX279128S DWC3 controller Navid Ghahremani
2026-10-10 13:11 ` sashiko-bot
2026-10-10 13:00 ` [PATCH 22/24] usb: dwc3: generic-plat: Add ZTE ZX279128S Navid Ghahremani
2026-10-10 13:14 ` sashiko-bot
2026-10-10 13:00 ` [PATCH 23/24] ARM: dts: zte: Add ZX279128S SoC description Navid Ghahremani
2026-10-10 13:14 ` sashiko-bot
2026-10-10 13:00 ` [PATCH 24/24] ARM: dts: zte: Add ZTE ZXHN H3600 board Navid Ghahremani
2026-10-12 19:20 ` [PATCH RFC 00/24] ARM: zte: Add support for Sanechips ZX279128S and ZTE ZXHN H3600 Stefan Dösinger
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=20261010130024.177503-19-ghahramani.navid@gmail.com \
--to=ghahramani.navid@gmail.com \
--cc=arnd@arndb.de \
--cc=devicetree@vger.kernel.org \
--cc=krzk@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--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