AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/20 03:33 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "NET_DSA_MXL862"
  ],
  "FocusSymbols": [
    "mxl862xx_devlink_flash_update",
    "mxl862xx_flash_validate",
    "mxl862xx_flash_firmware",
    "dsa_devlink_flash_update"
  ],
  "Reasoning": "The patch adds firmware flashing support via devlink to the MaxLinear MxL862xx DSA switch driver, including parsing of the firmware file which comes from userspace. It also adds a new devlink callback to the DSA core.",
  "WorthFuzzing": true
}

1/1 2026/07/20 03:33 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 56adf2bb7f3c63f1949f69e26368f210c5e71237\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 20 03:33:58 2026 +0000\n\n    syz-cluster: applied patch under review\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-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\nindex c87a955c13c48e..e2aa2934e9e162 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n@@ -70,6 +70,7 @@\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 \n #define MXL862XX_XPCS_PCS_CONFIG\t(MXL862XX_XPCS_MAGIC + 0x1)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\nnew file mode 100644\nindex 00000000000000..6a1de247c2f05d\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\n@@ -0,0 +1,482 @@\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+\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/property.h\u003e\n+#include \u003clinux/rtnetlink.h\u003e\n+#include \u003cnet/dsa.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\t0x02\n+\n+/* SB PDI handshake magic */\n+#define MXL862XX_SB_PDI_READY\t\t0xc55c\n+#define MXL862XX_SB_PDI_START\t\t0xf48f\n+#define MXL862XX_SB_PDI_END\t\t0x3cc3\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\t30000\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+\n+static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)\n+{\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t     MXL862XX_SB_PDI_CTRL_RST);\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t     MXL862XX_SB_PDI_CTRL_RST);\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\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+\tmxl862xx_sb_pdi_reset(priv);\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);\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+/**\n+ * mxl862xx_rescue_mode_detect - check whether the switch sits in MCUboot\n+ * @priv: driver private data\n+ *\n+ * MCUboot signals readiness for a firmware download with the SB PDI\n+ * ready magic; only the clause-22 SMDIO interface works in this state.\n+ *\n+ * Return: true if MCUboot is waiting for a firmware download.\n+ */\n+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)\n+{\n+\tint ret;\n+\n+\tret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);\n+\n+\treturn ret == MXL862XX_SB_PDI_READY;\n+}\n+\n+/* device_reprobe() -\u003e remove() frees priv while the work runs, so\n+ * the work struct cannot live in mxl862xx_priv.\n+ */\n+struct mxl862xx_reprobe {\n+\tstruct device *dev;\n+\tstruct delayed_work dwork;\n+};\n+\n+static void mxl862xx_reprobe_work_fn(struct work_struct *work)\n+{\n+\tstruct mxl862xx_reprobe *reprobe =\n+\t\tcontainer_of(work, struct mxl862xx_reprobe, dwork.work);\n+\n+\tif (device_reprobe(reprobe-\u003edev))\n+\t\tdev_err(reprobe-\u003edev, \"reprobe failed\\n\");\n+\tput_device(reprobe-\u003edev);\n+\tkfree(reprobe);\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 = 0;\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/* Failures from here on must go through end_magic so MCUboot\n+\t * reboots instead of waiting forever.\n+\t */\n+\n+\t/* Step 2: wait for bootloader ready */\n+\tmxl862xx_flash_notify(dl, \"Waiting for bootloader\", 0, 0);\n+\tmxl862xx_sb_pdi_reset(priv);\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\tMXL862XX_FW_READY_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: bootloader not ready: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 3: start handshake */\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t     MXL862XX_SB_PDI_START);\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: start handshake failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 4: transfer image header */\n+\tmxl862xx_flash_notify(dl, \"Erasing flash\", 0, 0);\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t     MXL862XX_SB_PDI_CTRL_WR);\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\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: header write failed: %pe\\n\",\n+\t\t\t\tERR_PTR(ret));\n+\t\t\tgoto end_magic;\n+\t\t}\n+\t}\n+\tmxl862xx_sb_pdi_reset(priv);\n+\n+\t/* the byte count in STAT triggers the erase */\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t     MXL862XX_FW_HDR_SIZE);\n+\n+\t/* ACK is byte count + 1 */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: header ACK failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 5: wait for erase to complete */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, 0,\n+\t\t\t\t\tMXL862XX_FW_ERASE_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: erase timeout: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 6: transfer payload */\n+\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t     MXL862XX_SB_PDI_CTRL_WR);\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\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\"flash: data write failed at %u/%u: %pe\\n\",\n+\t\t\t\tidx, payload_size, ERR_PTR(ret));\n+\t\t\tgoto end_magic;\n+\t\t}\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\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t     MXL862XX_SB_PDI_CTRL_RST);\n+\t\t\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,\n+\t\t\t\t\t     MXL862XX_FW_SB1_ADDR);\n+\t\t\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t     MXL862XX_SB_PDI_CTRL_WR);\n+\t\t} else if (word_idx \u003e= MXL862XX_FW_BANK_SLICE) {\n+\t\t\tret = mxl862xx_sb_pdi_flush_slice(priv, data_written);\n+\t\t\tif (ret) {\n+\t\t\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\t\t\"flash: write timeout at %u/%u: %pe\\n\",\n+\t\t\t\t\tidx, payload_size, ERR_PTR(ret));\n+\t\t\t\tgoto end_magic;\n+\t\t\t}\n+\t\t\tword_idx = 0;\n+\t\t\tdata_written = 0;\n+\t\t\tmxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t\t     MXL862XX_SB_PDI_CTRL_WR);\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+\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+\tconst char *model;\n+\tchar ver_str[32];\n+\tint ret;\n+\n+\tmodel = device_get_match_data(ds-\u003edev);\n+\tif (model) {\n+\t\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t\t     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,\n+\t\t\t\t\t\t     model);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/* canonical null version: no operational firmware on the switch */\n+\tif (priv-\u003erescue_mode)\n+\t\treturn devlink_info_version_running_put(req, \"fw\", \"0.0.0\");\n+\n+\tsnprintf(ver_str, sizeof(ver_str), \"%u.%u.%u\",\n+\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t priv-\u003efw_version.revision);\n+\n+\treturn devlink_info_version_running_put(req, \"fw\", ver_str);\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_sys_fw_image_version ver = {};\n+\tstruct mxl862xx_reprobe *reprobe;\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/* Everything needed to schedule the reprobe which restores\n+\t * normal operation must be in place before the switch is\n+\t * disturbed.\n+\t */\n+\treprobe = kzalloc_obj(*reprobe);\n+\tif (!reprobe)\n+\t\treturn -ENOMEM;\n+\n+\tif (!try_module_get(THIS_MODULE)) {\n+\t\tkfree(reprobe);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\treprobe-\u003edev = get_device(ds-\u003edev);\n+\tINIT_DELAYED_WORK(\u0026reprobe-\u003edwork, mxl862xx_reprobe_work_fn);\n+\n+\tif (priv-\u003erescue_mode)\n+\t\tdev_info(ds-\u003edev,\n+\t\t\t \"flash: recovering switch from 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+\trtnl_unlock();\n+\n+\tpriv-\u003eblock_host = true;\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\t/* lift the block to query the new firmware version */\n+\t\tpriv-\u003eblock_host = false;\n+\t\tpriv-\u003erescue_mode = false;\n+\t\tmemset(\u0026ver, 0, sizeof(ver));\n+\t\tif (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver) \u0026\u0026\n+\t\t    ver.iv_major)\n+\t\t\tdev_info(ds-\u003edev, \"flash: new firmware %u.%u.%u\\n\",\n+\t\t\t\t ver.iv_major, ver.iv_minor,\n+\t\t\t\t le16_to_cpu(ver.iv_revision));\n+\t}\n+\n+\tpriv-\u003eskip_teardown = true;\n+\n+\tschedule_delayed_work(\u0026reprobe-\u003edwork, msecs_to_jiffies(500));\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..57c2000cfee519\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\n@@ -0,0 +1,18 @@\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+\n+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);\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..80565de710d377 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\n@@ -15,6 +15,7 @@\n #include \u003clinux/unaligned.h\u003e\n #include \u003cnet/dsa.h\u003e\n #include \"mxl862xx.h\"\n+#include \"mxl862xx-cmd.h\"\n #include \"mxl862xx-host.h\"\n \n #define CTRL_BUSY_MASK\t\t\tBIT(15)\n@@ -340,6 +341,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 = 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,6 +511,41 @@ 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);\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..265a83f4b371b5 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@@ -628,9 +629,22 @@ static int mxl862xx_setup(struct dsa_switch *ds)\n \tif (ret)\n \t\treturn ret;\n \n-\tret = mxl862xx_wait_ready(ds);\n-\tif (ret)\n-\t\treturn ret;\n+\tpriv-\u003erescue_mode = mxl862xx_rescue_mode_detect(priv);\n+\tif (!priv-\u003erescue_mode) {\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\tpriv-\u003erescue_mode = mxl862xx_rescue_mode_detect(priv);\n+\t\t\tif (!priv-\u003erescue_mode)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tif (priv-\u003erescue_mode) {\n+\t\tdev_warn(ds-\u003edev,\n+\t\t\t \"switch stuck in MCUboot rescue mode, use devlink to flash new firmware\\n\");\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 +729,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 +1361,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@@ -1628,6 +1658,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 +2119,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {\n \t.get_pause_stats = mxl862xx_get_pause_stats,\n \t.get_rmon_stats = mxl862xx_get_rmon_stats,\n \t.get_stats64 = mxl862xx_get_stats64,\n+\t.devlink_info_get = mxl862xx_devlink_info_get,\n+\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n };\n \n static int mxl862xx_probe(struct mdio_device *mdiodev)\n@@ -2191,8 +2226,8 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)\n }\n \n static const struct of_device_id mxl862xx_of_match[] = {\n-\t{ .compatible = \"maxlinear,mxl86282\" },\n-\t{ .compatible = \"maxlinear,mxl86252\" },\n+\t{ .compatible = \"maxlinear,mxl86282\", .data = \"MaxLinear MxL86282\" },\n+\t{ .compatible = \"maxlinear,mxl86252\", .data = \"MaxLinear MxL86252\" },\n \t{ /* sentinel */ }\n };\n MODULE_DEVICE_TABLE(of, mxl862xx_of_match);\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h\nindex 432a5f3f2e08e6..5d21296561e3aa 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h\n@@ -319,6 +319,12 @@ 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 stuck in MCUboot; firmware API commands\n+ *                      fail fast, only clause-22 SMDIO works\n  * @stats_work:         periodic work item that polls RMON hardware counters\n  *                      and accumulates them into 64-bit per-port stats\n  */\n@@ -337,6 +343,9 @@ 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 \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..25311a87cbc56d 100644\n--- a/net/dsa/devlink.c\n+++ b/net/dsa/devlink.c\n@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,\n \treturn -EOPNOTSUPP;\n }\n \n+static int dsa_devlink_flash_update(struct devlink *dl,\n+\t\t\t\t    struct devlink_flash_update_params *params,\n+\t\t\t\t    struct netlink_ext_ack *extack)\n+{\n+\tstruct dsa_switch *ds = dsa_devlink_to_ds(dl);\n+\n+\tif (!ds-\u003eops-\u003edevlink_flash_update)\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn ds-\u003eops-\u003edevlink_flash_update(ds, params, extack);\n+}\n+\n static int dsa_devlink_sb_pool_get(struct devlink *dl,\n \t\t\t\t   unsigned int sb_index, u16 pool_index,\n \t\t\t\t   struct devlink_sb_pool_info *pool_info)\n@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,\n \n static const struct devlink_ops dsa_devlink_ops = {\n \t.info_get\t\t\t= dsa_devlink_info_get,\n+\t.flash_update\t\t\t= dsa_devlink_flash_update,\n \t.sb_pool_get\t\t\t= dsa_devlink_sb_pool_get,\n \t.sb_pool_set\t\t\t= dsa_devlink_sb_pool_set,\n \t.sb_port_pool_get\t\t= dsa_devlink_sb_port_pool_get,\n"
}

2/1 2026/07/20 03:34 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "NET_DSA_MXL862"
  ],
  "FocusSymbols": [
    "mxl862xx_devlink_flash_update",
    "mxl862xx_flash_validate",
    "mxl862xx_flash_firmware",
    "dsa_devlink_flash_update"
  ],
  "Reasoning": "The patch adds firmware flashing support via devlink to the MaxLinear MxL862xx DSA switch driver, including parsing of the firmware file which comes from userspace. It also adds a new devlink callback to the DSA core.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 56adf2bb7f3c63f1949f69e26368f210c5e71237
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 20 03:33:58 2026 +0000

    syz-cluster: applied patch under review

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-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c48e..e2aa2934e9e162 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -70,6 +70,7 @@
 #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 MXL862XX_XPCS_PCS_CONFIG	(MXL862XX_XPCS_MAGIC + 0x1)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
new file mode 100644
index 00000000000000..6a1de247c2f05d
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Firmware flash and devlink support for MaxLinear MxL862xx
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#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/property.h>
+#include <linux/rtnetlink.h>
+#include <net/dsa.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 */
+#define MXL862XX_SB_PDI_READY		0xc55c
+#define MXL862XX_SB_PDI_START		0xf48f
+#define MXL862XX_SB_PDI_END		0x3cc3
+
+/* 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	30000
+#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
+
+static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+			     MXL862XX_SB_PDI_CTRL_RST);
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+			     MXL862XX_SB_PDI_CTRL_RST);
+	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)
+{
+	mxl862xx_sb_pdi_reset(priv);
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);
+
+	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);
+}
+
+/**
+ * mxl862xx_rescue_mode_detect - check whether the switch sits in MCUboot
+ * @priv: driver private data
+ *
+ * MCUboot signals readiness for a firmware download with the SB PDI
+ * ready magic; only the clause-22 SMDIO interface works in this state.
+ *
+ * Return: true if MCUboot is waiting for a firmware download.
+ */
+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)
+{
+	int ret;
+
+	ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+
+	return ret == MXL862XX_SB_PDI_READY;
+}
+
+/* device_reprobe() -> remove() frees priv while the work runs, so
+ * the work struct cannot live in mxl862xx_priv.
+ */
+struct mxl862xx_reprobe {
+	struct device *dev;
+	struct delayed_work dwork;
+};
+
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
+{
+	struct mxl862xx_reprobe *reprobe =
+		container_of(work, struct mxl862xx_reprobe, dwork.work);
+
+	if (device_reprobe(reprobe->dev))
+		dev_err(reprobe->dev, "reprobe failed\n");
+	put_device(reprobe->dev);
+	kfree(reprobe);
+	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 = 0;
+	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;
+		}
+	}
+
+	/* Failures from here on must go through end_magic so MCUboot
+	 * reboots instead of waiting forever.
+	 */
+
+	/* Step 2: wait for bootloader ready */
+	mxl862xx_flash_notify(dl, "Waiting for bootloader", 0, 0);
+	mxl862xx_sb_pdi_reset(priv);
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+					MXL862XX_FW_READY_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: bootloader not ready: %pe\n", ERR_PTR(ret));
+		goto end_magic;
+	}
+
+	/* Step 3: start handshake */
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+			     MXL862XX_SB_PDI_START);
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
+					MXL862XX_FW_ACK_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: start handshake failed: %pe\n", ERR_PTR(ret));
+		goto end_magic;
+	}
+
+	/* Step 4: transfer image header */
+	mxl862xx_flash_notify(dl, "Erasing flash", 0, 0);
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+			     MXL862XX_SB_PDI_CTRL_WR);
+	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) {
+			dev_err(&priv->mdiodev->dev,
+				"flash: header write failed: %pe\n",
+				ERR_PTR(ret));
+			goto end_magic;
+		}
+	}
+	mxl862xx_sb_pdi_reset(priv);
+
+	/* the byte count in STAT triggers the erase */
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+			     MXL862XX_FW_HDR_SIZE);
+
+	/* ACK is byte count + 1 */
+	ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+					MXL862XX_FW_ACK_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: header ACK failed: %pe\n", ERR_PTR(ret));
+		goto end_magic;
+	}
+
+	/* Step 5: wait for erase to complete */
+	ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
+					MXL862XX_FW_ERASE_TIMEOUT_MS);
+	if (ret) {
+		dev_err(&priv->mdiodev->dev,
+			"flash: erase timeout: %pe\n", ERR_PTR(ret));
+		goto end_magic;
+	}
+
+	/* Step 6: transfer payload */
+	mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+			     MXL862XX_SB_PDI_CTRL_WR);
+
+	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) {
+			dev_err(&priv->mdiodev->dev,
+				"flash: data write failed at %u/%u: %pe\n",
+				idx, payload_size, ERR_PTR(ret));
+			goto end_magic;
+		}
+		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) {
+			mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+					     MXL862XX_SB_PDI_CTRL_RST);
+			mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+					     MXL862XX_FW_SB1_ADDR);
+			mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+					     MXL862XX_SB_PDI_CTRL_WR);
+		} else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
+			ret = mxl862xx_sb_pdi_flush_slice(priv, data_written);
+			if (ret) {
+				dev_err(&priv->mdiodev->dev,
+					"flash: write timeout at %u/%u: %pe\n",
+					idx, payload_size, ERR_PTR(ret));
+				goto end_magic;
+			}
+			word_idx = 0;
+			data_written = 0;
+			mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+					     MXL862XX_SB_PDI_CTRL_WR);
+
+			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);
+
+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;
+	const char *model;
+	char ver_str[32];
+	int ret;
+
+	model = device_get_match_data(ds->dev);
+	if (model) {
+		ret = devlink_info_version_fixed_put(req,
+						     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
+						     model);
+		if (ret)
+			return ret;
+	}
+
+	/* canonical null version: no operational firmware on the switch */
+	if (priv->rescue_mode)
+		return devlink_info_version_running_put(req, "fw", "0.0.0");
+
+	snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
+		 priv->fw_version.major, priv->fw_version.minor,
+		 priv->fw_version.revision);
+
+	return devlink_info_version_running_put(req, "fw", ver_str);
+}
+
+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_sys_fw_image_version ver = {};
+	struct mxl862xx_reprobe *reprobe;
+	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;
+	}
+
+	/* Everything needed to schedule the reprobe which restores
+	 * normal operation must be in place before the switch is
+	 * disturbed.
+	 */
+	reprobe = kzalloc_obj(*reprobe);
+	if (!reprobe)
+		return -ENOMEM;
+
+	if (!try_module_get(THIS_MODULE)) {
+		kfree(reprobe);
+		return -ENODEV;
+	}
+
+	reprobe->dev = get_device(ds->dev);
+	INIT_DELAYED_WORK(&reprobe->dwork, mxl862xx_reprobe_work_fn);
+
+	if (priv->rescue_mode)
+		dev_info(ds->dev,
+			 "flash: recovering switch from 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);
+	rtnl_unlock();
+
+	priv->block_host = true;
+
+	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) {
+		/* lift the block to query the new firmware version */
+		priv->block_host = false;
+		priv->rescue_mode = false;
+		memset(&ver, 0, sizeof(ver));
+		if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver) &&
+		    ver.iv_major)
+			dev_info(ds->dev, "flash: new firmware %u.%u.%u\n",
+				 ver.iv_major, ver.iv_minor,
+				 le16_to_cpu(ver.iv_revision));
+	}
+
+	priv->skip_teardown = true;
+
+	schedule_delayed_work(&reprobe->dwork, msecs_to_jiffies(500));
+
+	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..57c2000cfee519
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+struct mxl862xx_priv;
+
+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);
+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..80565de710d377 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -15,6 +15,7 @@
 #include <linux/unaligned.h>
 #include <net/dsa.h>
 #include "mxl862xx.h"
+#include "mxl862xx-cmd.h"
 #include "mxl862xx-host.h"
 
 #define CTRL_BUSY_MASK			BIT(15)
@@ -340,6 +341,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 = 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,6 +511,41 @@ 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);
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..265a83f4b371b5 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"
 
@@ -628,9 +629,22 @@ static int mxl862xx_setup(struct dsa_switch *ds)
 	if (ret)
 		return ret;
 
-	ret = mxl862xx_wait_ready(ds);
-	if (ret)
-		return ret;
+	priv->rescue_mode = mxl862xx_rescue_mode_detect(priv);
+	if (!priv->rescue_mode) {
+		ret = mxl862xx_wait_ready(ds);
+		if (ret) {
+			/* the reset may only now have triggered rescue mode */
+			priv->rescue_mode = mxl862xx_rescue_mode_detect(priv);
+			if (!priv->rescue_mode)
+				return ret;
+		}
+	}
+
+	if (priv->rescue_mode) {
+		dev_warn(ds->dev,
+			 "switch stuck in MCUboot rescue mode, use devlink to flash new firmware\n");
+		return 0;
+	}
 
 	mutex_init(&priv->serdes_lock);
 	for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
@@ -715,11 +729,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 +1361,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;
@@ -1628,6 +1658,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 +2119,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)
@@ -2191,8 +2226,8 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
 }
 
 static const struct of_device_id mxl862xx_of_match[] = {
-	{ .compatible = "maxlinear,mxl86282" },
-	{ .compatible = "maxlinear,mxl86252" },
+	{ .compatible = "maxlinear,mxl86282", .data = "MaxLinear MxL86282" },
+	{ .compatible = "maxlinear,mxl86252", .data = "MaxLinear MxL86252" },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mxl862xx_of_match);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08e6..5d21296561e3aa 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -319,6 +319,12 @@ 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 stuck in MCUboot; firmware API commands
+ *                      fail fast, only clause-22 SMDIO works
  * @stats_work:         periodic work item that polls RMON hardware counters
  *                      and accumulates them into 64-bit per-port stats
  */
@@ -337,6 +343,9 @@ struct mxl862xx_priv {
 	u16 evlan_ingress_size;
 	u16 evlan_egress_size;
 	u16 vf_block_size;
+	bool block_host;
+	bool skip_teardown;
+	bool rescue_mode;
 	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..25311a87cbc56d 100644
--- a/net/dsa/devlink.c
+++ b/net/dsa/devlink.c
@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,
 	return -EOPNOTSUPP;
 }
 
+static int dsa_devlink_flash_update(struct devlink *dl,
+				    struct devlink_flash_update_params *params,
+				    struct netlink_ext_ack *extack)
+{
+	struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+	if (!ds->ops->devlink_flash_update)
+		return -EOPNOTSUPP;
+
+	return ds->ops->devlink_flash_update(ds, params, extack);
+}
+
 static int dsa_devlink_sb_pool_get(struct devlink *dl,
 				   unsigned int sb_index, u16 pool_index,
 				   struct devlink_sb_pool_info *pool_info)
@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
 
 static const struct devlink_ops dsa_devlink_ops = {
 	.info_get			= dsa_devlink_info_get,
+	.flash_update			= dsa_devlink_flash_update,
 	.sb_pool_get			= dsa_devlink_sb_pool_get,
 	.sb_pool_set			= dsa_devlink_sb_pool_set,
 	.sb_port_pool_get		= dsa_devlink_sb_port_pool_get,

3/2 2026/07/20 03:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13354
							output: 23
							thoughts: 141
						

Thoughts:
**Checking the MXL862xx Driver Configuration**

Okay, so we've got a new patch here, looks like it's adding some important functionality: firmware flashing and Devlink support for the MaxLinear MxL862xx switch driver. Specifically, that's in `drivers/net/dsa/mxl862xx/mxl862xx-fw.c`. And it seems like it's also adding `devlink_flash_update` support into the DSA core itself, in `net/dsa/devlink.c`. That's a nice integration, keeping everything tidy.

The driver is supposed to be configured with `CONFIG_NET_DSA_MXL862`. Now, before I dive too deep into this code, I need to confirm that this config option is actually defined and available in our build. I don't want to waste time chasing phantom configurations. Time to pull out `grepper` and double-check, make sure `NET_DSA_MXL862` is legit. No surprises here.



4/2 2026/07/20 03:34 tool grepper
0m Args:
{
  "Expression": "NET_DSA_MXL862"
}

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

5/2 2026/07/20 03:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14004
							output: 46
							thoughts: 1585
						

Thoughts:
**Fuzzing Target: Firmware Flashing for MaxLinear MxL862xx DSA Switch Driver**

Okay, so I'm looking at this patch that introduces firmware flashing capability via devlink for the MaxLinear MxL862xx DSA switch driver. It also adds a new callback, `devlink_flash_update`, to the DSA core. My immediate thought is, "Can we trigger this in a virtualized environment like GCE or QEMU?"

The DSA core changes in `net/dsa/devlink.c` are potentially reachable, but currently, only the `mxl862xx` driver implements the actual `devlink_flash_update` functionality. This driver is, as far as I can see, a hardware driver specifically for a MaxLinear switch chip. My main question here: Can we emulate this switch, or at least trigger the code path with a dummy device? The driver uses MDIO, which, theoretically, could be instantiated through device tree or software. The crucial point is that it relies on MDIO for hardware communication. Can syzkaller somehow inject MDIO reads/writes or create a mock MDIO device? I know syzkaller has `syz_genetlink` and sometimes it can trigger probe via virtual buses, but usually MDIO devices are probed via device tree.

Then I remembered that `mxl862xx_devlink_flash_update` is called through the devlink netlink interface. So, if the device is probed, devlink commands can reach it. This is promising! The firmware file parsing (`mxl862xx_flash_validate`) happens in the kernel, and the file itself is supplied by userspace through devlink flash update. This is where things get interesting from a fuzzing perspective: `mxl862xx_flash_validate` takes a `const struct firmware *fw`. This `fw` comes directly from userspace. The validation logic itself looks vulnerable: there are checks on the firmware size and potential integer overflows when calculating file offsets. This is *definitely* parsing untrusted data, and therefore a prime target for fuzzing.

My primary focus needs to be on these functions: `mxl862xx_devlink_flash_update`, `mxl862xx_flash_validate`, `mxl862xx_flash_firmware`, and also the core DSA function, `dsa_devlink_flash_update`.

The next question: Is `mxl862xx_devlink_flash_update` itself reachable? It depends on the `mxl862xx` driver being probed. Syzkaller can probably probe some drivers, but hardware-specific drivers on MDIO are usually hard to reach unless there's a mock or some device tree overlay. But, since the DSA core change in `dsa_devlink_flash_update` is generic, it might be reachable if any DSA switch is present.

Now, checking the DSA switch drivers that syzkaller can probe... There's `dsa_loop`, which is a mock DSA switch. However, `dsa_loop` doesn't implement `devlink_flash_update`, so without the `mxl862xx` driver, it would return `-EOPNOTSUPP`. That would mean we wouldn't be able to easily reach `mxl862xx`, but we should still mark it as worth fuzzing, as it adds new netlink attributes, firmware parsing, and a new devlink op.

The instructions say that if code modifies reachable core kernel logic, we need to verify it. The question is, is this MDIO device reachable? The DSA loop driver exists, but I'm unsure if it's the same as the MDIO device... no.

But, given the instructions and the fact that we can trigger the `dsa_devlink_flash_update` core function, and that this handles user-supplied data, the netlink calls need to be fuzzed even if the `mxl862xx` driver can't be probed.

Let's double-check reachability of `dsa_devlink_flash_update`. Yes, if we have *any* DSA switch (like `dsa_loop`), the core function can be called via devlink netlink. It will call the individual switch op.

Here's my list of focus symbols:

*   `dsa_devlink_flash_update`
*   `mxl862xx_devlink_flash_update`
*   `mxl862xx_flash_validate`
*   `mxl862xx_flash_firmware`

And the necessary configs:

*   `NET_DSA_MXL862`
*   `


6/2 2026/07/20 03:34 tool grepper
0m Args:
{
  "Expression": "config NET_DSA_MXL862",
  "PathPrefix": "drivers/net/dsa/mxl862xx/Kconfig"
}

Results:
{
  "Output": "drivers/net/dsa/mxl862xx/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/dsa/mxl862xx/Kconfig:2:config NET_DSA_MXL862\ndrivers/net/dsa/mxl862xx/Kconfig-3-\ttristate \"MaxLinear MxL862xx\"\n"
}

7/2 2026/07/20 03:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16769
							output: 42
							thoughts: 12
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the Dependency Landscape**

Okay, so I need to get a handle on the relationships here. The obvious first step is to dive into the Kconfig file. I'm thinking, "Let's crack open that Kconfig and see what the build system is *really* relying on." I'm looking for the breadcrumbs, the dependencies, the *ifs* and *depends on* statements that govern how these components play together. This file is the key to understanding the configuration logic and how different features or modules are enabled or disabled based on various conditions. I'm going to quickly scan for anything that jumps out – complex nested dependencies, unusual use of `select` or `default`, or any circular references that might bite me later. My goal here is a quick, high-level understanding of the control flow before I get bogged down in the minutiae.



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

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

9/2 2026/07/20 03:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17231
							output: 144
							thoughts: 209
						

Thoughts:
**My Analysis of the MaxLinear MxL862xx DSA Driver Patch**

Okay, so I'm diving into this patch related to the MaxLinear MxL862xx DSA switch driver. The dependencies point directly to `NET_DSA`, and specifically, the key config is `NET_DSA_MXL862`.  That's my starting point. I'm double-checking, just to be thorough, that no other configuration options are relevant here, and it looks like that's the only one.

Now, let's see if the core functionality, particularly `dsa_devlink_flash_update`, is reachable. Yep, looks like we're good to go through devlink netlink, which is what I suspected.

Based on this understanding, I'm going to set some fuzzing parameters. This looks like a prime target for fuzzing, so I'll mark `WorthFuzzing` as `true`. I'll focus our fuzzing efforts on these specific symbols: `mxl862xx_devlink_flash_update`, `mxl862xx_flash_validate`, `mxl862xx_flash_firmware`, and also the core `dsa_devlink_flash_update`.

Finally, to enable this functionality for testing, I'll explicitly enable `NET_DSA_MXL862`.

The reasoning is clear: this patch is implementing firmware flashing support for the MxL862xx driver, using devlink. It introduces the necessary code to handle firmware files coming from userspace. Furthermore, it adds a new devlink callback to the DSA core, which seems important to be testing.  This introduces a new attack surface so it needs to be tested vigorously.



10/2 2026/07/20 03:34 tool set-results
0m Args:
{
  "EnableConfigs": [
    "NET_DSA_MXL862"
  ],
  "FocusSymbols": [
    "mxl862xx_devlink_flash_update",
    "mxl862xx_flash_validate",
    "mxl862xx_flash_firmware",
    "dsa_devlink_flash_update"
  ],
  "Reasoning": "The patch adds firmware flashing support via devlink to the MaxLinear MxL862xx DSA switch driver, including parsing of the firmware file which comes from userspace. It also adds a new devlink callback to the DSA core.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "NET_DSA_MXL862"
  ],
  "FocusSymbols": [
    "mxl862xx_devlink_flash_update",
    "mxl862xx_flash_validate",
    "mxl862xx_flash_firmware",
    "dsa_devlink_flash_update"
  ],
  "Reasoning": "The patch adds firmware flashing support via devlink to the MaxLinear MxL862xx DSA switch driver, including parsing of the firmware file which comes from userspace. It also adds a new devlink callback to the DSA core.",
  "WorthFuzzing": true
}

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