Commit f22f9583 authored by Andrew Lunn's avatar Andrew Lunn Committed by Ezequiel Garcia
Browse files

dsa: mv88e6xxx: Zodiac Spanning Tree Safety Net.

Provides a mechanism to protect the CPU from broadcast storm when
spanning tree does the wrong thing/is too slow.

/sys/kernel/debugfs/zii_hacks contains a directory per switch chip.
Within each directory are two files per user port:

/sys/kernel/debug/zii_hacks/0.1:00# ls
link_shutdown_1
link_shutdown_2
link_shutdown_3
link_shutdown_4
shutdown_link_on_member_violation_1
shutdown_link_on_member_violation_2
shutdown_link_on_member_violation_3
shutdown_link_on_member_violation_4

echo'ing y into shutdown_link_on_member_violation_? enables the
feature for that port. echo'ing n disables it. If an ATU member
violation interrupt occurs for a port which is enabled, the port is
disabled. For a port using a copper PHY, the PHY transmitter is
disabled. This can only be restored with a cold boot, or maybe a
hardware reset of the switch. Software reset is not sufficient. If the
port is using an SERDES, e.g for an SFF/SFP, the SERDES is powered
off. ifdown/ifup is probably sufficient to clear this.

link_shutdown-? will indicate if a port has sometime in the past been
disabled because of an ATU member violation. For a SERDES port, an
ifdown/up will restore the operation of the port, and the contents of
the file will reflect this.
parent 3f6f998f
......@@ -17,3 +17,4 @@ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += smi.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_ZII) += sysfs.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_ZII) += stsn.o
......@@ -42,6 +42,7 @@
#include "ptp.h"
#include "serdes.h"
#include "smi.h"
#include "stsn.h"
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6XXX_ZII)
#include "sysfs.h"
#endif
......@@ -2478,6 +2479,8 @@ static int mv88e6xxx_serdes_power(struct mv88e6xxx_chip *chip, int port,
err = mv88e6xxx_serdes_power_down(chip, port, lane);
}
zii_stsn_serdes_power(chip, port, on);
return err;
}
......@@ -3013,8 +3016,15 @@ static int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds)
static void mv88e6xxx_teardown(struct dsa_switch *ds)
{
struct mv88e6xxx_chip *chip = ds->priv;
mv88e6xxx_teardown_devlink_params(ds);
dsa_devlink_resources_unregister(ds);
dsa_devlink_resources_unregister(ds);
mv88e6xxx_teardown_devlink_params(ds);
zii_stsn_teardown(chip);
}
static int mv88e6xxx_setup(struct dsa_switch *ds)
......@@ -3027,6 +3037,10 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
chip->ds = ds;
ds->slave_mii_bus = mv88e6xxx_default_mdio_bus(chip);
err = zii_stsn_setup(chip);
if (err)
return err;
mv88e6xxx_reg_lock(chip);
if (chip->info->ops->setup_errata) {
......
......@@ -311,6 +311,8 @@ struct mv88e6xxx_chip {
struct kthread_worker *kworker;
struct kthread_delayed_work irq_poll_work;
void *stsn;
/* GPIO resources */
u8 gpio_data[2];
......
......@@ -12,6 +12,7 @@
#include "chip.h"
#include "global1.h"
#include "stsn.h"
/* Offset 0x01: ATU FID Register */
......@@ -394,6 +395,7 @@ static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id)
}
if (val & MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION) {
zii_stsn_violation(chip, spid);
dev_err_ratelimited(chip->dev,
"ATU member violation for %pM portvec %x spid %d %s\n",
entry.mac, entry.portvec, spid, ifnam);
......
......@@ -49,6 +49,7 @@
#define MV88E6XXX_PORT_STS_CMODE_2500BASEX 0x000b
#define MV88E6XXX_PORT_STS_CMODE_XAUI 0x000c
#define MV88E6XXX_PORT_STS_CMODE_RXAUI 0x000d
#define MV88E6xxx_PORT_STS_CMODE_PHY 0x000f
#define MV88E6185_PORT_STS_CDUPLEX 0x0008
#define MV88E6185_PORT_STS_CMODE_MASK 0x0007
#define MV88E6185_PORT_STS_CMODE_GMII_FD 0x0000
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Spanning Tree Safety Net
*
* Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <net/dsa.h>
#include "chip.h"
#include "port.h"
#include "serdes.h"
#include "stsn.h"
#define MII_CSCR1 0x10
#define MII_CSCR1_TX_DISABLE BIT(3)
struct dentry *debugfs_zii_hacks;
struct zii_stsn_priv {
struct dentry *debugfs_dir;
bool enabled[DSA_MAX_PORTS];
bool violated[DSA_MAX_PORTS];
};
void zii_stsn_teardown(struct mv88e6xxx_chip *chip)
{
struct zii_stsn_priv *stsn = chip->stsn;
debugfs_remove_recursive(stsn->debugfs_dir);
kfree(stsn);
}
int zii_stsn_setup(struct mv88e6xxx_chip *chip)
{
struct zii_stsn_priv *stsn;
char name[64];
int port;
stsn = kzalloc(sizeof(*stsn), GFP_KERNEL);
if (!stsn)
return -ENOMEM;
chip->stsn = stsn;
if (!debugfs_zii_hacks)
debugfs_zii_hacks = debugfs_create_dir("zii_hacks", NULL);
stsn->debugfs_dir = debugfs_create_dir(dev_name(chip->dev),
debugfs_zii_hacks);
for (port = 0; port < chip->ds->num_ports; port++) {
if (!dsa_is_user_port(chip->ds, port))
continue;
snprintf(name, sizeof(name),
"shutdown_link_on_member_violation_%d", port);
debugfs_create_bool(name, 0600, stsn->debugfs_dir,
&stsn->enabled[port]);
snprintf(name, sizeof(name), "link_shutdown_%d", port);
debugfs_create_bool(name, 0400, stsn->debugfs_dir,
&stsn->violated[port]);
}
return 0;
}
static void zii_stsn_phy_tx_disable(struct mv88e6xxx_chip *chip, int port)
{
struct mii_bus *bus;
u16 reg;
if (!chip->info->ops->phy_write || !chip->info->ops->phy_read) {
dev_err(chip->dev, "%s: No PHY ops\n", __func__);
return;
}
bus = mv88e6xxx_default_mdio_bus(chip);
chip->info->ops->phy_read(chip, bus, port, MII_CSCR1, &reg);
reg |= MII_CSCR1_TX_DISABLE;
chip->info->ops->phy_write(chip, bus, port, MII_CSCR1, reg);
}
void zii_stsn_violation(struct mv88e6xxx_chip *chip, int port)
{
struct zii_stsn_priv *stsn = chip->stsn;
u8 cmode = chip->ports[port].cmode;
u8 lane;
if (!dsa_is_user_port(chip->ds, port))
return;
if (!stsn->enabled[port])
return;
switch (cmode) {
case MV88E6xxx_PORT_STS_CMODE_PHY:
zii_stsn_phy_tx_disable(chip, port);
dev_info(chip->dev, "%s: Port %d PHY TX disabled\n",
__func__, port);
break;
case MV88E6XXX_PORT_STS_CMODE_100BASEX:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
case MV88E6XXX_PORT_STS_CMODE_SGMII:
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
case MV88E6XXX_PORT_STS_CMODE_XAUI:
case MV88E6XXX_PORT_STS_CMODE_RXAUI:
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane && chip->info->ops->serdes_power) {
chip->info->ops->serdes_power(chip, port, lane, false);
dev_info(chip->dev, "%s: Port %d SERDES powered down\n",
__func__, port);
} else {
dev_info(chip->dev,
"%s: Port %d missing SERDES lane!\n",
__func__, port);
}
break;
default:
dev_info(chip->dev,
"%s: Unsupported CMODE %d. Violation ignored\n",
__func__, cmode);
break;
}
if (stsn->violated[port]) {
dev_info(chip->dev, "Port %d violated again!", port);
return;
}
stsn->violated[port] = true;
}
void zii_stsn_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
struct zii_stsn_priv *stsn = chip->stsn;
if (!dsa_is_user_port(chip->ds, port))
return;
if (!stsn->enabled[port])
return;
if (stsn->violated[port]) {
dev_info(chip->dev, "Port %d SERDES powered up\n", port);
stsn->violated[port]= false;
}
}
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Spanning Tree Safety Net
*
* Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch>
*/
#ifndef _MV88E6XXX_STSN_H
#define _MV88E6XXX_STSN_H
int zii_stsn_setup(struct mv88e6xxx_chip *chip);
void zii_stsn_teardown(struct mv88e6xxx_chip *chip);
void zii_stsn_violation(struct mv88e6xxx_chip *chip, int port);
void zii_stsn_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
#endif /* _MV88E6XXX_STSN_H */
Supports Markdown
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