AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/12 13:09 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces a new MDIO MCU driver (mdio-airoha-en8811h) and refactors firmware loading in the Airoha EN8811H PHY driver and library (air_en8811h, air_phy_lib). These drivers target the physical Airoha EN8811H 2.5G Ethernet PHY ASIC and its MD32 microcontroller core via MDIO and Device Tree. The underlying hardware is not emulated in standard QEMU/GCE virtualized environments on amd64, rendering the modified code structurally unreachable for syzkaller fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/09/12 13:09 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 1fcc2c8d13d1e9f52a5e2410ef2faa89e0af7b4d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Sep 12 13:09:05 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\nnew file mode 100644\nindex 0000000000000..7c204b5f29c94\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\n@@ -0,0 +1,127 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/airoha,en8811h-mcu.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: Airoha EN8811H PHY MCU\n+\n+maintainers:\n+  - Aleksei Sviridkin \u003cf@lex.la\u003e\n+\n+description: |\n+  The Airoha EN8811H 2.5G PHY is built around an MD32 core that executes\n+  firmware from volatile RAM. The chip answers an MDIO address from\n+  power-on, but the PHY function does not exist until firmware has been\n+  loaded over MDIO, and it disappears again if that RAM is cleared.\n+\n+  This node describes the MD32 core. The PHY function answers on a child\n+  MDIO bus below it, at the same address the core occupies on the parent\n+  bus, and only while firmware is running.\n+\n+  The reset line is wired to the core, and asserting it clears the\n+  firmware RAM. It therefore belongs to this node: a reset described on\n+  the PHY node would be pulsed by that node's own lifecycle, whatever\n+  the core is doing.\n+\n+  A board that has firmware in hand while its MDIO bus is enumerated can\n+  describe the PHY on its own, as airoha,en8811h.yaml does, and that form\n+  stays valid. This binding is for the boards where firmware arrives\n+  later, so that there is no PHY to describe until the core has it.\n+\n+properties:\n+  $nodename:\n+    pattern: \"^mcu@[0-9a-f]+$\"\n+\n+  compatible:\n+    const: airoha,en8811h-mcu\n+\n+  reg:\n+    maxItems: 1\n+\n+  reset-gpios:\n+    maxItems: 1\n+    description:\n+      The GPIO phandle and specifier for the reset signal wired to the\n+      MD32 core.\n+\n+  reset-assert-us:\n+    description:\n+      Delay after the reset was asserted in microseconds. If this\n+      property is missing the delay will be skipped.\n+\n+  reset-deassert-us:\n+    description:\n+      Delay after the reset was deasserted in microseconds. If this\n+      property is missing the delay will be skipped.\n+\n+  mdio:\n+    $ref: mdio.yaml#\n+    description:\n+      The bus the PHY function answers on while firmware is running, at\n+      the same address this node occupies on the parent bus. Only that\n+      one address is passed through. The bus-level reset properties\n+      mdio.yaml allows are refused here, because the MDIO core asserts\n+      such a line when the bus registers, and that erases the firmware\n+      this PHY is made of.\n+\n+    properties:\n+      reset-gpios: false\n+      reset-delay-us: false\n+      reset-post-delay-us: false\n+\n+    patternProperties:\n+      '^ethernet-phy@[0-9a-f]+$':\n+        type: object\n+        $ref: airoha,en8811h.yaml#\n+        unevaluatedProperties: false\n+\n+        properties:\n+          reset-gpios: false\n+          reset-assert-us: false\n+          reset-deassert-us: false\n+          resets: false\n+          reset-names: false\n+\n+        description:\n+          The reset line belongs to the MCU node above. A reset described\n+          here would be pulsed by this node's own lifecycle, and that erases\n+          the firmware that created this PHY.\n+\n+    unevaluatedProperties: false\n+\n+required:\n+  - compatible\n+  - reg\n+  - mdio\n+\n+additionalProperties: false\n+\n+examples:\n+  - |\n+    #include \u003cdt-bindings/gpio/gpio.h\u003e\n+    #include \u003cdt-bindings/interrupt-controller/irq.h\u003e\n+\n+    mdio {\n+        #address-cells = \u003c1\u003e;\n+        #size-cells = \u003c0\u003e;\n+\n+        mcu@d {\n+            compatible = \"airoha,en8811h-mcu\";\n+            reg = \u003c0xd\u003e;\n+            reset-gpios = \u003c\u0026pio 14 GPIO_ACTIVE_LOW\u003e;\n+            reset-assert-us = \u003c10000\u003e;\n+            reset-deassert-us = \u003c20000\u003e;\n+\n+            mdio {\n+                #address-cells = \u003c1\u003e;\n+                #size-cells = \u003c0\u003e;\n+\n+                ethernet-phy@d {\n+                    compatible = \"ethernet-phy-id03a2.a411\";\n+                    reg = \u003c0xd\u003e;\n+                    interrupts-extended = \u003c\u0026pio 15 IRQ_TYPE_LEVEL_LOW\u003e;\n+                };\n+            };\n+        };\n+    };\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex b23fb6f2f4ef4..3bd86df2be571 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -750,6 +750,14 @@ S:\tSupported\n F:\tfs/aio.c\n F:\tinclude/linux/*aio*.h\n \n+AIROHA EN8811H MCU MDIO DRIVER\n+M:\tAleksei Sviridkin \u003cf@lex.la\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\n+F:\tdrivers/net/mdio/mdio-airoha-en8811h.c\n+F:\tinclude/linux/mdio/mdio-airoha-en8811h.h\n+\n AIROHA ETHERNET DRIVER\n M:\tLorenzo Bianconi \u003clorenzo@kernel.org\u003e\n L:\tlinux-arm-kernel@lists.infradead.org (moderated for non-subscribers)\ndiff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig\nindex d44278f26fabe..57c83bfd40905 100644\n--- a/drivers/net/mdio/Kconfig\n+++ b/drivers/net/mdio/Kconfig\n@@ -29,6 +29,19 @@ config MDIO_AIROHA\n \t  This module provides a driver for the MDIO busses found in the\n \t  Airoha AN7583 SoC's.\n \n+config MDIO_AIROHA_EN8811H\n+\ttristate \"Airoha EN8811H MDIO device support\"\n+\tdepends on OF_MDIO\n+\tdepends on FW_LOADER\n+\tselect AIR_NET_PHYLIB\n+\timply AIR_EN8811H_PHY\n+\thelp\n+\t  This module provides a driver for the Airoha EN8811H, which is an\n+\t  MD32 microcontroller until firmware is downloaded into it and only\n+\t  becomes an Ethernet PHY afterwards. The driver downloads that\n+\t  firmware once it becomes readable, or leaves in place firmware a\n+\t  bootloader already started.\n+\n config MDIO_SUN4I\n \ttristate \"Allwinner sun4i MDIO interface support\"\n \tdepends on ARCH_SUNXI || COMPILE_TEST\ndiff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile\nindex 0485867460262..06d096675daca 100644\n--- a/drivers/net/mdio/Makefile\n+++ b/drivers/net/mdio/Makefile\n@@ -6,6 +6,7 @@ obj-$(CONFIG_FWNODE_MDIO)\t+= fwnode_mdio.o\n obj-$(CONFIG_OF_MDIO)\t\t+= of_mdio.o\n \n obj-$(CONFIG_MDIO_AIROHA)\t\t+= mdio-airoha.o\n+obj-$(CONFIG_MDIO_AIROHA_EN8811H)\t+= mdio-airoha-en8811h.o\n obj-$(CONFIG_MDIO_ASPEED)\t\t+= mdio-aspeed.o\n obj-$(CONFIG_MDIO_BCM_IPROC)\t\t+= mdio-bcm-iproc.o\n obj-$(CONFIG_MDIO_BCM_UNIMAC)\t\t+= mdio-bcm-unimac.o\ndiff --git a/drivers/net/mdio/mdio-airoha-en8811h.c b/drivers/net/mdio/mdio-airoha-en8811h.c\nnew file mode 100644\nindex 0000000000000..7785525c57a64\n--- /dev/null\n+++ b/drivers/net/mdio/mdio-airoha-en8811h.c\n@@ -0,0 +1,378 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * Airoha EN8811H MDIO device driver\n+ *\n+ * The EN8811H is an MD32 microcontroller until firmware is downloaded into\n+ * it, and only then an Ethernet PHY.\n+ *\n+ * Copyright (C) 2026 Aleksei Sviridkin \u003cf@lex.la\u003e\n+ */\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \u003clinux/mdio.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/of_mdio.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/pm.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+\n+#include \u003clinux/mdio/mdio-airoha-en8811h.h\u003e\n+\n+#define EN8811H_FW_POLL_MIN_MS\t1000\n+#define EN8811H_FW_POLL_MAX_MS\t30000\n+#define EN8811H_FW_WARN_MS\t60000\n+\n+struct en8811h_mcu {\n+\tstruct mdio_device *mdiodev;\n+\tstruct gpio_desc *reset_gpio;\n+\tstruct delayed_work fw_poll;\n+\tstruct mii_bus *bus;\n+\tunsigned int poll_ms;\n+\tunsigned int waited_ms;\n+\tu32 fw_version;\n+\tbool warned;\n+\tbool fw_running;\n+};\n+\n+static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum)\n+{\n+\tstruct en8811h_mcu *mcu = bus-\u003epriv;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\treturn mdiobus_read_nested(mcu-\u003emdiodev-\u003ebus, addr, regnum);\n+}\n+\n+static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u16 val)\n+{\n+\tstruct en8811h_mcu *mcu = bus-\u003epriv;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\treturn mdiobus_write_nested(mcu-\u003emdiodev-\u003ebus, addr, regnum, val);\n+}\n+\n+static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad,\n+\t\t\t\tint regnum)\n+{\n+\tstruct en8811h_mcu *mcu = bus-\u003epriv;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\treturn mdiobus_c45_read_nested(mcu-\u003emdiodev-\u003ebus, addr, devad, regnum);\n+}\n+\n+static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad,\n+\t\t\t\t int regnum, u16 val)\n+{\n+\tstruct en8811h_mcu *mcu = bus-\u003epriv;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\treturn mdiobus_c45_write_nested(mcu-\u003emdiodev-\u003ebus, addr, devad, regnum,\n+\t\t\t\t\tval);\n+}\n+\n+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)\n+{\n+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\n+\tstruct device *dev = \u0026mcu-\u003emdiodev-\u003edev;\n+\tstruct device_node *np;\n+\tstruct mii_bus *bus;\n+\tint ret;\n+\n+\tnp = of_get_child_by_name(dev-\u003eof_node, \"mdio\");\n+\tif (!np)\n+\t\treturn -ENODEV;\n+\n+\t/* Not devm: this is retried, and a devm bus would only be freed at\n+\t * detach.\n+\t */\n+\tbus = mdiobus_alloc();\n+\tif (!bus) {\n+\t\tof_node_put(np);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tbus-\u003ename = \"airoha-en8811h\";\n+\tsnprintf(bus-\u003eid, MII_BUS_ID_SIZE, \"%s\", dev_name(dev));\n+\tbus-\u003epriv = mcu;\n+\tbus-\u003eparent = dev;\n+\tif (parent-\u003eread \u0026\u0026 parent-\u003ewrite) {\n+\t\tbus-\u003eread = en8811h_mcu_read;\n+\t\tbus-\u003ewrite = en8811h_mcu_write;\n+\t}\n+\tif (parent-\u003eread_c45 \u0026\u0026 parent-\u003ewrite_c45) {\n+\t\tbus-\u003eread_c45 = en8811h_mcu_read_c45;\n+\t\tbus-\u003ewrite_c45 = en8811h_mcu_write_c45;\n+\t}\n+\n+\tret = of_mdiobus_register(bus, np);\n+\tof_node_put(np);\n+\tif (!ret \u0026\u0026 !mdiobus_get_phy(bus, mcu-\u003emdiodev-\u003eaddr)) {\n+\t\t/* An ID read that failed leaves the bus registered and the\n+\t\t * PHY absent; of_mdiobus_register() returns 0 either way.\n+\t\t */\n+\t\tmdiobus_unregister(bus);\n+\t\tret = -ENODEV;\n+\t}\n+\tif (ret) {\n+\t\tmdiobus_free(bus);\n+\t\treturn ret;\n+\t}\n+\n+\tmcu-\u003ebus = bus;\n+\treturn 0;\n+}\n+\n+static void en8811h_mcu_fw_poll(struct work_struct *work)\n+{\n+\tstruct en8811h_mcu *mcu = container_of(to_delayed_work(work),\n+\t\t\t\t\t       struct en8811h_mcu, fw_poll);\n+\tstruct device *dev = \u0026mcu-\u003emdiodev-\u003edev;\n+\tint cached, ret;\n+\n+\tif (!mcu-\u003efw_running) {\n+\t\tret = air_en8811h_fw_download(mcu-\u003emdiodev, \u0026mcu-\u003efw_version,\n+\t\t\t\t\t      mcu-\u003ebus);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto retry;\n+\n+\t\tdev_dbg(dev, \"firmware %08x running after %ums\\n\",\n+\t\t\tmcu-\u003efw_version, mcu-\u003ewaited_ms);\n+\t\tcached = firmware_request_cache(dev, EN8811H_MD32_DM);\n+\t\tret = firmware_request_cache(dev, EN8811H_MD32_DSP) ?: cached;\n+\t\tif (ret)\n+\t\t\tdev_warn(dev, \"not cached, resume will read the files off a filesystem: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n+\t\tmcu-\u003efw_running = true;\n+\t\tmcu-\u003epoll_ms = EN8811H_FW_POLL_MIN_MS;\n+\t\tmcu-\u003ewaited_ms = 0;\n+\t\tmcu-\u003ewarned = false;\n+\t}\n+\n+\t/* Resume re-runs the download, so the bus can already be here. */\n+\tret = mcu-\u003ebus ? 0 : en8811h_mcu_bus_register(mcu);\n+\tif (!ret)\n+\t\treturn;\n+\n+retry:\n+\tif (!mcu-\u003ewarned \u0026\u0026 mcu-\u003ewaited_ms \u003e= EN8811H_FW_WARN_MS) {\n+\t\tif (!mcu-\u003efw_running)\n+\t\t\tdev_warn(dev, \"no firmware after %ums of waiting for %s and %s: %pe\\n\",\n+\t\t\t\t mcu-\u003ewaited_ms, EN8811H_MD32_DM,\n+\t\t\t\t EN8811H_MD32_DSP, ERR_PTR(ret));\n+\t\telse\n+\t\t\tdev_warn(dev, \"still no PHY at address %d, %ums after the firmware started: %pe\\n\",\n+\t\t\t\t mcu-\u003emdiodev-\u003eaddr, mcu-\u003ewaited_ms,\n+\t\t\t\t ERR_PTR(ret));\n+\t\tmcu-\u003ewarned = true;\n+\t}\n+\n+\t/* Registering again repeats the MDIO core's message and its uevents,\n+\t * so this phase gives up - except on a deferral, which only a retry\n+\t * resolves and which deferred_probe_timeout can make permanent.\n+\t */\n+\tif (mcu-\u003efw_running \u0026\u0026 ret != -EPROBE_DEFER \u0026\u0026\n+\t    mcu-\u003ewaited_ms \u003e= EN8811H_FW_WARN_MS)\n+\t\treturn;\n+\n+\t/* Count the sleep ahead: the first run was immediate. */\n+\tmcu-\u003ewaited_ms += mcu-\u003epoll_ms;\n+\tqueue_delayed_work(system_freezable_wq, \u0026mcu-\u003efw_poll,\n+\t\t\t   msecs_to_jiffies(mcu-\u003epoll_ms));\n+\tmcu-\u003epoll_ms = min(mcu-\u003epoll_ms * 2, EN8811H_FW_POLL_MAX_MS);\n+}\n+\n+/* The firmware lives in volatile RAM: no reset while the MD32 reports ready. */\n+static void en8811h_mcu_reset_unless_running(struct en8811h_mcu *mcu)\n+{\n+\tstruct mdio_device *mdiodev = mcu-\u003emdiodev;\n+\tstruct device *dev = \u0026mdiodev-\u003edev;\n+\tu32 assert_us = 0, deassert_us = 0;\n+\tint ret;\n+\n+\tret = air_en8811h_mcu_running(mdiodev, mcu-\u003ebus);\n+\tif (ret \u003e 0) {\n+\t\tdev_dbg(dev, \"MD32 already running, leaving reset alone\\n\");\n+\t\treturn;\n+\t}\n+\n+\tif (!mcu-\u003ereset_gpio)\n+\t\treturn;\n+\n+\t/* A failed read does not mean the firmware is gone: leave a\n+\t * deasserted line alone, and clear an asserted one before giving up.\n+\t */\n+\tif (ret \u003c 0 \u0026\u0026 gpiod_get_value_cansleep(mcu-\u003ereset_gpio) \u003c= 0) {\n+\t\tdev_dbg(dev, \"MD32 state unknown (%d), leaving reset alone\\n\",\n+\t\t\tret);\n+\t\treturn;\n+\t}\n+\n+\tdevice_property_read_u32(dev, \"reset-assert-us\", \u0026assert_us);\n+\tdevice_property_read_u32(dev, \"reset-deassert-us\", \u0026deassert_us);\n+\n+\tret = gpiod_direction_output(mcu-\u003ereset_gpio, 1);\n+\tif (ret) {\n+\t\tdev_warn(dev, \"reset not asserted: %pe\\n\", ERR_PTR(ret));\n+\t\treturn;\n+\t}\n+\n+\tif (assert_us)\n+\t\tfsleep(assert_us);\n+\n+\tgpiod_set_value_cansleep(mcu-\u003ereset_gpio, 0);\n+\tif (deassert_us)\n+\t\tfsleep(deassert_us);\n+}\n+\n+static int en8811h_mcu_probe(struct mdio_device *mdiodev)\n+{\n+\tstruct device *dev = \u0026mdiodev-\u003edev;\n+\tstruct en8811h_mcu *mcu;\n+\tstruct device_node *np;\n+\tunsigned int phys = 0;\n+\n+\tmcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);\n+\tif (!mcu)\n+\t\treturn -ENOMEM;\n+\n+\tmcu-\u003emdiodev = mdiodev;\n+\tmdiodev_set_drvdata(mdiodev, mcu);\n+\n+\t/* Registration needs this only once the firmware runs, but a DT\n+\t * hole should fail the bind now, not as a work-item error later.\n+\t */\n+\tnp = of_get_child_by_name(dev-\u003eof_node, \"mdio\");\n+\tif (!np || !of_device_is_available(np)) {\n+\t\tof_node_put(np);\n+\t\treturn dev_err_probe(dev, -ENODEV,\n+\t\t\t\t     \"no usable mdio node describing the PHY\\n\");\n+\t}\n+\n+\t/* Only the address this core occupies on the parent bus is passed\n+\t * through, so a node at any other one will never respond.\n+\t */\n+\tfor_each_available_child_of_node_scoped(np, child) {\n+\t\tu32 addr;\n+\n+\t\tif (of_property_read_u32(child, \"reg\", \u0026addr))\n+\t\t\tcontinue;\n+\t\tif (addr != mdiodev-\u003eaddr) {\n+\t\t\tdev_err(dev, \"%pOF: address %u is not answered here, only %u\\n\",\n+\t\t\t\tchild, addr, mdiodev-\u003eaddr);\n+\t\t\tof_node_put(np);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tphys++;\n+\t}\n+\tof_node_put(np);\n+\n+\tif (!phys)\n+\t\treturn dev_err_probe(dev, -ENODEV,\n+\t\t\t\t     \"mdio node describes no PHY\\n\");\n+\n+\t/* A truncated bus name loses the address that makes it unique. */\n+\tif (strlen(dev_name(dev)) \u003e= MII_BUS_ID_SIZE)\n+\t\treturn dev_err_probe(dev, -ENAMETOOLONG,\n+\t\t\t\t     \"name does not fit an MDIO bus id\\n\");\n+\n+\t/* The core claims reset-gpios only for devices flagged as PHYs. */\n+\tmcu-\u003ereset_gpio = devm_gpiod_get_optional(dev, \"reset\", GPIOD_ASIS);\n+\tif (IS_ERR(mcu-\u003ereset_gpio))\n+\t\treturn dev_err_probe(dev, PTR_ERR(mcu-\u003ereset_gpio),\n+\t\t\t\t     \"failed to get reset GPIO\\n\");\n+\n+\tif (mcu-\u003ereset_gpio)\n+\t\tgpiod_set_consumer_name(mcu-\u003ereset_gpio, \"EN8811H reset\");\n+\n+\ten8811h_mcu_reset_unless_running(mcu);\n+\n+\tmcu-\u003epoll_ms = EN8811H_FW_POLL_MIN_MS;\n+\tINIT_DELAYED_WORK(\u0026mcu-\u003efw_poll, en8811h_mcu_fw_poll);\n+\t/* Freezable: neither the file lookup nor the download may land on\n+\t * a suspending bus.\n+\t */\n+\tqueue_delayed_work(system_freezable_wq, \u0026mcu-\u003efw_poll, 0);\n+\n+\treturn 0;\n+}\n+\n+static void en8811h_mcu_remove(struct mdio_device *mdiodev)\n+{\n+\tstruct en8811h_mcu *mcu = mdiodev_get_drvdata(mdiodev);\n+\n+\tcancel_delayed_work_sync(\u0026mcu-\u003efw_poll);\n+\tif (mcu-\u003ebus) {\n+\t\tmdiobus_unregister(mcu-\u003ebus);\n+\t\tmdiobus_free(mcu-\u003ebus);\n+\t}\n+}\n+\n+static int en8811h_mcu_resume(struct device *dev)\n+{\n+\tstruct en8811h_mcu *mcu = dev_get_drvdata(dev);\n+\tint ret;\n+\n+\t/* Nothing to redo: the poll is armed and thaws with everything else. */\n+\tif (!mcu-\u003efw_running)\n+\t\treturn 0;\n+\n+\t/* Synchronous: the poll's workqueue is freezable and thaws only\n+\t * after the resume callbacks have run.\n+\t */\n+\ten8811h_mcu_reset_unless_running(mcu);\n+\tret = air_en8811h_fw_download(mcu-\u003emdiodev, \u0026mcu-\u003efw_version, mcu-\u003ebus);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(dev, \"firmware not restored, reloading: %pe\\n\",\n+\t\t\tERR_PTR(ret));\n+\t\tmcu-\u003efw_running = false;\n+\t\tmcu-\u003epoll_ms = EN8811H_FW_POLL_MIN_MS;\n+\t\tmcu-\u003ewaited_ms = 0;\n+\t\tmcu-\u003ewarned = false;\n+\t\t/* The poll may already be pending on a long backoff, and\n+\t\t * queue_delayed_work() would leave that timer alone.\n+\t\t */\n+\t\tmod_delayed_work(system_freezable_wq, \u0026mcu-\u003efw_poll, 0);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static DEFINE_SIMPLE_DEV_PM_OPS(en8811h_mcu_pm_ops, NULL, en8811h_mcu_resume);\n+\n+static const struct of_device_id en8811h_mcu_of_match[] = {\n+\t{ .compatible = \"airoha,en8811h-mcu\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, en8811h_mcu_of_match);\n+\n+static struct mdio_driver en8811h_mcu_driver = {\n+\t.probe = en8811h_mcu_probe,\n+\t.remove = en8811h_mcu_remove,\n+\t.mdiodrv.driver = {\n+\t\t.name = \"airoha-en8811h-mcu\",\n+\t\t.of_match_table = en8811h_mcu_of_match,\n+\t\t.pm = pm_sleep_ptr(\u0026en8811h_mcu_pm_ops),\n+\t\t/* No safe teardown under an attached PHY. */\n+\t\t.suppress_bind_attrs = true,\n+\t},\n+};\n+\n+mdio_module_driver(en8811h_mcu_driver);\n+\n+MODULE_FIRMWARE(EN8811H_MD32_DM);\n+MODULE_FIRMWARE(EN8811H_MD32_DSP);\n+\n+MODULE_DESCRIPTION(\"Airoha EN8811H MDIO device driver\");\n+MODULE_AUTHOR(\"Aleksei Sviridkin \u003cf@lex.la\u003e\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c\nindex 16a9171cb8384..940379e2737b4 100644\n--- a/drivers/net/phy/air_en8811h.c\n+++ b/drivers/net/phy/air_en8811h.c\n@@ -20,21 +20,15 @@\n #include \u003clinux/bitfield.h\u003e\n #include \u003clinux/property.h\u003e\n #include \u003clinux/wordpart.h\u003e\n-#include \u003clinux/unaligned.h\u003e\n \n #include \"air_phy_lib.h\"\n \n #define EN8811H_PHY_ID\t\t0x03a2a411\n #define AN8811HB_PHY_ID\t\t0xc0ff04a0\n \n-#define EN8811H_MD32_DM\t\t\"airoha/EthMD32.dm.bin\"\n-#define EN8811H_MD32_DSP\t\"airoha/EthMD32.DSP.bin\"\n #define AN8811HB_MD32_DM\t\"airoha/an8811hb/EthMD32_CRC.DM.bin\"\n #define AN8811HB_MD32_DSP\t\"airoha/an8811hb/EthMD32_CRC.DSP.bin\"\n \n-#define AIR_FW_ADDR_DM\t0x00000000\n-#define AIR_FW_ADDR_DSP\t0x00100000\n-\n /* MII Registers */\n #define AIR_AUX_CTRL_STATUS\t\t0x1d\n #define   AIR_AUX_CTRL_STATUS_SPEED_MASK\tGENMASK(4, 2)\n@@ -44,8 +38,6 @@\n #define   AIR_AUX_CTRL_STATUS_SPEED_2500\t0xc\n \n /* Registers on MDIO_MMD_VEND1 */\n-#define EN8811H_PHY_FW_STATUS\t\t0x8009\n-#define   EN8811H_PHY_READY\t\t\t0x02\n \n #define AIR_PHY_MCU_CMD_0\t\t0x800b\n #define AIR_PHY_MCU_CMD_1\t\t0x800c\n@@ -108,8 +100,6 @@\n #define EN8811H_2P5G_LPA\t\t0x3b30\n #define   EN8811H_2P5G_LPA_2P5G\t\t\tBIT(0)\n \n-#define EN8811H_FW_VERSION\t\t0x3b3c\n-\n #define EN8811H_POLARITY\t\t0xca0f8\n #define   EN8811H_POLARITY_TX_NORMAL\t\tBIT(0)\n #define   EN8811H_POLARITY_RX_REVERSE\t\tBIT(1)\n@@ -122,12 +112,6 @@\n #define EN8811H_CLK_CGM\t\t\t0xcf958\n #define   EN8811H_CLK_CGM_CKO\t\t\tBIT(26)\n \n-#define EN8811H_FW_CTRL_1\t\t0x0f0018\n-#define   EN8811H_FW_CTRL_1_START\t\t0x0\n-#define   EN8811H_FW_CTRL_1_FINISH\t\t0x1\n-#define EN8811H_FW_CTRL_2\t\t0x800000\n-#define EN8811H_FW_CTRL_2_LOADING\t\tBIT(11)\n-\n #define AN8811HB_CRC_PM_SET1\t\t0xf020c\n #define AN8811HB_CRC_PM_MON2\t\t0xf0218\n #define AN8811HB_CRC_PM_MON3\t\t0xf021c\n@@ -270,86 +254,15 @@ static int __air_pbus_reg_write(struct mdio_device *mdiodev,\n \t\t\t       upper_16_bits(pbus_data));\n }\n \n-static int __air_write_buf(struct phy_device *phydev, u32 address,\n-\t\t\t   const struct firmware *fw)\n+static int en8811h_wait_mcu_ready(struct phy_device *phydev)\n {\n-\tunsigned int offset;\n \tint ret;\n-\tu16 val;\n-\n-\tret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR);\n-\tif (ret \u003c 0)\n-\t\treturn ret;\n-\n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,\n-\t\t\t  upper_16_bits(address));\n-\tif (ret \u003c 0)\n-\t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,\n-\t\t\t  lower_16_bits(address));\n+\tret = air_en8811h_wait_mcu_ready(\u0026phydev-\u003emdio, false);\n \tif (ret \u003c 0)\n-\t\treturn ret;\n-\n-\tfor (offset = 0; offset \u003c fw-\u003esize; offset += 4) {\n-\t\tval = get_unaligned_le16(\u0026fw-\u003edata[offset + 2]);\n-\t\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val);\n-\t\tif (ret \u003c 0)\n-\t\t\treturn ret;\n-\n-\t\tval = get_unaligned_le16(\u0026fw-\u003edata[offset]);\n-\t\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val);\n-\t\tif (ret \u003c 0)\n-\t\t\treturn ret;\n-\t}\n+\t\tphydev_err(phydev, \"MCU not ready: %pe\\n\", ERR_PTR(ret));\n \n-\treturn 0;\n-}\n-\n-static int air_write_buf(struct phy_device *phydev, u32 address,\n-\t\t\t const struct firmware *fw)\n-{\n-\tint saved_page;\n-\tint ret = 0;\n-\n-\tif (fw-\u003esize % 4) {\n-\t\tphydev_err(phydev, \"firmware size %zu is not a multiple of 4\\n\",\n-\t\t\t   fw-\u003esize);\n-\t\treturn -EINVAL;\n-\t}\n-\n-\tsaved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);\n-\n-\tif (saved_page \u003e= 0) {\n-\t\tret = __air_write_buf(phydev, address, fw);\n-\t\tif (ret \u003c 0)\n-\t\t\tphydev_err(phydev, \"%s 0x%08x failed: %d\\n\", __func__,\n-\t\t\t\t   address, ret);\n-\t}\n-\n-\treturn phy_restore_page(phydev, saved_page, ret);\n-}\n-\n-static int en8811h_wait_mcu_ready(struct phy_device *phydev)\n-{\n-\tint ret, reg_value;\n-\n-\tret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,\n-\t\t\t\t\t EN8811H_FW_CTRL_1_FINISH);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/* Because of mdio-lock, may have to wait for multiple loads */\n-\tret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,\n-\t\t\t\t\tEN8811H_PHY_FW_STATUS, reg_value,\n-\t\t\t\t\treg_value == EN8811H_PHY_READY,\n-\t\t\t\t\t20000, 7500000, true);\n-\tif (ret) {\n-\t\tphydev_err(phydev, \"MCU not ready: 0x%x\\n\", reg_value);\n-\t\treturn -ENODEV;\n-\t}\n-\n-\treturn 0;\n+\treturn ret;\n }\n \n static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,\n@@ -411,7 +324,7 @@ static int an8811hb_load_file(struct phy_device *phydev, const char *name,\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = air_write_buf(phydev, address,  fw);\n+\tret = air_fw_write_buf(\u0026phydev-\u003emdio, address, fw, false);\n \trelease_firmware(fw);\n \treturn ret;\n }\n@@ -507,56 +420,16 @@ static int an8811hb_load_firmware(struct phy_device *phydev)\n \n static int en8811h_load_firmware(struct phy_device *phydev)\n {\n-\tstruct device *dev = \u0026phydev-\u003emdio.dev;\n-\tconst struct firmware *fw1, *fw2;\n+\tstruct en8811h_priv *priv = phydev-\u003epriv;\n \tint ret;\n \n-\tret = request_firmware_direct(\u0026fw1, EN8811H_MD32_DM, dev);\n-\tif (ret \u003c 0)\n-\t\treturn ret;\n-\n-\tret = request_firmware_direct(\u0026fw2, EN8811H_MD32_DSP, dev);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_rel1;\n-\n-\tret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,\n-\t\t\t\t\t EN8811H_FW_CTRL_1_START);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\tret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,\n-\t\t\t\t\t  EN8811H_FW_CTRL_2_LOADING,\n-\t\t\t\t\t  EN8811H_FW_CTRL_2_LOADING);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\tret = air_write_buf(phydev, AIR_FW_ADDR_DM,  fw1);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\tret = air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\tret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,\n-\t\t\t\t\t  EN8811H_FW_CTRL_2_LOADING, 0);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\tret = en8811h_wait_mcu_ready(phydev);\n-\tif (ret \u003c 0)\n-\t\tgoto en8811h_load_firmware_out;\n-\n-\ten8811h_print_fw_version(phydev);\n-\n-en8811h_load_firmware_out:\n-\trelease_firmware(fw2);\n-\n-en8811h_load_firmware_rel1:\n-\trelease_firmware(fw1);\n-\n+\tret = air_en8811h_fw_download(\u0026phydev-\u003emdio, \u0026priv-\u003efirmware_version,\n+\t\t\t\t      NULL);\n \tif (ret \u003c 0)\n \t\tphydev_err(phydev, \"Load firmware failed: %d\\n\", ret);\n+\telse\n+\t\tphydev_info(phydev, \"MD32 firmware version: %08x\\n\",\n+\t\t\t    priv-\u003efirmware_version);\n \n \treturn ret;\n }\n@@ -1156,8 +1029,8 @@ static int en8811h_probe(struct phy_device *phydev)\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\t/* mcu has just restarted after firmware load */\n-\tpriv-\u003emcu_needs_restart = false;\n+\t/* Firmware that was already running was never restarted here. */\n+\tpriv-\u003emcu_needs_restart = (ret == 1);\n \n \t/* MDIO_DEVS1/2 empty, so set mmds_present bits here */\n \tphydev-\u003ec45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;\ndiff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c\nindex 5141db19fa5ee..5902a6ba3643d 100644\n--- a/drivers/net/phy/air_phy_lib.c\n+++ b/drivers/net/phy/air_phy_lib.c\n@@ -8,37 +8,42 @@\n  */\n \n #include \u003clinux/export.h\u003e\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/mdio.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/phy.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n #include \u003clinux/wordpart.h\u003e\n \n #include \"air_phy_lib.h\"\n \n-static int __air_buckpbus_reg_read(struct phy_device *phydev,\n+static int __air_buckpbus_reg_read(struct mdio_device *mdiodev,\n \t\t\t\t   u32 pbus_address, u32 *pbus_data)\n {\n \tint pbus_data_low, pbus_data_high;\n \tint ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,\n+\t\t\t      AIR_BPBUS_MODE_ADDR_FIXED);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,\n-\t\t\t  upper_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH,\n+\t\t\t      upper_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,\n-\t\t\t  lower_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW,\n+\t\t\t      lower_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tpbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);\n+\tpbus_data_high = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH);\n \tif (pbus_data_high \u003c 0)\n \t\treturn pbus_data_high;\n \n-\tpbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);\n+\tpbus_data_low = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW);\n \tif (pbus_data_low \u003c 0)\n \t\treturn pbus_data_low;\n \n@@ -46,64 +51,66 @@ static int __air_buckpbus_reg_read(struct phy_device *phydev,\n \treturn 0;\n }\n \n-static int __air_buckpbus_reg_write(struct phy_device *phydev,\n+static int __air_buckpbus_reg_write(struct mdio_device *mdiodev,\n \t\t\t\t    u32 pbus_address, u32 pbus_data)\n {\n \tint ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,\n+\t\t\t      AIR_BPBUS_MODE_ADDR_FIXED);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,\n-\t\t\t  upper_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,\n+\t\t\t      upper_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,\n-\t\t\t  lower_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,\n+\t\t\t      lower_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,\n-\t\t\t  upper_16_bits(pbus_data));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH,\n+\t\t\t      upper_16_bits(pbus_data));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,\n-\t\t\t  lower_16_bits(pbus_data));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW,\n+\t\t\t      lower_16_bits(pbus_data));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n \treturn 0;\n }\n \n-static int __air_buckpbus_reg_modify(struct phy_device *phydev,\n+static int __air_buckpbus_reg_modify(struct mdio_device *mdiodev,\n \t\t\t\t     u32 pbus_address, u32 mask, u32 set)\n {\n \tint pbus_data_low, pbus_data_high;\n \tu32 pbus_data_old, pbus_data_new;\n \tint ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,\n+\t\t\t      AIR_BPBUS_MODE_ADDR_FIXED);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,\n-\t\t\t  upper_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH,\n+\t\t\t      upper_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,\n-\t\t\t  lower_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW,\n+\t\t\t      lower_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tpbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);\n+\tpbus_data_high = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH);\n \tif (pbus_data_high \u003c 0)\n \t\treturn pbus_data_high;\n \n-\tpbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);\n+\tpbus_data_low = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW);\n \tif (pbus_data_low \u003c 0)\n \t\treturn pbus_data_low;\n \n@@ -112,23 +119,23 @@ static int __air_buckpbus_reg_modify(struct phy_device *phydev,\n \tif (pbus_data_new == pbus_data_old)\n \t\treturn 0;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,\n-\t\t\t  upper_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,\n+\t\t\t      upper_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,\n-\t\t\t  lower_16_bits(pbus_address));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,\n+\t\t\t      lower_16_bits(pbus_address));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,\n-\t\t\t  upper_16_bits(pbus_data_new));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH,\n+\t\t\t      upper_16_bits(pbus_data_new));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n-\tret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,\n-\t\t\t  lower_16_bits(pbus_data_new));\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW,\n+\t\t\t      lower_16_bits(pbus_data_new));\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n@@ -144,7 +151,8 @@ int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,\n \tsaved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);\n \n \tif (saved_page \u003e= 0) {\n-\t\tret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);\n+\t\tret = __air_buckpbus_reg_read(\u0026phydev-\u003emdio, pbus_address,\n+\t\t\t\t\t      pbus_data);\n \t\tif (ret \u003c 0)\n \t\t\tphydev_err(phydev, \"%s 0x%08x failed: %d\\n\", __func__,\n \t\t\t\t   pbus_address, ret);\n@@ -163,7 +171,7 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,\n \tsaved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);\n \n \tif (saved_page \u003e= 0) {\n-\t\tret = __air_buckpbus_reg_write(phydev, pbus_address,\n+\t\tret = __air_buckpbus_reg_write(\u0026phydev-\u003emdio, pbus_address,\n \t\t\t\t\t       pbus_data);\n \t\tif (ret \u003c 0)\n \t\t\tphydev_err(phydev, \"%s 0x%08x failed: %d\\n\", __func__,\n@@ -183,8 +191,8 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,\n \tsaved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);\n \n \tif (saved_page \u003e= 0) {\n-\t\tret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,\n-\t\t\t\t\t\tset);\n+\t\tret = __air_buckpbus_reg_modify(\u0026phydev-\u003emdio, pbus_address,\n+\t\t\t\t\t\tmask, set);\n \t\tif (ret \u003c 0)\n \t\t\tphydev_err(phydev, \"%s 0x%08x failed: %d\\n\", __func__,\n \t\t\t\t   pbus_address, ret);\n@@ -194,6 +202,362 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,\n }\n EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify);\n \n+static int __air_write_buf(struct mdio_device *mdiodev, u32 address,\n+\t\t\t   const u8 *data, size_t len)\n+{\n+\tunsigned int offset;\n+\tint ret;\n+\tu16 val;\n+\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,\n+\t\t\t      AIR_BPBUS_MODE_ADDR_INCR);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,\n+\t\t\t      upper_16_bits(address));\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,\n+\t\t\t      lower_16_bits(address));\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tfor (offset = 0; offset \u003c len; offset += 4) {\n+\t\tval = get_unaligned_le16(\u0026data[offset + 2]);\n+\t\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH, val);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tval = get_unaligned_le16(\u0026data[offset]);\n+\t\tret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW, val);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* phy_select_page() needs a phy_device, which does not exist yet. */\n+static int __air_mdio_select_page(struct mdio_device *mdiodev, int page)\n+{\n+\tint saved_page, ret;\n+\n+\tsaved_page = __mdiodev_read(mdiodev, AIR_EXT_PAGE_ACCESS);\n+\tif (saved_page \u003c 0)\n+\t\treturn saved_page;\n+\n+\tif (saved_page != page) {\n+\t\tret = __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS, page);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn saved_page;\n+}\n+\n+static int __air_mdio_restore_page(struct mdio_device *mdiodev,\n+\t\t\t\t   int saved_page, int page, int ret)\n+{\n+\tint restore;\n+\n+\tif (saved_page != page) {\n+\t\trestore = __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS,\n+\t\t\t\t\t  saved_page);\n+\t\tif (ret \u003e= 0 \u0026\u0026 restore \u003c 0)\n+\t\t\tret = restore;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)\n+{\n+\tif (nested)\n+\t\tmutex_lock_nested(\u0026mdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\telse\n+\t\tmutex_lock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+}\n+\n+/* The PHY below reaches these registers under this lock through\n+ * phy_select_page(); the firmware read must stay outside it.\n+ */\n+static void air_child_bus_lock(struct mii_bus *child_bus)\n+{\n+\tif (child_bus)\n+\t\tmutex_lock(\u0026child_bus-\u003emdio_lock);\n+}\n+\n+static void air_child_bus_unlock(struct mii_bus *child_bus)\n+{\n+\tif (child_bus)\n+\t\tmutex_unlock(\u0026child_bus-\u003emdio_lock);\n+}\n+\n+int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,\n+\t\t     const struct firmware *fw, bool nested)\n+{\n+\tsize_t chunk, done = 0;\n+\tint saved_page, ret;\n+\n+\tif (fw-\u003esize % 4) {\n+\t\tdev_err(\u0026mdiodev-\u003edev, \"firmware size %zu is not a multiple of 4\\n\",\n+\t\t\tfw-\u003esize);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\twhile (done \u003c fw-\u003esize) {\n+\t\tchunk = min_t(size_t, fw-\u003esize - done, AIR_FW_CHUNK_BYTES);\n+\n+\t\tair_mdiodev_lock(mdiodev, nested);\n+\n+\t\tsaved_page = __air_mdio_select_page(mdiodev,\n+\t\t\t\t\t\t    AIR_PHY_PAGE_EXTENDED_4);\n+\t\tif (saved_page \u003c 0) {\n+\t\t\tret = saved_page;\n+\t\t} else {\n+\t\t\tret = __air_write_buf(mdiodev, address + done,\n+\t\t\t\t\t      fw-\u003edata + done, chunk);\n+\t\t\tret = __air_mdio_restore_page(mdiodev, saved_page,\n+\t\t\t\t\t\t      AIR_PHY_PAGE_EXTENDED_4,\n+\t\t\t\t\t\t      ret);\n+\t\t}\n+\n+\t\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tdone += chunk;\n+\t}\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(air_fw_write_buf);\n+\n+static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev,\n+\t\t\t\t      u32 pbus_address, u32 *pbus_data,\n+\t\t\t\t      bool nested)\n+{\n+\tint saved_page, ret;\n+\n+\tair_mdiodev_lock(mdiodev, nested);\n+\n+\tsaved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);\n+\tif (saved_page \u003c 0) {\n+\t\tret = saved_page;\n+\t} else {\n+\t\tret = __air_buckpbus_reg_read(mdiodev, pbus_address, pbus_data);\n+\t\tret = __air_mdio_restore_page(mdiodev, saved_page,\n+\t\t\t\t\t      AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev,\n+\t\t\t\t       u32 pbus_address, u32 pbus_data,\n+\t\t\t\t       bool nested)\n+{\n+\tint saved_page, ret;\n+\n+\tair_mdiodev_lock(mdiodev, nested);\n+\n+\tsaved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);\n+\tif (saved_page \u003c 0) {\n+\t\tret = saved_page;\n+\t} else {\n+\t\tret = __air_buckpbus_reg_write(mdiodev, pbus_address,\n+\t\t\t\t\t       pbus_data);\n+\t\tret = __air_mdio_restore_page(mdiodev, saved_page,\n+\t\t\t\t\t      AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev,\n+\t\t\t\t\tu32 pbus_address, u32 mask, u32 set,\n+\t\t\t\t\tbool nested)\n+{\n+\tint saved_page, ret;\n+\n+\tair_mdiodev_lock(mdiodev, nested);\n+\n+\tsaved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);\n+\tif (saved_page \u003c 0) {\n+\t\tret = saved_page;\n+\t} else {\n+\t\tret = __air_buckpbus_reg_modify(mdiodev, pbus_address,\n+\t\t\t\t\t\tmask, set);\n+\t\tret = __air_mdio_restore_page(mdiodev, saved_page,\n+\t\t\t\t\t      AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+/* mmd_phy_read() drops the errors from the three writes that select the\n+ * register, so a failed selection reads a different one back as success.\n+ */\n+static int __air_mmd_read(struct mdio_device *mdiodev, u16 devad, u16 regnum)\n+{\n+\tstruct mii_bus *bus = mdiodev-\u003ebus;\n+\tint addr = mdiodev-\u003eaddr;\n+\tint ret;\n+\n+\tret = __mdiobus_write(bus, addr, MII_MMD_CTRL, devad);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = __mdiobus_write(bus, addr, MII_MMD_DATA, regnum);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = __mdiobus_write(bus, addr, MII_MMD_CTRL,\n+\t\t\t      devad | MII_MMD_CTRL_NOINCR);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn __mdiobus_read(bus, addr, MII_MMD_DATA);\n+}\n+\n+static int air_mmd_status_read(struct mdio_device *mdiodev, bool nested)\n+{\n+\tint ret;\n+\n+\tair_mdiodev_lock(mdiodev, nested);\n+\tret = __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS);\n+\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+\n+\treturn ret;\n+}\n+\n+int air_en8811h_mcu_running(struct mdio_device *mdiodev,\n+\t\t\t    struct mii_bus *child_bus)\n+{\n+\tint ret;\n+\n+\tair_child_bus_lock(child_bus);\n+\tret = air_mmd_status_read(mdiodev, !!child_bus);\n+\tair_child_bus_unlock(child_bus);\n+\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn ret == EN8811H_PHY_READY;\n+}\n+EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);\n+\n+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested)\n+{\n+\tint ret, reg_value;\n+\n+\tret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,\n+\t\t\t\t\t  EN8811H_FW_CTRL_1_FINISH, nested);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Because of mdio-lock, may have to wait for multiple loads. A read\n+\t * error ends the poll at once, like phy_read_mmd_poll_timeout().\n+\t */\n+\tret = read_poll_timeout(air_mmd_status_read, reg_value,\n+\t\t\t\treg_value \u003c 0 ||\n+\t\t\t\treg_value == EN8811H_PHY_READY,\n+\t\t\t\t20000, 7500000, true, mdiodev, nested);\n+\tif (reg_value \u003c 0)\n+\t\treturn reg_value;\n+\tif (ret) {\n+\t\tdev_dbg(\u0026mdiodev-\u003edev, \"MCU not ready: 0x%x\\n\", reg_value);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready);\n+\n+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,\n+\t\t\t    struct mii_bus *child_bus)\n+{\n+\tstruct device *dev = \u0026mdiodev-\u003edev;\n+\tconst struct firmware *fw1, *fw2;\n+\tbool nested = !!child_bus;\n+\tint ret;\n+\n+\tret = air_en8811h_mcu_running(mdiodev, child_bus);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tif (ret) {\n+\t\tair_child_bus_lock(child_bus);\n+\t\tret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,\n+\t\t\t\t\t\t fw_version, nested);\n+\t\tair_child_bus_unlock(child_bus);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\treturn 1;\n+\t}\n+\n+\tret = request_firmware_direct(\u0026fw1, EN8811H_MD32_DM, dev);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = request_firmware_direct(\u0026fw2, EN8811H_MD32_DSP, dev);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_rel1;\n+\n+\tair_child_bus_lock(child_bus);\n+\n+\tret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,\n+\t\t\t\t\t  EN8811H_FW_CTRL_1_START, nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,\n+\t\t\t\t\t   EN8811H_FW_CTRL_2_LOADING,\n+\t\t\t\t\t   EN8811H_FW_CTRL_2_LOADING, nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1, nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2, nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,\n+\t\t\t\t\t   EN8811H_FW_CTRL_2_LOADING, 0,\n+\t\t\t\t\t   nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_en8811h_wait_mcu_ready(mdiodev, nested);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,\n+\t\t\t\t\t fw_version, nested);\n+\n+air_fw_download_out:\n+\tair_child_bus_unlock(child_bus);\n+\trelease_firmware(fw2);\n+\n+air_fw_download_rel1:\n+\trelease_firmware(fw1);\n+\n+\t/* No error print: callers log on their own terms, and a poller\n+\t * would repeat it on every retry.\n+\t */\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);\n+\n int air_phy_read_page(struct phy_device *phydev)\n {\n \treturn __phy_read(phydev, AIR_EXT_PAGE_ACCESS);\ndiff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h\nindex 01bb32e7c7c95..5f6f09446008c 100644\n--- a/drivers/net/phy/air_phy_lib.h\n+++ b/drivers/net/phy/air_phy_lib.h\n@@ -8,14 +8,22 @@\n #ifndef __AIR_PHY_LIB_H\n #define __AIR_PHY_LIB_H\n \n+#include \u003clinux/mdio/mdio-airoha-en8811h.h\u003e\n #include \u003clinux/phy.h\u003e\n \n+struct firmware;\n+\n #define AIR_EXT_PAGE_ACCESS\t\t0x1f\n \n #define AIR_PHY_PAGE_STANDARD\t\t0x0000\n #define AIR_PHY_PAGE_EXTENDED_1\t\t0x0001\n #define AIR_PHY_PAGE_EXTENDED_4\t\t0x0004\n \n+/* Bytes written between bus releases, so the other PHYs on it get a turn.\n+ * At a 2.5 MHz MDC this size holds the bus for about 30 ms.\n+ */\n+#define AIR_FW_CHUNK_BYTES\t\t2304\n+\n /* MII Registers Page 4*/\n #define AIR_BPBUS_MODE\t\t\t0x10\n #define   AIR_BPBUS_MODE_ADDR_FIXED\t\t0x0000\n@@ -29,6 +37,20 @@\n #define AIR_BPBUS_RD_DATA_HIGH\t\t0x17\n #define AIR_BPBUS_RD_DATA_LOW\t\t0x18\n \n+#define AIR_FW_ADDR_DM\t\t\t0x00000000\n+#define AIR_FW_ADDR_DSP\t\t\t0x00100000\n+\n+#define EN8811H_FW_CTRL_1\t\t0x0f0018\n+#define   EN8811H_FW_CTRL_1_START\t\t0x0\n+#define   EN8811H_FW_CTRL_1_FINISH\t\t0x1\n+#define EN8811H_FW_CTRL_2\t\t0x800000\n+#define   EN8811H_FW_CTRL_2_LOADING\t\tBIT(11)\n+\n+#define EN8811H_PHY_FW_STATUS\t\t0x8009\n+#define   EN8811H_PHY_READY\t\t\t0x02\n+\n+#define EN8811H_FW_VERSION\t\t0x3b3c\n+\n int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,\n \t\t\t\tu32 mask, u32 set);\n int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,\n@@ -38,4 +60,9 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,\n int air_phy_read_page(struct phy_device *phydev);\n int air_phy_write_page(struct phy_device *phydev, int page);\n \n+/* nested: the caller already holds the child bus this chip publishes. */\n+int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,\n+\t\t     const struct firmware *fw, bool nested);\n+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested);\n+\n #endif /* __AIR_PHY_LIB_H */\ndiff --git a/include/linux/mdio/mdio-airoha-en8811h.h b/include/linux/mdio/mdio-airoha-en8811h.h\nnew file mode 100644\nindex 0000000000000..383ff4a7f6a17\n--- /dev/null\n+++ b/include/linux/mdio/mdio-airoha-en8811h.h\n@@ -0,0 +1,29 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * Copyright (C) 2023, 2026 Airoha Technology Corp.\n+ * Copyright (C) 2026 Aleksei Sviridkin \u003cf@lex.la\u003e\n+ */\n+\n+#ifndef __LINUX_MDIO_AIROHA_EN8811H_H\n+#define __LINUX_MDIO_AIROHA_EN8811H_H\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct mdio_device;\n+struct mii_bus;\n+\n+#define EN8811H_MD32_DM\t\t\t\"airoha/EthMD32.dm.bin\"\n+#define EN8811H_MD32_DSP\t\t\"airoha/EthMD32.DSP.bin\"\n+\n+/* child_bus is the bus this chip publishes below mdiodev, or NULL.\n+ *\n+ * Returns 1 when the firmware runs, 0 when the MD32 is still in its\n+ * bootloader, and negative on a failed status read.\n+ */\n+int air_en8811h_mcu_running(struct mdio_device *mdiodev,\n+\t\t\t    struct mii_bus *child_bus);\n+/* Returns 1 when firmware was already running and was left in place. */\n+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,\n+\t\t\t    struct mii_bus *child_bus);\n+\n+#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */\n"
}

2/1 2026/09/12 13:09 agent patch-evaluator
3m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new MDIO MCU driver (mdio-airoha-en8811h) and refactors firmware loading in the Airoha EN8811H PHY driver and library (air_en8811h, air_phy_lib). These drivers target the physical Airoha EN8811H 2.5G Ethernet PHY ASIC and its MD32 microcontroller core via MDIO and Device Tree. The underlying hardware is not emulated in standard QEMU/GCE virtualized environments on amd64, rendering the modified code structurally unreachable for syzkaller fuzzing.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 1fcc2c8d13d1e9f52a5e2410ef2faa89e0af7b4d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Sep 12 13:09:05 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
new file mode 100644
index 0000000000000..7c204b5f29c94
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/airoha,en8811h-mcu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Airoha EN8811H PHY MCU
+
+maintainers:
+  - Aleksei Sviridkin <f@lex.la>
+
+description: |
+  The Airoha EN8811H 2.5G PHY is built around an MD32 core that executes
+  firmware from volatile RAM. The chip answers an MDIO address from
+  power-on, but the PHY function does not exist until firmware has been
+  loaded over MDIO, and it disappears again if that RAM is cleared.
+
+  This node describes the MD32 core. The PHY function answers on a child
+  MDIO bus below it, at the same address the core occupies on the parent
+  bus, and only while firmware is running.
+
+  The reset line is wired to the core, and asserting it clears the
+  firmware RAM. It therefore belongs to this node: a reset described on
+  the PHY node would be pulsed by that node's own lifecycle, whatever
+  the core is doing.
+
+  A board that has firmware in hand while its MDIO bus is enumerated can
+  describe the PHY on its own, as airoha,en8811h.yaml does, and that form
+  stays valid. This binding is for the boards where firmware arrives
+  later, so that there is no PHY to describe until the core has it.
+
+properties:
+  $nodename:
+    pattern: "^mcu@[0-9a-f]+$"
+
+  compatible:
+    const: airoha,en8811h-mcu
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      The GPIO phandle and specifier for the reset signal wired to the
+      MD32 core.
+
+  reset-assert-us:
+    description:
+      Delay after the reset was asserted in microseconds. If this
+      property is missing the delay will be skipped.
+
+  reset-deassert-us:
+    description:
+      Delay after the reset was deasserted in microseconds. If this
+      property is missing the delay will be skipped.
+
+  mdio:
+    $ref: mdio.yaml#
+    description:
+      The bus the PHY function answers on while firmware is running, at
+      the same address this node occupies on the parent bus. Only that
+      one address is passed through. The bus-level reset properties
+      mdio.yaml allows are refused here, because the MDIO core asserts
+      such a line when the bus registers, and that erases the firmware
+      this PHY is made of.
+
+    properties:
+      reset-gpios: false
+      reset-delay-us: false
+      reset-post-delay-us: false
+
+    patternProperties:
+      '^ethernet-phy@[0-9a-f]+$':
+        type: object
+        $ref: airoha,en8811h.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          reset-gpios: false
+          reset-assert-us: false
+          reset-deassert-us: false
+          resets: false
+          reset-names: false
+
+        description:
+          The reset line belongs to the MCU node above. A reset described
+          here would be pulsed by this node's own lifecycle, and that erases
+          the firmware that created this PHY.
+
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - mdio
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        mcu@d {
+            compatible = "airoha,en8811h-mcu";
+            reg = <0xd>;
+            reset-gpios = <&pio 14 GPIO_ACTIVE_LOW>;
+            reset-assert-us = <10000>;
+            reset-deassert-us = <20000>;
+
+            mdio {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                ethernet-phy@d {
+                    compatible = "ethernet-phy-id03a2.a411";
+                    reg = <0xd>;
+                    interrupts-extended = <&pio 15 IRQ_TYPE_LEVEL_LOW>;
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index b23fb6f2f4ef4..3bd86df2be571 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -750,6 +750,14 @@ S:	Supported
 F:	fs/aio.c
 F:	include/linux/*aio*.h
 
+AIROHA EN8811H MCU MDIO DRIVER
+M:	Aleksei Sviridkin <f@lex.la>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
+F:	drivers/net/mdio/mdio-airoha-en8811h.c
+F:	include/linux/mdio/mdio-airoha-en8811h.h
+
 AIROHA ETHERNET DRIVER
 M:	Lorenzo Bianconi <lorenzo@kernel.org>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig
index d44278f26fabe..57c83bfd40905 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -29,6 +29,19 @@ config MDIO_AIROHA
 	  This module provides a driver for the MDIO busses found in the
 	  Airoha AN7583 SoC's.
 
+config MDIO_AIROHA_EN8811H
+	tristate "Airoha EN8811H MDIO device support"
+	depends on OF_MDIO
+	depends on FW_LOADER
+	select AIR_NET_PHYLIB
+	imply AIR_EN8811H_PHY
+	help
+	  This module provides a driver for the Airoha EN8811H, which is an
+	  MD32 microcontroller until firmware is downloaded into it and only
+	  becomes an Ethernet PHY afterwards. The driver downloads that
+	  firmware once it becomes readable, or leaves in place firmware a
+	  bootloader already started.
+
 config MDIO_SUN4I
 	tristate "Allwinner sun4i MDIO interface support"
 	depends on ARCH_SUNXI || COMPILE_TEST
diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
index 0485867460262..06d096675daca 100644
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_FWNODE_MDIO)	+= fwnode_mdio.o
 obj-$(CONFIG_OF_MDIO)		+= of_mdio.o
 
 obj-$(CONFIG_MDIO_AIROHA)		+= mdio-airoha.o
+obj-$(CONFIG_MDIO_AIROHA_EN8811H)	+= mdio-airoha-en8811h.o
 obj-$(CONFIG_MDIO_ASPEED)		+= mdio-aspeed.o
 obj-$(CONFIG_MDIO_BCM_IPROC)		+= mdio-bcm-iproc.o
 obj-$(CONFIG_MDIO_BCM_UNIMAC)		+= mdio-bcm-unimac.o
diff --git a/drivers/net/mdio/mdio-airoha-en8811h.c b/drivers/net/mdio/mdio-airoha-en8811h.c
new file mode 100644
index 0000000000000..7785525c57a64
--- /dev/null
+++ b/drivers/net/mdio/mdio-airoha-en8811h.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Airoha EN8811H MDIO device driver
+ *
+ * The EN8811H is an MD32 microcontroller until firmware is downloaded into
+ * it, and only then an Ethernet PHY.
+ *
+ * Copyright (C) 2026 Aleksei Sviridkin <f@lex.la>
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/workqueue.h>
+
+#include <linux/mdio/mdio-airoha-en8811h.h>
+
+#define EN8811H_FW_POLL_MIN_MS	1000
+#define EN8811H_FW_POLL_MAX_MS	30000
+#define EN8811H_FW_WARN_MS	60000
+
+struct en8811h_mcu {
+	struct mdio_device *mdiodev;
+	struct gpio_desc *reset_gpio;
+	struct delayed_work fw_poll;
+	struct mii_bus *bus;
+	unsigned int poll_ms;
+	unsigned int waited_ms;
+	u32 fw_version;
+	bool warned;
+	bool fw_running;
+};
+
+static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum)
+{
+	struct en8811h_mcu *mcu = bus->priv;
+
+	if (addr != mcu->mdiodev->addr)
+		return -ENODEV;
+
+	return mdiobus_read_nested(mcu->mdiodev->bus, addr, regnum);
+}
+
+static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+	struct en8811h_mcu *mcu = bus->priv;
+
+	if (addr != mcu->mdiodev->addr)
+		return -ENODEV;
+
+	return mdiobus_write_nested(mcu->mdiodev->bus, addr, regnum, val);
+}
+
+static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad,
+				int regnum)
+{
+	struct en8811h_mcu *mcu = bus->priv;
+
+	if (addr != mcu->mdiodev->addr)
+		return -ENODEV;
+
+	return mdiobus_c45_read_nested(mcu->mdiodev->bus, addr, devad, regnum);
+}
+
+static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad,
+				 int regnum, u16 val)
+{
+	struct en8811h_mcu *mcu = bus->priv;
+
+	if (addr != mcu->mdiodev->addr)
+		return -ENODEV;
+
+	return mdiobus_c45_write_nested(mcu->mdiodev->bus, addr, devad, regnum,
+					val);
+}
+
+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)
+{
+	struct mii_bus *parent = mcu->mdiodev->bus;
+	struct device *dev = &mcu->mdiodev->dev;
+	struct device_node *np;
+	struct mii_bus *bus;
+	int ret;
+
+	np = of_get_child_by_name(dev->of_node, "mdio");
+	if (!np)
+		return -ENODEV;
+
+	/* Not devm: this is retried, and a devm bus would only be freed at
+	 * detach.
+	 */
+	bus = mdiobus_alloc();
+	if (!bus) {
+		of_node_put(np);
+		return -ENOMEM;
+	}
+
+	bus->name = "airoha-en8811h";
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+	bus->priv = mcu;
+	bus->parent = dev;
+	if (parent->read && parent->write) {
+		bus->read = en8811h_mcu_read;
+		bus->write = en8811h_mcu_write;
+	}
+	if (parent->read_c45 && parent->write_c45) {
+		bus->read_c45 = en8811h_mcu_read_c45;
+		bus->write_c45 = en8811h_mcu_write_c45;
+	}
+
+	ret = of_mdiobus_register(bus, np);
+	of_node_put(np);
+	if (!ret && !mdiobus_get_phy(bus, mcu->mdiodev->addr)) {
+		/* An ID read that failed leaves the bus registered and the
+		 * PHY absent; of_mdiobus_register() returns 0 either way.
+		 */
+		mdiobus_unregister(bus);
+		ret = -ENODEV;
+	}
+	if (ret) {
+		mdiobus_free(bus);
+		return ret;
+	}
+
+	mcu->bus = bus;
+	return 0;
+}
+
+static void en8811h_mcu_fw_poll(struct work_struct *work)
+{
+	struct en8811h_mcu *mcu = container_of(to_delayed_work(work),
+					       struct en8811h_mcu, fw_poll);
+	struct device *dev = &mcu->mdiodev->dev;
+	int cached, ret;
+
+	if (!mcu->fw_running) {
+		ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version,
+					      mcu->bus);
+		if (ret < 0)
+			goto retry;
+
+		dev_dbg(dev, "firmware %08x running after %ums\n",
+			mcu->fw_version, mcu->waited_ms);
+		cached = firmware_request_cache(dev, EN8811H_MD32_DM);
+		ret = firmware_request_cache(dev, EN8811H_MD32_DSP) ?: cached;
+		if (ret)
+			dev_warn(dev, "not cached, resume will read the files off a filesystem: %pe\n",
+				 ERR_PTR(ret));
+		mcu->fw_running = true;
+		mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+		mcu->waited_ms = 0;
+		mcu->warned = false;
+	}
+
+	/* Resume re-runs the download, so the bus can already be here. */
+	ret = mcu->bus ? 0 : en8811h_mcu_bus_register(mcu);
+	if (!ret)
+		return;
+
+retry:
+	if (!mcu->warned && mcu->waited_ms >= EN8811H_FW_WARN_MS) {
+		if (!mcu->fw_running)
+			dev_warn(dev, "no firmware after %ums of waiting for %s and %s: %pe\n",
+				 mcu->waited_ms, EN8811H_MD32_DM,
+				 EN8811H_MD32_DSP, ERR_PTR(ret));
+		else
+			dev_warn(dev, "still no PHY at address %d, %ums after the firmware started: %pe\n",
+				 mcu->mdiodev->addr, mcu->waited_ms,
+				 ERR_PTR(ret));
+		mcu->warned = true;
+	}
+
+	/* Registering again repeats the MDIO core's message and its uevents,
+	 * so this phase gives up - except on a deferral, which only a retry
+	 * resolves and which deferred_probe_timeout can make permanent.
+	 */
+	if (mcu->fw_running && ret != -EPROBE_DEFER &&
+	    mcu->waited_ms >= EN8811H_FW_WARN_MS)
+		return;
+
+	/* Count the sleep ahead: the first run was immediate. */
+	mcu->waited_ms += mcu->poll_ms;
+	queue_delayed_work(system_freezable_wq, &mcu->fw_poll,
+			   msecs_to_jiffies(mcu->poll_ms));
+	mcu->poll_ms = min(mcu->poll_ms * 2, EN8811H_FW_POLL_MAX_MS);
+}
+
+/* The firmware lives in volatile RAM: no reset while the MD32 reports ready. */
+static void en8811h_mcu_reset_unless_running(struct en8811h_mcu *mcu)
+{
+	struct mdio_device *mdiodev = mcu->mdiodev;
+	struct device *dev = &mdiodev->dev;
+	u32 assert_us = 0, deassert_us = 0;
+	int ret;
+
+	ret = air_en8811h_mcu_running(mdiodev, mcu->bus);
+	if (ret > 0) {
+		dev_dbg(dev, "MD32 already running, leaving reset alone\n");
+		return;
+	}
+
+	if (!mcu->reset_gpio)
+		return;
+
+	/* A failed read does not mean the firmware is gone: leave a
+	 * deasserted line alone, and clear an asserted one before giving up.
+	 */
+	if (ret < 0 && gpiod_get_value_cansleep(mcu->reset_gpio) <= 0) {
+		dev_dbg(dev, "MD32 state unknown (%d), leaving reset alone\n",
+			ret);
+		return;
+	}
+
+	device_property_read_u32(dev, "reset-assert-us", &assert_us);
+	device_property_read_u32(dev, "reset-deassert-us", &deassert_us);
+
+	ret = gpiod_direction_output(mcu->reset_gpio, 1);
+	if (ret) {
+		dev_warn(dev, "reset not asserted: %pe\n", ERR_PTR(ret));
+		return;
+	}
+
+	if (assert_us)
+		fsleep(assert_us);
+
+	gpiod_set_value_cansleep(mcu->reset_gpio, 0);
+	if (deassert_us)
+		fsleep(deassert_us);
+}
+
+static int en8811h_mcu_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct en8811h_mcu *mcu;
+	struct device_node *np;
+	unsigned int phys = 0;
+
+	mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
+	if (!mcu)
+		return -ENOMEM;
+
+	mcu->mdiodev = mdiodev;
+	mdiodev_set_drvdata(mdiodev, mcu);
+
+	/* Registration needs this only once the firmware runs, but a DT
+	 * hole should fail the bind now, not as a work-item error later.
+	 */
+	np = of_get_child_by_name(dev->of_node, "mdio");
+	if (!np || !of_device_is_available(np)) {
+		of_node_put(np);
+		return dev_err_probe(dev, -ENODEV,
+				     "no usable mdio node describing the PHY\n");
+	}
+
+	/* Only the address this core occupies on the parent bus is passed
+	 * through, so a node at any other one will never respond.
+	 */
+	for_each_available_child_of_node_scoped(np, child) {
+		u32 addr;
+
+		if (of_property_read_u32(child, "reg", &addr))
+			continue;
+		if (addr != mdiodev->addr) {
+			dev_err(dev, "%pOF: address %u is not answered here, only %u\n",
+				child, addr, mdiodev->addr);
+			of_node_put(np);
+			return -EINVAL;
+		}
+		phys++;
+	}
+	of_node_put(np);
+
+	if (!phys)
+		return dev_err_probe(dev, -ENODEV,
+				     "mdio node describes no PHY\n");
+
+	/* A truncated bus name loses the address that makes it unique. */
+	if (strlen(dev_name(dev)) >= MII_BUS_ID_SIZE)
+		return dev_err_probe(dev, -ENAMETOOLONG,
+				     "name does not fit an MDIO bus id\n");
+
+	/* The core claims reset-gpios only for devices flagged as PHYs. */
+	mcu->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
+	if (IS_ERR(mcu->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(mcu->reset_gpio),
+				     "failed to get reset GPIO\n");
+
+	if (mcu->reset_gpio)
+		gpiod_set_consumer_name(mcu->reset_gpio, "EN8811H reset");
+
+	en8811h_mcu_reset_unless_running(mcu);
+
+	mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+	INIT_DELAYED_WORK(&mcu->fw_poll, en8811h_mcu_fw_poll);
+	/* Freezable: neither the file lookup nor the download may land on
+	 * a suspending bus.
+	 */
+	queue_delayed_work(system_freezable_wq, &mcu->fw_poll, 0);
+
+	return 0;
+}
+
+static void en8811h_mcu_remove(struct mdio_device *mdiodev)
+{
+	struct en8811h_mcu *mcu = mdiodev_get_drvdata(mdiodev);
+
+	cancel_delayed_work_sync(&mcu->fw_poll);
+	if (mcu->bus) {
+		mdiobus_unregister(mcu->bus);
+		mdiobus_free(mcu->bus);
+	}
+}
+
+static int en8811h_mcu_resume(struct device *dev)
+{
+	struct en8811h_mcu *mcu = dev_get_drvdata(dev);
+	int ret;
+
+	/* Nothing to redo: the poll is armed and thaws with everything else. */
+	if (!mcu->fw_running)
+		return 0;
+
+	/* Synchronous: the poll's workqueue is freezable and thaws only
+	 * after the resume callbacks have run.
+	 */
+	en8811h_mcu_reset_unless_running(mcu);
+	ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version, mcu->bus);
+	if (ret < 0) {
+		dev_err(dev, "firmware not restored, reloading: %pe\n",
+			ERR_PTR(ret));
+		mcu->fw_running = false;
+		mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+		mcu->waited_ms = 0;
+		mcu->warned = false;
+		/* The poll may already be pending on a long backoff, and
+		 * queue_delayed_work() would leave that timer alone.
+		 */
+		mod_delayed_work(system_freezable_wq, &mcu->fw_poll, 0);
+	}
+
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(en8811h_mcu_pm_ops, NULL, en8811h_mcu_resume);
+
+static const struct of_device_id en8811h_mcu_of_match[] = {
+	{ .compatible = "airoha,en8811h-mcu" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, en8811h_mcu_of_match);
+
+static struct mdio_driver en8811h_mcu_driver = {
+	.probe = en8811h_mcu_probe,
+	.remove = en8811h_mcu_remove,
+	.mdiodrv.driver = {
+		.name = "airoha-en8811h-mcu",
+		.of_match_table = en8811h_mcu_of_match,
+		.pm = pm_sleep_ptr(&en8811h_mcu_pm_ops),
+		/* No safe teardown under an attached PHY. */
+		.suppress_bind_attrs = true,
+	},
+};
+
+mdio_module_driver(en8811h_mcu_driver);
+
+MODULE_FIRMWARE(EN8811H_MD32_DM);
+MODULE_FIRMWARE(EN8811H_MD32_DSP);
+
+MODULE_DESCRIPTION("Airoha EN8811H MDIO device driver");
+MODULE_AUTHOR("Aleksei Sviridkin <f@lex.la>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
index 16a9171cb8384..940379e2737b4 100644
--- a/drivers/net/phy/air_en8811h.c
+++ b/drivers/net/phy/air_en8811h.c
@@ -20,21 +20,15 @@
 #include <linux/bitfield.h>
 #include <linux/property.h>
 #include <linux/wordpart.h>
-#include <linux/unaligned.h>
 
 #include "air_phy_lib.h"
 
 #define EN8811H_PHY_ID		0x03a2a411
 #define AN8811HB_PHY_ID		0xc0ff04a0
 
-#define EN8811H_MD32_DM		"airoha/EthMD32.dm.bin"
-#define EN8811H_MD32_DSP	"airoha/EthMD32.DSP.bin"
 #define AN8811HB_MD32_DM	"airoha/an8811hb/EthMD32_CRC.DM.bin"
 #define AN8811HB_MD32_DSP	"airoha/an8811hb/EthMD32_CRC.DSP.bin"
 
-#define AIR_FW_ADDR_DM	0x00000000
-#define AIR_FW_ADDR_DSP	0x00100000
-
 /* MII Registers */
 #define AIR_AUX_CTRL_STATUS		0x1d
 #define   AIR_AUX_CTRL_STATUS_SPEED_MASK	GENMASK(4, 2)
@@ -44,8 +38,6 @@
 #define   AIR_AUX_CTRL_STATUS_SPEED_2500	0xc
 
 /* Registers on MDIO_MMD_VEND1 */
-#define EN8811H_PHY_FW_STATUS		0x8009
-#define   EN8811H_PHY_READY			0x02
 
 #define AIR_PHY_MCU_CMD_0		0x800b
 #define AIR_PHY_MCU_CMD_1		0x800c
@@ -108,8 +100,6 @@
 #define EN8811H_2P5G_LPA		0x3b30
 #define   EN8811H_2P5G_LPA_2P5G			BIT(0)
 
-#define EN8811H_FW_VERSION		0x3b3c
-
 #define EN8811H_POLARITY		0xca0f8
 #define   EN8811H_POLARITY_TX_NORMAL		BIT(0)
 #define   EN8811H_POLARITY_RX_REVERSE		BIT(1)
@@ -122,12 +112,6 @@
 #define EN8811H_CLK_CGM			0xcf958
 #define   EN8811H_CLK_CGM_CKO			BIT(26)
 
-#define EN8811H_FW_CTRL_1		0x0f0018
-#define   EN8811H_FW_CTRL_1_START		0x0
-#define   EN8811H_FW_CTRL_1_FINISH		0x1
-#define EN8811H_FW_CTRL_2		0x800000
-#define EN8811H_FW_CTRL_2_LOADING		BIT(11)
-
 #define AN8811HB_CRC_PM_SET1		0xf020c
 #define AN8811HB_CRC_PM_MON2		0xf0218
 #define AN8811HB_CRC_PM_MON3		0xf021c
@@ -270,86 +254,15 @@ static int __air_pbus_reg_write(struct mdio_device *mdiodev,
 			       upper_16_bits(pbus_data));
 }
 
-static int __air_write_buf(struct phy_device *phydev, u32 address,
-			   const struct firmware *fw)
+static int en8811h_wait_mcu_ready(struct phy_device *phydev)
 {
-	unsigned int offset;
 	int ret;
-	u16 val;
-
-	ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR);
-	if (ret < 0)
-		return ret;
-
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
-			  upper_16_bits(address));
-	if (ret < 0)
-		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
-			  lower_16_bits(address));
+	ret = air_en8811h_wait_mcu_ready(&phydev->mdio, false);
 	if (ret < 0)
-		return ret;
-
-	for (offset = 0; offset < fw->size; offset += 4) {
-		val = get_unaligned_le16(&fw->data[offset + 2]);
-		ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val);
-		if (ret < 0)
-			return ret;
-
-		val = get_unaligned_le16(&fw->data[offset]);
-		ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val);
-		if (ret < 0)
-			return ret;
-	}
+		phydev_err(phydev, "MCU not ready: %pe\n", ERR_PTR(ret));
 
-	return 0;
-}
-
-static int air_write_buf(struct phy_device *phydev, u32 address,
-			 const struct firmware *fw)
-{
-	int saved_page;
-	int ret = 0;
-
-	if (fw->size % 4) {
-		phydev_err(phydev, "firmware size %zu is not a multiple of 4\n",
-			   fw->size);
-		return -EINVAL;
-	}
-
-	saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
-
-	if (saved_page >= 0) {
-		ret = __air_write_buf(phydev, address, fw);
-		if (ret < 0)
-			phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
-				   address, ret);
-	}
-
-	return phy_restore_page(phydev, saved_page, ret);
-}
-
-static int en8811h_wait_mcu_ready(struct phy_device *phydev)
-{
-	int ret, reg_value;
-
-	ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
-					 EN8811H_FW_CTRL_1_FINISH);
-	if (ret)
-		return ret;
-
-	/* Because of mdio-lock, may have to wait for multiple loads */
-	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
-					EN8811H_PHY_FW_STATUS, reg_value,
-					reg_value == EN8811H_PHY_READY,
-					20000, 7500000, true);
-	if (ret) {
-		phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value);
-		return -ENODEV;
-	}
-
-	return 0;
+	return ret;
 }
 
 static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
@@ -411,7 +324,7 @@ static int an8811hb_load_file(struct phy_device *phydev, const char *name,
 	if (ret < 0)
 		return ret;
 
-	ret = air_write_buf(phydev, address,  fw);
+	ret = air_fw_write_buf(&phydev->mdio, address, fw, false);
 	release_firmware(fw);
 	return ret;
 }
@@ -507,56 +420,16 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
 
 static int en8811h_load_firmware(struct phy_device *phydev)
 {
-	struct device *dev = &phydev->mdio.dev;
-	const struct firmware *fw1, *fw2;
+	struct en8811h_priv *priv = phydev->priv;
 	int ret;
 
-	ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev);
-	if (ret < 0)
-		return ret;
-
-	ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev);
-	if (ret < 0)
-		goto en8811h_load_firmware_rel1;
-
-	ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
-					 EN8811H_FW_CTRL_1_START);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
-					  EN8811H_FW_CTRL_2_LOADING,
-					  EN8811H_FW_CTRL_2_LOADING);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	ret = air_write_buf(phydev, AIR_FW_ADDR_DM,  fw1);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
-					  EN8811H_FW_CTRL_2_LOADING, 0);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	ret = en8811h_wait_mcu_ready(phydev);
-	if (ret < 0)
-		goto en8811h_load_firmware_out;
-
-	en8811h_print_fw_version(phydev);
-
-en8811h_load_firmware_out:
-	release_firmware(fw2);
-
-en8811h_load_firmware_rel1:
-	release_firmware(fw1);
-
+	ret = air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version,
+				      NULL);
 	if (ret < 0)
 		phydev_err(phydev, "Load firmware failed: %d\n", ret);
+	else
+		phydev_info(phydev, "MD32 firmware version: %08x\n",
+			    priv->firmware_version);
 
 	return ret;
 }
@@ -1156,8 +1029,8 @@ static int en8811h_probe(struct phy_device *phydev)
 	if (ret < 0)
 		return ret;
 
-	/* mcu has just restarted after firmware load */
-	priv->mcu_needs_restart = false;
+	/* Firmware that was already running was never restarted here. */
+	priv->mcu_needs_restart = (ret == 1);
 
 	/* MDIO_DEVS1/2 empty, so set mmds_present bits here */
 	phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c
index 5141db19fa5ee..5902a6ba3643d 100644
--- a/drivers/net/phy/air_phy_lib.c
+++ b/drivers/net/phy/air_phy_lib.c
@@ -8,37 +8,42 @@
  */
 
 #include <linux/export.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
 #include <linux/module.h>
 #include <linux/phy.h>
+#include <linux/unaligned.h>
 #include <linux/wordpart.h>
 
 #include "air_phy_lib.h"
 
-static int __air_buckpbus_reg_read(struct phy_device *phydev,
+static int __air_buckpbus_reg_read(struct mdio_device *mdiodev,
 				   u32 pbus_address, u32 *pbus_data)
 {
 	int pbus_data_low, pbus_data_high;
 	int ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,
+			      AIR_BPBUS_MODE_ADDR_FIXED);
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
-			  upper_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH,
+			      upper_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
-			  lower_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW,
+			      lower_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
+	pbus_data_high = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH);
 	if (pbus_data_high < 0)
 		return pbus_data_high;
 
-	pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
+	pbus_data_low = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW);
 	if (pbus_data_low < 0)
 		return pbus_data_low;
 
@@ -46,64 +51,66 @@ static int __air_buckpbus_reg_read(struct phy_device *phydev,
 	return 0;
 }
 
-static int __air_buckpbus_reg_write(struct phy_device *phydev,
+static int __air_buckpbus_reg_write(struct mdio_device *mdiodev,
 				    u32 pbus_address, u32 pbus_data)
 {
 	int ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,
+			      AIR_BPBUS_MODE_ADDR_FIXED);
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
-			  upper_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,
+			      upper_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
-			  lower_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,
+			      lower_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
-			  upper_16_bits(pbus_data));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH,
+			      upper_16_bits(pbus_data));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
-			  lower_16_bits(pbus_data));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW,
+			      lower_16_bits(pbus_data));
 	if (ret < 0)
 		return ret;
 
 	return 0;
 }
 
-static int __air_buckpbus_reg_modify(struct phy_device *phydev,
+static int __air_buckpbus_reg_modify(struct mdio_device *mdiodev,
 				     u32 pbus_address, u32 mask, u32 set)
 {
 	int pbus_data_low, pbus_data_high;
 	u32 pbus_data_old, pbus_data_new;
 	int ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,
+			      AIR_BPBUS_MODE_ADDR_FIXED);
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
-			  upper_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH,
+			      upper_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
-			  lower_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW,
+			      lower_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
+	pbus_data_high = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH);
 	if (pbus_data_high < 0)
 		return pbus_data_high;
 
-	pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
+	pbus_data_low = __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW);
 	if (pbus_data_low < 0)
 		return pbus_data_low;
 
@@ -112,23 +119,23 @@ static int __air_buckpbus_reg_modify(struct phy_device *phydev,
 	if (pbus_data_new == pbus_data_old)
 		return 0;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
-			  upper_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,
+			      upper_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
-			  lower_16_bits(pbus_address));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,
+			      lower_16_bits(pbus_address));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
-			  upper_16_bits(pbus_data_new));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH,
+			      upper_16_bits(pbus_data_new));
 	if (ret < 0)
 		return ret;
 
-	ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
-			  lower_16_bits(pbus_data_new));
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW,
+			      lower_16_bits(pbus_data_new));
 	if (ret < 0)
 		return ret;
 
@@ -144,7 +151,8 @@ int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
 	saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
 
 	if (saved_page >= 0) {
-		ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);
+		ret = __air_buckpbus_reg_read(&phydev->mdio, pbus_address,
+					      pbus_data);
 		if (ret < 0)
 			phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
 				   pbus_address, ret);
@@ -163,7 +171,7 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
 	saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
 
 	if (saved_page >= 0) {
-		ret = __air_buckpbus_reg_write(phydev, pbus_address,
+		ret = __air_buckpbus_reg_write(&phydev->mdio, pbus_address,
 					       pbus_data);
 		if (ret < 0)
 			phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
@@ -183,8 +191,8 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
 	saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
 
 	if (saved_page >= 0) {
-		ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,
-						set);
+		ret = __air_buckpbus_reg_modify(&phydev->mdio, pbus_address,
+						mask, set);
 		if (ret < 0)
 			phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
 				   pbus_address, ret);
@@ -194,6 +202,362 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
 }
 EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify);
 
+static int __air_write_buf(struct mdio_device *mdiodev, u32 address,
+			   const u8 *data, size_t len)
+{
+	unsigned int offset;
+	int ret;
+	u16 val;
+
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_MODE,
+			      AIR_BPBUS_MODE_ADDR_INCR);
+	if (ret < 0)
+		return ret;
+
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH,
+			      upper_16_bits(address));
+	if (ret < 0)
+		return ret;
+
+	ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW,
+			      lower_16_bits(address));
+	if (ret < 0)
+		return ret;
+
+	for (offset = 0; offset < len; offset += 4) {
+		val = get_unaligned_le16(&data[offset + 2]);
+		ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH, val);
+		if (ret < 0)
+			return ret;
+
+		val = get_unaligned_le16(&data[offset]);
+		ret = __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW, val);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* phy_select_page() needs a phy_device, which does not exist yet. */
+static int __air_mdio_select_page(struct mdio_device *mdiodev, int page)
+{
+	int saved_page, ret;
+
+	saved_page = __mdiodev_read(mdiodev, AIR_EXT_PAGE_ACCESS);
+	if (saved_page < 0)
+		return saved_page;
+
+	if (saved_page != page) {
+		ret = __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS, page);
+		if (ret < 0)
+			return ret;
+	}
+
+	return saved_page;
+}
+
+static int __air_mdio_restore_page(struct mdio_device *mdiodev,
+				   int saved_page, int page, int ret)
+{
+	int restore;
+
+	if (saved_page != page) {
+		restore = __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS,
+					  saved_page);
+		if (ret >= 0 && restore < 0)
+			ret = restore;
+	}
+
+	return ret;
+}
+
+static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)
+{
+	if (nested)
+		mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+	else
+		mutex_lock(&mdiodev->bus->mdio_lock);
+}
+
+/* The PHY below reaches these registers under this lock through
+ * phy_select_page(); the firmware read must stay outside it.
+ */
+static void air_child_bus_lock(struct mii_bus *child_bus)
+{
+	if (child_bus)
+		mutex_lock(&child_bus->mdio_lock);
+}
+
+static void air_child_bus_unlock(struct mii_bus *child_bus)
+{
+	if (child_bus)
+		mutex_unlock(&child_bus->mdio_lock);
+}
+
+int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
+		     const struct firmware *fw, bool nested)
+{
+	size_t chunk, done = 0;
+	int saved_page, ret;
+
+	if (fw->size % 4) {
+		dev_err(&mdiodev->dev, "firmware size %zu is not a multiple of 4\n",
+			fw->size);
+		return -EINVAL;
+	}
+
+	while (done < fw->size) {
+		chunk = min_t(size_t, fw->size - done, AIR_FW_CHUNK_BYTES);
+
+		air_mdiodev_lock(mdiodev, nested);
+
+		saved_page = __air_mdio_select_page(mdiodev,
+						    AIR_PHY_PAGE_EXTENDED_4);
+		if (saved_page < 0) {
+			ret = saved_page;
+		} else {
+			ret = __air_write_buf(mdiodev, address + done,
+					      fw->data + done, chunk);
+			ret = __air_mdio_restore_page(mdiodev, saved_page,
+						      AIR_PHY_PAGE_EXTENDED_4,
+						      ret);
+		}
+
+		mutex_unlock(&mdiodev->bus->mdio_lock);
+		if (ret < 0)
+			return ret;
+
+		done += chunk;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(air_fw_write_buf);
+
+static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev,
+				      u32 pbus_address, u32 *pbus_data,
+				      bool nested)
+{
+	int saved_page, ret;
+
+	air_mdiodev_lock(mdiodev, nested);
+
+	saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
+	if (saved_page < 0) {
+		ret = saved_page;
+	} else {
+		ret = __air_buckpbus_reg_read(mdiodev, pbus_address, pbus_data);
+		ret = __air_mdio_restore_page(mdiodev, saved_page,
+					      AIR_PHY_PAGE_EXTENDED_4, ret);
+	}
+
+	mutex_unlock(&mdiodev->bus->mdio_lock);
+	return ret;
+}
+
+static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev,
+				       u32 pbus_address, u32 pbus_data,
+				       bool nested)
+{
+	int saved_page, ret;
+
+	air_mdiodev_lock(mdiodev, nested);
+
+	saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
+	if (saved_page < 0) {
+		ret = saved_page;
+	} else {
+		ret = __air_buckpbus_reg_write(mdiodev, pbus_address,
+					       pbus_data);
+		ret = __air_mdio_restore_page(mdiodev, saved_page,
+					      AIR_PHY_PAGE_EXTENDED_4, ret);
+	}
+
+	mutex_unlock(&mdiodev->bus->mdio_lock);
+	return ret;
+}
+
+static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev,
+					u32 pbus_address, u32 mask, u32 set,
+					bool nested)
+{
+	int saved_page, ret;
+
+	air_mdiodev_lock(mdiodev, nested);
+
+	saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
+	if (saved_page < 0) {
+		ret = saved_page;
+	} else {
+		ret = __air_buckpbus_reg_modify(mdiodev, pbus_address,
+						mask, set);
+		ret = __air_mdio_restore_page(mdiodev, saved_page,
+					      AIR_PHY_PAGE_EXTENDED_4, ret);
+	}
+
+	mutex_unlock(&mdiodev->bus->mdio_lock);
+	return ret;
+}
+
+/* mmd_phy_read() drops the errors from the three writes that select the
+ * register, so a failed selection reads a different one back as success.
+ */
+static int __air_mmd_read(struct mdio_device *mdiodev, u16 devad, u16 regnum)
+{
+	struct mii_bus *bus = mdiodev->bus;
+	int addr = mdiodev->addr;
+	int ret;
+
+	ret = __mdiobus_write(bus, addr, MII_MMD_CTRL, devad);
+	if (ret < 0)
+		return ret;
+
+	ret = __mdiobus_write(bus, addr, MII_MMD_DATA, regnum);
+	if (ret < 0)
+		return ret;
+
+	ret = __mdiobus_write(bus, addr, MII_MMD_CTRL,
+			      devad | MII_MMD_CTRL_NOINCR);
+	if (ret < 0)
+		return ret;
+
+	return __mdiobus_read(bus, addr, MII_MMD_DATA);
+}
+
+static int air_mmd_status_read(struct mdio_device *mdiodev, bool nested)
+{
+	int ret;
+
+	air_mdiodev_lock(mdiodev, nested);
+	ret = __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS);
+	mutex_unlock(&mdiodev->bus->mdio_lock);
+
+	return ret;
+}
+
+int air_en8811h_mcu_running(struct mdio_device *mdiodev,
+			    struct mii_bus *child_bus)
+{
+	int ret;
+
+	air_child_bus_lock(child_bus);
+	ret = air_mmd_status_read(mdiodev, !!child_bus);
+	air_child_bus_unlock(child_bus);
+
+	if (ret < 0)
+		return ret;
+
+	return ret == EN8811H_PHY_READY;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);
+
+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested)
+{
+	int ret, reg_value;
+
+	ret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,
+					  EN8811H_FW_CTRL_1_FINISH, nested);
+	if (ret)
+		return ret;
+
+	/* Because of mdio-lock, may have to wait for multiple loads. A read
+	 * error ends the poll at once, like phy_read_mmd_poll_timeout().
+	 */
+	ret = read_poll_timeout(air_mmd_status_read, reg_value,
+				reg_value < 0 ||
+				reg_value == EN8811H_PHY_READY,
+				20000, 7500000, true, mdiodev, nested);
+	if (reg_value < 0)
+		return reg_value;
+	if (ret) {
+		dev_dbg(&mdiodev->dev, "MCU not ready: 0x%x\n", reg_value);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready);
+
+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,
+			    struct mii_bus *child_bus)
+{
+	struct device *dev = &mdiodev->dev;
+	const struct firmware *fw1, *fw2;
+	bool nested = !!child_bus;
+	int ret;
+
+	ret = air_en8811h_mcu_running(mdiodev, child_bus);
+	if (ret < 0)
+		return ret;
+
+	if (ret) {
+		air_child_bus_lock(child_bus);
+		ret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,
+						 fw_version, nested);
+		air_child_bus_unlock(child_bus);
+		if (ret < 0)
+			return ret;
+
+		return 1;
+	}
+
+	ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev);
+	if (ret < 0)
+		return ret;
+
+	ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev);
+	if (ret < 0)
+		goto air_fw_download_rel1;
+
+	air_child_bus_lock(child_bus);
+
+	ret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,
+					  EN8811H_FW_CTRL_1_START, nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,
+					   EN8811H_FW_CTRL_2_LOADING,
+					   EN8811H_FW_CTRL_2_LOADING, nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1, nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2, nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,
+					   EN8811H_FW_CTRL_2_LOADING, 0,
+					   nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_en8811h_wait_mcu_ready(mdiodev, nested);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	ret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,
+					 fw_version, nested);
+
+air_fw_download_out:
+	air_child_bus_unlock(child_bus);
+	release_firmware(fw2);
+
+air_fw_download_rel1:
+	release_firmware(fw1);
+
+	/* No error print: callers log on their own terms, and a poller
+	 * would repeat it on every retry.
+	 */
+	return ret;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);
+
 int air_phy_read_page(struct phy_device *phydev)
 {
 	return __phy_read(phydev, AIR_EXT_PAGE_ACCESS);
diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h
index 01bb32e7c7c95..5f6f09446008c 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -8,14 +8,22 @@
 #ifndef __AIR_PHY_LIB_H
 #define __AIR_PHY_LIB_H
 
+#include <linux/mdio/mdio-airoha-en8811h.h>
 #include <linux/phy.h>
 
+struct firmware;
+
 #define AIR_EXT_PAGE_ACCESS		0x1f
 
 #define AIR_PHY_PAGE_STANDARD		0x0000
 #define AIR_PHY_PAGE_EXTENDED_1		0x0001
 #define AIR_PHY_PAGE_EXTENDED_4		0x0004
 
+/* Bytes written between bus releases, so the other PHYs on it get a turn.
+ * At a 2.5 MHz MDC this size holds the bus for about 30 ms.
+ */
+#define AIR_FW_CHUNK_BYTES		2304
+
 /* MII Registers Page 4*/
 #define AIR_BPBUS_MODE			0x10
 #define   AIR_BPBUS_MODE_ADDR_FIXED		0x0000
@@ -29,6 +37,20 @@
 #define AIR_BPBUS_RD_DATA_HIGH		0x17
 #define AIR_BPBUS_RD_DATA_LOW		0x18
 
+#define AIR_FW_ADDR_DM			0x00000000
+#define AIR_FW_ADDR_DSP			0x00100000
+
+#define EN8811H_FW_CTRL_1		0x0f0018
+#define   EN8811H_FW_CTRL_1_START		0x0
+#define   EN8811H_FW_CTRL_1_FINISH		0x1
+#define EN8811H_FW_CTRL_2		0x800000
+#define   EN8811H_FW_CTRL_2_LOADING		BIT(11)
+
+#define EN8811H_PHY_FW_STATUS		0x8009
+#define   EN8811H_PHY_READY			0x02
+
+#define EN8811H_FW_VERSION		0x3b3c
+
 int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
 				u32 mask, u32 set);
 int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
@@ -38,4 +60,9 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
 int air_phy_read_page(struct phy_device *phydev);
 int air_phy_write_page(struct phy_device *phydev, int page);
 
+/* nested: the caller already holds the child bus this chip publishes. */
+int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
+		     const struct firmware *fw, bool nested);
+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested);
+
 #endif /* __AIR_PHY_LIB_H */
diff --git a/include/linux/mdio/mdio-airoha-en8811h.h b/include/linux/mdio/mdio-airoha-en8811h.h
new file mode 100644
index 0000000000000..383ff4a7f6a17
--- /dev/null
+++ b/include/linux/mdio/mdio-airoha-en8811h.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2023, 2026 Airoha Technology Corp.
+ * Copyright (C) 2026 Aleksei Sviridkin <f@lex.la>
+ */
+
+#ifndef __LINUX_MDIO_AIROHA_EN8811H_H
+#define __LINUX_MDIO_AIROHA_EN8811H_H
+
+#include <linux/types.h>
+
+struct mdio_device;
+struct mii_bus;
+
+#define EN8811H_MD32_DM			"airoha/EthMD32.dm.bin"
+#define EN8811H_MD32_DSP		"airoha/EthMD32.DSP.bin"
+
+/* child_bus is the bus this chip publishes below mdiodev, or NULL.
+ *
+ * Returns 1 when the firmware runs, 0 when the MD32 is still in its
+ * bootloader, and negative on a failed status read.
+ */
+int air_en8811h_mcu_running(struct mdio_device *mdiodev,
+			    struct mii_bus *child_bus);
+/* Returns 1 when firmware was already running and was left in place. */
+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,
+			    struct mii_bus *child_bus);
+
+#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */

3/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21789
							output: 19
							thoughts: 898
						

Thoughts:
**Analyzing the Airoha EN8811H Patch for Fuzzing Reachability**

Okay, let's break down this patch series. First, I need to understand what it's doing. It looks like it's adding support for the Airoha EN8811H MCU MDIO driver, which means `mdio-airoha-en8811h.c` is the key file. It also looks like there's some firmware loading refactoring in `air_phy_lib.c` and `air_en8811h.c`. I have a good handle on those files.

Now, the crucial question: can this code *actually* be fuzzed, or is it unreachable? The "REACHABILITY IS THE PRIMARY GATE" criteria are what I need to consider. I know the negative criteria: vendor-specific hardware requiring physical cards that aren't emulated and unreachable execution paths. I need to figure out which it is.

The hardware is an Airoha EN8811H. I know this from the `airoha,en8811h-mcu` DT binding. The description is crucial: "The Airoha EN8811H 2.5G PHY is built around an MD32 core that executes firmware from volatile RAM... the PHY function does not exist until firmware has been loaded over MDIO." Okay, so it's an MDIO bus device. The code confirms this, linking the device to the MDIO driver:

```c
static struct mdio_driver en8811h_mcu_driver = {
	.probe = en8811h_mcu_probe,
...
```

and

```c
static struct phy_driver en8811h_driver[] = {
...
```

So, the question is: can MDIO devices, PHY devices, or devices using the device tree (`of_match_table`) be instantiated or probed in the standard QEMU/GCE syzkaller amd64 fuzzing environment? Can these be instantiated via MDIO? The driver itself looks like it's relying on `OF_MDIO` which suggests Device Tree. Does AMD64 even have Device Tree? It's time to check `OF_MDIO` in Kconfig. If this requires a *specific* MDIO bus, or if device tree isn't there, then this is *unreachable*, and fuzzing is out of the question.



4/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "config OF_MDIO"
}

Results:
{
  "Output": "drivers/net/mdio/Kconfig=9=config FWNODE_MDIO\n--\ndrivers/net/mdio/Kconfig-13-\ndrivers/net/mdio/Kconfig:14:config OF_MDIO\ndrivers/net/mdio/Kconfig-15-\tdef_tristate OF\n"
}

5/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23329
							output: 18
							thoughts: 17
						

Thoughts:
Here's my take on those thoughts:

**Analyzing `config OF` on x86/amd64 Systems**

Alright, let's dive in. My immediate thought is, "I need to examine the `config OF` setting, specifically within the context of x86 and amd64 architectures."  I need to understand what this configuration option controls and how it behaves on these common hardware platforms. I'm going to want to figure out what effect its setting has.  I'll probably start with the more obvious things, checking the default values and expected behavior.



6/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "config OF\\b"
}

Results:
{
  "Output": "drivers/of/Kconfig=8=config BUILTIN_DTB_ALL\n--\ndrivers/of/Kconfig-10-\ndrivers/of/Kconfig:11:menuconfig OF\ndrivers/of/Kconfig-12-\tbool \"Device Tree and Open Firmware support\"\n"
}

7/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23565
							output: 32
							thoughts: 249
						

Thoughts:
**My Line of Inquiry: Airoha EN8811H and Virtual MDIO Exploration**

Okay, so I'm looking at `OF_MDIO`, which, naturally, depends on `OF`. Now, I know `OF` can be enabled under certain conditions – compilation tests or specific platforms. But my question is: can I actually *get* at the Airoha EN8811H without real hardware in front of me? I need to figure out if it's reachable in a virtual or emulated environment.

First, a quick refresher. Airoha… right, they're part of MediaTek, a significant SoC and network IC vendor. The EN8811H is specifically a 2.5G Ethernet PHY transceiver. That means it’s essentially an MD32 microcontroller with PHY firmware, all loaded through MDIO.

Now, the crucial question: can I dynamically probe or attach an MDIO device or PHY like this from userspace? Can I create it virtually? My mind is racing through possibilities – netlink, mocks, USB, whatever method might allow me to simulate this.

The key is if there is *any* virtual MDIO bus or mock PHY that could, hypothetically, probe for an "airoha,en8811h-mcu" or even something like "ethernet-phy-id03a2.a411."

Hold on, I need to backtrack slightly. How *exactly* does the MDIO subsystem match up with the `en8811h_mcu_driver`? I need to understand that driver matching process within the kernel itself. This is where I'll probably find the key to any virtual environment setup.



8/2 2026/09/12 13:09 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "mdio_driver_register",
  "PathPrefix": "drivers/net/mdio"
}

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

9/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24224
							output: 20
							thoughts: 12
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into `mdio_module_driver`**

Okay, time to dig into `mdio_module_driver`. I need to refresh my understanding of its role, especially in the context of [*Insert user's field, e.g., "our new PHY implementation" or "the specific switch driver we're integrating"*]. I want to quickly evaluate how this driver interacts with the MDIO bus and the PHY registers. I'm thinking about the standard driver model, the probe/remove functions, and the binding logic. Specifically, I'll be looking for [ *Insert specific user's concern, e.g., "any potential conflicts with our custom MII interface" or "how it handles different MDIO speeds"*]. I'll need to confirm that it's handling the MDIO transactions correctly, ensuring proper read and write operations. I'll focus on potential race conditions and error handling, especially since we're dealing with hardware, where things can get dicey. I'm going to scan the code for any areas that might require modification or adaptation for our specific use case, and I'll keep an eye out for any obvious performance bottlenecks. Essentially, I'm trying to get a quick grasp of the driver's architecture and identify any red flags that need closer attention.



10/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "mdio_module_driver"
}

Results:
{
  "Output": "drivers/clk/qcom/nsscc-qca8k.c=2210=static struct mdio_driver nss_cc_qca8k_driver = {\n--\ndrivers/clk/qcom/nsscc-qca8k.c-2217-\ndrivers/clk/qcom/nsscc-qca8k.c:2218:mdio_module_driver(nss_cc_qca8k_driver);\ndrivers/clk/qcom/nsscc-qca8k.c-2219-\n--\ndrivers/net/dsa/b53/b53_mdio.c=391=static struct mdio_driver b53_mdio_driver = {\n--\ndrivers/net/dsa/b53/b53_mdio.c-399-};\ndrivers/net/dsa/b53/b53_mdio.c:400:mdio_module_driver(b53_mdio_driver);\ndrivers/net/dsa/b53/b53_mdio.c-401-\n--\ndrivers/net/dsa/lan9303_mdio.c=164=static struct mdio_driver lan9303_mdio_driver = {\n--\ndrivers/net/dsa/lan9303_mdio.c-172-};\ndrivers/net/dsa/lan9303_mdio.c:173:mdio_module_driver(lan9303_mdio_driver);\ndrivers/net/dsa/lan9303_mdio.c-174-\n--\ndrivers/net/dsa/lantiq/mxl-gsw1xx.c=926=static struct mdio_driver gsw1xx_driver = {\n--\ndrivers/net/dsa/lantiq/mxl-gsw1xx.c-935-\ndrivers/net/dsa/lantiq/mxl-gsw1xx.c:936:mdio_module_driver(gsw1xx_driver);\ndrivers/net/dsa/lantiq/mxl-gsw1xx.c-937-\n--\ndrivers/net/dsa/microchip/ksz8863_smi.c=211=static struct mdio_driver ksz8863_driver = {\n--\ndrivers/net/dsa/microchip/ksz8863_smi.c-220-\ndrivers/net/dsa/microchip/ksz8863_smi.c:221:mdio_module_driver(ksz8863_driver);\ndrivers/net/dsa/microchip/ksz8863_smi.c-222-\n--\ndrivers/net/dsa/motorcomm/chip.c=4778=static struct mdio_driver yt921x_mdio_driver = {\n--\ndrivers/net/dsa/motorcomm/chip.c-4787-\ndrivers/net/dsa/motorcomm/chip.c:4788:mdio_module_driver(yt921x_mdio_driver);\ndrivers/net/dsa/motorcomm/chip.c-4789-\n--\ndrivers/net/dsa/mt7530-mdio.c=264=static struct mdio_driver mt7530_mdio_driver = {\n--\ndrivers/net/dsa/mt7530-mdio.c-273-\ndrivers/net/dsa/mt7530-mdio.c:274:mdio_module_driver(mt7530_mdio_driver);\ndrivers/net/dsa/mt7530-mdio.c-275-\n--\ndrivers/net/dsa/mv88e6060.c=368=static struct mdio_driver mv88e6060_driver = {\n--\ndrivers/net/dsa/mv88e6060.c-377-\ndrivers/net/dsa/mv88e6060.c:378:mdio_module_driver(mv88e6060_driver);\ndrivers/net/dsa/mv88e6060.c-379-\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=7521=static struct mdio_driver mv88e6xxx_driver = {\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-7531-\ndrivers/net/dsa/mv88e6xxx/chip.c:7532:mdio_module_driver(mv88e6xxx_driver);\ndrivers/net/dsa/mv88e6xxx/chip.c-7533-\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2201=static struct mdio_driver mxl862xx_driver = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2210-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2211:mdio_module_driver(mxl862xx_driver);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2212-\n--\ndrivers/net/dsa/qca/ar9331.c=1128=static struct mdio_driver ar9331_sw_mdio_driver = {\n--\ndrivers/net/dsa/qca/ar9331.c-1137-\ndrivers/net/dsa/qca/ar9331.c:1138:mdio_module_driver(ar9331_sw_mdio_driver);\ndrivers/net/dsa/qca/ar9331.c-1139-\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c=2221=static struct mdio_driver qca8kmdio_driver = {\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c-2231-\ndrivers/net/dsa/qca/qca8k-8xxx.c:2232:mdio_module_driver(qca8kmdio_driver);\ndrivers/net/dsa/qca/qca8k-8xxx.c-2233-\n--\ndrivers/net/dsa/xrs700x/xrs700x_mdio.c=166=static struct mdio_driver xrs700x_mdio_driver = {\n--\ndrivers/net/dsa/xrs700x/xrs700x_mdio.c-175-\ndrivers/net/dsa/xrs700x/xrs700x_mdio.c:176:mdio_module_driver(xrs700x_mdio_driver);\ndrivers/net/dsa/xrs700x/xrs700x_mdio.c-177-\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c=359=static struct mdio_driver en8811h_mcu_driver = {\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c-370-\ndrivers/net/mdio/mdio-airoha-en8811h.c:371:mdio_module_driver(en8811h_mcu_driver);\ndrivers/net/mdio/mdio-airoha-en8811h.c-372-\n--\ndrivers/net/phy/xilinx_gmii2rgmii.c=138=static struct mdio_driver xgmiitorgmii_driver = {\n--\ndrivers/net/phy/xilinx_gmii2rgmii.c-145-\ndrivers/net/phy/xilinx_gmii2rgmii.c:146:mdio_module_driver(xgmiitorgmii_driver);\ndrivers/net/phy/xilinx_gmii2rgmii.c-147-\n--\ndrivers/phy/broadcom/phy-bcm-ns-usb3.c=232=static struct mdio_driver bcm_ns_usb3_mdio_driver = {\n--\ndrivers/phy/broadcom/phy-bcm-ns-usb3.c-241-\ndrivers/phy/broadcom/phy-bcm-ns-usb3.c:242:mdio_module_driver(bcm_ns_usb3_mdio_driver);\ndrivers/phy/broadcom/phy-bcm-ns-usb3.c-243-\n--\ndrivers/phy/broadcom/phy-bcm-ns2-pcie.c=73=static struct mdio_driver ns2_pci_phy_driver = {\n--\ndrivers/phy/broadcom/phy-bcm-ns2-pcie.c-81-};\ndrivers/phy/broadcom/phy-bcm-ns2-pcie.c:82:mdio_module_driver(ns2_pci_phy_driver);\ndrivers/phy/broadcom/phy-bcm-ns2-pcie.c-83-\n--\ninclude/linux/mdio.h=696=struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr);\n--\ninclude/linux/mdio.h-698-/**\ninclude/linux/mdio.h:699: * mdio_module_driver() - Helper macro for registering mdio drivers\ninclude/linux/mdio.h-700- * @_mdio_driver: driver to register\n--\ninclude/linux/mdio.h-705- */\ninclude/linux/mdio.h:706:#define mdio_module_driver(_mdio_driver) \\\ninclude/linux/mdio.h-707-\tmodule_driver(_mdio_driver, mdio_driver_register, mdio_driver_unregister)\n"
}

11/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27099
							output: 30
							thoughts: 60
						

Thoughts:
Okay, let's break this down.

**My Analysis of MDIO Driver Registration**

Right, so `mdio_module_driver` is responsible for getting the `mdio_driver` registered, and it's doing that through `mdio_driver_register`. Makes sense. Now, the key question is how the *devices* themselves are getting added to this infrastructure. Are we talking about a dedicated `mdio_device_register` function, or is it handled in a more generic way, maybe involving the device tree? I'll need to check the codebase for registration logic related to `mdio_device_register` or `of_mdiobus_register` to confirm the procedure. I'll need to see how these devices are being integrated with the MDIO bus.



12/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "of_mdiobus_register",
  "PathPrefix": "drivers/net"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 543 lines.\nUse more precise expression if possible.\n\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c=187=static int gswip_mdio(struct gswip_priv *priv)\n--\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-213-\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c:214:\terr = devm_of_mdiobus_register(dev, bus, mdio_np);\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-215-\n--\ndrivers/net/dsa/microchip/ksz_common.c=2321=int ksz_mdio_register(struct ksz_device *dev)\n--\ndrivers/net/dsa/microchip/ksz_common.c-2392-\ndrivers/net/dsa/microchip/ksz_common.c:2393:\tret = devm_of_mdiobus_register(ds-\u003edev, bus, mdio_np);\ndrivers/net/dsa/microchip/ksz_common.c-2394-\tif (ret) {\n--\ndrivers/net/dsa/microchip/lan937x_main.c=685=static int lan937x_mdio_register(struct ksz_device *dev)\n--\ndrivers/net/dsa/microchip/lan937x_main.c-755-\ndrivers/net/dsa/microchip/lan937x_main.c:756:\tret = devm_of_mdiobus_register(ds-\u003edev, bus, mdio_np);\ndrivers/net/dsa/microchip/lan937x_main.c-757-\tif (ret)\n--\ndrivers/net/dsa/motorcomm/chip.c=389=yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)\n--\ndrivers/net/dsa/motorcomm/chip.c-406-\ndrivers/net/dsa/motorcomm/chip.c:407:\tres = devm_of_mdiobus_register(dev, mbus, mnp);\ndrivers/net/dsa/motorcomm/chip.c-408-\tif (res)\n--\ndrivers/net/dsa/motorcomm/chip.c=520=yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp)\n--\ndrivers/net/dsa/motorcomm/chip.c-537-\ndrivers/net/dsa/motorcomm/chip.c:538:\tres = devm_of_mdiobus_register(dev, mbus, mnp);\ndrivers/net/dsa/motorcomm/chip.c-539-\tif (res)\n--\ndrivers/net/dsa/mt7530.c=2337=mt7530_setup_mdio(struct mt7530_priv *priv)\n--\ndrivers/net/dsa/mt7530.c-2372-\ndrivers/net/dsa/mt7530.c:2373:\tret = devm_of_mdiobus_register(dev, bus, mnp);\ndrivers/net/dsa/mt7530.c-2374-\tif (ret) {\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=3834=static int mv88e6xxx_mdio_register(struct mv88e6xxx_chip *chip,\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-3887-\ndrivers/net/dsa/mv88e6xxx/chip.c:3888:\terr = of_mdiobus_register(bus, np);\ndrivers/net/dsa/mv88e6xxx/chip.c-3889-\tif (err) {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=267=static int mxl862xx_setup_mdio(struct dsa_switch *ds)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-292-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:293:\tret = devm_of_mdiobus_register(dev, bus, mdio_np);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-294-\tof_node_put(mdio_np);\n--\ndrivers/net/dsa/netc/netc_main.c=149=static int netc_port_create_emdio_bus(struct netc_port *np,\n--\ndrivers/net/dsa/netc/netc_main.c-179-\ndrivers/net/dsa/netc/netc_main.c:180:\terr = devm_of_mdiobus_register(dev, bus, node);\ndrivers/net/dsa/netc/netc_main.c-181-\tif (err)\n--\ndrivers/net/dsa/ocelot/seville_vsc9953.c=878=static int vsc9953_mdio_bus_alloc(struct ocelot *ocelot)\n--\ndrivers/net/dsa/ocelot/seville_vsc9953.c-903-\t/* Needed in order to initialize the bus mutex lock */\ndrivers/net/dsa/ocelot/seville_vsc9953.c:904:\trc = devm_of_mdiobus_register(dev, bus, NULL);\ndrivers/net/dsa/ocelot/seville_vsc9953.c-905-\tif (rc \u003c 0) {\n--\ndrivers/net/dsa/qca/ar9331.c=357=static int ar9331_sw_mbus_init(struct ar9331_sw_priv *priv)\n--\ndrivers/net/dsa/qca/ar9331.c-381-\ndrivers/net/dsa/qca/ar9331.c:382:\tret = devm_of_mdiobus_register(dev, mbus, mnp);\ndrivers/net/dsa/qca/ar9331.c-383-\tof_node_put(mnp);\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c=947=qca8k_mdio_register(struct qca8k_priv *priv)\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c-986-\ndrivers/net/dsa/qca/qca8k-8xxx.c:987:\tret = devm_of_mdiobus_register(dev, bus, mdio);\ndrivers/net/dsa/qca/qca8k-8xxx.c-988-\n--\ndrivers/net/dsa/realtek/rtl83xx.c=75=int rtl83xx_setup_user_mdio(struct dsa_switch *ds)\n--\ndrivers/net/dsa/realtek/rtl83xx.c-100-\ndrivers/net/dsa/realtek/rtl83xx.c:101:\tret = devm_of_mdiobus_register(priv-\u003edev, bus, mdio_np);\ndrivers/net/dsa/realtek/rtl83xx.c-102-\tif (ret) {\n--\ndrivers/net/dsa/rzn1_a5psw.c=1117=static int a5psw_probe_mdio(struct a5psw *a5psw, struct device_node *node)\n--\ndrivers/net/dsa/rzn1_a5psw.c-1143-\ndrivers/net/dsa/rzn1_a5psw.c:1144:\treturn devm_of_mdiobus_register(dev, bus, node);\ndrivers/net/dsa/rzn1_a5psw.c-1145-}\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c=258=static int sja1105_mdiobus_base_tx_register(struct sja1105_private *priv,\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c-287-\ndrivers/net/dsa/sja1105/sja1105_mdio.c:288:\trc = of_mdiobus_register(bus, np);\ndrivers/net/dsa/sja1105/sja1105_mdio.c-289-\tif (rc) {\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c=312=static int sja1105_mdiobus_base_t1_register(struct sja1105_private *priv,\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c-343-\ndrivers/net/dsa/sja1105/sja1105_mdio.c:344:\trc = of_mdiobus_register(bus, np);\ndrivers/net/dsa/sja1105/sja1105_mdio.c-345-\tif (rc) {\n--\ndrivers/net/ethernet/actions/owl-emac.c=1321=static int owl_emac_mdio_init(struct net_device *netdev)\n--\ndrivers/net/ethernet/actions/owl-emac.c-1345-\ndrivers/net/ethernet/actions/owl-emac.c:1346:\tret = devm_of_mdiobus_register(dev, priv-\u003emii, mdio_node);\ndrivers/net/ethernet/actions/owl-emac.c-1347-\n--\ndrivers/net/ethernet/altera/altera_tse_main.c=121=static int altera_tse_mdio_create(struct net_device *dev, unsigned int id)\n--\ndrivers/net/ethernet/altera/altera_tse_main.c-157-\ndrivers/net/ethernet/altera/altera_tse_main.c:158:\tret = of_mdiobus_register(mdio, mdio_node);\ndrivers/net/ethernet/altera/altera_tse_main.c-159-\tif (ret != 0) {\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_hw.c=888=static int xgene_mdiobus_register(struct xgene_enet_pdata *pdata,\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_hw.c-912-\ndrivers/net/ethernet/apm/xgene/xgene_enet_hw.c:913:\t\tret = of_mdiobus_register(mdio, mdio_np);\ndrivers/net/ethernet/apm/xgene/xgene_enet_hw.c-914-\t\tof_node_put(mdio_np);\n--\ndrivers/net/ethernet/arc/emac_mdio.c=131=int arc_mdio_probe(struct arc_emac_priv *priv)\n--\ndrivers/net/ethernet/arc/emac_mdio.c-172-\ndrivers/net/ethernet/arc/emac_mdio.c:173:\terror = of_mdiobus_register(bus, mdio_node);\ndrivers/net/ethernet/arc/emac_mdio.c-174-\tof_node_put(mdio_node);\n--\ndrivers/net/ethernet/atheros/ag71xx.c=687=static int ag71xx_mdio_probe(struct ag71xx *ag)\n--\ndrivers/net/ethernet/atheros/ag71xx.c-727-\tmnp = of_get_child_by_name(np, \"mdio\");\ndrivers/net/ethernet/atheros/ag71xx.c:728:\terr = devm_of_mdiobus_register(dev, mii_bus, mnp);\ndrivers/net/ethernet/atheros/ag71xx.c-729-\tof_node_put(mnp);\n--\ndrivers/net/ethernet/broadcom/bgmac-bcma-mdio.c=211=struct mii_bus *bcma_mdio_mii_register(struct bgmac *bgmac)\n--\ndrivers/net/ethernet/broadcom/bgmac-bcma-mdio.c-235-\ndrivers/net/ethernet/broadcom/bgmac-bcma-mdio.c:236:\terr = of_mdiobus_register(mii_bus, np);\ndrivers/net/ethernet/broadcom/bgmac-bcma-mdio.c-237-\tof_node_put(np);\n--\ndrivers/net/ethernet/broadcom/genet/bcmmii.c=193=int bcmgenet_mii_config(struct net_device *dev, bool init)\n--\ndrivers/net/ethernet/broadcom/genet/bcmmii.c-233-\t\tphy_name = \"external RvMII\";\ndrivers/net/ethernet/broadcom/genet/bcmmii.c:234:\t\t/* of_mdiobus_register took care of reading the 'max-speed'\ndrivers/net/ethernet/broadcom/genet/bcmmii.c-235-\t\t * PHY property for us, effectively limiting the PHY supported\n--\ndrivers/net/ethernet/cadence/macb_main.c=1097=static int macb_mdiobus_register(struct macb *bp, struct device_node *mdio_np)\n--\ndrivers/net/ethernet/cadence/macb_main.c-1104-\tif (mdio_np)\ndrivers/net/ethernet/cadence/macb_main.c:1105:\t\treturn of_mdiobus_register(bp-\u003emii_bus, mdio_np);\ndrivers/net/ethernet/cadence/macb_main.c-1106-\n--\ndrivers/net/ethernet/cadence/macb_main.c-1118-\ndrivers/net/ethernet/cadence/macb_main.c:1119:\t\t\treturn of_mdiobus_register(bp-\u003emii_bus, np);\ndrivers/net/ethernet/cadence/macb_main.c-1120-\t\t}\n--\ndrivers/net/ethernet/engleder/tsnep_main.c=2423=static int tsnep_mdio_init(struct tsnep_adapter *adapter)\n--\ndrivers/net/ethernet/engleder/tsnep_main.c-2454-\ndrivers/net/ethernet/engleder/tsnep_main.c:2455:\tretval = of_mdiobus_register(adapter-\u003emdiobus, np);\ndrivers/net/ethernet/engleder/tsnep_main.c-2456-\n--\ndrivers/net/ethernet/faraday/ftgmac100.c=1734=static int ftgmac100_setup_mdio(struct net_device *netdev)\n--\ndrivers/net/ethernet/faraday/ftgmac100.c-1769-\ndrivers/net/ethernet/faraday/ftgmac100.c:1770:\terr = devm_of_mdiobus_register(priv-\u003edev, priv-\u003emii_bus, mdio_np);\ndrivers/net/ethernet/faraday/ftgmac100.c-1771-\tof_node_put(mdio_np);\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pci_mdio.c=36=static int enetc_pci_mdio_probe(struct pci_dev *pdev,\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pci_mdio.c-90-\ndrivers/net/ethernet/freescale/enetc/enetc_pci_mdio.c:91:\terr = of_mdiobus_register(bus, dev-\u003eof_node);\ndrivers/net/ethernet/freescale/enetc/enetc_pci_mdio.c-92-\tif (err)\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c=266=static int enetc_mdio_probe(struct enetc_pf *pf, struct device_node *np)\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-292-\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c:293:\terr = of_mdiobus_register(bus, np);\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-294-\tif (err)\n--\ndrivers/net/ethernet/freescale/fec_main.c=3042=static int fec_enet_mii_init(struct platform_device *pdev)\n--\ndrivers/net/ethernet/freescale/fec_main.c-3160-\ndrivers/net/ethernet/freescale/fec_main.c:3161:\terr = of_mdiobus_register(fep-\u003emii_bus, node);\ndrivers/net/ethernet/freescale/fec_main.c-3162-\tif (err)\n--\ndrivers/net/ethernet/freescale/fec_mpc52xx_phy.c=65=static int mpc52xx_fec_mdio_probe(struct platform_device *of)\n--\ndrivers/net/ethernet/freescale/fec_mpc52xx_phy.c-105-\ndrivers/net/ethernet/freescale/fec_mpc52xx_phy.c:106:\terr = of_mdiobus_register(bus, np);\ndrivers/net/ethernet/freescale/fec_mpc52xx_phy.c-107-\tif (err)\n--\ndrivers/net/ethernet/freescale/fs_enet/mii-bitbang.c=149=static int fs_enet_mdio_probe(struct platform_device *ofdev)\n--\ndrivers/net/ethernet/freescale/fs_enet/mii-bitbang.c-175-\ndrivers/net/ethernet/freescale/fs_enet/mii-bitbang.c:176:\tret = of_mdiobus_register(new_bus, ofdev-\u003edev.of_node);\ndrivers/net/ethernet/freescale/fs_enet/mii-bitbang.c-177-\tif (ret)\n--\ndrivers/net/ethernet/freescale/fs_enet/mii-fec.c=97=static int fs_enet_mdio_probe(struct platform_device *ofdev)\n--\ndrivers/net/ethernet/freescale/fs_enet/mii-fec.c-166-\ndrivers/net/ethernet/freescale/fs_enet/mii-fec.c:167:\tret = of_mdiobus_register(new_bus, ofdev-\u003edev.of_node);\ndrivers/net/ethernet/freescale/fs_enet/mii-fec.c-168-\tif (ret)\n--\ndrivers/net/ethernet/freescale/fsl_pq_mdio.c=409=static int fsl_pq_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/freescale/fsl_pq_mdio.c-493-\ndrivers/net/ethernet/freescale/fsl_pq_mdio.c:494:\terr = of_mdiobus_register(new_bus, np);\ndrivers/net/ethernet/freescale/fsl_pq_mdio.c-495-\tif (err) {\n--\ndrivers/net/ethernet/freescale/xgmac_mdio.c=371=static int xgmac_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/freescale/xgmac_mdio.c-426-\tif (is_of_node(fwnode))\ndrivers/net/ethernet/freescale/xgmac_mdio.c:427:\t\tret = of_mdiobus_register(bus, to_of_node(fwnode));\ndrivers/net/ethernet/freescale/xgmac_mdio.c-428-\telse if (is_acpi_node(fwnode))\n--\ndrivers/net/ethernet/hisilicon/hix5hd2_gmac.c=1094=static int hix5hd2_dev_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/hisilicon/hix5hd2_gmac.c-1185-\ndrivers/net/ethernet/hisilicon/hix5hd2_gmac.c:1186:\tret = of_mdiobus_register(bus, node);\ndrivers/net/ethernet/hisilicon/hix5hd2_gmac.c-1187-\tif (ret)\n--\ndrivers/net/ethernet/hisilicon/hns_mdio.c=495=static int hns_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/hisilicon/hns_mdio.c-583-\ndrivers/net/ethernet/hisilicon/hns_mdio.c:584:\t\tret = of_mdiobus_register(new_bus, pdev-\u003edev.of_node);\ndrivers/net/ethernet/hisilicon/hns_mdio.c-585-\t} else if (is_acpi_node(pdev-\u003edev.fwnode)) {\n--\ndrivers/net/ethernet/ibm/emac/core.c=2560=static int emac_dt_mdio_probe(struct emac_instance *dev)\n--\ndrivers/net/ethernet/ibm/emac/core.c-2584-\tsnprintf(bus-\u003eid, MII_BUS_ID_SIZE, \"%s\", dev-\u003eofdev-\u003ename);\ndrivers/net/ethernet/ibm/emac/core.c:2585:\tres = devm_of_mdiobus_register(\u0026dev-\u003eofdev-\u003edev, bus, mii_np);\ndrivers/net/ethernet/ibm/emac/core.c-2586-\tif (res) {\n--\ndrivers/net/ethernet/marvell/mvmdio.c=283=static int orion_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/marvell/mvmdio.c-381-\t/* For the platforms not supporting DT/ACPI fall-back\ndrivers/net/ethernet/marvell/mvmdio.c:382:\t * to mdiobus_register via of_mdiobus_register.\ndrivers/net/ethernet/marvell/mvmdio.c-383-\t */\n--\ndrivers/net/ethernet/marvell/mvmdio.c-386-\telse\ndrivers/net/ethernet/marvell/mvmdio.c:387:\t\tret = of_mdiobus_register(bus, pdev-\u003edev.of_node);\ndrivers/net/ethernet/marvell/mvmdio.c-388-\tif (ret \u003c 0) {\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c=998=static int mtk_mdio_init(struct mtk_eth *eth)\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-1037-\tdev_dbg(eth-\u003edev, \"MDC is running on %d Hz\\n\", MDC_MAX_FREQ / eth-\u003emdc_divider);\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c:1038:\tret = of_mdiobus_register(eth-\u003emii_bus, mii_np);\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-1039-\n--\ndrivers/net/ethernet/mediatek/mtk_star_emac.c=1422=static int mtk_star_mdio_init(struct net_device *ndev)\n--\ndrivers/net/ethernet/mediatek/mtk_star_emac.c-1447-\ndrivers/net/ethernet/mediatek/mtk_star_emac.c:1448:\tret = devm_of_mdiobus_register(dev, priv-\u003emii, mdio_node);\ndrivers/net/ethernet/mediatek/mtk_star_emac.c-1449-\n--\ndrivers/net/ethernet/ni/nixge.c=1212=static int nixge_mdio_setup(struct nixge_priv *priv, struct device_node *np)\n--\ndrivers/net/ethernet/ni/nixge.c-1230-\ndrivers/net/ethernet/ni/nixge.c:1231:\treturn of_mdiobus_register(bus, np);\ndrivers/net/ethernet/ni/nixge.c-1232-}\n--\ndrivers/net/ethernet/nxp/lpc_eth.c=790=static int lpc_mii_init(struct netdata_local *pldat)\n--\ndrivers/net/ethernet/nxp/lpc_eth.c-820-\tnode = of_get_child_by_name(pldat-\u003epdev-\u003edev.of_node, \"mdio\");\ndrivers/net/ethernet/nxp/lpc_eth.c:821:\terr = of_mdiobus_register(pldat-\u003emii_bus, node);\ndrivers/net/ethernet/nxp/lpc_eth.c-822-\tof_node_put(node);\n--\ndrivers/net/ethernet/qualcomm/emac/emac-phy.c=92=int emac_phy_config(struct platform_device *pdev, struct emac_adapter *adpt)\n--\ndrivers/net/ethernet/qualcomm/emac/emac-phy.c-137-\ndrivers/net/ethernet/qualcomm/emac/emac-phy.c:138:\t\tret = of_mdiobus_register(mii_bus, np);\ndrivers/net/ethernet/qualcomm/emac/emac-phy.c-139-\t\tif (ret) {\n--\ndrivers/net/ethernet/renesas/ravb_main.c=2623=static int ravb_mdio_init(struct ravb_private *priv)\n--\ndrivers/net/ethernet/renesas/ravb_main.c-2651-\t}\ndrivers/net/ethernet/renesas/ravb_main.c:2652:\terror = of_mdiobus_register(priv-\u003emii_bus, mdio_node);\ndrivers/net/ethernet/renesas/ravb_main.c-2653-\tof_node_put(mdio_node);\n--\ndrivers/net/ethernet/renesas/rswitch_main.c=1367=static int rswitch_mii_register(struct rswitch_device *rdev)\n--\ndrivers/net/ethernet/renesas/rswitch_main.c-1386-\tmdio_np = of_get_child_by_name(rdev-\u003enp_port, \"mdio\");\ndrivers/net/ethernet/renesas/rswitch_main.c:1387:\terr = of_mdiobus_register(mii_bus, mdio_np);\ndrivers/net/ethernet/renesas/rswitch_main.c-1388-\tif (err \u003c 0) {\n--\ndrivers/net/ethernet/renesas/rtsn.c=774=static int rtsn_mdio_alloc(struct rtsn_private *priv)\n--\ndrivers/net/ethernet/renesas/rtsn.c-812-\ndrivers/net/ethernet/renesas/rtsn.c:813:\tret = of_mdiobus_register(mii, mdio_node);\ndrivers/net/ethernet/renesas/rtsn.c-814-\tof_node_put(mdio_node);\n--\ndrivers/net/ethernet/renesas/sh_eth.c=3091=static int sh_mdio_init(struct sh_eth_private *mdp,\n--\ndrivers/net/ethernet/renesas/sh_eth.c-3131-\ndrivers/net/ethernet/renesas/sh_eth.c:3132:\tret = of_mdiobus_register(mdp-\u003emii_bus, dev-\u003eof_node);\ndrivers/net/ethernet/renesas/sh_eth.c-3133-\tif (ret)\n--\ndrivers/net/ethernet/socionext/netsec.c=1929=static int netsec_register_mdio(struct netsec_priv *priv, u32 phy_addr)\n--\ndrivers/net/ethernet/socionext/netsec.c-1958-\ndrivers/net/ethernet/socionext/netsec.c:1959:\t\tret = of_mdiobus_register(bus, parent);\ndrivers/net/ethernet/socionext/netsec.c-1960-\t\tof_node_put(mdio_node);\n--\ndrivers/net/ethernet/socionext/sni_ave.c=1163=static int ave_init(struct net_device *ndev)\n--\ndrivers/net/ethernet/socionext/sni_ave.c-1202-\t}\ndrivers/net/ethernet/socionext/sni_ave.c:1203:\tret = of_mdiobus_register(priv-\u003emdio, mdio_np);\ndrivers/net/ethernet/socionext/sni_ave.c-1204-\tof_node_put(mdio_np);\n--\ndrivers/net/ethernet/spacemit/k1_emac.c=988=static int emac_mdio_init(struct emac_priv *priv)\n--\ndrivers/net/ethernet/spacemit/k1_emac.c-1008-\ndrivers/net/ethernet/spacemit/k1_emac.c:1009:\tret = devm_of_mdiobus_register(dev, mii, mii_np);\ndrivers/net/ethernet/spacemit/k1_emac.c-1010-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c=595=int stmmac_mdio_register(struct net_device *ndev)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c-654-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:655:\terr = of_mdiobus_register(new_bus, mdio_node);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c-656-\tif (err == -ENODEV) {\n--\ndrivers/net/ethernet/sunplus/spl2sw_mdio.c=79=u32 spl2sw_mdio_init(struct spl2sw_common *comm)\n--\ndrivers/net/ethernet/sunplus/spl2sw_mdio.c-105-\ndrivers/net/ethernet/sunplus/spl2sw_mdio.c:106:\tret = of_mdiobus_register(mii_bus, mdio_np);\ndrivers/net/ethernet/sunplus/spl2sw_mdio.c-107-\tif (ret) {\n--\ndrivers/net/ethernet/ti/davinci_mdio.c=537=static int davinci_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/ti/davinci_mdio.c-642-\ndrivers/net/ethernet/ti/davinci_mdio.c:643:\tret = of_mdiobus_register(data-\u003ebus, dev-\u003eof_node);\ndrivers/net/ethernet/ti/davinci_mdio.c-644-\tif (ret)\n--\ndrivers/net/ethernet/xilinx/ll_temac_mdio.c=64=int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)\n--\ndrivers/net/ethernet/xilinx/ll_temac_mdio.c-116-\ndrivers/net/ethernet/xilinx/ll_temac_mdio.c:117:\trc = of_mdiobus_register(bus, np);\ndrivers/net/ethernet/xilinx/ll_temac_mdio.c-118-\tif (rc)\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_mdio.c=280=int axienet_mdio_setup(struct axienet_local *lp)\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_mdio.c-303-\t\tgoto unregister;\ndrivers/net/ethernet/xilinx/xilinx_axienet_mdio.c:304:\tret = of_mdiobus_register(bus, mdio_node);\ndrivers/net/ethernet/xilinx/xilinx_axienet_mdio.c-305-\tif (ret)\n--\ndrivers/net/ethernet/xilinx/xilinx_emaclite.c=804=static int xemaclite_mdio_setup(struct net_local *lp, struct device *dev)\n--\ndrivers/net/ethernet/xilinx/xilinx_emaclite.c-861-\ndrivers/net/ethernet/xilinx/xilinx_emaclite.c:862:\trc = of_mdiobus_register(bus, np);\ndrivers/net/ethernet/xilinx/xilinx_emaclite.c-863-\tof_node_put(np);\n--\ndrivers/net/ethernet/xscale/ixp4xx_eth.c=558=static int ixp4xx_mdio_register(struct eth_regs __iomem *regs)\n--\ndrivers/net/ethernet/xscale/ixp4xx_eth.c-571-\ndrivers/net/ethernet/xscale/ixp4xx_eth.c:572:\terr = of_mdiobus_register(mdio_bus, mdio_bus_np);\ndrivers/net/ethernet/xscale/ixp4xx_eth.c-573-\tif (err)\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c=84=static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c-117-\ndrivers/net/mdio/mdio-airoha-en8811h.c:118:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-airoha-en8811h.c-119-\tof_node_put(np);\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c-121-\t\t/* An ID read that failed leaves the bus registered and the\ndrivers/net/mdio/mdio-airoha-en8811h.c:122:\t\t * PHY absent; of_mdiobus_register() returns 0 either way.\ndrivers/net/mdio/mdio-airoha-en8811h.c-123-\t\t */\n--\ndrivers/net/mdio/mdio-airoha.c=203=static int airoha_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-airoha.c-250-\ndrivers/net/mdio/mdio-airoha.c:251:\tret = devm_of_mdiobus_register(dev, bus, dev-\u003eof_node);\ndrivers/net/mdio/mdio-airoha.c-252-\tif (ret) {\n--\ndrivers/net/mdio/mdio-aspeed.c=146=static int aspeed_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-aspeed.c-174-\ndrivers/net/mdio/mdio-aspeed.c:175:\trc = of_mdiobus_register(bus, pdev-\u003edev.of_node);\ndrivers/net/mdio/mdio-aspeed.c-176-\tif (rc) {\n--\ndrivers/net/mdio/mdio-bcm-iproc.c=122=static int iproc_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-bcm-iproc.c-153-\ndrivers/net/mdio/mdio-bcm-iproc.c:154:\trc = of_mdiobus_register(bus, pdev-\u003edev.of_node);\ndrivers/net/mdio/mdio-bcm-iproc.c-155-\tif (rc) {\n--\ndrivers/net/mdio/mdio-bcm-unimac.c=237=static int unimac_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-bcm-unimac.c-301-\ndrivers/net/mdio/mdio-bcm-unimac.c:302:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-bcm-unimac.c-303-\tif (ret) {\n--\ndrivers/net/mdio/mdio-gpio.c=157=static int mdio_gpio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-gpio.c-184-\ndrivers/net/mdio/mdio-gpio.c:185:\tret = of_mdiobus_register(new_bus, pdev-\u003edev.of_node);\ndrivers/net/mdio/mdio-gpio.c-186-\tif (ret)\n--\ndrivers/net/mdio/mdio-hisi-femac.c=72=static int hisi_femac_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-hisi-femac.c-105-\ndrivers/net/mdio/mdio-hisi-femac.c:106:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-hisi-femac.c-107-\tif (ret)\n--\ndrivers/net/mdio/mdio-ipq4019.c=326=static int ipq4019_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-ipq4019.c-369-\ndrivers/net/mdio/mdio-ipq4019.c:370:\tret = of_mdiobus_register(bus, pdev-\u003edev.of_node);\ndrivers/net/mdio/mdio-ipq4019.c-371-\tif (ret) {\n--\ndrivers/net/mdio/mdio-ipq8064.c=110=ipq8064_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-ipq8064.c-141-\ndrivers/net/mdio/mdio-ipq8064.c:142:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-ipq8064.c-143-\tif (ret)\n--\ndrivers/net/mdio/mdio-moxart.c=110=static int moxart_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-moxart.c-128-\t/* Setting PHY_MAC_INTERRUPT here even if it has no effect,\ndrivers/net/mdio/mdio-moxart.c:129:\t * of_mdiobus_register() sets these PHY_POLL.\ndrivers/net/mdio/mdio-moxart.c-130-\t * Ideally, the interrupt from MAC controller could be used to\n--\ndrivers/net/mdio/mdio-moxart.c-144-\ndrivers/net/mdio/mdio-moxart.c:145:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-moxart.c-146-\tif (ret \u003c 0)\n--\ndrivers/net/mdio/mdio-mscc-miim.c=270=static int mscc_miim_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-mscc-miim.c-330-\ndrivers/net/mdio/mdio-mscc-miim.c:331:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-mscc-miim.c-332-\tif (ret \u003c 0) {\n--\ndrivers/net/mdio/mdio-mux.c=139=int mdio_mux_init(struct device *dev,\n--\ndrivers/net/mdio/mdio-mux.c-224-\t\t\tcb-\u003emii_bus-\u003ewrite_c45 = mdio_mux_write_c45;\ndrivers/net/mdio/mdio-mux.c:225:\t\tr = of_mdiobus_register(cb-\u003emii_bus, child_bus_node);\ndrivers/net/mdio/mdio-mux.c-226-\t\tif (r) {\n--\ndrivers/net/mdio/mdio-mvusb.c=64=static int mvusb_mdio_probe(struct usb_interface *interface,\n--\ndrivers/net/mdio/mdio-mvusb.c-90-\ndrivers/net/mdio/mdio-mvusb.c:91:\treturn of_mdiobus_register(mdio, dev-\u003eof_node);\ndrivers/net/mdio/mdio-mvusb.c-92-}\n--\ndrivers/net/mdio/mdio-octeon.c=16=static int octeon_mdiobus_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-octeon.c-50-\ndrivers/net/mdio/mdio-octeon.c:51:\terr = of_mdiobus_register(bus-\u003emii_bus, pdev-\u003edev.of_node);\ndrivers/net/mdio/mdio-octeon.c-52-\tif (err)\n--\ndrivers/net/mdio/mdio-pic64hpsc.c=117=static int pic64hpsc_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-pic64hpsc.c-163-\ndrivers/net/mdio/mdio-pic64hpsc.c:164:\tret = devm_of_mdiobus_register(dev, bus, np);\ndrivers/net/mdio/mdio-pic64hpsc.c-165-\tif (ret) {\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c=833=static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,\n--\ndrivers/net/mdio/mdio-realtek-rtl9300.c-867-\ndrivers/net/mdio/mdio-realtek-rtl9300.c:868:\terr = devm_of_mdiobus_register(dev, bus, to_of_node(node));\ndrivers/net/mdio/mdio-realtek-rtl9300.c-869-\tif (err)\n--\ndrivers/net/mdio/mdio-sun4i.c=90=static int sun4i_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-sun4i.c-128-\ndrivers/net/mdio/mdio-sun4i.c:129:\tret = of_mdiobus_register(bus, np);\ndrivers/net/mdio/mdio-sun4i.c-130-\tif (ret \u003c 0)\n--\ndrivers/net/mdio/mdio-thunder.c=22=static int thunder_mdiobus_pci_probe(struct pci_dev *pdev,\n--\ndrivers/net/mdio/mdio-thunder.c-99-\ndrivers/net/mdio/mdio-thunder.c:100:\t\terr = of_mdiobus_register(bus-\u003emii_bus, node);\ndrivers/net/mdio/mdio-thunder.c-101-\t\tif (err)\ndrivers/net/mdio/mdio-thunder.c:102:\t\t\tdev_err(\u0026pdev-\u003edev, \"of_mdiobus_register failed\\n\");\ndrivers/net/mdio/mdio-thunder.c-103-\n--\ndrivers/net/mdio/mdio-xgene.c=327=static int xgene_mdio_probe(struct platform_device *pdev)\n--\ndrivers/net/mdio/mdio-xgene.c-392-\tif (dev-\u003eof_node) {\ndrivers/net/mdio/mdio-xgene.c:393:\t\tret = of_mdiobus_register(mdio_bus, dev-\u003eof_node);\ndrivers/net/mdio/mdio-xgene.c-394-\t} else {\n--\ndrivers/net/mdio/of_mdio.c=44=EXPORT_SYMBOL(of_mdiobus_phy_device_register);\ndrivers/net/mdio/of_mdio.c-45-\ndrivers/net/mdio/of_mdio.c:46:static int of_mdiobus_register_phy(struct mii_bus *mdio,\ndrivers/net/mdio/of_mdio.c-47-\t\t\t\t    struct device_node *child, u32 addr)\n--\ndrivers/net/mdio/of_mdio.c-51-\ndrivers/net/mdio/of_mdio.c:52:static int of_mdiobus_register_device(struct mii_bus *mdio,\ndrivers/net/mdio/of_mdio.c-53-\t\t\t\t      struct device_node *child, u32 addr)\n--\ndrivers/net/mdio/of_mdio.c=139=static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,\n--\ndrivers/net/mdio/of_mdio.c-167-\t\tif (of_mdiobus_child_is_phy(child))\ndrivers/net/mdio/of_mdio.c:168:\t\t\trc = of_mdiobus_register_phy(mdio, child, addr);\ndrivers/net/mdio/of_mdio.c-169-\t\telse\ndrivers/net/mdio/of_mdio.c:170:\t\t\trc = of_mdiobus_register_device(mdio, child, addr);\ndrivers/net/mdio/of_mdio.c-171-\n--\ndrivers/net/mdio/of_mdio.c-186-/**\ndrivers/net/mdio/of_mdio.c:187: * __of_mdiobus_register - Register mii_bus and create PHYs from the device tree\ndrivers/net/mdio/of_mdio.c-188- * @mdio: pointer to mii_bus structure\n--\ndrivers/net/mdio/of_mdio.c-194- */\ndrivers/net/mdio/of_mdio.c:195:int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\ndrivers/net/mdio/of_mdio.c-196-\t\t\t  struct module *owner)\n--\ndrivers/net/mdio/of_mdio.c-254-\t\t\t\t */\n\n"
}

13/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39882
							output: 40
							thoughts: 48
						

Thoughts:
Here's my summary, as if I were thinking these things:

**Analyzing the MDIO Device Registration**

Okay, let's dive into this. I need to understand how these MDIO devices are being registered, particularly those configured through the device tree. I'll focus on `drivers/net/mdio/of_mdio.c` and specifically the `of_mdiobus_register_device` function. I remember this function is crucial for binding the MDIO bus with the devices described in the device tree. Let me open the file and jump to line 50. I should be able to quickly grasp the surrounding code and start understanding the registration flow. I'm especially interested in how the device properties are parsed and how this information is used to populate the MDIO device structure. It's time to dig into the details and start building a mental model of this registration process.



14/2 2026/09/12 13:09 tool read-file
0m Args:
{
  "File": "drivers/net/mdio/of_mdio.c",
  "FirstLine": 50,
  "LineCount": 50
}

Results:
{
  "Contents": "  50:\t}\n  51:\t\n  52:\tstatic int of_mdiobus_register_device(struct mii_bus *mdio,\n  53:\t\t\t\t\t      struct device_node *child, u32 addr)\n  54:\t{\n  55:\t\tstruct fwnode_handle *fwnode = of_fwnode_handle(child);\n  56:\t\tstruct mdio_device *mdiodev;\n  57:\t\tint rc;\n  58:\t\n  59:\t\tmdiodev = mdio_device_create(mdio, addr);\n  60:\t\tif (IS_ERR(mdiodev))\n  61:\t\t\treturn PTR_ERR(mdiodev);\n  62:\t\n  63:\t\t/* Associate the OF node with the device structure so it\n  64:\t\t * can be looked up later.\n  65:\t\t */\n  66:\t\tdevice_set_node(\u0026mdiodev-\u003edev, fwnode_handle_get(fwnode));\n  67:\t\n  68:\t\t/* All data is now stored in the mdiodev struct; register it. */\n  69:\t\trc = mdio_device_register(mdiodev);\n  70:\t\tif (rc) {\n  71:\t\t\tmdio_device_free(mdiodev);\n  72:\t\t\treturn rc;\n  73:\t\t}\n  74:\t\n  75:\t\tdev_dbg(\u0026mdio-\u003edev, \"registered mdio device %pOFn at address %i\\n\",\n  76:\t\t\tchild, addr);\n  77:\t\treturn 0;\n  78:\t}\n  79:\t\n  80:\t/* The following is a list of PHY compatible strings which appear in\n  81:\t * some DTBs. The compatible string is never matched against a PHY\n  82:\t * driver, so is pointless. We only expect devices which are not PHYs\n  83:\t * to have a compatible string, so they can be matched to an MDIO\n  84:\t * driver.  Encourage users to upgrade their DT blobs to remove these.\n  85:\t */\n  86:\tstatic const struct of_device_id whitelist_phys[] = {\n  87:\t\t{ .compatible = \"brcm,40nm-ephy\" },\n  88:\t\t{ .compatible = \"broadcom,bcm5241\" },\n  89:\t\t{ .compatible = \"marvell,88E1111\", },\n  90:\t\t{ .compatible = \"marvell,88e1116\", },\n  91:\t\t{ .compatible = \"marvell,88e1118\", },\n  92:\t\t{ .compatible = \"marvell,88e1145\", },\n  93:\t\t{ .compatible = \"marvell,88e1149r\", },\n  94:\t\t{ .compatible = \"marvell,88e1310\", },\n  95:\t\t{ .compatible = \"marvell,88E1510\", },\n  96:\t\t{ .compatible = \"marvell,88E1514\", },\n  97:\t\t{ .compatible = \"moxa,moxart-rtl8201cp\", },\n  98:\t\t{}\n  99:\t};\n"
}

15/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40985
							output: 20
							thoughts: 512
						

Thoughts:
**Analyzing the Airoha EN8811H MDIO Device Driver in a Virtualized Environment**

Okay, let's break this down. My initial thought is about the `of_mdiobus_register_device` function and how it creates an `mdio_device`, specifically if the device tree has a "child" node and `of_mdiobus_child_is_phy` returns false. I'm focusing on the condition where `of_mdiobus_register` would be called with a device tree containing the "airoha,en8811h-mcu" compatible string. My gut tells me this is the core issue for our concern about AMD64 and QEMU.

The real question is, can this even happen in the context of amd64 within QEMU or Google Compute Engine, which is the setup we're concerned about in this fuzzing context?  My first thought is to verify whether `CONFIG_OF` (Device Tree support) is even enabled on x86_64, as that's the foundation for all this. Typically, x86 architectures use ACPI, not DT. But do we ever see DT nodes being loaded on x86_64?

Alright, let's step back and consider dependencies. The `OF_MDIO` option hinges on `OF`, so we need to verify both are properly set. Next, I need to check the MDIO driver configuration (`drivers/net/mdio/Kconfig`) and specifically the `MDIO_AIROHA_EN8811H` option. Based on this configuration it relies on `OF_MDIO`, `FW_LOADER`, and selects `AIR_NET_PHYLIB` implying `AIR_EN8811H_PHY`.

Now, the really critical point is whether `CONFIG_OF_MDIO` and `CONFIG_MDIO_AIROHA_EN8811H` are enabled.  I'm circling back to `CONFIG_OF` because even if the MDIO configuration is correct, if OF is not enabled, then we're stuck. I need to know if it's even a possibility on x86_64.

Okay, let's check `drivers/of/Kconfig`. `menuconfig OF bool "Device Tree and Open Firmware support"` doesn't have any dependencies listed, but is it enabled? Does the environment allow for Device Tree nodes to be passed somehow, even if it's not the primary boot method?

Here's the sticking point: even *if* `CONFIG_OF` is enabled, how would a DT node with `"airoha,en8811h-mcu"` ever exist in a standard virtualized environment like QEMU or GCE? These environments typically don't have this kind of specialized hardware or configuration readily available. This feels like the critical roadblock to overcome.

So, could syzkaller or a user space process be used to create OF nodes or attach MDIO devices? Is there any way to "inject" the correct device tree entries?

That brings me to the idea of Device Tree Overlays (DTOs) and specifically `CONFIG_OF_CONFIGFS`. Perhaps configfs can be used to load DT overlays dynamically? Let's investigate `CONFIG_OF_CONFIGFS` and figure out if this is a viable path forward.



16/2 2026/09/12 13:09 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "CONFIG_OF_CONFIGFS"
}

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

17/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42251
							output: 19
							thoughts: 10
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm deep in my domain of expertise:

**Checking `OF_OVERLAY`**

Right, let's take a quick look at `OF_OVERLAY`. That variable's significance is immediately apparent. Is it related to the rendering pipeline, the compositing stage, or perhaps something more specific to the particular frame-buffer setup? Depending on the context I am working within, its purpose will change. I should quickly check to see whether this refers to the OpenGL or Vulkan backend, or my own implementation of a custom API. Regardless, it's almost certainly involved in how the final image is constructed, particularly if we're dealing with layered visuals or effects. My immediate need is to understand what part of the system is failing, and if `OF_OVERLAY` is active, that would likely be my first point of investigation. Is this variable being correctly set? Are its values consistent with expectations? Are there any obvious conditional branches that depend on this variable and might be leading to unexpected behavior? Gotta jump in and get a grip on that quickly.



18/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "config OF_OVERLAY"
}

Results:
{
  "Output": "drivers/of/Kconfig=102=config OF_RESOLVE\n--\ndrivers/of/Kconfig-104-\ndrivers/of/Kconfig:105:config OF_OVERLAY\ndrivers/of/Kconfig-106-\tbool \"Device Tree overlays\"\n--\ndrivers/of/Kconfig-115-\ndrivers/of/Kconfig:116:config OF_OVERLAY_KUNIT_TEST\ndrivers/of/Kconfig-117-\ttristate \"Device Tree overlay KUnit tests\" if !KUNIT_ALL_TESTS\n"
}

19/2 2026/09/12 13:09 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42661
							output: 22
							thoughts: 49
						

Thoughts:
**Investigating Userspace Overlay Control**

Okay, so `OF_OVERLAY` is defined within `drivers/of/Kconfig`, that's the starting point. My next thought is: can userspace actually *apply* device tree overlays? That's the crucial question. I need to determine if there's any available interface for userspace to interact with the overlay mechanism. Essentially, I need to understand if this is a privileged operation or if it's designed to be accessible to standard user processes. The Kconfig location only tells me it's *defined*, not how it's *used*. I need to find the userland API, if one even exists.



20/2 2026/09/12 13:09 tool grepper
0m Args:
{
  "Expression": "of_overlay_fdt_apply"
}

Results:
{
  "Output": "Documentation/devicetree/overlay-notes.rst=109=The API is quite easy to use.\nDocumentation/devicetree/overlay-notes.rst-110-\nDocumentation/devicetree/overlay-notes.rst:111:1) Call of_overlay_fdt_apply() to create and apply an overlay changeset. The\nDocumentation/devicetree/overlay-notes.rst-112-   return value is an error or a cookie identifying this overlay.\n--\nDocumentation/devicetree/overlay-notes.rst-114-2) Call of_overlay_remove() to remove and clean up the overlay changeset\nDocumentation/devicetree/overlay-notes.rst:115:   previously created via the call to of_overlay_fdt_apply(). Removal of an\nDocumentation/devicetree/overlay-notes.rst-116-   overlay changeset that is stacked by another will not be permitted.\n--\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst=90=DT中的适当位置。在这种情况下,可以提供目标路径。通过标签的目标位置的语法是比\n--\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst-112-\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst:113:1) 调用of_overlay_fdt_apply()来创建和应用一个覆盖的变更集。返回值是一个\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst-114-   错误或一个识别这个覆盖的cookie。\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst-115-\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst:116:2) 调用of_overlay_remove()来删除和清理先前通过调用of_overlay_fdt_apply()\nDocumentation/translations/zh_CN/devicetree/overlay-notes.rst-117-   而创建的覆盖变更集。不允许删除一个被另一个覆盖的覆盖变化集。\n--\nMAINTAINERS=20465=K:\tof_overlay_notifier_\nMAINTAINERS:20466:K:\tof_overlay_fdt_apply\nMAINTAINERS-20467-K:\tof_overlay_remove\n--\ndrivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.c=20=static int __init imx8mp_hdmi_tx_connector_fixup_init(void)\n--\ndrivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.c-67-\ndrivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.c:68:\terr = of_overlay_fdt_apply(dtbo_start, dtbo_size, \u0026ovcs_id, NULL);\ndrivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.c-69-\tif (err)\n--\ndrivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c=143=static int __init tilcdc_panel_legacy_init(void)\n--\ndrivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c-163-\ndrivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c:164:\tret = of_overlay_fdt_apply(dtbo_start, dtbo_size, \u0026ovcs_id, NULL);\ndrivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c-165-\tif (ret)\n--\ndrivers/misc/lan966x_pci.c=126=static int lan966x_pci_load_overlay(struct lan966x_pci *data)\n--\ndrivers/misc/lan966x_pci.c-130-\ndrivers/misc/lan966x_pci.c:131:\treturn of_overlay_fdt_apply(dtbo_start, dtbo_size, \u0026data-\u003eovcs_id, dev_of_node(data-\u003edev));\ndrivers/misc/lan966x_pci.c-132-}\n--\ndrivers/of/of_kunit_helpers.c=25=EXPORT_SYMBOL_GPL(of_root_kunit_skip);\n--\ndrivers/of/of_kunit_helpers.c-28-\ndrivers/of/of_kunit_helpers.c:29:static void of_overlay_fdt_apply_kunit_exit(void *ovcs_id)\ndrivers/of/of_kunit_helpers.c-30-{\n--\ndrivers/of/of_kunit_helpers.c-34-/**\ndrivers/of/of_kunit_helpers.c:35: * of_overlay_fdt_apply_kunit() - Test managed of_overlay_fdt_apply()\ndrivers/of/of_kunit_helpers.c-36- * @test: test context\n--\ndrivers/of/of_kunit_helpers.c-40- *\ndrivers/of/of_kunit_helpers.c:41: * Just like of_overlay_fdt_apply(), except the overlay is managed by the test\ndrivers/of/of_kunit_helpers.c-42- * case and is automatically removed with of_overlay_remove() after the test\n--\ndrivers/of/of_kunit_helpers.c-46- */\ndrivers/of/of_kunit_helpers.c:47:int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt,\ndrivers/of/of_kunit_helpers.c-48-\t\t\t       u32 overlay_fdt_size, int *ovcs_id)\n--\ndrivers/of/of_kunit_helpers.c-58-\ndrivers/of/of_kunit_helpers.c:59:\tret = of_overlay_fdt_apply(overlay_fdt, overlay_fdt_size,\ndrivers/of/of_kunit_helpers.c-60-\t\t\t\t   ovcs_id, NULL);\n--\ndrivers/of/of_kunit_helpers.c-65-\ndrivers/of/of_kunit_helpers.c:66:\treturn kunit_add_action_or_reset(test, of_overlay_fdt_apply_kunit_exit,\ndrivers/of/of_kunit_helpers.c-67-\t\t\t\t\t copy_id);\ndrivers/of/of_kunit_helpers.c-68-}\ndrivers/of/of_kunit_helpers.c:69:EXPORT_SYMBOL_GPL(of_overlay_fdt_apply_kunit);\ndrivers/of/of_kunit_helpers.c-70-\n--\ndrivers/of/overlay.c=926=static int of_overlay_apply(struct overlay_changeset *ovcs,\n--\ndrivers/of/overlay.c-980-/**\ndrivers/of/overlay.c:981: * of_overlay_fdt_apply() - Create and apply an overlay changeset\ndrivers/of/overlay.c-982- * @overlay_fdt:\tpointer to overlay FDT\n--\ndrivers/of/overlay.c-999-\ndrivers/of/overlay.c:1000:int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,\ndrivers/of/overlay.c-1001-\t\t\t int *ret_ovcs_id, const struct device_node *base)\n--\ndrivers/of/overlay.c-1075-\t * result in a memory leak if the apply partly succeeded, so do NOT\ndrivers/of/overlay.c:1076:\t * goto err_free_ovcs.  Instead, the caller of of_overlay_fdt_apply()\ndrivers/of/overlay.c-1077-\t * can call of_overlay_remove();\n--\ndrivers/of/overlay.c-1089-}\ndrivers/of/overlay.c:1090:EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);\ndrivers/of/overlay.c-1091-\n--\ndrivers/of/overlay.c=1155=static int overlay_removal_is_ok(struct overlay_changeset *remove_ovcs)\n--\ndrivers/of/overlay.c-1173- * Removes an overlay if it is permissible.  @ovcs_id was previously returned\ndrivers/of/overlay.c:1174: * by of_overlay_fdt_apply().\ndrivers/of/overlay.c-1175- *\n--\ndrivers/of/unittest.c=3987=static int __init overlay_data_apply(const char *overlay_name, int *ovcs_id)\n--\ndrivers/of/unittest.c-4009-\ndrivers/of/unittest.c:4010:\tret = of_overlay_fdt_apply(info-\u003edtbo_begin, size, \u0026info-\u003eovcs_id,\ndrivers/of/unittest.c-4011-\t\t\t\t   NULL);\n--\ndrivers/of/unittest.c-4020-\tif (ret != info-\u003eexpected_result) {\ndrivers/of/unittest.c:4021:\t\tpr_err(\"of_overlay_fdt_apply() expected %d, ret=%d, %s\\n\",\ndrivers/of/unittest.c-4022-\t\t       info-\u003eexpected_result, ret, overlay_name);\n--\ndrivers/of/unittest.c=4328=static int testdrv_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n--\ndrivers/of/unittest.c-4350-\tsize = info-\u003edtbo_end - info-\u003edtbo_begin;\ndrivers/of/unittest.c:4351:\tret = of_overlay_fdt_apply(info-\u003edtbo_begin, size, \u0026ovcs_id, dn);\ndrivers/of/unittest.c-4352-\tif (ret)\n--\ninclude/kunit/of.h=16=void of_node_put_kunit(struct kunit *test, struct device_node *node)\n--\ninclude/kunit/of.h-24-\ninclude/kunit/of.h:25:int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt,\ninclude/kunit/of.h-26-\t\t\t       u32 overlay_fdt_size, int *ovcs_id);\n--\ninclude/kunit/of.h=29=static inline int\ninclude/kunit/of.h:30:of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt,\ninclude/kunit/of.h-31-\t\t\t   u32 overlay_fdt_size, int *ovcs_id)\n--\ninclude/kunit/of.h-39-/**\ninclude/kunit/of.h:40: * __of_overlay_apply_kunit() - Test managed of_overlay_fdt_apply() variant\ninclude/kunit/of.h-41- * @test: test context\n--\ninclude/kunit/of.h-47- *\ninclude/kunit/of.h:48: * Similar to of_overlay_fdt_apply(), except the overlay is managed by the test\ninclude/kunit/of.h-49- * case and is automatically removed with of_overlay_remove() after the test\n--\ninclude/kunit/of.h=54=static inline int __of_overlay_apply_kunit(struct kunit *test,\n--\ninclude/kunit/of.h-59-\ninclude/kunit/of.h:60:\treturn of_overlay_fdt_apply_kunit(test, overlay_begin,\ninclude/kunit/of.h-61-\t\t\t\t\t  overlay_end - overlay_begin,\n--\ninclude/kunit/of.h-72-/**\ninclude/kunit/of.h:73: * of_overlay_apply_kunit() - Test managed of_overlay_fdt_apply() for built-in overlays\ninclude/kunit/of.h-74- * @test: test context\n--\ninclude/linux/of.h=1830=struct of_overlay_notify_data {\n--\ninclude/linux/of.h-1836-\ninclude/linux/of.h:1837:int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,\ninclude/linux/of.h-1838-\t\t\t int *ovcs_id, const struct device_node *target_base);\n--\ninclude/linux/of.h=1843=int of_overlay_notifier_unregister(struct notifier_block *nb);\n--\ninclude/linux/of.h-1846-\ninclude/linux/of.h:1847:static inline int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,\ninclude/linux/of.h-1848-\t\t\t\t       int *ovcs_id, const struct device_node *target_base)\n"
}

21/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 46148
							output: 24
							thoughts: 301
						

Thoughts:
**My Investigation into Applying OF Overlays with PHY Drivers**

Okay, here's what's running through my head. I know there's no clean userspace interface to apply device tree overlays in mainline Linux – the configfs overlay approach was a dead end years ago. But wait... what about leveraging something else that's already in the kernel? The idea of `AIR_EN8811H_PHY`, and PHY drivers in general, just popped into my head. Could I somehow bind or, even better, *fuzz* a PHY driver?

Let's dive in. I need to understand how these PHY drivers work. I'll start by examining `drivers/net/phy/air_en8811h.c` as a representative example. Ah, right, a PHY driver, defined as `struct phy_driver`, gets registered with `phy_driver_register` or `module_phy_driver`. But how do these drivers actually *attach* themselves to a physical PHY?

Thinking this through, a PHY is always connected to an Ethernet MAC device, whether it's a real Ethernet controller, a DSA switch, or something similar. Then I start thinking laterally... can I somehow instantiate a PHY driver using *virtual* netdevs like dummy, veth, loopback, or tun/tap? But that's a dead end - none of those use PHY devices or MII buses.

Then it hits me: USB Ethernet dongles! Could a USB Ethernet dongle utilize an external PHY driver? My hunch says there might be a connection here. I should check if USB Ethernet dongles, like the asix or cdc_ether, leverage an `air_en8811h` or `airoha,en8811h` PHY. Time to grep for `EN8811H_PHY_ID` or `AIR_EN8811H_PHY` in the code and see what turns up. Let's see if this has legs.



22/2 2026/09/12 13:10 tool grepper
0m Args:
{
  "Expression": "AIR_EN8811H_PHY"
}

Results:
{
  "Output": "drivers/net/mdio/Kconfig=32=config MDIO_AIROHA_EN8811H\n--\ndrivers/net/mdio/Kconfig-36-\tselect AIR_NET_PHYLIB\ndrivers/net/mdio/Kconfig:37:\timply AIR_EN8811H_PHY\ndrivers/net/mdio/Kconfig-38-\thelp\n--\ndrivers/net/phy/Kconfig=93=config AIR_AN8801_PHY\n--\ndrivers/net/phy/Kconfig-98-\ndrivers/net/phy/Kconfig:99:config AIR_EN8811H_PHY\ndrivers/net/phy/Kconfig-100-\ttristate \"Airoha EN8811H 2.5 Gigabit PHY\"\n--\ndrivers/net/phy/Makefile=33=obj-$(CONFIG_AIR_AN8801_PHY)\t+= air_an8801.o\ndrivers/net/phy/Makefile:34:obj-$(CONFIG_AIR_EN8811H_PHY)   += air_en8811h.o\ndrivers/net/phy/Makefile-35-obj-$(CONFIG_AIR_NET_PHYLIB)\t+= air_phy_lib.o\n"
}

23/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 47216
							output: 45
							thoughts: 22
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing `air_en8811h.c` for Compatibility**

Okay, so I'm looking at this new piece of code and the first thing that jumps out is the need to verify its compatibility. Since we're dealing with a system that's likely already in place, the file name `air_en8811h.c` immediately triggers a search for existing integration points. I'm going to start by cross-referencing this against our existing `air_en8811h.c` – assuming there is one, of course. I need to understand the potential overlap, if any. Is this a replacement, an extension, or something entirely new? What's the best way to do this? Looking at the file name, the easiest and fastest way to get a good bearing is going to be comparing this to the existing file. That's where I will start. I will look for existing functions, data structures, and any other indicators that give an early indication of the scope and purpose. A function-by-function comparison is probably the best place to start. I'll be looking for changes in function signatures, new function calls, and particularly, any modifications to the core data structures that this file uses. The goal here is a quick assessment – a high-level view of how this new version aligns with, or diverges from, the current implementation. I'm looking for the 'aha!' moment - a clear pointer to the critical changes or dependencies.



24/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/air_en8811h.c",
  "FirstLine": 1260,
  "LineCount": 40
}

Results:
{
  "Contents": "1260:\t\tphydev-\u003emaster_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED;\n1261:\t\tphydev-\u003emaster_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;\n1262:\t\tphydev-\u003espeed = SPEED_UNKNOWN;\n1263:\t\tphydev-\u003eduplex = DUPLEX_UNKNOWN;\n1264:\t\tphydev-\u003epause = 0;\n1265:\t\tphydev-\u003easym_pause = 0;\n1266:\t\tphydev-\u003erate_matching = RATE_MATCH_PAUSE;\n1267:\t\n1268:\t\tret = genphy_read_master_slave(phydev);\n1269:\t\tif (ret \u003c 0)\n1270:\t\t\treturn ret;\n1271:\t\n1272:\t\tret = genphy_read_lpa(phydev);\n1273:\t\tif (ret \u003c 0)\n1274:\t\t\treturn ret;\n1275:\t\n1276:\t\tif (phy_id_compare_model(phydev-\u003ephy_id, AN8811HB_PHY_ID)) {\n1277:\t\t\tval = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);\n1278:\t\t\tif (val \u003c 0)\n1279:\t\t\t\treturn val;\n1280:\t\t\tlinkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,\n1281:\t\t\t\t\t phydev-\u003elp_advertising,\n1282:\t\t\t\t\t val \u0026 MDIO_AN_10GBT_STAT_LP2_5G);\n1283:\t\t} else {\n1284:\t\t\t/* Get link partner 2.5GBASE-T ability from vendor register */\n1285:\t\t\tret = air_phy_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA,\n1286:\t\t\t\t\t\t\t\u0026pbus_value);\n1287:\t\t\tif (ret \u003c 0)\n1288:\t\t\t\treturn ret;\n1289:\t\t\tlinkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,\n1290:\t\t\t\t\t phydev-\u003elp_advertising,\n1291:\t\t\t\t\t pbus_value \u0026 EN8811H_2P5G_LPA_2P5G);\n1292:\t\t}\n1293:\t\n1294:\t\tif (phydev-\u003eautoneg_complete)\n1295:\t\t\tphy_resolve_aneg_pause(phydev);\n1296:\t\n1297:\t\tif (!phydev-\u003elink)\n1298:\t\t\treturn 0;\n1299:\t\n"
}

25/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 48330
							output: 45
							thoughts: 19
						

Thoughts:
Okay, here's my interpretation of "Let's see the rest of `air_en8811h.c`," summarized in the first person, assuming I'm an expert in the field:

**Diving into `air_en8811h.c`**

Alright, time to get into the nitty-gritty. "Let's see the rest of `air_en8811h.c`" – that's the code I want to examine now. I'm already familiar with the initial parts, but understanding the entirety of the file is crucial to get the full picture, to grasp its overall behaviour and to identify where potential problems could be. I need to understand its relationship to other modules, the control flow, the data structures at play – everything. The previous portion of code likely provided a context to understand the intention behind the upcoming lines of code, and that I need to be aware of. I need to quickly identify and understand the algorithms used, the driver-specific functions, error handling, and any relevant hardware interactions.
I'm particularly interested in seeing how the prior code flows into this code, what's being initialized, what data is being passed around, how the important parts of the hardware are interfaced, and what edge cases are considered. I need to quickly grasp the implications of these lines: are there any potential race conditions? Memory leaks? Driver-specific limitations? This 'dive' will likely trigger questions to ensure my understanding of the functionality. Let's see if this code is well-structured, maintainable, and efficient. Time to dig in.



26/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/air_en8811h.c",
  "FirstLine": 1300,
  "LineCount": 70
}

Results:
{
  "Contents": "1300:\t\t/* Get real speed from vendor register */\n1301:\t\tval = phy_read(phydev, AIR_AUX_CTRL_STATUS);\n1302:\t\tif (val \u003c 0)\n1303:\t\t\treturn val;\n1304:\t\tswitch (val \u0026 AIR_AUX_CTRL_STATUS_SPEED_MASK) {\n1305:\t\tcase AIR_AUX_CTRL_STATUS_SPEED_2500:\n1306:\t\t\tphydev-\u003espeed = SPEED_2500;\n1307:\t\t\tbreak;\n1308:\t\tcase AIR_AUX_CTRL_STATUS_SPEED_1000:\n1309:\t\t\tphydev-\u003espeed = SPEED_1000;\n1310:\t\t\tbreak;\n1311:\t\tcase AIR_AUX_CTRL_STATUS_SPEED_100:\n1312:\t\t\tphydev-\u003espeed = SPEED_100;\n1313:\t\t\tbreak;\n1314:\t\tcase AIR_AUX_CTRL_STATUS_SPEED_10:\n1315:\t\t\tphydev-\u003espeed = SPEED_10;\n1316:\t\t\tbreak;\n1317:\t\t}\n1318:\t\n1319:\t\t/* Firmware before version 24011202 has no vendor register 2P5G_LPA.\n1320:\t\t * Assume link partner advertised it if connected at 2500Mbps.\n1321:\t\t */\n1322:\t\tif (priv-\u003efirmware_version \u003c 0x24011202) {\n1323:\t\t\tlinkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,\n1324:\t\t\t\t\t phydev-\u003elp_advertising,\n1325:\t\t\t\t\t phydev-\u003espeed == SPEED_2500);\n1326:\t\t}\n1327:\t\n1328:\t\t/* Only supports full duplex */\n1329:\t\tphydev-\u003eduplex = DUPLEX_FULL;\n1330:\t\n1331:\t\treturn 0;\n1332:\t}\n1333:\t\n1334:\tstatic int en8811h_clear_intr(struct phy_device *phydev)\n1335:\t{\n1336:\t\tint ret;\n1337:\t\n1338:\t\tret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3,\n1339:\t\t\t\t    AIR_PHY_MCU_CMD_3_DOCMD);\n1340:\t\tif (ret \u003c 0)\n1341:\t\t\treturn ret;\n1342:\t\n1343:\t\tret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4,\n1344:\t\t\t\t    AIR_PHY_MCU_CMD_4_INTCLR);\n1345:\t\tif (ret \u003c 0)\n1346:\t\t\treturn ret;\n1347:\t\n1348:\t\treturn 0;\n1349:\t}\n1350:\t\n1351:\tstatic irqreturn_t en8811h_handle_interrupt(struct phy_device *phydev)\n1352:\t{\n1353:\t\tint ret;\n1354:\t\n1355:\t\tret = en8811h_clear_intr(phydev);\n1356:\t\tif (ret \u003c 0) {\n1357:\t\t\tphy_error(phydev);\n1358:\t\t\treturn IRQ_NONE;\n1359:\t\t}\n1360:\t\n1361:\t\tphy_trigger_machine(phydev);\n1362:\t\n1363:\t\treturn IRQ_HANDLED;\n1364:\t}\n1365:\t\n1366:\tstatic int en8811h_resume(struct phy_device *phydev)\n1367:\t{\n1368:\t\tclk_restore_context();\n1369:\t\n"
}

27/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 49790
							output: 45
							thoughts: 19
						

Thoughts:
Here's my take on the thoughts, rendered as a first-person summary, keeping in mind my expertise:

**Diving into the `air_en8811h.c` Finale**

Alright, let's get down to the business end of `air_en8811h.c`. I need to understand how this module wraps up. I'm looking for the critical exit points, any final resource releases, and the overall success or failure handling. Specifically, I'm interested in:

*   **Error Reporting:** Are there any last-ditch error checks? Are error codes propagated correctly and are they logged sensibly? I want to make sure the error handling is robust, not just a bare-bones `return -1`.
*   **Resource Cleanup:** Did they remember to free all allocated memory? File handles? Anything that needs explicit deallocation before exit. Memory leaks are my bane. I'm going to look for those deallocation functions.
*   **State Management:** What's the final state of any shared data structures or hardware registers after the module's work is supposedly done? Did it leave things in a consistent and safe state?
*   **Success Indicator:** What's the function's return value? What signals success? A 0? A positive value? I need to understand this module's "OK, I'm done" signal to see if it's reliable.
*   **Potential Race Conditions or Concurrency Issues:** Finally, I'll be looking for any hints of incomplete work or unfinished business that might impact other modules that depend on this one. I want to make sure all of the tasks performed by the function have been completed and are not likely to create an issue if other tasks are running at the same time.

In other words, I'm hunting for any potential gotchas or vulnerabilities in the way this module closes out its operations. I need to make sure this code is not just functioning, but functioning soundly and reliably, without surprises. I'm looking to apply my experience in the field to identify risks.



28/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/air_en8811h.c",
  "FirstLine": 1410,
  "LineCount": 40
}

Results:
{
  "Contents": "1410:\t\t.led_hw_control_get\t= air_led_hw_control_get,\n1411:\t},\n1412:\t{\n1413:\t\tPHY_ID_MATCH_MODEL(AN8811HB_PHY_ID),\n1414:\t\t.name\t\t\t= \"Airoha AN8811HB\",\n1415:\t\t.probe\t\t\t= an8811hb_probe,\n1416:\t\t.remove\t\t\t= an8811hb_remove,\n1417:\t\t.get_features\t\t= en8811h_get_features,\n1418:\t\t.config_init\t\t= an8811hb_config_init,\n1419:\t\t.get_rate_matching\t= en8811h_get_rate_matching,\n1420:\t\t.config_aneg\t\t= en8811h_config_aneg,\n1421:\t\t.read_status\t\t= en8811h_read_status,\n1422:\t\t.resume\t\t\t= en8811h_resume,\n1423:\t\t.suspend\t\t= en8811h_suspend,\n1424:\t\t.config_intr\t\t= en8811h_clear_intr,\n1425:\t\t.handle_interrupt\t= en8811h_handle_interrupt,\n1426:\t\t.led_hw_is_supported\t= en8811h_led_hw_is_supported,\n1427:\t\t.read_page\t\t= air_phy_read_page,\n1428:\t\t.write_page\t\t= air_phy_write_page,\n1429:\t\t.led_blink_set\t\t= air_led_blink_set,\n1430:\t\t.led_brightness_set\t= air_led_brightness_set,\n1431:\t\t.led_hw_control_set\t= air_led_hw_control_set,\n1432:\t\t.led_hw_control_get\t= air_led_hw_control_get,\n1433:\t} };\n1434:\t\n1435:\tmodule_phy_driver(en8811h_driver);\n1436:\t\n1437:\tstatic const struct mdio_device_id __maybe_unused en8811h_tbl[] = {\n1438:\t\t{ PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) },\n1439:\t\t{ PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID) },\n1440:\t\t{ }\n1441:\t};\n1442:\t\n1443:\tMODULE_DEVICE_TABLE(mdio, en8811h_tbl);\n1444:\tMODULE_FIRMWARE(EN8811H_MD32_DM);\n1445:\tMODULE_FIRMWARE(EN8811H_MD32_DSP);\n1446:\tMODULE_FIRMWARE(AN8811HB_MD32_DM);\n1447:\tMODULE_FIRMWARE(AN8811HB_MD32_DSP);\n1448:\t\n1449:\tMODULE_DESCRIPTION(\"Airoha EN8811H and AN8811HB PHY drivers\");\n"
}

29/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 51148
							output: 32
							thoughts: 883
						

Thoughts:
**Analyzing Reachability of Airoha EN8811H Hardware in Syzkaller Fuzzing**

Okay, so I've got two new files to look at: `drivers/net/mdio/mdio-airoha-en8811h.c`, which is a new MDIO driver for the Airoha EN8811H MCU, and `drivers/net/phy/air_en8811h.c`, an existing PHY driver, now refactored to share firmware download code with the new MCU driver. The important part is that the firmware download logic is now in `air_phy_lib.c` (functions like `air_en8811h_fw_download` and `air_fw_write_buf`). This suggests a shared resource for both the MDIO and the PHY.

My immediate question: can these Airoha EN8811H devices, specifically hardware identified by 0x03a2a411 or AN8811HB, or the MDIO interface, be reached by syzkaller fuzzing? I need to consider if I can fuzz the modified code.

Let's carefully refer to the guidelines for when to return `WorthFuzzing=false`. The relevant part is about structurally unreachable hardware. It specifically mentions vendor-specific PCIe switches, SmartNICs, GPU drivers that rely on physical hardware not emulated by standard QEMU. It also mentions driver teardown and architectures other than the target.

The question then is, are PHY drivers considered "structurally unreachable hardware"? I need to analyze this very carefully. A PHY driver is for a Physical Layer transceiver chip, the stuff that talks to the physical network cable. How is a PHY device actually *instantiated* in a virtualized environment like QEMU?

In QEMU, the most common networking setups I know are:

*   **virtio-net**: Doesn't have an MII bus or PHY. It uses virtio queues directly. So, No.
*   **e1000/e1000e**: QEMU emulates an Intel 82540EM/82574L NIC. But the e1000/e1000e driver manages the PHY functionality internally. It *doesn't* use phylib or external PHY drivers. So, No.
*   **ne2k_pci, rtl8139, pcnet32**: None of these use phylib or external PHY drivers. So, No.
*   **Software-emulated devices**: These include USB gadgets.

Wait, this is interesting. Could a USB gadget *emulate* a USB network card that *does* use phylib and an external PHY driver? I need to check if *any* USB network driver uses phylib and external PHY drivers! That could be a way to get to it in a fuzzer.



30/2 2026/09/12 13:10 tool grepper
0m Args:
{
  "Expression": "phy_connect|phy_attach",
  "PathPrefix": "drivers/net/usb"
}

Results:
{
  "Output": "drivers/net/usb/asix_devices.c=703=static int ax88772_init_phy(struct usbnet *dev)\n--\ndrivers/net/usb/asix_devices.c-722-\ndrivers/net/usb/asix_devices.c:723:\tphy_attached_info(priv-\u003ephydev);\ndrivers/net/usb/asix_devices.c-724-\n--\ndrivers/net/usb/ax88172a.c=261=static int ax88172a_reset(struct usbnet *dev)\n--\ndrivers/net/usb/ax88172a.c-313-\ndrivers/net/usb/ax88172a.c:314:\tpriv-\u003ephydev = phy_connect(dev-\u003enet, priv-\u003ephy_name,\ndrivers/net/usb/ax88172a.c-315-\t\t\t\t   \u0026ax88172a_adjust_link,\n--\ndrivers/net/usb/smsc95xx.c=1147=static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)\n--\ndrivers/net/usb/smsc95xx.c-1284-\ndrivers/net/usb/smsc95xx.c:1285:\tret = phy_connect_direct(dev-\u003enet, pdata-\u003ephydev,\ndrivers/net/usb/smsc95xx.c-1286-\t\t\t\t \u0026smsc95xx_handle_link_change,\n--\ndrivers/net/usb/smsc95xx.c-1292-\ndrivers/net/usb/smsc95xx.c:1293:\tphy_attached_info(dev-\u003enet-\u003ephydev);\ndrivers/net/usb/smsc95xx.c-1294-\n"
}

31/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 53170
							output: 30
							thoughts: 100
						

Thoughts:
**Examining Network Driver PHY Interactions**

Okay, so I'm diving into some network driver code here. Specifically, I'm looking at `ax88172a.c`, `smsc95xx.c`, and `asix_devices.c`. It's clear these drivers are leveraging the `phylib` framework, which is standard practice for dealing with PHY (Physical Layer) devices in the Linux kernel. Now I'm wondering, which specific PHY IDs are these drivers actually connecting to? I need to understand the hardware they're supporting at a low level.

I'll start by digging into `asix_devices.c` since that seems like a good place to start. I'm going to follow the `priv->phydev` variable – how is it registered and created within the asix driver? Understanding the initialization process of the `phydev` will provide the connection between the Ethernet controller and the physical layer device. This is the crux of how the driver interfaces with the network hardware.



32/2 2026/09/12 13:10 tool grepper
0m Args:
{
  "Expression": "mdiobus_register",
  "PathPrefix": "drivers/net/usb"
}

Results:
{
  "Output": "drivers/net/usb/asix_devices.c=669=static int ax88772_init_mdio(struct usbnet *dev)\n--\ndrivers/net/usb/asix_devices.c-686-\ndrivers/net/usb/asix_devices.c:687:\tret = mdiobus_register(priv-\u003emdio);\ndrivers/net/usb/asix_devices.c-688-\tif (ret) {\n--\ndrivers/net/usb/ax88172a.c=61=static int ax88172a_init_mdio(struct usbnet *dev)\n--\ndrivers/net/usb/ax88172a.c-79-\ndrivers/net/usb/ax88172a.c:80:\tret = mdiobus_register(priv-\u003emdio);\ndrivers/net/usb/ax88172a.c-81-\tif (ret) {\n--\ndrivers/net/usb/lan78xx.c=2091=static int lan78xx_mdio_init(struct lan78xx_net *dev)\n--\ndrivers/net/usb/lan78xx.c-2121-\tnode = of_get_child_by_name(dev-\u003eudev-\u003edev.of_node, \"mdio\");\ndrivers/net/usb/lan78xx.c:2122:\tret = of_mdiobus_register(dev-\u003emdiobus, node);\ndrivers/net/usb/lan78xx.c-2123-\tof_node_put(node);\n--\ndrivers/net/usb/smsc95xx.c=1147=static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)\n--\ndrivers/net/usb/smsc95xx.c-1241-\ndrivers/net/usb/smsc95xx.c:1242:\tret = mdiobus_register(pdata-\u003emdiobus);\ndrivers/net/usb/smsc95xx.c-1243-\tif (ret) {\n"
}

33/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 54054
							output: 39
							thoughts: 273
						

Thoughts:
**My Line of Inquiry**

Okay, so I'm thinking about how things flow when `mdiobus_register` gets called within `asix_devices.c`, `ax88172a.c`, `smsc95xx.c`, or `lan78xx.c`. Right, it's that MDIO bus scan, looking for PHY ID registers - specifically MII_PHYSID1 and MII_PHYSID2 across addresses 0 to 31. This is where it gets interesting...

If we've got an emulated USB device in play – using raw-gadget or dummy_hcd – responding to the USB control transfers that *implement* those MDIO bus reads... is there a vulnerability? Can raw-gadget actually *emulate* an ASIX USB adapter?

Hold on... I'm pretty sure syzkaller has USB fuzzing capabilities, leveraging `sys_raw_gadget` and `dummy_hcd`.

So, the next question is, can syzkaller be configured to return a crafted PHY ID, like 0x03a2a411 (the EN8811H_PHY_ID, as I recall), in response to the USB read requests when probing for an ASIX or SMSC device?

I need to see how `ax88172a` or `smsc95xx` actually *reads* the MDIO bus. Time to dive into `ax88172a.c` and figure this out.



34/2 2026/09/12 13:10 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "mdio_read",
  "PathPrefix": "drivers/net/usb/ax88172a.c"
}

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

35/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 54710
							output: 42
							thoughts: 18
						

Thoughts:
Okay, let's dive into `ax88172a_init_mdio`. As an expert in my field, I know the significance of this function; it's the crucial initialization step for the Media Independent Interface (MII) and potentially the Reduced MII (RMII) within the ASIX AX88172A network controller. It's the point where communication with the PHY (Physical Layer) begins. I immediately expect this function to handle several key tasks:

**Initializing the MDIO Interface for the AX88172A**

First, I anticipate that this function will establish the fundamental communication parameters. This likely involves configuring the MDIO clock frequency – ensuring it's within the PHY's specifications and the AX88172A's capabilities. I'll need to confirm that the appropriate clock division or prescaler is being set. It's almost guaranteed there will be register writes to set this up correctly.

Next, I'll be looking for how the function deals with the MDIO data and command lines (MDC and MDIO). Are they being properly enabled and driven? Are there any specific GPIO assignments, or is it directly handling internal control registers? I'll probably scan for any initialization of internal MDIO control registers within the AX88172A chip itself.

I'll be keen to see if any specific PHY address is pre-configured or if there's a mechanism for automatically detecting the attached PHY. Good implementations often have a means to probe for a PHY. Also, I expect to see some kind of reset sequence, either for the MDIO interface itself or for the PHY, as a fundamental good practice. I hope to find some error handling – what happens if the MDIO initialization fails? Are there retries or fallback mechanisms? If there is an issue, what kind of feedback is given, and what are the debugging points? Finally, I need to see what happens when the initialization succeeds, and what subsequent steps are triggered.



36/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/usb/ax88172a.c",
  "FirstLine": 60,
  "LineCount": 40
}

Results:
{
  "Contents": "  60:\t/* use phylib infrastructure */\n  61:\tstatic int ax88172a_init_mdio(struct usbnet *dev)\n  62:\t{\n  63:\t\tstruct ax88172a_private *priv = dev-\u003edriver_priv;\n  64:\t\tint ret;\n  65:\t\n  66:\t\tpriv-\u003emdio = mdiobus_alloc();\n  67:\t\tif (!priv-\u003emdio) {\n  68:\t\t\tnetdev_err(dev-\u003enet, \"Could not allocate MDIO bus\\n\");\n  69:\t\t\treturn -ENOMEM;\n  70:\t\t}\n  71:\t\n  72:\t\tpriv-\u003emdio-\u003epriv = (void *)dev;\n  73:\t\tpriv-\u003emdio-\u003eread = \u0026asix_mdio_bus_read;\n  74:\t\tpriv-\u003emdio-\u003ewrite = \u0026asix_mdio_bus_write;\n  75:\t\tpriv-\u003emdio-\u003ename = \"Asix MDIO Bus\";\n  76:\t\t/* mii bus name is usb-\u003cusb bus number\u003e-\u003cusb device number\u003e */\n  77:\t\tsnprintf(priv-\u003emdio-\u003eid, MII_BUS_ID_SIZE, \"usb-%03d:%03d\",\n  78:\t\t\t dev-\u003eudev-\u003ebus-\u003ebusnum, dev-\u003eudev-\u003edevnum);\n  79:\t\n  80:\t\tret = mdiobus_register(priv-\u003emdio);\n  81:\t\tif (ret) {\n  82:\t\t\tnetdev_err(dev-\u003enet, \"Could not register MDIO bus\\n\");\n  83:\t\t\tgoto mfree;\n  84:\t\t}\n  85:\t\n  86:\t\tnetdev_info(dev-\u003enet, \"registered mdio bus %s\\n\", priv-\u003emdio-\u003eid);\n  87:\t\treturn 0;\n  88:\t\n  89:\tmfree:\n  90:\t\tmdiobus_free(priv-\u003emdio);\n  91:\t\treturn ret;\n  92:\t}\n  93:\t\n  94:\tstatic void ax88172a_remove_mdio(struct usbnet *dev)\n  95:\t{\n  96:\t\tstruct ax88172a_private *priv = dev-\u003edriver_priv;\n  97:\t\n  98:\t\tnetdev_info(dev-\u003enet, \"deregistering mdio bus %s\\n\", priv-\u003emdio-\u003eid);\n  99:\t\tmdiobus_unregister(priv-\u003emdio);\n"
}

37/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 55833
							output: 21
							thoughts: 52
						

Thoughts:
**My Initial Thoughts on MDIO Bus Registration**

Okay, hold on a second! Before I dive too deep, I need to clarify something about the `mdiobus_register` function. When it's called, does it actually *initiate* a bus scan immediately? I can't just assume anything here. I need to know the exact behavior.

The best way to figure this out is to go straight to the source. Let's look at the implementation of `mdiobus_register` within `drivers/net/phy/mdio_bus.c`. I need to understand exactly what happens inside that function to be sure. Time to crack open the code and see what's what.



38/2 2026/09/12 13:10 tool grepper
0m Args:
{
  "Expression": "int __mdiobus_register"
}

Results:
{
  "Output": "drivers/net/phy/mdio_bus_provider.c=525=static bool mdiobus_prevent_c45_scan(struct mii_bus *bus)\n--\ndrivers/net/phy/mdio_bus_provider.c-552- */\ndrivers/net/phy/mdio_bus_provider.c:553:int __mdiobus_register(struct mii_bus *bus, struct module *owner)\ndrivers/net/phy/mdio_bus_provider.c-554-{\n--\ninclude/linux/phy.h=460=static inline struct mii_bus *mdiobus_alloc(void)\n--\ninclude/linux/phy.h-464-\ninclude/linux/phy.h:465:int __mdiobus_register(struct mii_bus *bus, struct module *owner);\ninclude/linux/phy.h-466-int __devm_mdiobus_register(struct device *dev, struct mii_bus *bus,\n"
}

39/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 56310
							output: 42
							thoughts: 31
						

Thoughts:
Here's a breakdown of my thought process:

**Diving into the MDIO Bus Registration**

Okay, so I'm digging into `drivers/net/phy/mdio_bus_provider.c` to understand the registration process of an MDIO bus. Specifically, I want to understand what's happening within the `__mdiobus_register` function. Given my expertise, I'm expecting this function to be the core of bringing a new MDIO bus instance into the kernel's management. I imagine it'll handle things like: initialization, associating the bus with a specific hardware resource (like a specific I2C or SPI interface), and adding it to the kernel's internal linked lists or data structures for MDIO bus management. I'll likely be looking for how it handles resource allocation, device driver interactions, and any potential locking mechanisms to ensure thread safety. Let's see if my assumptions hold true. I'm keen to see the details of the implementation.



40/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/mdio_bus_provider.c",
  "FirstLine": 580,
  "LineCount": 40
}

Results:
{
  "Contents": " 580:\t\tbus-\u003edev.parent = bus-\u003eparent;\n 581:\t\tbus-\u003edev.class = \u0026mdio_bus_class;\n 582:\t\tbus-\u003edev.groups = NULL;\n 583:\t\tdev_set_name(\u0026bus-\u003edev, \"%s\", bus-\u003eid);\n 584:\t\n 585:\t\t/* If the bus state is allocated, we're registering a fresh bus\n 586:\t\t * that may have a fwnode associated with it. Grab a reference\n 587:\t\t * to the fwnode. This will be dropped when the bus is released.\n 588:\t\t * If the bus was set to unregistered, it means that the bus was\n 589:\t\t * previously registered, and we've already grabbed a reference.\n 590:\t\t */\n 591:\t\tif (bus-\u003estate == MDIOBUS_ALLOCATED)\n 592:\t\t\tfwnode_handle_get(dev_fwnode(\u0026bus-\u003edev));\n 593:\t\n 594:\t\t/* We need to set state to MDIOBUS_UNREGISTERED to correctly release\n 595:\t\t * the device in mdiobus_free()\n 596:\t\t *\n 597:\t\t * State will be updated later in this function in case of success\n 598:\t\t */\n 599:\t\tbus-\u003estate = MDIOBUS_UNREGISTERED;\n 600:\t\n 601:\t\terr = device_register(\u0026bus-\u003edev);\n 602:\t\tif (err) {\n 603:\t\t\tpr_err(\"mii_bus %s failed to register\\n\", bus-\u003eid);\n 604:\t\t\treturn -EINVAL;\n 605:\t\t}\n 606:\t\n 607:\t\tmutex_init(\u0026bus-\u003emdio_lock);\n 608:\t\tmutex_init(\u0026bus-\u003eshared_lock);\n 609:\t\n 610:\t\t/* assert bus level PHY GPIO reset */\n 611:\t\tgpiod = devm_gpiod_get_optional(\u0026bus-\u003edev, \"reset\", GPIOD_OUT_HIGH);\n 612:\t\tif (IS_ERR(gpiod)) {\n 613:\t\t\terr = dev_err_probe(\u0026bus-\u003edev, PTR_ERR(gpiod),\n 614:\t\t\t\t\t    \"mii_bus %s couldn't get reset GPIO\\n\",\n 615:\t\t\t\t\t    bus-\u003eid);\n 616:\t\t\tdevice_del(\u0026bus-\u003edev);\n 617:\t\t\treturn err;\n 618:\t\t} else\tif (gpiod) {\n 619:\t\t\tbus-\u003ereset_gpiod = gpiod;\n"
}

41/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 57277
							output: 42
							thoughts: 24
						

Thoughts:
Here's my take on those lines of code, presented as if I were thinking it through:

**Analyzing the MDIO Bus Provider Code**

Alright, let's dive into lines 630 through 670 of `mdio_bus_provider.c`. I need to get a clear picture of how this bus provider is handling something specific, based on where we are in the code. I'm going to start by getting a handle on the context. Okay, so from the surrounding code it looks like we're deep inside a function dealing with the MDIO bus's low-level operation. We're probably somewhere in a critical section, because this is often the case with hardware interactions.

I'm betting the surrounding code is handling a transaction. So let me look closely at the variable names in this block. Okay, I see `mdio_bus`, `phy_addr`, `reg_addr`, `data`. Right, we're manipulating data transfers. This confirms my suspicion that it is likely a read or write operation happening at a specific PHY register address. The names imply that the `mdio_bus` is the object, `phy_addr` the specific PHY, `reg_addr` the specific register in the PHY, and `data` the value to be transferred or the data that was read from the PHY's register.

I'm going to look for any mutex's, or spinlocks and confirm my initial thought about a critical section. If I don't see any, then I am missing the context. Hopefully I can find what type of action is happening in the current lines of code.

Okay, that seems to fit. Now I need to figure out what specific MDIO transaction, perhaps a read or write operation, it's involved in. Let's see if I can spot any bit-level manipulations or function calls that signal that. Alright, here's where I need to pay close attention. I see the usual suspects `mdio_bus_write()` or `mdio_bus_read()` and those are the telltale indicators of I/O operations. This is all standard stuff, just the kind of thing you expect from an MDIO driver. Let's move along.



42/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/mdio_bus_provider.c",
  "FirstLine": 630,
  "LineCount": 40
}

Results:
{
  "Contents": " 630:\t\t}\n 631:\t\n 632:\t\tif (bus-\u003eread) {\n 633:\t\t\terr = mdiobus_scan_bus_c22(bus);\n 634:\t\t\tif (err)\n 635:\t\t\t\tgoto error;\n 636:\t\t}\n 637:\t\n 638:\t\tprevent_c45_scan = mdiobus_prevent_c45_scan(bus);\n 639:\t\n 640:\t\tif (!prevent_c45_scan \u0026\u0026 bus-\u003eread_c45) {\n 641:\t\t\terr = mdiobus_scan_bus_c45(bus);\n 642:\t\t\tif (err)\n 643:\t\t\t\tgoto error;\n 644:\t\t}\n 645:\t\n 646:\t\tbus-\u003estate = MDIOBUS_REGISTERED;\n 647:\t\tdev_dbg(\u0026bus-\u003edev, \"probed\\n\");\n 648:\t\treturn 0;\n 649:\t\n 650:\terror:\n 651:\t\tfor (i = 0; i \u003c PHY_MAX_ADDR; i++) {\n 652:\t\t\tmdiodev = bus-\u003emdio_map[i];\n 653:\t\t\tif (!mdiodev)\n 654:\t\t\t\tcontinue;\n 655:\t\n 656:\t\t\tmdiodev-\u003edevice_remove(mdiodev);\n 657:\t\t\tmdiodev-\u003edevice_free(mdiodev);\n 658:\t\t}\n 659:\terror_reset_gpiod:\n 660:\t\t/* Put PHYs in RESET to save power */\n 661:\t\tif (bus-\u003ereset_gpiod)\n 662:\t\t\tgpiod_set_value_cansleep(bus-\u003ereset_gpiod, 1);\n 663:\t\n 664:\t\tdevice_del(\u0026bus-\u003edev);\n 665:\t\treturn err;\n 666:\t}\n 667:\tEXPORT_SYMBOL(__mdiobus_register);\n 668:\t\n 669:\tvoid mdiobus_unregister(struct mii_bus *bus)\n"
}

43/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58386
							output: 26
							thoughts: 42
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Analyzing the MDIO Bus Scan in C22 Mode**

Okay, so I'm looking at `mdiobus_scan_bus_c22`. My initial thought is to dive right in and understand the mechanics. The key is in the name – C22 mode suggests Clause 22, the older, simpler MDIO interface protocol. I need to see how it's implemented. Specifically, the function's task is to find connected devices by probing addresses. Seems straightforward enough – the core of it will be iterating through the target PHY addresses, from 0 to 31.



44/2 2026/09/12 13:10 tool grepper
0m Args:
{
  "Expression": "int mdiobus_scan_bus_c22"
}

Results:
{
  "Output": "drivers/net/phy/mdio_bus_provider.c=477=static struct phy_device *mdiobus_scan_c45(struct mii_bus *bus, int addr)\n--\ndrivers/net/phy/mdio_bus_provider.c-481-\ndrivers/net/phy/mdio_bus_provider.c:482:static int mdiobus_scan_bus_c22(struct mii_bus *bus)\ndrivers/net/phy/mdio_bus_provider.c-483-{\n"
}

45/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58739
							output: 42
							thoughts: 34
						

Thoughts:
Here's a breakdown of my thought process:

**Diving into the MDIO Bus Scan**

Okay, so I'm focusing in on `mdiobus_scan_bus_c22` within `drivers/net/phy/mdio_bus_provider.c`. This function is key. I'm immediately thinking, "This has to be where the MDIO bus is actually discovered and populated, specifically adhering to Clause 22 addressing, likely for legacy PHYs." I need to understand exactly how the system is probing for devices. Is it a brute-force approach, iterating through register addresses, or is there a more sophisticated method involving identification sequences or device-specific capabilities signaling? The function name strongly suggests a Clause 22 context, which means limited address space, so I'm expecting a more traditional, perhaps less flexible, enumeration procedure compared to Clause 45. I need to see how they handle potential address collisions, what their error handling looks like, and how the detected PHYs are ultimately registered with the system. My experience tells me there'll be important considerations related to interrupt handling, power management, and clocking associated with that specific bus architecture. Time to dig in and see the implementation details.



46/2 2026/09/12 13:10 tool read-file
0m Args:
{
  "File": "drivers/net/phy/mdio_bus_provider.c",
  "FirstLine": 480,
  "LineCount": 40
}

Results:
{
  "Contents": " 480:\t}\n 481:\t\n 482:\tstatic int mdiobus_scan_bus_c22(struct mii_bus *bus)\n 483:\t{\n 484:\t\tint i;\n 485:\t\n 486:\t\tfor (i = 0; i \u003c PHY_MAX_ADDR; i++) {\n 487:\t\t\tif ((bus-\u003ephy_mask \u0026 BIT(i)) == 0) {\n 488:\t\t\t\tstruct phy_device *phydev;\n 489:\t\n 490:\t\t\t\tphydev = mdiobus_scan_c22(bus, i);\n 491:\t\t\t\tif (IS_ERR(phydev) \u0026\u0026 (PTR_ERR(phydev) != -ENODEV))\n 492:\t\t\t\t\treturn PTR_ERR(phydev);\n 493:\t\t\t}\n 494:\t\t}\n 495:\t\treturn 0;\n 496:\t}\n 497:\t\n 498:\tstatic int mdiobus_scan_bus_c45(struct mii_bus *bus)\n 499:\t{\n 500:\t\tint i;\n 501:\t\n 502:\t\tfor (i = 0; i \u003c PHY_MAX_ADDR; i++) {\n 503:\t\t\tif ((bus-\u003ephy_mask \u0026 BIT(i)) == 0) {\n 504:\t\t\t\tstruct phy_device *phydev;\n 505:\t\n 506:\t\t\t\t/* Don't scan C45 if we already have a C22 device */\n 507:\t\t\t\tif (bus-\u003emdio_map[i])\n 508:\t\t\t\t\tcontinue;\n 509:\t\n 510:\t\t\t\tphydev = mdiobus_scan_c45(bus, i);\n 511:\t\t\t\tif (IS_ERR(phydev) \u0026\u0026 (PTR_ERR(phydev) != -ENODEV))\n 512:\t\t\t\t\treturn PTR_ERR(phydev);\n 513:\t\t\t}\n 514:\t\t}\n 515:\t\treturn 0;\n 516:\t}\n 517:\t\n 518:\t/* There are some C22 PHYs which do bad things when where is a C45\n 519:\t * transaction on the bus, like accepting a read themselves, and\n"
}

47/2 2026/09/12 13:10 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 59679
							output: 28
							thoughts: 38
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into the MDIO Bus Scan**

Okay, so `mdiobus_scan_c22` is the function we need to focus on. My understanding is that its primary role is to bring up a PHY device and get it registered within the system. That makes sense, a standard procedure for this type of system interaction. Let's delve in and see what's really happening under the hood. I want to confirm that registration.



48/2 2026/09/12 13:11 tool grepper
0m Args:
{
  "Expression": "struct phy_device \\*mdiobus_scan_c22"
}

Results:
{
  "Output": "drivers/net/phy/mdio_bus_provider.c=408=static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)\n--\ndrivers/net/phy/mdio_bus_provider.c-458- */\ndrivers/net/phy/mdio_bus_provider.c:459:struct phy_device *mdiobus_scan_c22(struct mii_bus *bus, int addr)\ndrivers/net/phy/mdio_bus_provider.c-460-{\n--\ninclude/linux/phy.h=480=struct mii_bus *mdio_find_bus(const char *mdio_name);\ninclude/linux/phy.h:481:struct phy_device *mdiobus_scan_c22(struct mii_bus *bus, int addr);\ninclude/linux/phy.h-482-\n"
}

49/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 60093
							output: 42
							thoughts: 16
						

Thoughts:
Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:

**Analyzing `mdiobus_scan` in the 408 Context**

Alright, so I need to understand this `mdiobus_scan` function.  Specifically, I want to see how it's being used within the 408 architecture. My mind immediately starts running through the typical considerations: what's the broader hardware context? What's the bus configuration? Are we dealing with a standard MDIO interface, or something vendor-specific? Is this part of the driver initialization sequence, a periodic poll, or triggered by some external event? I want to see how it interacts with the PHY devices attached to the MDIO bus. Is there any error handling? Debugging is always a concern. Is there a way I can isolate the issue if it presents itself? I'm already formulating questions in my head like, *What's the memory map like for the MDIO registers*? And, *How does this implementation differ, if at all, from other MDIO implementations I've worked with*? I'm hoping to get a clearer picture of the data structures involved, and the specific sequence of operations. This should give me a solid base to understand its behavior thoroughly.



50/2 2026/09/12 13:11 tool read-file
0m Args:
{
  "File": "drivers/net/phy/mdio_bus_provider.c",
  "FirstLine": 408,
  "LineCount": 40
}

Results:
{
  "Contents": " 408:\tstatic struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)\n 409:\t{\n 410:\t\tstruct phy_device *phydev = ERR_PTR(-ENODEV);\n 411:\t\tstruct fwnode_handle *fwnode;\n 412:\t\tchar node_name[16];\n 413:\t\tint err;\n 414:\t\n 415:\t\tphydev = get_phy_device(bus, addr, c45);\n 416:\t\tif (IS_ERR(phydev))\n 417:\t\t\treturn phydev;\n 418:\t\n 419:\t#if IS_ENABLED(CONFIG_OF_MDIO)\n 420:\t\t/* For DT, see if the auto-probed phy has a corresponding child\n 421:\t\t * in the bus node, and set the of_node pointer in this case.\n 422:\t\t */\n 423:\t\tof_mdiobus_link_mdiodev(bus, \u0026phydev-\u003emdio);\n 424:\t#endif\n 425:\t\n 426:\t\t/* Search for a swnode for the phy in the swnode hierarchy of the bus.\n 427:\t\t * If there is no swnode for the phy provided, just ignore it.\n 428:\t\t */\n 429:\t\tif (dev_fwnode(\u0026bus-\u003edev) \u0026\u0026 !dev_fwnode(\u0026phydev-\u003emdio.dev)) {\n 430:\t\t\tsnprintf(node_name, sizeof(node_name), \"ethernet-phy@%d\",\n 431:\t\t\t\t addr);\n 432:\t\t\tfwnode = fwnode_get_named_child_node(dev_fwnode(\u0026bus-\u003edev),\n 433:\t\t\t\t\t\t\t     node_name);\n 434:\t\t\tif (fwnode)\n 435:\t\t\t\tdevice_set_node(\u0026phydev-\u003emdio.dev, fwnode);\n 436:\t\t}\n 437:\t\n 438:\t\terr = phy_device_register(phydev);\n 439:\t\tif (err) {\n 440:\t\t\tphy_device_free(phydev);\n 441:\t\t\treturn ERR_PTR(-ENODEV);\n 442:\t\t}\n 443:\t\n 444:\t\treturn phydev;\n 445:\t}\n 446:\t\n 447:\t/**\n"
}

51/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 61076
							output: 35
							thoughts: 1681
						

Thoughts:
**Analyzing the WorthFuzzing Criteria for the Airoha EN8811H PHY Driver**

Okay, let's break this down. My initial thought process revolves around determining if this patch is worth fuzzing according to the given criteria. I'm focusing on "structurally unreachable hardware" as the key negative criterion.

First, I see `get_phy_device` is the entry point, reading PHY ID registers, creating a `phy_device` struct, and registering it. Then, `phy_device_register` triggers the binding process with any registered `phy_driver` that matches the `phy_id`. I immediately consider the PHY driver: `en8811h_driver`. This driver gets probed, and the `en8811h_probe` function is executed. Inside `en8811h_probe`, it loads firmware via `en8811h_load_firmware`, which then calls `air_en8811h_fw_download`. Now, I need to understand if `air_en8811h_fw_download` is relevant. It calls `air_en8811h_mcu_running`, and `request_firmware_direct`.

The critical question here is whether syzkaller can realistically reach this PHY driver, potentially through USB raw_gadget. Is this device structurally reachable? To answer this, I'm carefully re-reading the "negative criteria" in the prompt. I have to determine if this falls under "Structurally unreachable hardware". The criteria specifically mention vendor-specific PCIe switches, SmartNICs, or GPU drivers *requiring physical ASIC/PCIe cards not emulated in standard QEMU*.

I need to understand what this Airoha EN8811H is. The modified files include documentation, the MAINTAINERS file, Kconfig files, and the driver itself. This is critical. I confirm that it is a "Airoha EN8811H 2.5 Gigabit PHY". This implies it's a physical ASIC. Airoha, is a MediaTek subsidiary, and they manufacture Ethernet PHY transceiver ICs. My understanding is that QEMU doesn't emulate these types of specific, vendor-specific PHY devices.

So, is the Airoha EN8811H a physical ASIC/Ethernet PHY chip? Yes, it is. Does QEMU emulate an Airoha EN8811H PHY? No, it does not. Therefore, by default, ANY vendor PHY driver in `drivers/net/phy/` can be considered structurally unreachable.

However, let's examine `mdio-airoha-en8811h.c` too. The Kconfig for it states it depends on `OF_MDIO`, which in turn depends on `OF`. And I'm operating on an amd64 target. Let me verify if OF is used on amd64. I check the Kconfig in `arch/x86` to be absolutely sure.

Given this understanding, I'm leaning heavily towards this being "structurally unreachable hardware" based on the "negative criteria". Therefore, I conclude this patch is likely NOT worth fuzzing.



52/2 2026/09/12 13:11 tool grepper
0m Args:
{
  "Expression": "select OF\\b|depends on OF\\b",
  "PathPrefix": "arch/x86"
}

Results:
{
  "Output": "arch/x86/Kconfig=669=config X86_INTEL_CE\n--\narch/x86/Kconfig-676-\tselect X86_REBOOTFIXUPS\narch/x86/Kconfig:677:\tselect OF\narch/x86/Kconfig-678-\tselect OF_EARLY_FLATTREE\n--\narch/x86/Kconfig=2979=config OLPC\n--\narch/x86/Kconfig-2983-\tselect GPIOLIB_LEGACY\narch/x86/Kconfig:2984:\tselect OF\narch/x86/Kconfig-2985-\tselect OF_PROMTREE\n"
}

53/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 63666
							output: 34
							thoughts: 120
						

Thoughts:
**My Initial Thoughts on OF and x86 Architecture**

Okay, so I'm digging into how the `OF` (Open Firmware) configuration option behaves within the x86 architecture. The initial observation is that on amd64/x86 systems, it seems `OF` is only selected in two specific scenarios: when `X86_INTEL_CE` is enabled and when `OLPC` (One Laptop Per Child) is enabled. The immediate red flag is that both of these are inherently tied to 32-bit architecture, given the dependency on `X86_32`. This raises a crucial question: can `CONFIG_OF` even be legitimately enabled on x86_64 systems? It feels like something isn't quite right. To clarify this, I need to consult the definitive source of truth for x86 architecture configurations, so I'll need to immediately inspect `arch/x86/Kconfig` to look at how `X86_INTEL_CE` and `OLPC` are defined and if there's any pathway for `OF` on x86_64.



54/2 2026/09/12 13:11 tool grepper
0m Args:
{
  "Expression": "config X86_INTEL_CE",
  "PathPrefix": "arch/x86"
}

Results:
{
  "Output": "arch/x86/Kconfig=658=config X86_GOLDFISH\n--\narch/x86/Kconfig-668-\narch/x86/Kconfig:669:config X86_INTEL_CE\narch/x86/Kconfig-670-\tbool \"CE4100 TV platform\"\n"
}

55/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64161
							output: 36
							thoughts: 5
						

Thoughts:
Okay, I'm ready. Let's do this. I'll approach this as if these are my own thoughts, and tailored for a field expert. I'll start with the bolded header.

**Deconstructing and Synthesizing the Textual Essence**

Alright, I'm reading this and immediately I'm processing it as the initial data point. My initial assessment is that I need to filter the chaff and extract the core essence. I'm looking for the underlying structure, the architecture of the argument if there is one, or the key information if it's more descriptive. I am, in effect, performing my own rapid, intuitive parsing. I will need to identify the key elements, and then I must reframe it into something that reflects not just the "what" but the "why". Why is this significant? What are the implications? What assumptions are being made, implicitly or explicitly? Then, and only then, can I synthesize a summary that is both accurate and insightful, that will provide context and insight that makes sense to me, and someone like me. This is where I start.



56/2 2026/09/12 13:11 tool read-file
0m Args:
{
  "File": "arch/x86/Kconfig",
  "FirstLine": 669,
  "LineCount": 15
}

Results:
{
  "Contents": " 669:\tconfig X86_INTEL_CE\n 670:\t\tbool \"CE4100 TV platform\"\n 671:\t\tdepends on PCI\n 672:\t\tdepends on PCI_GODIRECT\n 673:\t\tdepends on X86_IO_APIC\n 674:\t\tdepends on X86_32\n 675:\t\tdepends on X86_EXTENDED_PLATFORM\n 676:\t\tselect X86_REBOOTFIXUPS\n 677:\t\tselect OF\n 678:\t\tselect OF_EARLY_FLATTREE\n 679:\t\thelp\n 680:\t\t  Select for the Intel CE media processor (CE4100) SOC.\n 681:\t\t  This option compiles in support for the CE4100 SOC for settop\n 682:\t\t  boxes and media devices.\n 683:\t\n"
}

57/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64670
							output: 37
							thoughts: 28
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as a first-person expert summary:

**Digging into Processor Dependencies**

Alright, so I'm looking at this architecture and the dependency graph is popping out. I see that `X86_INTEL_CE` has a hard requirement on `X86_32`. That makes perfect sense; `CE` is essentially a more specialized subset built *on top* of the standard 32-bit architecture. It's not a standalone thing. But, this naturally raises a broader question: does the underlying architecture of a processor impact its overall capabilities?

But, it does make me wonder... what's the deal with `OLPC`? Is it also built on `X86_32`? I know it was targeted at a specific kind of computing environment, with certain constraints, but I need to quickly recall its base architecture in order to have a thorough understanding of the relationship between these processor dependencies. I need to make sure that the architectural assumptions are accurate. I have to make sure my mental model of these systems is sound. I'm going to have to do a quick check on the OLPC architecture to solidify this dependency map.



58/2 2026/09/12 13:11 tool read-file
0m Args:
{
  "File": "arch/x86/Kconfig",
  "FirstLine": 2978,
  "LineCount": 15
}

Results:
{
  "Contents": "2978:\t\n2979:\tconfig OLPC\n2980:\t\tbool \"One Laptop Per Child support\"\n2981:\t\tdepends on !X86_PAE\n2982:\t\tselect GPIOLIB\n2983:\t\tselect GPIOLIB_LEGACY\n2984:\t\tselect OF\n2985:\t\tselect OF_PROMTREE\n2986:\t\tselect IRQ_DOMAIN\n2987:\t\tselect OLPC_EC\n2988:\t\thelp\n2989:\t\t  Add support for detecting the unique features of the OLPC\n2990:\t\t  XO hardware.\n2991:\t\n2992:\tconfig OLPC_XO1_PM\n"
}

59/2 2026/09/12 13:11 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65182
							output: 29
							thoughts: 72
						

Thoughts:
**My Initial Confusion and Investigation**

Okay, so the OLPC project, and more specifically its hardware configuration, is heavily reliant on features that are only present in 32-bit x86 architectures. This dependence on the `!X86_PAE` configuration is really starting to make me think about the design choices here. Suddenly, I'm struck by another question... Can `CONFIG_OF`, which I know plays a role in device tree management, even be selected on an amd64 (x86_64) architecture? It seems unlikely, but it's worth checking to see if there's an indirect dependency I am missing. Let me go through the Kconfig files, and see if there are any other configuration options that indirectly pull in Open Firmware on x86 architectures. Time to dig in and clarify this.



60/2 2026/09/12 13:12 tool grepper
0m Args:
{
  "Expression": "select OF\\b",
  "PathPrefix": "arch/x86"
}

Results:
{
  "Output": "arch/x86/Kconfig=669=config X86_INTEL_CE\n--\narch/x86/Kconfig-676-\tselect X86_REBOOTFIXUPS\narch/x86/Kconfig:677:\tselect OF\narch/x86/Kconfig-678-\tselect OF_EARLY_FLATTREE\n--\narch/x86/Kconfig=2979=config OLPC\n--\narch/x86/Kconfig-2983-\tselect GPIOLIB_LEGACY\narch/x86/Kconfig:2984:\tselect OF\narch/x86/Kconfig-2985-\tselect OF_PROMTREE\n"
}

61/2 2026/09/12 13:12 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65647
							output: 32
							thoughts: 58
						

Thoughts:
**Checking Device Tree Configuration**

Okay, let's see, I need to verify something about the Device Tree configuration. Specifically, I want to confirm if the `menuconfig OF` setting in `drivers/of/Kconfig` has a prompt associated with it. Hmm, let's see... looking at the Kconfig file, it looks like it's defined as a boolean with a prompt: `"Device Tree and Open Firmware support"`. Right, so it *does* have a prompt. Now I need to actually read the contents of `drivers/of/Kconfig` to confirm the details.



62/2 2026/09/12 13:12 tool read-file
0m Args:
{
  "File": "drivers/of/Kconfig",
  "FirstLine": 1,
  "LineCount": 25
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\tconfig DTC\n   3:\t\tbool\n   4:\t\n   5:\tconfig GENERIC_BUILTIN_DTB\n   6:\t\tbool\n   7:\t\n   8:\tconfig BUILTIN_DTB_ALL\n   9:\t\tbool\n  10:\t\n  11:\tmenuconfig OF\n  12:\t\tbool \"Device Tree and Open Firmware support\"\n  13:\t\thelp\n  14:\t\t  This option enables the device tree infrastructure.\n  15:\t\t  It is automatically selected by platforms that need it or can\n  16:\t\t  be enabled manually for unittests, overlays or\n  17:\t\t  compile-coverage.\n  18:\t\n  19:\tif OF\n  20:\t\n  21:\tconfig OF_UNITTEST\n  22:\t\tbool \"Device Tree runtime unit tests\"\n  23:\t\tdepends on OF_EARLY_FLATTREE\n  24:\t\tselect IRQ_DOMAIN\n  25:\t\tselect OF_RESOLVE\n"
}

63/2 2026/09/12 13:12 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 66138
							output: 162
							thoughts: 1894
						

Thoughts:
**Analysis of Structural Reachability for Fuzzing**

Okay, so I'm looking at this patch and need to determine if it's suitable for fuzzing.  I see `menuconfig OF` can be manually enabled, which is a good starting point. However, I need to dig deeper. My focus is on the `en8811h_mcu` and `air_en8811h` drivers.

First, `mdio-airoha-en8811h.c` is clearly an MDIO driver.  It's specifically designed for the "airoha,en8811h-mcu" compatible device, which sounds very specific.  The probe function requires a device tree node with that compatible string, and a child "mdio" node.  It uses MDIO to interact with an MD32 microcontroller on the Airoha EN8811H chip, writing and reading specific registers. It’s downloading firmware to the MD32 core through MDIO.

Then, there's `air_en8811h.c` and `air_phy_lib.c`, which are part of the PHY driver for the Airoha EN8811H and AN8811HB Ethernet PHY ASICs.  This driver probes when it finds a PHY ID, either 0x03a2a411 or 0xc0ff04a0. It then loads firmware, also through MDIO.  Both drivers, from what I can see, depend on the *physical* Airoha EN8811H hardware.

The question is, can these drivers execute within standard fuzzing environments? Let's check the guidelines. They explicitly state "Structurally unreachable hardware: Vendor-specific PCIe switches, SmartNICs, or GPU drivers... requiring physical ASIC/PCIe cards not emulated in standard QEMU" means `WorthFuzzing=false`.

I go through the modified files:  Documentation, MAINTAINERS, Kconfig & Makefile are non-executable or build system related. The driver files, which are the core of this change, are for the Airoha EN8811H MDIO controller and PHY, and a library for the PHY. The header files for these drivers are also included.

The Airoha EN8811H is a *physical* Ethernet PHY ASIC.  It's not emulated by QEMU, nor can it be instantiated without *physical* hardware or a specifically designed Device Tree (which I doubt exist generally or for the target platform).  Furthermore, on an amd64 platform, where QEMU/GCE are standard, we are running PC/ACPI platforms, and the hardware we’re talking about is not present.  There is no OpenFirmware or DeviceTree used here, and no Airoha EN8811H hardware available.

Therefore, all of this code is structurally unreachable, because it's vendor-specific, for a specific physical chip that isn’t emulated. This meets the criteria for `WorthFuzzing=false`.

I need to confirm the required format: `WorthFuzzing: false`, `Reasoning` explains the unreachable nature of the code due to the physical hardware dependence, an empty `FocusSymbols`, and `EnableConfigs`.

Double-checking: target architecture is amd64, the negative criteria match, physical Airoha EN8811H, no QEMU emulation.

It all checks out. I'm calling `set-results`.



64/2 2026/09/12 13:12 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new MDIO MCU driver (mdio-airoha-en8811h) and refactors firmware loading in the Airoha EN8811H PHY driver and library (air_en8811h, air_phy_lib). These drivers target the physical Airoha EN8811H 2.5G Ethernet PHY ASIC and its MD32 microcontroller core via MDIO and Device Tree. The underlying hardware is not emulated in standard QEMU/GCE virtualized environments on amd64, rendering the modified code structurally unreachable for syzkaller fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new MDIO MCU driver (mdio-airoha-en8811h) and refactors firmware loading in the Airoha EN8811H PHY driver and library (air_en8811h, air_phy_lib). These drivers target the physical Airoha EN8811H 2.5G Ethernet PHY ASIC and its MD32 microcontroller core via MDIO and Device Tree. The underlying hardware is not emulated in standard QEMU/GCE virtualized environments on amd64, rendering the modified code structurally unreachable for syzkaller fuzzing.",
  "WorthFuzzing": false
}

65/1 2026/09/12 13:12 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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)