AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/05 16:54 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "MDIO_REALTEK_RTL9300"
  ],
  "FocusSymbols": [
    "otto_emdio_notify_phy_attach",
    "otto_emdio_notify_phy_detach",
    "otto_emdio_init_polling",
    "otto_emdio_set_port_polling",
    "otto_emdio_8380_read_c22",
    "otto_emdio_8380_write_c22",
    "otto_emdio_8390_read_c22",
    "otto_emdio_8390_write_c22",
    "otto_emdio_8380_setup_controller"
  ],
  "Reasoning": "The patch adds hardware polling support to the Realtek RTL9300 MDIO controller driver and introduces `notify_phy_attach` and `notify_phy_detach` callbacks to the core PHY subsystem. The core changes are reachable during PHY device attachment/detachment, and the driver changes can be reached if the driver is probed via device tree injection.",
  "WorthFuzzing": true
}

1/1 2026/07/05 16:54 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit f47f0d6e2f30dcef7f724d6e978aa5aed5d6aee3\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Jul 5 16:54:31 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml\nindex 271e05bae9c574..67e0b23a8470ea 100644\n--- a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml\n+++ b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml\n@@ -4,7 +4,7 @@\n $id: http://devicetree.org/schemas/net/realtek,rtl9301-mdio.yaml#\n $schema: http://devicetree.org/meta-schemas/core.yaml#\n \n-title: Realtek RTL9300 MDIO Controller\n+title: Realtek Otto Switches Ethernet MDIO Controller\n \n maintainers:\n   - Chris Packham \u003cchris.packham@alliedtelesis.co.nz\u003e\n@@ -12,6 +12,16 @@ maintainers:\n properties:\n   compatible:\n     oneOf:\n+      - items:\n+          - enum:\n+              - realtek,rtl8381-mdio\n+              - realtek,rtl8382-mdio\n+          - const: realtek,rtl8380-mdio\n+      - items:\n+          - enum:\n+              - realtek,rtl8392-mdio\n+              - realtek,rtl8393-mdio\n+          - const: realtek,rtl8391-mdio\n       - items:\n           - enum:\n               - realtek,rtl9302b-mdio\n@@ -24,6 +34,8 @@ properties:\n               - realtek,rtl9313-mdio\n           - const: realtek,rtl9311-mdio\n       - enum:\n+          - realtek,rtl8380-mdio\n+          - realtek,rtl8391-mdio\n           - realtek,rtl9301-mdio\n           - realtek,rtl9311-mdio\n \ndiff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c\nindex 892ed3780a6572..d797d8f01c66e9 100644\n--- a/drivers/net/mdio/mdio-realtek-rtl9300.c\n+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c\n@@ -35,6 +35,74 @@\n  *\n  * The driver works out the mapping based on the MDIO bus described in device tree and phandles on\n  * the ethernet-ports property.\n+ *\n+ * The devices have a hardware polling unit that runs in the background without any CPU load. It\n+ * constantly scans the MDIO bus and the attached PHYs and updates the MAC status registers.\n+ *\n+ * How does the polling work?\n+ *\n+ * Each device has a SMI_POLL_CTRL register. A per-port bitmask decides if the hardware polling of\n+ * the associated bus/address is active or not. The hardware runs a tight loop over this and for\n+ * each set polling bit it issues a status check for the PHY. Attaching a logic analyzer to the\n+ * MDIO bus of an RTL8380 and RTL8393 gives the following commands (in kernel notation):\n+ *\n+ *\tRTL8380\t\t\t\tRTL8393\n+ *\t---------------------------\t---------------------------\n+ *\tphy_write(phy, 31, 0x0);\tphy_read(phy, 0);\n+ *\tphy_write(phy, 13, 0x7);\tphy_read(phy, 1);\n+ *\tphy_write(phy, 14, 0x3c);\tphy_read(phy, 4);\n+ *\tphy_write(phy, 13, 0x8007);\tphy_read(phy, 5);\n+ *\tphy_read(phy, 14);\t\tphy_read(phy, 6);\n+ *\tphy_write(phy, 13, 0x7);\tphy_read(phy, 9);\n+ *\tphy_write(phy, 14, 0x3d);\tphy_read(phy, 10);\n+ *\tphy_write(phy, 13, 0x8007);\tphy_read(phy, 15);\n+ *\tphy_read(phy, 14);\t\tphy_write(phy, 13, 0x7);\n+ *\tphy_read(phy, 9);\t\tphy_write(phy, 14, 0x3c);\n+ *\tphy_read(phy, 10);\t\tphy_write(phy, 13, 0x4007);\n+ *\tphy_read(phy, 15);\t\tphy_read(phy, 14);\n+ *\tphy_read(phy, 0);\t\tphy_write(phy, 13, 0x7);\n+ *\tphy_read(phy, 1);\t\tphy_write(phy, 14, 0x3d);\n+ *\tphy_read(phy, 4);\t\tphy_write(phy, 13, 0x4007);\n+ *\tphy_read(phy, 5);\t\tphy_read(phy, 14);\n+ *\tphy_read(phy, 6);\n+ *\n+ * From the above snippets it polls MDIO_AN_EEE_ADV and MDIO_AN_EEE_LPABLE via C45 over C22. As\n+ * of now it is unclear how the hardware decides if a PHY supports these registers. After one PHY\n+ * status is read, the polling engine goes over to the next PHY. Basically the bus is always busy\n+ * and the MAC status is updated in real-time.\n+ *\n+ * How does MDIO access from kernel work?\n+ *\n+ * When issuing MDIO accesses via an MMIO based interface the final write to the command register\n+ * sets a \"run command now\" bit. Between two polling sequences for different PHYs the hardware\n+ * checks if a user command needs to run and sends it onto the bus. Afterwards it simply continues\n+ * its polling work. Inspecting the command sequence for a paged read on the logic analyzer gives:\n+ *\n+ *\tRTL8380\t\t\t\tRTL8393\n+ *\t---------------------------\t---------------------------\n+ *\tphy_write(phy, 31, page);\tphy_write(phy, 31, page);\n+ *\tphy_write(phy, reg, value);\tphy_write(phy, reg, value);\n+ *\t\t\t\t\tphy_write(phy, 31, 0);\n+ *\n+ * What does this mean?\n+ *\n+ * There are slight differences in polling and PHY access between the models but the challenge\n+ * stays the same. On the one hand that greatly simplifies the MAC layer, on the other hand it\n+ * has some implications for the kernel PHY subsystem.\n+ *\n+ * - Without the polling and a proper MAC status, some of the link handling features do not work.\n+ *   Especially an unpopulated MAC_LINK_STS register cancels operations to other MAC registers.\n+ * - The Realtek page register 31 is magically modified in the background so that polling will\n+ *   read the right data. On the RTL838x polling simply resets it to zero. Other devices seem\n+ *   to track the page access \"magically\" in the background.\n+ * - A C45 over C22 kernel access sequence is most likely to fail because chances are high that\n+ *   the polling engine overwrites registers 13/14 in between.\n+ * - PHY firmware loading can have issues. Especially if a PHY is designed to expect a clean\n+ *   sequence of registers and values without deviation.\n+ * - An access to one PHY will need to wait for the next free slot of the polling engine.\n+ *\n+ * Conclusion: The Realtek MDIO bus driver PHY access must know and handle any interference that\n+ * arises from the above described hardware polling.\n  */\n \n #include \u003clinux/bitfield.h\u003e\n@@ -51,6 +119,50 @@\n #include \u003clinux/property.h\u003e\n #include \u003clinux/regmap.h\u003e\n \n+#define RTL8380_NUM_BUSES\t\t\t1\n+#define RTL8380_NUM_PAGES\t\t\t4096\n+#define RTL8380_NUM_PORTS\t\t\t28\n+#define RTL8380_SMI_GLB_CTRL\t\t\t0xa100\n+#define   RTL8380_SMI_PHY_PATCH_DONE\t\tBIT(15)\n+#define RTL8380_SMI_ACCESS_PHY_CTRL_0\t\t0xa1b8\n+#define RTL8380_SMI_ACCESS_PHY_CTRL_1\t\t0xa1bc\n+#define   RTL8380_PHY_CTRL_REG_ADDR\t\tGENMASK(24, 20)\n+#define   RTL8380_PHY_CTRL_PARK_PAGE\t\tGENMASK(19, 15)\n+#define   RTL8380_PHY_CTRL_MAIN_PAGE\t\tGENMASK(14, 3)\n+#define   RTL8380_PHY_CTRL_WRITE\t\tBIT(2)\n+#define   RTL8380_PHY_CTRL_READ\t\t\t0\n+#define   RTL8380_PHY_CTRL_TYPE_C45\t\tBIT(1)\n+#define   RTL8380_PHY_CTRL_TYPE_C22\t\t0\n+#define   RTL8380_PHY_CTRL_FAIL\t\t\t0 /* no fail indicator */\n+#define RTL8380_SMI_ACCESS_PHY_CTRL_2\t\t0xa1c0\n+#define   RTL8380_PHY_CTRL_INDATA\t\tGENMASK(31, 16)\n+#define   RTL8380_PHY_CTRL_DATA\t\t\tGENMASK(15, 0)\n+#define RTL8380_SMI_ACCESS_PHY_CTRL_3\t\t0xa1c4\n+#define RTL8380_SMI_POLL_CTRL\t\t\t0xa17c\n+#define RTL8380_SMI_PORT0_5_ADDR_CTRL\t\t0xa1c8\n+\n+#define RTL8390_NUM_BUSES\t\t\t2\n+#define RTL8390_NUM_PAGES\t\t\t8192\n+#define RTL8390_NUM_PORTS\t\t\t52\n+#define RTL8390_BCAST_PHYID_CTRL\t\t0x03ec\n+#define RTL8390_PHYREG_ACCESS_CTRL\t\t0x03dc\n+#define   RTL8390_PHY_CTRL_REG_ADDR\t\tGENMASK(9, 5)\n+#define   RTL8390_PHY_CTRL_MAIN_PAGE\t\tGENMASK(22, 10)\n+#define   RTL8390_PHY_CTRL_FAIL\t\t\tBIT(1)\n+#define   RTL8390_PHY_CTRL_WRITE\t\tBIT(3)\n+#define   RTL8390_PHY_CTRL_READ\t\t\t0\n+#define   RTL8390_PHY_CTRL_TYPE_C45\t\tBIT(2)\n+#define   RTL8390_PHY_CTRL_TYPE_C22\t\t0\n+#define RTL8390_PHYREG_CTRL\t\t\t0x03e0\n+#define   RTL8390_PHY_CTRL_EXT_PAGE\t\tGENMASK(8, 0)\n+#define RTL8390_PHYREG_DATA_CTRL\t\t0x03f0\n+#define   RTL8390_PHY_CTRL_INDATA\t\tGENMASK(31, 16)\n+#define   RTL8390_PHY_CTRL_DATA\t\t\tGENMASK(15, 0)\n+#define RTL8390_PHYREG_MMD_CTRL\t\t\t0x03f4\n+#define RTL8390_PHYREG_PORT_CTRL_LOW\t\t0x03e4\n+#define RTL8390_PHYREG_PORT_CTRL_HIGH\t\t0x03e8\n+#define RTL8390_SMI_PORT_POLLING_CTRL\t\t0x03fc\n+\n #define RTL9300_NUM_BUSES\t\t\t4\n #define RTL9300_NUM_PAGES\t\t\t4096\n #define RTL9300_NUM_PORTS\t\t\t28\n@@ -71,6 +183,7 @@\n #define   RTL9300_PHY_CTRL_INDATA\t\tGENMASK(31, 16)\n #define   RTL9300_PHY_CTRL_DATA\t\t\tGENMASK(15, 0)\n #define RTL9300_SMI_ACCESS_PHY_CTRL_3\t\t0xcb7c\n+#define RTL9300_SMI_POLL_CTRL\t\t\t0xca90\n #define RTL9300_SMI_PORT0_5_ADDR_CTRL\t\t0xcb80\n \n #define RTL9310_NUM_BUSES\t\t\t4\n@@ -96,11 +209,13 @@\n #define   RTL9310_PHY_CTRL_INDATA\t\tGENMASK(15, 0)\n #define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL\t0x0c18\n #define RTL9310_SMI_PORT_ADDR_CTRL\t\t0x0c74\n+#define RTL9310_SMI_PORT_POLLING_CTRL\t\t0x0ccc\n #define RTL9310_SMI_PORT_POLLING_SEL\t\t0x0c9c\n \n #define PHY_CTRL_CMD\t\t\t\tBIT(0)\n #define PHY_CTRL_MMD_DEVAD\t\t\tGENMASK(20, 16)\n #define PHY_CTRL_MMD_REG\t\t\tGENMASK(15, 0)\n+#define PHY_VENDOR_REALTEK\t\t\t0x001cc800\n \n #define MAP_ADDRS_PER_REG\t\t\t6\n #define MAP_BITS_PER_ADDR\t\t\t5\n@@ -126,7 +241,9 @@ struct otto_emdio_priv {\n \tconst struct otto_emdio_info *info;\n \tstruct regmap *regmap;\n \tstruct mutex lock; /* protect HW access */\n+\tDECLARE_BITMAP(phy_poll, MAX_PORTS);\n \tDECLARE_BITMAP(valid_ports, MAX_PORTS);\n+\tu16 page[MAX_PORTS];\n \tu8 smi_bus[MAX_PORTS];\n \tu8 smi_addr[MAX_PORTS];\n \tbool smi_bus_is_c45[MAX_SMI_BUSSES];\n@@ -143,6 +260,7 @@ struct otto_emdio_info {\n \tu8 num_buses;\n \tu8 num_ports;\n \tu16 num_pages;\n+\tu32 poll_ctrl;\n \tint (*setup_controller)(struct otto_emdio_priv *priv);\n \tint (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);\n \tint (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);\n@@ -178,6 +296,14 @@ static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)\n \treturn chan-\u003epriv;\n }\n \n+static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, bool active)\n+{\n+\tlockdep_assert_held(\u0026priv-\u003elock);\n+\n+\treturn regmap_assign_bits(priv-\u003eregmap, priv-\u003einfo-\u003epoll_ctrl + (port / 32) * 4,\n+\t\t\t\t  BIT(port % 32), active);\n+}\n+\n static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,\n \t\t\t      struct otto_emdio_cmd_regs *cmd_data)\n {\n@@ -186,9 +312,9 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,\n \tu32 cmdstate;\n \tint ret;\n \n-\t/* Defensive pre check just in case something goes horrible wrong */\n+\t/* Defensive pre check just in case something goes horribly wrong */\n \tret = regmap_read_poll_timeout(priv-\u003eregmap, info-\u003ecmd_regs.c22_data,\n-\t\t\t\t       cmdstate, !(cmdstate \u0026 PHY_CTRL_CMD), 10, 1000);\n+\t\t\t\t       cmdstate, !(cmdstate \u0026 PHY_CTRL_CMD), 10, 10000);\n \tif (ret)\n \t\treturn ret;\n \n@@ -228,7 +354,7 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,\n \t\treturn ret;\n \n \tret = regmap_read_poll_timeout(priv-\u003eregmap, info-\u003ecmd_regs.c22_data,\n-\t\t\t\t       cmdstate, !(cmdstate \u0026 PHY_CTRL_CMD), 10, 1000);\n+\t\t\t\t       cmdstate, !(cmdstate \u0026 PHY_CTRL_CMD), 10, 10000);\n \tif (ret)\n \t\treturn ret;\n \n@@ -265,13 +391,123 @@ static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,\n \treturn otto_emdio_run_cmd(bus, cmd | priv-\u003einfo-\u003ecmd_write, cmd_data);\n }\n \n+static int otto_emdio_8380_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c22_data\t= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |\n+\t\t\t\t  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |\n+\t\t\t\t  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n+\t\t.io_data\t= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),\n+\t};\n+\n+\treturn otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, \u0026cmd_data,\n+\t\t\t\t   RTL8380_PHY_CTRL_DATA, value);\n+}\n+\n+static int otto_emdio_8380_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c22_data\t= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |\n+\t\t\t\t  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |\n+\t\t\t\t  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n+\t\t.io_data\t= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),\n+\t\t.port_mask_low\t= BIT(port),\n+\t};\n+\n+\treturn otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, \u0026cmd_data);\n+}\n+\n+static int otto_emdio_8380_read_c45(struct mii_bus *bus, int port,\n+\t\t\t\t    int dev_addr, int regnum, u32 *value)\n+{\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c45_data\t= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |\n+\t\t\t\t  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),\n+\t\t.io_data\t= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),\n+\t};\n+\n+\treturn otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, \u0026cmd_data,\n+\t\t\t\t   RTL8380_PHY_CTRL_DATA, value);\n+}\n+\n+static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,\n+\t\t\t\t     int dev_addr, int regnum, u16 value)\n+{\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c45_data\t= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |\n+\t\t\t\t  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),\n+\t\t.io_data\t= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),\n+\t\t.port_mask_low\t= BIT(port),\n+\t};\n+\n+\treturn otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, \u0026cmd_data);\n+}\n+\n+static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c22_data\t= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |\n+\t\t\t\t  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n+\t\t.ext_page\t= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),\n+\t\t.io_data\t= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),\n+\t};\n+\n+\treturn otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, \u0026cmd_data,\n+\t\t\t\t   RTL8390_PHY_CTRL_DATA, value);\n+}\n+\n+static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c22_data\t= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |\n+\t\t\t\t  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n+\t\t.ext_page\t= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),\n+\t\t.io_data\t= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),\n+\t\t.port_mask_high\t= (u32)(BIT_ULL(port) \u003e\u003e 32),\n+\t\t.port_mask_low\t= (u32)(BIT_ULL(port)),\n+\t};\n+\n+\treturn otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, \u0026cmd_data);\n+}\n+\n+static int otto_emdio_8390_read_c45(struct mii_bus *bus, int port,\n+\t\t\t\t    int dev_addr, int regnum, u32 *value)\n+{\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c45_data\t= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |\n+\t\t\t\t  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),\n+\t\t.io_data\t= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),\n+\t};\n+\n+\treturn otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, \u0026cmd_data,\n+\t\t\t\t   RTL8390_PHY_CTRL_DATA, value);\n+}\n+\n+static int otto_emdio_8390_write_c45(struct mii_bus *bus, int port,\n+\t\t\t\t     int dev_addr, int regnum, u16 value)\n+{\n+\tstruct otto_emdio_cmd_regs cmd_data = {\n+\t\t.c45_data\t= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |\n+\t\t\t\t  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),\n+\t\t.io_data\t= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),\n+\t\t.port_mask_high\t= (u32)(BIT_ULL(port) \u003e\u003e 32),\n+\t\t.port_mask_low\t= (u32)(BIT_ULL(port)),\n+\t};\n+\n+\treturn otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, \u0026cmd_data);\n+}\n+\n static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\n {\n \tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n \tstruct otto_emdio_cmd_regs cmd_data = {\n \t\t.c22_data\t= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |\n \t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |\n-\t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),\n+\t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n \t\t.io_data\t= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),\n \t};\n \n@@ -285,7 +521,7 @@ static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum,\n \tstruct otto_emdio_cmd_regs cmd_data = {\n \t\t.c22_data\t= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |\n \t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |\n-\t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),\n+\t\t\t\t  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n \t\t.io_data\t= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),\n \t\t.port_mask_low\t= BIT(port),\n \t};\n@@ -325,7 +561,7 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u\n \tstruct otto_emdio_cmd_regs cmd_data = {\n \t\t.broadcast\t= FIELD_PREP(RTL9310_BC_PORT_ID, port),\n \t\t.c22_data\t= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |\n-\t\t\t\t  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),\n+\t\t\t\t  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n \t};\n \n \treturn otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, \u0026cmd_data,\n@@ -337,7 +573,7 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum,\n \tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);\n \tstruct otto_emdio_cmd_regs cmd_data = {\n \t\t.c22_data\t= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |\n-\t\t\t\t  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),\n+\t\t\t\t  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv-\u003epage[port]),\n \t\t.io_data\t= FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),\n \t\t.port_mask_high\t= (u32)(BIT_ULL(port) \u003e\u003e 32),\n \t\t.port_mask_low\t= (u32)(BIT_ULL(port)),\n@@ -383,8 +619,12 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)\n \tif (port \u003c 0)\n \t\treturn port;\n \n-\tscoped_guard(mutex, \u0026priv-\u003elock)\n+\tscoped_guard(mutex, \u0026priv-\u003elock) {\n+\t\tif (regnum == 31)\n+\t\t\treturn priv-\u003epage[port];\n+\n \t\tret = priv-\u003einfo-\u003eread_c22(bus, port, regnum, \u0026value);\n+\t}\n \n \treturn ret ? ret : value;\n }\n@@ -398,8 +638,17 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16\n \tif (port \u003c 0)\n \t\treturn port;\n \n-\tscoped_guard(mutex, \u0026priv-\u003elock)\n+\tscoped_guard(mutex, \u0026priv-\u003elock) {\n+\t\tif (regnum == 31) {\n+\t\t\tif (value \u003e= RAW_PAGE(priv))\n+\t\t\t\treturn -EINVAL;\n+\n+\t\t\tpriv-\u003epage[port] = value;\n+\t\t\treturn 0;\n+\t\t}\n+\n \t\tret = priv-\u003einfo-\u003ewrite_c22(bus, port, regnum, value);\n+\t}\n \n \treturn ret;\n }\n@@ -472,6 +721,15 @@ static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)\n \treturn 0;\n }\n \n+static int otto_emdio_8380_setup_controller(struct otto_emdio_priv *priv)\n+{\n+\t/*\n+\t * PHY_PATCH_DONE enables PHY control via SoC. This is required for PHY access, including\n+\t * patching and must be set before the PHYs are probed.\n+\t */\n+\treturn regmap_set_bits(priv-\u003eregmap, RTL8380_SMI_GLB_CTRL, RTL8380_SMI_PHY_PATCH_DONE);\n+}\n+\n static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)\n {\n \tu32 glb_ctrl_mask = 0, glb_ctrl_val = 0;\n@@ -508,6 +766,54 @@ static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)\n \treturn 0;\n }\n \n+static int otto_emdio_notify_phy_attach(struct phy_device *phydev)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev-\u003emdio.bus);\n+\tint port = otto_emdio_phy_to_port(phydev-\u003emdio.bus, phydev-\u003emdio.addr);\n+\tstruct otto_emdio_chan *chan = phydev-\u003emdio.bus-\u003epriv;\n+\tint ret;\n+\n+\tif (port \u003c 0)\n+\t\treturn port;\n+\n+\tif (test_bit(port, priv-\u003ephy_poll))\n+\t\treturn 0;\n+\n+\tif (!priv-\u003esmi_bus_is_c45[chan-\u003emdio_bus] \u0026\u0026\n+\t    (!phy_id_compare_vendor(phydev-\u003ephy_id, PHY_VENDOR_REALTEK))) {\n+\t\tphydev_err(phydev, \"Only Realtek PHYs allowed on C22 bus\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tscoped_guard(mutex, \u0026priv-\u003elock) {\n+\t\tret = otto_emdio_set_port_polling(priv, port, true);\n+\t\tif (!ret)\n+\t\t\t__set_bit(port, priv-\u003ephy_poll);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void otto_emdio_notify_phy_detach(struct phy_device *phydev)\n+{\n+\tstruct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev-\u003emdio.bus);\n+\tint port = otto_emdio_phy_to_port(phydev-\u003emdio.bus, phydev-\u003emdio.addr);\n+\tstruct mii_bus *bus = phydev-\u003emdio.bus;\n+\tint ret;\n+\n+\tif (port \u003c 0)\n+\t\treturn;\n+\n+\tscoped_guard(mutex, \u0026priv-\u003elock) {\n+\t\tret = otto_emdio_set_port_polling(priv, port, false);\n+\t\tif (!ret)\n+\t\t\t__clear_bit(port, priv-\u003ephy_poll);\n+\t}\n+\t\n+\tif (ret)\n+\t\tdev_err(bus-\u003eparent, \"failed to disable polling for port %d\\n\", port);\n+}\n+\n static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,\n \t\t\t\t struct fwnode_handle *node)\n {\n@@ -537,6 +843,9 @@ static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv\n \t\tbus-\u003ewrite = otto_emdio_write_c22;\n \t}\n \tbus-\u003eparent = dev;\n+\tbus-\u003enotify_phy_attach = otto_emdio_notify_phy_attach;\n+\tbus-\u003enotify_phy_detach = otto_emdio_notify_phy_detach;\n+\n \tchan = bus-\u003epriv;\n \tchan-\u003emdio_bus = mdio_bus;\n \tchan-\u003epriv = priv;\n@@ -653,6 +962,21 @@ static int otto_emdio_map_ports(struct device *dev)\n \treturn err;\n }\n \n+static int otto_emdio_init_polling(struct otto_emdio_priv *priv)\n+{\n+\tint err;\n+\n+\tfor (int port = 0; port \u003c priv-\u003einfo-\u003enum_ports; port++) {\n+\t\tscoped_guard(mutex, \u0026priv-\u003elock) {\n+\t\t\terr = otto_emdio_set_port_polling(priv, port, false);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int otto_emdio_probe(struct platform_device *pdev)\n {\n \tstruct device *dev = \u0026pdev-\u003edev;\n@@ -672,6 +996,10 @@ static int otto_emdio_probe(struct platform_device *pdev)\n \tif (IS_ERR(priv-\u003eregmap))\n \t\treturn PTR_ERR(priv-\u003eregmap);\n \n+\terr = otto_emdio_init_polling(priv);\n+\tif (err)\n+\t\treturn err;\n+\n \tplatform_set_drvdata(pdev, priv);\n \n \terr = otto_emdio_map_ports(dev);\n@@ -697,6 +1025,51 @@ static int otto_emdio_probe(struct platform_device *pdev)\n \treturn 0;\n }\n \n+static const struct otto_emdio_info otto_emdio_8380_info = {\n+\t.addr_map_base = RTL8380_SMI_PORT0_5_ADDR_CTRL,\n+\t.cmd_fail = RTL8380_PHY_CTRL_FAIL,\n+\t.cmd_read = RTL8380_PHY_CTRL_READ,\n+\t.cmd_write = RTL8380_PHY_CTRL_WRITE,\n+\t.cmd_regs = {\n+\t\t.c22_data = RTL8380_SMI_ACCESS_PHY_CTRL_1,\n+\t\t.c45_data = RTL8380_SMI_ACCESS_PHY_CTRL_3,\n+\t\t.io_data = RTL8380_SMI_ACCESS_PHY_CTRL_2,\n+\t\t.port_mask_low = RTL8380_SMI_ACCESS_PHY_CTRL_0,\n+\t},\n+\t.num_buses = RTL8380_NUM_BUSES,\n+\t.num_pages = RTL8380_NUM_PAGES,\n+\t.num_ports = RTL8380_NUM_PORTS,\n+\t.poll_ctrl = RTL8380_SMI_POLL_CTRL,\n+\t.setup_controller = otto_emdio_8380_setup_controller,\n+\t.read_c22 = otto_emdio_8380_read_c22,\n+\t.read_c45 = otto_emdio_8380_read_c45,\n+\t.write_c22 = otto_emdio_8380_write_c22,\n+\t.write_c45 = otto_emdio_8380_write_c45,\n+};\n+\n+static const struct otto_emdio_info otto_emdio_8390_info = {\n+\t.cmd_fail = RTL8390_PHY_CTRL_FAIL,\n+\t.cmd_read = RTL8390_PHY_CTRL_READ,\n+\t.cmd_write = RTL8390_PHY_CTRL_WRITE,\n+\t.cmd_regs = {\n+\t\t.broadcast = RTL8390_BCAST_PHYID_CTRL,\n+\t\t.c22_data = RTL8390_PHYREG_ACCESS_CTRL,\n+\t\t.c45_data = RTL8390_PHYREG_MMD_CTRL,\n+\t\t.ext_page = RTL8390_PHYREG_CTRL,\n+\t\t.io_data = RTL8390_PHYREG_DATA_CTRL,\n+\t\t.port_mask_low = RTL8390_PHYREG_PORT_CTRL_LOW,\n+\t\t.port_mask_high = RTL8390_PHYREG_PORT_CTRL_HIGH,\n+\t},\n+\t.num_buses = RTL8390_NUM_BUSES,\n+\t.num_pages = RTL8390_NUM_PAGES,\n+\t.num_ports = RTL8390_NUM_PORTS,\n+\t.poll_ctrl = RTL8390_SMI_PORT_POLLING_CTRL,\n+\t.read_c22 = otto_emdio_8390_read_c22,\n+\t.read_c45 = otto_emdio_8390_read_c45,\n+\t.write_c22 = otto_emdio_8390_write_c22,\n+\t.write_c45 = otto_emdio_8390_write_c45,\n+};\n+\n static const struct otto_emdio_info otto_emdio_9300_info = {\n \t.addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,\n \t.bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,\n@@ -712,6 +1085,7 @@ static const struct otto_emdio_info otto_emdio_9300_info = {\n \t.num_buses = RTL9300_NUM_BUSES,\n \t.num_ports = RTL9300_NUM_PORTS,\n \t.num_pages = RTL9300_NUM_PAGES,\n+\t.poll_ctrl = RTL9300_SMI_POLL_CTRL,\n \t.setup_controller = otto_emdio_9300_setup_controller,\n \t.read_c22 = otto_emdio_9300_read_c22,\n \t.read_c45 = otto_emdio_9300_read_c45,\n@@ -737,6 +1111,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {\n \t.num_buses = RTL9310_NUM_BUSES,\n \t.num_pages = RTL9310_NUM_PAGES,\n \t.num_ports = RTL9310_NUM_PORTS,\n+\t.poll_ctrl = RTL9310_SMI_PORT_POLLING_CTRL,\n \t.setup_controller = otto_emdio_9310_setup_controller,\n \t.read_c22 = otto_emdio_9310_read_c22,\n \t.read_c45 = otto_emdio_9310_read_c45,\n@@ -745,6 +1120,8 @@ static const struct otto_emdio_info otto_emdio_9310_info = {\n };\n \n static const struct of_device_id otto_emdio_ids[] = {\n+\t{ .compatible = \"realtek,rtl8380-mdio\", .data = \u0026otto_emdio_8380_info },\n+\t{ .compatible = \"realtek,rtl8391-mdio\", .data = \u0026otto_emdio_8390_info },\n \t{ .compatible = \"realtek,rtl9301-mdio\", .data = \u0026otto_emdio_9300_info },\n \t{ .compatible = \"realtek,rtl9311-mdio\", .data = \u0026otto_emdio_9310_info },\n \t{}\ndiff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c\nindex 0615228459ef42..676cbf183350d9 100644\n--- a/drivers/net/phy/phy_device.c\n+++ b/drivers/net/phy/phy_device.c\n@@ -1876,6 +1876,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,\n \tif (err)\n \t\tgoto error;\n \n+\tif (phydev-\u003emdio.bus-\u003enotify_phy_attach) {\n+\t\terr = phydev-\u003emdio.bus-\u003enotify_phy_attach(phydev);\n+\t\tif (err)\n+\t\t\tgoto error;\n+\t}\n+\n \tphy_resume(phydev);\n \n \t/**\n@@ -1919,6 +1925,9 @@ void phy_detach(struct phy_device *phydev)\n \tstruct module *ndev_owner = NULL;\n \tstruct mii_bus *bus;\n \n+\tif (phydev-\u003emdio.bus-\u003enotify_phy_detach)\n+\t\tphydev-\u003emdio.bus-\u003enotify_phy_detach(phydev);\n+\n \tif (phydev-\u003edevlink) {\n \t\tdevice_link_del(phydev-\u003edevlink);\n \t\tphydev-\u003edevlink = NULL;\ndiff --git a/include/linux/phy.h b/include/linux/phy.h\nindex 199a7aaa341bfd..3160ca99deab79 100644\n--- a/include/linux/phy.h\n+++ b/include/linux/phy.h\n@@ -376,6 +376,10 @@ struct mii_bus {\n \t\t\t int regnum, u16 val);\n \t/** @reset: Perform a reset of the bus */\n \tint (*reset)(struct mii_bus *bus);\n+\t/** @notify_phy_attach: Perform post-attach handling */\n+\tint (*notify_phy_attach)(struct phy_device *phydev);\n+\t/** @notify_phy_detach: Perform pre-detach handling */\n+\tvoid (*notify_phy_detach)(struct phy_device *phydev);\n \n \t/** @stats: Statistic counters per device on the bus */\n \tstruct mdio_bus_stats stats[PHY_MAX_ADDR];\n"
}

2/1 2026/07/05 16:54 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "MDIO_REALTEK_RTL9300"
  ],
  "FocusSymbols": [
    "otto_emdio_notify_phy_attach",
    "otto_emdio_notify_phy_detach",
    "otto_emdio_init_polling",
    "otto_emdio_set_port_polling",
    "otto_emdio_8380_read_c22",
    "otto_emdio_8380_write_c22",
    "otto_emdio_8390_read_c22",
    "otto_emdio_8390_write_c22",
    "otto_emdio_8380_setup_controller"
  ],
  "Reasoning": "The patch adds hardware polling support to the Realtek RTL9300 MDIO controller driver and introduces `notify_phy_attach` and `notify_phy_detach` callbacks to the core PHY subsystem. The core changes are reachable during PHY device attachment/detachment, and the driver changes can be reached if the driver is probed via device tree injection.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit f47f0d6e2f30dcef7f724d6e978aa5aed5d6aee3
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Jul 5 16:54:31 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
index 271e05bae9c574..67e0b23a8470ea 100644
--- a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
+++ b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
@@ -4,7 +4,7 @@
 $id: http://devicetree.org/schemas/net/realtek,rtl9301-mdio.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Realtek RTL9300 MDIO Controller
+title: Realtek Otto Switches Ethernet MDIO Controller
 
 maintainers:
   - Chris Packham <chris.packham@alliedtelesis.co.nz>
@@ -12,6 +12,16 @@ maintainers:
 properties:
   compatible:
     oneOf:
+      - items:
+          - enum:
+              - realtek,rtl8381-mdio
+              - realtek,rtl8382-mdio
+          - const: realtek,rtl8380-mdio
+      - items:
+          - enum:
+              - realtek,rtl8392-mdio
+              - realtek,rtl8393-mdio
+          - const: realtek,rtl8391-mdio
       - items:
           - enum:
               - realtek,rtl9302b-mdio
@@ -24,6 +34,8 @@ properties:
               - realtek,rtl9313-mdio
           - const: realtek,rtl9311-mdio
       - enum:
+          - realtek,rtl8380-mdio
+          - realtek,rtl8391-mdio
           - realtek,rtl9301-mdio
           - realtek,rtl9311-mdio
 
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 892ed3780a6572..d797d8f01c66e9 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -35,6 +35,74 @@
  *
  * The driver works out the mapping based on the MDIO bus described in device tree and phandles on
  * the ethernet-ports property.
+ *
+ * The devices have a hardware polling unit that runs in the background without any CPU load. It
+ * constantly scans the MDIO bus and the attached PHYs and updates the MAC status registers.
+ *
+ * How does the polling work?
+ *
+ * Each device has a SMI_POLL_CTRL register. A per-port bitmask decides if the hardware polling of
+ * the associated bus/address is active or not. The hardware runs a tight loop over this and for
+ * each set polling bit it issues a status check for the PHY. Attaching a logic analyzer to the
+ * MDIO bus of an RTL8380 and RTL8393 gives the following commands (in kernel notation):
+ *
+ *	RTL8380				RTL8393
+ *	---------------------------	---------------------------
+ *	phy_write(phy, 31, 0x0);	phy_read(phy, 0);
+ *	phy_write(phy, 13, 0x7);	phy_read(phy, 1);
+ *	phy_write(phy, 14, 0x3c);	phy_read(phy, 4);
+ *	phy_write(phy, 13, 0x8007);	phy_read(phy, 5);
+ *	phy_read(phy, 14);		phy_read(phy, 6);
+ *	phy_write(phy, 13, 0x7);	phy_read(phy, 9);
+ *	phy_write(phy, 14, 0x3d);	phy_read(phy, 10);
+ *	phy_write(phy, 13, 0x8007);	phy_read(phy, 15);
+ *	phy_read(phy, 14);		phy_write(phy, 13, 0x7);
+ *	phy_read(phy, 9);		phy_write(phy, 14, 0x3c);
+ *	phy_read(phy, 10);		phy_write(phy, 13, 0x4007);
+ *	phy_read(phy, 15);		phy_read(phy, 14);
+ *	phy_read(phy, 0);		phy_write(phy, 13, 0x7);
+ *	phy_read(phy, 1);		phy_write(phy, 14, 0x3d);
+ *	phy_read(phy, 4);		phy_write(phy, 13, 0x4007);
+ *	phy_read(phy, 5);		phy_read(phy, 14);
+ *	phy_read(phy, 6);
+ *
+ * From the above snippets it polls MDIO_AN_EEE_ADV and MDIO_AN_EEE_LPABLE via C45 over C22. As
+ * of now it is unclear how the hardware decides if a PHY supports these registers. After one PHY
+ * status is read, the polling engine goes over to the next PHY. Basically the bus is always busy
+ * and the MAC status is updated in real-time.
+ *
+ * How does MDIO access from kernel work?
+ *
+ * When issuing MDIO accesses via an MMIO based interface the final write to the command register
+ * sets a "run command now" bit. Between two polling sequences for different PHYs the hardware
+ * checks if a user command needs to run and sends it onto the bus. Afterwards it simply continues
+ * its polling work. Inspecting the command sequence for a paged read on the logic analyzer gives:
+ *
+ *	RTL8380				RTL8393
+ *	---------------------------	---------------------------
+ *	phy_write(phy, 31, page);	phy_write(phy, 31, page);
+ *	phy_write(phy, reg, value);	phy_write(phy, reg, value);
+ *					phy_write(phy, 31, 0);
+ *
+ * What does this mean?
+ *
+ * There are slight differences in polling and PHY access between the models but the challenge
+ * stays the same. On the one hand that greatly simplifies the MAC layer, on the other hand it
+ * has some implications for the kernel PHY subsystem.
+ *
+ * - Without the polling and a proper MAC status, some of the link handling features do not work.
+ *   Especially an unpopulated MAC_LINK_STS register cancels operations to other MAC registers.
+ * - The Realtek page register 31 is magically modified in the background so that polling will
+ *   read the right data. On the RTL838x polling simply resets it to zero. Other devices seem
+ *   to track the page access "magically" in the background.
+ * - A C45 over C22 kernel access sequence is most likely to fail because chances are high that
+ *   the polling engine overwrites registers 13/14 in between.
+ * - PHY firmware loading can have issues. Especially if a PHY is designed to expect a clean
+ *   sequence of registers and values without deviation.
+ * - An access to one PHY will need to wait for the next free slot of the polling engine.
+ *
+ * Conclusion: The Realtek MDIO bus driver PHY access must know and handle any interference that
+ * arises from the above described hardware polling.
  */
 
 #include <linux/bitfield.h>
@@ -51,6 +119,50 @@
 #include <linux/property.h>
 #include <linux/regmap.h>
 
+#define RTL8380_NUM_BUSES			1
+#define RTL8380_NUM_PAGES			4096
+#define RTL8380_NUM_PORTS			28
+#define RTL8380_SMI_GLB_CTRL			0xa100
+#define   RTL8380_SMI_PHY_PATCH_DONE		BIT(15)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_0		0xa1b8
+#define RTL8380_SMI_ACCESS_PHY_CTRL_1		0xa1bc
+#define   RTL8380_PHY_CTRL_REG_ADDR		GENMASK(24, 20)
+#define   RTL8380_PHY_CTRL_PARK_PAGE		GENMASK(19, 15)
+#define   RTL8380_PHY_CTRL_MAIN_PAGE		GENMASK(14, 3)
+#define   RTL8380_PHY_CTRL_WRITE		BIT(2)
+#define   RTL8380_PHY_CTRL_READ			0
+#define   RTL8380_PHY_CTRL_TYPE_C45		BIT(1)
+#define   RTL8380_PHY_CTRL_TYPE_C22		0
+#define   RTL8380_PHY_CTRL_FAIL			0 /* no fail indicator */
+#define RTL8380_SMI_ACCESS_PHY_CTRL_2		0xa1c0
+#define   RTL8380_PHY_CTRL_INDATA		GENMASK(31, 16)
+#define   RTL8380_PHY_CTRL_DATA			GENMASK(15, 0)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_3		0xa1c4
+#define RTL8380_SMI_POLL_CTRL			0xa17c
+#define RTL8380_SMI_PORT0_5_ADDR_CTRL		0xa1c8
+
+#define RTL8390_NUM_BUSES			2
+#define RTL8390_NUM_PAGES			8192
+#define RTL8390_NUM_PORTS			52
+#define RTL8390_BCAST_PHYID_CTRL		0x03ec
+#define RTL8390_PHYREG_ACCESS_CTRL		0x03dc
+#define   RTL8390_PHY_CTRL_REG_ADDR		GENMASK(9, 5)
+#define   RTL8390_PHY_CTRL_MAIN_PAGE		GENMASK(22, 10)
+#define   RTL8390_PHY_CTRL_FAIL			BIT(1)
+#define   RTL8390_PHY_CTRL_WRITE		BIT(3)
+#define   RTL8390_PHY_CTRL_READ			0
+#define   RTL8390_PHY_CTRL_TYPE_C45		BIT(2)
+#define   RTL8390_PHY_CTRL_TYPE_C22		0
+#define RTL8390_PHYREG_CTRL			0x03e0
+#define   RTL8390_PHY_CTRL_EXT_PAGE		GENMASK(8, 0)
+#define RTL8390_PHYREG_DATA_CTRL		0x03f0
+#define   RTL8390_PHY_CTRL_INDATA		GENMASK(31, 16)
+#define   RTL8390_PHY_CTRL_DATA			GENMASK(15, 0)
+#define RTL8390_PHYREG_MMD_CTRL			0x03f4
+#define RTL8390_PHYREG_PORT_CTRL_LOW		0x03e4
+#define RTL8390_PHYREG_PORT_CTRL_HIGH		0x03e8
+#define RTL8390_SMI_PORT_POLLING_CTRL		0x03fc
+
 #define RTL9300_NUM_BUSES			4
 #define RTL9300_NUM_PAGES			4096
 #define RTL9300_NUM_PORTS			28
@@ -71,6 +183,7 @@
 #define   RTL9300_PHY_CTRL_INDATA		GENMASK(31, 16)
 #define   RTL9300_PHY_CTRL_DATA			GENMASK(15, 0)
 #define RTL9300_SMI_ACCESS_PHY_CTRL_3		0xcb7c
+#define RTL9300_SMI_POLL_CTRL			0xca90
 #define RTL9300_SMI_PORT0_5_ADDR_CTRL		0xcb80
 
 #define RTL9310_NUM_BUSES			4
@@ -96,11 +209,13 @@
 #define   RTL9310_PHY_CTRL_INDATA		GENMASK(15, 0)
 #define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL	0x0c18
 #define RTL9310_SMI_PORT_ADDR_CTRL		0x0c74
+#define RTL9310_SMI_PORT_POLLING_CTRL		0x0ccc
 #define RTL9310_SMI_PORT_POLLING_SEL		0x0c9c
 
 #define PHY_CTRL_CMD				BIT(0)
 #define PHY_CTRL_MMD_DEVAD			GENMASK(20, 16)
 #define PHY_CTRL_MMD_REG			GENMASK(15, 0)
+#define PHY_VENDOR_REALTEK			0x001cc800
 
 #define MAP_ADDRS_PER_REG			6
 #define MAP_BITS_PER_ADDR			5
@@ -126,7 +241,9 @@ struct otto_emdio_priv {
 	const struct otto_emdio_info *info;
 	struct regmap *regmap;
 	struct mutex lock; /* protect HW access */
+	DECLARE_BITMAP(phy_poll, MAX_PORTS);
 	DECLARE_BITMAP(valid_ports, MAX_PORTS);
+	u16 page[MAX_PORTS];
 	u8 smi_bus[MAX_PORTS];
 	u8 smi_addr[MAX_PORTS];
 	bool smi_bus_is_c45[MAX_SMI_BUSSES];
@@ -143,6 +260,7 @@ struct otto_emdio_info {
 	u8 num_buses;
 	u8 num_ports;
 	u16 num_pages;
+	u32 poll_ctrl;
 	int (*setup_controller)(struct otto_emdio_priv *priv);
 	int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);
 	int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
@@ -178,6 +296,14 @@ static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)
 	return chan->priv;
 }
 
+static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, bool active)
+{
+	lockdep_assert_held(&priv->lock);
+
+	return regmap_assign_bits(priv->regmap, priv->info->poll_ctrl + (port / 32) * 4,
+				  BIT(port % 32), active);
+}
+
 static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 			      struct otto_emdio_cmd_regs *cmd_data)
 {
@@ -186,9 +312,9 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 	u32 cmdstate;
 	int ret;
 
-	/* Defensive pre check just in case something goes horrible wrong */
+	/* Defensive pre check just in case something goes horribly wrong */
 	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
-				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 10000);
 	if (ret)
 		return ret;
 
@@ -228,7 +354,7 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 		return ret;
 
 	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
-				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 10000);
 	if (ret)
 		return ret;
 
@@ -265,13 +391,123 @@ static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
 	return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
 }
 
+static int otto_emdio_8380_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data,
+				   RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),
+		.port_mask_low	= BIT(port),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8380_read_c45(struct mii_bus *bus, int port,
+				    int dev_addr, int regnum, u32 *value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data,
+				   RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
+				     int dev_addr, int regnum, u16 value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),
+		.port_mask_low	= BIT(port),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
+static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.ext_page	= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data,
+				   RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.ext_page	= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
+		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
+		.port_mask_low	= (u32)(BIT_ULL(port)),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8390_read_c45(struct mii_bus *bus, int port,
+				    int dev_addr, int regnum, u32 *value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data,
+				   RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c45(struct mii_bus *bus, int port,
+				     int dev_addr, int regnum, u16 value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
+		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
+		.port_mask_low	= (u32)(BIT_ULL(port)),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
 static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
 {
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
 				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
-				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
 	};
 
@@ -285,7 +521,7 @@ static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum,
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
 				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
-				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
 		.port_mask_low	= BIT(port),
 	};
@@ -325,7 +561,7 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u
 	struct otto_emdio_cmd_regs cmd_data = {
 		.broadcast	= FIELD_PREP(RTL9310_BC_PORT_ID, port),
 		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
-				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 	};
 
 	return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data,
@@ -337,7 +573,7 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum,
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
-				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
 		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
 		.port_mask_low	= (u32)(BIT_ULL(port)),
@@ -383,8 +619,12 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
 	if (port < 0)
 		return port;
 
-	scoped_guard(mutex, &priv->lock)
+	scoped_guard(mutex, &priv->lock) {
+		if (regnum == 31)
+			return priv->page[port];
+
 		ret = priv->info->read_c22(bus, port, regnum, &value);
+	}
 
 	return ret ? ret : value;
 }
@@ -398,8 +638,17 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
 	if (port < 0)
 		return port;
 
-	scoped_guard(mutex, &priv->lock)
+	scoped_guard(mutex, &priv->lock) {
+		if (regnum == 31) {
+			if (value >= RAW_PAGE(priv))
+				return -EINVAL;
+
+			priv->page[port] = value;
+			return 0;
+		}
+
 		ret = priv->info->write_c22(bus, port, regnum, value);
+	}
 
 	return ret;
 }
@@ -472,6 +721,15 @@ static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)
 	return 0;
 }
 
+static int otto_emdio_8380_setup_controller(struct otto_emdio_priv *priv)
+{
+	/*
+	 * PHY_PATCH_DONE enables PHY control via SoC. This is required for PHY access, including
+	 * patching and must be set before the PHYs are probed.
+	 */
+	return regmap_set_bits(priv->regmap, RTL8380_SMI_GLB_CTRL, RTL8380_SMI_PHY_PATCH_DONE);
+}
+
 static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)
 {
 	u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
@@ -508,6 +766,54 @@ static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)
 	return 0;
 }
 
+static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
+	int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+	struct otto_emdio_chan *chan = phydev->mdio.bus->priv;
+	int ret;
+
+	if (port < 0)
+		return port;
+
+	if (test_bit(port, priv->phy_poll))
+		return 0;
+
+	if (!priv->smi_bus_is_c45[chan->mdio_bus] &&
+	    (!phy_id_compare_vendor(phydev->phy_id, PHY_VENDOR_REALTEK))) {
+		phydev_err(phydev, "Only Realtek PHYs allowed on C22 bus\n");
+		return -EOPNOTSUPP;
+	}
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_set_port_polling(priv, port, true);
+		if (!ret)
+			__set_bit(port, priv->phy_poll);
+	}
+
+	return ret;
+}
+
+static void otto_emdio_notify_phy_detach(struct phy_device *phydev)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
+	int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+	struct mii_bus *bus = phydev->mdio.bus;
+	int ret;
+
+	if (port < 0)
+		return;
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_set_port_polling(priv, port, false);
+		if (!ret)
+			__clear_bit(port, priv->phy_poll);
+	}
+	
+	if (ret)
+		dev_err(bus->parent, "failed to disable polling for port %d\n", port);
+}
+
 static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,
 				 struct fwnode_handle *node)
 {
@@ -537,6 +843,9 @@ static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv
 		bus->write = otto_emdio_write_c22;
 	}
 	bus->parent = dev;
+	bus->notify_phy_attach = otto_emdio_notify_phy_attach;
+	bus->notify_phy_detach = otto_emdio_notify_phy_detach;
+
 	chan = bus->priv;
 	chan->mdio_bus = mdio_bus;
 	chan->priv = priv;
@@ -653,6 +962,21 @@ static int otto_emdio_map_ports(struct device *dev)
 	return err;
 }
 
+static int otto_emdio_init_polling(struct otto_emdio_priv *priv)
+{
+	int err;
+
+	for (int port = 0; port < priv->info->num_ports; port++) {
+		scoped_guard(mutex, &priv->lock) {
+			err = otto_emdio_set_port_polling(priv, port, false);
+			if (err)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
 static int otto_emdio_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -672,6 +996,10 @@ static int otto_emdio_probe(struct platform_device *pdev)
 	if (IS_ERR(priv->regmap))
 		return PTR_ERR(priv->regmap);
 
+	err = otto_emdio_init_polling(priv);
+	if (err)
+		return err;
+
 	platform_set_drvdata(pdev, priv);
 
 	err = otto_emdio_map_ports(dev);
@@ -697,6 +1025,51 @@ static int otto_emdio_probe(struct platform_device *pdev)
 	return 0;
 }
 
+static const struct otto_emdio_info otto_emdio_8380_info = {
+	.addr_map_base = RTL8380_SMI_PORT0_5_ADDR_CTRL,
+	.cmd_fail = RTL8380_PHY_CTRL_FAIL,
+	.cmd_read = RTL8380_PHY_CTRL_READ,
+	.cmd_write = RTL8380_PHY_CTRL_WRITE,
+	.cmd_regs = {
+		.c22_data = RTL8380_SMI_ACCESS_PHY_CTRL_1,
+		.c45_data = RTL8380_SMI_ACCESS_PHY_CTRL_3,
+		.io_data = RTL8380_SMI_ACCESS_PHY_CTRL_2,
+		.port_mask_low = RTL8380_SMI_ACCESS_PHY_CTRL_0,
+	},
+	.num_buses = RTL8380_NUM_BUSES,
+	.num_pages = RTL8380_NUM_PAGES,
+	.num_ports = RTL8380_NUM_PORTS,
+	.poll_ctrl = RTL8380_SMI_POLL_CTRL,
+	.setup_controller = otto_emdio_8380_setup_controller,
+	.read_c22 = otto_emdio_8380_read_c22,
+	.read_c45 = otto_emdio_8380_read_c45,
+	.write_c22 = otto_emdio_8380_write_c22,
+	.write_c45 = otto_emdio_8380_write_c45,
+};
+
+static const struct otto_emdio_info otto_emdio_8390_info = {
+	.cmd_fail = RTL8390_PHY_CTRL_FAIL,
+	.cmd_read = RTL8390_PHY_CTRL_READ,
+	.cmd_write = RTL8390_PHY_CTRL_WRITE,
+	.cmd_regs = {
+		.broadcast = RTL8390_BCAST_PHYID_CTRL,
+		.c22_data = RTL8390_PHYREG_ACCESS_CTRL,
+		.c45_data = RTL8390_PHYREG_MMD_CTRL,
+		.ext_page = RTL8390_PHYREG_CTRL,
+		.io_data = RTL8390_PHYREG_DATA_CTRL,
+		.port_mask_low = RTL8390_PHYREG_PORT_CTRL_LOW,
+		.port_mask_high = RTL8390_PHYREG_PORT_CTRL_HIGH,
+	},
+	.num_buses = RTL8390_NUM_BUSES,
+	.num_pages = RTL8390_NUM_PAGES,
+	.num_ports = RTL8390_NUM_PORTS,
+	.poll_ctrl = RTL8390_SMI_PORT_POLLING_CTRL,
+	.read_c22 = otto_emdio_8390_read_c22,
+	.read_c45 = otto_emdio_8390_read_c45,
+	.write_c22 = otto_emdio_8390_write_c22,
+	.write_c45 = otto_emdio_8390_write_c45,
+};
+
 static const struct otto_emdio_info otto_emdio_9300_info = {
 	.addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,
 	.bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,
@@ -712,6 +1085,7 @@ static const struct otto_emdio_info otto_emdio_9300_info = {
 	.num_buses = RTL9300_NUM_BUSES,
 	.num_ports = RTL9300_NUM_PORTS,
 	.num_pages = RTL9300_NUM_PAGES,
+	.poll_ctrl = RTL9300_SMI_POLL_CTRL,
 	.setup_controller = otto_emdio_9300_setup_controller,
 	.read_c22 = otto_emdio_9300_read_c22,
 	.read_c45 = otto_emdio_9300_read_c45,
@@ -737,6 +1111,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
 	.num_buses = RTL9310_NUM_BUSES,
 	.num_pages = RTL9310_NUM_PAGES,
 	.num_ports = RTL9310_NUM_PORTS,
+	.poll_ctrl = RTL9310_SMI_PORT_POLLING_CTRL,
 	.setup_controller = otto_emdio_9310_setup_controller,
 	.read_c22 = otto_emdio_9310_read_c22,
 	.read_c45 = otto_emdio_9310_read_c45,
@@ -745,6 +1120,8 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
 };
 
 static const struct of_device_id otto_emdio_ids[] = {
+	{ .compatible = "realtek,rtl8380-mdio", .data = &otto_emdio_8380_info },
+	{ .compatible = "realtek,rtl8391-mdio", .data = &otto_emdio_8390_info },
 	{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
 	{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
 	{}
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0615228459ef42..676cbf183350d9 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1876,6 +1876,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 	if (err)
 		goto error;
 
+	if (phydev->mdio.bus->notify_phy_attach) {
+		err = phydev->mdio.bus->notify_phy_attach(phydev);
+		if (err)
+			goto error;
+	}
+
 	phy_resume(phydev);
 
 	/**
@@ -1919,6 +1925,9 @@ void phy_detach(struct phy_device *phydev)
 	struct module *ndev_owner = NULL;
 	struct mii_bus *bus;
 
+	if (phydev->mdio.bus->notify_phy_detach)
+		phydev->mdio.bus->notify_phy_detach(phydev);
+
 	if (phydev->devlink) {
 		device_link_del(phydev->devlink);
 		phydev->devlink = NULL;
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341bfd..3160ca99deab79 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -376,6 +376,10 @@ struct mii_bus {
 			 int regnum, u16 val);
 	/** @reset: Perform a reset of the bus */
 	int (*reset)(struct mii_bus *bus);
+	/** @notify_phy_attach: Perform post-attach handling */
+	int (*notify_phy_attach)(struct phy_device *phydev);
+	/** @notify_phy_detach: Perform pre-detach handling */
+	void (*notify_phy_detach)(struct phy_device *phydev);
 
 	/** @stats: Statistic counters per device on the bus */
 	struct mdio_bus_stats stats[PHY_MAX_ADDR];

3/2 2026/07/05 16:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12996
							output: 25
							thoughts: 415
						

Thoughts:
**Reaching for Testability in the PHY Subsystem**

Okay, so I'm looking at this patch series. It seems like it's touching several areas: the Realtek MDIO controller driver, device tree bindings, and then some core PHY subsystem files (`phy_device.c` and `phy.h`). Specifically, they're adding these `notify_phy_attach` and `notify_phy_detach` callbacks to the `mii_bus` struct. My initial thought is, "Can we actually *test* this effectively? Is it even *worth* fuzzing?"

The changes to `phy_device.c` – adding those callbacks and using them in `phy_attach_direct` and `phy_detach` – that’s core PHY stuff. Definitely reachable. I'm less sure about the Realtek MDIO driver, `mdio-realtek-rtl9300.c`. That's hardware-specific. I'm guessing it's probably *not* going to be reachable in many virtualized environments unless there's an emulator or we're using device tree overlays somehow. But the *core* PHY subsystem changes? Those are fair game.

Alright, let's look closer at `phy_attach_direct` and `phy_detach`. Ah, right, they're calling those new callbacks: `phydev->mdio.bus->notify_phy_attach(phydev)` and `phydev->mdio.bus->notify_phy_detach(phydev)`. Okay, that's a *functional* change to the core PHY subsystem for sure. It's a pretty clean change, so let's start fuzzing around that section.

Now, let's see if this is tied to any specific Kconfig options. Obviously, `drivers/net/phy/phy_device.c` requires `CONFIG_PHYLIB`. I wonder about `mdio-realtek-rtl9300.c`. Is there some conditional compilation? I'd better double-check the Kconfig file for that driver.



4/2 2026/07/05 16:54 tool grepper
0m Args:
{
  "Expression": "mdio-realtek-rtl9300"
}

Results:
{
  "Output": "drivers/net/mdio/Makefile=23=obj-$(CONFIG_MDIO_PIC64HPSC)\t\t+= mdio-pic64hpsc.o\ndrivers/net/mdio/Makefile:24:obj-$(CONFIG_MDIO_REALTEK_RTL9300)\t+= mdio-realtek-rtl9300.o\ndrivers/net/mdio/Makefile-25-obj-$(CONFIG_MDIO_REGMAP)\t\t+= mdio-regmap.o\n"
}

5/2 2026/07/05 16:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14008
							output: 28
							thoughts: 33
						

Thoughts:
Okay, here's my summary, taking on the persona you've described:

**Checking the Kconfig for RTL9300 MDIO Support**

Alright, let's dive in. I need to understand the configuration options for this Realtek RTL9300 MDIO driver. My immediate thought is to check the `Kconfig` file, specifically within the `drivers/net/mdio/` directory. I know that's where the network MDIO driver configurations are typically housed. I'm looking for the symbol `CONFIG_MDIO_REALTEK_RTL9300`. That's the key. Knowing that symbol's presence and its dependencies will be crucial for understanding how this specific MDIO driver is enabled or disabled and what options are available when it comes to the build. I expect the file will give me details around the specific hardware support and any relevant default configurations. From there, I can figure out if it is enabled.



6/2 2026/07/05 16:54 tool grepper
0m Args:
{
  "Expression": "config MDIO_REALTEK_RTL9300"
}

Results:
{
  "Output": "drivers/net/mdio/Kconfig=164=config MDIO_IPQ8064\n--\ndrivers/net/mdio/Kconfig-171-\ndrivers/net/mdio/Kconfig:172:config MDIO_REALTEK_RTL9300\ndrivers/net/mdio/Kconfig-173-\ttristate \"Realtek RTL9300 MDIO interface support\"\n"
}

7/2 2026/07/05 16:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14387
							output: 47
							thoughts: 887
						

Thoughts:
**Fuzzing Feasibility Analysis: PHY Core Patch**

Okay, let's break this down. We're looking at a patch in `drivers/net/phy/phy_device.c`, which is right in the core PHY code. My initial thought is, is this worth the fuzzing effort? It's adding two new callbacks, `notify_phy_attach` and `notify_phy_detach`, to the `mii_bus` struct. At first glance, it seems the core change is simple: a non-NULL check and function call.

The primary user of these new callbacks appears to be `mdio-realtek-rtl9300.c`. Now, because this is a hardware-specific driver, my intuition says it's unlikely to be triggered in a general virtualized fuzzing setup. But, we all know intuition has to be challenged.

The core PHY code is *definitely* reachable. But, if `mdio-realtek-rtl9300.c` isn't used, the callbacks will be `NULL`, and the code won't execute, making that part of the patch irrelevant. The important bit to remember here is that a syzkaller style fuzzer can fake device trees or perform platform device injections.

Let's see: `mdio-realtek-rtl9300.c` is a platform driver. Syzkaller can probe platform drivers via `syz_devmem` or similar functionality, or inject device tree nodes. It's *possible* we can get this driver probed. If that happens, the `bus->notify_phy_attach` pointer will be set to `otto_emdio_notify_phy_attach`.

So, what does `otto_emdio_notify_phy_attach` *do*? It's interacting with hardware, specifically via `regmap`. This is where it gets interesting, as it could just fail at probe if the hardware isn't present, or, because it uses `regmap-mmio`, it might read/write to memory, which can be exploitable.

To proceed, let me examine how `regmap` is initialized within `otto_emdio_probe`. This should give me a better indication of how likely the hardware interactions are to cause issues in a fuzzed environment. We need to follow where the memory accesses are going.



8/2 2026/07/05 16:54 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "devm_regmap_init",
  "PathPrefix": "drivers/net/mdio/mdio-realtek-rtl9300.c"
}

Results:
{
  "error": "no matches"
}

9/2 2026/07/05 16:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15840
							output: 46
							thoughts: 35
						

Thoughts:
Here's my take on those thoughts:

**Analyzing the RTL9300 MDIO Driver**

Okay, time to dive into this. I need to take a look at `otto_emdio_probe`. Specifically, I'm going to navigate to `drivers/net/mdio/mdio-realtek-rtl9300.c`.  I'm expecting to find the probe function for the Realtek RTL9300 MDIO driver there. I want to see how this particular driver initializes and interacts with the MDIO bus, and how it handles the specific hardware details of the RTL9300. This function is likely the crux of the driver's ability to discover and configure the attached PHYs, and I want to understand its implementation thoroughly. This is where the core device setup logic lives.



10/2 2026/07/05 16:55 tool grepper
0m Args:
{
  "Expression": "otto_emdio_probe",
  "PathPrefix": "drivers/net/mdio/mdio-realtek-rtl9300.c"
}

Results:
{
  "Output": "drivers/net/mdio/mdio-realtek-rtl9300.c=797=static void otto_emdio_notify_phy_detach(struct phy_device *phydev)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-816-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:817:static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-818-\t\t\t\t struct fwnode_handle *node)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c=965=static int otto_emdio_init_polling(struct otto_emdio_priv *priv)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-979-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:980:static int otto_emdio_probe(struct platform_device *pdev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-981-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-1019-\tdevice_for_each_child_node_scoped(dev, child) {\ndrivers/net/mdio/mdio-realtek-rtl9300.c:1020:\t\terr = otto_emdio_probe_one(dev, priv, child);\ndrivers/net/mdio/mdio-realtek-rtl9300.c-1021-\t\tif (err)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c=1131=static struct platform_driver otto_emdio_driver = {\ndrivers/net/mdio/mdio-realtek-rtl9300.c:1132:\t.probe = otto_emdio_probe,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-1133-\t.driver = {\n"
}

11/2 2026/07/05 16:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16653
							output: 48
							thoughts: 24
						

Thoughts:
Here's my take on the thought process, as requested:

**Deconstructing the Register Map Acquisition**

Okay, time to dig into `otto_emdio_probe`. The goal is to figure out exactly how we're getting that `priv->regmap` handle. Understanding how the register map is initialized and accessed is critical for debugging any hardware interaction, and this is a good place to start. I need to trace the initialization process to make sure the assumptions made are correct. Let's see how they set up this crucial piece of the driver infrastructure. Knowing this is important.



12/2 2026/07/05 16:55 tool read-file
0m Args:
{
  "File": "drivers/net/mdio/mdio-realtek-rtl9300.c",
  "FirstLine": 980,
  "LineCount": 50
}

Results:
{
  "Contents": " 980:\tstatic int otto_emdio_probe(struct platform_device *pdev)\n 981:\t{\n 982:\t\tstruct device *dev = \u0026pdev-\u003edev;\n 983:\t\tstruct otto_emdio_priv *priv;\n 984:\t\tint err;\n 985:\t\n 986:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n 987:\t\tif (!priv)\n 988:\t\t\treturn -ENOMEM;\n 989:\t\n 990:\t\terr = devm_mutex_init(dev, \u0026priv-\u003elock);\n 991:\t\tif (err)\n 992:\t\t\treturn err;\n 993:\t\n 994:\t\tpriv-\u003einfo = device_get_match_data(dev);\n 995:\t\tpriv-\u003eregmap = syscon_node_to_regmap(dev-\u003eparent-\u003eof_node);\n 996:\t\tif (IS_ERR(priv-\u003eregmap))\n 997:\t\t\treturn PTR_ERR(priv-\u003eregmap);\n 998:\t\n 999:\t\terr = otto_emdio_init_polling(priv);\n1000:\t\tif (err)\n1001:\t\t\treturn err;\n1002:\t\n1003:\t\tplatform_set_drvdata(pdev, priv);\n1004:\t\n1005:\t\terr = otto_emdio_map_ports(dev);\n1006:\t\tif (err)\n1007:\t\t\treturn err;\n1008:\t\n1009:\t\terr = otto_emdio_setup_topology(priv);\n1010:\t\tif (err)\n1011:\t\t\treturn err;\n1012:\t\n1013:\t\tif (priv-\u003einfo-\u003esetup_controller) {\n1014:\t\t\terr = priv-\u003einfo-\u003esetup_controller(priv);\n1015:\t\t\tif (err)\n1016:\t\t\t\treturn dev_err_probe(dev, err, \"failed to setup MDIO bus controller\\n\");\n1017:\t\t}\n1018:\t\n1019:\t\tdevice_for_each_child_node_scoped(dev, child) {\n1020:\t\t\terr = otto_emdio_probe_one(dev, priv, child);\n1021:\t\t\tif (err)\n1022:\t\t\t\treturn err;\n1023:\t\t}\n1024:\t\n1025:\t\treturn 0;\n1026:\t}\n1027:\t\n1028:\tstatic const struct otto_emdio_info otto_emdio_8380_info = {\n1029:\t\t.addr_map_base = RTL8380_SMI_PORT0_5_ADDR_CTRL,\n"
}

13/2 2026/07/05 16:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17577
							output: 42
							thoughts: 452
						

Thoughts:
**Fuzzing Potential: A Quick Assessment**

Okay, so I'm looking at this patch and my initial thought is: it uses `syscon_node_to_regmap`, which means it *expects* the parent node to be a syscon. That suggests a specific hardware driver, which is a good starting point for fuzzing. But is it *worth* the effort?

The patch adds `notify_phy_attach` and `notify_phy_detach` to the `struct mii_bus` and calls them in `phy_attach_direct` and `phy_detach`. Those are core PHY subsystem functions. My first impression is that even if the specific driver isn't immediately reachable, the core changes are, which increases the likelihood of finding something interesting.

Hang on... the core changes are *just* this function pointer call:

```c
	if (phydev->mdio.bus->notify_phy_attach) {
		err = phydev->mdio.bus->notify_phy_attach(phydev);
		if (err)
			goto error;
	}
```

That's a very simple function pointer. If no other driver actually uses it, this is essentially a NULL check. Is it even worth fuzzing just for a NULL check? Probably not a good use of resources.

Ah, but wait! The patch *also* significantly modifies `mdio-realtek-rtl9300.c`. It adds polling support to the Realtek MDIO driver! *That* sounds much more promising. Can *this* driver be fuzzed?

Syzkaller can inject device trees, and that's the key. If it can inject a syscon parent (which is easy enough, since `syscon_node_to_regmap` just needs a compatible string) and *this* device, it can probe it. The `syscon_node_to_regmap` call is satisfied if the parent node has a compatible string that matches a syscon driver.

So is it worth fuzzing? Yes, absolutely. It's a driver, and Syzkaller excels at fuzzing drivers.

Now, let's see if `CONFIG_MDIO_REALTEK_RTL9300` is enabled or at least can be enabled. It's tristate. Great, it *can* be enabled. That's fantastic. Let me check the configuration to see if there are any specific configs that I need to be concerned with. Just `CONFIG_MDIO_REALTEK_RTL9300`. It seems straightforward. Let me make sure I'm not missing any other dependencies.



14/2 2026/07/05 16:55 tool grepper
0m Args:
{
  "Expression": "MDIO_REALTEK_RTL9300",
  "PathPrefix": "drivers/net/mdio/Kconfig"
}

Results:
{
  "Output": "drivers/net/mdio/Kconfig=164=config MDIO_IPQ8064\n--\ndrivers/net/mdio/Kconfig-171-\ndrivers/net/mdio/Kconfig:172:config MDIO_REALTEK_RTL9300\ndrivers/net/mdio/Kconfig-173-\ttristate \"Realtek RTL9300 MDIO interface support\"\n"
}

15/2 2026/07/05 16:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18755
							output: 37
							thoughts: 22
						

Thoughts:
Here's my thought process, summarized as you requested:

**Deconstructing `MDIO_REALTEK_RTL9300` Dependencies**

Okay, so I'm looking at `MDIO_REALTEK_RTL9300`. My immediate thought is, let's break down the dependencies of this module. As an expert in embedded systems architecture, I need to know *everything* it relies on to function correctly. This is fundamental for debugging, porting, or even just understanding its place within the larger system. I'm thinking about the layers... does it require specific hardware abstraction layers? Kernel drivers? Possibly low-level register definitions? And what about potential user-space components or configuration files? I need to get a comprehensive map of all the building blocks that make up this module so that it can be managed.



16/2 2026/07/05 16:55 tool read-file
0m Args:
{
  "File": "drivers/net/mdio/Kconfig",
  "FirstLine": 172,
  "LineCount": 10
}

Results:
{
  "Contents": " 172:\tconfig MDIO_REALTEK_RTL9300\n 173:\t\ttristate \"Realtek RTL9300 MDIO interface support\"\n 174:\t\tdepends on MACH_REALTEK_RTL || COMPILE_TEST\n 175:\t\thelp\n 176:\t\t  This driver supports the MDIO interface found in the Realtek\n 177:\t\t  RTL9300 family of Ethernet switches with integrated SoC.\n 178:\t\n 179:\tconfig MDIO_REGMAP\n 180:\t\ttristate\n 181:\t\thelp\n"
}

17/2 2026/07/05 16:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19160
							output: 50
							thoughts: 1766
						

Thoughts:
**Fuzzing Strategy for Realtek RTL9300 MDIO Hardware Polling**

Okay, so I'm looking at this patch and thinking about fuzzing the new hardware polling support for the Realtek RTL9300 MDIO controller.  It looks like this can be enabled with `COMPILE_TEST`, which is often already enabled in Syzkaller, but I'll double-check that.  The primary config is `MDIO_REALTEK_RTL9300` and I'll make sure that gets explicitly set. I'm focusing on that.

I've got the driver using regmap for MMIO, and Syzkaller can handle that through a mock or memory mapping, so that's good. `syscon_node_to_regmap` is usually how it maps to MMIO, so device tree injection should work. The core changes are minor, mostly adding those `notify_phy_attach` and `notify_phy_detach` callbacks to the `mii_bus` struct. The bulk of the logic is in the driver itself, in `mdio-realtek-rtl9300.c`.

The key functions to concentrate on are `otto_emdio_notify_phy_attach`, `otto_emdio_notify_phy_detach`, `otto_emdio_init_polling`, `otto_emdio_set_port_polling`, and all of the `otto_emdio_*_read_c##` and `otto_emdio_*_write_c##` functions (for both 8380 and 8390). I initially considered `phy_attach_direct` and `phy_detach` since they are core functions and are on the hot path, but they are very generic and would pull in a lot of other PHY drivers into the mix. This is not what I want. They are also control path. So, I will focus on the `otto_emdio_*` functions as they target the new code directly.

Specifically, `otto_emdio_notify_phy_attach` is reachable. The driver registers the MDIO bus during probe, and then PHY devices can be attached, which calls `phy_attach_direct`, which then calls `notify_phy_attach`. So, that's definitely a viable entry point. The same goes for the other functions:
-   `otto_emdio_notify_phy_attach`
-   `otto_emdio_notify_phy_detach`
-   `otto_emdio_init_polling`
-   `otto_emdio_set_port_polling`
-   `otto_emdio_8380_setup_controller`
-   `otto_emdio_8380_read_c22`
-   `otto_emdio_8380_write_c22`
-   `otto_emdio_8380_read_c45`
-   `otto_emdio_8380_write_c45`
-   `otto_emdio_8390_read_c22`
-   `otto_emdio_8390_write_c22`
-   `otto_emdio_8390_read_c45`
-   `otto_emdio_8390_write_c45`

This is a new feature in MDIO driver and core PHY subsystem, so fuzzing these is a good idea.  The primary config needed is `MDIO_REALTEK_RTL9300` and it depends on `MACH_REALTEK_RTL || COMPILE_TEST`.

I think that's the core of it. I've narrowed down the target, understand the reachability and setup, and have a good understanding of what config options to prioritize.  Let's get to fuzzing!



18/2 2026/07/05 16:55 tool grepper
0m Args:
{
  "Expression": "^static.*otto_emdio_.*\\(",
  "PathPrefix": "drivers/net/mdio/mdio-realtek-rtl9300.c"
}

Results:
{
  "Output": "drivers/net/mdio/mdio-realtek-rtl9300.c=271=struct otto_emdio_chan {\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-275-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:276:static int otto_emdio_phy_to_port(struct mii_bus *bus, int phy_id)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-277-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-291-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:292:static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-293-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-298-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:299:static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, bool active)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-300-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-306-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:307:static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-308-\t\t\t      struct otto_emdio_cmd_regs *cmd_data)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-363-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:364:static int otto_emdio_read_cmd(struct mii_bus *bus, u32 cmd,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-365-\t\t\t       struct otto_emdio_cmd_regs *cmd_data, u32 mask, u32 *value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-383-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:384:static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-385-\t\t\t\tstruct otto_emdio_cmd_regs *cmd_data)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-393-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:394:static int otto_emdio_8380_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-395-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-407-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:408:static int otto_emdio_8380_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-409-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-421-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:422:static int otto_emdio_8380_read_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-423-\t\t\t\t    int dev_addr, int regnum, u32 *value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-434-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:435:static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-436-\t\t\t\t     int dev_addr, int regnum, u16 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-447-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:448:static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-449-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-461-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:462:static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-463-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-476-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:477:static int otto_emdio_8390_read_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-478-\t\t\t\t    int dev_addr, int regnum, u32 *value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-489-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:490:static int otto_emdio_8390_write_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-491-\t\t\t\t     int dev_addr, int regnum, u16 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-503-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:504:static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-505-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-517-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:518:static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-519-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-531-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:532:static int otto_emdio_9300_read_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-533-\t\t\t\t    int dev_addr, int regnum, u32 *value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-544-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:545:static int otto_emdio_9300_write_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-546-\t\t\t\t     int dev_addr, int regnum, u16 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-557-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:558:static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-559-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-570-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:571:static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-572-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-584-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:585:static int otto_emdio_9310_read_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-586-\t\t\t\t    int dev_addr, int regnum, u32 *value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-597-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:598:static int otto_emdio_9310_write_c45(struct mii_bus *bus, int port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-599-\t\t\t\t     int dev_addr, int regnum, u16 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-611-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:612:static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-613-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-631-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:632:static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-633-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-655-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:656:static int otto_emdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-657-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-671-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:672:static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-673-\t\t\t\tint dev_addr, int regnum, u16 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-687-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:688:static int otto_emdio_write_mapping(struct otto_emdio_priv *priv, u32 base, u32 port,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-689-\t\t\t\t    u32 vals_per_reg, u32 bits_per_val, u32 value)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-697-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:698:static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-699-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-723-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:724:static int otto_emdio_8380_setup_controller(struct otto_emdio_priv *priv)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-725-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-732-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:733:static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-734-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-752-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:753:static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-754-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-768-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:769:static int otto_emdio_notify_phy_attach(struct phy_device *phydev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-770-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-796-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:797:static void otto_emdio_notify_phy_detach(struct phy_device *phydev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-798-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-816-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:817:static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,\ndrivers/net/mdio/mdio-realtek-rtl9300.c-818-\t\t\t\t struct fwnode_handle *node)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-861-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:862:static struct device_node *otto_emdio_get_bus_node(struct device_node *dn)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-863-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-880- */\ndrivers/net/mdio/mdio-realtek-rtl9300.c:881:static int otto_emdio_map_ports(struct device *dev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-882-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-964-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:965:static int otto_emdio_init_polling(struct otto_emdio_priv *priv)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-966-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-979-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:980:static int otto_emdio_probe(struct platform_device *pdev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-981-{\n"
}

19/2 2026/07/05 16:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26250
							output: 256
							thoughts: 504
						

Thoughts:
**Analyzing the Patch: Functions Added and Modified**

Alright, let's break this down systematically. My immediate thought is to identify the scope of this patch by explicitly listing the changes. I'm going to start by categorizing the functions.

I see a list of newly *added* functions: `otto_emdio_set_port_polling`, `otto_emdio_8380_read_c22`, `otto_emdio_8380_write_c22`, `otto_emdio_8380_read_c45`, `otto_emdio_8380_write_c45`, `otto_emdio_8390_read_c22`, `otto_emdio_8390_write_c22`, `otto_emdio_8390_read_c45`, `otto_emdio_8390_write_c45`, `otto_emdio_8380_setup_controller`, `otto_emdio_notify_phy_attach`, `otto_emdio_notify_phy_detach`, `otto_emdio_init_polling`.

Then, I'm noting the functions that have been *modified*: `otto_emdio_9300_read_c22`, `otto_emdio_9300_write_c22`, `otto_emdio_9310_read_c22`, `otto_emdio_9310_write_c22`, `otto_emdio_read_c22`, `otto_emdio_write_c22`, `otto_emdio_probe_one`, `otto_emdio_probe`, `phy_attach_direct`, `phy_detach`.

The significant additions seem to be around polling, specific PHY initialization, and attach/detach notifications. Looking at the changes, I'll need to focus on those new functions, including `otto_emdio_notify_phy_attach`, `otto_emdio_notify_phy_detach`, `otto_emdio_init_polling`, `otto_emdio_set_port_polling`. Also, the functions specific to handling the 8380 and 8390 PHYs (`otto_emdio_8380_read_c22`, `otto_emdio_8380_write_c22`, `otto_emdio_8390_read_c22`, `otto_emdio_8390_write_c22`, `otto_emdio_8380_setup_controller`) will warrant closer inspection.

It appears the new functions are quite important. I know what to focus on. Let's dig in.



20/2 2026/07/05 16:55 tool set-results
0m Args:
{
  "EnableConfigs": [
    "MDIO_REALTEK_RTL9300"
  ],
  "FocusSymbols": [
    "otto_emdio_notify_phy_attach",
    "otto_emdio_notify_phy_detach",
    "otto_emdio_init_polling",
    "otto_emdio_set_port_polling",
    "otto_emdio_8380_read_c22",
    "otto_emdio_8380_write_c22",
    "otto_emdio_8390_read_c22",
    "otto_emdio_8390_write_c22",
    "otto_emdio_8380_setup_controller"
  ],
  "Reasoning": "The patch adds hardware polling support to the Realtek RTL9300 MDIO controller driver and introduces `notify_phy_attach` and `notify_phy_detach` callbacks to the core PHY subsystem. The core changes are reachable during PHY device attachment/detachment, and the driver changes can be reached if the driver is probed via device tree injection.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "MDIO_REALTEK_RTL9300"
  ],
  "FocusSymbols": [
    "otto_emdio_notify_phy_attach",
    "otto_emdio_notify_phy_detach",
    "otto_emdio_init_polling",
    "otto_emdio_set_port_polling",
    "otto_emdio_8380_read_c22",
    "otto_emdio_8380_write_c22",
    "otto_emdio_8390_read_c22",
    "otto_emdio_8390_write_c22",
    "otto_emdio_8380_setup_controller"
  ],
  "Reasoning": "The patch adds hardware polling support to the Realtek RTL9300 MDIO controller driver and introduces `notify_phy_attach` and `notify_phy_detach` callbacks to the core PHY subsystem. The core changes are reachable during PHY device attachment/detachment, and the driver changes can be reached if the driver is probed via device tree injection.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)