[PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation
From: Markus Stockhausen <hidden>
Date: 2026-06-13 11:30:26
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); /* c22 over c45 MDIO_AN_EEE_ADV */ phy_write(phy, 14, 0x3c); phy_write(phy, 13, 0x8007); phy_read(phy, 14); phy_write(phy, 13, 0x7); /* c22 over c45 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, RTL839x 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) or unexpected data is sent start polling. Additionally: - Add a global "initialization done" tracker that stops the mechanism from kicking in during bus probing. - Add a global "link flapping" option that allows to disable the state tracker for the to-be-added RTL839x series completely. Signed-off-by: Markus Stockhausen <redacted> --- drivers/net/mdio/mdio-realtek-rtl9300.c | 62 ++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index a7fd075947b6..e206ee3e2b1c 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c@@ -196,10 +196,12 @@ struct otto_emdio_priv { struct mutex lock; /* protect HW access */ 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]; struct mii_bus *bus[MAX_SMI_BUSSES]; + bool init_done; }; struct otto_emdio_info {
@@ -209,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;
@@ -254,6 +257,43 @@ 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 (!priv->init_done) + 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 int otto_emdio_mmd_postfix(struct otto_emdio_priv *priv, int port, int regnum) +{ + if (priv->mmd_state[port] != 4) + return 0; + + priv->mmd_state[port] = 0; + + return otto_emdio_set_port_polling(priv, port, true); +} + static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd, struct otto_emdio_cmd_regs *cmd_data) {
@@ -463,7 +503,15 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum) if (regnum == 31) return priv->page[port]; + ret = otto_emdio_mmd_prefix(priv, port, regnum); + if (ret) + return ret; + ret = priv->info->read_c22(bus, port, regnum, &value); + if (ret) + return ret; + + ret = otto_emdio_mmd_postfix(priv, port, regnum); } return ret ? ret : value;
@@ -472,7 +520,7 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum) static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value) { struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); - int port; + int port, ret; port = otto_emdio_phy_to_port(bus, phy_id); if (port < 0)
@@ -487,7 +535,15 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 return 0; } - return priv->info->write_c22(bus, port, regnum, value); + ret = otto_emdio_mmd_prefix(priv, port, regnum); + if (ret) + return ret; + + ret = priv->info->write_c22(bus, port, regnum, value); + if (ret) + return ret; + + return otto_emdio_mmd_postfix(priv, port, regnum); } }
@@ -794,6 +850,8 @@ static int otto_emdio_probe(struct platform_device *pdev) return err; } + priv->init_done = true; + return 0; }
--
2.54.0