Thread (15 messages) 15 messages, 2 authors, 3d ago
WARM2d

[PATCH net-next v2 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation

From: Markus Stockhausen <hidden>
Date: 2026-06-29 15:23:54
Also in: linux-devicetree
Subsystem: ethernet phy library, networking drivers, the rest · Maintainers: Andrew Lunn, Heiner Kallweit, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

When reading the PHY state on c22 based buses the hardware polling unit
reads the EEE status with a sequence similar to this:

  ...
  phy_write(phy, 31, 0x0);
  phy_write(phy, 13, 0x7); /* c45 over c22 MDIO_AN_EEE_ADV */
  phy_write(phy, 14, 0x3c);
  phy_write(phy, 13, 0x8007);
  phy_read(phy, 14);
  phy_write(phy, 13, 0x7); /* c45 over c22 MDIO_AN_EEE_LPABLE */
  phy_write(phy, 14, 0x3d);
  phy_write(phy, 13, 0x8007);
  ...

If the Linux kernel wants to do the same in mmd_phy_read() via a call to
mmd_phy_indirect() this most likely fails. The commands are issued in a
straight sequence but between two of them the hardware polling might run
a status check for the same PHY. This effectively breaks the kernel access
and makes use of c45 over c22 unusable.

Detailed analysis shows that for RTL838x, RTL930x and RTL931x polling
can be safely deactivated during operation. The MAC layer will continue
to show the last known state. RTL839x is an exception from this. As soon
as polling is disabled the MAC link status register shows "port down".

Enhance the driver to detect this register 13/14/13/14 access sequence.
Before the first access to register 13 of a PHY disable polling for the
corresponding port. Reenable polling as soon as the sequence is finished
or any other unexpected input is detected. Some details about the stop
and start timing:

- The stopping is issued inflight while the polling engine is working.
  After it is finished no new polling for the port will be issued (tested
  with only one port with active polling).
- Reenabling the polling engine happens within ~25us after the last
  command of the MMD sequence. This is mostly due to MMIO overhead.

Technically speaking, add a simple state machine that increments a
per-port MMD counter for each successful step of the sequence. When the
first command starts (counter=1) stop polling. When the last command
finishes (counter=4), errors happen or unexpected data is sent, start
polling.

Additionally add a global "link flapping" option that allows to disable
the state tracker for the to-be-added RTL839x series completely.

Remark! The priv->bus[] array already existed since the initial driver
version but was never filled. To make use of it in this patch add
the initialization too.

Signed-off-by: Markus Stockhausen <redacted>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 54 +++++++++++++++++++++++++
 1 file changed, 54 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index a8e9a497a0dc..8b60645093e3 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -197,6 +197,7 @@ struct otto_emdio_priv {
 	DECLARE_BITMAP(phy_poll, MAX_PORTS);
 	DECLARE_BITMAP(valid_ports, MAX_PORTS);
 	u16 page[MAX_PORTS];
+	u8 mmd_state[MAX_PORTS];
 	u8 smi_bus[MAX_PORTS];
 	u8 smi_addr[MAX_PORTS];
 	bool smi_bus_is_c45[MAX_SMI_BUSSES];
@@ -210,6 +211,7 @@ struct otto_emdio_info {
 	u32 cmd_read;
 	u32 cmd_write;
 	struct otto_emdio_cmd_regs cmd_regs;
+	bool link_flap;
 	u8 num_buses;
 	u8 num_ports;
 	u16 num_pages;
@@ -259,6 +261,47 @@ static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, b
 				  BIT(port % 32), active);
 }
 
+static int otto_emdio_mmd_prefix(struct otto_emdio_priv *priv, int port, int regnum)
+{
+	u8 newstate, *state = &priv->mmd_state[port];
+	int expected, ret = 0;
+
+	if (!test_bit(port, priv->phy_poll))
+		return 0;
+	/*
+	 * Disabled polling might produce link flapping and false notification interrupts on the
+	 * MAC layer. In this case disable c45 over c22 MMD access because chances are high that
+	 * the register 13/14/13/14 sequence is intercepted by a parallel hardware access. As
+	 * a workaround the PHY must provide its own mmd read/write() callbacks and redirect to
+	 * normal c22 registers. See rtlgen_read_mmd().
+	 */
+	if (priv->info->link_flap)
+		return (regnum == MII_MMD_DATA || regnum == MII_MMD_CTRL) ? -EIO : 0;
+
+	expected = (*state & 1) ? MII_MMD_DATA : MII_MMD_CTRL;
+	newstate = regnum == expected ? *state + 1 : 0;
+
+	if (newstate == 1 || newstate < *state)
+		ret = otto_emdio_set_port_polling(priv, port, !newstate);
+	*state = newstate;
+
+	return ret;
+}
+
+static void otto_emdio_mmd_postfix(struct otto_emdio_priv *priv, int port, int cmdret)
+{
+	struct mii_bus *bus = priv->bus[priv->smi_bus[port]];
+
+	if (!test_bit(port, priv->phy_poll))
+		return;
+
+	if (cmdret || priv->mmd_state[port] == 4) {
+		priv->mmd_state[port] = 0;
+		if (otto_emdio_set_port_polling(priv, port, true))
+			dev_err(bus->parent, "failed to enable polling for port %d\n", port);
+	}
+}
+
 static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 			      struct otto_emdio_cmd_regs *cmd_data)
 {
@@ -465,10 +508,15 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
 		return port;
 
 	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_mmd_prefix(priv, port, regnum);
+		if (ret)
+			return ret;
+
 		if (regnum == 31)
 			return priv->page[port];
 
 		ret = priv->info->read_c22(bus, port, regnum, &value);
+		otto_emdio_mmd_postfix(priv, port, ret);
 	}
 
 	return ret ? ret : value;
@@ -484,6 +532,10 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
 		return port;
 
 	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_mmd_prefix(priv, port, regnum);
+		if (ret)
+			return ret;
+
 		if (regnum == 31) {
 			if (value >= RAW_PAGE(priv))
 				return -EINVAL;
@@ -493,6 +545,7 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
 		}
 
 		ret = priv->info->write_c22(bus, port, regnum, value);
+		otto_emdio_mmd_postfix(priv, port, ret);
 	}
 
 	return ret;
@@ -665,6 +718,7 @@ static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv
 	if (!bus)
 		return -ENOMEM;
 
+	priv->bus[mdio_bus] = bus;
 	bus->name = "Realtek Switch MDIO Bus";
 	if (priv->smi_bus_is_c45[mdio_bus]) {
 		bus->read_c45 = otto_emdio_read_c45;
-- 
2.54.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help