| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/04 18:09 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds HSR/PRP support to the TI PRU-ICSS Ethernet driver (icssm_prueth). This is a vendor-specific SoC Ethernet driver that requires physical TI hardware (PRU-ICSS) and specific remoteproc firmware. It cannot be reached or tested in standard virtualized environments like QEMU or GCE.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/04 18:09 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit e3354ab3789a48df367d24fc1bcb2dc79c466dc8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 4 18:09:26 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile\nindex f4276c9a77620..b6651fe73afd8 100644\n--- a/drivers/net/ethernet/ti/Makefile\n+++ b/drivers/net/ethernet/ti/Makefile\n@@ -4,7 +4,7 @@\n #\n \n obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o\n-icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o\n+icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o icssm/icssm_prueth_lre.o icssm/icssm_prueth_common.o\n \n ti-cpsw-common-y += cpsw-common.o davinci_cpdma.o\n ti-cpsw-priv-y += cpsw_priv.o cpsw_ethtool.o\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h\nnew file mode 100644\nindex 0000000000000..b5ab0ec87c5f7\n--- /dev/null\n+++ b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h\n@@ -0,0 +1,141 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Copyright (C) 2017-2020 Texas Instruments Incorporated - http://www.ti.com */\n+#ifndef __ICSS_LRE_FIRMWARE_H\n+#define __ICSS_LRE_FIRMWARE_H\n+\n+#define ICSS_LRE_HSR_MODE_OFFSET\t\t0x1E76\n+#define ICSS_LRE_MODEH\t\t\t\t0x01\n+\n+/* PRU0 DMEM */\n+#define ICSS_LRE_DBG_START\t\t\t0x1E00\n+\n+#define ICSS_LRE_DUPLICATE_HOST_TABLE\t\t0x0200\n+\n+/* PRU1 DMEM */\n+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0\t0x0200\n+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1\t0x0E00\n+\n+/* Size and setup (N and M) of duplicate host table */\n+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE\t0x1C08\n+/* Size and setup (N and M) of duplicate port table (HSR Only) */\n+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE\t0x1C1C\n+/* Time after which an entry is removed from the duplicate\n+ * table (10 ms resolution)\n+ */\n+#define ICSS_LRE_DUPLI_FORGET_TIME\t\t0x1C24\n+/* Time interval to check the port duplicate table */\n+#define ICSS_LRE_DUPLI_PORT_CHECK_RESO\t\t0x1C2C\n+/* Time interval to check the host duplicate table */\n+#define ICSS_LRE_DUPLI_HOST_CHECK_RESO\t\t0x1C30\n+/* NodeTable | Host | Port */\n+#define ICSS_LRE_HOST_TIMER_CHECK_FLAGS\t\t0x1C38\n+/* Arbitration flag for the host duplicate table */\n+#define ICSS_LRE_HOST_DUPLICATE_ARBITRATION\t0x1C3C\n+/* Supervision address in LRE */\n+#define ICSS_LRE_SUP_ADDR\t\t\t0x1C4C\n+#define ICSS_LRE_SUP_ADDR_LOW\t\t\t0x1C50\n+\n+/* Time in TimeTicks (1/100s) */\n+#define ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS\t40\n+#define ICSS_LRE_NODE_FORGET_TIME_60000_MS\t6000\n+#define ICSS_LRE_MAX_FORGET_TIME\t\t0xFFDF\n+\n+#define ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE\t0x0C00\n+#define ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE\t0x1800\n+#define ICSS_LRE_STATS_DMEM_SIZE\t\t0x0080\n+#define ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE\t0x0050\n+\n+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT\t0x800004 /* N = 128, M = 4 */\n+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT\t0x400004 /* N = 64, M = 4 */\n+#define ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR\t0x0\n+#define ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS\t0xA\n+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH\t0x4E1501\n+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW\t0x1\n+\n+/* SHARED RAM */\n+\n+/* 8 bytes of VLAN PCP to RX QUEUE MAPPING */\n+#define ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET\t\t0x124\n+#define ICSS_LRE_START\t\t\t\t0x140\n+\n+/* Count of HSR/PRP tagged frames successfully transmitted on port A/B */\n+#define ICSS_LRE_CNT_TX_A\t\t\t(ICSS_LRE_START + 4)\n+#define ICSS_LRE_DUPLICATE_DISCARD\t\t(ICSS_LRE_START + 104)\n+#define ICSS_LRE_TRANSPARENT_RECEPTION\t\t(ICSS_LRE_START + 108)\n+#define ICSS_LRE_CNT_NODES\t\t\t(ICSS_LRE_START + 52)\n+\n+/* SRAM */\n+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_ACCEPT\t\t0x01\n+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD\t\t0x02\n+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT\t0x01\n+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_PASS_RCT\t0x02\n+\n+/* Enable/disable interrupts for high/low priority instead of per port.\n+ * 0 = disabled (default), 1 = enabled\n+ */\n+#define ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET\t0x1FAA\n+/* Enable/disable timestamping of packets. 0 = disabled (default) 1 = enabled */\n+#define ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET\t0x1FAB\n+#define ICSS_LRE_TIMESTAMP_ARRAY_OFFSET\t\t0xC200\n+\n+/* HOST_TIMER_CHECK_FLAGS bits */\n+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CHECK_BIT\tBIT(0)\n+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CLEAR_BIT\tBIT(4)\n+#define ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT\tBIT(8)\n+#define ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT\t\tBIT(16)\n+#define ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT\t\tBIT(24)\n+#define ICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS \\\n+\t\t\t(ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT | \\\n+\t\t\t ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT)\n+\n+/* PRU1 DMEM */\n+/* Node table offsets are different for AM3/4 vs AM57/K2G, set by firmware */\n+#define ICSS_LRE_V1_0_HASH_MASK 0x3F\n+#define ICSS_LRE_V1_0_INDEX_ARRAY_NT 0x60\n+#define ICSS_LRE_V1_0_BIN_ARRAY 0x1A00\n+#define ICSS_LRE_V1_0_NODE_TABLE_NEW 0x1FC0\n+#define ICSS_LRE_V1_0_INDEX_ARRAY_LOC PRUETH_MEM_DRAM0\n+#define ICSS_LRE_V1_0_BIN_ARRAY_LOC PRUETH_MEM_DRAM0\n+#define ICSS_LRE_V1_0_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM\n+#define ICSS_LRE_V1_0_INDEX_TBL_MAX_ENTRIES 64\n+#define ICSS_LRE_V1_0_BIN_TBL_MAX_ENTRIES 128\n+#define ICSS_LRE_V1_0_NODE_TBL_MAX_ENTRIES 128\n+\n+#define ICSS_LRE_V2_1_HASH_MASK 0xFF\n+#define ICSS_LRE_V2_1_INDEX_ARRAY_NT 0x3000\n+#define ICSS_LRE_V2_1_BIN_ARRAY \\\n+\t(ICSS_LRE_V2_1_INDEX_ARRAY_NT + \\\n+\t(ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES * 6))\n+#define ICSS_LRE_V2_1_NODE_TABLE_NEW \\\n+\t(ICSS_LRE_V2_1_BIN_ARRAY + \\\n+\t(ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES * 8))\n+#define ICSS_LRE_V2_1_INDEX_ARRAY_LOC PRUETH_MEM_SHARED_RAM\n+#define ICSS_LRE_V2_1_BIN_ARRAY_LOC PRUETH_MEM_SHARED_RAM\n+#define ICSS_LRE_V2_1_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM\n+#define ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES 256\n+#define ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES 256\n+#define ICSS_LRE_V2_1_NODE_TBL_MAX_ENTRIES 256\n+\n+#define ICSS_LRE_NODE_FREE\t\t\t0x10\n+#define ICSS_LRE_NODE_TAKEN\t\t\t0x01\n+#define ICSS_LRE_NT_REM_NODE_TYPE_MASK\t\t0x1F\n+#define ICSS_LRE_NT_REM_NODE_TYPE_SHIFT\t\t0x00\n+\n+#define ICSS_LRE_NT_REM_NODE_TYPE_SANA\t\t0x01\n+#define ICSS_LRE_NT_REM_NODE_TYPE_SANB\t\t0x02\n+#define ICSS_LRE_NT_REM_NODE_TYPE_SANAB\t\t0x03\n+#define ICSS_LRE_NT_REM_NODE_TYPE_DAN\t\t0x04\n+#define ICSS_LRE_NT_REM_NODE_TYPE_REDBOX\t0x08\n+#define ICSS_LRE_NT_REM_NODE_TYPE_VDAN\t\t0x10\n+\n+#define ICSS_LRE_NT_REM_NODE_HSR_BIT\t\t0x20 /* if set node is HSR */\n+\n+#define ICSS_LRE_NT_REM_NODE_DUP_MASK\t\t0xC0\n+#define ICSS_LRE_NT_REM_NODE_DUP_SHIFT\t\t0x06\n+\n+/* Node entry duplicate type: DupAccept */\n+#define ICSS_LRE_NT_REM_NODE_DUP_ACCEPT\t\t0x40\n+/* Node entry duplicate type: DupDiscard */\n+#define ICSS_LRE_NT_REM_NODE_DUP_DISCARD\t0x80\n+\n+#endif /* __ICSS_LRE_FIRMWARE_H */\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c\nindex b7e94244355a3..43862946d4f14 100644\n--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c\n@@ -30,16 +30,83 @@\n \n #include \"icssm_prueth.h\"\n #include \"icssm_prueth_switch.h\"\n+#include \"icssm_prueth_lre.h\"\n #include \"icssm_vlan_mcast_filter_mmap.h\"\n #include \"../icssg/icssg_mii_rt.h\"\n #include \"../icssg/icss_iep.h\"\n \n #define OCMC_RAM_SIZE\t\t(SZ_64K)\n+#define PRUETH_ETHER_TYPE_OFFSET\t12\n \n #define TX_START_DELAY\t\t0x40\n #define TX_CLK_DELAY_100M\t0x6\n #define HR_TIMER_TX_DELAY_US\t100\n \n+#define NETIF_PRUETH_LRE_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \\\n+\t\t\t\t\t\t NETIF_F_HW_HSR_DUP | \\\n+\t\t\t\t\t\t NETIF_F_HW_HSR_TAG_RM)\n+\n+/* ICSSM (v2.1) - supports 64-bit IEP counter.\n+ * Firmware stores packet timestamps using lower 32 bits\n+ * which wraps at 0xffffffff.\n+ */\n+static const struct prueth_fw_offsets fw_offsets_v2_1 = {\n+\t.iep_wrap = 0xffffffff,\n+};\n+\n+/* ICSSM (v1.0) - supports 32-bit IEP counter, which resets the\n+ * counter every one second (nanosecond resolution).\n+ */\n+static const struct prueth_fw_offsets fw_offsets_v1_0 = {\n+\t.iep_wrap = NSEC_PER_SEC,\n+};\n+\n+static void icssm_prueth_set_fw_offsets(struct prueth *prueth)\n+{\n+\t/* Set Multicast filter control and table offsets */\n+\tif (PRUETH_IS_EMAC(prueth) || PRUETH_IS_SWITCH(prueth)) {\n+\t\tprueth-\u003efw_offsets.mc_ctrl_offset =\n+\t\t\tICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;\n+\t\tprueth-\u003efw_offsets.mc_filter_mask =\n+\t\t\tICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;\n+\t\tprueth-\u003efw_offsets.mc_filter_tbl =\n+\t\t\tICSS_EMAC_FW_MULTICAST_FILTER_TABLE;\n+\t} else {\n+\t\tprueth-\u003efw_offsets.mc_ctrl_offset =\n+\t\t\tICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT;\n+\t\tprueth-\u003efw_offsets.mc_filter_mask =\n+\t\t\tICSS_LRE_FW_MULTICAST_FILTER_MASK;\n+\t\tprueth-\u003efw_offsets.mc_filter_tbl =\n+\t\t\tICSS_LRE_FW_MULTICAST_FILTER_TABLE;\n+\t}\n+}\n+\n+/* Queue Descriptors initialization for HSR PRP */\n+const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES] = {\n+\t[PRUETH_PORT_QUEUE_HOST] = {\n+\t\t{ .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },\n+\t\t{ .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },\n+\t\t{ .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },\n+\t\t{ .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII0] = {\n+\t\t{ .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },\n+\t\t{ .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },\n+\t\t{ .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,\n+\t\t\t.wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },\n+\t\t{ .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,\n+\t\t\t.wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII1] = {\n+\t\t{ .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },\n+\t\t{ .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },\n+\t\t{ .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,\n+\t\t\t.wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },\n+\t\t{ .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,\n+\t\t\t.wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },\n+\t}\n+};\n+\n static void icssm_prueth_write_reg(struct prueth *prueth,\n \t\t\t\t enum prueth_mem region,\n \t\t\t\t unsigned int reg, u32 val)\n@@ -58,6 +125,17 @@ static void icssm_prueth_write_reg(struct prueth *prueth,\n static enum pruss_mem pruss_mem_ids[] = { PRUSS_MEM_DRAM0, PRUSS_MEM_DRAM1,\n \t\t\t\t\t PRUSS_MEM_SHRD_RAM2 };\n \n+struct prp_txopt_rct {\n+\t__be16 sequence_nr;\n+\t__be16 lan_id_and_lsdu_size;\n+\t__be16 prp_suffix;\n+};\n+\n+struct hsr_txopt_ethhdr {\n+\tstruct ethhdr ethhdr;\n+\tstruct hsr_tag hsr_tag;\n+};\n+\n static const struct prueth_queue_info queue_infos[][NUM_QUEUES] = {\n \t[PRUETH_PORT_QUEUE_HOST] = {\n \t\t[PRUETH_QUEUE1] = {\n@@ -309,12 +387,17 @@ static void icssm_prueth_hostinit(struct prueth *prueth)\n \ticssm_prueth_mii_init(prueth);\n }\n \n-/* This function initialize the driver in EMAC mode\n+/* Initialize the driver in EMAC, HSR or PRP mode\n * based on eth_type\n */\n static void icssm_prueth_init_ethernet_mode(struct prueth *prueth)\n {\n+\ticssm_prueth_set_fw_offsets(prueth);\n \ticssm_prueth_hostinit(prueth);\n+\tif (prueth_is_lre(prueth))\n+\t\ticssm_prueth_lre_config(prueth);\n+\telse if (PRUETH_IS_SWITCH(prueth))\n+\t\ticssm_prueth_sw_config_packet_timestamping(prueth);\n }\n \n static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable)\n@@ -507,15 +590,24 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n \t\t\t\t struct sk_buff *skb,\n \t\t\t\t enum prueth_queue_id queue_id)\n {\n+\tstruct prueth_queue_desc __iomem *queue_desc_other_port = NULL;\n \tstruct prueth_queue_desc __iomem *queue_desc;\n \tconst struct prueth_queue_info *txqueue;\n-\tstruct net_device *ndev = emac-\u003endev;\n \tstruct prueth *prueth = emac-\u003eprueth;\n+\tstruct hsr_txopt_ethhdr *hsr_ethhdr;\n \tunsigned int buffer_desc_count;\n+\tstruct prueth_emac *other_emac;\n \tint free_blocks, update_block;\n+\tstruct vlan_ethhdr *vlan_hdr;\n \tbool buffer_wrapped = false;\n \tint write_block, read_block;\n+\tint free_blocks_other_port;\n+\tint read_block_other_port;\n \tvoid *src_addr, *dst_addr;\n+\tu16 bd_rd_ptr_other_port;\n+\tstruct ethhdr *ethhdr;\n+\tbool is_vlan = false;\n+\tbool link_up = false;\n \tint pkt_block_size;\n \tvoid __iomem *sram;\n \tvoid __iomem *dram;\n@@ -523,16 +615,19 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n \tu16 update_wr_ptr;\n \tu32 wr_buf_desc;\n \tvoid *ocmc_ram;\n+\t__be16 proto;\n+\tu8 *hdr;\n+\n+\tother_emac = emac-\u003eprueth-\u003eemac[(emac-\u003eport_id == PRUETH_PORT_MII0) ?\n+\t\t\t\tPRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];\n+\n+\tif (prueth_is_lre(prueth) \u0026\u0026 (emac-\u003elink || other_emac-\u003elink))\n+\t\tlink_up = true;\n \n \tif (!PRUETH_IS_EMAC(prueth))\n \t\tdram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n \telse\n \t\tdram = emac-\u003eprueth-\u003emem[emac-\u003edram].va;\n-\tif (eth_skb_pad(skb)) {\n-\t\tif (netif_msg_tx_err(emac) \u0026\u0026 net_ratelimit())\n-\t\t\tnetdev_err(ndev, \"packet pad failed\\n\");\n-\t\treturn -ENOMEM;\n-\t}\n \n \t/* which port to tx: MII0 or MII1 */\n \ttxport = emac-\u003etx_port_queue;\n@@ -540,7 +635,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n \tpktlen = skb-\u003elen;\n \t/* Get the tx queue */\n \tqueue_desc = emac-\u003etx_queue_descs + queue_id;\n-\tif (!PRUETH_IS_EMAC(prueth))\n+\t/* Tx queue context */\n+\tif (prueth_is_lre(prueth))\n+\t\ttxqueue = \u0026lre_queue_infos[txport][queue_id];\n+\telse if (PRUETH_IS_SWITCH(prueth))\n \t\ttxqueue = \u0026sw_queue_infos[txport][queue_id];\n \telse\n \t\ttxqueue = \u0026queue_infos[txport][queue_id];\n@@ -563,8 +661,31 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n \t\tfree_blocks = buffer_desc_count;\n \t}\n \n+\t/* Fetch queue state for the second LRE port */\n+\tif (prueth_is_lre(prueth) \u0026\u0026 link_up) {\n+\t\tqueue_desc_other_port = emac-\u003etx_queue_descs_other_port +\n+\t\t\t\t\tqueue_id;\n+\t\tbd_rd_ptr_other_port = readw(\u0026queue_desc_other_port-\u003erd_ptr);\n+\n+\t\tread_block_other_port = (bd_rd_ptr_other_port -\n+\t\t\t\t\t txqueue-\u003ebuffer_desc_offset) / BD_SIZE;\n+\n+\t\tif (write_block \u003e read_block_other_port) {\n+\t\t\tfree_blocks_other_port = buffer_desc_count -\n+\t\t\t\t\t\t write_block;\n+\t\t\tfree_blocks_other_port += read_block_other_port;\n+\t\t} else if (write_block \u003c read_block_other_port) {\n+\t\t\tfree_blocks_other_port = read_block_other_port -\n+\t\t\t\t\t\t write_block;\n+\t\t} else {\n+\t\t\tfree_blocks_other_port = buffer_desc_count;\n+\t\t}\n+\n+\t\tif (free_blocks_other_port \u003c free_blocks)\n+\t\t\tfree_blocks = free_blocks_other_port;\n+\t}\n \tpkt_block_size = DIV_ROUND_UP(pktlen, ICSS_BLOCK_SIZE);\n-\tif (pkt_block_size \u003e free_blocks) /* out of queue space */\n+\tif (pkt_block_size \u003e= free_blocks) /* out of queue space */\n \t\treturn -ENOBUFS;\n \n \t/* calculate end BD address post write */\n@@ -609,6 +730,60 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n /* update first buffer descriptor */\n \twr_buf_desc = (pktlen \u003c\u003c PRUETH_BD_LENGTH_SHIFT) \u0026\n \t\t PRUETH_BD_LENGTH_MASK;\n+\tif (PRUETH_IS_HSR(prueth))\n+\t\twr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);\n+\n+\tif (prueth_is_lre(prueth)) {\n+\t\tethhdr = (struct ethhdr *)skb_mac_header(skb);\n+\t\tproto = ethhdr-\u003eh_proto;\n+\n+\t\tif (proto == htons(ETH_P_8021Q)) {\n+\t\t\tvlan_hdr = (struct vlan_ethhdr *)ethhdr;\n+\t\t\tproto = vlan_hdr-\u003eh_vlan_encapsulated_proto;\n+\t\t\tis_vlan = true;\n+\t\t}\n+\n+\t\t/* Extract HSR sequence number and LAN ID\n+\t\t * from the tag for the Buffer Descriptor\n+\t\t */\n+\t\tif (proto == htons(ETH_P_HSR)) {\n+\t\t\thdr = skb_mac_header(skb);\n+\n+\t\t\tif (is_vlan) {\n+\t\t\t\thsr_ethhdr =\n+\t\t\t\t\t(struct hsr_txopt_ethhdr *)(hdr +\n+\t\t\t\t\t\t\t\t VLAN_HLEN);\n+\t\t\t} else {\n+\t\t\t\thsr_ethhdr = (struct hsr_txopt_ethhdr *)hdr;\n+\t\t\t}\n+\n+\t\t\t/* PTP frames (ETH_P_1588) carry no LAN ID\n+\t\t\t * in the HSR tag\n+\t\t\t */\n+\t\t\tif (hsr_ethhdr-\u003ehsr_tag.encap_proto !=\n+\t\t\t htons(ETH_P_1588)) {\n+\t\t\t\twr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;\n+\t\t\t} else {\n+\t\t\t\twr_buf_desc |= (txport \u003c\u003c\n+\t\t\t\t\t\tPRUETH_BD_LAN_A_SHIFT);\n+\t\t\t}\n+\t\t\twr_buf_desc |= PRUETH_BD_RED_PKT_MASK;\n+\t\t} else {\n+\t\t\t/* Read PRP RCT to extract sequence number and LAN ID */\n+\t\t\tstruct prp_txopt_rct *rct =\n+\t\t\t\t(struct prp_txopt_rct *)(skb_tail_pointer(skb) -\n+\t\t\t\t\t\t\t ICSSM_LRE_TAG_SIZE);\n+\n+\t\t\tif (rct-\u003eprp_suffix == htons(ETH_P_PRP)) {\n+\t\t\t\twr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;\n+\t\t\t\twr_buf_desc |= PRUETH_BD_RED_PKT_MASK;\n+\t\t\t} else {\n+\t\t\t\twr_buf_desc |= (txport \u003c\u003c\n+\t\t\t\t\t\tPRUETH_BD_LAN_A_SHIFT);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tsram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n \tif (!PRUETH_IS_EMAC(prueth))\n \t\twritel(wr_buf_desc, sram + readw(\u0026queue_desc-\u003ewr_ptr));\n@@ -621,14 +796,26 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,\n \tupdate_wr_ptr = txqueue-\u003ebuffer_desc_offset + (update_block * BD_SIZE);\n \twritew(update_wr_ptr, \u0026queue_desc-\u003ewr_ptr);\n \n+\t/* update the write pointer in queue descriptor of other port */\n+\tif (prueth_is_lre(prueth) \u0026\u0026 link_up)\n+\t\twritew(update_wr_ptr, \u0026queue_desc_other_port-\u003ewr_ptr);\n+\n \treturn 0;\n }\n \n void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,\n \t\t\t struct prueth_packet_info *pkt_info)\n {\n-\tpkt_info-\u003eport = (buffer_descriptor \u0026 PRUETH_BD_PORT_MASK) \u003e\u003e\n-\t\t\t PRUETH_BD_PORT_SHIFT;\n+\tif (prueth_is_lre(prueth))\n+\t\tpkt_info-\u003estart_offset = !!(buffer_descriptor \u0026\n+\t\t\t\t\t PRUETH_BD_START_FLAG_MASK);\n+\telse\n+\t\tpkt_info-\u003estart_offset = false;\n+\n+\t/* Flag from BD to indicate packet is valid for HOST or not. */\n+\tpkt_info-\u003ehost_recv_flag = !!(buffer_descriptor \u0026\n+\t\t\t\t PRUETH_BD_HOST_RECV_MASK);\n+\n \tpkt_info-\u003elength = (buffer_descriptor \u0026 PRUETH_BD_LENGTH_MASK) \u003e\u003e\n \t\t\t PRUETH_BD_LENGTH_SHIFT;\n \tpkt_info-\u003ebroadcast = !!(buffer_descriptor \u0026 PRUETH_BD_BROADCAST_MASK);\n@@ -660,11 +847,17 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \tint read_block, update_block;\n \tunsigned int actual_pkt_len;\n \tbool buffer_wrapped = false;\n+\tint adjust_for_hsr_tag = 0;\n \tvoid *src_addr, *dst_addr;\n+\tu16 start_offset = 0;\n \tstruct sk_buff *skb;\n \tint pkt_block_size;\n \tvoid *ocmc_ram;\n+\tu16 type;\n \n+\tif (PRUETH_IS_HSR(emac-\u003eprueth))\n+\t\tstart_offset = (pkt_info-\u003estart_offset ?\n+\t\t\t\tICSSM_LRE_TAG_SIZE : 0);\n \t/* the PRU firmware deals mostly in pointers already\n \t * offset into ram, we would like to deal in indexes\n \t * within the queue we are working with for code\n@@ -687,7 +880,18 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \t/* calculate new pointer in ram */\n \t*bd_rd_ptr = rxqueue-\u003ebuffer_desc_offset + (update_block * BD_SIZE);\n \n-\tactual_pkt_len = pkt_info-\u003elength;\n+\tif (PRUETH_IS_HSR(emac-\u003eprueth)) {\n+\t\tif (!pkt_info-\u003ehost_recv_flag)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Exclude the HSR tag bytes already stripped by firmware, if any. */\n+\tactual_pkt_len = pkt_info-\u003elength - start_offset;\n+\n+\tif (PRUETH_IS_HSR(emac-\u003eprueth)) {\n+\t\tif (!start_offset \u0026\u0026 !pkt_info-\u003etimestamp)\n+\t\t\tactual_pkt_len -= ICSSM_LRE_TAG_SIZE;\n+\t}\n \n \t/* Allocate a socket buffer for this packet */\n \tskb = netdev_alloc_skb_ip_align(ndev, actual_pkt_len);\n@@ -707,6 +911,30 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \t */\n \tsrc_addr = ocmc_ram + rxqueue-\u003ebuffer_offset +\n \t\t (read_block * ICSS_BLOCK_SIZE);\n+\tsrc_addr += start_offset;\n+\n+\t/* Copy destination and source MAC address */\n+\tmemcpy(dst_addr, src_addr, PRUETH_ETHER_TYPE_OFFSET);\n+\tsrc_addr += PRUETH_ETHER_TYPE_OFFSET;\n+\tdst_addr += PRUETH_ETHER_TYPE_OFFSET;\n+\n+\tadjust_for_hsr_tag += PRUETH_ETHER_TYPE_OFFSET;\n+\n+\t/* Check for VLAN tag */\n+\ttype = get_unaligned_be16(src_addr);\n+\n+\tif (type == ETH_P_8021Q) {\n+\t\tmemcpy(dst_addr, src_addr, VLAN_HLEN);\n+\t\tsrc_addr += VLAN_HLEN;\n+\t\tdst_addr += VLAN_HLEN;\n+\t\tadjust_for_hsr_tag += VLAN_HLEN;\n+\t}\n+\n+\t/* HSR tag removal handling */\n+\tif (PRUETH_IS_HSR(emac-\u003eprueth)) {\n+\t\tif (!start_offset \u0026\u0026 !pkt_info-\u003etimestamp)\n+\t\t\tsrc_addr += ICSSM_LRE_TAG_SIZE;\n+\t}\n \n \t/* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */\n \tif (buffer_wrapped) { /* wrapped around buffer */\n@@ -720,19 +948,28 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \t\tif (pkt_info-\u003elength \u003c bytes)\n \t\t\tbytes = pkt_info-\u003elength;\n \n+\t\t/* If applicable, account for the HSR tag removed */\n+\t\tbytes -= start_offset;\n+\n+\t\tif (PRUETH_IS_HSR(emac-\u003eprueth)) {\n+\t\t\tif (!start_offset \u0026\u0026 !pkt_info-\u003etimestamp)\n+\t\t\t\tbytes -= ICSSM_LRE_TAG_SIZE;\n+\t\t}\n+\n \t\t/* copy non-wrapped part */\n-\t\tmemcpy(dst_addr, src_addr, bytes);\n+\t\tmemcpy(dst_addr, src_addr, bytes - adjust_for_hsr_tag);\n \n \t\t/* copy wrapped part */\n-\t\tdst_addr += bytes;\n+\t\tdst_addr += (bytes - adjust_for_hsr_tag);\n \t\tremaining = actual_pkt_len - bytes;\n \n \t\tsrc_addr = ocmc_ram + rxqueue-\u003ebuffer_offset;\n \t\tmemcpy(dst_addr, src_addr, remaining);\n \t\tsrc_addr += remaining;\n \t} else {\n-\t\tmemcpy(dst_addr, src_addr, actual_pkt_len);\n-\t\tsrc_addr += actual_pkt_len;\n+\t\tmemcpy(dst_addr, src_addr, actual_pkt_len -\n+\t\t adjust_for_hsr_tag);\n+\t\tsrc_addr += actual_pkt_len - adjust_for_hsr_tag;\n \t}\n \n \tif (PRUETH_IS_SWITCH(emac-\u003eprueth)) {\n@@ -741,6 +978,12 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \t\t\ticssm_prueth_sw_learn_fdb(emac, skb-\u003edata + ETH_ALEN);\n \t}\n \n+\t/* For PRP, the RCT trailer is at the frame tail, exclude it from\n+\t * the length to avoid passing it up the stack.\n+\t */\n+\tif (PRUETH_IS_PRP(emac-\u003eprueth) \u0026\u0026 pkt_info-\u003estart_offset)\n+\t\tactual_pkt_len -= ICSSM_LRE_TAG_SIZE;\n+\n \tskb_put(skb, actual_pkt_len);\n \n \t/* send packet up the stack */\n@@ -750,8 +993,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n \tlocal_bh_enable();\n \n \t/* update stats */\n-\temac-\u003estats.rx_bytes += actual_pkt_len;\n-\temac-\u003estats.rx_packets++;\n+\tatomic64_add(actual_pkt_len, \u0026emac-\u003estats.rx_bytes);\n+\tatomic64_inc(\u0026emac-\u003estats.rx_packets);\n \n \treturn 0;\n }\n@@ -790,7 +1033,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)\n \t\t\trxqueue = \u0026queue_infos[PRUETH_PORT_HOST][i];\n \t\toverflow_cnt = readb(\u0026queue_desc-\u003eoverflow_cnt);\n \t\tif (overflow_cnt \u003e 0) {\n-\t\t\temac-\u003estats.rx_over_errors += overflow_cnt;\n+\t\t\tatomic64_add(overflow_cnt, \u0026emac-\u003estats.rx_over_errors);\n \t\t\t/* reset to zero */\n \t\t\twriteb(0, \u0026queue_desc-\u003eoverflow_cnt);\n \t\t}\n@@ -804,16 +1047,15 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)\n \t\t\trd_buf_desc = readl(shared_ram + bd_rd_ptr);\n \t\t\ticssm_parse_packet_info(prueth, rd_buf_desc, \u0026pkt_info);\n \n-\t\t\tif (pkt_info.length \u003c= 0) {\n-\t\t\t\t/* a packet length of zero will cause us to\n-\t\t\t\t * never move the read pointer ahead, locking\n-\t\t\t\t * the driver, so we manually have to move it\n-\t\t\t\t * to the write pointer, discarding all\n-\t\t\t\t * remaining packets in this queue. This should\n-\t\t\t\t * never happen.\n+\t\t\tif (pkt_info.length \u003c EMAC_MIN_PKTLEN) {\n+\t\t\t\t/* if the packet is too small we skip it but we\n+\t\t\t\t * still need to move the read pointer ahead\n+\t\t\t\t * and assume something is wrong with the read\n+\t\t\t\t * pointer as the firmware should be filtering\n+\t\t\t\t * these packets\n \t\t\t\t */\n \t\t\t\tupdate_rd_ptr = bd_wr_ptr;\n-\t\t\t\temac-\u003estats.rx_length_errors++;\n+\t\t\t\tatomic64_inc(\u0026emac-\u003estats.rx_length_errors);\n \t\t\t} else if (pkt_info.length \u003e EMAC_MAX_FRM_SUPPORT) {\n \t\t\t\t/* if the packet is too large we skip it but we\n \t\t\t\t * still need to move the read pointer ahead\n@@ -822,7 +1064,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)\n \t\t\t\t * these packets\n \t\t\t\t */\n \t\t\t\tupdate_rd_ptr = bd_wr_ptr;\n-\t\t\t\temac-\u003estats.rx_length_errors++;\n+\t\t\t\tatomic64_inc(\u0026emac-\u003estats.rx_length_errors);\n \t\t\t} else {\n \t\t\t\tupdate_rd_ptr = bd_rd_ptr;\n \t\t\t\tret = icssm_emac_rx_packet(emac, \u0026update_rd_ptr,\n@@ -982,14 +1224,20 @@ static int icssm_emac_ndo_open(struct net_device *ndev)\n \t\ticssm_prueth_init_ethernet_mode(prueth);\n \n \t/* reset and start PRU firmware */\n-\tif (PRUETH_IS_SWITCH(prueth)) {\n+\tif (!PRUETH_IS_EMAC(prueth)) {\n+\t\t/* Switch, HSR and PRP protocols share same queue structure */\n \t\tret = icssm_prueth_sw_emac_config(emac);\n \t\tif (ret)\n-\t\t\treturn ret;\n+\t\t\tgoto free_hrtimer;\n \n-\t\tret = icssm_prueth_sw_init_fdb_table(prueth);\n-\t\tif (ret)\n-\t\t\treturn ret;\n+\t\tif (PRUETH_IS_SWITCH(prueth)) {\n+\t\t\tret = icssm_prueth_sw_init_fdb_table(prueth);\n+\t\t\tif (ret)\n+\t\t\t\tgoto free_hrtimer;\n+\t\t} else {\n+\t\t\t/* LRE mode: set up duplicate-table check flags */\n+\t\t\ticssm_prueth_lre_config_check_flags(prueth);\n+\t\t}\n \t} else {\n \t\ticssm_prueth_emac_config(emac);\n \t}\n@@ -1014,11 +1262,25 @@ static int icssm_emac_ndo_open(struct net_device *ndev)\n \t\t\tgoto iep_exit;\n \t}\n \n-\tret = icssm_emac_request_irqs(emac);\n-\tif (ret)\n-\t\tgoto rproc_shutdown;\n+\tif (PRUETH_IS_EMAC(prueth)) {\n+\t\tnapi_enable(\u0026emac-\u003enapi);\n+\t} else {\n+\t\tif (!prueth-\u003eemac_configured \u0026\u0026\n+\t\t (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {\n+\t\t\tnapi_enable(\u0026prueth-\u003enapi_hpq);\n+\t\t\tnapi_enable(\u0026prueth-\u003enapi_lpq);\n+\t\t}\n+\t}\n \n-\tnapi_enable(\u0026emac-\u003enapi);\n+\t/* In switch and LRE modes the shared HPQ/LPQ IRQs are used,\n+\t * register them here and reuse for both modes.\n+\t */\n+\tif (PRUETH_IS_EMAC(prueth))\n+\t\tret = icssm_emac_request_irqs(emac);\n+\telse\n+\t\tret = icssm_prueth_common_request_irqs(emac);\n+\tif (ret)\n+\t\tgoto disable_napi;\n \n \t/* start PHY */\n \tphy_start(emac-\u003ephydev);\n@@ -1035,7 +1297,17 @@ static int icssm_emac_ndo_open(struct net_device *ndev)\n \n \treturn 0;\n \n-rproc_shutdown:\n+disable_napi:\n+\tif (PRUETH_IS_EMAC(prueth)) {\n+\t\tnapi_disable(\u0026emac-\u003enapi);\n+\t} else {\n+\t\tif (!prueth-\u003eemac_configured \u0026\u0026\n+\t\t (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {\n+\t\t\tnapi_disable(\u0026prueth-\u003enapi_lpq);\n+\t\t\tnapi_disable(\u0026prueth-\u003enapi_hpq);\n+\t\t}\n+\t}\n+\n \tif (!PRUETH_IS_EMAC(prueth))\n \t\ticssm_prueth_sw_shutdown_prus(emac, ndev);\n \telse\n@@ -1046,6 +1318,9 @@ static int icssm_emac_ndo_open(struct net_device *ndev)\n \t\ticss_iep_exit(prueth-\u003eiep);\n free_mem:\n \ticssm_prueth_free_memory(emac-\u003eprueth);\n+free_hrtimer:\n+\tif (prueth_is_lre(prueth) \u0026\u0026 !prueth-\u003eemac_configured)\n+\t\ticssm_prueth_lre_cleanup(prueth);\n \treturn ret;\n }\n \n@@ -1067,20 +1342,34 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)\n \t/* disable the mac port */\n \ticssm_prueth_port_enable(emac, false);\n \n+\tnetif_stop_queue(ndev);\n+\n \t/* stop PHY */\n \tphy_stop(emac-\u003ephydev);\n \n-\tnapi_disable(\u0026emac-\u003enapi);\n \thrtimer_cancel(\u0026emac-\u003etx_hrtimer);\n \n+\tif (PRUETH_IS_EMAC(prueth)) {\n+\t\tnapi_disable(\u0026emac-\u003enapi);\n+\t\tfree_irq(emac-\u003erx_irq, ndev);\n+\t} else {\n+\t\tif (!prueth-\u003eemac_configured \u0026\u0026\n+\t\t (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {\n+\t\t\tnapi_disable(\u0026prueth-\u003enapi_lpq);\n+\t\t\tnapi_disable(\u0026prueth-\u003enapi_hpq);\n+\t\t}\n+\t\t/* Free IRQs on last port before halting PRU */\n+\t\ticssm_prueth_common_free_irqs(emac);\n+\t}\n+\n \t/* stop the PRU */\n \tif (!PRUETH_IS_EMAC(prueth))\n \t\ticssm_prueth_sw_shutdown_prus(emac, ndev);\n \telse\n \t\trproc_shutdown(emac-\u003epru);\n \n-\t/* free rx interrupts */\n-\tfree_irq(emac-\u003erx_irq, ndev);\n+\tif (prueth_is_lre(prueth) \u0026\u0026 !prueth-\u003eemac_configured)\n+\t\ticssm_prueth_lre_cleanup(prueth);\n \n \t/* free memory related to sw */\n \ticssm_prueth_free_memory(emac-\u003eprueth);\n@@ -1122,7 +1411,8 @@ static int icssm_prueth_change_mode(struct prueth *prueth,\n \t\t}\n \t}\n \n-\tif (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) {\n+\tif (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH ||\n+\t mode == PRUSS_ETHTYPE_HSR || mode == PRUSS_ETHTYPE_PRP) {\n \t\tprueth-\u003eeth_type = mode;\n \t} else {\n \t\tdev_err(prueth-\u003edev, \"unknown mode\\n\");\n@@ -1205,20 +1495,34 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,\n \t\t\t\t\t\tstruct net_device *ndev)\n {\n \tstruct prueth_emac *emac = netdev_priv(ndev);\n+\t/* Spinlock for Tx Queues */\n+\tspinlock_t *lock_queue;\n+\tunsigned long flags;\n \tint ret;\n \tu16 qid;\n \n \tqid = icssm_prueth_get_tx_queue_id(emac-\u003eprueth, skb);\n-\tret = icssm_prueth_tx_enqueue(emac, skb, qid);\n-\tif (ret) {\n-\t\tif (ret != -ENOBUFS \u0026\u0026 netif_msg_tx_err(emac) \u0026\u0026\n-\t\t net_ratelimit())\n-\t\t\tnetdev_err(ndev, \"packet queue failed: %d\\n\", ret);\n+\t/* Select the TX queue spin lock for this queue ID */\n+\tif (prueth_is_lre(emac-\u003eprueth))\n+\t\tlock_queue = \u0026emac-\u003eprueth-\u003elre_host_queue_lock[qid - 2];\n+\telse\n+\t\tlock_queue = \u0026emac-\u003ehost_queue_lock[qid - 2];\n+\n+\tif (eth_skb_pad(skb)) {\n+\t\tif (netif_msg_tx_err(emac) \u0026\u0026 net_ratelimit())\n+\t\t\tnetdev_err(ndev, \"packet pad failed\\n\");\n+\t\tret = -ENOMEM;\n \t\tgoto fail_tx;\n \t}\n \n-\temac-\u003estats.tx_packets++;\n-\temac-\u003estats.tx_bytes += skb-\u003elen;\n+\tspin_lock_irqsave(lock_queue, flags);\n+\tret = icssm_prueth_tx_enqueue(emac, skb, qid);\n+\tspin_unlock_irqrestore(lock_queue, flags);\n+\tif (ret)\n+\t\tgoto fail_tx;\n+\n+\tatomic64_inc(\u0026emac-\u003estats.tx_packets);\n+\tatomic64_add(skb-\u003elen, \u0026emac-\u003estats.tx_bytes);\n \tdev_kfree_skb_any(skb);\n \n \treturn NETDEV_TX_OK;\n@@ -1232,7 +1536,7 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,\n \t\tret = NETDEV_TX_BUSY;\n \t} else {\n \t\t/* error */\n-\t\temac-\u003estats.tx_dropped++;\n+\t\tatomic64_inc(\u0026emac-\u003estats.tx_dropped);\n \t\tret = NET_XMIT_DROP;\n \t}\n \n@@ -1252,13 +1556,13 @@ static void icssm_emac_ndo_get_stats64(struct net_device *ndev,\n {\n \tstruct prueth_emac *emac = netdev_priv(ndev);\n \n-\tstats-\u003erx_packets = emac-\u003estats.rx_packets;\n-\tstats-\u003erx_bytes = emac-\u003estats.rx_bytes;\n-\tstats-\u003etx_packets = emac-\u003estats.tx_packets;\n-\tstats-\u003etx_bytes = emac-\u003estats.tx_bytes;\n-\tstats-\u003etx_dropped = emac-\u003estats.tx_dropped;\n-\tstats-\u003erx_over_errors = emac-\u003estats.rx_over_errors;\n-\tstats-\u003erx_length_errors = emac-\u003estats.rx_length_errors;\n+\tstats-\u003erx_packets = atomic64_read(\u0026emac-\u003estats.rx_packets);\n+\tstats-\u003erx_bytes = atomic64_read(\u0026emac-\u003estats.rx_bytes);\n+\tstats-\u003etx_packets = atomic64_read(\u0026emac-\u003estats.tx_packets);\n+\tstats-\u003etx_bytes = atomic64_read(\u0026emac-\u003estats.tx_bytes);\n+\tstats-\u003etx_dropped = atomic64_read(\u0026emac-\u003estats.tx_dropped);\n+\tstats-\u003erx_over_errors = atomic64_read(\u0026emac-\u003estats.rx_over_errors);\n+\tstats-\u003erx_length_errors = atomic64_read(\u0026emac-\u003estats.rx_length_errors);\n }\n \n /* enable/disable MC filter */\n@@ -1266,11 +1570,16 @@ static void icssm_emac_mc_filter_ctrl(struct prueth_emac *emac, bool enable)\n {\n \tstruct prueth *prueth = emac-\u003eprueth;\n \tvoid __iomem *mc_filter_ctrl;\n+\tu32 mc_ctrl_offset;\n \tvoid __iomem *ram;\n \tu32 reg;\n \n \tram = prueth-\u003emem[emac-\u003edram].va;\n-\tmc_filter_ctrl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;\n+\tif (prueth_is_lre(prueth))\n+\t\tram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\tmc_ctrl_offset = prueth-\u003efw_offsets.mc_ctrl_offset;\n+\tmc_filter_ctrl = ram + mc_ctrl_offset;\n \n \tif (enable)\n \t\treg = ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED;\n@@ -1289,7 +1598,10 @@ static void icssm_emac_mc_filter_reset(struct prueth_emac *emac)\n \tvoid __iomem *ram;\n \n \tram = prueth-\u003emem[emac-\u003edram].va;\n-\tmc_filter_tbl_base = ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;\n+\tif (prueth_is_lre(prueth))\n+\t\tram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\tmc_filter_tbl_base = prueth-\u003efw_offsets.mc_filter_tbl;\n \n \tmc_filter_tbl = ram + mc_filter_tbl_base;\n \tmemset_io(mc_filter_tbl, 0, ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES);\n@@ -1302,11 +1614,16 @@ static void icssm_emac_mc_filter_hashmask\n {\n \tstruct prueth *prueth = emac-\u003eprueth;\n \tvoid __iomem *mc_filter_mask;\n+\tu32 mc_filter_mask_base;\n \tvoid __iomem *ram;\n \n \tram = prueth-\u003emem[emac-\u003edram].va;\n+\tif (prueth_is_lre(prueth))\n+\t\tram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n \n-\tmc_filter_mask = ram + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;\n+\tmc_filter_mask_base = prueth-\u003efw_offsets.mc_filter_mask;\n+\n+\tmc_filter_mask = ram + mc_filter_mask_base;\n \tmemcpy_toio(mc_filter_mask, mask,\n \t\t ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES);\n }\n@@ -1316,11 +1633,16 @@ static void icssm_emac_mc_filter_bin_update(struct prueth_emac *emac, u8 hash,\n {\n \tstruct prueth *prueth = emac-\u003eprueth;\n \tvoid __iomem *mc_filter_tbl;\n+\tu32 mc_filter_tbl_base;\n \tvoid __iomem *ram;\n \n \tram = prueth-\u003emem[emac-\u003edram].va;\n+\tif (prueth_is_lre(prueth))\n+\t\tram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n \n-\tmc_filter_tbl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;\n+\tmc_filter_tbl_base = prueth-\u003efw_offsets.mc_filter_tbl;\n+\n+\tmc_filter_tbl = ram + mc_filter_tbl_base;\n \twriteb(val, mc_filter_tbl + hash);\n }\n \n@@ -1360,6 +1682,8 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)\n {\n \tstruct prueth_emac *emac = netdev_priv(ndev);\n \tbool promisc = ndev-\u003eflags \u0026 IFF_PROMISC;\n+\t/* Spinlock for multicast filter table */\n+\tspinlock_t *mc_filter_tbl_lock;\n \tstruct netdev_hw_addr *ha;\n \tstruct prueth *prueth;\n \tunsigned long flags;\n@@ -1371,8 +1695,13 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)\n \tsram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n \treg = readl(sram + EMAC_PROMISCUOUS_MODE_OFFSET);\n \n+\tif (prueth_is_lre(prueth))\n+\t\tmc_filter_tbl_lock = \u0026prueth-\u003eaddr_lock;\n+\telse\n+\t\tmc_filter_tbl_lock = \u0026emac-\u003eaddr_lock;\n+\n \t/* It is a shared table. So lock the access */\n-\tspin_lock_irqsave(\u0026emac-\u003eaddr_lock, flags);\n+\tspin_lock_irqsave(mc_filter_tbl_lock, flags);\n \n \t/* Disable and reset multicast filter, allows allmulti */\n \ticssm_emac_mc_filter_ctrl(emac, false);\n@@ -1429,15 +1758,98 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)\n \t}\n \n unlock:\n-\tspin_unlock_irqrestore(\u0026emac-\u003eaddr_lock, flags);\n+\tspin_unlock_irqrestore(mc_filter_tbl_lock, flags);\n+}\n+\n+static netdev_features_t icssm_emac_ndo_fix_features(struct net_device *ndev,\n+\t\t\t\t\t\t netdev_features_t features)\n+{\n+\t/* hsr tag removal offload, hsr fwd offload and hsr dup offload are\n+\t * tightly coupled in firmware implementation. These features\n+\t * must always be enabled/disabled together.\n+\t */\n+\tif (!(ndev-\u003efeatures \u0026 NETIF_PRUETH_LRE_OFFLOAD_FEATURES))\n+\t\tif ((features \u0026 NETIF_F_HW_HSR_FWD) ||\n+\t\t (features \u0026 NETIF_F_HW_HSR_TAG_RM) ||\n+\t\t (features \u0026 NETIF_F_HW_HSR_DUP))\n+\t\t\tfeatures |= NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\n+\tif ((ndev-\u003efeatures \u0026 NETIF_F_HW_HSR_FWD) ||\n+\t (ndev-\u003efeatures \u0026 NETIF_F_HW_HSR_TAG_RM) ||\n+\t (ndev-\u003efeatures \u0026 NETIF_F_HW_HSR_DUP))\n+\t\tif (!(features \u0026 NETIF_F_HW_HSR_FWD) ||\n+\t\t !(features \u0026 NETIF_F_HW_HSR_TAG_RM) ||\n+\t\t !(features \u0026 NETIF_F_HW_HSR_DUP))\n+\t\t\tfeatures \u0026= ~NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\n+\treturn features;\n+}\n+\n+/**\n+ * icssm_emac_ndo_set_features - Configure HSR/PRP offload features\n+ * @ndev: network device\n+ * @features: Requested feature set\n+ *\n+ * Called by ethtool -K to configure HSR/PRP offload features. The request\n+ * is rejected if this interface or its paired interface is running.\n+ *\n+ * Return: 0 on success, -EINVAL or -EBUSY on error.\n+ */\n+static int icssm_emac_ndo_set_features(struct net_device *ndev,\n+\t\t\t\t netdev_features_t features)\n+{\n+\tstruct prueth_emac *emac, *other_emac;\n+\tnetdev_features_t have, wanted;\n+\tstruct prueth *prueth;\n+\tbool change_request;\n+\tint ret = -EBUSY;\n+\n+\temac = netdev_priv(ndev);\n+\tprueth = emac-\u003eprueth;\n+\t/* MAC instance index starts from 0. So index by port_id - 1 */\n+\tother_emac = emac-\u003eprueth-\u003eemac[(emac-\u003eport_id == PRUETH_PORT_MII0) ?\n+\t\t\t\tPRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];\n+\twanted = features \u0026 NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\thave = ndev-\u003efeatures \u0026 NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\tchange_request = ((wanted ^ have) != 0);\n+\n+\tif (!prueth-\u003efw_data-\u003esupport_lre)\n+\t\treturn 0;\n+\n+\tif (PRUETH_IS_SWITCH(prueth) \u0026\u0026 change_request) {\n+\t\t/* LRE offload cannot be enabled in switch mode, remove the\n+\t\t * bridge first to revert to EMAC mode.\n+\t\t */\n+\t\tnetdev_err(ndev,\n+\t\t\t \"Switch to HSR/PRP not allowed\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (netif_running(ndev) \u0026\u0026 change_request) {\n+\t\tnetdev_err(ndev,\n+\t\t\t \"Can't change feature when device runs\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tif (other_emac \u0026\u0026 netif_running(other_emac-\u003endev) \u0026\u0026 change_request) {\n+\t\tnetdev_err(ndev,\n+\t\t\t \"Can't change feature when other device runs\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n }\n \n static const struct net_device_ops emac_netdev_ops = {\n \t.ndo_open = icssm_emac_ndo_open,\n \t.ndo_stop = icssm_emac_ndo_stop,\n \t.ndo_start_xmit = icssm_emac_ndo_start_xmit,\n+\t.ndo_set_mac_address = eth_mac_addr,\n+\t.ndo_validate_addr = eth_validate_addr,\n \t.ndo_get_stats64 = icssm_emac_ndo_get_stats64,\n \t.ndo_set_rx_mode = icssm_emac_ndo_set_rx_mode,\n+\t.ndo_set_features = icssm_emac_ndo_set_features,\n+\t.ndo_fix_features = icssm_emac_ndo_fix_features,\n };\n \n /* get emac_port corresponding to eth_node name */\n@@ -1554,6 +1966,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,\n \tspin_lock_init(\u0026emac-\u003elock);\n \tspin_lock_init(\u0026emac-\u003eaddr_lock);\n \n+\tspin_lock_init(\u0026emac-\u003ehost_queue_lock[0]);\n+\tspin_lock_init(\u0026emac-\u003ehost_queue_lock[1]);\n+\n \t/* get mac address from DT and set private and netdev addr */\n \tret = of_get_ethdev_address(eth_node, ndev);\n \tif (!is_valid_ether_addr(ndev-\u003edev_addr)) {\n@@ -1589,14 +2004,33 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,\n \t\tndev-\u003ehw_features |= NETIF_F_HW_L2FW_DOFFLOAD;\n \t}\n \n+\tif (prueth-\u003esupport_lre)\n+\t\tndev-\u003ehw_features |= NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\n \tndev-\u003edev.of_node = eth_node;\n \tndev-\u003enetdev_ops = \u0026emac_netdev_ops;\n \n \tnetif_napi_add(ndev, \u0026emac-\u003enapi, icssm_emac_napi_poll);\n \n+\tif ((prueth-\u003esupport_lre || fw_data-\u003esupport_switch) \u0026\u0026\n+\t emac-\u003eport_id == PRUETH_PORT_MII0) {\n+\t\tnetif_napi_add(ndev, \u0026prueth-\u003enapi_hpq,\n+\t\t\t icssm_prueth_lre_napi_poll_hpq);\n+\t\tnetif_napi_add(ndev, \u0026prueth-\u003enapi_lpq,\n+\t\t\t icssm_prueth_lre_napi_poll_lpq);\n+\t}\n+\n \thrtimer_setup(\u0026emac-\u003etx_hrtimer, \u0026icssm_emac_tx_timer_callback,\n \t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);\n \n+\tif ((prueth-\u003esupport_lre || fw_data-\u003esupport_switch) \u0026\u0026\n+\t emac-\u003eport_id == PRUETH_PORT_MII0) {\n+\t\tprueth-\u003ehp-\u003endev = ndev;\n+\t\tprueth-\u003ehp-\u003epriority = 0;\n+\t\tprueth-\u003elp-\u003endev = ndev;\n+\t\tprueth-\u003elp-\u003epriority = 1;\n+\t}\n+\n \treturn 0;\n free:\n \temac-\u003endev = NULL;\n@@ -1608,6 +2042,7 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,\n static void icssm_prueth_netdev_exit(struct prueth *prueth,\n \t\t\t\t struct device_node *eth_node)\n {\n+\tconst struct prueth_private_data *fw_data = prueth-\u003efw_data;\n \tstruct prueth_emac *emac;\n \tenum prueth_mac mac;\n \n@@ -1622,6 +2057,13 @@ static void icssm_prueth_netdev_exit(struct prueth *prueth,\n \tphy_disconnect(emac-\u003ephydev);\n \n \tnetif_napi_del(\u0026emac-\u003enapi);\n+\n+\tif ((prueth-\u003esupport_lre || fw_data-\u003esupport_switch) \u0026\u0026\n+\t emac-\u003eport_id == PRUETH_PORT_MII0) {\n+\t\tnetif_napi_del(\u0026prueth-\u003enapi_hpq);\n+\t\tnetif_napi_del(\u0026prueth-\u003enapi_lpq);\n+\t}\n+\n \tprueth-\u003eemac[mac] = NULL;\n }\n \n@@ -1741,6 +2183,105 @@ static int icssm_prueth_ndev_port_unlink(struct net_device *ndev)\n \treturn ret;\n }\n \n+static int icssm_prueth_hsr_port_link(struct net_device *ndev,\n+\t\t\t\t struct net_device *hsr_ndev)\n+{\n+\tstruct prueth_emac *emac = netdev_priv(ndev);\n+\tstruct prueth *prueth = emac-\u003eprueth;\n+\tenum pruss_ethtype mode;\n+\tenum hsr_version ver;\n+\tunsigned long flags;\n+\tu8 all_slaves;\n+\tint ret = 0;\n+\n+\tif (PRUETH_IS_SWITCH(prueth))\n+\t\treturn -EOPNOTSUPP;\n+\n+\thsr_get_version(hsr_ndev, \u0026ver);\n+\n+\tif (ver == HSR_V1)\n+\t\tmode = PRUSS_ETHTYPE_HSR;\n+\telse if (ver == PRP_V1)\n+\t\tmode = PRUSS_ETHTYPE_PRP;\n+\telse\n+\t\treturn -EOPNOTSUPP;\n+\n+\tall_slaves = BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1);\n+\n+\tspin_lock_irqsave(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tif (!prueth-\u003ehsr_members) {\n+\t\tprueth-\u003ehsr_dev = hsr_ndev;\n+\t} else {\n+\t\t/* Adding the port to a second bridge is not supported */\n+\t\tif (prueth-\u003ehsr_dev != hsr_ndev) {\n+\t\t\tspin_unlock_irqrestore(\u0026prueth-\u003eaddr_lock, flags);\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n+\t}\n+\n+\tprueth-\u003ehsr_members |= BIT(emac-\u003eport_id);\n+\n+\tspin_unlock_irqrestore(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tif (!prueth_is_lre(prueth) \u0026\u0026 prueth-\u003ehsr_members == all_slaves) {\n+\t\tret = icssm_prueth_change_mode(prueth, mode);\n+\t\tif (ret \u003c 0) {\n+\t\t\tdev_err(prueth-\u003edev, \"Failed to enable %s mode\\n\",\n+\t\t\t\t(mode == PRUSS_ETHTYPE_HSR) ?\n+\t\t\t\t\"HSR\" : \"PRP\");\n+\t\t\tgoto free_hsr;\n+\t\t} else {\n+\t\t\tdev_info(prueth-\u003edev,\n+\t\t\t\t \"TI PRU ethernet now in %s mode\\n\",\n+\t\t\t\t (mode == PRUSS_ETHTYPE_HSR) ?\n+\t\t\t\t \"HSR\" : \"PRP\");\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+\n+free_hsr:\n+\tspin_lock_irqsave(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tprueth-\u003ehsr_dev = NULL;\n+\tprueth-\u003ehsr_members \u0026= ~all_slaves;\n+\n+\tspin_unlock_irqrestore(\u0026prueth-\u003eaddr_lock, flags);\n+\treturn ret;\n+}\n+\n+static int icssm_prueth_hsr_port_unlink(struct net_device *ndev)\n+{\n+\tstruct prueth_emac *emac = netdev_priv(ndev);\n+\tstruct prueth *prueth = emac-\u003eprueth;\n+\tunsigned long flags;\n+\tint ret = 0;\n+\n+\tspin_lock_irqsave(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tprueth-\u003ehsr_members \u0026= ~BIT(emac-\u003eport_id);\n+\n+\tspin_unlock_irqrestore(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tif (prueth_is_lre(prueth) \u0026\u0026 !prueth-\u003ehsr_members) {\n+\t\tret = icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_EMAC);\n+\t\tif (ret \u003c 0) {\n+\t\t\tdev_err(prueth-\u003edev, \"Failed to enable dual EMAC mode\\n\");\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tspin_lock_irqsave(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\tif (!prueth-\u003ehsr_members)\n+\t\tprueth-\u003ehsr_dev = NULL;\n+\n+\tspin_unlock_irqrestore(\u0026prueth-\u003eaddr_lock, flags);\n+\n+\treturn 0;\n+}\n+\n static int icssm_prueth_ndev_event(struct notifier_block *unused,\n \t\t\t\t unsigned long event, void *ptr)\n {\n@@ -1754,6 +2295,17 @@ static int icssm_prueth_ndev_event(struct notifier_block *unused,\n \tswitch (event) {\n \tcase NETDEV_CHANGEUPPER:\n \t\tinfo = ptr;\n+\t\tif (is_hsr_master(info-\u003eupper_dev)) {\n+\t\t\tif (info-\u003elinking) {\n+\t\t\t\tif (ndev-\u003efeatures \u0026\n+\t\t\t\t NETIF_PRUETH_LRE_OFFLOAD_FEATURES)\n+\t\t\t\t\tret = icssm_prueth_hsr_port_link\n+\t\t\t\t\t\t(ndev, info-\u003eupper_dev);\n+\t\t\t} else {\n+\t\t\t\tret = icssm_prueth_hsr_port_unlink(ndev);\n+\t\t\t}\n+\t\t}\n+\n \t\tif (netif_is_bridge_master(info-\u003eupper_dev)) {\n \t\t\tif (info-\u003elinking)\n \t\t\t\tret = icssm_prueth_ndev_port_link\n@@ -1796,6 +2348,7 @@ static int icssm_prueth_probe(struct platform_device *pdev)\n \tstruct device *dev = \u0026pdev-\u003edev;\n \tstruct device_node *np;\n \tstruct prueth *prueth;\n+\tbool has_lre = false;\n \tstruct pruss *pruss;\n \tint i, ret;\n \n@@ -1811,6 +2364,13 @@ static int icssm_prueth_probe(struct platform_device *pdev)\n \tprueth-\u003edev = dev;\n \tprueth-\u003efw_data = device_get_match_data(dev);\n \n+\tif (prueth-\u003efw_data-\u003efw_rev == FW_REV_V1_0)\n+\t\tprueth-\u003efw_offsets = fw_offsets_v1_0;\n+\telse if (prueth-\u003efw_data-\u003efw_rev == FW_REV_V2_1)\n+\t\tprueth-\u003efw_offsets = fw_offsets_v2_1;\n+\telse\n+\t\treturn -EINVAL;\n+\n \teth_ports_node = of_get_child_by_name(np, \"ethernet-ports\");\n \tif (!eth_ports_node)\n \t\treturn -ENOENT;\n@@ -1955,6 +2515,54 @@ static int icssm_prueth_probe(struct platform_device *pdev)\n \t\tprueth-\u003emem[PRUETH_MEM_OCMC].va,\n \t\tprueth-\u003emem[PRUETH_MEM_OCMC].size);\n \n+\tif (IS_ENABLED(CONFIG_HSR) \u0026\u0026 prueth-\u003efw_data-\u003esupport_lre)\n+\t\thas_lre = true;\n+\n+\t/* LRE requires both ethernet nodes to be present in\n+\t * DT, otherwise clear the support flag\n+\t */\n+\tif (has_lre \u0026\u0026 (!eth0_node || !eth1_node))\n+\t\thas_lre = false;\n+\n+\t/* Switch and LRE share HPQ/LPQ IRQs across both ports,\n+\t * allocate the shared priority structures once here\n+\t */\n+\tif (prueth-\u003efw_data-\u003esupport_switch || has_lre) {\n+\t\tprueth-\u003ehp = devm_kzalloc(dev,\n+\t\t\t\t\t sizeof(struct prueth_ndev_priority),\n+\t\t\t\t\t GFP_KERNEL);\n+\t\tif (!prueth-\u003ehp) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto free_pool;\n+\t\t}\n+\t\tprueth-\u003elp = devm_kzalloc(dev,\n+\t\t\t\t\t sizeof(struct prueth_ndev_priority),\n+\t\t\t\t\t GFP_KERNEL);\n+\t\tif (!prueth-\u003elp) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto free_pool;\n+\t\t}\n+\n+\t\tprueth-\u003erx_lpq_irq = of_irq_get_byname(np, \"rx_lp\");\n+\t\tif (prueth-\u003erx_lpq_irq \u003c 0) {\n+\t\t\tret = prueth-\u003erx_lpq_irq;\n+\t\t\tif (ret != -EPROBE_DEFER)\n+\t\t\t\tdev_err(prueth-\u003edev, \"could not get rx_lp irq\\n\");\n+\t\t\tgoto free_pool;\n+\t\t}\n+\t\tprueth-\u003erx_hpq_irq = of_irq_get_byname(np, \"rx_hp\");\n+\t\tif (prueth-\u003erx_hpq_irq \u003c 0) {\n+\t\t\tret = prueth-\u003erx_hpq_irq;\n+\t\t\tif (ret != -EPROBE_DEFER)\n+\t\t\t\tdev_err(prueth-\u003edev, \"could not get rx_hp irq\\n\");\n+\t\t\tgoto free_pool;\n+\t\t}\n+\t}\n+\n+\tprueth-\u003esupport_lre = has_lre;\n+\tspin_lock_init(\u0026prueth-\u003eaddr_lock);\n+\tspin_lock_init(\u0026prueth-\u003elre_host_queue_lock[0]);\n+\tspin_lock_init(\u0026prueth-\u003elre_host_queue_lock[1]);\n \t/* setup netdev interfaces */\n \tif (eth0_node) {\n \t\tret = icssm_prueth_netdev_init(prueth, eth0_node);\n@@ -2176,15 +2784,25 @@ static struct prueth_private_data am335x_prueth_pdata = {\n \t.fw_pru[PRUSS_PRU0] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am335x-pru0-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am335x-pru0-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am335x-pru0-pruprp-fw.elf\",\n \t\t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am335x-pru0-prusw-fw.elf\",\n \t},\n \t.fw_pru[PRUSS_PRU1] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am335x-pru1-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am335x-pru1-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am335x-pru1-pruprp-fw.elf\",\n \t\t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am335x-pru1-prusw-fw.elf\",\n \t},\n+\t.fw_rev = FW_REV_V1_0,\n+\t.support_lre = true,\n \t.support_switch = true,\n };\n \n@@ -2194,15 +2812,25 @@ static struct prueth_private_data am437x_prueth_pdata = {\n \t.fw_pru[PRUSS_PRU0] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am437x-pru0-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am437x-pru0-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am437x-pru0-pruprp-fw.elf\",\n \t\t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am437x-pru0-prusw-fw.elf\",\n \t},\n \t.fw_pru[PRUSS_PRU1] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am437x-pru1-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am437x-pru1-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am437x-pru1-pruprp-fw.elf\",\n \t\t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am437x-pru1-prusw-fw.elf\",\n \t},\n+\t.fw_rev = FW_REV_V1_0,\n+\t.support_lre = true,\n \t.support_switch = true,\n };\n \n@@ -2212,16 +2840,26 @@ static struct prueth_private_data am57xx_prueth_pdata = {\n \t.fw_pru[PRUSS_PRU0] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am57xx-pru0-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am57xx-pru0-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am57xx-pru0-pruprp-fw.elf\",\n \t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am57xx-pru0-prusw-fw.elf\",\n \t},\n \t.fw_pru[PRUSS_PRU1] = {\n \t\t.fw_name[PRUSS_ETHTYPE_EMAC] =\n \t\t\t\"ti-pruss/am57xx-pru1-prueth-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_HSR] =\n+\t\t\t\"ti-pruss/am57xx-pru1-pruhsr-fw.elf\",\n+\t\t.fw_name[PRUSS_ETHTYPE_PRP] =\n+\t\t\t\"ti-pruss/am57xx-pru1-pruprp-fw.elf\",\n \t\t.fw_name[PRUSS_ETHTYPE_SWITCH] =\n \t\t\t\"ti-pruss/am57xx-pru1-prusw-fw.elf\",\n \n \t},\n+\t.fw_rev = FW_REV_V2_1,\n+\t.support_lre = true,\n \t.support_switch = true,\n };\n \ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h\nindex d5b49b462c242..2447fb8b99e79 100644\n--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h\n@@ -16,13 +16,17 @@\n #include \"icssm_switch.h\"\n #include \"icssm_prueth_ptp.h\"\n #include \"icssm_prueth_fdb_tbl.h\"\n+#include \"icssm_lre_firmware.h\"\n \n /* ICSSM size of redundancy tag */\n #define ICSSM_LRE_TAG_SIZE\t6\n \n+#define PRUETH_TIMER_MS (10)\n+\n /* PRUSS local memory map */\n #define ICSS_LOCAL_SHARED_RAM\t0x00010000\n #define EMAC_MAX_PKTLEN\t\t(ETH_HLEN + VLAN_HLEN + ETH_DATA_LEN)\n+#define EMAC_MIN_PKTLEN\t\tETH_ZLEN\n /* Below macro is for 1528 Byte Frame support, to Allow even with\n * Redundancy tag\n */\n@@ -42,6 +46,8 @@ enum pruss_ethtype {\n \n #define PRUETH_IS_EMAC(p)\t((p)-\u003eeth_type == PRUSS_ETHTYPE_EMAC)\n #define PRUETH_IS_SWITCH(p)\t((p)-\u003eeth_type == PRUSS_ETHTYPE_SWITCH)\n+#define PRUETH_IS_HSR(p)\t((p)-\u003eeth_type == PRUSS_ETHTYPE_HSR)\n+#define PRUETH_IS_PRP(p)\t((p)-\u003eeth_type == PRUSS_ETHTYPE_PRP)\n \n /**\n * struct prueth_queue_desc - Queue descriptor\n@@ -86,8 +92,9 @@ struct prueth_queue_info {\n \n /**\n * struct prueth_packet_info - Info about a packet in buffer\n+ * @start_offset: true if frame carries an HSR/PRP start offset\n * @shadow: this packet is stored in the collision queue\n- * @port: port packet is on\n+ * @host_recv_flag: this frame should be received by host\n * @length: length of packet\n * @broadcast: this packet is a broadcast packet\n * @error: this packet has an error\n@@ -96,8 +103,9 @@ struct prueth_queue_info {\n * @timestamp: Specifies if timestamp is appended to the packet\n */\n struct prueth_packet_info {\n+\tbool start_offset;\n \tbool shadow;\n-\tunsigned int port;\n+\tbool host_recv_flag;\n \tunsigned int length;\n \tbool broadcast;\n \tbool error;\n@@ -171,6 +179,24 @@ enum prueth_mem {\n \tPRUETH_MEM_MAX,\n };\n \n+/* PRU firmware revision */\n+enum fw_revision {\n+\tFW_REV_INVALID = 0,\n+\tFW_REV_V1_0,\n+\tFW_REV_V2_1\n+};\n+\n+/* Firmware offsets/size information */\n+struct prueth_fw_offsets {\n+\tu32 mc_ctrl_offset;\n+\tu32 mc_filter_mask;\n+\tu32 mc_filter_tbl;\n+\t/* IEP wrap is used in the rx packet ordering logic and\n+\t * is different for ICSSM v1.0 vs 2.1\n+\t */\n+\tu32 iep_wrap;\n+};\n+\n enum pruss_device {\n \tPRUSS_AM57XX = 0,\n \tPRUSS_AM43XX,\n@@ -182,22 +208,26 @@ enum pruss_device {\n * struct prueth_private_data - PRU Ethernet private data\n * @driver_data: PRU Ethernet device name\n * @fw_pru: firmware names to be used for PRUSS ethernet usecases\n+ * @fw_rev: Firmware revision identifier\n * @support_switch: boolean to indicate if switch is enabled\n+ * @support_lre: boolean to indicate if LRE is enabled\n */\n struct prueth_private_data {\n \tenum pruss_device driver_data;\n \tconst struct prueth_firmware fw_pru[PRUSS_NUM_PRUS];\n+\tenum fw_revision fw_rev;\n \tbool support_switch;\n+\tbool support_lre;\n };\n \n struct prueth_emac_stats {\n-\tu64 tx_packets;\n-\tu64 tx_dropped;\n-\tu64 tx_bytes;\n-\tu64 rx_packets;\n-\tu64 rx_bytes;\n-\tu64 rx_length_errors;\n-\tu64 rx_over_errors;\n+\tatomic64_t tx_packets;\n+\tatomic64_t tx_dropped;\n+\tatomic64_t tx_bytes;\n+\tatomic64_t rx_packets;\n+\tatomic64_t rx_bytes;\n+\tatomic64_t rx_length_errors;\n+\tatomic64_t rx_over_errors;\n };\n \n /* data for each emac port */\n@@ -210,6 +240,8 @@ struct prueth_emac {\n \tstruct phy_device *phydev;\n \tstruct prueth_queue_desc __iomem *rx_queue_descs;\n \tstruct prueth_queue_desc __iomem *tx_queue_descs;\n+\t/* LRE duplicates each TX frame to both ports */\n+\tstruct prueth_queue_desc __iomem *tx_queue_descs_other_port;\n \n \tint link;\n \tint speed;\n@@ -233,11 +265,17 @@ struct prueth_emac {\n \tspinlock_t lock;\n \tspinlock_t addr_lock; /* serialize access to VLAN/MC filter table */\n \n+\tspinlock_t host_queue_lock[NUM_QUEUES / 2];\n \tstruct hrtimer tx_hrtimer;\n \tstruct prueth_emac_stats stats;\n \tint offload_fwd_mark;\n };\n \n+struct prueth_ndev_priority {\n+\tstruct net_device *ndev;\n+\tint priority;\n+};\n+\n struct prueth {\n \tstruct device *dev;\n \tstruct pruss *pruss;\n@@ -248,13 +286,26 @@ struct prueth {\n \tstruct icss_iep *iep;\n \n \tconst struct prueth_private_data *fw_data;\n-\tstruct prueth_fw_offsets *fw_offsets;\n+\tstruct prueth_fw_offsets fw_offsets;\n \n \tstruct device_node *eth_node[PRUETH_NUM_MACS];\n \tstruct prueth_emac *emac[PRUETH_NUM_MACS];\n \tstruct net_device *registered_netdevs[PRUETH_NUM_MACS];\n+\tstruct prueth_ndev_priority *hp, *lp;\n+\t/* NAPI for lp and hp queue scans */\n+\tstruct napi_struct napi_lpq;\n+\tstruct napi_struct napi_hpq;\n+\tint rx_lpq_irq;\n+\tint rx_hpq_irq;\n+\n+\tbool support_lre;\n+\tunsigned int tbl_check_mask;\n+\tstruct hrtimer tbl_check_timer;\n+\t/* serialize access to LRE VLAN/MC filter table */\n+\tspinlock_t addr_lock;\n \n \tstruct net_device *hw_bridge_dev;\n+\tstruct net_device *hsr_dev;\n \tstruct fdb_tbl *fdb_tbl;\n \n \tstruct notifier_block prueth_netdevice_nb;\n@@ -264,10 +315,15 @@ struct prueth {\n \tunsigned int eth_type;\n \tsize_t ocmc_ram_size;\n \tu8 emac_configured;\n+\tu8 hsr_members;\n \tu8 br_members;\n+\n+\t/* Per-queue TX lock - LRE uses only the two high-priority queues */\n+\tspinlock_t lre_host_queue_lock[NUM_QUEUES / 2];\n };\n \n extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES];\n+extern const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES];\n \n void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,\n \t\t\t struct prueth_packet_info *pkt_info);\n@@ -277,4 +333,15 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,\n void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash);\n void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash);\n u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask);\n+\n+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget);\n+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget);\n+\n+int icssm_prueth_common_request_irqs(struct prueth_emac *emac);\n+void icssm_prueth_common_free_irqs(struct prueth_emac *emac);\n+\n+static inline bool prueth_is_lre(struct prueth *prueth)\n+{\n+\treturn PRUETH_IS_HSR(prueth) || PRUETH_IS_PRP(prueth);\n+}\n #endif /* __NET_TI_PRUETH_H */\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c\nnew file mode 100644\nindex 0000000000000..a99cdbb79bbf0\n--- /dev/null\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c\n@@ -0,0 +1,278 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Texas Instruments ICSSM Ethernet Driver\n+ *\n+ * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/\n+ *\n+ */\n+\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/if_vlan.h\u003e\n+\n+#include \"icssm_prueth.h\"\n+#include \"icssm_prueth_switch.h\"\n+\n+static int icssm_prueth_common_emac_rx_packets(struct prueth_emac *emac,\n+\t\t\t\t\t int quota, u8 qid1, u8 qid2)\n+{\n+\tu16 bd_rd_ptr, bd_wr_ptr, update_rd_ptr, bd_rd_ptr_o, bd_wr_ptr_o;\n+\tconst struct prueth_queue_info *rxqueue, *rxqueue_o, *rxqueue_p;\n+\tstruct prueth_queue_desc __iomem *queue_desc, *queue_desc_o;\n+\tstruct prueth_packet_info pkt_info, pkt_info_o, *pkt_info_p;\n+\tu32 rd_buf_desc, rd_buf_desc_o, pkt_ts, pkt_ts_o, iep_wrap;\n+\tint ret, used = 0, port, port0_q_empty, port1_q_empty;\n+\tstruct prueth_emac *emac_p, *other_emac;\n+\tvoid __iomem *shared_ram, *ocmc_ram;\n+\tu8 overflow_cnt, overflow_cnt_o;\n+\tu16 *bd_rd_ptr_p, *bd_wr_ptr_p;\n+\tstruct prueth *prueth;\n+\n+\tprueth = emac-\u003eprueth;\n+\tocmc_ram = prueth-\u003emem[PRUETH_MEM_OCMC].va;\n+\tshared_ram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n+\tother_emac = prueth-\u003eemac[(emac-\u003eport_id == PRUETH_PORT_MII0) ?\n+\t\t\tPRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];\n+\n+\tiep_wrap = prueth-\u003efw_offsets.iep_wrap;\n+\t/* search host queues for packets */\n+\tqueue_desc = emac-\u003erx_queue_descs + qid1;\n+\tqueue_desc_o = other_emac-\u003erx_queue_descs + qid2;\n+\n+\trxqueue = \u0026sw_queue_infos[PRUETH_PORT_HOST][qid1];\n+\trxqueue_o = \u0026sw_queue_infos[PRUETH_PORT_HOST][qid2];\n+\n+\t/* skip Rx if budget is 0 */\n+\tif (!quota)\n+\t\treturn 0;\n+\n+\toverflow_cnt = readb(\u0026queue_desc-\u003eoverflow_cnt);\n+\toverflow_cnt_o = readb(\u0026queue_desc_o-\u003eoverflow_cnt);\n+\n+\tif (overflow_cnt \u003e 0) {\n+\t\tatomic64_add(overflow_cnt, \u0026emac-\u003estats.rx_over_errors);\n+\t\twriteb(0, \u0026queue_desc-\u003eoverflow_cnt);\n+\t}\n+\tif (overflow_cnt_o \u003e 0) {\n+\t\tatomic64_add(overflow_cnt_o, \u0026other_emac-\u003estats.rx_over_errors);\n+\t\twriteb(0, \u0026queue_desc_o-\u003eoverflow_cnt);\n+\t}\n+\n+\tbd_rd_ptr = readw(\u0026queue_desc-\u003erd_ptr);\n+\tbd_wr_ptr = readw(\u0026queue_desc-\u003ewr_ptr);\n+\n+\tbd_rd_ptr_o = readw(\u0026queue_desc_o-\u003erd_ptr);\n+\tbd_wr_ptr_o = readw(\u0026queue_desc_o-\u003ewr_ptr);\n+\n+\tport0_q_empty = (bd_rd_ptr == bd_wr_ptr);\n+\tport1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o);\n+\n+\twhile (!port0_q_empty || !port1_q_empty) {\n+\t\trd_buf_desc = readl(shared_ram + bd_rd_ptr);\n+\t\trd_buf_desc_o = readl(shared_ram + bd_rd_ptr_o);\n+\n+\t\ticssm_parse_packet_info(prueth, rd_buf_desc, \u0026pkt_info);\n+\t\ticssm_parse_packet_info(prueth, rd_buf_desc_o, \u0026pkt_info_o);\n+\n+\t\tpkt_ts = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +\n+\t\t\t bd_rd_ptr - SRAM_START_OFFSET);\n+\t\tpkt_ts_o = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +\n+\t\t\t\t bd_rd_ptr_o - SRAM_START_OFFSET);\n+\n+\t\tif (!port0_q_empty \u0026\u0026 !port1_q_empty) {\n+\t\t\t/* Both ports have a pending frame, pick the\n+\t\t\t * earlier one by comparing timestamps and\n+\t\t\t * account for wraparound.\n+\t\t\t */\n+\t\t\tif (pkt_ts \u003e pkt_ts_o)\n+\t\t\t\tport = (pkt_ts - pkt_ts_o) \u003e (iep_wrap / 2) ?\n+\t\t\t\t\t0 : 1;\n+\t\t\telse\n+\t\t\t\tport = (pkt_ts_o - pkt_ts) \u003e (iep_wrap / 2) ?\n+\t\t\t\t\t1 : 0;\n+\n+\t\t} else if (!port0_q_empty) {\n+\t\t\t/* Packet(s) in port0 queue only */\n+\t\t\tport = 0;\n+\t\t} else {\n+\t\t\t/* Packet(s) in port1 queue only */\n+\t\t\tport = 1;\n+\t\t}\n+\n+\t\t/* Select correct data structures for queue/packet selected */\n+\t\tif (port == 0) {\n+\t\t\tpkt_info_p = \u0026pkt_info;\n+\t\t\tbd_wr_ptr_p = \u0026bd_wr_ptr;\n+\t\t\tbd_rd_ptr_p = \u0026bd_rd_ptr;\n+\t\t\temac_p = emac;\n+\t\t\trxqueue_p = rxqueue;\n+\t\t} else {\n+\t\t\tpkt_info_p = \u0026pkt_info_o;\n+\t\t\tbd_wr_ptr_p = \u0026bd_wr_ptr_o;\n+\t\t\tbd_rd_ptr_p = \u0026bd_rd_ptr_o;\n+\t\t\temac_p = other_emac;\n+\t\t\trxqueue_p = rxqueue_o;\n+\t\t}\n+\n+\t\tif ((*pkt_info_p).length \u003c EMAC_MIN_PKTLEN) {\n+\t\t\t/* Undersized frame: firmware should have filtered\n+\t\t\t * these before they reach the host queue. Advance\n+\t\t\t * the read pointer to skip it.\n+\t\t\t */\n+\t\t\tupdate_rd_ptr = *bd_wr_ptr_p;\n+\t\t\tatomic64_inc(\u0026emac_p-\u003estats.rx_length_errors);\n+\t\t} else if ((*pkt_info_p).length \u003e EMAC_MAX_FRM_SUPPORT) {\n+\t\t\t/* Oversized frame: firmware should have filtered\n+\t\t\t * these before they reach the host queue. Advance\n+\t\t\t * the read pointer to skip it.\n+\t\t\t */\n+\t\t\tupdate_rd_ptr = *bd_wr_ptr_p;\n+\t\t\tatomic64_inc(\u0026emac_p-\u003estats.rx_length_errors);\n+\t\t} else {\n+\t\t\tupdate_rd_ptr = *bd_rd_ptr_p;\n+\t\t\tret = icssm_emac_rx_packet(emac_p, \u0026update_rd_ptr,\n+\t\t\t\t\t\t pkt_info_p, rxqueue_p);\n+\t\t\tif (ret)\n+\t\t\t\treturn used;\n+\n+\t\t\tused++;\n+\t\t}\n+\n+\t\t/* Leave the BD intact after reading. Firmware reuses it to\n+\t\t * forward the frame to the second LRE port.\n+\t\t */\n+\t\tif (port == 0) {\n+\t\t\twritew(update_rd_ptr, \u0026queue_desc-\u003erd_ptr);\n+\t\t\tbd_rd_ptr = update_rd_ptr;\n+\t\t} else {\n+\t\t\twritew(update_rd_ptr, \u0026queue_desc_o-\u003erd_ptr);\n+\t\t\tbd_rd_ptr_o = update_rd_ptr;\n+\t\t}\n+\n+\t\tport0_q_empty = (bd_rd_ptr == bd_wr_ptr) ? 1 : 0;\n+\t\tport1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o) ? 1 : 0;\n+\n+\t\tif (used \u003e= quota)\n+\t\t\treturn used;\n+\t}\n+\n+\treturn used;\n+}\n+\n+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget)\n+{\n+\tstruct prueth_emac *emac;\n+\tstruct net_device *ndev;\n+\tstruct prueth *prueth;\n+\tint num_rx_packets;\n+\tu8 qid1, qid2;\n+\n+\tprueth = container_of(napi, struct prueth, napi_lpq);\n+\tndev = prueth-\u003elp-\u003endev;\n+\temac = netdev_priv(ndev);\n+\tqid1 = PRUETH_QUEUE2;\n+\tqid2 = PRUETH_QUEUE4;\n+\n+\tnum_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,\n+\t\t\t\t\t\t\t qid1, qid2);\n+\tif (num_rx_packets \u003c budget \u0026\u0026 napi_complete_done(napi, num_rx_packets))\n+\t\tenable_irq(prueth-\u003erx_lpq_irq);\n+\n+\treturn num_rx_packets;\n+}\n+\n+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget)\n+{\n+\tstruct prueth_emac *emac;\n+\tstruct net_device *ndev;\n+\tstruct prueth *prueth;\n+\tint num_rx_packets;\n+\tu8 qid1, qid2;\n+\n+\tprueth = container_of(napi, struct prueth, napi_hpq);\n+\tndev = prueth-\u003ehp-\u003endev;\n+\temac = netdev_priv(ndev);\n+\tqid1 = PRUETH_QUEUE1;\n+\tqid2 = PRUETH_QUEUE3;\n+\n+\tnum_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,\n+\t\t\t\t\t\t\t qid1, qid2);\n+\tif (num_rx_packets \u003c budget \u0026\u0026 napi_complete_done(napi, num_rx_packets))\n+\t\tenable_irq(prueth-\u003erx_hpq_irq);\n+\n+\treturn num_rx_packets;\n+}\n+\n+static irqreturn_t icssm_prueth_common_emac_rx_hardirq(int irq, void *dev_id)\n+{\n+\tstruct prueth_ndev_priority *ndev_prio;\n+\tstruct prueth_emac *emac;\n+\tstruct net_device *ndev;\n+\tstruct prueth *prueth;\n+\n+\tndev_prio = (struct prueth_ndev_priority *)dev_id;\n+\tndev = ndev_prio-\u003endev;\n+\temac = netdev_priv(ndev);\n+\tprueth = emac-\u003eprueth;\n+\n+\t/* disable Rx system event */\n+\tif (ndev_prio-\u003epriority == 1) {\n+\t\tdisable_irq_nosync(prueth-\u003erx_lpq_irq);\n+\t\tnapi_schedule(\u0026prueth-\u003enapi_lpq);\n+\t} else {\n+\t\tdisable_irq_nosync(prueth-\u003erx_hpq_irq);\n+\t\tnapi_schedule(\u0026prueth-\u003enapi_hpq);\n+\t}\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+int icssm_prueth_common_request_irqs(struct prueth_emac *emac)\n+{\n+\tstruct prueth *prueth = emac-\u003eprueth;\n+\tint ret;\n+\n+\t/* Request irq when first port is initialized */\n+\tif (prueth-\u003eemac_configured)\n+\t\treturn 0;\n+\n+\tret = request_irq(prueth-\u003erx_hpq_irq,\n+\t\t\t icssm_prueth_common_emac_rx_hardirq,\n+\t\t\t IRQF_TRIGGER_HIGH, \"eth_hp_int\", prueth-\u003ehp);\n+\tif (ret) {\n+\t\tnetdev_err(emac-\u003endev, \"unable to request RX HPQ IRQ\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tret = request_irq(prueth-\u003erx_lpq_irq,\n+\t\t\t icssm_prueth_common_emac_rx_hardirq,\n+\t\t\t IRQF_TRIGGER_HIGH, \"eth_lp_int\", prueth-\u003elp);\n+\tif (ret) {\n+\t\tnetdev_err(emac-\u003endev, \"unable to request RX LPQ IRQ\\n\");\n+\t\tgoto free_rx_hpq_irq;\n+\t}\n+\n+\treturn 0;\n+\n+free_rx_hpq_irq:\n+\tfree_irq(prueth-\u003erx_hpq_irq, prueth-\u003ehp);\n+\n+\treturn ret;\n+}\n+\n+/**\n+ * icssm_prueth_common_free_irqs - free irq\n+ *\n+ * @emac: EMAC data structure\n+ *\n+ */\n+void icssm_prueth_common_free_irqs(struct prueth_emac *emac)\n+{\n+\tstruct prueth *prueth = emac-\u003eprueth;\n+\n+\t/* HSR/PRP: free irqs when last port is down */\n+\tif (prueth-\u003eemac_configured)\n+\t\treturn;\n+\n+\tfree_irq(prueth-\u003erx_lpq_irq, prueth-\u003elp);\n+\tfree_irq(prueth-\u003erx_hpq_irq, prueth-\u003ehp);\n+}\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c\nnew file mode 100644\nindex 0000000000000..239542101943e\n--- /dev/null\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c\n@@ -0,0 +1,224 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+/* Texas Instruments PRUETH hsr/prp Link Redundancy Entity (LRE) Driver.\n+ *\n+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com\n+ */\n+\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \"icssm_lre_firmware.h\"\n+#include \"icssm_prueth_lre.h\"\n+#include \"icssm_prueth.h\"\n+#include \"icssm_prueth_switch.h\"\n+\n+void icssm_prueth_lre_config_check_flags(struct prueth *prueth)\n+{\n+\tvoid __iomem *dram1 = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\t/* HSR/PRP: initialize check table when first port is up */\n+\tif (prueth-\u003eemac_configured)\n+\t\treturn;\n+\n+\tprueth-\u003etbl_check_mask = ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT;\n+\tif (PRUETH_IS_HSR(prueth))\n+\t\tprueth-\u003etbl_check_mask |=\n+\t\t\tICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS;\n+\twritel(prueth-\u003etbl_check_mask, dram1 + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);\n+}\n+\n+/* A group of PCPs are mapped to a Queue. This is the size of firmware\n+ * array in shared memory\n+ */\n+#define PCP_GROUP_TO_QUEUE_MAP_SIZE\t4\n+\n+/* PRU firmware default PCP to priority Queue map for ingress \u0026 egress\n+ *\n+ * At ingress to Host\n+ * ==================\n+ * byte 0 =\u003e PRU 1, PCP 0-3 =\u003e Q3\n+ * byte 1 =\u003e PRU 1, PCP 4-7 =\u003e Q2\n+ * byte 2 =\u003e PRU 0, PCP 0-3 =\u003e Q1\n+ * byte 3 =\u003e PRU 0, PCP 4-7 =\u003e Q0\n+ *\n+ * At egress to wire/network on PRU-0 and PRU-1\n+ * ============================================\n+ * byte 0 =\u003e Host, PCP 0-3 =\u003e Q3\n+ * byte 1 =\u003e Host, PCP 4-7 =\u003e Q2\n+ *\n+ * PRU-0\n+ * -----\n+ * byte 2 =\u003e PRU-1, PCP 0-3 =\u003e Q1\n+ * byte 3 =\u003e PRU-1, PCP 4-7 =\u003e Q0\n+ *\n+ * PRU-1\n+ * -----\n+ * byte 2 =\u003e PRU-0, PCP 0-3 =\u003e Q1\n+ * byte 3 =\u003e PRU-0, PCP 4-7 =\u003e Q0\n+ *\n+ * queue names below are named 1 based. i.e PRUETH_QUEUE1 is Q0,\n+ * PRUETH_QUEUE2 is Q1 and so forth. Firmware convention is that\n+ * a lower queue number has higher priority than a higher queue\n+ * number.\n+ */\n+static u8 fw_pcp_default_priority_queue_map[PCP_GROUP_TO_QUEUE_MAP_SIZE] = {\n+\t/* port 2 or PRU 1 */\n+\tPRUETH_QUEUE4, PRUETH_QUEUE3,\n+\t/* port 1 or PRU 0 */\n+\tPRUETH_QUEUE2, PRUETH_QUEUE1,\n+};\n+\n+static void icssm_prueth_lre_pcp_queue_map_config(struct prueth *prueth)\n+{\n+\tvoid __iomem *sram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n+\n+\tmemcpy_toio(sram + ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET,\n+\t\t \u0026fw_pcp_default_priority_queue_map[0],\n+\t\t PCP_GROUP_TO_QUEUE_MAP_SIZE);\n+}\n+\n+static void icssm_prueth_lre_host_table_init(struct prueth *prueth)\n+{\n+\tvoid __iomem *dram0 = prueth-\u003emem[PRUETH_MEM_DRAM0].va;\n+\tvoid __iomem *dram1 = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\tmemset_io(dram0 + ICSS_LRE_DUPLICATE_HOST_TABLE, 0,\n+\t\t ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE);\n+\n+\twritel(ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT,\n+\t dram1 + ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE);\n+\n+\twritel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,\n+\t dram1 + ICSS_LRE_DUPLI_HOST_CHECK_RESO);\n+\n+\twritel(ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR,\n+\t dram1 + ICSS_LRE_HOST_DUPLICATE_ARBITRATION);\n+}\n+\n+static void icssm_prueth_lre_port_table_init(struct prueth *prueth)\n+{\n+\tvoid __iomem *dram1 = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\tif (PRUETH_IS_HSR(prueth)) {\n+\t\tmemset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0, 0,\n+\t\t\t ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);\n+\t\tmemset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1, 0,\n+\t\t\t ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);\n+\n+\t\twritel(ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT,\n+\t\t dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);\n+\t} else {\n+\t\twritel(0, dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);\n+\t}\n+\n+\twritel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,\n+\t dram1 + ICSS_LRE_DUPLI_PORT_CHECK_RESO);\n+}\n+\n+static void icssm_prueth_lre_init(struct prueth *prueth)\n+{\n+\tvoid __iomem *sram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n+\n+\tmemset_io(sram + ICSS_LRE_START, 0, ICSS_LRE_STATS_DMEM_SIZE);\n+\n+\twritel(ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD,\n+\t sram + ICSS_LRE_DUPLICATE_DISCARD);\n+\twritel(ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT,\n+\t sram + ICSS_LRE_TRANSPARENT_RECEPTION);\n+}\n+\n+static void icssm_prueth_lre_dbg_init(struct prueth *prueth)\n+{\n+\tvoid __iomem *dram0 = prueth-\u003emem[PRUETH_MEM_DRAM0].va;\n+\n+\tmemset_io(dram0 + ICSS_LRE_DBG_START, 0,\n+\t\t ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE);\n+}\n+\n+static void icssm_prueth_lre_protocol_init(struct prueth *prueth)\n+{\n+\tvoid __iomem *dram0 = prueth-\u003emem[PRUETH_MEM_DRAM0].va;\n+\tvoid __iomem *dram1 = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\tif (PRUETH_IS_HSR(prueth))\n+\t\twritew(ICSS_LRE_MODEH, dram0 + ICSS_LRE_HSR_MODE_OFFSET);\n+\n+\twritel(ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS,\n+\t dram1 + ICSS_LRE_DUPLI_FORGET_TIME);\n+\twritel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH,\n+\t dram1 + ICSS_LRE_SUP_ADDR);\n+\twritel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW,\n+\t dram1 + ICSS_LRE_SUP_ADDR_LOW);\n+}\n+\n+static void icssm_prueth_lre_config_packet_timestamping(struct prueth *prueth)\n+{\n+\tvoid __iomem *sram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n+\n+\twriteb(1, sram + ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET);\n+\twriteb(1, sram + ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET);\n+}\n+\n+static enum hrtimer_restart icssm_prueth_lre_timer(struct hrtimer *timer)\n+{\n+\tstruct prueth *prueth;\n+\tunsigned int timeout;\n+\tvoid __iomem *dram;\n+\n+\tprueth = container_of(timer, struct prueth, tbl_check_timer);\n+\tdram = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\ttimeout = PRUETH_TIMER_MS;\n+\n+\thrtimer_forward_now(timer, ms_to_ktime(timeout));\n+\tif (prueth-\u003eemac_configured !=\n+\t (BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1)))\n+\t\treturn HRTIMER_RESTART;\n+\n+\t/* Set the flags for duplicate tables so the firmware checks and\n+\t * updates them every 10 milliseconds.\n+\t */\n+\twritel(prueth-\u003etbl_check_mask, dram + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);\n+\n+\treturn HRTIMER_RESTART;\n+}\n+\n+static void icssm_prueth_lre_init_timer(struct prueth *prueth)\n+{\n+\thrtimer_setup(\u0026prueth-\u003etbl_check_timer, \u0026icssm_prueth_lre_timer,\n+\t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL);\n+}\n+\n+static void icssm_prueth_lre_start_timer(struct prueth *prueth)\n+{\n+\tunsigned int timeout = PRUETH_TIMER_MS;\n+\n+\tif (hrtimer_active(\u0026prueth-\u003etbl_check_timer))\n+\t\treturn;\n+\n+\thrtimer_start(\u0026prueth-\u003etbl_check_timer, ms_to_ktime(timeout),\n+\t\t HRTIMER_MODE_REL);\n+}\n+\n+void icssm_prueth_lre_config(struct prueth *prueth)\n+{\n+\ticssm_prueth_lre_init_timer(prueth);\n+\ticssm_prueth_lre_start_timer(prueth);\n+\ticssm_prueth_lre_pcp_queue_map_config(prueth);\n+\ticssm_prueth_lre_host_table_init(prueth);\n+\ticssm_prueth_lre_port_table_init(prueth);\n+\ticssm_prueth_lre_init(prueth);\n+\ticssm_prueth_lre_dbg_init(prueth);\n+\ticssm_prueth_lre_protocol_init(prueth);\n+\t/* Enable per-packet timestamping so the driver can order\n+\t * received frames by arrival time across the two slave ports.\n+\t */\n+\ticssm_prueth_lre_config_packet_timestamping(prueth);\n+\n+}\n+\n+void icssm_prueth_lre_cleanup(struct prueth *prueth)\n+{\n+\tif (hrtimer_active(\u0026prueth-\u003etbl_check_timer))\n+\t\thrtimer_cancel(\u0026prueth-\u003etbl_check_timer);\n+}\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h\nnew file mode 100644\nindex 0000000000000..0fe4d1ae58234\n--- /dev/null\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h\n@@ -0,0 +1,19 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com\n+ */\n+\n+#ifndef __NET_TI_PRUETH_LRE_H\n+#define __NET_TI_PRUETH_LRE_H\n+\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/if_vlan.h\u003e\n+\n+#include \"icssm_prueth.h\"\n+#include \"icssm_lre_firmware.h\"\n+\n+void icssm_prueth_lre_config(struct prueth *prueth);\n+void icssm_prueth_lre_cleanup(struct prueth *prueth);\n+void icssm_prueth_lre_config_check_flags(struct prueth *prueth);\n+\n+#endif /* __NET_TI_PRUETH_LRE_H */\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c\nindex 66866ea37913f..bda2fac13d749 100644\n--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c\n@@ -199,6 +199,189 @@ static const struct prueth_queue_info rx_queue_infos[][NUM_QUEUES] = {\n \t},\n };\n \n+/* Tx Queue context for HSR and PRP */\n+const struct prueth_queue_info lre_queue_infos[][NUM_QUEUES] = {\n+\t[PRUETH_PORT_QUEUE_HOST] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q1_BUFFER_OFFSET,\n+\t\t\tP0_QUEUE_DESC_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q2_BUFFER_OFFSET,\n+\t\t\tP0_QUEUE_DESC_OFFSET + 8,\n+\t\t\tP0_Q2_BD_OFFSET,\n+\t\t\tP0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP0_Q3_BUFFER_OFFSET,\n+\t\t\tP0_QUEUE_DESC_OFFSET + 16,\n+\t\t\tP0_Q3_BD_OFFSET,\n+\t\t\tP0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP0_Q4_BUFFER_OFFSET,\n+\t\t\tP0_QUEUE_DESC_OFFSET + 24,\n+\t\t\tP0_Q4_BD_OFFSET,\n+\t\t\tP0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII0] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q3_BUFFER_OFFSET,\n+\t\t\tP0_Q3_BUFFER_OFFSET +\n+\t\t\t\t((HOST_QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP0_Q3_BD_OFFSET,\n+\t\t\tP0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q4_BUFFER_OFFSET,\n+\t\t\tP0_Q4_BUFFER_OFFSET +\n+\t\t\t\t((HOST_QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP0_Q4_BD_OFFSET,\n+\t\t\tP0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII1] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q1_BUFFER_OFFSET,\n+\t\t\tP0_Q1_BUFFER_OFFSET +\n+\t\t\t\t((HOST_QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP0_Q1_BD_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET +\n+\t\t\t\t((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q2_BUFFER_OFFSET,\n+\t\t\tP0_Q2_BUFFER_OFFSET +\n+\t\t\t\t((HOST_QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP0_Q2_BD_OFFSET,\n+\t\t\tP0_Q2_BD_OFFSET +\n+\t\t\t\t((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\n+\t},\n+};\n+\n+/* Rx Queue Context for HSR and PRP */\n+static const struct prueth_queue_info lre_rx_queue_infos[][NUM_QUEUES] = {\n+\t[PRUETH_PORT_QUEUE_HOST] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q1_BUFFER_OFFSET,\n+\t\t\tHOST_QUEUE_DESC_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q2_BUFFER_OFFSET,\n+\t\t\tHOST_QUEUE_DESC_OFFSET + 8,\n+\t\t\tP0_Q2_BD_OFFSET,\n+\t\t\tP0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP0_Q3_BUFFER_OFFSET,\n+\t\t\tHOST_QUEUE_DESC_OFFSET + 16,\n+\t\t\tP0_Q3_BD_OFFSET,\n+\t\t\tP0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP0_Q4_BUFFER_OFFSET,\n+\t\t\tHOST_QUEUE_DESC_OFFSET + 24,\n+\t\t\tP0_Q4_BD_OFFSET,\n+\t\t\tP0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII0] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q3_BUFFER_OFFSET,\n+\t\t\tP1_QUEUE_DESC_OFFSET,\n+\t\t\tP0_Q3_BD_OFFSET,\n+\t\t\tP0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q4_BUFFER_OFFSET,\n+\t\t\tP1_QUEUE_DESC_OFFSET + 8,\n+\t\t\tP0_Q4_BD_OFFSET,\n+\t\t\tP0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET,\n+\t\t\tP1_QUEUE_DESC_OFFSET + 16,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET,\n+\t\t\tP1_QUEUE_DESC_OFFSET + 24,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t},\n+\t[PRUETH_PORT_QUEUE_MII1] = {\n+\t\t[PRUETH_QUEUE1] = {\n+\t\t\tP0_Q1_BUFFER_OFFSET,\n+\t\t\tP2_QUEUE_DESC_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET,\n+\t\t\tP0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE2] = {\n+\t\t\tP0_Q2_BUFFER_OFFSET,\n+\t\t\tP2_QUEUE_DESC_OFFSET + 8,\n+\t\t\tP0_Q2_BD_OFFSET,\n+\t\t\tP0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE3] = {\n+\t\t\tP1_Q3_TXOPT_BUFFER_OFFSET,\n+\t\t\tP2_QUEUE_DESC_OFFSET + 16,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET,\n+\t\t\tP1_Q3_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t\t[PRUETH_QUEUE4] = {\n+\t\t\tP2_Q1_TXOPT_BUFFER_OFFSET,\n+\t\t\tP2_QUEUE_DESC_OFFSET + 24,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET,\n+\t\t\tP2_Q1_TXOPT_BD_OFFSET +\n+\t\t\t\t((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),\n+\t\t},\n+\t},\n+};\n+\n void icssm_prueth_sw_free_fdb_table(struct prueth *prueth)\n {\n \tif (prueth-\u003eemac_configured)\n@@ -856,8 +1039,12 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)\n \n \t/* queue information table */\n \tdram = dram1_base + P0_Q1_RX_CONTEXT_OFFSET;\n-\tmemcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],\n-\t\t sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));\n+\tif (prueth_is_lre(prueth))\n+\t\tmemcpy_toio(dram, lre_queue_infos[PRUETH_PORT_QUEUE_HOST],\n+\t\t\t sizeof(lre_queue_infos[PRUETH_PORT_QUEUE_HOST]));\n+\telse\n+\t\tmemcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],\n+\t\t\t sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));\n \n \t/* buffer descriptor offset table*/\n \tdram = dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR;\n@@ -882,8 +1069,23 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)\n \n \t/* queue table */\n \tdram = dram1_base + P0_QUEUE_DESC_OFFSET;\n-\tmemcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],\n-\t\t sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));\n+\tif (prueth_is_lre(prueth))\n+\t\tmemcpy_toio(dram,\n+\t\t\t hsr_prp_txopt_queue_descs[PRUETH_PORT_QUEUE_HOST],\n+\t\t\t sizeof(hsr_prp_txopt_queue_descs\n+\t\t\t\t [PRUETH_PORT_QUEUE_HOST]));\n+\telse\n+\t\tmemcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],\n+\t\t\t sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));\n+\n+}\n+\n+void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth)\n+{\n+\tvoid __iomem *sram = prueth-\u003emem[PRUETH_MEM_SHARED_RAM].va;\n+\n+\twriteb(1, sram + SWITCH_PRIORITY_INTRS_STATUS_OFFSET);\n+\twriteb(1, sram + SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET);\n }\n \n static int icssm_prueth_sw_port_config(struct prueth *prueth,\n@@ -975,6 +1177,109 @@ static int icssm_prueth_sw_port_config(struct prueth *prueth,\n \treturn 0;\n }\n \n+/* Configure TX/RX queue contexts and buffer descriptor tables for LRE port */\n+static int icssm_prueth_lre_port_config(struct prueth *prueth,\n+\t\t\t\t\tenum prueth_port port_id)\n+{\n+\tunsigned int tx_context_ofs_addr, rx_context_ofs, queue_desc_ofs;\n+\tvoid __iomem *dram, *dram_base, *dram_mac;\n+\tstruct prueth_emac *emac;\n+\n+\temac = prueth-\u003eemac[port_id - 1];\n+\tswitch (port_id) {\n+\tcase PRUETH_PORT_MII0:\n+\t\ttx_context_ofs_addr = TX_CONTEXT_P1_Q1_OFFSET_ADDR;\n+\t\trx_context_ofs = P1_Q1_RX_CONTEXT_OFFSET;\n+\t\tqueue_desc_ofs = P1_QUEUE_DESC_OFFSET;\n+\t\t/* for switch PORT MII0 mac addr is in DRAM0. */\n+\t\tdram_mac = prueth-\u003emem[PRUETH_MEM_DRAM0].va;\n+\t\tbreak;\n+\tcase PRUETH_PORT_MII1:\n+\t\ttx_context_ofs_addr = TX_CONTEXT_P2_Q1_OFFSET_ADDR;\n+\t\trx_context_ofs = P2_Q1_RX_CONTEXT_OFFSET;\n+\t\tqueue_desc_ofs = P2_QUEUE_DESC_OFFSET;\n+\n+\t\t/* for switch PORT MII1 mac addr is in DRAM1. */\n+\t\tdram_mac = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\t\tbreak;\n+\tdefault:\n+\t\tnetdev_err(emac-\u003endev, \"invalid port\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* setup mac address */\n+\tmemcpy_toio(dram_mac + PORT_MAC_ADDR, emac-\u003emac_addr, ETH_ALEN);\n+\n+\t/* Remaining switch port configs are in DRAM1 */\n+\tdram_base = prueth-\u003emem[PRUETH_MEM_DRAM1].va;\n+\n+\t/* queue information table */\n+\tmemcpy_toio(dram_base + tx_context_ofs_addr,\n+\t\t lre_queue_infos[port_id],\n+\t\t sizeof(lre_queue_infos[port_id]));\n+\n+\tmemcpy_toio(dram_base + rx_context_ofs,\n+\t\t lre_rx_queue_infos[port_id],\n+\t\t sizeof(lre_rx_queue_infos[port_id]));\n+\n+\t/* buffer descriptor offset table*/\n+\tdram = dram_base + QUEUE_DESCRIPTOR_OFFSET_ADDR +\n+\t\t(port_id * NUM_QUEUES * sizeof(u16));\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_desc_offset,\n+\t dram);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_desc_offset,\n+\t dram + 2);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_desc_offset,\n+\t dram + 4);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_desc_offset,\n+\t dram + 6);\n+\n+\t/* buffer offset table */\n+\tdram = dram_base + QUEUE_OFFSET_ADDR +\n+\t\tport_id * NUM_QUEUES * sizeof(u16);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_offset, dram);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_offset,\n+\t dram + 2);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_offset,\n+\t dram + 4);\n+\twritew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_offset,\n+\t dram + 6);\n+\n+\t/* queue size lookup table */\n+\tdram = dram_base + QUEUE_SIZE_ADDR +\n+\t\tport_id * NUM_QUEUES * sizeof(u16);\n+\twritew(HOST_QUEUE_1_SIZE, dram);\n+\twritew(HOST_QUEUE_2_SIZE, dram + 2);\n+\twritew(QUEUE_3_TXOPT_SIZE, dram + 4);\n+\twritew(QUEUE_4_TXOPT_SIZE, dram + 6);\n+\n+\t/* queue table */\n+\tmemcpy_toio(dram_base + queue_desc_ofs,\n+\t\t \u0026hsr_prp_txopt_queue_descs[port_id][0],\n+\t\t 4 * sizeof(hsr_prp_txopt_queue_descs[port_id][0]));\n+\n+\t/* In HSR/PRP mode both slave ports share the host receive queue\n+\t * descriptor region (P0_QUEUE_DESC_OFFSET). The firmware arbitrates\n+\t * ownership; the driver always reads from the same host-side descriptor\n+\t * base regardless of which physical port the frame arrived on.\n+\t */\n+\temac-\u003erx_queue_descs = dram_base + P0_QUEUE_DESC_OFFSET;\n+\temac-\u003etx_queue_descs = dram_base +\n+\t\tlre_rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset;\n+\n+\tif (port_id == PRUETH_PORT_MII0) {\n+\t\temac-\u003etx_queue_descs_other_port = dram_base +\n+\t\t\tlre_rx_queue_infos\n+\t\t\t[port_id + 1][PRUETH_QUEUE1].queue_desc_offset;\n+\t} else if (port_id == PRUETH_PORT_MII1) {\n+\t\temac-\u003etx_queue_descs_other_port = dram_base +\n+\t\t\tlre_rx_queue_infos\n+\t\t\t[port_id - 1][PRUETH_QUEUE1].queue_desc_offset;\n+\t}\n+\n+\treturn 0;\n+}\n+\n int icssm_prueth_sw_emac_config(struct prueth_emac *emac)\n {\n \tstruct prueth *prueth = emac-\u003eprueth;\n@@ -989,7 +1294,10 @@ int icssm_prueth_sw_emac_config(struct prueth_emac *emac)\n \tif (prueth-\u003eemac_configured \u0026 BIT(emac-\u003eport_id))\n \t\treturn 0;\n \n-\tret = icssm_prueth_sw_port_config(prueth, emac-\u003eport_id);\n+\tif (prueth_is_lre(prueth))\n+\t\tret = icssm_prueth_lre_port_config(prueth, emac-\u003eport_id);\n+\telse\n+\t\tret = icssm_prueth_sw_port_config(prueth, emac-\u003eport_id);\n \tif (ret)\n \t\treturn ret;\n \ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h\nindex e6111bba166e3..a009827b01d79 100644\n--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h\n@@ -17,6 +17,7 @@ u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth,\n \t\t\t\t enum prueth_port port);\n \n extern const struct prueth_queue_info sw_queue_infos[][4];\n+extern const struct prueth_queue_info lre_queue_infos[][4];\n \n void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth);\n int icssm_prueth_sw_init_fdb_table(struct prueth *prueth);\n@@ -29,6 +30,7 @@ void icssm_prueth_sw_fdb_del(struct prueth_emac *emac,\n int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac);\n int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac);\n void icssm_prueth_sw_hostconfig(struct prueth *prueth);\n+void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth);\n int icssm_prueth_sw_emac_config(struct prueth_emac *emac);\n int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev);\n int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac,\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h\nindex 5ba9ce14da445..b0d623b4979b0 100644\n--- a/drivers/net/ethernet/ti/icssm/icssm_switch.h\n+++ b/drivers/net/ethernet/ti/icssm/icssm_switch.h\n@@ -24,6 +24,9 @@\n #define QUEUE_3_SIZE\t\t97\t/* Protocol specific */\n #define QUEUE_4_SIZE\t\t97\t/* NRT (IP,ARP, ICMP) */\n \n+#define QUEUE_3_TXOPT_SIZE\t194\t/* Protocol specific - High Priority */\n+#define QUEUE_4_TXOPT_SIZE\t194\t/* NRT(IP,ARP, ICMP) - Low Priority*/\n+\n /* Host queue size (number of BDs). Each BD points to data buffer of 32 bytes.\n * HOST PORT QUEUES can buffer up to 4 full sized frames per queue\n */\n@@ -49,20 +52,18 @@\n *\t\t\t\tFor RED, NodeTable lookup was successful.\n * 7\t\tFlood\t\tPacket should be flooded (destination MAC\n *\t\t\t\taddress found in FDB). For switch only.\n- * 8..12\tBlock_length\tnumber of valid bytes in this specific block.\n- *\t\t\t\tWill be \u003c=32 bytes on last block of packet\n+ * 8\t\tRED_INFO\tSet if the frame carries an HSR or PRP\n+ *\t\t\t\tredundancy tag\n+ * 10\t\tHostRecv\tSet if the frame is destined for the host port\n * 13\t\tMore\t\t\"More\" bit indicating that there are more blocks\n * 14\t\tShadow\t\tindicates that \"index\" is pointing into shadow\n *\t\t\t\tbuffer\n * 15\t\tTimeStamp\tindicates that this packet has time stamp in\n *\t\t\t\tseparate buffer - only needed if PTP runs on\n *\t\t\t\thost\n- * 16..17\tPort\t\tdifferent meaning for ingress and egress,\n- *\t\t\t\tIngress: Port = 0 indicates phy port 1 and\n- *\t\t\t\tPort = 1 indicates phy port 2.\n- *\t\t\t\tEgress: 0 sends on phy port 1 and 1 sends on\n- *\t\t\t\tphy port 2. Port = 2 goes over MAC table\n- *\t\t\t\tlook-up\n+ * 16..17\tLAN\t\tDestination LAN for transmission:\n+ *\t\t\t\tbit 16 = LAN A, bit 17 = LAN B, set both to\n+ *\t\t\t\tduplicate to both LANs.\n * 18..28\tLength\t\t11 bit of total packet length which is put into\n *\t\t\t\tfirst BD only so that host access only one BD\n * 29\t\tVlanTag\t\tindicates that packet has Length/Type field of\n@@ -86,14 +87,21 @@\n #define PRUETH_BD_SW_FLOOD_MASK\t\tBIT(7)\n #define PRUETH_BD_SW_FLOOD_SHIFT\t7\n \n+#define PRUETH_BD_RED_PKT_MASK\t\tBIT(8)\n+#define PRUETH_BD_RED_PKT\t\t8\n+\n+#define PRUETH_BD_HOST_RECV_MASK\tBIT(10)\n+#define PRUETH_BD_HOST_RECV_SHIFT\t10\n+\n #define\tPRUETH_BD_SHADOW_MASK\t\tBIT(14)\n #define\tPRUETH_BD_SHADOW_SHIFT\t\t14\n \n #define PRUETH_BD_TIMESTAMP_MASK\tBIT(15)\n #define PRUETH_BD_TIMESTAMP_SHIFT\t15\n \n-#define PRUETH_BD_PORT_MASK\t\tGENMASK(17, 16)\n-#define PRUETH_BD_PORT_SHIFT\t\t16\n+#define PRUETH_BD_LAN_INFO_MASK\t\tGENMASK(17, 16)\n+#define PRUETH_BD_LAN_A_SHIFT\t\t16\n+#define PRUETH_BD_LAN_B_SHIFT\t\t17\n \n #define PRUETH_BD_LENGTH_MASK\t\tGENMASK(28, 18)\n #define PRUETH_BD_LENGTH_SHIFT\t\t18\n@@ -298,6 +306,9 @@\n #define P0_Q4_BD_OFFSET\t\t(P0_Q3_BD_OFFSET + HOST_QUEUE_3_SIZE * BD_SIZE)\n #define P0_Q3_BD_OFFSET\t\t(P0_Q2_BD_OFFSET + HOST_QUEUE_2_SIZE * BD_SIZE)\n #define P0_Q2_BD_OFFSET\t\t(P0_Q1_BD_OFFSET + HOST_QUEUE_1_SIZE * BD_SIZE)\n+#define P1_Q3_TXOPT_BD_OFFSET\t(P0_Q4_BD_OFFSET + HOST_QUEUE_4_SIZE * BD_SIZE)\n+#define P2_Q1_TXOPT_BD_OFFSET\t(P1_Q3_TXOPT_BD_OFFSET +\t\\\n+\t\t\t\t QUEUE_3_TXOPT_SIZE * BD_SIZE)\n #define P0_Q1_BD_OFFSET\t\tP0_BUFFER_DESC_OFFSET\n #define P0_BUFFER_DESC_OFFSET\tSRAM_START_OFFSET\n \n@@ -328,9 +339,19 @@\n \t\t\t\t ICSS_BLOCK_SIZE)\n #define P0_Q2_BUFFER_OFFSET\t(P0_Q1_BUFFER_OFFSET + HOST_QUEUE_1_SIZE * \\\n \t\t\t\t ICSS_BLOCK_SIZE)\n+#define P1_Q3_TXOPT_BUFFER_OFFSET\t(P0_Q4_BUFFER_OFFSET +\t\\\n+\t\t\t\t\t HOST_QUEUE_4_SIZE * ICSS_BLOCK_SIZE)\n+#define P2_Q1_TXOPT_BUFFER_OFFSET\t(P1_Q3_TXOPT_BUFFER_OFFSET +\t\\\n+\t\t\t\t\t QUEUE_3_TXOPT_SIZE * ICSS_BLOCK_SIZE)\n #define P0_COL_BUFFER_OFFSET\t0xEE00\n #define P0_Q1_BUFFER_OFFSET\t0x0000\n \n+/* Enable/disable interrupts for high/low priority instead of per port.\n+ * 0 = disabled (default), 1 = enabled\n+ */\n+#define SWITCH_PRIORITY_INTRS_STATUS_OFFSET\t0x1FAA\n+#define SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET\t0x1FAB\n+\n #define V2_1_FDB_TBL_LOC PRUETH_MEM_SHARED_RAM\n #define V2_1_FDB_TBL_OFFSET 0x2000\n \n"
} |
| 2/1 | 2026/08/04 18:09 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds HSR/PRP support to the TI PRU-ICSS Ethernet driver (icssm_prueth). This is a vendor-specific SoC Ethernet driver that requires physical TI hardware (PRU-ICSS) and specific remoteproc firmware. It cannot be reached or tested in standard virtualized environments like QEMU or GCE.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit e3354ab3789a48df367d24fc1bcb2dc79c466dc8
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 4 18:09:26 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
index f4276c9a77620..b6651fe73afd8 100644
--- a/drivers/net/ethernet/ti/Makefile
+++ b/drivers/net/ethernet/ti/Makefile
@@ -4,7 +4,7 @@
#
obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o
-icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o
+icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o icssm/icssm_prueth_lre.o icssm/icssm_prueth_common.o
ti-cpsw-common-y += cpsw-common.o davinci_cpdma.o
ti-cpsw-priv-y += cpsw_priv.o cpsw_ethtool.o
diff --git a/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
new file mode 100644
index 0000000000000..b5ab0ec87c5f7
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2017-2020 Texas Instruments Incorporated - http://www.ti.com */
+#ifndef __ICSS_LRE_FIRMWARE_H
+#define __ICSS_LRE_FIRMWARE_H
+
+#define ICSS_LRE_HSR_MODE_OFFSET 0x1E76
+#define ICSS_LRE_MODEH 0x01
+
+/* PRU0 DMEM */
+#define ICSS_LRE_DBG_START 0x1E00
+
+#define ICSS_LRE_DUPLICATE_HOST_TABLE 0x0200
+
+/* PRU1 DMEM */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0 0x0200
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1 0x0E00
+
+/* Size and setup (N and M) of duplicate host table */
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE 0x1C08
+/* Size and setup (N and M) of duplicate port table (HSR Only) */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE 0x1C1C
+/* Time after which an entry is removed from the duplicate
+ * table (10 ms resolution)
+ */
+#define ICSS_LRE_DUPLI_FORGET_TIME 0x1C24
+/* Time interval to check the port duplicate table */
+#define ICSS_LRE_DUPLI_PORT_CHECK_RESO 0x1C2C
+/* Time interval to check the host duplicate table */
+#define ICSS_LRE_DUPLI_HOST_CHECK_RESO 0x1C30
+/* NodeTable | Host | Port */
+#define ICSS_LRE_HOST_TIMER_CHECK_FLAGS 0x1C38
+/* Arbitration flag for the host duplicate table */
+#define ICSS_LRE_HOST_DUPLICATE_ARBITRATION 0x1C3C
+/* Supervision address in LRE */
+#define ICSS_LRE_SUP_ADDR 0x1C4C
+#define ICSS_LRE_SUP_ADDR_LOW 0x1C50
+
+/* Time in TimeTicks (1/100s) */
+#define ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS 40
+#define ICSS_LRE_NODE_FORGET_TIME_60000_MS 6000
+#define ICSS_LRE_MAX_FORGET_TIME 0xFFDF
+
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE 0x0C00
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE 0x1800
+#define ICSS_LRE_STATS_DMEM_SIZE 0x0080
+#define ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE 0x0050
+
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT 0x800004 /* N = 128, M = 4 */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT 0x400004 /* N = 64, M = 4 */
+#define ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR 0x0
+#define ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS 0xA
+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH 0x4E1501
+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW 0x1
+
+/* SHARED RAM */
+
+/* 8 bytes of VLAN PCP to RX QUEUE MAPPING */
+#define ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET 0x124
+#define ICSS_LRE_START 0x140
+
+/* Count of HSR/PRP tagged frames successfully transmitted on port A/B */
+#define ICSS_LRE_CNT_TX_A (ICSS_LRE_START + 4)
+#define ICSS_LRE_DUPLICATE_DISCARD (ICSS_LRE_START + 104)
+#define ICSS_LRE_TRANSPARENT_RECEPTION (ICSS_LRE_START + 108)
+#define ICSS_LRE_CNT_NODES (ICSS_LRE_START + 52)
+
+/* SRAM */
+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_ACCEPT 0x01
+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD 0x02
+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT 0x01
+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_PASS_RCT 0x02
+
+/* Enable/disable interrupts for high/low priority instead of per port.
+ * 0 = disabled (default), 1 = enabled
+ */
+#define ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET 0x1FAA
+/* Enable/disable timestamping of packets. 0 = disabled (default) 1 = enabled */
+#define ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET 0x1FAB
+#define ICSS_LRE_TIMESTAMP_ARRAY_OFFSET 0xC200
+
+/* HOST_TIMER_CHECK_FLAGS bits */
+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CHECK_BIT BIT(0)
+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CLEAR_BIT BIT(4)
+#define ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT BIT(8)
+#define ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT BIT(16)
+#define ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT BIT(24)
+#define ICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS \
+ (ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT | \
+ ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT)
+
+/* PRU1 DMEM */
+/* Node table offsets are different for AM3/4 vs AM57/K2G, set by firmware */
+#define ICSS_LRE_V1_0_HASH_MASK 0x3F
+#define ICSS_LRE_V1_0_INDEX_ARRAY_NT 0x60
+#define ICSS_LRE_V1_0_BIN_ARRAY 0x1A00
+#define ICSS_LRE_V1_0_NODE_TABLE_NEW 0x1FC0
+#define ICSS_LRE_V1_0_INDEX_ARRAY_LOC PRUETH_MEM_DRAM0
+#define ICSS_LRE_V1_0_BIN_ARRAY_LOC PRUETH_MEM_DRAM0
+#define ICSS_LRE_V1_0_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V1_0_INDEX_TBL_MAX_ENTRIES 64
+#define ICSS_LRE_V1_0_BIN_TBL_MAX_ENTRIES 128
+#define ICSS_LRE_V1_0_NODE_TBL_MAX_ENTRIES 128
+
+#define ICSS_LRE_V2_1_HASH_MASK 0xFF
+#define ICSS_LRE_V2_1_INDEX_ARRAY_NT 0x3000
+#define ICSS_LRE_V2_1_BIN_ARRAY \
+ (ICSS_LRE_V2_1_INDEX_ARRAY_NT + \
+ (ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES * 6))
+#define ICSS_LRE_V2_1_NODE_TABLE_NEW \
+ (ICSS_LRE_V2_1_BIN_ARRAY + \
+ (ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES * 8))
+#define ICSS_LRE_V2_1_INDEX_ARRAY_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_BIN_ARRAY_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES 256
+#define ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES 256
+#define ICSS_LRE_V2_1_NODE_TBL_MAX_ENTRIES 256
+
+#define ICSS_LRE_NODE_FREE 0x10
+#define ICSS_LRE_NODE_TAKEN 0x01
+#define ICSS_LRE_NT_REM_NODE_TYPE_MASK 0x1F
+#define ICSS_LRE_NT_REM_NODE_TYPE_SHIFT 0x00
+
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANA 0x01
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANB 0x02
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANAB 0x03
+#define ICSS_LRE_NT_REM_NODE_TYPE_DAN 0x04
+#define ICSS_LRE_NT_REM_NODE_TYPE_REDBOX 0x08
+#define ICSS_LRE_NT_REM_NODE_TYPE_VDAN 0x10
+
+#define ICSS_LRE_NT_REM_NODE_HSR_BIT 0x20 /* if set node is HSR */
+
+#define ICSS_LRE_NT_REM_NODE_DUP_MASK 0xC0
+#define ICSS_LRE_NT_REM_NODE_DUP_SHIFT 0x06
+
+/* Node entry duplicate type: DupAccept */
+#define ICSS_LRE_NT_REM_NODE_DUP_ACCEPT 0x40
+/* Node entry duplicate type: DupDiscard */
+#define ICSS_LRE_NT_REM_NODE_DUP_DISCARD 0x80
+
+#endif /* __ICSS_LRE_FIRMWARE_H */
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
index b7e94244355a3..43862946d4f14 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
@@ -30,16 +30,83 @@
#include "icssm_prueth.h"
#include "icssm_prueth_switch.h"
+#include "icssm_prueth_lre.h"
#include "icssm_vlan_mcast_filter_mmap.h"
#include "../icssg/icssg_mii_rt.h"
#include "../icssg/icss_iep.h"
#define OCMC_RAM_SIZE (SZ_64K)
+#define PRUETH_ETHER_TYPE_OFFSET 12
#define TX_START_DELAY 0x40
#define TX_CLK_DELAY_100M 0x6
#define HR_TIMER_TX_DELAY_US 100
+#define NETIF_PRUETH_LRE_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \
+ NETIF_F_HW_HSR_DUP | \
+ NETIF_F_HW_HSR_TAG_RM)
+
+/* ICSSM (v2.1) - supports 64-bit IEP counter.
+ * Firmware stores packet timestamps using lower 32 bits
+ * which wraps at 0xffffffff.
+ */
+static const struct prueth_fw_offsets fw_offsets_v2_1 = {
+ .iep_wrap = 0xffffffff,
+};
+
+/* ICSSM (v1.0) - supports 32-bit IEP counter, which resets the
+ * counter every one second (nanosecond resolution).
+ */
+static const struct prueth_fw_offsets fw_offsets_v1_0 = {
+ .iep_wrap = NSEC_PER_SEC,
+};
+
+static void icssm_prueth_set_fw_offsets(struct prueth *prueth)
+{
+ /* Set Multicast filter control and table offsets */
+ if (PRUETH_IS_EMAC(prueth) || PRUETH_IS_SWITCH(prueth)) {
+ prueth->fw_offsets.mc_ctrl_offset =
+ ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;
+ prueth->fw_offsets.mc_filter_mask =
+ ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;
+ prueth->fw_offsets.mc_filter_tbl =
+ ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ } else {
+ prueth->fw_offsets.mc_ctrl_offset =
+ ICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT;
+ prueth->fw_offsets.mc_filter_mask =
+ ICSS_LRE_FW_MULTICAST_FILTER_MASK;
+ prueth->fw_offsets.mc_filter_tbl =
+ ICSS_LRE_FW_MULTICAST_FILTER_TABLE;
+ }
+}
+
+/* Queue Descriptors initialization for HSR PRP */
+const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ { .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },
+ { .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },
+ { .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },
+ { .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ { .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },
+ { .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },
+ { .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,
+ .wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },
+ { .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,
+ .wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ { .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },
+ { .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },
+ { .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,
+ .wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },
+ { .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,
+ .wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },
+ }
+};
+
static void icssm_prueth_write_reg(struct prueth *prueth,
enum prueth_mem region,
unsigned int reg, u32 val)
@@ -58,6 +125,17 @@ static void icssm_prueth_write_reg(struct prueth *prueth,
static enum pruss_mem pruss_mem_ids[] = { PRUSS_MEM_DRAM0, PRUSS_MEM_DRAM1,
PRUSS_MEM_SHRD_RAM2 };
+struct prp_txopt_rct {
+ __be16 sequence_nr;
+ __be16 lan_id_and_lsdu_size;
+ __be16 prp_suffix;
+};
+
+struct hsr_txopt_ethhdr {
+ struct ethhdr ethhdr;
+ struct hsr_tag hsr_tag;
+};
+
static const struct prueth_queue_info queue_infos[][NUM_QUEUES] = {
[PRUETH_PORT_QUEUE_HOST] = {
[PRUETH_QUEUE1] = {
@@ -309,12 +387,17 @@ static void icssm_prueth_hostinit(struct prueth *prueth)
icssm_prueth_mii_init(prueth);
}
-/* This function initialize the driver in EMAC mode
+/* Initialize the driver in EMAC, HSR or PRP mode
* based on eth_type
*/
static void icssm_prueth_init_ethernet_mode(struct prueth *prueth)
{
+ icssm_prueth_set_fw_offsets(prueth);
icssm_prueth_hostinit(prueth);
+ if (prueth_is_lre(prueth))
+ icssm_prueth_lre_config(prueth);
+ else if (PRUETH_IS_SWITCH(prueth))
+ icssm_prueth_sw_config_packet_timestamping(prueth);
}
static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable)
@@ -507,15 +590,24 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
struct sk_buff *skb,
enum prueth_queue_id queue_id)
{
+ struct prueth_queue_desc __iomem *queue_desc_other_port = NULL;
struct prueth_queue_desc __iomem *queue_desc;
const struct prueth_queue_info *txqueue;
- struct net_device *ndev = emac->ndev;
struct prueth *prueth = emac->prueth;
+ struct hsr_txopt_ethhdr *hsr_ethhdr;
unsigned int buffer_desc_count;
+ struct prueth_emac *other_emac;
int free_blocks, update_block;
+ struct vlan_ethhdr *vlan_hdr;
bool buffer_wrapped = false;
int write_block, read_block;
+ int free_blocks_other_port;
+ int read_block_other_port;
void *src_addr, *dst_addr;
+ u16 bd_rd_ptr_other_port;
+ struct ethhdr *ethhdr;
+ bool is_vlan = false;
+ bool link_up = false;
int pkt_block_size;
void __iomem *sram;
void __iomem *dram;
@@ -523,16 +615,19 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
u16 update_wr_ptr;
u32 wr_buf_desc;
void *ocmc_ram;
+ __be16 proto;
+ u8 *hdr;
+
+ other_emac = emac->prueth->emac[(emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];
+
+ if (prueth_is_lre(prueth) && (emac->link || other_emac->link))
+ link_up = true;
if (!PRUETH_IS_EMAC(prueth))
dram = prueth->mem[PRUETH_MEM_DRAM1].va;
else
dram = emac->prueth->mem[emac->dram].va;
- if (eth_skb_pad(skb)) {
- if (netif_msg_tx_err(emac) && net_ratelimit())
- netdev_err(ndev, "packet pad failed\n");
- return -ENOMEM;
- }
/* which port to tx: MII0 or MII1 */
txport = emac->tx_port_queue;
@@ -540,7 +635,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
pktlen = skb->len;
/* Get the tx queue */
queue_desc = emac->tx_queue_descs + queue_id;
- if (!PRUETH_IS_EMAC(prueth))
+ /* Tx queue context */
+ if (prueth_is_lre(prueth))
+ txqueue = &lre_queue_infos[txport][queue_id];
+ else if (PRUETH_IS_SWITCH(prueth))
txqueue = &sw_queue_infos[txport][queue_id];
else
txqueue = &queue_infos[txport][queue_id];
@@ -563,8 +661,31 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
free_blocks = buffer_desc_count;
}
+ /* Fetch queue state for the second LRE port */
+ if (prueth_is_lre(prueth) && link_up) {
+ queue_desc_other_port = emac->tx_queue_descs_other_port +
+ queue_id;
+ bd_rd_ptr_other_port = readw(&queue_desc_other_port->rd_ptr);
+
+ read_block_other_port = (bd_rd_ptr_other_port -
+ txqueue->buffer_desc_offset) / BD_SIZE;
+
+ if (write_block > read_block_other_port) {
+ free_blocks_other_port = buffer_desc_count -
+ write_block;
+ free_blocks_other_port += read_block_other_port;
+ } else if (write_block < read_block_other_port) {
+ free_blocks_other_port = read_block_other_port -
+ write_block;
+ } else {
+ free_blocks_other_port = buffer_desc_count;
+ }
+
+ if (free_blocks_other_port < free_blocks)
+ free_blocks = free_blocks_other_port;
+ }
pkt_block_size = DIV_ROUND_UP(pktlen, ICSS_BLOCK_SIZE);
- if (pkt_block_size > free_blocks) /* out of queue space */
+ if (pkt_block_size >= free_blocks) /* out of queue space */
return -ENOBUFS;
/* calculate end BD address post write */
@@ -609,6 +730,60 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
/* update first buffer descriptor */
wr_buf_desc = (pktlen << PRUETH_BD_LENGTH_SHIFT) &
PRUETH_BD_LENGTH_MASK;
+ if (PRUETH_IS_HSR(prueth))
+ wr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);
+
+ if (prueth_is_lre(prueth)) {
+ ethhdr = (struct ethhdr *)skb_mac_header(skb);
+ proto = ethhdr->h_proto;
+
+ if (proto == htons(ETH_P_8021Q)) {
+ vlan_hdr = (struct vlan_ethhdr *)ethhdr;
+ proto = vlan_hdr->h_vlan_encapsulated_proto;
+ is_vlan = true;
+ }
+
+ /* Extract HSR sequence number and LAN ID
+ * from the tag for the Buffer Descriptor
+ */
+ if (proto == htons(ETH_P_HSR)) {
+ hdr = skb_mac_header(skb);
+
+ if (is_vlan) {
+ hsr_ethhdr =
+ (struct hsr_txopt_ethhdr *)(hdr +
+ VLAN_HLEN);
+ } else {
+ hsr_ethhdr = (struct hsr_txopt_ethhdr *)hdr;
+ }
+
+ /* PTP frames (ETH_P_1588) carry no LAN ID
+ * in the HSR tag
+ */
+ if (hsr_ethhdr->hsr_tag.encap_proto !=
+ htons(ETH_P_1588)) {
+ wr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;
+ } else {
+ wr_buf_desc |= (txport <<
+ PRUETH_BD_LAN_A_SHIFT);
+ }
+ wr_buf_desc |= PRUETH_BD_RED_PKT_MASK;
+ } else {
+ /* Read PRP RCT to extract sequence number and LAN ID */
+ struct prp_txopt_rct *rct =
+ (struct prp_txopt_rct *)(skb_tail_pointer(skb) -
+ ICSSM_LRE_TAG_SIZE);
+
+ if (rct->prp_suffix == htons(ETH_P_PRP)) {
+ wr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;
+ wr_buf_desc |= PRUETH_BD_RED_PKT_MASK;
+ } else {
+ wr_buf_desc |= (txport <<
+ PRUETH_BD_LAN_A_SHIFT);
+ }
+ }
+ }
+
sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
if (!PRUETH_IS_EMAC(prueth))
writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr));
@@ -621,14 +796,26 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
update_wr_ptr = txqueue->buffer_desc_offset + (update_block * BD_SIZE);
writew(update_wr_ptr, &queue_desc->wr_ptr);
+ /* update the write pointer in queue descriptor of other port */
+ if (prueth_is_lre(prueth) && link_up)
+ writew(update_wr_ptr, &queue_desc_other_port->wr_ptr);
+
return 0;
}
void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
struct prueth_packet_info *pkt_info)
{
- pkt_info->port = (buffer_descriptor & PRUETH_BD_PORT_MASK) >>
- PRUETH_BD_PORT_SHIFT;
+ if (prueth_is_lre(prueth))
+ pkt_info->start_offset = !!(buffer_descriptor &
+ PRUETH_BD_START_FLAG_MASK);
+ else
+ pkt_info->start_offset = false;
+
+ /* Flag from BD to indicate packet is valid for HOST or not. */
+ pkt_info->host_recv_flag = !!(buffer_descriptor &
+ PRUETH_BD_HOST_RECV_MASK);
+
pkt_info->length = (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >>
PRUETH_BD_LENGTH_SHIFT;
pkt_info->broadcast = !!(buffer_descriptor & PRUETH_BD_BROADCAST_MASK);
@@ -660,11 +847,17 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
int read_block, update_block;
unsigned int actual_pkt_len;
bool buffer_wrapped = false;
+ int adjust_for_hsr_tag = 0;
void *src_addr, *dst_addr;
+ u16 start_offset = 0;
struct sk_buff *skb;
int pkt_block_size;
void *ocmc_ram;
+ u16 type;
+ if (PRUETH_IS_HSR(emac->prueth))
+ start_offset = (pkt_info->start_offset ?
+ ICSSM_LRE_TAG_SIZE : 0);
/* the PRU firmware deals mostly in pointers already
* offset into ram, we would like to deal in indexes
* within the queue we are working with for code
@@ -687,7 +880,18 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
/* calculate new pointer in ram */
*bd_rd_ptr = rxqueue->buffer_desc_offset + (update_block * BD_SIZE);
- actual_pkt_len = pkt_info->length;
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!pkt_info->host_recv_flag)
+ return 0;
+ }
+
+ /* Exclude the HSR tag bytes already stripped by firmware, if any. */
+ actual_pkt_len = pkt_info->length - start_offset;
+
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ actual_pkt_len -= ICSSM_LRE_TAG_SIZE;
+ }
/* Allocate a socket buffer for this packet */
skb = netdev_alloc_skb_ip_align(ndev, actual_pkt_len);
@@ -707,6 +911,30 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
*/
src_addr = ocmc_ram + rxqueue->buffer_offset +
(read_block * ICSS_BLOCK_SIZE);
+ src_addr += start_offset;
+
+ /* Copy destination and source MAC address */
+ memcpy(dst_addr, src_addr, PRUETH_ETHER_TYPE_OFFSET);
+ src_addr += PRUETH_ETHER_TYPE_OFFSET;
+ dst_addr += PRUETH_ETHER_TYPE_OFFSET;
+
+ adjust_for_hsr_tag += PRUETH_ETHER_TYPE_OFFSET;
+
+ /* Check for VLAN tag */
+ type = get_unaligned_be16(src_addr);
+
+ if (type == ETH_P_8021Q) {
+ memcpy(dst_addr, src_addr, VLAN_HLEN);
+ src_addr += VLAN_HLEN;
+ dst_addr += VLAN_HLEN;
+ adjust_for_hsr_tag += VLAN_HLEN;
+ }
+
+ /* HSR tag removal handling */
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ src_addr += ICSSM_LRE_TAG_SIZE;
+ }
/* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */
if (buffer_wrapped) { /* wrapped around buffer */
@@ -720,19 +948,28 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
if (pkt_info->length < bytes)
bytes = pkt_info->length;
+ /* If applicable, account for the HSR tag removed */
+ bytes -= start_offset;
+
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ bytes -= ICSSM_LRE_TAG_SIZE;
+ }
+
/* copy non-wrapped part */
- memcpy(dst_addr, src_addr, bytes);
+ memcpy(dst_addr, src_addr, bytes - adjust_for_hsr_tag);
/* copy wrapped part */
- dst_addr += bytes;
+ dst_addr += (bytes - adjust_for_hsr_tag);
remaining = actual_pkt_len - bytes;
src_addr = ocmc_ram + rxqueue->buffer_offset;
memcpy(dst_addr, src_addr, remaining);
src_addr += remaining;
} else {
- memcpy(dst_addr, src_addr, actual_pkt_len);
- src_addr += actual_pkt_len;
+ memcpy(dst_addr, src_addr, actual_pkt_len -
+ adjust_for_hsr_tag);
+ src_addr += actual_pkt_len - adjust_for_hsr_tag;
}
if (PRUETH_IS_SWITCH(emac->prueth)) {
@@ -741,6 +978,12 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
icssm_prueth_sw_learn_fdb(emac, skb->data + ETH_ALEN);
}
+ /* For PRP, the RCT trailer is at the frame tail, exclude it from
+ * the length to avoid passing it up the stack.
+ */
+ if (PRUETH_IS_PRP(emac->prueth) && pkt_info->start_offset)
+ actual_pkt_len -= ICSSM_LRE_TAG_SIZE;
+
skb_put(skb, actual_pkt_len);
/* send packet up the stack */
@@ -750,8 +993,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
local_bh_enable();
/* update stats */
- emac->stats.rx_bytes += actual_pkt_len;
- emac->stats.rx_packets++;
+ atomic64_add(actual_pkt_len, &emac->stats.rx_bytes);
+ atomic64_inc(&emac->stats.rx_packets);
return 0;
}
@@ -790,7 +1033,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)
rxqueue = &queue_infos[PRUETH_PORT_HOST][i];
overflow_cnt = readb(&queue_desc->overflow_cnt);
if (overflow_cnt > 0) {
- emac->stats.rx_over_errors += overflow_cnt;
+ atomic64_add(overflow_cnt, &emac->stats.rx_over_errors);
/* reset to zero */
writeb(0, &queue_desc->overflow_cnt);
}
@@ -804,16 +1047,15 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)
rd_buf_desc = readl(shared_ram + bd_rd_ptr);
icssm_parse_packet_info(prueth, rd_buf_desc, &pkt_info);
- if (pkt_info.length <= 0) {
- /* a packet length of zero will cause us to
- * never move the read pointer ahead, locking
- * the driver, so we manually have to move it
- * to the write pointer, discarding all
- * remaining packets in this queue. This should
- * never happen.
+ if (pkt_info.length < EMAC_MIN_PKTLEN) {
+ /* if the packet is too small we skip it but we
+ * still need to move the read pointer ahead
+ * and assume something is wrong with the read
+ * pointer as the firmware should be filtering
+ * these packets
*/
update_rd_ptr = bd_wr_ptr;
- emac->stats.rx_length_errors++;
+ atomic64_inc(&emac->stats.rx_length_errors);
} else if (pkt_info.length > EMAC_MAX_FRM_SUPPORT) {
/* if the packet is too large we skip it but we
* still need to move the read pointer ahead
@@ -822,7 +1064,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget)
* these packets
*/
update_rd_ptr = bd_wr_ptr;
- emac->stats.rx_length_errors++;
+ atomic64_inc(&emac->stats.rx_length_errors);
} else {
update_rd_ptr = bd_rd_ptr;
ret = icssm_emac_rx_packet(emac, &update_rd_ptr,
@@ -982,14 +1224,20 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
icssm_prueth_init_ethernet_mode(prueth);
/* reset and start PRU firmware */
- if (PRUETH_IS_SWITCH(prueth)) {
+ if (!PRUETH_IS_EMAC(prueth)) {
+ /* Switch, HSR and PRP protocols share same queue structure */
ret = icssm_prueth_sw_emac_config(emac);
if (ret)
- return ret;
+ goto free_hrtimer;
- ret = icssm_prueth_sw_init_fdb_table(prueth);
- if (ret)
- return ret;
+ if (PRUETH_IS_SWITCH(prueth)) {
+ ret = icssm_prueth_sw_init_fdb_table(prueth);
+ if (ret)
+ goto free_hrtimer;
+ } else {
+ /* LRE mode: set up duplicate-table check flags */
+ icssm_prueth_lre_config_check_flags(prueth);
+ }
} else {
icssm_prueth_emac_config(emac);
}
@@ -1014,11 +1262,25 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
goto iep_exit;
}
- ret = icssm_emac_request_irqs(emac);
- if (ret)
- goto rproc_shutdown;
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_enable(&emac->napi);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_enable(&prueth->napi_hpq);
+ napi_enable(&prueth->napi_lpq);
+ }
+ }
- napi_enable(&emac->napi);
+ /* In switch and LRE modes the shared HPQ/LPQ IRQs are used,
+ * register them here and reuse for both modes.
+ */
+ if (PRUETH_IS_EMAC(prueth))
+ ret = icssm_emac_request_irqs(emac);
+ else
+ ret = icssm_prueth_common_request_irqs(emac);
+ if (ret)
+ goto disable_napi;
/* start PHY */
phy_start(emac->phydev);
@@ -1035,7 +1297,17 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
return 0;
-rproc_shutdown:
+disable_napi:
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_disable(&emac->napi);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_disable(&prueth->napi_lpq);
+ napi_disable(&prueth->napi_hpq);
+ }
+ }
+
if (!PRUETH_IS_EMAC(prueth))
icssm_prueth_sw_shutdown_prus(emac, ndev);
else
@@ -1046,6 +1318,9 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
icss_iep_exit(prueth->iep);
free_mem:
icssm_prueth_free_memory(emac->prueth);
+free_hrtimer:
+ if (prueth_is_lre(prueth) && !prueth->emac_configured)
+ icssm_prueth_lre_cleanup(prueth);
return ret;
}
@@ -1067,20 +1342,34 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
/* disable the mac port */
icssm_prueth_port_enable(emac, false);
+ netif_stop_queue(ndev);
+
/* stop PHY */
phy_stop(emac->phydev);
- napi_disable(&emac->napi);
hrtimer_cancel(&emac->tx_hrtimer);
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_disable(&emac->napi);
+ free_irq(emac->rx_irq, ndev);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_disable(&prueth->napi_lpq);
+ napi_disable(&prueth->napi_hpq);
+ }
+ /* Free IRQs on last port before halting PRU */
+ icssm_prueth_common_free_irqs(emac);
+ }
+
/* stop the PRU */
if (!PRUETH_IS_EMAC(prueth))
icssm_prueth_sw_shutdown_prus(emac, ndev);
else
rproc_shutdown(emac->pru);
- /* free rx interrupts */
- free_irq(emac->rx_irq, ndev);
+ if (prueth_is_lre(prueth) && !prueth->emac_configured)
+ icssm_prueth_lre_cleanup(prueth);
/* free memory related to sw */
icssm_prueth_free_memory(emac->prueth);
@@ -1122,7 +1411,8 @@ static int icssm_prueth_change_mode(struct prueth *prueth,
}
}
- if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) {
+ if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH ||
+ mode == PRUSS_ETHTYPE_HSR || mode == PRUSS_ETHTYPE_PRP) {
prueth->eth_type = mode;
} else {
dev_err(prueth->dev, "unknown mode\n");
@@ -1205,20 +1495,34 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,
struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
+ /* Spinlock for Tx Queues */
+ spinlock_t *lock_queue;
+ unsigned long flags;
int ret;
u16 qid;
qid = icssm_prueth_get_tx_queue_id(emac->prueth, skb);
- ret = icssm_prueth_tx_enqueue(emac, skb, qid);
- if (ret) {
- if (ret != -ENOBUFS && netif_msg_tx_err(emac) &&
- net_ratelimit())
- netdev_err(ndev, "packet queue failed: %d\n", ret);
+ /* Select the TX queue spin lock for this queue ID */
+ if (prueth_is_lre(emac->prueth))
+ lock_queue = &emac->prueth->lre_host_queue_lock[qid - 2];
+ else
+ lock_queue = &emac->host_queue_lock[qid - 2];
+
+ if (eth_skb_pad(skb)) {
+ if (netif_msg_tx_err(emac) && net_ratelimit())
+ netdev_err(ndev, "packet pad failed\n");
+ ret = -ENOMEM;
goto fail_tx;
}
- emac->stats.tx_packets++;
- emac->stats.tx_bytes += skb->len;
+ spin_lock_irqsave(lock_queue, flags);
+ ret = icssm_prueth_tx_enqueue(emac, skb, qid);
+ spin_unlock_irqrestore(lock_queue, flags);
+ if (ret)
+ goto fail_tx;
+
+ atomic64_inc(&emac->stats.tx_packets);
+ atomic64_add(skb->len, &emac->stats.tx_bytes);
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
@@ -1232,7 +1536,7 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,
ret = NETDEV_TX_BUSY;
} else {
/* error */
- emac->stats.tx_dropped++;
+ atomic64_inc(&emac->stats.tx_dropped);
ret = NET_XMIT_DROP;
}
@@ -1252,13 +1556,13 @@ static void icssm_emac_ndo_get_stats64(struct net_device *ndev,
{
struct prueth_emac *emac = netdev_priv(ndev);
- stats->rx_packets = emac->stats.rx_packets;
- stats->rx_bytes = emac->stats.rx_bytes;
- stats->tx_packets = emac->stats.tx_packets;
- stats->tx_bytes = emac->stats.tx_bytes;
- stats->tx_dropped = emac->stats.tx_dropped;
- stats->rx_over_errors = emac->stats.rx_over_errors;
- stats->rx_length_errors = emac->stats.rx_length_errors;
+ stats->rx_packets = atomic64_read(&emac->stats.rx_packets);
+ stats->rx_bytes = atomic64_read(&emac->stats.rx_bytes);
+ stats->tx_packets = atomic64_read(&emac->stats.tx_packets);
+ stats->tx_bytes = atomic64_read(&emac->stats.tx_bytes);
+ stats->tx_dropped = atomic64_read(&emac->stats.tx_dropped);
+ stats->rx_over_errors = atomic64_read(&emac->stats.rx_over_errors);
+ stats->rx_length_errors = atomic64_read(&emac->stats.rx_length_errors);
}
/* enable/disable MC filter */
@@ -1266,11 +1570,16 @@ static void icssm_emac_mc_filter_ctrl(struct prueth_emac *emac, bool enable)
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_ctrl;
+ u32 mc_ctrl_offset;
void __iomem *ram;
u32 reg;
ram = prueth->mem[emac->dram].va;
- mc_filter_ctrl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_ctrl_offset = prueth->fw_offsets.mc_ctrl_offset;
+ mc_filter_ctrl = ram + mc_ctrl_offset;
if (enable)
reg = ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED;
@@ -1289,7 +1598,10 @@ static void icssm_emac_mc_filter_reset(struct prueth_emac *emac)
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
- mc_filter_tbl_base = ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_filter_tbl_base = prueth->fw_offsets.mc_filter_tbl;
mc_filter_tbl = ram + mc_filter_tbl_base;
memset_io(mc_filter_tbl, 0, ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES);
@@ -1302,11 +1614,16 @@ static void icssm_emac_mc_filter_hashmask
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_mask;
+ u32 mc_filter_mask_base;
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
- mc_filter_mask = ram + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;
+ mc_filter_mask_base = prueth->fw_offsets.mc_filter_mask;
+
+ mc_filter_mask = ram + mc_filter_mask_base;
memcpy_toio(mc_filter_mask, mask,
ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES);
}
@@ -1316,11 +1633,16 @@ static void icssm_emac_mc_filter_bin_update(struct prueth_emac *emac, u8 hash,
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_tbl;
+ u32 mc_filter_tbl_base;
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
- mc_filter_tbl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ mc_filter_tbl_base = prueth->fw_offsets.mc_filter_tbl;
+
+ mc_filter_tbl = ram + mc_filter_tbl_base;
writeb(val, mc_filter_tbl + hash);
}
@@ -1360,6 +1682,8 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
bool promisc = ndev->flags & IFF_PROMISC;
+ /* Spinlock for multicast filter table */
+ spinlock_t *mc_filter_tbl_lock;
struct netdev_hw_addr *ha;
struct prueth *prueth;
unsigned long flags;
@@ -1371,8 +1695,13 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)
sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
reg = readl(sram + EMAC_PROMISCUOUS_MODE_OFFSET);
+ if (prueth_is_lre(prueth))
+ mc_filter_tbl_lock = &prueth->addr_lock;
+ else
+ mc_filter_tbl_lock = &emac->addr_lock;
+
/* It is a shared table. So lock the access */
- spin_lock_irqsave(&emac->addr_lock, flags);
+ spin_lock_irqsave(mc_filter_tbl_lock, flags);
/* Disable and reset multicast filter, allows allmulti */
icssm_emac_mc_filter_ctrl(emac, false);
@@ -1429,15 +1758,98 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev)
}
unlock:
- spin_unlock_irqrestore(&emac->addr_lock, flags);
+ spin_unlock_irqrestore(mc_filter_tbl_lock, flags);
+}
+
+static netdev_features_t icssm_emac_ndo_fix_features(struct net_device *ndev,
+ netdev_features_t features)
+{
+ /* hsr tag removal offload, hsr fwd offload and hsr dup offload are
+ * tightly coupled in firmware implementation. These features
+ * must always be enabled/disabled together.
+ */
+ if (!(ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES))
+ if ((features & NETIF_F_HW_HSR_FWD) ||
+ (features & NETIF_F_HW_HSR_TAG_RM) ||
+ (features & NETIF_F_HW_HSR_DUP))
+ features |= NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+
+ if ((ndev->features & NETIF_F_HW_HSR_FWD) ||
+ (ndev->features & NETIF_F_HW_HSR_TAG_RM) ||
+ (ndev->features & NETIF_F_HW_HSR_DUP))
+ if (!(features & NETIF_F_HW_HSR_FWD) ||
+ !(features & NETIF_F_HW_HSR_TAG_RM) ||
+ !(features & NETIF_F_HW_HSR_DUP))
+ features &= ~NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+
+ return features;
+}
+
+/**
+ * icssm_emac_ndo_set_features - Configure HSR/PRP offload features
+ * @ndev: network device
+ * @features: Requested feature set
+ *
+ * Called by ethtool -K to configure HSR/PRP offload features. The request
+ * is rejected if this interface or its paired interface is running.
+ *
+ * Return: 0 on success, -EINVAL or -EBUSY on error.
+ */
+static int icssm_emac_ndo_set_features(struct net_device *ndev,
+ netdev_features_t features)
+{
+ struct prueth_emac *emac, *other_emac;
+ netdev_features_t have, wanted;
+ struct prueth *prueth;
+ bool change_request;
+ int ret = -EBUSY;
+
+ emac = netdev_priv(ndev);
+ prueth = emac->prueth;
+ /* MAC instance index starts from 0. So index by port_id - 1 */
+ other_emac = emac->prueth->emac[(emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];
+ wanted = features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+ have = ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+ change_request = ((wanted ^ have) != 0);
+
+ if (!prueth->fw_data->support_lre)
+ return 0;
+
+ if (PRUETH_IS_SWITCH(prueth) && change_request) {
+ /* LRE offload cannot be enabled in switch mode, remove the
+ * bridge first to revert to EMAC mode.
+ */
+ netdev_err(ndev,
+ "Switch to HSR/PRP not allowed\n");
+ return -EINVAL;
+ }
+
+ if (netif_running(ndev) && change_request) {
+ netdev_err(ndev,
+ "Can't change feature when device runs\n");
+ return ret;
+ }
+
+ if (other_emac && netif_running(other_emac->ndev) && change_request) {
+ netdev_err(ndev,
+ "Can't change feature when other device runs\n");
+ return ret;
+ }
+
+ return 0;
}
static const struct net_device_ops emac_netdev_ops = {
.ndo_open = icssm_emac_ndo_open,
.ndo_stop = icssm_emac_ndo_stop,
.ndo_start_xmit = icssm_emac_ndo_start_xmit,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
.ndo_get_stats64 = icssm_emac_ndo_get_stats64,
.ndo_set_rx_mode = icssm_emac_ndo_set_rx_mode,
+ .ndo_set_features = icssm_emac_ndo_set_features,
+ .ndo_fix_features = icssm_emac_ndo_fix_features,
};
/* get emac_port corresponding to eth_node name */
@@ -1554,6 +1966,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
spin_lock_init(&emac->lock);
spin_lock_init(&emac->addr_lock);
+ spin_lock_init(&emac->host_queue_lock[0]);
+ spin_lock_init(&emac->host_queue_lock[1]);
+
/* get mac address from DT and set private and netdev addr */
ret = of_get_ethdev_address(eth_node, ndev);
if (!is_valid_ether_addr(ndev->dev_addr)) {
@@ -1589,14 +2004,33 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
ndev->hw_features |= NETIF_F_HW_L2FW_DOFFLOAD;
}
+ if (prueth->support_lre)
+ ndev->hw_features |= NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+
ndev->dev.of_node = eth_node;
ndev->netdev_ops = &emac_netdev_ops;
netif_napi_add(ndev, &emac->napi, icssm_emac_napi_poll);
+ if ((prueth->support_lre || fw_data->support_switch) &&
+ emac->port_id == PRUETH_PORT_MII0) {
+ netif_napi_add(ndev, &prueth->napi_hpq,
+ icssm_prueth_lre_napi_poll_hpq);
+ netif_napi_add(ndev, &prueth->napi_lpq,
+ icssm_prueth_lre_napi_poll_lpq);
+ }
+
hrtimer_setup(&emac->tx_hrtimer, &icssm_emac_tx_timer_callback,
CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
+ if ((prueth->support_lre || fw_data->support_switch) &&
+ emac->port_id == PRUETH_PORT_MII0) {
+ prueth->hp->ndev = ndev;
+ prueth->hp->priority = 0;
+ prueth->lp->ndev = ndev;
+ prueth->lp->priority = 1;
+ }
+
return 0;
free:
emac->ndev = NULL;
@@ -1608,6 +2042,7 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
static void icssm_prueth_netdev_exit(struct prueth *prueth,
struct device_node *eth_node)
{
+ const struct prueth_private_data *fw_data = prueth->fw_data;
struct prueth_emac *emac;
enum prueth_mac mac;
@@ -1622,6 +2057,13 @@ static void icssm_prueth_netdev_exit(struct prueth *prueth,
phy_disconnect(emac->phydev);
netif_napi_del(&emac->napi);
+
+ if ((prueth->support_lre || fw_data->support_switch) &&
+ emac->port_id == PRUETH_PORT_MII0) {
+ netif_napi_del(&prueth->napi_hpq);
+ netif_napi_del(&prueth->napi_lpq);
+ }
+
prueth->emac[mac] = NULL;
}
@@ -1741,6 +2183,105 @@ static int icssm_prueth_ndev_port_unlink(struct net_device *ndev)
return ret;
}
+static int icssm_prueth_hsr_port_link(struct net_device *ndev,
+ struct net_device *hsr_ndev)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ enum pruss_ethtype mode;
+ enum hsr_version ver;
+ unsigned long flags;
+ u8 all_slaves;
+ int ret = 0;
+
+ if (PRUETH_IS_SWITCH(prueth))
+ return -EOPNOTSUPP;
+
+ hsr_get_version(hsr_ndev, &ver);
+
+ if (ver == HSR_V1)
+ mode = PRUSS_ETHTYPE_HSR;
+ else if (ver == PRP_V1)
+ mode = PRUSS_ETHTYPE_PRP;
+ else
+ return -EOPNOTSUPP;
+
+ all_slaves = BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1);
+
+ spin_lock_irqsave(&prueth->addr_lock, flags);
+
+ if (!prueth->hsr_members) {
+ prueth->hsr_dev = hsr_ndev;
+ } else {
+ /* Adding the port to a second bridge is not supported */
+ if (prueth->hsr_dev != hsr_ndev) {
+ spin_unlock_irqrestore(&prueth->addr_lock, flags);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ prueth->hsr_members |= BIT(emac->port_id);
+
+ spin_unlock_irqrestore(&prueth->addr_lock, flags);
+
+ if (!prueth_is_lre(prueth) && prueth->hsr_members == all_slaves) {
+ ret = icssm_prueth_change_mode(prueth, mode);
+ if (ret < 0) {
+ dev_err(prueth->dev, "Failed to enable %s mode\n",
+ (mode == PRUSS_ETHTYPE_HSR) ?
+ "HSR" : "PRP");
+ goto free_hsr;
+ } else {
+ dev_info(prueth->dev,
+ "TI PRU ethernet now in %s mode\n",
+ (mode == PRUSS_ETHTYPE_HSR) ?
+ "HSR" : "PRP");
+ }
+ }
+
+ return 0;
+
+free_hsr:
+ spin_lock_irqsave(&prueth->addr_lock, flags);
+
+ prueth->hsr_dev = NULL;
+ prueth->hsr_members &= ~all_slaves;
+
+ spin_unlock_irqrestore(&prueth->addr_lock, flags);
+ return ret;
+}
+
+static int icssm_prueth_hsr_port_unlink(struct net_device *ndev)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&prueth->addr_lock, flags);
+
+ prueth->hsr_members &= ~BIT(emac->port_id);
+
+ spin_unlock_irqrestore(&prueth->addr_lock, flags);
+
+ if (prueth_is_lre(prueth) && !prueth->hsr_members) {
+ ret = icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_EMAC);
+ if (ret < 0) {
+ dev_err(prueth->dev, "Failed to enable dual EMAC mode\n");
+ return ret;
+ }
+ }
+
+ spin_lock_irqsave(&prueth->addr_lock, flags);
+
+ if (!prueth->hsr_members)
+ prueth->hsr_dev = NULL;
+
+ spin_unlock_irqrestore(&prueth->addr_lock, flags);
+
+ return 0;
+}
+
static int icssm_prueth_ndev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
@@ -1754,6 +2295,17 @@ static int icssm_prueth_ndev_event(struct notifier_block *unused,
switch (event) {
case NETDEV_CHANGEUPPER:
info = ptr;
+ if (is_hsr_master(info->upper_dev)) {
+ if (info->linking) {
+ if (ndev->features &
+ NETIF_PRUETH_LRE_OFFLOAD_FEATURES)
+ ret = icssm_prueth_hsr_port_link
+ (ndev, info->upper_dev);
+ } else {
+ ret = icssm_prueth_hsr_port_unlink(ndev);
+ }
+ }
+
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
ret = icssm_prueth_ndev_port_link
@@ -1796,6 +2348,7 @@ static int icssm_prueth_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct device_node *np;
struct prueth *prueth;
+ bool has_lre = false;
struct pruss *pruss;
int i, ret;
@@ -1811,6 +2364,13 @@ static int icssm_prueth_probe(struct platform_device *pdev)
prueth->dev = dev;
prueth->fw_data = device_get_match_data(dev);
+ if (prueth->fw_data->fw_rev == FW_REV_V1_0)
+ prueth->fw_offsets = fw_offsets_v1_0;
+ else if (prueth->fw_data->fw_rev == FW_REV_V2_1)
+ prueth->fw_offsets = fw_offsets_v2_1;
+ else
+ return -EINVAL;
+
eth_ports_node = of_get_child_by_name(np, "ethernet-ports");
if (!eth_ports_node)
return -ENOENT;
@@ -1955,6 +2515,54 @@ static int icssm_prueth_probe(struct platform_device *pdev)
prueth->mem[PRUETH_MEM_OCMC].va,
prueth->mem[PRUETH_MEM_OCMC].size);
+ if (IS_ENABLED(CONFIG_HSR) && prueth->fw_data->support_lre)
+ has_lre = true;
+
+ /* LRE requires both ethernet nodes to be present in
+ * DT, otherwise clear the support flag
+ */
+ if (has_lre && (!eth0_node || !eth1_node))
+ has_lre = false;
+
+ /* Switch and LRE share HPQ/LPQ IRQs across both ports,
+ * allocate the shared priority structures once here
+ */
+ if (prueth->fw_data->support_switch || has_lre) {
+ prueth->hp = devm_kzalloc(dev,
+ sizeof(struct prueth_ndev_priority),
+ GFP_KERNEL);
+ if (!prueth->hp) {
+ ret = -ENOMEM;
+ goto free_pool;
+ }
+ prueth->lp = devm_kzalloc(dev,
+ sizeof(struct prueth_ndev_priority),
+ GFP_KERNEL);
+ if (!prueth->lp) {
+ ret = -ENOMEM;
+ goto free_pool;
+ }
+
+ prueth->rx_lpq_irq = of_irq_get_byname(np, "rx_lp");
+ if (prueth->rx_lpq_irq < 0) {
+ ret = prueth->rx_lpq_irq;
+ if (ret != -EPROBE_DEFER)
+ dev_err(prueth->dev, "could not get rx_lp irq\n");
+ goto free_pool;
+ }
+ prueth->rx_hpq_irq = of_irq_get_byname(np, "rx_hp");
+ if (prueth->rx_hpq_irq < 0) {
+ ret = prueth->rx_hpq_irq;
+ if (ret != -EPROBE_DEFER)
+ dev_err(prueth->dev, "could not get rx_hp irq\n");
+ goto free_pool;
+ }
+ }
+
+ prueth->support_lre = has_lre;
+ spin_lock_init(&prueth->addr_lock);
+ spin_lock_init(&prueth->lre_host_queue_lock[0]);
+ spin_lock_init(&prueth->lre_host_queue_lock[1]);
/* setup netdev interfaces */
if (eth0_node) {
ret = icssm_prueth_netdev_init(prueth, eth0_node);
@@ -2176,15 +2784,25 @@ static struct prueth_private_data am335x_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am335x-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am335x-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am335x-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am335x-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am335x-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am335x-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am335x-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am335x-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V1_0,
+ .support_lre = true,
.support_switch = true,
};
@@ -2194,15 +2812,25 @@ static struct prueth_private_data am437x_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am437x-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am437x-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am437x-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am437x-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am437x-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am437x-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am437x-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am437x-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V1_0,
+ .support_lre = true,
.support_switch = true,
};
@@ -2212,16 +2840,26 @@ static struct prueth_private_data am57xx_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am57xx-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am57xx-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am57xx-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am57xx-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am57xx-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am57xx-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am57xx-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am57xx-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V2_1,
+ .support_lre = true,
.support_switch = true,
};
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
index d5b49b462c242..2447fb8b99e79 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
@@ -16,13 +16,17 @@
#include "icssm_switch.h"
#include "icssm_prueth_ptp.h"
#include "icssm_prueth_fdb_tbl.h"
+#include "icssm_lre_firmware.h"
/* ICSSM size of redundancy tag */
#define ICSSM_LRE_TAG_SIZE 6
+#define PRUETH_TIMER_MS (10)
+
/* PRUSS local memory map */
#define ICSS_LOCAL_SHARED_RAM 0x00010000
#define EMAC_MAX_PKTLEN (ETH_HLEN + VLAN_HLEN + ETH_DATA_LEN)
+#define EMAC_MIN_PKTLEN ETH_ZLEN
/* Below macro is for 1528 Byte Frame support, to Allow even with
* Redundancy tag
*/
@@ -42,6 +46,8 @@ enum pruss_ethtype {
#define PRUETH_IS_EMAC(p) ((p)->eth_type == PRUSS_ETHTYPE_EMAC)
#define PRUETH_IS_SWITCH(p) ((p)->eth_type == PRUSS_ETHTYPE_SWITCH)
+#define PRUETH_IS_HSR(p) ((p)->eth_type == PRUSS_ETHTYPE_HSR)
+#define PRUETH_IS_PRP(p) ((p)->eth_type == PRUSS_ETHTYPE_PRP)
/**
* struct prueth_queue_desc - Queue descriptor
@@ -86,8 +92,9 @@ struct prueth_queue_info {
/**
* struct prueth_packet_info - Info about a packet in buffer
+ * @start_offset: true if frame carries an HSR/PRP start offset
* @shadow: this packet is stored in the collision queue
- * @port: port packet is on
+ * @host_recv_flag: this frame should be received by host
* @length: length of packet
* @broadcast: this packet is a broadcast packet
* @error: this packet has an error
@@ -96,8 +103,9 @@ struct prueth_queue_info {
* @timestamp: Specifies if timestamp is appended to the packet
*/
struct prueth_packet_info {
+ bool start_offset;
bool shadow;
- unsigned int port;
+ bool host_recv_flag;
unsigned int length;
bool broadcast;
bool error;
@@ -171,6 +179,24 @@ enum prueth_mem {
PRUETH_MEM_MAX,
};
+/* PRU firmware revision */
+enum fw_revision {
+ FW_REV_INVALID = 0,
+ FW_REV_V1_0,
+ FW_REV_V2_1
+};
+
+/* Firmware offsets/size information */
+struct prueth_fw_offsets {
+ u32 mc_ctrl_offset;
+ u32 mc_filter_mask;
+ u32 mc_filter_tbl;
+ /* IEP wrap is used in the rx packet ordering logic and
+ * is different for ICSSM v1.0 vs 2.1
+ */
+ u32 iep_wrap;
+};
+
enum pruss_device {
PRUSS_AM57XX = 0,
PRUSS_AM43XX,
@@ -182,22 +208,26 @@ enum pruss_device {
* struct prueth_private_data - PRU Ethernet private data
* @driver_data: PRU Ethernet device name
* @fw_pru: firmware names to be used for PRUSS ethernet usecases
+ * @fw_rev: Firmware revision identifier
* @support_switch: boolean to indicate if switch is enabled
+ * @support_lre: boolean to indicate if LRE is enabled
*/
struct prueth_private_data {
enum pruss_device driver_data;
const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS];
+ enum fw_revision fw_rev;
bool support_switch;
+ bool support_lre;
};
struct prueth_emac_stats {
- u64 tx_packets;
- u64 tx_dropped;
- u64 tx_bytes;
- u64 rx_packets;
- u64 rx_bytes;
- u64 rx_length_errors;
- u64 rx_over_errors;
+ atomic64_t tx_packets;
+ atomic64_t tx_dropped;
+ atomic64_t tx_bytes;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t rx_length_errors;
+ atomic64_t rx_over_errors;
};
/* data for each emac port */
@@ -210,6 +240,8 @@ struct prueth_emac {
struct phy_device *phydev;
struct prueth_queue_desc __iomem *rx_queue_descs;
struct prueth_queue_desc __iomem *tx_queue_descs;
+ /* LRE duplicates each TX frame to both ports */
+ struct prueth_queue_desc __iomem *tx_queue_descs_other_port;
int link;
int speed;
@@ -233,11 +265,17 @@ struct prueth_emac {
spinlock_t lock;
spinlock_t addr_lock; /* serialize access to VLAN/MC filter table */
+ spinlock_t host_queue_lock[NUM_QUEUES / 2];
struct hrtimer tx_hrtimer;
struct prueth_emac_stats stats;
int offload_fwd_mark;
};
+struct prueth_ndev_priority {
+ struct net_device *ndev;
+ int priority;
+};
+
struct prueth {
struct device *dev;
struct pruss *pruss;
@@ -248,13 +286,26 @@ struct prueth {
struct icss_iep *iep;
const struct prueth_private_data *fw_data;
- struct prueth_fw_offsets *fw_offsets;
+ struct prueth_fw_offsets fw_offsets;
struct device_node *eth_node[PRUETH_NUM_MACS];
struct prueth_emac *emac[PRUETH_NUM_MACS];
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
+ struct prueth_ndev_priority *hp, *lp;
+ /* NAPI for lp and hp queue scans */
+ struct napi_struct napi_lpq;
+ struct napi_struct napi_hpq;
+ int rx_lpq_irq;
+ int rx_hpq_irq;
+
+ bool support_lre;
+ unsigned int tbl_check_mask;
+ struct hrtimer tbl_check_timer;
+ /* serialize access to LRE VLAN/MC filter table */
+ spinlock_t addr_lock;
struct net_device *hw_bridge_dev;
+ struct net_device *hsr_dev;
struct fdb_tbl *fdb_tbl;
struct notifier_block prueth_netdevice_nb;
@@ -264,10 +315,15 @@ struct prueth {
unsigned int eth_type;
size_t ocmc_ram_size;
u8 emac_configured;
+ u8 hsr_members;
u8 br_members;
+
+ /* Per-queue TX lock - LRE uses only the two high-priority queues */
+ spinlock_t lre_host_queue_lock[NUM_QUEUES / 2];
};
extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES];
+extern const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES];
void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
struct prueth_packet_info *pkt_info);
@@ -277,4 +333,15 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash);
void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash);
u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask);
+
+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget);
+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget);
+
+int icssm_prueth_common_request_irqs(struct prueth_emac *emac);
+void icssm_prueth_common_free_irqs(struct prueth_emac *emac);
+
+static inline bool prueth_is_lre(struct prueth *prueth)
+{
+ return PRUETH_IS_HSR(prueth) || PRUETH_IS_PRP(prueth);
+}
#endif /* __NET_TI_PRUETH_H */
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
new file mode 100644
index 0000000000000..a99cdbb79bbf0
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Texas Instruments ICSSM Ethernet Driver
+ *
+ * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/if_vlan.h>
+
+#include "icssm_prueth.h"
+#include "icssm_prueth_switch.h"
+
+static int icssm_prueth_common_emac_rx_packets(struct prueth_emac *emac,
+ int quota, u8 qid1, u8 qid2)
+{
+ u16 bd_rd_ptr, bd_wr_ptr, update_rd_ptr, bd_rd_ptr_o, bd_wr_ptr_o;
+ const struct prueth_queue_info *rxqueue, *rxqueue_o, *rxqueue_p;
+ struct prueth_queue_desc __iomem *queue_desc, *queue_desc_o;
+ struct prueth_packet_info pkt_info, pkt_info_o, *pkt_info_p;
+ u32 rd_buf_desc, rd_buf_desc_o, pkt_ts, pkt_ts_o, iep_wrap;
+ int ret, used = 0, port, port0_q_empty, port1_q_empty;
+ struct prueth_emac *emac_p, *other_emac;
+ void __iomem *shared_ram, *ocmc_ram;
+ u8 overflow_cnt, overflow_cnt_o;
+ u16 *bd_rd_ptr_p, *bd_wr_ptr_p;
+ struct prueth *prueth;
+
+ prueth = emac->prueth;
+ ocmc_ram = prueth->mem[PRUETH_MEM_OCMC].va;
+ shared_ram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+ other_emac = prueth->emac[(emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];
+
+ iep_wrap = prueth->fw_offsets.iep_wrap;
+ /* search host queues for packets */
+ queue_desc = emac->rx_queue_descs + qid1;
+ queue_desc_o = other_emac->rx_queue_descs + qid2;
+
+ rxqueue = &sw_queue_infos[PRUETH_PORT_HOST][qid1];
+ rxqueue_o = &sw_queue_infos[PRUETH_PORT_HOST][qid2];
+
+ /* skip Rx if budget is 0 */
+ if (!quota)
+ return 0;
+
+ overflow_cnt = readb(&queue_desc->overflow_cnt);
+ overflow_cnt_o = readb(&queue_desc_o->overflow_cnt);
+
+ if (overflow_cnt > 0) {
+ atomic64_add(overflow_cnt, &emac->stats.rx_over_errors);
+ writeb(0, &queue_desc->overflow_cnt);
+ }
+ if (overflow_cnt_o > 0) {
+ atomic64_add(overflow_cnt_o, &other_emac->stats.rx_over_errors);
+ writeb(0, &queue_desc_o->overflow_cnt);
+ }
+
+ bd_rd_ptr = readw(&queue_desc->rd_ptr);
+ bd_wr_ptr = readw(&queue_desc->wr_ptr);
+
+ bd_rd_ptr_o = readw(&queue_desc_o->rd_ptr);
+ bd_wr_ptr_o = readw(&queue_desc_o->wr_ptr);
+
+ port0_q_empty = (bd_rd_ptr == bd_wr_ptr);
+ port1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o);
+
+ while (!port0_q_empty || !port1_q_empty) {
+ rd_buf_desc = readl(shared_ram + bd_rd_ptr);
+ rd_buf_desc_o = readl(shared_ram + bd_rd_ptr_o);
+
+ icssm_parse_packet_info(prueth, rd_buf_desc, &pkt_info);
+ icssm_parse_packet_info(prueth, rd_buf_desc_o, &pkt_info_o);
+
+ pkt_ts = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +
+ bd_rd_ptr - SRAM_START_OFFSET);
+ pkt_ts_o = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +
+ bd_rd_ptr_o - SRAM_START_OFFSET);
+
+ if (!port0_q_empty && !port1_q_empty) {
+ /* Both ports have a pending frame, pick the
+ * earlier one by comparing timestamps and
+ * account for wraparound.
+ */
+ if (pkt_ts > pkt_ts_o)
+ port = (pkt_ts - pkt_ts_o) > (iep_wrap / 2) ?
+ 0 : 1;
+ else
+ port = (pkt_ts_o - pkt_ts) > (iep_wrap / 2) ?
+ 1 : 0;
+
+ } else if (!port0_q_empty) {
+ /* Packet(s) in port0 queue only */
+ port = 0;
+ } else {
+ /* Packet(s) in port1 queue only */
+ port = 1;
+ }
+
+ /* Select correct data structures for queue/packet selected */
+ if (port == 0) {
+ pkt_info_p = &pkt_info;
+ bd_wr_ptr_p = &bd_wr_ptr;
+ bd_rd_ptr_p = &bd_rd_ptr;
+ emac_p = emac;
+ rxqueue_p = rxqueue;
+ } else {
+ pkt_info_p = &pkt_info_o;
+ bd_wr_ptr_p = &bd_wr_ptr_o;
+ bd_rd_ptr_p = &bd_rd_ptr_o;
+ emac_p = other_emac;
+ rxqueue_p = rxqueue_o;
+ }
+
+ if ((*pkt_info_p).length < EMAC_MIN_PKTLEN) {
+ /* Undersized frame: firmware should have filtered
+ * these before they reach the host queue. Advance
+ * the read pointer to skip it.
+ */
+ update_rd_ptr = *bd_wr_ptr_p;
+ atomic64_inc(&emac_p->stats.rx_length_errors);
+ } else if ((*pkt_info_p).length > EMAC_MAX_FRM_SUPPORT) {
+ /* Oversized frame: firmware should have filtered
+ * these before they reach the host queue. Advance
+ * the read pointer to skip it.
+ */
+ update_rd_ptr = *bd_wr_ptr_p;
+ atomic64_inc(&emac_p->stats.rx_length_errors);
+ } else {
+ update_rd_ptr = *bd_rd_ptr_p;
+ ret = icssm_emac_rx_packet(emac_p, &update_rd_ptr,
+ pkt_info_p, rxqueue_p);
+ if (ret)
+ return used;
+
+ used++;
+ }
+
+ /* Leave the BD intact after reading. Firmware reuses it to
+ * forward the frame to the second LRE port.
+ */
+ if (port == 0) {
+ writew(update_rd_ptr, &queue_desc->rd_ptr);
+ bd_rd_ptr = update_rd_ptr;
+ } else {
+ writew(update_rd_ptr, &queue_desc_o->rd_ptr);
+ bd_rd_ptr_o = update_rd_ptr;
+ }
+
+ port0_q_empty = (bd_rd_ptr == bd_wr_ptr) ? 1 : 0;
+ port1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o) ? 1 : 0;
+
+ if (used >= quota)
+ return used;
+ }
+
+ return used;
+}
+
+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget)
+{
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+ int num_rx_packets;
+ u8 qid1, qid2;
+
+ prueth = container_of(napi, struct prueth, napi_lpq);
+ ndev = prueth->lp->ndev;
+ emac = netdev_priv(ndev);
+ qid1 = PRUETH_QUEUE2;
+ qid2 = PRUETH_QUEUE4;
+
+ num_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,
+ qid1, qid2);
+ if (num_rx_packets < budget && napi_complete_done(napi, num_rx_packets))
+ enable_irq(prueth->rx_lpq_irq);
+
+ return num_rx_packets;
+}
+
+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget)
+{
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+ int num_rx_packets;
+ u8 qid1, qid2;
+
+ prueth = container_of(napi, struct prueth, napi_hpq);
+ ndev = prueth->hp->ndev;
+ emac = netdev_priv(ndev);
+ qid1 = PRUETH_QUEUE1;
+ qid2 = PRUETH_QUEUE3;
+
+ num_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,
+ qid1, qid2);
+ if (num_rx_packets < budget && napi_complete_done(napi, num_rx_packets))
+ enable_irq(prueth->rx_hpq_irq);
+
+ return num_rx_packets;
+}
+
+static irqreturn_t icssm_prueth_common_emac_rx_hardirq(int irq, void *dev_id)
+{
+ struct prueth_ndev_priority *ndev_prio;
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+
+ ndev_prio = (struct prueth_ndev_priority *)dev_id;
+ ndev = ndev_prio->ndev;
+ emac = netdev_priv(ndev);
+ prueth = emac->prueth;
+
+ /* disable Rx system event */
+ if (ndev_prio->priority == 1) {
+ disable_irq_nosync(prueth->rx_lpq_irq);
+ napi_schedule(&prueth->napi_lpq);
+ } else {
+ disable_irq_nosync(prueth->rx_hpq_irq);
+ napi_schedule(&prueth->napi_hpq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int icssm_prueth_common_request_irqs(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+ int ret;
+
+ /* Request irq when first port is initialized */
+ if (prueth->emac_configured)
+ return 0;
+
+ ret = request_irq(prueth->rx_hpq_irq,
+ icssm_prueth_common_emac_rx_hardirq,
+ IRQF_TRIGGER_HIGH, "eth_hp_int", prueth->hp);
+ if (ret) {
+ netdev_err(emac->ndev, "unable to request RX HPQ IRQ\n");
+ return ret;
+ }
+
+ ret = request_irq(prueth->rx_lpq_irq,
+ icssm_prueth_common_emac_rx_hardirq,
+ IRQF_TRIGGER_HIGH, "eth_lp_int", prueth->lp);
+ if (ret) {
+ netdev_err(emac->ndev, "unable to request RX LPQ IRQ\n");
+ goto free_rx_hpq_irq;
+ }
+
+ return 0;
+
+free_rx_hpq_irq:
+ free_irq(prueth->rx_hpq_irq, prueth->hp);
+
+ return ret;
+}
+
+/**
+ * icssm_prueth_common_free_irqs - free irq
+ *
+ * @emac: EMAC data structure
+ *
+ */
+void icssm_prueth_common_free_irqs(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+
+ /* HSR/PRP: free irqs when last port is down */
+ if (prueth->emac_configured)
+ return;
+
+ free_irq(prueth->rx_lpq_irq, prueth->lp);
+ free_irq(prueth->rx_hpq_irq, prueth->hp);
+}
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
new file mode 100644
index 0000000000000..239542101943e
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Texas Instruments PRUETH hsr/prp Link Redundancy Entity (LRE) Driver.
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "icssm_lre_firmware.h"
+#include "icssm_prueth_lre.h"
+#include "icssm_prueth.h"
+#include "icssm_prueth_switch.h"
+
+void icssm_prueth_lre_config_check_flags(struct prueth *prueth)
+{
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ /* HSR/PRP: initialize check table when first port is up */
+ if (prueth->emac_configured)
+ return;
+
+ prueth->tbl_check_mask = ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT;
+ if (PRUETH_IS_HSR(prueth))
+ prueth->tbl_check_mask |=
+ ICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS;
+ writel(prueth->tbl_check_mask, dram1 + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);
+}
+
+/* A group of PCPs are mapped to a Queue. This is the size of firmware
+ * array in shared memory
+ */
+#define PCP_GROUP_TO_QUEUE_MAP_SIZE 4
+
+/* PRU firmware default PCP to priority Queue map for ingress & egress
+ *
+ * At ingress to Host
+ * ==================
+ * byte 0 => PRU 1, PCP 0-3 => Q3
+ * byte 1 => PRU 1, PCP 4-7 => Q2
+ * byte 2 => PRU 0, PCP 0-3 => Q1
+ * byte 3 => PRU 0, PCP 4-7 => Q0
+ *
+ * At egress to wire/network on PRU-0 and PRU-1
+ * ============================================
+ * byte 0 => Host, PCP 0-3 => Q3
+ * byte 1 => Host, PCP 4-7 => Q2
+ *
+ * PRU-0
+ * -----
+ * byte 2 => PRU-1, PCP 0-3 => Q1
+ * byte 3 => PRU-1, PCP 4-7 => Q0
+ *
+ * PRU-1
+ * -----
+ * byte 2 => PRU-0, PCP 0-3 => Q1
+ * byte 3 => PRU-0, PCP 4-7 => Q0
+ *
+ * queue names below are named 1 based. i.e PRUETH_QUEUE1 is Q0,
+ * PRUETH_QUEUE2 is Q1 and so forth. Firmware convention is that
+ * a lower queue number has higher priority than a higher queue
+ * number.
+ */
+static u8 fw_pcp_default_priority_queue_map[PCP_GROUP_TO_QUEUE_MAP_SIZE] = {
+ /* port 2 or PRU 1 */
+ PRUETH_QUEUE4, PRUETH_QUEUE3,
+ /* port 1 or PRU 0 */
+ PRUETH_QUEUE2, PRUETH_QUEUE1,
+};
+
+static void icssm_prueth_lre_pcp_queue_map_config(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ memcpy_toio(sram + ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET,
+ &fw_pcp_default_priority_queue_map[0],
+ PCP_GROUP_TO_QUEUE_MAP_SIZE);
+}
+
+static void icssm_prueth_lre_host_table_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ memset_io(dram0 + ICSS_LRE_DUPLICATE_HOST_TABLE, 0,
+ ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE);
+
+ writel(ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT,
+ dram1 + ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE);
+
+ writel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,
+ dram1 + ICSS_LRE_DUPLI_HOST_CHECK_RESO);
+
+ writel(ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR,
+ dram1 + ICSS_LRE_HOST_DUPLICATE_ARBITRATION);
+}
+
+static void icssm_prueth_lre_port_table_init(struct prueth *prueth)
+{
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ if (PRUETH_IS_HSR(prueth)) {
+ memset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0, 0,
+ ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);
+ memset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1, 0,
+ ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);
+
+ writel(ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT,
+ dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);
+ } else {
+ writel(0, dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);
+ }
+
+ writel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,
+ dram1 + ICSS_LRE_DUPLI_PORT_CHECK_RESO);
+}
+
+static void icssm_prueth_lre_init(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ memset_io(sram + ICSS_LRE_START, 0, ICSS_LRE_STATS_DMEM_SIZE);
+
+ writel(ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD,
+ sram + ICSS_LRE_DUPLICATE_DISCARD);
+ writel(ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT,
+ sram + ICSS_LRE_TRANSPARENT_RECEPTION);
+}
+
+static void icssm_prueth_lre_dbg_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+
+ memset_io(dram0 + ICSS_LRE_DBG_START, 0,
+ ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE);
+}
+
+static void icssm_prueth_lre_protocol_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ if (PRUETH_IS_HSR(prueth))
+ writew(ICSS_LRE_MODEH, dram0 + ICSS_LRE_HSR_MODE_OFFSET);
+
+ writel(ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS,
+ dram1 + ICSS_LRE_DUPLI_FORGET_TIME);
+ writel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH,
+ dram1 + ICSS_LRE_SUP_ADDR);
+ writel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW,
+ dram1 + ICSS_LRE_SUP_ADDR_LOW);
+}
+
+static void icssm_prueth_lre_config_packet_timestamping(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ writeb(1, sram + ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET);
+ writeb(1, sram + ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET);
+}
+
+static enum hrtimer_restart icssm_prueth_lre_timer(struct hrtimer *timer)
+{
+ struct prueth *prueth;
+ unsigned int timeout;
+ void __iomem *dram;
+
+ prueth = container_of(timer, struct prueth, tbl_check_timer);
+ dram = prueth->mem[PRUETH_MEM_DRAM1].va;
+ timeout = PRUETH_TIMER_MS;
+
+ hrtimer_forward_now(timer, ms_to_ktime(timeout));
+ if (prueth->emac_configured !=
+ (BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1)))
+ return HRTIMER_RESTART;
+
+ /* Set the flags for duplicate tables so the firmware checks and
+ * updates them every 10 milliseconds.
+ */
+ writel(prueth->tbl_check_mask, dram + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);
+
+ return HRTIMER_RESTART;
+}
+
+static void icssm_prueth_lre_init_timer(struct prueth *prueth)
+{
+ hrtimer_setup(&prueth->tbl_check_timer, &icssm_prueth_lre_timer,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+}
+
+static void icssm_prueth_lre_start_timer(struct prueth *prueth)
+{
+ unsigned int timeout = PRUETH_TIMER_MS;
+
+ if (hrtimer_active(&prueth->tbl_check_timer))
+ return;
+
+ hrtimer_start(&prueth->tbl_check_timer, ms_to_ktime(timeout),
+ HRTIMER_MODE_REL);
+}
+
+void icssm_prueth_lre_config(struct prueth *prueth)
+{
+ icssm_prueth_lre_init_timer(prueth);
+ icssm_prueth_lre_start_timer(prueth);
+ icssm_prueth_lre_pcp_queue_map_config(prueth);
+ icssm_prueth_lre_host_table_init(prueth);
+ icssm_prueth_lre_port_table_init(prueth);
+ icssm_prueth_lre_init(prueth);
+ icssm_prueth_lre_dbg_init(prueth);
+ icssm_prueth_lre_protocol_init(prueth);
+ /* Enable per-packet timestamping so the driver can order
+ * received frames by arrival time across the two slave ports.
+ */
+ icssm_prueth_lre_config_packet_timestamping(prueth);
+
+}
+
+void icssm_prueth_lre_cleanup(struct prueth *prueth)
+{
+ if (hrtimer_active(&prueth->tbl_check_timer))
+ hrtimer_cancel(&prueth->tbl_check_timer);
+}
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
new file mode 100644
index 0000000000000..0fe4d1ae58234
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
+ */
+
+#ifndef __NET_TI_PRUETH_LRE_H
+#define __NET_TI_PRUETH_LRE_H
+
+#include <linux/etherdevice.h>
+#include <linux/interrupt.h>
+#include <linux/if_vlan.h>
+
+#include "icssm_prueth.h"
+#include "icssm_lre_firmware.h"
+
+void icssm_prueth_lre_config(struct prueth *prueth);
+void icssm_prueth_lre_cleanup(struct prueth *prueth);
+void icssm_prueth_lre_config_check_flags(struct prueth *prueth);
+
+#endif /* __NET_TI_PRUETH_LRE_H */
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
index 66866ea37913f..bda2fac13d749 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
@@ -199,6 +199,189 @@ static const struct prueth_queue_info rx_queue_infos[][NUM_QUEUES] = {
},
};
+/* Tx Queue context for HSR and PRP */
+const struct prueth_queue_info lre_queue_infos[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P0_Q3_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 16,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P0_Q4_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 24,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q3_BUFFER_OFFSET,
+ P0_Q3_BUFFER_OFFSET +
+ ((HOST_QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q4_BUFFER_OFFSET,
+ P0_Q4_BUFFER_OFFSET +
+ ((HOST_QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_Q3_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_Q1_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P0_Q1_BUFFER_OFFSET +
+ ((HOST_QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET +
+ ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P0_Q2_BUFFER_OFFSET +
+ ((HOST_QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET +
+ ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_Q3_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_Q1_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+
+ },
+};
+
+/* Rx Queue Context for HSR and PRP */
+static const struct prueth_queue_info lre_rx_queue_infos[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P0_Q3_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 16,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P0_Q4_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 24,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q3_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q4_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 8,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 16,
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 24,
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 16,
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 24,
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+};
+
void icssm_prueth_sw_free_fdb_table(struct prueth *prueth)
{
if (prueth->emac_configured)
@@ -856,8 +1039,12 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)
/* queue information table */
dram = dram1_base + P0_Q1_RX_CONTEXT_OFFSET;
- memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],
- sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));
+ if (prueth_is_lre(prueth))
+ memcpy_toio(dram, lre_queue_infos[PRUETH_PORT_QUEUE_HOST],
+ sizeof(lre_queue_infos[PRUETH_PORT_QUEUE_HOST]));
+ else
+ memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],
+ sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));
/* buffer descriptor offset table*/
dram = dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR;
@@ -882,8 +1069,23 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)
/* queue table */
dram = dram1_base + P0_QUEUE_DESC_OFFSET;
- memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],
- sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));
+ if (prueth_is_lre(prueth))
+ memcpy_toio(dram,
+ hsr_prp_txopt_queue_descs[PRUETH_PORT_QUEUE_HOST],
+ sizeof(hsr_prp_txopt_queue_descs
+ [PRUETH_PORT_QUEUE_HOST]));
+ else
+ memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],
+ sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));
+
+}
+
+void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ writeb(1, sram + SWITCH_PRIORITY_INTRS_STATUS_OFFSET);
+ writeb(1, sram + SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET);
}
static int icssm_prueth_sw_port_config(struct prueth *prueth,
@@ -975,6 +1177,109 @@ static int icssm_prueth_sw_port_config(struct prueth *prueth,
return 0;
}
+/* Configure TX/RX queue contexts and buffer descriptor tables for LRE port */
+static int icssm_prueth_lre_port_config(struct prueth *prueth,
+ enum prueth_port port_id)
+{
+ unsigned int tx_context_ofs_addr, rx_context_ofs, queue_desc_ofs;
+ void __iomem *dram, *dram_base, *dram_mac;
+ struct prueth_emac *emac;
+
+ emac = prueth->emac[port_id - 1];
+ switch (port_id) {
+ case PRUETH_PORT_MII0:
+ tx_context_ofs_addr = TX_CONTEXT_P1_Q1_OFFSET_ADDR;
+ rx_context_ofs = P1_Q1_RX_CONTEXT_OFFSET;
+ queue_desc_ofs = P1_QUEUE_DESC_OFFSET;
+ /* for switch PORT MII0 mac addr is in DRAM0. */
+ dram_mac = prueth->mem[PRUETH_MEM_DRAM0].va;
+ break;
+ case PRUETH_PORT_MII1:
+ tx_context_ofs_addr = TX_CONTEXT_P2_Q1_OFFSET_ADDR;
+ rx_context_ofs = P2_Q1_RX_CONTEXT_OFFSET;
+ queue_desc_ofs = P2_QUEUE_DESC_OFFSET;
+
+ /* for switch PORT MII1 mac addr is in DRAM1. */
+ dram_mac = prueth->mem[PRUETH_MEM_DRAM1].va;
+ break;
+ default:
+ netdev_err(emac->ndev, "invalid port\n");
+ return -EINVAL;
+ }
+
+ /* setup mac address */
+ memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, ETH_ALEN);
+
+ /* Remaining switch port configs are in DRAM1 */
+ dram_base = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ /* queue information table */
+ memcpy_toio(dram_base + tx_context_ofs_addr,
+ lre_queue_infos[port_id],
+ sizeof(lre_queue_infos[port_id]));
+
+ memcpy_toio(dram_base + rx_context_ofs,
+ lre_rx_queue_infos[port_id],
+ sizeof(lre_rx_queue_infos[port_id]));
+
+ /* buffer descriptor offset table*/
+ dram = dram_base + QUEUE_DESCRIPTOR_OFFSET_ADDR +
+ (port_id * NUM_QUEUES * sizeof(u16));
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_desc_offset,
+ dram);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_desc_offset,
+ dram + 2);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_desc_offset,
+ dram + 4);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_desc_offset,
+ dram + 6);
+
+ /* buffer offset table */
+ dram = dram_base + QUEUE_OFFSET_ADDR +
+ port_id * NUM_QUEUES * sizeof(u16);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_offset, dram);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_offset,
+ dram + 2);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_offset,
+ dram + 4);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_offset,
+ dram + 6);
+
+ /* queue size lookup table */
+ dram = dram_base + QUEUE_SIZE_ADDR +
+ port_id * NUM_QUEUES * sizeof(u16);
+ writew(HOST_QUEUE_1_SIZE, dram);
+ writew(HOST_QUEUE_2_SIZE, dram + 2);
+ writew(QUEUE_3_TXOPT_SIZE, dram + 4);
+ writew(QUEUE_4_TXOPT_SIZE, dram + 6);
+
+ /* queue table */
+ memcpy_toio(dram_base + queue_desc_ofs,
+ &hsr_prp_txopt_queue_descs[port_id][0],
+ 4 * sizeof(hsr_prp_txopt_queue_descs[port_id][0]));
+
+ /* In HSR/PRP mode both slave ports share the host receive queue
+ * descriptor region (P0_QUEUE_DESC_OFFSET). The firmware arbitrates
+ * ownership; the driver always reads from the same host-side descriptor
+ * base regardless of which physical port the frame arrived on.
+ */
+ emac->rx_queue_descs = dram_base + P0_QUEUE_DESC_OFFSET;
+ emac->tx_queue_descs = dram_base +
+ lre_rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset;
+
+ if (port_id == PRUETH_PORT_MII0) {
+ emac->tx_queue_descs_other_port = dram_base +
+ lre_rx_queue_infos
+ [port_id + 1][PRUETH_QUEUE1].queue_desc_offset;
+ } else if (port_id == PRUETH_PORT_MII1) {
+ emac->tx_queue_descs_other_port = dram_base +
+ lre_rx_queue_infos
+ [port_id - 1][PRUETH_QUEUE1].queue_desc_offset;
+ }
+
+ return 0;
+}
+
int icssm_prueth_sw_emac_config(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
@@ -989,7 +1294,10 @@ int icssm_prueth_sw_emac_config(struct prueth_emac *emac)
if (prueth->emac_configured & BIT(emac->port_id))
return 0;
- ret = icssm_prueth_sw_port_config(prueth, emac->port_id);
+ if (prueth_is_lre(prueth))
+ ret = icssm_prueth_lre_port_config(prueth, emac->port_id);
+ else
+ ret = icssm_prueth_sw_port_config(prueth, emac->port_id);
if (ret)
return ret;
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
index e6111bba166e3..a009827b01d79 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
@@ -17,6 +17,7 @@ u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth,
enum prueth_port port);
extern const struct prueth_queue_info sw_queue_infos[][4];
+extern const struct prueth_queue_info lre_queue_infos[][4];
void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth);
int icssm_prueth_sw_init_fdb_table(struct prueth *prueth);
@@ -29,6 +30,7 @@ void icssm_prueth_sw_fdb_del(struct prueth_emac *emac,
int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac);
int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac);
void icssm_prueth_sw_hostconfig(struct prueth *prueth);
+void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth);
int icssm_prueth_sw_emac_config(struct prueth_emac *emac);
int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev);
int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac,
diff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h
index 5ba9ce14da445..b0d623b4979b0 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_switch.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_switch.h
@@ -24,6 +24,9 @@
#define QUEUE_3_SIZE 97 /* Protocol specific */
#define QUEUE_4_SIZE 97 /* NRT (IP,ARP, ICMP) */
+#define QUEUE_3_TXOPT_SIZE 194 /* Protocol specific - High Priority */
+#define QUEUE_4_TXOPT_SIZE 194 /* NRT(IP,ARP, ICMP) - Low Priority*/
+
/* Host queue size (number of BDs). Each BD points to data buffer of 32 bytes.
* HOST PORT QUEUES can buffer up to 4 full sized frames per queue
*/
@@ -49,20 +52,18 @@
* For RED, NodeTable lookup was successful.
* 7 Flood Packet should be flooded (destination MAC
* address found in FDB). For switch only.
- * 8..12 Block_length number of valid bytes in this specific block.
- * Will be <=32 bytes on last block of packet
+ * 8 RED_INFO Set if the frame carries an HSR or PRP
+ * redundancy tag
+ * 10 HostRecv Set if the frame is destined for the host port
* 13 More "More" bit indicating that there are more blocks
* 14 Shadow indicates that "index" is pointing into shadow
* buffer
* 15 TimeStamp indicates that this packet has time stamp in
* separate buffer - only needed if PTP runs on
* host
- * 16..17 Port different meaning for ingress and egress,
- * Ingress: Port = 0 indicates phy port 1 and
- * Port = 1 indicates phy port 2.
- * Egress: 0 sends on phy port 1 and 1 sends on
- * phy port 2. Port = 2 goes over MAC table
- * look-up
+ * 16..17 LAN Destination LAN for transmission:
+ * bit 16 = LAN A, bit 17 = LAN B, set both to
+ * duplicate to both LANs.
* 18..28 Length 11 bit of total packet length which is put into
* first BD only so that host access only one BD
* 29 VlanTag indicates that packet has Length/Type field of
@@ -86,14 +87,21 @@
#define PRUETH_BD_SW_FLOOD_MASK BIT(7)
#define PRUETH_BD_SW_FLOOD_SHIFT 7
+#define PRUETH_BD_RED_PKT_MASK BIT(8)
+#define PRUETH_BD_RED_PKT 8
+
+#define PRUETH_BD_HOST_RECV_MASK BIT(10)
+#define PRUETH_BD_HOST_RECV_SHIFT 10
+
#define PRUETH_BD_SHADOW_MASK BIT(14)
#define PRUETH_BD_SHADOW_SHIFT 14
#define PRUETH_BD_TIMESTAMP_MASK BIT(15)
#define PRUETH_BD_TIMESTAMP_SHIFT 15
-#define PRUETH_BD_PORT_MASK GENMASK(17, 16)
-#define PRUETH_BD_PORT_SHIFT 16
+#define PRUETH_BD_LAN_INFO_MASK GENMASK(17, 16)
+#define PRUETH_BD_LAN_A_SHIFT 16
+#define PRUETH_BD_LAN_B_SHIFT 17
#define PRUETH_BD_LENGTH_MASK GENMASK(28, 18)
#define PRUETH_BD_LENGTH_SHIFT 18
@@ -298,6 +306,9 @@
#define P0_Q4_BD_OFFSET (P0_Q3_BD_OFFSET + HOST_QUEUE_3_SIZE * BD_SIZE)
#define P0_Q3_BD_OFFSET (P0_Q2_BD_OFFSET + HOST_QUEUE_2_SIZE * BD_SIZE)
#define P0_Q2_BD_OFFSET (P0_Q1_BD_OFFSET + HOST_QUEUE_1_SIZE * BD_SIZE)
+#define P1_Q3_TXOPT_BD_OFFSET (P0_Q4_BD_OFFSET + HOST_QUEUE_4_SIZE * BD_SIZE)
+#define P2_Q1_TXOPT_BD_OFFSET (P1_Q3_TXOPT_BD_OFFSET + \
+ QUEUE_3_TXOPT_SIZE * BD_SIZE)
#define P0_Q1_BD_OFFSET P0_BUFFER_DESC_OFFSET
#define P0_BUFFER_DESC_OFFSET SRAM_START_OFFSET
@@ -328,9 +339,19 @@
ICSS_BLOCK_SIZE)
#define P0_Q2_BUFFER_OFFSET (P0_Q1_BUFFER_OFFSET + HOST_QUEUE_1_SIZE * \
ICSS_BLOCK_SIZE)
+#define P1_Q3_TXOPT_BUFFER_OFFSET (P0_Q4_BUFFER_OFFSET + \
+ HOST_QUEUE_4_SIZE * ICSS_BLOCK_SIZE)
+#define P2_Q1_TXOPT_BUFFER_OFFSET (P1_Q3_TXOPT_BUFFER_OFFSET + \
+ QUEUE_3_TXOPT_SIZE * ICSS_BLOCK_SIZE)
#define P0_COL_BUFFER_OFFSET 0xEE00
#define P0_Q1_BUFFER_OFFSET 0x0000
+/* Enable/disable interrupts for high/low priority instead of per port.
+ * 0 = disabled (default), 1 = enabled
+ */
+#define SWITCH_PRIORITY_INTRS_STATUS_OFFSET 0x1FAA
+#define SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET 0x1FAB
+
#define V2_1_FDB_TBL_LOC PRUETH_MEM_SHARED_RAM
#define V2_1_FDB_TBL_OFFSET 0x2000
|
| 3/2 | 2026/08/04 18:09 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40394 output: 37 thoughts: 372 Thoughts: **Assessing the Reachability and Functionality of a New HSR/PRP Patch for the TI ICSSM Driver** Okay, so there's a patch series applied to the `drivers/net/ethernet/ti/icssm/` directory. The commit message mentions "syz-cluster" and a patch under review, which adds HSR/PRP support to the TI ICSSM Ethernet driver, specifically `icssm_prueth`. They've introduced new files like `icssm_prueth_lre.c`, `icssm_prueth_common.c`, and `icssm_lre_firmware.h`, and modified existing ones, like `icssm_prueth.c` and `icssm_prueth_switch.c`. My immediate concern is whether we can actually test this. The driver itself, `drivers/net/ethernet/ti/icssm/icssm_prueth.c`, is a TI PRU Ethernet driver. This means we're dealing with the TI PRU-ICSS (Programmable Real-Time Unit Subsystem) Ethernet. These drivers tend to be hardware-specific, targeting TI SoCs like AM335x, AM437x, or AM57xx. The real question is: can we reach this code and confirm functionality? The challenge, as I see it, is the virtualization aspect. Are we going to have an easy time emulating this? Full emulation of TI SoCs, particularly the PRU-ICSS Ethernet, in standard QEMU is usually a problem. I'll need to check if there's any QEMU emulation for PRU-ICSS that's available. To get a quick idea, I'll search the kernel tree for dependencies, maybe search for `ti-pruss` or `icssm` in the code, or look into the Kconfig files, like `drivers/net/ethernet/ti/Kconfig`, to see if there's something to indicate the availability of the emulation for this type of hardware. From there, I can determine if we can properly validate this patch series. |
| 4/2 | 2026/08/04 18:09 | tool | grepper |
0mArgs:{
"Expression": "config TI_PRUETH",
"PathPrefix": "drivers/net/ethernet/ti/Kconfig"
}Results: {
"Output": "drivers/net/ethernet/ti/Kconfig=222=config TI_ICSS_IEP\n--\ndrivers/net/ethernet/ti/Kconfig-233-\ndrivers/net/ethernet/ti/Kconfig:234:config TI_PRUETH\ndrivers/net/ethernet/ti/Kconfig-235-\ttristate \"TI PRU Ethernet EMAC driver\"\n"
} |
| 5/2 | 2026/08/04 18:09 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41377 output: 37 thoughts: 14 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing the `TI_PRUETH` Dependencies** Okay, so I'm looking at `TI_PRUETH`. My initial thought is to immediately dive into its dependencies. I need to understand what this module relies on – is it low-level hardware drivers, specific network stacks, perhaps some RTOS components? I'm thinking I'll need to trace the connections, map out the relationships between different modules and libraries. The goal here is to get a handle on the full scope of `TI_PRUETH`'s functionality and its potential points of vulnerability or customization. I'll need to consider how this Ethernet driver interacts with the underlying hardware, how it handles DMA, and what its interrupt handling looks like. I'll probably start with the most obvious headers and symbol definitions to build a dependency graph. |
| 6/2 | 2026/08/04 18:09 | tool | grepper |
0mArgs:{
"Expression": "config TI_PRUETH",
"PathPrefix": "drivers/net/ethernet/ti/Kconfig"
}Results: {
"Output": "drivers/net/ethernet/ti/Kconfig=222=config TI_ICSS_IEP\n--\ndrivers/net/ethernet/ti/Kconfig-233-\ndrivers/net/ethernet/ti/Kconfig:234:config TI_PRUETH\ndrivers/net/ethernet/ti/Kconfig-235-\ttristate \"TI PRU Ethernet EMAC driver\"\n"
} |
| 7/2 | 2026/08/04 18:09 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41725 output: 39 thoughts: 25 Thoughts: Here's my thought process, summarized as you requested: **Kconfig Review - A Quick Check** Okay, before diving too deep, I should probably do a quick sanity check. I'm thinking I need to refresh my memory on the TI Ethernet driver configuration. Specifically, I'm going to take a look at the `drivers/net/ethernet/ti/Kconfig` file. I'll need to jump around line 234 in that file; I seem to recall that's where a crucial section is located that I need to confirm. This is just a preliminary step to ensure I'm remembering the relevant configuration options correctly before I proceed further. |
| 8/2 | 2026/08/04 18:09 | tool | read-file |
0mArgs:{
"File": "drivers/net/ethernet/ti/Kconfig",
"FirstLine": 230,
"LineCount": 20
}Results: {
"Contents": " 230:\t\n 231:\t\t To compile this driver as a module, choose M here. The module\n 232:\t\t will be called icss_iep.\n 233:\t\n 234:\tconfig TI_PRUETH\n 235:\t\ttristate \"TI PRU Ethernet EMAC driver\"\n 236:\t\tdepends on PRU_REMOTEPROC\n 237:\t\tdepends on NET_SWITCHDEV\n 238:\t\tselect TI_ICSS_IEP\n 239:\t\tdepends on PTP_1588_CLOCK_OPTIONAL\n 240:\t\thelp\n 241:\t\t Some TI SoCs has Programmable Realtime Unit (PRU) cores which can\n 242:\t\t support Single or Dual Ethernet ports with the help of firmware code\n 243:\t\t running on PRU cores. This driver supports remoteproc based\n 244:\t\t communication to PRU firmware to expose Ethernet interface to Linux.\n 245:\t\n 246:\tendif # NET_VENDOR_TI\n"
} |
| 9/2 | 2026/08/04 18:09 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 42205 output: 51 thoughts: 193 Thoughts: **Fuzzing the PRU RemoteProc Driver in QEMU** Okay, so I'm looking at this `PRU_REMOTEPROC` driver, and immediately my expert brain flags this as potentially tricky. This is a TI SoC-specific hardware driver, and that means it’s going to be a real pain to fuzz properly in QEMU. I know from experience that these types of drivers, the ones that rely on specific hardware features or remoteproc firmware like `ti-pruss/am335x-pru0-prueth-fw.elf`, are usually dead in the water without the actual hardware or a very accurate hardware emulation. The driver utilizes standard remoteproc functions like `rproc_shutdown` and `rproc_boot`, indicating a clear dependency on the PRU remoteproc driver for firmware loading. Without the underlying hardware, the `probe` function is almost guaranteed to stumble. It will likely fail to detect the relevant device tree nodes or fall apart trying to initialize the hardware. Time to confirm my suspicions. I'll need to dig into the `icssm_prueth_probe` function within the `drivers/net/ethernet/ti/icssm/icssm_prueth.c` source code. That should provide a concrete idea of how much hardware dependency there actually is. This is where the challenge lies, but I have a few ideas on how to approach this. |
| 10/2 | 2026/08/04 18:09 | tool | grepper |
0mArgs:{
"Expression": "icssm_prueth_probe",
"PathPrefix": "drivers/net/ethernet/ti/icssm/icssm_prueth.c"
}Results: {
"Output": "drivers/net/ethernet/ti/icssm/icssm_prueth.c=2324=static int icssm_prueth_register_notifiers(struct prueth *prueth)\n--\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c-2342-\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c:2343:static int icssm_prueth_probe(struct platform_device *pdev)\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c-2344-{\n--\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c=2874=static struct platform_driver prueth_driver = {\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c:2875:\t.probe = icssm_prueth_probe,\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c-2876-\t.remove = icssm_prueth_remove,\n"
} |
| 11/2 | 2026/08/04 18:09 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43057 output: 51 thoughts: 15 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Initial Assessment: Navigating the Landscape** Alright, the instruction is to read `icssm_prueth_probe`. Immediately, my mental gears kick into motion. The name itself suggests a specific, likely technical, element. Given my experience, "icssm" is highly suggestive - it's probably an acronym for something specialized, possibly relating to integrated circuits, signal measurement, or a specific research domain I'm familiar with. "Prueth" is likely the surname of someone in the same field; it makes me think it is a test procedure or configuration created by the same person, as well as an associated probe. "Probe" is a keyword. Clearly, this isn't some generic text, I need to understand its context. |
| 12/2 | 2026/08/04 18:10 | tool | read-file |
0mArgs:{
"File": "drivers/net/ethernet/ti/icssm/icssm_prueth.c",
"FirstLine": 2343,
"LineCount": 50
}Results: {
"Contents": "2343:\tstatic int icssm_prueth_probe(struct platform_device *pdev)\n2344:\t{\n2345:\t\tstruct device_node *eth0_node = NULL, *eth1_node = NULL;\n2346:\t\tstruct device_node *eth_node, *eth_ports_node;\n2347:\t\tenum pruss_pru_id pruss_id0, pruss_id1;\n2348:\t\tstruct device *dev = \u0026pdev-\u003edev;\n2349:\t\tstruct device_node *np;\n2350:\t\tstruct prueth *prueth;\n2351:\t\tbool has_lre = false;\n2352:\t\tstruct pruss *pruss;\n2353:\t\tint i, ret;\n2354:\t\n2355:\t\tnp = dev-\u003eof_node;\n2356:\t\tif (!np)\n2357:\t\t\treturn -ENODEV; /* we don't support non DT */\n2358:\t\n2359:\t\tprueth = devm_kzalloc(dev, sizeof(*prueth), GFP_KERNEL);\n2360:\t\tif (!prueth)\n2361:\t\t\treturn -ENOMEM;\n2362:\t\n2363:\t\tplatform_set_drvdata(pdev, prueth);\n2364:\t\tprueth-\u003edev = dev;\n2365:\t\tprueth-\u003efw_data = device_get_match_data(dev);\n2366:\t\n2367:\t\tif (prueth-\u003efw_data-\u003efw_rev == FW_REV_V1_0)\n2368:\t\t\tprueth-\u003efw_offsets = fw_offsets_v1_0;\n2369:\t\telse if (prueth-\u003efw_data-\u003efw_rev == FW_REV_V2_1)\n2370:\t\t\tprueth-\u003efw_offsets = fw_offsets_v2_1;\n2371:\t\telse\n2372:\t\t\treturn -EINVAL;\n2373:\t\n2374:\t\teth_ports_node = of_get_child_by_name(np, \"ethernet-ports\");\n2375:\t\tif (!eth_ports_node)\n2376:\t\t\treturn -ENOENT;\n2377:\t\n2378:\t\tfor_each_child_of_node(eth_ports_node, eth_node) {\n2379:\t\t\tu32 reg;\n2380:\t\n2381:\t\t\tif (strcmp(eth_node-\u003ename, \"ethernet-port\"))\n2382:\t\t\t\tcontinue;\n2383:\t\t\tret = of_property_read_u32(eth_node, \"reg\", \u0026reg);\n2384:\t\t\tif (ret \u003c 0) {\n2385:\t\t\t\tdev_err(dev, \"%pOF error reading port_id %d\\n\",\n2386:\t\t\t\t\teth_node, ret);\n2387:\t\t\t\tof_node_put(eth_node);\n2388:\t\t\t\tof_node_put(eth_ports_node);\n2389:\t\t\t\treturn ret;\n2390:\t\t\t}\n2391:\t\n2392:\t\t\tof_node_get(eth_node);\n"
} |
| 13/2 | 2026/08/04 18:10 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 44151 output: 104 thoughts: 417 Thoughts: **WorthFuzzing=false: TI PRU Ethernet Driver** Okay, let's break this down. This driver, from what I'm seeing, heavily depends on the device tree, specifically using `dev->of_node`. I know from experience that in standard QEMU setups, the necessary device tree nodes for this TI PRU Ethernet hardware just aren't going to be there. We're also looking at dependencies on `PRU_REMOTEPROC` and specific firmware files. The question is, can we even *reach* this code with syzkaller? My initial thought was maybe syzkaller could inject device tree nodes somehow using `syz_init_net_socket` or something similar, but that's a dead end. I know syzkaller has some tricks with `syz_usb_connect` and `syz_80211_inject`, but for platform devices, it's pretty much stuck with what's available in the QEMU machine or what you can create with sysfs/netlink. Wait, is there a general way to inject platform devices? No, not arbitrarily, especially not with specific MMIO regions and remoteproc dependencies. Therefore, this code path is pretty much unreachable in a virtualized environment like GCE or QEMU, which brings me to the instructions. They clearly say "WorthFuzzing=false if the patch only contains... code paths that are impossible to reach in virtualized environments like GCE or QEMU...". This is a vendor-specific SoC Ethernet driver, specifically the TI PRU Ethernet, that *needs* specific hardware (PRU-ICSS) and firmware. This isn't something that can be emulated in standard QEMU. Let me just double-check to see if there's any generic part I'm missing. Right, the patch modifies `drivers/net/ethernet/ti/icssm/`. It's all within the TI driver, so it's clearly hardware-specific. Given all that, I'm confident in returning `WorthFuzzing=false`. |
| 14/2 | 2026/08/04 18:10 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds HSR/PRP support to the TI PRU-ICSS Ethernet driver (icssm_prueth). This is a vendor-specific SoC Ethernet driver that requires physical TI hardware (PRU-ICSS) and specific remoteproc firmware. It cannot be reached or tested in standard virtualized environments like QEMU or GCE.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds HSR/PRP support to the TI PRU-ICSS Ethernet driver (icssm_prueth). This is a vendor-specific SoC Ethernet driver that requires physical TI hardware (PRU-ICSS) and specific remoteproc firmware. It cannot be reached or tested in standard virtualized environments like QEMU or GCE.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|