| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/29 06:19 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"PHYLINK",
"MDIO_AIROHA_EN8811H",
"AIR_EN8811H_PHY",
"OF_MDIO"
],
"FocusSymbols": [
"phylink_fwnode_phy_connect",
"phylink_disconnect_phy",
"phylink_ethtool_ksettings_get",
"phylink_ethtool_ksettings_set",
"air_en8811h_fw_download"
],
"KMSANReasoning": "The patch series introduces an MDIO device driver for the Airoha EN8811H MCU (mdio-airoha-en8811h.c), refactors firmware loading routines into air_phy_lib.c, and implements a 'slow-to-probe' delayed-work polling mechanism in phylink.c.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Memory Allocations: All newly introduced heap allocations (such as struct en8811h_mcu) are allocated using zero-initializing allocators (devm_kzalloc).\n2. Stack Variables \u0026 Output Arguments: All local variables and output pointers (such as fw_version, assert_us, deassert_us) are explicitly initialized before use or assigned by register read helpers.\n3. User Space Interactions: In phylink_ethtool_ksettings_get(), when a slow PHY is pending, the link modes are explicitly zeroed (linkmode_zero) and fields are set to SPEED_UNKNOWN / DUPLEX_UNKNOWN. Moreover, the ethtool core always zeroes the struct ethtool_link_ksettings before invoking driver callbacks. No uninitialized memory or structure padding is copied to user space.\n4. Concurrency and Lifecycle: The risks in this patch series involve concurrency, delayed work cancellation, workqueue freezing, RTNL locking, and device reference counting (fwnode/phy_device). These potential issues (e.g., use-after-free, double-free, lock ordering/deadlocks) are detected by standard KASAN, LOCKDEP, and refcount tracking.\n\nBecause no uninitialized memory reads or information leaks are introduced or exposed, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies core networking phylink logic (phylink.c) by adding delayed-work polling and state handling for slow-to-probe PHYs, adjusting phylink connect/disconnect lifecycles, and altering ethtool ksettings get/set paths. It also adds the Airoha EN8811H MCU MDIO driver and refactors firmware download routines in the Airoha PHY library.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/29 06:19 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit b9c708433b611814dd69a265f7453ac021c7fcef\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Aug 29 06:19:57 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..b51a14a3b7b51\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml\n@@ -0,0 +1,83 @@\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 driven by an MD32 MCU that executes\n+ firmware loaded over MDIO into volatile RAM. Until that firmware runs,\n+ the chip answers its PHY ID but cannot bring up a link, and the\n+ firmware files may live on a filesystem that is not yet mounted when\n+ the MDIO bus is scanned.\n+\n+ This node describes the MCU as an MDIO device in its own right. The\n+ driver downloads the firmware once the files become available (or\n+ detects firmware already left running by the bootloader) and only\n+ then registers the child MDIO bus, so the PHY node below never\n+ becomes visible before the chip is able to serve it.\n+\n+ The reset line is owned by this node rather than by the PHY node:\n+ the MD32 keeps its firmware only in volatile RAM, so the reset may\n+ be asserted only while the MCU is not executing firmware. A PHY-node\n+ reset would be pulsed by phy_detach() on every unbind and wipe the\n+ running firmware.\n+\n+properties:\n+ compatible:\n+ const: airoha,en8811h-mcu\n+\n+ reg:\n+ maxItems: 1\n+\n+ reset-gpios:\n+ maxItems: 1\n+\n+ reset-assert-us: true\n+\n+ reset-deassert-us: true\n+\n+ mdio:\n+ $ref: mdio.yaml#\n+ unevaluatedProperties: false\n+ description:\n+ The child bus holding the PHY itself, at the same address the\n+ chip answers on the parent bus.\n+\n+required:\n+ - compatible\n+ - reg\n+ - mdio\n+\n+additionalProperties: false\n+\n+examples:\n+ - |\n+ mdio {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ ethernet-phy-mcu@d {\n+ compatible = \"airoha,en8811h-mcu\";\n+ reg = \u003c0xd\u003e;\n+ reset-gpios = \u003c\u0026pio 14 1\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 8\u003e;\n+ };\n+ };\n+ };\n+ };\ndiff --git a/Documentation/devicetree/bindings/net/ethernet-controller.yaml b/Documentation/devicetree/bindings/net/ethernet-controller.yaml\nindex 1bafd687dcb18..ca82672b6cb38 100644\n--- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml\n+++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml\n@@ -113,6 +113,15 @@ properties:\n description:\n Specifies a reference to a node representing an IEEE 1588 PTP device.\n \n+ slow-to-probe:\n+ $ref: /schemas/types.yaml#/definitions/flag\n+ description:\n+ The PHY referenced by phy-handle may not exist, or may not have a\n+ usable driver, when this controller connects to it, because the\n+ driver or firmware arrives later (for example from a filesystem\n+ mounted after the controller probed). Instead of failing, keep\n+ the port and connect the PHY once it becomes usable.\n+\n rx-fifo-depth:\n $ref: /schemas/types.yaml#/definitions/uint32\n description:\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 460cb72688450..21d39049e692b 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -759,6 +759,13 @@ S:\tMaintained\n F:\tDocumentation/devicetree/bindings/net/airoha,en7581-eth.yaml\n F:\tdrivers/net/ethernet/airoha/\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+\n AIROHA PCIE PHY 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 a05229838cb45..d46bebd05cc81 100644\n--- a/drivers/net/mdio/Kconfig\n+++ b/drivers/net/mdio/Kconfig\n@@ -29,6 +29,17 @@ 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+\tselect AIR_NET_PHYLIB\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, before letting the PHY be probed.\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..b28e116289e6f\n--- /dev/null\n+++ b/drivers/net/mdio/mdio-airoha-en8811h.c\n@@ -0,0 +1,300 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * Airoha EN8811H MDIO device driver\n+ *\n+ * Until its firmware has been downloaded the EN8811H is not an Ethernet PHY,\n+ * it is an MD32 microcontroller waiting in its bootloader. Describing it as a\n+ * plain MDIO device lets the firmware be downloaded as soon as the files can\n+ * be read - in practice, once the filesystem holding them has been mounted -\n+ * and lets the PHY appear only after the chip is able to act as one.\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/property.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+\n+#include \"../phy/air_phy_lib.h\"\n+\n+/*\n+ * Poll rather than defer probing: request_firmware_direct() has no usermode\n+ * helper fallback, so a rootfs that is not mounted yet fails immediately and\n+ * would keep the deferred-probe list spinning for the whole mount window.\n+ *\n+ * Never give up. The firmware can arrive arbitrarily late and still be worth\n+ * waiting for - installing the firmware package on a running system is a\n+ * normal thing to do - and a driver that had stopped looking would turn that\n+ * into a needless reboot. Back off to a slow poll instead, and leave a single\n+ * breadcrumb for the system that simply does not have the files.\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+\tunsigned int poll_ms;\n+\tunsigned int waited_ms;\n+\tu32 fw_version;\n+\tbool warned;\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+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\n+\tint ret;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock_nested(\u0026parent-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tret = __mdiobus_read(parent, addr, regnum);\n+\tmutex_unlock(\u0026parent-\u003emdio_lock);\n+\n+\treturn ret;\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+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\n+\tint ret;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock_nested(\u0026parent-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tret = __mdiobus_write(parent, addr, regnum, val);\n+\tmutex_unlock(\u0026parent-\u003emdio_lock);\n+\n+\treturn ret;\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+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\n+\tint ret;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock_nested(\u0026parent-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tret = __mdiobus_c45_read(parent, addr, devad, regnum);\n+\tmutex_unlock(\u0026parent-\u003emdio_lock);\n+\n+\treturn ret;\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+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\n+\tint ret;\n+\n+\tif (addr != mcu-\u003emdiodev-\u003eaddr)\n+\t\treturn -ENODEV;\n+\n+\tmutex_lock_nested(\u0026parent-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tret = __mdiobus_c45_write(parent, addr, devad, regnum, val);\n+\tmutex_unlock(\u0026parent-\u003emdio_lock);\n+\n+\treturn ret;\n+}\n+\n+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)\n+{\n+\tstruct device *dev = \u0026mcu-\u003emdiodev-\u003edev;\n+\tstruct mii_bus *parent = mcu-\u003emdiodev-\u003ebus;\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\tdev_err(dev, \"no mdio node describing the PHY\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tbus = devm_mdiobus_alloc(dev);\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+\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 = devm_of_mdiobus_register(dev, bus, np);\n+\tof_node_put(np);\n+\n+\treturn ret;\n+}\n+\n+static void en8811h_mcu_fw_poll(struct work_struct *work)\n+{\n+\tstruct en8811h_mcu *mcu = container_of(to_delayed_work(work),\n+\t\t\t\t\t struct en8811h_mcu, fw_poll);\n+\tstruct device *dev = \u0026mcu-\u003emdiodev-\u003edev;\n+\tint ret;\n+\n+\t/* The chip enumerates as a C22 PHY; MMD access is indirect */\n+\tret = air_en8811h_fw_download(mcu-\u003emdiodev-\u003ebus, mcu-\u003emdiodev-\u003eaddr,\n+\t\t\t\t false, dev, \u0026mcu-\u003efw_version);\n+\tif (!ret) {\n+\t\tdev_dbg(dev, \"firmware %08x running after %ums\\n\",\n+\t\t\tmcu-\u003efw_version, mcu-\u003ewaited_ms);\n+\n+\t\t/* Unlike a missing firmware file, this does not resolve by\n+\t\t * itself, so unlike the poll there is no retry: surface it\n+\t\t * once and stop.\n+\t\t */\n+\t\tret = en8811h_mcu_bus_register(mcu);\n+\t\tif (ret)\n+\t\t\tdev_err(dev, \"failed to register the PHY's bus: %pe\\n\",\n+\t\t\t\tERR_PTR(ret));\n+\t\treturn;\n+\t}\n+\n+\tmcu-\u003ewaited_ms += mcu-\u003epoll_ms;\n+\tif (!mcu-\u003ewarned \u0026\u0026 mcu-\u003ewaited_ms \u003e= EN8811H_FW_WARN_MS) {\n+\t\t/* Missing files resolve by themselves once installed; a bus\n+\t\t * or register error will not, and deserves its own message.\n+\t\t */\n+\t\tif (ret == -ENOENT)\n+\t\t\tdev_warn(dev, \"still waiting for %s and %s\\n\",\n+\t\t\t\t EN8811H_MD32_DM, EN8811H_MD32_DSP);\n+\t\telse\n+\t\t\tdev_warn(dev, \"firmware download keeps failing: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n+\t\tmcu-\u003ewarned = true;\n+\t}\n+\n+\tmcu-\u003epoll_ms = min(mcu-\u003epoll_ms * 2, EN8811H_FW_POLL_MAX_MS);\n+\tqueue_delayed_work(system_freezable_wq, \u0026mcu-\u003efw_poll,\n+\t\t\t msecs_to_jiffies(mcu-\u003epoll_ms));\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+\tu32 deassert_us = 0;\n+\n+\tmcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);\n+\tif (!mcu)\n+\t\treturn -ENOMEM;\n+\n+\tmcu-\u003emdiodev = mdiodev;\n+\tmdiodev_set_drvdata(mdiodev, mcu);\n+\n+\t/* The bus registration only needs this once the firmware runs, but\n+\t * a DT hole should fail the bind now, not as a work-item error a\n+\t * second after probe already returned success.\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/*\n+\t * The core only claims reset-gpios for devices flagged as PHYs\n+\t * (mdiobus_register_device()), so claim it here. Owning it at this\n+\t * level is the point: phy_detach() asserts the reset of the PHY it\n+\t * detaches, which would wipe firmware the MD32 holds in RAM.\n+\t */\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+\t/*\n+\t * Firmware left running by the bootloader, or by a previous bind,\n+\t * lives in volatile RAM: the reset line must not be touched then.\n+\t * Only a chip still in its bootloader gets the clean reset cycle.\n+\t */\n+\tif (air_en8811h_mcu_running(mdiodev-\u003ebus, mdiodev-\u003eaddr, false)) {\n+\t\tdev_dbg(dev, \"MD32 already running, adopting it\\n\");\n+\t} else if (mcu-\u003ereset_gpio) {\n+\t\tu32 assert_us = 0;\n+\n+\t\tdevice_property_read_u32(dev, \"reset-assert-us\", \u0026assert_us);\n+\t\tdevice_property_read_u32(dev, \"reset-deassert-us\",\n+\t\t\t\t\t \u0026deassert_us);\n+\n+\t\tgpiod_direction_output(mcu-\u003ereset_gpio, 1);\n+\t\tif (assert_us)\n+\t\t\tfsleep(assert_us);\n+\n+\t\tgpiod_set_value_cansleep(mcu-\u003ereset_gpio, 0);\n+\t\tif (deassert_us)\n+\t\t\tfsleep(deassert_us);\n+\t}\n+\n+\tmcu-\u003epoll_ms = EN8811H_FW_POLL_MIN_MS;\n+\tINIT_DELAYED_WORK(\u0026mcu-\u003efw_poll, en8811h_mcu_fw_poll);\n+\t/* Freezable, so neither the file lookup nor the ~144KB MDIO\n+\t * download can land on a bus that is suspending. The download is\n+\t * long for a bound worker, but it runs once per firmware arrival.\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+}\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},\n+};\n+\n+mdio_module_driver(en8811h_mcu_driver);\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 edd49c193e476..fcc6e373edd69 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,80 +254,10 @@ 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-\tsaved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);\n-\n-\tif (saved_page \u003e= 0) {\n-\t\tret = __air_write_buf(phydev, address, fw);\n-\t\tif (ret \u003c 0)\n-\t\t\tphydev_err(phydev, \"%s 0x%08x failed: %d\\n\", __func__,\n-\t\t\t\t address, ret);\n-\t}\n-\n-\treturn phy_restore_page(phydev, saved_page, ret);\n-}\n-\n static int en8811h_wait_mcu_ready(struct phy_device *phydev)\n {\n-\tint ret, reg_value;\n-\n-\tret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,\n-\t\t\t\t\t EN8811H_FW_CTRL_1_FINISH);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/* Because of mdio-lock, may have to wait for multiple loads */\n-\tret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,\n-\t\t\t\t\tEN8811H_PHY_FW_STATUS, reg_value,\n-\t\t\t\t\treg_value == EN8811H_PHY_READY,\n-\t\t\t\t\t20000, 7500000, true);\n-\tif (ret) {\n-\t\tphydev_err(phydev, \"MCU not ready: 0x%x\\n\", reg_value);\n-\t\treturn -ENODEV;\n-\t}\n-\n-\treturn 0;\n+\treturn air_en8811h_wait_mcu_ready(phydev-\u003emdio.bus, phydev-\u003emdio.addr,\n+\t\t\t\t\t phydev-\u003eis_c45, \u0026phydev-\u003emdio.dev);\n }\n \n static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,\n@@ -405,7 +319,8 @@ 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(phydev-\u003emdio.bus, phydev-\u003emdio.addr, address,\n+\t\t\t fw);\n \trelease_firmware(fw);\n \treturn ret;\n }\n@@ -501,54 +416,12 @@ 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(phydev-\u003emdio.bus, phydev-\u003emdio.addr,\n+\t\t\t\t phydev-\u003eis_c45, \u0026phydev-\u003emdio.dev,\n+\t\t\t\t \u0026priv-\u003efirmware_version);\n \tif (ret \u003c 0)\n \t\tphydev_err(phydev, \"Load firmware failed: %d\\n\", ret);\n \n@@ -1157,7 +1030,10 @@ 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+\t/* Freshly downloaded firmware has just started; firmware adopted\n+\t * from the bootloader is already past its own start. Neither needs\n+\t * the restart a later resume would.\n+\t */\n \tpriv-\u003emcu_needs_restart = false;\n \n \t/* MDIO_DEVS1/2 empty, so set mmds_present bits here */\ndiff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c\nindex 5141db19fa5ee..c1187f357f4c3 100644\n--- a/drivers/net/phy/air_phy_lib.c\n+++ b/drivers/net/phy/air_phy_lib.c\n@@ -8,37 +8,43 @@\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+#include \"phylib.h\"\n \n-static int __air_buckpbus_reg_read(struct phy_device *phydev,\n+static int __air_buckpbus_reg_read(struct mii_bus *bus, int addr,\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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);\n \tif (pbus_data_low \u003c 0)\n \t\treturn pbus_data_low;\n \n@@ -46,64 +52,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 mii_bus *bus, int addr,\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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 mii_bus *bus, int addr,\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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);\n \tif (pbus_data_low \u003c 0)\n \t\treturn pbus_data_low;\n \n@@ -112,23 +120,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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 +152,9 @@ 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(phydev-\u003emdio.bus,\n+\t\t\t\t\t phydev-\u003emdio.addr,\n+\t\t\t\t\t pbus_address, 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,8 +173,9 @@ 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\t\t\t\t pbus_data);\n+\t\tret = __air_buckpbus_reg_write(phydev-\u003emdio.bus,\n+\t\t\t\t\t phydev-\u003emdio.addr,\n+\t\t\t\t\t pbus_address, 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@@ -183,8 +194,9 @@ 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(phydev-\u003emdio.bus,\n+\t\t\t\t\t\tphydev-\u003emdio.addr,\n+\t\t\t\t\t\tpbus_address, mask, 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 +206,297 @@ 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 mii_bus *bus, int addr, u32 address,\n+\t\t\t const struct firmware *fw)\n+{\n+\tunsigned int offset;\n+\tint ret;\n+\tu16 val;\n+\n+\tret = __mdiobus_write(bus, addr, AIR_BPBUS_MODE,\n+\t\t\t AIR_BPBUS_MODE_ADDR_INCR);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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+/* The phy_select_page() path is not usable here: these run before any\n+ * phy_device exists. Callers hold the bus lock across select/op/restore.\n+ */\n+static int air_mdio_select_page(struct mii_bus *bus, int addr, int page)\n+{\n+\tint saved_page, ret;\n+\n+\tsaved_page = __mdiobus_read(bus, addr, AIR_EXT_PAGE_ACCESS);\n+\tif (saved_page \u003c 0)\n+\t\treturn saved_page;\n+\n+\tif (saved_page != page) {\n+\t\tret = __mdiobus_write(bus, addr, 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 mii_bus *bus, int addr,\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 = __mdiobus_write(bus, addr, 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+int air_fw_write_buf(struct mii_bus *bus, int addr, u32 address,\n+\t\t const struct firmware *fw)\n+{\n+\tint saved_page, ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\n+\tsaved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);\n+\tif (saved_page \u003c 0) {\n+\t\tret = saved_page;\n+\t} else {\n+\t\tret = __air_write_buf(bus, addr, address, fw);\n+\t\tret = air_mdio_restore_page(bus, addr, saved_page,\n+\t\t\t\t\t AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(air_fw_write_buf);\n+\n+static int air_mdio_buckpbus_reg_read(struct mii_bus *bus, int addr,\n+\t\t\t\t u32 pbus_address, u32 *pbus_data)\n+{\n+\tint saved_page, ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\n+\tsaved_page = air_mdio_select_page(bus, addr, 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(bus, addr, pbus_address,\n+\t\t\t\t\t pbus_data);\n+\t\tret = air_mdio_restore_page(bus, addr, saved_page,\n+\t\t\t\t\t AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+static int air_mdio_buckpbus_reg_write(struct mii_bus *bus, int addr,\n+\t\t\t\t u32 pbus_address, u32 pbus_data)\n+{\n+\tint saved_page, ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\n+\tsaved_page = air_mdio_select_page(bus, addr, 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(bus, addr, pbus_address,\n+\t\t\t\t\t pbus_data);\n+\t\tret = air_mdio_restore_page(bus, addr, saved_page,\n+\t\t\t\t\t AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+static int air_mdio_buckpbus_reg_modify(struct mii_bus *bus, int addr,\n+\t\t\t\t\tu32 pbus_address, u32 mask, u32 set)\n+{\n+\tint saved_page, ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\n+\tsaved_page = air_mdio_select_page(bus, addr, 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(bus, addr, pbus_address,\n+\t\t\t\t\t\tmask, set);\n+\t\tret = air_mdio_restore_page(bus, addr, saved_page,\n+\t\t\t\t\t AIR_PHY_PAGE_EXTENDED_4, ret);\n+\t}\n+\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+static int air_mmd_status_read(struct mii_bus *bus, int addr, bool is_c45)\n+{\n+\tint ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\tret = mmd_phy_read(bus, addr, is_c45, MDIO_MMD_VEND1,\n+\t\t\t EN8811H_PHY_FW_STATUS);\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\n+\treturn ret;\n+}\n+\n+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45)\n+{\n+\treturn air_mmd_status_read(bus, addr, is_c45) == EN8811H_PHY_READY;\n+}\n+EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);\n+\n+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,\n+\t\t\t struct device *dev)\n+{\n+\tint ret, reg_value;\n+\n+\tret = air_mdio_buckpbus_reg_write(bus, addr, 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. A read\n+\t * error ends the poll at once, like phy_read_mmd_poll_timeout()\n+\t * would: the bus is not going to heal within the 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, bus, addr, is_c45);\n+\tif (reg_value \u003c 0)\n+\t\treturn reg_value;\n+\tif (ret) {\n+\t\tdev_err(dev, \"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 mii_bus *bus, int addr, bool is_c45,\n+\t\t\t struct device *dev, u32 *fw_version)\n+{\n+\tconst struct firmware *fw1, *fw2;\n+\tint ret;\n+\n+\tif (air_en8811h_mcu_running(bus, addr, is_c45)) {\n+\t\t/* Loaded by a bootloader, an earlier bind, or another\n+\t\t * device serving the chip. The wait below is what makes\n+\t\t * trusting the status register safe: a chip that was not\n+\t\t * in fact running fails there instead of coming up\n+\t\t * misprogrammed.\n+\t\t */\n+\t\tret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\tret = air_mdio_buckpbus_reg_read(bus, addr,\n+\t\t\t\t\t\t EN8811H_FW_VERSION,\n+\t\t\t\t\t\t fw_version);\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 0;\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(bus, addr, EN8811H_FW_CTRL_1,\n+\t\t\t\t\t EN8811H_FW_CTRL_1_START);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_modify(bus, addr, 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 air_fw_download_out;\n+\n+\tret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DM, fw1);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DSP, fw2);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,\n+\t\t\t\t\t EN8811H_FW_CTRL_2_LOADING, 0);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);\n+\tif (ret \u003c 0)\n+\t\tgoto air_fw_download_out;\n+\n+\tret = air_mdio_buckpbus_reg_read(bus, addr, EN8811H_FW_VERSION,\n+\t\t\t\t\t fw_version);\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 here: the callers retry or log on their own terms,\n+\t * and a poller retrying a half-installed firmware package would turn\n+\t * a print at this level into a permanent drumbeat.\n+\t */\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);\n+\n+MODULE_FIRMWARE(EN8811H_MD32_DM);\n+MODULE_FIRMWARE(EN8811H_MD32_DSP);\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..8d9f24da12711 100644\n--- a/drivers/net/phy/air_phy_lib.h\n+++ b/drivers/net/phy/air_phy_lib.h\n@@ -29,6 +29,23 @@\n #define AIR_BPBUS_RD_DATA_HIGH\t\t0x17\n #define AIR_BPBUS_RD_DATA_LOW\t\t0x18\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+#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 +55,14 @@ 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 mii_bus *bus, int addr, u32 address,\n+\t\t const struct firmware *fw);\n+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45);\n+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,\n+\t\t\t struct device *dev);\n+int air_en8811h_fw_download(struct mii_bus *bus, int addr, bool is_c45,\n+\t\t\t struct device *dev, u32 *fw_version);\n+\n #endif /* __AIR_PHY_LIB_H */\ndiff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c\nindex 5b8e956902fb5..6fbd1fa0be786 100644\n--- a/drivers/net/phy/phylink.c\n+++ b/drivers/net/phy/phylink.c\n@@ -73,6 +73,24 @@ struct phylink {\n \tstruct phylink_link_state phy_state;\n \tunsigned int phy_ib_mode;\n \tstruct work_struct resolve;\n+\t/* Set while waiting for a PHY that is expected to probe late.\n+\t * Written and read under rtnl only: phylink_disconnect_phy() puts\n+\t * and clears it there, which is what makes the poller's dereference\n+\t * safe.\n+\t */\n+\tstruct fwnode_handle *slow_phy_fwnode;\n+\tu32 slow_phy_flags;\n+\tstruct delayed_work slow_phy_poll;\n+\t/* Interval before the next poll. Set when the poller is armed and\n+\t * thereafter owned by it, since a delayed work never runs twice at\n+\t * once.\n+\t */\n+\tunsigned int slow_phy_poll_ms;\n+\tunsigned int slow_phy_waited_ms;\n+\t/* Written under rtnl, read unlocked by the poller; a stale read only\n+\t * costs an extra poll cycle.\n+\t */\n+\tbool slow_phy_stop;\n \tunsigned int pcs_neg_mode;\n \tunsigned int pcs_state;\n \n@@ -1829,6 +1847,8 @@ int phylink_set_fixed_link(struct phylink *pl,\n }\n EXPORT_SYMBOL_GPL(phylink_set_fixed_link);\n \n+static void phylink_slow_phy_poll(struct work_struct *work);\n+\n /**\n * phylink_create() - create a phylink instance\n * @config: a pointer to the target \u0026struct phylink_config\n@@ -1867,6 +1887,7 @@ struct phylink *phylink_create(struct phylink_config *config,\n \tmutex_init(\u0026pl-\u003ephydev_mutex);\n \tmutex_init(\u0026pl-\u003estate_mutex);\n \tINIT_WORK(\u0026pl-\u003eresolve, phylink_resolve);\n+\tINIT_DELAYED_WORK(\u0026pl-\u003eslow_phy_poll, phylink_slow_phy_poll);\n \n \tpl-\u003econfig = config;\n \tif (config-\u003etype == PHYLINK_NETDEV) {\n@@ -1950,6 +1971,10 @@ void phylink_destroy(struct phylink *pl)\n \tif (pl-\u003elink_gpio)\n \t\tgpiod_put(pl-\u003elink_gpio);\n \n+\tWRITE_ONCE(pl-\u003eslow_phy_stop, true);\n+\tcancel_delayed_work_sync(\u0026pl-\u003eslow_phy_poll);\n+\tfwnode_handle_put(pl-\u003eslow_phy_fwnode);\n+\n \tcancel_work_sync(\u0026pl-\u003eresolve);\n \tkfree(pl);\n }\n@@ -2201,10 +2226,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,\n }\n \n static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,\n-\t\t\t phy_interface_t interface)\n+\t\t\t phy_interface_t interface, u32 flags)\n {\n-\tu32 flags = 0;\n-\n \tif (WARN_ON(pl-\u003ecfg_link_an_mode == MLO_AN_FIXED))\n \t\treturn -EINVAL;\n \n@@ -2242,7 +2265,7 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)\n \t\tpl-\u003elink_config.interface = pl-\u003elink_interface;\n \t}\n \n-\tret = phylink_attach_phy(pl, phy, pl-\u003elink_interface);\n+\tret = phylink_attach_phy(pl, phy, pl-\u003elink_interface, 0);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n@@ -2254,6 +2277,135 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)\n }\n EXPORT_SYMBOL_GPL(phylink_connect_phy);\n \n+/* How often to re-check whether a late-probing PHY has turned up. */\n+#define PHYLINK_SLOW_PHY_POLL_MS\t1000\n+#define PHYLINK_SLOW_PHY_WARN_MS\t60000\n+\n+/* Ceiling that interval backs off to once a connect attempt has failed. */\n+#define PHYLINK_SLOW_PHY_POLL_MAX_MS\t30000\n+\n+/* True while a PHY declared slow-to-probe has not been connected yet. */\n+static bool phylink_slow_phy_pending(struct phylink *pl)\n+{\n+\treturn pl-\u003eslow_phy_fwnode \u0026\u0026 !pl-\u003ephydev;\n+}\n+\n+/* A phy-handle has resolved only once it names a device with its own driver\n+ * bound. A device that is absent, or present with the generic driver bound to\n+ * it, is equally unusable: this is the state slow-to-probe waits out, and both\n+ * the arming site and the poller must agree on it.\n+ *\n+ * Bound is not probed. phy_probe() assigns phydev-\u003edrv before it calls the\n+ * driver's own probe(), and only fills phydev-\u003esupported from get_features()\n+ * once that has returned; a device connected in between is validated against\n+ * an empty link-mode mask. PHY_DOWN is the documented state of a device whose\n+ * probe has not completed, and phy_probe() leaves it in PHY_READY.\n+ */\n+static bool phylink_phy_is_usable(struct phy_device *phy_dev)\n+{\n+\treturn phy_dev \u0026\u0026 phy_dev-\u003edrv \u0026\u0026 phy_dev-\u003estate != PHY_DOWN \u0026\u0026\n+\t !phy_driver_is_genphy(phy_dev);\n+}\n+\n+static void phylink_slow_phy_poll(struct work_struct *work)\n+{\n+\tstruct phylink *pl = container_of(to_delayed_work(work), struct phylink,\n+\t\t\t\t\t slow_phy_poll);\n+\tstruct phy_device *phy_dev;\n+\tint ret;\n+\n+\tif (READ_ONCE(pl-\u003eslow_phy_stop))\n+\t\treturn;\n+\n+\t/* phylink_disconnect_phy() cancels this work while holding rtnl, so\n+\t * blocking on rtnl here would deadlock against it. Requeue instead and\n+\t * let the canceller finish.\n+\t */\n+\tif (!rtnl_trylock())\n+\t\tgoto requeue;\n+\n+\tif (READ_ONCE(pl-\u003eslow_phy_stop)) {\n+\t\trtnl_unlock();\n+\t\treturn;\n+\t}\n+\n+\t/* Do not \"optimize\" the lookup out from under rtnl: rtnl is what\n+\t * makes phylink_disconnect_phy()'s put-and-clear of slow_phy_fwnode\n+\t * safe against this dereference. The uncontended trylock above is\n+\t * cheaper than the use-after-free.\n+\t */\n+\tphy_dev = fwnode_phy_find_device(pl-\u003eslow_phy_fwnode);\n+\tif (!phylink_phy_is_usable(phy_dev)) {\n+\t\tif (phy_dev)\n+\t\t\tphy_device_free(phy_dev);\n+\n+\t\t/* A PHY that never turns up would otherwise wait in complete\n+\t\t * silence: one breadcrumb after a minute, like the connect\n+\t\t * failure below gets, and like a missing firmware file gets\n+\t\t * from the MDIO side.\n+\t\t */\n+\t\tpl-\u003eslow_phy_waited_ms += pl-\u003eslow_phy_poll_ms;\n+\t\tif (pl-\u003eslow_phy_waited_ms \u003e= PHYLINK_SLOW_PHY_WARN_MS \u0026\u0026\n+\t\t pl-\u003eslow_phy_waited_ms - pl-\u003eslow_phy_poll_ms \u003c\n+\t\t PHYLINK_SLOW_PHY_WARN_MS)\n+\t\t\tphylink_warn(pl,\n+\t\t\t\t \"still waiting for the slow-to-probe PHY (%pfw)\\n\",\n+\t\t\t\t pl-\u003eslow_phy_fwnode);\n+\t\trtnl_unlock();\n+\t\tgoto requeue;\n+\t}\n+\n+\tif (pl-\u003elink_interface == PHY_INTERFACE_MODE_NA) {\n+\t\tpl-\u003elink_interface = phy_dev-\u003einterface;\n+\t\tpl-\u003elink_config.interface = pl-\u003elink_interface;\n+\t}\n+\n+\tret = phylink_attach_phy(pl, phy_dev, pl-\u003elink_interface,\n+\t\t\t\t pl-\u003eslow_phy_flags);\n+\tphy_device_free(phy_dev);\n+\tif (!ret) {\n+\t\tret = phylink_bringup_phy(pl, phy_dev,\n+\t\t\t\t\t pl-\u003elink_config.interface);\n+\t\tif (ret)\n+\t\t\tphy_detach(phy_dev);\n+\t\telse if (!test_bit(PHYLINK_DISABLE_STOPPED,\n+\t\t\t\t \u0026pl-\u003ephylink_disable_state))\n+\t\t\t/* phylink_start() starts the PHY it finds attached,\n+\t\t\t * and ran long before this one turned up.\n+\t\t\t */\n+\t\t\tphy_start(phy_dev);\n+\t}\n+\tif (ret) {\n+\t\t/* Whether this is permanent cannot be read off the errno: the\n+\t\t * same -EINVAL comes out of phylink_validate_phy() for a PHY\n+\t\t * that is still filling in its link modes and for one that\n+\t\t * genuinely cannot speak this interface. What decides it is\n+\t\t * the PHY's supported mask, and only the driver binding again\n+\t\t * changes that, so keep polling - giving up here loses the\n+\t\t * port for the lifetime of this phylink. Back off instead: a\n+\t\t * failed bringup ends in phy_detach(), which asserts the PHY's\n+\t\t * reset line. Report the first failure only, so a PHY that\n+\t\t * will never fit costs one line rather than a stream of them.\n+\t\t */\n+\t\tif (pl-\u003eslow_phy_poll_ms == PHYLINK_SLOW_PHY_POLL_MS)\n+\t\t\tphylink_err(pl, \"failed to connect late PHY: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n+\n+\t\tWRITE_ONCE(pl-\u003eslow_phy_poll_ms,\n+\t\t\t min_t(unsigned int, pl-\u003eslow_phy_poll_ms * 2,\n+\t\t\t\t PHYLINK_SLOW_PHY_POLL_MAX_MS));\n+\t}\n+\trtnl_unlock();\n+\n+\tif (!ret)\n+\t\treturn;\n+\n+requeue:\n+\tqueue_delayed_work(system_freezable_power_efficient_wq,\n+\t\t\t \u0026pl-\u003eslow_phy_poll,\n+\t\t\t msecs_to_jiffies(READ_ONCE(pl-\u003eslow_phy_poll_ms)));\n+}\n+\n /**\n * phylink_of_phy_connect() - connect the PHY specified in the DT mode.\n * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\n@@ -2282,7 +2434,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect);\n * Connect the phy specified @fwnode to the phylink instance specified\n * by @pl.\n *\n- * Returns 0 on success or a negative errno.\n+ * If the port node carries the slow-to-probe property and the PHY is not\n+ * usable yet, 0 is returned with no PHY connected: a poller connects it\n+ * once its driver has probed. Until then the MAC runs without a PHY and\n+ * ethtool reports no link modes.\n+ *\n+ * Returns 0 on success - the PHY connected, or the deferred connect\n+ * armed - or a negative errno.\n */\n int phylink_fwnode_phy_connect(struct phylink *pl,\n \t\t\t const struct fwnode_handle *fwnode,\n@@ -2304,6 +2462,32 @@ int phylink_fwnode_phy_connect(struct phylink *pl,\n \t}\n \n \tphy_dev = fwnode_phy_find_device(phy_fwnode);\n+\tif (!phylink_phy_is_usable(phy_dev) \u0026\u0026\n+\t fwnode_property_present(fwnode, \"slow-to-probe\")) {\n+\t\t/* The PHY is known to appear late, so keep the node and poll\n+\t\t * for it rather than failing the port for good. Returning an\n+\t\t * error here would also send DSA off to look for the PHY on\n+\t\t * the switch's own MDIO bus.\n+\t\t */\n+\t\tif (phy_dev)\n+\t\t\tphy_device_free(phy_dev);\n+\n+\t\tpl-\u003eslow_phy_fwnode = phy_fwnode;\n+\t\tpl-\u003eslow_phy_flags = flags;\n+\t\tWRITE_ONCE(pl-\u003eslow_phy_poll_ms, PHYLINK_SLOW_PHY_POLL_MS);\n+\t\tpl-\u003eslow_phy_waited_ms = 0;\n+\t\tWRITE_ONCE(pl-\u003eslow_phy_stop, false);\n+\t\t/* mod_ rather than queue_: a poll that a disconnect cancelled\n+\t\t * mid-backoff may still be pending with seconds left on it.\n+\t\t */\n+\t\t/* Freezable: attaching and starting a PHY has no place in\n+\t\t * the middle of a system suspend transition.\n+\t\t */\n+\t\tmod_delayed_work(system_freezable_power_efficient_wq,\n+\t\t\t\t \u0026pl-\u003eslow_phy_poll, 0);\n+\t\treturn 0;\n+\t}\n+\n \t/* We're done with the phy_node handle */\n \tfwnode_handle_put(phy_fwnode);\n \tif (!phy_dev)\n@@ -2345,6 +2529,18 @@ void phylink_disconnect_phy(struct phylink *pl)\n \n \tASSERT_RTNL();\n \n+\t/* Cannot cancel synchronously: the poller takes rtnl, which is held. */\n+\tWRITE_ONCE(pl-\u003eslow_phy_stop, true);\n+\tcancel_delayed_work(\u0026pl-\u003eslow_phy_poll);\n+\t/* The next connect re-arms from its own fwnode lookup; holding on to\n+\t * this one would leak a reference per connect cycle and keep the\n+\t * pending state - and its empty-ksettings window - alive on an\n+\t * interface whose PHY is long since usable. The poller cannot race\n+\t * this: it reads the node only under rtnl, after the stop check.\n+\t */\n+\tfwnode_handle_put(pl-\u003eslow_phy_fwnode);\n+\tpl-\u003eslow_phy_fwnode = NULL;\n+\n \tmutex_lock(\u0026pl-\u003ephydev_mutex);\n \tphy = pl-\u003ephydev;\n \tif (phy) {\n@@ -2916,6 +3112,17 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,\n \telse\n \t\tkset-\u003ebase.port = pl-\u003elink_port;\n \n+\t/* Until the PHY arrives the port can do nothing, so report no link\n+\t * modes at all rather than the MAC's own capabilities, which is what\n+\t * an empty SFP cage reports.\n+\t */\n+\tif (phylink_slow_phy_pending(pl)) {\n+\t\tlinkmode_zero(kset-\u003elink_modes.supported);\n+\t\tkset-\u003ebase.speed = SPEED_UNKNOWN;\n+\t\tkset-\u003ebase.duplex = DUPLEX_UNKNOWN;\n+\t\treturn 0;\n+\t}\n+\n \tlinkmode_copy(kset-\u003elink_modes.supported, pl-\u003esupported);\n \n \tswitch (pl-\u003eact_link_an_mode) {\n@@ -2983,6 +3190,12 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,\n \n \tASSERT_RTNL();\n \n+\t/* Without a PHY and without an SFP bus this would configure the MAC\n+\t * on its own, for a link that cannot come up yet.\n+\t */\n+\tif (phylink_slow_phy_pending(pl))\n+\t\treturn -EINVAL;\n+\n \tif (pl-\u003ephydev) {\n \t\tstruct ethtool_link_ksettings phy_kset = *kset;\n \n@@ -3730,7 +3943,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)\n \t/* Attach the PHY so that the PHY is present when we do the major\n \t * configuration step.\n \t */\n-\tret = phylink_attach_phy(pl, phy, config.interface);\n+\tret = phylink_attach_phy(pl, phy, config.interface, 0);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n"
} |
| 2/1 | 2026/08/29 06:20 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"PHYLINK",
"MDIO_AIROHA_EN8811H",
"AIR_EN8811H_PHY",
"OF_MDIO"
],
"FocusSymbols": [
"phylink_fwnode_phy_connect",
"phylink_disconnect_phy",
"phylink_ethtool_ksettings_get",
"phylink_ethtool_ksettings_set",
"air_en8811h_fw_download"
],
"Reasoning": "The patch modifies core networking phylink logic (phylink.c) by adding delayed-work polling and state handling for slow-to-probe PHYs, adjusting phylink connect/disconnect lifecycles, and altering ethtool ksettings get/set paths. It also adds the Airoha EN8811H MCU MDIO driver and refactors firmware download routines in the Airoha PHY library.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit b9c708433b611814dd69a265f7453ac021c7fcef
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 29 06:19:57 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..b51a14a3b7b51
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
@@ -0,0 +1,83 @@
+# 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 driven by an MD32 MCU that executes
+ firmware loaded over MDIO into volatile RAM. Until that firmware runs,
+ the chip answers its PHY ID but cannot bring up a link, and the
+ firmware files may live on a filesystem that is not yet mounted when
+ the MDIO bus is scanned.
+
+ This node describes the MCU as an MDIO device in its own right. The
+ driver downloads the firmware once the files become available (or
+ detects firmware already left running by the bootloader) and only
+ then registers the child MDIO bus, so the PHY node below never
+ becomes visible before the chip is able to serve it.
+
+ The reset line is owned by this node rather than by the PHY node:
+ the MD32 keeps its firmware only in volatile RAM, so the reset may
+ be asserted only while the MCU is not executing firmware. A PHY-node
+ reset would be pulsed by phy_detach() on every unbind and wipe the
+ running firmware.
+
+properties:
+ compatible:
+ const: airoha,en8811h-mcu
+
+ reg:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ reset-assert-us: true
+
+ reset-deassert-us: true
+
+ mdio:
+ $ref: mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ The child bus holding the PHY itself, at the same address the
+ chip answers on the parent bus.
+
+required:
+ - compatible
+ - reg
+ - mdio
+
+additionalProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-mcu@d {
+ compatible = "airoha,en8811h-mcu";
+ reg = <0xd>;
+ reset-gpios = <&pio 14 1>;
+ 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 8>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/net/ethernet-controller.yaml b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
index 1bafd687dcb18..ca82672b6cb38 100644
--- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml
+++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
@@ -113,6 +113,15 @@ properties:
description:
Specifies a reference to a node representing an IEEE 1588 PTP device.
+ slow-to-probe:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ The PHY referenced by phy-handle may not exist, or may not have a
+ usable driver, when this controller connects to it, because the
+ driver or firmware arrives later (for example from a filesystem
+ mounted after the controller probed). Instead of failing, keep
+ the port and connect the PHY once it becomes usable.
+
rx-fifo-depth:
$ref: /schemas/types.yaml#/definitions/uint32
description:
diff --git a/MAINTAINERS b/MAINTAINERS
index 460cb72688450..21d39049e692b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -759,6 +759,13 @@ S: Maintained
F: Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
F: drivers/net/ethernet/airoha/
+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
+
AIROHA PCIE PHY 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 a05229838cb45..d46bebd05cc81 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -29,6 +29,17 @@ 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
+ select AIR_NET_PHYLIB
+ 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, before letting the PHY be probed.
+
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..b28e116289e6f
--- /dev/null
+++ b/drivers/net/mdio/mdio-airoha-en8811h.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Airoha EN8811H MDIO device driver
+ *
+ * Until its firmware has been downloaded the EN8811H is not an Ethernet PHY,
+ * it is an MD32 microcontroller waiting in its bootloader. Describing it as a
+ * plain MDIO device lets the firmware be downloaded as soon as the files can
+ * be read - in practice, once the filesystem holding them has been mounted -
+ * and lets the PHY appear only after the chip is able to act as one.
+ *
+ * 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/property.h>
+#include <linux/workqueue.h>
+
+#include "../phy/air_phy_lib.h"
+
+/*
+ * Poll rather than defer probing: request_firmware_direct() has no usermode
+ * helper fallback, so a rootfs that is not mounted yet fails immediately and
+ * would keep the deferred-probe list spinning for the whole mount window.
+ *
+ * Never give up. The firmware can arrive arbitrarily late and still be worth
+ * waiting for - installing the firmware package on a running system is a
+ * normal thing to do - and a driver that had stopped looking would turn that
+ * into a needless reboot. Back off to a slow poll instead, and leave a single
+ * breadcrumb for the system that simply does not have the files.
+ */
+#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;
+ unsigned int poll_ms;
+ unsigned int waited_ms;
+ u32 fw_version;
+ bool warned;
+};
+
+static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_read(parent, addr, regnum);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_write(parent, addr, regnum, val);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_c45_read(parent, addr, devad, regnum);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_c45_write(parent, addr, devad, regnum, val);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)
+{
+ struct device *dev = &mcu->mdiodev->dev;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ struct device_node *np;
+ struct mii_bus *bus;
+ int ret;
+
+ np = of_get_child_by_name(dev->of_node, "mdio");
+ if (!np) {
+ dev_err(dev, "no mdio node describing the PHY\n");
+ return -ENODEV;
+ }
+
+ bus = devm_mdiobus_alloc(dev);
+ 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 = devm_of_mdiobus_register(dev, bus, np);
+ of_node_put(np);
+
+ return ret;
+}
+
+static void en8811h_mcu_fw_poll(struct work_struct *work)
+{
+ struct en8811h_mcu *mcu = container_of(to_delayed_work(work),
+ struct en8811h_mcu, fw_poll);
+ struct device *dev = &mcu->mdiodev->dev;
+ int ret;
+
+ /* The chip enumerates as a C22 PHY; MMD access is indirect */
+ ret = air_en8811h_fw_download(mcu->mdiodev->bus, mcu->mdiodev->addr,
+ false, dev, &mcu->fw_version);
+ if (!ret) {
+ dev_dbg(dev, "firmware %08x running after %ums\n",
+ mcu->fw_version, mcu->waited_ms);
+
+ /* Unlike a missing firmware file, this does not resolve by
+ * itself, so unlike the poll there is no retry: surface it
+ * once and stop.
+ */
+ ret = en8811h_mcu_bus_register(mcu);
+ if (ret)
+ dev_err(dev, "failed to register the PHY's bus: %pe\n",
+ ERR_PTR(ret));
+ return;
+ }
+
+ mcu->waited_ms += mcu->poll_ms;
+ if (!mcu->warned && mcu->waited_ms >= EN8811H_FW_WARN_MS) {
+ /* Missing files resolve by themselves once installed; a bus
+ * or register error will not, and deserves its own message.
+ */
+ if (ret == -ENOENT)
+ dev_warn(dev, "still waiting for %s and %s\n",
+ EN8811H_MD32_DM, EN8811H_MD32_DSP);
+ else
+ dev_warn(dev, "firmware download keeps failing: %pe\n",
+ ERR_PTR(ret));
+ mcu->warned = true;
+ }
+
+ mcu->poll_ms = min(mcu->poll_ms * 2, EN8811H_FW_POLL_MAX_MS);
+ queue_delayed_work(system_freezable_wq, &mcu->fw_poll,
+ msecs_to_jiffies(mcu->poll_ms));
+}
+
+static int en8811h_mcu_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ struct en8811h_mcu *mcu;
+ struct device_node *np;
+ u32 deassert_us = 0;
+
+ mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
+ if (!mcu)
+ return -ENOMEM;
+
+ mcu->mdiodev = mdiodev;
+ mdiodev_set_drvdata(mdiodev, mcu);
+
+ /* The bus registration only needs this once the firmware runs, but
+ * a DT hole should fail the bind now, not as a work-item error a
+ * second after probe already returned success.
+ */
+ 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 only claims reset-gpios for devices flagged as PHYs
+ * (mdiobus_register_device()), so claim it here. Owning it at this
+ * level is the point: phy_detach() asserts the reset of the PHY it
+ * detaches, which would wipe firmware the MD32 holds in RAM.
+ */
+ 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");
+
+ /*
+ * Firmware left running by the bootloader, or by a previous bind,
+ * lives in volatile RAM: the reset line must not be touched then.
+ * Only a chip still in its bootloader gets the clean reset cycle.
+ */
+ if (air_en8811h_mcu_running(mdiodev->bus, mdiodev->addr, false)) {
+ dev_dbg(dev, "MD32 already running, adopting it\n");
+ } else if (mcu->reset_gpio) {
+ u32 assert_us = 0;
+
+ device_property_read_u32(dev, "reset-assert-us", &assert_us);
+ device_property_read_u32(dev, "reset-deassert-us",
+ &deassert_us);
+
+ gpiod_direction_output(mcu->reset_gpio, 1);
+ if (assert_us)
+ fsleep(assert_us);
+
+ gpiod_set_value_cansleep(mcu->reset_gpio, 0);
+ if (deassert_us)
+ fsleep(deassert_us);
+ }
+
+ mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+ INIT_DELAYED_WORK(&mcu->fw_poll, en8811h_mcu_fw_poll);
+ /* Freezable, so neither the file lookup nor the ~144KB MDIO
+ * download can land on a bus that is suspending. The download is
+ * long for a bound worker, but it runs once per firmware arrival.
+ */
+ 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);
+}
+
+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,
+ },
+};
+
+mdio_module_driver(en8811h_mcu_driver);
+
+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 edd49c193e476..fcc6e373edd69 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,80 +254,10 @@ 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;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
-
- if (saved_page >= 0) {
- ret = __air_write_buf(phydev, address, fw);
- if (ret < 0)
- phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
- address, ret);
- }
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
static int en8811h_wait_mcu_ready(struct phy_device *phydev)
{
- int ret, reg_value;
-
- ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
- if (ret)
- return ret;
-
- /* Because of mdio-lock, may have to wait for multiple loads */
- ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
- EN8811H_PHY_FW_STATUS, reg_value,
- reg_value == EN8811H_PHY_READY,
- 20000, 7500000, true);
- if (ret) {
- phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value);
- return -ENODEV;
- }
-
- return 0;
+ return air_en8811h_wait_mcu_ready(phydev->mdio.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev);
}
static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
@@ -405,7 +319,8 @@ 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.bus, phydev->mdio.addr, address,
+ fw);
release_firmware(fw);
return ret;
}
@@ -501,54 +416,12 @@ 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.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev,
+ &priv->firmware_version);
if (ret < 0)
phydev_err(phydev, "Load firmware failed: %d\n", ret);
@@ -1157,7 +1030,10 @@ static int en8811h_probe(struct phy_device *phydev)
if (ret < 0)
return ret;
- /* mcu has just restarted after firmware load */
+ /* Freshly downloaded firmware has just started; firmware adopted
+ * from the bootloader is already past its own start. Neither needs
+ * the restart a later resume would.
+ */
priv->mcu_needs_restart = false;
/* MDIO_DEVS1/2 empty, so set mmds_present bits here */
diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c
index 5141db19fa5ee..c1187f357f4c3 100644
--- a/drivers/net/phy/air_phy_lib.c
+++ b/drivers/net/phy/air_phy_lib.c
@@ -8,37 +8,43 @@
*/
#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"
+#include "phylib.h"
-static int __air_buckpbus_reg_read(struct phy_device *phydev,
+static int __air_buckpbus_reg_read(struct mii_bus *bus, int addr,
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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);
if (pbus_data_low < 0)
return pbus_data_low;
@@ -46,64 +52,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 mii_bus *bus, int addr,
u32 pbus_address, u32 pbus_data)
{
int ret;
- ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+ ret = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 mii_bus *bus, int addr,
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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);
if (pbus_data_low < 0)
return pbus_data_low;
@@ -112,23 +120,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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_LOW,
+ lower_16_bits(pbus_data_new));
if (ret < 0)
return ret;
@@ -144,7 +152,9 @@ 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.bus,
+ phydev->mdio.addr,
+ pbus_address, pbus_data);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -163,8 +173,9 @@ 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,
- pbus_data);
+ ret = __air_buckpbus_reg_write(phydev->mdio.bus,
+ phydev->mdio.addr,
+ pbus_address, pbus_data);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -183,8 +194,9 @@ 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.bus,
+ phydev->mdio.addr,
+ pbus_address, mask, set);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -194,6 +206,297 @@ 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 mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ unsigned int offset;
+ int ret;
+ u16 val;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_INCR);
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_ADDR_HIGH,
+ upper_16_bits(address));
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_HIGH, val);
+ if (ret < 0)
+ return ret;
+
+ val = get_unaligned_le16(&fw->data[offset]);
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_LOW, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* The phy_select_page() path is not usable here: these run before any
+ * phy_device exists. Callers hold the bus lock across select/op/restore.
+ */
+static int air_mdio_select_page(struct mii_bus *bus, int addr, int page)
+{
+ int saved_page, ret;
+
+ saved_page = __mdiobus_read(bus, addr, AIR_EXT_PAGE_ACCESS);
+ if (saved_page < 0)
+ return saved_page;
+
+ if (saved_page != page) {
+ ret = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS, page);
+ if (ret < 0)
+ return ret;
+ }
+
+ return saved_page;
+}
+
+static int air_mdio_restore_page(struct mii_bus *bus, int addr,
+ int saved_page, int page, int ret)
+{
+ int restore;
+
+ if (saved_page != page) {
+ restore = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS,
+ saved_page);
+ if (ret >= 0 && restore < 0)
+ ret = restore;
+ }
+
+ return ret;
+}
+
+int air_fw_write_buf(struct mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_write_buf(bus, addr, address, fw);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_fw_write_buf);
+
+static int air_mdio_buckpbus_reg_read(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 *pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_read(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_write(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_write(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_modify(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 mask, u32 set)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_modify(bus, addr, pbus_address,
+ mask, set);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mmd_status_read(struct mii_bus *bus, int addr, bool is_c45)
+{
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = mmd_phy_read(bus, addr, is_c45, MDIO_MMD_VEND1,
+ EN8811H_PHY_FW_STATUS);
+ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
+}
+
+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45)
+{
+ return air_mmd_status_read(bus, addr, is_c45) == EN8811H_PHY_READY;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);
+
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev)
+{
+ int ret, reg_value;
+
+ ret = air_mdio_buckpbus_reg_write(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_FINISH);
+ 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()
+ * would: the bus is not going to heal within the timeout.
+ */
+ ret = read_poll_timeout(air_mmd_status_read, reg_value,
+ reg_value < 0 ||
+ reg_value == EN8811H_PHY_READY,
+ 20000, 7500000, true, bus, addr, is_c45);
+ if (reg_value < 0)
+ return reg_value;
+ if (ret) {
+ dev_err(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 mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version)
+{
+ const struct firmware *fw1, *fw2;
+ int ret;
+
+ if (air_en8811h_mcu_running(bus, addr, is_c45)) {
+ /* Loaded by a bootloader, an earlier bind, or another
+ * device serving the chip. The wait below is what makes
+ * trusting the status register safe: a chip that was not
+ * in fact running fails there instead of coming up
+ * misprogrammed.
+ */
+ ret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);
+ if (ret < 0)
+ return ret;
+
+ ret = air_mdio_buckpbus_reg_read(bus, addr,
+ EN8811H_FW_VERSION,
+ fw_version);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "MD32 already running, firmware %08x\n",
+ *fw_version);
+ return 0;
+ }
+
+ 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(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_START);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING,
+ EN8811H_FW_CTRL_2_LOADING);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DM, fw1);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DSP, fw2);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING, 0);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_read(bus, addr, EN8811H_FW_VERSION,
+ fw_version);
+ 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 here: the callers retry or log on their own terms,
+ * and a poller retrying a half-installed firmware package would turn
+ * a print at this level into a permanent drumbeat.
+ */
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);
+
+MODULE_FIRMWARE(EN8811H_MD32_DM);
+MODULE_FIRMWARE(EN8811H_MD32_DSP);
+
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..8d9f24da12711 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -29,6 +29,23 @@
#define AIR_BPBUS_RD_DATA_HIGH 0x17
#define AIR_BPBUS_RD_DATA_LOW 0x18
+#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin"
+#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin"
+
+#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 +55,14 @@ 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 mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw);
+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45);
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev);
+int air_en8811h_fw_download(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version);
+
#endif /* __AIR_PHY_LIB_H */
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 5b8e956902fb5..6fbd1fa0be786 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -73,6 +73,24 @@ struct phylink {
struct phylink_link_state phy_state;
unsigned int phy_ib_mode;
struct work_struct resolve;
+ /* Set while waiting for a PHY that is expected to probe late.
+ * Written and read under rtnl only: phylink_disconnect_phy() puts
+ * and clears it there, which is what makes the poller's dereference
+ * safe.
+ */
+ struct fwnode_handle *slow_phy_fwnode;
+ u32 slow_phy_flags;
+ struct delayed_work slow_phy_poll;
+ /* Interval before the next poll. Set when the poller is armed and
+ * thereafter owned by it, since a delayed work never runs twice at
+ * once.
+ */
+ unsigned int slow_phy_poll_ms;
+ unsigned int slow_phy_waited_ms;
+ /* Written under rtnl, read unlocked by the poller; a stale read only
+ * costs an extra poll cycle.
+ */
+ bool slow_phy_stop;
unsigned int pcs_neg_mode;
unsigned int pcs_state;
@@ -1829,6 +1847,8 @@ int phylink_set_fixed_link(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+static void phylink_slow_phy_poll(struct work_struct *work);
+
/**
* phylink_create() - create a phylink instance
* @config: a pointer to the target &struct phylink_config
@@ -1867,6 +1887,7 @@ struct phylink *phylink_create(struct phylink_config *config,
mutex_init(&pl->phydev_mutex);
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
+ INIT_DELAYED_WORK(&pl->slow_phy_poll, phylink_slow_phy_poll);
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
@@ -1950,6 +1971,10 @@ void phylink_destroy(struct phylink *pl)
if (pl->link_gpio)
gpiod_put(pl->link_gpio);
+ WRITE_ONCE(pl->slow_phy_stop, true);
+ cancel_delayed_work_sync(&pl->slow_phy_poll);
+ fwnode_handle_put(pl->slow_phy_fwnode);
+
cancel_work_sync(&pl->resolve);
kfree(pl);
}
@@ -2201,10 +2226,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
}
static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
- phy_interface_t interface)
+ phy_interface_t interface, u32 flags)
{
- u32 flags = 0;
-
if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED))
return -EINVAL;
@@ -2242,7 +2265,7 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
pl->link_config.interface = pl->link_interface;
}
- ret = phylink_attach_phy(pl, phy, pl->link_interface);
+ ret = phylink_attach_phy(pl, phy, pl->link_interface, 0);
if (ret < 0)
return ret;
@@ -2254,6 +2277,135 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
}
EXPORT_SYMBOL_GPL(phylink_connect_phy);
+/* How often to re-check whether a late-probing PHY has turned up. */
+#define PHYLINK_SLOW_PHY_POLL_MS 1000
+#define PHYLINK_SLOW_PHY_WARN_MS 60000
+
+/* Ceiling that interval backs off to once a connect attempt has failed. */
+#define PHYLINK_SLOW_PHY_POLL_MAX_MS 30000
+
+/* True while a PHY declared slow-to-probe has not been connected yet. */
+static bool phylink_slow_phy_pending(struct phylink *pl)
+{
+ return pl->slow_phy_fwnode && !pl->phydev;
+}
+
+/* A phy-handle has resolved only once it names a device with its own driver
+ * bound. A device that is absent, or present with the generic driver bound to
+ * it, is equally unusable: this is the state slow-to-probe waits out, and both
+ * the arming site and the poller must agree on it.
+ *
+ * Bound is not probed. phy_probe() assigns phydev->drv before it calls the
+ * driver's own probe(), and only fills phydev->supported from get_features()
+ * once that has returned; a device connected in between is validated against
+ * an empty link-mode mask. PHY_DOWN is the documented state of a device whose
+ * probe has not completed, and phy_probe() leaves it in PHY_READY.
+ */
+static bool phylink_phy_is_usable(struct phy_device *phy_dev)
+{
+ return phy_dev && phy_dev->drv && phy_dev->state != PHY_DOWN &&
+ !phy_driver_is_genphy(phy_dev);
+}
+
+static void phylink_slow_phy_poll(struct work_struct *work)
+{
+ struct phylink *pl = container_of(to_delayed_work(work), struct phylink,
+ slow_phy_poll);
+ struct phy_device *phy_dev;
+ int ret;
+
+ if (READ_ONCE(pl->slow_phy_stop))
+ return;
+
+ /* phylink_disconnect_phy() cancels this work while holding rtnl, so
+ * blocking on rtnl here would deadlock against it. Requeue instead and
+ * let the canceller finish.
+ */
+ if (!rtnl_trylock())
+ goto requeue;
+
+ if (READ_ONCE(pl->slow_phy_stop)) {
+ rtnl_unlock();
+ return;
+ }
+
+ /* Do not "optimize" the lookup out from under rtnl: rtnl is what
+ * makes phylink_disconnect_phy()'s put-and-clear of slow_phy_fwnode
+ * safe against this dereference. The uncontended trylock above is
+ * cheaper than the use-after-free.
+ */
+ phy_dev = fwnode_phy_find_device(pl->slow_phy_fwnode);
+ if (!phylink_phy_is_usable(phy_dev)) {
+ if (phy_dev)
+ phy_device_free(phy_dev);
+
+ /* A PHY that never turns up would otherwise wait in complete
+ * silence: one breadcrumb after a minute, like the connect
+ * failure below gets, and like a missing firmware file gets
+ * from the MDIO side.
+ */
+ pl->slow_phy_waited_ms += pl->slow_phy_poll_ms;
+ if (pl->slow_phy_waited_ms >= PHYLINK_SLOW_PHY_WARN_MS &&
+ pl->slow_phy_waited_ms - pl->slow_phy_poll_ms <
+ PHYLINK_SLOW_PHY_WARN_MS)
+ phylink_warn(pl,
+ "still waiting for the slow-to-probe PHY (%pfw)\n",
+ pl->slow_phy_fwnode);
+ rtnl_unlock();
+ goto requeue;
+ }
+
+ if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
+ pl->link_interface = phy_dev->interface;
+ pl->link_config.interface = pl->link_interface;
+ }
+
+ ret = phylink_attach_phy(pl, phy_dev, pl->link_interface,
+ pl->slow_phy_flags);
+ phy_device_free(phy_dev);
+ if (!ret) {
+ ret = phylink_bringup_phy(pl, phy_dev,
+ pl->link_config.interface);
+ if (ret)
+ phy_detach(phy_dev);
+ else if (!test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ /* phylink_start() starts the PHY it finds attached,
+ * and ran long before this one turned up.
+ */
+ phy_start(phy_dev);
+ }
+ if (ret) {
+ /* Whether this is permanent cannot be read off the errno: the
+ * same -EINVAL comes out of phylink_validate_phy() for a PHY
+ * that is still filling in its link modes and for one that
+ * genuinely cannot speak this interface. What decides it is
+ * the PHY's supported mask, and only the driver binding again
+ * changes that, so keep polling - giving up here loses the
+ * port for the lifetime of this phylink. Back off instead: a
+ * failed bringup ends in phy_detach(), which asserts the PHY's
+ * reset line. Report the first failure only, so a PHY that
+ * will never fit costs one line rather than a stream of them.
+ */
+ if (pl->slow_phy_poll_ms == PHYLINK_SLOW_PHY_POLL_MS)
+ phylink_err(pl, "failed to connect late PHY: %pe\n",
+ ERR_PTR(ret));
+
+ WRITE_ONCE(pl->slow_phy_poll_ms,
+ min_t(unsigned int, pl->slow_phy_poll_ms * 2,
+ PHYLINK_SLOW_PHY_POLL_MAX_MS));
+ }
+ rtnl_unlock();
+
+ if (!ret)
+ return;
+
+requeue:
+ queue_delayed_work(system_freezable_power_efficient_wq,
+ &pl->slow_phy_poll,
+ msecs_to_jiffies(READ_ONCE(pl->slow_phy_poll_ms)));
+}
+
/**
* phylink_of_phy_connect() - connect the PHY specified in the DT mode.
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2282,7 +2434,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
* Connect the phy specified @fwnode to the phylink instance specified
* by @pl.
*
- * Returns 0 on success or a negative errno.
+ * If the port node carries the slow-to-probe property and the PHY is not
+ * usable yet, 0 is returned with no PHY connected: a poller connects it
+ * once its driver has probed. Until then the MAC runs without a PHY and
+ * ethtool reports no link modes.
+ *
+ * Returns 0 on success - the PHY connected, or the deferred connect
+ * armed - or a negative errno.
*/
int phylink_fwnode_phy_connect(struct phylink *pl,
const struct fwnode_handle *fwnode,
@@ -2304,6 +2462,32 @@ int phylink_fwnode_phy_connect(struct phylink *pl,
}
phy_dev = fwnode_phy_find_device(phy_fwnode);
+ if (!phylink_phy_is_usable(phy_dev) &&
+ fwnode_property_present(fwnode, "slow-to-probe")) {
+ /* The PHY is known to appear late, so keep the node and poll
+ * for it rather than failing the port for good. Returning an
+ * error here would also send DSA off to look for the PHY on
+ * the switch's own MDIO bus.
+ */
+ if (phy_dev)
+ phy_device_free(phy_dev);
+
+ pl->slow_phy_fwnode = phy_fwnode;
+ pl->slow_phy_flags = flags;
+ WRITE_ONCE(pl->slow_phy_poll_ms, PHYLINK_SLOW_PHY_POLL_MS);
+ pl->slow_phy_waited_ms = 0;
+ WRITE_ONCE(pl->slow_phy_stop, false);
+ /* mod_ rather than queue_: a poll that a disconnect cancelled
+ * mid-backoff may still be pending with seconds left on it.
+ */
+ /* Freezable: attaching and starting a PHY has no place in
+ * the middle of a system suspend transition.
+ */
+ mod_delayed_work(system_freezable_power_efficient_wq,
+ &pl->slow_phy_poll, 0);
+ return 0;
+ }
+
/* We're done with the phy_node handle */
fwnode_handle_put(phy_fwnode);
if (!phy_dev)
@@ -2345,6 +2529,18 @@ void phylink_disconnect_phy(struct phylink *pl)
ASSERT_RTNL();
+ /* Cannot cancel synchronously: the poller takes rtnl, which is held. */
+ WRITE_ONCE(pl->slow_phy_stop, true);
+ cancel_delayed_work(&pl->slow_phy_poll);
+ /* The next connect re-arms from its own fwnode lookup; holding on to
+ * this one would leak a reference per connect cycle and keep the
+ * pending state - and its empty-ksettings window - alive on an
+ * interface whose PHY is long since usable. The poller cannot race
+ * this: it reads the node only under rtnl, after the stop check.
+ */
+ fwnode_handle_put(pl->slow_phy_fwnode);
+ pl->slow_phy_fwnode = NULL;
+
mutex_lock(&pl->phydev_mutex);
phy = pl->phydev;
if (phy) {
@@ -2916,6 +3112,17 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
else
kset->base.port = pl->link_port;
+ /* Until the PHY arrives the port can do nothing, so report no link
+ * modes at all rather than the MAC's own capabilities, which is what
+ * an empty SFP cage reports.
+ */
+ if (phylink_slow_phy_pending(pl)) {
+ linkmode_zero(kset->link_modes.supported);
+ kset->base.speed = SPEED_UNKNOWN;
+ kset->base.duplex = DUPLEX_UNKNOWN;
+ return 0;
+ }
+
linkmode_copy(kset->link_modes.supported, pl->supported);
switch (pl->act_link_an_mode) {
@@ -2983,6 +3190,12 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
ASSERT_RTNL();
+ /* Without a PHY and without an SFP bus this would configure the MAC
+ * on its own, for a link that cannot come up yet.
+ */
+ if (phylink_slow_phy_pending(pl))
+ return -EINVAL;
+
if (pl->phydev) {
struct ethtool_link_ksettings phy_kset = *kset;
@@ -3730,7 +3943,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
/* Attach the PHY so that the PHY is present when we do the major
* configuration step.
*/
- ret = phylink_attach_phy(pl, phy, config.interface);
+ ret = phylink_attach_phy(pl, phy, config.interface, 0);
if (ret < 0)
return ret;
|
| 3/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22784 output: 27 thoughts: 501 Thoughts: **Analyzing this Patch Series**
Okay, let's dive into this. First, I need to understand what this patch series actually *does*. I see a `Documentation` update with new bindings for the `airoha,en8811h-mcu` and a general `ethernet-controller`, along with the usual `MAINTAINERS` update. Standard stuff.
But the meat of it is the driver code changes: `Kconfig`, `Makefile` updates in the `mdio` directory, a brand new `mdio-airoha-en8811h.c` driver, and modifications to `air_en8811h.c` and `air_phy_lib.{c,h}`. And… significant changes to `phylink.c`. *Whoa*. That's the core of the net/phy phylink subsystem. This patch is touching core networking infrastructure. It’s modifying fundamental functions: `phylink_create`, `phylink_destroy`, the connection routines, ethtool integration, the slow poll functionality, etc. Basically all the key touchpoints.
Right, so this is used by *a lot* of Ethernet drivers. Things like `phylink_fwnode_phy_connect` are everywhere. That makes this a prime target for fuzzing. I need to figure out if these changes are reachable in a virtualized environment. Can I trigger this in GCE or QEMU? Syzkaller should definitely be able to exercise this if it's reachable. The phylink changes are the crucial point. Let's see how `phylink` is used in the kernel. How does it interact with netdevices? A quick search of the kernel tree should provide some answers.
|
| 4/2 | 2026/08/29 06:20 | tool | grepper |
0mArgs:{
"Expression": "phylink_create",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/ethernet/altera/altera_tse_main.c=1128=static int altera_tse_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/altera/altera_tse_main.c-1415-\ndrivers/net/ethernet/altera/altera_tse_main.c:1416:\tpriv-\u003ephylink = phylink_create(\u0026priv-\u003ephylink_config,\ndrivers/net/ethernet/altera/altera_tse_main.c-1417-\t\t\t\t of_fwnode_handle(priv-\u003edevice-\u003eof_node),\n--\ndrivers/net/ethernet/atheros/ag71xx.c=1074=static int ag71xx_phylink_setup(struct ag71xx *ag)\n--\ndrivers/net/ethernet/atheros/ag71xx.c-1108-\ndrivers/net/ethernet/atheros/ag71xx.c:1109:\tphylink = phylink_create(\u0026ag-\u003ephylink_config, ag-\u003epdev-\u003edev.fwnode,\ndrivers/net/ethernet/atheros/ag71xx.c-1110-\t\t\t\t ag-\u003ephy_if_mode, \u0026ag71xx_phylink_mac_ops);\n--\ndrivers/net/ethernet/cadence/macb_main.c=1023=static int macb_mii_probe(struct net_device *netdev)\n--\ndrivers/net/ethernet/cadence/macb_main.c-1085-\ndrivers/net/ethernet/cadence/macb_main.c:1086:\tbp-\u003ephylink = phylink_create(\u0026bp-\u003ephylink_config, bp-\u003epdev-\u003edev.fwnode,\ndrivers/net/ethernet/cadence/macb_main.c-1087-\t\t\t\t bp-\u003ephy_interface, \u0026macb_phylink_ops);\n--\ndrivers/net/ethernet/freescale/dpaa/dpaa_eth.c=201=static int dpaa_netdev_init(struct net_device *net_dev,\n--\ndrivers/net/ethernet/freescale/dpaa/dpaa_eth.c-274-\tmac_dev-\u003eupdate_speed = dpaa_eth_cgr_set_speed;\ndrivers/net/ethernet/freescale/dpaa/dpaa_eth.c:275:\tmac_dev-\u003ephylink = phylink_create(\u0026mac_dev-\u003ephylink_config,\ndrivers/net/ethernet/freescale/dpaa/dpaa_eth.c-276-\t\t\t\t\t dev_fwnode(mac_dev-\u003edev),\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c=560=int dpaa2_mac_connect(struct dpaa2_mac *mac)\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c-624-\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c:625:\tphylink = phylink_create(\u0026mac-\u003ephylink_config,\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c-626-\t\t\t\t dpmac_node, mac-\u003eif_mode,\n--\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c=931=static int enetc4_link_init(struct enetc_ndev_priv *priv,\n--\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-949-\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c:950:\terr = enetc_phylink_create(priv, node, \u0026enetc_pl_mac_ops);\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-951-\tif (err) {\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-952-\t\tdev_err(dev, \"Failed to create phylink\\n\");\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c:953:\t\tgoto err_phylink_create;\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-954-\t}\n--\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-957-\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c:958:err_phylink_create:\ndrivers/net/ethernet/freescale/enetc/enetc4_pf.c-959-\tenetc_mdiobus_destroy(pf);\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c=795=static int enetc_pf_probe(struct pci_dev *pdev,\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c-877-\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c:878:\terr = enetc_phylink_create(priv, node, \u0026enetc_mac_phylink_ops);\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c-879-\tif (err)\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c:880:\t\tgoto err_phylink_create;\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c-881-\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c-889-\tenetc_phylink_destroy(priv);\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c:890:err_phylink_create:\ndrivers/net/ethernet/freescale/enetc/enetc_pf.c-891-\tenetc_mdiobus_destroy(pf);\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c=419=EXPORT_SYMBOL_GPL(enetc_mdiobus_destroy);\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-420-\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c:421:int enetc_phylink_create(struct enetc_ndev_priv *priv, struct device_node *node,\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-422-\t\t\t const struct phylink_mac_ops *ops)\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-461-\tpf-\u003ephylink_config.mac_capabilities = mac_caps;\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c:462:\tphylink = phylink_create(\u0026pf-\u003ephylink_config, of_fwnode_handle(node),\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-463-\t\t\t\t pf-\u003eif_mode, ops);\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-472-}\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c:473:EXPORT_SYMBOL_GPL(enetc_phylink_create);\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.c-474-\n--\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.h=12=void enetc_mdiobus_destroy(struct enetc_pf *pf);\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.h:13:int enetc_phylink_create(struct enetc_ndev_priv *priv, struct device_node *node,\ndrivers/net/ethernet/freescale/enetc/enetc_pf_common.h-14-\t\t\t const struct phylink_mac_ops *ops);\n--\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c=853=static int fs_enet_probe(struct platform_device *ofdev)\n--\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c-932-\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c:933:\tphylink = phylink_create(\u0026fep-\u003ephylink_config, dev_fwnode(fep-\u003edev),\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c-934-\t\t\t\t phy_mode, \u0026fs_enet_phylink_mac_ops);\n--\ndrivers/net/ethernet/freescale/ucc_geth.c=3419=static int ucc_geth_probe(struct platform_device* ofdev)\n--\ndrivers/net/ethernet/freescale/ucc_geth.c-3573-\ndrivers/net/ethernet/freescale/ucc_geth.c:3574:\tphylink = phylink_create(\u0026ugeth-\u003ephylink_config, dev_fwnode(\u0026dev-\u003edev),\ndrivers/net/ethernet/freescale/ucc_geth.c-3575-\t\t\t\t phy_interface, \u0026ugeth_mac_ops);\n--\ndrivers/net/ethernet/marvell/mvneta.c=5480=static int mvneta_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/marvell/mvneta.c-5598-\ndrivers/net/ethernet/marvell/mvneta.c:5599:\tphylink = phylink_create(\u0026pp-\u003ephylink_config, pdev-\u003edev.fwnode,\ndrivers/net/ethernet/marvell/mvneta.c-5600-\t\t\t\t phy_mode, \u0026mvneta_phylink_ops);\n--\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c=6840=static int mvpp2_port_probe(struct platform_device *pdev,\n--\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c-7147-\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c:7148:\t\tphylink = phylink_create(\u0026port-\u003ephylink_config, port_fwnode,\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c-7149-\t\t\t\t\t phy_mode, \u0026mvpp2_phylink_ops);\n--\ndrivers/net/ethernet/marvell/prestera/prestera_main.c=370=static int prestera_port_sfp_bind(struct prestera_port *port)\n--\ndrivers/net/ethernet/marvell/prestera/prestera_main.c-416-\ndrivers/net/ethernet/marvell/prestera/prestera_main.c:417:\t\tphy_link = phylink_create(\u0026port-\u003ephy_config, fwnode,\ndrivers/net/ethernet/marvell/prestera/prestera_main.c-418-\t\t\t\t\t PHY_INTERFACE_MODE_INTERNAL,\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c=4824=static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-4968-\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c:4969:\tphylink = phylink_create(\u0026mac-\u003ephylink_config,\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-4970-\t\t\t\t of_fwnode_handle(mac-\u003eof_node),\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.c=746=struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.c-834-\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.c:835:\tif (fbnic_phylink_create(netdev)) {\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.c-836-\t\tfree_netdev(netdev);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h=111=int fbnic_phylink_get_fecparam(struct net_device *netdev,\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h-112-\t\t\t struct ethtool_fecparam *fecparam);\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h:113:int fbnic_phylink_create(struct net_device *netdev);\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h-114-void fbnic_phylink_destroy(struct net_device *netdev);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c=177=static const struct phylink_mac_ops fbnic_phylink_mac_ops = {\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-186-/**\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:187: * fbnic_phylink_create - Phylink device creation\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-188- * @netdev: Network Device struct to attach phylink device\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-196- **/\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:197:int fbnic_phylink_create(struct net_device *netdev)\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-198-{\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-231-\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:232:\tphylink = phylink_create(\u0026fbn-\u003ephylink_config, NULL,\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-233-\t\t\t\t fbnic_phylink_select_interface(fbn-\u003eaui),\n--\ndrivers/net/ethernet/microchip/lan743x_main.c=3152=static const struct phylink_mac_ops lan743x_phylink_mac_ops = {\n--\ndrivers/net/ethernet/microchip/lan743x_main.c-3159-\ndrivers/net/ethernet/microchip/lan743x_main.c:3160:static int lan743x_phylink_create(struct lan743x_adapter *adapter)\ndrivers/net/ethernet/microchip/lan743x_main.c-3161-{\n--\ndrivers/net/ethernet/microchip/lan743x_main.c-3211-\ndrivers/net/ethernet/microchip/lan743x_main.c:3212:\tpl = phylink_create(\u0026adapter-\u003ephylink_config, NULL,\ndrivers/net/ethernet/microchip/lan743x_main.c-3213-\t\t\t adapter-\u003ephy_interface, \u0026lan743x_phylink_mac_ops);\n--\ndrivers/net/ethernet/microchip/lan743x_main.c=3691=static int lan743x_pcidev_probe(struct pci_dev *pdev,\n--\ndrivers/net/ethernet/microchip/lan743x_main.c-3750-\ndrivers/net/ethernet/microchip/lan743x_main.c:3751:\tret = lan743x_phylink_create(adapter);\ndrivers/net/ethernet/microchip/lan743x_main.c-3752-\tif (ret \u003c 0) {\n--\ndrivers/net/ethernet/microchip/lan966x/lan966x_main.c=791=static int lan966x_probe_port(struct lan966x *lan966x, u32 p,\n--\ndrivers/net/ethernet/microchip/lan966x/lan966x_main.c-855-\ndrivers/net/ethernet/microchip/lan966x/lan966x_main.c:856:\tphylink = phylink_create(\u0026port-\u003ephylink_config,\ndrivers/net/ethernet/microchip/lan966x/lan966x_main.c-857-\t\t\t\t portnp,\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c=312=static int sparx5_create_port(struct sparx5 *sparx5,\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-388-\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c:389:\tphylink = phylink_create(\u0026spx5_port-\u003ephylink_config,\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-390-\t\t\t\t of_fwnode_handle(config-\u003enode),\n--\ndrivers/net/ethernet/mscc/ocelot_net.c=1774=static const struct phylink_mac_ops ocelot_phylink_ops = {\n--\ndrivers/net/ethernet/mscc/ocelot_net.c-1779-\ndrivers/net/ethernet/mscc/ocelot_net.c:1780:static int ocelot_port_phylink_create(struct ocelot *ocelot, int port,\ndrivers/net/ethernet/mscc/ocelot_net.c-1781-\t\t\t\t struct device_node *portnp)\n--\ndrivers/net/ethernet/mscc/ocelot_net.c-1820-\ndrivers/net/ethernet/mscc/ocelot_net.c:1821:\tphylink = phylink_create(\u0026priv-\u003ephylink_config,\ndrivers/net/ethernet/mscc/ocelot_net.c-1822-\t\t\t\t of_fwnode_handle(portnp),\n--\ndrivers/net/ethernet/mscc/ocelot_net.c=1843=int ocelot_probe_port(struct ocelot *ocelot, int port, struct regmap *target,\n--\ndrivers/net/ethernet/mscc/ocelot_net.c-1879-\ndrivers/net/ethernet/mscc/ocelot_net.c:1880:\terr = ocelot_port_phylink_create(ocelot, port, portnp);\ndrivers/net/ethernet/mscc/ocelot_net.c-1881-\tif (err)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c=1374=static int stmmac_phylink_setup(struct stmmac_priv *priv)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-1465-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:1466:\tphylink = phylink_create(config, dev_fwnode(priv-\u003edevice),\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-1467-\t\t\t\t priv-\u003eplat-\u003ephy_interface,\n--\ndrivers/net/ethernet/tehuti/tn40_phy.c=45=int tn40_phy_register(struct tn40_priv *priv)\n--\ndrivers/net/ethernet/tehuti/tn40_phy.c-62-\ndrivers/net/ethernet/tehuti/tn40_phy.c:63:\tphylink = phylink_create(config, NULL, PHY_INTERFACE_MODE_XAUI,\ndrivers/net/ethernet/tehuti/tn40_phy.c-64-\t\t\t\t \u0026tn40_mac_ops);\n--\ndrivers/net/ethernet/ti/am65-cpsw-nuss.c=2759=am65_cpsw_nuss_init_port_ndev(struct am65_cpsw_common *common, u32 port_idx)\n--\ndrivers/net/ethernet/ti/am65-cpsw-nuss.c-2843-\ndrivers/net/ethernet/ti/am65-cpsw-nuss.c:2844:\tphylink = phylink_create(\u0026port-\u003eslave.phylink_config,\ndrivers/net/ethernet/ti/am65-cpsw-nuss.c-2845-\t\t\t\t of_fwnode_handle(port-\u003eslave.port_np),\n--\ndrivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c=130=static int ngbe_phylink_init(struct wx *wx)\n--\ndrivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c-149-\ndrivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c:150:\tphylink = phylink_create(config, NULL, phy_mode, \u0026ngbe_mac_ops);\ndrivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c-151-\tif (IS_ERR(phylink))\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_aml.c=482=int txgbe_phylink_init_aml(struct txgbe *txgbe)\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_aml.c-512-\ndrivers/net/ethernet/wangxun/txgbe/txgbe_aml.c:513:\tphylink = phylink_create(config, NULL, phy_mode, \u0026txgbe_mac_ops_aml);\ndrivers/net/ethernet/wangxun/txgbe/txgbe_aml.c-514-\tif (IS_ERR(phylink))\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c=268=static int txgbe_phylink_init(struct txgbe *txgbe)\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c-292-\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c:293:\tphylink = phylink_create(config, fwnode, phy_mode, \u0026txgbe_mac_ops);\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c-294-\tif (IS_ERR(phylink))\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c=2795=static int axienet_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-3120-\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c:3121:\tlp-\u003ephylink = phylink_create(\u0026lp-\u003ephylink_config, pdev-\u003edev.fwnode,\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-3122-\t\t\t\t lp-\u003ephy_mode,\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-3125-\t\tret = PTR_ERR(lp-\u003ephylink);\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c:3126:\t\tdev_err(\u0026pdev-\u003edev, \"phylink_create error (%i)\\n\", ret);\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-3127-\t\tgoto cleanup_mdio;\n--\ndrivers/net/phy/phylink.c=533=static int phylink_validate_mac_and_pcs(struct phylink *pl,\n--\ndrivers/net/phy/phylink.c-548-\tif (pcs) {\ndrivers/net/phy/phylink.c:549:\t\t/* The PCS, if present, must be setup before phylink_create()\ndrivers/net/phy/phylink.c-550-\t\t * has been called. If the ops is not initialised, print an\n--\ndrivers/net/phy/phylink.c=1056=static enum inband_type phylink_get_inband_type(phy_interface_t interface)\n--\ndrivers/net/phy/phylink.c-1088- * phylink_pcs_neg_mode() - helper to determine PCS inband mode\ndrivers/net/phy/phylink.c:1089: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-1090- * @pcs: a pointer to \u0026struct phylink_pcs\n--\ndrivers/net/phy/phylink.c=1786=static int phylink_register_sfp(struct phylink *pl,\n--\ndrivers/net/phy/phylink.c-1810- * phylink_set_fixed_link() - set the fixed link\ndrivers/net/phy/phylink.c:1811: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-1812- * @state: a pointer to a struct phylink_link_state.\n--\ndrivers/net/phy/phylink.c=1850=static void phylink_slow_phy_poll(struct work_struct *work);\n--\ndrivers/net/phy/phylink.c-1852-/**\ndrivers/net/phy/phylink.c:1853: * phylink_create() - create a phylink instance\ndrivers/net/phy/phylink.c-1854- * @config: a pointer to the target \u0026struct phylink_config\n--\ndrivers/net/phy/phylink.c-1867- */\ndrivers/net/phy/phylink.c:1868:struct phylink *phylink_create(struct phylink_config *config,\ndrivers/net/phy/phylink.c-1869-\t\t\t const struct fwnode_handle *fwnode,\n--\ndrivers/net/phy/phylink.c-1956-}\ndrivers/net/phy/phylink.c:1957:EXPORT_SYMBOL_GPL(phylink_create);\ndrivers/net/phy/phylink.c-1958-\n--\ndrivers/net/phy/phylink.c-1960- * phylink_destroy() - cleanup and destroy the phylink instance\ndrivers/net/phy/phylink.c:1961: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-1962- *\n--\ndrivers/net/phy/phylink.c=1981=EXPORT_SYMBOL_GPL(phylink_destroy);\n--\ndrivers/net/phy/phylink.c-1984- * phylink_expects_phy() - Determine if phylink expects a phy to be attached\ndrivers/net/phy/phylink.c:1985: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-1986- *\n--\ndrivers/net/phy/phylink.c=2228=static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,\n--\ndrivers/net/phy/phylink.c-2244- * phylink_connect_phy() - connect a PHY to the phylink instance\ndrivers/net/phy/phylink.c:2245: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2246- * @phy: a pointer to a \u0026struct phy_device.\n--\ndrivers/net/phy/phylink.c=2310=static void phylink_slow_phy_poll(struct work_struct *work)\n--\ndrivers/net/phy/phylink.c-2410- * phylink_of_phy_connect() - connect the PHY specified in the DT mode.\ndrivers/net/phy/phylink.c:2411: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2412- * @dn: a pointer to a \u0026struct device_node.\n--\ndrivers/net/phy/phylink.c=2426=EXPORT_SYMBOL_GPL(phylink_of_phy_connect);\n--\ndrivers/net/phy/phylink.c-2429- * phylink_fwnode_phy_connect() - connect the PHY specified in the fwnode.\ndrivers/net/phy/phylink.c:2430: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2431- * @fwnode: a pointer to a \u0026struct fwnode_handle.\n--\ndrivers/net/phy/phylink.c=2517=EXPORT_SYMBOL_GPL(phylink_fwnode_phy_connect);\n--\ndrivers/net/phy/phylink.c-2521- * instance.\ndrivers/net/phy/phylink.c:2522: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2523- *\n--\ndrivers/net/phy/phylink.c=2564=static void phylink_link_changed(struct phylink *pl, bool up, const char *what)\n--\ndrivers/net/phy/phylink.c-2573- * phylink_mac_change() - notify phylink of a change in MAC state\ndrivers/net/phy/phylink.c:2574: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2575- * @up: indicates whether the link is currently up.\n--\ndrivers/net/phy/phylink.c=2606=static irqreturn_t phylink_link_handler(int irq, void *data)\n--\ndrivers/net/phy/phylink.c-2616- * phylink_start() - start a phylink instance\ndrivers/net/phy/phylink.c:2617: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2618- *\n--\ndrivers/net/phy/phylink.c=2679=EXPORT_SYMBOL_GPL(phylink_start);\n--\ndrivers/net/phy/phylink.c-2682- * phylink_stop() - stop a phylink instance\ndrivers/net/phy/phylink.c:2683: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2684- *\n--\ndrivers/net/phy/phylink.c=2713=EXPORT_SYMBOL_GPL(phylink_stop);\n--\ndrivers/net/phy/phylink.c-2716- * phylink_rx_clk_stop_block() - block PHY ability to stop receive clock in LPI\ndrivers/net/phy/phylink.c:2717: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2718- *\n--\ndrivers/net/phy/phylink.c=2742=EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_block);\n--\ndrivers/net/phy/phylink.c-2745- * phylink_rx_clk_stop_unblock() - unblock PHY ability to stop receive clock\ndrivers/net/phy/phylink.c:2746: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2747- *\n--\ndrivers/net/phy/phylink.c=2784=static bool phylink_phy_pm_speed_ctrl(struct phylink *pl)\n--\ndrivers/net/phy/phylink.c-2791- * phylink_suspend() - handle a network device suspend event\ndrivers/net/phy/phylink.c:2792: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2793- * @mac_wol: true if the MAC needs to receive packets for Wake-on-Lan\n--\ndrivers/net/phy/phylink.c=2843=EXPORT_SYMBOL_GPL(phylink_suspend);\n--\ndrivers/net/phy/phylink.c-2846- * phylink_prepare_resume() - prepare to resume a network device\ndrivers/net/phy/phylink.c:2847: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2848- *\n--\ndrivers/net/phy/phylink.c=2868=EXPORT_SYMBOL_GPL(phylink_prepare_resume);\n--\ndrivers/net/phy/phylink.c-2871- * phylink_resume() - handle a network device resume event\ndrivers/net/phy/phylink.c:2872: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2873- *\n--\ndrivers/net/phy/phylink.c=2911=EXPORT_SYMBOL_GPL(phylink_resume);\n--\ndrivers/net/phy/phylink.c-2914- * phylink_ethtool_get_wol() - get the wake on lan parameters for the PHY\ndrivers/net/phy/phylink.c:2915: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2916- * @wol: a pointer to \u0026struct ethtool_wolinfo to hold the read parameters\n--\ndrivers/net/phy/phylink.c=2948=EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol);\n--\ndrivers/net/phy/phylink.c-2951- * phylink_ethtool_set_wol() - set wake on lan parameters\ndrivers/net/phy/phylink.c:2952: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-2953- * @wol: a pointer to \u0026struct ethtool_wolinfo for the desired parameters\n--\ndrivers/net/phy/phylink.c=3080=static void phylink_get_ksettings(const struct phylink_link_state *state,\n--\ndrivers/net/phy/phylink.c-3095- * phylink_ethtool_ksettings_get() - get the current link settings\ndrivers/net/phy/phylink.c:3096: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3097- * @kset: a pointer to a \u0026struct ethtool_link_ksettings to hold link settings\n--\ndrivers/net/phy/phylink.c=3159=static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,\n--\ndrivers/net/phy/phylink.c-3180- * phylink_ethtool_ksettings_set() - set the link settings\ndrivers/net/phy/phylink.c:3181: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3182- * @kset: a pointer to a \u0026struct ethtool_link_ksettings for the desired modes\n--\ndrivers/net/phy/phylink.c=3353=EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set);\n--\ndrivers/net/phy/phylink.c-3356- * phylink_ethtool_nway_reset() - restart negotiation\ndrivers/net/phy/phylink.c:3357: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3358- *\n--\ndrivers/net/phy/phylink.c=3378=EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);\n--\ndrivers/net/phy/phylink.c-3381- * phylink_ethtool_get_pauseparam() - get the current pause parameters\ndrivers/net/phy/phylink.c:3382: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3383- * @pause: a pointer to a \u0026struct ethtool_pauseparam\n--\ndrivers/net/phy/phylink.c=3394=EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);\n--\ndrivers/net/phy/phylink.c-3397- * phylink_ethtool_set_pauseparam() - set the current pause parameters\ndrivers/net/phy/phylink.c:3398: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3399- * @pause: a pointer to a \u0026struct ethtool_pauseparam\n--\ndrivers/net/phy/phylink.c=3480=EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);\n--\ndrivers/net/phy/phylink.c-3484- * counter\ndrivers/net/phy/phylink.c:3485: * @pl: a pointer to a \u0026struct phylink returned from phylink_create().\ndrivers/net/phy/phylink.c-3486- *\n--\ndrivers/net/phy/phylink.c=3503=EXPORT_SYMBOL_GPL(phylink_get_eee_err);\n--\ndrivers/net/phy/phylink.c-3506- * phylink_ethtool_get_eee() - read the energy efficient ethernet parameters\ndrivers/net/phy/phylink.c:3507: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3508- * @eee: a pointer to a \u0026struct ethtool_keee for the read parameters\n--\ndrivers/net/phy/phylink.c=3529=EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);\n--\ndrivers/net/phy/phylink.c-3532- * phylink_ethtool_set_eee() - set the energy efficient ethernet parameters\ndrivers/net/phy/phylink.c:3533: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3534- * @eee: a pointer to a \u0026struct ethtool_keee for the desired parameters\n--\ndrivers/net/phy/phylink.c=3703=static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,\n--\ndrivers/net/phy/phylink.c-3721- * phylink_mii_ioctl() - generic mii ioctl interface\ndrivers/net/phy/phylink.c:3722: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3723- * @ifr: a pointer to a \u0026struct ifreq for socket ioctls\n--\ndrivers/net/phy/phylink.c=3796=EXPORT_SYMBOL_GPL(phylink_mii_ioctl);\n--\ndrivers/net/phy/phylink.c-3800- * link partners\ndrivers/net/phy/phylink.c:3801: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3802- * @sync: perform action synchronously\n--\ndrivers/net/phy/phylink.c=3821=EXPORT_SYMBOL_GPL(phylink_speed_down);\n--\ndrivers/net/phy/phylink.c-3825- * phylink_speed_down()\ndrivers/net/phy/phylink.c:3826: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-3827- *\n--\ndrivers/net/phy/phylink.c=4580=EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state);\n--\ndrivers/net/phy/phylink.c-4584- *\t\t\t\t which loses state\ndrivers/net/phy/phylink.c:4585: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-4586- *\n--\ndrivers/net/phy/phylink.c=4601=EXPORT_SYMBOL_GPL(phylink_replay_link_begin);\n--\ndrivers/net/phy/phylink.c-4605- *\t\t\t which lost state\ndrivers/net/phy/phylink.c:4606: * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\ndrivers/net/phy/phylink.c-4607- *\n--\ndrivers/net/usb/asix_devices.c=799=static int ax88772_phylink_setup(struct usbnet *dev)\n--\ndrivers/net/usb/asix_devices.c-819-\ndrivers/net/usb/asix_devices.c:820:\tphylink = phylink_create(\u0026priv-\u003ephylink_config, dev-\u003enet-\u003edev.fwnode,\ndrivers/net/usb/asix_devices.c-821-\t\t\t\t phy_if_mode, \u0026ax88772_phylink_mac_ops);\n--\ndrivers/net/usb/lan78xx.c=2799=static int lan78xx_phylink_setup(struct lan78xx_net *dev)\n--\ndrivers/net/usb/lan78xx.c-2838-\ndrivers/net/usb/lan78xx.c:2839:\tphylink = phylink_create(pc, dev-\u003enet-\u003edev.fwnode,\ndrivers/net/usb/lan78xx.c-2840-\t\t\t\t dev-\u003einterface, \u0026lan78xx_phylink_mac_ops);\n"
} |
| 5/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 34655 output: 39 thoughts: 156 Thoughts: **Phylink Exploration for Syzkaller Coverage** Okay, so I'm thinking about how to improve syzkaller coverage, particularly around network devices. It hits me that a lot of these USB ethernet devices – the asix (ax88772), lan78xx, and even some PCI and emulated network drivers – leverage phylink under the hood. That's a key connection! And, I know phylink *is* accessible in syzkaller. I've seen it through Raw Gadget USB emulation that hooks up things like the lan78xx and ax88772, and also via ethtool ioctls and netlink on interfaces that are using phylink. Right, so where's the best place to start? I should really dive into the recent patch changes to understand any potential new attack surface here, and so on. I should examine `drivers/net/phy/phylink.c` more closely, specifically around lines 2200 to 2600, 2900 to 3050, and 3100 to 3200. That's where I need to focus my attention. It will give me the best chance of understanding the new patch, if there are any. |
| 6/2 | 2026/08/29 06:20 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/phylink.c",
"FirstLine": 2420,
"LineCount": 80
}Results: {
"Contents": "2420:\t */\n2421:\tint phylink_of_phy_connect(struct phylink *pl, struct device_node *dn,\n2422:\t\t\t\t u32 flags)\n2423:\t{\n2424:\t\treturn phylink_fwnode_phy_connect(pl, of_fwnode_handle(dn), flags);\n2425:\t}\n2426:\tEXPORT_SYMBOL_GPL(phylink_of_phy_connect);\n2427:\t\n2428:\t/**\n2429:\t * phylink_fwnode_phy_connect() - connect the PHY specified in the fwnode.\n2430:\t * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\n2431:\t * @fwnode: a pointer to a \u0026struct fwnode_handle.\n2432:\t * @flags: PHY-specific flags to communicate to the PHY device driver\n2433:\t *\n2434:\t * Connect the phy specified @fwnode to the phylink instance specified\n2435:\t * by @pl.\n2436:\t *\n2437:\t * If the port node carries the slow-to-probe property and the PHY is not\n2438:\t * usable yet, 0 is returned with no PHY connected: a poller connects it\n2439:\t * once its driver has probed. Until then the MAC runs without a PHY and\n2440:\t * ethtool reports no link modes.\n2441:\t *\n2442:\t * Returns 0 on success - the PHY connected, or the deferred connect\n2443:\t * armed - or a negative errno.\n2444:\t */\n2445:\tint phylink_fwnode_phy_connect(struct phylink *pl,\n2446:\t\t\t\t const struct fwnode_handle *fwnode,\n2447:\t\t\t\t u32 flags)\n2448:\t{\n2449:\t\tstruct fwnode_handle *phy_fwnode;\n2450:\t\tstruct phy_device *phy_dev;\n2451:\t\tint ret;\n2452:\t\n2453:\t\tif (!phylink_expects_phy(pl))\n2454:\t\t\treturn 0;\n2455:\t\n2456:\t\tphy_fwnode = fwnode_get_phy_node(fwnode);\n2457:\t\tif (IS_ERR(phy_fwnode)) {\n2458:\t\t\t/* PHY mode requires a PHY to be specified. */\n2459:\t\t\tif (pl-\u003ecfg_link_an_mode == MLO_AN_PHY)\n2460:\t\t\t\treturn -ENODEV;\n2461:\t\t\treturn 0;\n2462:\t\t}\n2463:\t\n2464:\t\tphy_dev = fwnode_phy_find_device(phy_fwnode);\n2465:\t\tif (!phylink_phy_is_usable(phy_dev) \u0026\u0026\n2466:\t\t fwnode_property_present(fwnode, \"slow-to-probe\")) {\n2467:\t\t\t/* The PHY is known to appear late, so keep the node and poll\n2468:\t\t\t * for it rather than failing the port for good. Returning an\n2469:\t\t\t * error here would also send DSA off to look for the PHY on\n2470:\t\t\t * the switch's own MDIO bus.\n2471:\t\t\t */\n2472:\t\t\tif (phy_dev)\n2473:\t\t\t\tphy_device_free(phy_dev);\n2474:\t\n2475:\t\t\tpl-\u003eslow_phy_fwnode = phy_fwnode;\n2476:\t\t\tpl-\u003eslow_phy_flags = flags;\n2477:\t\t\tWRITE_ONCE(pl-\u003eslow_phy_poll_ms, PHYLINK_SLOW_PHY_POLL_MS);\n2478:\t\t\tpl-\u003eslow_phy_waited_ms = 0;\n2479:\t\t\tWRITE_ONCE(pl-\u003eslow_phy_stop, false);\n2480:\t\t\t/* mod_ rather than queue_: a poll that a disconnect cancelled\n2481:\t\t\t * mid-backoff may still be pending with seconds left on it.\n2482:\t\t\t */\n2483:\t\t\t/* Freezable: attaching and starting a PHY has no place in\n2484:\t\t\t * the middle of a system suspend transition.\n2485:\t\t\t */\n2486:\t\t\tmod_delayed_work(system_freezable_power_efficient_wq,\n2487:\t\t\t\t\t \u0026pl-\u003eslow_phy_poll, 0);\n2488:\t\t\treturn 0;\n2489:\t\t}\n2490:\t\n2491:\t\t/* We're done with the phy_node handle */\n2492:\t\tfwnode_handle_put(phy_fwnode);\n2493:\t\tif (!phy_dev)\n2494:\t\t\treturn -ENODEV;\n2495:\t\n2496:\t\t/* Use PHY device/driver interface */\n2497:\t\tif (pl-\u003elink_interface == PHY_INTERFACE_MODE_NA) {\n2498:\t\t\tpl-\u003elink_interface = phy_dev-\u003einterface;\n2499:\t\t\tpl-\u003elink_config.interface = pl-\u003elink_interface;\n"
} |
| 7/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 36492 output: 40 thoughts: 59 Thoughts: Here's my thinking: **Examining PHY Polling and Ethtool Settings in Network Drivers** Okay, so I'm diving into the network PHY interaction to troubleshoot this. My immediate thought is to zero in on how the slow PHY polling is handled within the kernel. I'll need to explore `drivers/net/phy/phylink.c`, specifically the `phylink_slow_phy_poll` function around lines 2300-2415. I'm assuming that function handles the less-frequent checks, perhaps for link status or specific PHY events. Understanding how that's implemented is crucial. Then, I'll switch gears and analyze how `ethtool` settings play a role. I'll look at the sections from lines 3100-3210 in the same file to see how those configurations interact with the PHY. This is likely where I'll find the interface between user-space network management and the hardware, so it will be important to confirm how they are handled, and if there are any conflicts. |
| 8/2 | 2026/08/29 06:20 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/phylink.c",
"FirstLine": 2300,
"LineCount": 100
}Results: {
"Contents": "2300:\t * once that has returned; a device connected in between is validated against\n2301:\t * an empty link-mode mask. PHY_DOWN is the documented state of a device whose\n2302:\t * probe has not completed, and phy_probe() leaves it in PHY_READY.\n2303:\t */\n2304:\tstatic bool phylink_phy_is_usable(struct phy_device *phy_dev)\n2305:\t{\n2306:\t\treturn phy_dev \u0026\u0026 phy_dev-\u003edrv \u0026\u0026 phy_dev-\u003estate != PHY_DOWN \u0026\u0026\n2307:\t\t !phy_driver_is_genphy(phy_dev);\n2308:\t}\n2309:\t\n2310:\tstatic void phylink_slow_phy_poll(struct work_struct *work)\n2311:\t{\n2312:\t\tstruct phylink *pl = container_of(to_delayed_work(work), struct phylink,\n2313:\t\t\t\t\t\t slow_phy_poll);\n2314:\t\tstruct phy_device *phy_dev;\n2315:\t\tint ret;\n2316:\t\n2317:\t\tif (READ_ONCE(pl-\u003eslow_phy_stop))\n2318:\t\t\treturn;\n2319:\t\n2320:\t\t/* phylink_disconnect_phy() cancels this work while holding rtnl, so\n2321:\t\t * blocking on rtnl here would deadlock against it. Requeue instead and\n2322:\t\t * let the canceller finish.\n2323:\t\t */\n2324:\t\tif (!rtnl_trylock())\n2325:\t\t\tgoto requeue;\n2326:\t\n2327:\t\tif (READ_ONCE(pl-\u003eslow_phy_stop)) {\n2328:\t\t\trtnl_unlock();\n2329:\t\t\treturn;\n2330:\t\t}\n2331:\t\n2332:\t\t/* Do not \"optimize\" the lookup out from under rtnl: rtnl is what\n2333:\t\t * makes phylink_disconnect_phy()'s put-and-clear of slow_phy_fwnode\n2334:\t\t * safe against this dereference. The uncontended trylock above is\n2335:\t\t * cheaper than the use-after-free.\n2336:\t\t */\n2337:\t\tphy_dev = fwnode_phy_find_device(pl-\u003eslow_phy_fwnode);\n2338:\t\tif (!phylink_phy_is_usable(phy_dev)) {\n2339:\t\t\tif (phy_dev)\n2340:\t\t\t\tphy_device_free(phy_dev);\n2341:\t\n2342:\t\t\t/* A PHY that never turns up would otherwise wait in complete\n2343:\t\t\t * silence: one breadcrumb after a minute, like the connect\n2344:\t\t\t * failure below gets, and like a missing firmware file gets\n2345:\t\t\t * from the MDIO side.\n2346:\t\t\t */\n2347:\t\t\tpl-\u003eslow_phy_waited_ms += pl-\u003eslow_phy_poll_ms;\n2348:\t\t\tif (pl-\u003eslow_phy_waited_ms \u003e= PHYLINK_SLOW_PHY_WARN_MS \u0026\u0026\n2349:\t\t\t pl-\u003eslow_phy_waited_ms - pl-\u003eslow_phy_poll_ms \u003c\n2350:\t\t\t PHYLINK_SLOW_PHY_WARN_MS)\n2351:\t\t\t\tphylink_warn(pl,\n2352:\t\t\t\t\t \"still waiting for the slow-to-probe PHY (%pfw)\\n\",\n2353:\t\t\t\t\t pl-\u003eslow_phy_fwnode);\n2354:\t\t\trtnl_unlock();\n2355:\t\t\tgoto requeue;\n2356:\t\t}\n2357:\t\n2358:\t\tif (pl-\u003elink_interface == PHY_INTERFACE_MODE_NA) {\n2359:\t\t\tpl-\u003elink_interface = phy_dev-\u003einterface;\n2360:\t\t\tpl-\u003elink_config.interface = pl-\u003elink_interface;\n2361:\t\t}\n2362:\t\n2363:\t\tret = phylink_attach_phy(pl, phy_dev, pl-\u003elink_interface,\n2364:\t\t\t\t\t pl-\u003eslow_phy_flags);\n2365:\t\tphy_device_free(phy_dev);\n2366:\t\tif (!ret) {\n2367:\t\t\tret = phylink_bringup_phy(pl, phy_dev,\n2368:\t\t\t\t\t\t pl-\u003elink_config.interface);\n2369:\t\t\tif (ret)\n2370:\t\t\t\tphy_detach(phy_dev);\n2371:\t\t\telse if (!test_bit(PHYLINK_DISABLE_STOPPED,\n2372:\t\t\t\t\t \u0026pl-\u003ephylink_disable_state))\n2373:\t\t\t\t/* phylink_start() starts the PHY it finds attached,\n2374:\t\t\t\t * and ran long before this one turned up.\n2375:\t\t\t\t */\n2376:\t\t\t\tphy_start(phy_dev);\n2377:\t\t}\n2378:\t\tif (ret) {\n2379:\t\t\t/* Whether this is permanent cannot be read off the errno: the\n2380:\t\t\t * same -EINVAL comes out of phylink_validate_phy() for a PHY\n2381:\t\t\t * that is still filling in its link modes and for one that\n2382:\t\t\t * genuinely cannot speak this interface. What decides it is\n2383:\t\t\t * the PHY's supported mask, and only the driver binding again\n2384:\t\t\t * changes that, so keep polling - giving up here loses the\n2385:\t\t\t * port for the lifetime of this phylink. Back off instead: a\n2386:\t\t\t * failed bringup ends in phy_detach(), which asserts the PHY's\n2387:\t\t\t * reset line. Report the first failure only, so a PHY that\n2388:\t\t\t * will never fit costs one line rather than a stream of them.\n2389:\t\t\t */\n2390:\t\t\tif (pl-\u003eslow_phy_poll_ms == PHYLINK_SLOW_PHY_POLL_MS)\n2391:\t\t\t\tphylink_err(pl, \"failed to connect late PHY: %pe\\n\",\n2392:\t\t\t\t\t ERR_PTR(ret));\n2393:\t\n2394:\t\t\tWRITE_ONCE(pl-\u003eslow_phy_poll_ms,\n2395:\t\t\t\t min_t(unsigned int, pl-\u003eslow_phy_poll_ms * 2,\n2396:\t\t\t\t\t PHYLINK_SLOW_PHY_POLL_MAX_MS));\n2397:\t\t}\n2398:\t\trtnl_unlock();\n2399:\t\n"
} |
| 9/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 38531 output: 40 thoughts: 17 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into ethtool and phylink.c** Okay, so we're looking at phylink.c, specifically how it handles ethtool. I need to make sure I understand the interaction here thoroughly. What exactly is going on with the ethtool interface within this context? What specific data structures and functions are being used? I need to quickly recall the typical ethtool interactions – does it involve specific ioctls, or is it abstracted differently in this part of the kernel? Are we dealing with link settings, statistics, or something else entirely? Time to refresh my memory on any recent commits or discussions concerning this area. I need to get a clear picture of the flow control. From what I recall, the handling is... |
| 10/2 | 2026/08/29 06:20 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/phylink.c",
"FirstLine": 3100,
"LineCount": 100
}Results: {
"Contents": "3100:\t * This will be the link settings read from the MAC, PHY or fixed link\n3101:\t * settings depending on the current negotiation mode.\n3102:\t */\n3103:\tint phylink_ethtool_ksettings_get(struct phylink *pl,\n3104:\t\t\t\t\t struct ethtool_link_ksettings *kset)\n3105:\t{\n3106:\t\tstruct phylink_link_state link_state;\n3107:\t\n3108:\t\tASSERT_RTNL();\n3109:\t\n3110:\t\tif (pl-\u003ephydev)\n3111:\t\t\tphy_ethtool_ksettings_get(pl-\u003ephydev, kset);\n3112:\t\telse\n3113:\t\t\tkset-\u003ebase.port = pl-\u003elink_port;\n3114:\t\n3115:\t\t/* Until the PHY arrives the port can do nothing, so report no link\n3116:\t\t * modes at all rather than the MAC's own capabilities, which is what\n3117:\t\t * an empty SFP cage reports.\n3118:\t\t */\n3119:\t\tif (phylink_slow_phy_pending(pl)) {\n3120:\t\t\tlinkmode_zero(kset-\u003elink_modes.supported);\n3121:\t\t\tkset-\u003ebase.speed = SPEED_UNKNOWN;\n3122:\t\t\tkset-\u003ebase.duplex = DUPLEX_UNKNOWN;\n3123:\t\t\treturn 0;\n3124:\t\t}\n3125:\t\n3126:\t\tlinkmode_copy(kset-\u003elink_modes.supported, pl-\u003esupported);\n3127:\t\n3128:\t\tswitch (pl-\u003eact_link_an_mode) {\n3129:\t\tcase MLO_AN_FIXED:\n3130:\t\t\t/* We are using fixed settings. Report these as the\n3131:\t\t\t * current link settings - and note that these also\n3132:\t\t\t * represent the supported speeds/duplex/pause modes.\n3133:\t\t\t */\n3134:\t\t\tphylink_get_fixed_state(pl, \u0026link_state);\n3135:\t\t\tphylink_get_ksettings(\u0026link_state, kset);\n3136:\t\t\tbreak;\n3137:\t\n3138:\t\tcase MLO_AN_INBAND:\n3139:\t\t\t/* If there is a phy attached, then use the reported\n3140:\t\t\t * settings from the phy with no modification.\n3141:\t\t\t */\n3142:\t\t\tif (pl-\u003ephydev)\n3143:\t\t\t\tbreak;\n3144:\t\n3145:\t\t\tphylink_mac_pcs_get_state(pl, \u0026link_state);\n3146:\t\n3147:\t\t\t/* The MAC is reporting the link results from its own PCS\n3148:\t\t\t * layer via in-band status. Report these as the current\n3149:\t\t\t * link settings.\n3150:\t\t\t */\n3151:\t\t\tphylink_get_ksettings(\u0026link_state, kset);\n3152:\t\t\tbreak;\n3153:\t\t}\n3154:\t\n3155:\t\treturn 0;\n3156:\t}\n3157:\tEXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);\n3158:\t\n3159:\tstatic bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,\n3160:\t\t\t\t\t\t phy_interface_t interface,\n3161:\t\t\t\t\t\t\tunsigned long *adv)\n3162:\t{\n3163:\t\tunsigned int inband = phylink_inband_caps(pl, interface);\n3164:\t\tunsigned int mask;\n3165:\t\n3166:\t\t/* If the PCS doesn't implement inband support, be permissive. */\n3167:\t\tif (!inband)\n3168:\t\t\treturn true;\n3169:\t\n3170:\t\tif (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))\n3171:\t\t\tmask = LINK_INBAND_ENABLE;\n3172:\t\telse\n3173:\t\t\tmask = LINK_INBAND_DISABLE;\n3174:\t\n3175:\t\t/* Check whether the PCS implements the required mode */\n3176:\t\treturn !!(inband \u0026 mask);\n3177:\t}\n3178:\t\n3179:\t/**\n3180:\t * phylink_ethtool_ksettings_set() - set the link settings\n3181:\t * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\n3182:\t * @kset: a pointer to a \u0026struct ethtool_link_ksettings for the desired modes\n3183:\t */\n3184:\tint phylink_ethtool_ksettings_set(struct phylink *pl,\n3185:\t\t\t\t\t const struct ethtool_link_ksettings *kset)\n3186:\t{\n3187:\t\t__ETHTOOL_DECLARE_LINK_MODE_MASK(support);\n3188:\t\tconst struct link_capabilities *c;\n3189:\t\tstruct phylink_link_state config;\n3190:\t\n3191:\t\tASSERT_RTNL();\n3192:\t\n3193:\t\t/* Without a PHY and without an SFP bus this would configure the MAC\n3194:\t\t * on its own, for a link that cannot come up yet.\n3195:\t\t */\n3196:\t\tif (phylink_slow_phy_pending(pl))\n3197:\t\t\treturn -EINVAL;\n3198:\t\n3199:\t\tif (pl-\u003ephydev) {\n"
} |
| 11/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 40257 output: 27 thoughts: 110 Thoughts: **Analyzing Recent Network PHY Changes** Okay, let's dive into this. My initial focus is to understand the scope of the recent modifications. I need to examine the specific code changes within `drivers/net/phy/phylink.c`, `drivers/net/phy/air_phy_lib.c`, `drivers/net/phy/air_en8811h.c`, and `drivers/net/mdio/mdio-airoha-en8811h.c`. Those files should provide a concentrated view of the implemented changes. After pinpointing the actual code modifications, I'll need to confirm any configuration changes. I'll need to review the related `Kconfig` files – specifically looking at those for `PHYLINK`, `AIR_NET_PHYLIB`, and `MDIO_AIROHA_EN8811H`. This is critical to understanding how these changes impact the build process and device configuration. The Kconfig entries will illuminate whether new features were introduced, existing ones altered, or dependencies modified. This will likely provide a good roadmap for understanding the motivation and implications of these commits. |
| 12/2 | 2026/08/29 06:20 | tool | grepper |
0mArgs:{
"Expression": "config PHYLINK",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/phy/Kconfig-5-\ndrivers/net/phy/Kconfig:6:config PHYLINK\ndrivers/net/phy/Kconfig-7-\ttristate\n"
} |
| 13/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 40691 output: 34 thoughts: 31 Thoughts: Okay, here's my take on those thoughts, framed as if they're my own: **Investigating PHY Configuration Options** Alright, time to dive into the kernel config files for some network PHY options. My immediate thought is to zero in on `drivers/net/phy/Kconfig`, specifically the section roughly from lines 1 to 50. I'm expecting to find the core configuration options that control the inclusion of different PHY drivers. This is where the magic happens, right? This will tell me which PHYs are compiled in, which are modules, and how they relate to the underlying MDIO layer. I'll also need to glance at `drivers/net/mdio/Kconfig`. This is going to be crucial to understand how the MDIO bus itself is configured, and how the PHY drivers plug into it. I'm looking for the base MDIO configuration and any potential dependencies or overrides that might affect the PHY choices. Probably how the MDIO core modules are chosen. Gotta make sure those settings are properly aligned or I'll be chasing my tail. |
| 14/2 | 2026/08/29 06:20 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/Kconfig",
"FirstLine": 1,
"LineCount": 50
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t#\n 3:\t# PHY Layer Configuration\n 4:\t#\n 5:\t\n 6:\tconfig PHYLINK\n 7:\t\ttristate\n 8:\t\tselect PHYLIB\n 9:\t\tselect SWPHY\n 10:\t\thelp\n 11:\t\t PHYlink models the link between the PHY and MAC, allowing fixed\n 12:\t\t configuration links, PHYs, and Serdes links with MAC level\n 13:\t\t autonegotiation modes.\n 14:\t\n 15:\tmenuconfig PHYLIB\n 16:\t\ttristate \"PHY Device support and infrastructure\"\n 17:\t\thelp\n 18:\t\t Ethernet controllers are usually attached to PHY\n 19:\t\t devices. This option provides infrastructure for\n 20:\t\t managing PHY devices.\n 21:\t\n 22:\tif PHYLIB\n 23:\t\n 24:\tconfig SWPHY\n 25:\t\tbool\n 26:\t\n 27:\tconfig PHY_PACKAGE\n 28:\t\t tristate\n 29:\t\n 30:\tconfig LED_TRIGGER_PHY\n 31:\t\tbool \"Support LED triggers for tracking link state\"\n 32:\t\tdepends on LEDS_TRIGGERS\n 33:\t\thelp\n 34:\t\t Adds support for a set of LED trigger events per-PHY. Link\n 35:\t\t state change will trigger the events, for consumption by an\n 36:\t\t LED class driver. There are triggers for each link speed currently\n 37:\t\t supported by the PHY and also a one common \"link\" trigger as a\n 38:\t\t logical-or of all the link speed ones.\n 39:\t\t All these triggers are named according to the following pattern:\n 40:\t\t \u003cmii bus id\u003e:\u003cphy\u003e:\u003cspeed\u003e\n 41:\t\n 42:\t\t Where speed is in the form:\n 43:\t\t\t\u003cSpeed in megabits\u003eMbps OR \u003cSpeed in gigabits\u003eGbps OR link\n 44:\t\t\tfor any speed known to the PHY.\n 45:\t\n 46:\tconfig OPEN_ALLIANCE_HELPERS\n 47:\t\tbool\n 48:\t\n 49:\tconfig PHYLIB_LEDS\n 50:\t\tdef_bool OF\n"
} |
| 15/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 41581 output: 35 thoughts: 31 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Examining PHY Library Configuration Options** Alright, let's get down to brass tacks. I need to refresh my memory regarding the physical layer (PHY) library options for these network drivers. I'm thinking, "Where do the configuration choices for `AIR_NET_PHYLIB` and `AIROHA_PHY` reside?" Time to navigate to the `drivers/net/phy/Kconfig` file. I'll need to double-check their conditional dependencies, defaults, and any associated help text. I want to ensure I fully understand how these PHY libraries are enabled, disabled, and configured within this particular build environment. It's crucial I grasp the interactions between these specific PHY options and the other related networking components. I'm mentally parsing the likely structure of the Kconfig file – is it a direct menuconfig entry, or is it hidden behind a broader dependency? Let's get to the source and solidify my understanding. |
| 16/2 | 2026/08/29 06:20 | tool | grepper |
0mArgs:{
"Expression": "AIR_NET_PHYLIB|AIROHA_PHY",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/mdio/Kconfig=32=config MDIO_AIROHA_EN8811H\n--\ndrivers/net/mdio/Kconfig-34-\tdepends on OF_MDIO\ndrivers/net/mdio/Kconfig:35:\tselect AIR_NET_PHYLIB\ndrivers/net/mdio/Kconfig-36-\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=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=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"
} |
| 17/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 42313 output: 27 thoughts: 1634 Thoughts: **Fuzzing Analysis: PHYLink and Airoha EN8811H Driver Patch** Alright, let's break down this patch and figure out its fuzzing potential. First, the patch touches `phylink.c`, which is a key component of the network PHY subsystem. This is definitely core kernel logic, and the modifications here involve critical aspects like link management, ethtool integration, workqueue handling for PHY probing, and the PHY connect/disconnect lifecycle. Specifically, I see changes to `phylink_slow_phy_poll`, `phylink_fwnode_phy_connect`, `phylink_disconnect_phy`, `phylink_ethtool_ksettings_get`, and `phylink_ethtool_ksettings_set`. These aren't just cosmetic changes; they're directly related to link state transitions, ethtool configuration, and asynchronous PHY interaction, all of which are good targets for fuzzing. This looks highly promising. And of course, it includes a new driver, `MDIO_AIROHA_EN8811H`, along with related changes to shared library code within `air_phy_lib.c` and `air_en8811h.c` with the firmware loading and control helper functions such as `air_en8811h_fw_download`, `air_en8811h_mcu_running`, `air_en8811h_wait_mcu_ready`, and `air_fw_write_buf`. Now, does this meet the criteria for fuzzing? Absolutely. `phylink.c` is definitely reachable. Ethtool ioctls/netlink, netdevice open/close/down/up, even virtual/emulated devices (like USB ethernet) can trigger this logic. The changes introduce asynchronous work, lock handling, and memory management, all of which are potential sources of bugs. Therefore, I'm confident we can say `WorthFuzzing = true`. Next, I need to identify the key entry points for fuzzing (the `FocusSymbols`). The focus here should be on non-static, non-inlined API entrypoints. From `phylink.c`, I want to focus on: * `phylink_fwnode_phy_connect` * `phylink_disconnect_phy` * `phylink_ethtool_ksettings_get` * `phylink_ethtool_ksettings_set` From `air_phy_lib.c`, I want to focus on: * `air_en8811h_fw_download` `phylink_ethtool_ksettings_get` and `phylink_ethtool_ksettings_set` are perfect candidates as they are exposed to user space. Finally, I need to identify the required kernel configurations. Let's dig into the Kconfig files for PHYLINK and the Airoha driver to make sure the fuzzing environment is properly set up and that all needed options are enabled. |
| 18/2 | 2026/08/29 06:20 | tool | grepper |
0mArgs:{
"Expression": "select PHYLINK",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/ethernet/altera/Kconfig=2=config ALTERA_TSE\n--\ndrivers/net/ethernet/altera/Kconfig-6-\tselect PHYLIB\ndrivers/net/ethernet/altera/Kconfig:7:\tselect PHYLINK\ndrivers/net/ethernet/altera/Kconfig-8-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/atheros/Kconfig=20=config AG71XX\n--\ndrivers/net/ethernet/atheros/Kconfig-22-\tdepends on ATH79 || COMPILE_TEST\ndrivers/net/ethernet/atheros/Kconfig:23:\tselect PHYLINK\ndrivers/net/ethernet/atheros/Kconfig-24-\timply NET_SELFTESTS\n--\ndrivers/net/ethernet/cadence/Kconfig=22=config MACB\n--\ndrivers/net/ethernet/cadence/Kconfig-25-\tdepends on PTP_1588_CLOCK_OPTIONAL\ndrivers/net/ethernet/cadence/Kconfig:26:\tselect PHYLINK\ndrivers/net/ethernet/cadence/Kconfig-27-\tselect CRC32\n--\ndrivers/net/ethernet/freescale/Kconfig=80=config UCC_GETH\n--\ndrivers/net/ethernet/freescale/Kconfig-83-\tselect FSL_PQ_MDIO\ndrivers/net/ethernet/freescale/Kconfig:84:\tselect PHYLINK\ndrivers/net/ethernet/freescale/Kconfig-85-\thelp\n--\ndrivers/net/ethernet/freescale/dpaa/Kconfig=2=menuconfig FSL_DPAA_ETH\n--\ndrivers/net/ethernet/freescale/dpaa/Kconfig-4-\tdepends on FSL_DPAA \u0026\u0026 FSL_FMAN\ndrivers/net/ethernet/freescale/dpaa/Kconfig:5:\tselect PHYLINK\ndrivers/net/ethernet/freescale/dpaa/Kconfig-6-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/freescale/dpaa2/Kconfig=2=config FSL_DPAA2_ETH\n--\ndrivers/net/ethernet/freescale/dpaa2/Kconfig-4-\tdepends on FSL_MC_BUS \u0026\u0026 FSL_MC_DPIO\ndrivers/net/ethernet/freescale/dpaa2/Kconfig:5:\tselect PHYLINK\ndrivers/net/ethernet/freescale/dpaa2/Kconfig-6-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/freescale/dpaa2/Kconfig=33=config FSL_DPAA2_SWITCH\n--\ndrivers/net/ethernet/freescale/dpaa2/Kconfig-37-\tdepends on FSL_MC_BUS \u0026\u0026 FSL_MC_DPIO\ndrivers/net/ethernet/freescale/dpaa2/Kconfig:38:\tselect PHYLINK\ndrivers/net/ethernet/freescale/dpaa2/Kconfig-39-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig=30=config FSL_ENETC\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig-37-\tselect NXP_ENETC_PF_COMMON\ndrivers/net/ethernet/freescale/enetc/Kconfig:38:\tselect PHYLINK\ndrivers/net/ethernet/freescale/enetc/Kconfig-39-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig=48=config NXP_ENETC4\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig-55-\tselect NXP_NTMP\ndrivers/net/ethernet/freescale/enetc/Kconfig:56:\tselect PHYLINK\ndrivers/net/ethernet/freescale/enetc/Kconfig-57-\tselect DIMLIB\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig=66=config FSL_ENETC_VF\n--\ndrivers/net/ethernet/freescale/enetc/Kconfig-71-\tselect FSL_ENETC_MDIO\ndrivers/net/ethernet/freescale/enetc/Kconfig:72:\tselect PHYLINK\ndrivers/net/ethernet/freescale/enetc/Kconfig-73-\tselect DIMLIB\n--\ndrivers/net/ethernet/freescale/fman/Kconfig=2=config FSL_FMAN\n--\ndrivers/net/ethernet/freescale/fman/Kconfig-5-\tselect GENERIC_ALLOCATOR\ndrivers/net/ethernet/freescale/fman/Kconfig:6:\tselect PHYLINK\ndrivers/net/ethernet/freescale/fman/Kconfig-7-\tselect PCS_LYNX\n--\ndrivers/net/ethernet/freescale/fs_enet/Kconfig=2=config FS_ENET\n--\ndrivers/net/ethernet/freescale/fs_enet/Kconfig-5-\tselect MII\ndrivers/net/ethernet/freescale/fs_enet/Kconfig:6:\tselect PHYLINK\ndrivers/net/ethernet/freescale/fs_enet/Kconfig-7-\n--\ndrivers/net/ethernet/marvell/Kconfig=59=config MVNETA\n--\ndrivers/net/ethernet/marvell/Kconfig-62-\tselect MVMDIO\ndrivers/net/ethernet/marvell/Kconfig:63:\tselect PHYLINK\ndrivers/net/ethernet/marvell/Kconfig-64-\tselect PAGE_POOL\n--\ndrivers/net/ethernet/marvell/Kconfig=86=config MVPP2\n--\ndrivers/net/ethernet/marvell/Kconfig-89-\tselect MVMDIO\ndrivers/net/ethernet/marvell/Kconfig:90:\tselect PHYLINK\ndrivers/net/ethernet/marvell/Kconfig-91-\tselect PAGE_POOL\n--\ndrivers/net/ethernet/marvell/prestera/Kconfig=6=config PRESTERA\n--\ndrivers/net/ethernet/marvell/prestera/Kconfig-10-\tselect NET_DEVLINK\ndrivers/net/ethernet/marvell/prestera/Kconfig:11:\tselect PHYLINK\ndrivers/net/ethernet/marvell/prestera/Kconfig-12-\thelp\n--\ndrivers/net/ethernet/mediatek/Kconfig=14=config NET_MEDIATEK_SOC\n--\ndrivers/net/ethernet/mediatek/Kconfig-17-\tselect PINCTRL\ndrivers/net/ethernet/mediatek/Kconfig:18:\tselect PHYLINK\ndrivers/net/ethernet/mediatek/Kconfig-19-\tselect DIMLIB\n--\ndrivers/net/ethernet/meta/Kconfig=20=config FBNIC\n--\ndrivers/net/ethernet/meta/Kconfig-29-\tselect PCS_XPCS\ndrivers/net/ethernet/meta/Kconfig:30:\tselect PHYLINK\ndrivers/net/ethernet/meta/Kconfig-31-\tselect PLDMFW\n--\ndrivers/net/ethernet/microchip/Kconfig=45=config LAN743X\n--\ndrivers/net/ethernet/microchip/Kconfig-51-\tselect CRC32\ndrivers/net/ethernet/microchip/Kconfig:52:\tselect PHYLINK\ndrivers/net/ethernet/microchip/Kconfig-53-\thelp\n--\ndrivers/net/ethernet/microchip/lan966x/Kconfig=1=config LAN966X_SWITCH\n--\ndrivers/net/ethernet/microchip/lan966x/Kconfig-7-\tdepends on BRIDGE || BRIDGE=n\ndrivers/net/ethernet/microchip/lan966x/Kconfig:8:\tselect PHYLINK\ndrivers/net/ethernet/microchip/lan966x/Kconfig-9-\tselect PAGE_POOL\n--\ndrivers/net/ethernet/microchip/sparx5/Kconfig=1=config SPARX5_SWITCH\n--\ndrivers/net/ethernet/microchip/sparx5/Kconfig-8-\tdepends on BRIDGE || BRIDGE=n\ndrivers/net/ethernet/microchip/sparx5/Kconfig:9:\tselect PHYLINK\ndrivers/net/ethernet/microchip/sparx5/Kconfig-10-\tselect PHY_SPARX5_SERDES\n--\ndrivers/net/ethernet/mscc/Kconfig=15=config MSCC_OCELOT_SWITCH_LIB\n--\ndrivers/net/ethernet/mscc/Kconfig-19-\tselect PACKING\ndrivers/net/ethernet/mscc/Kconfig:20:\tselect PHYLINK\ndrivers/net/ethernet/mscc/Kconfig-21-\ttristate\n--\ndrivers/net/ethernet/renesas/Kconfig=42=config RENESAS_ETHER_SWITCH\n--\ndrivers/net/ethernet/renesas/Kconfig-47-\tselect MII\ndrivers/net/ethernet/renesas/Kconfig:48:\tselect PHYLINK\ndrivers/net/ethernet/renesas/Kconfig-49-\tselect RENESAS_GEN4_PTP\n--\ndrivers/net/ethernet/stmicro/stmmac/Kconfig=2=config STMMAC_ETH\n--\ndrivers/net/ethernet/stmicro/stmmac/Kconfig-9-\tselect PAGE_POOL\ndrivers/net/ethernet/stmicro/stmmac/Kconfig:10:\tselect PHYLINK\ndrivers/net/ethernet/stmicro/stmmac/Kconfig-11-\tselect CRC32\n--\ndrivers/net/ethernet/tehuti/Kconfig=26=config TEHUTI_TN40\n--\ndrivers/net/ethernet/tehuti/Kconfig-30-\tselect FW_LOADER\ndrivers/net/ethernet/tehuti/Kconfig:31:\tselect PHYLINK\ndrivers/net/ethernet/tehuti/Kconfig-32-\thelp\n--\ndrivers/net/ethernet/ti/Kconfig=96=config TI_K3_AM65_CPSW_NUSS\n--\ndrivers/net/ethernet/ti/Kconfig-100-\tselect TI_DAVINCI_MDIO\ndrivers/net/ethernet/ti/Kconfig:101:\tselect PHYLINK\ndrivers/net/ethernet/ti/Kconfig-102-\tselect PAGE_POOL\n--\ndrivers/net/ethernet/wangxun/Kconfig=19=config LIBWX\n--\ndrivers/net/ethernet/wangxun/Kconfig-23-\tselect DIMLIB\ndrivers/net/ethernet/wangxun/Kconfig:24:\tselect PHYLINK\ndrivers/net/ethernet/wangxun/Kconfig-25-\thelp\n--\ndrivers/net/ethernet/xilinx/Kconfig=26=config XILINX_AXI_EMAC\n--\ndrivers/net/ethernet/xilinx/Kconfig-29-\tdepends on XILINX_DMA\ndrivers/net/ethernet/xilinx/Kconfig:30:\tselect PHYLINK\ndrivers/net/ethernet/xilinx/Kconfig-31-\tselect DIMLIB\n--\ndrivers/net/pcs/Kconfig=8=config PCS_XPCS\ndrivers/net/pcs/Kconfig-9-\ttristate \"Synopsys DesignWare Ethernet XPCS\"\ndrivers/net/pcs/Kconfig:10:\tselect PHYLINK\ndrivers/net/pcs/Kconfig-11-\thelp\n--\ndrivers/net/usb/Kconfig=113=config USB_LAN78XX\n--\ndrivers/net/usb/Kconfig-115-\tselect MII\ndrivers/net/usb/Kconfig:116:\tselect PHYLINK\ndrivers/net/usb/Kconfig-117-\tselect MICROCHIP_PHY\n--\ndrivers/net/usb/Kconfig=166=config USB_NET_AX8817X\n--\ndrivers/net/usb/Kconfig-169-\tselect CRC32\ndrivers/net/usb/Kconfig:170:\tselect PHYLINK\ndrivers/net/usb/Kconfig-171-\tselect AX88796B_PHY\n"
} |
| 19/2 | 2026/08/29 06:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 47679 output: 28 thoughts: 199 Thoughts: **Configuration Dependency Analysis** Okay, let's break this down. My immediate task is to understand the relationships between a few specific configurations. I'm focusing on `PHYLINK`, and then `MDIO_AIROHA_EN8811H`, `AIR_EN8811H_PHY`, and `AIR_NET_PHYLIB`. I already know `MDIO_AIROHA_EN8811H` depends on `OF_MDIO` and selects `AIR_NET_PHYLIB`, and `AIR_EN8811H_PHY` also selects `AIR_NET_PHYLIB`, but I want to be certain about the Kconfig implications. First up, let me examine the `MDIO_AIROHA_EN8811H` configuration within `drivers/net/mdio/Kconfig`. I need to confirm the dependency and selection are behaving as I anticipate. Ah, here it is: ```kconfig config MDIO_AIROHA_EN8811H tristate "Airoha EN8811H MDIO device support" depends on OF_MDIO select AIR_NET_PHYLIB help ... ``` Right, it's as expected. `MDIO_AIROHA_EN8811H` is tristate, depends on `OF_MDIO`, and crucially *selects* `AIR_NET_PHYLIB`. So, enabling this will, at a minimum, pull in `AIR_NET_PHYLIB`, assuming `OF_MDIO` is also enabled. That's a key piece of information. Now I have to look into `OF_MDIO` and its relationships, to see what else it might be selecting or depending on. |
| 20/2 | 2026/08/29 06:21 | tool | grepper |
0mArgs:{
"Expression": "config OF_MDIO",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/mdio/Kconfig=9=config FWNODE_MDIO\n--\ndrivers/net/mdio/Kconfig-13-\ndrivers/net/mdio/Kconfig:14:config OF_MDIO\ndrivers/net/mdio/Kconfig-15-\tdef_tristate OF\n"
} |
| 21/2 | 2026/08/29 06:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 48379 output: 35 thoughts: 28 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking PHY Configuration: A Necessary Precaution** Alright, before diving too deep, I need to make sure I understand the foundation of this network setup. Specifically, I need to verify how the `AIR_EN8811H_PHY` configuration option is handled. I'm going to quickly check its definition within the `drivers/net/phy/Kconfig` file. This is crucial; understanding the dependencies and implications of this config option will inform any modifications I might need to make down the line. It's a fundamental step to ensure the PHY is correctly enabled and configured within the driver framework. Better to have this foundational knowledge before troubleshooting any potential networking issues. It's a quick check, but a necessary one to set the stage. |
| 22/2 | 2026/08/29 06:21 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/Kconfig",
"FirstLine": 90,
"LineCount": 25
}Results: {
"Contents": " 90:\t\t AS21210PB1 that all register with the PHY ID 0x7500 0x7500\n 91:\t\t before the firmware is loaded.\n 92:\t\n 93:\tconfig AIR_AN8801_PHY\n 94:\t\ttristate \"Airoha AN8801 Gigabit PHY\"\n 95:\t\tselect AIR_NET_PHYLIB\n 96:\t\thelp\n 97:\t\t Currently supports the Airoha AN8801R PHY.\n 98:\t\n 99:\tconfig AIR_EN8811H_PHY\n 100:\t\ttristate \"Airoha EN8811H 2.5 Gigabit PHY\"\n 101:\t\tselect AIR_NET_PHYLIB\n 102:\t\tselect PHY_COMMON_PROPS\n 103:\t\thelp\n 104:\t\t Currently supports the Airoha EN8811H PHY.\n 105:\t\n 106:\tconfig AIR_NET_PHYLIB\n 107:\t\ttristate\n 108:\t\thelp\n 109:\t\t Airoha Ethernet PHY common library\n 110:\t\n 111:\tconfig AMD_PHY\n 112:\t\ttristate \"AMD and Altima PHYs\"\n 113:\t\thelp\n 114:\t\t Currently supports the AMD am79c874 and Altima AC101L.\n"
} |
| 23/2 | 2026/08/29 06:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 48999 output: 221 thoughts: 836 Thoughts: **My Analysis of the PHY Patch** Okay, let's break this down systematically. First, I need to ensure the necessary configurations are enabled. I see we require `PHYLINK`, `MDIO_AIROHA_EN8811H`, `AIR_EN8811H_PHY`, `OF_MDIO`, and `OF`. Checking `OF` is important as this is often used for device tree integration, so I'll keep that in mind. Next, I need to verify the symbol dependencies and ensure everything is where it's supposed to be. I'm seeing a deep dive into the code. Let me parse these symbols... In `drivers/net/phy/phylink.c`, it looks like we're working with core PHY link management: `phylink_slow_phy_poll`, `phylink_phy_is_usable`, `phylink_slow_phy_pending`, `phylink_attach_phy` which are static functions are internal. The key exported functions: `phylink_connect_phy`, `phylink_fwnode_phy_connect`, `phylink_disconnect_phy`, `phylink_ethtool_ksettings_get`, `phylink_ethtool_ksettings_set`, `phylink_create`, and `phylink_destroy` are the API entry points here, so I'll keep an eye on those. In `drivers/net/phy/air_phy_lib.c` we're digging into the Airoha library: `air_fw_write_buf`, `air_en8811h_mcu_running`, `air_en8811h_wait_mcu_ready`, and `air_en8811h_fw_download`. That last one is particularly interesting as it is exported, indicating firmware download functionality. In `drivers/net/phy/air_en8811h.c` it seems this is for specific device driver `en8811h_load_firmware`, `en8811h_wait_mcu_ready`, `en8811h_probe`. These are all static functions, likely internal to that specific driver. Finally, in `drivers/net/mdio/mdio-airoha-en8811h.c` we have `en8811h_mcu_probe` and `en8811h_mcu_fw_poll`. These look like the low-level MDIO interactions for the Airoha EN8811H. So, taking stock, I'll concentrate on the non-static / entry-point symbols. I'll want to focus on: `phylink_fwnode_phy_connect`, `phylink_disconnect_phy`, `phylink_ethtool_ksettings_get`, `phylink_ethtool_ksettings_set`, and `air_en8811h_fw_download`. These are the functions likely to be used by other parts of the system. Now, let's circle back to the 'set-results' requirements: * `WorthFuzzing`: Absolutely, this is significant. * `Reasoning`: The patch includes modifications to core PHY link management in `phylink.c`, along with support for "slow-to-probe" PHYs which suggests delayed work handling to improve driver robustness. There is state validation and the get/set interaction with ethtool ksettings, all of which are common fuzzing targets. We're also introducing MDIO support for the Airoha EN8811H PHY. These all suggest a good fuzzing target. * `FocusSymbols`: I'll define these as: `["phylink_fwnode_phy_connect", "phylink_disconnect_phy", "phylink_ethtool_ksettings_get", "phylink_ethtool_ksettings_set", "air_en8811h_fw_download"]` - these are the entry points to test. * `EnableConfigs`: I need to specify: `["PHYLINK", "MDIO_AIROHA_EN8811H", "AIR_EN8811H_PHY", "OF_MDIO"]` to make sure the fuzzing environment is set up correctly. Okay, with all that information distilled, it's time to call `set-results`. |
| 24/2 | 2026/08/29 06:21 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"PHYLINK",
"MDIO_AIROHA_EN8811H",
"AIR_EN8811H_PHY",
"OF_MDIO"
],
"FocusSymbols": [
"phylink_fwnode_phy_connect",
"phylink_disconnect_phy",
"phylink_ethtool_ksettings_get",
"phylink_ethtool_ksettings_set",
"air_en8811h_fw_download"
],
"Reasoning": "The patch modifies core networking phylink logic (phylink.c) by adding delayed-work polling and state handling for slow-to-probe PHYs, adjusting phylink connect/disconnect lifecycles, and altering ethtool ksettings get/set paths. It also adds the Airoha EN8811H MCU MDIO driver and refactors firmware download routines in the Airoha PHY library.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"PHYLINK",
"MDIO_AIROHA_EN8811H",
"AIR_EN8811H_PHY",
"OF_MDIO"
],
"FocusSymbols": [
"phylink_fwnode_phy_connect",
"phylink_disconnect_phy",
"phylink_ethtool_ksettings_get",
"phylink_ethtool_ksettings_set",
"air_en8811h_fw_download"
],
"Reasoning": "The patch modifies core networking phylink logic (phylink.c) by adding delayed-work polling and state handling for slow-to-probe PHYs, adjusting phylink connect/disconnect lifecycles, and altering ethtool ksettings get/set paths. It also adds the Airoha EN8811H MCU MDIO driver and refactors firmware download routines in the Airoha PHY library.",
"WorthFuzzing": true
} |
| 25/1 | 2026/08/29 06:21 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 26/2 | 2026/08/29 06:21 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series introduces an MDIO device driver for the Airoha EN8811H MCU (mdio-airoha-en8811h.c), refactors firmware loading routines into air_phy_lib.c, and implements a 'slow-to-probe' delayed-work polling mechanism in phylink.c.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Memory Allocations: All newly introduced heap allocations (such as struct en8811h_mcu) are allocated using zero-initializing allocators (devm_kzalloc).\n2. Stack Variables \u0026 Output Arguments: All local variables and output pointers (such as fw_version, assert_us, deassert_us) are explicitly initialized before use or assigned by register read helpers.\n3. User Space Interactions: In phylink_ethtool_ksettings_get(), when a slow PHY is pending, the link modes are explicitly zeroed (linkmode_zero) and fields are set to SPEED_UNKNOWN / DUPLEX_UNKNOWN. Moreover, the ethtool core always zeroes the struct ethtool_link_ksettings before invoking driver callbacks. No uninitialized memory or structure padding is copied to user space.\n4. Concurrency and Lifecycle: The risks in this patch series involve concurrency, delayed work cancellation, workqueue freezing, RTNL locking, and device reference counting (fwnode/phy_device). These potential issues (e.g., use-after-free, double-free, lock ordering/deadlocks) are detected by standard KASAN, LOCKDEP, and refcount tracking.\n\nBecause no uninitialized memory reads or information leaks are introduced or exposed, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit b9c708433b611814dd69a265f7453ac021c7fcef
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 29 06:19:57 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..b51a14a3b7b51
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml
@@ -0,0 +1,83 @@
+# 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 driven by an MD32 MCU that executes
+ firmware loaded over MDIO into volatile RAM. Until that firmware runs,
+ the chip answers its PHY ID but cannot bring up a link, and the
+ firmware files may live on a filesystem that is not yet mounted when
+ the MDIO bus is scanned.
+
+ This node describes the MCU as an MDIO device in its own right. The
+ driver downloads the firmware once the files become available (or
+ detects firmware already left running by the bootloader) and only
+ then registers the child MDIO bus, so the PHY node below never
+ becomes visible before the chip is able to serve it.
+
+ The reset line is owned by this node rather than by the PHY node:
+ the MD32 keeps its firmware only in volatile RAM, so the reset may
+ be asserted only while the MCU is not executing firmware. A PHY-node
+ reset would be pulsed by phy_detach() on every unbind and wipe the
+ running firmware.
+
+properties:
+ compatible:
+ const: airoha,en8811h-mcu
+
+ reg:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ reset-assert-us: true
+
+ reset-deassert-us: true
+
+ mdio:
+ $ref: mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ The child bus holding the PHY itself, at the same address the
+ chip answers on the parent bus.
+
+required:
+ - compatible
+ - reg
+ - mdio
+
+additionalProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-mcu@d {
+ compatible = "airoha,en8811h-mcu";
+ reg = <0xd>;
+ reset-gpios = <&pio 14 1>;
+ 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 8>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/net/ethernet-controller.yaml b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
index 1bafd687dcb18..ca82672b6cb38 100644
--- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml
+++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
@@ -113,6 +113,15 @@ properties:
description:
Specifies a reference to a node representing an IEEE 1588 PTP device.
+ slow-to-probe:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ The PHY referenced by phy-handle may not exist, or may not have a
+ usable driver, when this controller connects to it, because the
+ driver or firmware arrives later (for example from a filesystem
+ mounted after the controller probed). Instead of failing, keep
+ the port and connect the PHY once it becomes usable.
+
rx-fifo-depth:
$ref: /schemas/types.yaml#/definitions/uint32
description:
diff --git a/MAINTAINERS b/MAINTAINERS
index 460cb72688450..21d39049e692b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -759,6 +759,13 @@ S: Maintained
F: Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
F: drivers/net/ethernet/airoha/
+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
+
AIROHA PCIE PHY 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 a05229838cb45..d46bebd05cc81 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -29,6 +29,17 @@ 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
+ select AIR_NET_PHYLIB
+ 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, before letting the PHY be probed.
+
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..b28e116289e6f
--- /dev/null
+++ b/drivers/net/mdio/mdio-airoha-en8811h.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Airoha EN8811H MDIO device driver
+ *
+ * Until its firmware has been downloaded the EN8811H is not an Ethernet PHY,
+ * it is an MD32 microcontroller waiting in its bootloader. Describing it as a
+ * plain MDIO device lets the firmware be downloaded as soon as the files can
+ * be read - in practice, once the filesystem holding them has been mounted -
+ * and lets the PHY appear only after the chip is able to act as one.
+ *
+ * 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/property.h>
+#include <linux/workqueue.h>
+
+#include "../phy/air_phy_lib.h"
+
+/*
+ * Poll rather than defer probing: request_firmware_direct() has no usermode
+ * helper fallback, so a rootfs that is not mounted yet fails immediately and
+ * would keep the deferred-probe list spinning for the whole mount window.
+ *
+ * Never give up. The firmware can arrive arbitrarily late and still be worth
+ * waiting for - installing the firmware package on a running system is a
+ * normal thing to do - and a driver that had stopped looking would turn that
+ * into a needless reboot. Back off to a slow poll instead, and leave a single
+ * breadcrumb for the system that simply does not have the files.
+ */
+#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;
+ unsigned int poll_ms;
+ unsigned int waited_ms;
+ u32 fw_version;
+ bool warned;
+};
+
+static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_read(parent, addr, regnum);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_write(parent, addr, regnum, val);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_c45_read(parent, addr, devad, regnum);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ int ret;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ mutex_lock_nested(&parent->mdio_lock, MDIO_MUTEX_NESTED);
+ ret = __mdiobus_c45_write(parent, addr, devad, regnum, val);
+ mutex_unlock(&parent->mdio_lock);
+
+ return ret;
+}
+
+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)
+{
+ struct device *dev = &mcu->mdiodev->dev;
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ struct device_node *np;
+ struct mii_bus *bus;
+ int ret;
+
+ np = of_get_child_by_name(dev->of_node, "mdio");
+ if (!np) {
+ dev_err(dev, "no mdio node describing the PHY\n");
+ return -ENODEV;
+ }
+
+ bus = devm_mdiobus_alloc(dev);
+ 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 = devm_of_mdiobus_register(dev, bus, np);
+ of_node_put(np);
+
+ return ret;
+}
+
+static void en8811h_mcu_fw_poll(struct work_struct *work)
+{
+ struct en8811h_mcu *mcu = container_of(to_delayed_work(work),
+ struct en8811h_mcu, fw_poll);
+ struct device *dev = &mcu->mdiodev->dev;
+ int ret;
+
+ /* The chip enumerates as a C22 PHY; MMD access is indirect */
+ ret = air_en8811h_fw_download(mcu->mdiodev->bus, mcu->mdiodev->addr,
+ false, dev, &mcu->fw_version);
+ if (!ret) {
+ dev_dbg(dev, "firmware %08x running after %ums\n",
+ mcu->fw_version, mcu->waited_ms);
+
+ /* Unlike a missing firmware file, this does not resolve by
+ * itself, so unlike the poll there is no retry: surface it
+ * once and stop.
+ */
+ ret = en8811h_mcu_bus_register(mcu);
+ if (ret)
+ dev_err(dev, "failed to register the PHY's bus: %pe\n",
+ ERR_PTR(ret));
+ return;
+ }
+
+ mcu->waited_ms += mcu->poll_ms;
+ if (!mcu->warned && mcu->waited_ms >= EN8811H_FW_WARN_MS) {
+ /* Missing files resolve by themselves once installed; a bus
+ * or register error will not, and deserves its own message.
+ */
+ if (ret == -ENOENT)
+ dev_warn(dev, "still waiting for %s and %s\n",
+ EN8811H_MD32_DM, EN8811H_MD32_DSP);
+ else
+ dev_warn(dev, "firmware download keeps failing: %pe\n",
+ ERR_PTR(ret));
+ mcu->warned = true;
+ }
+
+ mcu->poll_ms = min(mcu->poll_ms * 2, EN8811H_FW_POLL_MAX_MS);
+ queue_delayed_work(system_freezable_wq, &mcu->fw_poll,
+ msecs_to_jiffies(mcu->poll_ms));
+}
+
+static int en8811h_mcu_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ struct en8811h_mcu *mcu;
+ struct device_node *np;
+ u32 deassert_us = 0;
+
+ mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
+ if (!mcu)
+ return -ENOMEM;
+
+ mcu->mdiodev = mdiodev;
+ mdiodev_set_drvdata(mdiodev, mcu);
+
+ /* The bus registration only needs this once the firmware runs, but
+ * a DT hole should fail the bind now, not as a work-item error a
+ * second after probe already returned success.
+ */
+ 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 only claims reset-gpios for devices flagged as PHYs
+ * (mdiobus_register_device()), so claim it here. Owning it at this
+ * level is the point: phy_detach() asserts the reset of the PHY it
+ * detaches, which would wipe firmware the MD32 holds in RAM.
+ */
+ 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");
+
+ /*
+ * Firmware left running by the bootloader, or by a previous bind,
+ * lives in volatile RAM: the reset line must not be touched then.
+ * Only a chip still in its bootloader gets the clean reset cycle.
+ */
+ if (air_en8811h_mcu_running(mdiodev->bus, mdiodev->addr, false)) {
+ dev_dbg(dev, "MD32 already running, adopting it\n");
+ } else if (mcu->reset_gpio) {
+ u32 assert_us = 0;
+
+ device_property_read_u32(dev, "reset-assert-us", &assert_us);
+ device_property_read_u32(dev, "reset-deassert-us",
+ &deassert_us);
+
+ gpiod_direction_output(mcu->reset_gpio, 1);
+ if (assert_us)
+ fsleep(assert_us);
+
+ gpiod_set_value_cansleep(mcu->reset_gpio, 0);
+ if (deassert_us)
+ fsleep(deassert_us);
+ }
+
+ mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+ INIT_DELAYED_WORK(&mcu->fw_poll, en8811h_mcu_fw_poll);
+ /* Freezable, so neither the file lookup nor the ~144KB MDIO
+ * download can land on a bus that is suspending. The download is
+ * long for a bound worker, but it runs once per firmware arrival.
+ */
+ 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);
+}
+
+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,
+ },
+};
+
+mdio_module_driver(en8811h_mcu_driver);
+
+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 edd49c193e476..fcc6e373edd69 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,80 +254,10 @@ 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;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
-
- if (saved_page >= 0) {
- ret = __air_write_buf(phydev, address, fw);
- if (ret < 0)
- phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
- address, ret);
- }
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
static int en8811h_wait_mcu_ready(struct phy_device *phydev)
{
- int ret, reg_value;
-
- ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
- if (ret)
- return ret;
-
- /* Because of mdio-lock, may have to wait for multiple loads */
- ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
- EN8811H_PHY_FW_STATUS, reg_value,
- reg_value == EN8811H_PHY_READY,
- 20000, 7500000, true);
- if (ret) {
- phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value);
- return -ENODEV;
- }
-
- return 0;
+ return air_en8811h_wait_mcu_ready(phydev->mdio.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev);
}
static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
@@ -405,7 +319,8 @@ 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.bus, phydev->mdio.addr, address,
+ fw);
release_firmware(fw);
return ret;
}
@@ -501,54 +416,12 @@ 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.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev,
+ &priv->firmware_version);
if (ret < 0)
phydev_err(phydev, "Load firmware failed: %d\n", ret);
@@ -1157,7 +1030,10 @@ static int en8811h_probe(struct phy_device *phydev)
if (ret < 0)
return ret;
- /* mcu has just restarted after firmware load */
+ /* Freshly downloaded firmware has just started; firmware adopted
+ * from the bootloader is already past its own start. Neither needs
+ * the restart a later resume would.
+ */
priv->mcu_needs_restart = false;
/* MDIO_DEVS1/2 empty, so set mmds_present bits here */
diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c
index 5141db19fa5ee..c1187f357f4c3 100644
--- a/drivers/net/phy/air_phy_lib.c
+++ b/drivers/net/phy/air_phy_lib.c
@@ -8,37 +8,43 @@
*/
#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"
+#include "phylib.h"
-static int __air_buckpbus_reg_read(struct phy_device *phydev,
+static int __air_buckpbus_reg_read(struct mii_bus *bus, int addr,
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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);
if (pbus_data_low < 0)
return pbus_data_low;
@@ -46,64 +52,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 mii_bus *bus, int addr,
u32 pbus_address, u32 pbus_data)
{
int ret;
- ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+ ret = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 mii_bus *bus, int addr,
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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_read(bus, addr, 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 = __mdiobus_read(bus, addr, AIR_BPBUS_RD_DATA_LOW);
if (pbus_data_low < 0)
return pbus_data_low;
@@ -112,23 +120,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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_LOW,
+ lower_16_bits(pbus_data_new));
if (ret < 0)
return ret;
@@ -144,7 +152,9 @@ 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.bus,
+ phydev->mdio.addr,
+ pbus_address, pbus_data);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -163,8 +173,9 @@ 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,
- pbus_data);
+ ret = __air_buckpbus_reg_write(phydev->mdio.bus,
+ phydev->mdio.addr,
+ pbus_address, pbus_data);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -183,8 +194,9 @@ 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.bus,
+ phydev->mdio.addr,
+ pbus_address, mask, set);
if (ret < 0)
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
pbus_address, ret);
@@ -194,6 +206,297 @@ 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 mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ unsigned int offset;
+ int ret;
+ u16 val;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_INCR);
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_ADDR_HIGH,
+ upper_16_bits(address));
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, 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 = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_HIGH, val);
+ if (ret < 0)
+ return ret;
+
+ val = get_unaligned_le16(&fw->data[offset]);
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_LOW, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* The phy_select_page() path is not usable here: these run before any
+ * phy_device exists. Callers hold the bus lock across select/op/restore.
+ */
+static int air_mdio_select_page(struct mii_bus *bus, int addr, int page)
+{
+ int saved_page, ret;
+
+ saved_page = __mdiobus_read(bus, addr, AIR_EXT_PAGE_ACCESS);
+ if (saved_page < 0)
+ return saved_page;
+
+ if (saved_page != page) {
+ ret = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS, page);
+ if (ret < 0)
+ return ret;
+ }
+
+ return saved_page;
+}
+
+static int air_mdio_restore_page(struct mii_bus *bus, int addr,
+ int saved_page, int page, int ret)
+{
+ int restore;
+
+ if (saved_page != page) {
+ restore = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS,
+ saved_page);
+ if (ret >= 0 && restore < 0)
+ ret = restore;
+ }
+
+ return ret;
+}
+
+int air_fw_write_buf(struct mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_write_buf(bus, addr, address, fw);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_fw_write_buf);
+
+static int air_mdio_buckpbus_reg_read(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 *pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_read(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_write(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_write(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_modify(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 mask, u32 set)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_modify(bus, addr, pbus_address,
+ mask, set);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mmd_status_read(struct mii_bus *bus, int addr, bool is_c45)
+{
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = mmd_phy_read(bus, addr, is_c45, MDIO_MMD_VEND1,
+ EN8811H_PHY_FW_STATUS);
+ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
+}
+
+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45)
+{
+ return air_mmd_status_read(bus, addr, is_c45) == EN8811H_PHY_READY;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);
+
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev)
+{
+ int ret, reg_value;
+
+ ret = air_mdio_buckpbus_reg_write(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_FINISH);
+ 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()
+ * would: the bus is not going to heal within the timeout.
+ */
+ ret = read_poll_timeout(air_mmd_status_read, reg_value,
+ reg_value < 0 ||
+ reg_value == EN8811H_PHY_READY,
+ 20000, 7500000, true, bus, addr, is_c45);
+ if (reg_value < 0)
+ return reg_value;
+ if (ret) {
+ dev_err(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 mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version)
+{
+ const struct firmware *fw1, *fw2;
+ int ret;
+
+ if (air_en8811h_mcu_running(bus, addr, is_c45)) {
+ /* Loaded by a bootloader, an earlier bind, or another
+ * device serving the chip. The wait below is what makes
+ * trusting the status register safe: a chip that was not
+ * in fact running fails there instead of coming up
+ * misprogrammed.
+ */
+ ret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);
+ if (ret < 0)
+ return ret;
+
+ ret = air_mdio_buckpbus_reg_read(bus, addr,
+ EN8811H_FW_VERSION,
+ fw_version);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "MD32 already running, firmware %08x\n",
+ *fw_version);
+ return 0;
+ }
+
+ 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(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_START);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING,
+ EN8811H_FW_CTRL_2_LOADING);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DM, fw1);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DSP, fw2);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING, 0);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_read(bus, addr, EN8811H_FW_VERSION,
+ fw_version);
+ 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 here: the callers retry or log on their own terms,
+ * and a poller retrying a half-installed firmware package would turn
+ * a print at this level into a permanent drumbeat.
+ */
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);
+
+MODULE_FIRMWARE(EN8811H_MD32_DM);
+MODULE_FIRMWARE(EN8811H_MD32_DSP);
+
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..8d9f24da12711 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -29,6 +29,23 @@
#define AIR_BPBUS_RD_DATA_HIGH 0x17
#define AIR_BPBUS_RD_DATA_LOW 0x18
+#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin"
+#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin"
+
+#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 +55,14 @@ 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 mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw);
+bool air_en8811h_mcu_running(struct mii_bus *bus, int addr, bool is_c45);
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev);
+int air_en8811h_fw_download(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version);
+
#endif /* __AIR_PHY_LIB_H */
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 5b8e956902fb5..6fbd1fa0be786 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -73,6 +73,24 @@ struct phylink {
struct phylink_link_state phy_state;
unsigned int phy_ib_mode;
struct work_struct resolve;
+ /* Set while waiting for a PHY that is expected to probe late.
+ * Written and read under rtnl only: phylink_disconnect_phy() puts
+ * and clears it there, which is what makes the poller's dereference
+ * safe.
+ */
+ struct fwnode_handle *slow_phy_fwnode;
+ u32 slow_phy_flags;
+ struct delayed_work slow_phy_poll;
+ /* Interval before the next poll. Set when the poller is armed and
+ * thereafter owned by it, since a delayed work never runs twice at
+ * once.
+ */
+ unsigned int slow_phy_poll_ms;
+ unsigned int slow_phy_waited_ms;
+ /* Written under rtnl, read unlocked by the poller; a stale read only
+ * costs an extra poll cycle.
+ */
+ bool slow_phy_stop;
unsigned int pcs_neg_mode;
unsigned int pcs_state;
@@ -1829,6 +1847,8 @@ int phylink_set_fixed_link(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+static void phylink_slow_phy_poll(struct work_struct *work);
+
/**
* phylink_create() - create a phylink instance
* @config: a pointer to the target &struct phylink_config
@@ -1867,6 +1887,7 @@ struct phylink *phylink_create(struct phylink_config *config,
mutex_init(&pl->phydev_mutex);
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
+ INIT_DELAYED_WORK(&pl->slow_phy_poll, phylink_slow_phy_poll);
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
@@ -1950,6 +1971,10 @@ void phylink_destroy(struct phylink *pl)
if (pl->link_gpio)
gpiod_put(pl->link_gpio);
+ WRITE_ONCE(pl->slow_phy_stop, true);
+ cancel_delayed_work_sync(&pl->slow_phy_poll);
+ fwnode_handle_put(pl->slow_phy_fwnode);
+
cancel_work_sync(&pl->resolve);
kfree(pl);
}
@@ -2201,10 +2226,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
}
static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
- phy_interface_t interface)
+ phy_interface_t interface, u32 flags)
{
- u32 flags = 0;
-
if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED))
return -EINVAL;
@@ -2242,7 +2265,7 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
pl->link_config.interface = pl->link_interface;
}
- ret = phylink_attach_phy(pl, phy, pl->link_interface);
+ ret = phylink_attach_phy(pl, phy, pl->link_interface, 0);
if (ret < 0)
return ret;
@@ -2254,6 +2277,135 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
}
EXPORT_SYMBOL_GPL(phylink_connect_phy);
+/* How often to re-check whether a late-probing PHY has turned up. */
+#define PHYLINK_SLOW_PHY_POLL_MS 1000
+#define PHYLINK_SLOW_PHY_WARN_MS 60000
+
+/* Ceiling that interval backs off to once a connect attempt has failed. */
+#define PHYLINK_SLOW_PHY_POLL_MAX_MS 30000
+
+/* True while a PHY declared slow-to-probe has not been connected yet. */
+static bool phylink_slow_phy_pending(struct phylink *pl)
+{
+ return pl->slow_phy_fwnode && !pl->phydev;
+}
+
+/* A phy-handle has resolved only once it names a device with its own driver
+ * bound. A device that is absent, or present with the generic driver bound to
+ * it, is equally unusable: this is the state slow-to-probe waits out, and both
+ * the arming site and the poller must agree on it.
+ *
+ * Bound is not probed. phy_probe() assigns phydev->drv before it calls the
+ * driver's own probe(), and only fills phydev->supported from get_features()
+ * once that has returned; a device connected in between is validated against
+ * an empty link-mode mask. PHY_DOWN is the documented state of a device whose
+ * probe has not completed, and phy_probe() leaves it in PHY_READY.
+ */
+static bool phylink_phy_is_usable(struct phy_device *phy_dev)
+{
+ return phy_dev && phy_dev->drv && phy_dev->state != PHY_DOWN &&
+ !phy_driver_is_genphy(phy_dev);
+}
+
+static void phylink_slow_phy_poll(struct work_struct *work)
+{
+ struct phylink *pl = container_of(to_delayed_work(work), struct phylink,
+ slow_phy_poll);
+ struct phy_device *phy_dev;
+ int ret;
+
+ if (READ_ONCE(pl->slow_phy_stop))
+ return;
+
+ /* phylink_disconnect_phy() cancels this work while holding rtnl, so
+ * blocking on rtnl here would deadlock against it. Requeue instead and
+ * let the canceller finish.
+ */
+ if (!rtnl_trylock())
+ goto requeue;
+
+ if (READ_ONCE(pl->slow_phy_stop)) {
+ rtnl_unlock();
+ return;
+ }
+
+ /* Do not "optimize" the lookup out from under rtnl: rtnl is what
+ * makes phylink_disconnect_phy()'s put-and-clear of slow_phy_fwnode
+ * safe against this dereference. The uncontended trylock above is
+ * cheaper than the use-after-free.
+ */
+ phy_dev = fwnode_phy_find_device(pl->slow_phy_fwnode);
+ if (!phylink_phy_is_usable(phy_dev)) {
+ if (phy_dev)
+ phy_device_free(phy_dev);
+
+ /* A PHY that never turns up would otherwise wait in complete
+ * silence: one breadcrumb after a minute, like the connect
+ * failure below gets, and like a missing firmware file gets
+ * from the MDIO side.
+ */
+ pl->slow_phy_waited_ms += pl->slow_phy_poll_ms;
+ if (pl->slow_phy_waited_ms >= PHYLINK_SLOW_PHY_WARN_MS &&
+ pl->slow_phy_waited_ms - pl->slow_phy_poll_ms <
+ PHYLINK_SLOW_PHY_WARN_MS)
+ phylink_warn(pl,
+ "still waiting for the slow-to-probe PHY (%pfw)\n",
+ pl->slow_phy_fwnode);
+ rtnl_unlock();
+ goto requeue;
+ }
+
+ if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
+ pl->link_interface = phy_dev->interface;
+ pl->link_config.interface = pl->link_interface;
+ }
+
+ ret = phylink_attach_phy(pl, phy_dev, pl->link_interface,
+ pl->slow_phy_flags);
+ phy_device_free(phy_dev);
+ if (!ret) {
+ ret = phylink_bringup_phy(pl, phy_dev,
+ pl->link_config.interface);
+ if (ret)
+ phy_detach(phy_dev);
+ else if (!test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ /* phylink_start() starts the PHY it finds attached,
+ * and ran long before this one turned up.
+ */
+ phy_start(phy_dev);
+ }
+ if (ret) {
+ /* Whether this is permanent cannot be read off the errno: the
+ * same -EINVAL comes out of phylink_validate_phy() for a PHY
+ * that is still filling in its link modes and for one that
+ * genuinely cannot speak this interface. What decides it is
+ * the PHY's supported mask, and only the driver binding again
+ * changes that, so keep polling - giving up here loses the
+ * port for the lifetime of this phylink. Back off instead: a
+ * failed bringup ends in phy_detach(), which asserts the PHY's
+ * reset line. Report the first failure only, so a PHY that
+ * will never fit costs one line rather than a stream of them.
+ */
+ if (pl->slow_phy_poll_ms == PHYLINK_SLOW_PHY_POLL_MS)
+ phylink_err(pl, "failed to connect late PHY: %pe\n",
+ ERR_PTR(ret));
+
+ WRITE_ONCE(pl->slow_phy_poll_ms,
+ min_t(unsigned int, pl->slow_phy_poll_ms * 2,
+ PHYLINK_SLOW_PHY_POLL_MAX_MS));
+ }
+ rtnl_unlock();
+
+ if (!ret)
+ return;
+
+requeue:
+ queue_delayed_work(system_freezable_power_efficient_wq,
+ &pl->slow_phy_poll,
+ msecs_to_jiffies(READ_ONCE(pl->slow_phy_poll_ms)));
+}
+
/**
* phylink_of_phy_connect() - connect the PHY specified in the DT mode.
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2282,7 +2434,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
* Connect the phy specified @fwnode to the phylink instance specified
* by @pl.
*
- * Returns 0 on success or a negative errno.
+ * If the port node carries the slow-to-probe property and the PHY is not
+ * usable yet, 0 is returned with no PHY connected: a poller connects it
+ * once its driver has probed. Until then the MAC runs without a PHY and
+ * ethtool reports no link modes.
+ *
+ * Returns 0 on success - the PHY connected, or the deferred connect
+ * armed - or a negative errno.
*/
int phylink_fwnode_phy_connect(struct phylink *pl,
const struct fwnode_handle *fwnode,
@@ -2304,6 +2462,32 @@ int phylink_fwnode_phy_connect(struct phylink *pl,
}
phy_dev = fwnode_phy_find_device(phy_fwnode);
+ if (!phylink_phy_is_usable(phy_dev) &&
+ fwnode_property_present(fwnode, "slow-to-probe")) {
+ /* The PHY is known to appear late, so keep the node and poll
+ * for it rather than failing the port for good. Returning an
+ * error here would also send DSA off to look for the PHY on
+ * the switch's own MDIO bus.
+ */
+ if (phy_dev)
+ phy_device_free(phy_dev);
+
+ pl->slow_phy_fwnode = phy_fwnode;
+ pl->slow_phy_flags = flags;
+ WRITE_ONCE(pl->slow_phy_poll_ms, PHYLINK_SLOW_PHY_POLL_MS);
+ pl->slow_phy_waited_ms = 0;
+ WRITE_ONCE(pl->slow_phy_stop, false);
+ /* mod_ rather than queue_: a poll that a disconnect cancelled
+ * mid-backoff may still be pending with seconds left on it.
+ */
+ /* Freezable: attaching and starting a PHY has no place in
+ * the middle of a system suspend transition.
+ */
+ mod_delayed_work(system_freezable_power_efficient_wq,
+ &pl->slow_phy_poll, 0);
+ return 0;
+ }
+
/* We're done with the phy_node handle */
fwnode_handle_put(phy_fwnode);
if (!phy_dev)
@@ -2345,6 +2529,18 @@ void phylink_disconnect_phy(struct phylink *pl)
ASSERT_RTNL();
+ /* Cannot cancel synchronously: the poller takes rtnl, which is held. */
+ WRITE_ONCE(pl->slow_phy_stop, true);
+ cancel_delayed_work(&pl->slow_phy_poll);
+ /* The next connect re-arms from its own fwnode lookup; holding on to
+ * this one would leak a reference per connect cycle and keep the
+ * pending state - and its empty-ksettings window - alive on an
+ * interface whose PHY is long since usable. The poller cannot race
+ * this: it reads the node only under rtnl, after the stop check.
+ */
+ fwnode_handle_put(pl->slow_phy_fwnode);
+ pl->slow_phy_fwnode = NULL;
+
mutex_lock(&pl->phydev_mutex);
phy = pl->phydev;
if (phy) {
@@ -2916,6 +3112,17 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
else
kset->base.port = pl->link_port;
+ /* Until the PHY arrives the port can do nothing, so report no link
+ * modes at all rather than the MAC's own capabilities, which is what
+ * an empty SFP cage reports.
+ */
+ if (phylink_slow_phy_pending(pl)) {
+ linkmode_zero(kset->link_modes.supported);
+ kset->base.speed = SPEED_UNKNOWN;
+ kset->base.duplex = DUPLEX_UNKNOWN;
+ return 0;
+ }
+
linkmode_copy(kset->link_modes.supported, pl->supported);
switch (pl->act_link_an_mode) {
@@ -2983,6 +3190,12 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
ASSERT_RTNL();
+ /* Without a PHY and without an SFP bus this would configure the MAC
+ * on its own, for a link that cannot come up yet.
+ */
+ if (phylink_slow_phy_pending(pl))
+ return -EINVAL;
+
if (pl->phydev) {
struct ethtool_link_ksettings phy_kset = *kset;
@@ -3730,7 +3943,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
/* Attach the PHY so that the PHY is present when we do the major
* configuration step.
*/
- ret = phylink_attach_phy(pl, phy, config.interface);
+ ret = phylink_attach_phy(pl, phy, config.interface, 0);
if (ret < 0)
return ret;
|
| 27/3 | 2026/08/29 06:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22781 output: 34 thoughts: 1175 Thoughts: **KMSAN Fuzzing Justification Analysis** Alright, let's break this down. My goal is to determine if this patch warrants spawning a dedicated KMSAN fuzzing session, in addition to the usual KASAN checks. I need to be methodical and think like a KMSAN detector. I need to ask myself whether the patch introduces or modifies code in ways that might expose uninitialized memory reads or information leaks to userspace. First, I need to keep the ruleset in mind: *I will set NeedsKMSAN=true ONLY if* the changes introduce or modify kernel structures passed to userspace, or conditional logic depending on uninitialized variables, or allocations with uninitialized fields, or bounds checks that expose uninitialized buffer bytes. Otherwise, if it's mostly about out-of-bounds, use-after-free, locking bugs, etc., *I will set NeedsKMSAN=false*. Now, let's review the diff. We have DT binding documentation updates for `airoha,en8811h-mcu` and `ethernet-controller`. Those are unlikely to trigger anything. There's a `MAINTAINERS` update (irrelevant) and build configuration changes. Again, these are not going to trigger anything. The core of the patch is the new driver for the Airoha EN8811H MCU MDIO device and the refactoring of firmware download logic. It allocates a `struct en8811h_mcu` with `devm_kzalloc`, which should zero out the allocated memory and is good practice. This is promising. The `air_en8811h_fw_download()` function stands out as something to look at, which reads values into `fw_version`. This is passed into the function from the calling function. I will keep an eye out for how `fw_version` is initialized. The refactored `air_phy_lib.c` and `air_phy_lib.h` files contain MDIO register operations and firmware upload routines. I need to scan these to see if any local variables are passed to callers. The `phylink.c` changes introduce support for "slow-to-probe" PHY devices. The critical part here is the `phylink_ethtool_ksettings_get` function. This function *could* be a vector. The function reads `kset->link_modes.supported`, `kset->base.speed` and `kset->base.duplex`. The function checks `phylink_slow_phy_pending(pl)`, and if true, it calls `linkmode_zero()` on `kset->link_modes.supported` and sets `kset->base.speed` and `kset->base.duplex` to `SPEED_UNKNOWN` and `DUPLEX_UNKNOWN` respectively. This looks good *if* that `kset` is completely initialized, which it *should* be given the function that calls it. I need to trace this function and understand if it passes potentially uninitialized values to the calling function. I will scan the code with a code searcher and grepper if need be. My conclusion at this stage is that it is *highly unlikely* that this patch warrants a dedicated KMSAN fuzzing session. The `devm_kzalloc` call and thorough initialization of structures within the new driver and refactoring are positive signs. The usage of variables in the `air_phy_lib` routines need to be validated. The `phylink_ethtool_ksettings_get` code looks clean at the moment. However, I need to do a full call trace and understand how the `kset` is passed and handled, to be certain. |
| 28/3 | 2026/08/29 06:21 | tool | grepper |
0mArgs:{
"Expression": "phylink_ethtool_ksettings_get",
"PathPrefix": "drivers/net"
}Results: {
"Output": "drivers/net/ethernet/altera/altera_tse_ethtool.c=232=static int tse_ethtool_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/altera/altera_tse_ethtool.c-236-\ndrivers/net/ethernet/altera/altera_tse_ethtool.c:237:\treturn phylink_ethtool_ksettings_get(priv-\u003ephylink, cmd);\ndrivers/net/ethernet/altera/altera_tse_ethtool.c-238-}\n--\ndrivers/net/ethernet/atheros/ag71xx.c=464=static int ag71xx_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/atheros/ag71xx.c-468-\ndrivers/net/ethernet/atheros/ag71xx.c:469:\treturn phylink_ethtool_ksettings_get(ag-\u003ephylink, kset);\ndrivers/net/ethernet/atheros/ag71xx.c-470-}\n--\ndrivers/net/ethernet/cadence/macb_main.c=3651=static int macb_get_link_ksettings(struct net_device *netdev,\n--\ndrivers/net/ethernet/cadence/macb_main.c-3655-\ndrivers/net/ethernet/cadence/macb_main.c:3656:\treturn phylink_ethtool_ksettings_get(bp-\u003ephylink, kset);\ndrivers/net/ethernet/cadence/macb_main.c-3657-}\n--\ndrivers/net/ethernet/cadence/macb_main.c=4292=static int macb_taprio_setup_replace(struct net_device *netdev,\n--\ndrivers/net/ethernet/cadence/macb_main.c-4319-\t/* Get the current link speed */\ndrivers/net/ethernet/cadence/macb_main.c:4320:\terr = phylink_ethtool_ksettings_get(bp-\u003ephylink, \u0026kset);\ndrivers/net/ethernet/cadence/macb_main.c-4321-\tif (unlikely(err)) {\n--\ndrivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c=56=static int dpaa_get_link_ksettings(struct net_device *net_dev,\n--\ndrivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c-61-\ndrivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c:62:\treturn phylink_ethtool_ksettings_get(mac_dev-\u003ephylink, cmd);\ndrivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c-63-}\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c=101=dpaa2_eth_get_link_ksettings(struct net_device *net_dev,\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c-109-\tif (dpaa2_eth_is_type_phy(priv)) {\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c:110:\t\terr = phylink_ethtool_ksettings_get(priv-\u003emac-\u003ephylink,\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c-111-\t\t\t\t\t\t link_settings);\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c=58=dpaa2_switch_get_link_ksettings(struct net_device *netdev,\n--\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c-67-\tif (dpaa2_switch_port_is_type_phy(port_priv)) {\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c:68:\t\terr = phylink_ethtool_ksettings_get(port_priv-\u003emac-\u003ephylink,\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c-69-\t\t\t\t\t\t link_ksettings);\n--\ndrivers/net/ethernet/freescale/enetc/enetc_ethtool.c=1266=static int enetc_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/freescale/enetc/enetc_ethtool.c-1273-\ndrivers/net/ethernet/freescale/enetc/enetc_ethtool.c:1274:\treturn phylink_ethtool_ksettings_get(priv-\u003ephylink, cmd);\ndrivers/net/ethernet/freescale/enetc/enetc_ethtool.c-1275-}\n--\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c=804=static int fs_ethtool_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c-808-\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c:809:\treturn phylink_ethtool_ksettings_get(fep-\u003ephylink, cmd);\ndrivers/net/ethernet/freescale/fs_enet/fs_enet-main.c-810-}\n--\ndrivers/net/ethernet/freescale/ucc_geth_ethtool.c=104=uec_get_ksettings(struct net_device *netdev, struct ethtool_link_ksettings *cmd)\n--\ndrivers/net/ethernet/freescale/ucc_geth_ethtool.c-107-\ndrivers/net/ethernet/freescale/ucc_geth_ethtool.c:108:\treturn phylink_ethtool_ksettings_get(ugeth-\u003ephylink, cmd);\ndrivers/net/ethernet/freescale/ucc_geth_ethtool.c-109-}\n--\ndrivers/net/ethernet/marvell/mvneta.c=4710=mvneta_ethtool_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/marvell/mvneta.c-4714-\ndrivers/net/ethernet/marvell/mvneta.c:4715:\treturn phylink_ethtool_ksettings_get(pp-\u003ephylink, cmd);\ndrivers/net/ethernet/marvell/mvneta.c-4716-}\n--\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c=5574=static int mvpp2_ethtool_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c-5581-\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c:5582:\treturn phylink_ethtool_ksettings_get(port-\u003ephylink, cmd);\ndrivers/net/ethernet/marvell/mvpp2/mvpp2_main.c-5583-}\n--\ndrivers/net/ethernet/marvell/prestera/prestera_ethtool.c=513=prestera_ethtool_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/marvell/prestera/prestera_ethtool.c-524-\tif (port-\u003ephy_link)\ndrivers/net/ethernet/marvell/prestera/prestera_ethtool.c:525:\t\treturn phylink_ethtool_ksettings_get(port-\u003ephy_link, ecmd);\ndrivers/net/ethernet/marvell/prestera/prestera_ethtool.c-526-\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c=4537=static int mtk_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-4544-\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c:4545:\treturn phylink_ethtool_ksettings_get(mac-\u003ephylink, cmd);\ndrivers/net/ethernet/mediatek/mtk_eth_soc.c-4546-}\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_ethtool.c=2016=static const struct ethtool_ops fbnic_ethtool_ops = {\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_ethtool.c-2062-\t.set_tunable\t\t\t= fbnic_set_tunable,\ndrivers/net/ethernet/meta/fbnic/fbnic_ethtool.c:2063:\t.get_link_ksettings\t\t= fbnic_phylink_ethtool_ksettings_get,\ndrivers/net/ethernet/meta/fbnic/fbnic_ethtool.c-2064-\t.get_fec_stats\t\t\t= fbnic_get_fec_stats,\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h=107=int fbnic_phylink_set_pauseparam(struct net_device *netdev,\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h-108-\t\t\t\t struct ethtool_pauseparam *pause);\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h:109:int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,\ndrivers/net/ethernet/meta/fbnic/fbnic_netdev.h-110-\t\t\t\t\tstruct ethtool_link_ksettings *cmd);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c=45=fbnic_phylink_get_supported_fec_modes(unsigned long *supported)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-63-\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:64:int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-65-\t\t\t\t\tstruct ethtool_link_ksettings *cmd)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-69-\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:70:\terr = phylink_ethtool_ksettings_get(fbn-\u003ephylink, cmd);\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-71-\tif (!err) {\n--\ndrivers/net/ethernet/microchip/lan743x_ethtool.c=1099=lan743x_ethtool_get_link_ksettings(struct net_device *netdev,\n--\ndrivers/net/ethernet/microchip/lan743x_ethtool.c-1103-\ndrivers/net/ethernet/microchip/lan743x_ethtool.c:1104:\treturn phylink_ethtool_ksettings_get(adapter-\u003ephylink, cmd);\ndrivers/net/ethernet/microchip/lan743x_ethtool.c-1105-}\n--\ndrivers/net/ethernet/microchip/lan966x/lan966x_ethtool.c=508=static int lan966x_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/microchip/lan966x/lan966x_ethtool.c-512-\ndrivers/net/ethernet/microchip/lan966x/lan966x_ethtool.c:513:\treturn phylink_ethtool_ksettings_get(port-\u003ephylink, cmd);\ndrivers/net/ethernet/microchip/lan966x/lan966x_ethtool.c-514-}\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_ethtool.c=1143=static int sparx5_get_link_settings(struct net_device *ndev,\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_ethtool.c-1147-\ndrivers/net/ethernet/microchip/sparx5/sparx5_ethtool.c:1148:\treturn phylink_ethtool_ksettings_get(port-\u003ephylink, cmd);\ndrivers/net/ethernet/microchip/sparx5/sparx5_ethtool.c-1149-}\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c=320=static int stmmac_ethtool_get_link_ksettings(struct net_device *dev,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c-324-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c:325:\treturn phylink_ethtool_ksettings_get(priv-\u003ephylink, cmd);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c-326-}\n--\ndrivers/net/ethernet/tehuti/tn40.c=1576=static int tn40_ethtool_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/tehuti/tn40.c-1580-\ndrivers/net/ethernet/tehuti/tn40.c:1581:\treturn phylink_ethtool_ksettings_get(priv-\u003ephylink, cmd);\ndrivers/net/ethernet/tehuti/tn40.c-1582-}\n--\ndrivers/net/ethernet/ti/am65-cpsw-ethtool.c=496=static int am65_cpsw_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/ti/am65-cpsw-ethtool.c-500-\ndrivers/net/ethernet/ti/am65-cpsw-ethtool.c:501:\treturn phylink_ethtool_ksettings_get(salve-\u003ephylink, ecmd);\ndrivers/net/ethernet/ti/am65-cpsw-ethtool.c-502-}\n--\ndrivers/net/ethernet/wangxun/libwx/wx_ethtool.c=249=int wx_get_link_ksettings(struct net_device *netdev,\n--\ndrivers/net/ethernet/wangxun/libwx/wx_ethtool.c-253-\ndrivers/net/ethernet/wangxun/libwx/wx_ethtool.c:254:\treturn phylink_ethtool_ksettings_get(wx-\u003ephylink, cmd);\ndrivers/net/ethernet/wangxun/libwx/wx_ethtool.c-255-}\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c=2282=axienet_ethtools_get_link_ksettings(struct net_device *ndev,\n--\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-2286-\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c:2287:\treturn phylink_ethtool_ksettings_get(lp-\u003ephylink, cmd);\ndrivers/net/ethernet/xilinx/xilinx_axienet_main.c-2288-}\n--\ndrivers/net/phy/phylink.c=3080=static void phylink_get_ksettings(const struct phylink_link_state *state,\n--\ndrivers/net/phy/phylink.c-3094-/**\ndrivers/net/phy/phylink.c:3095: * phylink_ethtool_ksettings_get() - get the current link settings\ndrivers/net/phy/phylink.c-3096- * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\n--\ndrivers/net/phy/phylink.c-3102- */\ndrivers/net/phy/phylink.c:3103:int phylink_ethtool_ksettings_get(struct phylink *pl,\ndrivers/net/phy/phylink.c-3104-\t\t\t\t struct ethtool_link_ksettings *kset)\n--\ndrivers/net/phy/phylink.c-3156-}\ndrivers/net/phy/phylink.c:3157:EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);\ndrivers/net/phy/phylink.c-3158-\n--\ndrivers/net/usb/lan78xx.c=1853=static int lan78xx_get_link_ksettings(struct net_device *net,\n--\ndrivers/net/usb/lan78xx.c-1857-\ndrivers/net/usb/lan78xx.c:1858:\treturn phylink_ethtool_ksettings_get(dev-\u003ephylink, cmd);\ndrivers/net/usb/lan78xx.c-1859-}\n"
} |
| 29/3 | 2026/08/29 06:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 29338 output: 39 thoughts: 30 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into phylink_ethtool_ksettings_get()** Okay, time to dig into the `phylink_ethtool_ksettings_get()` function. I know this lives in `drivers/net/phy/phylink.c`, so that's where I'll start. It's on my radar because I'm looking at how we're handling ethtool settings within the phylink framework, and this function name screams "that's where the action is" for fetching the kernel settings. I expect it handles retrieving the current link settings, perhaps translating them from the internal phylink representation to something ethtool can understand and report back. The "ksettings" part suggests it's getting *kernel* configuration, so I'll be looking for how it interacts with the underlying PHY and any associated device properties. I need to understand this function's logic to see if there are any potential gotchas or areas for optimization in how we expose PHY configurations to the userspace via ethtool. Let's see how it gets it done. |
| 30/3 | 2026/08/29 06:21 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/phylink.c",
"FirstLine": 3090,
"LineCount": 60
}Results: {
"Contents": "3090:\t\t\t\t\t\t state-\u003eadvertising) ?\n3091:\t\t\t\t\tAUTONEG_ENABLE : AUTONEG_DISABLE;\n3092:\t}\n3093:\t\n3094:\t/**\n3095:\t * phylink_ethtool_ksettings_get() - get the current link settings\n3096:\t * @pl: a pointer to a \u0026struct phylink returned from phylink_create()\n3097:\t * @kset: a pointer to a \u0026struct ethtool_link_ksettings to hold link settings\n3098:\t *\n3099:\t * Read the current link settings for the phylink instance specified by @pl.\n3100:\t * This will be the link settings read from the MAC, PHY or fixed link\n3101:\t * settings depending on the current negotiation mode.\n3102:\t */\n3103:\tint phylink_ethtool_ksettings_get(struct phylink *pl,\n3104:\t\t\t\t\t struct ethtool_link_ksettings *kset)\n3105:\t{\n3106:\t\tstruct phylink_link_state link_state;\n3107:\t\n3108:\t\tASSERT_RTNL();\n3109:\t\n3110:\t\tif (pl-\u003ephydev)\n3111:\t\t\tphy_ethtool_ksettings_get(pl-\u003ephydev, kset);\n3112:\t\telse\n3113:\t\t\tkset-\u003ebase.port = pl-\u003elink_port;\n3114:\t\n3115:\t\t/* Until the PHY arrives the port can do nothing, so report no link\n3116:\t\t * modes at all rather than the MAC's own capabilities, which is what\n3117:\t\t * an empty SFP cage reports.\n3118:\t\t */\n3119:\t\tif (phylink_slow_phy_pending(pl)) {\n3120:\t\t\tlinkmode_zero(kset-\u003elink_modes.supported);\n3121:\t\t\tkset-\u003ebase.speed = SPEED_UNKNOWN;\n3122:\t\t\tkset-\u003ebase.duplex = DUPLEX_UNKNOWN;\n3123:\t\t\treturn 0;\n3124:\t\t}\n3125:\t\n3126:\t\tlinkmode_copy(kset-\u003elink_modes.supported, pl-\u003esupported);\n3127:\t\n3128:\t\tswitch (pl-\u003eact_link_an_mode) {\n3129:\t\tcase MLO_AN_FIXED:\n3130:\t\t\t/* We are using fixed settings. Report these as the\n3131:\t\t\t * current link settings - and note that these also\n3132:\t\t\t * represent the supported speeds/duplex/pause modes.\n3133:\t\t\t */\n3134:\t\t\tphylink_get_fixed_state(pl, \u0026link_state);\n3135:\t\t\tphylink_get_ksettings(\u0026link_state, kset);\n3136:\t\t\tbreak;\n3137:\t\n3138:\t\tcase MLO_AN_INBAND:\n3139:\t\t\t/* If there is a phy attached, then use the reported\n3140:\t\t\t * settings from the phy with no modification.\n3141:\t\t\t */\n3142:\t\t\tif (pl-\u003ephydev)\n3143:\t\t\t\tbreak;\n3144:\t\n3145:\t\t\tphylink_mac_pcs_get_state(pl, \u0026link_state);\n3146:\t\n3147:\t\t\t/* The MAC is reporting the link results from its own PCS\n3148:\t\t\t * layer via in-band status. Report these as the current\n3149:\t\t\t * link settings.\n"
} |
| 31/3 | 2026/08/29 06:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 30591 output: 31 thoughts: 31 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Diving into ethtool_get_link_ksettings Implementation** Alright, time to get my hands dirty and understand how `ethtool_get_link_ksettings` is actually working. I want to trace its path, specifically from the user-space perspective, and understand how it interacts with the ethtool core through the netlink or ioctl interfaces. I'm thinking I'll need to dissect the code to follow the requests as they are sent, handled by the kernel, and the data is eventually returned to the application. I need to understand what information is being exchanged, what specific kernel structures are being populated or queried, and what potential error conditions are being handled along the way. I'm keen to figure out the efficiency of this call and how it may be impacting overall performance. Let's see if there are any interesting optimizations or potential bottlenecks that I can identify, maybe look for possible race conditions too. I should also check to see if there are any vendor-specific extensions that are being utilized here. |
| 32/3 | 2026/08/29 06:21 | tool | grepper |
0mArgs:{
"Expression": "get_link_ksettings",
"PathPrefix": "net/ethtool"
}Results: {
"Output": "net/ethtool/ioctl.c=429=struct ethtool_link_usettings {\n--\nnet/ethtool/ioctl.c-438-/* Internal kernel helper to query a device ethtool_link_settings. */\nnet/ethtool/ioctl.c:439:int netif_get_link_ksettings(struct net_device *dev,\nnet/ethtool/ioctl.c-440-\t\t\t struct ethtool_link_ksettings *link_ksettings)\n--\nnet/ethtool/ioctl.c-443-\nnet/ethtool/ioctl.c:444:\tif (!dev-\u003eethtool_ops-\u003eget_link_ksettings)\nnet/ethtool/ioctl.c-445-\t\treturn -EOPNOTSUPP;\n--\nnet/ethtool/ioctl.c-450-\tmemset(link_ksettings, 0, sizeof(*link_ksettings));\nnet/ethtool/ioctl.c:451:\treturn dev-\u003eethtool_ops-\u003eget_link_ksettings(dev, link_ksettings);\nnet/ethtool/ioctl.c-452-}\nnet/ethtool/ioctl.c:453:EXPORT_SYMBOL(netif_get_link_ksettings);\nnet/ethtool/ioctl.c-454-\nnet/ethtool/ioctl.c-455-/* Convenience helper for callers that hold only rtnl_lock(). */\nnet/ethtool/ioctl.c:456:int __ethtool_get_link_ksettings(struct net_device *dev,\nnet/ethtool/ioctl.c-457-\t\t\t\t struct ethtool_link_ksettings *link_ksettings)\n--\nnet/ethtool/ioctl.c-463-\tnetdev_lock_ops(dev);\nnet/ethtool/ioctl.c:464:\tret = netif_get_link_ksettings(dev, link_ksettings);\nnet/ethtool/ioctl.c-465-\tnetdev_unlock_ops(dev);\n--\nnet/ethtool/ioctl.c-467-}\nnet/ethtool/ioctl.c:468:EXPORT_SYMBOL(__ethtool_get_link_ksettings);\nnet/ethtool/ioctl.c-469-\n--\nnet/ethtool/ioctl.c=518=store_link_ksettings_for_user(void __user *to,\n--\nnet/ethtool/ioctl.c-540-/* Query device for its ethtool_link_settings. */\nnet/ethtool/ioctl.c:541:static int ethtool_get_link_ksettings(struct net_device *dev,\nnet/ethtool/ioctl.c-542-\t\t\t\t void __user *useraddr)\n--\nnet/ethtool/ioctl.c-547-\tnetdev_assert_locked_ops_compat(dev);\nnet/ethtool/ioctl.c:548:\tif (!dev-\u003eethtool_ops-\u003eget_link_ksettings)\nnet/ethtool/ioctl.c-549-\t\treturn -EOPNOTSUPP;\n--\nnet/ethtool/ioctl.c-581-\tmemset(\u0026link_ksettings, 0, sizeof(link_ksettings));\nnet/ethtool/ioctl.c:582:\terr = dev-\u003eethtool_ops-\u003eget_link_ksettings(dev, \u0026link_ksettings);\nnet/ethtool/ioctl.c-583-\tif (err \u003c 0)\n--\nnet/ethtool/ioctl.c=661=EXPORT_SYMBOL(ethtool_virtdev_set_link_ksettings);\n--\nnet/ethtool/ioctl.c-665- * Backward compatibility note: for compatibility with legacy ethtool, this is\nnet/ethtool/ioctl.c:666: * now implemented via get_link_ksettings. When driver reports higher link mode\nnet/ethtool/ioctl.c-667- * bits, a kernel warning is logged once (with name of 1st driver/device) to\n--\nnet/ethtool/ioctl.c=672=static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)\n--\nnet/ethtool/ioctl.c-678-\tnetdev_assert_locked_ops_compat(dev);\nnet/ethtool/ioctl.c:679:\tif (!dev-\u003eethtool_ops-\u003eget_link_ksettings)\nnet/ethtool/ioctl.c-680-\t\treturn -EOPNOTSUPP;\n--\nnet/ethtool/ioctl.c-685-\tmemset(\u0026link_ksettings, 0, sizeof(link_ksettings));\nnet/ethtool/ioctl.c:686:\terr = dev-\u003eethtool_ops-\u003eget_link_ksettings(dev, \u0026link_ksettings);\nnet/ethtool/ioctl.c-687-\tif (err \u003c 0)\n--\nnet/ethtool/ioctl.c=3272=dev_ethtool_locked(struct net *net, struct net_device *dev,\n--\nnet/ethtool/ioctl.c-3542-\tcase ETHTOOL_GLINKSETTINGS:\nnet/ethtool/ioctl.c:3543:\t\trc = ethtool_get_link_ksettings(dev, useraddr);\nnet/ethtool/ioctl.c-3544-\t\tbreak;\n--\nnet/ethtool/linkinfo.c=24=static int linkinfo_prepare_data(const struct ethnl_req_info *req_base,\n--\nnet/ethtool/linkinfo.c-36-\t\treturn ret;\nnet/ethtool/linkinfo.c:37:\tret = netif_get_link_ksettings(dev, \u0026data-\u003eksettings);\nnet/ethtool/linkinfo.c-38-\tif (ret \u003c 0)\n--\nnet/ethtool/linkinfo.c=87=ethnl_set_linkinfo_validate(struct ethnl_req_info *req_info,\n--\nnet/ethtool/linkinfo.c-91-\nnet/ethtool/linkinfo.c:92:\tif (!ops-\u003eget_link_ksettings || !ops-\u003eset_link_ksettings)\nnet/ethtool/linkinfo.c-93-\t\treturn -EOPNOTSUPP;\n--\nnet/ethtool/linkinfo.c=98=ethnl_set_linkinfo(struct ethnl_req_info *req_info, struct genl_info *info)\n--\nnet/ethtool/linkinfo.c-106-\nnet/ethtool/linkinfo.c:107:\tret = netif_get_link_ksettings(dev, \u0026ksettings);\nnet/ethtool/linkinfo.c-108-\tif (ret \u003c 0) {\n--\nnet/ethtool/linkmodes.c=28=static int linkmodes_prepare_data(const struct ethnl_req_info *req_base,\n--\nnet/ethtool/linkmodes.c-41-\nnet/ethtool/linkmodes.c:42:\tret = netif_get_link_ksettings(dev, \u0026data-\u003eksettings);\nnet/ethtool/linkmodes.c-43-\tif (ret \u003c 0) {\n--\nnet/ethtool/linkmodes.c=303=ethnl_set_linkmodes_validate(struct ethnl_req_info *req_info,\n--\nnet/ethtool/linkmodes.c-312-\nnet/ethtool/linkmodes.c:313:\tif (!ops-\u003eget_link_ksettings || !ops-\u003eset_link_ksettings)\nnet/ethtool/linkmodes.c-314-\t\treturn -EOPNOTSUPP;\n--\nnet/ethtool/linkmodes.c=319=ethnl_set_linkmodes(struct ethnl_req_info *req_info, struct genl_info *info)\n--\nnet/ethtool/linkmodes.c-326-\nnet/ethtool/linkmodes.c:327:\tret = netif_get_link_ksettings(dev, \u0026ksettings);\nnet/ethtool/linkmodes.c-328-\tif (ret \u003c 0) {\n"
} |
| 33/3 | 2026/08/29 06:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 32990 output: 391 thoughts: 1084 Thoughts: **KMSAN Applicability Assessment** Okay, let's break this down. My initial concern was about potential memory safety issues, specifically regarding uninitialized memory reads. The code initializes `link_ksettings` with `memset`, and `ethtool` similarly initializes its structures, which is good practice. The patch includes a few new files and modifications to existing ones, related to MDIO drivers, PHY drivers, and phylink. After a thorough review, I've confirmed that there are no obvious instances of uninitialized stack variables copied to userspace, conditional branches on uninitialized variables, uninitialized heap buffers, or bounds checks that could lead to reading uninitialized memory. There's also no indication of potential information leaks to user space through uninitialized memory. The initialization patterns are consistent and safe. However, just because there aren't *obvious* uninitialized memory reads doesn't mean the patch is completely free of memory-related concerns. There are other categories of bugs: * **Race Conditions/Synchronization/Lock Ordering:** This is the most likely area for subtle issues. The code deals with MDIO bus access, workqueues, delayed work, and PHY connections. We have to consider whether there are potential race conditions, incorrect lock ordering, or synchronization problems, especially around `rtnl_trylock`, delayed work and the PHY disconnect/destroy path, or interactions with MDIO mutexes. LOCKDEP and KASAN would be invaluable for this. * **Memory Leaks/Reference Counting:** There are operations involving `fwnode_handle` and `phy_device`. Double-checking reference counting and freeing logic would be important. KASAN and KMEMLEAK would catch memory leaks and reference counting errors. * **Use-After-Free/Double Free:** Similarly, incorrect reference counting or logic errors can lead to use-after-free or double free bugs. KASAN would be the appropriate tool here. * **NULL Pointer Dereferences:** These are always a risk, particularly when dealing with pointers from different sources or functions. Standard kernel crash analysis and KASAN would be helpful in identifying them. None of these potential issues directly relate to KMSAN's primary purpose, which is to detect *uninitialized* memory reads. While KMSAN is excellent for detecting *some* memory safety violations, KASAN is better suited to catch these. Thus, I'm confident in concluding that `NeedsKMSAN` should be `false`. The patch seems well-behaved in terms of initialization, but other memory safety tools should be used for further analysis. |
| 34/3 | 2026/08/29 06:22 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces an MDIO device driver for the Airoha EN8811H MCU (mdio-airoha-en8811h.c), refactors firmware loading routines into air_phy_lib.c, and implements a 'slow-to-probe' delayed-work polling mechanism in phylink.c.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Memory Allocations: All newly introduced heap allocations (such as struct en8811h_mcu) are allocated using zero-initializing allocators (devm_kzalloc).\n2. Stack Variables \u0026 Output Arguments: All local variables and output pointers (such as fw_version, assert_us, deassert_us) are explicitly initialized before use or assigned by register read helpers.\n3. User Space Interactions: In phylink_ethtool_ksettings_get(), when a slow PHY is pending, the link modes are explicitly zeroed (linkmode_zero) and fields are set to SPEED_UNKNOWN / DUPLEX_UNKNOWN. Moreover, the ethtool core always zeroes the struct ethtool_link_ksettings before invoking driver callbacks. No uninitialized memory or structure padding is copied to user space.\n4. Concurrency and Lifecycle: The risks in this patch series involve concurrency, delayed work cancellation, workqueue freezing, RTNL locking, and device reference counting (fwnode/phy_device). These potential issues (e.g., use-after-free, double-free, lock ordering/deadlocks) are detected by standard KASAN, LOCKDEP, and refcount tracking.\n\nBecause no uninitialized memory reads or information leaks are introduced or exposed, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces an MDIO device driver for the Airoha EN8811H MCU (mdio-airoha-en8811h.c), refactors firmware loading routines into air_phy_lib.c, and implements a 'slow-to-probe' delayed-work polling mechanism in phylink.c.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Memory Allocations: All newly introduced heap allocations (such as struct en8811h_mcu) are allocated using zero-initializing allocators (devm_kzalloc).\n2. Stack Variables \u0026 Output Arguments: All local variables and output pointers (such as fw_version, assert_us, deassert_us) are explicitly initialized before use or assigned by register read helpers.\n3. User Space Interactions: In phylink_ethtool_ksettings_get(), when a slow PHY is pending, the link modes are explicitly zeroed (linkmode_zero) and fields are set to SPEED_UNKNOWN / DUPLEX_UNKNOWN. Moreover, the ethtool core always zeroes the struct ethtool_link_ksettings before invoking driver callbacks. No uninitialized memory or structure padding is copied to user space.\n4. Concurrency and Lifecycle: The risks in this patch series involve concurrency, delayed work cancellation, workqueue freezing, RTNL locking, and device reference counting (fwnode/phy_device). These potential issues (e.g., use-after-free, double-free, lock ordering/deadlocks) are detected by standard KASAN, LOCKDEP, and refcount tracking.\n\nBecause no uninitialized memory reads or information leaks are introduced or exposed, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|