| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/26 11:52 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flash support for the MaxLinear MxL862xx DSA switch via devlink. The mxl862xx driver is a vendor-specific hardware driver that communicates with the physical switch chip over an MDIO bus. During probe, it performs a hardware presence check (mxl862xx_rescue_mode_detect) that requires the physical chip to latch and return specific values. Without the physical hardware (which is not emulated in standard QEMU), the driver fails to probe, making the new devlink flash update code unreachable in a virtualized environment. The generic DSA devlink plumbing changes only affect drivers implementing the new op, which is currently only mxl862xx.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/26 11:52 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 7936df7af708975c9d83ee296bf3614dc8623166\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Jul 26 11:52:36 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 4745148fecf4fe..058999d0dc56f2 100644\n--- a/Documentation/networking/devlink/index.rst\n+++ b/Documentation/networking/devlink/index.rst\n@@ -94,6 +94,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 00000000000000..b3e9f7d4d4969e\n--- /dev/null\n+++ b/Documentation/networking/devlink/mxl862xx.rst\n@@ -0,0 +1,63 @@\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. Not\n+ reported for a switch sitting in MCUboot rescue mode as the\n+ registers are only accessible with a running firmware.\n+ * - ``asic.rev``\n+ - fixed\n+ - 0\n+ - The chip version read from the CHIP ID registers. Not reported\n+ in MCUboot rescue mode either.\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+ In MCUboot rescue mode nothing is reported while an interrupted\n+ download is still being recovered in the background; once the\n+ loader is ready to accept a new image the version is reported (as\n+ both running and stored), which is the signal that a flash will be\n+ accepted. It reads \"0.0.0\" when the switch came up straight into\n+ MCUboot without ever running firmware.\n+\n+Flash update\n+============\n+\n+The ``mxl862xx`` driver implements support for ``devlink dev flash``.\n+The signed firmware image is transferred to the switch over the same\n+MDIO bus which is also used to manage the switch, then verified and\n+installed by the MCUboot bootloader running on the switch. All ports\n+of the switch are closed for the duration of the update and the driver\n+reprobes the switch after it has rebooted into the new firmware. A\n+complete flash and reprobe cycle takes about one minute.\n+\n+A switch stuck in MCUboot rescue mode, e.g. after an interrupted\n+update, is registered without user ports. If the previous download was\n+interrupted mid-transfer the loader is wedged; the driver drains it\n+back to a clean ready state in the background, which can easily take\n+more than 10 minutes. During that recovery ``devlink dev info`` reports\n+no firmware version and ``devlink dev flash`` returns ``-EBUSY``.\n+Once the loader is ready the firmware version appears and flashing a\n+firmware image through the regular update flow recovers the switch.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 60cff00953dcbe..3a55ed2bad9cba 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -16107,6 +16107,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/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile\nindex a7be0e6669dfa5..bccac0d0f703f6 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 a180a5decffc01..6f771895984cb3 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 c87a955c13c48e..a865425aa61e1f 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 00000000000000..d28ceb98cd564e\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\n@@ -0,0 +1,884 @@\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 ; address/page latch\n+ * MII reg 0x00 := / =\u003e \u003cu16 data\u003e ; data window\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 end of transfer / finalise\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: reset; 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+ * STAT := END(0x3cc3) ---------------------\u003e finalise\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, validates, and - if it was in rescue - clears its\n+ * rescue-enable bit so boot_go boots the new image, ONLY when r_remain hits\n+ * EXACTLY 0. A count larger than r_remain underflows the 32-bit counter and\n+ * wedges the loader until a power cycle. Hence:\n+ * - never send a slice/chunk count larger than what is outstanding;\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 spinning in the slice loop\n+ * holding STAT=0 (no magic). Feed single 1-byte chunks (one DATA word +\n+ * STAT=1) until r_remain reaches 0, then STAT=END; the loader finalises the\n+ * (now corrupt) image and re-arms READY for a clean reflash.\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 wedged\n+ * loader is drained back to READY by a background self-heal (rescue_heal_work) so\n+ * the multi-minute recovery never holds the devlink lock. devlink dev info\n+ * exposes the fw version (the \"flashable\" signal) only once at READY;\n+ * flash_update returns -EBUSY until then, 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 and the 0xe1xx offsets are OTP-configurable; derive\n+ * them from the DT binding, do not assume fixed values.\n+ */\n+\n+#include \u003clinux/crc32.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/overflow.h\u003e\n+#include \u003clinux/rtnetlink.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003cnet/dsa.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+\n+#include \"mxl862xx.h\"\n+#include \"mxl862xx-api.h\"\n+#include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n+#include \"mxl862xx-host.h\"\n+\n+/* SB PDI registers (clause-22 SMDIO address space) */\n+#define MXL862XX_SB_PDI_CTRL\t\t0xe100\n+#define MXL862XX_SB_PDI_ADDR\t\t0xe101\n+#define MXL862XX_SB_PDI_DATA\t\t0xe102\n+#define MXL862XX_SB_PDI_STAT\t\t0xe103\n+\n+/* SB PDI CTRL modes */\n+#define MXL862XX_SB_PDI_CTRL_RST\t0x00\n+#define MXL862XX_SB_PDI_CTRL_WR\t\t0x02\n+\n+/* SB PDI handshake magic (published/consumed via STAT) */\n+#define MXL862XX_SB_PDI_READY\t\t0xc55c\t/* loader idle, console loop */\n+#define MXL862XX_SB_PDI_DL_READY\t0xc33c\t/* loader idle, flashless loop */\n+#define MXL862XX_SB_PDI_START\t\t0xf48f\n+#define MXL862XX_SB_PDI_END\t\t0x3cc3\n+#define MXL862XX_SB_PDI_RDREG\t\t0xe2c0\t/* register-read cmd (| index) */\n+#define MXL862XX_SB_PDI_RDREG_MARK\t0x7c23\t/* marker placed in DATA for RDREG */\n+\n+/* Behavioural presence probe: two distinct 16-bit latches on ADDR/DATA. */\n+#define MXL862XX_SB_PDI_PROBE_A\t\t0x5a5a\n+#define MXL862XX_SB_PDI_PROBE_D\t\t0xa5a5\n+\n+/* 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\t120000\n+#define MXL862XX_FW_REBOOT_DELAY_MS\t5000\n+#define MXL862XX_FW_REPROBE_DELAY_MS\t500\n+#define MXL862XX_RESCUE_READY_TIMEOUT_MS 1000\n+\n+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)\n+{\n+\tint ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_RST);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_RST);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_RST);\n+}\n+\n+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,\n+\t\t\t\t unsigned long timeout_ms)\n+{\n+\tunsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);\n+\tint ret;\n+\n+\tdo {\n+\t\tret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif ((u16)ret == expected)\n+\t\t\treturn 0;\n+\t\tusleep_range(10000, 11000);\n+\t} while (time_before(jiffies, timeout));\n+\n+\treturn -ETIMEDOUT;\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+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+/* Poll STAT while draining a stuck download: 0 means \"feed the next chunk\",\n+ * READY means the loader left the receive loop and re-armed its command loop,\n+ * anything else is a transient (the count being consumed) - BUSY past the\n+ * window means the counter has hit zero and the loader is finalising.\n+ */\n+enum { MXL862XX_DRAIN_FEED, MXL862XX_DRAIN_READY, MXL862XX_DRAIN_BUSY };\n+static int mxl862xx_sb_pdi_poll_drain(struct mxl862xx_priv *priv,\n+\t\t\t\t unsigned long timeout_ms)\n+{\n+\tunsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);\n+\tint ret;\n+\n+\tdo {\n+\t\tret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif ((u16)ret == MXL862XX_SB_PDI_READY)\n+\t\t\treturn MXL862XX_DRAIN_READY;\n+\t\tif ((u16)ret == 0)\n+\t\t\treturn MXL862XX_DRAIN_FEED;\n+\t\tusleep_range(50, 150);\n+\t} while (time_before(jiffies, timeout));\n+\n+\treturn MXL862XX_DRAIN_BUSY;\n+}\n+\n+/* Recover a switch whose SB PDI download was interrupted mid-transfer - the\n+ * host died after MCUboot began erasing flash, whether it aborted mid erase or\n+ * mid image-write, both end up in the same place: the payload receive loop.\n+ * There the loader publishes STAT=0, waits for the host to write a byte-count\n+ * to STAT, DMAs that many bytes and subtracts the count from a remaining-bytes\n+ * counter, leaving the loop only when the counter reaches exactly zero. The\n+ * image size died with the host, so we feed single-byte chunks (see\n+ * MXL862XX_DRAIN_CHUNK_BYTES) to walk the counter to zero without underflow,\n+ * then send END. The loader runs its (failed) checksum and either re-arms\n+ * READY or, if a valid image survived in flash, boots it. Returns 0 when READY\n+ * is reached (the caller can flash), 1 when the loader booted instead (the\n+ * switch is recovered but the intended image was not written), \u003c0 on error.\n+ * Does NOT recover a counter already underflowed by an earlier oversized-chunk\n+ * attempt - that needs a power cycle.\n+ */\n+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)\n+{\n+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n+\t/* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */\n+\tu32 max_chunks = 2u * (16u \u003c\u003c 20) / MXL862XX_DRAIN_CHUNK_BYTES;\n+\tu32 chunk = 0;\n+\tint ret;\n+\n+\tdev_warn(dev, \"flash: draining interrupted download\\n\");\n+\n+\twhile (chunk \u003c max_chunks) {\n+\t\t/* Teardown can interrupt this minutes-long drain. */\n+\t\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\t\treturn -ECANCELED;\n+\n+\t\tret = mxl862xx_sb_pdi_poll_drain(priv, 2000);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif (ret == MXL862XX_DRAIN_READY)\n+\t\t\treturn 0;\n+\t\tif (ret == MXL862XX_DRAIN_BUSY)\n+\t\t\tbreak;\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\tcond_resched();\n+\t}\n+\n+\tif (chunk \u003e= max_chunks) {\n+\t\tdev_err(dev,\n+\t\t\t\"flash: interrupted download did not drain after %u chunks\\n\",\n+\t\t\tchunk);\n+\t\treturn -ETIMEDOUT;\n+\t}\n+\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\treturn ret;\n+\n+\treturn mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\t MXL862XX_FW_ACK_TIMEOUT_MS);\n+}\n+\n+/* Background self-heal: drain a wedged download back to READY off the devlink\n+ * flash path, so the minutes-long recovery never holds the devlink lock.\n+ * Scheduled from probe; publishes rescue_ready on success.\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+\tdev_info(dev, \"recovering interrupted download in background\\n\");\n+\tret = mxl862xx_rescue_drain(priv);\n+\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\treturn;\n+\tif (!ret) {\n+\t\tWRITE_ONCE(priv-\u003erescue_ready, true);\n+\t\tdev_info(dev, \"recovery complete, ready to flash\\n\");\n+\t}\n+}\n+\n+/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller can rule\n+ * the loader out before any C45 API request (which spews CRC errors when no WSP\n+ * firmware answers). A scratch write to ADDR/DATA must latch or the chip is\n+ * absent (-ENODEV); STAT then classifies the state, poked destructively only\n+ * when 0, the one value a running firmware never holds:\n+ *\n+ * - 0xc33c: flashless loop, ready.\n+ * - 0xc55c: console loop, if the register-read challenge is serviced.\n+ * - other non-zero: running firmware, left unpoked.\n+ * - 0: wedged receive loop, if a 1-byte slice-advance drains back to 0.\n+ *\n+ * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, or negative (-ENODEV/SMDIO).\n+ */\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)\n+{\n+\tint stat, dat, ret, rb, a, d;\n+\n+\t/* rescue_ready gates flashing; a wedged loader needs the drain first. */\n+\tpriv-\u003erescue_ready = false;\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: unambiguous ready magic, leave it alone. */\n+\tif ((u16)stat == MXL862XX_SB_PDI_DL_READY) {\n+\t\tpriv-\u003erescue_ready = true;\n+\t\treturn MXL862XX_IN_RESCUE;\n+\t}\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_RESCUE_READY_TIMEOUT_MS);\n+\t\tdat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);\n+\t\tif (dat \u003c 0)\n+\t\t\treturn dat;\n+\t\tmxl862xx_sb_pdi_reset(priv);\n+\t\tif (!rb \u0026\u0026 (u16)dat != MXL862XX_SB_PDI_RDREG_MARK) {\n+\t\t\tpriv-\u003erescue_ready = true;\n+\t\t\treturn MXL862XX_IN_RESCUE;\n+\t\t}\n+\t\treturn -ENODEV;\n+\t}\n+\n+\t/* Any other non-zero value is a running firmware, not a loader. */\n+\tif (stat)\n+\t\treturn MXL862XX_NOT_RESCUE;\n+\n+\t/* STAT == 0: a wedged receive loop consumes a 1-byte slice-advance back\n+\t * to 0 (feed one DATA word first, like a drain chunk).\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, 1);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\trb = mxl862xx_sb_pdi_poll_stat(priv, 0, MXL862XX_RESCUE_READY_TIMEOUT_MS);\n+\tif (!rb)\n+\t\treturn MXL862XX_IN_RESCUE;\n+\n+\treturn -ENODEV;\n+}\n+\n+/* Post-flash reprobe. Runs from a self-contained heap work (not a kthread and\n+ * not the devlink caller's context): kthread_create() in the caller context\n+ * fails -EINTR if the devlink command was Ctrl-C'd, and schedule_work() cannot.\n+ * device_reprobe() -\u003e remove() frees priv, so this must not touch priv; it uses\n+ * only the held device reference and drops its module reference afterwards.\n+ */\n+struct mxl862xx_reprobe_work {\n+\tstruct work_struct work;\n+\tstruct device *dev;\n+};\n+\n+static void mxl862xx_reprobe_work_fn(struct work_struct *work)\n+{\n+\tstruct mxl862xx_reprobe_work *rw =\n+\t\tcontainer_of(work, struct mxl862xx_reprobe_work, work);\n+\tstruct device *dev = rw-\u003edev;\n+\n+\tmsleep(MXL862XX_FW_REPROBE_DELAY_MS);\n+\tif (device_reprobe(dev))\n+\t\tdev_err(dev, \"reprobe failed\\n\");\n+\tput_device(dev);\n+\tkfree(rw);\n+\tmodule_put(THIS_MODULE);\n+}\n+\n+/* MCUboot firmware image header */\n+struct mxl862xx_fw_hdr {\n+\t__le32 image_type;\n+\t__le32 image_size_1;\n+\t__le32 image_checksum_1;\n+\t__le32 image_size_2;\n+\t__le32 image_checksum_2;\n+} __packed;\n+\n+static int mxl862xx_flash_validate(struct mxl862xx_priv *priv,\n+\t\t\t\t const struct firmware *fw,\n+\t\t\t\t u32 *payload_size)\n+{\n+\tconst struct mxl862xx_fw_hdr *hdr;\n+\tu32 size1, size2, total;\n+\tconst u8 *payload;\n+\tu32 crc;\n+\n+\tif (fw-\u003esize \u003c MXL862XX_FW_HDR_SIZE)\n+\t\treturn -EINVAL;\n+\n+\thdr = (const struct mxl862xx_fw_hdr *)fw-\u003edata;\n+\tpayload = fw-\u003edata + MXL862XX_FW_HDR_SIZE;\n+\tsize1 = le32_to_cpu(hdr-\u003eimage_size_1);\n+\tsize2 = le32_to_cpu(hdr-\u003eimage_size_2);\n+\n+\tif (check_add_overflow(size1, size2, \u0026total) ||\n+\t total \u003e fw-\u003esize - MXL862XX_FW_HDR_SIZE) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: firmware file too small for declared size\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!total) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: firmware file with empty payload\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (size1) {\n+\t\tcrc = ~crc32_le(~0U, payload, size1);\n+\t\tif (crc != le32_to_cpu(hdr-\u003eimage_checksum_1)) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: image 1 CRC mismatch (got %08x, expected %08x)\\n\",\n+\t\t\t\tcrc, le32_to_cpu(hdr-\u003eimage_checksum_1));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (size2) {\n+\t\tcrc = ~crc32_le(~0U, payload + size1, size2);\n+\t\tif (crc != le32_to_cpu(hdr-\u003eimage_checksum_2)) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: image 2 CRC mismatch (got %08x, expected %08x)\\n\",\n+\t\t\t\tcrc, le32_to_cpu(hdr-\u003eimage_checksum_2));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\t*payload_size = total;\n+\n+\treturn 0;\n+}\n+\n+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,\n+\t\t\t\t const struct firmware *fw,\n+\t\t\t\t u32 payload_size, struct devlink *dl)\n+{\n+\tconst u8 *payload = fw-\u003edata + MXL862XX_FW_HDR_SIZE;\n+\tu32 word_idx = 0, data_written = 0, idx = 0;\n+\tunsigned long next_notify = jiffies - 1;\n+\tu16 word, fdata;\n+\tint ret, i;\n+\n+\t/* Step 1: reboot the firmware into MCUboot rescue mode */\n+\tif (!priv-\u003erescue_mode) {\n+\t\tret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,\n+\t\t\t\t\tfalse, false);\n+\t\tif (ret) {\n+\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: FW_UPDATE command failed: %pe\\n\",\n+\t\t\t\tERR_PTR(ret));\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\t/* Step 2: wait for bootloader ready */\n+\tmxl862xx_flash_notify(dl, \"Waiting for bootloader\", 0, 0);\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* Failures from here on must go through end_magic so MCUboot\n+\t * reboots instead of waiting forever.\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\treturn ret;\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\treturn ret;\n+\t}\n+\n+\t/* Step 4: transfer image header */\n+\tmxl862xx_flash_notify(dl, \"Erasing flash\", 0, 0);\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\tfor (i = 0; i \u003c MXL862XX_FW_HDR_SIZE / 2; i++) {\n+\t\tword = fw-\u003edata[i * 2] |\n+\t\t ((u16)fw-\u003edata[i * 2 + 1] \u003c\u003c 8);\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto write_err;\n+\t}\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* the byte count in STAT triggers the erase */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t MXL862XX_FW_HDR_SIZE);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* ACK is byte count + 1 */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: header ACK failed: %pe\\n\", ERR_PTR(ret));\n+\t\treturn ret;\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\treturn ret;\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\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_slice(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\treturn ret;\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 write timeout: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\tmxl862xx_flash_notify(dl, \"Flashing\", payload_size, payload_size);\n+\tgoto end_magic;\n+\n+write_err:\n+\tdev_err(\u0026priv-\u003emdiodev-\u003edev, \"flash: SMDIO write failed: %pe\\n\",\n+\t\tERR_PTR(ret));\n+end_magic:\n+\t/* reboot MCUboot even after a failed transfer */\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t MXL862XX_SB_PDI_END);\n+\tmsleep(MXL862XX_FW_REBOOT_DELAY_MS);\n+\n+\treturn ret;\n+}\n+\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t struct devlink_info_req *req,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\tchar buf[16];\n+\tint ret;\n+\n+\t/* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The\n+\t * fw version doubles as the \"ready to flash\" signal: report it only\n+\t * once the loader is at a clean READY, nothing while still draining.\n+\t */\n+\tif (priv-\u003erescue_mode) {\n+\t\tif (!READ_ONCE(priv-\u003erescue_ready))\n+\t\t\treturn 0;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"%u.%u.%u\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\t\tret = devlink_info_version_running_put(req, \"fw\", buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\treturn devlink_info_version_stored_put(req, \"fw\", buf);\n+\t}\n+\n+\tsnprintf(buf, sizeof(buf), \"%04X\", priv-\u003easic_id);\n+\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,\n+\t\t\t\t\t buf);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tsnprintf(buf, sizeof(buf), \"%u\", priv-\u003easic_rev);\n+\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,\n+\t\t\t\t\t buf);\n+\tif (ret)\n+\t\treturn ret;\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, \"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, \"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 mxl862xx_reprobe_work *rw;\n+\tstruct dsa_port *dp;\n+\tu32 payload_size;\n+\tint ret, 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+\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. */\n+\tif (priv-\u003erescue_mode \u0026\u0026 !READ_ONCE(priv-\u003erescue_ready)) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t \"switch is recovering an interrupted download, retry shortly\");\n+\t\treturn -EBUSY;\n+\t}\n+\n+\t/* The references the reprobe work needs to restore normal operation\n+\t * must be held before the switch is disturbed; the work itself is\n+\t * scheduled only once the flash is done (see below).\n+\t */\n+\tif (!try_module_get(THIS_MODULE))\n+\t\treturn -ENODEV;\n+\n+\tget_device(ds-\u003edev);\n+\n+\tif (priv-\u003erescue_mode)\n+\t\tdev_info(ds-\u003edev,\n+\t\t\t \"flash: flashing switch via MCUboot rescue mode\\n\");\n+\telse\n+\t\tdev_info(ds-\u003edev, \"flash: running firmware %u.%u.%u\\n\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\n+\t/* Close ports while the firmware is still alive so the DSA\n+\t * core's MDB/FDB tracking is drained, and detach user ports\n+\t * so userspace cannot reopen them during the flash. The\n+\t * conduit belongs to the MAC driver and is only closed.\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+\tcancel_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\tpriv-\u003erescue_mode = false;\n+\t\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\t\t/* The reprobe below re-reads and logs the new version. */\n+\t}\n+\n+\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tpriv-\u003eskip_teardown = true;\n+\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\t/* Hand the reprobe to a self-contained work, scheduled last. Doing it\n+\t * from a work (rather than a kthread spawned here) avoids both the\n+\t * hung-task splat of a thread parked across the flash and the -EINTR\n+\t * that kthread_create() returns if the devlink command was Ctrl-C'd.\n+\t * Its module and device references are already held above.\n+\t */\n+\trw = kzalloc_obj(*rw);\n+\tif (!rw) {\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"flash: out of memory for reprobe; reload the driver to restore operation\\n\");\n+\t\tput_device(ds-\u003edev);\n+\t\tmodule_put(THIS_MODULE);\n+\t\treturn ret ? ret : -ENOMEM;\n+\t}\n+\tINIT_WORK(\u0026rw-\u003ework, mxl862xx_reprobe_work_fn);\n+\trw-\u003edev = ds-\u003edev;\n+\tschedule_work(\u0026rw-\u003ework);\n+\n+\treturn ret;\n+}\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\nnew file mode 100644\nindex 00000000000000..7cd87c7ad87167\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\n@@ -0,0 +1,20 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+\n+#ifndef __MXL862XX_FW_H\n+#define __MXL862XX_FW_H\n+\n+#include \u003cnet/dsa.h\u003e\n+\n+struct mxl862xx_priv;\n+struct work_struct;\n+\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);\n+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t struct devlink_info_req *req,\n+\t\t\t struct netlink_ext_ack *extack);\n+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n+\t\t\t\t struct devlink_flash_update_params *params,\n+\t\t\t\t struct netlink_ext_ack *extack);\n+\n+#endif /* __MXL862XX_FW_H */\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\nindex 4acd216f7cc009..2dbd074c0fe26c 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n@@ -15,6 +15,8 @@\n #include \u003clinux/unaligned.h\u003e\n #include \u003cnet/dsa.h\u003e\n #include \"mxl862xx.h\"\n+#include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n #include \"mxl862xx-host.h\"\n \n #define CTRL_BUSY_MASK\t\t\tBIT(15)\n@@ -340,6 +342,21 @@ 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+\tif (priv-\u003eblock_host \u0026\u0026 cmd != SYS_MISC_FW_UPDATE) {\n+\t\tret = -EBUSY;\n+\t\tgoto out;\n+\t}\n+\n \tmax = (size + 1) / 2;\n \n \tret = mxl862xx_busy_wait(priv);\n@@ -495,12 +512,49 @@ 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+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 66d6ae198aff45..4e054c6e4c0e40 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 b689652aa9b920..a5b6940b552e9e 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,\n \n \tswitch (port) {\n \tcase 9 ... 16:\n+\t\tif (priv-\u003erescue_mode)\n+\t\t\treturn NULL;\n \t\tif (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {\n \t\t\tdev_warn_once(dp-\u003eds-\u003edev,\n \t\t\t\t \"SerDes PCS unsupported on old firmware.\\n\");\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c\nindex 45d237b3a40f10..9ac668c13c9e70 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,6 +230,35 @@ 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+/* Read the static chip part number and version from the CHIP ID\n+ * registers. Only possible with a running firmware, so the values are\n+ * cached at setup and left zero when the switch is in rescue mode.\n+ */\n+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)\n+{\n+\tstruct mxl862xx_sys_reg_rw reg = {};\n+\tu16 chipid_l, chipid_m;\n+\tint ret;\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_L);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_l = le32_to_cpu(reg.val);\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_M);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_m = le32_to_cpu(reg.val);\n+\n+\tpriv-\u003easic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |\n+\t\t\tFIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) \u003c\u003c 4;\n+\tpriv-\u003easic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);\n+\n+\treturn 0;\n+}\n+\n static int mxl862xx_wait_ready(struct dsa_switch *ds)\n {\n \tstruct mxl862xx_sys_fw_image_version ver = {};\n@@ -254,6 +291,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)\n \t\tpriv-\u003efw_version.major = ver.iv_major;\n \t\tpriv-\u003efw_version.minor = ver.iv_minor;\n \t\tpriv-\u003efw_version.revision = le16_to_cpu(ver.iv_revision);\n+\n+\t\tret = mxl862xx_read_chip_id(priv);\n+\t\tif (ret)\n+\t\t\tdev_warn(ds-\u003edev, \"failed to read chip ID: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n \t\treturn 0;\n \n not_ready_yet:\n@@ -622,15 +664,49 @@ static int mxl862xx_setup(struct dsa_switch *ds)\n \tint n_user_ports = 0, max_vlans;\n \tint ingress_finals, vid_rules;\n \tstruct dsa_port *dp;\n-\tint ret, i;\n+\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 spews CRC errors when none\n+\t * answers. Touch C45 only once rescue is ruled out.\n+\t */\n+\trescue = mxl862xx_rescue_mode_detect(priv);\n+\tif (rescue \u003c 0)\n+\t\treturn rescue;\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);\n+\t\t\tif (rescue \u003c 0)\n+\t\t\t\treturn rescue;\n+\t\t\tif (rescue == MXL862XX_NOT_RESCUE)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tpriv-\u003erescue_mode = rescue;\n+\n+\tif (priv-\u003erescue_mode) {\n+\t\tif (priv-\u003erescue_ready) {\n+\t\t\tdev_warn(ds-\u003edev,\n+\t\t\t\t \"switch in MCUboot rescue mode, use devlink to flash new firmware\\n\");\n+\t\t} else {\n+\t\t\t/* Drain the wedged download in the background so it\n+\t\t\t * never holds the devlink lock; info and flash become\n+\t\t\t * available once ready.\n+\t\t\t */\n+\t\t\tdev_warn(ds-\u003edev,\n+\t\t\t\t \"switch in MCUboot with an interrupted download, recovering in background\\n\");\n+\t\t\tqueue_work(system_long_wq, \u0026priv-\u003erescue_heal_work);\n+\t\t}\n+\t\treturn 0;\n+\t}\n \n \tmutex_init(\u0026priv-\u003eserdes_lock);\n \tfor (i = 0; i \u003c ARRAY_SIZE(priv-\u003eserdes_ports); i++)\n@@ -715,11 +791,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)\n static int mxl862xx_port_enable(struct dsa_switch *ds, int port,\n \t\t\t\tstruct phy_device *phydev)\n {\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (priv-\u003erescue_mode)\n+\t\treturn 0;\n+\n \treturn mxl862xx_port_state(ds, port, true);\n }\n \n static void mxl862xx_port_disable(struct dsa_switch *ds, int port)\n {\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\n+\tif (priv-\u003erescue_mode)\n+\t\treturn;\n+\n \tif (mxl862xx_port_state(ds, port, false))\n \t\tdev_err(ds-\u003edev, \"failed to disable port %d\\n\", port);\n }\n@@ -1337,6 +1423,12 @@ 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+\t/* DSA reinits failed user ports as unused; shared ports must\n+\t * succeed for the tree to register.\n+\t */\n+\tif (priv-\u003erescue_mode)\n+\t\treturn dsa_port_is_user(dp) ? -ENODEV : 0;\n+\n \tret = mxl862xx_port_state(ds, port, false);\n \tif (ret)\n \t\treturn ret;\n@@ -1572,6 +1664,11 @@ 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/* -ENODEV: the firmware and its MAC table are gone, nothing left\n+\t * to delete\n+\t */\n+\tif (ret == -ENODEV)\n+\t\treturn 0;\n \tif (ret)\n \t\treturn ret;\n \n@@ -1628,6 +1725,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,\n \tstruct mxl862xx_priv *priv = ds-\u003epriv;\n \tint ret;\n \n+\tif (priv-\u003erescue_mode)\n+\t\treturn;\n+\n \tswitch (state) {\n \tcase BR_STATE_DISABLED:\n \t\tparam.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);\n@@ -2086,6 +2186,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {\n \t.get_pause_stats = mxl862xx_get_pause_stats,\n \t.get_rmon_stats = mxl862xx_get_rmon_stats,\n \t.get_stats64 = mxl862xx_get_stats64,\n+\t.devlink_info_get = mxl862xx_devlink_info_get,\n+\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n };\n \n static int mxl862xx_probe(struct mdio_device *mdiodev)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h\nindex 432a5f3f2e08e6..72dc31cc2b62fa 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h\n@@ -14,6 +14,10 @@ struct mxl862xx_priv;\n #define MXL862XX_FIRST_SERDES_PORT\t9\n #define MXL862XX_SERDES_SLOTS\t\t4\n \n+/* mxl862xx_rescue_mode_detect() return codes (negative values are errors) */\n+#define MXL862XX_NOT_RESCUE\t\t0\n+#define MXL862XX_IN_RESCUE\t\t1\n+\n #define MXL862XX_DEFAULT_BRIDGE\t\t0\n #define MXL862XX_MAX_BRIDGES\t\t48\n #define MXL862XX_MAX_BRIDGE_PORTS\t128\n@@ -303,6 +307,10 @@ struct mxl862xx_fw_version {\n * flooding)\n * @fw_version: cached firmware version, populated at probe and\n * compared with MXL862XX_FW_VER_MIN()\n+ * @asic_id: chip part number read from the CHIP ID registers,\n+ * reported as the devlink \"asic.id\" fixed version\n+ * @asic_rev: chip version read from the CHIP ID registers,\n+ * reported as the devlink \"asic.rev\" fixed version\n * @serdes_ports: SerDes interfaces incl. sub-interfaces in case of\n * 10G_QXGMII or QSGMII\n * @serdes_refcount: per-XPCS count of sub-ports enabled by phylink;\n@@ -319,6 +327,15 @@ struct mxl862xx_fw_version {\n * @evlan_ingress_size: per-port ingress Extended VLAN block size\n * @evlan_egress_size: per-port egress Extended VLAN block size\n * @vf_block_size: per-port VLAN Filter block size\n+ * @block_host: reject firmware API commands (except FW_UPDATE)\n+ * during a firmware flash\n+ * @skip_teardown: discard firmware API commands during the teardown\n+ * triggered by the post-flash reprobe\n+ * @rescue_mode: switch is in MCUboot; firmware API commands fail fast,\n+ * only clause-22 SMDIO works\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_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 +343,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,6 +357,10 @@ struct mxl862xx_priv {\n \tu16 evlan_ingress_size;\n \tu16 evlan_egress_size;\n \tu16 vf_block_size;\n+\tbool block_host;\n+\tbool skip_teardown;\n+\tbool rescue_mode;\n+\tbool rescue_ready;\n \tstruct delayed_work stats_work;\n };\n \ndiff --git a/include/net/dsa.h b/include/net/dsa.h\nindex 8c16ef23cc102a..c9e19348de6193 100644\n--- a/include/net/dsa.h\n+++ b/include/net/dsa.h\n@@ -1170,6 +1170,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 ed342f345692bb..d6022267a839cd 100644\n--- a/net/dsa/devlink.c\n+++ b/net/dsa/devlink.c\n@@ -20,6 +20,15 @@ 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+\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@@ -167,18 +176,31 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,\n \t\t\t\t\t\t\tp_max);\n }\n \n-static const struct devlink_ops dsa_devlink_ops = {\n-\t.info_get\t\t\t= dsa_devlink_info_get,\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-\t.sb_port_pool_set\t\t= dsa_devlink_sb_port_pool_set,\n-\t.sb_tc_pool_bind_get\t\t= dsa_devlink_sb_tc_pool_bind_get,\n-\t.sb_tc_pool_bind_set\t\t= dsa_devlink_sb_tc_pool_bind_set,\n-\t.sb_occ_snapshot\t\t= dsa_devlink_sb_occ_snapshot,\n-\t.sb_occ_max_clear\t\t= dsa_devlink_sb_occ_max_clear,\n-\t.sb_occ_port_pool_get\t\t= dsa_devlink_sb_occ_port_pool_get,\n+/* The devlink core rejects flash requests up front when the flash_update\n+ * op is absent, before fetching the firmware file from userspace. Only\n+ * install the op for switches whose driver implements it, so that\n+ * unsupported requests keep failing early.\n+ */\n+#define DSA_DEVLINK_OPS\t\t\t\t\t\t\t\\\n+\t.info_get\t\t\t= dsa_devlink_info_get,\t\t\\\n+\t.sb_pool_get\t\t\t= dsa_devlink_sb_pool_get,\t\\\n+\t.sb_pool_set\t\t\t= dsa_devlink_sb_pool_set,\t\\\n+\t.sb_port_pool_get\t\t= dsa_devlink_sb_port_pool_get,\t\\\n+\t.sb_port_pool_set\t\t= dsa_devlink_sb_port_pool_set,\t\\\n+\t.sb_tc_pool_bind_get\t\t= dsa_devlink_sb_tc_pool_bind_get, \\\n+\t.sb_tc_pool_bind_set\t\t= dsa_devlink_sb_tc_pool_bind_set, \\\n+\t.sb_occ_snapshot\t\t= dsa_devlink_sb_occ_snapshot,\t\\\n+\t.sb_occ_max_clear\t\t= dsa_devlink_sb_occ_max_clear,\t\\\n+\t.sb_occ_port_pool_get\t\t= dsa_devlink_sb_occ_port_pool_get, \\\n \t.sb_occ_tc_port_bind_get\t= dsa_devlink_sb_occ_tc_port_bind_get,\n+\n+static const struct devlink_ops dsa_devlink_ops = {\n+\tDSA_DEVLINK_OPS\n+};\n+\n+static const struct devlink_ops dsa_devlink_flash_ops = {\n+\tDSA_DEVLINK_OPS\n+\t.flash_update\t\t\t= dsa_devlink_flash_update,\n };\n \n int dsa_devlink_param_get(struct devlink *dl, u32 id,\n@@ -378,12 +400,16 @@ void dsa_switch_devlink_unregister(struct dsa_switch *ds)\n int dsa_switch_devlink_alloc(struct dsa_switch *ds)\n {\n \tstruct dsa_devlink_priv *dl_priv;\n+\tconst struct devlink_ops *ops;\n \tstruct devlink *dl;\n \n+\tops = ds-\u003eops-\u003edevlink_flash_update ? \u0026dsa_devlink_flash_ops\n+\t\t\t\t\t : \u0026dsa_devlink_ops;\n+\n \t/* Add the switch to devlink before calling setup, so that setup can\n \t * add dpipe tables\n \t */\n-\tdl = devlink_alloc(\u0026dsa_devlink_ops, sizeof(*dl_priv), ds-\u003edev);\n+\tdl = devlink_alloc(ops, sizeof(*dl_priv), ds-\u003edev);\n \tif (!dl)\n \t\treturn -ENOMEM;\n \n"
} |
| 2/1 | 2026/07/26 11:52 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flash support for the MaxLinear MxL862xx DSA switch via devlink. The mxl862xx driver is a vendor-specific hardware driver that communicates with the physical switch chip over an MDIO bus. During probe, it performs a hardware presence check (mxl862xx_rescue_mode_detect) that requires the physical chip to latch and return specific values. Without the physical hardware (which is not emulated in standard QEMU), the driver fails to probe, making the new devlink flash update code unreachable in a virtualized environment. The generic DSA devlink plumbing changes only affect drivers implementing the new op, which is currently only mxl862xx.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 7936df7af708975c9d83ee296bf3614dc8623166
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Jul 26 11:52:36 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index 4745148fecf4fe..058999d0dc56f2 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -94,6 +94,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 00000000000000..b3e9f7d4d4969e
--- /dev/null
+++ b/Documentation/networking/devlink/mxl862xx.rst
@@ -0,0 +1,63 @@
+.. 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. Not
+ reported for a switch sitting in MCUboot rescue mode as the
+ registers are only accessible with a running firmware.
+ * - ``asic.rev``
+ - fixed
+ - 0
+ - The chip version read from the CHIP ID registers. Not reported
+ in MCUboot rescue mode either.
+ * - ``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.
+ In MCUboot rescue mode nothing is reported while an interrupted
+ download is still being recovered in the background; once the
+ loader is ready to accept a new image the version is reported (as
+ both running and stored), which is the signal that a flash will be
+ accepted. It reads "0.0.0" when the switch came up straight into
+ MCUboot without ever running firmware.
+
+Flash update
+============
+
+The ``mxl862xx`` driver implements support for ``devlink dev flash``.
+The signed firmware image is transferred to the switch over the same
+MDIO bus which is also used to manage the switch, then verified and
+installed by the MCUboot bootloader running on the switch. All ports
+of the switch are closed for the duration of the update and the driver
+reprobes the switch after it has rebooted into the new firmware. A
+complete flash and reprobe cycle takes about one minute.
+
+A switch stuck in MCUboot rescue mode, e.g. after an interrupted
+update, is registered without user ports. If the previous download was
+interrupted mid-transfer the loader is wedged; the driver drains it
+back to a clean ready state in the background, which can easily take
+more than 10 minutes. During that recovery ``devlink dev info`` reports
+no firmware version and ``devlink dev flash`` returns ``-EBUSY``.
+Once the loader is ready the firmware version appears and flashing a
+firmware image through the regular update flow recovers the switch.
diff --git a/MAINTAINERS b/MAINTAINERS
index 60cff00953dcbe..3a55ed2bad9cba 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16107,6 +16107,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/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile
index a7be0e6669dfa5..bccac0d0f703f6 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 a180a5decffc01..6f771895984cb3 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 c87a955c13c48e..a865425aa61e1f 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 00000000000000..d28ceb98cd564e
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,884 @@
+// 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> ; address/page latch
+ * MII reg 0x00 := / => <u16 data> ; data window
+ *
+ * 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 -> end of transfer / finalise
+ * 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: reset; 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 := END(0x3cc3) ---------------------> finalise
+ *
+ * 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, validates, and - if it was in rescue - clears its
+ * rescue-enable bit so boot_go boots the new image, ONLY when r_remain hits
+ * EXACTLY 0. A count larger than r_remain underflows the 32-bit counter and
+ * wedges the loader until a power cycle. Hence:
+ * - never send a slice/chunk count larger than what is outstanding;
+ * - 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 spinning in the slice loop
+ * holding STAT=0 (no magic). Feed single 1-byte chunks (one DATA word +
+ * STAT=1) until r_remain reaches 0, then STAT=END; the loader finalises the
+ * (now corrupt) image and re-arms READY for a clean reflash.
+ *
+ * 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 multi-minute recovery never holds the devlink lock. devlink dev info
+ * exposes the fw version (the "flashable" signal) only once at READY;
+ * flash_update returns -EBUSY until then, and reprobes to WSP firmware on success.
+ *
+ * Notes:
+ * - Chip id/revision (0xc0d28884/88) are NOT reachable on this channel; they
+ * need the clause-45 MMD firmware mailbox, which is dead under MCUboot.
+ * Rescue identity is by SB PDI behaviour only (mxl862xx_rescue_mode_detect).
+ * - The SMDIO PHY address and the 0xe1xx offsets are OTP-configurable; derive
+ * them from the DT binding, do not assume fixed values.
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/overflow.h>
+#include <linux/rtnetlink.h>
+#include <linux/workqueue.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "mxl862xx.h"
+#include "mxl862xx-api.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
+#include "mxl862xx-host.h"
+
+/* SB PDI registers (clause-22 SMDIO address space) */
+#define MXL862XX_SB_PDI_CTRL 0xe100
+#define MXL862XX_SB_PDI_ADDR 0xe101
+#define MXL862XX_SB_PDI_DATA 0xe102
+#define MXL862XX_SB_PDI_STAT 0xe103
+
+/* SB PDI CTRL modes */
+#define MXL862XX_SB_PDI_CTRL_RST 0x00
+#define MXL862XX_SB_PDI_CTRL_WR 0x02
+
+/* SB PDI handshake magic (published/consumed via STAT) */
+#define MXL862XX_SB_PDI_READY 0xc55c /* loader idle, console loop */
+#define MXL862XX_SB_PDI_DL_READY 0xc33c /* loader idle, flashless loop */
+#define MXL862XX_SB_PDI_START 0xf48f
+#define MXL862XX_SB_PDI_END 0x3cc3
+#define MXL862XX_SB_PDI_RDREG 0xe2c0 /* register-read cmd (| index) */
+#define MXL862XX_SB_PDI_RDREG_MARK 0x7c23 /* marker placed in DATA for RDREG */
+
+/* Behavioural presence probe: two distinct 16-bit latches on ADDR/DATA. */
+#define MXL862XX_SB_PDI_PROBE_A 0x5a5a
+#define MXL862XX_SB_PDI_PROBE_D 0xa5a5
+
+/* 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 120000
+#define MXL862XX_FW_REBOOT_DELAY_MS 5000
+#define MXL862XX_FW_REPROBE_DELAY_MS 500
+#define MXL862XX_RESCUE_READY_TIMEOUT_MS 1000
+
+static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+ int ret;
+
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_RST);
+ if (ret < 0)
+ return ret;
+
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+ MXL862XX_SB_PDI_CTRL_RST);
+ if (ret < 0)
+ return ret;
+
+ return mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+ MXL862XX_SB_PDI_CTRL_RST);
+}
+
+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
+ unsigned long timeout_ms)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
+ int ret;
+
+ do {
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+ if (ret < 0)
+ return ret;
+ if ((u16)ret == expected)
+ return 0;
+ usleep_range(10000, 11000);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+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);
+}
+
+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
+
+/* Poll STAT while draining a stuck download: 0 means "feed the next chunk",
+ * READY means the loader left the receive loop and re-armed its command loop,
+ * anything else is a transient (the count being consumed) - BUSY past the
+ * window means the counter has hit zero and the loader is finalising.
+ */
+enum { MXL862XX_DRAIN_FEED, MXL862XX_DRAIN_READY, MXL862XX_DRAIN_BUSY };
+static int mxl862xx_sb_pdi_poll_drain(struct mxl862xx_priv *priv,
+ unsigned long timeout_ms)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
+ int ret;
+
+ do {
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+ if (ret < 0)
+ return ret;
+ if ((u16)ret == MXL862XX_SB_PDI_READY)
+ return MXL862XX_DRAIN_READY;
+ if ((u16)ret == 0)
+ return MXL862XX_DRAIN_FEED;
+ usleep_range(50, 150);
+ } while (time_before(jiffies, timeout));
+
+ return MXL862XX_DRAIN_BUSY;
+}
+
+/* Recover a switch whose SB PDI download was interrupted mid-transfer - the
+ * host died after MCUboot began erasing flash, whether it aborted mid erase or
+ * mid image-write, both end up in the same place: the payload receive loop.
+ * There the loader publishes STAT=0, waits for the host to write a byte-count
+ * to STAT, DMAs that many bytes and subtracts the count from a remaining-bytes
+ * counter, leaving the loop only when the counter reaches exactly zero. The
+ * image size died with the host, so we feed single-byte chunks (see
+ * MXL862XX_DRAIN_CHUNK_BYTES) to walk the counter to zero without underflow,
+ * then send END. The loader runs its (failed) checksum and either re-arms
+ * READY or, if a valid image survived in flash, boots it. Returns 0 when READY
+ * is reached (the caller can flash), 1 when the loader booted instead (the
+ * switch is recovered but the intended image was not written), <0 on error.
+ * Does NOT recover a counter already underflowed by an earlier oversized-chunk
+ * attempt - that needs a power cycle.
+ */
+static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)
+{
+ struct device *dev = &priv->mdiodev->dev;
+ /* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */
+ u32 max_chunks = 2u * (16u << 20) / MXL862XX_DRAIN_CHUNK_BYTES;
+ u32 chunk = 0;
+ int ret;
+
+ dev_warn(dev, "flash: draining interrupted download\n");
+
+ while (chunk < max_chunks) {
+ /* Teardown can interrupt this minutes-long drain. */
+ if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+ return -ECANCELED;
+
+ ret = mxl862xx_sb_pdi_poll_drain(priv, 2000);
+ if (ret < 0)
+ return ret;
+ if (ret == MXL862XX_DRAIN_READY)
+ return 0;
+ if (ret == MXL862XX_DRAIN_BUSY)
+ break;
+
+ /* 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++;
+ cond_resched();
+ }
+
+ if (chunk >= max_chunks) {
+ dev_err(dev,
+ "flash: interrupted download did not drain after %u chunks\n",
+ chunk);
+ return -ETIMEDOUT;
+ }
+
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_END);
+ if (ret < 0)
+ return ret;
+
+ return mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+}
+
+/* Background self-heal: drain a wedged download back to READY off the devlink
+ * flash path, so the minutes-long recovery never holds the devlink lock.
+ * Scheduled from probe; publishes rescue_ready on success.
+ */
+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;
+
+ dev_info(dev, "recovering interrupted download in background\n");
+ ret = mxl862xx_rescue_drain(priv);
+ if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+ return;
+ if (!ret) {
+ WRITE_ONCE(priv->rescue_ready, true);
+ dev_info(dev, "recovery complete, ready to flash\n");
+ }
+}
+
+/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller can rule
+ * the loader out before any C45 API request (which spews CRC errors when no WSP
+ * firmware answers). A scratch write to ADDR/DATA must latch or the chip is
+ * absent (-ENODEV); STAT then classifies the state, poked destructively only
+ * when 0, the one value a running firmware never holds:
+ *
+ * - 0xc33c: flashless loop, ready.
+ * - 0xc55c: console loop, if the register-read challenge is serviced.
+ * - other non-zero: running firmware, left unpoked.
+ * - 0: wedged receive loop, if a 1-byte slice-advance drains back to 0.
+ *
+ * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, or negative (-ENODEV/SMDIO).
+ */
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)
+{
+ int stat, dat, ret, rb, a, d;
+
+ /* rescue_ready gates flashing; a wedged loader needs the drain first. */
+ priv->rescue_ready = false;
+
+ /* 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: unambiguous ready magic, leave it alone. */
+ if ((u16)stat == MXL862XX_SB_PDI_DL_READY) {
+ priv->rescue_ready = true;
+ return MXL862XX_IN_RESCUE;
+ }
+
+ /* 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_RESCUE_READY_TIMEOUT_MS);
+ dat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);
+ if (dat < 0)
+ return dat;
+ mxl862xx_sb_pdi_reset(priv);
+ if (!rb && (u16)dat != MXL862XX_SB_PDI_RDREG_MARK) {
+ priv->rescue_ready = true;
+ return MXL862XX_IN_RESCUE;
+ }
+ return -ENODEV;
+ }
+
+ /* Any other non-zero value is a running firmware, not a loader. */
+ if (stat)
+ return MXL862XX_NOT_RESCUE;
+
+ /* STAT == 0: a wedged receive loop consumes a 1-byte slice-advance back
+ * to 0 (feed one DATA word first, like a drain chunk).
+ */
+ 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, 1);
+ if (ret < 0)
+ return ret;
+ rb = mxl862xx_sb_pdi_poll_stat(priv, 0, MXL862XX_RESCUE_READY_TIMEOUT_MS);
+ if (!rb)
+ return MXL862XX_IN_RESCUE;
+
+ return -ENODEV;
+}
+
+/* Post-flash reprobe. Runs from a self-contained heap work (not a kthread and
+ * not the devlink caller's context): kthread_create() in the caller context
+ * fails -EINTR if the devlink command was Ctrl-C'd, and schedule_work() cannot.
+ * device_reprobe() -> remove() frees priv, so this must not touch priv; it uses
+ * only the held device reference and drops its module reference afterwards.
+ */
+struct mxl862xx_reprobe_work {
+ struct work_struct work;
+ struct device *dev;
+};
+
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_reprobe_work *rw =
+ container_of(work, struct mxl862xx_reprobe_work, work);
+ struct device *dev = rw->dev;
+
+ msleep(MXL862XX_FW_REPROBE_DELAY_MS);
+ if (device_reprobe(dev))
+ dev_err(dev, "reprobe failed\n");
+ put_device(dev);
+ kfree(rw);
+ module_put(THIS_MODULE);
+}
+
+/* MCUboot firmware image header */
+struct mxl862xx_fw_hdr {
+ __le32 image_type;
+ __le32 image_size_1;
+ __le32 image_checksum_1;
+ __le32 image_size_2;
+ __le32 image_checksum_2;
+} __packed;
+
+static int mxl862xx_flash_validate(struct mxl862xx_priv *priv,
+ const struct firmware *fw,
+ u32 *payload_size)
+{
+ const struct mxl862xx_fw_hdr *hdr;
+ u32 size1, size2, total;
+ const u8 *payload;
+ u32 crc;
+
+ if (fw->size < MXL862XX_FW_HDR_SIZE)
+ return -EINVAL;
+
+ hdr = (const struct mxl862xx_fw_hdr *)fw->data;
+ payload = fw->data + MXL862XX_FW_HDR_SIZE;
+ size1 = le32_to_cpu(hdr->image_size_1);
+ size2 = le32_to_cpu(hdr->image_size_2);
+
+ if (check_add_overflow(size1, size2, &total) ||
+ total > fw->size - MXL862XX_FW_HDR_SIZE) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: firmware file too small for declared size\n");
+ return -EINVAL;
+ }
+
+ if (!total) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: firmware file with empty payload\n");
+ return -EINVAL;
+ }
+
+ if (size1) {
+ crc = ~crc32_le(~0U, payload, size1);
+ if (crc != le32_to_cpu(hdr->image_checksum_1)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_1));
+ return -EINVAL;
+ }
+ }
+
+ if (size2) {
+ crc = ~crc32_le(~0U, payload + size1, size2);
+ if (crc != le32_to_cpu(hdr->image_checksum_2)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_2));
+ return -EINVAL;
+ }
+ }
+
+ *payload_size = total;
+
+ return 0;
+}
+
+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
+ const struct firmware *fw,
+ u32 payload_size, struct devlink *dl)
+{
+ const u8 *payload = fw->data + MXL862XX_FW_HDR_SIZE;
+ u32 word_idx = 0, data_written = 0, idx = 0;
+ unsigned long next_notify = jiffies - 1;
+ u16 word, fdata;
+ int ret, i;
+
+ /* Step 1: reboot the firmware into MCUboot rescue mode */
+ if (!priv->rescue_mode) {
+ ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
+ false, false);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: FW_UPDATE command failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+
+ /* Step 2: wait for bootloader ready */
+ mxl862xx_flash_notify(dl, "Waiting for bootloader", 0, 0);
+ ret = mxl862xx_sb_pdi_reset(priv);
+ if (ret < 0)
+ goto write_err;
+
+ /* Failures from here on must go through end_magic so MCUboot
+ * reboots instead of waiting forever.
+ */
+ 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));
+ return ret;
+ }
+
+ /* 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));
+ return ret;
+ }
+
+ /* Step 4: transfer image header */
+ mxl862xx_flash_notify(dl, "Erasing flash", 0, 0);
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ if (ret < 0)
+ goto write_err;
+
+ for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
+ word = fw->data[i * 2] |
+ ((u16)fw->data[i * 2 + 1] << 8);
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
+ if (ret < 0)
+ goto write_err;
+ }
+
+ ret = mxl862xx_sb_pdi_reset(priv);
+ if (ret < 0)
+ goto write_err;
+
+ /* the byte count in STAT triggers the erase */
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_FW_HDR_SIZE);
+ if (ret < 0)
+ goto write_err;
+
+ /* ACK is byte count + 1 */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: header ACK failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ /* 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));
+ return ret;
+ }
+
+ /* 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) {
+ 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_slice(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));
+ return ret;
+ }
+ 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 write timeout: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ mxl862xx_flash_notify(dl, "Flashing", payload_size, payload_size);
+ goto end_magic;
+
+write_err:
+ dev_err(&priv->mdiodev->dev, "flash: SMDIO write failed: %pe\n",
+ ERR_PTR(ret));
+end_magic:
+ /* reboot MCUboot even after a failed transfer */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_END);
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+
+ return ret;
+}
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ char buf[16];
+ int ret;
+
+ /* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The
+ * fw version doubles as the "ready to flash" signal: report it only
+ * once the loader is at a clean READY, nothing while still draining.
+ */
+ if (priv->rescue_mode) {
+ if (!READ_ONCE(priv->rescue_ready))
+ return 0;
+
+ snprintf(buf, sizeof(buf), "%u.%u.%u",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+ ret = devlink_info_version_running_put(req, "fw", buf);
+ if (ret)
+ return ret;
+ return devlink_info_version_stored_put(req, "fw", buf);
+ }
+
+ 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;
+
+ 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, "fw", buf);
+ if (ret)
+ return ret;
+
+ /* boots this image from its own flash: stored == running */
+ return devlink_info_version_stored_put(req, "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 mxl862xx_reprobe_work *rw;
+ struct dsa_port *dp;
+ u32 payload_size;
+ int ret, i;
+
+ if (params->component) {
+ NL_SET_ERR_MSG_MOD(extack, "component is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ 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. */
+ if (priv->rescue_mode && !READ_ONCE(priv->rescue_ready)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "switch is recovering an interrupted download, retry shortly");
+ return -EBUSY;
+ }
+
+ /* The references the reprobe work needs to restore normal operation
+ * must be held before the switch is disturbed; the work itself is
+ * scheduled only once the flash is done (see below).
+ */
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ get_device(ds->dev);
+
+ if (priv->rescue_mode)
+ dev_info(ds->dev,
+ "flash: flashing switch via MCUboot rescue mode\n");
+ else
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ /* Close ports while the firmware is still alive so the DSA
+ * core's MDB/FDB tracking is drained, and detach user ports
+ * so userspace cannot reopen them during the flash. The
+ * conduit belongs to the MAC driver and is only closed.
+ */
+ 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);
+ cancel_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);
+ priv->rescue_mode = false;
+ mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+ /* The reprobe below re-reads and logs the new version. */
+ }
+
+ mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+ priv->skip_teardown = true;
+ mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+ /* Hand the reprobe to a self-contained work, scheduled last. Doing it
+ * from a work (rather than a kthread spawned here) avoids both the
+ * hung-task splat of a thread parked across the flash and the -EINTR
+ * that kthread_create() returns if the devlink command was Ctrl-C'd.
+ * Its module and device references are already held above.
+ */
+ rw = kzalloc_obj(*rw);
+ if (!rw) {
+ dev_err(ds->dev,
+ "flash: out of memory for reprobe; reload the driver to restore operation\n");
+ put_device(ds->dev);
+ module_put(THIS_MODULE);
+ return ret ? ret : -ENOMEM;
+ }
+ INIT_WORK(&rw->work, mxl862xx_reprobe_work_fn);
+ rw->dev = ds->dev;
+ schedule_work(&rw->work);
+
+ return ret;
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
new file mode 100644
index 00000000000000..7cd87c7ad87167
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+struct mxl862xx_priv;
+struct work_struct;
+
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);
+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack);
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc009..2dbd074c0fe26c 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -15,6 +15,8 @@
#include <linux/unaligned.h>
#include <net/dsa.h>
#include "mxl862xx.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
@@ -340,6 +342,21 @@ 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;
+ }
+
+ if (priv->block_host && cmd != SYS_MISC_FW_UPDATE) {
+ ret = -EBUSY;
+ goto out;
+ }
+
max = (size + 1) / 2;
ret = mxl862xx_busy_wait(priv);
@@ -495,12 +512,49 @@ 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
+
+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 66d6ae198aff45..4e054c6e4c0e40 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 b689652aa9b920..a5b6940b552e9e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
switch (port) {
case 9 ... 16:
+ if (priv->rescue_mode)
+ return NULL;
if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {
dev_warn_once(dp->ds->dev,
"SerDes PCS unsupported on old firmware.\n");
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 45d237b3a40f10..9ac668c13c9e70 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,6 +230,35 @@ 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);
}
+/* Read the static chip part number and version from the CHIP ID
+ * registers. Only possible with a running firmware, so the values are
+ * cached at setup and left zero when the switch is in rescue mode.
+ */
+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_sys_reg_rw reg = {};
+ u16 chipid_l, chipid_m;
+ int ret;
+
+ reg.addr = cpu_to_le32(MXL862XX_CHIPID_L);
+ ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+ if (ret)
+ return ret;
+ chipid_l = le32_to_cpu(reg.val);
+
+ reg.addr = cpu_to_le32(MXL862XX_CHIPID_M);
+ ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+ if (ret)
+ return ret;
+ chipid_m = le32_to_cpu(reg.val);
+
+ priv->asic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |
+ FIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) << 4;
+ priv->asic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);
+
+ return 0;
+}
+
static int mxl862xx_wait_ready(struct dsa_switch *ds)
{
struct mxl862xx_sys_fw_image_version ver = {};
@@ -254,6 +291,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)
priv->fw_version.major = ver.iv_major;
priv->fw_version.minor = ver.iv_minor;
priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
+
+ ret = mxl862xx_read_chip_id(priv);
+ if (ret)
+ dev_warn(ds->dev, "failed to read chip ID: %pe\n",
+ ERR_PTR(ret));
return 0;
not_ready_yet:
@@ -622,15 +664,49 @@ static int mxl862xx_setup(struct dsa_switch *ds)
int n_user_ports = 0, max_vlans;
int ingress_finals, vid_rules;
struct dsa_port *dp;
- int ret, i;
+ 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 spews CRC errors when none
+ * answers. Touch C45 only once rescue is ruled out.
+ */
+ rescue = mxl862xx_rescue_mode_detect(priv);
+ if (rescue < 0)
+ 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);
+ if (rescue < 0)
+ return rescue;
+ if (rescue == MXL862XX_NOT_RESCUE)
+ return ret;
+ }
+ }
+
+ priv->rescue_mode = rescue;
+
+ if (priv->rescue_mode) {
+ if (priv->rescue_ready) {
+ dev_warn(ds->dev,
+ "switch in MCUboot rescue mode, use devlink to flash new firmware\n");
+ } else {
+ /* Drain the wedged download in the background so it
+ * never holds the devlink lock; info and flash become
+ * available once ready.
+ */
+ dev_warn(ds->dev,
+ "switch in MCUboot with an interrupted download, recovering in background\n");
+ queue_work(system_long_wq, &priv->rescue_heal_work);
+ }
+ return 0;
+ }
mutex_init(&priv->serdes_lock);
for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
@@ -715,11 +791,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)
static int mxl862xx_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phydev)
{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (priv->rescue_mode)
+ return 0;
+
return mxl862xx_port_state(ds, port, true);
}
static void mxl862xx_port_disable(struct dsa_switch *ds, int port)
{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (priv->rescue_mode)
+ return;
+
if (mxl862xx_port_state(ds, port, false))
dev_err(ds->dev, "failed to disable port %d\n", port);
}
@@ -1337,6 +1423,12 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
+ /* DSA reinits failed user ports as unused; shared ports must
+ * succeed for the tree to register.
+ */
+ if (priv->rescue_mode)
+ return dsa_port_is_user(dp) ? -ENODEV : 0;
+
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
@@ -1572,6 +1664,11 @@ 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);
+ /* -ENODEV: the firmware and its MAC table are gone, nothing left
+ * to delete
+ */
+ if (ret == -ENODEV)
+ return 0;
if (ret)
return ret;
@@ -1628,6 +1725,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
struct mxl862xx_priv *priv = ds->priv;
int ret;
+ if (priv->rescue_mode)
+ return;
+
switch (state) {
case BR_STATE_DISABLED:
param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
@@ -2086,6 +2186,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .devlink_info_get = mxl862xx_devlink_info_get,
+ .devlink_flash_update = mxl862xx_devlink_flash_update,
};
static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08e6..72dc31cc2b62fa 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -14,6 +14,10 @@ struct mxl862xx_priv;
#define MXL862XX_FIRST_SERDES_PORT 9
#define MXL862XX_SERDES_SLOTS 4
+/* mxl862xx_rescue_mode_detect() return codes (negative values are errors) */
+#define MXL862XX_NOT_RESCUE 0
+#define MXL862XX_IN_RESCUE 1
+
#define MXL862XX_DEFAULT_BRIDGE 0
#define MXL862XX_MAX_BRIDGES 48
#define MXL862XX_MAX_BRIDGE_PORTS 128
@@ -303,6 +307,10 @@ struct mxl862xx_fw_version {
* flooding)
* @fw_version: cached firmware version, populated at probe and
* compared with MXL862XX_FW_VER_MIN()
+ * @asic_id: chip part number read from the CHIP ID registers,
+ * reported as the devlink "asic.id" fixed version
+ * @asic_rev: chip version read from the CHIP ID registers,
+ * reported as the devlink "asic.rev" fixed version
* @serdes_ports: SerDes interfaces incl. sub-interfaces in case of
* 10G_QXGMII or QSGMII
* @serdes_refcount: per-XPCS count of sub-ports enabled by phylink;
@@ -319,6 +327,15 @@ struct mxl862xx_fw_version {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
+ * @block_host: reject firmware API commands (except FW_UPDATE)
+ * during a firmware flash
+ * @skip_teardown: discard firmware API commands during the teardown
+ * triggered by the post-flash reprobe
+ * @rescue_mode: switch is in MCUboot; firmware API commands fail fast,
+ * only clause-22 SMDIO works
+ * @rescue_ready: (rescue_mode) loader is at a clean READY and will accept
+ * a flash; false while rescue_heal_work is draining
+ * @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 +343,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,6 +357,10 @@ struct mxl862xx_priv {
u16 evlan_ingress_size;
u16 evlan_egress_size;
u16 vf_block_size;
+ bool block_host;
+ bool skip_teardown;
+ bool rescue_mode;
+ bool rescue_ready;
struct delayed_work stats_work;
};
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc102a..c9e19348de6193 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1170,6 +1170,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 ed342f345692bb..d6022267a839cd 100644
--- a/net/dsa/devlink.c
+++ b/net/dsa/devlink.c
@@ -20,6 +20,15 @@ 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);
+
+ 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)
@@ -167,18 +176,31 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
p_max);
}
-static const struct devlink_ops dsa_devlink_ops = {
- .info_get = dsa_devlink_info_get,
- .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,
- .sb_port_pool_set = dsa_devlink_sb_port_pool_set,
- .sb_tc_pool_bind_get = dsa_devlink_sb_tc_pool_bind_get,
- .sb_tc_pool_bind_set = dsa_devlink_sb_tc_pool_bind_set,
- .sb_occ_snapshot = dsa_devlink_sb_occ_snapshot,
- .sb_occ_max_clear = dsa_devlink_sb_occ_max_clear,
- .sb_occ_port_pool_get = dsa_devlink_sb_occ_port_pool_get,
+/* The devlink core rejects flash requests up front when the flash_update
+ * op is absent, before fetching the firmware file from userspace. Only
+ * install the op for switches whose driver implements it, so that
+ * unsupported requests keep failing early.
+ */
+#define DSA_DEVLINK_OPS \
+ .info_get = dsa_devlink_info_get, \
+ .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, \
+ .sb_port_pool_set = dsa_devlink_sb_port_pool_set, \
+ .sb_tc_pool_bind_get = dsa_devlink_sb_tc_pool_bind_get, \
+ .sb_tc_pool_bind_set = dsa_devlink_sb_tc_pool_bind_set, \
+ .sb_occ_snapshot = dsa_devlink_sb_occ_snapshot, \
+ .sb_occ_max_clear = dsa_devlink_sb_occ_max_clear, \
+ .sb_occ_port_pool_get = dsa_devlink_sb_occ_port_pool_get, \
.sb_occ_tc_port_bind_get = dsa_devlink_sb_occ_tc_port_bind_get,
+
+static const struct devlink_ops dsa_devlink_ops = {
+ DSA_DEVLINK_OPS
+};
+
+static const struct devlink_ops dsa_devlink_flash_ops = {
+ DSA_DEVLINK_OPS
+ .flash_update = dsa_devlink_flash_update,
};
int dsa_devlink_param_get(struct devlink *dl, u32 id,
@@ -378,12 +400,16 @@ void dsa_switch_devlink_unregister(struct dsa_switch *ds)
int dsa_switch_devlink_alloc(struct dsa_switch *ds)
{
struct dsa_devlink_priv *dl_priv;
+ const struct devlink_ops *ops;
struct devlink *dl;
+ ops = ds->ops->devlink_flash_update ? &dsa_devlink_flash_ops
+ : &dsa_devlink_ops;
+
/* Add the switch to devlink before calling setup, so that setup can
* add dpipe tables
*/
- dl = devlink_alloc(&dsa_devlink_ops, sizeof(*dl_priv), ds->dev);
+ dl = devlink_alloc(ops, sizeof(*dl_priv), ds->dev);
if (!dl)
return -ENOMEM;
|
| 3/2 | 2026/07/26 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24193 output: 50 thoughts: 476 Thoughts: **Assessing the Fuzzability of a MaxLinear MxL862xx Firmware Patch** Okay, so I'm looking at this patch – it looks like it adds firmware flashing and devlink support for the MaxLinear MxL862xx switch. The core of it seems to be in `mxl862xx-fw.c`, which implements the firmware update via devlink, and then there are modifications to `mxl862xx.c` and `devlink.c`. Naturally, my first question is: can we even *reach* this code in a virtualized environment for fuzzing? DSA switches are usually physical hardware, aren't they? But perhaps there's an emulation path, or maybe a dummy MDIO bus could work? Let's dig in a bit. The key here is that it's an MDIO device driver. The obvious question is whether we can conjure up some dummy MDIO devices within a QEMU setup or even syzkaller. I know syzkaller has the ability to create dummy devices, I was thinking it uses `mdio` subsystem or similar, or potentially `netdevsim`, however, `netdevsim` doesn't emulate MDIO devices. Wait, does syzkaller have something like `syz_mdio_...`? No, that doesn't exist. So the options seem to be creating devices via the device tree or potentially through I2C/MDIO injection, if that's even possible. The problem with physical switch drivers like DSA is they're notoriously difficult to probe without the real hardware, unless there's a specific emulator available. Let me confirm that `mxl862xx` is definitely a physical hardware driver. Yep, "MaxLinear MxL862xx". Okay, I am now thinking I should have started by reading the instructions. Hold on, the fuzzing instructions specifically say: "Return WorthFuzzing=false if the patch only contains... - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU." This is, by definition, a *vendor-specific DSA switch driver* (MaxLinear MxL862xx) that lives on an MDIO bus. So, the million-dollar question: can it be probed in QEMU? Given that it appears to fit the "no go" description from the fuzzing instructions, I'm going to have to do some thorough digging in the driver's probe function in `drivers/net/dsa/mxl862xx/mxl862xx.c` and confirm that QEMU is a viable option. |
| 4/2 | 2026/07/26 11:52 | tool | grepper |
0mArgs:{
"Expression": "mxl862xx_probe",
"PathPrefix": "drivers/net/dsa/mxl862xx/mxl862xx.c"
}Results: {
"Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2157=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2192-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2193:static int mxl862xx_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2194-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2302=static struct mdio_driver mxl862xx_driver = {\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2303:\t.probe = mxl862xx_probe,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2304-\t.remove = mxl862xx_remove,\n"
} |
| 5/2 | 2026/07/26 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25618 output: 26 thoughts: 85 Thoughts: **MDIO Fuzzing: A Syzkaller Exploration** Okay, so I'm looking at this MDIO driver and the question immediately pops into my head: can we effectively fuzz MDIO devices within the Syzkaller framework? My initial assessment is that Syzkaller lacks a truly generic MDIO device emulator. It doesn't seem to offer a way to probe arbitrary MDIO drivers directly without relying on a specific virtual bus implementation. Hmm, wasn't there something like `mdio-mock`... or am I misremembering? No, that's not it. Right, I need to verify this assumption. Let's see if there are any existing fuzzing capabilities specifically designed for MDIO within Syzkaller. It's time to dig deeper and see what's currently available. |
| 6/2 | 2026/07/26 11:52 | tool | grepper |
0mError:no matches Args: {
"Expression": "mdio",
"PathPrefix": "sys/linux/"
}Results: {
"error": "no matches"
} |
| 7/2 | 2026/07/26 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25911 output: 52 thoughts: 322 Thoughts: **My Reasoning on Investigating MDIO Devices** Okay, so I need to search through kernel code, but I can't use `grepper` in `sys/linux/` because of the syzkaller specifics. I'm limited to `read-file` or `codesearch-dir-index`, or, the instructions suggest `read-description`. The text explicitly points out that syzlang descriptions or expressions with 'syz_' are off-limits for `grepper`. The instructions specifically tell me to use this, but wait...I don't have it in my toolset! I only have `grepper`, `codesearch-dir-index`, `read-file`, and `set-results`. It's frustrating when the documentation refers to a tool that isn't available, but I'll work with what I have. Regardless, I'm thinking about MDIO devices, and my initial intuition is that they are generally tied to physical hardware. I need to figure out if `CONFIG_NET_DSA_MXL862` represents a physical hardware driver. Looking at the code confirms this; it's a DSA switch driver for the MaxLinear MxL862xx. The association with `mlxsw` further reinforces this requirement for physical hardware or specialized I2C/MDIO hardware that isn't emulated in standard QEMU. The real question is, could this device be probed through a software MDIO bus? Even if the probing succeeds, the fundamental communication involves MDIO reads/writes. Without the actual hardware present, any probe is going to fail. I should probably focus on `mxl862xx_probe` to be sure. |
| 8/2 | 2026/07/26 11:52 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
"FirstLine": 2193,
"LineCount": 100
}Results: {
"Contents": "2193:\tstatic int mxl862xx_probe(struct mdio_device *mdiodev)\n2194:\t{\n2195:\t\tstruct device *dev = \u0026mdiodev-\u003edev;\n2196:\t\tstruct mxl862xx_priv *priv;\n2197:\t\tstruct dsa_switch *ds;\n2198:\t\tint err, i;\n2199:\t\n2200:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n2201:\t\tif (!priv)\n2202:\t\t\treturn -ENOMEM;\n2203:\t\n2204:\t\tpriv-\u003emdiodev = mdiodev;\n2205:\t\n2206:\t\tds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);\n2207:\t\tif (!ds)\n2208:\t\t\treturn -ENOMEM;\n2209:\t\n2210:\t\tpriv-\u003eds = ds;\n2211:\t\tds-\u003edev = dev;\n2212:\t\tds-\u003epriv = priv;\n2213:\t\tds-\u003eops = \u0026mxl862xx_switch_ops;\n2214:\t\tds-\u003ephylink_mac_ops = \u0026mxl862xx_phylink_mac_ops;\n2215:\t\tds-\u003enum_ports = MXL862XX_MAX_PORTS;\n2216:\t\tds-\u003efdb_isolation = true;\n2217:\t\tds-\u003emax_num_bridges = MXL862XX_MAX_BRIDGES;\n2218:\t\n2219:\t\tmxl862xx_host_init(priv);\n2220:\t\n2221:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++) {\n2222:\t\t\tpriv-\u003eports[i].priv = priv;\n2223:\t\t\tINIT_WORK(\u0026priv-\u003eports[i].host_flood_work,\n2224:\t\t\t\t mxl862xx_host_flood_work_fn);\n2225:\t\t\tspin_lock_init(\u0026priv-\u003eports[i].stats_lock);\n2226:\t\t}\n2227:\t\n2228:\t\tINIT_DELAYED_WORK(\u0026priv-\u003estats_work, mxl862xx_stats_work_fn);\n2229:\t\n2230:\t\tdev_set_drvdata(dev, ds);\n2231:\t\n2232:\t\terr = dsa_register_switch(ds);\n2233:\t\tif (err) {\n2234:\t\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2235:\t\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2236:\t\t\tmxl862xx_host_shutdown(priv);\n2237:\t\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2238:\t\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2239:\t\t}\n2240:\t\n2241:\t\treturn err;\n2242:\t}\n2243:\t\n2244:\tstatic void mxl862xx_remove(struct mdio_device *mdiodev)\n2245:\t{\n2246:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2247:\t\tstruct mxl862xx_priv *priv;\n2248:\t\tint i;\n2249:\t\n2250:\t\tif (!ds)\n2251:\t\t\treturn;\n2252:\t\n2253:\t\tpriv = ds-\u003epriv;\n2254:\t\n2255:\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2256:\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2257:\t\n2258:\t\tdsa_unregister_switch(ds);\n2259:\t\n2260:\t\tmxl862xx_host_shutdown(priv);\n2261:\t\n2262:\t\t/* Cancel any pending host flood work. dsa_unregister_switch()\n2263:\t\t * has already called port_teardown (which sets setup_done=false),\n2264:\t\t * but a worker could still be blocked on rtnl_lock(). Since we\n2265:\t\t * are now outside RTNL, cancel_work_sync() will not deadlock.\n2266:\t\t */\n2267:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2268:\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2269:\t}\n2270:\t\n2271:\tstatic void mxl862xx_shutdown(struct mdio_device *mdiodev)\n2272:\t{\n2273:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2274:\t\tstruct mxl862xx_priv *priv;\n2275:\t\tint i;\n2276:\t\n2277:\t\tif (!ds)\n2278:\t\t\treturn;\n2279:\t\n2280:\t\tpriv = ds-\u003epriv;\n2281:\t\n2282:\t\tdsa_switch_shutdown(ds);\n2283:\t\n2284:\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2285:\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2286:\t\n2287:\t\tmxl862xx_host_shutdown(priv);\n2288:\t\n2289:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2290:\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2291:\t\n2292:\t\tdev_set_drvdata(\u0026mdiodev-\u003edev, NULL);\n"
} |
| 9/2 | 2026/07/26 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28248 output: 51 thoughts: 44 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the weeds of embedded systems and driver development: **Initial Thoughts on a Switch Registration** Alright, I've got the probe function registering the switch – good, that's the entry point. The crucial part here is that `dsa_register_switch` will, at some point, trigger `mxl862xx_setup`. That’s the real meat of setting up this MXL862xx chip, so that's where my attention needs to shift. Let's dig into `mxl862xx_setup` and see what's happening there. I need to understand the hardware initialization sequence, any configuration register writes, and the overall state setup for this switch. |
| 10/2 | 2026/07/26 11:53 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
"FirstLine": 664,
"LineCount": 100
}Results: {
"Contents": " 664:\t\tint n_user_ports = 0, max_vlans;\n 665:\t\tint ingress_finals, vid_rules;\n 666:\t\tstruct dsa_port *dp;\n 667:\t\tint ret, i, rescue;\n 668:\t\n 669:\t\t/* Detect the loader over SB PDI first: it needs no firmware, unlike the\n 670:\t\t * C45 API (mxl862xx_reset/wait_ready) which spews CRC errors when none\n 671:\t\t * answers. Touch C45 only once rescue is ruled out.\n 672:\t\t */\n 673:\t\trescue = mxl862xx_rescue_mode_detect(priv);\n 674:\t\tif (rescue \u003c 0)\n 675:\t\t\treturn rescue;\n 676:\t\n 677:\t\tif (rescue == MXL862XX_NOT_RESCUE) {\n 678:\t\t\tret = mxl862xx_reset(priv);\n 679:\t\t\tif (ret)\n 680:\t\t\t\treturn ret;\n 681:\t\n 682:\t\t\tret = mxl862xx_wait_ready(ds);\n 683:\t\t\tif (ret) {\n 684:\t\t\t\t/* the reset may only now have triggered rescue mode */\n 685:\t\t\t\trescue = mxl862xx_rescue_mode_detect(priv);\n 686:\t\t\t\tif (rescue \u003c 0)\n 687:\t\t\t\t\treturn rescue;\n 688:\t\t\t\tif (rescue == MXL862XX_NOT_RESCUE)\n 689:\t\t\t\t\treturn ret;\n 690:\t\t\t}\n 691:\t\t}\n 692:\t\n 693:\t\tpriv-\u003erescue_mode = rescue;\n 694:\t\n 695:\t\tif (priv-\u003erescue_mode) {\n 696:\t\t\tif (priv-\u003erescue_ready) {\n 697:\t\t\t\tdev_warn(ds-\u003edev,\n 698:\t\t\t\t\t \"switch in MCUboot rescue mode, use devlink to flash new firmware\\n\");\n 699:\t\t\t} else {\n 700:\t\t\t\t/* Drain the wedged download in the background so it\n 701:\t\t\t\t * never holds the devlink lock; info and flash become\n 702:\t\t\t\t * available once ready.\n 703:\t\t\t\t */\n 704:\t\t\t\tdev_warn(ds-\u003edev,\n 705:\t\t\t\t\t \"switch in MCUboot with an interrupted download, recovering in background\\n\");\n 706:\t\t\t\tqueue_work(system_long_wq, \u0026priv-\u003erescue_heal_work);\n 707:\t\t\t}\n 708:\t\t\treturn 0;\n 709:\t\t}\n 710:\t\n 711:\t\tmutex_init(\u0026priv-\u003eserdes_lock);\n 712:\t\tfor (i = 0; i \u003c ARRAY_SIZE(priv-\u003eserdes_ports); i++)\n 713:\t\t\tmxl862xx_setup_pcs(priv, \u0026priv-\u003eserdes_ports[i],\n 714:\t\t\t\t\t i + MXL862XX_FIRST_SERDES_PORT);\n 715:\t\n 716:\t\t/* Calculate Extended VLAN block sizes.\n 717:\t\t * With VLAN Filter handling VID membership checks:\n 718:\t\t * Ingress: only final catchall rules (PVID insertion, 802.1Q\n 719:\t\t * accept, non-8021Q TPID handling, discard).\n 720:\t\t * Block sized to exactly fit the finals -- no per-VID\n 721:\t\t * ingress EVLAN rules are needed. (7 entries.)\n 722:\t\t * Egress: 2 rules per VID that needs tag stripping (untagged VIDs).\n 723:\t\t * No egress final catchalls -- VLAN Filter does the discard.\n 724:\t\t * CPU: EVLAN is left disabled on CPU ports -- frames pass\n 725:\t\t * through without EVLAN processing.\n 726:\t\t *\n 727:\t\t * Total EVLAN budget:\n 728:\t\t * n_user_ports * (ingress + egress) \u003c= 1024.\n 729:\t\t * Ingress blocks are small (7 entries), so almost all capacity\n 730:\t\t * goes to egress VID rules.\n 731:\t\t */\n 732:\t\tdsa_switch_for_each_user_port(dp, ds)\n 733:\t\t\tn_user_ports++;\n 734:\t\n 735:\t\tif (n_user_ports) {\n 736:\t\t\tingress_finals = ARRAY_SIZE(ingress_aware_final);\n 737:\t\t\tvid_rules = ARRAY_SIZE(vid_accept_standard);\n 738:\t\n 739:\t\t\t/* Ingress block: fixed at finals count (7 entries) */\n 740:\t\t\tpriv-\u003eevlan_ingress_size = ingress_finals;\n 741:\t\n 742:\t\t\t/* Egress block: remaining budget divided equally among\n 743:\t\t\t * user ports. Each untagged VID needs vid_rules (2)\n 744:\t\t\t * EVLAN entries for tag stripping. Tagged-only VIDs\n 745:\t\t\t * need no EVLAN rules at all.\n 746:\t\t\t */\n 747:\t\t\tmax_vlans = (MXL862XX_TOTAL_EVLAN_ENTRIES -\n 748:\t\t\t\t n_user_ports * ingress_finals) /\n 749:\t\t\t\t (n_user_ports * vid_rules);\n 750:\t\t\tpriv-\u003eevlan_egress_size = vid_rules * max_vlans;\n 751:\t\n 752:\t\t\t/* VLAN Filter block: one per user port. The 1024-entry\n 753:\t\t\t * table is divided equally among user ports. Each port\n 754:\t\t\t * gets its own VF block for per-port VID membership --\n 755:\t\t\t * discard_unmatched_tagged handles the rest.\n 756:\t\t\t */\n 757:\t\t\tpriv-\u003evf_block_size = MXL862XX_TOTAL_VF_ENTRIES / n_user_ports;\n 758:\t\t}\n 759:\t\n 760:\t\tret = mxl862xx_setup_drop_meter(ds);\n 761:\t\tif (ret)\n 762:\t\t\treturn ret;\n 763:\t\n"
} |
| 11/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30168 output: 53 thoughts: 47 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience: **Analyzing the mxl862xx_rescue_mode_detect Function** Alright, let's take a look at this `mxl862xx_rescue_mode_detect` function. From what I see, it's designed to read some data from MDIO. Seems pretty straightforward - trying to probe a PHY, likely during some sort of recovery or boot process. The critical part is that if this MDIO read goes south, the function's going to return a negative value, which is my indicator of a failure. Makes sense; failure to communicate over MDIO to a PHY in early boot is often a showstopper. Let me go ahead and start digging into the details of the implementation to see if anything jumps out. I’m guessing it’s looking for a specific register value or bit pattern. I'll check how robust the MDIO access itself is - are there retries? Timeout considerations? Any chance of errata related to this specific silicon version? |
| 12/2 | 2026/07/26 11:53 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx-fw.c",
"FirstLine": 250,
"LineCount": 100
}Results: {
"Contents": " 250:\t/* Recover a switch whose SB PDI download was interrupted mid-transfer - the\n 251:\t * host died after MCUboot began erasing flash, whether it aborted mid erase or\n 252:\t * mid image-write, both end up in the same place: the payload receive loop.\n 253:\t * There the loader publishes STAT=0, waits for the host to write a byte-count\n 254:\t * to STAT, DMAs that many bytes and subtracts the count from a remaining-bytes\n 255:\t * counter, leaving the loop only when the counter reaches exactly zero. The\n 256:\t * image size died with the host, so we feed single-byte chunks (see\n 257:\t * MXL862XX_DRAIN_CHUNK_BYTES) to walk the counter to zero without underflow,\n 258:\t * then send END. The loader runs its (failed) checksum and either re-arms\n 259:\t * READY or, if a valid image survived in flash, boots it. Returns 0 when READY\n 260:\t * is reached (the caller can flash), 1 when the loader booted instead (the\n 261:\t * switch is recovered but the intended image was not written), \u003c0 on error.\n 262:\t * Does NOT recover a counter already underflowed by an earlier oversized-chunk\n 263:\t * attempt - that needs a power cycle.\n 264:\t */\n 265:\tstatic int mxl862xx_rescue_drain(struct mxl862xx_priv *priv)\n 266:\t{\n 267:\t\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n 268:\t\t/* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */\n 269:\t\tu32 max_chunks = 2u * (16u \u003c\u003c 20) / MXL862XX_DRAIN_CHUNK_BYTES;\n 270:\t\tu32 chunk = 0;\n 271:\t\tint ret;\n 272:\t\n 273:\t\tdev_warn(dev, \"flash: draining interrupted download\\n\");\n 274:\t\n 275:\t\twhile (chunk \u003c max_chunks) {\n 276:\t\t\t/* Teardown can interrupt this minutes-long drain. */\n 277:\t\t\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n 278:\t\t\t\treturn -ECANCELED;\n 279:\t\n 280:\t\t\tret = mxl862xx_sb_pdi_poll_drain(priv, 2000);\n 281:\t\t\tif (ret \u003c 0)\n 282:\t\t\t\treturn ret;\n 283:\t\t\tif (ret == MXL862XX_DRAIN_READY)\n 284:\t\t\t\treturn 0;\n 285:\t\t\tif (ret == MXL862XX_DRAIN_BUSY)\n 286:\t\t\t\tbreak;\n 287:\t\n 288:\t\t\t/* Feed one zero byte; reset cleared the write latch. */\n 289:\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n 290:\t\t\t\t\t\t MXL862XX_SB_PDI_CTRL_WR);\n 291:\t\t\tif (ret \u003c 0)\n 292:\t\t\t\treturn ret;\n 293:\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);\n 294:\t\t\tif (ret \u003c 0)\n 295:\t\t\t\treturn ret;\n 296:\t\t\tret = mxl862xx_sb_pdi_reset(priv);\n 297:\t\t\tif (ret \u003c 0)\n 298:\t\t\t\treturn ret;\n 299:\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n 300:\t\t\t\t\t\t MXL862XX_DRAIN_CHUNK_BYTES);\n 301:\t\t\tif (ret \u003c 0)\n 302:\t\t\t\treturn ret;\n 303:\t\t\tchunk++;\n 304:\t\t\tcond_resched();\n 305:\t\t}\n 306:\t\n 307:\t\tif (chunk \u003e= max_chunks) {\n 308:\t\t\tdev_err(dev,\n 309:\t\t\t\t\"flash: interrupted download did not drain after %u chunks\\n\",\n 310:\t\t\t\tchunk);\n 311:\t\t\treturn -ETIMEDOUT;\n 312:\t\t}\n 313:\t\n 314:\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n 315:\t\t\t\t\t MXL862XX_SB_PDI_END);\n 316:\t\tif (ret \u003c 0)\n 317:\t\t\treturn ret;\n 318:\t\n 319:\t\treturn mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n 320:\t\t\t\t\t\t MXL862XX_FW_ACK_TIMEOUT_MS);\n 321:\t}\n 322:\t\n 323:\t/* Background self-heal: drain a wedged download back to READY off the devlink\n 324:\t * flash path, so the minutes-long recovery never holds the devlink lock.\n 325:\t * Scheduled from probe; publishes rescue_ready on success.\n 326:\t */\n 327:\tvoid mxl862xx_rescue_heal_work_fn(struct work_struct *work)\n 328:\t{\n 329:\t\tstruct mxl862xx_priv *priv =\n 330:\t\t\tcontainer_of(work, struct mxl862xx_priv, rescue_heal_work);\n 331:\t\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n 332:\t\tint ret;\n 333:\t\n 334:\t\tdev_info(dev, \"recovering interrupted download in background\\n\");\n 335:\t\tret = mxl862xx_rescue_drain(priv);\n 336:\t\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n 337:\t\t\treturn;\n 338:\t\tif (!ret) {\n 339:\t\t\tWRITE_ONCE(priv-\u003erescue_ready, true);\n 340:\t\t\tdev_info(dev, \"recovery complete, ready to flash\\n\");\n 341:\t\t}\n 342:\t}\n 343:\t\n 344:\t/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller can rule\n 345:\t * the loader out before any C45 API request (which spews CRC errors when no WSP\n 346:\t * firmware answers). A scratch write to ADDR/DATA must latch or the chip is\n 347:\t * absent (-ENODEV); STAT then classifies the state, poked destructively only\n 348:\t * when 0, the one value a running firmware never holds:\n 349:\t *\n"
} |
| 13/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32365 output: 53 thoughts: 21 Thoughts: Okay, let's dive into this. Here's my take on understanding this `mxl862xx_rescue_mode_detect` function and what's probably going through my head: **Analyzing `mxl862xx_rescue_mode_detect`** Alright, I'm digging into this function, `mxl862xx_rescue_mode_detect`. My immediate thought is, "Okay, this is about identifying if the system is in some kind of recovery or rescue state. Probably a critical function, given the name." I need to understand the entry point so I can understand what signals or hardware characteristics would indicate a 'rescue mode'. I'm starting to think about what sort of hardware is running this and what failure states can happen. Is this for a bootloader? A specific chip? Does it use GPIO pins, memory addresses, or a specific register configuration to determine the mode? What are the possible triggers for rescue mode? Power-on sequence? Specific key presses? Error flags from a previous boot attempt? I need to look for those hints. I'm going to start mentally tracing execution. What are the checks happening inside the function? What are the logical conditions and branching? I'm anticipating that there'll be conditional statements, checking various parameters. Are there specific register reads? If so, which ones and what are the expected values? Any specific memory locations being examined? What's the impact of each of the conditions, is a rescue mode indicated by a return value, or a side-effect? I'm also considering the implications of a positive detection. Does it halt execution, modify boot parameters, or perhaps initiate a different boot sequence or attempt a chip recovery? I need to understand the broader context. What other functions call this? What does the rest of the module look like? I need to see the dependencies, especially what's happening *after* this function. Context is crucial. Ultimately, I'm trying to build a solid mental model of how this function works, so I can confidently determine when it's being used and in what context. It looks like it is rescue mode detection, I want to understand how it gets into rescue mode and out of it! |
| 14/2 | 2026/07/26 11:53 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx-fw.c",
"FirstLine": 350,
"LineCount": 100
}Results: {
"Contents": " 350:\t * - 0xc33c: flashless loop, ready.\n 351:\t * - 0xc55c: console loop, if the register-read challenge is serviced.\n 352:\t * - other non-zero: running firmware, left unpoked.\n 353:\t * - 0: wedged receive loop, if a 1-byte slice-advance drains back to 0.\n 354:\t *\n 355:\t * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, or negative (-ENODEV/SMDIO).\n 356:\t */\n 357:\tint mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)\n 358:\t{\n 359:\t\tint stat, dat, ret, rb, a, d;\n 360:\t\n 361:\t\t/* rescue_ready gates flashing; a wedged loader needs the drain first. */\n 362:\t\tpriv-\u003erescue_ready = false;\n 363:\t\n 364:\t\t/* Presence: a live chip latches the scratch write, an absent one floats. */\n 365:\t\ta = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n 366:\t\t\t\t\t MXL862XX_SB_PDI_PROBE_A);\n 367:\t\tif (a \u003c 0)\n 368:\t\t\treturn a;\n 369:\t\td = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n 370:\t\t\t\t\t MXL862XX_SB_PDI_PROBE_D);\n 371:\t\tif (d \u003c 0)\n 372:\t\t\treturn d;\n 373:\t\ta = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_ADDR);\n 374:\t\tif (a \u003c 0)\n 375:\t\t\treturn a;\n 376:\t\td = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);\n 377:\t\tif (d \u003c 0)\n 378:\t\t\treturn d;\n 379:\t\tif ((u16)a != MXL862XX_SB_PDI_PROBE_A ||\n 380:\t\t (u16)d != MXL862XX_SB_PDI_PROBE_D)\n 381:\t\t\treturn -ENODEV;\n 382:\t\n 383:\t\tret = mxl862xx_sb_pdi_reset(priv);\n 384:\t\tif (ret \u003c 0)\n 385:\t\t\treturn ret;\n 386:\t\n 387:\t\tstat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);\n 388:\t\tif (stat \u003c 0)\n 389:\t\t\treturn stat;\n 390:\t\n 391:\t\t/* Flashless-download loop: unambiguous ready magic, leave it alone. */\n 392:\t\tif ((u16)stat == MXL862XX_SB_PDI_DL_READY) {\n 393:\t\t\tpriv-\u003erescue_ready = true;\n 394:\t\t\treturn MXL862XX_IN_RESCUE;\n 395:\t\t}\n 396:\t\n 397:\t\t/* Console loop at READY: confirm the live mailbox with the register-read\n 398:\t\t * challenge (consumes the marker from DATA and re-arms READY).\n 399:\t\t */\n 400:\t\tif ((u16)stat == MXL862XX_SB_PDI_READY) {\n 401:\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n 402:\t\t\t\t\t\t MXL862XX_SB_PDI_RDREG_MARK);\n 403:\t\t\tif (ret \u003c 0)\n 404:\t\t\t\treturn ret;\n 405:\t\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n 406:\t\t\t\t\t\t MXL862XX_SB_PDI_RDREG);\n 407:\t\t\tif (ret \u003c 0)\n 408:\t\t\t\treturn ret;\n 409:\t\t\trb = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n 410:\t\t\t\t\t\t MXL862XX_RESCUE_READY_TIMEOUT_MS);\n 411:\t\t\tdat = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA);\n 412:\t\t\tif (dat \u003c 0)\n 413:\t\t\t\treturn dat;\n 414:\t\t\tmxl862xx_sb_pdi_reset(priv);\n 415:\t\t\tif (!rb \u0026\u0026 (u16)dat != MXL862XX_SB_PDI_RDREG_MARK) {\n 416:\t\t\t\tpriv-\u003erescue_ready = true;\n 417:\t\t\t\treturn MXL862XX_IN_RESCUE;\n 418:\t\t\t}\n 419:\t\t\treturn -ENODEV;\n 420:\t\t}\n 421:\t\n 422:\t\t/* Any other non-zero value is a running firmware, not a loader. */\n 423:\t\tif (stat)\n 424:\t\t\treturn MXL862XX_NOT_RESCUE;\n 425:\t\n 426:\t\t/* STAT == 0: a wedged receive loop consumes a 1-byte slice-advance back\n 427:\t\t * to 0 (feed one DATA word first, like a drain chunk).\n 428:\t\t */\n 429:\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n 430:\t\t\t\t\t MXL862XX_SB_PDI_CTRL_WR);\n 431:\t\tif (ret \u003c 0)\n 432:\t\t\treturn ret;\n 433:\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000);\n 434:\t\tif (ret \u003c 0)\n 435:\t\t\treturn ret;\n 436:\t\tret = mxl862xx_sb_pdi_reset(priv);\n 437:\t\tif (ret \u003c 0)\n 438:\t\t\treturn ret;\n 439:\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, 1);\n 440:\t\tif (ret \u003c 0)\n 441:\t\t\treturn ret;\n 442:\t\trb = mxl862xx_sb_pdi_poll_stat(priv, 0, MXL862XX_RESCUE_READY_TIMEOUT_MS);\n 443:\t\tif (!rb)\n 444:\t\t\treturn MXL862XX_IN_RESCUE;\n 445:\t\n 446:\t\treturn -ENODEV;\n 447:\t}\n 448:\t\n 449:\t/* Post-flash reprobe. Runs from a self-contained heap work (not a kthread and\n"
} |
| 15/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34884 output: 27 thoughts: 857 Thoughts: **Analyzing the Fuzzing Potential of the MxL862xx Driver** Okay, so the probe function of this `mxl862xx` driver calls `mxl862xx_rescue_mode_detect`. It's writing to MDIO and reading back – a pretty standard check. In a virtualized environment, a dummy MDIO bus might return garbage, like `0xffff` or `0x0000`, or even echo back what was written. But hold on, does Syzkaller *actually* have a generic, value-storing dummy MDIO bus? No, not that I'm aware of. Does Syzkaller fuzz MDIO devices directly with injected reads/writes? Again, seems unlikely without a dedicated Syzkaller pseudo-syscall or a special dummy driver. Alright, let's see if there's any evidence of MDIO fuzzing within Syzkaller itself. I can check, but `drivers/net/phy/mdio-mock.c` is probably not what I'm looking for. Let's check the Syzkaller configs and see if `CONFIG_NET_DSA_MXL862` is enabled. Ah, the patch author is "syz-cluster," so Syzkaller *is* testing this! If it's being tested, it implies *some* level of reachability, or at least an attempt at reachability. But wait, the instructions say to return `WorthFuzzing=false` if it’s a vendor-specific driver requiring hardware not emulated in standard QEMU. This *is* a DSA switch driver for a MaxLinear MxL862xx. Does it require physical hardware? Yeah, QEMU definitely doesn't emulate this specific chip. So, is there any way to reach this code via software emulation somehow? No, no QEMU emulation exists for it. Is there any dummy MDIO bus in the kernel used by Syzkaller? There is `drivers/net/phy/mdio-bcm-unimac.c` but it seems to be for another type of device. There is `drivers/net/phy/fixed_phy.c` but that is for PHYs. Okay, let's think about the Syzkaller ecosystem. It's got `vhci` for USB, `mac80211_hwsim` for Wi-Fi, `netdevsim` for netdevs… Does it have an equivalent for MDIO? I need to figure out what kind of driver `mxl862xx` is. It's an MDIO driver. To probe an MDIO driver, you need an MDIO device on an MDIO bus. So how can we create an MDIO device from userspace? Hmm, unless there's some device tree magic, a specific sysfs trigger, or maybe `netdevsim`, there is no way. No, `netdevsim` doesn't create MDIO buses. `dsa_loop` is a dummy DSA switch, but it is a specific driver. Can Syzkaller *actually* reach `mxl862xx_probe`? If it can't, then there is no point in fuzzing. Let me check if there is any MDIO device creation in Syzkaller. |
| 16/2 | 2026/07/26 11:53 | tool | grepper |
0mArgs:{
"Expression": "mdio",
"PathPrefix": "net/dsa/"
}Results: {
"Output": "net/dsa/dsa.c=622=static int dsa_switch_setup(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-654-\tif (!ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read) {\nnet/dsa/dsa.c:655:\t\tds-\u003euser_mii_bus = mdiobus_alloc();\nnet/dsa/dsa.c-656-\t\tif (!ds-\u003euser_mii_bus) {\n--\nnet/dsa/dsa.c-662-\nnet/dsa/dsa.c:663:\t\terr = mdiobus_register(ds-\u003euser_mii_bus);\nnet/dsa/dsa.c-664-\t\tif (err \u003c 0)\n--\nnet/dsa/dsa.c-674-\tif (ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read)\nnet/dsa/dsa.c:675:\t\tmdiobus_free(ds-\u003euser_mii_bus);\nnet/dsa/dsa.c-676-teardown:\n--\nnet/dsa/dsa.c=686=static void dsa_switch_teardown(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-693-\tif (ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read) {\nnet/dsa/dsa.c:694:\t\tmdiobus_unregister(ds-\u003euser_mii_bus);\nnet/dsa/dsa.c:695:\t\tmdiobus_free(ds-\u003euser_mii_bus);\nnet/dsa/dsa.c-696-\t\tds-\u003euser_mii_bus = NULL;\n--\nnet/dsa/port.c-11-#include \u003clinux/notifier.h\u003e\nnet/dsa/port.c:12:#include \u003clinux/of_mdio.h\u003e\nnet/dsa/port.c-13-#include \u003clinux/of_net.h\u003e\n--\nnet/dsa/port.c=1726=static const char * const dsa_switches_apply_workarounds[] = {\n--\nnet/dsa/port.c-1796-#if IS_ENABLED(CONFIG_NET_DSA_SMSC_LAN9303_MDIO)\nnet/dsa/port.c:1797:\t\"smsc,lan9303-mdio\",\nnet/dsa/port.c-1798-#endif\n--\nnet/dsa/user.c-13-#include \u003clinux/of_net.h\u003e\nnet/dsa/user.c:14:#include \u003clinux/of_mdio.h\u003e\nnet/dsa/user.c:15:#include \u003clinux/mdio.h\u003e\nnet/dsa/user.c-16-#include \u003cnet/netdev_lock.h\u003e\n--\nnet/dsa/user.c=2638=static int dsa_user_phy_connect(struct net_device *user_dev, int addr,\n--\nnet/dsa/user.c-2643-\nnet/dsa/user.c:2644:\tuser_dev-\u003ephydev = mdiobus_get_phy(ds-\u003euser_mii_bus, addr);\nnet/dsa/user.c-2645-\tif (!user_dev-\u003ephydev) {\n"
} |
| 17/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 37337 output: 35 thoughts: 122 Thoughts: **MDIO Device Instantiation Conundrum** Okay, so I need to figure out how to handle this `mxl862xx` device. First things first, I'll search for `mxl862xx` within the `Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml` file to get the lay of the land. Right, it's an MDIO device, as expected. Now, here's the kicker: Can I instantiate MDIO devices directly from userspace? I know with I2C, it's possible through the `sysfs` interface. Ah, yes, I'm thinking about using I2C/MDIO device instantiation via sysfs. I had a quick look but now I remember MDIO buses don't have that handy `new_device` sysfs attribute like I2C does. Dang it. Okay, time to double-check my assumptions. Let me quickly verify if the MDIO bus even exposes a `new_device` attribute; I'll need to check the kernel's MDIO bus implementation. |
| 18/2 | 2026/07/26 11:53 | tool | grepper |
0mError:no matches Args: {
"Expression": "new_device",
"PathPrefix": "drivers/net/phy/mdio_bus.c"
}Results: {
"error": "no matches"
} |
| 19/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 37750 output: 23 thoughts: 693 Thoughts: **Analyzing the MDIO Driver and Syzkaller's Potential** Okay, so I'm looking at this MDIO bus driver, and I don't see any explicit instantiation of a "new_device" within the `mdio_bus.c` code itself. This is making me think about how syzkaller can even get to this driver and trigger its probe function. I know syzkaller can use device tree overlays, so that's where my mind goes immediately. Wait, yes! Syzkaller *does* support device tree overlays. I think I vaguely remember a pseudo-syscall like `syz_init_net_socket`... no, that's not right. Ah, it's `syz_mount_image`, and it can load overlays *if* the kernel is built with `CONFIG_OF_OVERLAY` enabled. So, the question becomes, can syzkaller use these overlays to instantiate MDIO devices? Yes, that's it! If it loads a DT overlay, it *should* be able to create an MDIO device. But... even if the device *is* created, the driver's probe function will still need to communicate with the hardware via the MDIO bus. If there's no real hardware connected, the MDIO read/write operations will likely fail or return some default value, like `0xffff` from a floating bus or `0` from a dummy bus. Let me circle back to that `mxl862xx_rescue_mode_detect` function in the driver. Okay, I see it now. It writes `0x5a5a` to `MXL862XX_SB_PDI_ADDR` and `0xa5a5` to `MXL862XX_SB_PDI_DATA`, and then reads those values back. If they don't match, it returns `-ENODEV`. ```c 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; ``` In the absence of real hardware, the MDIO read is *unlikely* to return `0x5a5a` or `0xa5a5`. It'll probably be `0xffff` (bus floating) or `0` (dummy bus). The key here is whether the kernel includes a *special* dummy MDIO bus implementation that can store written values. This is crucial. So, the next step is to check if there is a "mock" MDIO bus driver, perhaps something like `drivers/net/phy/mdio-mock.c`, or something similar that simulates MDIO interactions. If such a thing exists and gets loaded with syzkaller, then the driver may trigger! |
| 20/2 | 2026/07/26 11:53 | tool | codesearch-dir-index |
0mArgs:{
"Dir": "drivers/net/phy"
}Results: {
"Files": [
"adin.c",
"adin1100.c",
"adin1140-phy.c",
"air_an8801.c",
"air_en8811h.c",
"air_phy_lib.c",
"air_phy_lib.h",
"amd.c",
"as21xxx.c",
"ax88796b.c",
"ax88796b_rust.rs",
"bcm-cygnus.c",
"bcm-phy-lib.c",
"bcm-phy-lib.h",
"bcm-phy-ptp.c",
"bcm54140.c",
"bcm63xx.c",
"bcm7xxx.c",
"bcm84881.c",
"bcm87xx.c",
"broadcom.c",
"cicada.c",
"cortina.c",
"davicom.c",
"dp83640.c",
"dp83640_reg.h",
"dp83822.c",
"dp83848.c",
"dp83867.c",
"dp83869.c",
"dp83tc811.c",
"dp83td510.c",
"dp83tg720.c",
"et1011c.c",
"fixed_phy.c",
"icplus.c",
"intel-xway.c",
"linkmode.c",
"lxt.c",
"marvell-88q2xxx.c",
"marvell-88x2222.c",
"marvell.c",
"marvell10g.c",
"mdio-open-alliance.h",
"mdio_bus.c",
"mdio_bus_provider.c",
"mdio_device.c",
"mdio_devres.c",
"meson-gxl.c",
"micrel.c",
"microchip.c",
"microchip_rds_ptp.c",
"microchip_rds_ptp.h",
"microchip_t1.c",
"microchip_t1s.c",
"mii_timestamper.c",
"motorcomm.c",
"mxl-86110.c",
"mxl-gpy.c",
"national.c",
"ncn26000.c",
"nxp-c45-tja11xx-macsec.c",
"nxp-c45-tja11xx.c",
"nxp-c45-tja11xx.h",
"nxp-cbtx.c",
"nxp-tja11xx.c",
"open_alliance_helpers.c",
"open_alliance_helpers.h",
"phy-c45.c",
"phy-caps.h",
"phy-core.c",
"phy.c",
"phy_caps.c",
"phy_device.c",
"phy_led_triggers.c",
"phy_link_topology.c",
"phy_package.c",
"phy_port.c",
"phylib-internal.h",
"phylib.h",
"phylink.c",
"qsemi.c",
"qt2025.rs",
"rockchip.c",
"sfp-bus.c",
"sfp.c",
"sfp.h",
"smsc.c",
"ste10Xp.c",
"stubs.c",
"swphy.c",
"swphy.h",
"teranetics.c",
"uPD60620.c",
"vitesse.c",
"xilinx_gmii2rgmii.c"
],
"Subdirs": [
"aquantia",
"mediatek",
"mscc",
"qcom",
"realtek"
]
} |
| 21/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40304 output: 24 thoughts: 38 Thoughts: Okay, here's my interpretation of those thoughts, summarized from an expert's perspective, presented as first-person internal monologue: **Investigating MDIO Driver Availability** Hmm, no `mdio-mock.c`... that's unexpected. I was just expecting to see that as a quick debugging option. Wait a minute... hold on, what's this? There's `mdio-bcm-unimac.c` in `drivers/net/mdio/`? Okay, that changes things. This implies a Broadcom Unimac MDIO implementation is available, which is a key piece of information. This isn't just a generic MDIO implementation, this is tied to specific hardware. That potentially saves me a lot of time if I'm working with a Broadcom Unimac based setup. I need to investigate that file further. |
| 22/2 | 2026/07/26 11:53 | tool | codesearch-dir-index |
0mArgs:{
"Dir": "drivers/net/mdio"
}Results: {
"Files": [
"acpi_mdio.c",
"fwnode_mdio.c",
"mdio-airoha.c",
"mdio-aspeed.c",
"mdio-bcm-iproc.c",
"mdio-bcm-unimac.c",
"mdio-bitbang.c",
"mdio-cavium.c",
"mdio-cavium.h",
"mdio-gpio.c",
"mdio-hisi-femac.c",
"mdio-i2c.c",
"mdio-ipq4019.c",
"mdio-ipq8064.c",
"mdio-moxart.c",
"mdio-mscc-miim.c",
"mdio-mux-bcm-iproc.c",
"mdio-mux-bcm6368.c",
"mdio-mux-gpio.c",
"mdio-mux-meson-g12a.c",
"mdio-mux-meson-gxl.c",
"mdio-mux-mmioreg.c",
"mdio-mux-multiplexer.c",
"mdio-mux.c",
"mdio-mvusb.c",
"mdio-octeon.c",
"mdio-pic64hpsc.c",
"mdio-realtek-rtl9300.c",
"mdio-regmap.c",
"mdio-sun4i.c",
"mdio-thunder.c",
"mdio-xgene.c",
"of_mdio.c"
],
"Subdirs": null
} |
| 23/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40951 output: 50 thoughts: 381 Thoughts: **Analyzing the mxl862xx Driver for Potential Vulnerabilities** Okay, so I'm diving into this `mxl862xx` code, and the immediate thought is: are these MDIO buses being used in a straightforward way, or are there more complex interactions going on? I need to clarify that none of them act like dummy buses just storing values. I see mentions of `mdio-regmap.c` and `mdio-bitbang.c`, so I should keep those in mind as possible points of interest, but don't need to consider them primary. I'm thinking about how syzkaller is fuzzing this and suddenly, I'm stuck on `mxl862xx_rescue_mode_detect`. How is syzkaller *actually* getting past that? Wait, maybe it's *not* passing it. If it doesn't pass it, the probe function is supposed to return `-ENODEV`, which means the driver isn't registered. If the driver isn't registered, we never even get to `devlink flash update` code, which is obviously important to consider. Now, my line of thought moves to: Is there *any* way to reach `mxl862xx_devlink_flash_update` without the driver successfully probing? Nope, it's part of `mxl862xx_switch_ops`, which is registered via `dsa_register_switch(ds)` in `mxl862xx_probe`. So if `mxl862xx_probe` fails, `dsa_register_switch` isn't called, and the devlink operations are not registered. Okay, back to fundamentals. `mxl862xx_probe` calls `dsa_register_switch(ds)`, which in turn calls `dsa_switch_setup(ds)`. That one calls `ds->ops->setup(ds)`. So the `setup` op is `mxl862xx_setup`. Ah! The call to `mxl862xx_rescue_mode_detect` is happening from `mxl862xx_setup`, *not* from `mxl862xx_probe`! This is important! Now I just need to verify that I'm correct in that assessment, and then I'll understand what is actually happening. |
| 24/2 | 2026/07/26 11:53 | tool | grepper |
0mArgs:{
"Expression": "mxl862xx_setup",
"PathPrefix": "drivers/net/dsa/mxl862xx/mxl862xx.c"
}Results: {
"Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=262=static int mxl862xx_wait_ready(struct dsa_switch *ds)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-308-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:309:static int mxl862xx_setup_mdio(struct dsa_switch *ds)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-310-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=341=static int mxl862xx_bridge_config_fwd(struct dsa_switch *ds, u16 bridge_id,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-382- */\ndrivers/net/dsa/mxl862xx/mxl862xx.c:383:static int mxl862xx_setup_drop_meter(struct dsa_switch *ds)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-384-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=641=static void mxl862xx_free_bridge(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-660-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:661:static int mxl862xx_setup(struct dsa_switch *ds)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-662-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-712-\tfor (i = 0; i \u003c ARRAY_SIZE(priv-\u003eserdes_ports); i++)\ndrivers/net/dsa/mxl862xx/mxl862xx.c:713:\t\tmxl862xx_setup_pcs(priv, \u0026priv-\u003eserdes_ports[i],\ndrivers/net/dsa/mxl862xx/mxl862xx.c-714-\t\t\t\t i + MXL862XX_FIRST_SERDES_PORT);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-759-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:760:\tret = mxl862xx_setup_drop_meter(ds);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-761-\tif (ret)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-766-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:767:\treturn mxl862xx_setup_mdio(ds);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-768-}\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=1277=static int mxl862xx_port_vlan_del(struct dsa_switch *ds, int port,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-1344-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:1345:static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)\ndrivers/net/dsa/mxl862xx/mxl862xx.c-1346-{\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=1419=static int mxl862xx_port_setup(struct dsa_switch *ds, int port)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-1455-\tif (is_cpu_port)\ndrivers/net/dsa/mxl862xx/mxl862xx.c:1456:\t\treturn mxl862xx_setup_cpu_bridge(ds, port);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-1457-\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2157=static const struct dsa_switch_ops mxl862xx_switch_ops = {\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2158-\t.get_tag_protocol = mxl862xx_get_tag_protocol,\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2159:\t.setup = mxl862xx_setup,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2160-\t.port_setup = mxl862xx_port_setup,\n"
} |
| 25/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43286 output: 34 thoughts: 136 Thoughts: **Debugging the MXL862XX Driver Probe Failure** Okay, so I'm tracking down this probe failure in the MXL862XX driver. Right, `mxl862xx_setup` is clearly the `setup` operation we're interested in, that's straightforward. Now, let's see, `dsa_register_switch` calls `dsa_switch_setup`, which in turn, invokes `ds->ops->setup` – precisely the hook to our `mxl862xx_setup` function. The flow makes sense: a failure in `setup` means `dsa_register_switch` will fail, and if *that* fails, `mxl862xx_probe` is bound to fail. But wait a minute... I just realized something I hadn't considered. Isn't `dsa_switch_devlink_alloc` *before* the call to setup? That changes things; there's a devlink allocated before our `setup` operation? Okay, I should really check the source to be sure. I'm going to jump into the `net/dsa` code - either `dsa.c` or the devlink module, `devlink.c` - to see if that's where the problem lies. |
| 26/2 | 2026/07/26 11:53 | tool | grepper |
0mArgs:{
"Expression": "dsa_switch_devlink_alloc",
"PathPrefix": "net/dsa/"
}Results: {
"Output": "net/dsa/devlink.c=395=void dsa_switch_devlink_unregister(struct dsa_switch *ds)\n--\nnet/dsa/devlink.c-399-\nnet/dsa/devlink.c:400:int dsa_switch_devlink_alloc(struct dsa_switch *ds)\nnet/dsa/devlink.c-401-{\n--\nnet/dsa/devlink.h=12=void dsa_switch_devlink_unregister(struct dsa_switch *ds);\nnet/dsa/devlink.h:13:int dsa_switch_devlink_alloc(struct dsa_switch *ds);\nnet/dsa/devlink.h-14-void dsa_switch_devlink_free(struct dsa_switch *ds);\n--\nnet/dsa/dsa.c=622=static int dsa_switch_setup(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-635-\nnet/dsa/dsa.c:636:\terr = dsa_switch_devlink_alloc(ds);\nnet/dsa/dsa.c-637-\tif (err)\n"
} |
| 27/2 | 2026/07/26 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 44050 output: 38 thoughts: 45 Thoughts: Okay, here's my take on those thoughts, framed from a more experienced perspective: **Initial Assessment of Device Link Allocation** Right, so `dsa_switch_devlink_alloc` is being called *before* the typical `setup` routine. That immediately flags a potential area of optimization or a different initialization flow than I'm used to. We need to understand the intent behind this seemingly early allocation. Let's delve into the actual implementation of `dsa_switch_setup` within `net/dsa/dsa.c`. It is imperative to know *why* we're allocating so early and what dependencies or constraints that places on subsequent operations. This could be about resource pre-allocation, hardware discovery, or a specific handling of the devlink structure. |
| 28/2 | 2026/07/26 11:54 | tool | read-file |
0mArgs:{
"File": "net/dsa/dsa.c",
"FirstLine": 622,
"LineCount": 100
}Results: {
"Contents": " 622:\tstatic int dsa_switch_setup(struct dsa_switch *ds)\n 623:\t{\n 624:\t\tint err;\n 625:\t\n 626:\t\tif (ds-\u003esetup)\n 627:\t\t\treturn 0;\n 628:\t\n 629:\t\t/* Initialize ds-\u003ephys_mii_mask before registering the user MDIO bus\n 630:\t\t * driver and before ops-\u003esetup() has run, since the switch drivers and\n 631:\t\t * the user MDIO bus driver rely on these values for probing PHY\n 632:\t\t * devices or not\n 633:\t\t */\n 634:\t\tds-\u003ephys_mii_mask |= dsa_user_ports(ds);\n 635:\t\n 636:\t\terr = dsa_switch_devlink_alloc(ds);\n 637:\t\tif (err)\n 638:\t\t\treturn err;\n 639:\t\n 640:\t\terr = dsa_switch_register_notifier(ds);\n 641:\t\tif (err)\n 642:\t\t\tgoto devlink_free;\n 643:\t\n 644:\t\tds-\u003econfigure_vlan_while_not_filtering = true;\n 645:\t\n 646:\t\terr = ds-\u003eops-\u003esetup(ds);\n 647:\t\tif (err \u003c 0)\n 648:\t\t\tgoto unregister_notifier;\n 649:\t\n 650:\t\terr = dsa_switch_setup_tag_protocol(ds);\n 651:\t\tif (err)\n 652:\t\t\tgoto teardown;\n 653:\t\n 654:\t\tif (!ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read) {\n 655:\t\t\tds-\u003euser_mii_bus = mdiobus_alloc();\n 656:\t\t\tif (!ds-\u003euser_mii_bus) {\n 657:\t\t\t\terr = -ENOMEM;\n 658:\t\t\t\tgoto teardown;\n 659:\t\t\t}\n 660:\t\n 661:\t\t\tdsa_user_mii_bus_init(ds);\n 662:\t\n 663:\t\t\terr = mdiobus_register(ds-\u003euser_mii_bus);\n 664:\t\t\tif (err \u003c 0)\n 665:\t\t\t\tgoto free_user_mii_bus;\n 666:\t\t}\n 667:\t\n 668:\t\tdsa_switch_devlink_register(ds);\n 669:\t\n 670:\t\tds-\u003esetup = true;\n 671:\t\treturn 0;\n 672:\t\n 673:\tfree_user_mii_bus:\n 674:\t\tif (ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read)\n 675:\t\t\tmdiobus_free(ds-\u003euser_mii_bus);\n 676:\tteardown:\n 677:\t\tif (ds-\u003eops-\u003eteardown)\n 678:\t\t\tds-\u003eops-\u003eteardown(ds);\n 679:\tunregister_notifier:\n 680:\t\tdsa_switch_unregister_notifier(ds);\n 681:\tdevlink_free:\n 682:\t\tdsa_switch_devlink_free(ds);\n 683:\t\treturn err;\n 684:\t}\n 685:\t\n 686:\tstatic void dsa_switch_teardown(struct dsa_switch *ds)\n 687:\t{\n 688:\t\tif (!ds-\u003esetup)\n 689:\t\t\treturn;\n 690:\t\n 691:\t\tdsa_switch_devlink_unregister(ds);\n 692:\t\n 693:\t\tif (ds-\u003euser_mii_bus \u0026\u0026 ds-\u003eops-\u003ephy_read) {\n 694:\t\t\tmdiobus_unregister(ds-\u003euser_mii_bus);\n 695:\t\t\tmdiobus_free(ds-\u003euser_mii_bus);\n 696:\t\t\tds-\u003euser_mii_bus = NULL;\n 697:\t\t}\n 698:\t\n 699:\t\tdsa_switch_teardown_tag_protocol(ds);\n 700:\t\n 701:\t\tif (ds-\u003eops-\u003eteardown)\n 702:\t\t\tds-\u003eops-\u003eteardown(ds);\n 703:\t\n 704:\t\tdsa_switch_unregister_notifier(ds);\n 705:\t\n 706:\t\tdsa_switch_devlink_free(ds);\n 707:\t\n 708:\t\tds-\u003esetup = false;\n 709:\t}\n 710:\t\n 711:\t/* First tear down the non-shared, then the shared ports. This ensures that\n 712:\t * all work items scheduled by our switchdev handlers for user ports have\n 713:\t * completed before we destroy the refcounting kept on the shared ports.\n 714:\t */\n 715:\tstatic void dsa_tree_teardown_ports(struct dsa_switch_tree *dst)\n 716:\t{\n 717:\t\tstruct dsa_port *dp;\n 718:\t\n 719:\t\tlist_for_each_entry(dp, \u0026dst-\u003eports, list)\n 720:\t\t\tif (dsa_port_is_user(dp) || dsa_port_is_unused(dp))\n 721:\t\t\t\tdsa_port_teardown(dp);\n"
} |
| 29/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45732 output: 34 thoughts: 106 Thoughts: **My Analysis of Device Registration and Devlink Visibility** Okay, so I'm looking at this code and something's immediately jumping out at me. It seems that `dsa_switch_devlink_register(ds)` is called *after* the `ds->ops->setup(ds)` function. That's a bit unusual. Now, if the `setup` operation fails, the code cleanly unregisters the notifier and frees the device link instance with `devlink_free`. That makes sense. However, and this is the crux of it, the devlink device is NEVER registered in that failure path! This suggests a potential issue, a race condition, or just a logic gap. Wait a second, let me think this through. `devlink_alloc` handles the initial memory allocation for the devlink instance, right? But that instance isn't actually accessible to userspace, it's invisible, until the call to `devlink_register`. Aha! That confirms my suspicion. The devlink device is essentially "born" with `devlink_alloc`, but doesn't *exist* from a user perspective until registration. Alright, so this requires a deeper look, a more thorough verification. I need to dig into the details to be certain of the potential implications of this registration sequence. Let's make sure I understand the consequences of the device not getting registered in the event of an early failure. I need to be sure. |
| 30/2 | 2026/07/26 11:54 | tool | grepper |
0mArgs:{
"Expression": "dsa_switch_devlink_register",
"PathPrefix": "net/dsa/"
}Results: {
"Output": "net/dsa/devlink.c=377=void dsa_port_devlink_teardown(struct dsa_port *dp)\n--\nnet/dsa/devlink.c-389-\nnet/dsa/devlink.c:390:void dsa_switch_devlink_register(struct dsa_switch *ds)\nnet/dsa/devlink.c-391-{\n--\nnet/dsa/devlink.h=10=void dsa_port_devlink_teardown(struct dsa_port *dp);\nnet/dsa/devlink.h:11:void dsa_switch_devlink_register(struct dsa_switch *ds);\nnet/dsa/devlink.h-12-void dsa_switch_devlink_unregister(struct dsa_switch *ds);\n--\nnet/dsa/dsa.c=622=static int dsa_switch_setup(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-667-\nnet/dsa/dsa.c:668:\tdsa_switch_devlink_register(ds);\nnet/dsa/dsa.c-669-\n"
} |
| 31/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46485 output: 37 thoughts: 15 Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own: **Analyzing the `dsa_switch_devlink_register` Function** Alright, so the first thing on my mind is `dsa_switch_devlink_register`. I'm immediately curious about its purpose within the broader DSA (Distributed Switch Architecture) context. It's pretty clear from the name that this function is involved in registering something related to a switch and, specifically, how that switch interacts with devlink. I need to understand what this registration entails. I'm thinking, is this function responsible for: * Establishing the initial devlink object for the switch? * Linking the DSA switch driver with the devlink core? * Setting up specific devlink resources or configurations required by the switch? * Handling the lifecycle management of the devlink integration? I'm also considering the implications. A proper devlink registration is crucial for making the switch discoverable, manageable, and interoperable within the system. Without it, we're likely missing out on critical features and performance benefits that devlink provides. We need to be able to monitor the device, manage queues, and potentially push out some firmware upgrades. I should also look into the function's arguments. What data does it take as input? Does it receive a pointer to the DSA switch device structure, or something else? Understanding the input parameters will be key to grasping how it integrates with the rest of the DSA driver and devlink infrastructure. |
| 32/2 | 2026/07/26 11:54 | tool | read-file |
0mArgs:{
"File": "net/dsa/devlink.c",
"FirstLine": 390,
"LineCount": 20
}Results: {
"Contents": " 390:\tvoid dsa_switch_devlink_register(struct dsa_switch *ds)\n 391:\t{\n 392:\t\tdevlink_register(ds-\u003edevlink);\n 393:\t}\n 394:\t\n 395:\tvoid dsa_switch_devlink_unregister(struct dsa_switch *ds)\n 396:\t{\n 397:\t\tdevlink_unregister(ds-\u003edevlink);\n 398:\t}\n 399:\t\n 400:\tint dsa_switch_devlink_alloc(struct dsa_switch *ds)\n 401:\t{\n 402:\t\tstruct dsa_devlink_priv *dl_priv;\n 403:\t\tconst struct devlink_ops *ops;\n 404:\t\tstruct devlink *dl;\n 405:\t\n 406:\t\tops = ds-\u003eops-\u003edevlink_flash_update ? \u0026dsa_devlink_flash_ops\n 407:\t\t\t\t\t\t : \u0026dsa_devlink_ops;\n 408:\t\n 409:\t\t/* Add the switch to devlink before calling setup, so that setup can\n"
} |
| 33/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47176 output: 30 thoughts: 482 Thoughts: **Analyzing the Path to `mxl862xx_devlink_flash_update`** Okay, here's the breakdown as I see it. The core issue is ensuring `mxl862xx_devlink_flash_update` can be reached. This function, and therefore the ability to update the flash, hinges on `devlink_register` being called. It's clear that if `mxl862xx_setup` fails, `devlink_register` isn't called, effectively blocking any userspace interaction, like `devlink_flash_update`. So, the path is: `mxl862xx_setup` *must* succeed. For `mxl862xx_setup` to succeed, `mxl862xx_rescue_mode_detect` has to return either `MXL862XX_IN_RESCUE` or `MXL862XX_NOT_RESCUE`. If it's the latter, then we get into the further success requirements; `mxl862xx_reset` and `mxl862xx_wait_ready` must also succeed. Remember that `mxl862xx_reset` involves MDIO writes and waiting for a bit to clear, while `mxl862xx_wait_ready` relies on reading the firmware version via the MDIO API. The tricky part comes in with the hardware detection. If there's no physical hardware, I know that `mxl862xx_rescue_mode_detect` will return `-ENODEV` because a scratch write to ADDR/DATA will fail to latch. Meaning when it reads back the values, it should be `0xffff` or `0x0000` rather than the expected `0x5a5a` and `0xa5a5`. But here's a wrinkle: I need to consider syzkaller and its environment. What if syzkaller is using a dummy MDIO bus that actually *stores* the written values? This is important because it could potentially trick `mxl862xx_rescue_mode_detect` into thinking there *is* a valid device when there isn't. So, that means I need to verify whether syzkaller implements a value-storing, mock MDIO bus implementation. Let's check. I can search the kernel code for clues. I should start by looking for `mdiobus_alloc` or `mdiobus_register` within the `drivers/net/phy/` or `drivers/net/mdio/` directories. Maybe something like `mdio-mock` or `mdio-dummy`. I've already scanned those, so let's check `drivers/net/ethernet/` next. Some ethernet drivers have their own MDIO buses, though usually tied to their specific hardware, so unlikely to be generally useful. What about `netdevsim`? Does it even implement an MDIO bus? This feels like the next logical step in this investigation. |
| 34/2 | 2026/07/26 11:54 | tool | grepper |
0mError:no matches Args: {
"Expression": "mdio",
"PathPrefix": "drivers/net/netdevsim/"
}Results: {
"error": "no matches"
} |
| 35/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48357 output: 35 thoughts: 19 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Investigating Network Device Simulation and Loopback Functionality** Alright, I'm thinking about the current state of `netdevsim`. Specifically, I was just reviewing the code and it hit me: there's no MDIO support implemented there. Okay, that's not ideal, but probably not the end of the world for the intended use cases, though it does limit its testing scope somewhat. Then my mind wandered and the question hit me: what about `dsa_loop`? I'm curious if it offers a viable alternative or complementary approach for certain testing scenarios. Does it expose the functionality to bypass the physical medium and test specific aspects of the network stack, perhaps independent of an actual MDIO setup, which is what I'm looking for here? Need to dive deeper into the `dsa_loop` code to see if it provides the level of control and isolation I'm trying to achieve. Exploring the relationship between these two, `netdevsim` and `dsa_loop` to see where the functionality overlaps, and what might be the best approach for testing these lower level functions, and the higher level functions that depend on them. |
| 36/2 | 2026/07/26 11:54 | tool | grepper |
0mArgs:{
"Expression": "mdio",
"PathPrefix": "drivers/net/dsa/dsa_loop.c"
}Results: {
"Output": "drivers/net/dsa/dsa_loop.c=74=static struct phy_device *phydevs[PHY_MAX_ADDR];\ndrivers/net/dsa/dsa_loop.c:75:static struct mdio_device *switch_mdiodev;\ndrivers/net/dsa/dsa_loop.c-76-\n--\ndrivers/net/dsa/dsa_loop.c=184=static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum)\n--\ndrivers/net/dsa/dsa_loop.c-189-\ndrivers/net/dsa/dsa_loop.c:190:\tret = mdiobus_read_nested(bus, ps-\u003eport_base + port, regnum);\ndrivers/net/dsa/dsa_loop.c-191-\tif (ret \u003c 0)\n--\ndrivers/net/dsa/dsa_loop.c=199=static int dsa_loop_phy_write(struct dsa_switch *ds, int port,\n--\ndrivers/net/dsa/dsa_loop.c-205-\ndrivers/net/dsa/dsa_loop.c:206:\tret = mdiobus_write_nested(bus, ps-\u003eport_base + port, regnum, value);\ndrivers/net/dsa/dsa_loop.c-207-\tif (ret \u003c 0)\n--\ndrivers/net/dsa/dsa_loop.c=250=static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,\n--\ndrivers/net/dsa/dsa_loop.c-263-\t/* Just do a sleeping operation to make lockdep checks effective */\ndrivers/net/dsa/dsa_loop.c:264:\tmdiobus_read(bus, ps-\u003eport_base + port, MII_BMSR);\ndrivers/net/dsa/dsa_loop.c-265-\n--\ndrivers/net/dsa/dsa_loop.c=283=static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,\n--\ndrivers/net/dsa/dsa_loop.c-292-\t/* Just do a sleeping operation to make lockdep checks effective */\ndrivers/net/dsa/dsa_loop.c:293:\tmdiobus_read(bus, ps-\u003eport_base + port, MII_BMSR);\ndrivers/net/dsa/dsa_loop.c-294-\n--\ndrivers/net/dsa/dsa_loop.c=334=static const struct dsa_switch_ops dsa_loop_driver = {\n--\ndrivers/net/dsa/dsa_loop.c-354-\ndrivers/net/dsa/dsa_loop.c:355:static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-356-{\ndrivers/net/dsa/dsa_loop.c:357:\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\ndrivers/net/dsa/dsa_loop.c-358-\tstruct dsa_loop_priv *ps;\n--\ndrivers/net/dsa/dsa_loop.c-364-\ndrivers/net/dsa/dsa_loop.c:365:\tds = devm_kzalloc(\u0026mdiodev-\u003edev, sizeof(*ds), GFP_KERNEL);\ndrivers/net/dsa/dsa_loop.c-366-\tif (!ds)\n--\ndrivers/net/dsa/dsa_loop.c-368-\ndrivers/net/dsa/dsa_loop.c:369:\tds-\u003edev = \u0026mdiodev-\u003edev;\ndrivers/net/dsa/dsa_loop.c-370-\tds-\u003enum_ports = DSA_LOOP_NUM_PORTS;\ndrivers/net/dsa/dsa_loop.c-371-\ndrivers/net/dsa/dsa_loop.c:372:\tps = devm_kzalloc(\u0026mdiodev-\u003edev, sizeof(*ps), GFP_KERNEL);\ndrivers/net/dsa/dsa_loop.c-373-\tif (!ps)\n--\ndrivers/net/dsa/dsa_loop.c-381-\ndrivers/net/dsa/dsa_loop.c:382:\tds-\u003edev = \u0026mdiodev-\u003edev;\ndrivers/net/dsa/dsa_loop.c-383-\tds-\u003eops = \u0026dsa_loop_driver;\ndrivers/net/dsa/dsa_loop.c-384-\tds-\u003epriv = ps;\ndrivers/net/dsa/dsa_loop.c:385:\tps-\u003ebus = mdiodev-\u003ebus;\ndrivers/net/dsa/dsa_loop.c-386-\ndrivers/net/dsa/dsa_loop.c:387:\tdev_set_drvdata(\u0026mdiodev-\u003edev, ds);\ndrivers/net/dsa/dsa_loop.c-388-\n--\ndrivers/net/dsa/dsa_loop.c-390-\tif (!ret)\ndrivers/net/dsa/dsa_loop.c:391:\t\tdev_info(\u0026mdiodev-\u003edev, \"%s: 0x%0x\\n\",\ndrivers/net/dsa/dsa_loop.c-392-\t\t\t pdata-\u003ename, pdata-\u003eenabled_ports);\n--\ndrivers/net/dsa/dsa_loop.c-396-\ndrivers/net/dsa/dsa_loop.c:397:static void dsa_loop_drv_remove(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-398-{\ndrivers/net/dsa/dsa_loop.c:399:\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\ndrivers/net/dsa/dsa_loop.c-400-\tstruct dsa_loop_priv *ps;\n--\ndrivers/net/dsa/dsa_loop.c-410-\ndrivers/net/dsa/dsa_loop.c:411:static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-412-{\ndrivers/net/dsa/dsa_loop.c:413:\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\ndrivers/net/dsa/dsa_loop.c-414-\n--\ndrivers/net/dsa/dsa_loop.c-419-\ndrivers/net/dsa/dsa_loop.c:420:\tdev_set_drvdata(\u0026mdiodev-\u003edev, NULL);\ndrivers/net/dsa/dsa_loop.c-421-}\ndrivers/net/dsa/dsa_loop.c-422-\ndrivers/net/dsa/dsa_loop.c:423:static struct mdio_driver dsa_loop_drv = {\ndrivers/net/dsa/dsa_loop.c:424:\t.mdiodrv.driver\t= {\ndrivers/net/dsa/dsa_loop.c-425-\t\t.name\t= \"dsa-loop\",\n--\ndrivers/net/dsa/dsa_loop.c=432=static int dsa_loop_bus_match(struct device *dev,\n--\ndrivers/net/dsa/dsa_loop.c-434-{\ndrivers/net/dsa/dsa_loop.c:435:\treturn drv == \u0026dsa_loop_drv.mdiodrv.driver;\ndrivers/net/dsa/dsa_loop.c-436-}\n--\ndrivers/net/dsa/dsa_loop.c=438=static void dsa_loop_phydevs_unregister(void)\n--\ndrivers/net/dsa/dsa_loop.c-445-\ndrivers/net/dsa/dsa_loop.c:446:static int __init dsa_loop_create_switch_mdiodev(void)\ndrivers/net/dsa/dsa_loop.c-447-{\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/dsa/dsa_loop.c-466-\ndrivers/net/dsa/dsa_loop.c:467:\tswitch_mdiodev = mdio_device_create(bus, 31);\ndrivers/net/dsa/dsa_loop.c:468:\tif (IS_ERR(switch_mdiodev))\ndrivers/net/dsa/dsa_loop.c-469-\t\tgoto out;\ndrivers/net/dsa/dsa_loop.c-470-\ndrivers/net/dsa/dsa_loop.c:471:\tswitch_mdiodev-\u003ebus_match = dsa_loop_bus_match;\ndrivers/net/dsa/dsa_loop.c:472:\tswitch_mdiodev-\u003edev.platform_data = \u0026dsa_loop_pdata;\ndrivers/net/dsa/dsa_loop.c-473-\ndrivers/net/dsa/dsa_loop.c:474:\tret = mdio_device_register(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c-475-\tif (ret)\ndrivers/net/dsa/dsa_loop.c:476:\t\tmdio_device_free(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c-477-out:\n--\ndrivers/net/dsa/dsa_loop.c=482=static int __init dsa_loop_init(void)\n--\ndrivers/net/dsa/dsa_loop.c-486-\ndrivers/net/dsa/dsa_loop.c:487:\tret = dsa_loop_create_switch_mdiodev();\ndrivers/net/dsa/dsa_loop.c-488-\tif (ret)\n--\ndrivers/net/dsa/dsa_loop.c-493-\ndrivers/net/dsa/dsa_loop.c:494:\tret = mdio_driver_register(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c-495-\tif (ret) {\ndrivers/net/dsa/dsa_loop.c-496-\t\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c:497:\t\tmdio_device_remove(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c:498:\t\tmdio_device_free(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c-499-\t}\n--\ndrivers/net/dsa/dsa_loop.c=505=static void __exit dsa_loop_exit(void)\ndrivers/net/dsa/dsa_loop.c-506-{\ndrivers/net/dsa/dsa_loop.c:507:\tmdio_driver_unregister(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c-508-\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c:509:\tmdio_device_remove(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c:510:\tmdio_device_free(switch_mdiodev);\ndrivers/net/dsa/dsa_loop.c-511-}\n"
} |
| 37/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 51743 output: 37 thoughts: 155 Thoughts: **My Assessment of the `dsa_loop` and `mxl862xx` Driver Interactions** Okay, so I'm looking at how syzkaller could potentially interact with the `dsa_loop` driver and the `mxl862xx` driver, particularly concerning their MDIO bus usage. I see that `dsa_loop` is using the `fixed-0` MDIO bus, and I understand it's creating an MDIO device there. But, `dsa_loop` is a very specialized driver. My immediate question is whether syzkaller could somehow create an MDIO device specifically for the `mxl862xx` chip on that same `fixed-0` bus. Thinking it through, the answer *should* be yes. Syzkaller is quite powerful with device tree overlays. I could specify the `compatible = "maxlinear,mxl862xx"` property and create an MDIO device on `fixed-0` or any other MDIO bus that syzkaller can reach. That's the standard approach. However, I need to pause for a second. The `fixed-0` MDIO bus, if I recall correctly, is a *fixed PHY* bus. And that implies it isn't meant to store arbitrary MDIO registers. It's a special-purpose bus with a specific, well-defined purpose, unlike a standard MDIO bus. Therefore, I need to confirm how the fixed PHY driver handles MDIO accesses. I'm going to quickly check `drivers/net/phy/fixed_phy.c` to refresh my memory on the underlying mechanics and see how that bus is implemented. I need to be sure I'm not overlooking a fundamental incompatibility here. |
| 38/2 | 2026/07/26 11:54 | tool | grepper |
0mArgs:{
"Expression": "fixed_mdio_read",
"PathPrefix": "drivers/net/phy/fixed_phy.c"
}Results: {
"Output": "drivers/net/phy/fixed_phy.c=59=EXPORT_SYMBOL_GPL(fixed_phy_change_carrier);\ndrivers/net/phy/fixed_phy.c-60-\ndrivers/net/phy/fixed_phy.c:61:static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)\ndrivers/net/phy/fixed_phy.c-62-{\n--\ndrivers/net/phy/fixed_phy.c=195=static int __init fixed_mdio_bus_init(void)\n--\ndrivers/net/phy/fixed_phy.c-204-\tfmb_mii_bus-\u003ename = \"Fixed MDIO Bus\";\ndrivers/net/phy/fixed_phy.c:205:\tfmb_mii_bus-\u003eread = \u0026fixed_mdio_read;\ndrivers/net/phy/fixed_phy.c-206-\tfmb_mii_bus-\u003ewrite = \u0026fixed_mdio_write;\n"
} |
| 39/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52561 output: 38 thoughts: 21 Thoughts: Okay, here's my take on those functions, thinking through it as you asked: **Analyzing `fixed_mdio_read` and `fixed_mdio_write`** Alright, let's dive into these `fixed_mdio` functions. My initial impression is these are likely low-level routines for directly accessing an MDIO interface, probably hard-coded for a specific hardware setup or a specific PHY address. The "fixed" in the name probably refers to the MDIO bus address, which may imply that we're dealing with a system where there's no dynamic PHY discovery or MDIO address scanning. Rather, this part of the code assumes that a specific PHY or group of PHYs is always at a known MDIO address. I'd expect `fixed_mdio_read` to take at least the PHY register address as input and return the value read from that register. The implementation details would be key here. I'd be looking for: * **Bus Access:** How is the MDIO bus actually accessed? Is it bit-banged, or does it leverage a dedicated MDIO controller? If bit-banged, I'd expect to see the low-level logic for asserting the MDC (clock) and MDIO (data) signals. A dedicated controller implies some register-level programming to initiate the read and then polling for completion. * **Error Handling:** What kind of error checking is in place? Are there timeouts? What happens if the MDIO access fails (e.g., no response from the PHY, incorrect parity)? I'd want to see robust error handling to prevent the driver from getting stuck. * **Register Selection:** Is the PHY address or the register address part of the function signature, or are they hardcoded within the function? This will give me a better understanding of the flexibility of the function. * **Data Representation:** How is the read data represented (e.g., an `uint16_t`? `uint32_t`?) What is the endianness, and how is it handled if the data is interpreted as a word? * **Clarity of Read:** Are there any bit masks used, etc? Similarly, for `fixed_mdio_write`, I'm expecting similar considerations but a data value as an additional input, and the operation is of course, a write instead of a read. I'll be looking for: * **Write Sequence:** The write sequence implementation (if bit-banged) would be the inverse of the read sequence in terms of signal transitions. * **Error Return:** What are the error states, and what are the failure conditions? I'd want to quickly assess if these functions are safe, whether they are used only under mutex, what kinds of errors they generate, and how. Understanding the interplay between these two functions will be critical in assessing the driver. |
| 40/2 | 2026/07/26 11:54 | tool | read-file |
0mArgs:{
"File": "drivers/net/phy/fixed_phy.c",
"FirstLine": 61,
"LineCount": 50
}Results: {
"Contents": " 61:\tstatic int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)\n 62:\t{\n 63:\t\tstruct fixed_phy *fp;\n 64:\t\n 65:\t\tfp = fixed_phy_find(phy_addr);\n 66:\t\tif (!fp)\n 67:\t\t\treturn 0xffff;\n 68:\t\n 69:\t\tif (fp-\u003elink_update)\n 70:\t\t\tfp-\u003elink_update(fp-\u003ephydev-\u003eattached_dev, \u0026fp-\u003estatus);\n 71:\t\n 72:\t\treturn swphy_read_reg(reg_num, \u0026fp-\u003estatus);\n 73:\t}\n 74:\t\n 75:\tstatic int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num,\n 76:\t\t\t\t u16 val)\n 77:\t{\n 78:\t\treturn 0;\n 79:\t}\n 80:\t\n 81:\t/*\n 82:\t * If something weird is required to be done with link/speed,\n 83:\t * network driver is able to assign a function to implement this.\n 84:\t * May be useful for PHY's that need to be software-driven.\n 85:\t */\n 86:\tint fixed_phy_set_link_update(struct phy_device *phydev,\n 87:\t\t\t\t int (*link_update)(struct net_device *,\n 88:\t\t\t\t\t\t\t struct fixed_phy_status *))\n 89:\t{\n 90:\t\tstruct fixed_phy *fp;\n 91:\t\n 92:\t\tif (!phydev || !phydev-\u003emdio.bus)\n 93:\t\t\treturn -EINVAL;\n 94:\t\n 95:\t\tfp = fixed_phy_find(phydev-\u003emdio.addr);\n 96:\t\tif (!fp)\n 97:\t\t\treturn -ENOENT;\n 98:\t\n 99:\t\tfp-\u003elink_update = link_update;\n 100:\t\tfp-\u003ephydev = phydev;\n 101:\t\n 102:\t\treturn 0;\n 103:\t}\n 104:\tEXPORT_SYMBOL_GPL(fixed_phy_set_link_update);\n 105:\t\n 106:\tstatic void fixed_phy_del(int phy_addr)\n 107:\t{\n 108:\t\tstruct fixed_phy *fp;\n 109:\t\n 110:\t\tfp = fixed_phy_find(phy_addr);\n"
} |
| 41/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53906 output: 35 thoughts: 1825 Thoughts: **Analysis of the MxL862xx Driver for Fuzzing Suitability** Okay, so I'm looking at this patch for the MaxLinear MxL862xx DSA switch driver. The `fixed_mdio_write` function just returns nothing, and `fixed_mdio_read` either returns `0xffff` or a value from `swphy_read_reg`. This doesn't store any values. My initial thought is that if syzkaller creates an MDIO device on `fixed-0`, the `mxl862xx_rescue_mode_detect` function will be triggered. It attempts to write and read specific probe values (`0x5a5a` and `0xa5a5`). But since `fixed_mdio_read` can only return `0xffff` or a real register value (which isn't the probe value), `mxl862xx_rescue_mode_detect` will inevitably return `-ENODEV`. The driver is going to fail to probe. I need to see if syzkaller can use any other MDIO bus implementations. I checked for an `mdio-mock` device, but there isn't one. The `mdio-bitbang` requires GPIOs, and the `mdio-i2c` and `mdio-regmap` options need I2C and regmap which makes them unfeasible. If there's no way to get past `mxl862xx_rescue_mode_detect` without actual hardware, that means the devlink flash update code is going to be UNREACHABLE in a virtualized environment. Hold on, let's look at `mxl862xx_rescue_mode_detect` again. It reads from two specific addresses: `MXL862XX_SB_PDI_ADDR` and `MXL862XX_SB_PDI_DATA`. If those reads don't return the expected probe values (`0x5a5a` and `0xa5a5`), it returns `-ENODEV`. Could I use something like eBPF or kprobes to intercept MDIO reads/writes? No, syzkaller doesn't have that capability currently. What about using a dummy I2C device that pretends to be an MDIO bus? Nope. Syzkaller doesn't have a generic I2C device emulator that can be programmed to return specific values based on register addresses. But wait, syzkaller *does* have emulators for USB (`vhci`), WiFi (`mac80211_hwsim`), netdevs (`netdevsim`), DRM (`vkms`), and ALSA (`snd-dummy`). Does it have anything for MDIO or I2C? There's no pseudo-syscall like `syz_i2c_setup`. I checked for similar syscalls to the usb and pci setups, but I don't see any for MDIO. So, without hardware, `mxl862xx_probe` fails at `mxl862xx_rescue_mode_detect` (or earlier if the MDIO bus isn't even present). Okay, but what are the chances that `mxl862xx_smdio_read` will return `0x5a5a` by pure chance? The probe values are `0x5a5a` (ADDR) and `0xa5a5` (DATA). It's extremely unlikely a random MDIO bus will return those exact values. What if the read is from a memory-mapped IO region syzkaller could map and write to? `mxl862xx_smdio_read` uses `__mdiobus_read`, if the bus is memory-mapped, would syzkaller map the memory and write the values? Usually not, syzkaller doesn't do that for platform devices unless there is a specific syzkaller description for it. It would be very specific and hard to trigger. Ah, the instructions! "Return WorthFuzzing=false if the patch only contains: ... - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU." This is a *vendor-specific DSA switch driver* for the MaxLinear MxL862xx, and it requires the physical chip connected via MDIO. It's not emulated in QEMU. That is exactly what the rule describes. Wait, is an MDIO DSA switch driver included in this category? Yes. It's a "vendor-specific ... driver ... that requires physical ... hardware ... not emulated in standard QEM |
| 42/2 | 2026/07/26 11:54 | tool | read-file |
0mArgs:{
"File": "net/dsa/devlink.c",
"FirstLine": 1,
"LineCount": 50
}Results: {
"Contents": " 1:\t// SPDX-License-Identifier: GPL-2.0-or-later\n 2:\t/*\n 3:\t * DSA devlink handling\n 4:\t */\n 5:\t\n 6:\t#include \u003cnet/dsa.h\u003e\n 7:\t#include \u003cnet/devlink.h\u003e\n 8:\t\n 9:\t#include \"devlink.h\"\n 10:\t\n 11:\tstatic int dsa_devlink_info_get(struct devlink *dl,\n 12:\t\t\t\t\tstruct devlink_info_req *req,\n 13:\t\t\t\t\tstruct netlink_ext_ack *extack)\n 14:\t{\n 15:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n 16:\t\n 17:\t\tif (ds-\u003eops-\u003edevlink_info_get)\n 18:\t\t\treturn ds-\u003eops-\u003edevlink_info_get(ds, req, extack);\n 19:\t\n 20:\t\treturn -EOPNOTSUPP;\n 21:\t}\n 22:\t\n 23:\tstatic int dsa_devlink_flash_update(struct devlink *dl,\n 24:\t\t\t\t\t struct devlink_flash_update_params *params,\n 25:\t\t\t\t\t struct netlink_ext_ack *extack)\n 26:\t{\n 27:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n 28:\t\n 29:\t\treturn ds-\u003eops-\u003edevlink_flash_update(ds, params, extack);\n 30:\t}\n 31:\t\n 32:\tstatic int dsa_devlink_sb_pool_get(struct devlink *dl,\n 33:\t\t\t\t\t unsigned int sb_index, u16 pool_index,\n 34:\t\t\t\t\t struct devlink_sb_pool_info *pool_info)\n 35:\t{\n 36:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n 37:\t\n 38:\t\tif (!ds-\u003eops-\u003edevlink_sb_pool_get)\n 39:\t\t\treturn -EOPNOTSUPP;\n 40:\t\n 41:\t\treturn ds-\u003eops-\u003edevlink_sb_pool_get(ds, sb_index, pool_index,\n 42:\t\t\t\t\t\t pool_info);\n 43:\t}\n 44:\t\n 45:\tstatic int dsa_devlink_sb_pool_set(struct devlink *dl, unsigned int sb_index,\n 46:\t\t\t\t\t u16 pool_index, u32 size,\n 47:\t\t\t\t\t enum devlink_sb_threshold_type threshold_type,\n 48:\t\t\t\t\t struct netlink_ext_ack *extack)\n 49:\t{\n 50:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n"
} |
| 43/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57540 output: 37 thoughts: 52 Thoughts: Here's my understanding of the patch, distilled into my internal thought process: **Diving into the DSA Devlink Integration** Okay, so this patch is aimed squarely at enhancing the Device Link (Devlink) capabilities within the Distributed Switch Architecture (DSA) subsystem. The core of the change revolves around introducing a new function, `dsa_devlink_flash_update`, which is a pivotal addition to `net/dsa/devlink.c`. I immediately recognize this as significant, as flash updates are often a critical aspect of device management and firmware handling, especially in networking hardware where updates are essential for performance and bug fixes. I see that this also means changes to the existing `dsa_devlink_ops` structure. This likely involves adding function pointers related to the new flash update functionality, indicating the intention to seamlessly integrate this capability within the Devlink framework. Furthermore, the introduction of a new structure, `dsa_devlink_flash_ops`, is a clear indication of a more formalized and organized approach to flash-related operations. It suggests a separation of concerns, providing a dedicated interface for handling these specific tasks within the broader Devlink context. My immediate thought is, this is a sensible architectural choice, and should simplify future modifications and enable easier integration with new hardware. I will also be looking closely at the specific functions defined within the new structure to understand the specifics of how flash update operations will be carried out. |
| 44/2 | 2026/07/26 11:54 | tool | read-file |
0mArgs:{
"File": "net/dsa/devlink.c",
"FirstLine": 176,
"LineCount": 50
}Results: {
"Contents": " 176:\t\t\t\t\t\t\t\tp_max);\n 177:\t}\n 178:\t\n 179:\t/* The devlink core rejects flash requests up front when the flash_update\n 180:\t * op is absent, before fetching the firmware file from userspace. Only\n 181:\t * install the op for switches whose driver implements it, so that\n 182:\t * unsupported requests keep failing early.\n 183:\t */\n 184:\t#define DSA_DEVLINK_OPS\t\t\t\t\t\t\t\\\n 185:\t\t.info_get\t\t\t= dsa_devlink_info_get,\t\t\\\n 186:\t\t.sb_pool_get\t\t\t= dsa_devlink_sb_pool_get,\t\\\n 187:\t\t.sb_pool_set\t\t\t= dsa_devlink_sb_pool_set,\t\\\n 188:\t\t.sb_port_pool_get\t\t= dsa_devlink_sb_port_pool_get,\t\\\n 189:\t\t.sb_port_pool_set\t\t= dsa_devlink_sb_port_pool_set,\t\\\n 190:\t\t.sb_tc_pool_bind_get\t\t= dsa_devlink_sb_tc_pool_bind_get, \\\n 191:\t\t.sb_tc_pool_bind_set\t\t= dsa_devlink_sb_tc_pool_bind_set, \\\n 192:\t\t.sb_occ_snapshot\t\t= dsa_devlink_sb_occ_snapshot,\t\\\n 193:\t\t.sb_occ_max_clear\t\t= dsa_devlink_sb_occ_max_clear,\t\\\n 194:\t\t.sb_occ_port_pool_get\t\t= dsa_devlink_sb_occ_port_pool_get, \\\n 195:\t\t.sb_occ_tc_port_bind_get\t= dsa_devlink_sb_occ_tc_port_bind_get,\n 196:\t\n 197:\tstatic const struct devlink_ops dsa_devlink_ops = {\n 198:\t\tDSA_DEVLINK_OPS\n 199:\t};\n 200:\t\n 201:\tstatic const struct devlink_ops dsa_devlink_flash_ops = {\n 202:\t\tDSA_DEVLINK_OPS\n 203:\t\t.flash_update\t\t\t= dsa_devlink_flash_update,\n 204:\t};\n 205:\t\n 206:\tint dsa_devlink_param_get(struct devlink *dl, u32 id,\n 207:\t\t\t\t struct devlink_param_gset_ctx *ctx,\n 208:\t\t\t\t struct netlink_ext_ack *extack)\n 209:\t{\n 210:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n 211:\t\n 212:\t\tif (!ds-\u003eops-\u003edevlink_param_get)\n 213:\t\t\treturn -EOPNOTSUPP;\n 214:\t\n 215:\t\treturn ds-\u003eops-\u003edevlink_param_get(ds, id, ctx);\n 216:\t}\n 217:\tEXPORT_SYMBOL_GPL(dsa_devlink_param_get);\n 218:\t\n 219:\tint dsa_devlink_param_set(struct devlink *dl, u32 id,\n 220:\t\t\t\t struct devlink_param_gset_ctx *ctx,\n 221:\t\t\t\t struct netlink_ext_ack *extack)\n 222:\t{\n 223:\t\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n 224:\t\n 225:\t\tif (!ds-\u003eops-\u003edevlink_param_set)\n"
} |
| 45/2 | 2026/07/26 11:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 58908 output: 33 thoughts: 214 Thoughts: **Analyzing the Reachability of `devlink_flash_update` in DSA** Okay, so the changes in `net/dsa/devlink.c` are essentially setup work, making it possible for DSA drivers to implement `devlink_flash_update`. It's designed to be generic, but right now, it's only *actually* used by the `mxl862xx` driver, with the possibility of other drivers using it down the line. The key question is: Is this generic code truly reachable? Let's break this down. The code *becomes* reachable if any DSA driver actually *implements* `devlink_flash_update`. However, `dsa_devlink_flash_update`, which is the entry point for this functionality, is only called if the relevant `devlink_flash_update` operation is set in the DSA driver's operations structure (`ds->ops->devlink_flash_update`). At present, only `mxl862xx` sets this. Now, here's the catch: `mxl862xx` can't be probed unless we're dealing with *real* hardware. This means, in a virtualized environment, `dsa_devlink_flash_update` is effectively unreachable. But wait a minute... what if another DSA driver, like `dsa_loop` or `b53` or another, decides to implement `devlink_flash_update` at some point? We can't immediately dismiss the code as entirely unused. I need to verify whether any other drivers currently *do* implement `devlink_flash_update`. Gotta check that out. |
| 46/2 | 2026/07/26 11:55 | tool | grepper |
0mArgs:{
"Expression": "devlink_flash_update",
"PathPrefix": "drivers/net/dsa/"
}Results: {
"Output": "drivers/net/dsa/mxl862xx/mxl862xx-fw.c=207=static void mxl862xx_flash_notify(struct devlink *dl, const char *status,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-209-{\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:210:\tdevlink_flash_update_status_notify(dl, status, NULL, done, total);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-211-}\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c=722=int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-772-\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:773:int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c:774:\t\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.c-775-\t\t\t\t struct netlink_ext_ack *extack)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h=13=int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h-15-\t\t\t struct netlink_ext_ack *extack);\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h:16:int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h:17:\t\t\t\t struct devlink_flash_update_params *params,\ndrivers/net/dsa/mxl862xx/mxl862xx-fw.h-18-\t\t\t\t struct netlink_ext_ack *extack);\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2157=static const struct dsa_switch_ops mxl862xx_switch_ops = {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2189-\t.devlink_info_get = mxl862xx_devlink_info_get,\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2190:\t.devlink_flash_update = mxl862xx_devlink_flash_update,\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2191-};\n"
} |
| 47/2 | 2026/07/26 11:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 60341 output: 179 thoughts: 799 Thoughts: **Patch Analysis: Is this Worth Fuzzing?** Okay, let's break this down. My initial assessment is that this patch isn't introducing any *reachable* functional changes in a virtualized environment, therefore it should not be worth fuzzing. I need to be sure though, so let me explain my reasoning. Initially, I see that the code change primarily revolves around the `mxl862xx` driver, and that only this driver implements the new `devlink_flash_update` function. The problem is, as I recall, `mxl862xx` is a vendor-specific DSA switch driver, and, because it performs a hardware presence check through an MDIO bus during probe, it cannot be probed in a virtualized environment, like QEMU. That means the `devlink.c` code that calls `ds->ops->devlink_flash_update` is completely unreachable in any standard virtualized setup. The logic within that branch of `net/dsa/devlink.c` would be irrelevant, since the `mxl862xx` driver would never reach that code. However, I paused for a moment because of `dsa_switch_devlink_alloc`. This function is called for *all* DSA switches. The code selects between two `devlink_ops` structs based on whether `devlink_flash_update` is present: ```c ops = ds->ops->devlink_flash_update ? &dsa_devlink_flash_ops : &dsa_devlink_ops; /* Add the switch to devlink before calling setup, so that setup can * add dpipe tables */ dl = devlink_alloc(ops, sizeof(*dl_priv), ds->dev); ``` So *this* is reachable for all DSA switches, including something generic like `dsa_loop`. But even then, for switches that don't have `devlink_flash_update` implemented (like `dsa_loop`), the code will select `dsa_devlink_ops`, which is functionally the *same* as it was before. It's just a macro now. So, there is no new logic being reached for the existing switches. The *only* new logic is when `devlink_flash_update` is present, and that is only in `mxl862xx`, which, again, is unreachable. I need to confirm if `mxl862xx` can be probed in QEMU, since syzkaller might be testing this patch because it landed on the netdev mailing list, and syzkaller blindly tests patches. It does not know if the patch is reachable, it just fuzzes. My job is to determine if the patch is worth fuzzing. The fact that it is a vendor-specific hardware driver that requires physical hardware not emulated in QEMU, the patch is NOT worth fuzzing. Looking back at the instructions: "Return WorthFuzzing=false if the patch only contains: ... - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU." `mxl862xx` perfectly fits this description. It's a vendor-specific DSA switch driver requiring physical hardware. So, I will return `WorthFuzzing=false`. |
| 48/2 | 2026/07/26 11:55 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flash support for the MaxLinear MxL862xx DSA switch via devlink. The mxl862xx driver is a vendor-specific hardware driver that communicates with the physical switch chip over an MDIO bus. During probe, it performs a hardware presence check (mxl862xx_rescue_mode_detect) that requires the physical chip to latch and return specific values. Without the physical hardware (which is not emulated in standard QEMU), the driver fails to probe, making the new devlink flash update code unreachable in a virtualized environment. The generic DSA devlink plumbing changes only affect drivers implementing the new op, which is currently only mxl862xx.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flash support for the MaxLinear MxL862xx DSA switch via devlink. The mxl862xx driver is a vendor-specific hardware driver that communicates with the physical switch chip over an MDIO bus. During probe, it performs a hardware presence check (mxl862xx_rescue_mode_detect) that requires the physical chip to latch and return specific values. Without the physical hardware (which is not emulated in standard QEMU), the driver fails to probe, making the new devlink flash update code unreachable in a virtualized environment. The generic DSA devlink plumbing changes only affect drivers implementing the new op, which is currently only mxl862xx.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|