AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series introduces firmware flashing, MCUboot rescue mode detection/recovery, and devlink flash update support for MaxLinear MxL862xx DSA Ethernet switches. It also adds a DSA devlink flash update dispatch helper in net/dsa/devlink.c and a deferred device reprobe helper (device_schedule_reprobe) in the driver core. The modified driver requires physical MaxLinear MxL862xx switch hardware on an MDIO bus, which is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE), and the generic devlink/driver-core helpers are only exercised by this driver.",
  "WorthFuzzing": false
}

1/1 2026/09/07 23:29 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d15203d65c8d310d843fbc4052292690ec607c64\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Sep 7 23:29:53 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst\nindex d4a83fdcff7fe..145cb8a4becfb 100644\n--- a/Documentation/networking/devlink/index.rst\n+++ b/Documentation/networking/devlink/index.rst\n@@ -95,6 +95,7 @@ parameters, info versions, and other features it supports.\n    mlx5\n    mlxsw\n    mv88e6xxx\n+   mxl862xx\n    netdevsim\n    nfp\n    octeontx2\ndiff --git a/Documentation/networking/devlink/mxl862xx.rst b/Documentation/networking/devlink/mxl862xx.rst\nnew file mode 100644\nindex 0000000000000..5b24d8cb57bc9\n--- /dev/null\n+++ b/Documentation/networking/devlink/mxl862xx.rst\n@@ -0,0 +1,74 @@\n+.. SPDX-License-Identifier: GPL-2.0\n+\n+========================\n+mxl862xx devlink support\n+========================\n+\n+This document describes the devlink features implemented by the\n+``mxl862xx`` device driver.\n+\n+Info versions\n+=============\n+\n+The ``mxl862xx`` driver reports the following versions\n+\n+.. list-table:: devlink info versions implemented\n+   :widths: 5 5 5 85\n+\n+   * - Name\n+     - Type\n+     - Example\n+     - Description\n+   * - ``asic.id``\n+     - fixed\n+     - 8628\n+     - The chip part number read from the CHIP ID registers. Omitted\n+       when the part number reads as zero, which happens for a switch\n+       sitting in MCUboot rescue mode (the registers need a running\n+       firmware), for an unfused part, and after a failed flash.\n+   * - ``asic.rev``\n+     - fixed\n+     - 0\n+     - The chip version read from the same register word, so it is\n+       omitted whenever ``asic.id`` is.\n+   * - ``fw``\n+     - running, stored\n+     - 1.0.70\n+     - Version of the firmware running on the switch, reported as both\n+       running and stored since the switch boots it from its own flash.\n+       It is omitted while no firmware version is known: after a failed\n+       flash, and in MCUboot rescue mode while an interrupted download\n+       is still being recovered in the background. Once the loader is\n+       ready to accept a new image the version appears as \"0.0.0\",\n+       which no released firmware reports, so version-comparing tools\n+       offer any available release as an upgrade. Use ``devlink dev\n+       flash`` to tell a recovering switch from a ready one, see below;\n+       a missing version on its own does not say why.\n+\n+Flash update\n+============\n+\n+The ``mxl862xx`` driver implements support for ``devlink dev flash``.\n+The signed firmware image is transferred to the switch over the same\n+MDIO bus which is also used to manage the switch, then verified and\n+installed by the MCUboot bootloader running on the switch. All ports\n+of the switch are closed for the duration of the update and the driver\n+reprobes the switch after it has rebooted into the new firmware. A\n+complete flash and reprobe cycle takes about one minute.\n+\n+A switch stuck in MCUboot rescue mode, e.g. after an interrupted\n+update, is registered without user ports. If the previous download was\n+interrupted mid-transfer the loader is wedged; the driver drains it\n+back to a clean ready state in the background, one byte at a time,\n+which takes tens of minutes for a large image and is reported through\n+the kernel log as it progresses. During that recovery ``devlink dev\n+flash`` returns ``-EBUSY`` with an extack message saying so, and\n+``devlink dev info`` reports no firmware version. Once the loader is\n+ready the firmware version appears and flashing a firmware image\n+through the regular update flow recovers the switch.\n+\n+If the recovery fails, the loader needs a power cycle: ``devlink dev\n+flash`` then returns ``-EIO`` and says so in its extack message. The\n+driver only re-examines the switch when it binds, so on a board where\n+the switch can be power cycled on its own, unbind and rebind the driver\n+afterwards to have the recovered switch recognised.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex b23fb6f2f4ef4..c97e710dda0c8 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -16246,6 +16246,7 @@ M:\tDaniel Golle \u003cdaniel@makrotopia.org\u003e\n L:\tnetdev@vger.kernel.org\n S:\tMaintained\n F:\tDocumentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml\n+F:\tDocumentation/networking/devlink/mxl862xx.rst\n F:\tdrivers/net/dsa/mxl862xx/\n F:\tnet/dsa/tag_mxl862xx.c\n \ndiff --git a/drivers/base/base.h b/drivers/base/base.h\nindex a5b7abc10ff02..6234e37de7e99 100644\n--- a/drivers/base/base.h\n+++ b/drivers/base/base.h\n@@ -106,6 +106,10 @@ struct driver_private {\n  * @dead: This device is currently either in the process of or has been\n  *\t  removed from the system. Any asynchronous events scheduled for this\n  *\t  device should exit without taking any action.\n+ * @shutdown_done: Set once device_shutdown() has reached this device, under\n+ *\t  the device lock, before any shutdown callback runs. Read under the\n+ *\t  device lock. A deferred re-probe scheduled with\n+ *\t  device_schedule_reprobe() must not detach the device anymore.\n  *\n  * Nothing outside of the driver core should ever touch these fields.\n  */\n@@ -120,6 +124,7 @@ struct device_private {\n \tchar *deferred_probe_reason;\n \tstruct device *device;\n \tu8 dead:1;\n+\tu8 shutdown_done:1;\n };\n #define to_device_private_parent(obj)\t\\\n \tcontainer_of(obj, struct device_private, knode_parent)\ndiff --git a/drivers/base/core.c b/drivers/base/core.c\nindex 4c0c373998a19..3dcd8a3c3aa55 100644\n--- a/drivers/base/core.c\n+++ b/drivers/base/core.c\n@@ -4910,6 +4910,9 @@ void device_shutdown(void)\n \t\t\tdevice_lock(parent);\n \t\tdevice_lock(dev);\n \n+\t\tif (dev-\u003ep)\n+\t\t\tdev-\u003ep-\u003eshutdown_done = true;\n+\n \t\t/* Don't allow any more runtime suspends */\n \t\tpm_runtime_get_noresume(dev);\n \t\tpm_runtime_barrier(dev);\ndiff --git a/drivers/base/dd.c b/drivers/base/dd.c\nindex f6525a7ee8c54..81a3433f726e8 100644\n--- a/drivers/base/dd.c\n+++ b/drivers/base/dd.c\n@@ -1315,7 +1315,7 @@ EXPORT_SYMBOL_GPL(driver_attach);\n  * __device_release_driver() must be called with @dev lock held.\n  * When called for a USB interface, @dev-\u003eparent lock must be held as well.\n  */\n-static void __device_release_driver(struct device *dev, struct device *parent)\n+static bool __device_release_driver(struct device *dev, struct device *parent)\n {\n \tstruct device_driver *drv;\n \n@@ -1336,7 +1336,7 @@ static void __device_release_driver(struct device *dev, struct device *parent)\n \t\t\t */\n \t\t\tif (dev-\u003edriver != drv) {\n \t\t\t\tpm_runtime_put(dev);\n-\t\t\t\treturn;\n+\t\t\t\treturn false;\n \t\t\t}\n \t\t}\n \n@@ -1359,7 +1359,10 @@ static void __device_release_driver(struct device *dev, struct device *parent)\n \n \t\tbus_notify(dev, BUS_NOTIFY_UNBOUND_DRIVER);\n \t\tkobject_uevent(\u0026dev-\u003ekobj, KOBJ_UNBIND);\n+\t\treturn true;\n \t}\n+\n+\treturn false;\n }\n \n void device_release_driver_internal(struct device *dev,\n@@ -1436,3 +1439,116 @@ void driver_detach(const struct device_driver *drv)\n \t\tput_device(dev);\n \t}\n }\n+\n+struct device_reprobe {\n+\tstruct delayed_work work;\n+\tstruct device *dev;\n+\tstruct device *parent;\n+\tconst struct device_driver *drv;\n+\tbool parent_lock;\n+};\n+\n+static void device_reprobe_work_fn(struct work_struct *work)\n+{\n+\tstruct device_reprobe *rp = container_of(work, struct device_reprobe,\n+\t\t\t\t\t\t work.work);\n+\tstruct device *parent = rp-\u003eparent;\n+\tstruct device *dev = rp-\u003edev;\n+\tbool detached = false;\n+\tint ret;\n+\n+\t/*\n+\t * A device unregistered before the work runs may have outlived the\n+\t * module providing its bus type, so dev-\u003ebus is not read until the\n+\t * device is known to be live.\n+\t */\n+\tif (rp-\u003eparent_lock)\n+\t\tdevice_lock(parent);\n+\tdevice_lock(dev);\n+\t/*\n+\t * rp-\u003edrv is only ever compared, never dereferenced: the driver it\n+\t * points to may have been unregistered and freed by now.\n+\t */\n+\tif (!dev-\u003ep-\u003edead \u0026\u0026 !dev-\u003ep-\u003eshutdown_done \u0026\u0026 dev-\u003edriver == rp-\u003edrv)\n+\t\tdetached = __device_release_driver(dev, parent);\n+\tdevice_unlock(dev);\n+\tif (rp-\u003eparent_lock)\n+\t\tdevice_unlock(parent);\n+\n+\tif (detached) {\n+\t\t/*\n+\t\t * device_attach() must run with the parent locked on buses\n+\t\t * that require it, mirroring bus_rescan_devices_helper().\n+\t\t */\n+\t\tif (rp-\u003eparent_lock)\n+\t\t\tdevice_lock(parent);\n+\t\tret = device_attach(dev);\n+\t\tif (ret \u003c 0)\n+\t\t\tdev_err_probe(dev, ret,\n+\t\t\t\t      \"re-probe failed, device left unbound\\n\");\n+\t\tif (rp-\u003eparent_lock)\n+\t\t\tdevice_unlock(parent);\n+\t}\n+\n+\tput_device(dev);\n+\tput_device(parent);\n+\tkfree(rp);\n+}\n+\n+/**\n+ * device_schedule_reprobe - schedule a deferred detach and re-probe\n+ * @dev: device to detach and re-probe\n+ * @delay_ms: delay in milliseconds before the re-probe runs\n+ *\n+ * Schedule a detach and re-probe of @dev after @delay_ms milliseconds.\n+ * The re-probe is skipped if, by the time the scheduled work runs, the\n+ * device has been removed, the system shutdown sequence has reached the\n+ * device, or @dev is no longer bound to the driver that was bound at\n+ * scheduling time. In particular an administrative unbind is never\n+ * undone by a stale re-probe.\n+ *\n+ * The work function is built-in text, so the bound driver may call this\n+ * from its own code without holding a module reference. If the driver\n+ * module is unloaded before the work runs, driver unregistration unbinds\n+ * @dev first and the scheduled work does nothing.\n+ *\n+ * Multiple pending re-probes for the same device are individually safe;\n+ * a caller that wants at most one pending re-probe must gate scheduling\n+ * itself.\n+ *\n+ * The work is freezable: a re-probe pending across system suspend runs\n+ * once the system has resumed.\n+ *\n+ * May only be called from process context.\n+ *\n+ * Returns: 0 on success, -EINVAL if @dev is not a registered device\n+ * bound to a driver, -ENOMEM on allocation failure.\n+ */\n+int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)\n+{\n+\tstruct device_reprobe *rp;\n+\n+\tif (!dev-\u003ebus || !dev-\u003ep || !device_is_registered(dev))\n+\t\treturn -EINVAL;\n+\tif (!dev-\u003edriver)\n+\t\treturn -EINVAL;\n+\n+\trp = kzalloc_obj(*rp);\n+\tif (!rp)\n+\t\treturn -ENOMEM;\n+\n+\trp-\u003edev = get_device(dev);\n+\t/*\n+\t * Pin the parent too: the work locks it, and an unregister of @dev\n+\t * would otherwise drop the last reference before the work runs.\n+\t */\n+\trp-\u003eparent = get_device(dev-\u003eparent);\n+\trp-\u003eparent_lock = dev-\u003eparent \u0026\u0026 dev-\u003ebus-\u003eneed_parent_lock;\n+\trp-\u003edrv = READ_ONCE(dev-\u003edriver);\n+\tINIT_DELAYED_WORK(\u0026rp-\u003ework, device_reprobe_work_fn);\n+\tqueue_delayed_work(system_freezable_wq, \u0026rp-\u003ework,\n+\t\t\t   msecs_to_jiffies(delay_ms));\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(device_schedule_reprobe);\ndiff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile\nindex a7be0e6669dfa..bccac0d0f703f 100644\n--- a/drivers/net/dsa/mxl862xx/Makefile\n+++ b/drivers/net/dsa/mxl862xx/Makefile\n@@ -1,3 +1,3 @@\n # SPDX-License-Identifier: GPL-2.0\n obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o\n-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o\n+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h\nindex a180a5decffc0..6f771895984cb 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h\n@@ -1224,6 +1224,16 @@ struct mxl862xx_sys_fw_image_version {\n \t__le32 iv_build_num;\n } __packed;\n \n+/**\n+ * struct mxl862xx_sys_reg_rw - System register read/write\n+ * @addr: 32-bit register address\n+ * @val: register value\n+ */\n+struct mxl862xx_sys_reg_rw {\n+\t__le32 addr;\n+\t__le32 val;\n+} __packed;\n+\n /**\n  * enum mxl862xx_port_type - Port Type\n  * @MXL862XX_LOGICAL_PORT: Logical Port\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\nindex c87a955c13c48..a865425aa61e1 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n@@ -70,7 +70,9 @@\n #define INT_GPHY_READ\t\t\t(GPY_GPY2XX_MAGIC + 0x1)\n #define INT_GPHY_WRITE\t\t\t(GPY_GPY2XX_MAGIC + 0x2)\n \n+#define SYS_MISC_FW_UPDATE\t\t(SYS_MISC_MAGIC + 0x1)\n #define SYS_MISC_FW_VERSION\t\t(SYS_MISC_MAGIC + 0x2)\n+#define SYS_MISC_REG_RD\t\t\t(SYS_MISC_MAGIC + 0x8)\n \n #define MXL862XX_XPCS_PCS_CONFIG\t(MXL862XX_XPCS_MAGIC + 0x1)\n #define MXL862XX_XPCS_PCS_GET_STATE\t(MXL862XX_XPCS_MAGIC + 0x2)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\nnew file mode 100644\nindex 0000000000000..042a4eee6af81\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\n@@ -0,0 +1,1062 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Firmware flash and devlink support for MaxLinear MxL862xx\n+ *\n+ * Copyright (C) 2025 Daniel Golle \u003cdaniel@makrotopia.org\u003e\n+ *\n+ * SB PDI - firmware download interface over clause-22 SMDIO\n+ * =========================================================\n+ *\n+ * The MxL862xx MCUboot loader accepts a firmware image through four \"SB PDI\"\n+ * registers in the switch SMDIO register space. It runs whenever no WSP\n+ * firmware is active: the normal firmware update enters it deliberately - the\n+ * SYS_MISC_FW_UPDATE API command sets a sticky rescue bit and reboots into\n+ * MCUboot - and the loader also stays here when the stored WSP firmware fails\n+ * its boot-time integrity check. This driver drives the loader's 0xc55c\n+ * \"console\" download path.\n+ *\n+ * SMDIO register access (mxl862xx_smdio_read/write):\n+ *   MII reg 0x1f := (\u003csb_pdi_reg\u003e \u0026 0xfff0)   ; page latch\n+ *   MII reg (\u003csb_pdi_reg\u003e \u0026 0x000f) := / =\u003e \u003cu16 data\u003e\n+ * so CTRL/ADDR/DATA/STAT (0xe100..0xe103) are MII regs 0/1/2/3 of page\n+ * 0xe100, not all reg 0x00.\n+ *\n+ * SB PDI registers (host name/addr  -\u003e  MCU mailbox):\n+ *   CTRL 0xe100 -\u003e 0xc0938400   mode: RST=0x00  RD=0x01  WR=0x02\n+ *   ADDR 0xe101 -\u003e 0xc0938404   SB target word address (SB1 bank = 0x7800)\n+ *   DATA 0xe102 -\u003e 0xc0938408   16-bit data / reply word\n+ *   STAT 0xe103 -\u003e 0xc093840c   handshake: a magic (below) or a byte count\n+ *\n+ * STAT magics:\n+ *   READY  0xc55c   loader idle in the console loop        (this driver)\n+ *   DL_RDY 0xc33c   loader idle in the flashless loop\n+ *   START  0xf48f   host   -\u003e begin download session\n+ *   ACK    0xf490   loader -\u003e START acknowledged (START + 1)\n+ *   END    0x3cc3   host   -\u003e finalise now (optional, see below)\n+ *   RDREG  0xe2c0   host   -\u003e register-read command (| index), see below\n+ *\n+ * Console flash path (STAT=0xc55c) - mxl862xx_flash_firmware():\n+ *\n+ *   host                                   loader\n+ *   ----                                   ------\n+ *   reset (CTRL=ADDR=DATA=0)\n+ *   read STAT ............................ 0xc55c   (READY, idle)\n+ *   STAT := START(0xf48f)  --------------\u003e\n+ *                          \u003c-------------- STAT = 0xf490 (ACK)\n+ *   CTRL := WR\n+ *   DATA := hdr[0..9]  (20-byte header: type,size1,crc1,size2,crc2)\n+ *   reset; STAT := 20 (header len)  -----\u003e parse hdr; r_remain=size1+size2;\n+ *                                          ERASE target region(s)\n+ *                          \u003c-------------- STAT=21 (len+1), then STAT=0\n+ *                                          (erased)\n+ *   -- payload, streamed in slices: --\n+ *   CTRL := WR\n+ *   DATA := word x N ...\n+ *     at word 16384: CTRL:=RST; ADDR:=0x7800; CTRL:=WR  (half-bank -\u003e SB1)\n+ *     at word 32760: flush slice:\n+ *        reset; STAT := \u003cbytes_this_slice\u003e ---\u003e r_remain -= bytes; program\n+ *                          \u003c------------------- STAT=0  (ready for next slice)\n+ *   ... repeat until the whole payload is sent ...\n+ *                          \u003c------------------- STAT=0  image verified\n+ *                                               (STAT=1: image rejected)\n+ *   STAT := END(0x3cc3)  ---------------------\u003e finalise and boot\n+ *\n+ * The r_remain == 0 rule (critical):\n+ *   Every host STAT write in the payload phase is a byte count; the loader\n+ *   does r_remain -= count and stays in the receive loop while r_remain != 0.\n+ *   It leaves the loop ONLY when r_remain hits EXACTLY 0, and a count larger\n+ *   than r_remain underflows the 32-bit counter and wedges the loader until a\n+ *   power cycle. Having left it, the loader verifies the image, publishes the\n+ *   verdict in STAT (0 good, 1 rejected) and waits 2 s for END before\n+ *   finalising regardless -- clearing its rescue-enable bit so boot_go boots\n+ *   the new image -- so END only saves that wait. Hence:\n+ *     - never send a slice/chunk count larger than what is outstanding;\n+ *     - a STAT write is a command only once the loader has left the loop;\n+ *     - the loader leaves the count in STAT while it programs the chunk, so\n+ *       a lingering count does not distinguish \"busy\" from \"verdict\";\n+ *     - interrupted-download recovery feeds 1 byte at a time (see below).\n+ *\n+ * Interrupted-flash recovery (mxl862xx_rescue_drain):\n+ *   A host that dies mid-payload leaves the loader in the receive loop holding\n+ *   STAT=0. Feed single 1-byte chunks (one DATA word + STAT=1) until r_remain\n+ *   reaches 0; the loader then verifies the (now corrupt) image, publishes its\n+ *   verdict and comes back to READY by itself. END is never sent here: while\n+ *   r_remain is non-zero it would be consumed as a 15555-byte count, and a\n+ *   lingering STAT=1 cannot be told from a chunk still being programmed.\n+ *\n+ * Register-read challenge (non-destructive liveness proof):\n+ *   DATA := 0x7c23 (marker); STAT := 0xe2c0|idx\n+ *     -\u003e loader returns a runtime word in DATA and re-arms STAT=0xc55c.\n+ *   The reply source is loader BSS, not a chip id; used only to prove a live\n+ *   mailbox in mxl862xx_rescue_mode_detect().\n+ *\n+ * The other STAT ready magic, 0xc33c, marks the loader's flashless\n+ * chip-to-chip download mode (MxL86281S 16-port tier); this driver does not\n+ * use it.\n+ *\n+ * Rescue lifecycle (devlink): probe runs mxl862xx_rescue_mode_detect(); a\n+ * wedged loader is drained back to READY by a background self-heal\n+ * (rescue_heal_work), so the long recovery never holds the devlink lock.\n+ * devlink dev info exposes the fw version (the \"flashable\" signal) only once at\n+ * READY; flash_update returns -EBUSY until then, and reprobes to WSP firmware\n+ * on success.\n+ *\n+ * Notes:\n+ *   - Chip id/revision (0xc0d28884/88) are NOT reachable on this channel; they\n+ *     need the clause-45 MMD firmware mailbox, which is dead under MCUboot.\n+ *     Rescue identity is by SB PDI behaviour only (mxl862xx_rescue_mode_detect).\n+ *   - The SMDIO PHY address comes from the device tree; the 0xe1xx register\n+ *     offsets are the OTP reset defaults and the only layout supported here.\n+ */\n+\n+#include \u003clinux/crc32.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/overflow.h\u003e\n+#include \u003clinux/rtnetlink.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003cnet/dsa.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+\n+#include \"mxl862xx.h\"\n+#include \"mxl862xx-api.h\"\n+#include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n+#include \"mxl862xx-host.h\"\n+\n+/* SB PDI registers (clause-22 SMDIO address space) */\n+#define MXL862XX_SB_PDI_CTRL\t\t0xe100\n+#define MXL862XX_SB_PDI_ADDR\t\t0xe101\n+#define MXL862XX_SB_PDI_DATA\t\t0xe102\n+#define MXL862XX_SB_PDI_STAT\t\t0xe103\n+\n+/* SB PDI CTRL modes */\n+#define MXL862XX_SB_PDI_CTRL_RST\t0x00\n+#define MXL862XX_SB_PDI_CTRL_WR\t\t0x02\n+\n+/* SB PDI handshake magic (published/consumed via STAT) */\n+#define MXL862XX_SB_PDI_READY\t\t0xc55c\t/* loader idle, console loop */\n+#define MXL862XX_SB_PDI_DL_READY\t0xc33c\t/* loader idle, flashless loop */\n+#define MXL862XX_SB_PDI_START\t\t0xf48f\n+#define MXL862XX_SB_PDI_END\t\t0x3cc3\n+#define MXL862XX_SB_PDI_RDREG\t\t0xe2c0\t/* register-read cmd (| index) */\n+#define MXL862XX_SB_PDI_RDREG_MARK\t0x7c23\t/* marker placed in DATA for RDREG */\n+\n+/* Behavioural presence probe: two distinct 16-bit latches on ADDR/DATA. */\n+#define MXL862XX_SB_PDI_PROBE_A\t\t0x5a5a\n+#define MXL862XX_SB_PDI_PROBE_D\t\t0xa5a5\n+\n+/* Image verification verdict published in STAT once the receive loop ends */\n+#define MXL862XX_SB_PDI_VERIFY_OK\t0\n+#define MXL862XX_SB_PDI_VERIFY_BAD\t1\n+\n+/* Firmware transfer geometry */\n+#define MXL862XX_FW_HDR_SIZE\t\t20\n+#define MXL862XX_FW_BANK_HALF\t\t16384\t/* words per half-bank */\n+#define MXL862XX_FW_BANK_SLICE\t\t32760\t/* words per full slice */\n+#define MXL862XX_FW_SB1_ADDR\t\t0x7800\t/* SB1 word address */\n+\n+/* Timeouts (generous upper bounds) */\n+#define MXL862XX_FW_READY_TIMEOUT_MS\t3000\n+#define MXL862XX_FW_ACK_TIMEOUT_MS\t5000\n+#define MXL862XX_FW_ERASE_TIMEOUT_MS\t300000\n+#define MXL862XX_FW_WRITE_TIMEOUT_MS\t60000\n+#define MXL862XX_FW_REBOOT_DELAY_MS\t5000\n+#define MXL862XX_FW_REPROBE_DELAY_MS\t500\n+/* One loader mailbox step: program a 1-byte chunk or service a command */\n+#define MXL862XX_SB_PDI_STEP_MS\t\t2000\n+/* Covers the loader's END wait, verification and the reset into READY */\n+#define MXL862XX_SB_PDI_VERIFY_MS\t15000\n+\n+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)\n+{\n+\tint ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t   MXL862XX_SB_PDI_CTRL_RST);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t\t   MXL862XX_SB_PDI_CTRL_RST);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\t\t\t\t    MXL862XX_SB_PDI_CTRL_RST);\n+}\n+\n+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,\n+\t\t\t\t     unsigned long timeout_ms)\n+{\n+\tint ret, val;\n+\n+\tret = read_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t\tval \u003c 0 || (u16)val == expected,\n+\t\t\t\t10000, timeout_ms * 1000, false,\n+\t\t\t\tpriv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\treturn ret;\n+}\n+\n+static int mxl862xx_sb_pdi_flush_slice(struct mxl862xx_priv *priv,\n+\t\t\t\t       u32 data_written)\n+{\n+\tint ret;\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn mxl862xx_sb_pdi_poll_stat(priv, 0,\n+\t\t\t\t\t MXL862XX_FW_WRITE_TIMEOUT_MS);\n+}\n+\n+/* Flush the last slice, which ends the receive loop: the loader verifies the\n+ * image and replaces the count in STAT with its verdict, so wait for the count\n+ * to go rather than for a fixed value.\n+ */\n+static int mxl862xx_sb_pdi_flush_last(struct mxl862xx_priv *priv,\n+\t\t\t\t      u32 data_written)\n+{\n+\tint ret, val;\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = read_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t\tval \u003c 0 || (u16)val != (u16)data_written,\n+\t\t\t\t10000, (MXL862XX_FW_WRITE_TIMEOUT_MS +\n+\t\t\t\t\tMXL862XX_SB_PDI_VERIFY_MS) * 1000,\n+\t\t\t\tfalse, priv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\n+\tif (!ret \u0026\u0026 (u16)val == MXL862XX_SB_PDI_VERIFY_OK)\n+\t\treturn 0;\n+\n+\t/* A final count of 1 is indistinguishable from the reject verdict, so\n+\t * a timeout still holding it lands here too.\n+\t */\n+\tif ((u16)val == MXL862XX_SB_PDI_VERIFY_BAD) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: loader rejected the image\\n\");\n+\t\treturn -EBADMSG;\n+\t}\n+\n+\treturn ret ? ret : -EPROTO;\n+}\n+\n+static void mxl862xx_flash_notify(struct devlink *dl, const char *status,\n+\t\t\t\t  u32 done, u32 total)\n+{\n+\tdevlink_flash_update_status_notify(dl, status, NULL, done, total);\n+}\n+\n+/* Byte-count of each chunk fed to the loader during drain. It MUST be 1: the\n+ * loader only lets us observe \"counter == 0\", never \"counter \u003c step\", so any\n+ * step \u003e 1 can subtract past zero, underflow the 32-bit counter and wedge the\n+ * loader for ~2^32 more bytes (a state only a power cycle clears). Stepping by\n+ * 1 walks the counter through every value and is guaranteed to land on zero\n+ * whatever its (possibly odd) start. A 1-byte chunk is a path the loader\n+ * already handles: the normal transfer ends with a single trailing byte for\n+ * odd-sized images (see Step 6).\n+ */\n+#define MXL862XX_DRAIN_CHUNK_BYTES\t1\n+\n+/* Log the drain's progress every so many bytes; it can run for a long time */\n+#define MXL862XX_DRAIN_LOG_BYTES\t(128 * 1024)\n+\n+/* Wait for the loader to ask for the next chunk (STAT 0) or to come back to its\n+ * command loop (STAT READY), and return the STAT value either way. On timeout\n+ * that is whatever STAT still holds, which carries no further information: the\n+ * loader keeps the count we wrote visible while it programs the chunk, and that\n+ * is the same value it publishes as the \"image rejected\" verdict once the\n+ * counter reaches zero.\n+ *\n+ * STAT 0 is unambiguous here even though it is also the \"image verified\"\n+ * verdict: by the r_remain == 0 rule the loader leaves the receive loop the\n+ * moment the counter reaches zero, so it is never both inside the loop asking\n+ * for a chunk and publishing a verdict. Once it has left, the next STAT write\n+ * is a command rather than a count, so feeding one more chunk after a verdict\n+ * cannot underflow anything either.\n+ */\n+static int mxl862xx_sb_pdi_poll_drain(struct mxl862xx_priv *priv,\n+\t\t\t\t      unsigned long timeout_ms)\n+{\n+\tint val;\n+\n+\tread_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t  val \u003c 0 || (u16)val == MXL862XX_SB_PDI_READY ||\n+\t\t\t  (u16)val == 0,\n+\t\t\t  50, timeout_ms * 1000, false,\n+\t\t\t  priv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\treturn (u16)val;\n+}\n+\n+/* The loader is not asking for a chunk: it may still be programming the last\n+ * one, or the counter has reached zero and it is verifying the image and\n+ * resetting into READY. Wait that out -- STAT cannot tell the two apart, and\n+ * guessing would mean writing END into a live receive loop.\n+ *\n+ * Return: 0 once the loader has left the loop, -EAGAIN if it asks for another\n+ * chunk after all, -EIO for a loader still holding the count when the verify\n+ * window expires, or an SMDIO bus error.\n+ */\n+static int mxl862xx_rescue_drain_finish(struct mxl862xx_priv *priv, u32 chunk)\n+{\n+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n+\tint stat;\n+\n+\tstat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_VERIFY_MS);\n+\tif (stat \u003c 0)\n+\t\treturn stat;\n+\tif (stat == MXL862XX_SB_PDI_READY)\n+\t\treturn 0;\n+\tif (stat == MXL862XX_SB_PDI_VERIFY_BAD) {\n+\t\tdev_err(dev,\n+\t\t\t\"flash: loader stuck after %u chunks, power cycle it\\n\",\n+\t\t\tchunk);\n+\t\treturn -EIO;\n+\t}\n+\tif (stat) {\n+\t\t/* A firmware is answering, not the loader: an image survived\n+\t\t * in flash and booted.\n+\t\t */\n+\t\tdev_info(dev, \"flash: firmware booted while draining\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\treturn -EAGAIN;\n+}\n+\n+/* Recover a switch whose SB PDI download was interrupted mid-transfer - the\n+ * host died after MCUboot began erasing flash, whether it aborted mid erase or\n+ * mid image-write, both end up in the same place: the payload receive loop.\n+ * There the loader publishes STAT=0, waits for the host to write a byte-count\n+ * to STAT, DMAs that many bytes and subtracts the count from a remaining-bytes\n+ * counter, leaving the loop only when the counter reaches exactly zero. The\n+ * image size died with the host, so we feed single-byte chunks (see\n+ * MXL862XX_DRAIN_CHUNK_BYTES) to walk the counter to zero without underflow.\n+ * The loader then verifies the (now corrupt) image and returns to READY by\n+ * itself, or boots a valid image that happened to survive in flash; either way\n+ * the caller's reprobe classifies the result. Every byte costs about a dozen\n+ * MDIO frames, so a multi-MiB outstanding count takes tens of minutes.\n+ * Returns 0 once the loader has left the receive loop, \u003c0 on error. Does NOT\n+ * recover a counter already underflowed by an earlier oversized-chunk attempt\n+ * - that needs a power cycle.\n+ */\n+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)\n+{\n+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n+\t/* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */\n+\tu32 max_chunks = 2u * (16u \u003c\u003c 20) / MXL862XX_DRAIN_CHUNK_BYTES;\n+\tu32 chunk = 0;\n+\tint ret, stat;\n+\n+\twhile (chunk \u003c max_chunks) {\n+\t\t/* Teardown can interrupt this long drain. */\n+\t\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\t\treturn -ECANCELED;\n+\n+\t\tstat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_STEP_MS);\n+\t\tif (stat \u003c 0)\n+\t\t\treturn stat;\n+\t\tif (stat == MXL862XX_SB_PDI_READY)\n+\t\t\treturn 0;\n+\n+\t\tif (stat) {\n+\t\t\tret = mxl862xx_rescue_drain_finish(priv, chunk);\n+\t\t\tif (ret != -EAGAIN)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\t/* Feed one zero byte; reset cleared the write latch. */\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tret = mxl862xx_sb_pdi_reset(priv);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t\t   MXL862XX_DRAIN_CHUNK_BYTES);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tchunk++;\n+\t\tif (!(chunk % MXL862XX_DRAIN_LOG_BYTES))\n+\t\t\tdev_info(dev, \"flash: drained %u KiB so far\\n\",\n+\t\t\t\t chunk / 1024);\n+\t\tcond_resched();\n+\t}\n+\n+\tdev_err(dev,\n+\t\t\"flash: interrupted download did not drain after %u chunks\\n\",\n+\t\tchunk);\n+\n+\treturn -ETIMEDOUT;\n+}\n+\n+/* Background self-heal: drain a wedged download off the devlink flash path, so\n+ * the long recovery never holds the devlink lock. Scheduled from probe;\n+ * reprobes on success so the probe-time detection re-classifies the switch.\n+ */\n+void mxl862xx_rescue_heal_work_fn(struct work_struct *work)\n+{\n+\tstruct mxl862xx_priv *priv =\n+\t\tcontainer_of(work, struct mxl862xx_priv, rescue_heal_work);\n+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n+\tint ret;\n+\n+\tret = mxl862xx_rescue_drain(priv);\n+\tif (ret == -ECANCELED)\n+\t\treturn;\n+\tif (ret) {\n+\t\t/* Nothing retries this, so say so: rescue_ready stays clear\n+\t\t * and devlink dev flash reports why it refuses.\n+\t\t */\n+\t\tdev_err(dev, \"flash: download recovery failed: %pe\\n\",\n+\t\t\tERR_PTR(ret));\n+\t\tWRITE_ONCE(priv-\u003erescue_failed, true);\n+\t\treturn;\n+\t}\n+\n+\t/* The interrupted transfer is finalised; reprobe so the probe-time\n+\t * detection brings the driver up -- flashable in rescue mode if the\n+\t * loader is at READY, or normally if a valid image booted. The core\n+\t * skips the re-probe on its own if the device is unbound first; the\n+\t * flag test only avoids scheduling one certain to be skipped. A\n+\t * failed hand-off leaves nothing to reclassify the switch, so mark\n+\t * recovery failed rather than promise a retry that cannot succeed.\n+\t */\n+\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\treturn;\n+\n+\tif (device_schedule_reprobe(dev, MXL862XX_FW_REPROBE_DELAY_MS))\n+\t\tWRITE_ONCE(priv-\u003erescue_failed, true);\n+}\n+\n+/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller can rule\n+ * the loader out before any C45 API request (which spews CRC errors when no WSP\n+ * firmware answers). A scratch write to ADDR/DATA must latch or the chip is\n+ * absent (-ENODEV); the mailbox is reset first, or a transfer interrupted with\n+ * CTRL=WR would take that write as a payload word instead of latching it. STAT\n+ * then classifies the state, poked destructively only when 0, the one value a\n+ * running firmware never holds:\n+ *\n+ *  - 0xc33c: flashless loop; recognised but not supported here.\n+ *  - 0xc55c: console loop, if the register-read challenge is serviced.\n+ *  - other non-zero: running firmware, left unpoked.\n+ *  - 0: wedged receive loop; the 1-byte slice-advance then says whether it\n+ *    still needs draining or has just finished.\n+ *\n+ * The scratch write reaches a running firmware too, but lands in mailbox\n+ * registers it does not read, so it is inert there.\n+ *\n+ * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, -ENODEV when the scratch\n+ * write does not latch, which is a switch that does not answer at all or one\n+ * whose SB PDI window is not at the offsets above, -EOPNOTSUPP for the\n+ * flashless loop, -ENXIO for a READY loader whose mailbox fails the challenge,\n+ * or an SMDIO bus error.\n+ */\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)\n+{\n+\tint stat, dat, ret, rb, a, d;\n+\n+\t/* rescue_ready gates flashing; a wedged loader needs the drain first. */\n+\tWRITE_ONCE(priv-\u003erescue_ready, false);\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\t/* Presence: a live chip latches the scratch write, an absent one floats. */\n+\ta = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t\t MXL862XX_SB_PDI_PROBE_A);\n+\tif (a \u003c 0)\n+\t\treturn a;\n+\td = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\t\t\t\t MXL862XX_SB_PDI_PROBE_D);\n+\tif (d \u003c 0)\n+\t\treturn d;\n+\ta = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_ADDR);\n+\tif (a \u003c 0)\n+\t\treturn a;\n+\td = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);\n+\tif (d \u003c 0)\n+\t\treturn d;\n+\tif ((u16)a != MXL862XX_SB_PDI_PROBE_A ||\n+\t    (u16)d != MXL862XX_SB_PDI_PROBE_D)\n+\t\treturn -ENODEV;\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tstat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);\n+\tif (stat \u003c 0)\n+\t\treturn stat;\n+\n+\t/* Flashless-download loop (MxL86281S tier): this driver does not\n+\t * support it -- the console flash path expects READY. Treat it as an\n+\t * unusable configuration, like any other unsupported state.\n+\t */\n+\tif ((u16)stat == MXL862XX_SB_PDI_DL_READY)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* Console loop at READY: confirm the live mailbox with the register-read\n+\t * challenge (consumes the marker from DATA and re-arms READY).\n+\t */\n+\tif ((u16)stat == MXL862XX_SB_PDI_READY) {\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\t\t\t\t\t   MXL862XX_SB_PDI_RDREG_MARK);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t\t   MXL862XX_SB_PDI_RDREG);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\trb = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\t       MXL862XX_SB_PDI_STEP_MS);\n+\t\tdat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);\n+\t\tret = mxl862xx_sb_pdi_reset(priv);\n+\t\t/* Never re-arming READY fails the challenge like any other\n+\t\t * unserviced command; report it as such rather than as a bus\n+\t\t * timeout the bus never saw.\n+\t\t */\n+\t\tif (rb == -ETIMEDOUT)\n+\t\t\trb = -ENXIO;\n+\t\tif (rb \u003c 0)\n+\t\t\treturn rb;\n+\t\tif (dat \u003c 0)\n+\t\t\treturn dat;\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif ((u16)dat != MXL862XX_SB_PDI_RDREG_MARK) {\n+\t\t\tWRITE_ONCE(priv-\u003erescue_ready, true);\n+\t\t\treturn MXL862XX_IN_RESCUE;\n+\t\t}\n+\t\t/* READY but the marker is untouched, so nothing is servicing the\n+\t\t * mailbox. A firmware publishing 0xc55c as its status word looks\n+\t\t * exactly like this, and flashing one would be far worse than\n+\t\t * refusing to bind, so treat it as unusable.\n+\t\t */\n+\t\treturn -ENXIO;\n+\t}\n+\n+\t/* Any other non-zero value is a running firmware, not a loader. */\n+\tif (stat)\n+\t\treturn MXL862XX_NOT_RESCUE;\n+\n+\t/* STAT == 0: a wedged receive loop takes a 1-byte slice-advance (feed\n+\t * one DATA word first, like a drain chunk) and asks for the next chunk\n+\t * by publishing 0 again. Had that byte been the last one outstanding,\n+\t * the loader leaves the loop instead and returns to READY, having\n+\t * consumed the advance -- proof enough of a live mailbox to skip the\n+\t * challenge. Anything else means it is still working on it. All three\n+\t * are rescue, so this never fails probe; only the drain does.\n+\t */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t   MXL862XX_DRAIN_CHUNK_BYTES);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\trb = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_STEP_MS);\n+\tif (rb \u003c 0)\n+\t\treturn rb;\n+\tif (rb == MXL862XX_SB_PDI_READY)\n+\t\tWRITE_ONCE(priv-\u003erescue_ready, true);\n+\n+\treturn MXL862XX_IN_RESCUE;\n+}\n+\n+/* MCUboot firmware image header */\n+struct mxl862xx_fw_hdr {\n+\t__le32 image_type;\n+\t__le32 image_size_1;\n+\t__le32 image_checksum_1;\n+\t__le32 image_size_2;\n+\t__le32 image_checksum_2;\n+} __packed;\n+\n+static int mxl862xx_flash_validate(struct mxl862xx_priv *priv,\n+\t\t\t\t   const struct firmware *fw,\n+\t\t\t\t   u32 *payload_size)\n+{\n+\tconst struct mxl862xx_fw_hdr *hdr;\n+\tu32 size1, size2, total;\n+\tconst u8 *payload;\n+\tu32 crc;\n+\n+\tif (fw-\u003esize \u003c MXL862XX_FW_HDR_SIZE)\n+\t\treturn -EINVAL;\n+\n+\thdr = (const struct mxl862xx_fw_hdr *)fw-\u003edata;\n+\tpayload = fw-\u003edata + MXL862XX_FW_HDR_SIZE;\n+\tsize1 = le32_to_cpu(hdr-\u003eimage_size_1);\n+\tsize2 = le32_to_cpu(hdr-\u003eimage_size_2);\n+\n+\tif (check_add_overflow(size1, size2, \u0026total) ||\n+\t    total \u003e fw-\u003esize - MXL862XX_FW_HDR_SIZE) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: firmware file too small for declared size\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!total) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: firmware file with empty payload\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (size1) {\n+\t\tcrc = ~crc32_le(~0U, payload, size1);\n+\t\tif (crc != le32_to_cpu(hdr-\u003eimage_checksum_1)) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: image 1 CRC mismatch (got %08x, expected %08x)\\n\",\n+\t\t\t\tcrc, le32_to_cpu(hdr-\u003eimage_checksum_1));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (size2) {\n+\t\tcrc = ~crc32_le(~0U, payload + size1, size2);\n+\t\tif (crc != le32_to_cpu(hdr-\u003eimage_checksum_2)) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: image 2 CRC mismatch (got %08x, expected %08x)\\n\",\n+\t\t\t\tcrc, le32_to_cpu(hdr-\u003eimage_checksum_2));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\t*payload_size = total;\n+\n+\treturn 0;\n+}\n+\n+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,\n+\t\t\t\t   const struct firmware *fw,\n+\t\t\t\t   u32 payload_size, struct devlink *dl)\n+{\n+\tconst u8 *payload = fw-\u003edata + MXL862XX_FW_HDR_SIZE;\n+\tu32 word_idx = 0, data_written = 0, idx = 0;\n+\tunsigned long next_notify = jiffies - 1;\n+\tu16 word, fdata;\n+\tint ret, i;\n+\n+\t/* Step 1: reboot the firmware into MCUboot rescue mode */\n+\tif (!priv-\u003erescue_mode) {\n+\t\tret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,\n+\t\t\t\t\tfalse, false);\n+\t\tif (ret) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: FW_UPDATE command failed: %pe\\n\",\n+\t\t\t\tERR_PTR(ret));\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\t/* Step 2: wait for bootloader ready */\n+\tmxl862xx_flash_notify(dl, \"Waiting for bootloader\", 0, 0);\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* Failures from here on end up at no_end, which returns the error\n+\t * without signalling END -- see there.\n+\t */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\tMXL862XX_FW_READY_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: bootloader not ready: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto no_end;\n+\t}\n+\n+\t/* Step 3: start handshake */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t   MXL862XX_SB_PDI_START);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: start handshake failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto no_end;\n+\t}\n+\n+\t/* Step 4: transfer image header */\n+\tmxl862xx_flash_notify(dl, \"Erasing flash\", 0, 0);\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\tfor (i = 0; i \u003c MXL862XX_FW_HDR_SIZE / 2; i++) {\n+\t\tword = fw-\u003edata[i * 2] |\n+\t\t       ((u16)fw-\u003edata[i * 2 + 1] \u003c\u003c 8);\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto write_err;\n+\t}\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* the byte count in STAT triggers the erase */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t   MXL862XX_FW_HDR_SIZE);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* ACK is byte count + 1 */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: header ACK failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto no_end;\n+\t}\n+\n+\t/* Step 5: wait for erase to complete */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, 0,\n+\t\t\t\t\tMXL862XX_FW_ERASE_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: erase timeout: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto no_end;\n+\t}\n+\n+\t/* Step 6: transfer payload */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\twhile (idx \u003c payload_size) {\n+\t\tcond_resched();\n+\t\tif (idx + 1 \u003c payload_size) {\n+\t\t\tfdata = payload[idx] |\n+\t\t\t\t((u16)payload[idx + 1] \u003c\u003c 8);\n+\t\t\tidx += 2;\n+\t\t\tdata_written += 2;\n+\t\t} else {\n+\t\t\tfdata = payload[idx];\n+\t\t\tidx++;\n+\t\t\tdata_written++;\n+\t\t}\n+\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto write_err;\n+\t\tword_idx++;\n+\n+\t\tif (idx \u003e= payload_size) {\n+\t\t\tret = mxl862xx_sb_pdi_flush_last(priv, data_written);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Half-bank boundary: switch to SB1 address */\n+\t\tif (word_idx == MXL862XX_FW_BANK_HALF) {\n+\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t\t   MXL862XX_SB_PDI_CTRL_RST);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto write_err;\n+\n+\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t\t\t\t   MXL862XX_FW_SB1_ADDR);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto write_err;\n+\n+\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto write_err;\n+\t\t} else if (word_idx \u003e= MXL862XX_FW_BANK_SLICE) {\n+\t\t\tret = mxl862xx_sb_pdi_flush_slice(priv, data_written);\n+\t\t\tif (ret) {\n+\t\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\t\"flash: write timeout at %u/%u: %pe\\n\",\n+\t\t\t\t\tidx, payload_size, ERR_PTR(ret));\n+\t\t\t\tgoto no_end;\n+\t\t\t}\n+\t\t\tword_idx = 0;\n+\t\t\tdata_written = 0;\n+\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t\t   MXL862XX_SB_PDI_CTRL_WR);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto write_err;\n+\n+\t\t\tif (time_after(jiffies, next_notify)) {\n+\t\t\t\tmxl862xx_flash_notify(dl, \"Flashing\", idx,\n+\t\t\t\t\t\t      payload_size);\n+\t\t\t\tnext_notify = jiffies + msecs_to_jiffies(500);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: final slice failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto no_end;\n+\t}\n+\n+\tmxl862xx_flash_notify(dl, \"Flashing\", payload_size, payload_size);\n+\n+\t/* Success: the loader has left the receive loop at r_remain == 0 and\n+\t * verified the image, so END(0x3cc3) is a finalise/boot request rather\n+\t * than a byte count. Signal it here -- and only here -- to boot the new\n+\t * image without waiting out the loader's 2 s END timeout.\n+\t */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t   MXL862XX_SB_PDI_END);\n+\tmsleep(MXL862XX_FW_REBOOT_DELAY_MS);\n+\treturn ret;\n+\n+write_err:\n+\tdev_err(\u0026priv-\u003emdiodev-\u003edev, \"flash: SMDIO write failed: %pe\\n\",\n+\t\tERR_PTR(ret));\n+no_end:\n+\t/* A failure leaves the loader mid transfer; do not signal END (a STAT\n+\t * write is a byte count then, and END would be misread as one, risking\n+\t * a receive-counter underflow). Return the error; the caller reprobes.\n+\t */\n+\treturn ret;\n+}\n+\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t      struct devlink_info_req *req,\n+\t\t\t      struct netlink_ext_ack *extack)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\tchar buf[16];\n+\tint ret;\n+\n+\t/* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The\n+\t * fw version doubles as the \"ready to flash\" signal: report it only\n+\t * once the loader is at a clean READY, nothing while still draining.\n+\t */\n+\tif (priv-\u003erescue_mode) {\n+\t\tif (!READ_ONCE(priv-\u003erescue_ready))\n+\t\t\treturn 0;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"%u.%u.%u\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\t\tret = devlink_info_version_running_put(req,\n+\t\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\treturn devlink_info_version_stored_put(req,\n+\t\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\t}\n+\n+\t/* A 0 part number means the CHIP ID read failed or the part is\n+\t * unfused; omit it rather than publish a bogus \"0000\" that fwupd\n+\t * would match firmware against -- it then falls back to the driver\n+\t * name.\n+\t */\n+\tif (priv-\u003easic_id) {\n+\t\tsnprintf(buf, sizeof(buf), \"%04X\", priv-\u003easic_id);\n+\t\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t\t     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,\n+\t\t\t\t\t\t     buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"%u\", priv-\u003easic_rev);\n+\t\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t\t     DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,\n+\t\t\t\t\t\t     buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/* An all-zero version is the cache a failed flash left behind, not a\n+\t * released firmware; omit it like the part number above.\n+\t */\n+\tif (!priv-\u003efw_version.major \u0026\u0026 !priv-\u003efw_version.minor \u0026\u0026\n+\t    !priv-\u003efw_version.revision)\n+\t\treturn 0;\n+\n+\tsnprintf(buf, sizeof(buf), \"%u.%u.%u\",\n+\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t priv-\u003efw_version.revision);\n+\n+\tret = devlink_info_version_running_put(req,\n+\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* boots this image from its own flash: stored == running */\n+\treturn devlink_info_version_stored_put(req,\n+\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+}\n+\n+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n+\t\t\t\t  struct devlink_flash_update_params *params,\n+\t\t\t\t  struct netlink_ext_ack *extack)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\tstruct dsa_port *dp;\n+\tu32 payload_size;\n+\tint ret, err, i;\n+\n+\tif (params-\u003ecomponent) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"component is not supported\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\t/* A previous flash is still waiting for its reprobe: the firmware API\n+\t * is short-circuited, so the raw SB PDI writes below would run against\n+\t * a switch this driver no longer tracks.\n+\t */\n+\tif (priv-\u003eskip_teardown) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t   \"device is reinitializing, retry later\");\n+\t\treturn -EBUSY;\n+\t}\n+\n+\tret = mxl862xx_flash_validate(priv, params-\u003efw, \u0026payload_size);\n+\tif (ret) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"firmware image validation failed\");\n+\t\treturn ret;\n+\t}\n+\n+\t/* Refuse to flash while the background self-heal is still draining, and\n+\t * for good once it has given up on the loader.\n+\t */\n+\tif (READ_ONCE(priv-\u003erescue_failed)) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t   \"download recovery failed, power cycle the switch\");\n+\t\treturn -EIO;\n+\t}\n+\n+\tif (priv-\u003erescue_mode \u0026\u0026 !READ_ONCE(priv-\u003erescue_ready)) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t   \"switch is recovering an interrupted download, retry shortly\");\n+\t\treturn -EBUSY;\n+\t}\n+\n+\tif (priv-\u003erescue_mode)\n+\t\tdev_info(ds-\u003edev,\n+\t\t\t \"flash: flashing switch via MCUboot rescue mode\\n\");\n+\telse\n+\t\tdev_info(ds-\u003edev, \"flash: running firmware %u.%u.%u\\n\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\n+\t/* Close ports while the firmware is still alive so the DSA core's\n+\t * MDB/FDB tracking is drained, and detach user ports so userspace\n+\t * cannot reopen them during the flash. The conduit is only closed,\n+\t * not detached: it belongs to the MAC driver. This driver binds a\n+\t * single switch with a direct host link and no cascade ports, so the\n+\t * conduit serves only this switch, and flashing it reboots the switch,\n+\t * which takes the tree down regardless.\n+\t */\n+\trtnl_lock();\n+\tdsa_switch_for_each_user_port(dp, ds) {\n+\t\tif (dp-\u003euser) {\n+\t\t\tdev_close(dp-\u003euser);\n+\t\t\tnetif_device_detach(dp-\u003euser);\n+\t\t}\n+\t}\n+\tdsa_switch_for_each_cpu_port(dp, ds)\n+\t\tdev_close(dp-\u003econduit);\n+\t/* The bridge defers the STP state changes triggered by closing\n+\t * the ports; let them reach the firmware while it is still alive.\n+\t */\n+\tswitchdev_deferred_process();\n+\trtnl_unlock();\n+\n+\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tpriv-\u003eblock_host = true;\n+\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n+\tdisable_delayed_work_sync(\u0026priv-\u003estats_work);\n+\tcancel_work_sync(\u0026priv-\u003ecrc_err_work);\n+\tfor (i = 0; i \u003c ds-\u003enum_ports; i++)\n+\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n+\n+\tret = mxl862xx_flash_firmware(priv, params-\u003efw, payload_size,\n+\t\t\t\t      ds-\u003edevlink);\n+\tif (ret)\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"firmware transfer failed\");\n+\n+\tif (!ret) {\n+\t\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock,\n+\t\t\t\t  MDIO_MUTEX_NESTED);\n+\t\t/* Keep block_host set so host writes stay blocked, but let the\n+\t\t * readiness poll below read the freshly booted firmware.\n+\t\t */\n+\t\tpriv-\u003eflash_reading = true;\n+\t\tpriv-\u003erescue_mode = false;\n+\t\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\t\t/* Refresh the cached versions so the flash update only\n+\t\t * completes once the new firmware is confirmed running and\n+\t\t * devlink dev info reports it. Must happen before setting\n+\t\t * skip_teardown, which discards all firmware API reads.\n+\t\t */\n+\t\tret = mxl862xx_wait_ready(ds);\n+\t\tif (ret)\n+\t\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t\t   \"new firmware did not become ready\");\n+\t}\n+\n+\tif (ret) {\n+\t\t/* The switch is in MCUboot with erased or partly written flash;\n+\t\t * drop the cached identity so devlink dev info stops reporting\n+\t\t * the pre-flash version until the reprobe re-reads the truth.\n+\t\t */\n+\t\tmemset(\u0026priv-\u003efw_version, 0, sizeof(priv-\u003efw_version));\n+\t\tpriv-\u003easic_id = 0;\n+\t\tpriv-\u003easic_rev = 0;\n+\t}\n+\n+\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tpriv-\u003eflash_reading = false;\n+\tpriv-\u003eblock_host = false;\n+\tpriv-\u003eskip_teardown = true;\n+\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\t/* Reinitialise through a deferred re-probe: remove() runs with\n+\t * skip_teardown set, then a fresh probe() starts against whatever\n+\t * the switch now runs. The core skips the re-probe if the device\n+\t * is unbound or shut down before it fires.\n+\t */\n+\terr = device_schedule_reprobe(ds-\u003edev, MXL862XX_FW_REPROBE_DELAY_MS);\n+\n+\treturn ret ? ret : err;\n+}\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\nnew file mode 100644\nindex 0000000000000..7cd87c7ad8716\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\n@@ -0,0 +1,20 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+\n+#ifndef __MXL862XX_FW_H\n+#define __MXL862XX_FW_H\n+\n+#include \u003cnet/dsa.h\u003e\n+\n+struct mxl862xx_priv;\n+struct work_struct;\n+\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);\n+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t      struct devlink_info_req *req,\n+\t\t\t      struct netlink_ext_ack *extack);\n+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n+\t\t\t\t  struct devlink_flash_update_params *params,\n+\t\t\t\t  struct netlink_ext_ack *extack);\n+\n+#endif /* __MXL862XX_FW_H */\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\nindex 4acd216f7cc00..23e5dfe81e364 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n@@ -15,6 +15,8 @@\n #include \u003clinux/unaligned.h\u003e\n #include \u003cnet/dsa.h\u003e\n #include \"mxl862xx.h\"\n+#include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n #include \"mxl862xx-host.h\"\n \n #define CTRL_BUSY_MASK\t\t\tBIT(15)\n@@ -340,6 +342,26 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,\n \n \tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n \n+\tif (priv-\u003eskip_teardown) {\n+\t\tret = read ? -ENODEV : 0;\n+\t\tgoto out;\n+\t}\n+\n+\tif (priv-\u003erescue_mode) {\n+\t\tret = -ENODEV;\n+\t\tgoto out;\n+\t}\n+\n+\t/* During the post-flash readiness poll block_host stays set, but the\n+\t * flash path's own firmware version reads must reach the new image;\n+\t * host writes stay blocked so stale resource IDs cannot corrupt it.\n+\t */\n+\tif (priv-\u003eblock_host \u0026\u0026 cmd != SYS_MISC_FW_UPDATE \u0026\u0026\n+\t    !(priv-\u003eflash_reading \u0026\u0026 read)) {\n+\t\tret = -EBUSY;\n+\t\tgoto out;\n+\t}\n+\n \tmax = (size + 1) / 2;\n \n \tret = mxl862xx_busy_wait(priv);\n@@ -495,12 +517,54 @@ int mxl862xx_reset(struct mxl862xx_priv *priv)\n \treturn ret;\n }\n \n+#define MXL862XX_SMDIO_ADDR_REG\t\t0x1f\n+#define MXL862XX_SMDIO_PAGE_MASK\t0xfff0\n+#define MXL862XX_SMDIO_OFF_MASK\t\t0x000f\n+\n+/* Paged clause-22 window: the page goes into MII register 0x1f, the low nibble\n+ * of addr selects one of the 16 registers within it. Both helpers take the MDIO\n+ * bus lock per transaction, so callers must not already hold it -- unlike\n+ * mxl862xx_api_wrap(), which holds it across a whole firmware command.\n+ */\n+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr)\n+{\n+\tstruct mii_bus *bus = priv-\u003emdiodev-\u003ebus;\n+\tint phy = priv-\u003emdiodev-\u003eaddr;\n+\tint ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\tret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,\n+\t\t\t      addr \u0026 MXL862XX_SMDIO_PAGE_MASK);\n+\tif (ret \u003e= 0)\n+\t\tret = __mdiobus_read(bus, phy, addr \u0026 MXL862XX_SMDIO_OFF_MASK);\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val)\n+{\n+\tstruct mii_bus *bus = priv-\u003emdiodev-\u003ebus;\n+\tint phy = priv-\u003emdiodev-\u003eaddr;\n+\tint ret;\n+\n+\tmutex_lock(\u0026bus-\u003emdio_lock);\n+\tret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,\n+\t\t\t      addr \u0026 MXL862XX_SMDIO_PAGE_MASK);\n+\tif (ret \u003e= 0)\n+\t\tret = __mdiobus_write(bus, phy, addr \u0026 MXL862XX_SMDIO_OFF_MASK,\n+\t\t\t\t      val);\n+\tmutex_unlock(\u0026bus-\u003emdio_lock);\n+\treturn ret;\n+}\n+\n void mxl862xx_host_init(struct mxl862xx_priv *priv)\n {\n \tINIT_WORK(\u0026priv-\u003ecrc_err_work, mxl862xx_crc_err_work_fn);\n+\tINIT_WORK(\u0026priv-\u003erescue_heal_work, mxl862xx_rescue_heal_work_fn);\n }\n \n void mxl862xx_host_shutdown(struct mxl862xx_priv *priv)\n {\n \tcancel_work_sync(\u0026priv-\u003ecrc_err_work);\n+\tcancel_work_sync(\u0026priv-\u003erescue_heal_work);\n }\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h\nindex 66d6ae198aff4..4e054c6e4c0e4 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h\n@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,\n \tmxl862xx_api_wrap(dev, cmd, \u0026(data), sizeof((data)), true, true)\n \n int mxl862xx_reset(struct mxl862xx_priv *priv);\n+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);\n+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);\n \n #endif /* __MXL862XX_HOST_H */\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\nindex b689652aa9b92..a5b6940b552e9 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,\n \n \tswitch (port) {\n \tcase 9 ... 16:\n+\t\tif (priv-\u003erescue_mode)\n+\t\t\treturn NULL;\n \t\tif (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {\n \t\t\tdev_warn_once(dp-\u003eds-\u003edev,\n \t\t\t\t      \"SerDes PCS unsupported on old firmware.\\n\");\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c\nindex cfa7e3e269a28..c50c5ca328618 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c\n@@ -21,6 +21,7 @@\n #include \"mxl862xx.h\"\n #include \"mxl862xx-api.h\"\n #include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n #include \"mxl862xx-host.h\"\n #include \"mxl862xx-phylink.h\"\n \n@@ -71,6 +72,13 @@ static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {\n #define MXL862XX_READY_TIMEOUT_MS\t10000\n #define MXL862XX_READY_POLL_MS\t\t100\n \n+/* Chip ID registers, read via SYS_MISC_REG_RD */\n+#define MXL862XX_CHIPID_L\t\t0xc0d28884\n+#define MXL862XX_CHIPID_M\t\t0xc0d28888\n+#define MXL862XX_CHIPID_L_PNUML\t\tGENMASK(15, 12)\n+#define MXL862XX_CHIPID_M_PNUMM\t\tGENMASK(11, 0)\n+#define MXL862XX_CHIPID_M_VERSION\tGENMASK(14, 12)\n+\n #define MXL862XX_TCM_INST_SEL\t\t0xe00\n #define MXL862XX_TCM_CBS\t\t0xe12\n #define MXL862XX_TCM_EBS\t\t0xe13\n@@ -222,7 +230,46 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr,\n \treturn mxl862xx_phy_write_mmd(bus-\u003epriv, addr, devadd, regnum, val);\n }\n \n-static int mxl862xx_wait_ready(struct dsa_switch *ds)\n+/* Read the static chip part number and version from the CHIP ID\n+ * registers. Only possible with a running firmware, so the values are\n+ * cached at setup and left zero when the switch is in rescue mode.\n+ */\n+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)\n+{\n+\tstruct mxl862xx_sys_reg_rw reg = {};\n+\tu16 chipid_l, chipid_m;\n+\tint ret;\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_L);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_l = le32_to_cpu(reg.val);\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_M);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_m = le32_to_cpu(reg.val);\n+\n+\tpriv-\u003easic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |\n+\t\t\tFIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) \u003c\u003c 4;\n+\tpriv-\u003easic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mxl862xx_wait_ready - wait for the switch firmware to become operational\n+ * @ds: DSA switch instance\n+ *\n+ * Poll the firmware until it reports its version and accepts\n+ * configuration commands, then cache the firmware version and chip ID.\n+ * Takes at least two seconds.\n+ *\n+ * Return: 0 on success or a negative error code.\n+ */\n+int mxl862xx_wait_ready(struct dsa_switch *ds)\n {\n \tstruct mxl862xx_sys_fw_image_version ver = {};\n \tunsigned long start = jiffies, timeout;\n@@ -254,6 +301,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)\n \t\tpriv-\u003efw_version.major = ver.iv_major;\n \t\tpriv-\u003efw_version.minor = ver.iv_minor;\n \t\tpriv-\u003efw_version.revision = le16_to_cpu(ver.iv_revision);\n+\n+\t\tret = mxl862xx_read_chip_id(priv);\n+\t\tif (ret)\n+\t\t\tdev_warn(ds-\u003edev, \"failed to read chip ID: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n \t\treturn 0;\n \n not_ready_yet:\n@@ -622,21 +674,66 @@ static int mxl862xx_setup(struct dsa_switch *ds)\n \tint n_user_ports = 0, max_vlans;\n \tint ingress_finals, vid_rules;\n \tstruct dsa_port *dp;\n-\tint ret, i;\n-\n-\tret = mxl862xx_reset(priv);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\tret = mxl862xx_wait_ready(ds);\n-\tif (ret)\n-\t\treturn ret;\n+\tint ret, i, rescue;\n \n+\t/* The SerDes state is software-only and must exist before anything can\n+\t * reach phylink, including a rescue-mode flash clearing rescue_mode\n+\t * ahead of the reprobe.\n+\t */\n \tmutex_init(\u0026priv-\u003eserdes_lock);\n \tfor (i = 0; i \u003c ARRAY_SIZE(priv-\u003eserdes_ports); i++)\n \t\tmxl862xx_setup_pcs(priv, \u0026priv-\u003eserdes_ports[i],\n \t\t\t\t   i + MXL862XX_FIRST_SERDES_PORT);\n \n+\t/* Detect the loader over SB PDI first: it needs no firmware, unlike the\n+\t * C45 API (mxl862xx_reset/wait_ready) which spews CRC errors when none\n+\t * answers. Touch C45 only once rescue is ruled out.\n+\t */\n+\trescue = mxl862xx_rescue_mode_detect(priv);\n+\tif (rescue \u003c 0) {\n+\t\tdev_err(ds-\u003edev, \"switch state detection failed: %pe\\n\",\n+\t\t\tERR_PTR(rescue));\n+\t\treturn rescue;\n+\t}\n+\n+\tif (rescue == MXL862XX_NOT_RESCUE) {\n+\t\tret = mxl862xx_reset(priv);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = mxl862xx_wait_ready(ds);\n+\t\tif (ret) {\n+\t\t\t/* the reset may only now have triggered rescue mode */\n+\t\t\trescue = mxl862xx_rescue_mode_detect(priv);\n+\t\t\tif (rescue \u003c 0) {\n+\t\t\t\tdev_err(ds-\u003edev,\n+\t\t\t\t\t\"switch not responding after reset: %pe\\n\",\n+\t\t\t\t\tERR_PTR(rescue));\n+\t\t\t\treturn rescue;\n+\t\t\t}\n+\t\t\tif (rescue == MXL862XX_NOT_RESCUE)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tpriv-\u003erescue_mode = rescue;\n+\n+\tif (priv-\u003erescue_mode) {\n+\t\tif (priv-\u003erescue_ready) {\n+\t\t\tdev_warn(ds-\u003edev,\n+\t\t\t\t \"switch in MCUboot rescue mode, use devlink to flash new firmware\\n\");\n+\t\t} else {\n+\t\t\t/* Drain the wedged download in the background so it\n+\t\t\t * never holds the devlink lock; info and flash become\n+\t\t\t * available once ready.\n+\t\t\t */\n+\t\t\tdev_warn(ds-\u003edev,\n+\t\t\t\t \"switch in MCUboot with an interrupted download, recovering in background\\n\");\n+\t\t\tqueue_work(system_long_wq, \u0026priv-\u003erescue_heal_work);\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n \t/* Calculate Extended VLAN block sizes.\n \t * With VLAN Filter handling VID membership checks:\n \t *   Ingress: only final catchall rules (PVID insertion, 802.1Q\n@@ -715,11 +812,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)\n static int mxl862xx_port_enable(struct dsa_switch *ds, int port,\n \t\t\t\tstruct phy_device *phydev)\n {\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (priv-\u003erescue_mode)\n+\t\treturn 0;\n+\n \treturn mxl862xx_port_state(ds, port, true);\n }\n \n static void mxl862xx_port_disable(struct dsa_switch *ds, int port)\n {\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (priv-\u003erescue_mode)\n+\t\treturn;\n+\n \tif (mxl862xx_port_state(ds, port, false))\n \t\tdev_err(ds-\u003edev, \"failed to disable port %d\\n\", port);\n }\n@@ -1337,6 +1444,17 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)\n \tbool is_cpu_port = dsa_port_is_cpu(dp);\n \tint ret;\n \n+\tif (dsa_port_is_dsa(dp)) {\n+\t\tdev_err(ds-\u003edev, \"port %d: DSA links not supported\\n\", port);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\t/* DSA reinits failed user ports as unused; shared ports must\n+\t * succeed for the tree to register.\n+\t */\n+\tif (priv-\u003erescue_mode)\n+\t\treturn dsa_port_is_user(dp) ? -ENODEV : 0;\n+\n \tret = mxl862xx_port_state(ds, port, false);\n \tif (ret)\n \t\treturn ret;\n@@ -1346,11 +1464,6 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)\n \tif (dsa_port_is_unused(dp))\n \t\treturn 0;\n \n-\tif (dsa_port_is_dsa(dp)) {\n-\t\tdev_err(ds-\u003edev, \"port %d: DSA links not supported\\n\", port);\n-\t\treturn -EOPNOTSUPP;\n-\t}\n-\n \tret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);\n \tif (ret)\n \t\treturn ret;\n@@ -1572,6 +1685,12 @@ static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,\n \tether_addr_copy(qparam.mac, mdb-\u003eaddr);\n \n \tret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);\n+\t/* Post-flash teardown or MCUboot: the firmware and its MAC table are\n+\t * gone, so there is nothing left to delete. Outside those, -ENODEV is a\n+\t * bus error and must be reported.\n+\t */\n+\tif (ret == -ENODEV \u0026\u0026 (priv-\u003eskip_teardown || priv-\u003erescue_mode))\n+\t\treturn 0;\n \tif (ret)\n \t\treturn ret;\n \n@@ -1628,6 +1747,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,\n \tstruct mxl862xx_priv *priv = ds-\u003epriv;\n \tint ret;\n \n+\tif (priv-\u003erescue_mode)\n+\t\treturn;\n+\n \tswitch (state) {\n \tcase BR_STATE_DISABLED:\n \t\tparam.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);\n@@ -2086,6 +2208,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {\n \t.get_pause_stats = mxl862xx_get_pause_stats,\n \t.get_rmon_stats = mxl862xx_get_rmon_stats,\n \t.get_stats64 = mxl862xx_get_stats64,\n+\t.devlink_info_get = mxl862xx_devlink_info_get,\n+\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n };\n \n static int mxl862xx_probe(struct mdio_device *mdiodev)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h\nindex 432a5f3f2e08e..bdaf295c8a9f6 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h\n@@ -4,7 +4,9 @@\n #define __MXL862XX_H\n \n #include \u003casm/byteorder.h\u003e\n+#include \u003clinux/bitops.h\u003e\n #include \u003clinux/mdio.h\u003e\n+#include \u003clinux/mutex.h\u003e\n #include \u003clinux/workqueue.h\u003e\n #include \u003cnet/dsa.h\u003e\n \n@@ -14,6 +16,10 @@ struct mxl862xx_priv;\n #define MXL862XX_FIRST_SERDES_PORT\t9\n #define MXL862XX_SERDES_SLOTS\t\t4\n \n+/* mxl862xx_rescue_mode_detect() return codes (negative values are errors) */\n+#define MXL862XX_NOT_RESCUE\t\t0\n+#define MXL862XX_IN_RESCUE\t\t1\n+\n #define MXL862XX_DEFAULT_BRIDGE\t\t0\n #define MXL862XX_MAX_BRIDGES\t\t48\n #define MXL862XX_MAX_BRIDGE_PORTS\t128\n@@ -303,6 +309,10 @@ struct mxl862xx_fw_version {\n  *                      flooding)\n  * @fw_version:         cached firmware version, populated at probe and\n  *                      compared with MXL862XX_FW_VER_MIN()\n+ * @asic_id:            chip part number read from the CHIP ID registers,\n+ *                      reported as the devlink \"asic.id\" fixed version\n+ * @asic_rev:           chip version read from the CHIP ID registers,\n+ *                      reported as the devlink \"asic.rev\" fixed version\n  * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of\n  *                      10G_QXGMII or QSGMII\n  * @serdes_refcount:    per-XPCS count of sub-ports enabled by phylink;\n@@ -319,6 +329,22 @@ struct mxl862xx_fw_version {\n  * @evlan_ingress_size: per-port ingress Extended VLAN block size\n  * @evlan_egress_size:  per-port egress Extended VLAN block size\n  * @vf_block_size:      per-port VLAN Filter block size\n+ * @block_host:         reject firmware API commands (except FW_UPDATE)\n+ *                      during a firmware flash\n+ * @flash_reading:      let the flash path's own firmware reads through\n+ *                      block_host while polling the freshly booted image\n+ * @skip_teardown:      discard firmware API commands during the teardown\n+ *                      triggered by the post-flash reprobe\n+ * @rescue_mode:        switch is in MCUboot; firmware API commands fail fast,\n+ *                      only clause-22 SMDIO works. Set from setup() before the\n+ *                      switch is registered and cleared under the MDIO bus lock\n+ *                      for the benefit of mxl862xx_api_wrap(); other readers\n+ *                      only need it to be a stable single flag.\n+ * @rescue_ready:       (rescue_mode) loader is at a clean READY and will accept\n+ *                      a flash; false while rescue_heal_work is draining\n+ * @rescue_failed:      (rescue_mode) the self-heal gave up; the loader needs a\n+ *                      power cycle and no flash can be accepted\n+ * @rescue_heal_work:   background self-heal draining a wedged download to READY\n  * @stats_work:         periodic work item that polls RMON hardware counters\n  *                      and accumulates them into 64-bit per-port stats\n  */\n@@ -326,9 +352,12 @@ struct mxl862xx_priv {\n \tstruct dsa_switch *ds;\n \tstruct mdio_device *mdiodev;\n \tstruct work_struct crc_err_work;\n+\tstruct work_struct rescue_heal_work;\n \tunsigned long flags;\n \tu16 drop_meter;\n \tstruct mxl862xx_fw_version fw_version;\n+\tu16 asic_id;\n+\tu8 asic_rev;\n \tstruct mxl862xx_pcs serdes_ports[8];\n \tint serdes_refcount[2];\n \tstruct mutex serdes_lock;\n@@ -337,7 +366,15 @@ struct mxl862xx_priv {\n \tu16 evlan_ingress_size;\n \tu16 evlan_egress_size;\n \tu16 vf_block_size;\n+\tbool block_host;\n+\tbool flash_reading;\n+\tbool skip_teardown;\n+\tbool rescue_mode;\n+\tbool rescue_ready;\n+\tbool rescue_failed;\n \tstruct delayed_work stats_work;\n };\n \n+int mxl862xx_wait_ready(struct dsa_switch *ds);\n+\n #endif /* __MXL862XX_H */\ndiff --git a/include/linux/device.h b/include/linux/device.h\nindex aee79fd6b32b4..7a99169505772 100644\n--- a/include/linux/device.h\n+++ b/include/linux/device.h\n@@ -1314,6 +1314,8 @@ int  __must_check device_attach(struct device *dev);\n int __must_check driver_attach(const struct device_driver *drv);\n void device_initial_probe(struct device *dev);\n int __must_check device_reprobe(struct device *dev);\n+int __must_check device_schedule_reprobe(struct device *dev,\n+\t\t\t\t\t unsigned int delay_ms);\n \n bool device_is_bound(struct device *dev);\n \ndiff --git a/include/net/dsa.h b/include/net/dsa.h\nindex 7507d632e7c6d..9babab92a0fc6 100644\n--- a/include/net/dsa.h\n+++ b/include/net/dsa.h\n@@ -1174,6 +1174,9 @@ struct dsa_switch_ops {\n \tint\t(*devlink_info_get)(struct dsa_switch *ds,\n \t\t\t\t    struct devlink_info_req *req,\n \t\t\t\t    struct netlink_ext_ack *extack);\n+\tint\t(*devlink_flash_update)(struct dsa_switch *ds,\n+\t\t\t\t\tstruct devlink_flash_update_params *params,\n+\t\t\t\t\tstruct netlink_ext_ack *extack);\n \tint\t(*devlink_sb_pool_get)(struct dsa_switch *ds,\n \t\t\t\t       unsigned int sb_index, u16 pool_index,\n \t\t\t\t       struct devlink_sb_pool_info *pool_info);\ndiff --git a/net/dsa/devlink.c b/net/dsa/devlink.c\nindex ed342f345692b..25311a87cbc56 100644\n--- a/net/dsa/devlink.c\n+++ b/net/dsa/devlink.c\n@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,\n \treturn -EOPNOTSUPP;\n }\n \n+static int dsa_devlink_flash_update(struct devlink *dl,\n+\t\t\t\t    struct devlink_flash_update_params *params,\n+\t\t\t\t    struct netlink_ext_ack *extack)\n+{\n+\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n+\n+\tif (!ds-\u003eops-\u003edevlink_flash_update)\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn ds-\u003eops-\u003edevlink_flash_update(ds, params, extack);\n+}\n+\n static int dsa_devlink_sb_pool_get(struct devlink *dl,\n \t\t\t\t   unsigned int sb_index, u16 pool_index,\n \t\t\t\t   struct devlink_sb_pool_info *pool_info)\n@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,\n \n static const struct devlink_ops dsa_devlink_ops = {\n \t.info_get\t\t\t= dsa_devlink_info_get,\n+\t.flash_update\t\t\t= dsa_devlink_flash_update,\n \t.sb_pool_get\t\t\t= dsa_devlink_sb_pool_get,\n \t.sb_pool_set\t\t\t= dsa_devlink_sb_pool_set,\n \t.sb_port_pool_get\t\t= dsa_devlink_sb_port_pool_get,\n"
}

2/1 2026/09/07 23:29 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces firmware flashing, MCUboot rescue mode detection/recovery, and devlink flash update support for MaxLinear MxL862xx DSA Ethernet switches. It also adds a DSA devlink flash update dispatch helper in net/dsa/devlink.c and a deferred device reprobe helper (device_schedule_reprobe) in the driver core. The modified driver requires physical MaxLinear MxL862xx switch hardware on an MDIO bus, which is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE), and the generic devlink/driver-core helpers are only exercised by this driver.",
  "WorthFuzzing": false
}

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

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

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

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

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

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

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

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

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

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

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

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

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

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit d15203d65c8d310d843fbc4052292690ec607c64
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Sep 7 23:29:53 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index d4a83fdcff7fe..145cb8a4becfb 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -95,6 +95,7 @@ parameters, info versions, and other features it supports.
    mlx5
    mlxsw
    mv88e6xxx
+   mxl862xx
    netdevsim
    nfp
    octeontx2
diff --git a/Documentation/networking/devlink/mxl862xx.rst b/Documentation/networking/devlink/mxl862xx.rst
new file mode 100644
index 0000000000000..5b24d8cb57bc9
--- /dev/null
+++ b/Documentation/networking/devlink/mxl862xx.rst
@@ -0,0 +1,74 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================
+mxl862xx devlink support
+========================
+
+This document describes the devlink features implemented by the
+``mxl862xx`` device driver.
+
+Info versions
+=============
+
+The ``mxl862xx`` driver reports the following versions
+
+.. list-table:: devlink info versions implemented
+   :widths: 5 5 5 85
+
+   * - Name
+     - Type
+     - Example
+     - Description
+   * - ``asic.id``
+     - fixed
+     - 8628
+     - The chip part number read from the CHIP ID registers. Omitted
+       when the part number reads as zero, which happens for a switch
+       sitting in MCUboot rescue mode (the registers need a running
+       firmware), for an unfused part, and after a failed flash.
+   * - ``asic.rev``
+     - fixed
+     - 0
+     - The chip version read from the same register word, so it is
+       omitted whenever ``asic.id`` is.
+   * - ``fw``
+     - running, stored
+     - 1.0.70
+     - Version of the firmware running on the switch, reported as both
+       running and stored since the switch boots it from its own flash.
+       It is omitted while no firmware version is known: after a failed
+       flash, and in MCUboot rescue mode while an interrupted download
+       is still being recovered in the background. Once the loader is
+       ready to accept a new image the version appears as "0.0.0",
+       which no released firmware reports, so version-comparing tools
+       offer any available release as an upgrade. Use ``devlink dev
+       flash`` to tell a recovering switch from a ready one, see below;
+       a missing version on its own does not say why.
+
+Flash update
+============
+
+The ``mxl862xx`` driver implements support for ``devlink dev flash``.
+The signed firmware image is transferred to the switch over the same
+MDIO bus which is also used to manage the switch, then verified and
+installed by the MCUboot bootloader running on the switch. All ports
+of the switch are closed for the duration of the update and the driver
+reprobes the switch after it has rebooted into the new firmware. A
+complete flash and reprobe cycle takes about one minute.
+
+A switch stuck in MCUboot rescue mode, e.g. after an interrupted
+update, is registered without user ports. If the previous download was
+interrupted mid-transfer the loader is wedged; the driver drains it
+back to a clean ready state in the background, one byte at a time,
+which takes tens of minutes for a large image and is reported through
+the kernel log as it progresses. During that recovery ``devlink dev
+flash`` returns ``-EBUSY`` with an extack message saying so, and
+``devlink dev info`` reports no firmware version. Once the loader is
+ready the firmware version appears and flashing a firmware image
+through the regular update flow recovers the switch.
+
+If the recovery fails, the loader needs a power cycle: ``devlink dev
+flash`` then returns ``-EIO`` and says so in its extack message. The
+driver only re-examines the switch when it binds, so on a board where
+the switch can be power cycled on its own, unbind and rebind the driver
+afterwards to have the recovered switch recognised.
diff --git a/MAINTAINERS b/MAINTAINERS
index b23fb6f2f4ef4..c97e710dda0c8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16246,6 +16246,7 @@ M:	Daniel Golle <daniel@makrotopia.org>
 L:	netdev@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml
+F:	Documentation/networking/devlink/mxl862xx.rst
 F:	drivers/net/dsa/mxl862xx/
 F:	net/dsa/tag_mxl862xx.c
 
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff02..6234e37de7e99 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -106,6 +106,10 @@ struct driver_private {
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
+ * @shutdown_done: Set once device_shutdown() has reached this device, under
+ *	  the device lock, before any shutdown callback runs. Read under the
+ *	  device lock. A deferred re-probe scheduled with
+ *	  device_schedule_reprobe() must not detach the device anymore.
  *
  * Nothing outside of the driver core should ever touch these fields.
  */
@@ -120,6 +124,7 @@ struct device_private {
 	char *deferred_probe_reason;
 	struct device *device;
 	u8 dead:1;
+	u8 shutdown_done:1;
 };
 #define to_device_private_parent(obj)	\
 	container_of(obj, struct device_private, knode_parent)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4c0c373998a19..3dcd8a3c3aa55 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4910,6 +4910,9 @@ void device_shutdown(void)
 			device_lock(parent);
 		device_lock(dev);
 
+		if (dev->p)
+			dev->p->shutdown_done = true;
+
 		/* Don't allow any more runtime suspends */
 		pm_runtime_get_noresume(dev);
 		pm_runtime_barrier(dev);
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index f6525a7ee8c54..81a3433f726e8 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -1315,7 +1315,7 @@ EXPORT_SYMBOL_GPL(driver_attach);
  * __device_release_driver() must be called with @dev lock held.
  * When called for a USB interface, @dev->parent lock must be held as well.
  */
-static void __device_release_driver(struct device *dev, struct device *parent)
+static bool __device_release_driver(struct device *dev, struct device *parent)
 {
 	struct device_driver *drv;
 
@@ -1336,7 +1336,7 @@ static void __device_release_driver(struct device *dev, struct device *parent)
 			 */
 			if (dev->driver != drv) {
 				pm_runtime_put(dev);
-				return;
+				return false;
 			}
 		}
 
@@ -1359,7 +1359,10 @@ static void __device_release_driver(struct device *dev, struct device *parent)
 
 		bus_notify(dev, BUS_NOTIFY_UNBOUND_DRIVER);
 		kobject_uevent(&dev->kobj, KOBJ_UNBIND);
+		return true;
 	}
+
+	return false;
 }
 
 void device_release_driver_internal(struct device *dev,
@@ -1436,3 +1439,116 @@ void driver_detach(const struct device_driver *drv)
 		put_device(dev);
 	}
 }
+
+struct device_reprobe {
+	struct delayed_work work;
+	struct device *dev;
+	struct device *parent;
+	const struct device_driver *drv;
+	bool parent_lock;
+};
+
+static void device_reprobe_work_fn(struct work_struct *work)
+{
+	struct device_reprobe *rp = container_of(work, struct device_reprobe,
+						 work.work);
+	struct device *parent = rp->parent;
+	struct device *dev = rp->dev;
+	bool detached = false;
+	int ret;
+
+	/*
+	 * A device unregistered before the work runs may have outlived the
+	 * module providing its bus type, so dev->bus is not read until the
+	 * device is known to be live.
+	 */
+	if (rp->parent_lock)
+		device_lock(parent);
+	device_lock(dev);
+	/*
+	 * rp->drv is only ever compared, never dereferenced: the driver it
+	 * points to may have been unregistered and freed by now.
+	 */
+	if (!dev->p->dead && !dev->p->shutdown_done && dev->driver == rp->drv)
+		detached = __device_release_driver(dev, parent);
+	device_unlock(dev);
+	if (rp->parent_lock)
+		device_unlock(parent);
+
+	if (detached) {
+		/*
+		 * device_attach() must run with the parent locked on buses
+		 * that require it, mirroring bus_rescan_devices_helper().
+		 */
+		if (rp->parent_lock)
+			device_lock(parent);
+		ret = device_attach(dev);
+		if (ret < 0)
+			dev_err_probe(dev, ret,
+				      "re-probe failed, device left unbound\n");
+		if (rp->parent_lock)
+			device_unlock(parent);
+	}
+
+	put_device(dev);
+	put_device(parent);
+	kfree(rp);
+}
+
+/**
+ * device_schedule_reprobe - schedule a deferred detach and re-probe
+ * @dev: device to detach and re-probe
+ * @delay_ms: delay in milliseconds before the re-probe runs
+ *
+ * Schedule a detach and re-probe of @dev after @delay_ms milliseconds.
+ * The re-probe is skipped if, by the time the scheduled work runs, the
+ * device has been removed, the system shutdown sequence has reached the
+ * device, or @dev is no longer bound to the driver that was bound at
+ * scheduling time. In particular an administrative unbind is never
+ * undone by a stale re-probe.
+ *
+ * The work function is built-in text, so the bound driver may call this
+ * from its own code without holding a module reference. If the driver
+ * module is unloaded before the work runs, driver unregistration unbinds
+ * @dev first and the scheduled work does nothing.
+ *
+ * Multiple pending re-probes for the same device are individually safe;
+ * a caller that wants at most one pending re-probe must gate scheduling
+ * itself.
+ *
+ * The work is freezable: a re-probe pending across system suspend runs
+ * once the system has resumed.
+ *
+ * May only be called from process context.
+ *
+ * Returns: 0 on success, -EINVAL if @dev is not a registered device
+ * bound to a driver, -ENOMEM on allocation failure.
+ */
+int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)
+{
+	struct device_reprobe *rp;
+
+	if (!dev->bus || !dev->p || !device_is_registered(dev))
+		return -EINVAL;
+	if (!dev->driver)
+		return -EINVAL;
+
+	rp = kzalloc_obj(*rp);
+	if (!rp)
+		return -ENOMEM;
+
+	rp->dev = get_device(dev);
+	/*
+	 * Pin the parent too: the work locks it, and an unregister of @dev
+	 * would otherwise drop the last reference before the work runs.
+	 */
+	rp->parent = get_device(dev->parent);
+	rp->parent_lock = dev->parent && dev->bus->need_parent_lock;
+	rp->drv = READ_ONCE(dev->driver);
+	INIT_DELAYED_WORK(&rp->work, device_reprobe_work_fn);
+	queue_delayed_work(system_freezable_wq, &rp->work,
+			   msecs_to_jiffies(delay_ms));
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(device_schedule_reprobe);
diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile
index a7be0e6669dfa..bccac0d0f703f 100644
--- a/drivers/net/dsa/mxl862xx/Makefile
+++ b/drivers/net/dsa/mxl862xx/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index a180a5decffc0..6f771895984cb 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -1224,6 +1224,16 @@ struct mxl862xx_sys_fw_image_version {
 	__le32 iv_build_num;
 } __packed;
 
+/**
+ * struct mxl862xx_sys_reg_rw - System register read/write
+ * @addr: 32-bit register address
+ * @val: register value
+ */
+struct mxl862xx_sys_reg_rw {
+	__le32 addr;
+	__le32 val;
+} __packed;
+
 /**
  * enum mxl862xx_port_type - Port Type
  * @MXL862XX_LOGICAL_PORT: Logical Port
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c48..a865425aa61e1 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -70,7 +70,9 @@
 #define INT_GPHY_READ			(GPY_GPY2XX_MAGIC + 0x1)
 #define INT_GPHY_WRITE			(GPY_GPY2XX_MAGIC + 0x2)
 
+#define SYS_MISC_FW_UPDATE		(SYS_MISC_MAGIC + 0x1)
 #define SYS_MISC_FW_VERSION		(SYS_MISC_MAGIC + 0x2)
+#define SYS_MISC_REG_RD			(SYS_MISC_MAGIC + 0x8)
 
 #define MXL862XX_XPCS_PCS_CONFIG	(MXL862XX_XPCS_MAGIC + 0x1)
 #define MXL862XX_XPCS_PCS_GET_STATE	(MXL862XX_XPCS_MAGIC + 0x2)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
new file mode 100644
index 0000000000000..042a4eee6af81
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,1062 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Firmware flash and devlink support for MaxLinear MxL862xx
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ *
+ * SB PDI - firmware download interface over clause-22 SMDIO
+ * =========================================================
+ *
+ * The MxL862xx MCUboot loader accepts a firmware image through four "SB PDI"
+ * registers in the switch SMDIO register space. It runs whenever no WSP
+ * firmware is active: the normal firmware update enters it deliberately - the
+ * SYS_MISC_FW_UPDATE API command sets a sticky rescue bit and reboots into
+ * MCUboot - and the loader also stays here when the stored WSP firmware fails
+ * its boot-time integrity check. This driver drives the loader's 0xc55c
+ * "console" download path.
+ *
+ * SMDIO register access (mxl862xx_smdio_read/write):
+ *   MII reg 0x1f := (<sb_pdi_reg> & 0xfff0)   ; page latch
+ *   MII reg (<sb_pdi_reg> & 0x000f) := / => <u16 data>
+ * so CTRL/ADDR/DATA/STAT (0xe100..0xe103) are MII regs 0/1/2/3 of page
+ * 0xe100, not all reg 0x00.
+ *
+ * SB PDI registers (host name/addr  ->  MCU mailbox):
+ *   CTRL 0xe100 -> 0xc0938400   mode: RST=0x00  RD=0x01  WR=0x02
+ *   ADDR 0xe101 -> 0xc0938404   SB target word address (SB1 bank = 0x7800)
+ *   DATA 0xe102 -> 0xc0938408   16-bit data / reply word
+ *   STAT 0xe103 -> 0xc093840c   handshake: a magic (below) or a byte count
+ *
+ * STAT magics:
+ *   READY  0xc55c   loader idle in the console loop        (this driver)
+ *   DL_RDY 0xc33c   loader idle in the flashless loop
+ *   START  0xf48f   host   -> begin download session
+ *   ACK    0xf490   loader -> START acknowledged (START + 1)
+ *   END    0x3cc3   host   -> finalise now (optional, see below)
+ *   RDREG  0xe2c0   host   -> register-read command (| index), see below
+ *
+ * Console flash path (STAT=0xc55c) - mxl862xx_flash_firmware():
+ *
+ *   host                                   loader
+ *   ----                                   ------
+ *   reset (CTRL=ADDR=DATA=0)
+ *   read STAT ............................ 0xc55c   (READY, idle)
+ *   STAT := START(0xf48f)  -------------->
+ *                          <-------------- STAT = 0xf490 (ACK)
+ *   CTRL := WR
+ *   DATA := hdr[0..9]  (20-byte header: type,size1,crc1,size2,crc2)
+ *   reset; STAT := 20 (header len)  -----> parse hdr; r_remain=size1+size2;
+ *                                          ERASE target region(s)
+ *                          <-------------- STAT=21 (len+1), then STAT=0
+ *                                          (erased)
+ *   -- payload, streamed in slices: --
+ *   CTRL := WR
+ *   DATA := word x N ...
+ *     at word 16384: CTRL:=RST; ADDR:=0x7800; CTRL:=WR  (half-bank -> SB1)
+ *     at word 32760: flush slice:
+ *        reset; STAT := <bytes_this_slice> ---> r_remain -= bytes; program
+ *                          <------------------- STAT=0  (ready for next slice)
+ *   ... repeat until the whole payload is sent ...
+ *                          <------------------- STAT=0  image verified
+ *                                               (STAT=1: image rejected)
+ *   STAT := END(0x3cc3)  ---------------------> finalise and boot
+ *
+ * The r_remain == 0 rule (critical):
+ *   Every host STAT write in the payload phase is a byte count; the loader
+ *   does r_remain -= count and stays in the receive loop while r_remain != 0.
+ *   It leaves the loop ONLY when r_remain hits EXACTLY 0, and a count larger
+ *   than r_remain underflows the 32-bit counter and wedges the loader until a
+ *   power cycle. Having left it, the loader verifies the image, publishes the
+ *   verdict in STAT (0 good, 1 rejected) and waits 2 s for END before
+ *   finalising regardless -- clearing its rescue-enable bit so boot_go boots
+ *   the new image -- so END only saves that wait. Hence:
+ *     - never send a slice/chunk count larger than what is outstanding;
+ *     - a STAT write is a command only once the loader has left the loop;
+ *     - the loader leaves the count in STAT while it programs the chunk, so
+ *       a lingering count does not distinguish "busy" from "verdict";
+ *     - interrupted-download recovery feeds 1 byte at a time (see below).
+ *
+ * Interrupted-flash recovery (mxl862xx_rescue_drain):
+ *   A host that dies mid-payload leaves the loader in the receive loop holding
+ *   STAT=0. Feed single 1-byte chunks (one DATA word + STAT=1) until r_remain
+ *   reaches 0; the loader then verifies the (now corrupt) image, publishes its
+ *   verdict and comes back to READY by itself. END is never sent here: while
+ *   r_remain is non-zero it would be consumed as a 15555-byte count, and a
+ *   lingering STAT=1 cannot be told from a chunk still being programmed.
+ *
+ * Register-read challenge (non-destructive liveness proof):
+ *   DATA := 0x7c23 (marker); STAT := 0xe2c0|idx
+ *     -> loader returns a runtime word in DATA and re-arms STAT=0xc55c.
+ *   The reply source is loader BSS, not a chip id; used only to prove a live
+ *   mailbox in mxl862xx_rescue_mode_detect().
+ *
+ * The other STAT ready magic, 0xc33c, marks the loader's flashless
+ * chip-to-chip download mode (MxL86281S 16-port tier); this driver does not
+ * use it.
+ *
+ * Rescue lifecycle (devlink): probe runs mxl862xx_rescue_mode_detect(); a
+ * wedged loader is drained back to READY by a background self-heal
+ * (rescue_heal_work), so the long recovery never holds the devlink lock.
+ * devlink dev info exposes the fw version (the "flashable" signal) only once at
+ * READY; flash_update returns -EBUSY until then, and reprobes to WSP firmware
+ * on success.
+ *
+ * Notes:
+ *   - Chip id/revision (0xc0d28884/88) are NOT reachable on this channel; they
+ *     need the clause-45 MMD firmware mailbox, which is dead under MCUboot.
+ *     Rescue identity is by SB PDI behaviour only (mxl862xx_rescue_mode_detect).
+ *   - The SMDIO PHY address comes from the device tree; the 0xe1xx register
+ *     offsets are the OTP reset defaults and the only layout supported here.
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/netdevice.h>
+#include <linux/overflow.h>
+#include <linux/rtnetlink.h>
+#include <linux/workqueue.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "mxl862xx.h"
+#include "mxl862xx-api.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
+#include "mxl862xx-host.h"
+
+/* SB PDI registers (clause-22 SMDIO address space) */
+#define MXL862XX_SB_PDI_CTRL		0xe100
+#define MXL862XX_SB_PDI_ADDR		0xe101
+#define MXL862XX_SB_PDI_DATA		0xe102
+#define MXL862XX_SB_PDI_STAT		0xe103
+
+/* SB PDI CTRL modes */
+#define MXL862XX_SB_PDI_CTRL_RST	0x00
+#define MXL862XX_SB_PDI_CTRL_WR		0x02
+
+/* SB PDI handshake magic (published/consumed via STAT) */
+#define MXL862XX_SB_PDI_READY		0xc55c	/* loader idle, console loop */
+#define MXL862XX_SB_PDI_DL_READY	0xc33c	/* loader idle, flashless loop */
+#define MXL862XX_SB_PDI_START		0xf48f
+#define MXL862XX_SB_PDI_END		0x3cc3
+#define MXL862XX_SB_PDI_RDREG		0xe2c0	/* register-read cmd (| index) */
+#define MXL862XX_SB_PDI_RDREG_MARK	0x7c23	/* marker placed in DATA for RDREG */
+
+/* Behavioural presence probe: two distinct 16-bit latches on ADDR/DATA. */
+#define MXL862XX_SB_PDI_PROBE_A		0x5a5a
+#define MXL862XX_SB_PDI_PROBE_D		0xa5a5
+
+/* Image verification verdict published in STAT once the receive loop ends */
+#define MXL862XX_SB_PDI_VERIFY_OK	0
+#define MXL862XX_SB_PDI_VERIFY_BAD	1
+
+/* Firmware transfer geometry */
+#define MXL862XX_FW_HDR_SIZE		20
+#define MXL862XX_FW_BANK_HALF		16384	/* words per half-bank */
+#define MXL862XX_FW_BANK_SLICE		32760	/* words per full slice */
+#define MXL862XX_FW_SB1_ADDR		0x7800	/* SB1 word address */
+
+/* Timeouts (generous upper bounds) */
+#define MXL862XX_FW_READY_TIMEOUT_MS	3000
+#define MXL862XX_FW_ACK_TIMEOUT_MS	5000
+#define MXL862XX_FW_ERASE_TIMEOUT_MS	300000
+#define MXL862XX_FW_WRITE_TIMEOUT_MS	60000
+#define MXL862XX_FW_REBOOT_DELAY_MS	5000
+#define MXL862XX_FW_REPROBE_DELAY_MS	500
+/* One loader mailbox step: program a 1-byte chunk or service a command */
+#define MXL862XX_SB_PDI_STEP_MS		2000
+/* Covers the loader's END wait, verification and the reset into READY */
+#define MXL862XX_SB_PDI_VERIFY_MS	15000
+
+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+	int ret;
+
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+				   MXL862XX_SB_PDI_CTRL_RST);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+				   MXL862XX_SB_PDI_CTRL_RST);
+	if (ret < 0)
+		return ret;
+
+	return mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+				    MXL862XX_SB_PDI_CTRL_RST);
+}
+
+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
+				     unsigned long timeout_ms)
+{
+	int ret, val;
+
+	ret = read_poll_timeout(mxl862xx_smdio_read, val,
+				val < 0 || (u16)val == expected,
+				10000, timeout_ms * 1000, false,
+				priv, MXL862XX_SB_PDI_STAT);
+	if (val < 0)
+		return val;
+	return ret;
+}
+
+static int mxl862xx_sb_pdi_flush_slice(struct mxl862xx_priv *priv,
+				       u32 data_written)
+{
+	int ret;
+
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);
+	if (ret < 0)
+		return ret;
+
+	return mxl862xx_sb_pdi_poll_stat(priv, 0,
+					 MXL862XX_FW_WRITE_TIMEOUT_MS);
+}
+
+/* Flush the last slice, which ends the receive loop: the loader verifies the
+ * image and replaces the count in STAT with its verdict, so wait for the count
+ * to go rather than for a fixed value.
+ */
+static int mxl862xx_sb_pdi_flush_last(struct mxl862xx_priv *priv,
+				      u32 data_written)
+{
+	int ret, val;
+
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);
+	if (ret < 0)
+		return ret;
+
+	ret = read_poll_timeout(mxl862xx_smdio_read, val,
+				val < 0 || (u16)val != (u16)data_written,
+				10000, (MXL862XX_FW_WRITE_TIMEOUT_MS +
+					MXL862XX_SB_PDI_VERIFY_MS) * 1000,
+				false, priv, MXL862XX_SB_PDI_STAT);
+	if (val < 0)
+		return val;
+
+	if (!ret && (u16)val == MXL862XX_SB_PDI_VERIFY_OK)
+		return 0;
+
+	/* A final count of 1 is indistinguishable from the reject verdict, so
+	 * a timeout still holding it lands here too.
+	 */
+	if ((u16)val == MXL862XX_SB_PDI_VERIFY_BAD) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: loader rejected the image\n");
+		return -EBADMSG;
+	}
+
+	return ret ? ret : -EPROTO;
+}
+
+static void mxl862xx_flash_notify(struct devlink *dl, const char *status,
+				  u32 done, u32 total)
+{
+	devlink_flash_update_status_notify(dl, status, NULL, done, total);
+}
+
+/* Byte-count of each chunk fed to the loader during drain. It MUST be 1: the
+ * loader only lets us observe "counter == 0", never "counter < step", so any
+ * step > 1 can subtract past zero, underflow the 32-bit counter and wedge the
+ * loader for ~2^32 more bytes (a state only a power cycle clears). Stepping by
+ * 1 walks the counter through every value and is guaranteed to land on zero
+ * whatever its (possibly odd) start. A 1-byte chunk is a path the loader
+ * already handles: the normal transfer ends with a single trailing byte for
+ * odd-sized images (see Step 6).
+ */
+#define MXL862XX_DRAIN_CHUNK_BYTES	1
+
+/* Log the drain's progress every so many bytes; it can run for a long time */
+#define MXL862XX_DRAIN_LOG_BYTES	(128 * 1024)
+
+/* Wait for the loader to ask for the next chunk (STAT 0) or to come back to its
+ * command loop (STAT READY), and return the STAT value either way. On timeout
+ * that is whatever STAT still holds, which carries no further information: the
+ * loader keeps the count we wrote visible while it programs the chunk, and that
+ * is the same value it publishes as the "image rejected" verdict once the
+ * counter reaches zero.
+ *
+ * STAT 0 is unambiguous here even though it is also the "image verified"
+ * verdict: by the r_remain == 0 rule the loader leaves the receive loop the
+ * moment the counter reaches zero, so it is never both inside the loop asking
+ * for a chunk and publishing a verdict. Once it has left, the next STAT write
+ * is a command rather than a count, so feeding one more chunk after a verdict
+ * cannot underflow anything either.
+ */
+static int mxl862xx_sb_pdi_poll_drain(struct mxl862xx_priv *priv,
+				      unsigned long timeout_ms)
+{
+	int val;
+
+	read_poll_timeout(mxl862xx_smdio_read, val,
+			  val < 0 || (u16)val == MXL862XX_SB_PDI_READY ||
+			  (u16)val == 0,
+			  50, timeout_ms * 1000, false,
+			  priv, MXL862XX_SB_PDI_STAT);
+	if (val < 0)
+		return val;
+	return (u16)val;
+}
+
+/* The loader is not asking for a chunk: it may still be programming the last
+ * one, or the counter has reached zero and it is verifying the image and
+ * resetting into READY. Wait that out -- STAT cannot tell the two apart, and
+ * guessing would mean writing END into a live receive loop.
+ *
+ * Return: 0 once the loader has left the loop, -EAGAIN if it asks for another
+ * chunk after all, -EIO for a loader still holding the count when the verify
+ * window expires, or an SMDIO bus error.
+ */
+static int mxl862xx_rescue_drain_finish(struct mxl862xx_priv *priv, u32 chunk)
+{
+	struct device *dev = &priv->mdiodev->dev;
+	int stat;
+
+	stat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_VERIFY_MS);
+	if (stat < 0)
+		return stat;
+	if (stat == MXL862XX_SB_PDI_READY)
+		return 0;
+	if (stat == MXL862XX_SB_PDI_VERIFY_BAD) {
+		dev_err(dev,
+			"flash: loader stuck after %u chunks, power cycle it\n",
+			chunk);
+		return -EIO;
+	}
+	if (stat) {
+		/* A firmware is answering, not the loader: an image survived
+		 * in flash and booted.
+		 */
+		dev_info(dev, "flash: firmware booted while draining\n");
+		return 0;
+	}
+
+	return -EAGAIN;
+}
+
+/* Recover a switch whose SB PDI download was interrupted mid-transfer - the
+ * host died after MCUboot began erasing flash, whether it aborted mid erase or
+ * mid image-write, both end up in the same place: the payload receive loop.
+ * There the loader publishes STAT=0, waits for the host to write a byte-count
+ * to STAT, DMAs that many bytes and subtracts the count from a remaining-bytes
+ * counter, leaving the loop only when the counter reaches exactly zero. The
+ * image size died with the host, so we feed single-byte chunks (see
+ * MXL862XX_DRAIN_CHUNK_BYTES) to walk the counter to zero without underflow.
+ * The loader then verifies the (now corrupt) image and returns to READY by
+ * itself, or boots a valid image that happened to survive in flash; either way
+ * the caller's reprobe classifies the result. Every byte costs about a dozen
+ * MDIO frames, so a multi-MiB outstanding count takes tens of minutes.
+ * Returns 0 once the loader has left the receive loop, <0 on error. Does NOT
+ * recover a counter already underflowed by an earlier oversized-chunk attempt
+ * - that needs a power cycle.
+ */
+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)
+{
+	struct device *dev = &priv->mdiodev->dev;
+	/* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */
+	u32 max_chunks = 2u * (16u << 20) / MXL862XX_DRAIN_CHUNK_BYTES;
+	u32 chunk = 0;
+	int ret, stat;
+
+	while (chunk < max_chunks) {
+		/* Teardown can interrupt this long drain. */
+		if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+			return -ECANCELED;
+
+		stat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_STEP_MS);
+		if (stat < 0)
+			return stat;
+		if (stat == MXL862XX_SB_PDI_READY)
+			return 0;
+
+		if (stat) {
+			ret = mxl862xx_rescue_drain_finish(priv, chunk);
+			if (ret != -EAGAIN)
+				return ret;
+		}
+
+		/* Feed one zero byte; reset cleared the write latch. */
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+					   MXL862XX_SB_PDI_CTRL_WR);
+		if (ret < 0)
+			return ret;
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);
+		if (ret < 0)
+			return ret;
+		ret = mxl862xx_sb_pdi_reset(priv);
+		if (ret < 0)
+			return ret;
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+					   MXL862XX_DRAIN_CHUNK_BYTES);
+		if (ret < 0)
+			return ret;
+		chunk++;
+		if (!(chunk % MXL862XX_DRAIN_LOG_BYTES))
+			dev_info(dev, "flash: drained %u KiB so far\n",
+				 chunk / 1024);
+		cond_resched();
+	}
+
+	dev_err(dev,
+		"flash: interrupted download did not drain after %u chunks\n",
+		chunk);
+
+	return -ETIMEDOUT;
+}
+
+/* Background self-heal: drain a wedged download off the devlink flash path, so
+ * the long recovery never holds the devlink lock. Scheduled from probe;
+ * reprobes on success so the probe-time detection re-classifies the switch.
+ */
+void mxl862xx_rescue_heal_work_fn(struct work_struct *work)
+{
+	struct mxl862xx_priv *priv =
+		container_of(work, struct mxl862xx_priv, rescue_heal_work);
+	struct device *dev = &priv->mdiodev->dev;
+	int ret;
+
+	ret = mxl862xx_rescue_drain(priv);
+	if (ret == -ECANCELED)
+		return;
+	if (ret) {
+		/* Nothing retries this, so say so: rescue_ready stays clear
+		 * and devlink dev flash reports why it refuses.
+		 */
+		dev_err(dev, "flash: download recovery failed: %pe\n",
+			ERR_PTR(ret));
+		WRITE_ONCE(priv->rescue_failed, true);
+		return;
+	}
+
+	/* The interrupted transfer is finalised; reprobe so the probe-time
+	 * detection brings the driver up -- flashable in rescue mode if the
+	 * loader is at READY, or normally if a valid image booted. The core
+	 * skips the re-probe on its own if the device is unbound first; the
+	 * flag test only avoids scheduling one certain to be skipped. A
+	 * failed hand-off leaves nothing to reclassify the switch, so mark
+	 * recovery failed rather than promise a retry that cannot succeed.
+	 */
+	if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+		return;
+
+	if (device_schedule_reprobe(dev, MXL862XX_FW_REPROBE_DELAY_MS))
+		WRITE_ONCE(priv->rescue_failed, true);
+}
+
+/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller can rule
+ * the loader out before any C45 API request (which spews CRC errors when no WSP
+ * firmware answers). A scratch write to ADDR/DATA must latch or the chip is
+ * absent (-ENODEV); the mailbox is reset first, or a transfer interrupted with
+ * CTRL=WR would take that write as a payload word instead of latching it. STAT
+ * then classifies the state, poked destructively only when 0, the one value a
+ * running firmware never holds:
+ *
+ *  - 0xc33c: flashless loop; recognised but not supported here.
+ *  - 0xc55c: console loop, if the register-read challenge is serviced.
+ *  - other non-zero: running firmware, left unpoked.
+ *  - 0: wedged receive loop; the 1-byte slice-advance then says whether it
+ *    still needs draining or has just finished.
+ *
+ * The scratch write reaches a running firmware too, but lands in mailbox
+ * registers it does not read, so it is inert there.
+ *
+ * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, -ENODEV when the scratch
+ * write does not latch, which is a switch that does not answer at all or one
+ * whose SB PDI window is not at the offsets above, -EOPNOTSUPP for the
+ * flashless loop, -ENXIO for a READY loader whose mailbox fails the challenge,
+ * or an SMDIO bus error.
+ */
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)
+{
+	int stat, dat, ret, rb, a, d;
+
+	/* rescue_ready gates flashing; a wedged loader needs the drain first. */
+	WRITE_ONCE(priv->rescue_ready, false);
+
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		return ret;
+
+	/* Presence: a live chip latches the scratch write, an absent one floats. */
+	a = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+				 MXL862XX_SB_PDI_PROBE_A);
+	if (a < 0)
+		return a;
+	d = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+				 MXL862XX_SB_PDI_PROBE_D);
+	if (d < 0)
+		return d;
+	a = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_ADDR);
+	if (a < 0)
+		return a;
+	d = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);
+	if (d < 0)
+		return d;
+	if ((u16)a != MXL862XX_SB_PDI_PROBE_A ||
+	    (u16)d != MXL862XX_SB_PDI_PROBE_D)
+		return -ENODEV;
+
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		return ret;
+
+	stat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+	if (stat < 0)
+		return stat;
+
+	/* Flashless-download loop (MxL86281S tier): this driver does not
+	 * support it -- the console flash path expects READY. Treat it as an
+	 * unusable configuration, like any other unsupported state.
+	 */
+	if ((u16)stat == MXL862XX_SB_PDI_DL_READY)
+		return -EOPNOTSUPP;
+
+	/* Console loop at READY: confirm the live mailbox with the register-read
+	 * challenge (consumes the marker from DATA and re-arms READY).
+	 */
+	if ((u16)stat == MXL862XX_SB_PDI_READY) {
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+					   MXL862XX_SB_PDI_RDREG_MARK);
+		if (ret < 0)
+			return ret;
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+					   MXL862XX_SB_PDI_RDREG);
+		if (ret < 0)
+			return ret;
+		rb = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+					       MXL862XX_SB_PDI_STEP_MS);
+		dat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);
+		ret = mxl862xx_sb_pdi_reset(priv);
+		/* Never re-arming READY fails the challenge like any other
+		 * unserviced command; report it as such rather than as a bus
+		 * timeout the bus never saw.
+		 */
+		if (rb == -ETIMEDOUT)
+			rb = -ENXIO;
+		if (rb < 0)
+			return rb;
+		if (dat < 0)
+			return dat;
+		if (ret < 0)
+			return ret;
+		if ((u16)dat != MXL862XX_SB_PDI_RDREG_MARK) {
+			WRITE_ONCE(priv->rescue_ready, true);
+			return MXL862XX_IN_RESCUE;
+		}
+		/* READY but the marker is untouched, so nothing is servicing the
+		 * mailbox. A firmware publishing 0xc55c as its status word looks
+		 * exactly like this, and flashing one would be far worse than
+		 * refusing to bind, so treat it as unusable.
+		 */
+		return -ENXIO;
+	}
+
+	/* Any other non-zero value is a running firmware, not a loader. */
+	if (stat)
+		return MXL862XX_NOT_RESCUE;
+
+	/* STAT == 0: a wedged receive loop takes a 1-byte slice-advance (feed
+	 * one DATA word first, like a drain chunk) and asks for the next chunk
+	 * by publishing 0 again. Had that byte been the last one outstanding,
+	 * the loader leaves the loop instead and returns to READY, having
+	 * consumed the advance -- proof enough of a live mailbox to skip the
+	 * challenge. Anything else means it is still working on it. All three
+	 * are rescue, so this never fails probe; only the drain does.
+	 */
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+				   MXL862XX_SB_PDI_CTRL_WR);
+	if (ret < 0)
+		return ret;
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);
+	if (ret < 0)
+		return ret;
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		return ret;
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+				   MXL862XX_DRAIN_CHUNK_BYTES);
+	if (ret < 0)
+		return ret;
+
+	rb = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_STEP_MS);
+	if (rb < 0)
+		return rb;
+	if (rb == MXL862XX_SB_PDI_READY)
+		WRITE_ONCE(priv->rescue_ready, true);
+
+	return MXL862XX_IN_RESCUE;
+}
+
+/* MCUboot firmware image header */
+struct mxl862xx_fw_hdr {
+	__le32 image_type;
+	__le32 image_size_1;
+	__le32 image_checksum_1;
+	__le32 image_size_2;
+	__le32 image_checksum_2;
+} __packed;
+
+static int mxl862xx_flash_validate(struct mxl862xx_priv *priv,
+				   const struct firmware *fw,
+				   u32 *payload_size)
+{
+	const struct mxl862xx_fw_hdr *hdr;
+	u32 size1, size2, total;
+	const u8 *payload;
+	u32 crc;
+
+	if (fw->size < MXL862XX_FW_HDR_SIZE)
+		return -EINVAL;
+
+	hdr = (const struct mxl862xx_fw_hdr *)fw->data;
+	payload = fw->data + MXL862XX_FW_HDR_SIZE;
+	size1 = le32_to_cpu(hdr->image_size_1);
+	size2 = le32_to_cpu(hdr->image_size_2);
+
+	if (check_add_overflow(size1, size2, &total) ||
+	    total > fw->size - MXL862XX_FW_HDR_SIZE) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: firmware file too small for declared size\n");
+		return -EINVAL;
+	}
+
+	if (!total) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: firmware file with empty payload\n");
+		return -EINVAL;
+	}
+
+	if (size1) {
+		crc = ~crc32_le(~0U, payload, size1);
+		if (crc != le32_to_cpu(hdr->image_checksum_1)) {
+			dev_err(&priv->mdiodev->dev,
+				"flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
+				crc, le32_to_cpu(hdr->image_checksum_1));
+			return -EINVAL;
+		}
+	}
+
+	if (size2) {
+		crc = ~crc32_le(~0U, payload + size1, size2);
+		if (crc != le32_to_cpu(hdr->image_checksum_2)) {
+			dev_err(&priv->mdiodev->dev,
+				"flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
+				crc, le32_to_cpu(hdr->image_checksum_2));
+			return -EINVAL;
+		}
+	}
+
+	*payload_size = total;
+
+	return 0;
+}
+
+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
+				   const struct firmware *fw,
+				   u32 payload_size, struct devlink *dl)
+{
+	const u8 *payload = fw->data + MXL862XX_FW_HDR_SIZE;
+	u32 word_idx = 0, data_written = 0, idx = 0;
+	unsigned long next_notify = jiffies - 1;
+	u16 word, fdata;
+	int ret, i;
+
+	/* Step 1: reboot the firmware into MCUboot rescue mode */
+	if (!priv->rescue_mode) {
+		ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
+					false, false);
+		if (ret) {
+			dev_err(&priv->mdiodev->dev,
+				"flash: FW_UPDATE command failed: %pe\n",
+				ERR_PTR(ret));
+			return ret;
+		}
+	}
+
+	/* Step 2: wait for bootloader ready */
+	mxl862xx_flash_notify(dl, "Waiting for bootloader", 0, 0);
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		goto write_err;
+
+	/* Failures from here on end up at no_end, which returns the error
+	 * without signalling END -- see there.
+	 */
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+					MXL862XX_FW_READY_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: bootloader not ready: %pe\n", ERR_PTR(ret));
+		goto no_end;
+	}
+
+	/* Step 3: start handshake */
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+				   MXL862XX_SB_PDI_START);
+	if (ret < 0)
+		goto write_err;
+
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
+					MXL862XX_FW_ACK_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: start handshake failed: %pe\n", ERR_PTR(ret));
+		goto no_end;
+	}
+
+	/* Step 4: transfer image header */
+	mxl862xx_flash_notify(dl, "Erasing flash", 0, 0);
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+				   MXL862XX_SB_PDI_CTRL_WR);
+	if (ret < 0)
+		goto write_err;
+
+	for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
+		word = fw->data[i * 2] |
+		       ((u16)fw->data[i * 2 + 1] << 8);
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
+		if (ret < 0)
+			goto write_err;
+	}
+
+	ret = mxl862xx_sb_pdi_reset(priv);
+	if (ret < 0)
+		goto write_err;
+
+	/* the byte count in STAT triggers the erase */
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+				   MXL862XX_FW_HDR_SIZE);
+	if (ret < 0)
+		goto write_err;
+
+	/* ACK is byte count + 1 */
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+					MXL862XX_FW_ACK_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: header ACK failed: %pe\n", ERR_PTR(ret));
+		goto no_end;
+	}
+
+	/* Step 5: wait for erase to complete */
+	ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
+					MXL862XX_FW_ERASE_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: erase timeout: %pe\n", ERR_PTR(ret));
+		goto no_end;
+	}
+
+	/* Step 6: transfer payload */
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+				   MXL862XX_SB_PDI_CTRL_WR);
+	if (ret < 0)
+		goto write_err;
+
+	while (idx < payload_size) {
+		cond_resched();
+		if (idx + 1 < payload_size) {
+			fdata = payload[idx] |
+				((u16)payload[idx + 1] << 8);
+			idx += 2;
+			data_written += 2;
+		} else {
+			fdata = payload[idx];
+			idx++;
+			data_written++;
+		}
+
+		ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
+		if (ret < 0)
+			goto write_err;
+		word_idx++;
+
+		if (idx >= payload_size) {
+			ret = mxl862xx_sb_pdi_flush_last(priv, data_written);
+			break;
+		}
+
+		/* Half-bank boundary: switch to SB1 address */
+		if (word_idx == MXL862XX_FW_BANK_HALF) {
+			ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+						   MXL862XX_SB_PDI_CTRL_RST);
+			if (ret < 0)
+				goto write_err;
+
+			ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+						   MXL862XX_FW_SB1_ADDR);
+			if (ret < 0)
+				goto write_err;
+
+			ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+						   MXL862XX_SB_PDI_CTRL_WR);
+			if (ret < 0)
+				goto write_err;
+		} else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
+			ret = mxl862xx_sb_pdi_flush_slice(priv, data_written);
+			if (ret) {
+				dev_err(&priv->mdiodev->dev,
+					"flash: write timeout at %u/%u: %pe\n",
+					idx, payload_size, ERR_PTR(ret));
+				goto no_end;
+			}
+			word_idx = 0;
+			data_written = 0;
+			ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+						   MXL862XX_SB_PDI_CTRL_WR);
+			if (ret < 0)
+				goto write_err;
+
+			if (time_after(jiffies, next_notify)) {
+				mxl862xx_flash_notify(dl, "Flashing", idx,
+						      payload_size);
+				next_notify = jiffies + msecs_to_jiffies(500);
+			}
+		}
+	}
+
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: final slice failed: %pe\n", ERR_PTR(ret));
+		goto no_end;
+	}
+
+	mxl862xx_flash_notify(dl, "Flashing", payload_size, payload_size);
+
+	/* Success: the loader has left the receive loop at r_remain == 0 and
+	 * verified the image, so END(0x3cc3) is a finalise/boot request rather
+	 * than a byte count. Signal it here -- and only here -- to boot the new
+	 * image without waiting out the loader's 2 s END timeout.
+	 */
+	ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+				   MXL862XX_SB_PDI_END);
+	msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+	return ret;
+
+write_err:
+	dev_err(&priv->mdiodev->dev, "flash: SMDIO write failed: %pe\n",
+		ERR_PTR(ret));
+no_end:
+	/* A failure leaves the loader mid transfer; do not signal END (a STAT
+	 * write is a byte count then, and END would be misread as one, risking
+	 * a receive-counter underflow). Return the error; the caller reprobes.
+	 */
+	return ret;
+}
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+			      struct devlink_info_req *req,
+			      struct netlink_ext_ack *extack)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	char buf[16];
+	int ret;
+
+	/* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The
+	 * fw version doubles as the "ready to flash" signal: report it only
+	 * once the loader is at a clean READY, nothing while still draining.
+	 */
+	if (priv->rescue_mode) {
+		if (!READ_ONCE(priv->rescue_ready))
+			return 0;
+
+		snprintf(buf, sizeof(buf), "%u.%u.%u",
+			 priv->fw_version.major, priv->fw_version.minor,
+			 priv->fw_version.revision);
+		ret = devlink_info_version_running_put(req,
+				DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+		if (ret)
+			return ret;
+		return devlink_info_version_stored_put(req,
+				DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+	}
+
+	/* A 0 part number means the CHIP ID read failed or the part is
+	 * unfused; omit it rather than publish a bogus "0000" that fwupd
+	 * would match firmware against -- it then falls back to the driver
+	 * name.
+	 */
+	if (priv->asic_id) {
+		snprintf(buf, sizeof(buf), "%04X", priv->asic_id);
+		ret = devlink_info_version_fixed_put(req,
+						     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
+						     buf);
+		if (ret)
+			return ret;
+
+		snprintf(buf, sizeof(buf), "%u", priv->asic_rev);
+		ret = devlink_info_version_fixed_put(req,
+						     DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,
+						     buf);
+		if (ret)
+			return ret;
+	}
+
+	/* An all-zero version is the cache a failed flash left behind, not a
+	 * released firmware; omit it like the part number above.
+	 */
+	if (!priv->fw_version.major && !priv->fw_version.minor &&
+	    !priv->fw_version.revision)
+		return 0;
+
+	snprintf(buf, sizeof(buf), "%u.%u.%u",
+		 priv->fw_version.major, priv->fw_version.minor,
+		 priv->fw_version.revision);
+
+	ret = devlink_info_version_running_put(req,
+			DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+	if (ret)
+		return ret;
+
+	/* boots this image from its own flash: stored == running */
+	return devlink_info_version_stored_put(req,
+			DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+}
+
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+				  struct devlink_flash_update_params *params,
+				  struct netlink_ext_ack *extack)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct dsa_port *dp;
+	u32 payload_size;
+	int ret, err, i;
+
+	if (params->component) {
+		NL_SET_ERR_MSG_MOD(extack, "component is not supported");
+		return -EOPNOTSUPP;
+	}
+
+	/* A previous flash is still waiting for its reprobe: the firmware API
+	 * is short-circuited, so the raw SB PDI writes below would run against
+	 * a switch this driver no longer tracks.
+	 */
+	if (priv->skip_teardown) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "device is reinitializing, retry later");
+		return -EBUSY;
+	}
+
+	ret = mxl862xx_flash_validate(priv, params->fw, &payload_size);
+	if (ret) {
+		NL_SET_ERR_MSG_MOD(extack, "firmware image validation failed");
+		return ret;
+	}
+
+	/* Refuse to flash while the background self-heal is still draining, and
+	 * for good once it has given up on the loader.
+	 */
+	if (READ_ONCE(priv->rescue_failed)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "download recovery failed, power cycle the switch");
+		return -EIO;
+	}
+
+	if (priv->rescue_mode && !READ_ONCE(priv->rescue_ready)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "switch is recovering an interrupted download, retry shortly");
+		return -EBUSY;
+	}
+
+	if (priv->rescue_mode)
+		dev_info(ds->dev,
+			 "flash: flashing switch via MCUboot rescue mode\n");
+	else
+		dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+			 priv->fw_version.major, priv->fw_version.minor,
+			 priv->fw_version.revision);
+
+	/* Close ports while the firmware is still alive so the DSA core's
+	 * MDB/FDB tracking is drained, and detach user ports so userspace
+	 * cannot reopen them during the flash. The conduit is only closed,
+	 * not detached: it belongs to the MAC driver. This driver binds a
+	 * single switch with a direct host link and no cascade ports, so the
+	 * conduit serves only this switch, and flashing it reboots the switch,
+	 * which takes the tree down regardless.
+	 */
+	rtnl_lock();
+	dsa_switch_for_each_user_port(dp, ds) {
+		if (dp->user) {
+			dev_close(dp->user);
+			netif_device_detach(dp->user);
+		}
+	}
+	dsa_switch_for_each_cpu_port(dp, ds)
+		dev_close(dp->conduit);
+	/* The bridge defers the STP state changes triggered by closing
+	 * the ports; let them reach the firmware while it is still alive.
+	 */
+	switchdev_deferred_process();
+	rtnl_unlock();
+
+	mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+	priv->block_host = true;
+	mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+	set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
+	disable_delayed_work_sync(&priv->stats_work);
+	cancel_work_sync(&priv->crc_err_work);
+	for (i = 0; i < ds->num_ports; i++)
+		cancel_work_sync(&priv->ports[i].host_flood_work);
+
+	ret = mxl862xx_flash_firmware(priv, params->fw, payload_size,
+				      ds->devlink);
+	if (ret)
+		NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed");
+
+	if (!ret) {
+		mutex_lock_nested(&priv->mdiodev->bus->mdio_lock,
+				  MDIO_MUTEX_NESTED);
+		/* Keep block_host set so host writes stay blocked, but let the
+		 * readiness poll below read the freshly booted firmware.
+		 */
+		priv->flash_reading = true;
+		priv->rescue_mode = false;
+		mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+		/* Refresh the cached versions so the flash update only
+		 * completes once the new firmware is confirmed running and
+		 * devlink dev info reports it. Must happen before setting
+		 * skip_teardown, which discards all firmware API reads.
+		 */
+		ret = mxl862xx_wait_ready(ds);
+		if (ret)
+			NL_SET_ERR_MSG_MOD(extack,
+					   "new firmware did not become ready");
+	}
+
+	if (ret) {
+		/* The switch is in MCUboot with erased or partly written flash;
+		 * drop the cached identity so devlink dev info stops reporting
+		 * the pre-flash version until the reprobe re-reads the truth.
+		 */
+		memset(&priv->fw_version, 0, sizeof(priv->fw_version));
+		priv->asic_id = 0;
+		priv->asic_rev = 0;
+	}
+
+	mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+	priv->flash_reading = false;
+	priv->block_host = false;
+	priv->skip_teardown = true;
+	mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+	/* Reinitialise through a deferred re-probe: remove() runs with
+	 * skip_teardown set, then a fresh probe() starts against whatever
+	 * the switch now runs. The core skips the re-probe if the device
+	 * is unbound or shut down before it fires.
+	 */
+	err = device_schedule_reprobe(ds->dev, MXL862XX_FW_REPROBE_DELAY_MS);
+
+	return ret ? ret : err;
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
new file mode 100644
index 0000000000000..7cd87c7ad8716
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+struct mxl862xx_priv;
+struct work_struct;
+
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);
+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+			      struct devlink_info_req *req,
+			      struct netlink_ext_ack *extack);
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+				  struct devlink_flash_update_params *params,
+				  struct netlink_ext_ack *extack);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc00..23e5dfe81e364 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -15,6 +15,8 @@
 #include <linux/unaligned.h>
 #include <net/dsa.h>
 #include "mxl862xx.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
 #include "mxl862xx-host.h"
 
 #define CTRL_BUSY_MASK			BIT(15)
@@ -340,6 +342,26 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
 
 	mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
 
+	if (priv->skip_teardown) {
+		ret = read ? -ENODEV : 0;
+		goto out;
+	}
+
+	if (priv->rescue_mode) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/* During the post-flash readiness poll block_host stays set, but the
+	 * flash path's own firmware version reads must reach the new image;
+	 * host writes stay blocked so stale resource IDs cannot corrupt it.
+	 */
+	if (priv->block_host && cmd != SYS_MISC_FW_UPDATE &&
+	    !(priv->flash_reading && read)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
 	max = (size + 1) / 2;
 
 	ret = mxl862xx_busy_wait(priv);
@@ -495,12 +517,54 @@ int mxl862xx_reset(struct mxl862xx_priv *priv)
 	return ret;
 }
 
+#define MXL862XX_SMDIO_ADDR_REG		0x1f
+#define MXL862XX_SMDIO_PAGE_MASK	0xfff0
+#define MXL862XX_SMDIO_OFF_MASK		0x000f
+
+/* Paged clause-22 window: the page goes into MII register 0x1f, the low nibble
+ * of addr selects one of the 16 registers within it. Both helpers take the MDIO
+ * bus lock per transaction, so callers must not already hold it -- unlike
+ * mxl862xx_api_wrap(), which holds it across a whole firmware command.
+ */
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr)
+{
+	struct mii_bus *bus = priv->mdiodev->bus;
+	int phy = priv->mdiodev->addr;
+	int ret;
+
+	mutex_lock(&bus->mdio_lock);
+	ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+			      addr & MXL862XX_SMDIO_PAGE_MASK);
+	if (ret >= 0)
+		ret = __mdiobus_read(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK);
+	mutex_unlock(&bus->mdio_lock);
+	return ret;
+}
+
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val)
+{
+	struct mii_bus *bus = priv->mdiodev->bus;
+	int phy = priv->mdiodev->addr;
+	int ret;
+
+	mutex_lock(&bus->mdio_lock);
+	ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+			      addr & MXL862XX_SMDIO_PAGE_MASK);
+	if (ret >= 0)
+		ret = __mdiobus_write(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK,
+				      val);
+	mutex_unlock(&bus->mdio_lock);
+	return ret;
+}
+
 void mxl862xx_host_init(struct mxl862xx_priv *priv)
 {
 	INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
+	INIT_WORK(&priv->rescue_heal_work, mxl862xx_rescue_heal_work_fn);
 }
 
 void mxl862xx_host_shutdown(struct mxl862xx_priv *priv)
 {
 	cancel_work_sync(&priv->crc_err_work);
+	cancel_work_sync(&priv->rescue_heal_work);
 }
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
index 66d6ae198aff4..4e054c6e4c0e4 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
 	mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
 
 int mxl862xx_reset(struct mxl862xx_priv *priv);
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);
 
 #endif /* __MXL862XX_HOST_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
index b689652aa9b92..a5b6940b552e9 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
 
 	switch (port) {
 	case 9 ... 16:
+		if (priv->rescue_mode)
+			return NULL;
 		if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {
 			dev_warn_once(dp->ds->dev,
 				      "SerDes PCS unsupported on old firmware.\n");
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index cfa7e3e269a28..c50c5ca328618 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -21,6 +21,7 @@
 #include "mxl862xx.h"
 #include "mxl862xx-api.h"
 #include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
 #include "mxl862xx-host.h"
 #include "mxl862xx-phylink.h"
 
@@ -71,6 +72,13 @@ static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {
 #define MXL862XX_READY_TIMEOUT_MS	10000
 #define MXL862XX_READY_POLL_MS		100
 
+/* Chip ID registers, read via SYS_MISC_REG_RD */
+#define MXL862XX_CHIPID_L		0xc0d28884
+#define MXL862XX_CHIPID_M		0xc0d28888
+#define MXL862XX_CHIPID_L_PNUML		GENMASK(15, 12)
+#define MXL862XX_CHIPID_M_PNUMM		GENMASK(11, 0)
+#define MXL862XX_CHIPID_M_VERSION	GENMASK(14, 12)
+
 #define MXL862XX_TCM_INST_SEL		0xe00
 #define MXL862XX_TCM_CBS		0xe12
 #define MXL862XX_TCM_EBS		0xe13
@@ -222,7 +230,46 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr,
 	return mxl862xx_phy_write_mmd(bus->priv, addr, devadd, regnum, val);
 }
 
-static int mxl862xx_wait_ready(struct dsa_switch *ds)
+/* Read the static chip part number and version from the CHIP ID
+ * registers. Only possible with a running firmware, so the values are
+ * cached at setup and left zero when the switch is in rescue mode.
+ */
+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)
+{
+	struct mxl862xx_sys_reg_rw reg = {};
+	u16 chipid_l, chipid_m;
+	int ret;
+
+	reg.addr = cpu_to_le32(MXL862XX_CHIPID_L);
+	ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+	if (ret)
+		return ret;
+	chipid_l = le32_to_cpu(reg.val);
+
+	reg.addr = cpu_to_le32(MXL862XX_CHIPID_M);
+	ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+	if (ret)
+		return ret;
+	chipid_m = le32_to_cpu(reg.val);
+
+	priv->asic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |
+			FIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) << 4;
+	priv->asic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);
+
+	return 0;
+}
+
+/**
+ * mxl862xx_wait_ready - wait for the switch firmware to become operational
+ * @ds: DSA switch instance
+ *
+ * Poll the firmware until it reports its version and accepts
+ * configuration commands, then cache the firmware version and chip ID.
+ * Takes at least two seconds.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int mxl862xx_wait_ready(struct dsa_switch *ds)
 {
 	struct mxl862xx_sys_fw_image_version ver = {};
 	unsigned long start = jiffies, timeout;
@@ -254,6 +301,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)
 		priv->fw_version.major = ver.iv_major;
 		priv->fw_version.minor = ver.iv_minor;
 		priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
+
+		ret = mxl862xx_read_chip_id(priv);
+		if (ret)
+			dev_warn(ds->dev, "failed to read chip ID: %pe\n",
+				 ERR_PTR(ret));
 		return 0;
 
 not_ready_yet:
@@ -622,21 +674,66 @@ static int mxl862xx_setup(struct dsa_switch *ds)
 	int n_user_ports = 0, max_vlans;
 	int ingress_finals, vid_rules;
 	struct dsa_port *dp;
-	int ret, i;
-
-	ret = mxl862xx_reset(priv);
-	if (ret)
-		return ret;
-
-	ret = mxl862xx_wait_ready(ds);
-	if (ret)
-		return ret;
+	int ret, i, rescue;
 
+	/* The SerDes state is software-only and must exist before anything can
+	 * reach phylink, including a rescue-mode flash clearing rescue_mode
+	 * ahead of the reprobe.
+	 */
 	mutex_init(&priv->serdes_lock);
 	for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
 		mxl862xx_setup_pcs(priv, &priv->serdes_ports[i],
 				   i + MXL862XX_FIRST_SERDES_PORT);
 
+	/* Detect the loader over SB PDI first: it needs no firmware, unlike the
+	 * C45 API (mxl862xx_reset/wait_ready) which spews CRC errors when none
+	 * answers. Touch C45 only once rescue is ruled out.
+	 */
+	rescue = mxl862xx_rescue_mode_detect(priv);
+	if (rescue < 0) {
+		dev_err(ds->dev, "switch state detection failed: %pe\n",
+			ERR_PTR(rescue));
+		return rescue;
+	}
+
+	if (rescue == MXL862XX_NOT_RESCUE) {
+		ret = mxl862xx_reset(priv);
+		if (ret)
+			return ret;
+
+		ret = mxl862xx_wait_ready(ds);
+		if (ret) {
+			/* the reset may only now have triggered rescue mode */
+			rescue = mxl862xx_rescue_mode_detect(priv);
+			if (rescue < 0) {
+				dev_err(ds->dev,
+					"switch not responding after reset: %pe\n",
+					ERR_PTR(rescue));
+				return rescue;
+			}
+			if (rescue == MXL862XX_NOT_RESCUE)
+				return ret;
+		}
+	}
+
+	priv->rescue_mode = rescue;
+
+	if (priv->rescue_mode) {
+		if (priv->rescue_ready) {
+			dev_warn(ds->dev,
+				 "switch in MCUboot rescue mode, use devlink to flash new firmware\n");
+		} else {
+			/* Drain the wedged download in the background so it
+			 * never holds the devlink lock; info and flash become
+			 * available once ready.
+			 */
+			dev_warn(ds->dev,
+				 "switch in MCUboot with an interrupted download, recovering in background\n");
+			queue_work(system_long_wq, &priv->rescue_heal_work);
+		}
+		return 0;
+	}
+
 	/* Calculate Extended VLAN block sizes.
 	 * With VLAN Filter handling VID membership checks:
 	 *   Ingress: only final catchall rules (PVID insertion, 802.1Q
@@ -715,11 +812,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)
 static int mxl862xx_port_enable(struct dsa_switch *ds, int port,
 				struct phy_device *phydev)
 {
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (priv->rescue_mode)
+		return 0;
+
 	return mxl862xx_port_state(ds, port, true);
 }
 
 static void mxl862xx_port_disable(struct dsa_switch *ds, int port)
 {
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (priv->rescue_mode)
+		return;
+
 	if (mxl862xx_port_state(ds, port, false))
 		dev_err(ds->dev, "failed to disable port %d\n", port);
 }
@@ -1337,6 +1444,17 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
 	bool is_cpu_port = dsa_port_is_cpu(dp);
 	int ret;
 
+	if (dsa_port_is_dsa(dp)) {
+		dev_err(ds->dev, "port %d: DSA links not supported\n", port);
+		return -EOPNOTSUPP;
+	}
+
+	/* DSA reinits failed user ports as unused; shared ports must
+	 * succeed for the tree to register.
+	 */
+	if (priv->rescue_mode)
+		return dsa_port_is_user(dp) ? -ENODEV : 0;
+
 	ret = mxl862xx_port_state(ds, port, false);
 	if (ret)
 		return ret;
@@ -1346,11 +1464,6 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
 	if (dsa_port_is_unused(dp))
 		return 0;
 
-	if (dsa_port_is_dsa(dp)) {
-		dev_err(ds->dev, "port %d: DSA links not supported\n", port);
-		return -EOPNOTSUPP;
-	}
-
 	ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
 	if (ret)
 		return ret;
@@ -1572,6 +1685,12 @@ static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,
 	ether_addr_copy(qparam.mac, mdb->addr);
 
 	ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+	/* Post-flash teardown or MCUboot: the firmware and its MAC table are
+	 * gone, so there is nothing left to delete. Outside those, -ENODEV is a
+	 * bus error and must be reported.
+	 */
+	if (ret == -ENODEV && (priv->skip_teardown || priv->rescue_mode))
+		return 0;
 	if (ret)
 		return ret;
 
@@ -1628,6 +1747,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
 	struct mxl862xx_priv *priv = ds->priv;
 	int ret;
 
+	if (priv->rescue_mode)
+		return;
+
 	switch (state) {
 	case BR_STATE_DISABLED:
 		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
@@ -2086,6 +2208,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.get_pause_stats = mxl862xx_get_pause_stats,
 	.get_rmon_stats = mxl862xx_get_rmon_stats,
 	.get_stats64 = mxl862xx_get_stats64,
+	.devlink_info_get = mxl862xx_devlink_info_get,
+	.devlink_flash_update = mxl862xx_devlink_flash_update,
 };
 
 static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08e..bdaf295c8a9f6 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,7 +4,9 @@
 #define __MXL862XX_H
 
 #include <asm/byteorder.h>
+#include <linux/bitops.h>
 #include <linux/mdio.h>
+#include <linux/mutex.h>
 #include <linux/workqueue.h>
 #include <net/dsa.h>
 
@@ -14,6 +16,10 @@ struct mxl862xx_priv;
 #define MXL862XX_FIRST_SERDES_PORT	9
 #define MXL862XX_SERDES_SLOTS		4
 
+/* mxl862xx_rescue_mode_detect() return codes (negative values are errors) */
+#define MXL862XX_NOT_RESCUE		0
+#define MXL862XX_IN_RESCUE		1
+
 #define MXL862XX_DEFAULT_BRIDGE		0
 #define MXL862XX_MAX_BRIDGES		48
 #define MXL862XX_MAX_BRIDGE_PORTS	128
@@ -303,6 +309,10 @@ struct mxl862xx_fw_version {
  *                      flooding)
  * @fw_version:         cached firmware version, populated at probe and
  *                      compared with MXL862XX_FW_VER_MIN()
+ * @asic_id:            chip part number read from the CHIP ID registers,
+ *                      reported as the devlink "asic.id" fixed version
+ * @asic_rev:           chip version read from the CHIP ID registers,
+ *                      reported as the devlink "asic.rev" fixed version
  * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of
  *                      10G_QXGMII or QSGMII
  * @serdes_refcount:    per-XPCS count of sub-ports enabled by phylink;
@@ -319,6 +329,22 @@ struct mxl862xx_fw_version {
  * @evlan_ingress_size: per-port ingress Extended VLAN block size
  * @evlan_egress_size:  per-port egress Extended VLAN block size
  * @vf_block_size:      per-port VLAN Filter block size
+ * @block_host:         reject firmware API commands (except FW_UPDATE)
+ *                      during a firmware flash
+ * @flash_reading:      let the flash path's own firmware reads through
+ *                      block_host while polling the freshly booted image
+ * @skip_teardown:      discard firmware API commands during the teardown
+ *                      triggered by the post-flash reprobe
+ * @rescue_mode:        switch is in MCUboot; firmware API commands fail fast,
+ *                      only clause-22 SMDIO works. Set from setup() before the
+ *                      switch is registered and cleared under the MDIO bus lock
+ *                      for the benefit of mxl862xx_api_wrap(); other readers
+ *                      only need it to be a stable single flag.
+ * @rescue_ready:       (rescue_mode) loader is at a clean READY and will accept
+ *                      a flash; false while rescue_heal_work is draining
+ * @rescue_failed:      (rescue_mode) the self-heal gave up; the loader needs a
+ *                      power cycle and no flash can be accepted
+ * @rescue_heal_work:   background self-heal draining a wedged download to READY
  * @stats_work:         periodic work item that polls RMON hardware counters
  *                      and accumulates them into 64-bit per-port stats
  */
@@ -326,9 +352,12 @@ struct mxl862xx_priv {
 	struct dsa_switch *ds;
 	struct mdio_device *mdiodev;
 	struct work_struct crc_err_work;
+	struct work_struct rescue_heal_work;
 	unsigned long flags;
 	u16 drop_meter;
 	struct mxl862xx_fw_version fw_version;
+	u16 asic_id;
+	u8 asic_rev;
 	struct mxl862xx_pcs serdes_ports[8];
 	int serdes_refcount[2];
 	struct mutex serdes_lock;
@@ -337,7 +366,15 @@ struct mxl862xx_priv {
 	u16 evlan_ingress_size;
 	u16 evlan_egress_size;
 	u16 vf_block_size;
+	bool block_host;
+	bool flash_reading;
+	bool skip_teardown;
+	bool rescue_mode;
+	bool rescue_ready;
+	bool rescue_failed;
 	struct delayed_work stats_work;
 };
 
+int mxl862xx_wait_ready(struct dsa_switch *ds);
+
 #endif /* __MXL862XX_H */
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b4..7a99169505772 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1314,6 +1314,8 @@ int  __must_check device_attach(struct device *dev);
 int __must_check driver_attach(const struct device_driver *drv);
 void device_initial_probe(struct device *dev);
 int __must_check device_reprobe(struct device *dev);
+int __must_check device_schedule_reprobe(struct device *dev,
+					 unsigned int delay_ms);
 
 bool device_is_bound(struct device *dev);
 
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 7507d632e7c6d..9babab92a0fc6 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1174,6 +1174,9 @@ struct dsa_switch_ops {
 	int	(*devlink_info_get)(struct dsa_switch *ds,
 				    struct devlink_info_req *req,
 				    struct netlink_ext_ack *extack);
+	int	(*devlink_flash_update)(struct dsa_switch *ds,
+					struct devlink_flash_update_params *params,
+					struct netlink_ext_ack *extack);
 	int	(*devlink_sb_pool_get)(struct dsa_switch *ds,
 				       unsigned int sb_index, u16 pool_index,
 				       struct devlink_sb_pool_info *pool_info);
diff --git a/net/dsa/devlink.c b/net/dsa/devlink.c
index ed342f345692b..25311a87cbc56 100644
--- a/net/dsa/devlink.c
+++ b/net/dsa/devlink.c
@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,
 	return -EOPNOTSUPP;
 }
 
+static int dsa_devlink_flash_update(struct devlink *dl,
+				    struct devlink_flash_update_params *params,
+				    struct netlink_ext_ack *extack)
+{
+	struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+	if (!ds->ops->devlink_flash_update)
+		return -EOPNOTSUPP;
+
+	return ds->ops->devlink_flash_update(ds, params, extack);
+}
+
 static int dsa_devlink_sb_pool_get(struct devlink *dl,
 				   unsigned int sb_index, u16 pool_index,
 				   struct devlink_sb_pool_info *pool_info)
@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
 
 static const struct devlink_ops dsa_devlink_ops = {
 	.info_get			= dsa_devlink_info_get,
+	.flash_update			= dsa_devlink_flash_update,
 	.sb_pool_get			= dsa_devlink_sb_pool_get,
 	.sb_pool_set			= dsa_devlink_sb_pool_set,
 	.sb_port_pool_get		= dsa_devlink_sb_port_pool_get,

3/2 2026/09/07 23:29 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 30141
							output: 20
							thoughts: 635
						

Thoughts:
**Analyzing the Patch Series: A Deep Dive**

Okay, let's break down this patch series and what it's trying to achieve. My initial assessment suggests a significant reworking related to device management and particularly focuses on enhancing the `mxl862xx` driver, likely for networking hardware.

First, I see the patch touches documentation (`Documentation/networking/devlink/index.rst` & `mxl862xx.rst`) and the `MAINTAINERS` file. Standard stuff for introducing new functionality and ensuring we know who to contact.

Then, there's the core of the change: modifications within the driver model itself. We have changes to `drivers/base/base.h`, `drivers/base/core.c`, `drivers/base/dd.c`, and `include/linux/device.h`. The addition of `shutdown_done` to `struct device_private` caught my eye.  `device_shutdown()` now explicitly sets it, which implies a more refined shutdown sequence. The change to `__device_release_driver` to return a `bool` is interesting; a clear indication of whether or not a driver unbound properly. The introduction of `device_schedule_reprobe()` and `device_reprobe_work_fn()` is the real standout. We're introducing delayed work to detach and then reprobe a device, which screams "handling device hiccups or recovery" to me. It's using `system_freezable_wq`, so we have to ensure any side-effects of this are understood when considering other subsystems which may use the same workqueue. Also, the export of `device_schedule_reprobe` makes it more useful for other parts of the system and a good architectural choice. But, who is calling it? Let's verify by grepping the kernel code for any other calls to `device_schedule_reprobe`.

Moving on, the `drivers/net/dsa/mxl862xx/*` directory is heavily impacted.  This driver has received the lion's share of changes, introducing firmware flashing support, devlink integration, rescue mode handling, and devlink flash updates. This implies a significant capability expansion for the MxL862xx hardware, which seems to require a more involved lifecycle management. This reinforces the need for the driver core changes for handling the new device states.

Finally, the updates to `include/net/dsa.h` and `net/dsa/devlink.c` indicate further integration of the devlink framework, particularly with the addition of a `devlink_flash_update` callback to the `struct dsa_switch_ops` and `dsa_devlink_ops`. This suggests these switches are using devlink in new ways now.

My initial question is: how are these changes impacting the driver model, and what new possibilities are opening up? The introduction of `device_schedule_reprobe` has very broad implications. How broadly is it intended to be used? Are there any potential implications for virtualized environments? Let me dig a little deeper.



4/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "device_schedule_reprobe"
}

Results:
{
  "Output": "drivers/base/base.h=80=struct driver_private {\n--\ndrivers/base/base.h-111- *\t  device lock. A deferred re-probe scheduled with\ndrivers/base/base.h:112: *\t  device_schedule_reprobe() must not detach the device anymore.\ndrivers/base/base.h-113- *\n--\ndrivers/base/dd.c=1451=static void device_reprobe_work_fn(struct work_struct *work)\n--\ndrivers/base/dd.c-1498-/**\ndrivers/base/dd.c:1499: * device_schedule_reprobe - schedule a deferred detach and re-probe\ndrivers/base/dd.c-1500- * @dev: device to detach and re-probe\n--\ndrivers/base/dd.c-1526- */\ndrivers/base/dd.c:1527:int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)\ndrivers/base/dd.c-1528-{\n--\ndrivers/base/dd.c-1553-}\ndrivers/base/dd.c:1554:EXPORT_SYMBOL_GPL(device_schedule_reprobe);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=421=void mxl862xx_rescue_heal_work_fn(struct work_struct *work)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-451-\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:452:\tif (device_schedule_reprobe(dev, MXL862XX_FW_REPROBE_DELAY_MS))\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-453-\t\tWRITE_ONCE(priv-\u003erescue_failed, true);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=926=int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-1058-\t */\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:1059:\terr = device_schedule_reprobe(ds-\u003edev, MXL862XX_FW_REPROBE_DELAY_MS);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-1060-\n--\ninclude/linux/device.h=1316=int __must_check device_reprobe(struct device *dev);\ninclude/linux/device.h:1317:int __must_check device_schedule_reprobe(struct device *dev,\ninclude/linux/device.h-1318-\t\t\t\t\t unsigned int delay_ms);\n"
}

5/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 32218
							output: 23
							thoughts: 76
						

Thoughts:
Okay, here's my take on those thoughts, framed from my own expert perspective:

**Investigating the `mxl862xx` Driver**

Alright, so `mxl862xx` popped up. My first instinct, as always, is to understand its role within the system. I need to figure out what kind of device this driver is associated with. My immediate thought is, "Is this another MDIO/DSA switch driver?" Given the context of networking and the file path convention (`drivers/net/dsa/...`), that's the most likely starting point. I need to confirm whether `mxl862xx` handles a specific Maxim Integrated chip, or is a more generic DSA component. I know how those DSA switches are typically instantiated, and if there are specific hardware requirements that I need to be aware of. The best way is to go straight to the source: checking out the code itself, starting with that directory (`drivers/net/dsa/mxl862xx/`) should give me the answers I'm looking for, and will probably involve checking the device-tree bindings and probe/remove functions.



6/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "NET_DSA_MXL862"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/mxl862xx/Kconfig:2:config NET_DSA_MXL862\ndrivers/net/dsa/mxl862xx/Kconfig-3-\ttristate \"MaxLinear MxL862xx\"\n--\ndrivers/net/dsa/mxl862xx/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/mxl862xx/Makefile:2:obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o\ndrivers/net/dsa/mxl862xx/Makefile-3-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 32815
							output: 42
							thoughts: 69
						

Thoughts:
Here's my summary, as though I'm thinking it:

**Analyzing the mxl862xx Driver Code**

Alright, let's dive in. To really understand what we're dealing with, I need to get a clear picture of this `mxl862xx` driver. Time to crack open the Kconfig and the source file – specifically, `drivers/net/dsa/mxl862xx/Kconfig` and `drivers/net/dsa/mxl862xx/mxl862xx.c`. The Kconfig will give me a quick overview of configuration options, but the `mxl862xx.c` file is where the real meat of the driver lives. I want to see how this thing is structured. I need to get the general idea, the device identification (what's the `mdio_driver` structure like?), and how it's integrated into the kernel's device tree setup (look for `of_device_id` entries to see what it's looking for). With those pieces, I should quickly be able to ascertain its role and what kind of hardware it supports.



8/2 2026/09/07 23:30 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/mxl862xx/Kconfig",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\tconfig NET_DSA_MXL862\n   3:\t\ttristate \"MaxLinear MxL862xx\"\n   4:\t\tdepends on NET_DSA\n   5:\t\tselect CRC16\n   6:\t\tselect NET_DSA_TAG_MXL_862XX\n   7:\t\thelp\n   8:\t\t  This enables support for the MaxLinear MxL862xx switch family.\n   9:\t\t  These switches have two 10GE SerDes interfaces, one typically\n  10:\t\t  used as CPU port.\n  11:\t\t   - MxL86282 has eight 2.5 Gigabit PHYs\n  12:\t\t   - MxL86252 has five 2.5 Gigabit PHYs\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 33386
							output: 46
							thoughts: 40
						

Thoughts:
Here's my thinking process:

**Diving into the mxl862xx Probe**

Okay, so I'm focusing on the `mxl862xx` driver within the `drivers/net/dsa/mxl862xx` directory, specifically the `mxl862xx.c` file. I need to understand how this driver is probed. That means I need to figure out how the system identifies and initializes a `mxl862xx` device during boot or hotplug. My goal is to determine the core steps and critical data structures involved. Let's see how the driver handles the detection and setup phase. I need to trace the execution path and identify any registration of device-specific information, resource allocation, and any potential interactions with other subsystems. I'll be looking for clues in the probe function.



10/2 2026/09/07 23:30 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "module_mdio_driver|mdio_driver_register",
  "PathPrefix": "drivers/net/dsa/mxl862xx"
}

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

11/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 33667
							output: 37
							thoughts: 31
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**Navigating the MXL862XX Driver: A Quick Check**

Right, let's take a look at the tail end of `drivers/net/dsa/mxl862xx/mxl862xx.c`. I need to zero in on what's going on with this driver, and frankly, the best way to get a quick overview is often to hit the end of the file. No need to dig too deep right now. Maybe there's a quick patch, a recent commit, or a crucial function call I can spot without spending too much time parsing the whole thing. The end is often where you find the recent activity or the "meat" of the module's interactions with the kernel framework. This is a common tactic for a quick assessment. Let's see what's there...



12/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "mdio_driver",
  "PathPrefix": "drivers/net/dsa/mxl862xx"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2323=MODULE_DEVICE_TABLE(of, mxl862xx_of_match);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2324-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2325:static struct mdio_driver mxl862xx_driver = {\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2326-\t.probe  = mxl862xx_probe,\n"
}

13/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 34113
							output: 35
							thoughts: 91
						

Thoughts:
**My Assessment of the `mxl862xx_driver` in Relation to MDIO and DSA**

Okay, so I'm looking at this `mxl862xx_driver` and it's clearly an `mdio_driver`.  That much is obvious. Now, based on my experience, MDIO switch devices generally depend on a physical or simulated MDIO bus and a corresponding device tree node (OF). But, there's something that's making me pause. I need to dig deeper because I've spotted this in `net/dsa/devlink.c`:  `dsa_devlink_flash_update` has been added to `dsa_devlink_ops`. This signals to me that something more complex could be at play, and I need to understand this new aspect. I will start by focusing my attention on that specific file, `net/dsa/devlink.c`, to figure out the significance and implications of this.



14/2 2026/09/07 23:30 tool read-file
0m Args:
{
  "File": "net/dsa/devlink.c",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0-or-later\n   2:\t/*\n   3:\t * DSA devlink handling\n   4:\t */\n   5:\t\n   6:\t#include \u003cnet/dsa.h\u003e\n   7:\t#include \u003cnet/devlink.h\u003e\n   8:\t\n   9:\t#include \"devlink.h\"\n  10:\t\n  11:\tstatic int dsa_devlink_info_get(struct devlink *dl,\n  12:\t\t\t\t\tstruct devlink_info_req *req,\n  13:\t\t\t\t\tstruct netlink_ext_ack *extack)\n  14:\t{\n  15:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n  16:\t\n  17:\t\tif (ds-\u003eops-\u003edevlink_info_get)\n  18:\t\t\treturn ds-\u003eops-\u003edevlink_info_get(ds, req, extack);\n  19:\t\n  20:\t\treturn -EOPNOTSUPP;\n  21:\t}\n  22:\t\n  23:\tstatic int dsa_devlink_flash_update(struct devlink *dl,\n  24:\t\t\t\t\t    struct devlink_flash_update_params *params,\n  25:\t\t\t\t\t    struct netlink_ext_ack *extack)\n  26:\t{\n  27:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n  28:\t\n  29:\t\tif (!ds-\u003eops-\u003edevlink_flash_update)\n  30:\t\t\treturn -EOPNOTSUPP;\n  31:\t\n  32:\t\treturn ds-\u003eops-\u003edevlink_flash_update(ds, params, extack);\n  33:\t}\n  34:\t\n  35:\tstatic int dsa_devlink_sb_pool_get(struct devlink *dl,\n  36:\t\t\t\t\t   unsigned int sb_index, u16 pool_index,\n  37:\t\t\t\t\t   struct devlink_sb_pool_info *pool_info)\n  38:\t{\n  39:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n  40:\t\n"
}

15/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 35018
							output: 30
							thoughts: 251
						

Thoughts:
**My Initial Assessment of Recent Kernel Driver Changes**

Okay, so I'm diving into these recent changes in the `drivers/base` area, specifically `core.c` and `dd.c`. Let's see... in `core.c`, I see `device_shutdown()` now sets `dev->p->shutdown_done = true`. Nothing immediately jumps out there, but I'll make a note of it.

Then, in `dd.c`, `__device_release_driver` now returns a boolean, indicating if it actually detached a driver. That's a reasonable change for clarity. More interesting are the additions of `device_schedule_reprobe` and `device_reprobe_work_fn`. That hints at some improvements in the driver probing/re-probing flow. Okay, this is significant because I'm wondering if this code is reachable by fuzzing, such as in QEMU or GCE.

Now, the important question is reachability for fuzzing. Can I trigger any of this in a standard, virtualized environment? I need to analyze if these functions and the new logic are exposed by standard fuzzing such as QEMU/GCE. Specifically, I'm thinking about things like `dsa_devlink_flash_update`, `device_schedule_reprobe`, and even a driver like `mxl862xx`. Is any of this reachable in the common fuzzing setups? Can I instantiate and trigger `device_schedule_reprobe`?

Let's think. Can I create DSA switches through `netdevsim` or some kind of virtual interface? Or does DSA *always* require mock devices or specific hardware interactions? Does something like `dsa_loop` or a similar virtual DSA switch even *exist* in the kernel? I need to know. I'm going to quickly grep for any references to `dsa_loop`, or other DSA drivers.



16/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "dsa_loop",
  "PathPrefix": "drivers/net/dsa"
}

Results:
{
  "Output": "drivers/net/dsa/Makefile=3=bcm-sf2-objs\t\t\t:= bcm_sf2.o bcm_sf2_cfp.o\ndrivers/net/dsa/Makefile:4:obj-$(CONFIG_NET_DSA_LOOP)\t+= dsa_loop.o\ndrivers/net/dsa/Makefile-5-obj-$(CONFIG_NET_DSA_KS8995) \t+= ks8995.o\n--\ndrivers/net/dsa/dsa_loop.c-24-\ndrivers/net/dsa/dsa_loop.c:25:struct dsa_loop_vlan {\ndrivers/net/dsa/dsa_loop.c-26-\tu16 members;\n--\ndrivers/net/dsa/dsa_loop.c-29-\ndrivers/net/dsa/dsa_loop.c:30:struct dsa_loop_mib_entry {\ndrivers/net/dsa/dsa_loop.c-31-\tchar name[ETH_GSTRING_LEN];\n--\ndrivers/net/dsa/dsa_loop.c-34-\ndrivers/net/dsa/dsa_loop.c:35:enum dsa_loop_mib_counters {\ndrivers/net/dsa/dsa_loop.c-36-\tDSA_LOOP_PHY_READ_OK,\n--\ndrivers/net/dsa/dsa_loop.c-42-\ndrivers/net/dsa/dsa_loop.c:43:struct dsa_loop_port {\ndrivers/net/dsa/dsa_loop.c:44:\tstruct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];\ndrivers/net/dsa/dsa_loop.c-45-\tu16 pvid;\n--\ndrivers/net/dsa/dsa_loop.c-48-\ndrivers/net/dsa/dsa_loop.c:49:struct dsa_loop_priv {\ndrivers/net/dsa/dsa_loop.c-50-\tstruct mii_bus\t*bus;\ndrivers/net/dsa/dsa_loop.c-51-\tunsigned int\tport_base;\ndrivers/net/dsa/dsa_loop.c:52:\tstruct dsa_loop_vlan vlans[VLAN_N_VID];\ndrivers/net/dsa/dsa_loop.c-53-\tstruct net_device *netdev;\ndrivers/net/dsa/dsa_loop.c:54:\tstruct dsa_loop_port ports[DSA_MAX_PORTS];\ndrivers/net/dsa/dsa_loop.c-55-};\ndrivers/net/dsa/dsa_loop.c-56-\ndrivers/net/dsa/dsa_loop.c:57:struct dsa_loop_pdata {\ndrivers/net/dsa/dsa_loop.c-58-\t/* Must be first, such that dsa_register_switch() can access this\n--\ndrivers/net/dsa/dsa_loop.c-66-\ndrivers/net/dsa/dsa_loop.c:67:static struct dsa_loop_mib_entry dsa_loop_mibs[] = {\ndrivers/net/dsa/dsa_loop.c-68-\t[DSA_LOOP_PHY_READ_OK]\t= { \"phy_read_ok\", },\n--\ndrivers/net/dsa/dsa_loop.c=75=static struct mdio_device *switch_mdiodev;\ndrivers/net/dsa/dsa_loop.c-76-\ndrivers/net/dsa/dsa_loop.c:77:enum dsa_loop_devlink_resource_id {\ndrivers/net/dsa/dsa_loop.c-78-\tDSA_LOOP_DEVLINK_PARAM_ID_NONE,  /* DEVLINK_RESOURCE_ID_PARENT_TOP */\n--\ndrivers/net/dsa/dsa_loop.c-81-\ndrivers/net/dsa/dsa_loop.c:82:static u64 dsa_loop_devlink_vtu_get(void *priv)\ndrivers/net/dsa/dsa_loop.c-83-{\ndrivers/net/dsa/dsa_loop.c:84:\tstruct dsa_loop_priv *ps = priv;\ndrivers/net/dsa/dsa_loop.c-85-\tunsigned int i, count = 0;\ndrivers/net/dsa/dsa_loop.c:86:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-87-\n--\ndrivers/net/dsa/dsa_loop.c-96-\ndrivers/net/dsa/dsa_loop.c:97:static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-98-{\ndrivers/net/dsa/dsa_loop.c-99-\tstruct devlink_resource_size_params size_params;\ndrivers/net/dsa/dsa_loop.c:100:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-101-\tint err;\n--\ndrivers/net/dsa/dsa_loop.c-115-\t\t\t\t\t      DSA_LOOP_DEVLINK_PARAM_ID_VTU,\ndrivers/net/dsa/dsa_loop.c:116:\t\t\t\t\t      dsa_loop_devlink_vtu_get, ps);\ndrivers/net/dsa/dsa_loop.c-117-\n--\ndrivers/net/dsa/dsa_loop.c-124-\ndrivers/net/dsa/dsa_loop.c:125:static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/dsa_loop.c-126-\t\t\t\t\t\t   int port,\n--\ndrivers/net/dsa/dsa_loop.c-133-\ndrivers/net/dsa/dsa_loop.c:134:static int dsa_loop_setup(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-135-{\ndrivers/net/dsa/dsa_loop.c:136:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-137-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-139-\tfor (i = 0; i \u003c ds-\u003enum_ports; i++)\ndrivers/net/dsa/dsa_loop.c:140:\t\tmemcpy(ps-\u003eports[i].mib, dsa_loop_mibs,\ndrivers/net/dsa/dsa_loop.c:141:\t\t       sizeof(dsa_loop_mibs));\ndrivers/net/dsa/dsa_loop.c-142-\n--\ndrivers/net/dsa/dsa_loop.c-144-\ndrivers/net/dsa/dsa_loop.c:145:\treturn dsa_loop_setup_devlink_resources(ds);\ndrivers/net/dsa/dsa_loop.c-146-}\ndrivers/net/dsa/dsa_loop.c-147-\ndrivers/net/dsa/dsa_loop.c:148:static void dsa_loop_teardown(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-149-{\n--\ndrivers/net/dsa/dsa_loop.c-152-\ndrivers/net/dsa/dsa_loop.c:153:static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset)\ndrivers/net/dsa/dsa_loop.c-154-{\n--\ndrivers/net/dsa/dsa_loop.c-160-\ndrivers/net/dsa/dsa_loop.c:161:static void dsa_loop_get_strings(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-162-\t\t\t\t u32 stringset, uint8_t *data)\ndrivers/net/dsa/dsa_loop.c-163-{\ndrivers/net/dsa/dsa_loop.c:164:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-165-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-173-\ndrivers/net/dsa/dsa_loop.c:174:static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-175-\t\t\t\t       uint64_t *data)\ndrivers/net/dsa/dsa_loop.c-176-{\ndrivers/net/dsa/dsa_loop.c:177:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-178-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-183-\ndrivers/net/dsa/dsa_loop.c:184:static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum)\ndrivers/net/dsa/dsa_loop.c-185-{\ndrivers/net/dsa/dsa_loop.c:186:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-187-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-198-\ndrivers/net/dsa/dsa_loop.c:199:static int dsa_loop_phy_write(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-200-\t\t\t      int regnum, u16 value)\ndrivers/net/dsa/dsa_loop.c-201-{\ndrivers/net/dsa/dsa_loop.c:202:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-203-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-214-\ndrivers/net/dsa/dsa_loop.c:215:static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-216-\t\t\t\t     struct dsa_bridge bridge,\n--\ndrivers/net/dsa/dsa_loop.c-225-\ndrivers/net/dsa/dsa_loop.c:226:static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-227-\t\t\t\t       struct dsa_bridge bridge)\n--\ndrivers/net/dsa/dsa_loop.c-232-\ndrivers/net/dsa/dsa_loop.c:233:static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-234-\t\t\t\t\tu8 state)\n--\ndrivers/net/dsa/dsa_loop.c-239-\ndrivers/net/dsa/dsa_loop.c:240:static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-241-\t\t\t\t\tbool vlan_filtering,\n--\ndrivers/net/dsa/dsa_loop.c-249-\ndrivers/net/dsa/dsa_loop.c:250:static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-251-\t\t\t\t  const struct switchdev_obj_port_vlan *vlan,\n--\ndrivers/net/dsa/dsa_loop.c-255-\tbool pvid = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_PVID;\ndrivers/net/dsa/dsa_loop.c:256:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-257-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:258:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-259-\n--\ndrivers/net/dsa/dsa_loop.c-282-\ndrivers/net/dsa/dsa_loop.c:283:static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-284-\t\t\t\t  const struct switchdev_obj_port_vlan *vlan)\n--\ndrivers/net/dsa/dsa_loop.c-286-\tbool untagged = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_UNTAGGED;\ndrivers/net/dsa/dsa_loop.c:287:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-288-\tu16 pvid = ps-\u003eports[port].pvid;\ndrivers/net/dsa/dsa_loop.c-289-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:290:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-291-\n--\ndrivers/net/dsa/dsa_loop.c-310-\ndrivers/net/dsa/dsa_loop.c:311:static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-312-\t\t\t\t    int new_mtu)\ndrivers/net/dsa/dsa_loop.c-313-{\ndrivers/net/dsa/dsa_loop.c:314:\tstruct dsa_loop_priv *priv = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-315-\n--\ndrivers/net/dsa/dsa_loop.c-320-\ndrivers/net/dsa/dsa_loop.c:321:static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port)\ndrivers/net/dsa/dsa_loop.c-322-{\n--\ndrivers/net/dsa/dsa_loop.c-325-\ndrivers/net/dsa/dsa_loop.c:326:static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port,\ndrivers/net/dsa/dsa_loop.c-327-\t\t\t\t      struct phylink_config *config)\n--\ndrivers/net/dsa/dsa_loop.c-333-\ndrivers/net/dsa/dsa_loop.c:334:static const struct dsa_switch_ops dsa_loop_driver = {\ndrivers/net/dsa/dsa_loop.c:335:\t.get_tag_protocol\t= dsa_loop_get_protocol,\ndrivers/net/dsa/dsa_loop.c:336:\t.setup\t\t\t= dsa_loop_setup,\ndrivers/net/dsa/dsa_loop.c:337:\t.teardown\t\t= dsa_loop_teardown,\ndrivers/net/dsa/dsa_loop.c:338:\t.get_strings\t\t= dsa_loop_get_strings,\ndrivers/net/dsa/dsa_loop.c:339:\t.get_ethtool_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:340:\t.get_sset_count\t\t= dsa_loop_get_sset_count,\ndrivers/net/dsa/dsa_loop.c:341:\t.get_ethtool_phy_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:342:\t.phy_read\t\t= dsa_loop_phy_read,\ndrivers/net/dsa/dsa_loop.c:343:\t.phy_write\t\t= dsa_loop_phy_write,\ndrivers/net/dsa/dsa_loop.c:344:\t.port_bridge_join\t= dsa_loop_port_bridge_join,\ndrivers/net/dsa/dsa_loop.c:345:\t.port_bridge_leave\t= dsa_loop_port_bridge_leave,\ndrivers/net/dsa/dsa_loop.c:346:\t.port_stp_state_set\t= dsa_loop_port_stp_state_set,\ndrivers/net/dsa/dsa_loop.c:347:\t.port_vlan_filtering\t= dsa_loop_port_vlan_filtering,\ndrivers/net/dsa/dsa_loop.c:348:\t.port_vlan_add\t\t= dsa_loop_port_vlan_add,\ndrivers/net/dsa/dsa_loop.c:349:\t.port_vlan_del\t\t= dsa_loop_port_vlan_del,\ndrivers/net/dsa/dsa_loop.c:350:\t.port_change_mtu\t= dsa_loop_port_change_mtu,\ndrivers/net/dsa/dsa_loop.c:351:\t.port_max_mtu\t\t= dsa_loop_port_max_mtu,\ndrivers/net/dsa/dsa_loop.c:352:\t.phylink_get_caps\t= dsa_loop_phylink_get_caps,\ndrivers/net/dsa/dsa_loop.c-353-};\ndrivers/net/dsa/dsa_loop.c-354-\ndrivers/net/dsa/dsa_loop.c:355:static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-356-{\ndrivers/net/dsa/dsa_loop.c:357:\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\ndrivers/net/dsa/dsa_loop.c:358:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-359-\tstruct dsa_switch *ds;\n--\ndrivers/net/dsa/dsa_loop.c-382-\tds-\u003edev = \u0026mdiodev-\u003edev;\ndrivers/net/dsa/dsa_loop.c:383:\tds-\u003eops = \u0026dsa_loop_driver;\ndrivers/net/dsa/dsa_loop.c-384-\tds-\u003epriv = ps;\n--\ndrivers/net/dsa/dsa_loop.c-396-\ndrivers/net/dsa/dsa_loop.c:397:static void dsa_loop_drv_remove(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-398-{\ndrivers/net/dsa/dsa_loop.c-399-\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\ndrivers/net/dsa/dsa_loop.c:400:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-401-\n--\ndrivers/net/dsa/dsa_loop.c-410-\ndrivers/net/dsa/dsa_loop.c:411:static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-412-{\n--\ndrivers/net/dsa/dsa_loop.c-422-\ndrivers/net/dsa/dsa_loop.c:423:static struct mdio_driver dsa_loop_drv = {\ndrivers/net/dsa/dsa_loop.c-424-\t.mdiodrv.driver\t= {\n--\ndrivers/net/dsa/dsa_loop.c-426-\t},\ndrivers/net/dsa/dsa_loop.c:427:\t.probe\t= dsa_loop_drv_probe,\ndrivers/net/dsa/dsa_loop.c:428:\t.remove\t= dsa_loop_drv_remove,\ndrivers/net/dsa/dsa_loop.c:429:\t.shutdown = dsa_loop_drv_shutdown,\ndrivers/net/dsa/dsa_loop.c-430-};\ndrivers/net/dsa/dsa_loop.c-431-\ndrivers/net/dsa/dsa_loop.c:432:static int dsa_loop_bus_match(struct device *dev,\ndrivers/net/dsa/dsa_loop.c-433-\t\t\t      const struct device_driver *drv)\ndrivers/net/dsa/dsa_loop.c-434-{\ndrivers/net/dsa/dsa_loop.c:435:\treturn drv == \u0026dsa_loop_drv.mdiodrv.driver;\ndrivers/net/dsa/dsa_loop.c-436-}\ndrivers/net/dsa/dsa_loop.c-437-\ndrivers/net/dsa/dsa_loop.c:438:static void dsa_loop_phydevs_unregister(void)\ndrivers/net/dsa/dsa_loop.c-439-{\n--\ndrivers/net/dsa/dsa_loop.c-445-\ndrivers/net/dsa/dsa_loop.c:446:static int __init dsa_loop_create_switch_mdiodev(void)\ndrivers/net/dsa/dsa_loop.c-447-{\ndrivers/net/dsa/dsa_loop.c:448:\tstatic struct dsa_loop_pdata dsa_loop_pdata = {\ndrivers/net/dsa/dsa_loop.c-449-\t\t.cd = {\n--\ndrivers/net/dsa/dsa_loop.c-470-\ndrivers/net/dsa/dsa_loop.c:471:\tswitch_mdiodev-\u003ebus_match = dsa_loop_bus_match;\ndrivers/net/dsa/dsa_loop.c:472:\tswitch_mdiodev-\u003edev.platform_data = \u0026dsa_loop_pdata;\ndrivers/net/dsa/dsa_loop.c-473-\n--\ndrivers/net/dsa/dsa_loop.c-481-\ndrivers/net/dsa/dsa_loop.c:482:static int __init dsa_loop_init(void)\ndrivers/net/dsa/dsa_loop.c-483-{\n--\ndrivers/net/dsa/dsa_loop.c-486-\ndrivers/net/dsa/dsa_loop.c:487:\tret = dsa_loop_create_switch_mdiodev();\ndrivers/net/dsa/dsa_loop.c-488-\tif (ret)\n--\ndrivers/net/dsa/dsa_loop.c-493-\ndrivers/net/dsa/dsa_loop.c:494:\tret = mdio_driver_register(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c-495-\tif (ret) {\ndrivers/net/dsa/dsa_loop.c:496:\t\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-497-\t\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-502-}\ndrivers/net/dsa/dsa_loop.c:503:module_init(dsa_loop_init);\ndrivers/net/dsa/dsa_loop.c-504-\ndrivers/net/dsa/dsa_loop.c:505:static void __exit dsa_loop_exit(void)\ndrivers/net/dsa/dsa_loop.c-506-{\ndrivers/net/dsa/dsa_loop.c:507:\tmdio_driver_unregister(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c:508:\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-509-\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-511-}\ndrivers/net/dsa/dsa_loop.c:512:module_exit(dsa_loop_exit);\ndrivers/net/dsa/dsa_loop.c-513-\n"
}

17/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 42249
							output: 20
							thoughts: 71
						

Thoughts:
**Analyzing Flash Update Implementation**

Okay, so looking at this patch, it's clear `dsa_loop_driver` doesn't provide an implementation for `devlink_flash_update`. That's fine; not every driver needs to handle that functionality. But, I need to understand the landscape here. The patch *does* reference `mxl862xx` implementing `devlink_flash_update`.

My next step is to get a broader view. I need to quickly identify *all* drivers within the system that currently support or utilize the `devlink_flash_update` function. Knowing who's in that group will provide crucial context for this change and help me understand its potential impact or integration points. Time to dig in and check across the entire codebase.



18/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "devlink_flash_update"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 701 lines.\nUse more precise expression if possible.\n\ndrivers/dpll/zl3073x/devlink.c=137=void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg,\n--\ndrivers/dpll/zl3073x/devlink.c-141-\ndrivers/dpll/zl3073x/devlink.c:142:\tdevlink_flash_update_status_notify(devlink, msg, component, done,\ndrivers/dpll/zl3073x/devlink.c-143-\t\t\t\t\t   total);\n--\ndrivers/dpll/zl3073x/devlink.c=202=zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev,\n--\ndrivers/dpll/zl3073x/devlink.c-220-/**\ndrivers/dpll/zl3073x/devlink.c:221: * zl3073x_devlink_flash_update - Devlink flash update callback\ndrivers/dpll/zl3073x/devlink.c-222- * @devlink: devlink structure pointer\n--\ndrivers/dpll/zl3073x/devlink.c=228=static int\ndrivers/dpll/zl3073x/devlink.c:229:zl3073x_devlink_flash_update(struct devlink *devlink,\ndrivers/dpll/zl3073x/devlink.c:230:\t\t\t     struct devlink_flash_update_params *params,\ndrivers/dpll/zl3073x/devlink.c-231-\t\t\t     struct netlink_ext_ack *extack)\n--\ndrivers/dpll/zl3073x/devlink.c=271=static const struct devlink_ops zl3073x_devlink_ops = {\n--\ndrivers/dpll/zl3073x/devlink.c-275-\t.reload_up = zl3073x_devlink_reload_up,\ndrivers/dpll/zl3073x/devlink.c:276:\t.flash_update = zl3073x_devlink_flash_update,\ndrivers/dpll/zl3073x/devlink.c-277-};\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=262=static void mxl862xx_flash_notify(struct devlink *dl, const char *status,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-264-{\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:265:\tdevlink_flash_update_status_notify(dl, status, NULL, done, total);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-266-}\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=857=int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-925-\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:926:int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:927:\t\t\t\t  struct devlink_flash_update_params *params,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-928-\t\t\t\t  struct netlink_ext_ack *extack)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h=13=int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h-15-\t\t\t      struct netlink_ext_ack *extack);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h:16:int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h:17:\t\t\t\t  struct devlink_flash_update_params *params,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h-18-\t\t\t\t  struct netlink_ext_ack *extack);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2179=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2211-\t.devlink_info_get = mxl862xx_devlink_info_get,\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2212:\t.devlink_flash_update = mxl862xx_devlink_flash_update,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2213-};\n--\ndrivers/net/ethernet/amd/pds_core/core.h=285=int pdsc_dl_flash_update(struct devlink *dl,\ndrivers/net/ethernet/amd/pds_core/core.h:286:\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/core.h-287-\t\t\t struct netlink_ext_ack *extack);\n--\ndrivers/net/ethernet/amd/pds_core/core.h=359=int pdsc_firmware_update(struct pdsc *pdsc,\ndrivers/net/ethernet/amd/pds_core/core.h:360:\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/core.h-361-\t\t\t struct netlink_ext_ack *extack);\n--\ndrivers/net/ethernet/amd/pds_core/devlink.c=87=int pdsc_dl_flash_update(struct devlink *dl,\ndrivers/net/ethernet/amd/pds_core/devlink.c:88:\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/devlink.c-89-\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=154=pdsc_legacy_firmware_update(struct pdsc *pdsc,\ndrivers/net/ethernet/amd/pds_core/fw.c:155:\t\t\t    struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/fw.c-156-\t\t\t    struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-177-\tdl = priv_to_devlink(pdsc);\ndrivers/net/ethernet/amd/pds_core/fw.c:178:\tdevlink_flash_update_status_notify(dl, \"Preparing to flash\",\ndrivers/net/ethernet/amd/pds_core/fw.c-179-\t\t\t\t\t   NULL, 0, 0);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-191-\t\tif (offset \u003e= next_interval) {\ndrivers/net/ethernet/amd/pds_core/fw.c:192:\t\t\tdevlink_flash_update_status_notify(dl, \"Downloading\",\ndrivers/net/ethernet/amd/pds_core/fw.c-193-\t\t\t\t\t\t\t   NULL, offset,\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-213-\t}\ndrivers/net/ethernet/amd/pds_core/fw.c:214:\tdevlink_flash_update_status_notify(dl, \"Downloading\", NULL,\ndrivers/net/ethernet/amd/pds_core/fw.c-215-\t\t\t\t\t   fw-\u003esize, fw-\u003esize);\ndrivers/net/ethernet/amd/pds_core/fw.c-216-\ndrivers/net/ethernet/amd/pds_core/fw.c:217:\tdevlink_flash_update_timeout_notify(dl, \"Installing\", NULL,\ndrivers/net/ethernet/amd/pds_core/fw.c-218-\t\t\t\t\t    PDSC_FW_INSTALL_TIMEOUT);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-234-\ndrivers/net/ethernet/amd/pds_core/fw.c:235:\tdevlink_flash_update_timeout_notify(dl, \"Selecting\", NULL,\ndrivers/net/ethernet/amd/pds_core/fw.c-236-\t\t\t\t\t    PDSC_FW_SELECT_TIMEOUT);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-254-\tif (err)\ndrivers/net/ethernet/amd/pds_core/fw.c:255:\t\tdevlink_flash_update_status_notify(dl, \"Flash failed\",\ndrivers/net/ethernet/amd/pds_core/fw.c-256-\t\t\t\t\t\t   NULL, 0, 0);\ndrivers/net/ethernet/amd/pds_core/fw.c-257-\telse\ndrivers/net/ethernet/amd/pds_core/fw.c:258:\t\tdevlink_flash_update_status_notify(dl, \"Flash done\",\ndrivers/net/ethernet/amd/pds_core/fw.c-259-\t\t\t\t\t\t   NULL, 0, 0);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=269=struct pds_core_fwu_priv {\ndrivers/net/ethernet/amd/pds_core/fw.c-270-\tstruct pldmfw context;\ndrivers/net/ethernet/amd/pds_core/fw.c:271:\tstruct devlink_flash_update_params *params;\ndrivers/net/ethernet/amd/pds_core/fw.c-272-\tstruct netlink_ext_ack *extack;\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=748=static int pdsc_flash_component(struct pldmfw *context,\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-781-\tif (pdsc_skip_component(priv, component-\u003eidentifier)) {\ndrivers/net/ethernet/amd/pds_core/fw.c:782:\t\tdevlink_flash_update_status_notify(dl, \"Skipped\",\ndrivers/net/ethernet/amd/pds_core/fw.c-783-\t\t\t\t\t\t   component_name, 0, 0);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-838-\t\t\terr = 0;\ndrivers/net/ethernet/amd/pds_core/fw.c:839:\t\t\tdevlink_flash_update_status_notify(dl, \"Skipped\",\ndrivers/net/ethernet/amd/pds_core/fw.c-840-\t\t\t\t\t\t\t   component_name,\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-851-\t\toffset += copy_sz;\ndrivers/net/ethernet/amd/pds_core/fw.c:852:\t\tdevlink_flash_update_status_notify(dl,\ndrivers/net/ethernet/amd/pds_core/fw.c-853-\t\t\t\t\t\t   \"Erasing/Flashing\",\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-861-err_out:\ndrivers/net/ethernet/amd/pds_core/fw.c:862:\tdevlink_flash_update_status_notify(dl,\ndrivers/net/ethernet/amd/pds_core/fw.c-863-\t\t\t\t\t   \"Erasing/Flashing Component Failed\",\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=881=static int pdsc_finalize_update(struct pldmfw *context)\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-905-\tif (err) {\ndrivers/net/ethernet/amd/pds_core/fw.c:906:\t\tdevlink_flash_update_status_notify(dl, \"Finalize Update Failed\",\ndrivers/net/ethernet/amd/pds_core/fw.c-907-\t\t\t\t\t\t   component_name, 0, 0);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c-911-\ndrivers/net/ethernet/amd/pds_core/fw.c:912:\tdevlink_flash_update_status_notify(dl, \"Finalized Update\",\ndrivers/net/ethernet/amd/pds_core/fw.c-913-\t\t\t\t\t   component_name, 0, 0);\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=925=static int pdsc_pldm_firmware_update(struct pdsc *pdsc,\ndrivers/net/ethernet/amd/pds_core/fw.c:926:\t\t\t\t     struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/fw.c-927-\t\t\t\t     struct netlink_ext_ack *extack,\n--\ndrivers/net/ethernet/amd/pds_core/fw.c=969=int pdsc_firmware_update(struct pdsc *pdsc,\ndrivers/net/ethernet/amd/pds_core/fw.c:970:\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/amd/pds_core/fw.c-971-\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c=36=bnxt_dl_flash_update(struct devlink *dl,\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c:37:\t\t     struct devlink_flash_update_params *params,\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-38-\t\t     struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-42-\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c:43:\tdevlink_flash_update_status_notify(dl, \"Preparing to flash\", NULL, 0, 0);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-44-\trc = bnxt_flash_package_from_fw_obj(bp-\u003edev, params-\u003efw, 0, extack);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-45-\tif (!rc)\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c:46:\t\tdevlink_flash_update_status_notify(dl, \"Flashing done\", NULL, 0, 0);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-47-\telse\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c:48:\t\tdevlink_flash_update_status_notify(dl, \"Flashing failed\", NULL, 0, 0);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c-49-\treturn rc;\n--\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c=242=static int hinic_firmware_update(struct hinic_devlink_priv *priv,\n--\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c-280-\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c:281:static int hinic_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c:282:\t\t\t\t      struct devlink_flash_update_params *params,\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c-283-\t\t\t\t      struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c=290=static const struct devlink_ops hinic_devlink_ops = {\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c:291:\t.flash_update = hinic_devlink_flash_update,\ndrivers/net/ethernet/huawei/hinic/hinic_devlink.c-292-};\n--\ndrivers/net/ethernet/intel/ice/devlink/devlink.c=1323=static const struct devlink_ops ice_devlink_ops = {\n--\ndrivers/net/ethernet/intel/ice/devlink/devlink.c-1331-\t.info_get = ice_devlink_info_get,\ndrivers/net/ethernet/intel/ice/devlink/devlink.c:1332:\t.flash_update = ice_devlink_flash_update,\ndrivers/net/ethernet/intel/ice/devlink/devlink.c-1333-\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c=406=ice_write_nvm_module(struct ice_pf *pf, u16 module, const char *component,\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-420-\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:421:\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-422-\t\t\t\t\t   component, 0, length);\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-447-\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:448:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-449-\t\t\t\t\t\t   component, offset, length);\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-454-\tif (err)\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:455:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing failed\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-456-\t\t\t\t\t\t   component, length, length);\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-457-\telse\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:458:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing done\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-459-\t\t\t\t\t\t   component, length, length);\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c=485=ice_erase_nvm_module(struct ice_pf *pf, u16 module, const char *component,\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-500-\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:501:\tdevlink_flash_update_timeout_notify(devlink, \"Erasing\", component, ICE_FW_ERASE_TIMEOUT);\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-502-\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-548-\tif (err)\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:549:\t\tdevlink_flash_update_status_notify(devlink, \"Erasing failed\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-550-\t\t\t\t\t\t   component, 0, 0);\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-551-\telse\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:552:\t\tdevlink_flash_update_status_notify(devlink, \"Erasing done\",\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-553-\t\t\t\t\t\t   component, 0, 0);\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c=701=static int ice_finalize_update(struct pldmfw *context)\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-727-\tcase ICE_AQC_NVM_EMPR_FLAG:\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:728:\t\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-729-\t\t\t\t\t\t   \"Activate new firmware by devlink reload action fw_activate\",\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-732-\tcase ICE_AQC_NVM_PERST_FLAG:\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:733:\t\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-734-\t\t\t\t\t\t   \"Activate new firmware by rebooting the system\",\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-738-\tdefault:\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:739:\t\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-740-\t\t\t\t\t\t   \"Activate new firmware by power cycling the system\",\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c=915=ice_cancel_pending_update(struct ice_pf *pf, const char *component,\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-948-\t */\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:949:\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-950-\t\t\t\t\t   \"Canceling previous pending update\",\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-974-/**\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:975: * ice_devlink_flash_update - Write a firmware image to the device\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-976- * @devlink: pointer to devlink associated with the device to update\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-989- */\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:990:int ice_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:991:\t\t\t     struct devlink_flash_update_params *params,\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-992-\t\t\t     struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-1039-\ndrivers/net/ethernet/intel/ice/ice_fw_update.c:1040:\tdevlink_flash_update_status_notify(devlink, \"Preparing to flash\", NULL, 0, 0);\ndrivers/net/ethernet/intel/ice/ice_fw_update.c-1041-\n--\ndrivers/net/ethernet/intel/ice/ice_fw_update.h-6-\ndrivers/net/ethernet/intel/ice/ice_fw_update.h:7:int ice_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/intel/ice/ice_fw_update.h:8:\t\t\t     struct devlink_flash_update_params *params,\ndrivers/net/ethernet/intel/ice/ice_fw_update.h-9-\t\t\t     struct netlink_ext_ack *extack);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c=267=static int ixgbe_write_nvm_module(struct ixgbe_adapter *adapter, u16 module,\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-277-\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:278:\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-279-\t\t\t\t\t   component, 0, length);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-301-\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:302:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-303-\t\t\t\t\t\t   component, offset, length);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-306-\tif (err)\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:307:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing failed\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-308-\t\t\t\t\t\t   component, length, length);\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-309-\telse\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:310:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing done\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-311-\t\t\t\t\t\t   component, length, length);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c=337=static int ixgbe_erase_nvm_module(struct ixgbe_adapter *adapter, u16 module,\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-344-\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:345:\tdevlink_flash_update_timeout_notify(devlink, \"Erasing\", component,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-346-\t\t\t\t\t    IXGBE_FW_ERASE_TIMEOUT);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-349-\tif (err)\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:350:\t\tdevlink_flash_update_status_notify(devlink, \"Erasing failed\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-351-\t\t\t\t\t\t   component, 0, 0);\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-352-\telse\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:353:\t\tdevlink_flash_update_status_notify(devlink, \"Erasing done\",\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-354-\t\t\t\t\t\t   component, 0, 0);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c=467=static int ixgbe_finalize_update(struct pldmfw *context)\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-485-\tif (!adapter-\u003efw_emp_reset_disabled)\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:486:\t\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-487-\t\t\t\t\t\t   \"Suggested is to activate new firmware by devlink reload, if it doesn't work then a power cycle is required\",\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c=558=static int ixgbe_cancel_pending_update(struct ixgbe_adapter *adapter,\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-591-\t */\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:592:\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-593-\t\t\t\t\t   \"Canceling previous pending update\",\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c=627=int ixgbe_flash_pldm_image(struct devlink *devlink,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:628:\t\t\t   struct devlink_flash_update_params *params,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-629-\t\t\t   struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-681-\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c:682:\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.c-683-\t\t\t\t\t   \"Preparing to flash\", NULL, 0, 0);\n--\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.h=7=int ixgbe_flash_pldm_image(struct devlink *devlink,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.h:8:\t\t\t   struct devlink_flash_update_params *params,\ndrivers/net/ethernet/intel/ixgbe/ixgbe_fw_update.h-9-\t\t\t   struct netlink_ext_ack *extack);\n--\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c-14-\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c:15:static int mlx5_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c:16:\t\t\t\t     struct devlink_flash_update_params *params,\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c-17-\t\t\t\t     struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c=368=static const struct devlink_ops mlx5_devlink_ops = {\n--\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c-390-#endif\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c:391:\t.flash_update = mlx5_devlink_flash_update,\ndrivers/net/ethernet/mellanox/mlx5/core/devlink.c-392-\t.info_get = mlx5_devlink_info_get,\n--\ndrivers/net/ethernet/mellanox/mlxfw/mlxfw_fsm.c=194=static void mlxfw_status_notify(struct mlxfw_dev *mlxfw_dev,\n--\ndrivers/net/ethernet/mellanox/mlxfw/mlxfw_fsm.c-197-{\ndrivers/net/ethernet/mellanox/mlxfw/mlxfw_fsm.c:198:\tdevlink_flash_update_status_notify(mlxfw_dev-\u003edevlink, msg, comp_name,\ndrivers/net/ethernet/mellanox/mlxfw/mlxfw_fsm.c-199-\t\t\t\t\t   done_bytes, total_bytes);\n--\ndrivers/net/ethernet/mellanox/mlxsw/core.c=1301=static int mlxsw_core_fw_flash_update(struct mlxsw_core *mlxsw_core,\ndrivers/net/ethernet/mellanox/mlxsw/core.c:1302:\t\t\t\t      struct devlink_flash_update_params *params,\ndrivers/net/ethernet/mellanox/mlxsw/core.c-1303-\t\t\t\t      struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/mellanox/mlxsw/core.c=1593=mlxsw_devlink_core_bus_device_reload_up(struct devlink *devlink, enum devlink_reload_action action,\n--\ndrivers/net/ethernet/mellanox/mlxsw/core.c-1608-\ndrivers/net/ethernet/mellanox/mlxsw/core.c:1609:static int mlxsw_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/mellanox/mlxsw/core.c:1610:\t\t\t\t      struct devlink_flash_update_params *params,\ndrivers/net/ethernet/mellanox/mlxsw/core.c-1611-\t\t\t\t      struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/mellanox/mlxsw/core.c=1734=static const struct devlink_ops mlxsw_devlink_ops = {\n--\ndrivers/net/ethernet/mellanox/mlxsw/core.c-1749-\t.info_get\t\t\t= mlxsw_devlink_info_get,\ndrivers/net/ethernet/mellanox/mlxsw/core.c:1750:\t.flash_update\t\t\t= mlxsw_devlink_flash_update,\ndrivers/net/ethernet/mellanox/mlxsw/core.c-1751-\t.trap_init\t\t\t= mlxsw_devlink_trap_init,\n--\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c=110=static int\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c:111:mlxsw_linecard_dev_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c:112:\t\t\t\t\tstruct devlink_flash_update_params *params,\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c-113-\t\t\t\t\tstruct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c=122=static const struct devlink_ops mlxsw_linecard_dev_devlink_ops = {\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c-123-\t.info_get\t\t\t= mlxsw_linecard_dev_devlink_info_get,\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c:124:\t.flash_update\t\t\t= mlxsw_linecard_dev_devlink_flash_update,\ndrivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c-125-};\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c=116=fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-154-\t\t\t anti_rollback_ver, fbd-\u003efw_cap.anti_rollback_version);\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:155:\t\tdevlink_flash_update_status_notify(devlink, buf,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-156-\t\t\t\t\t\t   \"Anti-Rollback\", 0, 0);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c=194=fbnic_flash_component(struct pldmfw *context,\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-225-\t\t\t component-\u003eidentifier);\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:226:\t\tdevlink_flash_update_status_notify(devlink, buf, NULL, 0,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-227-\t\t\t\t\t\t   size);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-247-\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:248:\tdevlink_flash_update_timeout_notify(devlink, \"Initializing\",\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-249-\t\t\t\t\t    component_name, 15);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-277-\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:278:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-279-\t\t\t\t\t\t   component_name,\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-299-\t\t\t err);\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:300:\t\tdevlink_flash_update_status_notify(devlink, buf,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-301-\t\t\t\t\t\t   component_name, 0, 0);\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c=316=static int\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:317:fbnic_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:318:\t\t\t   struct devlink_flash_update_params *params,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-319-\t\t\t   struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c=368=static const struct devlink_ops fbnic_devlink_ops = {\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-369-\t.info_get\t= fbnic_devlink_info_get,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c:370:\t.flash_update\t= fbnic_devlink_flash_update,\ndrivers/net/ethernet/meta/fbnic/fbnic_devlink.c-371-};\n--\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c=310=static int\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c:311:nfp_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c:312:\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c-313-\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c=318=const struct devlink_ops nfp_devlink_ops = {\n--\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c-323-\t.info_get\t\t= nfp_devlink_info_get,\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c:324:\t.flash_update\t\t= nfp_devlink_flash_update,\ndrivers/net/ethernet/netronome/nfp/nfp_devlink.c-325-};\n--\ndrivers/net/ethernet/pensando/ionic/ionic_devlink.c=12=static int ionic_dl_flash_update(struct devlink *dl,\ndrivers/net/ethernet/pensando/ionic/ionic_devlink.c:13:\t\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/ethernet/pensando/ionic/ionic_devlink.c-14-\t\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c=94=int ionic_firmware_update(struct ionic_lif *lif, const struct firmware *fw,\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-109-\tdl = priv_to_devlink(ionic);\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:110:\tdevlink_flash_update_status_notify(dl, \"Preparing to flash\", NULL, 0, 0);\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-111-\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-126-\t\tif (offset \u003e= next_interval) {\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:127:\t\t\tdevlink_flash_update_status_notify(dl, \"Downloading\", NULL,\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-128-\t\t\t\t\t\t\t   offset, fw-\u003esize);\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-149-\t}\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:150:\tdevlink_flash_update_status_notify(dl, \"Downloading\", NULL,\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-151-\t\t\t\t\t   fw-\u003esize, fw-\u003esize);\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-152-\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:153:\tdevlink_flash_update_timeout_notify(dl, \"Installing\", NULL,\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-154-\t\t\t\t\t    IONIC_FW_INSTALL_TIMEOUT);\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-173-\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:174:\tdevlink_flash_update_timeout_notify(dl, \"Selecting\", NULL,\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-175-\t\t\t\t\t    IONIC_FW_SELECT_TIMEOUT);\n--\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-196-\tif (err)\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:197:\t\tdevlink_flash_update_status_notify(dl, \"Flash failed\", NULL, 0, 0);\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-198-\telse\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c:199:\t\tdevlink_flash_update_status_notify(dl, \"Flash done\", NULL, 0, 0);\ndrivers/net/ethernet/pensando/ionic/ionic_fw.c-200-\treturn err;\n--\ndrivers/net/ethernet/sfc/efx_devlink.c=595=static int efx_devlink_info_get(struct devlink *devlink,\n--\ndrivers/net/ethernet/sfc/efx_devlink.c-618-\ndrivers/net/ethernet/sfc/efx_devlink.c:619:static int efx_devlink_flash_update(struct devlink *devlink,\ndrivers/net/ethernet/sfc/efx_devlink.c:620:\t\t\t\t    struct devlink_flash_update_params *params,\ndrivers/net/ethernet/sfc/efx_devlink.c-621-\t\t\t\t    struct netlink_ext_ack *extack)\n--\ndrivers/net/ethernet/sfc/efx_devlink.c=629=static const struct devlink_ops sfc_devlink_ops = {\ndrivers/net/ethernet/sfc/efx_devlink.c-630-\t.supported_flash_update_params\t= 0,\ndrivers/net/ethernet/sfc/efx_devlink.c:631:\t.flash_update\t\t\t= efx_devlink_flash_update,\ndrivers/net/ethernet/sfc/efx_devlink.c-632-\t.info_get\t\t\t= efx_devlink_info_get,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c=270=static int efx_reflash_erase_partition(struct efx_nic *efx,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-298-\t\tif (offset \u003e= next_update) {\ndrivers/net/ethernet/sfc/efx_reflash.c:299:\t\t\tdevlink_flash_update_status_notify(devlink, \"Erasing\",\ndrivers/net/ethernet/sfc/efx_reflash.c-300-\t\t\t\t\t\t\t   NULL, offset,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-314-\ndrivers/net/ethernet/sfc/efx_reflash.c:315:\tdevlink_flash_update_status_notify(devlink, \"Erasing\", NULL,\ndrivers/net/ethernet/sfc/efx_reflash.c-316-\t\t\t\t\t   partition_size, partition_size);\n--\ndrivers/net/ethernet/sfc/efx_reflash.c=321=static int efx_reflash_write_partition(struct efx_nic *efx,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-346-\t\tif (offset \u003e= next_update) {\ndrivers/net/ethernet/sfc/efx_reflash.c:347:\t\t\tdevlink_flash_update_status_notify(devlink, \"Writing\",\ndrivers/net/ethernet/sfc/efx_reflash.c-348-\t\t\t\t\t\t\t   NULL, offset,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-367-\t\tif (offset \u003e= next_update)\ndrivers/net/ethernet/sfc/efx_reflash.c:368:\t\t\tdevlink_flash_update_status_notify(devlink, \"Writing\",\ndrivers/net/ethernet/sfc/efx_reflash.c-369-\t\t\t\t\t\t\t   NULL, offset,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-388-\ndrivers/net/ethernet/sfc/efx_reflash.c:389:\tdevlink_flash_update_status_notify(devlink, \"Writing\", NULL, data_size,\ndrivers/net/ethernet/sfc/efx_reflash.c-390-\t\t\t\t\t   data_size);\n--\ndrivers/net/ethernet/sfc/efx_reflash.c=395=int efx_reflash_flash_firmware(struct efx_nic *efx, const struct firmware *fw,\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-411-\ndrivers/net/ethernet/sfc/efx_reflash.c:412:\tdevlink_flash_update_status_notify(devlink, \"Checking update\", NULL, 0, 0);\ndrivers/net/ethernet/sfc/efx_reflash.c-413-\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-486-\ndrivers/net/ethernet/sfc/efx_reflash.c:487:\tdevlink_flash_update_status_notify(devlink, \"Starting update\", NULL, 0, 0);\ndrivers/net/ethernet/sfc/efx_reflash.c-488-\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-506-\ndrivers/net/ethernet/sfc/efx_reflash.c:507:\tdevlink_flash_update_timeout_notify(devlink, \"Finishing update\", NULL,\ndrivers/net/ethernet/sfc/efx_reflash.c-508-\t\t\t\t\t    EFX_DEVLINK_UPDATE_FINISH_TIMEOUT);\n--\ndrivers/net/ethernet/sfc/efx_reflash.c-517-\tmutex_unlock(\u0026efx-\u003ereflash_mutex);\ndrivers/net/ethernet/sfc/efx_reflash.c:518:\tdevlink_flash_update_status_notify(devlink, rc ? \"Update failed\" :\ndrivers/net/ethernet/sfc/efx_reflash.c-519-\t\t\t\t\t\t\t \"Update complete\",\n--\ndrivers/net/netdevsim/dev.c=1004=static int nsim_dev_flash_update(struct devlink *devlink,\ndrivers/net/netdevsim/dev.c:1005:\t\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/netdevsim/dev.c-1006-\t\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/netdevsim/dev.c-1014-\tif (nsim_dev-\u003efw_update_status) {\ndrivers/net/netdevsim/dev.c:1015:\t\tdevlink_flash_update_status_notify(devlink,\ndrivers/net/netdevsim/dev.c-1016-\t\t\t\t\t\t   \"Preparing to flash\",\n--\ndrivers/net/netdevsim/dev.c-1021-\t\tif (nsim_dev-\u003efw_update_status)\ndrivers/net/netdevsim/dev.c:1022:\t\t\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/netdevsim/dev.c-1023-\t\t\t\t\t\t\t   params-\u003ecomponent,\n--\ndrivers/net/netdevsim/dev.c-1029-\tif (nsim_dev-\u003efw_update_status) {\ndrivers/net/netdevsim/dev.c:1030:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing\",\ndrivers/net/netdevsim/dev.c-1031-\t\t\t\t\t\t   params-\u003ecomponent,\n--\ndrivers/net/netdevsim/dev.c-1033-\t\t\t\t\t\t   NSIM_DEV_FLASH_SIZE);\ndrivers/net/netdevsim/dev.c:1034:\t\tdevlink_flash_update_timeout_notify(devlink, \"Flash select\",\ndrivers/net/netdevsim/dev.c-1035-\t\t\t\t\t\t    params-\u003ecomponent, 81);\ndrivers/net/netdevsim/dev.c:1036:\t\tdevlink_flash_update_status_notify(devlink, \"Flashing done\",\ndrivers/net/netdevsim/dev.c-1037-\t\t\t\t\t\t   params-\u003ecomponent, 0, 0);\n--\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c=64=int ipc_coredump_get_list(struct iosm_devlink *devlink, u16 cmd)\n--\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c-112-\t\t\t\tfilename, file_size);\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c:113:\t\t\tdevlink_flash_update_status_notify(devlink-\u003edevlink_ctx,\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c-114-\t\t\t\t\t\t\t   filename,\n--\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c-116-\t\t\tsnprintf(size, sizeof(size), \"%d\", file_size);\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c:117:\t\t\tdevlink_flash_update_status_notify(devlink-\u003edevlink_ctx,\ndrivers/net/wwan/iosm/iosm_ipc_coredump.c-118-\t\t\t\t\t\t\t   size, \"FILE SIZE\",\n--\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c=59=ipc_devlink_get_flash_comp_type(const char comp_str[], u32 len)\n--\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c-78- */\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c:79:static int ipc_devlink_flash_update(struct devlink *devlink,\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c:80:\t\t\t\t    struct devlink_flash_update_params *params,\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c-81-\t\t\t\t    struct netlink_ext_ack *extack)\n--\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c-119-\tdefault:\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c:120:\t\tdevlink_flash_update_status_notify(devlink, \"Invalid component\",\ndrivers/net/wwan/iosm/iosm_ipc_devlink.c-121-\t\t\t\t\t\t   NULL, 0, 0);\n\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 56647
							output: 36
							thoughts: 89
						

Thoughts:
**Analyzing Driver Core Reachability**

Okay, let's dive into this. I need to understand the impact of these `drivers/base/` changes on the reachability within the driver core. Specifically, I see `device_schedule_reprobe` has been introduced, which is a key signal. Then, there's a modification to `__device_release_driver` in `drivers/base/dd.c` where it now returns a boolean instead of void. That's a definite point of interest and potential control flow impact. I think focusing on `__device_release_driver` is the logical next step. I'll need to understand what this boolean signifies. Does it indicate success, failure, or something more nuanced in the driver release process? This will be crucial to understand how the system recovers.



20/2 2026/09/07 23:30 tool read-file
0m Args:
{
  "File": "drivers/base/dd.c",
  "FirstLine": 1315,
  "LineCount": 60
}

Results:
{
  "Contents": "1315:\t * __device_release_driver() must be called with @dev lock held.\n1316:\t * When called for a USB interface, @dev-\u003eparent lock must be held as well.\n1317:\t */\n1318:\tstatic bool __device_release_driver(struct device *dev, struct device *parent)\n1319:\t{\n1320:\t\tstruct device_driver *drv;\n1321:\t\n1322:\t\tdrv = dev-\u003edriver;\n1323:\t\tif (drv) {\n1324:\t\t\tpm_runtime_get_sync(dev);\n1325:\t\n1326:\t\t\twhile (device_links_busy(dev)) {\n1327:\t\t\t\t__device_driver_unlock(dev, parent);\n1328:\t\n1329:\t\t\t\tdevice_links_unbind_consumers(dev);\n1330:\t\n1331:\t\t\t\t__device_driver_lock(dev, parent);\n1332:\t\t\t\t/*\n1333:\t\t\t\t * A concurrent invocation of the same function might\n1334:\t\t\t\t * have released the driver successfully while this one\n1335:\t\t\t\t * was waiting, so check for that.\n1336:\t\t\t\t */\n1337:\t\t\t\tif (dev-\u003edriver != drv) {\n1338:\t\t\t\t\tpm_runtime_put(dev);\n1339:\t\t\t\t\treturn false;\n1340:\t\t\t\t}\n1341:\t\t\t}\n1342:\t\n1343:\t\t\tdriver_sysfs_remove(dev);\n1344:\t\n1345:\t\t\tbus_notify(dev, BUS_NOTIFY_UNBIND_DRIVER);\n1346:\t\n1347:\t\t\tpm_runtime_put_sync(dev);\n1348:\t\n1349:\t\t\tdevice_remove(dev);\n1350:\t\n1351:\t\t\tif (dev-\u003ebus \u0026\u0026 dev-\u003ebus-\u003edma_cleanup)\n1352:\t\t\t\tdev-\u003ebus-\u003edma_cleanup(dev);\n1353:\t\n1354:\t\t\tdevice_unbind_cleanup(dev);\n1355:\t\t\tdevice_links_driver_cleanup(dev);\n1356:\t\n1357:\t\t\tklist_remove(\u0026dev-\u003ep-\u003eknode_driver);\n1358:\t\t\tdevice_pm_check_callbacks(dev);\n1359:\t\n1360:\t\t\tbus_notify(dev, BUS_NOTIFY_UNBOUND_DRIVER);\n1361:\t\t\tkobject_uevent(\u0026dev-\u003ekobj, KOBJ_UNBIND);\n1362:\t\t\treturn true;\n1363:\t\t}\n1364:\t\n1365:\t\treturn false;\n1366:\t}\n1367:\t\n1368:\tvoid device_release_driver_internal(struct device *dev,\n1369:\t\t\t\t\t    const struct device_driver *drv,\n1370:\t\t\t\t\t    struct device *parent)\n1371:\t{\n1372:\t\t__device_driver_lock(dev, parent);\n1373:\t\n1374:\t\tif (!drv || drv == dev-\u003edriver)\n"
}

21/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 57758
							output: 36
							thoughts: 161
						

Thoughts:
**My Investigation into Device Driver Release**

Okay, so I'm diving into this `device_release_driver_internal` function, and it looks like it's the one calling `__device_release_driver(dev, parent);` (I'm skipping over the return value for now, let's keep it simple). My next step, as always, is to understand the calling contexts. Let's see who's calling `__device_release_driver`. Well, we've got `device_release_driver_internal` itself (obviously), and then `device_reprobe_work_fn`. Hmm, interesting. Now, where is `device_schedule_reprobe` called? I need to trace this back. Ah, only in `drivers/net/dsa/mxl862xx/mxl862xx-fw.c`! That narrows things down a bit, usually good to get that kind of info up front. Wait a second, something just pinged in my brain... doesn't `device_shutdown()` also relate to this? Let me quickly pull up `drivers/base/core.c` and take a look, I'll need to go to line 4910, just to be sure...



22/2 2026/09/07 23:30 tool read-file
0m Args:
{
  "File": "drivers/base/core.c",
  "FirstLine": 4905,
  "LineCount": 25
}

Results:
{
  "Contents": "4905:\t\t\tlist_del_init(\u0026dev-\u003ekobj.entry);\n4906:\t\t\tspin_unlock(\u0026devices_kset-\u003elist_lock);\n4907:\t\n4908:\t\t\t/* hold lock to avoid race with probe/release */\n4909:\t\t\tif (parent)\n4910:\t\t\t\tdevice_lock(parent);\n4911:\t\t\tdevice_lock(dev);\n4912:\t\n4913:\t\t\tif (dev-\u003ep)\n4914:\t\t\t\tdev-\u003ep-\u003eshutdown_done = true;\n4915:\t\n4916:\t\t\t/* Don't allow any more runtime suspends */\n4917:\t\t\tpm_runtime_get_noresume(dev);\n4918:\t\t\tpm_runtime_barrier(dev);\n4919:\t\n4920:\t\t\tif (dev-\u003eclass \u0026\u0026 dev-\u003eclass-\u003eshutdown_pre) {\n4921:\t\t\t\tif (initcall_debug)\n4922:\t\t\t\t\tdev_info(dev, \"shutdown_pre\\n\");\n4923:\t\t\t\tdev-\u003eclass-\u003eshutdown_pre(dev);\n4924:\t\t\t}\n4925:\t\t\tif (dev-\u003ebus \u0026\u0026 dev-\u003ebus-\u003eshutdown) {\n4926:\t\t\t\tif (initcall_debug)\n4927:\t\t\t\t\tdev_info(dev, \"shutdown\\n\");\n4928:\t\t\t\tdev-\u003ebus-\u003eshutdown(dev);\n4929:\t\t\t} else if (dev-\u003edriver \u0026\u0026 dev-\u003edriver-\u003eshutdown) {\n"
}

23/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 58612
							output: 21
							thoughts: 534
						

Thoughts:
**Analyzing a Potentially Unreachable Code Path**

Okay, let's break this down. First, `device_shutdown()` sets `shutdown_done = true;`. That's a starting point, but not the focus. Now, the key is `mxl862xx`. I know that's a MaxLinear MxL862xx switch driver, located in `drivers/net/dsa/mxl862xx`. It communicates with external MaxLinear Ethernet switch chips. The question is, is this hardware actually present in the target environment, like QEMU or GCE? Nope! It's a physical Ethernet switch ASIC. Standard QEMU doesn't emulate these. This immediately triggers alarm bells regarding potential unreachable code.

Let's go back to the prompt's negative criteria. It points to "Structurally unreachable hardware" like vendor-specific PCIe switches, which require physical ASICs that aren't emulated. That seems like a direct hit! Then there are "Unreachable execution paths," specifically driver teardown callbacks that are triggered by hot-unplug or manual unbinding. This also seems relevant and needs careful consideration.

Now, hold on. Let's not jump to conclusions. What else in the patch was changed? I need to broaden my scope. Let's look at `include/net/dsa.h` and `net/dsa/devlink.c`. Specifically, `dsa_devlink_flash_update`.

```c
static int dsa_devlink_flash_update(struct devlink *dl,
				    struct devlink_flash_update_params *params,
				    struct netlink_ext_ack *extack)
{
	struct dsa_switch *ds = dsa_devlink_to_ds(dl);

	if (!ds->ops->devlink_flash_update)
		return -EOPNOTSUPP;

	return ds->ops->devlink_flash_update(ds, params, extack);
}
```

Okay, `dsa_devlink_flash_update` in the `mxl862xx` driver. But here's where things get interesting. Is this function implemented by any *other* DSA switch driver in the tree? If only the `mxl862xx` implements it, and the chip is not emulated, then that might make the code unreachable. The next step is a code search across the entire kernel tree for `\.devlink_flash_update` to confirm.



24/2 2026/09/07 23:30 tool grepper
0m Args:
{
  "Expression": "\\.devlink_flash_update"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2179=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2211-\t.devlink_info_get = mxl862xx_devlink_info_get,\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2212:\t.devlink_flash_update = mxl862xx_devlink_flash_update,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2213-};\n"
}

25/2 2026/09/07 23:30 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 59909
							output: 51
							thoughts: 545
						

Thoughts:
**My Analysis of the DSA and Devlink Flash Update Mechanisms**

Okay, so `mxl862xx` is the *only* DSA driver I can find that actually implements the `devlink_flash_update` functionality. That's a key observation. If I were to try to use devlink flash update with any other DSA switch, like the `dsa_loop` driver, I'd get an `-EOPNOTSUPP` error immediately. This is because the underlying `ds->ops->devlink_flash_update` function would be `NULL` in those other cases.

Now, a critical question arises: Can I actually *reach* `dsa_devlink_flash_update` via devlink flash update netlink commands even if a mock DSA switch like `dsa_loop` is registered? Let me dig a little deeper. I need to confirm whether `dsa_loop` or any similar driver actually registers a DSA switch without any physical hardware present.

Even if I *could* register `dsa_loop`, calling devlink flash update on it would still trigger that boilerplate delegation:
```c
static int dsa_devlink_flash_update(struct devlink *dl,
				    struct devlink_flash_update_params *params,
				    struct netlink_ext_ack *extack)
{
	struct dsa_switch *ds = dsa_devlink_to_ds(dl);

	if (!ds->ops->devlink_flash_update)
		return -EOPNOTSUPP;

	return ds->ops->devlink_flash_update(ds, params, extack);
}
```
That code just checks for the NULL function pointer and returns -EOPNOTSUPP anyway. So that's a dead end.

Next, I need to consider changes in the driver core and how they might affect this. Specifically, the interactions in `drivers/base/dd.c` (regarding `device_schedule_reprobe` and `device_reprobe_work_fn`) and `drivers/base/core.c` (`device_shutdown`). I see those are only called by `mxl862xx-fw.c` specifically (through `mxl862xx_rescue_heal_work_fn` and `mxl862xx_devlink_flash_update`). During a normal fuzzing scenario (without the real `mxl862xx` hardware), `device_schedule_reprobe` is never called. And, of course, `mxl862xx` is a hardware driver for the MaxLinear MxL862xx switch chips, an MDIO connected Ethernet switch ASIC.

My current concern is whether `mxl862xx` can be probed *without* its physical hardware being present. I need to carefully review the `mxl862xx_probe` function.



26/2 2026/09/07 23:30 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
  "FirstLine": 2208,
  "LineCount": 80
}

Results:
{
  "Contents": "2208:\t\t.get_pause_stats = mxl862xx_get_pause_stats,\n2209:\t\t.get_rmon_stats = mxl862xx_get_rmon_stats,\n2210:\t\t.get_stats64 = mxl862xx_get_stats64,\n2211:\t\t.devlink_info_get = mxl862xx_devlink_info_get,\n2212:\t\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n2213:\t};\n2214:\t\n2215:\tstatic int mxl862xx_probe(struct mdio_device *mdiodev)\n2216:\t{\n2217:\t\tstruct device *dev = \u0026mdiodev-\u003edev;\n2218:\t\tstruct mxl862xx_priv *priv;\n2219:\t\tstruct dsa_switch *ds;\n2220:\t\tint err, i;\n2221:\t\n2222:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n2223:\t\tif (!priv)\n2224:\t\t\treturn -ENOMEM;\n2225:\t\n2226:\t\tpriv-\u003emdiodev = mdiodev;\n2227:\t\n2228:\t\tds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);\n2229:\t\tif (!ds)\n2230:\t\t\treturn -ENOMEM;\n2231:\t\n2232:\t\tpriv-\u003eds = ds;\n2233:\t\tds-\u003edev = dev;\n2234:\t\tds-\u003epriv = priv;\n2235:\t\tds-\u003eops = \u0026mxl862xx_switch_ops;\n2236:\t\tds-\u003ephylink_mac_ops = \u0026mxl862xx_phylink_mac_ops;\n2237:\t\tds-\u003enum_ports = MXL862XX_MAX_PORTS;\n2238:\t\tds-\u003eassisted_learning_on_cpu_port = true;\n2239:\t\tds-\u003efdb_isolation = true;\n2240:\t\tds-\u003emax_num_bridges = MXL862XX_MAX_BRIDGES;\n2241:\t\n2242:\t\tmxl862xx_host_init(priv);\n2243:\t\n2244:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++) {\n2245:\t\t\tpriv-\u003eports[i].priv = priv;\n2246:\t\t\tINIT_WORK(\u0026priv-\u003eports[i].host_flood_work,\n2247:\t\t\t\t  mxl862xx_host_flood_work_fn);\n2248:\t\t\tspin_lock_init(\u0026priv-\u003eports[i].stats_lock);\n2249:\t\t}\n2250:\t\n2251:\t\tINIT_DELAYED_WORK(\u0026priv-\u003estats_work, mxl862xx_stats_work_fn);\n2252:\t\n2253:\t\tdev_set_drvdata(dev, ds);\n2254:\t\n2255:\t\terr = dsa_register_switch(ds);\n2256:\t\tif (err) {\n2257:\t\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2258:\t\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2259:\t\t\tmxl862xx_host_shutdown(priv);\n2260:\t\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2261:\t\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2262:\t\t}\n2263:\t\n2264:\t\treturn err;\n2265:\t}\n2266:\t\n2267:\tstatic void mxl862xx_remove(struct mdio_device *mdiodev)\n2268:\t{\n2269:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2270:\t\tstruct mxl862xx_priv *priv;\n2271:\t\tint i;\n2272:\t\n2273:\t\tif (!ds)\n2274:\t\t\treturn;\n2275:\t\n2276:\t\tpriv = ds-\u003epriv;\n2277:\t\n2278:\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2279:\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2280:\t\n2281:\t\tdsa_unregister_switch(ds);\n2282:\t\n2283:\t\tmxl862xx_host_shutdown(priv);\n2284:\t\n2285:\t\t/* Cancel any pending host flood work. dsa_unregister_switch()\n2286:\t\t * has already called port_teardown (which sets setup_done=false),\n2287:\t\t * but a worker could still be blocked on rtnl_lock(). Since we\n"
}

27/2 2026/09/07 23:30 llm patch-evaluator
1m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 62477
							output: 163
							thoughts: 1327
						

Thoughts:
**Triage of MaxLinear MxL862xx Switch Driver Changes for Fuzzing**

Okay, let's break this down. The patch introduces changes centered around the MaxLinear MxL862xx switch ASIC driver. Specifically, `mxl862xx_probe` kicks things off, registering the switch and then calling `mxl862xx_setup`. `mxl862xx_setup` then calls `mxl862xx_rescue_mode_detect`, which is where the hardware interaction begins.  This detection routine probes the MDIO bus with specific addresses and data (`MXL862XX_SB_PDI_ADDR`, `MXL862XX_SB_PDI_DATA`, and associated probe values), and returns `-ENODEV` if the chip isn't present, or doesn't respond correctly. The driver is bound based on Device Tree entries (e.g., "maxlinear,mxl86282"). This strongly indicates physical hardware.

Now, considering the fuzzing guidelines, let's review:  The changes involve the `drivers/net/dsa/mxl862xx/` directory, which indicates a vendor-specific switch ASIC driver (MaxLinear MxL862xx MDIO switch).  I see that this driver is tightly coupled with its hardware; its purpose is to interface with a physical ASIC, and the rescue mode detection confirms this.

I also checked if the generic changes in `drivers/base/` or `net/dsa/devlink.c` could be triggered or worth fuzzing on their own:
1. `drivers/base/core.c`: in `device_shutdown()`, sets `dev->p->shutdown_done = true;`. `device_shutdown()` is only called on system shutdown/halt/reboot/kexec.
2. `drivers/base/dd.c`: `__device_release_driver()` returning a boolean indicating if release happened, and adding `device_schedule_reprobe()` which is helper infrastructure created specifically for `mxl862xx` (no other in-tree caller).
3. `net/dsa/devlink.c`: `dsa_devlink_flash_update()` is just a stub wrapper that dispatches to `ds->ops->devlink_flash_update`. The only driver implementing `devlink_flash_update` in DSA is `mxl862xx`.
4. `drivers/net/dsa/mxl862xx/*`: Vendor-specific switch ASIC driver (MaxLinear MxL862xx MDIO switch).

Therefore, this entire patch series is either directly related to the MaxLinear MxL862xx switch ASIC driver, documentation/MAINTAINERS entries, or helper functions/fields (`device_schedule_reprobe`, `shutdown_done`, `dsa_devlink_flash_update`) exclusively for this driver.  Since fuzzing would require this specific physical hardware, it's not going to happen in any standard virtualized fuzzing environment like QEMU or Google Compute Engine.

So, the conclusion is straightforward: this is **not** worth fuzzing.  The changes are not reachable in standard fuzzing environments because of hardware requirements.

Final settings will be:
*   `WorthFuzzing`: false
*   `FocusSymbols`: []
*   `EnableConfigs`: []
*   `Reasoning`: The patch adds firmware update and rescue mode handling for MaxLinear MxL862xx DSA switches and creates DSA devlink flash update plumbing and a driver core reprobe helper (`device_schedule_reprobe()`) only called by mxl862xx. The driver is specific to the hardware, requiring physical MaxLinear MxL862xx switch hardware on an MDIO bus not emulated in virtualized environments.



28/2 2026/09/07 23:31 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces firmware flashing, MCUboot rescue mode detection/recovery, and devlink flash update support for MaxLinear MxL862xx DSA Ethernet switches. It also adds a DSA devlink flash update dispatch helper in net/dsa/devlink.c and a deferred device reprobe helper (device_schedule_reprobe) in the driver core. The modified driver requires physical MaxLinear MxL862xx switch hardware on an MDIO bus, which is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE), and the generic devlink/driver-core helpers are only exercised by this driver.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces firmware flashing, MCUboot rescue mode detection/recovery, and devlink flash update support for MaxLinear MxL862xx DSA Ethernet switches. It also adds a DSA devlink flash update dispatch helper in net/dsa/devlink.c and a deferred device reprobe helper (device_schedule_reprobe) in the driver core. The modified driver requires physical MaxLinear MxL862xx switch hardware on an MDIO bus, which is structurally unreachable in standard virtualized fuzzing environments (QEMU/GCE), and the generic devlink/driver-core helpers are only exercised by this driver.",
  "WorthFuzzing": false
}

29/1 2026/09/07 23:31 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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