eb4733d7cf
There are several issues which may be seen when the link goes down while forwarding traffic, all of which can be attributed to the fact that the port flushing procedure from the reference manual was not closely followed. With flow control enabled on both the ingress port and the egress port, it may happen when a link goes down that Ethernet packets are in flight. In flow control mode, frames are held back and not dropped. When there is enough traffic in flight (example: iperf3 TCP), then the ingress port might enter congestion and never exit that state. This is a problem, because it is the egress port's link that went down, and that has caused the inability of the ingress port to send packets to any other port. This is solved by flushing the egress port's queues when it goes down. There is also a problem when performing stream splitting for IEEE 802.1CB traffic (not yet upstream, but a sort of multicast, basically). There, if one port from the destination ports mask goes down, splitting the stream towards the other destinations will no longer be performed. This can be traced down to this line: ocelot_port_writel(ocelot_port, 0, DEV_MAC_ENA_CFG); which should have been instead, as per the reference manual: ocelot_port_rmwl(ocelot_port, 0, DEV_MAC_ENA_CFG_RX_ENA, DEV_MAC_ENA_CFG); Basically only DEV_MAC_ENA_CFG_RX_ENA should be disabled, but not DEV_MAC_ENA_CFG_TX_ENA - I don't have further insight into why that is the case, but apparently multicasting to several ports will cause issues if at least one of them doesn't have DEV_MAC_ENA_CFG_TX_ENA set. I am not sure what the state of the Ocelot VSC7514 driver is, but probably not as bad as Felix/Seville, since VSC7514 uses phylib and has the following in ocelot_adjust_link: if (!phydev->link) return; therefore the port is not really put down when the link is lost, unlike the DSA drivers which use .phylink_mac_link_down for that. Nonetheless, I put ocelot_port_flush() in the common ocelot.c because it needs to access some registers from drivers/net/ethernet/mscc/ocelot_rew.h which are not exported in include/soc/mscc/ and a bugfix patch should probably not move headers around. Fixes: bdeced75b13f ("net: dsa: felix: Add PCS operations for PHYLINK") Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
152 lines
3.4 KiB
C
152 lines
3.4 KiB
C
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
|
/*
|
|
* Microsemi Ocelot Switch driver
|
|
*
|
|
* Copyright (c) 2017 Microsemi Corporation
|
|
*/
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include "ocelot.h"
|
|
|
|
u32 __ocelot_read_ix(struct ocelot *ocelot, u32 reg, u32 offset)
|
|
{
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
u32 val;
|
|
|
|
WARN_ON(!target);
|
|
|
|
regmap_read(ocelot->targets[target],
|
|
ocelot->map[target][reg & REG_MASK] + offset, &val);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(__ocelot_read_ix);
|
|
|
|
void __ocelot_write_ix(struct ocelot *ocelot, u32 val, u32 reg, u32 offset)
|
|
{
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
|
|
WARN_ON(!target);
|
|
|
|
regmap_write(ocelot->targets[target],
|
|
ocelot->map[target][reg & REG_MASK] + offset, val);
|
|
}
|
|
EXPORT_SYMBOL(__ocelot_write_ix);
|
|
|
|
void __ocelot_rmw_ix(struct ocelot *ocelot, u32 val, u32 mask, u32 reg,
|
|
u32 offset)
|
|
{
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
|
|
WARN_ON(!target);
|
|
|
|
regmap_update_bits(ocelot->targets[target],
|
|
ocelot->map[target][reg & REG_MASK] + offset,
|
|
mask, val);
|
|
}
|
|
EXPORT_SYMBOL(__ocelot_rmw_ix);
|
|
|
|
u32 ocelot_port_readl(struct ocelot_port *port, u32 reg)
|
|
{
|
|
struct ocelot *ocelot = port->ocelot;
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
u32 val;
|
|
|
|
WARN_ON(!target);
|
|
|
|
regmap_read(port->target, ocelot->map[target][reg & REG_MASK], &val);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(ocelot_port_readl);
|
|
|
|
void ocelot_port_writel(struct ocelot_port *port, u32 val, u32 reg)
|
|
{
|
|
struct ocelot *ocelot = port->ocelot;
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
|
|
WARN_ON(!target);
|
|
|
|
regmap_write(port->target, ocelot->map[target][reg & REG_MASK], val);
|
|
}
|
|
EXPORT_SYMBOL(ocelot_port_writel);
|
|
|
|
void ocelot_port_rmwl(struct ocelot_port *port, u32 val, u32 mask, u32 reg)
|
|
{
|
|
u32 cur = ocelot_port_readl(port, reg);
|
|
|
|
ocelot_port_writel(port, (cur & (~mask)) | val, reg);
|
|
}
|
|
EXPORT_SYMBOL(ocelot_port_rmwl);
|
|
|
|
u32 __ocelot_target_read_ix(struct ocelot *ocelot, enum ocelot_target target,
|
|
u32 reg, u32 offset)
|
|
{
|
|
u32 val;
|
|
|
|
regmap_read(ocelot->targets[target],
|
|
ocelot->map[target][reg] + offset, &val);
|
|
return val;
|
|
}
|
|
|
|
void __ocelot_target_write_ix(struct ocelot *ocelot, enum ocelot_target target,
|
|
u32 val, u32 reg, u32 offset)
|
|
{
|
|
regmap_write(ocelot->targets[target],
|
|
ocelot->map[target][reg] + offset, val);
|
|
}
|
|
|
|
int ocelot_regfields_init(struct ocelot *ocelot,
|
|
const struct reg_field *const regfields)
|
|
{
|
|
unsigned int i;
|
|
u16 target;
|
|
|
|
for (i = 0; i < REGFIELD_MAX; i++) {
|
|
struct reg_field regfield = {};
|
|
u32 reg = regfields[i].reg;
|
|
|
|
if (!reg)
|
|
continue;
|
|
|
|
target = regfields[i].reg >> TARGET_OFFSET;
|
|
|
|
regfield.reg = ocelot->map[target][reg & REG_MASK];
|
|
regfield.lsb = regfields[i].lsb;
|
|
regfield.msb = regfields[i].msb;
|
|
regfield.id_size = regfields[i].id_size;
|
|
regfield.id_offset = regfields[i].id_offset;
|
|
|
|
ocelot->regfields[i] =
|
|
devm_regmap_field_alloc(ocelot->dev,
|
|
ocelot->targets[target],
|
|
regfield);
|
|
|
|
if (IS_ERR(ocelot->regfields[i]))
|
|
return PTR_ERR(ocelot->regfields[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ocelot_regfields_init);
|
|
|
|
static struct regmap_config ocelot_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
};
|
|
|
|
struct regmap *ocelot_regmap_init(struct ocelot *ocelot, struct resource *res)
|
|
{
|
|
void __iomem *regs;
|
|
|
|
regs = devm_ioremap_resource(ocelot->dev, res);
|
|
if (IS_ERR(regs))
|
|
return ERR_CAST(regs);
|
|
|
|
ocelot_regmap_config.name = res->name;
|
|
|
|
return devm_regmap_init_mmio(ocelot->dev, regs, &ocelot_regmap_config);
|
|
}
|
|
EXPORT_SYMBOL(ocelot_regmap_init);
|