AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series adds devlink firmware flashing, rescue mode support, and firmware version reporting for the MaxLinear MxL862xx Ethernet switch driver (drivers/net/dsa/mxl862xx/), introduces a deferred reprobe helper device_schedule_reprobe() in drivers/base/dd.c, and plumbs devlink_flash_update into DSA's devlink operations (net/dsa/devlink.c). The MxL862xx is a physical Ethernet switch ASIC requiring dedicated MDIO bus hardware, which is not present or emulated in standard virtualized fuzzing environments (QEMU/GCE). Furthermore, device_schedule_reprobe() is solely called by the unreachable mxl862xx driver, and mxl862xx is the only DSA driver that implements .devlink_flash_update. Therefore, the modified code is structurally unreachable.",
  "WorthFuzzing": false
}

1/1 2026/09/28 00:52 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 37957babf9b094443d4c34f813db57f5252bd780\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Sep 28 00:52:39 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 1af780c811ee2..53d3ef16d13f4 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..c2dfc72ef9a40\n--- /dev/null\n+++ b/Documentation/networking/devlink/mxl862xx.rst\n@@ -0,0 +1,95 @@\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 CHIP ID registers as well. Both\n+       values are published behind the same check, so it is omitted\n+       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; it is reported as\n+       running only, since the driver cannot tell what the flash holds\n+       while the loader runs. 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; they\n+come back registered but down, so userspace brings them up again. A\n+complete flash and reprobe cycle takes about one minute. In the rare\n+case that the reprobe cannot be scheduled at all, the kernel log says\n+so, ``devlink dev flash`` reports that error or the transfer's own if\n+the transfer failed as well, and the driver stays bound to a switch it\n+no longer tracks, refusing further updates with ``-EBUSY``, until it is\n+unbound and rebound. A reboot started\n+while an update is running waits for the transfer to finish, and an\n+update requested after the system has begun shutting down is refused\n+with ``-ENODEV``.\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 driver gives up on the recovery, ``devlink dev flash`` returns\n+``-EIO`` and says so in its extack message. The drain runs once and is\n+never resumed, so a failed MDIO transaction ends it as well. A loader\n+that stops answering the drain needs a power cycle; a completed drain\n+whose reprobe could not be scheduled, and a drain a bus error cut\n+short, need only a driver rebind. The driver re-examines the switch\n+when it binds and at no other time, so a power cycle on a board where\n+the switch can be cycled on its own still has to be followed by an\n+unbind and rebind for the recovered switch to be recognised.\n+\n+A download interrupted during its opening handshake, before the image\n+header reached the loader, is reported the same way. The loader waits\n+for a header that no later session can supply, so that state needs a\n+power cycle.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 6de1ff058db64..cb1dad080f63c 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -16250,6 +16250,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/dd.c b/drivers/base/dd.c\nindex f6525a7ee8c54..823a25f9c0895 100644\n--- a/drivers/base/dd.c\n+++ b/drivers/base/dd.c\n@@ -1436,3 +1436,129 @@ 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+\tconst struct device_driver *drv;\n+\tconst char *drv_name;\n+\tstruct device *dev;\n+\tunsigned long delay;\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 *dev = rp-\u003edev;\n+\tbool detached = false;\n+\tint ret;\n+\n+\tdevice_lock(dev);\n+\t/*\n+\t * rp-\u003edrv is only compared, never dereferenced: the driver it points\n+\t * to may have been unregistered and freed. The saved name rejects a\n+\t * freed address the allocator has since handed to another driver.\n+\t */\n+\tif (!dev-\u003ep-\u003edead \u0026\u0026 dev-\u003edriver == rp-\u003edrv \u0026\u0026\n+\t    !strcmp(dev-\u003edriver-\u003ename, rp-\u003edrv_name)) {\n+\t\tif (defer_all_probes) {\n+\t\t\tdevice_unlock(dev);\n+\t\t\tqueue_delayed_work(system_freezable_wq, \u0026rp-\u003ework,\n+\t\t\t\t\t   rp-\u003edelay);\n+\t\t\treturn;\n+\t\t}\n+\t\t__device_release_driver(dev, NULL);\n+\t\tdetached = true;\n+\t}\n+\tdevice_unlock(dev);\n+\n+\tif (detached) {\n+\t\tret = device_attach(dev);\n+\t\t/* 0 is unbound too: a failed probe is folded into \"no match\" */\n+\t\tif (ret \u003c= 0 \u0026\u0026 ret != -EPROBE_DEFER)\n+\t\t\tdev_err(dev, \"re-probe left the device unbound\\n\");\n+\t}\n+\n+\tput_device(dev);\n+\tkfree(rp-\u003edrv_name);\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+ * from built-in driver-core work rather than a driver-owned work item,\n+ * so the bound driver may call it without pinning its own module. The\n+ * binding is recorded as the driver pointer plus a copy of its name; the\n+ * pointer is only ever compared, never dereferenced, and the name copy\n+ * guards against a freed \u0026struct device_driver address the allocator\n+ * later hands to a different driver.\n+ *\n+ * The re-probe is skipped when the work runs if @dev has since been\n+ * removed, is no longer bound or is bound to a different driver; an\n+ * unbind followed by a rebind of the same driver within the delay still\n+ * gets it. While probing is blocked, for a system suspend or shutdown,\n+ * the work re-arms itself after @delay_ms, so a request pending across\n+ * suspend runs once the system has resumed and one that fires during a\n+ * shutdown detaches nothing. A failed re-probe leaves @dev unbound, as a\n+ * failed initial probe would.\n+ *\n+ * This is device_reprobe() deferred, and shares its limitations;\n+ * __device_release_driver() is unchanged. The detach and the re-attach\n+ * are not one locked operation, so an administrative unbind arriving\n+ * between them may be undone, and the attach half runs the normal probe\n+ * path with no shutdown re-check of its own. If @dev has managed\n+ * consumers, detaching it unbinds them as any driver release does, so a\n+ * re-probe a concurrent device_shutdown() overtakes may run -\u003eremove()\n+ * in place of -\u003eshutdown(). None of this is specific to this helper.\n+ *\n+ * Buses that take the parent lock to bind (only usb_bus_type) are refused\n+ * with -EINVAL: the parent would have to be recorded before either lock\n+ * is held, where device_move() can replace it.\n+ *\n+ * Context: May sleep (allocates with %GFP_KERNEL). Must be called by the\n+ * driver bound to @dev, from a process context its -\u003eremove() waits for,\n+ * so that the binding outlives the call; @dev's own device lock may be\n+ * held, but not from -\u003eprobe(), which the scheduled work would detach.\n+ *\n+ * Returns: 0 on success, -EINVAL if @dev is not a registered device\n+ * bound to a driver or sits on a bus which takes the parent lock to\n+ * bind, -ENOMEM on allocation failure.\n+ */\n+int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)\n+{\n+\tconst struct device_driver *drv;\n+\tstruct device_reprobe *rp;\n+\n+\tdrv = READ_ONCE(dev-\u003edriver);\n+\t/*\n+\t * A bus taking the parent lock would need @dev's parent pinned until\n+\t * the work runs, which device_move() can invalidate.\n+\t */\n+\tif (!drv || !dev-\u003ebus || dev-\u003ebus-\u003eneed_parent_lock || !dev-\u003ep ||\n+\t    dev-\u003ep-\u003edead || !device_is_registered(dev))\n+\t\treturn -EINVAL;\n+\n+\trp = kzalloc_obj(*rp);\n+\tif (!rp)\n+\t\treturn -ENOMEM;\n+\n+\trp-\u003edrv_name = kstrdup(drv-\u003ename, GFP_KERNEL);\n+\tif (!rp-\u003edrv_name) {\n+\t\tkfree(rp);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\trp-\u003edev = get_device(dev);\n+\trp-\u003edrv = drv;\n+\trp-\u003edelay = msecs_to_jiffies(delay_ms);\n+\n+\tINIT_DELAYED_WORK(\u0026rp-\u003ework, device_reprobe_work_fn);\n+\tqueue_delayed_work(system_freezable_wq, \u0026rp-\u003ework, rp-\u003edelay);\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(device_schedule_reprobe);\ndiff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kconfig\nindex e51a67a3cf9bf..8bb524fe28d1f 100644\n--- a/drivers/net/dsa/mxl862xx/Kconfig\n+++ b/drivers/net/dsa/mxl862xx/Kconfig\n@@ -3,6 +3,7 @@ config NET_DSA_MXL862\n \ttristate \"MaxLinear MxL862xx\"\n \tdepends on NET_DSA\n \tselect CRC16\n+\tselect CRC32\n \tselect NET_DSA_TAG_MXL_862XX\n \thelp\n \t  This enables support for the MaxLinear MxL862xx switch family.\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..236dc79460f71\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\n@@ -0,0 +1,1156 @@\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, -EIO for good once the\n+ * recovery has given up, and reprobes to WSP firmware 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/sched.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+/* STAT poll intervals: a mailbox step is quick, an erase is not */\n+#define MXL862XX_SB_PDI_POLL_US\t\t50\n+#define MXL862XX_SB_PDI_SLOW_POLL_US\t10000\n+\n+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)\n+{\n+\tint ret;\n+\n+\t/* CTRL selects RST mode; ADDR and DATA are cleared to 0. */\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, 0x0000);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);\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, or\n+ * -ECANCELED once teardown asks the caller to stop. On timeout the value is\n+ * 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 sleep_us,\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  test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags),\n+\t\t\t  sleep_us, timeout_ms * 1000, false,\n+\t\t\t  priv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\treturn -ECANCELED;\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. Before the first chunk it may also still be erasing\n+ * for the session that died, which no verify window covers. Wait that out --\n+ * STAT cannot tell the cases apart, and guessing would mean writing END into a\n+ * 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 window\n+ * expires, -ECANCELED on teardown, 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+\tif (chunk)\n+\t\tstat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US,\n+\t\t\t\t\t\t  MXL862XX_SB_PDI_VERIFY_MS);\n+\telse\n+\t\tstat = mxl862xx_sb_pdi_poll_drain(priv,\n+\t\t\t\t\t\t  MXL862XX_SB_PDI_SLOW_POLL_US,\n+\t\t\t\t\t\t  MXL862XX_FW_ERASE_TIMEOUT_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\tif (!chunk) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"flash: loader still busy after the erase window\\n\");\n+\t\t\treturn -EIO;\n+\t\t}\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+/* Walk the loader's receive counter to zero (see the header): the image size\n+ * died with the host, and a chunk larger than what is outstanding underflows\n+ * the counter, so only single bytes are safe. Tens of minutes for a multi-MiB\n+ * remainder. Returns 0 once the loader has left the receive loop, \u003c0 on error;\n+ * a counter an earlier oversized chunk underflowed needs a power cycle.\n+ */\n+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)\n+{\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+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\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_POLL_US,\n+\t\t\t\t\t\t  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 only runs into its timeouts\n+ * when no WSP firmware answers). A scratch write to ADDR/DATA must latch or the\n+ * chip is absent (-ENODEV); the mailbox is reset first, or a transfer\n+ * interrupted with CTRL=WR would take that write as a payload word instead of\n+ * latching it. STAT then classifies the state, poked destructively only when 0,\n+ * the one value a 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+ *  - 0xf48f/0xf490: a download handshake nobody can finish; rescue, but only\n+ *    a power cycle gets the loader out of it.\n+ *  - other non-zero: running firmware, left unpoked. With @settle the loader\n+ *    gets one step to publish 0 or READY first; a count outliving that is\n+ *    rescue only when it is the erase of a session that died (header length\n+ *    + 1), anything else a firmware that never became ready.\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+ * -ECANCELED when teardown interrupts a poll, or an SMDIO bus error.\n+ */\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv, bool settle)\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/* The download handshake lives in STAT too and outlives a mailbox\n+\t * reset, so an aborted transfer leaves the loader waiting for a\n+\t * header no later session can supply: only a power cycle clears it.\n+\t */\n+\tif ((u16)stat == MXL862XX_SB_PDI_START ||\n+\t    (u16)stat == MXL862XX_SB_PDI_START + 1) {\n+\t\tWRITE_ONCE(priv-\u003erescue_failed, true);\n+\t\treturn MXL862XX_IN_RESCUE;\n+\t}\n+\n+\t/* Any other non-zero value is a running firmware, not a loader -- but\n+\t * the loader also holds the count of a chunk it is programming or\n+\t * erasing for, so let a caller whose clause-45 wait has failed give it\n+\t * a step for the next state. Only the erase of the session that died\n+\t * outlives that wait; any other count left is a firmware that answered\n+\t * the reset without becoming ready.\n+\t */\n+\tif (stat) {\n+\t\tif (!settle)\n+\t\t\treturn MXL862XX_NOT_RESCUE;\n+\n+\t\tstat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US,\n+\t\t\t\t\t\t  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\tWRITE_ONCE(priv-\u003erescue_ready, true);\n+\t\t\treturn MXL862XX_IN_RESCUE;\n+\t\t}\n+\t\tif (stat == MXL862XX_FW_HDR_SIZE + 1)\n+\t\t\treturn MXL862XX_IN_RESCUE;\n+\t\tif (stat)\n+\t\t\treturn MXL862XX_NOT_RESCUE;\n+\t}\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_POLL_US,\n+\t\t\t\t\tMXL862XX_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, val, i;\n+\n+\t/* Step 1: reboot the firmware into MCUboot rescue mode */\n+\tif (!READ_ONCE(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/* The ACK is the count + 1, replaced by 0 as soon as the erase is done,\n+\t * so wait for the count to go: a short erase retires the ACK between\n+\t * two polls.\n+\t */\n+\tret = read_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t\tval \u003c 0 || (u16)val != MXL862XX_FW_HDR_SIZE,\n+\t\t\t\t10000, MXL862XX_FW_ACK_TIMEOUT_MS * 1000, false,\n+\t\t\t\tpriv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\tret = val;\n+\tif (!ret \u0026\u0026 (u16)val != MXL862XX_FW_HDR_SIZE + 1 \u0026\u0026 (u16)val != 0)\n+\t\tret = -EPROTO;\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+\tif (ret \u003c 0)\n+\t\tdev_warn(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t \"flash: END signalling failed, waiting the loader out: %pe\\n\",\n+\t\t\t ERR_PTR(ret));\n+\n+\tmsleep(MXL862XX_FW_REBOOT_DELAY_MS);\n+\treturn 0;\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 * and as running only, since what the flash holds is unknown here.\n+\t */\n+\tif (READ_ONCE(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\treturn devlink_info_version_running_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/* Written under the instance lock this call is holding. */\n+\tif (priv-\u003eshutting_down) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"device is shutting down\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\t/* A previous flash has short-circuited the firmware API until the\n+\t * reprobe or a rebind, so the raw SB PDI writes below would run\n+\t * against 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   \"a previous flash left the driver awaiting reinitialisation\");\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, \"loader cannot accept a download\");\n+\t\treturn -EIO;\n+\t}\n+\n+\tif (READ_ONCE(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\");\n+\t\treturn -EBUSY;\n+\t}\n+\n+\tif (READ_ONCE(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_owner = current;\n+\t\tWRITE_ONCE(priv-\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 * and with it the loader's clean READY state.\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\tWRITE_ONCE(priv-\u003erescue_ready, false);\n+\t}\n+\n+\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tpriv-\u003eflash_owner = NULL;\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+\tif (err)\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"flash: re-probe could not be scheduled (%pe); unbind and rebind to reinitialise\\n\",\n+\t\t\tERR_PTR(err));\n+\n+\treturn ret ? ret : err;\n+}\n+\n+/* The devlink core holds the instance lock across a flash, so taking it here\n+ * waits for a transfer in flight instead of cutting the image in half, and\n+ * bars one that has not started yet.\n+ */\n+void mxl862xx_flash_shutdown(struct dsa_switch *ds)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (!ds-\u003edevlink)\n+\t\treturn;\n+\n+\t/* A flash holds the instance lock for its whole run. Announce the\n+\t * wait so the delay is not mistaken for a hang.\n+\t */\n+\tif (!devl_trylock(ds-\u003edevlink)) {\n+\t\tdev_info(ds-\u003edev,\n+\t\t\t \"firmware update in progress, waiting for it to finish\\n\");\n+\t\tdevl_lock(ds-\u003edevlink);\n+\t}\n+\tpriv-\u003eshutting_down = true;\n+\tdevl_unlock(ds-\u003edevlink);\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..02e5a627e9471\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\n@@ -0,0 +1,21 @@\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, bool settle);\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+void mxl862xx_flash_shutdown(struct dsa_switch *ds);\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..6d9ff283969ab 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n@@ -12,9 +12,12 @@\n #include \u003clinux/crc16.h\u003e\n #include \u003clinux/iopoll.h\u003e\n #include \u003clinux/limits.h\u003e\n+#include \u003clinux/sched.h\u003e\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@@ -248,11 +251,8 @@ static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)\n \tlen_enc = ret;\n \n \tret = mxl862xx_crc6_verify(ctrl_enc, len_enc, \u0026fw_result);\n-\tif (ret) {\n-\t\tif (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, \u0026priv-\u003eflags))\n-\t\t\tschedule_work(\u0026priv-\u003ecrc_err_work);\n-\t\treturn -EIO;\n-\t}\n+\tif (ret)\n+\t\treturn -EBADMSG;\n \n \treturn fw_result;\n }\n@@ -303,7 +303,9 @@ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,\n \n \tret = mxl862xx_issue_cmd(priv, cmd, size);\n \n-\t/* Handle errors returned by the firmware as -EIO.\n+\t/* Handle errors returned by the firmware as -EIO, a CRC mismatch,\n+\t * found here or reported by the firmware, as -EBADMSG for\n+\t * mxl862xx_api_wrap() to act on.\n \t * The firmware is based on Zephyr OS and uses the errors as\n \t * defined in errno.h of Zephyr OS. See\n \t * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h\n@@ -314,13 +316,12 @@ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,\n \t *   -1023: CRC-16 mismatch on data payload\n \t */\n \tif (ret \u003c 0) {\n-\t\tif ((ret == MXL862XX_FW_CRC6_ERR ||\n-\t\t     ret == MXL862XX_FW_CRC16_ERR) \u0026\u0026\n-\t\t    !test_and_set_bit(MXL862XX_FLAG_CRC_ERR, \u0026priv-\u003eflags))\n-\t\t\tschedule_work(\u0026priv-\u003ecrc_err_work);\n \t\tif (!quiet)\n \t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n \t\t\t\t\"CMD %04x returned error %d\\n\", cmd, ret);\n+\t\tif (ret == -EBADMSG || ret == MXL862XX_FW_CRC6_ERR ||\n+\t\t    ret == MXL862XX_FW_CRC16_ERR)\n+\t\t\treturn -EBADMSG;\n \t\treturn -EIO;\n \t}\n \n@@ -340,6 +341,29 @@ 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 * A blocked write reports success: the reprobe discards the switch\n+\t * configuration anyway, and a bridge tearing down over a flash must\n+\t * not see port_vlan_del() fail, which leaks its VLAN group.\n+\t */\n+\tif (priv-\u003eblock_host \u0026\u0026 cmd != SYS_MISC_FW_UPDATE \u0026\u0026\n+\t    !(read \u0026\u0026 priv-\u003eflash_owner == current)) {\n+\t\tret = read ? -EBUSY : 0;\n+\t\tgoto out;\n+\t}\n+\n \tmax = (size + 1) / 2;\n \n \tret = mxl862xx_busy_wait(priv);\n@@ -460,9 +484,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,\n \t}\n \n \tif (crc16(0xffff, (const u8 *)data, size) != crc) {\n-\t\tif (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, \u0026priv-\u003eflags))\n-\t\t\tschedule_work(\u0026priv-\u003ecrc_err_work);\n-\t\tret = -EIO;\n+\t\tret = -EBADMSG;\n \t\tgoto out;\n \t}\n \n@@ -472,6 +494,15 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,\n \tdev_dbg(\u0026priv-\u003emdiodev-\u003edev, \"RET %d DATA %*ph\\n\", ret, size, data);\n \n out:\n+\t/* A quiet command is expected to go unanswered by a switch still\n+\t * booting or sitting in its loader, so it does not arm the shutdown.\n+\t */\n+\tif (ret == -EBADMSG) {\n+\t\tif (!quiet \u0026\u0026\n+\t\t    !test_and_set_bit(MXL862XX_FLAG_CRC_ERR, \u0026priv-\u003eflags))\n+\t\t\tschedule_work(\u0026priv-\u003ecrc_err_work);\n+\t\tret = -EIO;\n+\t}\n \tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n \n \treturn ret;\n@@ -495,12 +526,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..df77bbf108d5e 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n@@ -47,7 +47,12 @@ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,\n \t\tfallthrough;\n \tcase 10 ... 12:\n \tcase 14 ... 16:\n-\t\tif (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))\n+\t\t/* Rescue mode has no firmware version, so bypass the gate and\n+\t\t * advertise the full set; a CPU port on a quad sub-interface\n+\t\t * would otherwise get an empty mask and fail phylink_create().\n+\t\t */\n+\t\tif (!READ_ONCE(priv-\u003erescue_mode) \u0026\u0026\n+\t\t    !MXL862XX_FW_VER_MIN(priv, 1, 0, 84))\n \t\t\tbreak;\n \t\t__set_bit(PHY_INTERFACE_MODE_QSGMII, config-\u003esupported_interfaces);\n \t\t__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config-\u003esupported_interfaces);\n@@ -406,6 +411,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,\n \n \tswitch (port) {\n \tcase 9 ... 16:\n+\t\tif (READ_ONCE(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 e05ad52cd297e..f16e3e33a0464 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,45 @@ 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+/* The CHIP ID registers are only readable through the firmware mailbox, so\n+ * the values are cached here and stay zero while no firmware answers.\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 the chip\n+ * ID when it can be read. 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 +300,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@@ -616,27 +667,85 @@ static void mxl862xx_free_bridge(struct dsa_switch *ds,\n \tpriv-\u003ebridges[bridge-\u003enum] = 0;\n }\n \n+static void mxl862xx_setup_rescue(struct dsa_switch *ds)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (priv-\u003erescue_ready) {\n+\t\tdev_warn(ds-\u003edev,\n+\t\t\t \"switch in MCUboot rescue mode, use devlink to flash new firmware\\n\");\n+\t\treturn;\n+\t}\n+\n+\tif (priv-\u003erescue_failed) {\n+\t\tdev_warn(ds-\u003edev,\n+\t\t\t \"switch in MCUboot with an unfinishable download handshake; see Documentation/networking/devlink/mxl862xx.rst\\n\");\n+\t\treturn;\n+\t}\n+\n+\t/* Drain the wedged download in the background so it never holds the\n+\t * devlink lock; info and flash become available once ready.\n+\t */\n+\tdev_warn(ds-\u003edev,\n+\t\t \"switch in MCUboot with an interrupted download, recovering in background\\n\");\n+\tqueue_work(system_long_wq, \u0026priv-\u003erescue_heal_work);\n+}\n+\n static int mxl862xx_setup(struct dsa_switch *ds)\n {\n \tstruct mxl862xx_priv *priv = ds-\u003epriv;\n \tint n_user_ports = 0, max_vlans;\n \tint ingress_finals, vid_rules;\n \tstruct dsa_port *dp;\n-\tint ret, i;\n+\tint ret, i, rescue;\n \n-\tret = mxl862xx_reset(priv);\n-\tif (ret)\n-\t\treturn ret;\n+\t/* Detect the loader over SB PDI first: it needs no firmware, unlike the\n+\t * C45 API (mxl862xx_reset/wait_ready), which spends its whole 10 s\n+\t * window on a mailbox nobody answers. Touch C45 only once rescue is\n+\t * ruled out.\n+\t */\n+\trescue = mxl862xx_rescue_mode_detect(priv, false);\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-\tret = mxl862xx_wait_ready(ds);\n-\tif (ret)\n-\t\treturn ret;\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, true);\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+\t/* Software-only SerDes state, needed before anything can reach phylink,\n+\t * including a rescue-mode flash clearing rescue_mode ahead of 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+\tif (priv-\u003erescue_mode) {\n+\t\tmxl862xx_setup_rescue(ds);\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@@ -727,11 +836,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 (READ_ONCE(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 (READ_ONCE(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@@ -1349,6 +1468,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 (READ_ONCE(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@@ -1358,11 +1488,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@@ -1547,6 +1672,16 @@ static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,\n \tqparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb-\u003evid));\n \n \tret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);\n+\t/* A flash blocks the API (-EBUSY); its teardown or MCUboot drops the\n+\t * MAC table (-ENODEV); there is then nothing to program, and the\n+\t * reprobe rebuilds the configuration. The flash moves from block_host\n+\t * to skip_teardown outside this call, so either errno counts under\n+\t * either flag. See mxl862xx_port_mdb_del().\n+\t */\n+\tif ((ret == -EBUSY || ret == -ENODEV) \u0026\u0026\n+\t    (priv-\u003eblock_host || priv-\u003eskip_teardown ||\n+\t     READ_ONCE(priv-\u003erescue_mode)))\n+\t\treturn 0;\n \tif (ret)\n \t\treturn ret;\n \n@@ -1584,6 +1719,16 @@ 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/* A flash blocks the API (-EBUSY); its teardown or MCUboot drops the\n+\t * MAC table (-ENODEV); a delete then has nothing to do, and the flash\n+\t * moves from block_host to skip_teardown outside this call, so either\n+\t * errno counts under either flag. Outside these, both are bus errors\n+\t * and must be reported.\n+\t */\n+\tif ((ret == -EBUSY || ret == -ENODEV) \u0026\u0026\n+\t    (priv-\u003eblock_host || priv-\u003eskip_teardown ||\n+\t     READ_ONCE(priv-\u003erescue_mode)))\n+\t\treturn 0;\n \tif (ret)\n \t\treturn ret;\n \n@@ -1640,6 +1785,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 (READ_ONCE(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@@ -1818,6 +1966,11 @@ static void mxl862xx_get_ethtool_stats(struct dsa_switch *ds, int port,\n \tvoid *field;\n \n \tret = mxl862xx_read_rmon(ds, port, \u0026cnt);\n+\t/* The flash gates refuse with these, and ethtool -S needs no privilege\n+\t * to reach them, so they must not log.\n+\t */\n+\tif (ret == -EBUSY || ret == -ENODEV)\n+\t\treturn;\n \tif (ret) {\n \t\tdev_err(ds-\u003edev, \"failed to read RMON stats on port %d\\n\", port);\n \t\treturn;\n@@ -2097,6 +2250,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)\n@@ -2161,6 +2316,12 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)\n \n \tpriv = ds-\u003epriv;\n \n+\t/* Wait out a flash in flight and bar a new one before the DSA core\n+\t * frees the user netdevs; the devlink instance lock does not cover\n+\t * that free, which dsa_unregister_switch() reaches first.\n+\t */\n+\tmxl862xx_flash_shutdown(ds);\n+\n \tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n \n \tdsa_unregister_switch(ds);\n@@ -2187,6 +2348,8 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)\n \n \tpriv = ds-\u003epriv;\n \n+\tmxl862xx_flash_shutdown(ds);\n+\n \tdsa_switch_shutdown(ds);\n \n \tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h\nindex 432a5f3f2e08e..b9c34d4a154ff 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@@ -297,12 +303,19 @@ struct mxl862xx_fw_version {\n  * @flags:              atomic status flags; %MXL862XX_FLAG_CRC_ERR is set\n  *                      before CRC-triggered shutdown and cleared after;\n  *                      %MXL862XX_FLAG_WORK_STOPPED is set before cancelling\n- *                      stats_work to prevent rescheduling during teardown\n+ *                      stats_work to prevent rescheduling during teardown,\n+ *                      and ends the rescue drain and its reprobe hand-off\n  * @drop_meter:         index of the single shared zero-rate firmware meter\n  *                      used to unconditionally drop traffic (used to block\n  *                      flooding)\n- * @fw_version:         cached firmware version, populated at probe and\n- *                      compared with MXL862XX_FW_VER_MIN()\n+ * @fw_version:         cached firmware version, compared with\n+ *                      MXL862XX_FW_VER_MIN(); refreshed by\n+ *                      mxl862xx_wait_ready() after a flash and cleared by\n+ *                      a failed one until the reprobe\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 +332,30 @@ 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:         during a firmware flash, a host firmware read fails\n+ *                      with -EBUSY and a write reports success without\n+ *                      touching the bus, so a teardown racing the flash\n+ *                      does not fail; FW_UPDATE and the flash owner's own\n+ *                      reads still reach the bus\n+ * @flash_owner:        task running the post-flash readiness poll; only its\n+ *                      own firmware reads pass block_host\n+ * @skip_teardown:      during the post-flash reprobe teardown, a host\n+ *                      firmware read fails with -ENODEV and a write reports\n+ *                      success without touching the bus\n+ * @shutting_down:      set under the devlink instance lock once -\u003eshutdown()\n+ *                      or .remove() has begun, so no flash starts while the\n+ *                      switch is going away\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 with WRITE_ONCE() under\n+ *                      the MDIO bus lock for the benefit of mxl862xx_api_wrap();\n+ *                      readers outside that lock use READ_ONCE().\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 loader cannot accept a flash; the\n+ *                      remedy depends on the cause and is described in\n+ *                      Documentation/networking/devlink/mxl862xx.rst\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 +363,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 +377,16 @@ struct mxl862xx_priv {\n \tu16 evlan_ingress_size;\n \tu16 evlan_egress_size;\n \tu16 vf_block_size;\n+\tstruct task_struct *flash_owner;\n+\tbool block_host;\n+\tbool skip_teardown;\n+\tbool shutting_down;\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 5d12191b6f6f5..1fcf4af6c5064 100644\n--- a/include/net/dsa.h\n+++ b/include/net/dsa.h\n@@ -1176,6 +1176,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/28 00:52 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series adds devlink firmware flashing, rescue mode support, and firmware version reporting for the MaxLinear MxL862xx Ethernet switch driver (drivers/net/dsa/mxl862xx/), introduces a deferred reprobe helper device_schedule_reprobe() in drivers/base/dd.c, and plumbs devlink_flash_update into DSA's devlink operations (net/dsa/devlink.c). The MxL862xx is a physical Ethernet switch ASIC requiring dedicated MDIO bus hardware, which is not present or emulated in standard virtualized fuzzing environments (QEMU/GCE). Furthermore, device_schedule_reprobe() is solely called by the unreachable mxl862xx driver, and mxl862xx is the only DSA driver that implements .devlink_flash_update. Therefore, the modified code is structurally unreachable.",
  "WorthFuzzing": false
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 37957babf9b094443d4c34f813db57f5252bd780
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Sep 28 00:52:39 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index 1af780c811ee2..53d3ef16d13f4 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..c2dfc72ef9a40
--- /dev/null
+++ b/Documentation/networking/devlink/mxl862xx.rst
@@ -0,0 +1,95 @@
+.. 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 CHIP ID registers as well. Both
+       values are published behind the same check, 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; it is reported as
+       running only, since the driver cannot tell what the flash holds
+       while the loader runs. 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; they
+come back registered but down, so userspace brings them up again. A
+complete flash and reprobe cycle takes about one minute. In the rare
+case that the reprobe cannot be scheduled at all, the kernel log says
+so, ``devlink dev flash`` reports that error or the transfer's own if
+the transfer failed as well, and the driver stays bound to a switch it
+no longer tracks, refusing further updates with ``-EBUSY``, until it is
+unbound and rebound. A reboot started
+while an update is running waits for the transfer to finish, and an
+update requested after the system has begun shutting down is refused
+with ``-ENODEV``.
+
+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 driver gives up on the recovery, ``devlink dev flash`` returns
+``-EIO`` and says so in its extack message. The drain runs once and is
+never resumed, so a failed MDIO transaction ends it as well. A loader
+that stops answering the drain needs a power cycle; a completed drain
+whose reprobe could not be scheduled, and a drain a bus error cut
+short, need only a driver rebind. The driver re-examines the switch
+when it binds and at no other time, so a power cycle on a board where
+the switch can be cycled on its own still has to be followed by an
+unbind and rebind for the recovered switch to be recognised.
+
+A download interrupted during its opening handshake, before the image
+header reached the loader, is reported the same way. The loader waits
+for a header that no later session can supply, so that state needs a
+power cycle.
diff --git a/MAINTAINERS b/MAINTAINERS
index 6de1ff058db64..cb1dad080f63c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16250,6 +16250,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/dd.c b/drivers/base/dd.c
index f6525a7ee8c54..823a25f9c0895 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -1436,3 +1436,129 @@ void driver_detach(const struct device_driver *drv)
 		put_device(dev);
 	}
 }
+
+struct device_reprobe {
+	struct delayed_work work;
+	const struct device_driver *drv;
+	const char *drv_name;
+	struct device *dev;
+	unsigned long delay;
+};
+
+static void device_reprobe_work_fn(struct work_struct *work)
+{
+	struct device_reprobe *rp = container_of(work, struct device_reprobe,
+						 work.work);
+	struct device *dev = rp->dev;
+	bool detached = false;
+	int ret;
+
+	device_lock(dev);
+	/*
+	 * rp->drv is only compared, never dereferenced: the driver it points
+	 * to may have been unregistered and freed. The saved name rejects a
+	 * freed address the allocator has since handed to another driver.
+	 */
+	if (!dev->p->dead && dev->driver == rp->drv &&
+	    !strcmp(dev->driver->name, rp->drv_name)) {
+		if (defer_all_probes) {
+			device_unlock(dev);
+			queue_delayed_work(system_freezable_wq, &rp->work,
+					   rp->delay);
+			return;
+		}
+		__device_release_driver(dev, NULL);
+		detached = true;
+	}
+	device_unlock(dev);
+
+	if (detached) {
+		ret = device_attach(dev);
+		/* 0 is unbound too: a failed probe is folded into "no match" */
+		if (ret <= 0 && ret != -EPROBE_DEFER)
+			dev_err(dev, "re-probe left the device unbound\n");
+	}
+
+	put_device(dev);
+	kfree(rp->drv_name);
+	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,
+ * from built-in driver-core work rather than a driver-owned work item,
+ * so the bound driver may call it without pinning its own module. The
+ * binding is recorded as the driver pointer plus a copy of its name; the
+ * pointer is only ever compared, never dereferenced, and the name copy
+ * guards against a freed &struct device_driver address the allocator
+ * later hands to a different driver.
+ *
+ * The re-probe is skipped when the work runs if @dev has since been
+ * removed, is no longer bound or is bound to a different driver; an
+ * unbind followed by a rebind of the same driver within the delay still
+ * gets it. While probing is blocked, for a system suspend or shutdown,
+ * the work re-arms itself after @delay_ms, so a request pending across
+ * suspend runs once the system has resumed and one that fires during a
+ * shutdown detaches nothing. A failed re-probe leaves @dev unbound, as a
+ * failed initial probe would.
+ *
+ * This is device_reprobe() deferred, and shares its limitations;
+ * __device_release_driver() is unchanged. The detach and the re-attach
+ * are not one locked operation, so an administrative unbind arriving
+ * between them may be undone, and the attach half runs the normal probe
+ * path with no shutdown re-check of its own. If @dev has managed
+ * consumers, detaching it unbinds them as any driver release does, so a
+ * re-probe a concurrent device_shutdown() overtakes may run ->remove()
+ * in place of ->shutdown(). None of this is specific to this helper.
+ *
+ * Buses that take the parent lock to bind (only usb_bus_type) are refused
+ * with -EINVAL: the parent would have to be recorded before either lock
+ * is held, where device_move() can replace it.
+ *
+ * Context: May sleep (allocates with %GFP_KERNEL). Must be called by the
+ * driver bound to @dev, from a process context its ->remove() waits for,
+ * so that the binding outlives the call; @dev's own device lock may be
+ * held, but not from ->probe(), which the scheduled work would detach.
+ *
+ * Returns: 0 on success, -EINVAL if @dev is not a registered device
+ * bound to a driver or sits on a bus which takes the parent lock to
+ * bind, -ENOMEM on allocation failure.
+ */
+int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)
+{
+	const struct device_driver *drv;
+	struct device_reprobe *rp;
+
+	drv = READ_ONCE(dev->driver);
+	/*
+	 * A bus taking the parent lock would need @dev's parent pinned until
+	 * the work runs, which device_move() can invalidate.
+	 */
+	if (!drv || !dev->bus || dev->bus->need_parent_lock || !dev->p ||
+	    dev->p->dead || !device_is_registered(dev))
+		return -EINVAL;
+
+	rp = kzalloc_obj(*rp);
+	if (!rp)
+		return -ENOMEM;
+
+	rp->drv_name = kstrdup(drv->name, GFP_KERNEL);
+	if (!rp->drv_name) {
+		kfree(rp);
+		return -ENOMEM;
+	}
+
+	rp->dev = get_device(dev);
+	rp->drv = drv;
+	rp->delay = msecs_to_jiffies(delay_ms);
+
+	INIT_DELAYED_WORK(&rp->work, device_reprobe_work_fn);
+	queue_delayed_work(system_freezable_wq, &rp->work, rp->delay);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(device_schedule_reprobe);
diff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kconfig
index e51a67a3cf9bf..8bb524fe28d1f 100644
--- a/drivers/net/dsa/mxl862xx/Kconfig
+++ b/drivers/net/dsa/mxl862xx/Kconfig
@@ -3,6 +3,7 @@ config NET_DSA_MXL862
 	tristate "MaxLinear MxL862xx"
 	depends on NET_DSA
 	select CRC16
+	select CRC32
 	select NET_DSA_TAG_MXL_862XX
 	help
 	  This enables support for the MaxLinear MxL862xx switch family.
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..236dc79460f71
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,1156 @@
+// 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, -EIO for good once the
+ * recovery has given up, 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/sched.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
+/* STAT poll intervals: a mailbox step is quick, an erase is not */
+#define MXL862XX_SB_PDI_POLL_US		50
+#define MXL862XX_SB_PDI_SLOW_POLL_US	10000
+
+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+	int ret;
+
+	/* CTRL selects RST mode; ADDR and DATA are cleared to 0. */
+	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, 0x0000);
+	if (ret < 0)
+		return ret;
+
+	return mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);
+}
+
+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, or
+ * -ECANCELED once teardown asks the caller to stop. On timeout the value 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 sleep_us,
+				      unsigned long timeout_ms)
+{
+	int val;
+
+	read_poll_timeout(mxl862xx_smdio_read, val,
+			  val < 0 || (u16)val == MXL862XX_SB_PDI_READY ||
+			  (u16)val == 0 ||
+			  test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags),
+			  sleep_us, timeout_ms * 1000, false,
+			  priv, MXL862XX_SB_PDI_STAT);
+	if (val < 0)
+		return val;
+	if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+		return -ECANCELED;
+	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. Before the first chunk it may also still be erasing
+ * for the session that died, which no verify window covers. Wait that out --
+ * STAT cannot tell the cases 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 window
+ * expires, -ECANCELED on teardown, 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;
+
+	if (chunk)
+		stat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US,
+						  MXL862XX_SB_PDI_VERIFY_MS);
+	else
+		stat = mxl862xx_sb_pdi_poll_drain(priv,
+						  MXL862XX_SB_PDI_SLOW_POLL_US,
+						  MXL862XX_FW_ERASE_TIMEOUT_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) {
+		if (!chunk) {
+			dev_err(dev,
+				"flash: loader still busy after the erase window\n");
+			return -EIO;
+		}
+		/* 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;
+}
+
+/* Walk the loader's receive counter to zero (see the header): the image size
+ * died with the host, and a chunk larger than what is outstanding underflows
+ * the counter, so only single bytes are safe. Tens of minutes for a multi-MiB
+ * remainder. Returns 0 once the loader has left the receive loop, <0 on error;
+ * a counter an earlier oversized chunk underflowed needs a power cycle.
+ */
+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)
+{
+	/* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */
+	u32 max_chunks = 2u * (16u << 20) / MXL862XX_DRAIN_CHUNK_BYTES;
+	struct device *dev = &priv->mdiodev->dev;
+	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_POLL_US,
+						  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 only runs into its timeouts
+ * 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.
+ *  - 0xf48f/0xf490: a download handshake nobody can finish; rescue, but only
+ *    a power cycle gets the loader out of it.
+ *  - other non-zero: running firmware, left unpoked. With @settle the loader
+ *    gets one step to publish 0 or READY first; a count outliving that is
+ *    rescue only when it is the erase of a session that died (header length
+ *    + 1), anything else a firmware that never became ready.
+ *  - 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,
+ * -ECANCELED when teardown interrupts a poll, or an SMDIO bus error.
+ */
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv, bool settle)
+{
+	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;
+	}
+
+	/* The download handshake lives in STAT too and outlives a mailbox
+	 * reset, so an aborted transfer leaves the loader waiting for a
+	 * header no later session can supply: only a power cycle clears it.
+	 */
+	if ((u16)stat == MXL862XX_SB_PDI_START ||
+	    (u16)stat == MXL862XX_SB_PDI_START + 1) {
+		WRITE_ONCE(priv->rescue_failed, true);
+		return MXL862XX_IN_RESCUE;
+	}
+
+	/* Any other non-zero value is a running firmware, not a loader -- but
+	 * the loader also holds the count of a chunk it is programming or
+	 * erasing for, so let a caller whose clause-45 wait has failed give it
+	 * a step for the next state. Only the erase of the session that died
+	 * outlives that wait; any other count left is a firmware that answered
+	 * the reset without becoming ready.
+	 */
+	if (stat) {
+		if (!settle)
+			return MXL862XX_NOT_RESCUE;
+
+		stat = mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US,
+						  MXL862XX_SB_PDI_STEP_MS);
+		if (stat < 0)
+			return stat;
+		if (stat == MXL862XX_SB_PDI_READY) {
+			WRITE_ONCE(priv->rescue_ready, true);
+			return MXL862XX_IN_RESCUE;
+		}
+		if (stat == MXL862XX_FW_HDR_SIZE + 1)
+			return MXL862XX_IN_RESCUE;
+		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_POLL_US,
+					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, val, i;
+
+	/* Step 1: reboot the firmware into MCUboot rescue mode */
+	if (!READ_ONCE(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;
+
+	/* The ACK is the count + 1, replaced by 0 as soon as the erase is done,
+	 * so wait for the count to go: a short erase retires the ACK between
+	 * two polls.
+	 */
+	ret = read_poll_timeout(mxl862xx_smdio_read, val,
+				val < 0 || (u16)val != MXL862XX_FW_HDR_SIZE,
+				10000, MXL862XX_FW_ACK_TIMEOUT_MS * 1000, false,
+				priv, MXL862XX_SB_PDI_STAT);
+	if (val < 0)
+		ret = val;
+	if (!ret && (u16)val != MXL862XX_FW_HDR_SIZE + 1 && (u16)val != 0)
+		ret = -EPROTO;
+	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);
+	if (ret < 0)
+		dev_warn(&priv->mdiodev->dev,
+			 "flash: END signalling failed, waiting the loader out: %pe\n",
+			 ERR_PTR(ret));
+
+	msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+	return 0;
+
+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,
+	 * and as running only, since what the flash holds is unknown here.
+	 */
+	if (READ_ONCE(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);
+		return devlink_info_version_running_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;
+	}
+
+	/* Written under the instance lock this call is holding. */
+	if (priv->shutting_down) {
+		NL_SET_ERR_MSG_MOD(extack, "device is shutting down");
+		return -ENODEV;
+	}
+
+	/* A previous flash has short-circuited the firmware API until the
+	 * reprobe or a rebind, 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,
+				   "a previous flash left the driver awaiting reinitialisation");
+		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, "loader cannot accept a download");
+		return -EIO;
+	}
+
+	if (READ_ONCE(priv->rescue_mode) && !READ_ONCE(priv->rescue_ready)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "switch is recovering an interrupted download");
+		return -EBUSY;
+	}
+
+	if (READ_ONCE(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_owner = current;
+		WRITE_ONCE(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,
+		 * and with it the loader's clean READY state.
+		 */
+		memset(&priv->fw_version, 0, sizeof(priv->fw_version));
+		priv->asic_id = 0;
+		priv->asic_rev = 0;
+		WRITE_ONCE(priv->rescue_ready, false);
+	}
+
+	mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+	priv->flash_owner = NULL;
+	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);
+	if (err)
+		dev_err(ds->dev,
+			"flash: re-probe could not be scheduled (%pe); unbind and rebind to reinitialise\n",
+			ERR_PTR(err));
+
+	return ret ? ret : err;
+}
+
+/* The devlink core holds the instance lock across a flash, so taking it here
+ * waits for a transfer in flight instead of cutting the image in half, and
+ * bars one that has not started yet.
+ */
+void mxl862xx_flash_shutdown(struct dsa_switch *ds)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (!ds->devlink)
+		return;
+
+	/* A flash holds the instance lock for its whole run. Announce the
+	 * wait so the delay is not mistaken for a hang.
+	 */
+	if (!devl_trylock(ds->devlink)) {
+		dev_info(ds->dev,
+			 "firmware update in progress, waiting for it to finish\n");
+		devl_lock(ds->devlink);
+	}
+	priv->shutting_down = true;
+	devl_unlock(ds->devlink);
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
new file mode 100644
index 0000000000000..02e5a627e9471
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,21 @@
+/* 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, bool settle);
+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);
+void mxl862xx_flash_shutdown(struct dsa_switch *ds);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc00..6d9ff283969ab 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -12,9 +12,12 @@
 #include <linux/crc16.h>
 #include <linux/iopoll.h>
 #include <linux/limits.h>
+#include <linux/sched.h>
 #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)
@@ -248,11 +251,8 @@ static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)
 	len_enc = ret;
 
 	ret = mxl862xx_crc6_verify(ctrl_enc, len_enc, &fw_result);
-	if (ret) {
-		if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
-			schedule_work(&priv->crc_err_work);
-		return -EIO;
-	}
+	if (ret)
+		return -EBADMSG;
 
 	return fw_result;
 }
@@ -303,7 +303,9 @@ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
 
 	ret = mxl862xx_issue_cmd(priv, cmd, size);
 
-	/* Handle errors returned by the firmware as -EIO.
+	/* Handle errors returned by the firmware as -EIO, a CRC mismatch,
+	 * found here or reported by the firmware, as -EBADMSG for
+	 * mxl862xx_api_wrap() to act on.
 	 * The firmware is based on Zephyr OS and uses the errors as
 	 * defined in errno.h of Zephyr OS. See
 	 * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h
@@ -314,13 +316,12 @@ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
 	 *   -1023: CRC-16 mismatch on data payload
 	 */
 	if (ret < 0) {
-		if ((ret == MXL862XX_FW_CRC6_ERR ||
-		     ret == MXL862XX_FW_CRC16_ERR) &&
-		    !test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
-			schedule_work(&priv->crc_err_work);
 		if (!quiet)
 			dev_err(&priv->mdiodev->dev,
 				"CMD %04x returned error %d\n", cmd, ret);
+		if (ret == -EBADMSG || ret == MXL862XX_FW_CRC6_ERR ||
+		    ret == MXL862XX_FW_CRC16_ERR)
+			return -EBADMSG;
 		return -EIO;
 	}
 
@@ -340,6 +341,29 @@ 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.
+	 * A blocked write reports success: the reprobe discards the switch
+	 * configuration anyway, and a bridge tearing down over a flash must
+	 * not see port_vlan_del() fail, which leaks its VLAN group.
+	 */
+	if (priv->block_host && cmd != SYS_MISC_FW_UPDATE &&
+	    !(read && priv->flash_owner == current)) {
+		ret = read ? -EBUSY : 0;
+		goto out;
+	}
+
 	max = (size + 1) / 2;
 
 	ret = mxl862xx_busy_wait(priv);
@@ -460,9 +484,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
 	}
 
 	if (crc16(0xffff, (const u8 *)data, size) != crc) {
-		if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
-			schedule_work(&priv->crc_err_work);
-		ret = -EIO;
+		ret = -EBADMSG;
 		goto out;
 	}
 
@@ -472,6 +494,15 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
 	dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data);
 
 out:
+	/* A quiet command is expected to go unanswered by a switch still
+	 * booting or sitting in its loader, so it does not arm the shutdown.
+	 */
+	if (ret == -EBADMSG) {
+		if (!quiet &&
+		    !test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
+			schedule_work(&priv->crc_err_work);
+		ret = -EIO;
+	}
 	mutex_unlock(&priv->mdiodev->bus->mdio_lock);
 
 	return ret;
@@ -495,12 +526,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..df77bbf108d5e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -47,7 +47,12 @@ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
 		fallthrough;
 	case 10 ... 12:
 	case 14 ... 16:
-		if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
+		/* Rescue mode has no firmware version, so bypass the gate and
+		 * advertise the full set; a CPU port on a quad sub-interface
+		 * would otherwise get an empty mask and fail phylink_create().
+		 */
+		if (!READ_ONCE(priv->rescue_mode) &&
+		    !MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
 			break;
 		__set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
 		__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
@@ -406,6 +411,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
 
 	switch (port) {
 	case 9 ... 16:
+		if (READ_ONCE(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 e05ad52cd297e..f16e3e33a0464 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,45 @@ 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)
+/* The CHIP ID registers are only readable through the firmware mailbox, so
+ * the values are cached here and stay zero while no firmware answers.
+ */
+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 the chip
+ * ID when it can be read. 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 +300,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:
@@ -616,27 +667,85 @@ static void mxl862xx_free_bridge(struct dsa_switch *ds,
 	priv->bridges[bridge->num] = 0;
 }
 
+static void mxl862xx_setup_rescue(struct dsa_switch *ds)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (priv->rescue_ready) {
+		dev_warn(ds->dev,
+			 "switch in MCUboot rescue mode, use devlink to flash new firmware\n");
+		return;
+	}
+
+	if (priv->rescue_failed) {
+		dev_warn(ds->dev,
+			 "switch in MCUboot with an unfinishable download handshake; see Documentation/networking/devlink/mxl862xx.rst\n");
+		return;
+	}
+
+	/* 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);
+}
+
 static int mxl862xx_setup(struct dsa_switch *ds)
 {
 	struct mxl862xx_priv *priv = ds->priv;
 	int n_user_ports = 0, max_vlans;
 	int ingress_finals, vid_rules;
 	struct dsa_port *dp;
-	int ret, i;
+	int ret, i, rescue;
 
-	ret = mxl862xx_reset(priv);
-	if (ret)
-		return ret;
+	/* Detect the loader over SB PDI first: it needs no firmware, unlike the
+	 * C45 API (mxl862xx_reset/wait_ready), which spends its whole 10 s
+	 * window on a mailbox nobody answers. Touch C45 only once rescue is
+	 * ruled out.
+	 */
+	rescue = mxl862xx_rescue_mode_detect(priv, false);
+	if (rescue < 0) {
+		dev_err(ds->dev, "switch state detection failed: %pe\n",
+			ERR_PTR(rescue));
+		return rescue;
+	}
 
-	ret = mxl862xx_wait_ready(ds);
-	if (ret)
-		return ret;
+	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, true);
+			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;
+
+	/* Software-only SerDes state, needed before anything can reach phylink,
+	 * including a rescue-mode flash clearing rescue_mode ahead of 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);
 
+	if (priv->rescue_mode) {
+		mxl862xx_setup_rescue(ds);
+		return 0;
+	}
+
 	/* Calculate Extended VLAN block sizes.
 	 * With VLAN Filter handling VID membership checks:
 	 *   Ingress: only final catchall rules (PVID insertion, 802.1Q
@@ -727,11 +836,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 (READ_ONCE(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 (READ_ONCE(priv->rescue_mode))
+		return;
+
 	if (mxl862xx_port_state(ds, port, false))
 		dev_err(ds->dev, "failed to disable port %d\n", port);
 }
@@ -1349,6 +1468,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 (READ_ONCE(priv->rescue_mode))
+		return dsa_port_is_user(dp) ? -ENODEV : 0;
+
 	ret = mxl862xx_port_state(ds, port, false);
 	if (ret)
 		return ret;
@@ -1358,11 +1488,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;
@@ -1547,6 +1672,16 @@ static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,
 	qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
 
 	ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+	/* A flash blocks the API (-EBUSY); its teardown or MCUboot drops the
+	 * MAC table (-ENODEV); there is then nothing to program, and the
+	 * reprobe rebuilds the configuration. The flash moves from block_host
+	 * to skip_teardown outside this call, so either errno counts under
+	 * either flag. See mxl862xx_port_mdb_del().
+	 */
+	if ((ret == -EBUSY || ret == -ENODEV) &&
+	    (priv->block_host || priv->skip_teardown ||
+	     READ_ONCE(priv->rescue_mode)))
+		return 0;
 	if (ret)
 		return ret;
 
@@ -1584,6 +1719,16 @@ 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);
+	/* A flash blocks the API (-EBUSY); its teardown or MCUboot drops the
+	 * MAC table (-ENODEV); a delete then has nothing to do, and the flash
+	 * moves from block_host to skip_teardown outside this call, so either
+	 * errno counts under either flag. Outside these, both are bus errors
+	 * and must be reported.
+	 */
+	if ((ret == -EBUSY || ret == -ENODEV) &&
+	    (priv->block_host || priv->skip_teardown ||
+	     READ_ONCE(priv->rescue_mode)))
+		return 0;
 	if (ret)
 		return ret;
 
@@ -1640,6 +1785,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
 	struct mxl862xx_priv *priv = ds->priv;
 	int ret;
 
+	if (READ_ONCE(priv->rescue_mode))
+		return;
+
 	switch (state) {
 	case BR_STATE_DISABLED:
 		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
@@ -1818,6 +1966,11 @@ static void mxl862xx_get_ethtool_stats(struct dsa_switch *ds, int port,
 	void *field;
 
 	ret = mxl862xx_read_rmon(ds, port, &cnt);
+	/* The flash gates refuse with these, and ethtool -S needs no privilege
+	 * to reach them, so they must not log.
+	 */
+	if (ret == -EBUSY || ret == -ENODEV)
+		return;
 	if (ret) {
 		dev_err(ds->dev, "failed to read RMON stats on port %d\n", port);
 		return;
@@ -2097,6 +2250,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)
@@ -2161,6 +2316,12 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
 
 	priv = ds->priv;
 
+	/* Wait out a flash in flight and bar a new one before the DSA core
+	 * frees the user netdevs; the devlink instance lock does not cover
+	 * that free, which dsa_unregister_switch() reaches first.
+	 */
+	mxl862xx_flash_shutdown(ds);
+
 	set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
 
 	dsa_unregister_switch(ds);
@@ -2187,6 +2348,8 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
 
 	priv = ds->priv;
 
+	mxl862xx_flash_shutdown(ds);
+
 	dsa_switch_shutdown(ds);
 
 	set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08e..b9c34d4a154ff 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
@@ -297,12 +303,19 @@ struct mxl862xx_fw_version {
  * @flags:              atomic status flags; %MXL862XX_FLAG_CRC_ERR is set
  *                      before CRC-triggered shutdown and cleared after;
  *                      %MXL862XX_FLAG_WORK_STOPPED is set before cancelling
- *                      stats_work to prevent rescheduling during teardown
+ *                      stats_work to prevent rescheduling during teardown,
+ *                      and ends the rescue drain and its reprobe hand-off
  * @drop_meter:         index of the single shared zero-rate firmware meter
  *                      used to unconditionally drop traffic (used to block
  *                      flooding)
- * @fw_version:         cached firmware version, populated at probe and
- *                      compared with MXL862XX_FW_VER_MIN()
+ * @fw_version:         cached firmware version, compared with
+ *                      MXL862XX_FW_VER_MIN(); refreshed by
+ *                      mxl862xx_wait_ready() after a flash and cleared by
+ *                      a failed one until the reprobe
+ * @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 +332,30 @@ 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:         during a firmware flash, a host firmware read fails
+ *                      with -EBUSY and a write reports success without
+ *                      touching the bus, so a teardown racing the flash
+ *                      does not fail; FW_UPDATE and the flash owner's own
+ *                      reads still reach the bus
+ * @flash_owner:        task running the post-flash readiness poll; only its
+ *                      own firmware reads pass block_host
+ * @skip_teardown:      during the post-flash reprobe teardown, a host
+ *                      firmware read fails with -ENODEV and a write reports
+ *                      success without touching the bus
+ * @shutting_down:      set under the devlink instance lock once ->shutdown()
+ *                      or .remove() has begun, so no flash starts while the
+ *                      switch is going away
+ * @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 with WRITE_ONCE() under
+ *                      the MDIO bus lock for the benefit of mxl862xx_api_wrap();
+ *                      readers outside that lock use READ_ONCE().
+ * @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 loader cannot accept a flash; the
+ *                      remedy depends on the cause and is described in
+ *                      Documentation/networking/devlink/mxl862xx.rst
+ * @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 +363,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 +377,16 @@ struct mxl862xx_priv {
 	u16 evlan_ingress_size;
 	u16 evlan_egress_size;
 	u16 vf_block_size;
+	struct task_struct *flash_owner;
+	bool block_host;
+	bool skip_teardown;
+	bool shutting_down;
+	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 5d12191b6f6f5..1fcf4af6c5064 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1176,6 +1176,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/28 00:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34353
							output: 22
							thoughts: 559
						

Thoughts:
**Analyzing this Patch: A Deep Dive**

Okay, let's break down this patch. My immediate thought is to understand the scope of the changes. First, I'll identify the touched files: a quick scan shows several documentation updates in `Documentation/networking/devlink/`, a `MAINTAINERS` update, and then a significant cluster of changes within the driver space, specifically around `drivers/net/dsa/mxl862xx/`. There's also some interesting activity in `drivers/base/dd.c`, `net/dsa/devlink.c` and a couple of header changes.

Now, focusing on the core areas: `drivers/base/dd.c` is getting my attention. The addition of `device_schedule_reprobe()` and `device_reprobe_work_fn()` hints at a mechanism for rescheduling device probing, potentially to handle late-arriving dependencies or external factors influencing device initialization. This has implications for robustness and how the system handles device discovery and bring-up. The changes to `net/dsa/devlink.c` are also piquing my interest, as this usually means some integration with the Device Link infrastructure.

However, the real meat of the patch seems to be within the `drivers/net/dsa/mxl862xx/` directory. That's a significant number of files – Kconfig, Makefile, header files, and several C files. This indicates a fairly substantial update or possibly even the introduction of a new driver for the `mxl862xx` device. Given the DSA (Distributed Switch Architecture) context, it's likely a switch or similar network device.

Before I go further, though, I'm going to pause and consider something important, especially in the context of my work. Given the scope of this patch, I'm wondering if this `mxl862xx` hardware is even reachable in standard virtualized fuzzing environments like Syzkaller? Does it represent a realistic attack surface for my fuzzing efforts? I need to check the characteristics of this device to understand it's attack surface. I'll need to figure out what kind of device this is. Let's delve into the `drivers/net/dsa/mxl862xx/` directory and see what we can find. I need to understand what this thing *is* before I go any further.



4/2 2026/09/28 00:52 tool grepper
0m Args:
{
  "Expression": "mxl862xx_probe"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2220=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2256-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2257:static int mxl862xx_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2258-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2373=static struct mdio_driver mxl862xx_driver = {\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2374:\t.probe  = mxl862xx_probe,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2375-\t.remove = mxl862xx_remove,\n"
}

5/2 2026/09/28 00:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35747
							output: 51
							thoughts: 67
						

Thoughts:
Here's my take on the thought process, as requested:

**Digging into the mxl862xx Driver**

Okay, so `mxl862xx` is an MDIO-attached Ethernet switch ASIC driver. Alright, that much is clear from the name. My immediate question, though, is how is this thing actually *probed*? Is it being instantiated via Device Tree, or does it rely on the MDIO bus itself for discovery? I need to get into the details to understand the initialization flow.

Right, let's dive into `mxl862xx.c`. That's where I expect to find the meat of the driver and answer my probing question. I'll be looking for clues on how the driver registers with the MDIO subsystem, how it identifies the specific ASIC, and how it handles the initial configuration. Gotta get a solid handle on how this driver gets off the ground.



6/2 2026/09/28 00:52 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
  "FirstLine": 2360,
  "LineCount": 40
}

Results:
{
  "Contents": "2360:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2361:\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2362:\t\n2363:\t\tdev_set_drvdata(\u0026mdiodev-\u003edev, NULL);\n2364:\t}\n2365:\t\n2366:\tstatic const struct of_device_id mxl862xx_of_match[] = {\n2367:\t\t{ .compatible = \"maxlinear,mxl86282\" },\n2368:\t\t{ .compatible = \"maxlinear,mxl86252\" },\n2369:\t\t{ /* sentinel */ }\n2370:\t};\n2371:\tMODULE_DEVICE_TABLE(of, mxl862xx_of_match);\n2372:\t\n2373:\tstatic struct mdio_driver mxl862xx_driver = {\n2374:\t\t.probe  = mxl862xx_probe,\n2375:\t\t.remove = mxl862xx_remove,\n2376:\t\t.shutdown = mxl862xx_shutdown,\n2377:\t\t.mdiodrv.driver = {\n2378:\t\t\t.name = \"mxl862xx\",\n2379:\t\t\t.of_match_table = mxl862xx_of_match,\n2380:\t\t},\n2381:\t};\n2382:\t\n2383:\tmdio_module_driver(mxl862xx_driver);\n2384:\t\n2385:\tMODULE_DESCRIPTION(\"Driver for MaxLinear MxL862xx switch family\");\n2386:\tMODULE_LICENSE(\"GPL\");\n"
}

7/2 2026/09/28 00:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36531
							output: 20
							thoughts: 102
						

Thoughts:
**Analyzing the mxl862xx_driver**

Okay, so I'm looking at this `mxl862xx_driver` here. It seems pretty straightforward; it's an `mdio_driver` and it's using the OF device tree to match specific hardware based on the `maxlinear,mxl86282` or `maxlinear,mxl86252` compatible strings. Standard MDIO device matching with the `of_match_table` method – it expects a device tree node that describes both the MDIO bus itself and the switch connected to it. Nothing particularly groundbreaking there.

But wait... I'm curious about something. I see this driver, or perhaps something it relies on, uses `device_schedule_reprobe`. I don't recall seeing this pattern frequently. Is this the only driver in the codebase that leverages this function? Best to check now, before getting too deep. Let me just run a quick `grep` for `device_schedule_reprobe` to confirm my suspicion... yeah, I need to know if this pattern is used anywhere else.



8/2 2026/09/28 00:52 tool grepper
0m Args:
{
  "Expression": "device_schedule_reprobe"
}

Results:
{
  "Output": "drivers/base/dd.c=1448=static void device_reprobe_work_fn(struct work_struct *work)\n--\ndrivers/base/dd.c-1487-/**\ndrivers/base/dd.c:1488: * device_schedule_reprobe - schedule a deferred detach and re-probe\ndrivers/base/dd.c-1489- * @dev: device to detach and re-probe\n--\ndrivers/base/dd.c-1530- */\ndrivers/base/dd.c:1531:int device_schedule_reprobe(struct device *dev, unsigned int delay_ms)\ndrivers/base/dd.c-1532-{\n--\ndrivers/base/dd.c-1563-}\ndrivers/base/dd.c:1564:EXPORT_SYMBOL_GPL(device_schedule_reprobe);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=433=void mxl862xx_rescue_heal_work_fn(struct work_struct *work)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-463-\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:464:\tif (device_schedule_reprobe(dev, MXL862XX_FW_REPROBE_DELAY_MS))\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-465-\t\tWRITE_ONCE(priv-\u003erescue_failed, true);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=986=int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-1125-\t */\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:1126:\terr = device_schedule_reprobe(ds-\u003edev, MXL862XX_FW_REPROBE_DELAY_MS);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-1127-\tif (err)\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"
}

9/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37576
							output: 21
							thoughts: 313
						

Thoughts:
**Investigating Isolated Function Calls**

Okay, here's what I'm seeing: `device_schedule_reprobe` has been added in `drivers/base/dd.c`, but it's only invoked from within `mxl862xx-fw.c`! That's a bit odd. I wonder, is this function *truly* only used in that one specific place? Let me quickly verify - nope, it seems isolated to `mxl862xx-fw.c`. Interesting.

Now, let's switch gears and consider `dsa_devlink_flash_update`, which lives in `net/dsa/devlink.c`. I see that the function `dsa_devlink_flash_update` is defined there, and it's responsible for calling `ds->ops->devlink_flash_update`. So, it's a wrapper. I can see the underlying operation is called through the `dsa_switch`'s ops structure, which is typical.

The relevant code snippet confirms this behavior, and explicitly assigns the `dsa_devlink_flash_update` function to the `flash_update` operation within the `dsa_devlink_ops` structure. Okay, so that's the setup. This looks good.

Now, the question becomes: which DSA drivers are *actually* implementing `.devlink_flash_update`? I'll use grep to see who's really using this functionality. I'll need to search the codebase for that exact string. Let's see what that uncovers.



10/2 2026/09/28 00:53 tool grepper
0m Args:
{
  "Expression": "\\.devlink_flash_update"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2220=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2253-\t.devlink_info_get = mxl862xx_devlink_info_get,\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2254:\t.devlink_flash_update = mxl862xx_devlink_flash_update,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2255-};\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 38447
							output: 33
							thoughts: 125
						

Thoughts:
**Investigating DSA Device Flash Updates**

Okay, so the initial thought is that only the `mxl862xx` driver actually uses `.devlink_flash_update`. Hmm, but does that mean *no other* DSA driver can ever use `dsa_devlink_flash_update`? That doesn't seem right. It's worth digging into.

If a userspace process sends a `DEVLINK_CMD_FLASH_UPDATE` command targeting a DSA device, what happens?  This makes me wonder...can we even *create* a DSA switch instance in a virtual environment like standard syzkaller, QEMU, or even Google Compute Engine on amd64? This is a crucial question; I need to know if I can reproduce this behavior or debug without hardware.

My next step is to explore how DSA switches are probed and instantiated in the kernel. I need to look for any existing virtual or emulated DSA switch implementations. I’ll search specifically for the DSA switch drivers themselves and how those switches are created in general. I need to understand the fundamental architecture and identify potential avenues for testing.



12/2 2026/09/28 00:53 tool grepper
0m Args:
{
  "Expression": "dsa_register_switch",
  "PathPrefix": "drivers/net/dsa/"
}

Results:
{
  "Output": "drivers/net/dsa/b53/b53_common.c=3308=int b53_switch_register(struct b53_device *dev)\n--\ndrivers/net/dsa/b53/b53_common.c-3326-\ndrivers/net/dsa/b53/b53_common.c:3327:\treturn dsa_register_switch(dev-\u003eds);\ndrivers/net/dsa/b53/b53_common.c-3328-}\n--\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\ndrivers/net/dsa/dsa_loop.c-59-\t * without gory pointer manipulations\n--\ndrivers/net/dsa/dsa_loop.c=355=static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/dsa_loop.c-388-\ndrivers/net/dsa/dsa_loop.c:389:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/dsa_loop.c-390-\tif (!ret)\n--\ndrivers/net/dsa/hirschmann/hellcreek.c=1933=static int hellcreek_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/hirschmann/hellcreek.c-2026-\ndrivers/net/dsa/hirschmann/hellcreek.c:2027:\tret = dsa_register_switch(hellcreek-\u003eds);\ndrivers/net/dsa/hirschmann/hellcreek.c-2028-\tif (ret) {\n--\ndrivers/net/dsa/lan9303-core.c=1404=static int lan9303_register_switch(struct lan9303 *chip)\n--\ndrivers/net/dsa/lan9303-core.c-1416-\ndrivers/net/dsa/lan9303-core.c:1417:\treturn dsa_register_switch(chip-\u003eds);\ndrivers/net/dsa/lan9303-core.c-1418-}\n--\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c=1679=int gswip_probe_common(struct gswip_priv *priv, u32 version)\n--\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-1704-\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c:1705:\terr = dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-1706-\tif (err)\n--\ndrivers/net/dsa/microchip/ksz_common.c=3966=int ksz_switch_register(struct ksz_device *dev)\n--\ndrivers/net/dsa/microchip/ksz_common.c-4123-\ndrivers/net/dsa/microchip/ksz_common.c:4124:\tret = dsa_register_switch(dev-\u003eds);\ndrivers/net/dsa/microchip/ksz_common.c-4125-\tif (ret)\n--\ndrivers/net/dsa/motorcomm/chip.c=4724=static int yt921x_mdio_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/motorcomm/chip.c-4768-\ndrivers/net/dsa/motorcomm/chip.c:4769:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/motorcomm/chip.c-4770-}\n--\ndrivers/net/dsa/mt7530-mdio.c=159=mt7530_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mt7530-mdio.c-223-\ndrivers/net/dsa/mt7530-mdio.c:224:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/mt7530-mdio.c-225-}\n--\ndrivers/net/dsa/mt7530-mmio.c=30=mt7988_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/mt7530-mmio.c-63-\ndrivers/net/dsa/mt7530-mmio.c:64:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/mt7530-mmio.c-65-}\n--\ndrivers/net/dsa/mt7628.c=565=static int mt7628_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/mt7628.c-606-\ndrivers/net/dsa/mt7628.c:607:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mt7628.c-608-}\n--\ndrivers/net/dsa/mv88e6060.c=304=static int mv88e6060_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mv88e6060.c-335-\ndrivers/net/dsa/mv88e6060.c:336:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mv88e6060.c-337-}\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=3989=static int mv88e6xxx_setup(struct dsa_switch *ds)\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-4004-\t * depends on the physical switch topology. We need to let DSA figure\ndrivers/net/dsa/mv88e6xxx/chip.c:4005:\t * that out and therefore we cannot set this at dsa_register_switch()\ndrivers/net/dsa/mv88e6xxx/chip.c-4006-\t * time.\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=7320=static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-7345-\ndrivers/net/dsa/mv88e6xxx/chip.c:7346:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mv88e6xxx/chip.c-7347-}\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2257=static int mxl862xx_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2296-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2297:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2298-\tif (err) {\n--\ndrivers/net/dsa/netc/netc_main.c=2435=static int netc_switch_probe(struct pci_dev *pdev,\n--\ndrivers/net/dsa/netc/netc_main.c-2478-\ndrivers/net/dsa/netc/netc_main.c:2479:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/netc/netc_main.c-2480-\tif (err)\n--\ndrivers/net/dsa/ocelot/felix.c=2390=int felix_register_switch(struct device *dev, resource_size_t switch_base,\n--\ndrivers/net/dsa/ocelot/felix.c-2428-\ndrivers/net/dsa/ocelot/felix.c:2429:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/ocelot/felix.c-2430-\tif (err)\n--\ndrivers/net/dsa/qca/ar9331.c=1031=static int ar9331_sw_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/qca/ar9331.c-1078-\ndrivers/net/dsa/qca/ar9331.c:1079:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/qca/ar9331.c-1080-\tif (ret)\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c=2059=qca8k_sw_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c-2120-\ndrivers/net/dsa/qca/qca8k-8xxx.c:2121:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/qca/qca8k-8xxx.c-2122-}\n--\ndrivers/net/dsa/realtek/rtl83xx.c=268=int rtl83xx_register_switch(struct realtek_priv *priv)\n--\ndrivers/net/dsa/realtek/rtl83xx.c-284-\ndrivers/net/dsa/realtek/rtl83xx.c:285:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/realtek/rtl83xx.c-286-\tif (ret) {\n--\ndrivers/net/dsa/rzn1_a5psw.c=1207=static int a5psw_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/rzn1_a5psw.c-1262-\ndrivers/net/dsa/rzn1_a5psw.c:1263:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/rzn1_a5psw.c-1264-\tif (ret) {\n--\ndrivers/net/dsa/sja1105/sja1105_main.c=3237=static int sja1105_probe(struct spi_device *spi)\n--\ndrivers/net/dsa/sja1105/sja1105_main.c-3340-\ndrivers/net/dsa/sja1105/sja1105_main.c:3341:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/sja1105/sja1105_main.c-3342-}\n--\ndrivers/net/dsa/vitesse-vsc73xx-core.c=2333=int vsc73xx_probe(struct vsc73xx *vsc)\n--\ndrivers/net/dsa/vitesse-vsc73xx-core.c-2384-\tvsc-\u003eds-\u003ephylink_mac_ops = \u0026vsc73xx_phylink_mac_ops;\ndrivers/net/dsa/vitesse-vsc73xx-core.c:2385:\tret = dsa_register_switch(vsc-\u003eds);\ndrivers/net/dsa/vitesse-vsc73xx-core.c-2386-\tif (ret) {\n--\ndrivers/net/dsa/xrs700x/xrs700x.c=818=int xrs700x_switch_register(struct xrs700x *priv)\n--\ndrivers/net/dsa/xrs700x/xrs700x.c-841-\ndrivers/net/dsa/xrs700x/xrs700x.c:842:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/xrs700x/xrs700x.c-843-}\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 42094
							output: 39
							thoughts: 40
						

Thoughts:
Here's my analysis of the situation:

**Investigating the "dsa_loop" Module**

Okay, the first thing I'm doing is zeroing in on this `dsa_loop.c` file. The name itself raises a flag – "loop." I'm immediately curious: What exactly is this `dsa_loop` functionality within the broader context? I need to understand its purpose. Is it a debugging tool, a testing mechanism, or perhaps a more integral part of the Distributed Switch Architecture (DSA)?

My next step is clear: I'm diving directly into the source code itself, specifically `drivers/net/dsa/dsa_loop.c`. I need to see the implementation details to form a complete understanding. I'll be looking for clues on how it's initialized, how it's used, and what its specific role is within the DSA framework. I'll be searching for the function definitions, data structures, and any comments that illuminate its function. I'm expecting to uncover its connections to other DSA components and network interfaces.



14/2 2026/09/28 00:53 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 1,
  "LineCount": 70
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0-or-later\n   2:\t/*\n   3:\t * Distributed Switch Architecture loopback driver\n   4:\t *\n   5:\t * Copyright (C) 2016, Florian Fainelli \u003cf.fainelli@gmail.com\u003e\n   6:\t */\n   7:\t\n   8:\t#include \u003clinux/platform_device.h\u003e\n   9:\t#include \u003clinux/netdevice.h\u003e\n  10:\t#include \u003clinux/phy.h\u003e\n  11:\t#include \u003clinux/phy_fixed.h\u003e\n  12:\t#include \u003clinux/export.h\u003e\n  13:\t#include \u003clinux/ethtool.h\u003e\n  14:\t#include \u003clinux/workqueue.h\u003e\n  15:\t#include \u003clinux/module.h\u003e\n  16:\t#include \u003clinux/if_bridge.h\u003e\n  17:\t#include \u003clinux/if_vlan.h\u003e\n  18:\t#include \u003clinux/types.h\u003e\n  19:\t#include \u003cnet/dsa.h\u003e\n  20:\t\n  21:\t#define DSA_LOOP_NUM_PORTS\t6\n  22:\t#define DSA_LOOP_CPU_PORT\t(DSA_LOOP_NUM_PORTS - 1)\n  23:\t#define NUM_FIXED_PHYS\t\t(DSA_LOOP_NUM_PORTS - 2)\n  24:\t\n  25:\tstruct dsa_loop_vlan {\n  26:\t\tu16 members;\n  27:\t\tu16 untagged;\n  28:\t};\n  29:\t\n  30:\tstruct dsa_loop_mib_entry {\n  31:\t\tchar name[ETH_GSTRING_LEN];\n  32:\t\tunsigned long val;\n  33:\t};\n  34:\t\n  35:\tenum dsa_loop_mib_counters {\n  36:\t\tDSA_LOOP_PHY_READ_OK,\n  37:\t\tDSA_LOOP_PHY_READ_ERR,\n  38:\t\tDSA_LOOP_PHY_WRITE_OK,\n  39:\t\tDSA_LOOP_PHY_WRITE_ERR,\n  40:\t\t__DSA_LOOP_CNT_MAX,\n  41:\t};\n  42:\t\n  43:\tstruct dsa_loop_port {\n  44:\t\tstruct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];\n  45:\t\tu16 pvid;\n  46:\t\tint mtu;\n  47:\t};\n  48:\t\n  49:\tstruct dsa_loop_priv {\n  50:\t\tstruct mii_bus\t*bus;\n  51:\t\tunsigned int\tport_base;\n  52:\t\tstruct dsa_loop_vlan vlans[VLAN_N_VID];\n  53:\t\tstruct net_device *netdev;\n  54:\t\tstruct dsa_loop_port ports[DSA_MAX_PORTS];\n  55:\t};\n  56:\t\n  57:\tstruct dsa_loop_pdata {\n  58:\t\t/* Must be first, such that dsa_register_switch() can access this\n  59:\t\t * without gory pointer manipulations\n  60:\t\t */\n  61:\t\tstruct dsa_chip_data cd;\n  62:\t\tconst char *name;\n  63:\t\tunsigned int enabled_ports;\n  64:\t\tconst char *netdev;\n  65:\t};\n  66:\t\n  67:\tstatic struct dsa_loop_mib_entry dsa_loop_mibs[] = {\n  68:\t\t[DSA_LOOP_PHY_READ_OK]\t= { \"phy_read_ok\", },\n  69:\t\t[DSA_LOOP_PHY_READ_ERR]\t= { \"phy_read_err\", },\n  70:\t\t[DSA_LOOP_PHY_WRITE_OK] = { \"phy_write_ok\", },\n"
}

15/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43408
							output: 36
							thoughts: 20
						

Thoughts:
Here's my take on those thoughts:

**Checking `dsa_loop` and `devlink_flash_update`**

Okay, so I need to quickly determine something about `dsa_loop`. The question is, does it actually implement the `devlink_flash_update` function? That's the crux of it. I need to know if this code path includes that specific functionality. Let's dig in and figure this out quickly. I'll need to check the code.



16/2 2026/09/28 00:53 tool grepper
0m Args:
{
  "Expression": "devlink_",
  "PathPrefix": "drivers/net/dsa/dsa_loop.c"
}

Results:
{
  "Output": "drivers/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-{\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;\n--\ndrivers/net/dsa/dsa_loop.c-102-\ndrivers/net/dsa/dsa_loop.c:103:\tdevlink_resource_size_params_init(\u0026size_params, ARRAY_SIZE(ps-\u003evlans),\ndrivers/net/dsa/dsa_loop.c-104-\t\t\t\t\t  ARRAY_SIZE(ps-\u003evlans),\n--\ndrivers/net/dsa/dsa_loop.c-106-\ndrivers/net/dsa/dsa_loop.c:107:\terr = dsa_devlink_resource_register(ds, \"VTU\", ARRAY_SIZE(ps-\u003evlans),\ndrivers/net/dsa/dsa_loop.c-108-\t\t\t\t\t    DSA_LOOP_DEVLINK_PARAM_ID_VTU,\n--\ndrivers/net/dsa/dsa_loop.c-113-\ndrivers/net/dsa/dsa_loop.c:114:\tdsa_devlink_resource_occ_get_register(ds,\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-120-out:\ndrivers/net/dsa/dsa_loop.c:121:\tdsa_devlink_resources_unregister(ds);\ndrivers/net/dsa/dsa_loop.c-122-\treturn err;\n--\ndrivers/net/dsa/dsa_loop.c=134=static int dsa_loop_setup(struct dsa_switch *ds)\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-}\n--\ndrivers/net/dsa/dsa_loop.c=148=static void dsa_loop_teardown(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-149-{\ndrivers/net/dsa/dsa_loop.c:150:\tdsa_devlink_resources_unregister(ds);\ndrivers/net/dsa/dsa_loop.c-151-}\n"
}

17/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 44574
							output: 41
							thoughts: 275
						

Thoughts:
**Analyzing the DSA Devlink Flash Update Path**

Okay, let's break this down. My initial thought is to look at the implementation of the `dsa_devlink_flash_update` function in `net/dsa/devlink.c`. I see that it dispatches the flash update request to the `dsa_switch`'s operation structure. The critical line is that check for `ds->ops->devlink_flash_update`, and the return of `-EOPNOTSUPP`.

My immediate concern is whether or not this code path is even *used* within syzkaller's test framework. It seems unlikely that this `devlink_flash_update` would be supported by default, so I'm wondering if a DSA switch like `dsa_loop` is instantiated at all during syzkaller's execution of tests.

Given that syzkaller doesn't instantiate `dsa_loop` automatically, unless specifically configured to do so, or defined in syzlang. It seems like the path may not be relevant without extra steps. So I need to go deeper. Perhaps looking at the module init of `dsa_loop` will provide a clue.



18/2 2026/09/28 00:53 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 400,
  "LineCount": 50
}

Results:
{
  "Contents": " 400:\t\tstruct dsa_loop_priv *ps;\n 401:\t\n 402:\t\tif (!ds)\n 403:\t\t\treturn;\n 404:\t\n 405:\t\tps = ds-\u003epriv;\n 406:\t\n 407:\t\tdsa_unregister_switch(ds);\n 408:\t\tdev_put(ps-\u003enetdev);\n 409:\t}\n 410:\t\n 411:\tstatic void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\n 412:\t{\n 413:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n 414:\t\n 415:\t\tif (!ds)\n 416:\t\t\treturn;\n 417:\t\n 418:\t\tdsa_switch_shutdown(ds);\n 419:\t\n 420:\t\tdev_set_drvdata(\u0026mdiodev-\u003edev, NULL);\n 421:\t}\n 422:\t\n 423:\tstatic struct mdio_driver dsa_loop_drv = {\n 424:\t\t.mdiodrv.driver\t= {\n 425:\t\t\t.name\t= \"dsa-loop\",\n 426:\t\t},\n 427:\t\t.probe\t= dsa_loop_drv_probe,\n 428:\t\t.remove\t= dsa_loop_drv_remove,\n 429:\t\t.shutdown = dsa_loop_drv_shutdown,\n 430:\t};\n 431:\t\n 432:\tstatic int dsa_loop_bus_match(struct device *dev,\n 433:\t\t\t\t      const struct device_driver *drv)\n 434:\t{\n 435:\t\treturn drv == \u0026dsa_loop_drv.mdiodrv.driver;\n 436:\t}\n 437:\t\n 438:\tstatic void dsa_loop_phydevs_unregister(void)\n 439:\t{\n 440:\t\tfor (int i = 0; i \u003c NUM_FIXED_PHYS; i++) {\n 441:\t\t\tif (!IS_ERR(phydevs[i]))\n 442:\t\t\t\tfixed_phy_unregister(phydevs[i]);\n 443:\t\t}\n 444:\t}\n 445:\t\n 446:\tstatic int __init dsa_loop_create_switch_mdiodev(void)\n 447:\t{\n 448:\t\tstatic struct dsa_loop_pdata dsa_loop_pdata = {\n 449:\t\t\t.cd = {\n"
}

19/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 45842
							output: 31
							thoughts: 871
						

Thoughts:
**Analyzing the Reachability of `device_schedule_reprobe`**

Okay, hold on a second! This is interesting. I'm looking at a new function, `device_schedule_reprobe`, that was added to `drivers/base/dd.c`. My initial thought is to figure out its call sites. Let's dig into `drivers/base/dd.c` to understand the context. Where exactly *is* this thing called? After a quick search, it looks like only `drivers/net/dsa/mxl862xx/mxl862xx-fw.c` uses it. Hmm, that's peculiar.

Can `mxl862xx` even execute under QEMU or in syzkaller? Is this a reachable path at all? Let's check what it is. Is it reachable hardware, or is this driver, by its very nature, structurally unreachable? Let me refer to my instructions: "Structurally unreachable hardware" could mean vendor-specific PCIe switches, SmartNICs, or GPU drivers. They require special hardware that isn't emulated. Is `mxl862xx` in that category?

But wait! `device_schedule_reprobe` is in `drivers/base/dd.c`. That's core kernel territory. But, just because it's there doesn't mean it's easily triggered. Here's what I see in `dd.c`: the function uses a `delayed_work` structure and is exported via `EXPORT_SYMBOL_GPL`.

Okay, even though it's in the core kernel code, I need to know if any user-space action, syscall, or reachable driver could invoke `device_schedule_reprobe`. Let's run `grepper` and see who calls it in the entire kernel tree.

Okay, the results are in. And the answer is only:
1. `mxl862xx_rescue_heal_work_fn` in `drivers/net/dsa/mxl862xx/mxl862xx-fw.c`
2. `mxl862xx_devlink_flash_update` in `drivers/net/dsa/mxl862xx/mxl862xx-fw.c`

Got it. So it's used *exclusively* within that `mxl862xx` driver. Right, now let me check `dsa_devlink_flash_update` in `net/dsa/devlink.c`. Who calls *that*? Okay, this is registered in `dsa_devlink_ops`: It looks like the flash update function is called through the devlink interface, `dsa_devlink_ops`. Can *that* be set up in a virtualized environment like QEMU?

So, back to the big question: Is any DSA device registered or created by default in a standard virtualized environment? Is syzkaller set up to do that? I need to know if that path is open. Let's dig into the kernel configs and syzkaller setup to see if DSA devices are supported by default.



20/2 2026/09/28 00:53 tool grepper
0m Args:
{
  "Expression": "CONFIG_NET_DSA",
  "PathPrefix": "drivers/net/dsa"
}

Results:
{
  "Output": "drivers/net/dsa/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/Makefile:2:obj-$(CONFIG_NET_DSA_BCM_SF2)\t+= bcm-sf2.o\ndrivers/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_MT7530)\t+= mt7530.o\ndrivers/net/dsa/Makefile:6:obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o\ndrivers/net/dsa/Makefile:7:obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o\ndrivers/net/dsa/Makefile:8:obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o\ndrivers/net/dsa/Makefile:9:obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o\ndrivers/net/dsa/Makefile:10:obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o\ndrivers/net/dsa/Makefile:11:obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o\ndrivers/net/dsa/Makefile:12:obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o\ndrivers/net/dsa/Makefile:13:obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o\ndrivers/net/dsa/Makefile:14:obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o\ndrivers/net/dsa/Makefile:15:obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o\ndrivers/net/dsa/Makefile:16:obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o\ndrivers/net/dsa/Makefile-17-obj-y\t\t\t\t+= b53/\n--\ndrivers/net/dsa/hirschmann/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/hirschmann/Makefile:2:obj-$(CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK)\t+= hellcreek_sw.o\ndrivers/net/dsa/hirschmann/Makefile-3-hellcreek_sw-objs := hellcreek.o\n--\ndrivers/net/dsa/lantiq/Makefile:1:obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o\ndrivers/net/dsa/lantiq/Makefile:2:obj-$(CONFIG_NET_DSA_LANTIQ_COMMON) += lantiq_gswip_common.o\ndrivers/net/dsa/lantiq/Makefile:3:obj-$(CONFIG_NET_DSA_MXL_GSW1XX) += mxl-gsw1xx.o\n--\ndrivers/net/dsa/microchip/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/microchip/Makefile:2:obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON)\t+= ksz_switch.o\ndrivers/net/dsa/microchip/Makefile-3-ksz_switch-objs := ksz_common.o ksz_dcb.o\n--\ndrivers/net/dsa/microchip/Makefile=6=ksz_switch-objs += lan937x_main.o\ndrivers/net/dsa/microchip/Makefile-7-\ndrivers/net/dsa/microchip/Makefile:8:ifdef CONFIG_NET_DSA_MICROCHIP_KSZ_PTP\ndrivers/net/dsa/microchip/Makefile-9-ksz_switch-objs += ksz_ptp.o\ndrivers/net/dsa/microchip/Makefile=10=endif\ndrivers/net/dsa/microchip/Makefile-11-\ndrivers/net/dsa/microchip/Makefile:12:obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C)\t+= ksz9477_i2c.o\ndrivers/net/dsa/microchip/Makefile:13:obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI)\t\t+= ksz_spi.o\ndrivers/net/dsa/microchip/Makefile:14:obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI)\t+= ksz8863_smi.o\n--\ndrivers/net/dsa/microchip/ksz_common.h=169=struct ksz_port {\n--\ndrivers/net/dsa/microchip/ksz_common.h-192-\tstruct phylink_pcs *pcs;\ndrivers/net/dsa/microchip/ksz_common.h:193:#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)\ndrivers/net/dsa/microchip/ksz_common.h-194-\tstruct kernel_hwtstamp_config tstamp_config;\n--\ndrivers/net/dsa/microchip/ksz_ptp.h-10-\ndrivers/net/dsa/microchip/ksz_ptp.h:11:#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)\ndrivers/net/dsa/microchip/ksz_ptp.h-12-\n--\ndrivers/net/dsa/microchip/ksz_ptp.h=87=static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {}\n--\ndrivers/net/dsa/microchip/ksz_ptp.h-102-\ndrivers/net/dsa/microchip/ksz_ptp.h:103:#endif\t/* End of CONFIG_NET_DSA_MICROCHIP_KSZ_PTP */\ndrivers/net/dsa/microchip/ksz_ptp.h-104-\n--\ndrivers/net/dsa/motorcomm/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/motorcomm/Makefile:2:obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o\ndrivers/net/dsa/motorcomm/Makefile-3-yt921x-objs := chip.o\ndrivers/net/dsa/motorcomm/Makefile:4:yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o\ndrivers/net/dsa/motorcomm/Makefile-5-yt921x-objs += smi.o\n--\ndrivers/net/dsa/motorcomm/chip.c=4227=static void yt921x_dsa_teardown(struct dsa_switch *ds)\ndrivers/net/dsa/motorcomm/chip.c-4228-{\ndrivers/net/dsa/motorcomm/chip.c:4229:#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\ndrivers/net/dsa/motorcomm/chip.c-4230-\tstruct yt921x_priv *priv = to_yt921x_priv(ds);\n--\ndrivers/net/dsa/motorcomm/chip.c=4546=static int yt921x_dsa_setup(struct dsa_switch *ds)\n--\ndrivers/net/dsa/motorcomm/chip.c-4591-\ndrivers/net/dsa/motorcomm/chip.c:4592:#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\ndrivers/net/dsa/motorcomm/chip.c-4593-\tres = yt921x_leds_setup(priv);\n--\ndrivers/net/dsa/motorcomm/chip.h=936=struct yt921x_port {\n--\ndrivers/net/dsa/motorcomm/chip.h-946-\ndrivers/net/dsa/motorcomm/chip.h:947:#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\ndrivers/net/dsa/motorcomm/chip.h-948-\tunsigned char led_duty;\n--\ndrivers/net/dsa/motorcomm/leds.h=96=struct yt921x_led {\n--\ndrivers/net/dsa/motorcomm/leds.h-101-\ndrivers/net/dsa/motorcomm/leds.h:102:#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\ndrivers/net/dsa/motorcomm/leds.h-103-\n--\ndrivers/net/dsa/mv88e6xxx/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/mv88e6xxx/Makefile:2:obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o\ndrivers/net/dsa/mv88e6xxx/Makefile-3-mv88e6xxx-objs := chip.o\n--\ndrivers/net/dsa/mv88e6xxx/Makefile=10=mv88e6xxx-objs += global2_scratch.o\ndrivers/net/dsa/mv88e6xxx/Makefile:11:mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o\ndrivers/net/dsa/mv88e6xxx/Makefile:12:mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_LEDS) += leds.o\ndrivers/net/dsa/mv88e6xxx/Makefile-13-mv88e6xxx-objs += pcs-6185.o\n--\ndrivers/net/dsa/mv88e6xxx/Makefile=18=mv88e6xxx-objs += port_hidden.o\ndrivers/net/dsa/mv88e6xxx/Makefile:19:mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o\ndrivers/net/dsa/mv88e6xxx/Makefile-20-mv88e6xxx-objs += serdes.o\n--\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-110-\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h:111:#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-112-\n--\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h=133=int mv88e6165_global_disable(struct mv88e6xxx_chip *chip);\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-134-\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h:135:#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-136-\n--\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h=175=static inline void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip)\n--\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-178-\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h:179:#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */\ndrivers/net/dsa/mv88e6xxx/hwtstamp.h-180-\n--\ndrivers/net/dsa/mv88e6xxx/port.h=579=int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);\ndrivers/net/dsa/mv88e6xxx/port.h:580:#ifdef CONFIG_NET_DSA_MV88E6XXX_LEDS\ndrivers/net/dsa/mv88e6xxx/port.h-581-int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port);\n--\ndrivers/net/dsa/mv88e6xxx/ptp.h-66-\ndrivers/net/dsa/mv88e6xxx/ptp.h:67:#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP\ndrivers/net/dsa/mv88e6xxx/ptp.h-68-\n--\ndrivers/net/dsa/mv88e6xxx/ptp.h=77=extern const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops;\ndrivers/net/dsa/mv88e6xxx/ptp.h-78-\ndrivers/net/dsa/mv88e6xxx/ptp.h:79:#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */\ndrivers/net/dsa/mv88e6xxx/ptp.h-80-\n--\ndrivers/net/dsa/mv88e6xxx/ptp.h=92=static const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = {};\ndrivers/net/dsa/mv88e6xxx/ptp.h-93-\ndrivers/net/dsa/mv88e6xxx/ptp.h:94:#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */\ndrivers/net/dsa/mv88e6xxx/ptp.h-95-\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--\ndrivers/net/dsa/netc/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/netc/Makefile:2:obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o\ndrivers/net/dsa/netc/Makefile-3-nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o\n--\ndrivers/net/dsa/ocelot/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/ocelot/Makefile:2:obj-$(CONFIG_NET_DSA_MSCC_FELIX_DSA_LIB) += mscc_felix_dsa_lib.o\ndrivers/net/dsa/ocelot/Makefile:3:obj-$(CONFIG_NET_DSA_MSCC_FELIX) += mscc_felix.o\ndrivers/net/dsa/ocelot/Makefile:4:obj-$(CONFIG_NET_DSA_MSCC_OCELOT_EXT) += mscc_ocelot_ext.o\ndrivers/net/dsa/ocelot/Makefile:5:obj-$(CONFIG_NET_DSA_MSCC_SEVILLE) += mscc_seville.o\ndrivers/net/dsa/ocelot/Makefile-6-\n--\ndrivers/net/dsa/qca/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/qca/Makefile:2:obj-$(CONFIG_NET_DSA_AR9331)\t+= ar9331.o\ndrivers/net/dsa/qca/Makefile:3:obj-$(CONFIG_NET_DSA_QCA8K)\t+= qca8k.o\ndrivers/net/dsa/qca/Makefile-4-qca8k-y \t\t\t+= qca8k-common.o qca8k-8xxx.o\ndrivers/net/dsa/qca/Makefile:5:ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT\ndrivers/net/dsa/qca/Makefile-6-qca8k-y\t\t\t\t+= qca8k-leds.o\n--\ndrivers/net/dsa/qca/qca8k_leds.h-6-/* Leds Support function */\ndrivers/net/dsa/qca/qca8k_leds.h:7:#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT\ndrivers/net/dsa/qca/qca8k_leds.h-8-int qca8k_setup_led_ctrl(struct qca8k_priv *priv);\n--\ndrivers/net/dsa/realtek/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/realtek/Makefile:2:obj-$(CONFIG_NET_DSA_REALTEK)\t\t+= realtek_dsa.o\ndrivers/net/dsa/realtek/Makefile-3-realtek_dsa-objs\t\t\t:= rtl83xx.o\ndrivers/net/dsa/realtek/Makefile-4-\ndrivers/net/dsa/realtek/Makefile:5:ifdef CONFIG_NET_DSA_REALTEK_MDIO\ndrivers/net/dsa/realtek/Makefile-6-realtek_dsa-objs += realtek-mdio.o\ndrivers/net/dsa/realtek/Makefile=7=endif\ndrivers/net/dsa/realtek/Makefile-8-\ndrivers/net/dsa/realtek/Makefile:9:ifdef CONFIG_NET_DSA_REALTEK_SMI\ndrivers/net/dsa/realtek/Makefile-10-realtek_dsa-objs += realtek-smi.o\ndrivers/net/dsa/realtek/Makefile=11=endif\ndrivers/net/dsa/realtek/Makefile-12-\ndrivers/net/dsa/realtek/Makefile:13:obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o\ndrivers/net/dsa/realtek/Makefile-14-rtl8366-objs \t\t\t\t:= rtl8366-core.o rtl8366rb.o\ndrivers/net/dsa/realtek/Makefile:15:ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS\ndrivers/net/dsa/realtek/Makefile-16-rtl8366-objs \t\t\t\t+= rtl8366rb-leds.o\ndrivers/net/dsa/realtek/Makefile=17=endif\ndrivers/net/dsa/realtek/Makefile:18:obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o\ndrivers/net/dsa/realtek/Makefile-19-rtl8365mb-objs := rtl8365mb_main.o \\\n--\ndrivers/net/dsa/realtek/realtek-mdio.h-5-\ndrivers/net/dsa/realtek/realtek-mdio.h:6:#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO)\ndrivers/net/dsa/realtek/realtek-mdio.h-7-\n--\ndrivers/net/dsa/realtek/realtek-mdio.h=20=void realtek_mdio_shutdown(struct mdio_device *mdiodev);\ndrivers/net/dsa/realtek/realtek-mdio.h-21-\ndrivers/net/dsa/realtek/realtek-mdio.h:22:#else /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO) */\ndrivers/net/dsa/realtek/realtek-mdio.h-23-\n--\ndrivers/net/dsa/realtek/realtek-mdio.h=42=static inline void realtek_mdio_shutdown(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/realtek/realtek-mdio.h-45-\ndrivers/net/dsa/realtek/realtek-mdio.h:46:#endif /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO) */\ndrivers/net/dsa/realtek/realtek-mdio.h-47-\n--\ndrivers/net/dsa/realtek/realtek-smi.h-5-\ndrivers/net/dsa/realtek/realtek-smi.h:6:#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI)\ndrivers/net/dsa/realtek/realtek-smi.h-7-\n--\ndrivers/net/dsa/realtek/realtek-smi.h=20=void realtek_smi_shutdown(struct platform_device *pdev);\ndrivers/net/dsa/realtek/realtek-smi.h-21-\ndrivers/net/dsa/realtek/realtek-smi.h:22:#else /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI) */\ndrivers/net/dsa/realtek/realtek-smi.h-23-\n--\ndrivers/net/dsa/realtek/realtek-smi.h=42=static inline void realtek_smi_shutdown(struct platform_device *pdev)\n--\ndrivers/net/dsa/realtek/realtek-smi.h-45-\ndrivers/net/dsa/realtek/realtek-smi.h:46:#endif /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI) */\ndrivers/net/dsa/realtek/realtek-smi.h-47-\n--\ndrivers/net/dsa/realtek/rtl8366rb.h=47=enum rtl8366_ledgroup_mode {\n--\ndrivers/net/dsa/realtek/rtl8366rb.h-67-\ndrivers/net/dsa/realtek/rtl8366rb.h:68:#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS)\ndrivers/net/dsa/realtek/rtl8366rb.h-69-\n--\ndrivers/net/dsa/realtek/rtl8366rb.h=94=struct rtl8366rb {\n--\ndrivers/net/dsa/realtek/rtl8366rb.h-96-\tbool pvid_enabled[RTL8366RB_NUM_PORTS];\ndrivers/net/dsa/realtek/rtl8366rb.h:97:#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS)\ndrivers/net/dsa/realtek/rtl8366rb.h-98-\tstruct rtl8366rb_led leds[RTL8366RB_NUM_PORTS][RTL8366RB_NUM_LEDGROUPS];\n--\ndrivers/net/dsa/sja1105/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/sja1105/Makefile:2:obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o\ndrivers/net/dsa/sja1105/Makefile-3-\ndrivers/net/dsa/sja1105/Makefile=4=sja1105-objs := \\\n--\ndrivers/net/dsa/sja1105/Makefile-14-\ndrivers/net/dsa/sja1105/Makefile:15:ifdef CONFIG_NET_DSA_SJA1105_PTP\ndrivers/net/dsa/sja1105/Makefile-16-sja1105-objs += sja1105_ptp.o\ndrivers/net/dsa/sja1105/Makefile=17=endif\ndrivers/net/dsa/sja1105/Makefile-18-\ndrivers/net/dsa/sja1105/Makefile:19:ifdef CONFIG_NET_DSA_SJA1105_TAS\ndrivers/net/dsa/sja1105/Makefile-20-sja1105-objs += sja1105_tas.o\ndrivers/net/dsa/sja1105/Makefile=21=endif\ndrivers/net/dsa/sja1105/Makefile-22-\ndrivers/net/dsa/sja1105/Makefile:23:ifdef CONFIG_NET_DSA_SJA1105_VL\ndrivers/net/dsa/sja1105/Makefile-24-sja1105-objs += sja1105_vl.o\n--\ndrivers/net/dsa/sja1105/sja1105_ptp.h-8-\ndrivers/net/dsa/sja1105/sja1105_ptp.h:9:#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP)\ndrivers/net/dsa/sja1105/sja1105_ptp.h-10-\n--\ndrivers/net/dsa/sja1105/sja1105_ptp.h=181=static inline int sja1105_ptp_commit(struct dsa_switch *ds,\n--\ndrivers/net/dsa/sja1105/sja1105_ptp.h-207-\ndrivers/net/dsa/sja1105/sja1105_ptp.h:208:#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) */\ndrivers/net/dsa/sja1105/sja1105_ptp.h-209-\n--\ndrivers/net/dsa/sja1105/sja1105_tas.h=11=struct sja1105_private;\ndrivers/net/dsa/sja1105/sja1105_tas.h-12-\ndrivers/net/dsa/sja1105/sja1105_tas.h:13:#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS)\ndrivers/net/dsa/sja1105/sja1105_tas.h-14-\n--\ndrivers/net/dsa/sja1105/sja1105_tas.h=97=static inline int sja1105_init_scheduling(struct sja1105_private *priv)\n--\ndrivers/net/dsa/sja1105/sja1105_tas.h-101-\ndrivers/net/dsa/sja1105/sja1105_tas.h:102:#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS) */\ndrivers/net/dsa/sja1105/sja1105_tas.h-103-\n--\ndrivers/net/dsa/sja1105/sja1105_vl.h-8-\ndrivers/net/dsa/sja1105/sja1105_vl.h:9:#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_VL)\ndrivers/net/dsa/sja1105/sja1105_vl.h-10-\n--\ndrivers/net/dsa/sja1105/sja1105_vl.h=63=static inline int sja1105_vl_stats(struct sja1105_private *priv, int port,\n--\ndrivers/net/dsa/sja1105/sja1105_vl.h-71-\ndrivers/net/dsa/sja1105/sja1105_vl.h:72:#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_VL) */\ndrivers/net/dsa/sja1105/sja1105_vl.h-73-\n--\ndrivers/net/dsa/xrs700x/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/xrs700x/Makefile:2:obj-$(CONFIG_NET_DSA_XRS700X) += xrs700x.o\ndrivers/net/dsa/xrs700x/Makefile:3:obj-$(CONFIG_NET_DSA_XRS700X_I2C) += xrs700x_i2c.o\ndrivers/net/dsa/xrs700x/Makefile:4:obj-$(CONFIG_NET_DSA_XRS700X_MDIO) += xrs700x_mdio.o\n"
}

21/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 54949
							output: 41
							thoughts: 70
						

Thoughts:
Here's my understanding of the situation, tailored for my expertise:

**Examining the dsa_loop Driver**

Okay, so I'm looking into how the `dsa_loop.c` driver behaves. My initial thought is to confirm that it's gated by the usual `CONFIG_NET_DSA_LOOP` Kconfig symbol – makes sense. Now, the real question is, how exactly does this loopback driver function? I need to delve into its core mechanisms. My primary target for investigation is likely going to be the `dsa_loop_create_switch_mdiodev` function. That's the logical starting point to understand the creation and initialization of the virtual switch device used for the loopback functionality. From there, I can probably trace the data path.



22/2 2026/09/28 00:53 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 445,
  "LineCount": 60
}

Results:
{
  "Contents": " 445:\t\n 446:\tstatic int __init dsa_loop_create_switch_mdiodev(void)\n 447:\t{\n 448:\t\tstatic struct dsa_loop_pdata dsa_loop_pdata = {\n 449:\t\t\t.cd = {\n 450:\t\t\t\t.port_names[0] = \"lan1\",\n 451:\t\t\t\t.port_names[1] = \"lan2\",\n 452:\t\t\t\t.port_names[2] = \"lan3\",\n 453:\t\t\t\t.port_names[3] = \"lan4\",\n 454:\t\t\t\t.port_names[DSA_LOOP_CPU_PORT] = \"cpu\",\n 455:\t\t\t},\n 456:\t\t\t.name = \"DSA mockup driver\",\n 457:\t\t\t.enabled_ports = 0x1f,\n 458:\t\t\t.netdev = \"eth0\",\n 459:\t\t};\n 460:\t\tstruct mii_bus *bus;\n 461:\t\tint ret = -ENODEV;\n 462:\t\n 463:\t\tbus = mdio_find_bus(\"fixed-0\");\n 464:\t\tif (WARN_ON(!bus))\n 465:\t\t\treturn ret;\n 466:\t\n 467:\t\tswitch_mdiodev = mdio_device_create(bus, 31);\n 468:\t\tif (IS_ERR(switch_mdiodev))\n 469:\t\t\tgoto out;\n 470:\t\n 471:\t\tswitch_mdiodev-\u003ebus_match = dsa_loop_bus_match;\n 472:\t\tswitch_mdiodev-\u003edev.platform_data = \u0026dsa_loop_pdata;\n 473:\t\n 474:\t\tret = mdio_device_register(switch_mdiodev);\n 475:\t\tif (ret)\n 476:\t\t\tmdio_device_free(switch_mdiodev);\n 477:\tout:\n 478:\t\tput_device(\u0026bus-\u003edev);\n 479:\t\treturn ret;\n 480:\t}\n 481:\t\n 482:\tstatic int __init dsa_loop_init(void)\n 483:\t{\n 484:\t\tunsigned int i;\n 485:\t\tint ret;\n 486:\t\n 487:\t\tret = dsa_loop_create_switch_mdiodev();\n 488:\t\tif (ret)\n 489:\t\t\treturn ret;\n 490:\t\n 491:\t\tfor (i = 0; i \u003c NUM_FIXED_PHYS; i++)\n 492:\t\t\tphydevs[i] = fixed_phy_register_100fd();\n 493:\t\n 494:\t\tret = mdio_driver_register(\u0026dsa_loop_drv);\n 495:\t\tif (ret) {\n 496:\t\t\tdsa_loop_phydevs_unregister();\n 497:\t\t\tmdio_device_remove(switch_mdiodev);\n 498:\t\t\tmdio_device_free(switch_mdiodev);\n 499:\t\t}\n 500:\t\n 501:\t\treturn ret;\n 502:\t}\n 503:\tmodule_init(dsa_loop_init);\n 504:\t\n"
}

23/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 56108
							output: 41
							thoughts: 47
						

Thoughts:
Okay, let's see. Here's how I'm thinking through this, as I parse this information:

**Analyzing the `dsa_loop` Driver's Capabilities**

Alright, so `dsa_loop` is just a mockup driver, got it. But, wait a second... does being a mockup automatically exclude it from implementing crucial functions like `devlink_flash_update`? I need to clarify that. It's a key question. My immediate next step is to examine the `dsa_loop_switch_ops` structure. That's where the rubber meets the road. It will reveal whether the driver has *any* flash update capabilities, even if it's a test implementation. That's my line of inquiry now. Let's see what's in there.



24/2 2026/09/28 00:53 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "dsa_loop_switch_ops",
  "PathPrefix": "drivers/net/dsa/dsa_loop.c"
}

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

25/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 56381
							output: 40
							thoughts: 20
						

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

**Investigating `dsa_switch_ops` in `dsa_loop.c`**

Right, so I need to understand how this DSA switch is interacting with the loopback interface. The logical starting point for understanding that interaction is the `dsa_switch_ops` structure within `dsa_loop.c`. I need to figure out how these operations are defined and how they're implemented within the loopback context. What are the key function pointers defined here? Are they basic operations, or is there some more complex logic? I need to analyze those specific operations to fully grasp the data path and control flow within the DSA framework in the loopback scenario. This will allow me to reason about the implications for my understanding of the system's overall architecture. I expect I'll gain some important insights into the DSA driver framework as a whole, too. Let's dig in and see what's what.



26/2 2026/09/28 00:53 tool grepper
0m Args:
{
  "Expression": "struct dsa_switch_ops",
  "PathPrefix": "drivers/net/dsa/dsa_loop.c"
}

Results:
{
  "Output": "drivers/net/dsa/dsa_loop.c=326=static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port,\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,\n"
}

27/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 56811
							output: 41
							thoughts: 23
						

Thoughts:
Here's a summary of my thought process, as requested:

**Deconstructing `dsa_loop_driver` Implementation**

Okay, so I need to dive into `dsa_loop.c` and figure out what's actually happening within the `dsa_loop_driver` implementation. I'm going to focus specifically on how it's *implemented* – what are the core functions, data structures, and the overall logic flow? I'm not just interested in the *what* it does, but the *how*. I'll be looking for things like:

*   **Initialization:** How is the driver initialized? Are there any specific parameters, registers, or hardware resources being set up? What's the context it's operating in (kernel modules, specific hardware...)?
*   **Data Structures:** What custom data structures does it use to manage its internal state? How are they organized, and what information do they contain? Any interesting pointers or linked lists involved? What synchronization mechanisms are employed?
*   **Core Functions:** What are the key function calls? I'm particularly interested in the ones that handle data transfers, device control, and any interrupt-related logic.
*   **Error Handling:** How does the driver handle potential errors? Are there specific error codes, logging mechanisms, or retry strategies?
*   **Interaction with Hardware/Kernel:** How does the driver interface with the underlying hardware (if any) or the rest of the kernel? Are there any specific device tree bindings or platform-specific considerations? Does it use any specific DMA channels or memory allocation functions? What about power management considerations?
*   **Concurrency/Synchronization:** Is the driver designed with concurrency in mind? How does it handle multiple threads/processes accessing the same hardware? Are there any mutexes, spinlocks, or other synchronization primitives used?

Ultimately, I want a very detailed picture of the driver's inner workings. I need to understand the control flow, data management, and any potential bottlenecks or areas of concern. This level of understanding is critical for debugging, optimization, or even extending the driver's functionality later on.



28/2 2026/09/28 00:53 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 330,
  "LineCount": 35
}

Results:
{
  "Contents": " 330:\t\t__clear_bit(PHY_INTERFACE_MODE_NA, config-\u003esupported_interfaces);\n 331:\t\tconfig-\u003emac_capabilities = ~0;\n 332:\t}\n 333:\t\n 334:\tstatic const struct dsa_switch_ops dsa_loop_driver = {\n 335:\t\t.get_tag_protocol\t= dsa_loop_get_protocol,\n 336:\t\t.setup\t\t\t= dsa_loop_setup,\n 337:\t\t.teardown\t\t= dsa_loop_teardown,\n 338:\t\t.get_strings\t\t= dsa_loop_get_strings,\n 339:\t\t.get_ethtool_stats\t= dsa_loop_get_ethtool_stats,\n 340:\t\t.get_sset_count\t\t= dsa_loop_get_sset_count,\n 341:\t\t.get_ethtool_phy_stats\t= dsa_loop_get_ethtool_stats,\n 342:\t\t.phy_read\t\t= dsa_loop_phy_read,\n 343:\t\t.phy_write\t\t= dsa_loop_phy_write,\n 344:\t\t.port_bridge_join\t= dsa_loop_port_bridge_join,\n 345:\t\t.port_bridge_leave\t= dsa_loop_port_bridge_leave,\n 346:\t\t.port_stp_state_set\t= dsa_loop_port_stp_state_set,\n 347:\t\t.port_vlan_filtering\t= dsa_loop_port_vlan_filtering,\n 348:\t\t.port_vlan_add\t\t= dsa_loop_port_vlan_add,\n 349:\t\t.port_vlan_del\t\t= dsa_loop_port_vlan_del,\n 350:\t\t.port_change_mtu\t= dsa_loop_port_change_mtu,\n 351:\t\t.port_max_mtu\t\t= dsa_loop_port_max_mtu,\n 352:\t\t.phylink_get_caps\t= dsa_loop_phylink_get_caps,\n 353:\t};\n 354:\t\n 355:\tstatic int dsa_loop_drv_probe(struct mdio_device *mdiodev)\n 356:\t{\n 357:\t\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\n 358:\t\tstruct dsa_loop_priv *ps;\n 359:\t\tstruct dsa_switch *ds;\n 360:\t\tint ret;\n 361:\t\n 362:\t\tif (!pdata)\n 363:\t\t\treturn -ENODEV;\n 364:\t\n"
}

29/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58044
							output: 51
							thoughts: 565
						

Thoughts:
**Analyzing the Devlink Firmware Flash Update Patch**

Okay, so I'm diving into this patch related to Devlink firmware flash updates, specifically for the MaxLinear MxL862xx switch driver. My initial thought, looking at the code, is that the `dsa_loop_driver` doesn't implement `devlink_flash_update`, and in fact, no other driver does besides `mxl862xx`, at least not directly. I need to keep that in mind.

Now I'm focusing on `net/dsa/devlink.c`. I see `dsa_devlink_flash_update`, and how it relies on the `ds->ops->devlink_flash_update` pointer. Since `dsa_loop` uses `dsa_devlink_ops`, and that's not implemented for `dsa_loop`, this returns `-EOPNOTSUPP`. That's expected, but I want to make sure I fully understand the implication. Does Syzkaller even enable `dsa_loop` or `CONFIG_NET_DSA`? It's important to know the context of how this code is being tested.

Even if that function call returns -EOPNOTSUPP, I need to evaluate the whole patch. What's the overall picture? I see it's about adding Devlink firmware flash update and rescue mode support to the `mxl862xx` driver, along with a `device_schedule_reprobe()` helper in the driver core and the necessary plumbing in `net/dsa/devlink.c`.

Let's break down each part. First, the `device_schedule_reprobe` helper in `drivers/base/dd.c`. The key here is, is this exported? Yes, it's `EXPORT_SYMBOL_GPL`. And, is it being used anywhere? I see that it's only called by `drivers/net/dsa/mxl862xx/mxl862xx-fw.c`. That's a good place to start, so let's dig into that and make sure this function gets called and does the right thing.

Now, a very important question: can `mxl862xx` probe *without* actual hardware present? This is crucial for testing and development, and might reveal interesting details about what can trigger these flash updates. I need to carefully check the `mxl862xx_probe` function. This will give me a better understanding of the code being added.



30/2 2026/09/28 00:53 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
  "FirstLine": 2255,
  "LineCount": 80
}

Results:
{
  "Contents": "2255:\t};\n2256:\t\n2257:\tstatic int mxl862xx_probe(struct mdio_device *mdiodev)\n2258:\t{\n2259:\t\tstruct device *dev = \u0026mdiodev-\u003edev;\n2260:\t\tstruct mxl862xx_priv *priv;\n2261:\t\tstruct dsa_switch *ds;\n2262:\t\tint err, i;\n2263:\t\n2264:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n2265:\t\tif (!priv)\n2266:\t\t\treturn -ENOMEM;\n2267:\t\n2268:\t\tpriv-\u003emdiodev = mdiodev;\n2269:\t\n2270:\t\tds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);\n2271:\t\tif (!ds)\n2272:\t\t\treturn -ENOMEM;\n2273:\t\n2274:\t\tpriv-\u003eds = ds;\n2275:\t\tds-\u003edev = dev;\n2276:\t\tds-\u003epriv = priv;\n2277:\t\tds-\u003eops = \u0026mxl862xx_switch_ops;\n2278:\t\tds-\u003ephylink_mac_ops = \u0026mxl862xx_phylink_mac_ops;\n2279:\t\tds-\u003enum_ports = MXL862XX_MAX_PORTS;\n2280:\t\tds-\u003eassisted_learning_on_cpu_port = true;\n2281:\t\tds-\u003efdb_isolation = true;\n2282:\t\tds-\u003emax_num_bridges = MXL862XX_MAX_BRIDGES;\n2283:\t\n2284:\t\tmxl862xx_host_init(priv);\n2285:\t\n2286:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++) {\n2287:\t\t\tpriv-\u003eports[i].priv = priv;\n2288:\t\t\tINIT_WORK(\u0026priv-\u003eports[i].host_flood_work,\n2289:\t\t\t\t  mxl862xx_host_flood_work_fn);\n2290:\t\t\tspin_lock_init(\u0026priv-\u003eports[i].stats_lock);\n2291:\t\t}\n2292:\t\n2293:\t\tINIT_DELAYED_WORK(\u0026priv-\u003estats_work, mxl862xx_stats_work_fn);\n2294:\t\n2295:\t\tdev_set_drvdata(dev, ds);\n2296:\t\n2297:\t\terr = dsa_register_switch(ds);\n2298:\t\tif (err) {\n2299:\t\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2300:\t\t\tmxl862xx_host_shutdown(priv);\n2301:\t\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2302:\t\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2303:\t\t}\n2304:\t\n2305:\t\treturn err;\n2306:\t}\n2307:\t\n2308:\tstatic void mxl862xx_remove(struct mdio_device *mdiodev)\n2309:\t{\n2310:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2311:\t\tstruct mxl862xx_priv *priv;\n2312:\t\tint i;\n2313:\t\n2314:\t\tif (!ds)\n2315:\t\t\treturn;\n2316:\t\n2317:\t\tpriv = ds-\u003epriv;\n2318:\t\n2319:\t\t/* Wait out a flash in flight and bar a new one before the DSA core\n2320:\t\t * frees the user netdevs; the devlink instance lock does not cover\n2321:\t\t * that free, which dsa_unregister_switch() reaches first.\n2322:\t\t */\n2323:\t\tmxl862xx_flash_shutdown(ds);\n2324:\t\n2325:\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2326:\t\n2327:\t\tdsa_unregister_switch(ds);\n2328:\t\n2329:\t\tmxl862xx_host_shutdown(priv);\n2330:\t\n2331:\t\t/* Cancel any pending host flood work. dsa_unregister_switch()\n2332:\t\t * has already called port_teardown (which sets setup_done=false),\n2333:\t\t * but a worker could still be blocked on rtnl_lock(). Since we\n2334:\t\t * are now outside RTNL, cancel_work_sync() will not deadlock.\n"
}

31/2 2026/09/28 00:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 60490
							output: 17
							thoughts: 2114
						

Thoughts:
**Reaching a Conclusion on Fuzzing Worthiness**

Okay, here's how I'm thinking through this. I need to figure out if these code changes are worth fuzzing, and I need to be *sure*.

First, I'm looking at `mxl862xx_probe` and its related functions. I see it's all about probing and setting up a MaxLinear switch ASIC. I drill down, and I'm seeing `mxl862xx_rescue_mode_detect` doing SMDIO bus communication to check if it's really the right chip.  That's a hard stop. The `mxl862xx` driver, as the `mxl862xx_of_match` table indicates, is specifically for the MaxLinear MxL862xx family, and I *know* those are Ethernet switch ICs that require physical hardware.  No dice, QEMU and GCE don't emulate those. So, that's a dead end – Structurally unreachable hardware.  It's a definite `WorthFuzzing=false` for *that* code.

But now, I've got to be extra careful. I need to evaluate the *other* changes listed in the instructions.  I'm rereading the rules: "REACHABILITY IS THE PRIMARY GATE". I need to be very strict about what's *actually* going to execute in a standard virtualized environment. Section 2 confirms my suspicion regarding specific hardware. "Unreachable execution paths" and "Structurally unreachable hardware" are my focus.

Alright, let's look at `drivers/base/dd.c`. The changes are the addition of `device_schedule_reprobe` and `device_reprobe_work_fn`. Can these functions be reached by any test in syzkaller?  I'm checking if anything in the entire kernel tree calls `device_schedule_reprobe` using grep.  And the grep results confirm it; the *only* callers I see are within the `drivers/net/dsa/mxl862xx` directory. That's a *major* clue.  `mxl862xx` again!  This function is triggered by the MaxLinear driver, again, a physical hardware dependency.  It is *not* reachable in a standard QEMU setup. `device_schedule_reprobe` has no user-space interface, either! So, the addition to `dd.c` is also `WorthFuzzing=false`.

Now, I move on to `net/dsa/devlink.c` and `include/net/dsa.h`. I see changes related to `dsa_devlink_flash_update`.  It's all about updating the flash memory of the device.  This uses netlink to send a `DEVLINK_CMD_FLASH_UPDATE`. This is potentially reachable via user-space interaction *if* the supporting infrastructure is present.

Now, the crucial question: can I get a DSA switch driver instantiated *without* actual hardware in QEMU? I need a DSA switch driver that can exist in a "pure software" setup. I need to analyze `dsa_loop.c` and determine if there is a way to create a DSA device within a QEMU environment. I inspect `dsa_loop_create_switch_mdiodev`. I see `mdio_find_bus("fixed-0")`. Does "fixed-0" exist by default? Let me grep to find the answer.



32/2 2026/09/28 00:54 tool grepper
0m Args:
{
  "Expression": "fixed-0"
}

Results:
{
  "Output": "arch/arm/boot/dts/samsung/s5pv210-aries.dtsi=57=\tbt_codec: bt-sco {\n--\narch/arm/boot/dts/samsung/s5pv210-aries.dtsi-61-\narch/arm/boot/dts/samsung/s5pv210-aries.dtsi:62:\tvibrator_pwr: regulator-fixed-0 {\narch/arm/boot/dts/samsung/s5pv210-aries.dtsi-63-\t\tcompatible = \"regulator-fixed\";\n--\narch/arm64/boot/dts/renesas/r9a09g056n48-rzv2n-evk.dts=73=\treg_0p8v: regulator-0p8v {\narch/arm64/boot/dts/renesas/r9a09g056n48-rzv2n-evk.dts-74-\t\tcompatible = \"regulator-fixed\";\narch/arm64/boot/dts/renesas/r9a09g056n48-rzv2n-evk.dts:75:\t\tregulator-name = \"fixed-0.8V\";\narch/arm64/boot/dts/renesas/r9a09g056n48-rzv2n-evk.dts-76-\t\tregulator-min-microvolt = \u003c800000\u003e;\n--\narch/arm64/boot/dts/renesas/r9a09g057h44-rzv2h-evk.dts=73=\treg_0p8v: regulator-0p8v {\n--\narch/arm64/boot/dts/renesas/r9a09g057h44-rzv2h-evk.dts-75-\narch/arm64/boot/dts/renesas/r9a09g057h44-rzv2h-evk.dts:76:\t\tregulator-name = \"fixed-0.8V\";\narch/arm64/boot/dts/renesas/r9a09g057h44-rzv2h-evk.dts-77-\t\tregulator-min-microvolt = \u003c800000\u003e;\n--\narch/arm64/boot/dts/renesas/rzg3e-smarc-som.dtsi=71=\treg_vdd0p8v_others: regulator-vdd0p8v-others {\n--\narch/arm64/boot/dts/renesas/rzg3e-smarc-som.dtsi-73-\narch/arm64/boot/dts/renesas/rzg3e-smarc-som.dtsi:74:\t\tregulator-name = \"fixed-0.8V\";\narch/arm64/boot/dts/renesas/rzg3e-smarc-som.dtsi-75-\t\tregulator-min-microvolt = \u003c800000\u003e;\n--\ndrivers/net/dsa/dsa_loop.c=446=static int __init dsa_loop_create_switch_mdiodev(void)\n--\ndrivers/net/dsa/dsa_loop.c-462-\ndrivers/net/dsa/dsa_loop.c:463:\tbus = mdio_find_bus(\"fixed-0\");\ndrivers/net/dsa/dsa_loop.c-464-\tif (WARN_ON(!bus))\n--\ndrivers/net/phy/fixed_phy.c=195=static int __init fixed_mdio_bus_init(void)\n--\ndrivers/net/phy/fixed_phy.c-202-\ndrivers/net/phy/fixed_phy.c:203:\tsnprintf(fmb_mii_bus-\u003eid, MII_BUS_ID_SIZE, \"fixed-0\");\ndrivers/net/phy/fixed_phy.c-204-\tfmb_mii_bus-\u003ename = \"Fixed MDIO Bus\";\n"
}

33/2 2026/09/28 00:54 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64414
							output: 31
							thoughts: 457
						

Thoughts:
**Analyzing the Impact of `dsa_devlink_flash_update` on `dsa_loop`**

Okay, so even if `dsa_loop` *is* registered, what does `dsa_devlink_flash_update` actually *do*? I need to understand its behavior. Let's look at the code: it's a simple wrapper function. It takes a `devlink`, `params`, and `extack`. Crucially, it retrieves the `dsa_switch` struct from the `devlink` and checks for a `devlink_flash_update` function pointer within the `dsa_switch_ops`.

Right, now the critical question: Does `dsa_loop` actually *implement* this `devlink_flash_update`? I know from experience that it shouldn't. Let's confirm. Yes, `dsa_loop_driver` does NOT define a `.devlink_flash_update` within its structure.

Okay, let's step back and see if *any* other DSA switch driver, other than `mxl862xx`, even defines it. Time to grep the kernel source for `.devlink_flash_update`. Hmm, only `mxl862xx` has it. And as I remember, `mxl862xx` is for a physical Ethernet switch ASIC, and it needs MDIO access to an MxL862xx hardware chip.

So, what happens if we *do* try to execute `dsa_devlink_flash_update` on `dsa_loop`?  It's a simple check. If the function pointer in `ds->ops` is NULL, it returns `-EOPNOTSUPP`. That's it! It's just a 3-line wrapper, and it will return a negative error code.

Now I need to ensure that this isn't even possible on a standard system. Is `dsa_loop` even enabled in, say, syzkaller or standard kernels? Let me quickly check the defconfigs and the Kconfig for `NET_DSA_LOOP`. Let's see...



34/2 2026/09/28 00:54 tool grepper
0m Args:
{
  "Expression": "NET_DSA_LOOP",
  "PathPrefix": "drivers/net/dsa"
}

Results:
{
  "Output": "drivers/net/dsa/Kconfig=7=config NET_DSA_BCM_SF2\n--\ndrivers/net/dsa/Kconfig-18-\ndrivers/net/dsa/Kconfig:19:config NET_DSA_LOOP\ndrivers/net/dsa/Kconfig-20-\ttristate \"DSA mock-up Ethernet switch chip support\"\n--\ndrivers/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_MT7530)\t+= mt7530.o\n"
}

35/2 2026/09/28 00:54 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65567
							output: 221
							thoughts: 3311
						

Thoughts:
**Analysis of the Proposed Patch: WorthFuzzing Evaluation**

Alright, let's break down this patch and determine if it's "WorthFuzzing." My initial thought is that we're dealing with code that's not easily reachable in a standard fuzzing environment. The introduction of `dsa_loop` and the attempt to integrate it seem like a red herring; the crux of the issue lies in `mxl862xx`.

The code introduces functionality related to `mxl862xx`, a driver for MaxLinear MxL862xx switches. These switches are *physical ASICs*, not virtual devices. The code specifically calls the hardware over MDIO interfaces and relies on register reads and writes. I'm already seeing the issue here: standard QEMU setups won't emulate these registers and interactions properly.

The `device_schedule_reprobe` function, while interesting, is the key indicator: it's *only* called within the context of `mxl862xx`, specifically for firmware update and rescue operations related to the physical hardware. There's no other call to it in the kernel.

The `dsa_devlink_flash_update` functionality in `net/dsa/devlink.c` is just a forwarder. It checks if the underlying DSA driver (e.g., `mxl862xx`) implements the flash update operation. If not, it just returns -EOPNOTSUPP. If it *is* implemented, it calls the `mxl862xx` function. Even if you try to trigger this through netlink commands, you're still hitting the hardware requirement.

The primary criterion for "WorthFuzzing" is reachability in standard environments. This patch clearly fails. It's working with hardware registers and requiring a *physical* device. The only way you could even begin to approach it is with specialized hardware setups, which is outside the scope of normal QEMU/KVM fuzzing.

Therefore, because all the significant code modifications (including `device_schedule_reprobe` and `dsa_devlink_flash_update`) are tied to physical hardware that isn't emulated in standard fuzzing environments, this patch is *not* worth fuzzing. It's structurally unreachable in the target environment.



36/2 2026/09/28 00:54 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series adds devlink firmware flashing, rescue mode support, and firmware version reporting for the MaxLinear MxL862xx Ethernet switch driver (drivers/net/dsa/mxl862xx/), introduces a deferred reprobe helper device_schedule_reprobe() in drivers/base/dd.c, and plumbs devlink_flash_update into DSA's devlink operations (net/dsa/devlink.c). The MxL862xx is a physical Ethernet switch ASIC requiring dedicated MDIO bus hardware, which is not present or emulated in standard virtualized fuzzing environments (QEMU/GCE). Furthermore, device_schedule_reprobe() is solely called by the unreachable mxl862xx driver, and mxl862xx is the only DSA driver that implements .devlink_flash_update. Therefore, the modified code is structurally unreachable.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series adds devlink firmware flashing, rescue mode support, and firmware version reporting for the MaxLinear MxL862xx Ethernet switch driver (drivers/net/dsa/mxl862xx/), introduces a deferred reprobe helper device_schedule_reprobe() in drivers/base/dd.c, and plumbs devlink_flash_update into DSA's devlink operations (net/dsa/devlink.c). The MxL862xx is a physical Ethernet switch ASIC requiring dedicated MDIO bus hardware, which is not present or emulated in standard virtualized fuzzing environments (QEMU/GCE). Furthermore, device_schedule_reprobe() is solely called by the unreachable mxl862xx driver, and mxl862xx is the only DSA driver that implements .devlink_flash_update. Therefore, the modified code is structurally unreachable.",
  "WorthFuzzing": false
}

37/1 2026/09/28 00:54 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)