net: dsa: mv88e6xxx: prevent interrupt storm caused by mv88e6390x_port_set_cmode [Linux 5.0]

net: dsa: mv88e6xxx: prevent interrupt storm caused by mv88e6390x_port_set_cmode [Linux 5.0]

This Linux kernel change "net: dsa: mv88e6xxx: prevent interrupt storm caused by mv88e6390x_port_set_cmode" is included in the Linux 5.0 release. This change is authored by Heiner Kallweit <hkallweit1 [at] gmail.com> on Thu Feb 28 07:39:15 2019 +0100. The commit for this change in Linux stable tree is ed8fe20 (patch).

net: dsa: mv88e6xxx: prevent interrupt storm caused by mv88e6390x_port_set_cmode

When debugging another issue I faced an interrupt storm in this
driver (88E6390, port 9 in SGMII mode), consisting of alternating
link-up / link-down interrupts. Analysis showed that the driver
wanted to set a cmode that was set already. But so far
mv88e6390x_port_set_cmode() doesn't check this and powers down
SERDES, what causes the link to break, and eventually results in
the described interrupt storm.

Fix this by checking whether the cmode actually changes. We want
that the very first call to mv88e6390x_port_set_cmode() always
configures the registers, therefore initialize port.cmode with
a value that is different from any supported cmode value.
We have to take care that we only init the ports cmode once
chip->info->num_ports is set.

v2:
- add small helper and init the number of actual ports only

Fixes: 364e9d7776a3 ("net: dsa: mv88e6xxx: Power on/off SERDES on cmode change")
Signed-off-by: Heiner Kallweit <[email protected]>
Signed-off-by: David S. Miller <[email protected]>

There are 15 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/net/dsa/mv88e6xxx/chip.c | 10 ++++++++++
 drivers/net/dsa/mv88e6xxx/port.c |  4 ++++
 drivers/net/dsa/mv88e6xxx/port.h |  1 +
 3 files changed, 15 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 9d36d2b..f5cad42 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4595,6 +4595,14 @@ static int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip,
    return 0;
 }

+static void mv88e6xxx_ports_cmode_init(struct mv88e6xxx_chip *chip)
+{
+   int i;
+
+   for (i = 0; i < mv88e6xxx_num_ports(chip); i++)
+       chip->ports[i].cmode = MV88E6XXX_PORT_STS_CMODE_INVALID;
+}
+
 static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds,
                            int port)
 {
@@ -4631,6 +4639,8 @@ static const char *mv88e6xxx_drv_probe(struct device *dsa_dev,
    if (err)
        goto free;

+   mv88e6xxx_ports_cmode_init(chip);
+
    mutex_lock(&chip->reg_lock);
    err = mv88e6xxx_switch_reset(chip);
    mutex_unlock(&chip->reg_lock);
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index 4b02c96..79ab51e 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -398,6 +398,10 @@ int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
        cmode = 0;
    }

+   /* cmode doesn't change, nothing to do for us */
+   if (cmode == chip->ports[port].cmode)
+       return 0;
+
    lane = mv88e6390x_serdes_get_lane(chip, port);
    if (lane < 0)
        return lane;
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index e583641..4aadf32 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -52,6 +52,7 @@
 #define MV88E6185_PORT_STS_CMODE_1000BASE_X    0x0005
 #define MV88E6185_PORT_STS_CMODE_PHY       0x0006
 #define MV88E6185_PORT_STS_CMODE_DISABLED  0x0007
+#define MV88E6XXX_PORT_STS_CMODE_INVALID   0xff

 /* Offset 0x01: MAC (or PCS or Physical) Control Register */
 #define MV88E6XXX_PORT_MAC_CTL             0x01

Leave a Reply

Your email address will not be published. Required fields are marked *