Commit 2d61298f authored by Lukasz Majewski's avatar Lukasz Majewski Committed by Paolo Abeni
Browse files

net: dsa: microchip: Enable HSR offloading for KSZ9477



This patch adds functions for providing in KSZ9477 switch HSR
(High-availability Seamless Redundancy) hardware offloading.

According to AN3474 application note following features are provided:
- TX packet duplication from host to switch (NETIF_F_HW_HSR_DUP)
- RX packet duplication discarding
- Prevention of packet loop

For last two ones - there is a probability that some packets will not
be filtered in HW (in some special cases - described in AN3474).
Hence, the HSR core code shall be used to discard those not caught frames.

Moreover, some switch registers adjustments are required - like setting
MAC address of HSR network interface.

Additionally, the KSZ9477 switch has been configured to forward frames
between HSR ports (e.g. 1,2) members to provide support for
NETIF_F_HW_HSR_FWD flag.

Join and leave functions are written in a way, that are executed with
single port - i.e. configuration is NOT done only when second HSR port
is configured.

Co-developed-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarLukasz Majewski <lukma@denx.de>
Reviewed-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent e5de2ad1
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
@@ -1143,6 +1143,83 @@ int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val)
	return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val);
}

/* The KSZ9477 provides following HW features to accelerate
 * HSR frames handling:
 *
 * 1. TX PACKET DUPLICATION FROM HOST TO SWITCH
 * 2. RX PACKET DUPLICATION DISCARDING
 * 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING
 *
 * Only one from point 1. has the NETIF_F* flag available.
 *
 * Ones from point 2 and 3 are "best effort" - i.e. those will
 * work correctly most of the time, but it may happen that some
 * frames will not be caught - to be more specific; there is a race
 * condition in hardware such that, when duplicate packets are received
 * on member ports very close in time to each other, the hardware fails
 * to detect that they are duplicates.
 *
 * Hence, the SW needs to handle those special cases. However, the speed
 * up gain is considerable when above features are used.
 *
 * Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames
 * can be forwarded in the switch fabric between HSR ports.
 */
#define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD)

void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr)
{
	struct ksz_device *dev = ds->priv;
	struct net_device *slave;
	struct dsa_port *hsr_dp;
	u8 data, hsr_ports = 0;

	/* Program which port(s) shall support HSR */
	ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port));

	/* Forward frames between HSR ports (i.e. bridge together HSR ports) */
	if (dev->hsr_ports) {
		dsa_hsr_foreach_port(hsr_dp, ds, hsr)
			hsr_ports |= BIT(hsr_dp->index);

		hsr_ports |= BIT(dsa_upstream_port(ds, port));
		dsa_hsr_foreach_port(hsr_dp, ds, hsr)
			ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports);
	}

	if (!dev->hsr_ports) {
		/* Enable discarding of received HSR frames */
		ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data);
		data |= HSR_DUPLICATE_DISCARD;
		data &= ~HSR_NODE_UNICAST;
		ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data);
	}

	/* Enable per port self-address filtering.
	 * The global self-address filtering has already been enabled in the
	 * ksz9477_reset_switch() function.
	 */
	ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true);

	/* Setup HW supported features for lan HSR ports */
	slave = dsa_to_port(ds, port)->slave;
	slave->features |= KSZ9477_SUPPORTED_HSR_FEATURES;
}

void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr)
{
	struct ksz_device *dev = ds->priv;

	/* Clear port HSR support */
	ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0);

	/* Disable forwarding frames between HSR ports */
	ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port)));

	/* Disable per port self-address filtering */
	ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false);
}

int ksz9477_switch_init(struct ksz_device *dev)
{
	u8 data8;
+2 −0
Original line number Diff line number Diff line
@@ -56,6 +56,8 @@ int ksz9477_reset_switch(struct ksz_device *dev);
int ksz9477_switch_init(struct ksz_device *dev);
void ksz9477_switch_exit(struct ksz_device *dev);
void ksz9477_port_queue_split(struct ksz_device *dev, int port);
void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr);
void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr);

int ksz9477_port_acl_init(struct ksz_device *dev, int port);
void ksz9477_port_acl_free(struct ksz_device *dev, int port);
+147 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/if_hsr.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
@@ -3541,6 +3542,149 @@ static int ksz_setup_tc(struct dsa_switch *ds, int port,
	}
}

static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
				    const unsigned char *addr)
{
	struct dsa_port *dp = dsa_to_port(ds, port);

	if (dp->hsr_dev) {
		dev_err(ds->dev,
			"Cannot change MAC address on port %d with active HSR offload\n",
			port);
		return -EBUSY;
	}

	return 0;
}

/* Program the switch's MAC address register with the MAC address of the
 * requesting user port. This single address is used by the switch for multiple
 * features, like HSR self-address filtering and WoL. Other user ports are
 * allowed to share ownership of this address as long as their MAC address is
 * the same. The user ports' MAC addresses must not change while they have
 * ownership of the switch MAC address.
 */
static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
				  struct netlink_ext_ack *extack)
{
	struct net_device *slave = dsa_to_port(ds, port)->slave;
	const unsigned char *addr = slave->dev_addr;
	struct ksz_switch_macaddr *switch_macaddr;
	struct ksz_device *dev = ds->priv;
	const u16 *regs = dev->info->regs;
	int i;

	/* Make sure concurrent MAC address changes are blocked */
	ASSERT_RTNL();

	switch_macaddr = dev->switch_macaddr;
	if (switch_macaddr) {
		if (!ether_addr_equal(switch_macaddr->addr, addr)) {
			NL_SET_ERR_MSG_FMT_MOD(extack,
					       "Switch already configured for MAC address %pM",
					       switch_macaddr->addr);
			return -EBUSY;
		}

		refcount_inc(&switch_macaddr->refcount);
		return 0;
	}

	switch_macaddr = kzalloc(sizeof(*switch_macaddr), GFP_KERNEL);
	if (!switch_macaddr)
		return -ENOMEM;

	ether_addr_copy(switch_macaddr->addr, addr);
	refcount_set(&switch_macaddr->refcount, 1);
	dev->switch_macaddr = switch_macaddr;

	/* Program the switch MAC address to hardware */
	for (i = 0; i < ETH_ALEN; i++)
		ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]);

	return 0;
}

static void ksz_switch_macaddr_put(struct dsa_switch *ds)
{
	struct ksz_switch_macaddr *switch_macaddr;
	struct ksz_device *dev = ds->priv;
	const u16 *regs = dev->info->regs;
	int i;

	/* Make sure concurrent MAC address changes are blocked */
	ASSERT_RTNL();

	switch_macaddr = dev->switch_macaddr;
	if (!refcount_dec_and_test(&switch_macaddr->refcount))
		return;

	for (i = 0; i < ETH_ALEN; i++)
		ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, 0);

	dev->switch_macaddr = NULL;
	kfree(switch_macaddr);
}

static int ksz_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr,
			struct netlink_ext_ack *extack)
{
	struct ksz_device *dev = ds->priv;
	enum hsr_version ver;
	int ret;

	ret = hsr_get_version(hsr, &ver);
	if (ret)
		return ret;

	if (dev->chip_id != KSZ9477_CHIP_ID) {
		NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload");
		return -EOPNOTSUPP;
	}

	/* KSZ9477 can support HW offloading of only 1 HSR device */
	if (dev->hsr_dev && hsr != dev->hsr_dev) {
		NL_SET_ERR_MSG_MOD(extack, "Offload supported for a single HSR");
		return -EOPNOTSUPP;
	}

	/* KSZ9477 only supports HSR v0 and v1 */
	if (!(ver == HSR_V0 || ver == HSR_V1)) {
		NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported");
		return -EOPNOTSUPP;
	}

	/* Self MAC address filtering, to avoid frames traversing
	 * the HSR ring more than once.
	 */
	ret = ksz_switch_macaddr_get(ds, port, extack);
	if (ret)
		return ret;

	ksz9477_hsr_join(ds, port, hsr);
	dev->hsr_dev = hsr;
	dev->hsr_ports |= BIT(port);

	return 0;
}

static int ksz_hsr_leave(struct dsa_switch *ds, int port,
			 struct net_device *hsr)
{
	struct ksz_device *dev = ds->priv;

	WARN_ON(dev->chip_id != KSZ9477_CHIP_ID);

	ksz9477_hsr_leave(ds, port, hsr);
	dev->hsr_ports &= ~BIT(port);
	if (!dev->hsr_ports)
		dev->hsr_dev = NULL;

	ksz_switch_macaddr_put(ds);

	return 0;
}

static const struct dsa_switch_ops ksz_switch_ops = {
	.get_tag_protocol	= ksz_get_tag_protocol,
	.connect_tag_protocol   = ksz_connect_tag_protocol,
@@ -3560,6 +3704,9 @@ static const struct dsa_switch_ops ksz_switch_ops = {
	.get_sset_count		= ksz_sset_count,
	.port_bridge_join	= ksz_port_bridge_join,
	.port_bridge_leave	= ksz_port_bridge_leave,
	.port_hsr_join		= ksz_hsr_join,
	.port_hsr_leave		= ksz_hsr_leave,
	.port_set_mac_address	= ksz_port_set_mac_address,
	.port_stp_state_set	= ksz_port_stp_state_set,
	.port_teardown		= ksz_port_teardown,
	.port_pre_bridge_flags	= ksz_port_pre_bridge_flags,
+9 −0
Original line number Diff line number Diff line
@@ -101,6 +101,11 @@ struct ksz_ptp_irq {
	int num;
};

struct ksz_switch_macaddr {
	unsigned char addr[ETH_ALEN];
	refcount_t refcount;
};

struct ksz_port {
	bool remove_tag;		/* Remove Tag flag set, for ksz8795 only */
	bool learning;
@@ -170,6 +175,10 @@ struct ksz_device {
	struct mutex lock_irq;		/* IRQ Access */
	struct ksz_irq girq;
	struct ksz_ptp_data ptp_data;

	struct ksz_switch_macaddr *switch_macaddr;
	struct net_device *hsr_dev;     /* HSR */
	u8 hsr_ports;
};

/* List of supported models */