From nobody Thu Sep 24 20:03:13 2026 Received: from server.couthit.com (server.couthit.com [162.240.164.96]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1058C493D36; Mon, 21 Sep 2026 13:44:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.240.164.96 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998293; cv=none; b=DN5i92vSen4q2S/gGgYKPmqKrd2/YYt2ZeHR/HcgUf4gYLY1UMYtXRyB7TWj67EOJ55revynfgHNJbT/8h8oO8mGs2ewnAQgEzhehBP+LPR0r/N2S/KzErY62H0UjE0/MjszByKDCFSORmMSXb8zn/wh1X2Y1fhRFOkFw+48Kig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998293; c=relaxed/simple; bh=TDO1oGZgcCGv/5m545hGVRhM1TsrMs9jV9bkB0aR9qk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mmrBQCsPGeLvP72bwSubZFs0DOUmm0lXviKKSfFZSb5HM/w8kaJAcepv+xPQDpJ+ts3uJ7NORLqkw0DndhYwmJmfEOE6A7Lx1u4KZJuQz1Bx5khkBJoaAqrsmM0FFxsYIk37vbq1OznWINTeNIppTEoSHAzTb25y36zUNK81xHQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com; spf=pass smtp.mailfrom=couthit.com; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b=BEyXXhs+; arc=none smtp.client-ip=162.240.164.96 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=couthit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b="BEyXXhs+" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=couthit.com ; s=default; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=t0cPZTSp6A1o4RGZoNkdLEuqGep5PwPW5QO5rU+VFAo=; b=BEyXXhs+EFuelXivL60JhsY2TE lF63z8SFSdqU0NWpMh0pYfvvZlY3w4TIO7U6Jfc2aLVLSFyq7u+pDO2zDfazy6/vIeLT9MWbSe0b2 IHmiKwlM68n1u+BSmTEpJmfIhargvW9KV9npJhzQGAOUbBnkHewDxjHcnoXJoBJBXgGN/hIkgdYKs CXOnPfk+d459Hh/mTu0IARe1ufKuxCSRUel/CmQPSMmzooHaWVgWhXnJ2+VRyKVT4h1BwYMqBKQ3b yXkmeL2Kk8xEp+XPl6VdMkFcWumUWVignfFaADdRm8F9VMPwPCrzuex2OMNPhZp3znBir76kaEo0r VKnVag/g==; Received: from [115.246.246.98] (port=57742 helo=cypher.couthit.local) by server.couthit.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.5) (envelope-from ) id 1x8eK2-00000008L1c-0RTs; Mon, 21 Sep 2026 09:44:43 -0400 From: Parvathi Pudi To: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, danishanwar@ti.com, parvathi@couthit.com, rogerq@kernel.org, pmohan@couthit.com, afd@ti.com, vadim.fedorenko@linux.dev, haokexin@gmail.com, basharath@couthit.com, arnd@arndb.de Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org, pratheesh@ti.com, j-rameshbabu@ti.com, vigneshr@ti.com, praneeth@ti.com, srk@ti.com, rogerq@ti.com, m-malladi@ti.com, krishna@couthit.com, mohan@couthit.com Subject: [PATCH net-next v4 1/3] net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for AM57xx, AM437x and AM335x Date: Mon, 21 Sep 2026 19:11:21 +0530 Message-ID: <20260921134343.200426-2-parvathi@couthit.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260921134343.200426-1-parvathi@couthit.com> References: <20260921134343.200426-1-parvathi@couthit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - server.couthit.com X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - couthit.com X-Get-Message-Sender-Via: server.couthit.com: authenticated_id: parvathi@couthit.com X-Authenticated-Sender: server.couthit.com: parvathi@couthit.com X-Source: X-Source-Args: X-Source-Dir: Content-Type: text/plain; charset="utf-8" From: Roger Quadros The PRU-ICSS subsystem on AM335x, AM437x and AM57xx SoCs supports dedicated firmware implementing the IEC 62439-3 redundancy protocols: HSR and PRP. Extend the ICSSM PRUETH driver to enable these operating modes in addition to the existing dual-EMAC and RSTP switch configurations. In both HSR and PRP modes, the two PRU Ethernet ports operate as LRE (Link Redundancy Entity) slave ports, while the host port acts as the master. In case of HW offload mode, Frame duplicate detection/discard for both HSR and PRP and L2 forwarding in case of HSR are handled entirely by firmware within the PRU cores. For HSR, frames received on one PRU port are forwarded to the host and to the peer PRU port (store-and-forward or cut-through), providing a redundant ring path. For PRP, any one of the PRU port forwards received frames to the host after firmware discards the duplicates. The PRU-ICSS subsystem loads the Dual EMAC firmware by default. To enable HSR or PRP functionality, the firmware must be changed accordingly. The required reconfiguration steps are detailed below. To switch from dual-EMAC to HSR (example: eth2 and eth3 as slave raw ports): $ ip link set eth2 down && ip link set eth3 down $ ip link set eth2 address $ ip link set eth3 address $ ethtool -K eth2 hsr-tag-rm-offload on $ ethtool -K eth2 hsr-fwd-offload on $ ethtool -K eth2 hsr-dup-offload on $ ethtool -K eth3 hsr-tag-rm-offload on $ ethtool -K eth3 hsr-fwd-offload on $ ethtool -K eth3 hsr-dup-offload on $ ip link add name hsr0 type hsr slave1 eth2 slave2 eth3 supervison 45 version 1 $ ip link set eth2 up $ ip link set eth3 up To switch from dual-EMAC to PRP (example: eth2 and eth3 as slave raw ports): $ ip link set eth2 down && ip link set eth3 down $ ip link set eth2 address $ ip link set eth3 address $ ethtool -K eth2 hsr-tag-rm-offload on $ ethtool -K eth2 hsr-dup-offload on $ ethtool -K eth3 hsr-tag-rm-offload on $ ethtool -K eth3 hsr-dup-offload on $ ip link add name prp0 type hsr slave1 eth2 slave2 eth3 supervision 45 proto 1 $ ip link set eth2 up $ ip link set eth3 up To revert back to dual-EMAC: $ ip link set eth2 down && ip link set eth3 down $ ip link delete hsr0 or prp0 $ ethtool -K eth2 hsr-tag-rm-offload off $ ethtool -K eth2 hsr-fwd-offload off $ ethtool -K eth2 hsr-dup-offload off $ ethtool -K eth3 hsr-tag-rm-offload off $ ethtool -K eth3 hsr-fwd-offload off $ ethtool -K eth3 hsr-dup-offload off Signed-off-by: Roger Quadros Signed-off-by: Andrew F. Davis Signed-off-by: Parvathi Pudi --- drivers/net/ethernet/ti/Kconfig | 1 + drivers/net/ethernet/ti/Makefile | 2 +- .../ethernet/ti/icssm/icssm_lre_firmware.h | 141 +++++++ drivers/net/ethernet/ti/icssm/icssm_prueth.c | 385 +++++++++++++++++- drivers/net/ethernet/ti/icssm/icssm_prueth.h | 32 +- .../net/ethernet/ti/icssm/icssm_prueth_lre.c | 211 ++++++++++ .../net/ethernet/ti/icssm/icssm_prueth_lre.h | 19 + 7 files changed, 768 insertions(+), 23 deletions(-) create mode 100644 drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kcon= fig index c60b04921c62..afed42572fd1 100644 --- a/drivers/net/ethernet/ti/Kconfig +++ b/drivers/net/ethernet/ti/Kconfig @@ -237,6 +237,7 @@ config TI_PRUETH depends on NET_SWITCHDEV select TI_ICSS_IEP depends on PTP_1588_CLOCK_OPTIONAL + depends on HSR || !HSR help Some TI SoCs has Programmable Realtime Unit (PRU) cores which can support Single or Dual Ethernet ports with the help of firmware code diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Mak= efile index f4276c9a7762..e4a10d60e1a6 100644 --- a/drivers/net/ethernet/ti/Makefile +++ b/drivers/net/ethernet/ti/Makefile @@ -4,7 +4,7 @@ # =20 obj-$(CONFIG_TI_PRUETH) +=3D icssm-prueth.o -icssm-prueth-y :=3D icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm= /icssm_switchdev.o +icssm-prueth-y :=3D icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm= /icssm_switchdev.o icssm/icssm_prueth_lre.o =20 ti-cpsw-common-y +=3D cpsw-common.o davinci_cpdma.o ti-cpsw-priv-y +=3D cpsw_priv.o cpsw_ethtool.o diff --git a/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h b/drivers/n= et/ethernet/ti/icssm/icssm_lre_firmware.h new file mode 100644 index 000000000000..b5ab0ec87c5f --- /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 =3D 128, M = =3D 4 */ +#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT 0x400004 /* N =3D 64, M = =3D 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 =3D disabled (default), 1 =3D enabled + */ +#define ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET 0x1FAA +/* Enable/disable timestamping of packets. 0 =3D disabled (default) 1 =3D = 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/eth= ernet/ti/icssm/icssm_prueth.c index b7e94244355a..6c8888d9d66a 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c @@ -30,6 +30,7 @@ =20 #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" @@ -40,6 +41,30 @@ #define TX_CLK_DELAY_100M 0x6 #define HR_TIMER_TX_DELAY_US 100 =20 +#define NETIF_PRUETH_LRE_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \ + NETIF_F_HW_HSR_TAG_RM) + +static const struct prueth_fw_offsets fw_offsets_v2_1; +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 =3D + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET; + prueth->fw_offsets.mc_filter_mask =3D + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET; + prueth->fw_offsets.mc_filter_tbl =3D + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE; + } else { + prueth->fw_offsets.mc_ctrl_offset =3D + ICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT; + prueth->fw_offsets.mc_filter_mask =3D + ICSS_LRE_FW_MULTICAST_FILTER_MASK; + prueth->fw_offsets.mc_filter_tbl =3D + ICSS_LRE_FW_MULTICAST_FILTER_TABLE; + } +} + static void icssm_prueth_write_reg(struct prueth *prueth, enum prueth_mem region, unsigned int reg, u32 val) @@ -309,12 +334,15 @@ static void icssm_prueth_hostinit(struct prueth *prue= th) icssm_prueth_mii_init(prueth); } =20 -/* 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); } =20 static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable) @@ -609,6 +637,9 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *= emac, /* update first buffer descriptor */ wr_buf_desc =3D (pktlen << PRUETH_BD_LENGTH_SHIFT) & PRUETH_BD_LENGTH_MASK; + if (PRUETH_IS_HSR(prueth)) + wr_buf_desc |=3D BIT(PRUETH_BD_HSR_FRAME_SHIFT); + sram =3D prueth->mem[PRUETH_MEM_SHARED_RAM].va; if (!PRUETH_IS_EMAC(prueth)) writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr)); @@ -627,6 +658,12 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac = *emac, void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor, struct prueth_packet_info *pkt_info) { + if (prueth_is_lre(prueth)) + pkt_info->start_offset =3D !!(buffer_descriptor & + PRUETH_BD_START_FLAG_MASK); + else + pkt_info->start_offset =3D false; + pkt_info->port =3D (buffer_descriptor & PRUETH_BD_PORT_MASK) >> PRUETH_BD_PORT_SHIFT; pkt_info->length =3D (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >> @@ -661,10 +698,14 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u1= 6 *bd_rd_ptr, unsigned int actual_pkt_len; bool buffer_wrapped =3D false; void *src_addr, *dst_addr; + u16 start_offset =3D 0; struct sk_buff *skb; int pkt_block_size; void *ocmc_ram; =20 + if (PRUETH_IS_HSR(emac->prueth)) + start_offset =3D (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 +728,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 = *bd_rd_ptr, /* calculate new pointer in ram */ *bd_rd_ptr =3D rxqueue->buffer_desc_offset + (update_block * BD_SIZE); =20 - actual_pkt_len =3D pkt_info->length; + /* Exclude the HSR tag bytes already stripped by firmware, if any. */ + actual_pkt_len =3D pkt_info->length - start_offset; =20 /* Allocate a socket buffer for this packet */ skb =3D netdev_alloc_skb_ip_align(ndev, actual_pkt_len); @@ -707,6 +749,7 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 = *bd_rd_ptr, */ src_addr =3D ocmc_ram + rxqueue->buffer_offset + (read_block * ICSS_BLOCK_SIZE); + src_addr +=3D start_offset; =20 /* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */ if (buffer_wrapped) { /* wrapped around buffer */ @@ -720,6 +763,9 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 = *bd_rd_ptr, if (pkt_info->length < bytes) bytes =3D pkt_info->length; =20 + /* If applicable, account for the HSR tag removed */ + bytes -=3D start_offset; + /* copy non-wrapped part */ memcpy(dst_addr, src_addr, bytes); =20 @@ -741,6 +787,12 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16= *bd_rd_ptr, icssm_prueth_sw_learn_fdb(emac, skb->data + ETH_ALEN); } =20 + /* 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 -=3D ICSSM_LRE_TAG_SIZE; + skb_put(skb, actual_pkt_len); =20 /* send packet up the stack */ @@ -804,13 +856,12 @@ static int icssm_emac_rx_packets(struct prueth_emac *= emac, int budget) rd_buf_desc =3D readl(shared_ram + bd_rd_ptr); icssm_parse_packet_info(prueth, rd_buf_desc, &pkt_info); =20 - if (pkt_info.length <=3D 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 =3D bd_wr_ptr; emac->stats.rx_length_errors++; @@ -982,14 +1033,20 @@ static int icssm_emac_ndo_open(struct net_device *nd= ev) icssm_prueth_init_ethernet_mode(prueth); =20 /* 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 =3D icssm_prueth_sw_emac_config(emac); if (ret) - return ret; + goto free_hrtimer; =20 - ret =3D icssm_prueth_sw_init_fdb_table(prueth); - if (ret) - return ret; + if (PRUETH_IS_SWITCH(prueth)) { + ret =3D 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); } @@ -1046,6 +1103,9 @@ static int icssm_emac_ndo_open(struct net_device *nde= v) 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; } =20 @@ -1079,6 +1139,9 @@ static int icssm_emac_ndo_stop(struct net_device *nde= v) else rproc_shutdown(emac->pru); =20 + if (prueth_is_lre(prueth) && !prueth->emac_configured) + icssm_prueth_lre_cleanup(prueth); + /* free rx interrupts */ free_irq(emac->rx_irq, ndev); =20 @@ -1122,7 +1185,8 @@ static int icssm_prueth_change_mode(struct prueth *pr= ueth, } } =20 - if (mode =3D=3D PRUSS_ETHTYPE_EMAC || mode =3D=3D PRUSS_ETHTYPE_SWITCH) { + if (mode =3D=3D PRUSS_ETHTYPE_EMAC || mode =3D=3D PRUSS_ETHTYPE_SWITCH || + mode =3D=3D PRUSS_ETHTYPE_HSR || mode =3D=3D PRUSS_ETHTYPE_PRP) { prueth->eth_type =3D mode; } else { dev_err(prueth->dev, "unknown mode\n"); @@ -1266,11 +1330,16 @@ static void icssm_emac_mc_filter_ctrl(struct prueth= _emac *emac, bool enable) { struct prueth *prueth =3D emac->prueth; void __iomem *mc_filter_ctrl; + u32 mc_ctrl_offset; void __iomem *ram; u32 reg; =20 ram =3D prueth->mem[emac->dram].va; - mc_filter_ctrl =3D ram + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET; + if (prueth_is_lre(prueth)) + ram =3D prueth->mem[PRUETH_MEM_DRAM1].va; + + mc_ctrl_offset =3D prueth->fw_offsets.mc_ctrl_offset; + mc_filter_ctrl =3D ram + mc_ctrl_offset; =20 if (enable) reg =3D ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED; @@ -1289,7 +1358,10 @@ static void icssm_emac_mc_filter_reset(struct prueth= _emac *emac) void __iomem *ram; =20 ram =3D prueth->mem[emac->dram].va; - mc_filter_tbl_base =3D ICSS_EMAC_FW_MULTICAST_FILTER_TABLE; + if (prueth_is_lre(prueth)) + ram =3D prueth->mem[PRUETH_MEM_DRAM1].va; + + mc_filter_tbl_base =3D prueth->fw_offsets.mc_filter_tbl; =20 mc_filter_tbl =3D ram + mc_filter_tbl_base; memset_io(mc_filter_tbl, 0, ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES); @@ -1302,11 +1374,16 @@ static void icssm_emac_mc_filter_hashmask { struct prueth *prueth =3D emac->prueth; void __iomem *mc_filter_mask; + u32 mc_filter_mask_base; void __iomem *ram; =20 ram =3D prueth->mem[emac->dram].va; + if (prueth_is_lre(prueth)) + ram =3D prueth->mem[PRUETH_MEM_DRAM1].va; =20 - mc_filter_mask =3D ram + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET; + mc_filter_mask_base =3D prueth->fw_offsets.mc_filter_mask; + + mc_filter_mask =3D ram + mc_filter_mask_base; memcpy_toio(mc_filter_mask, mask, ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES); } @@ -1316,11 +1393,16 @@ static void icssm_emac_mc_filter_bin_update(struct = prueth_emac *emac, u8 hash, { struct prueth *prueth =3D emac->prueth; void __iomem *mc_filter_tbl; + u32 mc_filter_tbl_base; void __iomem *ram; =20 ram =3D prueth->mem[emac->dram].va; + if (prueth_is_lre(prueth)) + ram =3D prueth->mem[PRUETH_MEM_DRAM1].va; =20 - mc_filter_tbl =3D ram + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE; + mc_filter_tbl_base =3D prueth->fw_offsets.mc_filter_tbl; + + mc_filter_tbl =3D ram + mc_filter_tbl_base; writeb(val, mc_filter_tbl + hash); } =20 @@ -1360,6 +1442,8 @@ static void icssm_emac_ndo_set_rx_mode(struct net_dev= ice *ndev) { struct prueth_emac *emac =3D netdev_priv(ndev); bool promisc =3D 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 +1455,13 @@ static void icssm_emac_ndo_set_rx_mode(struct net_de= vice *ndev) sram =3D prueth->mem[PRUETH_MEM_SHARED_RAM].va; reg =3D readl(sram + EMAC_PROMISCUOUS_MODE_OFFSET); =20 + if (prueth_is_lre(prueth)) + mc_filter_tbl_lock =3D &prueth->addr_lock; + else + mc_filter_tbl_lock =3D &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); =20 /* Disable and reset multicast filter, allows allmulti */ icssm_emac_mc_filter_ctrl(emac, false); @@ -1429,15 +1518,109 @@ static void icssm_emac_ndo_set_rx_mode(struct net_= device *ndev) } =20 unlock: - spin_unlock_irqrestore(&emac->addr_lock, flags); + spin_unlock_irqrestore(mc_filter_tbl_lock, flags); +} + +static netdev_features_t icssm_emac_ndo_fix_features(struct net_device *nd= ev, + netdev_features_t features) +{ + /* hsr tag removal offload and hsr fwd offload are tightly coupled in + * firmware implementation. Both these features need to be enabled / + * disabled together. + */ + if (!(ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES)) + if ((features & NETIF_F_HW_HSR_FWD) || + (features & NETIF_F_HW_HSR_TAG_RM)) + features |=3D NETIF_PRUETH_LRE_OFFLOAD_FEATURES; + + if ((ndev->features & NETIF_F_HW_HSR_FWD) || + (ndev->features & NETIF_F_HW_HSR_TAG_RM)) + if (!(features & NETIF_F_HW_HSR_FWD) || + !(features & NETIF_F_HW_HSR_TAG_RM)) + features &=3D ~NETIF_PRUETH_LRE_OFFLOAD_FEATURES; + + return features; +} + +/** + * icssm_emac_ndo_set_features - Configure HSR/PRP offload features + * @ndev: network device + * @features: Requested feature set + * + * Called by ethtool -K to configure HSR/PRP offload features. The request + * is rejected if this interface or its paired interface is running. + * + * Return: 0 on success, -EINVAL or -EBUSY on error. + */ +static int icssm_emac_ndo_set_features(struct net_device *ndev, + netdev_features_t features) +{ + struct prueth_emac *emac, *other_emac; + netdev_features_t have, wanted; + struct net_device *upper; + struct prueth *prueth; + bool change_request; + int ret =3D -EBUSY; + + emac =3D netdev_priv(ndev); + prueth =3D emac->prueth; + /* MAC instance index starts from 0. So index by port_id - 1 */ + other_emac =3D emac->prueth->emac[(emac->port_id =3D=3D PRUETH_PORT_MII0)= ? + PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1]; + wanted =3D features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES; + have =3D ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES; + change_request =3D ((wanted ^ have) !=3D 0); + + if (!prueth->fw_data->support_lre) + return 0; + + if (PRUETH_IS_SWITCH(prueth) && change_request) { + /* LRE offload cannot be enabled in switch mode, remove the + * bridge first to revert to EMAC mode. + */ + netdev_err(ndev, + "Switch to HSR/PRP not allowed\n"); + return -EINVAL; + } + + upper =3D netdev_master_upper_dev_get(ndev); + if (upper && is_hsr_master(upper) && change_request) { + /* Firmware mode is switched to HSR/PRP based on the offload + * features set at the time this port joins the hsr device, so + * changing the features afterwards would desync ndev->features + * from the firmware mode without the hsr core noticing. + * Require unlinking from the hsr device first. + */ + netdev_err(ndev, + "Can't change offload features while port is an HSR/PRP slave\n"); + return -EBUSY; + } + + 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; } =20 static const struct net_device_ops emac_netdev_ops =3D { .ndo_open =3D icssm_emac_ndo_open, .ndo_stop =3D icssm_emac_ndo_stop, .ndo_start_xmit =3D icssm_emac_ndo_start_xmit, + .ndo_set_mac_address =3D eth_mac_addr, + .ndo_validate_addr =3D eth_validate_addr, .ndo_get_stats64 =3D icssm_emac_ndo_get_stats64, .ndo_set_rx_mode =3D icssm_emac_ndo_set_rx_mode, + .ndo_set_features =3D icssm_emac_ndo_set_features, + .ndo_fix_features =3D icssm_emac_ndo_fix_features, }; =20 /* get emac_port corresponding to eth_node name */ @@ -1589,6 +1772,9 @@ static int icssm_prueth_netdev_init(struct prueth *pr= ueth, ndev->hw_features |=3D NETIF_F_HW_L2FW_DOFFLOAD; } =20 + if (prueth->support_lre) + ndev->hw_features |=3D NETIF_PRUETH_LRE_OFFLOAD_FEATURES; + ndev->dev.of_node =3D eth_node; ndev->netdev_ops =3D &emac_netdev_ops; =20 @@ -1741,6 +1927,110 @@ static int icssm_prueth_ndev_port_unlink(struct net= _device *ndev) return ret; } =20 +static int icssm_prueth_hsr_port_link(struct net_device *ndev, + struct net_device *hsr_ndev) +{ + struct prueth_emac *emac =3D netdev_priv(ndev); + struct prueth *prueth =3D emac->prueth; + enum pruss_ethtype mode, prev_mode; + enum hsr_version ver; + unsigned long flags; + u8 all_slaves; + int ret =3D 0; + + if (PRUETH_IS_SWITCH(prueth)) + return -EOPNOTSUPP; + + hsr_get_version(hsr_ndev, &ver); + + if (ver =3D=3D HSR_V1) + mode =3D PRUSS_ETHTYPE_HSR; + else if (ver =3D=3D PRP_V1) + mode =3D PRUSS_ETHTYPE_PRP; + else + return -EOPNOTSUPP; + + all_slaves =3D BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1); + + spin_lock_irqsave(&prueth->addr_lock, flags); + + if (!prueth->hsr_members) { + prueth->hsr_dev =3D hsr_ndev; + } else { + /* Adding the port to a second bridge is not supported */ + if (prueth->hsr_dev !=3D hsr_ndev) { + spin_unlock_irqrestore(&prueth->addr_lock, flags); + return -EOPNOTSUPP; + } + } + + prueth->hsr_members |=3D BIT(emac->port_id); + + spin_unlock_irqrestore(&prueth->addr_lock, flags); + + if (!prueth_is_lre(prueth) && prueth->hsr_members =3D=3D all_slaves) { + prev_mode =3D prueth->eth_type; + ret =3D icssm_prueth_change_mode(prueth, mode); + if (ret < 0) { + dev_err(prueth->dev, "Failed to enable %s mode\n", + (mode =3D=3D PRUSS_ETHTYPE_HSR) ? + "HSR" : "PRP"); + goto free_hsr; + } else { + dev_info(prueth->dev, + "TI PRU ethernet now in %s mode\n", + (mode =3D=3D PRUSS_ETHTYPE_HSR) ? + "HSR" : "PRP"); + } + } + + return 0; + +free_hsr: + prueth->eth_type =3D prev_mode; + + spin_lock_irqsave(&prueth->addr_lock, flags); + + prueth->hsr_dev =3D NULL; + prueth->hsr_members &=3D ~all_slaves; + + spin_unlock_irqrestore(&prueth->addr_lock, flags); + return ret; +} + +static int icssm_prueth_hsr_port_unlink(struct net_device *ndev) +{ + struct prueth_emac *emac =3D netdev_priv(ndev); + struct prueth *prueth =3D emac->prueth; + enum pruss_ethtype prev_mode; + unsigned long flags; + int ret =3D 0; + + spin_lock_irqsave(&prueth->addr_lock, flags); + + prueth->hsr_members &=3D ~BIT(emac->port_id); + + spin_unlock_irqrestore(&prueth->addr_lock, flags); + + if (prueth_is_lre(prueth) && !prueth->hsr_members) { + prev_mode =3D prueth->eth_type; + ret =3D icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_EMAC); + if (ret < 0) { + dev_err(prueth->dev, "Failed to enable dual EMAC mode\n"); + prueth->eth_type =3D prev_mode; + } + } + + spin_lock_irqsave(&prueth->addr_lock, flags); + + if (!prueth->hsr_members) + prueth->hsr_dev =3D NULL; + + spin_unlock_irqrestore(&prueth->addr_lock, flags); + + return ret; +} + static int icssm_prueth_ndev_event(struct notifier_block *unused, unsigned long event, void *ptr) { @@ -1754,6 +2044,17 @@ static int icssm_prueth_ndev_event(struct notifier_b= lock *unused, switch (event) { case NETDEV_CHANGEUPPER: info =3D ptr; + if (is_hsr_master(info->upper_dev)) { + if (info->linking) { + if (ndev->features & + NETIF_PRUETH_LRE_OFFLOAD_FEATURES) + ret =3D icssm_prueth_hsr_port_link + (ndev, info->upper_dev); + } else { + ret =3D icssm_prueth_hsr_port_unlink(ndev); + } + } + if (netif_is_bridge_master(info->upper_dev)) { if (info->linking) ret =3D icssm_prueth_ndev_port_link @@ -1796,6 +2097,7 @@ static int icssm_prueth_probe(struct platform_device = *pdev) struct device *dev =3D &pdev->dev; struct device_node *np; struct prueth *prueth; + bool has_lre =3D false; struct pruss *pruss; int i, ret; =20 @@ -1810,6 +2112,9 @@ static int icssm_prueth_probe(struct platform_device = *pdev) platform_set_drvdata(pdev, prueth); prueth->dev =3D dev; prueth->fw_data =3D device_get_match_data(dev); + prueth->fw_offsets =3D fw_offsets_v2_1; + + icssm_prueth_set_fw_offsets(prueth); =20 eth_ports_node =3D of_get_child_by_name(np, "ethernet-ports"); if (!eth_ports_node) @@ -1955,6 +2260,17 @@ static int icssm_prueth_probe(struct platform_device= *pdev) prueth->mem[PRUETH_MEM_OCMC].va, prueth->mem[PRUETH_MEM_OCMC].size); =20 + if (IS_ENABLED(CONFIG_HSR) && prueth->fw_data->support_lre) + has_lre =3D 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 =3D false; + + prueth->support_lre =3D has_lre; + spin_lock_init(&prueth->addr_lock); /* setup netdev interfaces */ if (eth0_node) { ret =3D icssm_prueth_netdev_init(prueth, eth0_node); @@ -2176,15 +2492,24 @@ static struct prueth_private_data am335x_prueth_pda= ta =3D { .fw_pru[PRUSS_PRU0] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am335x-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am335x-pru0-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am335x-pru0-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am335x-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am335x-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am335x-pru1-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am335x-pru1-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am335x-pru1-prusw-fw.elf", }, + .support_lre =3D true, .support_switch =3D true, }; =20 @@ -2194,15 +2519,24 @@ static struct prueth_private_data am437x_prueth_pda= ta =3D { .fw_pru[PRUSS_PRU0] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am437x-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am437x-pru0-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am437x-pru0-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am437x-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am437x-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am437x-pru1-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am437x-pru1-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am437x-pru1-prusw-fw.elf", }, + .support_lre =3D true, .support_switch =3D true, }; =20 @@ -2212,16 +2546,25 @@ static struct prueth_private_data am57xx_prueth_pda= ta =3D { .fw_pru[PRUSS_PRU0] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am57xx-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am57xx-pru0-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am57xx-pru0-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am57xx-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] =3D { .fw_name[PRUSS_ETHTYPE_EMAC] =3D "ti-pruss/am57xx-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_HSR] =3D + "ti-pruss/am57xx-pru1-pruhsr-fw.elf", + .fw_name[PRUSS_ETHTYPE_PRP] =3D + "ti-pruss/am57xx-pru1-pruprp-fw.elf", .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am57xx-pru1-prusw-fw.elf", =20 }, + .support_lre =3D true, .support_switch =3D true, }; =20 diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/eth= ernet/ti/icssm/icssm_prueth.h index d5b49b462c24..a5d5bcd08bcd 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" =20 /* ICSSM size of redundancy tag */ #define ICSSM_LRE_TAG_SIZE 6 =20 +#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 { =20 #define PRUETH_IS_EMAC(p) ((p)->eth_type =3D=3D PRUSS_ETHTYPE_EMAC) #define PRUETH_IS_SWITCH(p) ((p)->eth_type =3D=3D PRUSS_ETHTYPE_SWITCH) +#define PRUETH_IS_HSR(p) ((p)->eth_type =3D=3D PRUSS_ETHTYPE_HSR) +#define PRUETH_IS_PRP(p) ((p)->eth_type =3D=3D PRUSS_ETHTYPE_PRP) =20 /** * struct prueth_queue_desc - Queue descriptor @@ -86,6 +92,7 @@ struct prueth_queue_info { =20 /** * 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 * @length: length of packet @@ -96,6 +103,7 @@ 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; unsigned int length; @@ -171,6 +179,13 @@ enum prueth_mem { PRUETH_MEM_MAX, }; =20 +/* Firmware offsets/size information */ +struct prueth_fw_offsets { + u32 mc_ctrl_offset; + u32 mc_filter_mask; + u32 mc_filter_tbl; +}; + enum pruss_device { PRUSS_AM57XX =3D 0, PRUSS_AM43XX, @@ -183,11 +198,13 @@ enum pruss_device { * @driver_data: PRU Ethernet device name * @fw_pru: firmware names to be used for PRUSS ethernet usecases * @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]; bool support_switch; + bool support_lre; }; =20 struct prueth_emac_stats { @@ -248,13 +265,20 @@ struct prueth { struct icss_iep *iep; =20 const struct prueth_private_data *fw_data; - struct prueth_fw_offsets *fw_offsets; + struct prueth_fw_offsets fw_offsets; =20 struct device_node *eth_node[PRUETH_NUM_MACS]; struct prueth_emac *emac[PRUETH_NUM_MACS]; struct net_device *registered_netdevs[PRUETH_NUM_MACS]; =20 + 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; =20 struct notifier_block prueth_netdevice_nb; @@ -264,6 +288,7 @@ struct prueth { unsigned int eth_type; size_t ocmc_ram_size; u8 emac_configured; + u8 hsr_members; u8 br_members; }; =20 @@ -277,4 +302,9 @@ 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); + +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_lre.c b/drivers/net= /ethernet/ti/icssm/icssm_prueth_lre.c new file mode 100644 index 000000000000..6276dd1e8bb1 --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c @@ -0,0 +1,211 @@ +// 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 +#include + +#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 =3D 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 =3D ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT; + if (PRUETH_IS_HSR(prueth)) + prueth->tbl_check_mask |=3D + 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 + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * byte 0 =3D> PRU 1, PCP 0-3 =3D> Q3 + * byte 1 =3D> PRU 1, PCP 4-7 =3D> Q2 + * byte 2 =3D> PRU 0, PCP 0-3 =3D> Q1 + * byte 3 =3D> PRU 0, PCP 4-7 =3D> Q0 + * + * At egress to wire/network on PRU-0 and PRU-1 + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * byte 0 =3D> Host, PCP 0-3 =3D> Q3 + * byte 1 =3D> Host, PCP 4-7 =3D> Q2 + * + * PRU-0 + * ----- + * byte 2 =3D> PRU-1, PCP 0-3 =3D> Q1 + * byte 3 =3D> PRU-1, PCP 4-7 =3D> Q0 + * + * PRU-1 + * ----- + * byte 2 =3D> PRU-0, PCP 0-3 =3D> Q1 + * byte 3 =3D> PRU-0, PCP 4-7 =3D> 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] = =3D { + /* 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 =3D 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 =3D prueth->mem[PRUETH_MEM_DRAM0].va; + void __iomem *dram1 =3D 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 =3D 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 =3D 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 =3D 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 =3D prueth->mem[PRUETH_MEM_DRAM0].va; + void __iomem *dram1 =3D 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 enum hrtimer_restart icssm_prueth_lre_timer(struct hrtimer *timer) +{ + struct prueth *prueth; + unsigned int timeout; + void __iomem *dram; + + prueth =3D container_of(timer, struct prueth, tbl_check_timer); + dram =3D prueth->mem[PRUETH_MEM_DRAM1].va; + timeout =3D PRUETH_TIMER_MS; + + hrtimer_forward_now(timer, ms_to_ktime(timeout)); + if (prueth->emac_configured !=3D + (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 =3D 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); +} + +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 000000000000..0fe4d1ae5823 --- /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 +#include +#include + +#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 */ --=20 2.43.0 From nobody Thu Sep 24 20:03:13 2026 Received: from server.couthit.com (server.couthit.com [162.240.164.96]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3D94C49F134; Mon, 21 Sep 2026 13:45:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.240.164.96 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998305; cv=none; b=OsN/nEBufXxcbIuBABohDYnB5KugCoMHiTwa39UGrI4KPEEI+4NH+MEsq1TrY57KU9jKHlzU72u15kTIKcKKff4IeYh70XVT7mFC1p0T0GSByb4AmBU+0T32SLCIeRpd0qfea0uTsq2yK+k+Ldp96/asqSb5sr2RppsofXxd7Pc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998305; c=relaxed/simple; bh=06kkCb1wCv/o6MYNP8kGcg5PwpyrRVBJ1peuPu3knm0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Rah91VlXybEEwBJ/9L4sV7DRJ41eChq9nxRVQQ+MnU6x9wOoxv65zV5DkZ3eLAkSbwHOtq5dRMZAf5cHaUreQReE6LvERmcsMeY+itEVsgID7x8bKXOF9/HzYjqE446KPy4/VbH72LOKJ9GPIMdEJQoz+nsxiYojySuAc8KAumI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com; spf=pass smtp.mailfrom=couthit.com; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b=Fd96B27j; arc=none smtp.client-ip=162.240.164.96 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=couthit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b="Fd96B27j" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=couthit.com ; s=default; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=OyQsO4BP0SpUy+dfWeNyAiXinPHRUTMlceoDJQgTfwo=; b=Fd96B27jRbP6dqISS6jocAKmU+ OvHhbU/81/XickxQQEi2wf1I2saYdgDH9J8t8mC9HEWVX+Ikog7xJvM5x6d8YV2djFAEzfBln4wAa XKd3UOGsRuhlFqEA5DjM6xyYa0hUIW3872e3Qrk3HTyrVH73FVePN2ZpOCptk/gdhN51vhomQLEzR IwxI8WtRFoT06JMRIxS38vOfiFpBC07cfMyCGo0c3VdLWB3EblhYtBxxZ7P79GoZCYqDVNRIpJJOc w/mx+ff0FxIyDr2qW7cZhnD7Wm6W7AehEazZ1ux7O9nOA5g+AnkUgDTeQBBbzM5wE50IY7WJHZpsv CNYosL7Q==; Received: from [115.246.246.98] (port=57742 helo=cypher.couthit.local) by server.couthit.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.5) (envelope-from ) id 1x8eKH-00000008L1c-2QOp; Mon, 21 Sep 2026 09:44:58 -0400 From: Parvathi Pudi To: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, danishanwar@ti.com, parvathi@couthit.com, rogerq@kernel.org, pmohan@couthit.com, afd@ti.com, vadim.fedorenko@linux.dev, haokexin@gmail.com, basharath@couthit.com, arnd@arndb.de Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org, pratheesh@ti.com, j-rameshbabu@ti.com, vigneshr@ti.com, praneeth@ti.com, srk@ti.com, rogerq@ti.com, m-malladi@ti.com, krishna@couthit.com, mohan@couthit.com Subject: [PATCH net-next v4 2/3] net: ti: icssm-prueth: Add priority based RX IRQ handlers Date: Mon, 21 Sep 2026 19:11:22 +0530 Message-ID: <20260921134343.200426-3-parvathi@couthit.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260921134343.200426-1-parvathi@couthit.com> References: <20260921134343.200426-1-parvathi@couthit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - server.couthit.com X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - couthit.com X-Get-Message-Sender-Via: server.couthit.com: authenticated_id: parvathi@couthit.com X-Authenticated-Sender: server.couthit.com: parvathi@couthit.com X-Source: X-Source-Args: X-Source-Dir: Content-Type: text/plain; charset="utf-8" From: Roger Quadros This patch adds support for priority based interrupt handling for the STP/ RSTP Switch, HSR and PRP protocols along with extra logic to address first come first served to avoid port dominance. In RSTP switch, HSR, and PRP modes the host port can receive frames from any one of PRU ports. Servicing RX interrupts in arrival order does not guarantee the frames are delivered to the stack in wire-arrival order due to port dominance. In order to achieve that, each PRU records an IEP (Industrial Ethernet Peripheral) arrival HW timestamp into the receive buffer and pass this information to driver. The driver will read the RX HW timestamp from frame and process the frame which has arrived first among the two ports, giving the stack wire-arrival ordering. Dual-EMAC mode continues to use per-port interrupts. Also convert struct prueth_emac_stats counters from u64 to atomic64_t, since napi_hpq and napi_lpq can now run concurrently on different CPUs and update the same counters. The IEP wrap value used in the timestamp comparison differs between firmware revisions, so this patch adds an fw_rev field to the platform data and an enum fw_revision to identify it. icssm_prueth_probe() now fails with -EINVAL if a platform data entry does not set fw_rev. icssm_emac_ndo_stop() also gains a netif_stop_queue() call, since TX must stop before the shared RX/TX resources are reconfigured or torn down. Signed-off-by: Roger Quadros Signed-off-by: Andrew F. Davis Signed-off-by: Basharath Hussain Khaja Signed-off-by: Parvathi Pudi --- drivers/net/ethernet/ti/Makefile | 2 +- drivers/net/ethernet/ti/icssm/icssm_prueth.c | 172 ++++++++-- drivers/net/ethernet/ti/icssm/icssm_prueth.h | 44 ++- .../ethernet/ti/icssm/icssm_prueth_common.c | 311 ++++++++++++++++++ .../net/ethernet/ti/icssm/icssm_prueth_lre.c | 13 + .../ethernet/ti/icssm/icssm_prueth_switch.c | 8 + .../ethernet/ti/icssm/icssm_prueth_switch.h | 1 + drivers/net/ethernet/ti/icssm/icssm_switch.h | 6 + 8 files changed, 517 insertions(+), 40 deletions(-) create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_common.c diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Mak= efile index e4a10d60e1a6..b6651fe73afd 100644 --- a/drivers/net/ethernet/ti/Makefile +++ b/drivers/net/ethernet/ti/Makefile @@ -4,7 +4,7 @@ # =20 obj-$(CONFIG_TI_PRUETH) +=3D icssm-prueth.o -icssm-prueth-y :=3D icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm= /icssm_switchdev.o icssm/icssm_prueth_lre.o +icssm-prueth-y :=3D icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm= /icssm_switchdev.o icssm/icssm_prueth_lre.o icssm/icssm_prueth_common.o =20 ti-cpsw-common-y +=3D cpsw-common.o davinci_cpdma.o ti-cpsw-priv-y +=3D cpsw_priv.o cpsw_ethtool.o diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/eth= ernet/ti/icssm/icssm_prueth.c index 6c8888d9d66a..4a37935b8d35 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c @@ -44,7 +44,21 @@ #define NETIF_PRUETH_LRE_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \ NETIF_F_HW_HSR_TAG_RM) =20 -static const struct prueth_fw_offsets fw_offsets_v2_1; +/* 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 =3D { + .iep_wrap =3D 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 =3D { + .iep_wrap =3D NSEC_PER_SEC, +}; + static void icssm_prueth_set_fw_offsets(struct prueth *prueth) { /* Set Multicast filter control and table offsets */ @@ -343,6 +357,8 @@ static void icssm_prueth_init_ethernet_mode(struct prue= th *prueth) icssm_prueth_hostinit(prueth); if (prueth_is_lre(prueth)) icssm_prueth_lre_config(prueth); + else if (PRUETH_IS_SWITCH(prueth)) + icssm_prueth_sw_config_packet_timestamping(prueth); } =20 static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable) @@ -802,8 +818,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 = *bd_rd_ptr, local_bh_enable(); =20 /* update stats */ - emac->stats.rx_bytes +=3D actual_pkt_len; - emac->stats.rx_packets++; + atomic64_add(actual_pkt_len, &emac->stats.rx_bytes); + atomic64_inc(&emac->stats.rx_packets); =20 return 0; } @@ -825,7 +841,6 @@ static int icssm_emac_rx_packets(struct prueth_emac *em= ac, int budget) =20 shared_ram =3D emac->prueth->mem[PRUETH_MEM_SHARED_RAM].va; =20 - /* Start and end queue is made common for EMAC, RSTP */ start_queue =3D emac->rx_queue_start; end_queue =3D emac->rx_queue_end; =20 @@ -836,13 +851,10 @@ static int icssm_emac_rx_packets(struct prueth_emac *= emac, int budget) /* search host queues for packets */ for (i =3D start_queue; i <=3D end_queue; i++) { queue_desc =3D emac->rx_queue_descs + i; - if (PRUETH_IS_SWITCH(emac->prueth)) - rxqueue =3D &sw_queue_infos[PRUETH_PORT_HOST][i]; - else - rxqueue =3D &queue_infos[PRUETH_PORT_HOST][i]; + rxqueue =3D &queue_infos[PRUETH_PORT_HOST][i]; overflow_cnt =3D readb(&queue_desc->overflow_cnt); if (overflow_cnt > 0) { - emac->stats.rx_over_errors +=3D overflow_cnt; + atomic64_add(overflow_cnt, &emac->stats.rx_over_errors); /* reset to zero */ writeb(0, &queue_desc->overflow_cnt); } @@ -864,7 +876,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *em= ac, int budget) * these packets */ update_rd_ptr =3D bd_wr_ptr; - emac->stats.rx_length_errors++; + atomic64_inc(&emac->stats.rx_length_errors); } else if (pkt_info.length > EMAC_MAX_FRM_SUPPORT) { /* if the packet is too large we skip it but we * still need to move the read pointer ahead @@ -873,7 +885,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *em= ac, int budget) * these packets */ update_rd_ptr =3D bd_wr_ptr; - emac->stats.rx_length_errors++; + atomic64_inc(&emac->stats.rx_length_errors); } else { update_rd_ptr =3D bd_rd_ptr; ret =3D icssm_emac_rx_packet(emac, &update_rd_ptr, @@ -1071,11 +1083,25 @@ static int icssm_emac_ndo_open(struct net_device *n= dev) goto iep_exit; } =20 - ret =3D 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); + } + } =20 - 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 =3D icssm_emac_request_irqs(emac); + else + ret =3D icssm_prueth_common_request_irqs(emac); + if (ret) + goto disable_napi; =20 /* start PHY */ phy_start(emac->phydev); @@ -1092,7 +1118,10 @@ static int icssm_emac_ndo_open(struct net_device *nd= ev) =20 return 0; =20 -rproc_shutdown: +disable_napi: + if (PRUETH_IS_EMAC(prueth)) + napi_disable(&emac->napi); + if (!PRUETH_IS_EMAC(prueth)) icssm_prueth_sw_shutdown_prus(emac, ndev); else @@ -1127,11 +1156,25 @@ static int icssm_emac_ndo_stop(struct net_device *n= dev) /* disable the mac port */ icssm_prueth_port_enable(emac, false); =20 + hrtimer_cancel(&emac->tx_hrtimer); + + netif_stop_queue(ndev); + /* stop PHY */ phy_stop(emac->phydev); =20 - 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); + } =20 /* stop the PRU */ if (!PRUETH_IS_EMAC(prueth)) @@ -1142,9 +1185,6 @@ static int icssm_emac_ndo_stop(struct net_device *nde= v) if (prueth_is_lre(prueth) && !prueth->emac_configured) icssm_prueth_lre_cleanup(prueth); =20 - /* free rx interrupts */ - free_irq(emac->rx_irq, ndev); - /* free memory related to sw */ icssm_prueth_free_memory(emac->prueth); =20 @@ -1281,8 +1321,8 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struc= t sk_buff *skb, goto fail_tx; } =20 - emac->stats.tx_packets++; - emac->stats.tx_bytes +=3D skb->len; + atomic64_inc(&emac->stats.tx_packets); + atomic64_add(skb->len, &emac->stats.tx_bytes); dev_kfree_skb_any(skb); =20 return NETDEV_TX_OK; @@ -1296,7 +1336,7 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struc= t sk_buff *skb, ret =3D NETDEV_TX_BUSY; } else { /* error */ - emac->stats.tx_dropped++; + atomic64_inc(&emac->stats.tx_dropped); ret =3D NET_XMIT_DROP; } =20 @@ -1316,13 +1356,13 @@ static void icssm_emac_ndo_get_stats64(struct net_d= evice *ndev, { struct prueth_emac *emac =3D netdev_priv(ndev); =20 - stats->rx_packets =3D emac->stats.rx_packets; - stats->rx_bytes =3D emac->stats.rx_bytes; - stats->tx_packets =3D emac->stats.tx_packets; - stats->tx_bytes =3D emac->stats.tx_bytes; - stats->tx_dropped =3D emac->stats.tx_dropped; - stats->rx_over_errors =3D emac->stats.rx_over_errors; - stats->rx_length_errors =3D emac->stats.rx_length_errors; + stats->rx_packets =3D atomic64_read(&emac->stats.rx_packets); + stats->rx_bytes =3D atomic64_read(&emac->stats.rx_bytes); + stats->tx_packets =3D atomic64_read(&emac->stats.tx_packets); + stats->tx_bytes =3D atomic64_read(&emac->stats.tx_bytes); + stats->tx_dropped =3D atomic64_read(&emac->stats.tx_dropped); + stats->rx_over_errors =3D atomic64_read(&emac->stats.rx_over_errors); + stats->rx_length_errors =3D atomic64_read(&emac->stats.rx_length_errors); } =20 /* enable/disable MC filter */ @@ -1780,9 +1820,25 @@ static int icssm_prueth_netdev_init(struct prueth *p= rueth, =20 netif_napi_add(ndev, &emac->napi, icssm_emac_napi_poll); =20 + if ((prueth->support_lre || fw_data->support_switch) && + emac->port_id =3D=3D PRUETH_PORT_MII0) { + netif_napi_add(ndev, &prueth->napi_hpq, + icssm_prueth_common_napi_poll_hpq); + netif_napi_add(ndev, &prueth->napi_lpq, + icssm_prueth_common_napi_poll_lpq); + } + hrtimer_setup(&emac->tx_hrtimer, &icssm_emac_tx_timer_callback, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); =20 + if ((prueth->support_lre || fw_data->support_switch) && + emac->port_id =3D=3D PRUETH_PORT_MII0) { + prueth->hp->ndev =3D ndev; + prueth->hp->priority =3D 0; + prueth->lp->ndev =3D ndev; + prueth->lp->priority =3D 1; + } + return 0; free: emac->ndev =3D NULL; @@ -1794,6 +1850,7 @@ static int icssm_prueth_netdev_init(struct prueth *pr= ueth, static void icssm_prueth_netdev_exit(struct prueth *prueth, struct device_node *eth_node) { + const struct prueth_private_data *fw_data =3D prueth->fw_data; struct prueth_emac *emac; enum prueth_mac mac; =20 @@ -1808,6 +1865,13 @@ static void icssm_prueth_netdev_exit(struct prueth *= prueth, phy_disconnect(emac->phydev); =20 netif_napi_del(&emac->napi); + + if ((prueth->support_lre || fw_data->support_switch) && + emac->port_id =3D=3D PRUETH_PORT_MII0) { + netif_napi_del(&prueth->napi_hpq); + netif_napi_del(&prueth->napi_lpq); + } + prueth->emac[mac] =3D NULL; } =20 @@ -2112,7 +2176,13 @@ static int icssm_prueth_probe(struct platform_device= *pdev) platform_set_drvdata(pdev, prueth); prueth->dev =3D dev; prueth->fw_data =3D device_get_match_data(dev); - prueth->fw_offsets =3D fw_offsets_v2_1; + + if (prueth->fw_data->fw_rev =3D=3D FW_REV_V1_0) + prueth->fw_offsets =3D fw_offsets_v1_0; + else if (prueth->fw_data->fw_rev =3D=3D FW_REV_V2_1) + prueth->fw_offsets =3D fw_offsets_v2_1; + else + return -EINVAL; =20 icssm_prueth_set_fw_offsets(prueth); =20 @@ -2269,6 +2339,41 @@ static int icssm_prueth_probe(struct platform_device= *pdev) if (has_lre && (!eth0_node || !eth1_node)) has_lre =3D false; =20 + /* 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 =3D devm_kzalloc(dev, + sizeof(struct prueth_ndev_priority), + GFP_KERNEL); + if (!prueth->hp) { + ret =3D -ENOMEM; + goto free_pool; + } + prueth->lp =3D devm_kzalloc(dev, + sizeof(struct prueth_ndev_priority), + GFP_KERNEL); + if (!prueth->lp) { + ret =3D -ENOMEM; + goto free_pool; + } + + prueth->rx_lpq_irq =3D of_irq_get_byname(np, "rx_lp"); + if (prueth->rx_lpq_irq < 0) { + ret =3D prueth->rx_lpq_irq; + if (ret !=3D -EPROBE_DEFER) + dev_err(prueth->dev, "could not get rx_lp irq\n"); + goto free_pool; + } + prueth->rx_hpq_irq =3D of_irq_get_byname(np, "rx_hp"); + if (prueth->rx_hpq_irq < 0) { + ret =3D prueth->rx_hpq_irq; + if (ret !=3D -EPROBE_DEFER) + dev_err(prueth->dev, "could not get rx_hp irq\n"); + goto free_pool; + } + } + prueth->support_lre =3D has_lre; spin_lock_init(&prueth->addr_lock); /* setup netdev interfaces */ @@ -2509,6 +2614,7 @@ static struct prueth_private_data am335x_prueth_pdata= =3D { .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am335x-pru1-prusw-fw.elf", }, + .fw_rev =3D FW_REV_V1_0, .support_lre =3D true, .support_switch =3D true, }; @@ -2536,6 +2642,7 @@ static struct prueth_private_data am437x_prueth_pdata= =3D { .fw_name[PRUSS_ETHTYPE_SWITCH] =3D "ti-pruss/am437x-pru1-prusw-fw.elf", }, + .fw_rev =3D FW_REV_V1_0, .support_lre =3D true, .support_switch =3D true, }; @@ -2564,6 +2671,7 @@ static struct prueth_private_data am57xx_prueth_pdata= =3D { "ti-pruss/am57xx-pru1-prusw-fw.elf", =20 }, + .fw_rev =3D FW_REV_V2_1, .support_lre =3D true, .support_switch =3D true, }; diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/eth= ernet/ti/icssm/icssm_prueth.h index a5d5bcd08bcd..8cca5b87d4ff 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h @@ -179,11 +179,22 @@ enum prueth_mem { PRUETH_MEM_MAX, }; =20 +/* PRU firmware revision */ +enum fw_revision { + FW_REV_INVALID =3D 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; }; =20 enum pruss_device { @@ -197,24 +208,26 @@ enum pruss_device { * struct prueth_private_data - PRU Ethernet private data * @driver_data: PRU Ethernet device name * @fw_pru: firmware names to be used for PRUSS ethernet usecases + * @fw_rev: Firmware revision identifier * @support_switch: boolean to indicate if switch is enabled * @support_lre: boolean to indicate if LRE is enabled */ struct prueth_private_data { enum pruss_device driver_data; const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS]; + enum fw_revision fw_rev; bool support_switch; bool support_lre; }; =20 struct prueth_emac_stats { - u64 tx_packets; - u64 tx_dropped; - u64 tx_bytes; - u64 rx_packets; - u64 rx_bytes; - u64 rx_length_errors; - u64 rx_over_errors; + atomic64_t tx_packets; + atomic64_t tx_dropped; + atomic64_t tx_bytes; + atomic64_t rx_packets; + atomic64_t rx_bytes; + atomic64_t rx_length_errors; + atomic64_t rx_over_errors; }; =20 /* data for each emac port */ @@ -255,6 +268,11 @@ struct prueth_emac { int offload_fwd_mark; }; =20 +struct prueth_ndev_priority { + struct net_device *ndev; + int priority; +}; + struct prueth { struct device *dev; struct pruss *pruss; @@ -270,6 +288,12 @@ struct prueth { 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; =20 bool support_lre; unsigned int tbl_check_mask; @@ -303,6 +327,12 @@ 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); =20 +int icssm_prueth_common_napi_poll_lpq(struct napi_struct *napi, int budget= ); +int icssm_prueth_common_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); 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 000000000000..9b7b876240b5 --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Texas Instruments ICSSM Ethernet Driver + * + * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti= .com/ + * + */ + +#include +#include +#include + +#include "icssm_prueth.h" +#include "icssm_prueth_switch.h" + +static int icssm_prueth_common_emac_rx_packets(struct prueth_emac *emac, + int quota, u8 qid1, u8 qid2) +{ + u16 bd_rd_ptr, bd_wr_ptr, update_rd_ptr, bd_rd_ptr_o, bd_wr_ptr_o; + const struct prueth_queue_info *rxqueue, *rxqueue_o, *rxqueue_p; + struct prueth_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 =3D 0, port, port0_q_empty, port1_q_empty; + struct prueth_queue_desc __iomem *queue_desc_o =3D NULL; + struct prueth_queue_desc __iomem *queue_desc =3D NULL; + struct prueth_emac *emac_p, *other_emac; + void __iomem *shared_ram, *ocmc_ram; + bool port0_configured, port1_configured; + u8 overflow_cnt, overflow_cnt_o; + u16 *bd_rd_ptr_p, *bd_wr_ptr_p; + struct prueth *prueth; + + prueth =3D emac->prueth; + ocmc_ram =3D prueth->mem[PRUETH_MEM_OCMC].va; + shared_ram =3D prueth->mem[PRUETH_MEM_SHARED_RAM].va; + other_emac =3D prueth->emac[(emac->port_id =3D=3D PRUETH_PORT_MII0) ? + PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1]; + + iep_wrap =3D prueth->fw_offsets.iep_wrap; + rxqueue =3D &sw_queue_infos[PRUETH_PORT_HOST][qid1]; + rxqueue_o =3D &sw_queue_infos[PRUETH_PORT_HOST][qid2]; + + /* skip Rx if budget is 0 */ + if (!quota) + return 0; + + /* A port's rx_queue_descs is only valid once that port has + * completed its own ndo_open() for the currently active mode: + * before that it is NULL (never opened) or stale (opened in a + * different mode), since the shared HP/LP IRQs and PRUs are + * started as soon as the first port opens, independent of the + * peer port's state. Treat an unconfigured port's queue as + * permanently empty instead of dereferencing rx_queue_descs. + */ + port0_configured =3D prueth->emac_configured & BIT(emac->port_id); + port1_configured =3D other_emac && + (prueth->emac_configured & BIT(other_emac->port_id)); + + if (port0_configured) { + queue_desc =3D emac->rx_queue_descs + qid1; + + overflow_cnt =3D readb(&queue_desc->overflow_cnt); + if (overflow_cnt > 0) { + atomic64_add(overflow_cnt, &emac->stats.rx_over_errors); + writeb(0, &queue_desc->overflow_cnt); + } + + bd_rd_ptr =3D readw(&queue_desc->rd_ptr); + bd_wr_ptr =3D readw(&queue_desc->wr_ptr); + port0_q_empty =3D (bd_rd_ptr =3D=3D bd_wr_ptr); + } else { + bd_rd_ptr =3D 0; + bd_wr_ptr =3D 0; + port0_q_empty =3D 1; + } + + if (port1_configured) { + queue_desc_o =3D other_emac->rx_queue_descs + qid2; + + overflow_cnt_o =3D readb(&queue_desc_o->overflow_cnt); + if (overflow_cnt_o > 0) { + atomic64_add(overflow_cnt_o, + &other_emac->stats.rx_over_errors); + writeb(0, &queue_desc_o->overflow_cnt); + } + + bd_rd_ptr_o =3D readw(&queue_desc_o->rd_ptr); + bd_wr_ptr_o =3D readw(&queue_desc_o->wr_ptr); + port1_q_empty =3D (bd_rd_ptr_o =3D=3D bd_wr_ptr_o); + } else { + bd_rd_ptr_o =3D 0; + bd_wr_ptr_o =3D 0; + port1_q_empty =3D 1; + } + + while (!port0_q_empty || !port1_q_empty) { + rd_buf_desc =3D readl(shared_ram + bd_rd_ptr); + rd_buf_desc_o =3D 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 =3D readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET + + bd_rd_ptr - SRAM_START_OFFSET); + pkt_ts_o =3D 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 =3D (pkt_ts - pkt_ts_o) > (iep_wrap / 2) ? + 0 : 1; + else + port =3D (pkt_ts_o - pkt_ts) > (iep_wrap / 2) ? + 1 : 0; + + } else if (!port0_q_empty) { + /* Packet(s) in port0 queue only */ + port =3D 0; + } else { + /* Packet(s) in port1 queue only */ + port =3D 1; + } + + /* Select correct data structures for queue/packet selected */ + if (port =3D=3D 0) { + pkt_info_p =3D &pkt_info; + bd_wr_ptr_p =3D &bd_wr_ptr; + bd_rd_ptr_p =3D &bd_rd_ptr; + emac_p =3D emac; + rxqueue_p =3D rxqueue; + } else { + pkt_info_p =3D &pkt_info_o; + bd_wr_ptr_p =3D &bd_wr_ptr_o; + bd_rd_ptr_p =3D &bd_rd_ptr_o; + emac_p =3D other_emac; + rxqueue_p =3D 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 =3D *bd_wr_ptr_p; + atomic64_inc(&emac_p->stats.rx_length_errors); + } else if ((*pkt_info_p).length > EMAC_MAX_FRM_SUPPORT) { + /* Oversized frame: firmware should have filtered + * these before they reach the host queue. Advance + * the read pointer to skip it. + */ + update_rd_ptr =3D *bd_wr_ptr_p; + atomic64_inc(&emac_p->stats.rx_length_errors); + } else { + update_rd_ptr =3D *bd_rd_ptr_p; + ret =3D icssm_emac_rx_packet(emac_p, &update_rd_ptr, + pkt_info_p, rxqueue_p); + if (ret) + return used; + + used++; + } + + /* Zero the BD after consuming it, a misaligned rd_ptr + * would otherwise mistake stale data for a valid incoming + * frame. + */ + if (port =3D=3D 0) { + writel(0, shared_ram + bd_rd_ptr); + writew(update_rd_ptr, &queue_desc->rd_ptr); + bd_rd_ptr =3D update_rd_ptr; + } else { + writel(0, shared_ram + bd_rd_ptr_o); + writew(update_rd_ptr, &queue_desc_o->rd_ptr); + bd_rd_ptr_o =3D update_rd_ptr; + } + + port0_q_empty =3D (bd_rd_ptr =3D=3D bd_wr_ptr) ? 1 : 0; + port1_q_empty =3D (bd_rd_ptr_o =3D=3D bd_wr_ptr_o) ? 1 : 0; + + if (used >=3D quota) + return used; + } + + return used; +} + +int icssm_prueth_common_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 =3D container_of(napi, struct prueth, napi_lpq); + ndev =3D prueth->lp->ndev; + emac =3D netdev_priv(ndev); + qid1 =3D PRUETH_QUEUE2; + qid2 =3D PRUETH_QUEUE4; + + num_rx_packets =3D 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_common_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 =3D container_of(napi, struct prueth, napi_hpq); + ndev =3D prueth->hp->ndev; + emac =3D netdev_priv(ndev); + qid1 =3D PRUETH_QUEUE1; + qid2 =3D PRUETH_QUEUE3; + + num_rx_packets =3D 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 =3D (struct prueth_ndev_priority *)dev_id; + ndev =3D ndev_prio->ndev; + emac =3D netdev_priv(ndev); + prueth =3D emac->prueth; + + /* disable Rx system event */ + if (ndev_prio->priority =3D=3D 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 =3D emac->prueth; + int ret; + + /* Request irq when first port is initialized */ + if (prueth->emac_configured) + return 0; + + ret =3D 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"); + goto free_napi_hpq_lpq; + } + + ret =3D 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); + +free_napi_hpq_lpq: + napi_disable(&prueth->napi_lpq); + napi_disable(&prueth->napi_hpq); + + 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 =3D emac->prueth; + + /* HSR/PRP/Switch: 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 index 6276dd1e8bb1..239542101943 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c @@ -152,6 +152,14 @@ static void icssm_prueth_lre_protocol_init(struct prue= th *prueth) dram1 + ICSS_LRE_SUP_ADDR_LOW); } =20 +static void icssm_prueth_lre_config_packet_timestamping(struct prueth *pru= eth) +{ + void __iomem *sram =3D 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; @@ -202,6 +210,11 @@ void icssm_prueth_lre_config(struct prueth *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); + } =20 void icssm_prueth_lre_cleanup(struct prueth *prueth) diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/= net/ethernet/ti/icssm/icssm_prueth_switch.c index 66866ea37913..9d67fc7c23ac 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c @@ -886,6 +886,14 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth) sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST])); } =20 +void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth) +{ + void __iomem *sram =3D prueth->mem[PRUETH_MEM_SHARED_RAM].va; + + writeb(1, sram + SWITCH_PRIORITY_INTRS_STATUS_OFFSET); + writeb(1, sram + SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET); +} + static int icssm_prueth_sw_port_config(struct prueth *prueth, enum prueth_port port_id) { diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/= net/ethernet/ti/icssm/icssm_prueth_switch.h index e6111bba166e..fe7197f9a6da 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h @@ -29,6 +29,7 @@ void icssm_prueth_sw_fdb_del(struct prueth_emac *emac, int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac); int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac); void icssm_prueth_sw_hostconfig(struct prueth *prueth); +void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth); int icssm_prueth_sw_emac_config(struct prueth_emac *emac); int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *nd= ev); int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac, diff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/eth= ernet/ti/icssm/icssm_switch.h index 5ba9ce14da44..b4d6b8660539 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_switch.h +++ b/drivers/net/ethernet/ti/icssm/icssm_switch.h @@ -301,6 +301,12 @@ #define P0_Q1_BD_OFFSET P0_BUFFER_DESC_OFFSET #define P0_BUFFER_DESC_OFFSET SRAM_START_OFFSET =20 +/* Enable/disable interrupts for high/low priority instead of per port. + * 0 =3D disabled (default), 1 =3D enabled + */ +#define SWITCH_PRIORITY_INTRS_STATUS_OFFSET 0x1FAA +#define SWITCH_TIMESTAMP_PKTS_STATUS_OFFSET 0x1FAB + /* Memory Usage of L3 OCMC RAM */ =20 /* L3 64KB Memory - mainly buffer Pool */ --=20 2.43.0 From nobody Thu Sep 24 20:03:13 2026 Received: from server.couthit.com (server.couthit.com [162.240.164.96]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8D6EE4A3F02; Mon, 21 Sep 2026 13:45:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.240.164.96 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998324; cv=none; b=ZfV/EgppcHIllfe7MmyZOkjgTuwsOm+PctFW/HABrSEu3az2UWIqRkvk1JT/gt3hJNIqmPmdWjLC0x6LcjfWXtYkTa2fA9uowabIsTJFd4eGxtCA0+x7ZtdQp1TOh4zHqbQLh5bVsyUj6yUXCAtCzINo15MHJ6wbxIuA2I8z5Pg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789998324; c=relaxed/simple; bh=Op4A8A0y0QmcuxvMhUNjOZEkEM/dD52jMn+xHhHVlTo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BH9br1z7PlNAmvDcVXzzV/vZudhyjdeoS4e/l4QMOjcmtPnCFj3UasgK6WZAEOdzKXqUqaiua4AOVR0iUdAE12aIPPjOcn8CKUTdmPzrWd+iMtlQ0Gt1v5J0gzJIAoYqmC7lQMuYyBv2OfMV3Kp6fr/n90G4oRRIgOTKZF/0i0A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com; spf=pass smtp.mailfrom=couthit.com; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b=FWHCxf+U; arc=none smtp.client-ip=162.240.164.96 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=couthit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=couthit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=couthit.com header.i=@couthit.com header.b="FWHCxf+U" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=couthit.com ; s=default; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=7OSlXD2h1PRBcpZ6rRtTEAuyRTVRcDI3N5oG8HVqHUU=; b=FWHCxf+UpDtYgAM9THc4sgWIrt oV2v3TBanYvtwypBO2ubdaXpIwllzTSNq9Ndav9w85RSBG01SOPlzDr+2tK5EashB+CI3o8+osQAD ra6uUEMd0eYUDi2WcsAjs3VJrI2cm0ti8A2Mv1yH9EOeW6ml+xhfCDX9PSX5x4gtGQA7UNW0biBK6 iPlyOldLWkIrmdKID3L8Vj7mJlbABVuMshRV1OZhd8UzfHQG++Imio2lDvKClTOWeR0WRGI2uYk+s 8/qu93L9GhBlQsJCmoK18JAsXj8xyut2x7Y1dIWwT467RUeNW8FmQI94RRKgZzD7WD0ovQ+vWihHn s9RhQroA==; Received: from [115.246.246.98] (port=57742 helo=cypher.couthit.local) by server.couthit.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.5) (envelope-from ) id 1x8eKY-00000008L1c-0sze; Mon, 21 Sep 2026 09:45:15 -0400 From: Parvathi Pudi To: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, danishanwar@ti.com, parvathi@couthit.com, rogerq@kernel.org, pmohan@couthit.com, afd@ti.com, vadim.fedorenko@linux.dev, haokexin@gmail.com, basharath@couthit.com, arnd@arndb.de Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org, pratheesh@ti.com, j-rameshbabu@ti.com, vigneshr@ti.com, praneeth@ti.com, srk@ti.com, rogerq@ti.com, m-malladi@ti.com, krishna@couthit.com, mohan@couthit.com Subject: [PATCH net-next v4 3/3] net: ti: icssm-prueth: Support duplicate HW offload feature for HSR and PRP Date: Mon, 21 Sep 2026 19:11:23 +0530 Message-ID: <20260921134343.200426-4-parvathi@couthit.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260921134343.200426-1-parvathi@couthit.com> References: <20260921134343.200426-1-parvathi@couthit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - server.couthit.com X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - couthit.com X-Get-Message-Sender-Via: server.couthit.com: authenticated_id: parvathi@couthit.com X-Authenticated-Sender: server.couthit.com: parvathi@couthit.com X-Source: X-Source-Args: X-Source-Dir: Content-Type: text/plain; charset="utf-8" From: Roger Quadros In HSR and PRP modes each outgoing frame must be sent on both PRU slave ports. Previously the driver was writing the frame into each port's transmit queue independently after updating the tags resulting in performing two OCMC buffer copy operations. Frame duplicate offloading is implemented with a common shared queue between the two ports. The driver writes the frame once into OCMC RAM, each port reads from the shared queue and replicates the transmission to both PRU ports, synchronising between PRU ports are maintained within firmware with appropriate handling. For HSR the driver inspects the encapsulated ethertype in the HSR tag. PTP frames (ETH_P_1588) are sent on the directed port only to avoid double duplication and all other HSR frames are duplicated to both ports. VLAN-tagged HSR frames are handled by advancing past the 4-byte VLAN header before reading the HSR tag. For PRP the driver checks the 6-byte RCT trailer for the ETH_P_PRP suffix to identify redundancy-tagged frames. Frames without an RCT are sent on the originating port only. On the receive side, icssm_parse_packet_info() reads the new HostRecv bit from the firmware instead of the unused BD Port field. This bit tells which HSR copy should be passed to the network stack. icssm_emac_rx_packet() ignores the other duplicate without creating an skb, since receiving duplicate HSR packets is expected and not an error. The OCMC-to-skb copy is also split into MAC header, VLAN, and payload parts. This makes it possible to remove the HSR tag from the middle of the packet. Signed-off-by: Roger Quadros Signed-off-by: Andrew F. Davis Signed-off-by: Parvathi Pudi --- drivers/net/ethernet/ti/icssm/icssm_prueth.c | 266 ++++++++++++--- drivers/net/ethernet/ti/icssm/icssm_prueth.h | 13 +- .../ethernet/ti/icssm/icssm_prueth_common.c | 11 +- .../ethernet/ti/icssm/icssm_prueth_switch.c | 322 +++++++++++++++++- .../ethernet/ti/icssm/icssm_prueth_switch.h | 1 + drivers/net/ethernet/ti/icssm/icssm_switch.h | 34 +- 6 files changed, 576 insertions(+), 71 deletions(-) diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/eth= ernet/ti/icssm/icssm_prueth.c index 4a37935b8d35..6d5dd17b548a 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c @@ -36,12 +36,14 @@ #include "../icssg/icss_iep.h" =20 #define OCMC_RAM_SIZE (SZ_64K) +#define PRUETH_ETHER_TYPE_OFFSET 12 =20 #define TX_START_DELAY 0x40 #define TX_CLK_DELAY_100M 0x6 #define HR_TIMER_TX_DELAY_US 100 =20 #define NETIF_PRUETH_LRE_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \ + NETIF_F_HW_HSR_DUP | \ NETIF_F_HW_HSR_TAG_RM) =20 /* ICSSM (v2.1) - supports 64-bit IEP counter. @@ -79,6 +81,32 @@ static void icssm_prueth_set_fw_offsets(struct prueth *p= rueth) } } =20 +/* Queue Descriptors initialization for HSR PRP */ +const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES] =3D= { + [PRUETH_PORT_QUEUE_HOST] =3D { + { .rd_ptr =3D P0_Q1_BD_OFFSET, .wr_ptr =3D P0_Q1_BD_OFFSET, }, + { .rd_ptr =3D P0_Q2_BD_OFFSET, .wr_ptr =3D P0_Q2_BD_OFFSET, }, + { .rd_ptr =3D P0_Q3_BD_OFFSET, .wr_ptr =3D P0_Q3_BD_OFFSET, }, + { .rd_ptr =3D P0_Q4_BD_OFFSET, .wr_ptr =3D P0_Q4_BD_OFFSET, }, + }, + [PRUETH_PORT_QUEUE_MII0] =3D { + { .rd_ptr =3D P0_Q3_BD_OFFSET, .wr_ptr =3D P0_Q3_BD_OFFSET, }, + { .rd_ptr =3D P0_Q4_BD_OFFSET, .wr_ptr =3D P0_Q4_BD_OFFSET, }, + { .rd_ptr =3D P1_Q3_TXOPT_BD_OFFSET, + .wr_ptr =3D P1_Q3_TXOPT_BD_OFFSET, }, + { .rd_ptr =3D P2_Q1_TXOPT_BD_OFFSET, + .wr_ptr =3D P2_Q1_TXOPT_BD_OFFSET, }, + }, + [PRUETH_PORT_QUEUE_MII1] =3D { + { .rd_ptr =3D P0_Q1_BD_OFFSET, .wr_ptr =3D P0_Q1_BD_OFFSET, }, + { .rd_ptr =3D P0_Q2_BD_OFFSET, .wr_ptr =3D P0_Q2_BD_OFFSET, }, + { .rd_ptr =3D P1_Q3_TXOPT_BD_OFFSET, + .wr_ptr =3D P1_Q3_TXOPT_BD_OFFSET, }, + { .rd_ptr =3D P2_Q1_TXOPT_BD_OFFSET, + .wr_ptr =3D P2_Q1_TXOPT_BD_OFFSET, }, + } +}; + static void icssm_prueth_write_reg(struct prueth *prueth, enum prueth_mem region, unsigned int reg, u32 val) @@ -97,6 +125,17 @@ static void icssm_prueth_write_reg(struct prueth *pruet= h, static enum pruss_mem pruss_mem_ids[] =3D { PRUSS_MEM_DRAM0, PRUSS_MEM_DRA= M1, PRUSS_MEM_SHRD_RAM2 }; =20 +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] =3D { [PRUETH_PORT_QUEUE_HOST] =3D { [PRUETH_QUEUE1] =3D { @@ -551,15 +590,22 @@ 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 =3D NULL; struct prueth_queue_desc __iomem *queue_desc; const struct prueth_queue_info *txqueue; - struct net_device *ndev =3D emac->ndev; struct prueth *prueth =3D emac->prueth; + struct hsr_txopt_ethhdr *hsr_ethhdr; unsigned int buffer_desc_count; int free_blocks, update_block; + struct vlan_ethhdr *vlan_hdr; bool buffer_wrapped =3D 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 =3D false; int pkt_block_size; void __iomem *sram; void __iomem *dram; @@ -567,16 +613,13 @@ 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; =20 if (!PRUETH_IS_EMAC(prueth)) dram =3D prueth->mem[PRUETH_MEM_DRAM1].va; else dram =3D 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; - } =20 /* which port to tx: MII0 or MII1 */ txport =3D emac->tx_port_queue; @@ -584,7 +627,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac = *emac, pktlen =3D skb->len; /* Get the tx queue */ queue_desc =3D emac->tx_queue_descs + queue_id; - if (!PRUETH_IS_EMAC(prueth)) + /* Tx queue context */ + if (prueth_is_lre(prueth)) + txqueue =3D &lre_queue_infos[txport][queue_id]; + else if (PRUETH_IS_SWITCH(prueth)) txqueue =3D &sw_queue_infos[txport][queue_id]; else txqueue =3D &queue_infos[txport][queue_id]; @@ -607,6 +653,29 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac = *emac, free_blocks =3D buffer_desc_count; } =20 + /* Fetch queue state for the second LRE port */ + if (prueth_is_lre(prueth)) { + queue_desc_other_port =3D emac->tx_queue_descs_other_port + + queue_id; + bd_rd_ptr_other_port =3D readw(&queue_desc_other_port->rd_ptr); + + read_block_other_port =3D (bd_rd_ptr_other_port - + txqueue->buffer_desc_offset) / BD_SIZE; + + if (write_block > read_block_other_port) { + free_blocks_other_port =3D buffer_desc_count - + write_block; + free_blocks_other_port +=3D read_block_other_port; + } else if (write_block < read_block_other_port) { + free_blocks_other_port =3D read_block_other_port - + write_block; + } else { + free_blocks_other_port =3D buffer_desc_count; + } + + if (free_blocks_other_port < free_blocks) + free_blocks =3D free_blocks_other_port; + } pkt_block_size =3D DIV_ROUND_UP(pktlen, ICSS_BLOCK_SIZE); if (pkt_block_size > free_blocks) /* out of queue space */ return -ENOBUFS; @@ -656,6 +725,57 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac = *emac, if (PRUETH_IS_HSR(prueth)) wr_buf_desc |=3D BIT(PRUETH_BD_HSR_FRAME_SHIFT); =20 + if (prueth_is_lre(prueth)) { + ethhdr =3D (struct ethhdr *)skb_mac_header(skb); + proto =3D ethhdr->h_proto; + + if (proto =3D=3D htons(ETH_P_8021Q)) { + vlan_hdr =3D (struct vlan_ethhdr *)ethhdr; + proto =3D vlan_hdr->h_vlan_encapsulated_proto; + is_vlan =3D true; + } + + /* Check if the SKB has HSR tag */ + if (PRUETH_IS_HSR(prueth) && proto =3D=3D htons(ETH_P_HSR)) { + hdr =3D skb_mac_header(skb); + + if (is_vlan) { + hsr_ethhdr =3D + (struct hsr_txopt_ethhdr *)(hdr + + VLAN_HLEN); + } else { + hsr_ethhdr =3D (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 !=3D + htons(ETH_P_1588)) { + wr_buf_desc |=3D PRUETH_BD_LAN_INFO_MASK; + } else { + wr_buf_desc |=3D (txport << + PRUETH_BD_LAN_A_SHIFT); + } + wr_buf_desc |=3D PRUETH_BD_RED_PKT_MASK; + } else if (PRUETH_IS_PRP(prueth)) { + /* Check if the SKB has PRP tag */ + struct prp_txopt_rct *rct =3D + (struct prp_txopt_rct *)(skb_tail_pointer(skb) - + ICSSM_LRE_TAG_SIZE); + + if (rct->prp_suffix =3D=3D htons(ETH_P_PRP)) { + wr_buf_desc |=3D PRUETH_BD_LAN_INFO_MASK; + wr_buf_desc |=3D PRUETH_BD_RED_PKT_MASK; + } else { + wr_buf_desc |=3D (txport << + PRUETH_BD_LAN_A_SHIFT); + } + } else { + wr_buf_desc |=3D (txport << PRUETH_BD_LAN_A_SHIFT); + } + } + sram =3D prueth->mem[PRUETH_MEM_SHARED_RAM].va; if (!PRUETH_IS_EMAC(prueth)) writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr)); @@ -668,6 +788,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac = *emac, update_wr_ptr =3D txqueue->buffer_desc_offset + (update_block * BD_SIZE); writew(update_wr_ptr, &queue_desc->wr_ptr); =20 + /* update the write pointer in queue descriptor of other port */ + if (prueth_is_lre(prueth)) + writew(update_wr_ptr, &queue_desc_other_port->wr_ptr); + return 0; } =20 @@ -680,8 +804,10 @@ void icssm_parse_packet_info(struct prueth *prueth, u3= 2 buffer_descriptor, else pkt_info->start_offset =3D false; =20 - pkt_info->port =3D (buffer_descriptor & PRUETH_BD_PORT_MASK) >> - PRUETH_BD_PORT_SHIFT; + /* Flag from BD to indicate packet is valid for HOST or not. */ + pkt_info->host_recv_flag =3D !!(buffer_descriptor & + PRUETH_BD_HOST_RECV_MASK); + pkt_info->length =3D (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >> PRUETH_BD_LENGTH_SHIFT; pkt_info->broadcast =3D !!(buffer_descriptor & PRUETH_BD_BROADCAST_MASK); @@ -711,17 +837,19 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u1= 6 *bd_rd_ptr, struct net_device *ndev =3D emac->ndev; unsigned int buffer_desc_count; int read_block, update_block; + struct vlan_ethhdr *vlan_hdr; unsigned int actual_pkt_len; bool buffer_wrapped =3D false; + int adjust_for_hsr_tag =3D 0; void *src_addr, *dst_addr; - u16 start_offset =3D 0; + bool has_hsr_tag =3D false; + bool has_vlan =3D false; + struct ethhdr *ethhdr; struct sk_buff *skb; int pkt_block_size; void *ocmc_ram; + __be16 proto; =20 - if (PRUETH_IS_HSR(emac->prueth)) - start_offset =3D (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 @@ -730,6 +858,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 = *bd_rd_ptr, buffer_desc_count =3D icssm_get_buff_desc_count(rxqueue); read_block =3D (*bd_rd_ptr - rxqueue->buffer_desc_offset) / BD_SIZE; pkt_block_size =3D DIV_ROUND_UP(pkt_info->length, ICSS_BLOCK_SIZE); + /* OCMC RAM is not cached and read order is not important */ + ocmc_ram =3D (__force void *)emac->prueth->mem[PRUETH_MEM_OCMC].va; =20 /* calculate end BD address post read */ update_block =3D read_block + pkt_block_size; @@ -741,11 +871,36 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u1= 6 *bd_rd_ptr, buffer_wrapped =3D true; } =20 + /* Get the start address of the first buffer from + * the read buffer description + */ + src_addr =3D ocmc_ram + rxqueue->buffer_offset + + (read_block * ICSS_BLOCK_SIZE); + /* calculate new pointer in ram */ *bd_rd_ptr =3D rxqueue->buffer_desc_offset + (update_block * BD_SIZE); =20 - /* Exclude the HSR tag bytes already stripped by firmware, if any. */ - actual_pkt_len =3D pkt_info->length - start_offset; + if (PRUETH_IS_HSR(emac->prueth)) { + if (!pkt_info->host_recv_flag) + return 0; + + ethhdr =3D (struct ethhdr *)src_addr; + proto =3D ethhdr->h_proto; + + if (proto =3D=3D htons(ETH_P_8021Q)) { + has_vlan =3D true; + vlan_hdr =3D (struct vlan_ethhdr *)ethhdr; + proto =3D vlan_hdr->h_vlan_encapsulated_proto; + } + + if (proto =3D=3D htons(ETH_P_HSR) && !pkt_info->timestamp) + has_hsr_tag =3D true; + } + + actual_pkt_len =3D pkt_info->length; + + if (has_hsr_tag) + actual_pkt_len -=3D ICSSM_LRE_TAG_SIZE; =20 /* Allocate a socket buffer for this packet */ skb =3D netdev_alloc_skb_ip_align(ndev, actual_pkt_len); @@ -757,15 +912,23 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u1= 6 *bd_rd_ptr, =20 dst_addr =3D skb->data; =20 - /* OCMC RAM is not cached and read order is not important */ - ocmc_ram =3D (__force void *)emac->prueth->mem[PRUETH_MEM_OCMC].va; + /* Copy destination and source MAC address */ + memcpy(dst_addr, src_addr, PRUETH_ETHER_TYPE_OFFSET); + src_addr +=3D PRUETH_ETHER_TYPE_OFFSET; + dst_addr +=3D PRUETH_ETHER_TYPE_OFFSET; =20 - /* Get the start address of the first buffer from - * the read buffer description - */ - src_addr =3D ocmc_ram + rxqueue->buffer_offset + - (read_block * ICSS_BLOCK_SIZE); - src_addr +=3D start_offset; + adjust_for_hsr_tag +=3D PRUETH_ETHER_TYPE_OFFSET; + + if (has_vlan) { + memcpy(dst_addr, src_addr, VLAN_HLEN); + src_addr +=3D VLAN_HLEN; + dst_addr +=3D VLAN_HLEN; + adjust_for_hsr_tag +=3D VLAN_HLEN; + } + + /* HSR tag removal handling */ + if (has_hsr_tag) + src_addr +=3D ICSSM_LRE_TAG_SIZE; =20 /* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */ if (buffer_wrapped) { /* wrapped around buffer */ @@ -779,22 +942,23 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u1= 6 *bd_rd_ptr, if (pkt_info->length < bytes) bytes =3D pkt_info->length; =20 - /* If applicable, account for the HSR tag removed */ - bytes -=3D start_offset; + if (has_hsr_tag) + bytes -=3D ICSSM_LRE_TAG_SIZE; =20 /* copy non-wrapped part */ - memcpy(dst_addr, src_addr, bytes); + memcpy(dst_addr, src_addr, bytes - adjust_for_hsr_tag); =20 /* copy wrapped part */ - dst_addr +=3D bytes; + dst_addr +=3D (bytes - adjust_for_hsr_tag); remaining =3D actual_pkt_len - bytes; =20 src_addr =3D ocmc_ram + rxqueue->buffer_offset; memcpy(dst_addr, src_addr, remaining); src_addr +=3D remaining; } else { - memcpy(dst_addr, src_addr, actual_pkt_len); - src_addr +=3D actual_pkt_len; + memcpy(dst_addr, src_addr, actual_pkt_len - + adjust_for_hsr_tag); + src_addr +=3D actual_pkt_len - adjust_for_hsr_tag; } =20 if (PRUETH_IS_SWITCH(emac->prueth)) { @@ -1309,17 +1473,31 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(str= uct sk_buff *skb, struct net_device *ndev) { struct prueth_emac *emac =3D netdev_priv(ndev); + /* Spinlock for Tx Queues */ + spinlock_t *lock_queue; + unsigned long flags; int ret; u16 qid; =20 qid =3D icssm_prueth_get_tx_queue_id(emac->prueth, skb); + /* Select the TX queue spin lock for this queue ID */ + if (prueth_is_lre(emac->prueth)) + lock_queue =3D &emac->prueth->lre_host_queue_lock[qid - 2]; + else + lock_queue =3D &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"); + atomic64_inc(&emac->stats.tx_dropped); + return NETDEV_TX_OK; + } + + spin_lock_irqsave(lock_queue, flags); ret =3D icssm_prueth_tx_enqueue(emac, skb, qid); - if (ret) { - if (ret !=3D -ENOBUFS && netif_msg_tx_err(emac) && - net_ratelimit()) - netdev_err(ndev, "packet queue failed: %d\n", ret); + spin_unlock_irqrestore(lock_queue, flags); + if (ret) goto fail_tx; - } =20 atomic64_inc(&emac->stats.tx_packets); atomic64_add(skb->len, &emac->stats.tx_bytes); @@ -1564,19 +1742,22 @@ static void icssm_emac_ndo_set_rx_mode(struct net_d= evice *ndev) static netdev_features_t icssm_emac_ndo_fix_features(struct net_device *nd= ev, netdev_features_t features) { - /* hsr tag removal offload and hsr fwd offload are tightly coupled in - * firmware implementation. Both these features need to be enabled / - * disabled together. + /* hsr tag removal offload, hsr fwd offload and hsr dup offload are + * tightly coupled in firmware implementation. These features + * must always be enabled/disabled together. */ if (!(ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES)) if ((features & NETIF_F_HW_HSR_FWD) || - (features & NETIF_F_HW_HSR_TAG_RM)) + (features & NETIF_F_HW_HSR_TAG_RM) || + (features & NETIF_F_HW_HSR_DUP)) features |=3D NETIF_PRUETH_LRE_OFFLOAD_FEATURES; =20 if ((ndev->features & NETIF_F_HW_HSR_FWD) || - (ndev->features & NETIF_F_HW_HSR_TAG_RM)) + (ndev->features & NETIF_F_HW_HSR_TAG_RM) || + (ndev->features & NETIF_F_HW_HSR_DUP)) if (!(features & NETIF_F_HW_HSR_FWD) || - !(features & NETIF_F_HW_HSR_TAG_RM)) + !(features & NETIF_F_HW_HSR_TAG_RM) || + !(features & NETIF_F_HW_HSR_DUP)) features &=3D ~NETIF_PRUETH_LRE_OFFLOAD_FEATURES; =20 return features; @@ -1777,6 +1958,9 @@ static int icssm_prueth_netdev_init(struct prueth *pr= ueth, spin_lock_init(&emac->lock); spin_lock_init(&emac->addr_lock); =20 + spin_lock_init(&emac->host_queue_lock[0]); + spin_lock_init(&emac->host_queue_lock[1]); + /* get mac address from DT and set private and netdev addr */ ret =3D of_get_ethdev_address(eth_node, ndev); if (!is_valid_ether_addr(ndev->dev_addr)) { @@ -2376,6 +2560,8 @@ static int icssm_prueth_probe(struct platform_device = *pdev) =20 prueth->support_lre =3D has_lre; spin_lock_init(&prueth->addr_lock); + spin_lock_init(&prueth->lre_host_queue_lock[0]); + spin_lock_init(&prueth->lre_host_queue_lock[1]); /* setup netdev interfaces */ if (eth0_node) { ret =3D icssm_prueth_netdev_init(prueth, eth0_node); diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/eth= ernet/ti/icssm/icssm_prueth.h index 8cca5b87d4ff..9ed4176b66fa 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h @@ -94,7 +94,7 @@ 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 @@ -105,7 +105,7 @@ struct prueth_queue_info { struct prueth_packet_info { bool start_offset; bool shadow; - unsigned int port; + bool host_recv_flag; unsigned int length; bool broadcast; bool error; @@ -240,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; =20 int link; int speed; @@ -263,6 +265,7 @@ struct prueth_emac { spinlock_t lock; spinlock_t addr_lock; /* serialize access to VLAN/MC filter table */ =20 + spinlock_t host_queue_lock[NUM_QUEUES / 2]; struct hrtimer tx_hrtimer; struct prueth_emac_stats stats; int offload_fwd_mark; @@ -314,9 +317,15 @@ struct prueth { u8 emac_configured; u8 hsr_members; u8 br_members; + + /* Per-queue TX lock - LRE uses only two priority queues + * one for high priority and the other for low priority. + */ + spinlock_t lre_host_queue_lock[NUM_QUEUES / 2]; }; =20 extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES]; +extern const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEU= ES]; =20 void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor, struct prueth_packet_info *pkt_info); diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c b/drivers/= net/ethernet/ti/icssm/icssm_prueth_common.c index 9b7b876240b5..e7139534b56f 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c @@ -163,16 +163,17 @@ static int icssm_prueth_common_emac_rx_packets(struct= prueth_emac *emac, used++; } =20 - /* Zero the BD after consuming it, a misaligned rd_ptr - * would otherwise mistake stale data for a valid incoming - * frame. + /* Leave the BD intact after reading. Firmware reuses it to + * forward the frame to the second LRE port. */ if (port =3D=3D 0) { - writel(0, shared_ram + bd_rd_ptr); + if (PRUETH_IS_SWITCH(prueth)) + writel(0, shared_ram + bd_rd_ptr); writew(update_rd_ptr, &queue_desc->rd_ptr); bd_rd_ptr =3D update_rd_ptr; } else { - writel(0, shared_ram + bd_rd_ptr_o); + if (PRUETH_IS_SWITCH(prueth)) + writel(0, shared_ram + bd_rd_ptr_o); writew(update_rd_ptr, &queue_desc_o->rd_ptr); bd_rd_ptr_o =3D update_rd_ptr; } diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/= net/ethernet/ti/icssm/icssm_prueth_switch.c index 9d67fc7c23ac..a36664d70d82 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] =3D { }, }; =20 +/* Tx Queue context for HSR and PRP */ +const struct prueth_queue_info lre_queue_infos[][NUM_QUEUES] =3D { + [PRUETH_PORT_QUEUE_HOST] =3D { + [PRUETH_QUEUE1] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + [PRUETH_QUEUE1] =3D { + 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_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] =3D { + 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_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] =3D { + 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] =3D { + 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] =3D { + [PRUETH_QUEUE1] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + 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] =3D= { + [PRUETH_PORT_QUEUE_HOST] =3D { + [PRUETH_QUEUE1] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + [PRUETH_QUEUE1] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + [PRUETH_QUEUE1] =3D { + 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] =3D { + 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] =3D { + 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] =3D { + 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) =20 /* queue information table */ dram =3D 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])); =20 /* buffer descriptor offset table*/ dram =3D dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR; @@ -882,8 +1069,15 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth) =20 /* queue table */ dram =3D 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])); + } =20 void icssm_prueth_sw_config_packet_timestamping(struct prueth *prueth) @@ -898,7 +1092,7 @@ static int icssm_prueth_sw_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; + void __iomem *dram, *dram_base; struct prueth_emac *emac; void __iomem *dram1_base; =20 @@ -909,26 +1103,17 @@ static int icssm_prueth_sw_port_config(struct prueth= *prueth, tx_context_ofs_addr =3D TX_CONTEXT_P1_Q1_OFFSET_ADDR; rx_context_ofs =3D P1_Q1_RX_CONTEXT_OFFSET; queue_desc_ofs =3D P1_QUEUE_DESC_OFFSET; - - /* for switch PORT MII0 mac addr is in DRAM0. */ - dram_mac =3D prueth->mem[PRUETH_MEM_DRAM0].va; break; case PRUETH_PORT_MII1: tx_context_ofs_addr =3D TX_CONTEXT_P2_Q1_OFFSET_ADDR; rx_context_ofs =3D P2_Q1_RX_CONTEXT_OFFSET; queue_desc_ofs =3D P2_QUEUE_DESC_OFFSET; - - /* for switch PORT MII1 mac addr is in DRAM1. */ - dram_mac =3D prueth->mem[PRUETH_MEM_DRAM1].va; break; default: netdev_err(emac->ndev, "invalid port\n"); return -EINVAL; } =20 - /* setup mac address */ - memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, 6); - /* Remaining switch port configs are in DRAM1 */ dram_base =3D prueth->mem[PRUETH_MEM_DRAM1].va; =20 @@ -983,21 +1168,126 @@ static int icssm_prueth_sw_port_config(struct pruet= h *prueth, return 0; } =20 +/* Configure TX/RX queue contexts and buffer descriptor tables for LRE por= t */ +static void 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; + struct prueth_emac *emac; + + emac =3D prueth->emac[port_id - 1]; + if (port_id =3D=3D PRUETH_PORT_MII0) { + tx_context_ofs_addr =3D TX_CONTEXT_P1_Q1_OFFSET_ADDR; + rx_context_ofs =3D P1_Q1_RX_CONTEXT_OFFSET; + queue_desc_ofs =3D P1_QUEUE_DESC_OFFSET; + } else if (port_id =3D=3D PRUETH_PORT_MII1) { + tx_context_ofs_addr =3D TX_CONTEXT_P2_Q1_OFFSET_ADDR; + rx_context_ofs =3D P2_Q1_RX_CONTEXT_OFFSET; + queue_desc_ofs =3D P2_QUEUE_DESC_OFFSET; + } + + /* Remaining switch port configs are in DRAM1 */ + dram_base =3D 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 =3D 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 =3D 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 =3D 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 =3D dram_base + P0_QUEUE_DESC_OFFSET; + emac->tx_queue_descs =3D dram_base + + lre_rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset; + + if (port_id =3D=3D PRUETH_PORT_MII0) { + emac->tx_queue_descs_other_port =3D dram_base + + lre_rx_queue_infos + [port_id + 1][PRUETH_QUEUE1].queue_desc_offset; + } else if (port_id =3D=3D PRUETH_PORT_MII1) { + emac->tx_queue_descs_other_port =3D dram_base + + lre_rx_queue_infos + [port_id - 1][PRUETH_QUEUE1].queue_desc_offset; + } +} + int icssm_prueth_sw_emac_config(struct prueth_emac *emac) { struct prueth *prueth =3D emac->prueth; u32 sharedramaddr, ocmcaddr; - int ret; + void __iomem *dram_mac; + int ret =3D 0; =20 /* PRU needs local shared RAM address for C28 */ sharedramaddr =3D ICSS_LOCAL_SHARED_RAM; /* PRU needs real global OCMC address for C30*/ ocmcaddr =3D (u32)prueth->mem[PRUETH_MEM_OCMC].pa; =20 + if (emac->port_id =3D=3D PRUETH_PORT_MII0) { + dram_mac =3D prueth->mem[PRUETH_MEM_DRAM0].va; + } else if (emac->port_id =3D=3D PRUETH_PORT_MII1) { + dram_mac =3D prueth->mem[PRUETH_MEM_DRAM1].va; + } else { + netdev_err(emac->ndev, "invalid port\n"); + return -EINVAL; + } + + /* setup mac address */ + memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, ETH_ALEN); + if (prueth->emac_configured & BIT(emac->port_id)) return 0; =20 - ret =3D icssm_prueth_sw_port_config(prueth, emac->port_id); + if (prueth_is_lre(prueth) && !prueth->emac_configured) { + icssm_prueth_lre_port_config(prueth, PRUETH_PORT_MII0); + icssm_prueth_lre_port_config(prueth, PRUETH_PORT_MII1); + } else if (PRUETH_IS_SWITCH(prueth)) { + ret =3D icssm_prueth_sw_port_config(prueth, emac->port_id); + } if (ret) return ret; =20 diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/= net/ethernet/ti/icssm/icssm_prueth_switch.h index fe7197f9a6da..a009827b01d7 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); =20 extern const struct prueth_queue_info sw_queue_infos[][4]; +extern const struct prueth_queue_info lre_queue_infos[][4]; =20 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/eth= ernet/ti/icssm/icssm_switch.h index b4d6b8660539..ec5925c812c3 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) */ =20 +#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 by= tes. * HOST PORT QUEUES can buffer up to 4 full sized frames per queue */ @@ -51,18 +54,19 @@ * address found in FDB). For switch only. * 8..12 Block_length number of valid bytes in this specific block. * Will be <=3D32 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. + * For HSR only. * 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 =3D 0 indicates phy port 1 and - * Port =3D 1 indicates phy port 2. - * Egress: 0 sends on phy port 1 and 1 sends on - * phy port 2. Port =3D 2 goes over MAC table - * look-up + * 16..17 LAN Destination LAN for transmission: + * bit 16 =3D LAN A, bit 17 =3D LAN B, set both to + * duplicate to both LANs. For HSR and PRP only. * 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 +90,21 @@ #define PRUETH_BD_SW_FLOOD_MASK BIT(7) #define PRUETH_BD_SW_FLOOD_SHIFT 7 =20 +#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 =20 #define PRUETH_BD_TIMESTAMP_MASK BIT(15) #define PRUETH_BD_TIMESTAMP_SHIFT 15 =20 -#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 =20 #define PRUETH_BD_LENGTH_MASK GENMASK(28, 18) #define PRUETH_BD_LENGTH_SHIFT 18 @@ -298,6 +309,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_SI= ZE) +#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 =20 @@ -334,6 +348,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 =20 --=20 2.43.0