Commit 3254ce83 authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'introduce-hsr-offload-support-for-icssg'

MD Danish Anwar says:

====================
Introduce HSR offload support for ICSSG

This series introduces HSR offload support for ICSSG driver. To support HSR
offload to hardware, ICSSG HSR firmware is used.

This series introduces,
1. HSR frame offload support for ICSSG driver.
2. HSR Tx Packet duplication offload
3. HSR Tx Tag and Rx Tag offload
4. Multicast filtering support in HSR offload mode.
5. Dependencies related to IEP.

HSR Test Setup:
--------------

     ___________           ___________           ___________
    |           | Link AB |           | Link BC |           |
  __|   AM64*   |_________|   AM64    |_________|   AM64*   |___
 |  | Station A |         | Station B |         | Station C |   |
 |  |___________|         |___________|         |___________|   |
 |                                                              |
 |______________________________________________________________|
                            Link CA
 *Could be any device that supports two ethernet interfaces.

Steps to switch to HSR frame forward offload mode:
-------------------------------------------------
Example assuming eth1, eth2 ports of ICSSG1 on AM64-EVM

  1) Enable HSR offload for both interfaces
      ethtool -K eth1 hsr-fwd-offload on
      ethtool -K eth1 hsr-dup-offload on
      ethtool -K eth1 hsr-tag-ins-offload on
      ethtool -K eth1 hsr-tag-rm-offload on

      ethtool -K eth2 hsr-fwd-offload on
      ethtool -K eth2 hsr-dup-offload on
      ethtool -K eth2 hsr-tag-ins-offload on
      ethtool -K eth2 hsr-tag-rm-offload on

  2) Create HSR interface and add slave interfaces to it
      ip link add name hsr0 type hsr slave1 eth1 slave2 eth2 \
    supervision 45 version 1

  3) Add IP address to the HSR interface
      ip addr add <IP_ADDR>/24 dev hsr0

  4) Bring up the HSR interface
      ip link set hsr0 up

Switching back to previous mode:
--------------------------------
  1) Delete HSR interface
      ip link delete hsr0

  2) Disable HSR port-to-port offloading mode, packet duplication
      ethtool -K eth1 hsr-fwd-offload off
      ethtool -K eth1 hsr-dup-offload off
      ethtool -K eth1 hsr-tag-ins-offload off
      ethtool -K eth1 hsr-tag-rm-offload off

      ethtool -K eth2 hsr-fwd-offload off
      ethtool -K eth2 hsr-dup-offload off
      ethtool -K eth2 hsr-tag-ins-offload off
      ethtool -K eth2 hsr-tag-rm-offload off

Testing the port-to-port frame forward offload feature:
-----------------------------------------------------
  1) Connect the LAN cables as shown in the test setup.
  2) Configure Station A and Station C in HSR non-offload mode.
  3) Configure Station B is HSR offload mode.
  4) Since HSR is a redundancy protocol, disconnect cable "Link CA",
     to ensure frames from Station A reach Station C only through
     Station B.
  5) Run iperf3 Server on Station C and client on station A.
  7) Check the CPU usage on Station B.

CPU usage report on Station B using mpstat when running UDP iperf3:
-------------------------------------------------------------------

  1) Non-Offload case
  -------------------
  CPU  %usr  %nice  %sys %iowait  %irq  %soft  %steal  %guest   %idle
  all  0.00   0.00  0.50    0.00  3.52  29.15    0.00    0.00   66.83
    0  0.00   0.00  0.00    0.00  7.00  58.00    0.00    0.00   35.00
    1  0.00   0.00  0.99    0.00  0.99   0.00    0.00    0.00   98.02

  2) Offload case
  ---------------
  CPU  %usr  %nice  %sys %iowait  %irq  %soft  %steal  %guest   %idle
  all  0.00   0.00  0.00    0.00  0.50   0.00    0.00    0.00   99.50
    0  0.00   0.00  0.99    0.00  0.00   0.00    0.00    0.00   99.01
    1  0.00   0.00  0.00    0.00  0.00   0.00    0.00    0.00  100.00

Note:
1) At the very least, hsr-fwd-offload must be enabled.
   Without offloading the port-to-port offload, other
   HSR offloads cannot be enabled.

2) hsr-tag-ins-offload and hsr-dup-offload are tightly coupled in
   the firmware implementation. They both need to be enabled / disabled
   together.

v1: https://lore.kernel.org/20240808110800.1281716-1-danishanwar@ti.com/
v2: https://lore.kernel.org/20240813074233.2473876-1-danishanwar@ti.com
v3: https://lore.kernel.org/20240828091901.3120935-1-danishanwar@ti.com/
v4: https://lore.kernel.org/20240904100506.3665892-1-danishanwar@ti.com/
v5: https://lore.kernel.org/20240906111538.1259418-1-danishanwar@ti.com/

[0] https://lore.kernel.org/202409061658.vSwcFJiK-lkp@intel.com/
[1] https://lore.kernel.org/20240828091901.3120935-5-danishanwar@ti.com/
[2] https://lore.kernel.org/20240828091901.3120935-7-danishanwar@ti.com/
[3] https://lore.kernel.org/20240813074233.2473876-2-danishanwar@ti.com/
[4] https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=e846be0fba85
====================

Link: https://patch.msgid.link/20240911081603.2521729-1-danishanwar@ti.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 34c44eb3 1d6ae965
......@@ -53,78 +53,6 @@
#define IEP_CAP_CFG_CAPNR_1ST_EVENT_EN(n) BIT(LATCH_INDEX(n))
#define IEP_CAP_CFG_CAP_ASYNC_EN(n) BIT(LATCH_INDEX(n) + 10)
enum {
ICSS_IEP_GLOBAL_CFG_REG,
ICSS_IEP_GLOBAL_STATUS_REG,
ICSS_IEP_COMPEN_REG,
ICSS_IEP_SLOW_COMPEN_REG,
ICSS_IEP_COUNT_REG0,
ICSS_IEP_COUNT_REG1,
ICSS_IEP_CAPTURE_CFG_REG,
ICSS_IEP_CAPTURE_STAT_REG,
ICSS_IEP_CAP6_RISE_REG0,
ICSS_IEP_CAP6_RISE_REG1,
ICSS_IEP_CAP7_RISE_REG0,
ICSS_IEP_CAP7_RISE_REG1,
ICSS_IEP_CMP_CFG_REG,
ICSS_IEP_CMP_STAT_REG,
ICSS_IEP_CMP0_REG0,
ICSS_IEP_CMP0_REG1,
ICSS_IEP_CMP1_REG0,
ICSS_IEP_CMP1_REG1,
ICSS_IEP_CMP8_REG0,
ICSS_IEP_CMP8_REG1,
ICSS_IEP_SYNC_CTRL_REG,
ICSS_IEP_SYNC0_STAT_REG,
ICSS_IEP_SYNC1_STAT_REG,
ICSS_IEP_SYNC_PWIDTH_REG,
ICSS_IEP_SYNC0_PERIOD_REG,
ICSS_IEP_SYNC1_DELAY_REG,
ICSS_IEP_SYNC_START_REG,
ICSS_IEP_MAX_REGS,
};
/**
* struct icss_iep_plat_data - Plat data to handle SoC variants
* @config: Regmap configuration data
* @reg_offs: register offsets to capture offset differences across SoCs
* @flags: Flags to represent IEP properties
*/
struct icss_iep_plat_data {
const struct regmap_config *config;
u32 reg_offs[ICSS_IEP_MAX_REGS];
u32 flags;
};
struct icss_iep {
struct device *dev;
void __iomem *base;
const struct icss_iep_plat_data *plat_data;
struct regmap *map;
struct device_node *client_np;
unsigned long refclk_freq;
int clk_tick_time; /* one refclk tick time in ns */
struct ptp_clock_info ptp_info;
struct ptp_clock *ptp_clock;
struct mutex ptp_clk_mutex; /* PHC access serializer */
u32 def_inc;
s16 slow_cmp_inc;
u32 slow_cmp_count;
const struct icss_iep_clockops *ops;
void *clockops_data;
u32 cycle_time_ns;
u32 perout_enabled;
bool pps_enabled;
int cap_cmp_irq;
u64 period;
u32 latch_enable;
struct work_struct work;
};
/**
* icss_iep_get_count_hi() - Get the upper 32 bit IEP counter
* @iep: Pointer to structure representing IEP.
......
......@@ -12,7 +12,78 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/regmap.h>
struct icss_iep;
enum {
ICSS_IEP_GLOBAL_CFG_REG,
ICSS_IEP_GLOBAL_STATUS_REG,
ICSS_IEP_COMPEN_REG,
ICSS_IEP_SLOW_COMPEN_REG,
ICSS_IEP_COUNT_REG0,
ICSS_IEP_COUNT_REG1,
ICSS_IEP_CAPTURE_CFG_REG,
ICSS_IEP_CAPTURE_STAT_REG,
ICSS_IEP_CAP6_RISE_REG0,
ICSS_IEP_CAP6_RISE_REG1,
ICSS_IEP_CAP7_RISE_REG0,
ICSS_IEP_CAP7_RISE_REG1,
ICSS_IEP_CMP_CFG_REG,
ICSS_IEP_CMP_STAT_REG,
ICSS_IEP_CMP0_REG0,
ICSS_IEP_CMP0_REG1,
ICSS_IEP_CMP1_REG0,
ICSS_IEP_CMP1_REG1,
ICSS_IEP_CMP8_REG0,
ICSS_IEP_CMP8_REG1,
ICSS_IEP_SYNC_CTRL_REG,
ICSS_IEP_SYNC0_STAT_REG,
ICSS_IEP_SYNC1_STAT_REG,
ICSS_IEP_SYNC_PWIDTH_REG,
ICSS_IEP_SYNC0_PERIOD_REG,
ICSS_IEP_SYNC1_DELAY_REG,
ICSS_IEP_SYNC_START_REG,
ICSS_IEP_MAX_REGS,
};
/**
* struct icss_iep_plat_data - Plat data to handle SoC variants
* @config: Regmap configuration data
* @reg_offs: register offsets to capture offset differences across SoCs
* @flags: Flags to represent IEP properties
*/
struct icss_iep_plat_data {
const struct regmap_config *config;
u32 reg_offs[ICSS_IEP_MAX_REGS];
u32 flags;
};
struct icss_iep {
struct device *dev;
void __iomem *base;
const struct icss_iep_plat_data *plat_data;
struct regmap *map;
struct device_node *client_np;
unsigned long refclk_freq;
int clk_tick_time; /* one refclk tick time in ns */
struct ptp_clock_info ptp_info;
struct ptp_clock *ptp_clock;
struct mutex ptp_clk_mutex; /* PHC access serializer */
u32 def_inc;
s16 slow_cmp_inc;
u32 slow_cmp_count;
const struct icss_iep_clockops *ops;
void *clockops_data;
u32 cycle_time_ns;
u32 perout_enabled;
bool pps_enabled;
int cap_cmp_irq;
u64 period;
u32 latch_enable;
struct work_struct work;
};
extern const struct icss_iep_clockops prueth_iep_clockops;
/* Firmware specific clock operations */
......
......@@ -290,6 +290,7 @@ void icssg_class_set_host_mac_addr(struct regmap *miig_rt, const u8 *mac)
mac[2] << 16 | mac[3] << 24));
regmap_write(miig_rt, MAC_INTERFACE_1, (u32)(mac[4] | mac[5] << 8));
}
EXPORT_SYMBOL_GPL(icssg_class_set_host_mac_addr);
void icssg_class_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac)
{
......
......@@ -660,14 +660,15 @@ enum netdev_tx icssg_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev
{
struct cppi5_host_desc_t *first_desc, *next_desc, *cur_desc;
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct netdev_queue *netif_txq;
struct prueth_tx_chn *tx_chn;
dma_addr_t desc_dma, buf_dma;
u32 pkt_len, dst_tag_id;
int i, ret = 0, q_idx;
bool in_tx_ts = 0;
int tx_ts_cookie;
void **swdata;
u32 pkt_len;
u32 *epib;
pkt_len = skb_headlen(skb);
......@@ -712,9 +713,20 @@ enum netdev_tx icssg_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev
/* set dst tag to indicate internal qid at the firmware which is at
* bit8..bit15. bit0..bit7 indicates port num for directed
* packets in case of switch mode operation
* packets in case of switch mode operation and port num 0
* for undirected packets in case of HSR offload mode
*/
cppi5_desc_set_tags_ids(&first_desc->hdr, 0, (emac->port_id | (q_idx << 8)));
dst_tag_id = emac->port_id | (q_idx << 8);
if (prueth->is_hsr_offload_mode &&
(ndev->features & NETIF_F_HW_HSR_DUP))
dst_tag_id = PRUETH_UNDIRECTED_PKT_DST_TAG;
if (prueth->is_hsr_offload_mode &&
(ndev->features & NETIF_F_HW_HSR_TAG_INS))
epib[1] |= PRUETH_UNDIRECTED_PKT_TAG_INS;
cppi5_desc_set_tags_ids(&first_desc->hdr, 0, dst_tag_id);
k3_udma_glue_tx_dma_to_cppi5_addr(tx_chn->tx_chn, &buf_dma);
cppi5_hdesc_attach_buf(first_desc, buf_dma, pkt_len, buf_dma, pkt_len);
swdata = cppi5_hdesc_get_swdata(first_desc);
......
......@@ -107,7 +107,7 @@ static const struct map hwq_map[2][ICSSG_NUM_OTHER_QUEUES] = {
},
};
static void icssg_config_mii_init_switch(struct prueth_emac *emac)
static void icssg_config_mii_init_fw_offload(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
int mii = prueth_emac_slice(emac);
......@@ -278,7 +278,7 @@ static int emac_r30_is_done(struct prueth_emac *emac)
return 1;
}
static int prueth_switch_buffer_setup(struct prueth_emac *emac)
static int prueth_fw_offload_buffer_setup(struct prueth_emac *emac)
{
struct icssg_buffer_pool_cfg __iomem *bpool_cfg;
struct icssg_rxq_ctx __iomem *rxq_ctx;
......@@ -424,7 +424,7 @@ static void icssg_init_emac_mode(struct prueth *prueth)
icssg_class_set_host_mac_addr(prueth->miig_rt, mac);
}
static void icssg_init_switch_mode(struct prueth *prueth)
static void icssg_init_fw_offload_mode(struct prueth *prueth)
{
u32 addr = prueth->shram.pa + EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET;
int i;
......@@ -455,8 +455,8 @@ int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice)
struct icssg_flow_cfg __iomem *flow_cfg;
int ret;
if (prueth->is_switch_mode)
icssg_init_switch_mode(prueth);
if (prueth->is_switch_mode || prueth->is_hsr_offload_mode)
icssg_init_fw_offload_mode(prueth);
else
icssg_init_emac_mode(prueth);
......@@ -472,8 +472,8 @@ int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice)
regmap_update_bits(prueth->miig_rt, ICSSG_CFG_OFFSET,
ICSSG_CFG_DEFAULT, ICSSG_CFG_DEFAULT);
icssg_miig_set_interface_mode(prueth->miig_rt, slice, emac->phy_if);
if (prueth->is_switch_mode)
icssg_config_mii_init_switch(emac);
if (prueth->is_switch_mode || prueth->is_hsr_offload_mode)
icssg_config_mii_init_fw_offload(emac);
else
icssg_config_mii_init(emac);
icssg_config_ipg(emac);
......@@ -498,8 +498,8 @@ int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice)
writeb(0, config + SPL_PKT_DEFAULT_PRIORITY);
writeb(0, config + QUEUE_NUM_UNTAGGED);
if (prueth->is_switch_mode)
ret = prueth_switch_buffer_setup(emac);
if (prueth->is_switch_mode || prueth->is_hsr_offload_mode)
ret = prueth_fw_offload_buffer_setup(emac);
else
ret = prueth_emac_buffer_setup(emac);
if (ret)
......@@ -531,7 +531,9 @@ static const struct icssg_r30_cmd emac_r32_bitmask[] = {
{{EMAC_NONE, 0xffff4000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx ENABLE*/
{{EMAC_NONE, 0xbfff0000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx DISABLE*/
{{0xffff0010, EMAC_NONE, 0xffff0010, EMAC_NONE}}, /* VLAN AWARE*/
{{0xffef0000, EMAC_NONE, 0xffef0000, EMAC_NONE}} /* VLAN UNWARE*/
{{0xffef0000, EMAC_NONE, 0xffef0000, EMAC_NONE}}, /* VLAN UNWARE*/
{{0xffff2000, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* HSR_RX_OFFLOAD_ENABLE */
{{0xdfff0000, EMAC_NONE, EMAC_NONE, EMAC_NONE}} /* HSR_RX_OFFLOAD_DISABLE */
};
int icssg_set_port_state(struct prueth_emac *emac,
......
......@@ -80,6 +80,8 @@ enum icssg_port_state_cmd {
ICSSG_EMAC_PORT_PREMPT_TX_DISABLE,
ICSSG_EMAC_PORT_VLAN_AWARE_ENABLE,
ICSSG_EMAC_PORT_VLAN_AWARE_DISABLE,
ICSSG_EMAC_HSR_RX_OFFLOAD_ENABLE,
ICSSG_EMAC_HSR_RX_OFFLOAD_DISABLE,
ICSSG_EMAC_PORT_MAX_COMMANDS
};
......
......@@ -13,6 +13,7 @@
#include <linux/dma/ti-cppi5.h>
#include <linux/etherdevice.h>
#include <linux/genalloc.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
......@@ -40,6 +41,11 @@
#define DEFAULT_PORT_MASK 1
#define DEFAULT_UNTAG_MASK 1
#define NETIF_PRUETH_HSR_OFFLOAD_FEATURES (NETIF_F_HW_HSR_FWD | \
NETIF_F_HW_HSR_DUP | \
NETIF_F_HW_HSR_TAG_INS | \
NETIF_F_HW_HSR_TAG_RM)
/* CTRLMMR_ICSSG_RGMII_CTRL register bits */
#define ICSSG_CTRL_RGMII_ID_MODE BIT(24)
......@@ -118,6 +124,19 @@ static irqreturn_t prueth_tx_ts_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
static struct icssg_firmwares icssg_hsr_firmwares[] = {
{
.pru = "ti-pruss/am65x-sr2-pru0-pruhsr-fw.elf",
.rtu = "ti-pruss/am65x-sr2-rtu0-pruhsr-fw.elf",
.txpru = "ti-pruss/am65x-sr2-txpru0-pruhsr-fw.elf",
},
{
.pru = "ti-pruss/am65x-sr2-pru1-pruhsr-fw.elf",
.rtu = "ti-pruss/am65x-sr2-rtu1-pruhsr-fw.elf",
.txpru = "ti-pruss/am65x-sr2-txpru1-pruhsr-fw.elf",
}
};
static struct icssg_firmwares icssg_switch_firmwares[] = {
{
.pru = "ti-pruss/am65x-sr2-pru0-prusw-fw.elf",
......@@ -152,6 +171,8 @@ static int prueth_emac_start(struct prueth *prueth, struct prueth_emac *emac)
if (prueth->is_switch_mode)
firmwares = icssg_switch_firmwares;
else if (prueth->is_hsr_offload_mode)
firmwares = icssg_hsr_firmwares;
else
firmwares = icssg_emac_firmwares;
......@@ -365,7 +386,8 @@ static void prueth_iep_settime(void *clockops_data, u64 ns)
sc_desc.cyclecounter0_set = cyclecount & GENMASK(31, 0);
sc_desc.cyclecounter1_set = (cyclecount & GENMASK(63, 32)) >> 32;
sc_desc.iepcount_set = ns % cycletime;
sc_desc.CMP0_current = cycletime - 4; //Count from 0 to (cycle time)-4
/* Count from 0 to (cycle time) - emac->iep->def_inc */
sc_desc.CMP0_current = cycletime - emac->iep->def_inc;
memcpy_toio(sc_descp, &sc_desc, sizeof(sc_desc));
......@@ -470,6 +492,36 @@ static int icssg_prueth_del_mcast(struct net_device *ndev, const u8 *addr)
return 0;
}
static int icssg_prueth_hsr_add_mcast(struct net_device *ndev, const u8 *addr)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
icssg_fdb_add_del(emac, addr, prueth->default_vlan,
ICSSG_FDB_ENTRY_P0_MEMBERSHIP |
ICSSG_FDB_ENTRY_P1_MEMBERSHIP |
ICSSG_FDB_ENTRY_P2_MEMBERSHIP |
ICSSG_FDB_ENTRY_BLOCK, true);
icssg_vtbl_modify(emac, emac->port_vlan, BIT(emac->port_id),
BIT(emac->port_id), true);
return 0;
}
static int icssg_prueth_hsr_del_mcast(struct net_device *ndev, const u8 *addr)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
icssg_fdb_add_del(emac, addr, prueth->default_vlan,
ICSSG_FDB_ENTRY_P0_MEMBERSHIP |
ICSSG_FDB_ENTRY_P1_MEMBERSHIP |
ICSSG_FDB_ENTRY_P2_MEMBERSHIP |
ICSSG_FDB_ENTRY_BLOCK, false);
return 0;
}
/**
* emac_ndo_open - EMAC device open
* @ndev: network adapter device
......@@ -630,6 +682,9 @@ static int emac_ndo_stop(struct net_device *ndev)
icssg_class_disable(prueth->miig_rt, prueth_emac_slice(emac));
if (emac->prueth->is_hsr_offload_mode)
__dev_mc_unsync(ndev, icssg_prueth_hsr_del_mcast);
else
__dev_mc_unsync(ndev, icssg_prueth_del_mcast);
atomic_set(&emac->tdown_cnt, emac->tx_ch_num);
......@@ -708,7 +763,12 @@ static void emac_ndo_set_rx_mode_work(struct work_struct *work)
return;
}
__dev_mc_sync(ndev, icssg_prueth_add_mcast, icssg_prueth_del_mcast);
if (emac->prueth->is_hsr_offload_mode)
__dev_mc_sync(ndev, icssg_prueth_hsr_add_mcast,
icssg_prueth_hsr_del_mcast);
else
__dev_mc_sync(ndev, icssg_prueth_add_mcast,
icssg_prueth_del_mcast);
}
/**
......@@ -725,6 +785,29 @@ static void emac_ndo_set_rx_mode(struct net_device *ndev)
queue_work(emac->cmd_wq, &emac->rx_mode_work);
}
static netdev_features_t emac_ndo_fix_features(struct net_device *ndev,
netdev_features_t features)
{
/* hsr tag insertion offload and hsr dup offload are tightly coupled in
* firmware implementation. Both these features need to be enabled /
* disabled together.
*/
if (!(ndev->features & (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_TAG_INS)))
if ((features & NETIF_F_HW_HSR_DUP) ||
(features & NETIF_F_HW_HSR_TAG_INS))
features |= NETIF_F_HW_HSR_DUP |
NETIF_F_HW_HSR_TAG_INS;
if ((ndev->features & NETIF_F_HW_HSR_DUP) ||
(ndev->features & NETIF_F_HW_HSR_TAG_INS))
if (!(features & NETIF_F_HW_HSR_DUP) ||
!(features & NETIF_F_HW_HSR_TAG_INS))
features &= ~(NETIF_F_HW_HSR_DUP |
NETIF_F_HW_HSR_TAG_INS);
return features;
}
static const struct net_device_ops emac_netdev_ops = {
.ndo_open = emac_ndo_open,
.ndo_stop = emac_ndo_stop,
......@@ -736,6 +819,7 @@ static const struct net_device_ops emac_netdev_ops = {
.ndo_eth_ioctl = icssg_ndo_ioctl,
.ndo_get_stats64 = icssg_ndo_get_stats64,
.ndo_get_phys_port_name = icssg_ndo_get_phys_port_name,
.ndo_fix_features = emac_ndo_fix_features,
};
static int prueth_netdev_init(struct prueth *prueth,
......@@ -864,6 +948,7 @@ static int prueth_netdev_init(struct prueth *prueth,
ndev->ethtool_ops = &icssg_ethtool_ops;
ndev->hw_features = NETIF_F_SG;
ndev->features = ndev->hw_features;
ndev->hw_features |= NETIF_PRUETH_HSR_OFFLOAD_FEATURES;
netif_napi_add(ndev, &emac->napi_rx, icssg_napi_rx_poll);
hrtimer_init(&emac->rx_hrtimer, CLOCK_MONOTONIC,
......@@ -952,7 +1037,7 @@ static void prueth_emac_restart(struct prueth *prueth)
netif_device_attach(emac1->ndev);
}
static void icssg_enable_switch_mode(struct prueth *prueth)
static void icssg_change_mode(struct prueth *prueth)
{
struct prueth_emac *emac;
int mac;
......@@ -961,6 +1046,13 @@ static void icssg_enable_switch_mode(struct prueth *prueth)
for (mac = PRUETH_MAC0; mac < PRUETH_NUM_MACS; mac++) {
emac = prueth->emac[mac];
if (prueth->is_hsr_offload_mode) {
if (emac->ndev->features & NETIF_F_HW_HSR_TAG_RM)
icssg_set_port_state(emac, ICSSG_EMAC_HSR_RX_OFFLOAD_ENABLE);
else
icssg_set_port_state(emac, ICSSG_EMAC_HSR_RX_OFFLOAD_DISABLE);
}
if (netif_running(emac->ndev)) {
icssg_fdb_add_del(emac, eth_stp_addr, prueth->default_vlan,
ICSSG_FDB_ENTRY_P0_MEMBERSHIP |
......@@ -972,7 +1064,12 @@ static void icssg_enable_switch_mode(struct prueth *prueth)
BIT(emac->port_id) | DEFAULT_PORT_MASK,
BIT(emac->port_id) | DEFAULT_UNTAG_MASK,
true);
if (prueth->is_hsr_offload_mode)
icssg_vtbl_modify(emac, DEFAULT_VID,
DEFAULT_PORT_MASK,
DEFAULT_UNTAG_MASK, true);
icssg_set_pvid(prueth, emac->port_vlan, emac->port_id);
if (prueth->is_switch_mode)
icssg_set_port_state(emac, ICSSG_EMAC_PORT_VLAN_AWARE_ENABLE);
}
}
......@@ -1011,7 +1108,7 @@ static int prueth_netdevice_port_link(struct net_device *ndev,
prueth->is_switch_mode = true;
prueth->default_vlan = 1;
emac->port_vlan = prueth->default_vlan;
icssg_enable_switch_mode(prueth);
icssg_change_mode(prueth);
}
}
......@@ -1039,6 +1136,61 @@ static void prueth_netdevice_port_unlink(struct net_device *ndev)
prueth->hw_bridge_dev = NULL;
}
static int prueth_hsr_port_link(struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct prueth_emac *emac0;
struct prueth_emac *emac1;
emac0 = prueth->emac[PRUETH_MAC0];
emac1 = prueth->emac[PRUETH_MAC1];
if (prueth->is_switch_mode)
return -EOPNOTSUPP;
prueth->hsr_members |= BIT(emac->port_id);
if (!prueth->is_hsr_offload_mode) {
if (prueth->hsr_members & BIT(PRUETH_PORT_MII0) &&
prueth->hsr_members & BIT(PRUETH_PORT_MII1)) {
if (!(emac0->ndev->features &
NETIF_PRUETH_HSR_OFFLOAD_FEATURES) &&
!(emac1->ndev->features &
NETIF_PRUETH_HSR_OFFLOAD_FEATURES))
return -EOPNOTSUPP;
prueth->is_hsr_offload_mode = true;
prueth->default_vlan = 1;
emac0->port_vlan = prueth->default_vlan;
emac1->port_vlan = prueth->default_vlan;
icssg_change_mode(prueth);
netdev_dbg(ndev, "Enabling HSR offload mode\n");
}
}
return 0;
}
static void prueth_hsr_port_unlink(struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct prueth_emac *emac0;
struct prueth_emac *emac1;
emac0 = prueth->emac[PRUETH_MAC0];
emac1 = prueth->emac[PRUETH_MAC1];
prueth->hsr_members &= ~BIT(emac->port_id);
if (prueth->is_hsr_offload_mode) {
prueth->is_hsr_offload_mode = false;
emac0->port_vlan = 0;
emac1->port_vlan = 0;
prueth->hsr_dev = NULL;
prueth_emac_restart(prueth);
netdev_dbg(ndev, "Disabling HSR Offload mode\n");
}
}
/* netdev notifier */
static int prueth_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
......@@ -1046,6 +1198,8 @@ static int prueth_netdevice_event(struct notifier_block *unused,
struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);
struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
int ret = NOTIFY_DONE;
if (ndev->netdev_ops != &emac_netdev_ops)
......@@ -1055,6 +1209,25 @@ static int prueth_netdevice_event(struct notifier_block *unused,
case NETDEV_CHANGEUPPER:
info = ptr;
if ((ndev->features & NETIF_PRUETH_HSR_OFFLOAD_FEATURES) &&
is_hsr_master(info->upper_dev)) {
if (info->linking) {
if (!prueth->hsr_dev) {
prueth->hsr_dev = info->upper_dev;
icssg_class_set_host_mac_addr(prueth->miig_rt,
prueth->hsr_dev->dev_addr);
} else {
if (prueth->hsr_dev != info->upper_dev) {
netdev_dbg(ndev, "Both interfaces must be linked to same upper device\n");
return -EOPNOTSUPP;
}
}
prueth_hsr_port_link(ndev);
} else {
prueth_hsr_port_unlink(ndev);
}
}
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
ret = prueth_netdevice_port_link(ndev, info->upper_dev, extack);
......
......@@ -59,6 +59,9 @@
#define IEP_DEFAULT_CYCLE_TIME_NS 1000000 /* 1 ms */
#define PRUETH_UNDIRECTED_PKT_DST_TAG 0
#define PRUETH_UNDIRECTED_PKT_TAG_INS BIT(30)
/* Firmware status codes */
#define ICSS_HS_FW_READY 0x55555555
#define ICSS_HS_FW_DEAD 0xDEAD0000 /* lower 16 bits contain error code */
......@@ -243,11 +246,14 @@ struct icssg_firmwares {
* @iep1: pointer to IEP1 device
* @vlan_tbl: VLAN-FID table pointer
* @hw_bridge_dev: pointer to HW bridge net device
* @hsr_dev: pointer to the HSR net device
* @br_members: bitmask of bridge member ports
* @hsr_members: bitmask of hsr member ports
* @prueth_netdevice_nb: netdevice notifier block
* @prueth_switchdev_nb: switchdev notifier block
* @prueth_switchdev_bl_nb: switchdev blocking notifier block
* @is_switch_mode: flag to indicate if device is in Switch mode
* @is_hsr_offload_mode: flag to indicate if device is in hsr offload mode
* @is_switchmode_supported: indicates platform support for switch mode
* @switch_id: ID for mapping switch ports to bridge
* @default_vlan: Default VLAN for host
......@@ -279,11 +285,14 @@ struct prueth {
struct prueth_vlan_tbl *vlan_tbl;
struct net_device *hw_bridge_dev;
struct net_device *hsr_dev;
u8 br_members;
u8 hsr_members;
struct notifier_block prueth_netdevice_nb;
struct notifier_block prueth_switchdev_nb;
struct notifier_block prueth_switchdev_bl_nb;
bool is_switch_mode;
bool is_hsr_offload_mode;
bool is_switchmode_supported;
unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
int default_vlan;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment