AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/08 19:22 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series introduces a device tree binding and a new MDIO driver for the Airoha EN8811H PHY MCU (mdio-airoha-en8811h.c), while refactoring firmware loading and MDIO register access helpers in the Airoha PHY library (air_en8811h.c, air_phy_lib.c). These drivers require specific Airoha physical ASIC/PHY hardware and device-tree bindings that are not emulated in standard virtualized test environments (QEMU/GCE) on amd64, rendering the executable changes structurally unreachable.",
  "WorthFuzzing": false
}

1/1 2026/09/08 19:22 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 895b4bcb4ad50e11a069c1a9e4e803aac57eef58\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 8 19:22:40 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..b44331d80315b\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\n@@ -0,0 +1,119 @@\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.\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..33efc6bc875f4 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 adopts firmware a bootloader\n+\t  left running.\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..7224558914727\n--- /dev/null\n+++ b/drivers/net/mdio/mdio-airoha-en8811h.c\n@@ -0,0 +1,382 @@\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) {\n+\t\tbus-\u003eread = en8811h_mcu_read;\n+\t\tbus-\u003ewrite = en8811h_mcu_write;\n+\t}\n+\tif (parent-\u003eread_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+/* Serialise with the PHY below, which reaches the same registers under\n+ * this lock through phy_select_page().\n+ */\n+static struct mii_bus *en8811h_mcu_chip_lock(struct en8811h_mcu *mcu)\n+{\n+\tstruct mii_bus *bus = mcu-\u003ebus;\n+\n+\tif (bus)\n+\t\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\n+\treturn bus;\n+}\n+\n+static void en8811h_mcu_chip_unlock(struct mii_bus *bus)\n+{\n+\tif (bus)\n+\t\tmutex_unlock(\u0026bus-\u003emdio_lock);\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+\tstruct mii_bus *chip;\n+\tint ret;\n+\n+\tif (!mcu-\u003efw_running) {\n+\t\tchip = en8811h_mcu_chip_lock(mcu);\n+\t\tret = air_en8811h_fw_download(mcu-\u003emdiodev, \u0026mcu-\u003efw_version,\n+\t\t\t\t\t      !!chip);\n+\t\ten8811h_mcu_chip_unlock(chip);\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\tret = firmware_request_cache(dev, EN8811H_MD32_DM) ?:\n+\t\t      firmware_request_cache(dev, EN8811H_MD32_DSP);\n+\t\tif (ret)\n+\t\t\tdev_dbg(dev, \"not cached, resume will want the files: %pe\\n\",\n+\t\t\t\tERR_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, and\n+\t * fwnode_mdio defers while the PHY node's interrupt controller is\n+\t * missing, so a failure is not necessarily permanent.\n+\t */\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, \"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/* Past its budget, only a deferral will change on its own: every\n+\t * other error repeats a bus registration, its message from the MDIO\n+\t * core and its uevents for the uptime. Firmware files are the\n+\t * exception, since they can be installed at any time.\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+\tmcu-\u003epoll_ms = min(mcu-\u003epoll_ms * 2, EN8811H_FW_POLL_MAX_MS);\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+}\n+\n+/* The firmware lives in volatile RAM: no reset while the MD32 reports ready. */\n+static void en8811h_mcu_reset_if_dormant(struct en8811h_mcu *mcu, bool nested)\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, nested);\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 is not a dormant chip, so do not touch a line that\n+\t * is already deasserted. An asserted one is why the read failed.\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+\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)\n+\t\treturn dev_err_probe(dev, -ENODEV,\n+\t\t\t\t     \"no mdio node describing the PHY\\n\");\n+\tof_node_put(np);\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_if_dormant(mcu, false);\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\tmcu-\u003ebus = NULL;\n+\t}\n+}\n+\n+static int en8811h_mcu_resume(struct device *dev)\n+{\n+\tstruct en8811h_mcu *mcu = dev_get_drvdata(dev);\n+\tstruct mii_bus *chip;\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/* Not on the workqueue: a DSA port's PHY resumes right after this\n+\t * one and calls phy_init_hw(), which needs the firmware by then.\n+\t */\n+\tchip = en8811h_mcu_chip_lock(mcu);\n+\ten8811h_mcu_reset_if_dormant(mcu, !!chip);\n+\tret = air_en8811h_fw_download(mcu-\u003emdiodev, \u0026mcu-\u003efw_version, !!chip);\n+\ten8811h_mcu_chip_unlock(chip);\n+\tif (ret \u003c 0) {\n+\t\t/* The reload restores this chip, not the PHY below it: its\n+\t\t * own resume has already failed by then and nothing calls\n+\t\t * phy_init_hw() twice.\n+\t\t */\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\tqueue_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/* Tearing the child bus down under an attached PHY is not\n+\t\t * something this driver can make safe on its own.\n+\t\t */\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..65fa1707f57d1 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,14 @@ 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-{\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-\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-\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+\tint ret = air_en8811h_wait_mcu_ready(\u0026phydev-\u003emdio, false);\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+\tif (ret \u003c 0)\n+\t\tphydev_err(phydev, \"MCU not ready: %pe\\n\", ERR_PTR(ret));\n \n-\treturn 0;\n+\treturn ret;\n }\n \n static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,\n@@ -411,7 +323,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,54 +419,11 @@ 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      false);\n \tif (ret \u003c 0)\n \t\tphydev_err(phydev, \"Load firmware failed: %d\\n\", ret);\n \n@@ -1156,8 +1025,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/* Adopted firmware 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..7f75302736010 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,343 @@ 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+/* Nested when the caller already holds the bus this chip hangs below. */\n+static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)\n+{\n+\tif (nested)\n+\t\tmdiodev_lock_nested(mdiodev);\n+\telse\n+\t\tmdiodev_lock(mdiodev);\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\tmdiodev_unlock(mdiodev);\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+\tmdiodev_unlock(mdiodev);\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+\tmdiodev_unlock(mdiodev);\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+\tmdiodev_unlock(mdiodev);\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+\tmdiodev_unlock(mdiodev);\n+\n+\treturn ret;\n+}\n+\n+int air_en8811h_mcu_running(struct mdio_device *mdiodev, bool nested)\n+{\n+\tint ret = air_mmd_status_read(mdiodev, nested);\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    bool nested)\n+{\n+\tconst struct firmware *fw1, *fw2;\n+\tstruct device *dev = \u0026mdiodev-\u003edev;\n+\tint ret;\n+\n+\tret = air_en8811h_mcu_running(mdiodev, nested);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tif (ret) {\n+\t\tret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,\n+\t\t\t\t\t\t fw_version, nested);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tdev_info(dev, \"MD32 already running, firmware %08x\\n\",\n+\t\t\t *fw_version);\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+\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+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tdev_info(dev, \"MD32 firmware version: %08x\\n\", *fw_version);\n+\n+air_fw_download_out:\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..56ba987a68cc9 100644\n--- a/drivers/net/phy/air_phy_lib.h\n+++ b/drivers/net/phy/air_phy_lib.h\n@@ -10,12 +10,19 @@\n \n #include \u003clinux/phy.h\u003e\n \n+#include \u003clinux/mdio/mdio-airoha-en8811h.h\u003e\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 +36,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 +59,10 @@ 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+struct firmware;\n+\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..4494dc6c1022c\n--- /dev/null\n+++ b/include/linux/mdio/mdio-airoha-en8811h.h\n@@ -0,0 +1,25 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * Copyright (C) 2026 Airoha Technology Corp.\n+ * Copyright (C) 2026 Collabora Ltd.\n+ *                    Louis-Alexis Eyraud \u003clouisalexis.eyraud@collabora.com\u003e\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+\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+/* Returns 1 running, 0 dormant, negative on a failed status read. */\n+int air_en8811h_mcu_running(struct mdio_device *mdiodev, bool nested);\n+/* Returns 1 when it adopted firmware that was already running. */\n+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,\n+\t\t\t    bool nested);\n+\n+#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */\ndiff --git a/include/linux/phy.h b/include/linux/phy.h\nindex 3d8afe6b7f1c5..e74a8f5cf5c64 100644\n--- a/include/linux/phy.h\n+++ b/include/linux/phy.h\n@@ -2284,6 +2284,22 @@ static inline void phy_unlock_mdio_bus(struct phy_device *phydev)\n \tmutex_unlock(\u0026phydev-\u003emdio.bus-\u003emdio_lock);\n }\n \n+static inline void mdiodev_lock(struct mdio_device *mdiodev)\n+{\n+\tmutex_lock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+}\n+\n+/* For a bus reached through another one, as mdiobus_read_nested() is. */\n+static inline void mdiodev_lock_nested(struct mdio_device *mdiodev)\n+{\n+\tmutex_lock_nested(\u0026mdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+}\n+\n+static inline void mdiodev_unlock(struct mdio_device *mdiodev)\n+{\n+\tmutex_unlock(\u0026mdiodev-\u003ebus-\u003emdio_lock);\n+}\n+\n void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)\n \t__printf(2, 3);\n char *phy_attached_info_irq(struct phy_device *phydev)\n"
}

2/1 2026/09/08 19:22 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces a device tree binding and a new MDIO driver for the Airoha EN8811H PHY MCU (mdio-airoha-en8811h.c), while refactoring firmware loading and MDIO register access helpers in the Airoha PHY library (air_en8811h.c, air_phy_lib.c). These drivers require specific Airoha physical ASIC/PHY hardware and device-tree bindings that are not emulated in standard virtualized test environments (QEMU/GCE) on amd64, rendering the executable changes structurally unreachable.",
  "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 895b4bcb4ad50e11a069c1a9e4e803aac57eef58
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 19:22:40 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..b44331d80315b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
@@ -0,0 +1,119 @@
+# 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.
+
+    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..33efc6bc875f4 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 adopts firmware a bootloader
+	  left running.
+
 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..7224558914727
--- /dev/null
+++ b/drivers/net/mdio/mdio-airoha-en8811h.c
@@ -0,0 +1,382 @@
+// 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) {
+		bus->read = en8811h_mcu_read;
+		bus->write = en8811h_mcu_write;
+	}
+	if (parent->read_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;
+}
+
+/* Serialise with the PHY below, which reaches the same registers under
+ * this lock through phy_select_page().
+ */
+static struct mii_bus *en8811h_mcu_chip_lock(struct en8811h_mcu *mcu)
+{
+	struct mii_bus *bus = mcu->bus;
+
+	if (bus)
+		mutex_lock(&bus->mdio_lock);
+
+	return bus;
+}
+
+static void en8811h_mcu_chip_unlock(struct mii_bus *bus)
+{
+	if (bus)
+		mutex_unlock(&bus->mdio_lock);
+}
+
+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;
+	struct mii_bus *chip;
+	int ret;
+
+	if (!mcu->fw_running) {
+		chip = en8811h_mcu_chip_lock(mcu);
+		ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version,
+					      !!chip);
+		en8811h_mcu_chip_unlock(chip);
+		if (ret < 0)
+			goto retry;
+
+		dev_dbg(dev, "firmware %08x running after %ums\n",
+			mcu->fw_version, mcu->waited_ms);
+		ret = firmware_request_cache(dev, EN8811H_MD32_DM) ?:
+		      firmware_request_cache(dev, EN8811H_MD32_DSP);
+		if (ret)
+			dev_dbg(dev, "not cached, resume will want the files: %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, and
+	 * fwnode_mdio defers while the PHY node's interrupt controller is
+	 * missing, so a failure is not necessarily permanent.
+	 */
+	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, "no PHY at address %d, %ums after the firmware started: %pe\n",
+				 mcu->mdiodev->addr, mcu->waited_ms,
+				 ERR_PTR(ret));
+		mcu->warned = true;
+	}
+
+	/* Past its budget, only a deferral will change on its own: every
+	 * other error repeats a bus registration, its message from the MDIO
+	 * core and its uevents for the uptime. Firmware files are the
+	 * exception, since they can be installed at any time.
+	 */
+	if (mcu->fw_running && ret != -EPROBE_DEFER &&
+	    mcu->waited_ms >= EN8811H_FW_WARN_MS)
+		return;
+
+	mcu->poll_ms = min(mcu->poll_ms * 2, EN8811H_FW_POLL_MAX_MS);
+	/* 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));
+}
+
+/* The firmware lives in volatile RAM: no reset while the MD32 reports ready. */
+static void en8811h_mcu_reset_if_dormant(struct en8811h_mcu *mcu, bool nested)
+{
+	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, nested);
+	if (ret > 0) {
+		dev_dbg(dev, "MD32 already running, leaving reset alone\n");
+		return;
+	}
+
+	if (!mcu->reset_gpio)
+		return;
+
+	/* A failed read is not a dormant chip, so do not touch a line that
+	 * is already deasserted. An asserted one is why the read failed.
+	 */
+	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;
+
+	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)
+		return dev_err_probe(dev, -ENODEV,
+				     "no mdio node describing the PHY\n");
+	of_node_put(np);
+
+	/* 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_if_dormant(mcu, false);
+
+	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);
+		mcu->bus = NULL;
+	}
+}
+
+static int en8811h_mcu_resume(struct device *dev)
+{
+	struct en8811h_mcu *mcu = dev_get_drvdata(dev);
+	struct mii_bus *chip;
+	int ret;
+
+	/* Nothing to redo: the poll is armed and thaws with everything else. */
+	if (!mcu->fw_running)
+		return 0;
+
+	/* Not on the workqueue: a DSA port's PHY resumes right after this
+	 * one and calls phy_init_hw(), which needs the firmware by then.
+	 */
+	chip = en8811h_mcu_chip_lock(mcu);
+	en8811h_mcu_reset_if_dormant(mcu, !!chip);
+	ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version, !!chip);
+	en8811h_mcu_chip_unlock(chip);
+	if (ret < 0) {
+		/* The reload restores this chip, not the PHY below it: its
+		 * own resume has already failed by then and nothing calls
+		 * phy_init_hw() twice.
+		 */
+		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;
+		queue_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),
+		/* Tearing the child bus down under an attached PHY is not
+		 * something this driver can make safe on its own.
+		 */
+		.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..65fa1707f57d1 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,14 @@ 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)
-{
-	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));
-	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;
-	}
-
-	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;
+	int ret = air_en8811h_wait_mcu_ready(&phydev->mdio, false);
 
-	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;
-	}
+	if (ret < 0)
+		phydev_err(phydev, "MCU not ready: %pe\n", ERR_PTR(ret));
 
-	return 0;
+	return ret;
 }
 
 static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
@@ -411,7 +323,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,54 +419,11 @@ 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,
+				      false);
 	if (ret < 0)
 		phydev_err(phydev, "Load firmware failed: %d\n", ret);
 
@@ -1156,8 +1025,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;
+	/* Adopted firmware 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..7f75302736010 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,343 @@ 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;
+}
+
+/* Nested when the caller already holds the bus this chip hangs below. */
+static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)
+{
+	if (nested)
+		mdiodev_lock_nested(mdiodev);
+	else
+		mdiodev_lock(mdiodev);
+}
+
+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);
+		}
+
+		mdiodev_unlock(mdiodev);
+		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);
+	}
+
+	mdiodev_unlock(mdiodev);
+	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);
+	}
+
+	mdiodev_unlock(mdiodev);
+	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);
+	}
+
+	mdiodev_unlock(mdiodev);
+	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);
+	mdiodev_unlock(mdiodev);
+
+	return ret;
+}
+
+int air_en8811h_mcu_running(struct mdio_device *mdiodev, bool nested)
+{
+	int ret = air_mmd_status_read(mdiodev, nested);
+
+	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,
+			    bool nested)
+{
+	const struct firmware *fw1, *fw2;
+	struct device *dev = &mdiodev->dev;
+	int ret;
+
+	ret = air_en8811h_mcu_running(mdiodev, nested);
+	if (ret < 0)
+		return ret;
+
+	if (ret) {
+		ret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,
+						 fw_version, nested);
+		if (ret < 0)
+			return ret;
+
+		dev_info(dev, "MD32 already running, firmware %08x\n",
+			 *fw_version);
+		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;
+
+	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);
+	if (ret < 0)
+		goto air_fw_download_out;
+
+	dev_info(dev, "MD32 firmware version: %08x\n", *fw_version);
+
+air_fw_download_out:
+	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..56ba987a68cc9 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -10,12 +10,19 @@
 
 #include <linux/phy.h>
 
+#include <linux/mdio/mdio-airoha-en8811h.h>
+
 #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 +36,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 +59,10 @@ 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);
 
+struct firmware;
+
+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..4494dc6c1022c
--- /dev/null
+++ b/include/linux/mdio/mdio-airoha-en8811h.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2026 Airoha Technology Corp.
+ * Copyright (C) 2026 Collabora Ltd.
+ *                    Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
+ * 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;
+
+#define EN8811H_MD32_DM			"airoha/EthMD32.dm.bin"
+#define EN8811H_MD32_DSP		"airoha/EthMD32.DSP.bin"
+
+/* Returns 1 running, 0 dormant, negative on a failed status read. */
+int air_en8811h_mcu_running(struct mdio_device *mdiodev, bool nested);
+/* Returns 1 when it adopted firmware that was already running. */
+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,
+			    bool nested);
+
+#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 3d8afe6b7f1c5..e74a8f5cf5c64 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2284,6 +2284,22 @@ static inline void phy_unlock_mdio_bus(struct phy_device *phydev)
 	mutex_unlock(&phydev->mdio.bus->mdio_lock);
 }
 
+static inline void mdiodev_lock(struct mdio_device *mdiodev)
+{
+	mutex_lock(&mdiodev->bus->mdio_lock);
+}
+
+/* For a bus reached through another one, as mdiobus_read_nested() is. */
+static inline void mdiodev_lock_nested(struct mdio_device *mdiodev)
+{
+	mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+}
+
+static inline void mdiodev_unlock(struct mdio_device *mdiodev)
+{
+	mutex_unlock(&mdiodev->bus->mdio_lock);
+}
+
 void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)
 	__printf(2, 3);
 char *phy_attached_info_irq(struct phy_device *phydev)

3/2 2026/09/08 19:22 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 21729
							output: 23
							thoughts: 849
						

Thoughts:
**Triage Time: Airoha MDIO Driver**

Okay, let's break down this patch. It's introducing support for an Airoha EN8811H MCU MDIO driver. We're talking about a new driver file (`mdio-airoha-en8811h.c`), some PHY driver refactoring (`air_phy_lib.c`, `air_en8811h.c`), DT bindings (`airoha,en8811h-mcu.yaml`), and some MDIO and PHY-related helper additions in the include directories.

Now, triage time! My initial thought is to assess reachability and fuzzability. The subsystems touched include documentation (non-executable, thankfully), the `MAINTAINERS` file (also not executable), and a handful of driver and header files within the `drivers/net/` and `include/linux/` trees.

First things first, what are we actually dealing with here?  `mdio-airoha-en8811h.c` is an MDIO device driver for an Airoha EN8811H PHY MCU. The `air_en8811h.c` file is the PHY driver for that same Airoha chip.  Essentially, it's an MDIO driver that *talks* to a specific PHY.

The million-dollar question: Can we actually *get* to this in a standard, virtualized environment like QEMU or GCE?  Remember our triage philosophy – reachability is the primary gate.  We're talking about MDIO and PHY drivers. We can't fuzz code that doesn't run.

Let's see... Section 2 of the instructions emphasizes structurally unreachable hardware. We have to ask: do we have access to physical Airoha EN8811H hardware to test this? Probably not. The instructions are clear – we need standard emulated devices. So, is an MDIO driver accessible without real hardware? Is there some way to create it or mock it in userspace?

So, back to the driver. It looks like `en8811h_mcu_driver` is registered as an MDIO driver. MDIO devices are typically discovered through Device Tree (which we have a binding for!), ACPI, or a platform MDIO bus. Okay, syzkaller is running on x86_64/amd64.  Without a dedicated Device Tree node or an MDIO bus instantiated, this driver won't be probed in the QEMU environment.

We need to figure out if MDIO devices or PHY devices can be instantiated or mocked in Linux from userspace. If not, this is going to be a hard "no" for fuzzing, given the current triage guidelines.



4/2 2026/09/08 19:22 tool grepper
0m Args:
{
  "Expression": "airoha,en8811h"
}

Results:
{
  "Output": "Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-3----\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml:4:$id: http://devicetree.org/schemas/net/airoha,en8811h-mcu.yaml#\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-5-$schema: http://devicetree.org/meta-schemas/core.yaml#\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml=12=description: |\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-27-  A board that has firmware in hand while its MDIO bus is enumerated can\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml:28:  describe the PHY on its own, as airoha,en8811h.yaml does, and that form\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-29-  stays valid. This binding is for the boards where firmware arrives\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml=32=properties:\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-36-  compatible:\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml:37:    const: airoha,en8811h-mcu\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-38-\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-67-        type: object\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml:68:        $ref: airoha,en8811h.yaml#\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-69-        unevaluatedProperties: false\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml=92=examples:\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-101-        mcu@d {\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml:102:            compatible = \"airoha,en8811h-mcu\";\nDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml-103-            reg = \u003c0xd\u003e;\n--\nDocumentation/devicetree/bindings/net/airoha,en8811h.yaml-3----\nDocumentation/devicetree/bindings/net/airoha,en8811h.yaml:4:$id: http://devicetree.org/schemas/net/airoha,en8811h.yaml#\nDocumentation/devicetree/bindings/net/airoha,en8811h.yaml-5-$schema: http://devicetree.org/meta-schemas/core.yaml#\n--\nMAINTAINERS=756=S:\tMaintained\nMAINTAINERS:757:F:\tDocumentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\nMAINTAINERS-758-F:\tdrivers/net/mdio/mdio-airoha-en8811h.c\n--\ndrivers/net/mdio/mdio-airoha-en8811h.c=355=static const struct of_device_id en8811h_mcu_of_match[] = {\ndrivers/net/mdio/mdio-airoha-en8811h.c:356:\t{ .compatible = \"airoha,en8811h-mcu\" },\ndrivers/net/mdio/mdio-airoha-en8811h.c-357-\t{ }\n"
}

5/2 2026/09/08 19:22 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 24296
							output: 22
							thoughts: 218
						

Thoughts:
**Examining the PHY Abstraction Layer's Locking Mechanisms**

Okay, let's dive into this. I need to understand the locking strategy used within the PHY abstraction layer, specifically what's been added in `include/linux/phy.h`. From what I see here, it's defining a few inline functions for managing the mutex associated with an MDIO bus. These are crucial: `mdiodev_lock`, `mdiodev_lock_nested`, and `mdiodev_unlock`. They seem straightforward – locking and unlocking the `mdio_lock` mutex that belongs to the MDIO bus. The `nested` version is interesting; it suggests the potential for nested locking, which I'd expect if we're dealing with a bus accessed through another bus, like what you'd see in the `mdiobus_read_nested()` function.

Now, the important question is: where are these functions *actually* being used? Knowing the call sites is key to understanding the control flow and potential contention points within the MDIO framework. Time to fire up the grep and get a list of all their usage to check where the locks are used and in which situations. I need to see the context of these calls to understand the concurrency implications of this design.



6/2 2026/09/08 19:22 tool grepper
0m Args:
{
  "Expression": "mdiodev_(lock|unlock)"
}

Results:
{
  "Output": "drivers/net/phy/air_phy_lib.c=260=static int __air_mdio_restore_page(struct mdio_device *mdiodev,\n--\ndrivers/net/phy/air_phy_lib.c-275-/* Nested when the caller already holds the bus this chip hangs below. */\ndrivers/net/phy/air_phy_lib.c:276:static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)\ndrivers/net/phy/air_phy_lib.c-277-{\ndrivers/net/phy/air_phy_lib.c-278-\tif (nested)\ndrivers/net/phy/air_phy_lib.c:279:\t\tmdiodev_lock_nested(mdiodev);\ndrivers/net/phy/air_phy_lib.c-280-\telse\ndrivers/net/phy/air_phy_lib.c:281:\t\tmdiodev_lock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-282-}\n--\ndrivers/net/phy/air_phy_lib.c=284=int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,\n--\ndrivers/net/phy/air_phy_lib.c-298-\ndrivers/net/phy/air_phy_lib.c:299:\t\tair_mdiodev_lock(mdiodev, nested);\ndrivers/net/phy/air_phy_lib.c-300-\n--\ndrivers/net/phy/air_phy_lib.c-312-\ndrivers/net/phy/air_phy_lib.c:313:\t\tmdiodev_unlock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-314-\t\tif (ret \u003c 0)\n--\ndrivers/net/phy/air_phy_lib.c=324=static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev,\n--\ndrivers/net/phy/air_phy_lib.c-329-\ndrivers/net/phy/air_phy_lib.c:330:\tair_mdiodev_lock(mdiodev, nested);\ndrivers/net/phy/air_phy_lib.c-331-\n--\ndrivers/net/phy/air_phy_lib.c-340-\ndrivers/net/phy/air_phy_lib.c:341:\tmdiodev_unlock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-342-\treturn ret;\n--\ndrivers/net/phy/air_phy_lib.c=345=static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev,\n--\ndrivers/net/phy/air_phy_lib.c-350-\ndrivers/net/phy/air_phy_lib.c:351:\tair_mdiodev_lock(mdiodev, nested);\ndrivers/net/phy/air_phy_lib.c-352-\n--\ndrivers/net/phy/air_phy_lib.c-362-\ndrivers/net/phy/air_phy_lib.c:363:\tmdiodev_unlock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-364-\treturn ret;\n--\ndrivers/net/phy/air_phy_lib.c=367=static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev,\n--\ndrivers/net/phy/air_phy_lib.c-372-\ndrivers/net/phy/air_phy_lib.c:373:\tair_mdiodev_lock(mdiodev, nested);\ndrivers/net/phy/air_phy_lib.c-374-\n--\ndrivers/net/phy/air_phy_lib.c-384-\ndrivers/net/phy/air_phy_lib.c:385:\tmdiodev_unlock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-386-\treturn ret;\n--\ndrivers/net/phy/air_phy_lib.c=414=static int air_mmd_status_read(struct mdio_device *mdiodev, bool nested)\n--\ndrivers/net/phy/air_phy_lib.c-417-\ndrivers/net/phy/air_phy_lib.c:418:\tair_mdiodev_lock(mdiodev, nested);\ndrivers/net/phy/air_phy_lib.c-419-\tret = __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS);\ndrivers/net/phy/air_phy_lib.c:420:\tmdiodev_unlock(mdiodev);\ndrivers/net/phy/air_phy_lib.c-421-\n--\ninclude/linux/phy.h=2282=static inline void phy_unlock_mdio_bus(struct phy_device *phydev)\n--\ninclude/linux/phy.h-2286-\ninclude/linux/phy.h:2287:static inline void mdiodev_lock(struct mdio_device *mdiodev)\ninclude/linux/phy.h-2288-{\n--\ninclude/linux/phy.h-2292-/* For a bus reached through another one, as mdiobus_read_nested() is. */\ninclude/linux/phy.h:2293:static inline void mdiodev_lock_nested(struct mdio_device *mdiodev)\ninclude/linux/phy.h-2294-{\n--\ninclude/linux/phy.h-2297-\ninclude/linux/phy.h:2298:static inline void mdiodev_unlock(struct mdio_device *mdiodev)\ninclude/linux/phy.h-2299-{\n"
}

7/2 2026/09/08 19:23 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 26314
							output: 37
							thoughts: 98
						

Thoughts:
Here's my thought process, as I analyze this:

**Analyzing `mdiodev` Locking in the PHY Driver Context**

Okay, so I'm looking at these locking primitives: `mdiodev_lock`, `mdiodev_unlock`, and `mdiodev_lock_nested`. The code is pointing out that they're all defined as inline helpers, and they're *only* used within `drivers/net/phy/air_phy_lib.c`. That tells me a few things immediately. First, these are likely very specific to the AirPHY implementation. Second, being inline means there's potentially less overhead, as the compiler can optimize them more aggressively based on their usage. Third, I'll need to understand the AirPHY library to understand *why* these locks are necessary and how they're being used.

Now, a critical next step. To get a handle on the broader picture, I need to understand the dependencies. The focus is on finding out what files use `air_phy_lib.c`, as well as `air_en8811h.c` and `mdio-airoha-en8811h.c`, probably to understand what's interacting with the library and therefore, interacting with the locking mechanisms. This is likely where the real meat of the locking logic is. Knowing what calls this code directly is the critical next step. I'm going to look for includes, function calls, and any other references to understand the data flow and concurrency patterns within this portion of the network PHY driver subsystem.



8/2 2026/09/08 19:23 tool grepper
0m Args:
{
  "Expression": "AIR_NET_PHYLIB|AIR_EN8811H_PHY|air_phy_lib"
}

Results:
{
  "Output": "drivers/net/mdio/Kconfig=32=config MDIO_AIROHA_EN8811H\n--\ndrivers/net/mdio/Kconfig-35-\tdepends on FW_LOADER\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\ndrivers/net/phy/Kconfig-94-\ttristate \"Airoha AN8801 Gigabit PHY\"\ndrivers/net/phy/Kconfig:95:\tselect AIR_NET_PHYLIB\ndrivers/net/phy/Kconfig-96-\thelp\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\"\ndrivers/net/phy/Kconfig:101:\tselect AIR_NET_PHYLIB\ndrivers/net/phy/Kconfig-102-\tselect PHY_COMMON_PROPS\n--\ndrivers/net/phy/Kconfig-105-\ndrivers/net/phy/Kconfig:106:config AIR_NET_PHYLIB\ndrivers/net/phy/Kconfig-107-\ttristate\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\ndrivers/net/phy/Makefile-36-obj-$(CONFIG_AMD_PHY)\t\t+= amd.o\n--\ndrivers/net/phy/air_an8801.c-21-\ndrivers/net/phy/air_an8801.c:22:#include \"air_phy_lib.h\"\ndrivers/net/phy/air_an8801.c-23-\n--\ndrivers/net/phy/air_en8811h.c-23-\ndrivers/net/phy/air_en8811h.c:24:#include \"air_phy_lib.h\"\ndrivers/net/phy/air_en8811h.c-25-\n--\ndrivers/net/phy/air_phy_lib.c-18-\ndrivers/net/phy/air_phy_lib.c:19:#include \"air_phy_lib.h\"\ndrivers/net/phy/air_phy_lib.c-20-\n"
}

9/2 2026/09/08 19:23 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 27523
							output: 160
							thoughts: 1387
						

Thoughts:
**Analysis of Reachability for Fuzzing**

Okay, so I'm looking at this patch and my initial thought is, "Can we actually *reach* this code in a standard fuzzing environment?" Let's break it down: `drivers/net/mdio/mdio-airoha-en8811h.c` is the MDIO driver for the Airoha EN8811H hardware, and we also have `drivers/net/phy/air_en8811h.c`, an Ethernet PHY driver for the same. There's also `drivers/net/phy/air_phy_lib.c`, which is the Airoha PHY library, and `air_an8801.c`.

My first gut check is to determine if any of these pieces of code are even remotely accessible in the typical virtualized environments we use (QEMU or GCE on x86_64). I need to determine if Airoha EN8811H hardware or MDIO buses are emulated in QEMU. Then, I need to know if userspace can create or attach MDIO or PHY devices.

From what I understand, MDIO bus registration is handled by Ethernet controller drivers, or by OF platform drivers. PHY devices are probed when the MDIO bus scans addresses, reading the PHY ID registers, or parsing the Device Tree. Specifically, these Airoha MDIO devices are probed via Device Tree `of_mdio`.

The problem is, as far as I know, none of this hardware exists in standard amd64 QEMU/GCE VMs. Syzkaller won't be able to instantiate an arbitrary MDIO or PHY device without virtual hardware or mock devices.

Now, let's go back to the instructions. Section 2 clearly states that we need to return `WorthFuzzing=false` when all the modified code strictly falls into one of these categories. The negative criteria include: Structurally unreachable hardware and non-kernel/non-executable changes. Section 1 also states that reachability is the *primary* gate.

Let's look at the changed files and map them back to those criteria:

1.  `Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml`: Device tree schema documentation. That's non-executable, documentation.
2.  `MAINTAINERS`: Maintainers file update. Again, non-executable.
3.  `drivers/net/mdio/Kconfig`: Added `CONFIG_MDIO_AIROHA_EN8811H`. Build system change.
4.  `drivers/net/mdio/Makefile`: Build rule. Build system change.
5.  `drivers/net/mdio/mdio-airoha-en8811h.c`: New MDIO driver, but *for* a specific Airoha MCU. This is structurally unreachable in our standard environments.
6.  `drivers/net/phy/air_en8811h.c`: Driver for a *specific* Airoha PHY. Structurally unreachable.
7.  `drivers/net/phy/air_phy_lib.c`: Library functions for Airoha PHYs. Functionality is specific to Airoha hardware, so this is also structurally unreachable.
8.  `drivers/net/phy/air_phy_lib.h`: Internal header for Airoha PHYs.
9. `include/linux/mdio/mdio-airoha-en8811h.h`: Header for Airoha EN8811H MCU driver.
10. `include/linux/phy.h`: Adds inline lock helpers. Only used by the Airoha PHY library, so structurally unreachable.

It's clear to me that all modified code is either non-kernel / non-executable (Documentation, MAINTAINERS, Kconfig/Makefile) or specific to Airoha EN8811H / AN8811HB hardware (an MDIO microcontroller / Ethernet PHY chip). And we *know* this hardware is not emulated.

Therefore, my conclusion is that `WorthFuzzing` must be `false`.

For `set-results`, I'll set the following:

*   `WorthFuzzing`: `false`
*   `Reasoning`: "The changes add Device Tree bindings and support for the Airoha EN8811H PHY MCU MDIO driver (`mdio-airoha-en8811h.c`) and refactor firmware loading routines in the Airoha PHY driver library (`air_en8811h.c`, `air_phy_lib.c`). This hardware


10/2 2026/09/08 19:23 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces a device tree binding and a new MDIO driver for the Airoha EN8811H PHY MCU (mdio-airoha-en8811h.c), while refactoring firmware loading and MDIO register access helpers in the Airoha PHY library (air_en8811h.c, air_phy_lib.c). These drivers require specific Airoha physical ASIC/PHY hardware and device-tree bindings that are not emulated in standard virtualized test environments (QEMU/GCE) on amd64, rendering the executable changes structurally unreachable.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces a device tree binding and a new MDIO driver for the Airoha EN8811H PHY MCU (mdio-airoha-en8811h.c), while refactoring firmware loading and MDIO register access helpers in the Airoha PHY library (air_en8811h.c, air_phy_lib.c). These drivers require specific Airoha physical ASIC/PHY hardware and device-tree bindings that are not emulated in standard virtualized test environments (QEMU/GCE) on amd64, rendering the executable changes structurally unreachable.",
  "WorthFuzzing": false
}

11/1 2026/09/08 19:23 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)