| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/30 03:30 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flashing support via devlink to the `mxl862xx` DSA switch driver. This driver is for MaxLinear MxL862xx hardware switches connected via an MDIO bus. Since there is no software emulator for this specific hardware in standard QEMU, and no generic way to instantiate arbitrary MDIO devices from userspace without device tree support, the probe function of this driver will never be executed in a virtualized fuzzing environment. Consequently, the newly added devlink callbacks are unreachable.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/30 03:30 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit e5677565300846a3bf69735d1ccc33d005f45d2f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Jul 30 03:30:30 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 4745148fecf4f..058999d0dc56f 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 0000000000000..b3e9f7d4d4969\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 2d420d40782e3..ed58911ef264c 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 a7be0e6669dfa..bccac0d0f703f 100644\n--- a/drivers/net/dsa/mxl862xx/Makefile\n+++ b/drivers/net/dsa/mxl862xx/Makefile\n@@ -1,3 +1,3 @@\n # SPDX-License-Identifier: GPL-2.0\n obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o\n-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o\n+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h\nindex a180a5decffc0..6f771895984cb 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h\n@@ -1224,6 +1224,16 @@ struct mxl862xx_sys_fw_image_version {\n \t__le32 iv_build_num;\n } __packed;\n \n+/**\n+ * struct mxl862xx_sys_reg_rw - System register read/write\n+ * @addr: 32-bit register address\n+ * @val: register value\n+ */\n+struct mxl862xx_sys_reg_rw {\n+\t__le32 addr;\n+\t__le32 val;\n+} __packed;\n+\n /**\n * enum mxl862xx_port_type - Port Type\n * @MXL862XX_LOGICAL_PORT: Logical Port\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\nindex c87a955c13c48..a865425aa61e1 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h\n@@ -70,7 +70,9 @@\n #define INT_GPHY_READ\t\t\t(GPY_GPY2XX_MAGIC + 0x1)\n #define INT_GPHY_WRITE\t\t\t(GPY_GPY2XX_MAGIC + 0x2)\n \n+#define SYS_MISC_FW_UPDATE\t\t(SYS_MISC_MAGIC + 0x1)\n #define SYS_MISC_FW_VERSION\t\t(SYS_MISC_MAGIC + 0x2)\n+#define SYS_MISC_REG_RD\t\t\t(SYS_MISC_MAGIC + 0x8)\n \n #define MXL862XX_XPCS_PCS_CONFIG\t(MXL862XX_XPCS_MAGIC + 0x1)\n #define MXL862XX_XPCS_PCS_GET_STATE\t(MXL862XX_XPCS_MAGIC + 0x2)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\nnew file mode 100644\nindex 0000000000000..9446c2626765c\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c\n@@ -0,0 +1,955 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Firmware flash and devlink support for MaxLinear MxL862xx\n+ *\n+ * Copyright (C) 2025 Daniel Golle \u003cdaniel@makrotopia.org\u003e\n+ *\n+ * SB PDI - firmware download interface over clause-22 SMDIO\n+ * =========================================================\n+ *\n+ * The MxL862xx MCUboot loader accepts a firmware image through four \"SB PDI\"\n+ * registers in the switch SMDIO register space. It runs whenever no WSP\n+ * firmware is active: the normal firmware update enters it deliberately - the\n+ * SYS_MISC_FW_UPDATE API command sets a sticky rescue bit and reboots into\n+ * MCUboot - and the loader also stays here when the stored WSP firmware fails\n+ * its boot-time integrity check. This driver drives the loader's 0xc55c\n+ * \"console\" download path.\n+ *\n+ * SMDIO register access (mxl862xx_smdio_read/write):\n+ * MII reg 0x1f := (\u003csb_pdi_reg\u003e \u0026 0xfff0) ; page latch\n+ * MII reg (\u003csb_pdi_reg\u003e \u0026 0x000f) := / =\u003e \u003cu16 data\u003e\n+ * so CTRL/ADDR/DATA/STAT (0xe100..0xe103) are MII regs 0/1/2/3 of page\n+ * 0xe100, not all reg 0x00.\n+ *\n+ * SB PDI registers (host name/addr -\u003e MCU mailbox):\n+ * CTRL 0xe100 -\u003e 0xc0938400 mode: RST=0x00 RD=0x01 WR=0x02\n+ * ADDR 0xe101 -\u003e 0xc0938404 SB target word address (SB1 bank = 0x7800)\n+ * DATA 0xe102 -\u003e 0xc0938408 16-bit data / reply word\n+ * STAT 0xe103 -\u003e 0xc093840c handshake: a magic (below) or a byte count\n+ *\n+ * STAT magics:\n+ * READY 0xc55c loader idle in the console loop (this driver)\n+ * DL_RDY 0xc33c loader idle in the flashless loop\n+ * START 0xf48f host -\u003e begin download session\n+ * ACK 0xf490 loader -\u003e START acknowledged (START + 1)\n+ * END 0x3cc3 host -\u003e 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/iopoll.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+\tint ret, val;\n+\n+\tret = read_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t\tval \u003c 0 || (u16)val == expected,\n+\t\t\t\t10000, timeout_ms * 1000, false,\n+\t\t\t\tpriv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\treturn ret;\n+}\n+\n+static int mxl862xx_sb_pdi_flush_slice(struct mxl862xx_priv *priv,\n+\t\t\t\t u32 data_written)\n+{\n+\tint ret;\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn mxl862xx_sb_pdi_poll_stat(priv, 0,\n+\t\t\t\t\t MXL862XX_FW_WRITE_TIMEOUT_MS);\n+}\n+\n+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+/* Post-flash reprobe. device_reprobe() detaches the driver -- running\n+ * remove(), which frees priv -- then re-probes, so this work touches only its\n+ * own device and module references and frees itself. A re-probe failure leaves\n+ * the device unbound, exactly as a failed initial probe would, so it is only\n+ * logged. Running from a workqueue keeps device_reprobe() out of the devlink\n+ * caller's locking and signal context.\n+ */\n+struct mxl862xx_reprobe {\n+\tstruct delayed_work work;\n+\tstruct device *dev;\n+};\n+\n+static void mxl862xx_reprobe_work_fn(struct work_struct *work)\n+{\n+\tstruct mxl862xx_reprobe *rp =\n+\t\tcontainer_of(work, struct mxl862xx_reprobe, work.work);\n+\tstruct device *dev = rp-\u003edev;\n+\n+\tif (device_reprobe(dev))\n+\t\tdev_err(dev, \"reprobe failed; device left unbound\\n\");\n+\tput_device(dev);\n+\tkfree(rp);\n+\tmodule_put(THIS_MODULE);\n+}\n+\n+/* Allocate the reprobe up front, before the switch is disturbed, so an\n+ * allocation failure aborts cleanly. The caller holds a module and a device\n+ * reference; the work releases both once it runs. Returns NULL on -ENOMEM.\n+ */\n+static struct mxl862xx_reprobe *mxl862xx_reprobe_alloc(struct device *dev)\n+{\n+\tstruct mxl862xx_reprobe *rp;\n+\n+\trp = kzalloc_obj(*rp);\n+\tif (!rp)\n+\t\treturn NULL;\n+\trp-\u003edev = dev;\n+\tINIT_DELAYED_WORK(\u0026rp-\u003ework, mxl862xx_reprobe_work_fn);\n+\treturn rp;\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+\tint val;\n+\n+\tread_poll_timeout(mxl862xx_smdio_read, val,\n+\t\t\t val \u003c 0 || (u16)val == MXL862XX_SB_PDI_READY ||\n+\t\t\t (u16)val == 0,\n+\t\t\t 50, timeout_ms * 1000, false,\n+\t\t\t priv, MXL862XX_SB_PDI_STAT);\n+\tif (val \u003c 0)\n+\t\treturn val;\n+\tif ((u16)val == MXL862XX_SB_PDI_READY)\n+\t\treturn MXL862XX_DRAIN_READY;\n+\tif ((u16)val == 0)\n+\t\treturn MXL862XX_DRAIN_FEED;\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 validates the (now corrupt) image and re-arms\n+ * READY, or boots a valid image that happened to survive in flash. This only\n+ * finalises the transfer; the caller's reprobe classifies whichever state\n+ * results. Returns 0 once finalised, \u003c0 on error. Does NOT recover a counter\n+ * already underflowed by an earlier oversized-chunk attempt - that needs a\n+ * 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+\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+\t/* Let the loader validate and re-arm READY, or boot a surviving image;\n+\t * the caller's reprobe then classifies whichever state results.\n+\t */\n+\tmsleep(MXL862XX_FW_REBOOT_DELAY_MS);\n+\treturn 0;\n+}\n+\n+/* Background self-heal: drain a wedged download off the devlink flash path, so\n+ * the minutes-long recovery never holds the devlink lock. Scheduled from probe;\n+ * reprobes on success so the probe-time detection re-classifies the switch.\n+ */\n+void mxl862xx_rescue_heal_work_fn(struct work_struct *work)\n+{\n+\tstruct mxl862xx_priv *priv =\n+\t\tcontainer_of(work, struct mxl862xx_priv, rescue_heal_work);\n+\tstruct device *dev = \u0026priv-\u003emdiodev-\u003edev;\n+\tstruct mxl862xx_reprobe *ko;\n+\tint ret;\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\treturn;\n+\n+\t/* The interrupted transfer is finalised; reprobe so the probe-time\n+\t * detection brings the driver up -- flashable in rescue mode if the\n+\t * loader is at READY, or normally if a valid image booted. The refs\n+\t * are released by the reprobe once it completes.\n+\t */\n+\tif (!try_module_get(THIS_MODULE))\n+\t\treturn;\n+\tget_device(dev);\n+\tko = mxl862xx_reprobe_alloc(dev);\n+\tif (!ko) {\n+\t\tput_device(dev);\n+\t\tmodule_put(THIS_MODULE);\n+\t\treturn;\n+\t}\n+\tqueue_delayed_work(system_long_wq, \u0026ko-\u003ework,\n+\t\t\t msecs_to_jiffies(MXL862XX_FW_REPROBE_DELAY_MS));\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 (MxL86281S tier): this driver does not\n+\t * support it -- the console flash path expects READY. Treat it as an\n+\t * unusable configuration, like any other unsupported state.\n+\t */\n+\tif ((u16)stat == MXL862XX_SB_PDI_DL_READY)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* Console loop at READY: confirm the live mailbox with the register-read\n+\t * challenge (consumes the marker from DATA and re-arms READY).\n+\t */\n+\tif ((u16)stat == MXL862XX_SB_PDI_READY) {\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,\n+\t\t\t\t\t MXL862XX_SB_PDI_RDREG_MARK);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t\t MXL862XX_SB_PDI_RDREG);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\trb = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\t MXL862XX_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 -ENXIO;\n+\t}\n+\n+\t/* Any other non-zero value is a running firmware, not a loader. */\n+\tif (stat)\n+\t\treturn MXL862XX_NOT_RESCUE;\n+\n+\t/* STAT == 0: a wedged receive loop 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 -ENXIO;\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 jump to end_magic, which just returns the\n+\t * error without signalling END -- see there.\n+\t */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,\n+\t\t\t\t\tMXL862XX_FW_READY_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: bootloader not ready: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 3: start handshake */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t MXL862XX_SB_PDI_START);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: start handshake failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto end_magic;\n+\t}\n+\n+\t/* Step 4: transfer image header */\n+\tmxl862xx_flash_notify(dl, \"Erasing flash\", 0, 0);\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\tfor (i = 0; i \u003c MXL862XX_FW_HDR_SIZE / 2; i++) {\n+\t\tword = fw-\u003edata[i * 2] |\n+\t\t ((u16)fw-\u003edata[i * 2 + 1] \u003c\u003c 8);\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto write_err;\n+\t}\n+\n+\tret = mxl862xx_sb_pdi_reset(priv);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* the byte count in STAT triggers the erase */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t MXL862XX_FW_HDR_SIZE);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\t/* ACK is byte count + 1 */\n+\tret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,\n+\t\t\t\t\tMXL862XX_FW_ACK_TIMEOUT_MS);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003emdiodev-\u003edev,\n+\t\t\t\"flash: header ACK failed: %pe\\n\", ERR_PTR(ret));\n+\t\tgoto 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+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,\n+\t\t\t\t MXL862XX_SB_PDI_CTRL_WR);\n+\tif (ret \u003c 0)\n+\t\tgoto write_err;\n+\n+\twhile (idx \u003c payload_size) {\n+\t\tcond_resched();\n+\t\tif (idx + 1 \u003c payload_size) {\n+\t\t\tfdata = payload[idx] |\n+\t\t\t\t((u16)payload[idx + 1] \u003c\u003c 8);\n+\t\t\tidx += 2;\n+\t\t\tdata_written += 2;\n+\t\t} else {\n+\t\t\tfdata = payload[idx];\n+\t\t\tidx++;\n+\t\t\tdata_written++;\n+\t\t}\n+\n+\t\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto write_err;\n+\t\tword_idx++;\n+\n+\t\tif (idx \u003e= payload_size) {\n+\t\t\tret = mxl862xx_sb_pdi_flush_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\tgoto end_magic;\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+\n+\t/* Success: the loader has left the receive loop at r_remain == 0 and\n+\t * is back in its command loop, where END(0x3cc3) is a finalise/boot\n+\t * request rather than a byte count. Signal it here -- and only here --\n+\t * to boot the freshly written image.\n+\t */\n+\tret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,\n+\t\t\t\t MXL862XX_SB_PDI_END);\n+\tmsleep(MXL862XX_FW_REBOOT_DELAY_MS);\n+\treturn ret;\n+\n+write_err:\n+\tdev_err(\u0026priv-\u003emdiodev-\u003edev, \"flash: SMDIO write failed: %pe\\n\",\n+\t\tERR_PTR(ret));\n+end_magic:\n+\t/* A failure leaves the loader mid transfer; do not signal END (a STAT\n+\t * write is a byte count then, and END would be misread as one, risking\n+\t * a receive-counter underflow). Return the error; the caller reprobes.\n+\t */\n+\treturn ret;\n+}\n+\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t struct devlink_info_req *req,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\n+\tchar buf[16];\n+\tint ret;\n+\n+\t/* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The\n+\t * fw version doubles as the \"ready to flash\" signal: report it only\n+\t * once the loader is at a clean READY, nothing while still draining.\n+\t */\n+\tif (priv-\u003erescue_mode) {\n+\t\tif (!READ_ONCE(priv-\u003erescue_ready))\n+\t\t\treturn 0;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"%u.%u.%u\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\t\tret = devlink_info_version_running_put(req,\n+\t\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\treturn devlink_info_version_stored_put(req,\n+\t\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\t}\n+\n+\t/* A 0 part number means the CHIP ID read failed or the part is\n+\t * unfused; omit it rather than publish a bogus \"0000\" that fwupd\n+\t * would match firmware against -- it then falls back to the driver\n+\t * name.\n+\t */\n+\tif (priv-\u003easic_id) {\n+\t\tsnprintf(buf, sizeof(buf), \"%04X\", priv-\u003easic_id);\n+\t\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t\t DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,\n+\t\t\t\t\t\t buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"%u\", priv-\u003easic_rev);\n+\t\tret = devlink_info_version_fixed_put(req,\n+\t\t\t\t\t\t DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,\n+\t\t\t\t\t\t buf);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tsnprintf(buf, sizeof(buf), \"%u.%u.%u\",\n+\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t priv-\u003efw_version.revision);\n+\n+\tret = devlink_info_version_running_put(req,\n+\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* boots this image from its own flash: stored == running */\n+\treturn devlink_info_version_stored_put(req,\n+\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW, buf);\n+}\n+\n+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n+\t\t\t\t struct devlink_flash_update_params *params,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct mxl862xx_reprobe *ko;\n+\tstruct mxl862xx_priv *priv = ds-\u003epriv;\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+\t/* Allocate the reprobe work before disturbing the switch, so an\n+\t * -ENOMEM here cannot strand it flashed but never reprobed.\n+\t */\n+\tko = mxl862xx_reprobe_alloc(ds-\u003edev);\n+\tif (!ko) {\n+\t\tput_device(ds-\u003edev);\n+\t\tmodule_put(THIS_MODULE);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tif (priv-\u003erescue_mode)\n+\t\tdev_info(ds-\u003edev,\n+\t\t\t \"flash: flashing switch via MCUboot rescue mode\\n\");\n+\telse\n+\t\tdev_info(ds-\u003edev, \"flash: running firmware %u.%u.%u\\n\",\n+\t\t\t priv-\u003efw_version.major, priv-\u003efw_version.minor,\n+\t\t\t priv-\u003efw_version.revision);\n+\n+\t/* Close ports while the firmware is still alive so the DSA core's\n+\t * MDB/FDB tracking is drained, and detach user ports so userspace\n+\t * cannot reopen them during the flash. The conduit is only closed,\n+\t * not detached: it belongs to the MAC driver. This driver binds a\n+\t * single switch with a direct host link and no cascade ports, so the\n+\t * conduit serves only this switch, and flashing it reboots the switch,\n+\t * which takes the tree down regardless.\n+\t */\n+\trtnl_lock();\n+\tdsa_switch_for_each_user_port(dp, ds) {\n+\t\tif (dp-\u003euser) {\n+\t\t\tdev_close(dp-\u003euser);\n+\t\t\tnetif_device_detach(dp-\u003euser);\n+\t\t}\n+\t}\n+\tdsa_switch_for_each_cpu_port(dp, ds)\n+\t\tdev_close(dp-\u003econduit);\n+\t/* The bridge defers the STP state changes triggered by closing\n+\t * the ports; let them reach the firmware while it is still alive.\n+\t */\n+\tswitchdev_deferred_process();\n+\trtnl_unlock();\n+\n+\tmutex_lock_nested(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock, MDIO_MUTEX_NESTED);\n+\tpriv-\u003eblock_host = true;\n+\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n+\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-\u003eblock_host = false;\n+\t\tpriv-\u003erescue_mode = false;\n+\t\tmutex_unlock(\u0026priv-\u003emdiodev-\u003ebus-\u003emdio_lock);\n+\n+\t\t/* Refresh the cached versions so the flash update only\n+\t\t * completes once the new firmware is confirmed running and\n+\t\t * devlink dev info reports it. Must happen before setting\n+\t\t * skip_teardown, which discards all firmware API reads.\n+\t\t */\n+\t\tret = mxl862xx_wait_ready(ds);\n+\t\tif (ret)\n+\t\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t\t \"new firmware did not become ready\");\n+\t}\n+\n+\tif (ret) {\n+\t\t/* The switch is in MCUboot with erased or partly written flash;\n+\t\t * drop the cached identity so devlink dev info stops reporting\n+\t\t * the pre-flash version until the reprobe re-reads the truth.\n+\t\t */\n+\t\tmemset(\u0026priv-\u003efw_version, 0, sizeof(priv-\u003efw_version));\n+\t\tpriv-\u003easic_id = 0;\n+\t}\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/* Queue the reprobe last; the work was allocated up front and its\n+\t * module and device references are already held.\n+\t */\n+\tqueue_delayed_work(system_long_wq, \u0026ko-\u003ework,\n+\t\t\t msecs_to_jiffies(MXL862XX_FW_REPROBE_DELAY_MS));\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 0000000000000..7cd87c7ad8716\n--- /dev/null\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h\n@@ -0,0 +1,20 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+\n+#ifndef __MXL862XX_FW_H\n+#define __MXL862XX_FW_H\n+\n+#include \u003cnet/dsa.h\u003e\n+\n+struct mxl862xx_priv;\n+struct work_struct;\n+\n+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);\n+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);\n+int mxl862xx_devlink_info_get(struct dsa_switch *ds,\n+\t\t\t struct devlink_info_req *req,\n+\t\t\t struct netlink_ext_ack *extack);\n+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,\n+\t\t\t\t struct devlink_flash_update_params *params,\n+\t\t\t\t struct netlink_ext_ack *extack);\n+\n+#endif /* __MXL862XX_FW_H */\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c\nindex 4acd216f7cc00..2dbd074c0fe26 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 66d6ae198aff4..4e054c6e4c0e4 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h\n@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,\n \tmxl862xx_api_wrap(dev, cmd, \u0026(data), sizeof((data)), true, true)\n \n int mxl862xx_reset(struct mxl862xx_priv *priv);\n+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);\n+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);\n \n #endif /* __MXL862XX_HOST_H */\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\nindex b689652aa9b92..a5b6940b552e9 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c\n@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,\n \n \tswitch (port) {\n \tcase 9 ... 16:\n+\t\tif (priv-\u003erescue_mode)\n+\t\t\treturn NULL;\n \t\tif (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {\n \t\t\tdev_warn_once(dp-\u003eds-\u003edev,\n \t\t\t\t \"SerDes PCS unsupported on old firmware.\\n\");\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c\nindex 45d237b3a40f1..8d21747cdf15e 100644\n--- a/drivers/net/dsa/mxl862xx/mxl862xx.c\n+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c\n@@ -21,6 +21,7 @@\n #include \"mxl862xx.h\"\n #include \"mxl862xx-api.h\"\n #include \"mxl862xx-cmd.h\"\n+#include \"mxl862xx-fw.h\"\n #include \"mxl862xx-host.h\"\n #include \"mxl862xx-phylink.h\"\n \n@@ -71,6 +72,13 @@ static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {\n #define MXL862XX_READY_TIMEOUT_MS\t10000\n #define MXL862XX_READY_POLL_MS\t\t100\n \n+/* Chip ID registers, read via SYS_MISC_REG_RD */\n+#define MXL862XX_CHIPID_L\t\t0xc0d28884\n+#define MXL862XX_CHIPID_M\t\t0xc0d28888\n+#define MXL862XX_CHIPID_L_PNUML\t\tGENMASK(15, 12)\n+#define MXL862XX_CHIPID_M_PNUMM\t\tGENMASK(11, 0)\n+#define MXL862XX_CHIPID_M_VERSION\tGENMASK(14, 12)\n+\n #define MXL862XX_TCM_INST_SEL\t\t0xe00\n #define MXL862XX_TCM_CBS\t\t0xe12\n #define MXL862XX_TCM_EBS\t\t0xe13\n@@ -222,7 +230,46 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr,\n \treturn mxl862xx_phy_write_mmd(bus-\u003epriv, addr, devadd, regnum, val);\n }\n \n-static int mxl862xx_wait_ready(struct dsa_switch *ds)\n+/* Read the static chip part number and version from the CHIP ID\n+ * registers. Only possible with a running firmware, so the values are\n+ * cached at setup and left zero when the switch is in rescue mode.\n+ */\n+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)\n+{\n+\tstruct mxl862xx_sys_reg_rw reg = {};\n+\tu16 chipid_l, chipid_m;\n+\tint ret;\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_L);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_l = le32_to_cpu(reg.val);\n+\n+\treg.addr = cpu_to_le32(MXL862XX_CHIPID_M);\n+\tret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);\n+\tif (ret)\n+\t\treturn ret;\n+\tchipid_m = le32_to_cpu(reg.val);\n+\n+\tpriv-\u003easic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |\n+\t\t\tFIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) \u003c\u003c 4;\n+\tpriv-\u003easic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mxl862xx_wait_ready - wait for the switch firmware to become operational\n+ * @ds: DSA switch instance\n+ *\n+ * Poll the firmware until it reports its version and accepts\n+ * configuration commands, then cache the firmware version and chip ID.\n+ * Takes at least two seconds.\n+ *\n+ * Return: 0 on success or a negative error code.\n+ */\n+int mxl862xx_wait_ready(struct dsa_switch *ds)\n {\n \tstruct mxl862xx_sys_fw_image_version ver = {};\n \tunsigned long start = jiffies, timeout;\n@@ -254,6 +301,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)\n \t\tpriv-\u003efw_version.major = ver.iv_major;\n \t\tpriv-\u003efw_version.minor = ver.iv_minor;\n \t\tpriv-\u003efw_version.revision = le16_to_cpu(ver.iv_revision);\n+\n+\t\tret = mxl862xx_read_chip_id(priv);\n+\t\tif (ret)\n+\t\t\tdev_warn(ds-\u003edev, \"failed to read chip ID: %pe\\n\",\n+\t\t\t\t ERR_PTR(ret));\n \t\treturn 0;\n \n not_ready_yet:\n@@ -622,15 +674,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 +801,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 +1433,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 +1674,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 +1735,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@@ -2015,6 +2125,12 @@ static void mxl862xx_stats_work_fn(struct work_struct *work)\n \tstruct dsa_switch *ds = priv-\u003eds;\n \tstruct dsa_port *dp;\n \n+\t/* A get_stats64() re-arm can race the flash teardown's WORK_STOPPED\n+\t * set and cancel; bail here so a stray poll never runs during a flash.\n+\t */\n+\tif (test_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags))\n+\t\treturn;\n+\n \tdsa_switch_for_each_available_port(dp, ds)\n \t\tmxl862xx_stats_poll(ds, dp-\u003eindex);\n \n@@ -2086,6 +2202,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {\n \t.get_pause_stats = mxl862xx_get_pause_stats,\n \t.get_rmon_stats = mxl862xx_get_rmon_stats,\n \t.get_stats64 = mxl862xx_get_stats64,\n+\t.devlink_info_get = mxl862xx_devlink_info_get,\n+\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n };\n \n static int mxl862xx_probe(struct mdio_device *mdiodev)\ndiff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h\nindex 432a5f3f2e08e..129985f2bbf3d 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,7 +357,13 @@ 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 \n+int mxl862xx_wait_ready(struct dsa_switch *ds);\n+\n #endif /* __MXL862XX_H */\ndiff --git a/include/net/dsa.h b/include/net/dsa.h\nindex 8c16ef23cc102..c9e19348de619 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 ed342f345692b..d6022267a839c 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/30 03:30 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flashing support via devlink to the `mxl862xx` DSA switch driver. This driver is for MaxLinear MxL862xx hardware switches connected via an MDIO bus. Since there is no software emulator for this specific hardware in standard QEMU, and no generic way to instantiate arbitrary MDIO devices from userspace without device tree support, the probe function of this driver will never be executed in a virtualized fuzzing environment. Consequently, the newly added devlink callbacks are unreachable.",
"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 e5677565300846a3bf69735d1ccc33d005f45d2f
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Jul 30 03:30:30 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index 4745148fecf4f..058999d0dc56f 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 0000000000000..b3e9f7d4d4969
--- /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 2d420d40782e3..ed58911ef264c 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 a7be0e6669dfa..bccac0d0f703f 100644
--- a/drivers/net/dsa/mxl862xx/Makefile
+++ b/drivers/net/dsa/mxl862xx/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index a180a5decffc0..6f771895984cb 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -1224,6 +1224,16 @@ struct mxl862xx_sys_fw_image_version {
__le32 iv_build_num;
} __packed;
+/**
+ * struct mxl862xx_sys_reg_rw - System register read/write
+ * @addr: 32-bit register address
+ * @val: register value
+ */
+struct mxl862xx_sys_reg_rw {
+ __le32 addr;
+ __le32 val;
+} __packed;
+
/**
* enum mxl862xx_port_type - Port Type
* @MXL862XX_LOGICAL_PORT: Logical Port
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c48..a865425aa61e1 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -70,7 +70,9 @@
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
+#define SYS_MISC_FW_UPDATE (SYS_MISC_MAGIC + 0x1)
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
+#define SYS_MISC_REG_RD (SYS_MISC_MAGIC + 0x8)
#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
#define MXL862XX_XPCS_PCS_GET_STATE (MXL862XX_XPCS_MAGIC + 0x2)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
new file mode 100644
index 0000000000000..9446c2626765c
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,955 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Firmware flash and devlink support for MaxLinear MxL862xx
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ *
+ * SB PDI - firmware download interface over clause-22 SMDIO
+ * =========================================================
+ *
+ * The MxL862xx MCUboot loader accepts a firmware image through four "SB PDI"
+ * registers in the switch SMDIO register space. It runs whenever no WSP
+ * firmware is active: the normal firmware update enters it deliberately - the
+ * SYS_MISC_FW_UPDATE API command sets a sticky rescue bit and reboots into
+ * MCUboot - and the loader also stays here when the stored WSP firmware fails
+ * its boot-time integrity check. This driver drives the loader's 0xc55c
+ * "console" download path.
+ *
+ * SMDIO register access (mxl862xx_smdio_read/write):
+ * MII reg 0x1f := (<sb_pdi_reg> & 0xfff0) ; page latch
+ * MII reg (<sb_pdi_reg> & 0x000f) := / => <u16 data>
+ * so CTRL/ADDR/DATA/STAT (0xe100..0xe103) are MII regs 0/1/2/3 of page
+ * 0xe100, not all reg 0x00.
+ *
+ * SB PDI registers (host name/addr -> MCU mailbox):
+ * CTRL 0xe100 -> 0xc0938400 mode: RST=0x00 RD=0x01 WR=0x02
+ * ADDR 0xe101 -> 0xc0938404 SB target word address (SB1 bank = 0x7800)
+ * DATA 0xe102 -> 0xc0938408 16-bit data / reply word
+ * STAT 0xe103 -> 0xc093840c handshake: a magic (below) or a byte count
+ *
+ * STAT magics:
+ * READY 0xc55c loader idle in the console loop (this driver)
+ * DL_RDY 0xc33c loader idle in the flashless loop
+ * START 0xf48f host -> begin download session
+ * ACK 0xf490 loader -> START acknowledged (START + 1)
+ * END 0x3cc3 host -> 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/iopoll.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)
+{
+ int ret, val;
+
+ ret = read_poll_timeout(mxl862xx_smdio_read, val,
+ val < 0 || (u16)val == expected,
+ 10000, timeout_ms * 1000, false,
+ priv, MXL862XX_SB_PDI_STAT);
+ if (val < 0)
+ return val;
+ return ret;
+}
+
+static int mxl862xx_sb_pdi_flush_slice(struct mxl862xx_priv *priv,
+ u32 data_written)
+{
+ int ret;
+
+ ret = mxl862xx_sb_pdi_reset(priv);
+ if (ret < 0)
+ return ret;
+
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);
+ if (ret < 0)
+ return ret;
+
+ return mxl862xx_sb_pdi_poll_stat(priv, 0,
+ MXL862XX_FW_WRITE_TIMEOUT_MS);
+}
+
+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);
+}
+
+/* Post-flash reprobe. device_reprobe() detaches the driver -- running
+ * remove(), which frees priv -- then re-probes, so this work touches only its
+ * own device and module references and frees itself. A re-probe failure leaves
+ * the device unbound, exactly as a failed initial probe would, so it is only
+ * logged. Running from a workqueue keeps device_reprobe() out of the devlink
+ * caller's locking and signal context.
+ */
+struct mxl862xx_reprobe {
+ struct delayed_work work;
+ struct device *dev;
+};
+
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_reprobe *rp =
+ container_of(work, struct mxl862xx_reprobe, work.work);
+ struct device *dev = rp->dev;
+
+ if (device_reprobe(dev))
+ dev_err(dev, "reprobe failed; device left unbound\n");
+ put_device(dev);
+ kfree(rp);
+ module_put(THIS_MODULE);
+}
+
+/* Allocate the reprobe up front, before the switch is disturbed, so an
+ * allocation failure aborts cleanly. The caller holds a module and a device
+ * reference; the work releases both once it runs. Returns NULL on -ENOMEM.
+ */
+static struct mxl862xx_reprobe *mxl862xx_reprobe_alloc(struct device *dev)
+{
+ struct mxl862xx_reprobe *rp;
+
+ rp = kzalloc_obj(*rp);
+ if (!rp)
+ return NULL;
+ rp->dev = dev;
+ INIT_DELAYED_WORK(&rp->work, mxl862xx_reprobe_work_fn);
+ return rp;
+}
+
+/* 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)
+{
+ int val;
+
+ read_poll_timeout(mxl862xx_smdio_read, val,
+ val < 0 || (u16)val == MXL862XX_SB_PDI_READY ||
+ (u16)val == 0,
+ 50, timeout_ms * 1000, false,
+ priv, MXL862XX_SB_PDI_STAT);
+ if (val < 0)
+ return val;
+ if ((u16)val == MXL862XX_SB_PDI_READY)
+ return MXL862XX_DRAIN_READY;
+ if ((u16)val == 0)
+ return MXL862XX_DRAIN_FEED;
+ 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 validates the (now corrupt) image and re-arms
+ * READY, or boots a valid image that happened to survive in flash. This only
+ * finalises the transfer; the caller's reprobe classifies whichever state
+ * results. Returns 0 once finalised, <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;
+
+ 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;
+
+ /* Let the loader validate and re-arm READY, or boot a surviving image;
+ * the caller's reprobe then classifies whichever state results.
+ */
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+ return 0;
+}
+
+/* Background self-heal: drain a wedged download off the devlink flash path, so
+ * the minutes-long recovery never holds the devlink lock. Scheduled from probe;
+ * reprobes on success so the probe-time detection re-classifies the switch.
+ */
+void mxl862xx_rescue_heal_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_priv *priv =
+ container_of(work, struct mxl862xx_priv, rescue_heal_work);
+ struct device *dev = &priv->mdiodev->dev;
+ struct mxl862xx_reprobe *ko;
+ int ret;
+
+ ret = mxl862xx_rescue_drain(priv);
+ if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+ return;
+ if (ret)
+ return;
+
+ /* The interrupted transfer is finalised; reprobe so the probe-time
+ * detection brings the driver up -- flashable in rescue mode if the
+ * loader is at READY, or normally if a valid image booted. The refs
+ * are released by the reprobe once it completes.
+ */
+ if (!try_module_get(THIS_MODULE))
+ return;
+ get_device(dev);
+ ko = mxl862xx_reprobe_alloc(dev);
+ if (!ko) {
+ put_device(dev);
+ module_put(THIS_MODULE);
+ return;
+ }
+ queue_delayed_work(system_long_wq, &ko->work,
+ msecs_to_jiffies(MXL862XX_FW_REPROBE_DELAY_MS));
+}
+
+/* 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 (MxL86281S tier): this driver does not
+ * support it -- the console flash path expects READY. Treat it as an
+ * unusable configuration, like any other unsupported state.
+ */
+ if ((u16)stat == MXL862XX_SB_PDI_DL_READY)
+ return -EOPNOTSUPP;
+
+ /* Console loop at READY: confirm the live mailbox with the register-read
+ * challenge (consumes the marker from DATA and re-arms READY).
+ */
+ if ((u16)stat == MXL862XX_SB_PDI_READY) {
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+ MXL862XX_SB_PDI_RDREG_MARK);
+ if (ret < 0)
+ return ret;
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_RDREG);
+ if (ret < 0)
+ return ret;
+ rb = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+ MXL862XX_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 -ENXIO;
+ }
+
+ /* Any other non-zero value is a running firmware, not a loader. */
+ if (stat)
+ return MXL862XX_NOT_RESCUE;
+
+ /* STAT == 0: a wedged receive loop 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 -ENXIO;
+}
+
+/* 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 jump to end_magic, which just returns the
+ * error without signalling END -- see there.
+ */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+ MXL862XX_FW_READY_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: bootloader not ready: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 3: start handshake */
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_START);
+ if (ret < 0)
+ goto write_err;
+
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: start handshake failed: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 4: transfer image header */
+ mxl862xx_flash_notify(dl, "Erasing flash", 0, 0);
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ if (ret < 0)
+ goto write_err;
+
+ for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
+ word = fw->data[i * 2] |
+ ((u16)fw->data[i * 2 + 1] << 8);
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
+ if (ret < 0)
+ goto write_err;
+ }
+
+ ret = mxl862xx_sb_pdi_reset(priv);
+ if (ret < 0)
+ goto write_err;
+
+ /* the byte count in STAT triggers the erase */
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_FW_HDR_SIZE);
+ if (ret < 0)
+ goto write_err;
+
+ /* ACK is byte count + 1 */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: header ACK failed: %pe\n", ERR_PTR(ret));
+ goto 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 */
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ if (ret < 0)
+ goto write_err;
+
+ while (idx < payload_size) {
+ cond_resched();
+ if (idx + 1 < payload_size) {
+ fdata = payload[idx] |
+ ((u16)payload[idx + 1] << 8);
+ idx += 2;
+ data_written += 2;
+ } else {
+ fdata = payload[idx];
+ idx++;
+ data_written++;
+ }
+
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
+ if (ret < 0)
+ goto write_err;
+ word_idx++;
+
+ if (idx >= payload_size) {
+ ret = mxl862xx_sb_pdi_flush_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));
+ goto end_magic;
+ }
+ 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);
+
+ /* Success: the loader has left the receive loop at r_remain == 0 and
+ * is back in its command loop, where END(0x3cc3) is a finalise/boot
+ * request rather than a byte count. Signal it here -- and only here --
+ * to boot the freshly written image.
+ */
+ ret = mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_END);
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+ return ret;
+
+write_err:
+ dev_err(&priv->mdiodev->dev, "flash: SMDIO write failed: %pe\n",
+ ERR_PTR(ret));
+end_magic:
+ /* A failure leaves the loader mid transfer; do not signal END (a STAT
+ * write is a byte count then, and END would be misread as one, risking
+ * a receive-counter underflow). Return the error; the caller reprobes.
+ */
+ return ret;
+}
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ char buf[16];
+ int ret;
+
+ /* No chip-id/revision in MCUboot (needs the firmware MMD mailbox). The
+ * fw version doubles as the "ready to flash" signal: report it only
+ * once the loader is at a clean READY, nothing while still draining.
+ */
+ if (priv->rescue_mode) {
+ if (!READ_ONCE(priv->rescue_ready))
+ return 0;
+
+ snprintf(buf, sizeof(buf), "%u.%u.%u",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+ ret = devlink_info_version_running_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+ if (ret)
+ return ret;
+ return devlink_info_version_stored_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+ }
+
+ /* A 0 part number means the CHIP ID read failed or the part is
+ * unfused; omit it rather than publish a bogus "0000" that fwupd
+ * would match firmware against -- it then falls back to the driver
+ * name.
+ */
+ if (priv->asic_id) {
+ snprintf(buf, sizeof(buf), "%04X", priv->asic_id);
+ ret = devlink_info_version_fixed_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
+ buf);
+ if (ret)
+ return ret;
+
+ snprintf(buf, sizeof(buf), "%u", priv->asic_rev);
+ ret = devlink_info_version_fixed_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,
+ buf);
+ if (ret)
+ return ret;
+ }
+
+ snprintf(buf, sizeof(buf), "%u.%u.%u",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ ret = devlink_info_version_running_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+ if (ret)
+ return ret;
+
+ /* boots this image from its own flash: stored == running */
+ return devlink_info_version_stored_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW, buf);
+}
+
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_reprobe *ko;
+ struct mxl862xx_priv *priv = ds->priv;
+ 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);
+
+ /* Allocate the reprobe work before disturbing the switch, so an
+ * -ENOMEM here cannot strand it flashed but never reprobed.
+ */
+ ko = mxl862xx_reprobe_alloc(ds->dev);
+ if (!ko) {
+ put_device(ds->dev);
+ module_put(THIS_MODULE);
+ return -ENOMEM;
+ }
+
+ if (priv->rescue_mode)
+ dev_info(ds->dev,
+ "flash: flashing switch via MCUboot rescue mode\n");
+ else
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ /* Close ports while the firmware is still alive so the DSA core's
+ * MDB/FDB tracking is drained, and detach user ports so userspace
+ * cannot reopen them during the flash. The conduit is only closed,
+ * not detached: it belongs to the MAC driver. This driver binds a
+ * single switch with a direct host link and no cascade ports, so the
+ * conduit serves only this switch, and flashing it reboots the switch,
+ * which takes the tree down regardless.
+ */
+ rtnl_lock();
+ dsa_switch_for_each_user_port(dp, ds) {
+ if (dp->user) {
+ dev_close(dp->user);
+ netif_device_detach(dp->user);
+ }
+ }
+ dsa_switch_for_each_cpu_port(dp, ds)
+ dev_close(dp->conduit);
+ /* The bridge defers the STP state changes triggered by closing
+ * the ports; let them reach the firmware while it is still alive.
+ */
+ switchdev_deferred_process();
+ rtnl_unlock();
+
+ mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+ priv->block_host = true;
+ mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+ set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
+ 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->block_host = false;
+ priv->rescue_mode = false;
+ mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+ /* Refresh the cached versions so the flash update only
+ * completes once the new firmware is confirmed running and
+ * devlink dev info reports it. Must happen before setting
+ * skip_teardown, which discards all firmware API reads.
+ */
+ ret = mxl862xx_wait_ready(ds);
+ if (ret)
+ NL_SET_ERR_MSG_MOD(extack,
+ "new firmware did not become ready");
+ }
+
+ if (ret) {
+ /* The switch is in MCUboot with erased or partly written flash;
+ * drop the cached identity so devlink dev info stops reporting
+ * the pre-flash version until the reprobe re-reads the truth.
+ */
+ memset(&priv->fw_version, 0, sizeof(priv->fw_version));
+ priv->asic_id = 0;
+ }
+
+ mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+ priv->skip_teardown = true;
+ mutex_unlock(&priv->mdiodev->bus->mdio_lock);
+
+ /* Queue the reprobe last; the work was allocated up front and its
+ * module and device references are already held.
+ */
+ queue_delayed_work(system_long_wq, &ko->work,
+ msecs_to_jiffies(MXL862XX_FW_REPROBE_DELAY_MS));
+
+ 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 0000000000000..7cd87c7ad8716
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+struct mxl862xx_priv;
+struct work_struct;
+
+int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);
+void mxl862xx_rescue_heal_work_fn(struct work_struct *work);
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack);
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc00..2dbd074c0fe26 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 66d6ae198aff4..4e054c6e4c0e4 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
int mxl862xx_reset(struct mxl862xx_priv *priv);
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);
#endif /* __MXL862XX_HOST_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
index b689652aa9b92..a5b6940b552e9 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
switch (port) {
case 9 ... 16:
+ if (priv->rescue_mode)
+ return NULL;
if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {
dev_warn_once(dp->ds->dev,
"SerDes PCS unsupported on old firmware.\n");
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 45d237b3a40f1..8d21747cdf15e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -21,6 +21,7 @@
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
@@ -71,6 +72,13 @@ static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {
#define MXL862XX_READY_TIMEOUT_MS 10000
#define MXL862XX_READY_POLL_MS 100
+/* Chip ID registers, read via SYS_MISC_REG_RD */
+#define MXL862XX_CHIPID_L 0xc0d28884
+#define MXL862XX_CHIPID_M 0xc0d28888
+#define MXL862XX_CHIPID_L_PNUML GENMASK(15, 12)
+#define MXL862XX_CHIPID_M_PNUMM GENMASK(11, 0)
+#define MXL862XX_CHIPID_M_VERSION GENMASK(14, 12)
+
#define MXL862XX_TCM_INST_SEL 0xe00
#define MXL862XX_TCM_CBS 0xe12
#define MXL862XX_TCM_EBS 0xe13
@@ -222,7 +230,46 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr,
return mxl862xx_phy_write_mmd(bus->priv, addr, devadd, regnum, val);
}
-static int mxl862xx_wait_ready(struct dsa_switch *ds)
+/* Read the static chip part number and version from the CHIP ID
+ * registers. Only possible with a running firmware, so the values are
+ * cached at setup and left zero when the switch is in rescue mode.
+ */
+static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_sys_reg_rw reg = {};
+ u16 chipid_l, chipid_m;
+ int ret;
+
+ reg.addr = cpu_to_le32(MXL862XX_CHIPID_L);
+ ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+ if (ret)
+ return ret;
+ chipid_l = le32_to_cpu(reg.val);
+
+ reg.addr = cpu_to_le32(MXL862XX_CHIPID_M);
+ ret = MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg);
+ if (ret)
+ return ret;
+ chipid_m = le32_to_cpu(reg.val);
+
+ priv->asic_id = FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) |
+ FIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) << 4;
+ priv->asic_rev = FIELD_GET(MXL862XX_CHIPID_M_VERSION, chipid_m);
+
+ return 0;
+}
+
+/**
+ * mxl862xx_wait_ready - wait for the switch firmware to become operational
+ * @ds: DSA switch instance
+ *
+ * Poll the firmware until it reports its version and accepts
+ * configuration commands, then cache the firmware version and chip ID.
+ * Takes at least two seconds.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int mxl862xx_wait_ready(struct dsa_switch *ds)
{
struct mxl862xx_sys_fw_image_version ver = {};
unsigned long start = jiffies, timeout;
@@ -254,6 +301,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)
priv->fw_version.major = ver.iv_major;
priv->fw_version.minor = ver.iv_minor;
priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
+
+ ret = mxl862xx_read_chip_id(priv);
+ if (ret)
+ dev_warn(ds->dev, "failed to read chip ID: %pe\n",
+ ERR_PTR(ret));
return 0;
not_ready_yet:
@@ -622,15 +674,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 +801,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 +1433,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 +1674,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 +1735,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);
@@ -2015,6 +2125,12 @@ static void mxl862xx_stats_work_fn(struct work_struct *work)
struct dsa_switch *ds = priv->ds;
struct dsa_port *dp;
+ /* A get_stats64() re-arm can race the flash teardown's WORK_STOPPED
+ * set and cancel; bail here so a stray poll never runs during a flash.
+ */
+ if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+ return;
+
dsa_switch_for_each_available_port(dp, ds)
mxl862xx_stats_poll(ds, dp->index);
@@ -2086,6 +2202,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .devlink_info_get = mxl862xx_devlink_info_get,
+ .devlink_flash_update = mxl862xx_devlink_flash_update,
};
static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08e..129985f2bbf3d 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,7 +357,13 @@ 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;
};
+int mxl862xx_wait_ready(struct dsa_switch *ds);
+
#endif /* __MXL862XX_H */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc102..c9e19348de619 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 ed342f345692b..d6022267a839c 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/30 03:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25432 output: 20 thoughts: 470 Thoughts: **Analyzing the MxL862xx Devlink Flash Patch** Okay, so I'm looking at this patch that adds devlink flash support to the `mxl862xx` DSA switch driver. It's modifying several files, including a new one, `mxl862xx-fw.c`, and the core driver files like `mxl862xx.c` and `mxl862xx-host.c`. The patch introduces `mxl862xx_devlink_flash_update` and `mxl862xx_devlink_info_get`, which is what you'd expect. Now, my immediate thought is, can I actually test this in a virtualized environment, specifically with tools like QEMU or syzkaller? The `mxl862xx` is a MaxLinear MxL862xx switch, and it's a DSA switch driver. DSA switches usually interface via I2C, MDIO, or SPI, so I need to figure out the specific bus it uses. I'll need to check the `Kconfig` file in `drivers/net/dsa/mxl862xx` or possibly `mxl862xx.c` itself. Aha, the patch adds `mxl862xx_smdio_read` and `mxl862xx_smdio_write`, which call `__mdiobus_read` and `__mdiobus_write`. So, it's an MDIO device. The million-dollar question now is, can I emulate MDIO devices in QEMU or, more importantly, in syzkaller? Syzkaller does have *some* support for MDIO/PHY devices, potentially via `macb` or `mdio` pseudo-devices, but wait… I don't think syzkaller has a generic MDIO device emulator. It looks like it's a no-go there. Hmmm, the `mxl862xx` is a DSA switch, so it's typically probed through Device Tree (OF) or the MDIO bus. So even if syzkaller doesn't have an emulator, if there's a virtual MDIO bus it could inject the device into, I could potentially get away with it. Let me check if there is *any* existing support for MDIO or DSA switches within syzkaller. Right, syzkaller *can* inject I2C devices using `vhci` or `i2c-dev`, but MDIO? Not sure about that. I need to figure out if there is any way to actually *reach* this `mxl862xx` driver in a virtualized setting. Let's see if this is reachable. |
| 4/2 | 2026/07/30 03:30 | tool | grepper |
0mArgs:{
"Expression": "mxl862xx"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 2586 lines.\nUse more precise expression if possible.\n\nDocumentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml-3----\nDocumentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml:4:$id: http://devicetree.org/schemas/net/dsa/maxlinear,mxl862xx.yaml#\nDocumentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml-5-$schema: http://devicetree.org/meta-schemas/core.yaml#\n--\nDocumentation/networking/devlink/index.rst=77=parameters, info versions, and other features it supports.\n--\nDocumentation/networking/devlink/index.rst-96- mv88e6xxx\nDocumentation/networking/devlink/index.rst:97: mxl862xx\nDocumentation/networking/devlink/index.rst-98- netdevsim\n--\nDocumentation/networking/devlink/mxl862xx.rst-3-========================\nDocumentation/networking/devlink/mxl862xx.rst:4:mxl862xx devlink support\nDocumentation/networking/devlink/mxl862xx.rst-5-========================\n--\nDocumentation/networking/devlink/mxl862xx.rst=7=This document describes the devlink features implemented by the\nDocumentation/networking/devlink/mxl862xx.rst:8:``mxl862xx`` device driver.\nDocumentation/networking/devlink/mxl862xx.rst-9-\nDocumentation/networking/devlink/mxl862xx.rst=10=Info versions\n--\nDocumentation/networking/devlink/mxl862xx.rst-12-\nDocumentation/networking/devlink/mxl862xx.rst:13:The ``mxl862xx`` driver reports the following versions\nDocumentation/networking/devlink/mxl862xx.rst-14-\n--\nDocumentation/networking/devlink/mxl862xx.rst=45=Flash update\n--\nDocumentation/networking/devlink/mxl862xx.rst-47-\nDocumentation/networking/devlink/mxl862xx.rst:48:The ``mxl862xx`` driver implements support for ``devlink dev flash``.\nDocumentation/networking/devlink/mxl862xx.rst-49-The signed firmware image is transferred to the switch over the same\n--\nMAINTAINERS=16108=S:\tMaintained\nMAINTAINERS:16109:F:\tDocumentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml\nMAINTAINERS:16110:F:\tDocumentation/networking/devlink/mxl862xx.rst\nMAINTAINERS:16111:F:\tdrivers/net/dsa/mxl862xx/\nMAINTAINERS:16112:F:\tnet/dsa/tag_mxl862xx.c\nMAINTAINERS-16113-\n--\ndrivers/net/dsa/Kconfig=75=source \"drivers/net/dsa/mv88e6xxx/Kconfig\"\ndrivers/net/dsa/Kconfig-76-\ndrivers/net/dsa/Kconfig:77:source \"drivers/net/dsa/mxl862xx/Kconfig\"\ndrivers/net/dsa/Kconfig-78-\n--\ndrivers/net/dsa/Makefile=22=obj-y\t\t\t\t+= mv88e6xxx/\ndrivers/net/dsa/Makefile:23:obj-y\t\t\t\t+= mxl862xx/\ndrivers/net/dsa/Makefile-24-obj-y\t\t\t\t+= netc/\n--\ndrivers/net/dsa/mxl862xx/Makefile-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/mxl862xx/Makefile:2:obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o\ndrivers/net/dsa/mxl862xx/Makefile:3:mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h=16=struct mdio_relay_data {\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-23-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:24: * struct mxl862xx_register_mod - Register access parameter to directly\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-25- * modify internal registers\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-31- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:32:struct mxl862xx_register_mod {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-33-\t__le16 addr;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-38-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:39: * enum mxl862xx_mac_table_filter - Source/Destination MAC address filtering\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-40- *\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-45- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:46:enum mxl862xx_mac_table_filter {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-47-\tMXL862XX_MAC_FILTER_NONE = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-64-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:65: * struct mxl862xx_mac_table_add - MAC Table Entry to be added\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-66- * @fid: Filtering Identifier (FID) (not supported by all switches)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-74- * @mac: MAC Address to add to the table\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:75: * @filter_flag: See \u0026enum mxl862xx_mac_table_filter\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-76- * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-83- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:84:struct mxl862xx_mac_table_add {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-85-\t__le16 fid;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-100-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:101: * struct mxl862xx_mac_table_remove - MAC Table Entry to be removed\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-102- * @fid: Filtering Identifier (FID)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-103- * @mac: MAC Address to be removed from the table.\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:104: * @filter_flag: See \u0026enum mxl862xx_mac_table_filter\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-105- * @tci: TCI for B-Step\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-109- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:110:struct mxl862xx_mac_table_remove {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-111-\t__le16 fid;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-117-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:118: * struct mxl862xx_mac_table_read - MAC Table Entry to be read\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-119- * @initial: Restart the get operation from the beginning of the table\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-128- * @mac: MAC Address. Filled out by the switch API implementation.\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:129: * @filter_flag: See \u0026enum mxl862xx_mac_table_filter\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-130- * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-141- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:142:struct mxl862xx_mac_table_read {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-143-\tu8 initial;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-162-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:163: * struct mxl862xx_mac_table_query - MAC Table Entry key-based lookup\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-164- * @mac: MAC Address to search for (input)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-172- * @static_entry: Indicates if this is a Static Entry\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:173: * @filter_flag: See \u0026enum mxl862xx_mac_table_filter (input+output)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-174- * @igmp_controlled: IGMP controlled flag\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-180- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:181:struct mxl862xx_mac_table_query {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-182-\tu8 mac[ETH_ALEN];\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-200-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:201: * enum mxl862xx_mac_clear_type - MAC table clear type\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-202- * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-204- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:205:enum mxl862xx_mac_clear_type {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-206-\tMXL862XX_MAC_CLEAR_PHY_PORT = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-210-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:211: * struct mxl862xx_mac_table_clear - MAC table clear\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:212: * @type: see \u0026enum mxl862xx_mac_clear_type\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-213- * @port_id: physical port id\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-214- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:215:struct mxl862xx_mac_table_clear {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-216-\tu8 type;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-220-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:221: * enum mxl862xx_age_timer - Aging Timer Value.\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-222- * @MXL862XX_AGETIMER_1_SEC: 1 second aging time\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-228- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:229:enum mxl862xx_age_timer {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-230-\tMXL862XX_AGETIMER_1_SEC = 1,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-238-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:239: * struct mxl862xx_bridge_alloc - Bridge Allocation\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-240- * @bridge_id: If the bridge allocation is successful, a valid ID will be\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-247- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:248:struct mxl862xx_bridge_alloc {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-249-\t__le16 bridge_id;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-252-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:253: * enum mxl862xx_bridge_config_mask - Bridge configuration mask\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-254- * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT:\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-267- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:268:enum mxl862xx_bridge_config_mask {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-269-\tMXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(0),\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-278-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:279: * enum mxl862xx_bridge_port_egress_meter - Meters for egress traffic type\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-280- * @MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST:\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-293- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:294:enum mxl862xx_bridge_port_egress_meter {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-295-\tMXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-304-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:305: * struct mxl862xx_qos_meter_cfg - Rate meter configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-306- * @enable: Enable/disable meter\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-320- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:321:struct mxl862xx_qos_meter_cfg {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-322-\tu8 enable;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-338-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:339: * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-340- * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-343- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:344:enum mxl862xx_bridge_forward_mode {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-345-\tMXL862XX_BRIDGE_FORWARD_FLOOD = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-349-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:350: * struct mxl862xx_bridge_config - Bridge Configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-351- * @bridge_id: Bridge ID (FID)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:352: * @mask: See \u0026enum mxl862xx_bridge_config_mask\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-353- * @mac_learning_limit_enable: Enable MAC learning limitation\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-363- * @forward_broadcast: Forwarding mode of broadcast traffic. See\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:364: * \u0026enum mxl862xx_bridge_forward_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-365- * @forward_unknown_multicast_ip: Forwarding mode of unknown multicast IP\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-366- * traffic.\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:367: * See \u0026enum mxl862xx_bridge_forward_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-368- * @forward_unknown_multicast_non_ip: Forwarding mode of unknown multicast\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-369- * non-IP traffic.\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:370: * See \u0026enum mxl862xx_bridge_forward_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-371- * @forward_unknown_unicast: Forwarding mode of unknown unicast traffic. See\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:372: * \u0026enum mxl862xx_bridge_forward_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-373- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:374:struct mxl862xx_bridge_config {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-375-\t__le16 bridge_id;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:376:\t__le32 mask; /* enum mxl862xx_bridge_config_mask */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-377-\tu8 mac_learning_limit_enable;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-382-\t__le16 traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX];\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:383:\t__le32 forward_broadcast; /* enum mxl862xx_bridge_forward_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:384:\t__le32 forward_unknown_multicast_ip; /* enum mxl862xx_bridge_forward_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:385:\t__le32 forward_unknown_multicast_non_ip; /* enum mxl862xx_bridge_forward_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:386:\t__le32 forward_unknown_unicast; /* enum mxl862xx_bridge_forward_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-387-} __packed;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-389-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:390: * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-391- * @bridge_port_id: If the bridge port allocation is successful, a valid ID\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-397- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:398:struct mxl862xx_bridge_port_alloc {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-399-\t__le16 bridge_port_id;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-402-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:403: * enum mxl862xx_bridge_port_config_mask - Bridge Port configuration mask\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-404- * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID:\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-462- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:463:enum mxl862xx_bridge_port_config_mask {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-464-\tMXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID = BIT(0),\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-491-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:492: * enum mxl862xx_color_marking_mode - Color Marking Mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-493- * @MXL862XX_MARKING_ALL_GREEN: mark packets (except critical) to green\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-501- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:502:enum mxl862xx_color_marking_mode {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-503-\tMXL862XX_MARKING_ALL_GREEN = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-513-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:514: * enum mxl862xx_color_remarking_mode - Color Remarking Mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-515- * @MXL862XX_REMARKING_NONE: values from last process stage\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-522- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:523:enum mxl862xx_color_remarking_mode {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-524-\tMXL862XX_REMARKING_NONE = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-533-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:534: * enum mxl862xx_pmapper_mapping_mode - P-mapper Mapping Mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-535- * @MXL862XX_PMAPPER_MAPPING_PCP: Use PCP for VLAN tagged packets to derive\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-541- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:542:enum mxl862xx_pmapper_mapping_mode {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-543-\tMXL862XX_PMAPPER_MAPPING_PCP = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-548-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:549: * struct mxl862xx_pmapper - P-mapper Configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-550- * @pmapper_id: Index of P-mapper (0-31)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-560- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:561:struct mxl862xx_pmapper {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-562-\t__le16 pmapper_id;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-566-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:567: * struct mxl862xx_bridge_port_config - Bridge Port Configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-568- * @bridge_port_id: Bridge Port ID allocated by bridge port allocation\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:569: * @mask: See \u0026enum mxl862xx_bridge_port_config_mask\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-570- * @bridge_id: Bridge ID (FID) to which this bridge port is associated\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-583- * @ingress_marking_mode: Ingress color marking mode. See\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:584: * \u0026enum mxl862xx_color_marking_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-585- * @egress_remarking_mode: Color remarking for egress traffic. See\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:586: * \u0026enum mxl862xx_color_remarking_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-587- * @ingress_metering_enable: Traffic metering on ingress traffic applies\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-597- * @pmapper_mapping_mode: P-mapper mapping mode. See\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:598: * \u0026enum mxl862xx_pmapper_mapping_mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-599- * @pmapper_id_valid: When true, P-mapper is re-used; when false,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-653- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:654:struct mxl862xx_bridge_port_config {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-655-\t__le16 bridge_port_id;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:656:\t__le32 mask; /* enum mxl862xx_bridge_port_config_mask */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-657-\t__le16 bridge_id;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-663-\t__le16 egress_extended_vlan_block_size;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:664:\t__le32 ingress_marking_mode; /* enum mxl862xx_color_marking_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:665:\t__le32 egress_remarking_mode; /* enum mxl862xx_color_remarking_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-666-\tu8 ingress_metering_enable;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-672-\t__le16 dest_sub_if_id_group;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:673:\t__le32 pmapper_mapping_mode; /* enum mxl862xx_pmapper_mapping_mode */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-674-\tu8 pmapper_id_valid;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:675:\tstruct mxl862xx_pmapper pmapper;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-676-\t__le16 bridge_port_map[8];\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-709-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:710: * struct mxl862xx_cfg - Global Switch configuration Attributes\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:711: * @mac_table_age_timer: See \u0026enum mxl862xx_age_timer\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-712- * @age_timer: Custom MAC table aging timer in seconds\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-722- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:723:struct mxl862xx_cfg {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:724:\t__le32 mac_table_age_timer; /* enum mxl862xx_age_timer */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-725-\t__le32 age_timer;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-734-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:735: * enum mxl862xx_extended_vlan_filter_type - Extended VLAN filter tag type\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-736- * @MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NORMAL: Normal tagged\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-740- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:741:enum mxl862xx_extended_vlan_filter_type {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-742-\tMXL862XX_EXTENDEDVLAN_FILTER_TYPE_NORMAL = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-748-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:749: * enum mxl862xx_extended_vlan_filter_tpid - Extended VLAN filter TPID\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-750- * @MXL862XX_EXTENDEDVLAN_FILTER_TPID_NO_FILTER: No TPID filter\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-753- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:754:enum mxl862xx_extended_vlan_filter_tpid {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-755-\tMXL862XX_EXTENDEDVLAN_FILTER_TPID_NO_FILTER = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-760-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:761: * enum mxl862xx_extended_vlan_filter_dei - Extended VLAN filter DEI\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-762- * @MXL862XX_EXTENDEDVLAN_FILTER_DEI_NO_FILTER: No DEI filter\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-765- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:766:enum mxl862xx_extended_vlan_filter_dei {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-767-\tMXL862XX_EXTENDEDVLAN_FILTER_DEI_NO_FILTER = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-772-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:773: * enum mxl862xx_extended_vlan_treatment_remove_tag - Tag removal action\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-774- * @MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG: Do not remove tag\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-778- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:779:enum mxl862xx_extended_vlan_treatment_remove_tag {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-780-\tMXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-786-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:787: * enum mxl862xx_extended_vlan_treatment_priority - Treatment priority mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-788- * @MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL: Use explicit value\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-792- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:793:enum mxl862xx_extended_vlan_treatment_priority {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-794-\tMXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-800-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:801: * enum mxl862xx_extended_vlan_treatment_vid - Treatment VID mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-802- * @MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL: Use explicit VID value\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-805- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:806:enum mxl862xx_extended_vlan_treatment_vid {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-807-\tMXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-812-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:813: * enum mxl862xx_extended_vlan_treatment_tpid - Treatment TPID mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-814- * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_TPID: Copy from inner tag\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-818- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:819:enum mxl862xx_extended_vlan_treatment_tpid {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-820-\tMXL862XX_EXTENDEDVLAN_TREATMENT_INNER_TPID = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-826-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:827: * enum mxl862xx_extended_vlan_treatment_dei - Treatment DEI mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-828- * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_DEI: Copy from inner tag\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-832- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:833:enum mxl862xx_extended_vlan_treatment_dei {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-834-\tMXL862XX_EXTENDEDVLAN_TREATMENT_INNER_DEI = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-840-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:841: * enum mxl862xx_extended_vlan_4_tpid_mode - 4-TPID mode selector\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-842- * @MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_1: VLAN TPID type 1\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-846- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:847:enum mxl862xx_extended_vlan_4_tpid_mode {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-848-\tMXL862XX_EXTENDEDVLAN_TPID_VTETYPE_1 = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-854-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:855: * enum mxl862xx_extended_vlan_filter_ethertype - Filter EtherType match\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-856- * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_NO_FILTER: No filter\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-864- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:865:enum mxl862xx_extended_vlan_filter_ethertype {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-866-\tMXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_NO_FILTER = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-876-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:877: * struct mxl862xx_extendedvlan_filter_vlan - Per-tag filter in Extended VLAN\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:878: * @type: Tag presence/type match (see \u0026enum mxl862xx_extended_vlan_filter_type)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-879- * @priority_enable: Enable PCP value matching\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-882- * @vid_val: VID value to match\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:883: * @tpid: TPID match mode (see \u0026enum mxl862xx_extended_vlan_filter_tpid)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:884: * @dei: DEI match mode (see \u0026enum mxl862xx_extended_vlan_filter_dei)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-885- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:886:struct mxl862xx_extendedvlan_filter_vlan {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-887-\t__le32 type;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-896-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:897: * struct mxl862xx_extendedvlan_filter - Extended VLAN filter configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-898- * @original_packet_filter_mode: If true, filter on original (pre-treatment)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-899- * packet\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:900: * @filter_4_tpid_mode: 4-TPID mode (see \u0026enum mxl862xx_extended_vlan_4_tpid_mode)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-901- * @outer_vlan: Outer VLAN tag filter\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-903- * @ether_type: EtherType filter (see\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:904: * \u0026enum mxl862xx_extended_vlan_filter_ethertype)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-905- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:906:struct mxl862xx_extendedvlan_filter {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-907-\tu8 original_packet_filter_mode;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-908-\t__le32 filter_4_tpid_mode;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:909:\tstruct mxl862xx_extendedvlan_filter_vlan outer_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:910:\tstruct mxl862xx_extendedvlan_filter_vlan inner_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-911-\t__le32 ether_type;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-914-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:915: * struct mxl862xx_extendedvlan_treatment_vlan - Per-tag treatment in\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-916- * Extended VLAN\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-917- * @priority_mode: Priority assignment mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:918: * (see \u0026enum mxl862xx_extended_vlan_treatment_priority)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-919- * @priority_val: Priority value (when mode is VAL)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-920- * @vid_mode: VID assignment mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:921: * (see \u0026enum mxl862xx_extended_vlan_treatment_vid)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-922- * @vid_val: VID value (when mode is VAL)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-923- * @tpid: TPID assignment mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:924: * (see \u0026enum mxl862xx_extended_vlan_treatment_tpid)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-925- * @dei: DEI assignment mode\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:926: * (see \u0026enum mxl862xx_extended_vlan_treatment_dei)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-927- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:928:struct mxl862xx_extendedvlan_treatment_vlan {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-929-\t__le32 priority_mode;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-937-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:938: * struct mxl862xx_extendedvlan_treatment - Extended VLAN treatment\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-939- * @remove_tag: Tag removal action\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:940: * (see \u0026enum mxl862xx_extended_vlan_treatment_remove_tag)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-941- * @treatment_4_tpid_mode: 4-TPID treatment mode\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-958- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:959:struct mxl862xx_extendedvlan_treatment {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-960-\t__le32 remove_tag;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-962-\tu8 add_outer_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:963:\tstruct mxl862xx_extendedvlan_treatment_vlan outer_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-964-\tu8 add_inner_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:965:\tstruct mxl862xx_extendedvlan_treatment_vlan inner_vlan;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-966-\tu8 reassign_bridge_port;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-980-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:981: * struct mxl862xx_extendedvlan_alloc - Extended VLAN block allocation\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-982- * @number_of_entries: Number of entries to allocate (input) / allocated\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-988- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:989:struct mxl862xx_extendedvlan_alloc {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-990-\t__le16 number_of_entries;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-994-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:995: * struct mxl862xx_extendedvlan_config - Extended VLAN entry configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-996- * @extended_vlan_block_id: Block ID from allocation\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1002- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1003:struct mxl862xx_extendedvlan_config {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1004-\t__le16 extended_vlan_block_id;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1005-\t__le16 entry_index;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1006:\tstruct mxl862xx_extendedvlan_filter filter;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1007:\tstruct mxl862xx_extendedvlan_treatment treatment;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1008-} __packed;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1010-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1011: * enum mxl862xx_vlan_filter_tci_mask - VLAN Filter TCI mask\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1012- * @MXL862XX_VLAN_FILTER_TCI_MASK_VID: TCI mask for VLAN ID\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1015- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1016:enum mxl862xx_vlan_filter_tci_mask {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1017-\tMXL862XX_VLAN_FILTER_TCI_MASK_VID = 0,\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1022-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1023: * struct mxl862xx_vlanfilter_alloc - VLAN Filter block allocation\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1024- * @number_of_entries: Number of entries to allocate (input) / allocated\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1034- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1035:struct mxl862xx_vlanfilter_alloc {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1036-\t__le16 number_of_entries;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1043-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1044: * struct mxl862xx_vlanfilter_config - VLAN Filter entry configuration\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1045- * @vlan_filter_block_id: Block ID from allocation\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1047- * @vlan_filter_mask: TCI field(s) to match (see\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1048: * \u0026enum mxl862xx_vlan_filter_tci_mask)\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1049- * @val: TCI value(s) to match (VID, PCP, or full TCI depending on mask)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1054- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1055:struct mxl862xx_vlanfilter_config {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1056-\t__le16 vlan_filter_block_id;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1057-\t__le16 entry_index;\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1058:\t__le32 vlan_filter_mask; /* enum mxl862xx_vlan_filter_tci_mask */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1059-\t__le32 val;\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1063-/**\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1064: * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1065- * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1069- */\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h:1070:enum mxl862xx_ss_sp_tag_mask {\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1071-\tMXL862XX_SS_SP_TAG_MASK_RX = BIT(0),\n--\ndrivers/net/dsa/mxl862xx/mxl862xx-api.h-1077-/**\n\n"
} |
| 5/2 | 2026/07/30 03:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43297 output: 43 thoughts: 68 Thoughts: **Analyzing the MXL862XX Driver Configuration** Okay, so I'm looking at this driver, `drivers/net/dsa/mxl862xx/mxl862xx.c`, and immediately I'm thinking about how it's configured. I've got a hunch it's controlled by a specific Kconfig option, and, aha, there it is: `CONFIG_NET_DSA_MXL862`. Now, I need to confirm the relationship and understand the dependencies. Let me go take a look at the relevant Kconfig file, `drivers/net/dsa/mxl862xx/Kconfig`, to see how this all hangs together. I need to understand what other options might influence the inclusion of this driver, and how it fits into the broader networking ecosystem. |
| 6/2 | 2026/07/30 03:30 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/Kconfig",
"FirstLine": 1,
"LineCount": 100
}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"
} |
| 7/2 | 2026/07/30 03:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43796 output: 52 thoughts: 43 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm already quite familiar with device driver development and networking, presented from the first-person perspective: **MDIO Driver Investigation** Alright, so the driver in question is for an MDIO device. My first thought is to figure out the standard registration process, and since I know that networking drivers often use similar approaches, I'll start by taking a look at a familiar DSA driver - the `mxl862xx.c` file. Specifically, I'll check `/drivers/net/dsa/mxl862xx/mxl862xx.c` to understand precisely how that driver registers itself within the kernel. I'm expecting it to use standard MDIO bus registration functions, possibly with some DSA-specific enhancements. By examining how that DSA driver is set up, I can infer the general methods used by MDIO drivers to interact with the MDIO bus and devices, and then adapt the same process for this specific MDIO device. |
| 8/2 | 2026/07/30 03:30 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
"FirstLine": 2200,
"LineCount": 100
}Results: {
"Contents": "2200:\t\t.get_eth_mac_stats = mxl862xx_get_eth_mac_stats,\n2201:\t\t.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,\n2202:\t\t.get_pause_stats = mxl862xx_get_pause_stats,\n2203:\t\t.get_rmon_stats = mxl862xx_get_rmon_stats,\n2204:\t\t.get_stats64 = mxl862xx_get_stats64,\n2205:\t\t.devlink_info_get = mxl862xx_devlink_info_get,\n2206:\t\t.devlink_flash_update = mxl862xx_devlink_flash_update,\n2207:\t};\n2208:\t\n2209:\tstatic int mxl862xx_probe(struct mdio_device *mdiodev)\n2210:\t{\n2211:\t\tstruct device *dev = \u0026mdiodev-\u003edev;\n2212:\t\tstruct mxl862xx_priv *priv;\n2213:\t\tstruct dsa_switch *ds;\n2214:\t\tint err, i;\n2215:\t\n2216:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n2217:\t\tif (!priv)\n2218:\t\t\treturn -ENOMEM;\n2219:\t\n2220:\t\tpriv-\u003emdiodev = mdiodev;\n2221:\t\n2222:\t\tds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);\n2223:\t\tif (!ds)\n2224:\t\t\treturn -ENOMEM;\n2225:\t\n2226:\t\tpriv-\u003eds = ds;\n2227:\t\tds-\u003edev = dev;\n2228:\t\tds-\u003epriv = priv;\n2229:\t\tds-\u003eops = \u0026mxl862xx_switch_ops;\n2230:\t\tds-\u003ephylink_mac_ops = \u0026mxl862xx_phylink_mac_ops;\n2231:\t\tds-\u003enum_ports = MXL862XX_MAX_PORTS;\n2232:\t\tds-\u003efdb_isolation = true;\n2233:\t\tds-\u003emax_num_bridges = MXL862XX_MAX_BRIDGES;\n2234:\t\n2235:\t\tmxl862xx_host_init(priv);\n2236:\t\n2237:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++) {\n2238:\t\t\tpriv-\u003eports[i].priv = priv;\n2239:\t\t\tINIT_WORK(\u0026priv-\u003eports[i].host_flood_work,\n2240:\t\t\t\t mxl862xx_host_flood_work_fn);\n2241:\t\t\tspin_lock_init(\u0026priv-\u003eports[i].stats_lock);\n2242:\t\t}\n2243:\t\n2244:\t\tINIT_DELAYED_WORK(\u0026priv-\u003estats_work, mxl862xx_stats_work_fn);\n2245:\t\n2246:\t\tdev_set_drvdata(dev, ds);\n2247:\t\n2248:\t\terr = dsa_register_switch(ds);\n2249:\t\tif (err) {\n2250:\t\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2251:\t\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2252:\t\t\tmxl862xx_host_shutdown(priv);\n2253:\t\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2254:\t\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2255:\t\t}\n2256:\t\n2257:\t\treturn err;\n2258:\t}\n2259:\t\n2260:\tstatic void mxl862xx_remove(struct mdio_device *mdiodev)\n2261:\t{\n2262:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2263:\t\tstruct mxl862xx_priv *priv;\n2264:\t\tint i;\n2265:\t\n2266:\t\tif (!ds)\n2267:\t\t\treturn;\n2268:\t\n2269:\t\tpriv = ds-\u003epriv;\n2270:\t\n2271:\t\tset_bit(MXL862XX_FLAG_WORK_STOPPED, \u0026priv-\u003eflags);\n2272:\t\tcancel_delayed_work_sync(\u0026priv-\u003estats_work);\n2273:\t\n2274:\t\tdsa_unregister_switch(ds);\n2275:\t\n2276:\t\tmxl862xx_host_shutdown(priv);\n2277:\t\n2278:\t\t/* Cancel any pending host flood work. dsa_unregister_switch()\n2279:\t\t * has already called port_teardown (which sets setup_done=false),\n2280:\t\t * but a worker could still be blocked on rtnl_lock(). Since we\n2281:\t\t * are now outside RTNL, cancel_work_sync() will not deadlock.\n2282:\t\t */\n2283:\t\tfor (i = 0; i \u003c MXL862XX_MAX_PORTS; i++)\n2284:\t\t\tcancel_work_sync(\u0026priv-\u003eports[i].host_flood_work);\n2285:\t}\n2286:\t\n2287:\tstatic void mxl862xx_shutdown(struct mdio_device *mdiodev)\n2288:\t{\n2289:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n2290:\t\tstruct mxl862xx_priv *priv;\n2291:\t\tint i;\n2292:\t\n2293:\t\tif (!ds)\n2294:\t\t\treturn;\n2295:\t\n2296:\t\tpriv = ds-\u003epriv;\n2297:\t\n2298:\t\tdsa_switch_shutdown(ds);\n2299:\t\n"
} |
| 9/2 | 2026/07/30 03:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45764 output: 26 thoughts: 263 Thoughts: **MDIO Device Driver Investigation** Okay, so I'm looking at this; it's an MDIO device driver. The question immediately jumps to mind: Can syzkaller even fuzz MDIO devices? I know `macb` has an MDIO bus, right? But syzkaller doesn't have a generic MDIO device injector. Hold on, though. Thinking back, syzkaller does have this diverse range of pseudo-devices, like `vhci` for USB, `mac80211_hwsim` for Wi-Fi, `vkms` for DRM, `uinput` for input, `hci` for Bluetooth, `vcan` for CAN, even `i2c-dev` for I2C, and `snd-aloop` for ALSA. It's a pretty comprehensive toolkit. But...nothing for MDIO. No generic MDIO device emulator. The next thing I'm wondering is, can a userspace program create MDIO devices? Hmm, typically, there isn't a *generic* way to do that from userspace, unless maybe there's a special driver like `mdio-gpio` or something, but usually, it relies on device tree setup. Wait, does syzkaller have *any* support for device tree overlays? No, syzkaller doesn't use those. Okay, so how do we handle this? Hmm, wait a second. Doesn't syzkaller have some sort of pseudo-device or pseudo-interface for MDIO? It's vaguely familiar. I should check `sys/linux/dev_mdio.txt` or something similar in the syzkaller source. That should be a good place to start. |
| 10/2 | 2026/07/30 03:31 | tool | grepper |
0mError:no matches Args: {
"Expression": "mdio",
"PathPrefix": "sys/linux/"
}Results: {
"error": "no matches"
} |
| 11/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46432 output: 18 thoughts: 175 Thoughts: **Investigating MDIO Device Creation in QEMU** Okay, hold on a second, I need to backtrack here. We were talking about binding drivers, and suddenly I thought, what about that `sys/linux/sysfs.txt` file? Can we actually *bind* a driver through sysfs? Sure, if the device already exists, that should work fine. But the real question is, how does the device *get* created in the first place? Typically, from my experience, MDIO devices are brought into existence by the MDIO bus driver itself. It does this by either enumerating PHYs, or reading information from the device tree. That makes sense, right? If there's no MDIO bus driver, then there won't *be* any MDIO devices. Now, this is where it gets tricky, because I need to switch gears again. I need to be sure... does QEMU even *have* an MDIO bus? I feel like it does... I seem to recall QEMU having network cards that might expose an MDIO bus, like the `e1000` or the `rtl8139`? Wait, no, the `e1000` has its own internal PHY, so that's out. Okay, let's take a different approach. I need to remember the mock DSA switch, `dsa_loop`. Hmmm, I think that one *might* use MDIO... I need to double-check `dsa_loop`. Let's just go look. |
| 12/2 | 2026/07/30 03:31 | tool | grepper |
0mArgs:{
"Expression": "dsa_loop"
}Results: {
"Output": "Documentation/networking/dsa/dsa.rst=159=all attached switch ports must be down while doing this).\nDocumentation/networking/dsa/dsa.rst-160-\nDocumentation/networking/dsa/dsa.rst:161:It is desirable that all tagging protocols are testable with the ``dsa_loop``\nDocumentation/networking/dsa/dsa.rst-162-mockup driver, which can be attached to any network interface. The goal is that\n--\ndrivers/net/dsa/Makefile=3=bcm-sf2-objs\t\t\t:= bcm_sf2.o bcm_sf2_cfp.o\ndrivers/net/dsa/Makefile:4:obj-$(CONFIG_NET_DSA_LOOP)\t+= dsa_loop.o\ndrivers/net/dsa/Makefile-5-obj-$(CONFIG_NET_DSA_KS8995) \t+= ks8995.o\n--\ndrivers/net/dsa/dsa_loop.c-24-\ndrivers/net/dsa/dsa_loop.c:25:struct dsa_loop_vlan {\ndrivers/net/dsa/dsa_loop.c-26-\tu16 members;\n--\ndrivers/net/dsa/dsa_loop.c-29-\ndrivers/net/dsa/dsa_loop.c:30:struct dsa_loop_mib_entry {\ndrivers/net/dsa/dsa_loop.c-31-\tchar name[ETH_GSTRING_LEN];\n--\ndrivers/net/dsa/dsa_loop.c-34-\ndrivers/net/dsa/dsa_loop.c:35:enum dsa_loop_mib_counters {\ndrivers/net/dsa/dsa_loop.c-36-\tDSA_LOOP_PHY_READ_OK,\n--\ndrivers/net/dsa/dsa_loop.c-42-\ndrivers/net/dsa/dsa_loop.c:43:struct dsa_loop_port {\ndrivers/net/dsa/dsa_loop.c:44:\tstruct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];\ndrivers/net/dsa/dsa_loop.c-45-\tu16 pvid;\n--\ndrivers/net/dsa/dsa_loop.c-48-\ndrivers/net/dsa/dsa_loop.c:49:struct dsa_loop_priv {\ndrivers/net/dsa/dsa_loop.c-50-\tstruct mii_bus\t*bus;\ndrivers/net/dsa/dsa_loop.c-51-\tunsigned int\tport_base;\ndrivers/net/dsa/dsa_loop.c:52:\tstruct dsa_loop_vlan vlans[VLAN_N_VID];\ndrivers/net/dsa/dsa_loop.c-53-\tstruct net_device *netdev;\ndrivers/net/dsa/dsa_loop.c:54:\tstruct dsa_loop_port ports[DSA_MAX_PORTS];\ndrivers/net/dsa/dsa_loop.c-55-};\ndrivers/net/dsa/dsa_loop.c-56-\ndrivers/net/dsa/dsa_loop.c:57:struct dsa_loop_pdata {\ndrivers/net/dsa/dsa_loop.c-58-\t/* Must be first, such that dsa_register_switch() can access this\n--\ndrivers/net/dsa/dsa_loop.c-66-\ndrivers/net/dsa/dsa_loop.c:67:static struct dsa_loop_mib_entry dsa_loop_mibs[] = {\ndrivers/net/dsa/dsa_loop.c-68-\t[DSA_LOOP_PHY_READ_OK]\t= { \"phy_read_ok\", },\n--\ndrivers/net/dsa/dsa_loop.c=75=static struct mdio_device *switch_mdiodev;\ndrivers/net/dsa/dsa_loop.c-76-\ndrivers/net/dsa/dsa_loop.c:77:enum dsa_loop_devlink_resource_id {\ndrivers/net/dsa/dsa_loop.c-78-\tDSA_LOOP_DEVLINK_PARAM_ID_NONE, /* DEVLINK_RESOURCE_ID_PARENT_TOP */\n--\ndrivers/net/dsa/dsa_loop.c-81-\ndrivers/net/dsa/dsa_loop.c:82:static u64 dsa_loop_devlink_vtu_get(void *priv)\ndrivers/net/dsa/dsa_loop.c-83-{\ndrivers/net/dsa/dsa_loop.c:84:\tstruct dsa_loop_priv *ps = priv;\ndrivers/net/dsa/dsa_loop.c-85-\tunsigned int i, count = 0;\ndrivers/net/dsa/dsa_loop.c:86:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-87-\n--\ndrivers/net/dsa/dsa_loop.c-96-\ndrivers/net/dsa/dsa_loop.c:97:static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-98-{\ndrivers/net/dsa/dsa_loop.c-99-\tstruct devlink_resource_size_params size_params;\ndrivers/net/dsa/dsa_loop.c:100:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-101-\tint err;\n--\ndrivers/net/dsa/dsa_loop.c-115-\t\t\t\t\t DSA_LOOP_DEVLINK_PARAM_ID_VTU,\ndrivers/net/dsa/dsa_loop.c:116:\t\t\t\t\t dsa_loop_devlink_vtu_get, ps);\ndrivers/net/dsa/dsa_loop.c-117-\n--\ndrivers/net/dsa/dsa_loop.c-124-\ndrivers/net/dsa/dsa_loop.c:125:static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/dsa_loop.c-126-\t\t\t\t\t\t int port,\n--\ndrivers/net/dsa/dsa_loop.c-133-\ndrivers/net/dsa/dsa_loop.c:134:static int dsa_loop_setup(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-135-{\ndrivers/net/dsa/dsa_loop.c:136:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-137-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-139-\tfor (i = 0; i \u003c ds-\u003enum_ports; i++)\ndrivers/net/dsa/dsa_loop.c:140:\t\tmemcpy(ps-\u003eports[i].mib, dsa_loop_mibs,\ndrivers/net/dsa/dsa_loop.c:141:\t\t sizeof(dsa_loop_mibs));\ndrivers/net/dsa/dsa_loop.c-142-\n--\ndrivers/net/dsa/dsa_loop.c-144-\ndrivers/net/dsa/dsa_loop.c:145:\treturn dsa_loop_setup_devlink_resources(ds);\ndrivers/net/dsa/dsa_loop.c-146-}\ndrivers/net/dsa/dsa_loop.c-147-\ndrivers/net/dsa/dsa_loop.c:148:static void dsa_loop_teardown(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-149-{\n--\ndrivers/net/dsa/dsa_loop.c-152-\ndrivers/net/dsa/dsa_loop.c:153:static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset)\ndrivers/net/dsa/dsa_loop.c-154-{\n--\ndrivers/net/dsa/dsa_loop.c-160-\ndrivers/net/dsa/dsa_loop.c:161:static void dsa_loop_get_strings(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-162-\t\t\t\t u32 stringset, uint8_t *data)\ndrivers/net/dsa/dsa_loop.c-163-{\ndrivers/net/dsa/dsa_loop.c:164:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-165-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-173-\ndrivers/net/dsa/dsa_loop.c:174:static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-175-\t\t\t\t uint64_t *data)\ndrivers/net/dsa/dsa_loop.c-176-{\ndrivers/net/dsa/dsa_loop.c:177:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-178-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-183-\ndrivers/net/dsa/dsa_loop.c:184:static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum)\ndrivers/net/dsa/dsa_loop.c-185-{\ndrivers/net/dsa/dsa_loop.c:186:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-187-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-198-\ndrivers/net/dsa/dsa_loop.c:199:static int dsa_loop_phy_write(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-200-\t\t\t int regnum, u16 value)\ndrivers/net/dsa/dsa_loop.c-201-{\ndrivers/net/dsa/dsa_loop.c:202:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-203-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-214-\ndrivers/net/dsa/dsa_loop.c:215:static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-216-\t\t\t\t struct dsa_bridge bridge,\n--\ndrivers/net/dsa/dsa_loop.c-225-\ndrivers/net/dsa/dsa_loop.c:226:static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-227-\t\t\t\t struct dsa_bridge bridge)\n--\ndrivers/net/dsa/dsa_loop.c-232-\ndrivers/net/dsa/dsa_loop.c:233:static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-234-\t\t\t\t\tu8 state)\n--\ndrivers/net/dsa/dsa_loop.c-239-\ndrivers/net/dsa/dsa_loop.c:240:static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-241-\t\t\t\t\tbool vlan_filtering,\n--\ndrivers/net/dsa/dsa_loop.c-249-\ndrivers/net/dsa/dsa_loop.c:250:static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-251-\t\t\t\t const struct switchdev_obj_port_vlan *vlan,\n--\ndrivers/net/dsa/dsa_loop.c-255-\tbool pvid = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_PVID;\ndrivers/net/dsa/dsa_loop.c:256:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-257-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:258:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-259-\n--\ndrivers/net/dsa/dsa_loop.c-282-\ndrivers/net/dsa/dsa_loop.c:283:static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-284-\t\t\t\t const struct switchdev_obj_port_vlan *vlan)\n--\ndrivers/net/dsa/dsa_loop.c-286-\tbool untagged = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_UNTAGGED;\ndrivers/net/dsa/dsa_loop.c:287:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-288-\tu16 pvid = ps-\u003eports[port].pvid;\ndrivers/net/dsa/dsa_loop.c-289-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:290:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-291-\n--\ndrivers/net/dsa/dsa_loop.c-310-\ndrivers/net/dsa/dsa_loop.c:311:static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-312-\t\t\t\t int new_mtu)\ndrivers/net/dsa/dsa_loop.c-313-{\ndrivers/net/dsa/dsa_loop.c:314:\tstruct dsa_loop_priv *priv = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-315-\n--\ndrivers/net/dsa/dsa_loop.c-320-\ndrivers/net/dsa/dsa_loop.c:321:static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port)\ndrivers/net/dsa/dsa_loop.c-322-{\n--\ndrivers/net/dsa/dsa_loop.c-325-\ndrivers/net/dsa/dsa_loop.c:326:static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port,\ndrivers/net/dsa/dsa_loop.c-327-\t\t\t\t struct phylink_config *config)\n--\ndrivers/net/dsa/dsa_loop.c-333-\ndrivers/net/dsa/dsa_loop.c:334:static const struct dsa_switch_ops dsa_loop_driver = {\ndrivers/net/dsa/dsa_loop.c:335:\t.get_tag_protocol\t= dsa_loop_get_protocol,\ndrivers/net/dsa/dsa_loop.c:336:\t.setup\t\t\t= dsa_loop_setup,\ndrivers/net/dsa/dsa_loop.c:337:\t.teardown\t\t= dsa_loop_teardown,\ndrivers/net/dsa/dsa_loop.c:338:\t.get_strings\t\t= dsa_loop_get_strings,\ndrivers/net/dsa/dsa_loop.c:339:\t.get_ethtool_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:340:\t.get_sset_count\t\t= dsa_loop_get_sset_count,\ndrivers/net/dsa/dsa_loop.c:341:\t.get_ethtool_phy_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:342:\t.phy_read\t\t= dsa_loop_phy_read,\ndrivers/net/dsa/dsa_loop.c:343:\t.phy_write\t\t= dsa_loop_phy_write,\ndrivers/net/dsa/dsa_loop.c:344:\t.port_bridge_join\t= dsa_loop_port_bridge_join,\ndrivers/net/dsa/dsa_loop.c:345:\t.port_bridge_leave\t= dsa_loop_port_bridge_leave,\ndrivers/net/dsa/dsa_loop.c:346:\t.port_stp_state_set\t= dsa_loop_port_stp_state_set,\ndrivers/net/dsa/dsa_loop.c:347:\t.port_vlan_filtering\t= dsa_loop_port_vlan_filtering,\ndrivers/net/dsa/dsa_loop.c:348:\t.port_vlan_add\t\t= dsa_loop_port_vlan_add,\ndrivers/net/dsa/dsa_loop.c:349:\t.port_vlan_del\t\t= dsa_loop_port_vlan_del,\ndrivers/net/dsa/dsa_loop.c:350:\t.port_change_mtu\t= dsa_loop_port_change_mtu,\ndrivers/net/dsa/dsa_loop.c:351:\t.port_max_mtu\t\t= dsa_loop_port_max_mtu,\ndrivers/net/dsa/dsa_loop.c:352:\t.phylink_get_caps\t= dsa_loop_phylink_get_caps,\ndrivers/net/dsa/dsa_loop.c-353-};\ndrivers/net/dsa/dsa_loop.c-354-\ndrivers/net/dsa/dsa_loop.c:355:static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-356-{\ndrivers/net/dsa/dsa_loop.c:357:\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\ndrivers/net/dsa/dsa_loop.c:358:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-359-\tstruct dsa_switch *ds;\n--\ndrivers/net/dsa/dsa_loop.c-382-\tds-\u003edev = \u0026mdiodev-\u003edev;\ndrivers/net/dsa/dsa_loop.c:383:\tds-\u003eops = \u0026dsa_loop_driver;\ndrivers/net/dsa/dsa_loop.c-384-\tds-\u003epriv = ps;\n--\ndrivers/net/dsa/dsa_loop.c-396-\ndrivers/net/dsa/dsa_loop.c:397:static void dsa_loop_drv_remove(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-398-{\ndrivers/net/dsa/dsa_loop.c-399-\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\ndrivers/net/dsa/dsa_loop.c:400:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-401-\n--\ndrivers/net/dsa/dsa_loop.c-410-\ndrivers/net/dsa/dsa_loop.c:411:static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-412-{\n--\ndrivers/net/dsa/dsa_loop.c-422-\ndrivers/net/dsa/dsa_loop.c:423:static struct mdio_driver dsa_loop_drv = {\ndrivers/net/dsa/dsa_loop.c-424-\t.mdiodrv.driver\t= {\n--\ndrivers/net/dsa/dsa_loop.c-426-\t},\ndrivers/net/dsa/dsa_loop.c:427:\t.probe\t= dsa_loop_drv_probe,\ndrivers/net/dsa/dsa_loop.c:428:\t.remove\t= dsa_loop_drv_remove,\ndrivers/net/dsa/dsa_loop.c:429:\t.shutdown = dsa_loop_drv_shutdown,\ndrivers/net/dsa/dsa_loop.c-430-};\ndrivers/net/dsa/dsa_loop.c-431-\ndrivers/net/dsa/dsa_loop.c:432:static int dsa_loop_bus_match(struct device *dev,\ndrivers/net/dsa/dsa_loop.c-433-\t\t\t const struct device_driver *drv)\ndrivers/net/dsa/dsa_loop.c-434-{\ndrivers/net/dsa/dsa_loop.c:435:\treturn drv == \u0026dsa_loop_drv.mdiodrv.driver;\ndrivers/net/dsa/dsa_loop.c-436-}\ndrivers/net/dsa/dsa_loop.c-437-\ndrivers/net/dsa/dsa_loop.c:438:static void dsa_loop_phydevs_unregister(void)\ndrivers/net/dsa/dsa_loop.c-439-{\n--\ndrivers/net/dsa/dsa_loop.c-445-\ndrivers/net/dsa/dsa_loop.c:446:static int __init dsa_loop_create_switch_mdiodev(void)\ndrivers/net/dsa/dsa_loop.c-447-{\ndrivers/net/dsa/dsa_loop.c:448:\tstatic struct dsa_loop_pdata dsa_loop_pdata = {\ndrivers/net/dsa/dsa_loop.c-449-\t\t.cd = {\n--\ndrivers/net/dsa/dsa_loop.c-470-\ndrivers/net/dsa/dsa_loop.c:471:\tswitch_mdiodev-\u003ebus_match = dsa_loop_bus_match;\ndrivers/net/dsa/dsa_loop.c:472:\tswitch_mdiodev-\u003edev.platform_data = \u0026dsa_loop_pdata;\ndrivers/net/dsa/dsa_loop.c-473-\n--\ndrivers/net/dsa/dsa_loop.c-481-\ndrivers/net/dsa/dsa_loop.c:482:static int __init dsa_loop_init(void)\ndrivers/net/dsa/dsa_loop.c-483-{\n--\ndrivers/net/dsa/dsa_loop.c-486-\ndrivers/net/dsa/dsa_loop.c:487:\tret = dsa_loop_create_switch_mdiodev();\ndrivers/net/dsa/dsa_loop.c-488-\tif (ret)\n--\ndrivers/net/dsa/dsa_loop.c-493-\ndrivers/net/dsa/dsa_loop.c:494:\tret = mdio_driver_register(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c-495-\tif (ret) {\ndrivers/net/dsa/dsa_loop.c:496:\t\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-497-\t\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-502-}\ndrivers/net/dsa/dsa_loop.c:503:module_init(dsa_loop_init);\ndrivers/net/dsa/dsa_loop.c-504-\ndrivers/net/dsa/dsa_loop.c:505:static void __exit dsa_loop_exit(void)\ndrivers/net/dsa/dsa_loop.c-506-{\ndrivers/net/dsa/dsa_loop.c:507:\tmdio_driver_unregister(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c:508:\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-509-\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-511-}\ndrivers/net/dsa/dsa_loop.c:512:module_exit(dsa_loop_exit);\ndrivers/net/dsa/dsa_loop.c-513-\n"
} |
| 13/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53613 output: 29 thoughts: 294 Thoughts: **Investigating MDIO Device Instantiation** Okay, so I'm trying to figure out how to create an MDIO device, specifically for testing purposes, likely to expose a potential vulnerability. I'm looking at how `dsa_loop` is set up - it's a *mock* driver that seems to conjure its own `mdio_device` and registers it directly. Then I'm comparing that to a *real* driver, like `mxl862xx`, which registers an `mdio_driver`... the more standard approach. I'm wondering if there's a way to instantiate an MDIO device from userspace, kind of like how you might interact with an I2C device. Can I somehow trigger that process directly? My initial thought was to use device tree overlays, because that would be a natural fit... but then I remembered, and this is a major sticking point, that Syzkaller, which I'm using, *doesn't* support device tree overlays. Strike that idea. So, next, I started to think about sysfs. I can create new I2C devices with a simple `echo` command into `/sys/bus/i2c/devices/i2c-0/new_device`. Is there a similar interface for the MDIO bus? Does such a file exist? My next step is to examine the MDIO bus code itself, specifically `drivers/net/phy/mdio_bus.c`. I need to dig into the internals to see if it provides any hooks or interfaces that I can leverage to dynamically create or register an MDIO device from userspace. |
| 14/2 | 2026/07/30 03:31 | tool | grepper |
0mError:no matches Args: {
"Expression": "new_device",
"PathPrefix": "drivers/net/phy/"
}Results: {
"error": "no matches"
} |
| 15/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54298 output: 40 thoughts: 273 Thoughts: **My Analysis of the mxl862xx Driver's Probe Path** Okay, so there's no explicit `new_device` call specifically for the `mxl862xx` driver, which is my first clue. Now, I'm thinking, hang on... What about drivers like `macb` or `fec`, other network drivers that are known to register an MDIO bus? They often pull device information, including the MDIO bus itself, from the device tree, right? Alright, so if there isn't a corresponding device tree node for `mxl862xx`, it's not going to get created. Makes sense. But then I'm wondering, can `mxl862xx` even *be* probed on something like a PCI card? No, that's absurd; it's an MDIO device, not PCI. The real challenge here is figuring out how to trigger the `mxl862xx_probe` function. And if the device isn't instantiated, that probe function is never called, simple as that. Which, of course, means the devlink flash update functionality is unreachable. I need to make sure I'm not missing anything. I need to double-check: is there *any* other way this thing can be probed, even an unlikely one? Let's see… ACPI? Nope, it only has an `of_match_table`, so that's a dead end. Platform data? The driver doesn't seem to expose a platform driver, it's defined as an `mdio_driver`. Now I need to go back to `mxl862xx.c` and make sure I understand the `mdio_driver_register` call. This is key to understanding how it *can* be attached. |
| 16/2 | 2026/07/30 03:31 | tool | grepper |
0mError:no matches Args: {
"Expression": "mdio_driver_register",
"PathPrefix": "drivers/net/dsa/mxl862xx/"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55013 output: 38 thoughts: 8 Thoughts: Okay, here's my summary, presented as a stream of thought: **Unpacking Driver Registration** Hmm, hold on a second. This whole process hinges on how the driver itself is registered. I need to understand that fundamental mechanism first. Is it a static registration within the OS kernel? Or is there some dynamic discovery mechanism at play? What are the implications of each approach? Are we talking about a simple table lookup, or something more complex with device IDs and driver probing? This "registration" step is the linchpin. If I can't pin that down, I'm going to have a hard time understanding the whole architecture. I need the specifics of that registration! The devil, as always, is in the details, so how are the drivers being identified? |
| 18/2 | 2026/07/30 03:31 | tool | grepper |
0mArgs:{
"Expression": "mdio_driver",
"PathPrefix": "drivers/net/dsa/mxl862xx/"
}Results: {
"Output": "drivers/net/dsa/mxl862xx/mxl862xx.c=2316=MODULE_DEVICE_TABLE(of, mxl862xx_of_match);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2317-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2318:static struct mdio_driver mxl862xx_driver = {\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2319-\t.probe = mxl862xx_probe,\n"
} |
| 19/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55397 output: 51 thoughts: 10 Thoughts: Okay, I'm ready. Here's a summary as if it's my own thought process, assuming I'm an expert in my field: **Initial Assessment: Navigating the Source** Right, so I'm looking at this text, and the immediate thought is "Okay, let's get a good grasp of the whole picture." It's like starting a complex analysis; you can't jump to conclusions without the full dataset. I need to understand the context, the scope, everything contained within this file before I can even begin a proper, detailed interpretation or evaluation. This initial "Let's see the rest of the file," is really me just mentally gearing up to dive in, ensuring I don't miss any critical piece of information that might affect my conclusions down the line. It's about being thorough and methodical, the first step in a complex process. |
| 20/2 | 2026/07/30 03:31 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/mxl862xx/mxl862xx.c",
"FirstLine": 2310,
"LineCount": 50
}Results: {
"Contents": "2310:\t\n2311:\tstatic const struct of_device_id mxl862xx_of_match[] = {\n2312:\t\t{ .compatible = \"maxlinear,mxl86282\" },\n2313:\t\t{ .compatible = \"maxlinear,mxl86252\" },\n2314:\t\t{ /* sentinel */ }\n2315:\t};\n2316:\tMODULE_DEVICE_TABLE(of, mxl862xx_of_match);\n2317:\t\n2318:\tstatic struct mdio_driver mxl862xx_driver = {\n2319:\t\t.probe = mxl862xx_probe,\n2320:\t\t.remove = mxl862xx_remove,\n2321:\t\t.shutdown = mxl862xx_shutdown,\n2322:\t\t.mdiodrv.driver = {\n2323:\t\t\t.name = \"mxl862xx\",\n2324:\t\t\t.of_match_table = mxl862xx_of_match,\n2325:\t\t},\n2326:\t};\n2327:\t\n2328:\tmdio_module_driver(mxl862xx_driver);\n2329:\t\n2330:\tMODULE_DESCRIPTION(\"Driver for MaxLinear MxL862xx switch family\");\n2331:\tMODULE_LICENSE(\"GPL\");\n"
} |
| 21/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56022 output: 19 thoughts: 113 Thoughts: **MDIO Driver Dilemma** Okay, so I'm looking at this driver and, yep, it's definitely an MDIO driver. The immediate challenge, of course, is how to actually test it with syzkaller. Unlike I2C, where we have `i2c-dev` or virtual hardware containers, and similar mechanisms for USB and PCI, syzkaller doesn't seem to have a straightforward way to inject an MDIO device into the QEMU environment. This means, as it currently stands, this driver won't automatically bind to any hardware when running under syzkaller. Hmm... The core issue then becomes how to fabricate the required MDIO bus interactions. Is there a virtual MDIO bus implementation out there that I can populate from userspace? My gut tells me there has to be something similar to the virtual I2C or PCI mechanisms. I'll need to check the kernel source, specifically something under the `drivers/net/phy` directory. Let's start with `mdio-mockup.c` – that seems like a good place to begin searching for a solution or a workaround. |
| 22/2 | 2026/07/30 03:31 | tool | grepper |
0mError:no matches Args: {
"Expression": "mdio.*mockup"
}Results: {
"error": "no matches"
} |
| 23/2 | 2026/07/30 03:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56406 output: 20 thoughts: 46 Thoughts: **MDIO Device Creation Inquiry** Okay, so I'm thinking, I need to figure out how these MDIO devices are created, right? I'm trying to trace the creation of these PHY devices, and I've got this niggling thought about `mdio-bcm-unimac.c`. But no, that doesn't seem to be the core of the creation process, so let's rule that out quickly. The real question is, what can trigger this device creation? What's the sequence of events here? I need to get to the bottom of how the system knows it needs to instantiate these MDIO devices in the first place. I think I need to start looking in `drivers/net/phy/mdio_bus.c`. That seems to be the logical place to start digging deeper. I need to understand what's happening in there. |
| 24/2 | 2026/07/30 03:31 | tool | grepper |
0mArgs:{
"Expression": "mdio_device_register"
}Results: {
"Output": "drivers/net/dsa/dsa_loop.c=446=static int __init dsa_loop_create_switch_mdiodev(void)\n--\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)\n--\ndrivers/net/mdio/of_mdio.c=52=static int of_mdiobus_register_device(struct mii_bus *mdio,\n--\ndrivers/net/mdio/of_mdio.c-68-\t/* All data is now stored in the mdiodev struct; register it. */\ndrivers/net/mdio/of_mdio.c:69:\trc = mdio_device_register(mdiodev);\ndrivers/net/mdio/of_mdio.c-70-\tif (rc) {\n--\ndrivers/net/pcs/pcs-xpcs-plat.c=337=static int xpcs_plat_init_dev(struct dw_xpcs_plat *pxpcs)\n--\ndrivers/net/pcs/pcs-xpcs-plat.c-356-\ndrivers/net/pcs/pcs-xpcs-plat.c:357:\tret = mdio_device_register(mdiodev);\ndrivers/net/pcs/pcs-xpcs-plat.c-358-\tif (ret) {\n--\ndrivers/net/phy/air_en8811h.c=1074=static int an8811hb_probe(struct phy_device *phydev)\n--\ndrivers/net/phy/air_en8811h.c-1097-\ndrivers/net/phy/air_en8811h.c:1098:\tret = mdio_device_register(mdiodev);\ndrivers/net/phy/air_en8811h.c-1099-\tif (ret)\n--\ndrivers/net/phy/mdio_device.c-26-/**\ndrivers/net/phy/mdio_device.c:27: * mdio_device_register_reset - Read and initialize the reset properties of\ndrivers/net/phy/mdio_device.c-28- *\t\t\t\tan mdio device\n--\ndrivers/net/phy/mdio_device.c-32- */\ndrivers/net/phy/mdio_device.c:33:static int mdio_device_register_reset(struct mdio_device *mdiodev)\ndrivers/net/phy/mdio_device.c-34-{\n--\ndrivers/net/phy/mdio_device.c=143=EXPORT_SYMBOL(mdio_device_create);\n--\ndrivers/net/phy/mdio_device.c-145-/**\ndrivers/net/phy/mdio_device.c:146: * mdio_device_register - Register the mdio device on the MDIO bus\ndrivers/net/phy/mdio_device.c-147- * @mdiodev: mdio_device structure to be added to the MDIO bus\n--\ndrivers/net/phy/mdio_device.c-150- */\ndrivers/net/phy/mdio_device.c:151:int mdio_device_register(struct mdio_device *mdiodev)\ndrivers/net/phy/mdio_device.c-152-{\n--\ndrivers/net/phy/mdio_device.c-172-}\ndrivers/net/phy/mdio_device.c:173:EXPORT_SYMBOL(mdio_device_register);\ndrivers/net/phy/mdio_device.c-174-\n--\ndrivers/net/phy/mdio_device.c-180- * This doesn't free the mdio_device itself, it merely reverses the effects\ndrivers/net/phy/mdio_device.c:181: * of mdio_device_register(). Use mdio_device_free() to free the device\ndrivers/net/phy/mdio_device.c-182- * after calling this function.\n--\ndrivers/net/phy/mdio_device.c=191=int mdiobus_register_device(struct mdio_device *mdiodev)\n--\ndrivers/net/phy/mdio_device.c-198-\tif (mdiodev-\u003eflags \u0026 MDIO_DEVICE_FLAG_PHY) {\ndrivers/net/phy/mdio_device.c:199:\t\terr = mdio_device_register_reset(mdiodev);\ndrivers/net/phy/mdio_device.c-200-\t\tif (err)\n--\ninclude/linux/mdio.h=95=struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr);\ninclude/linux/mdio.h:96:int mdio_device_register(struct mdio_device *mdiodev);\ninclude/linux/mdio.h-97-void mdio_device_remove(struct mdio_device *mdiodev);\n"
} |
| 25/2 | 2026/07/30 03:32 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57941 output: 26 thoughts: 709 Thoughts: **Unreachable Code Path Analysis** Okay, let's break this down. My initial thought process revolves around reaching the `mxl862xx` driver, and therefore the devlink flash update functionality. I see that `mdio_device_register` is the key entry point here, and it's called from a few places. The device tree registration, the DSA loop (mock) and some platform-specific initializations. I'm immediately concerned because there doesn't seem to be a straightforward way to instantiate an arbitrary MDIO device from userspace directly. I'm thinking about Syzkaller and how it could trigger the probe. The `mxl862xx` driver's reliance on device tree is a hurdle. Without a suitable device tree node, the driver will never be probed. I briefly consider the `dsa_loop` mock, but quickly realize the driver's `bus_match` prevents the driver from binding to it. Then I investigate `mdio-gpio`, but again, the inability of Syzkaller to create platform devices with arbitrary device tree nodes is a problem. I'm checking for Syzkaller's abilities to create some things like `netdevsim` but it does not apply to MDIO devices. I am also going through the pseudo-syscalls but I see nothing for MDIO or DSA. So, is there a backdoor? Maybe through another network path? I realize this is a DSA switch driver, so it only registers an `mdio_driver`. If the probe function isn't called, the devlink ops won't be registered, and the flash update functionality is unreachable. It's a dead end. Next, I need to check the QEMU machine types Syzkaller uses, like `q35`, `pc` (x86_64), or `virt` (arm64, riscv64). None of them have a MaxLinear MxL862xx switch connected via MDIO. So, this code path is definitely unreachable in these virtualized environments. Now, let's review the prompt. It's asking if the code is unreachable in environments like GCE or QEMU, and this code certainly seems to be. This is a vendor-specific DSA switch (MaxLinear MxL862xx) connected via MDIO. It needs physical hardware and there's no emulator in QEMU, nor can I instantiate it via a software-emulated approach like `mac80211_hwsim` or `netdevsim`. I just need to verify again, no `syz_` pseudo-syscalls for MDIO or DSA. Given this, I'm confident in concluding that this code is unreachable in these standard virtualized environments and should be flagged as `WorthFuzzing=false`. |
| 26/2 | 2026/07/30 03:32 | tool | grepper |
0mError:no matches Args: {
"Expression": "syz_.*mdio",
"PathPrefix": ""
}Results: {
"error": "no matches"
} |
| 27/2 | 2026/07/30 03:32 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 59287 output: 26 thoughts: 2 Thoughts: Okay, here's the summary, assuming "No" is the only text I have to work with, and tailored for an expert audience. **Initial Assessment: Disagreement and Rejection** My immediate response is negative. The provided input, whatever it might be, triggers an immediate rejection. There's a fundamental disconnect between the proposition and my existing understanding or expectations. The brevity of the statement, "No," suggests a very strong and likely well-reasoned objection; not a simple dismissal, but a decisive stance. Further information is required to understand the justification, of course, but the starting point is, unequivocally, disagreement. |
| 28/2 | 2026/07/30 03:32 | tool | grepper |
0mError:no matches Args: {
"Expression": "syz_.*dsa",
"PathPrefix": ""
}Results: {
"error": "no matches"
} |
| 29/2 | 2026/07/30 03:32 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 59460 output: 138 thoughts: 176 Thoughts: **Unreachable `mxl862xx` Devlink Callbacks in This Fuzzing Environment** Okay, no. Wait, hold on. What about `dsa_loop`? That's a mock DSA switch, right? But it only binds to *its* own driver, `dsa_loop`. So, yeah, that seals it. `mxl862xx` is definitely unreachable in this setup. Let me recap my reasoning to be absolutely clear. The patch I'm looking at adds firmware flashing capabilities via devlink to the `mxl862xx` DSA switch driver. This driver is specifically for MaxLinear MxL862xx hardware switches connected over an MDIO bus. The rub? There's no software emulation for that particular piece of hardware in standard QEMU, and there isn't a generic way to instantiate random MDIO devices from user space *unless* we have device tree support. And we don't. So, that means the `probe` function of this driver is never going to run in our virtualized fuzzing environment. Consequently, all those shiny new devlink callbacks that the patch introduces? They're completely untouchable. They will never get hit during fuzzing. Therefore, WorthFuzzing=false. Let's move on. |
| 30/2 | 2026/07/30 03:32 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flashing support via devlink to the `mxl862xx` DSA switch driver. This driver is for MaxLinear MxL862xx hardware switches connected via an MDIO bus. Since there is no software emulator for this specific hardware in standard QEMU, and no generic way to instantiate arbitrary MDIO devices from userspace without device tree support, the probe function of this driver will never be executed in a virtualized fuzzing environment. Consequently, the newly added devlink callbacks are unreachable.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds firmware flashing support via devlink to the `mxl862xx` DSA switch driver. This driver is for MaxLinear MxL862xx hardware switches connected via an MDIO bus. Since there is no software emulator for this specific hardware in standard QEMU, and no generic way to instantiate arbitrary MDIO devices from userspace without device tree support, the probe function of this driver will never be executed in a virtualized fuzzing environment. Consequently, the newly added devlink callbacks are unreachable.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|