| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/23 00:50 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces support for Realtek RTL8380 and RTL8390 MDIO controllers in the mdio-realtek-rtl9300 platform driver, and adds notify_phy_attach/notify_phy_detach callbacks to the core PHY subsystem which are only used by this specific driver. Since this is a platform driver for specific Realtek SoCs, it cannot be probed in a generic virtualized environment (like QEMU or GCE) without specific hardware or device tree support. The core PHY subsystem changes are effectively unreachable because no reachable driver sets these callbacks. Thus, the modified code cannot be executed during fuzzing.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/23 00:50 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 110b22f845d8fad94a06e59c37f2ce08f648ac85\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Jul 23 00:50:20 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 afd52a1cd7f831..aebc76ae5e560c 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@@ -50,6 +118,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@@ -70,6 +182,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@@ -95,11 +208,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@@ -125,7 +240,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@@ -142,6 +259,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@@ -177,6 +295,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@@ -185,9 +311,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@@ -227,7 +353,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@@ -264,13 +390,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@@ -284,7 +520,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@@ -324,7 +560,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@@ -336,7 +572,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@@ -382,8 +618,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@@ -397,8 +637,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@@ -471,6 +720,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@@ -507,6 +765,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 (!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\tif (test_bit(port, priv-\u003ephy_poll))\n+\t\t\treturn 0;\n+\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+\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@@ -536,6 +842,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@@ -652,6 +961,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@@ -671,6 +995,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@@ -696,6 +1024,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@@ -711,6 +1084,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@@ -736,6 +1110,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@@ -744,6 +1119,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 94b2e85e00a379..a62c2ff6c2aabb 100644\n--- a/drivers/net/phy/phy_device.c\n+++ b/drivers/net/phy/phy_device.c\n@@ -1876,6 +1876,13 @@ 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\tphydev-\u003emdio_bus_notified = true;\n+\t}\n+\n \tphy_resume(phydev);\n \n \t/**\n@@ -1919,6 +1926,10 @@ void phy_detach(struct phy_device *phydev)\n \tstruct module *ndev_owner = NULL;\n \tstruct mii_bus *bus;\n \n+\tif (phydev-\u003emdio_bus_notified \u0026\u0026 phydev-\u003emdio.bus-\u003enotify_phy_detach)\n+\t\tphydev-\u003emdio.bus-\u003enotify_phy_detach(phydev);\n+\tphydev-\u003emdio_bus_notified = false;\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 11092c3175b3de..2d30e087a23373 100644\n--- a/include/linux/phy.h\n+++ b/include/linux/phy.h\n@@ -376,6 +376,23 @@ 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/**\n+\t * @notify_phy_attach: Perform post-attach handling for MDIO bus\n+\t * drivers. Called in phy_attach_direct() right after phy_init_hw()\n+\t * and before phy_resume(). At this point, the PHY is still\n+\t * suspended, and @phydev-\u003eattached_dev may be NULL (e.g. for\n+\t * standalone PHYs). Runs in process context and may sleep. Return\n+\t * 0 on success or a negative errno on failure (which aborts the\n+\t * attach sequence).\n+\t */\n+\tint (*notify_phy_attach)(struct phy_device *phydev);\n+\t/**\n+\t * @notify_phy_detach: Perform pre-detach handling for MDIO bus\n+\t * drivers. Called in phy_detach() before device links and resources\n+\t * are cleaned up. Runs in process context and may sleep. Only\n+\t * invoked if @notify_phy_attach was previously called and succeeded.\n+\t */\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@@ -574,6 +591,7 @@ struct phy_oatc14_sqi_capability {\n * @has_fixups: Set to true if this PHY has fixups/quirks.\n * @suspended: Set to true if this PHY has been suspended successfully.\n * @suspended_by_mdio_bus: Set to true if this PHY was suspended by MDIO bus.\n+ * @mdio_bus_notified: Set to true if post-attach bus notificaton succeeded.\n * @sysfs_links: Internal boolean tracking sysfs symbolic links setup/removal.\n * @loopback_enabled: Set true if this PHY has been loopbacked successfully.\n * @downshifted_rate: Set true if link speed has been downshifted.\n@@ -685,6 +703,7 @@ struct phy_device {\n \tunsigned has_fixups:1;\n \tunsigned suspended:1;\n \tunsigned suspended_by_mdio_bus:1;\n+\tunsigned mdio_bus_notified:1;\n \tunsigned sysfs_links:1;\n \tunsigned loopback_enabled:1;\n \tunsigned downshifted_rate:1;\n"
} |
| 2/1 | 2026/07/23 00:50 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces support for Realtek RTL8380 and RTL8390 MDIO controllers in the mdio-realtek-rtl9300 platform driver, and adds notify_phy_attach/notify_phy_detach callbacks to the core PHY subsystem which are only used by this specific driver. Since this is a platform driver for specific Realtek SoCs, it cannot be probed in a generic virtualized environment (like QEMU or GCE) without specific hardware or device tree support. The core PHY subsystem changes are effectively unreachable because no reachable driver sets these callbacks. Thus, the modified code cannot be executed during fuzzing.",
"WorthFuzzing": false
}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 110b22f845d8fad94a06e59c37f2ce08f648ac85
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Jul 23 00:50:20 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 afd52a1cd7f831..aebc76ae5e560c 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>
@@ -50,6 +118,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
@@ -70,6 +182,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
@@ -95,11 +208,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
@@ -125,7 +240,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];
@@ -142,6 +259,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);
@@ -177,6 +295,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)
{
@@ -185,9 +311,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;
@@ -227,7 +353,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;
@@ -264,13 +390,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),
};
@@ -284,7 +520,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),
};
@@ -324,7 +560,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,
@@ -336,7 +572,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)),
@@ -382,8 +618,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;
}
@@ -397,8 +637,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;
}
@@ -471,6 +720,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;
@@ -507,6 +765,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 (!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) {
+ if (test_bit(port, priv->phy_poll))
+ return 0;
+
+ 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)
{
@@ -536,6 +842,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;
@@ -652,6 +961,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;
@@ -671,6 +995,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);
@@ -696,6 +1024,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,
@@ -711,6 +1084,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,
@@ -736,6 +1110,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,
@@ -744,6 +1119,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 94b2e85e00a379..a62c2ff6c2aabb 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1876,6 +1876,13 @@ 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;
+ phydev->mdio_bus_notified = true;
+ }
+
phy_resume(phydev);
/**
@@ -1919,6 +1926,10 @@ void phy_detach(struct phy_device *phydev)
struct module *ndev_owner = NULL;
struct mii_bus *bus;
+ if (phydev->mdio_bus_notified && phydev->mdio.bus->notify_phy_detach)
+ phydev->mdio.bus->notify_phy_detach(phydev);
+ phydev->mdio_bus_notified = false;
+
if (phydev->devlink) {
device_link_del(phydev->devlink);
phydev->devlink = NULL;
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 11092c3175b3de..2d30e087a23373 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -376,6 +376,23 @@ 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 for MDIO bus
+ * drivers. Called in phy_attach_direct() right after phy_init_hw()
+ * and before phy_resume(). At this point, the PHY is still
+ * suspended, and @phydev->attached_dev may be NULL (e.g. for
+ * standalone PHYs). Runs in process context and may sleep. Return
+ * 0 on success or a negative errno on failure (which aborts the
+ * attach sequence).
+ */
+ int (*notify_phy_attach)(struct phy_device *phydev);
+ /**
+ * @notify_phy_detach: Perform pre-detach handling for MDIO bus
+ * drivers. Called in phy_detach() before device links and resources
+ * are cleaned up. Runs in process context and may sleep. Only
+ * invoked if @notify_phy_attach was previously called and succeeded.
+ */
+ void (*notify_phy_detach)(struct phy_device *phydev);
/** @stats: Statistic counters per device on the bus */
struct mdio_bus_stats stats[PHY_MAX_ADDR];
@@ -574,6 +591,7 @@ struct phy_oatc14_sqi_capability {
* @has_fixups: Set to true if this PHY has fixups/quirks.
* @suspended: Set to true if this PHY has been suspended successfully.
* @suspended_by_mdio_bus: Set to true if this PHY was suspended by MDIO bus.
+ * @mdio_bus_notified: Set to true if post-attach bus notificaton succeeded.
* @sysfs_links: Internal boolean tracking sysfs symbolic links setup/removal.
* @loopback_enabled: Set true if this PHY has been loopbacked successfully.
* @downshifted_rate: Set true if link speed has been downshifted.
@@ -685,6 +703,7 @@ struct phy_device {
unsigned has_fixups:1;
unsigned suspended:1;
unsigned suspended_by_mdio_bus:1;
+ unsigned mdio_bus_notified:1;
unsigned sysfs_links:1;
unsigned loopback_enabled:1;
unsigned downshifted_rate:1;
|
| 3/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13571 output: 42 thoughts: 1016 Thoughts: **Reaching the Realtek RTL8380/RTL8390 MDIO Controller Code** Okay, so we have a patch that's touching `Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml`, `drivers/net/mdio/mdio-realtek-rtl9300.c`, `drivers/net/phy/phy_device.c`, and `include/linux/phy.h`. This is adding support for the Realtek RTL8380 and RTL8390 MDIO controllers, which is fine, but I need to understand if this code is actually *reachable* in a typical test environment, like QEMU or GCE. It's adding `notify_phy_attach` and `notify_phy_detach` callbacks to the `mii_bus` struct and calling them from `phy_attach_direct` and `phy_detach`. These callbacks are specific to the `mdio-realtek-rtl9300.c` driver, enabling/disabling hardware polling. My immediate thought is, can I even get the `mdio-realtek-rtl9300.c` driver to probe? It's a platform driver, so it needs a platform device instance. These usually come from Device Tree (DT) overlays, but I know syzkaller doesn't generally load DT overlays unless specifically configured for it, and I haven't configured for it. Syzkaller usually relies on a few techniques for device creation. Does it have a special syscall like `syz_dev_create`, `syz_platform_device_add` or similar? No, I don't think it does, or at least not in a generic way. I am pretty sure it uses `syz_genetlink_get_family` or `syz_init_net_socket` for creating platform devices, if a specific subsystem allows it. Ah, but wait... `phy_attach_direct` and `phy_detach` are core PHY functions. *Those* are always reachable if *any* PHY is attached or detached. The patch is modifying those to call the new callbacks, but only if they're set. So, the new code path within `phy_attach_direct` or `phy_detach` *is* reachable if *any* PHY is attached, but only does something if the `notify_phy_attach` or `notify_phy_detach` callbacks are set. Are there any other drivers setting those callbacks? No, the patch only sets them in the `mdio-realtek-rtl9300.c` driver. So the real question is, can the probe for `mdio-realtek-rtl9300.c` be triggered? The driver matches `"realtek,rtl8380-mdio"`, `"realtek,rtl8391-mdio"`, `"realtek,rtl9301-mdio"`, and `"realtek,rtl9311-mdio"`. Unless we are running on a Realtek SoC or manage to create the platform device, its probe function won't be called. Syzkaller doesn't generally inject arbitrary DT nodes, so it's unlikely it's generating the DT node for this controller. Can I create the platform device from userspace via sysfs? No, that's not possible. I know there's `platform_device_register`, but that's kernel-internal. Therefore, since the driver is a platform driver for specific SoCs, and it is highly unlikely to probe in a generic QEMU/GCE environment, I don't think I can trigger the new code. I should check the Kconfig, though, just to be thorough. I'll look at `drivers/net/mdio/Kconfig` for `MDIO_REALTEK_RTL9300` to be absolutely certain it's enabled. |
| 4/2 | 2026/07/23 00:50 | tool | grepper |
0mArgs:{
"Expression": "MDIO_REALTEK_RTL9300",
"PathPrefix": "drivers/net/mdio/Kconfig"
}Results: {
"Output": "drivers/net/mdio/Kconfig=166=config MDIO_IPQ8064\n--\ndrivers/net/mdio/Kconfig-173-\ndrivers/net/mdio/Kconfig:174:config MDIO_REALTEK_RTL9300\ndrivers/net/mdio/Kconfig-175-\ttristate \"Realtek RTL9300 MDIO interface support\"\n"
} |
| 5/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15601 output: 43 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. |
| 6/2 | 2026/07/23 00:50 | tool | grepper |
0mArgs:{
"Expression": "config MDIO_REALTEK_RTL9300",
"PathPrefix": "drivers/net/mdio/Kconfig"
}Results: {
"Output": "drivers/net/mdio/Kconfig=166=config MDIO_IPQ8064\n--\ndrivers/net/mdio/Kconfig-173-\ndrivers/net/mdio/Kconfig:174:config MDIO_REALTEK_RTL9300\ndrivers/net/mdio/Kconfig-175-\ttristate \"Realtek RTL9300 MDIO interface support\"\n"
} |
| 7/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15959 output: 37 thoughts: 22 Thoughts: Here's my take on the thought process, summarized as you requested: **Navigating the Kconfig Landscape** Alright, let's dive in. I need to understand some configuration options, and I have a strong hunch that the `drivers/net/mdio/Kconfig` file is where to start. Specifically, I'm thinking around line 174. That's a reasonable area for me to begin my exploration. I'm expecting to find some crucial definitions or dependencies related to the MDIO driver configuration there. This should give me the base information I need to proceed. I have a feeling the information I am searching for will be around that area. |
| 8/2 | 2026/07/23 00:50 | tool | read-file |
0mArgs:{
"File": "drivers/net/mdio/Kconfig",
"FirstLine": 170,
"LineCount": 20
}Results: {
"Contents": " 170:\t\thelp\n 171:\t\t This driver supports the MDIO interface found in the network\n 172:\t\t interface units of the IPQ8064 SoC\n 173:\t\n 174:\tconfig MDIO_REALTEK_RTL9300\n 175:\t\ttristate \"Realtek RTL9300 MDIO interface support\"\n 176:\t\tdepends on MACH_REALTEK_RTL || COMPILE_TEST\n 177:\t\thelp\n 178:\t\t This driver supports the MDIO interface found in the Realtek\n 179:\t\t RTL9300 family of Ethernet switches with integrated SoC.\n 180:\t\n 181:\tconfig MDIO_REGMAP\n 182:\t\ttristate\n 183:\t\thelp\n 184:\t\t This driver allows using MDIO devices that are not sitting on a\n 185:\t\t regular MDIO bus, but still exposes the standard 802.3 register\n 186:\t\t layout. It's regmap-based so that it can be used on integrated,\n 187:\t\t memory-mapped PHYs, SPI PHYs and so on. A new virtual MDIO bus is\n 188:\t\t created, and its read/write operations are mapped to the underlying\n 189:\t\t regmap. Users willing to use this driver must explicitly select\n"
} |
| 9/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16521 output: 45 thoughts: 78 Thoughts: **Analyzing the Realtek RTL9300 Driver's Activation** Okay, so the Realtek RTL9300 driver's activation hinges on either `MACH_REALTEK_RTL` or, more commonly, `COMPILE_TEST`. Even when we force the inclusion via `COMPILE_TEST`, it's still a *platform* driver. That's a crucial distinction. The real question then becomes, can the driver be successfully probed in that scenario? My next step is clear: I need to scrutinize the source code, specifically `drivers/net/mdio/mdio-realtek-rtl9300.c`, to look for that all-important `module_platform_driver` call. This will confirm how the driver interacts with the platform and whether the probing mechanism is set up correctly, regardless of the `COMPILE_TEST` flag's presence. I need to understand if there is platform registration of this driver. |
| 10/2 | 2026/07/23 00:50 | tool | grepper |
0mArgs:{
"Expression": "module_platform_driver",
"PathPrefix": "drivers/net/mdio/mdio-realtek-rtl9300.c"
}Results: {
"Output": "drivers/net/mdio/mdio-realtek-rtl9300.c=1130=static struct platform_driver otto_emdio_driver = {\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-1137-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:1138:module_platform_driver(otto_emdio_driver);\ndrivers/net/mdio/mdio-realtek-rtl9300.c-1139-\n"
} |
| 11/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16993 output: 25 thoughts: 335 Thoughts: **Analyzing the Reachability of Driver Code in a Fuzzing Context**
Okay, so this is definitely a platform driver. The immediate concern is that in our virtualized environment, like QEMU x86_64 or arm64, unless we have a very specific board model configured to instantiate this particular device, or if syzkaller has a mechanism to inject platform devices on the fly, the driver's `probe` function is never going to be called. And as far as I know, syzkaller doesn't have a *generic* method to inject platform devices. We can inject USB devices with dummy_hcd and PCI devices through `syz_pci_*`, but not just any platform device, unless there's some special subsystem interface exposed, like with mac80211_hwsim or netdevsim.
So, realistically, the code within `mdio-realtek-rtl9300.c` is unreachable. That's the crux of it.
But what about the changes in `phy_device.c`? Those are the next thing I need to investigate. I know that `phy_attach_direct` and `phy_detach` are reachable, but what about the new code that's been added?
```c
if (phydev->mdio.bus->notify_phy_attach) {
err = phydev->mdio.bus->notify_phy_attach(phydev);
if (err)
goto error;
phydev->mdio_bus_notified = true;
}
```
Looking at this, it looks like this code will only be executed if a `notify_phy_attach` function pointer is set on the `mdio.bus`. But, I'm pretty certain no currently reachable driver sets that pointer. So this new code is effectively dead, never executed in our fuzzing environment.
Hold on, I better double-check that. Is there any other driver that I'm missing that actually does set `notify_phy_attach`? I need to go through the code again just to be certain. Let me do a quick scan.
|
| 12/2 | 2026/07/23 00:50 | tool | grepper |
0mArgs:{
"Expression": "notify_phy_attach",
"PathPrefix": ""
}Results: {
"Output": "drivers/net/mdio/mdio-realtek-rtl9300.c=752=static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-767-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:768:static int otto_emdio_notify_phy_attach(struct phy_device *phydev)\ndrivers/net/mdio/mdio-realtek-rtl9300.c-769-{\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c=816=static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-844-\tbus-\u003eparent = dev;\ndrivers/net/mdio/mdio-realtek-rtl9300.c:845:\tbus-\u003enotify_phy_attach = otto_emdio_notify_phy_attach;\ndrivers/net/mdio/mdio-realtek-rtl9300.c-846-\tbus-\u003enotify_phy_detach = otto_emdio_notify_phy_detach;\n--\ndrivers/net/phy/phy_device.c=1752=int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,\n--\ndrivers/net/phy/phy_device.c-1878-\ndrivers/net/phy/phy_device.c:1879:\tif (phydev-\u003emdio.bus-\u003enotify_phy_attach) {\ndrivers/net/phy/phy_device.c:1880:\t\terr = phydev-\u003emdio.bus-\u003enotify_phy_attach(phydev);\ndrivers/net/phy/phy_device.c-1881-\t\tif (err)\n--\ninclude/linux/phy.h=363=struct mii_bus {\n--\ninclude/linux/phy.h-379-\t/**\ninclude/linux/phy.h:380:\t * @notify_phy_attach: Perform post-attach handling for MDIO bus\ninclude/linux/phy.h-381-\t * drivers. Called in phy_attach_direct() right after phy_init_hw()\n--\ninclude/linux/phy.h-387-\t */\ninclude/linux/phy.h:388:\tint (*notify_phy_attach)(struct phy_device *phydev);\ninclude/linux/phy.h-389-\t/**\n--\ninclude/linux/phy.h-392-\t * are cleaned up. Runs in process context and may sleep. Only\ninclude/linux/phy.h:393:\t * invoked if @notify_phy_attach was previously called and succeeded.\ninclude/linux/phy.h-394-\t */\n"
} |
| 13/2 | 2026/07/23 00:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18561 output: 164 thoughts: 325 Thoughts: **Unreachable Code in the Realtek MDIO Driver** Okay, so I'm looking at this patch and my initial thought is, "This looks like code that's not going to get hit by fuzzing." It appears that the only driver setting `notify_phy_attach` is `mdio-realtek-rtl9300.c`. Now, that's a platform driver – Realtek RTL8380 and RTL8390 MDIO controllers specifically. I know from experience that these types of platform drivers are incredibly difficult, if not impossible, to instantiate in a generic virtualized environment. Things like QEMU or Google Compute Engine just won't cut it. You really need the specific hardware or a perfectly crafted device tree. Let's do a quick sanity check to be absolutely certain. Can we somehow force this driver to probe? Since it's a platform driver, Syzkaller won't be able to just magically trigger it unless it's properly instantiated. I need to think this through. Can it be brought up via an I2C or SPI device? Nope. It's a platform device, so it's not going to attach to those buses. What about a PCI device? Still no. And definitely not USB. So yeah, I'm pretty confident this code is unreachable. Therefore, because this driver can't be reached in the fuzzing environment, `WorthFuzzing` should definitely be `false`. The core PHY subsystem changes introduced by the patch, including the added `notify_phy_attach` callbacks, are effectively dead code in a fuzzing context. |
| 14/2 | 2026/07/23 00:51 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch introduces support for Realtek RTL8380 and RTL8390 MDIO controllers in the mdio-realtek-rtl9300 platform driver, and adds notify_phy_attach/notify_phy_detach callbacks to the core PHY subsystem which are only used by this specific driver. Since this is a platform driver for specific Realtek SoCs, it cannot be probed in a generic virtualized environment (like QEMU or GCE) without specific hardware or device tree support. The core PHY subsystem changes are effectively unreachable because no reachable driver sets these callbacks. Thus, the modified code cannot be executed during fuzzing.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces support for Realtek RTL8380 and RTL8390 MDIO controllers in the mdio-realtek-rtl9300 platform driver, and adds notify_phy_attach/notify_phy_detach callbacks to the core PHY subsystem which are only used by this specific driver. Since this is a platform driver for specific Realtek SoCs, it cannot be probed in a generic virtualized environment (like QEMU or GCE) without specific hardware or device tree support. The core PHY subsystem changes are effectively unreachable because no reachable driver sets these callbacks. Thus, the modified code cannot be executed during fuzzing.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|