AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/06/30 20:46 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the TI ICSSM PRU Ethernet driver to add HSR/PRP Link Redundancy Entity (LRE) support. This is a hardware-specific driver for Texas Instruments SoCs (AM335x, AM437x, AM57xx) that requires the Programmable Real-Time Unit Subsystem (PRUSS) hardware. Since there is no software emulation for this hardware in standard virtualized environments (like GCE or QEMU), the driver cannot probe and the modified code paths are impossible to reach during fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/06/30 20:46 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 2294a1f44e63ce8d93339ade7c82a77d2518baf5\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Jun 30 20:46:16 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 f4276c9a776205..b6651fe73afd8a 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 00000000000000..b5ab0ec87c5f7e\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 b7e94244355a3b..cbe666a212c3b0 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,15 @@ 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 }\n \n static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable)\n@@ -507,15 +588,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 +613,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 +633,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 +659,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 +728,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 +794,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 +845,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 +878,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 +909,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 +946,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 +976,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@@ -804,13 +1045,12 @@ 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@@ -912,6 +1152,25 @@ static int icssm_emac_request_irqs(struct prueth_emac *emac)\n \treturn ret;\n }\n \n+static int icssm_emac_sanitize_feature_flags(struct prueth_emac *emac)\n+{\n+\tnetdev_features_t request_lre;\n+\n+\trequest_lre = emac-\u003endev-\u003efeatures \u0026 NETIF_PRUETH_LRE_OFFLOAD_FEATURES;\n+\n+\tif (prueth_is_lre(emac-\u003eprueth) \u0026\u0026 !request_lre) {\n+\t\tnetdev_err(emac-\u003endev, \"Error: Turn ON HSR offload\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!prueth_is_lre(emac-\u003eprueth) \u0026\u0026 request_lre) {\n+\t\tnetdev_err(emac-\u003endev, \"Error: Turn OFF HSR offload\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n /* Function to free memory related to sw */\n static void icssm_prueth_free_memory(struct prueth *prueth)\n {\n@@ -978,18 +1237,28 @@ static int icssm_emac_ndo_open(struct net_device *ndev)\n \t/* set h/w MAC as user might have re-configured */\n \tether_addr_copy(emac-\u003emac_addr, ndev-\u003edev_addr);\n \n+\tret = icssm_emac_sanitize_feature_flags(emac);\n+\tif (ret)\n+\t\treturn ret;\n+\n \tif (!prueth-\u003eemac_configured)\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 +1283,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 +1318,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 +1339,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 +1363,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))\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 +1432,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,18 +1516,30 @@ 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+\traw_spinlock_t *lock_queue;\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+\traw_spin_lock(lock_queue);\n+\tret = icssm_prueth_tx_enqueue(emac, skb, qid);\n+\traw_spin_unlock(lock_queue);\n+\tif (ret)\n+\t\tgoto fail_tx;\n+\n \temac-\u003estats.tx_packets++;\n \temac-\u003estats.tx_bytes += skb-\u003elen;\n \tdev_kfree_skb_any(skb);\n@@ -1266,11 +1589,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 +1617,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 +1633,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 +1652,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 +1701,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 +1714,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 +1777,73 @@ 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+/**\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)) {\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 };\n \n /* get emac_port corresponding to eth_node name */\n@@ -1554,6 +1960,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+\traw_spin_lock_init(\u0026emac-\u003ehost_queue_lock[0]);\n+\traw_spin_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 +1998,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 +2036,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 +2051,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 +2177,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(\u0026emac-\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(\u0026emac-\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(\u0026emac-\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(\u0026emac-\u003eaddr_lock, flags);\n+\n+\tprueth-\u003ehsr_dev = NULL;\n+\tprueth-\u003ehsr_members \u0026= ~BIT(emac-\u003eport_id);\n+\n+\tspin_unlock_irqrestore(\u0026emac-\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(\u0026emac-\u003eaddr_lock, flags);\n+\n+\tprueth-\u003ehsr_members \u0026= ~BIT(emac-\u003eport_id);\n+\n+\tspin_unlock_irqrestore(\u0026emac-\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(\u0026emac-\u003eaddr_lock, flags);\n+\n+\tif (!prueth-\u003ehsr_members)\n+\t\tprueth-\u003ehsr_dev = NULL;\n+\n+\tspin_unlock_irqrestore(\u0026emac-\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 +2289,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 +2342,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 +2358,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 +2509,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+\traw_spin_lock_init(\u0026prueth-\u003elre_host_queue_lock[0]);\n+\traw_spin_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 +2778,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 +2806,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 +2834,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 d5b49b462c242f..129844cbf1e835 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,12 +208,16 @@ 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@@ -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+\traw_spinlock_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+\traw_spinlock_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 00000000000000..bfd48f656f22b6\n--- /dev/null\n+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c\n@@ -0,0 +1,283 @@\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 net_device_stats *ndevstats, *ndevstats_o, *ndevstats_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+\tndevstats = \u0026emac-\u003endev-\u003estats;\n+\tndevstats_o = \u0026other_emac-\u003endev-\u003estats;\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\temac-\u003endev-\u003estats.rx_over_errors += overflow_cnt;\n+\t\twriteb(0, \u0026queue_desc-\u003eoverflow_cnt);\n+\t}\n+\tif (overflow_cnt_o \u003e 0) {\n+\t\tother_emac-\u003endev-\u003estats.rx_over_errors += overflow_cnt_o;\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\tndevstats_p = ndevstats;\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\tndevstats_p = ndevstats_o;\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\tndevstats_p-\u003erx_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\tndevstats_p-\u003erx_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 00000000000000..239542101943ec\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 00000000000000..0fe4d1ae582343\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 66866ea37913fb..1b2486170ab3cb 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,15 @@ 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 static int icssm_prueth_sw_port_config(struct prueth *prueth,\n@@ -975,6 +1169,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 +1286,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 e6111bba166e3f..0f4595c6075f3a 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);\ndiff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h\nindex 5ba9ce14da4456..089e43cadc2527 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,6 +339,10 @@\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"
}

2/1 2026/06/30 20:46 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the TI ICSSM PRU Ethernet driver to add HSR/PRP Link Redundancy Entity (LRE) support. This is a hardware-specific driver for Texas Instruments SoCs (AM335x, AM437x, AM57xx) that requires the Programmable Real-Time Unit Subsystem (PRUSS) hardware. Since there is no software emulation for this hardware in standard virtualized environments (like GCE or QEMU), the driver cannot probe and the modified code paths are impossible to reach during fuzzing.",
  "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).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 2294a1f44e63ce8d93339ade7c82a77d2518baf5
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Jun 30 20:46:16 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
index f4276c9a776205..b6651fe73afd8a 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 00000000000000..b5ab0ec87c5f7e
--- /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 b7e94244355a3b..cbe666a212c3b0 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,15 @@ 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);
 }
 
 static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable)
@@ -507,15 +588,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 +613,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 +633,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 +659,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 +728,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 +794,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 +845,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 +878,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 +909,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 +946,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 +976,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 */
@@ -804,13 +1045,12 @@ 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++;
@@ -912,6 +1152,25 @@ static int icssm_emac_request_irqs(struct prueth_emac *emac)
 	return ret;
 }
 
+static int icssm_emac_sanitize_feature_flags(struct prueth_emac *emac)
+{
+	netdev_features_t request_lre;
+
+	request_lre = emac->ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
+
+	if (prueth_is_lre(emac->prueth) && !request_lre) {
+		netdev_err(emac->ndev, "Error: Turn ON HSR offload\n");
+		return -EINVAL;
+	}
+
+	if (!prueth_is_lre(emac->prueth) && request_lre) {
+		netdev_err(emac->ndev, "Error: Turn OFF HSR offload\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /* Function to free memory related to sw */
 static void icssm_prueth_free_memory(struct prueth *prueth)
 {
@@ -978,18 +1237,28 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
 	/* set h/w MAC as user might have re-configured */
 	ether_addr_copy(emac->mac_addr, ndev->dev_addr);
 
+	ret = icssm_emac_sanitize_feature_flags(emac);
+	if (ret)
+		return ret;
+
 	if (!prueth->emac_configured)
 		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 +1283,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 +1318,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 +1339,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 +1363,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))
+		icssm_prueth_lre_cleanup(prueth);
 
 	/* free memory related to sw */
 	icssm_prueth_free_memory(emac->prueth);
@@ -1122,7 +1432,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,18 +1516,30 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,
 						struct net_device *ndev)
 {
 	struct prueth_emac *emac = netdev_priv(ndev);
+	raw_spinlock_t *lock_queue;
 	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;
 	}
 
+	raw_spin_lock(lock_queue);
+	ret = icssm_prueth_tx_enqueue(emac, skb, qid);
+	raw_spin_unlock(lock_queue);
+	if (ret)
+		goto fail_tx;
+
 	emac->stats.tx_packets++;
 	emac->stats.tx_bytes += skb->len;
 	dev_kfree_skb_any(skb);
@@ -1266,11 +1589,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 +1617,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 +1633,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 +1652,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 +1701,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 +1714,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 +1777,73 @@ 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);
+}
+
+/**
+ * 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)) {
+		/* 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,
 };
 
 /* get emac_port corresponding to eth_node name */
@@ -1554,6 +1960,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
 	spin_lock_init(&emac->lock);
 	spin_lock_init(&emac->addr_lock);
 
+	raw_spin_lock_init(&emac->host_queue_lock[0]);
+	raw_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 +1998,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 +2036,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 +2051,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 +2177,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(&emac->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(&emac->addr_lock, flags);
+			return -EOPNOTSUPP;
+		}
+	}
+
+	prueth->hsr_members |= BIT(emac->port_id);
+
+	spin_unlock_irqrestore(&emac->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(&emac->addr_lock, flags);
+
+	prueth->hsr_dev = NULL;
+	prueth->hsr_members &= ~BIT(emac->port_id);
+
+	spin_unlock_irqrestore(&emac->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(&emac->addr_lock, flags);
+
+	prueth->hsr_members &= ~BIT(emac->port_id);
+
+	spin_unlock_irqrestore(&emac->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(&emac->addr_lock, flags);
+
+	if (!prueth->hsr_members)
+		prueth->hsr_dev = NULL;
+
+	spin_unlock_irqrestore(&emac->addr_lock, flags);
+
+	return 0;
+}
+
 static int icssm_prueth_ndev_event(struct notifier_block *unused,
 				   unsigned long event, void *ptr)
 {
@@ -1754,6 +2289,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 +2342,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 +2358,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 +2509,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);
+	raw_spin_lock_init(&prueth->lre_host_queue_lock[0]);
+	raw_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 +2778,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 +2806,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 +2834,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 d5b49b462c242f..129844cbf1e835 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,12 +208,16 @@ 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 {
@@ -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 */
 
+	raw_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 */
+	raw_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 00000000000000..bfd48f656f22b6
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
@@ -0,0 +1,283 @@
+// 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 net_device_stats *ndevstats, *ndevstats_o, *ndevstats_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];
+	ndevstats = &emac->ndev->stats;
+	ndevstats_o = &other_emac->ndev->stats;
+
+	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) {
+		emac->ndev->stats.rx_over_errors += overflow_cnt;
+		writeb(0, &queue_desc->overflow_cnt);
+	}
+	if (overflow_cnt_o > 0) {
+		other_emac->ndev->stats.rx_over_errors += overflow_cnt_o;
+		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;
+			ndevstats_p = ndevstats;
+			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;
+			ndevstats_p = ndevstats_o;
+			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;
+			ndevstats_p->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;
+			ndevstats_p->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 00000000000000..239542101943ec
--- /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 00000000000000..0fe4d1ae582343
--- /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 66866ea37913fb..1b2486170ab3cb 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,15 @@ 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]));
+
 }
 
 static int icssm_prueth_sw_port_config(struct prueth *prueth,
@@ -975,6 +1169,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 +1286,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 e6111bba166e3f..0f4595c6075f3a 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);
diff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h
index 5ba9ce14da4456..089e43cadc2527 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,6 +339,10 @@
 				 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
 

3/2 2026/06/30 20:46 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38461
							output: 25
							thoughts: 526
						

Thoughts:
**Analyzing the New HSR/PRP LRE Support in the TI ICSSM Ethernet Driver**

Okay, so we have a patch that adds HSR/PRP Link Redundancy Entity (LRE) support to the TI ICSSM Ethernet driver. The files modified and added look standard for this type of feature, but what's the likelihood we can actually fuzz this? The driver itself is clearly hardware-specific, intended for TI PRU Ethernet on their SoCs – AM335x, AM437x, AM57xx. My immediate thought is that this isn't going to be reachable in a virtualized environment like GCE or QEMU, unless there's a hardware emulator or it's a virtual driver.

The core of the issue is the dependency on the PRUSS – the Programmable Real-Time Unit Subsystem. The `icssm_prueth_probe` function is looking for specific device tree nodes like "ti,am3359-prueth", and "ti,am4376-prueth" and "ti,am5728-prueth" and it's using functions like `pruss_get` and `pruss_request_mem_region` which means it needs the actual hardware. Now, I'm pretty sure there's no upstream kernel support for a software emulator of the PRUSS, so there's no way this driver is going to probe and instantiate network devices on a generic x86_64 or arm64 VM.

So, realistically, since the probe fails, the netdevs won't be created, and the main code isn't reachable. I need to make *absolutely* sure, though. Let me double-check once more for any software emulation of the PRUSS; maybe there's something hiding that I'm missing, but I'm not optimistic.



4/2 2026/06/30 20:46 tool grepper
0m Args:
{
  "Expression": "ti,am3359-prueth"
}

Results:
{
  "Output": "Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml=19=properties:\n--\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml-23-      - ti,am4376-prueth   # for AM43x SoC family\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml:24:      - ti,am3359-prueth   # for AM33x SoC family\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml-25-\n--\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml=112=examples:\n--\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml-194-    pruss_eth: pruss-eth {\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml:195:      compatible = \"ti,am3359-prueth\";\nDocumentation/devicetree/bindings/net/ti,icssm-prueth.yaml-196-      ti,prus = \u003c\u0026pru0\u003e, \u003c\u0026pru1\u003e;\n--\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c=2860=static const struct of_device_id prueth_dt_match[] = {\n--\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c-2862-\t{ .compatible = \"ti,am4376-prueth\", .data = \u0026am437x_prueth_pdata, },\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c:2863:\t{ .compatible = \"ti,am3359-prueth\", .data = \u0026am335x_prueth_pdata, },\ndrivers/net/ethernet/ti/icssm/icssm_prueth.c-2864-\t{ /* sentinel */ }\n"
}

5/2 2026/06/30 20:46 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39963
							output: 148
							thoughts: 42
						

Thoughts:
**My Assessment: Hardware Driver Suitability for Fuzzing**

Alright, looking at this code, it's pretty clear. This is a driver specifically built for Texas Instruments SoCs. And the kicker? There's no software emulator to mimic that hardware. So, any attempt to run or test this in a standard, virtualized environment is just going to hit a brick wall. The code simply won't be reachable, it's locked to the hardware. Considering all of this, I need to flag this as unsuitable for standard fuzzing techniques. Therefore, I will return `WorthFuzzing=false`. It's a waste of resources to even consider it further in this context.



6/2 2026/06/30 20:46 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the TI ICSSM PRU Ethernet driver to add HSR/PRP Link Redundancy Entity (LRE) support. This is a hardware-specific driver for Texas Instruments SoCs (AM335x, AM437x, AM57xx) that requires the Programmable Real-Time Unit Subsystem (PRUSS) hardware. Since there is no software emulation for this hardware in standard virtualized environments (like GCE or QEMU), the driver cannot probe and the modified code paths are impossible to reach during fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the TI ICSSM PRU Ethernet driver to add HSR/PRP Link Redundancy Entity (LRE) support. This is a hardware-specific driver for Texas Instruments SoCs (AM335x, AM437x, AM57xx) that requires the Programmable Real-Time Unit Subsystem (PRUSS) hardware. Since there is no software emulation for this hardware in standard virtualized environments (like GCE or QEMU), the driver cannot probe and the modified code paths are impossible to reach during fuzzing.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)