From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CC2D5378D70; Wed, 23 Sep 2026 02:33:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130813; cv=none; b=IXI9fQ8EkT5GaiIYFVVr3YJmZSpN5n6Od6mRWNN6l5OuDAMMqfU6YPHUuFuoU3YNR9W1iBau2KCyo0ni0Jjmo63Cek6CA3EiLMc4GTJjzn2QzU2QyuvtWIBqq89eSCy4pbtnFvqd/thhQLJEI5/0WcIwkumpJ59/NhuTxd9PgaU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130813; c=relaxed/simple; bh=wu7VZ/2R1Nx1dp2rAZtZLF3eq7XJDY7+txLDpPR+IuI=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=hsrtHr6z0e0N3w8NmeWUQsjIoRZ+XynTanVTlbcl1K8LZZwcE5vaABF3XuGXdq7j1OwJXP/cWkfWDH+CTrtfMTXV5DlxAOQ9HiU/kFbwKdVnpD7ZC2r8+J2x3A0U7SiHFotxhncv57WK+c4BxmSWgvWdtFaE6fSbh1fXd02h+bc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9CnR-000000002yh-043G; Wed, 23 Sep 2026 02:33:21 +0000 Date: Wed, 23 Sep 2026 03:33:17 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 1/6] net: dsa: add devlink flash_update callback to dsa_switch_ops Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a devlink_flash_update callback to dsa_switch_ops so that DSA drivers can support devlink dev flash without open-coding the devlink plumbing. Like the other trampolines in net/dsa/devlink.c, the op returns -EOPNOTSUPP when the driver does not implement the callback. The devlink core takes a non-NULL ops->flash_update as the capability gate, so on a switch without the callback a flash request now fetches the firmware file first: a missing file fails with -ENOENT instead of -EOPNOTSUPP, and a file that is found reaches the trampoline inside a FLASH_UPDATE/FLASH_UPDATE_END notification pair. Both are acceptable for an operation as infrequent as a firmware update. The devlink core calls the op with the devlink instance lock held and without rtnl_lock, whereas DSA serialises its switch and port ops under rtnl_lock, so a driver has to serialise a flash against its own ops itself. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn --- v17: no changes v16: - commit message: name the errno and the notifications the shared ops table changes for switches without the callback (found by Sashiko AI review) - commit message: a missing firmware file fails before the notification pair, which wraps only the call into the trampoline v15: no changes v14: no changes, picked up Andrew's v13 Reviewed-by v13: no changes v12: no changes v11: no changes v10: no changes v9: install the flash_update op unconditionally and return -EOPNOTSUPP from the trampoline like the other DSA devlink trampolines, instead of a second devlink_ops permutation (Andrew Lunn) v8: - retitled: this patch adds the callback, its first user is patch 3 - describe the op's calling context in the commit message v7: no changes v6: no changes v5: no changes v4: only install the flash_update op for drivers implementing the callback so the devlink core keeps rejecting unsupported flash requests before fetching the firmware file v3: no changes v2: align continuation lines with the open parenthesis --- include/net/dsa.h | 3 +++ net/dsa/devlink.c | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/net/dsa.h b/include/net/dsa.h index 5d12191b6f6f..1fcf4af6c506 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -1176,6 +1176,9 @@ struct dsa_switch_ops { int (*devlink_info_get)(struct dsa_switch *ds, struct devlink_info_req *req, struct netlink_ext_ack *extack); + int (*devlink_flash_update)(struct dsa_switch *ds, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack); int (*devlink_sb_pool_get)(struct dsa_switch *ds, unsigned int sb_index, u16 pool_index, struct devlink_sb_pool_info *pool_info); diff --git a/net/dsa/devlink.c b/net/dsa/devlink.c index ed342f345692..25311a87cbc5 100644 --- a/net/dsa/devlink.c +++ b/net/dsa/devlink.c @@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl, return -EOPNOTSUPP; } =20 +static int dsa_devlink_flash_update(struct devlink *dl, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct dsa_switch *ds =3D dsa_devlink_to_ds(dl); + + if (!ds->ops->devlink_flash_update) + return -EOPNOTSUPP; + + return ds->ops->devlink_flash_update(ds, params, extack); +} + static int dsa_devlink_sb_pool_get(struct devlink *dl, unsigned int sb_index, u16 pool_index, struct devlink_sb_pool_info *pool_info) @@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port= *dlp, =20 static const struct devlink_ops dsa_devlink_ops =3D { .info_get =3D dsa_devlink_info_get, + .flash_update =3D dsa_devlink_flash_update, .sb_pool_get =3D dsa_devlink_sb_pool_get, .sb_pool_set =3D dsa_devlink_sb_pool_set, .sb_port_pool_get =3D dsa_devlink_sb_port_pool_get, --=20 2.55.0 From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A64A037F338; Wed, 23 Sep 2026 02:33:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130814; cv=none; b=KPk/QFQWr4dWVHGbq4pcbV/ZwvZh+ZM+rkU44xS8JGfPSf7Wrt6L8E0DWACwIFRRVPyrjHGG3ScSlHnZyTNajp3HoOPZR/d9ZKzZgMhugN7pvAKAcik0vAXrN/lkvGH+eEl1hFUNEVWUj0h3REsObrpnIHnZ/DtK4xX2/IoG6sY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130814; c=relaxed/simple; bh=oPhyFNV9LhT6uAsd9WlKoCn3e9TCzWj7aAtNZNvLtVw=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=I59ORyUtTw4Nw8HFAybor0HqFuf5y3XFwmqs8ZlHn8nlcrtxSh5vZIN1c+rh0u1LPsFASWx/ILLdLFwdzAvRVTdevIxEKBkkzvtegl5Q8pcWidiYYYgQfGDYYmEUO6E6ydyMjtzjYT1Oer6V8jgOgco58eLJPbuwZ1hZcWi8HkE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9CnY-000000002zF-12Wy; Wed, 23 Sep 2026 02:33:28 +0000 Date: Wed, 23 Sep 2026 03:33:25 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 2/6] net: dsa: mxl862xx: add SMDIO clause-22 register access Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22 SMDIO register access. The switch's MCUboot bootloader exposes only clause-22 registers; the clause-45 MMD interface needs the running firmware and is unavailable while the switch is in MCUboot. The MDIO bus lock is held per-transaction (not across polls) so that SB PDI polling during flash erase does not starve other non-switch users of the same MDIO bus, such as separate PHYs providing WAN or management interfaces. Unlike mxl862xx_api_wrap(), which takes the bus lock with MDIO_MUTEX_NESTED because it can be entered from the accessors of the switch-internal MDIO bus while that bus's lock of the same lock class is already held, the SMDIO helpers take it with a plain mutex_lock(). They are only called from probe and devlink flash contexts where no other MDIO bus lock can be held. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn --- v17: no changes v16: no changes v15: no changes v14: no changes v13: no changes v12: no changes v11: no changes v10: no changes v9: no changes, picked up Andrew's v5 Reviewed-by v8: document the paged register window and the per-transaction bus locking next to the helpers, rather than only in this changelog (found by Sashiko AI review) v7: no changes v6: no changes v5: no changes v4: no changes v3: explain the plain mutex_lock() vs MDIO_MUTEX_NESTED choice in the commit message v2: clarify in the commit message that the per-transaction bus locking is about unrelated non-switch devices on the same MDIO bus (Andrew Lunn) --- drivers/net/dsa/mxl862xx/mxl862xx-host.c | 40 ++++++++++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx-host.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl= 862xx/mxl862xx-host.c index 4acd216f7cc0..11759fa6069b 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c @@ -495,6 +495,46 @@ int mxl862xx_reset(struct mxl862xx_priv *priv) return ret; } =20 +#define MXL862XX_SMDIO_ADDR_REG 0x1f +#define MXL862XX_SMDIO_PAGE_MASK 0xfff0 +#define MXL862XX_SMDIO_OFF_MASK 0x000f + +/* Paged clause-22 window: the page goes into MII register 0x1f, the low n= ibble + * of addr selects one of the 16 registers within it. Both helpers take th= e MDIO + * bus lock per transaction, so callers must not already hold it -- unlike + * mxl862xx_api_wrap(), which holds it across a whole firmware command. + */ +int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr) +{ + struct mii_bus *bus =3D priv->mdiodev->bus; + int phy =3D priv->mdiodev->addr; + int ret; + + mutex_lock(&bus->mdio_lock); + ret =3D __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG, + addr & MXL862XX_SMDIO_PAGE_MASK); + if (ret >=3D 0) + ret =3D __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 =3D priv->mdiodev->bus; + int phy =3D priv->mdiodev->addr; + int ret; + + mutex_lock(&bus->mdio_lock); + ret =3D __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG, + addr & MXL862XX_SMDIO_PAGE_MASK); + if (ret >=3D 0) + ret =3D __mdiobus_write(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK, + val); + mutex_unlock(&bus->mdio_lock); + return ret; +} + void mxl862xx_host_init(struct mxl862xx_priv *priv) { INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl= 862xx/mxl862xx-host.h index 66d6ae198aff..4e054c6e4c0e 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) =20 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); =20 #endif /* __MXL862XX_HOST_H */ --=20 2.55.0 From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D139B3815FB; Wed, 23 Sep 2026 02:34:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130843; cv=none; b=G12IaklgZtyubQHGC7inBlwcY9mW8kGXLQmReFn5FVS4DluqPAAD/PgdhgP/qGwE8Nmr7j9VmVrk0ASjQgbtJk1A6gBAAKLl8GrxBlZ++TGVuZu/U/oQUuOpTsmeeXGXW4FC9dzM4qmP7247MO975YBZMksG9k2MopbsvTEUD4M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130843; c=relaxed/simple; bh=uZGz3QOyEneEXepZww9qWHC9X3NxJzI0McMUAq8o8Qc=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=ew65CSBip7THP9yk371x8WoxZgauyt/gjUCK8fOSKJPEOKAElOzhn42+NujVr3SkJqdQjt6G23t/ZuqAPFJI9F75/d9O5/YYV3FAorE5aEmPrjUpet2MEiEYOcPeGnqcNPFzadnCaAdmybwdvewi5ylv7nW5mvmrHoCuqTJnlmg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9Co1-0000000030a-0SL3; Wed, 23 Sep 2026 02:33:57 +0000 Date: Wed, 23 Sep 2026 03:33:54 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 3/6] driver core: add device_schedule_reprobe() Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Drivers that need a deferred re-probe of their own device open-code a work item in module text. iwlwifi (iwl_trans_schedule_reprobe(), for a firmware crash a lighter restart cannot fix) and hci_h5 (h5_btrtl_resume(), RTL devices lose their firmware state over suspend) both end that work function with put_device(); kfree(); module_put(THIS_MODULE);, where a concurrent rmmod can free the module text the epilogue is still executing. Neither gates the re-probe on the device still being bound to the driver that scheduled it, which a driver cannot do from outside because device_reprobe() takes the device lock internally. Add device_schedule_reprobe(), which detaches and re-probes a device after a caller-specified delay. The work function is built-in text, so a caller needs no module reference. The binding is recorded as the driver pointer plus a copy of its name: the pointer is only ever compared, never dereferenced, and the name copy rejects a freed struct device_driver address the allocator later hands to a different driver. The first user is the mxl862xx devlink flash path added later in this series. Nothing is locked or validated in the caller's context, so the helper may be called with the device lock held, as the PM callbacks, ->remove() and ->shutdown() hold it. Buses that take the parent lock to bind are refused with -EINVAL: that lock has to be taken before @dev's own, so the parent would have to be recorded before either is held, where device_move() can replace it without taking any device lock. usb_bus_type is the only such bus and no caller needs it today. The detach is skipped once probing is blocked, which device_shutdown() and dpm_prepare() both set before they touch any device, and the work is freezable so one pending across suspend runs after resume. Beyond that gate this is device_reprobe() deferred and __device_release_driver() is unchanged, so it carries device_reprobe()'s pre-existing limitations: the detach and the re-attach are not one locked operation, so an administrative unbind between them may be undone, and detaching a device that has managed consumers unbinds them as any release does, so a re-probe a concurrent device_shutdown() overtakes may run ->remove() in place of ->shutdown(). None of this is specific to the helper. Assisted-by: LLM Signed-off-by: Daniel Golle --- v17: - drop the abort_if_blocked flag and the bool return of __device_release_driver(), leaving that function unchanged: the flag left the device-links state half torn down when it fired and did not cover the consumers unbound in the same window, so the shutdown-vs- release window it targeted is documented as pre-existing to every unbind path instead (found by Sashiko AI review) - record the bound driver's name beside the pointer and compare both, so a freed struct device_driver address reused by another driver is not mistaken for the original binding (found by Sashiko AI review) - kernel-doc: add a Context line and state the pre-existing limitations shared with device_reprobe() (found by Sashiko AI review) v16: - commit message: device_shutdown() blocks probing only once wait_for_device_probe() has returned, so a re-probe already past the test detaches the device instead of leaving it bound for its ->shutdown() - take no lock in the caller's context and drop the parent snapshot, refusing buses that need the parent lock instead: the caller-context device lock inverted against the devlink instance lock on the flash path and against a synchronous work cancel on the rescue path, and a pinned parent can be freed by device_move() (found by Sashiko AI review) - abandon the release when probing is blocked while the device links loop has the locks dropped, rather than calling that window pre-existing: a deferred re-probe is the one unbind that may be abandoned, so it is the one that can close it (found by Sashiko AI review) - commit message: describe what this patch changes rather than bugs in drivers it does not convert, and name the first user (found by Sashiko AI review) - kernel-doc: drop the promise that an administrative unbind always wins, which unbind_store() does not guarantee (found by Sashiko AI review) v15: - skip the detach while probing is blocked instead of adding a per-device shutdown_done flag: device_shutdown() blocks probing before its walk starts, so the flag left a window where the work detached a device that then neither re-attached nor got its ->shutdown() call (found by Sashiko AI review) - validate the device and snapshot the parent, its locking requirement and the bound driver under the device lock, so an unregister racing the allocation can neither leave a freed parent pinned nor pair a NULL parent with a request to lock it (found by Sashiko AI review) - keep -EPROBE_DEFER out of the re-probe error path, where dev_err_probe() would record the message as the device's deferred probe reason (found by Sashiko AI review) - kernel-doc: a stale re-probe leaves an unbound device unbound, which an unbind followed by a rebind within the delay does not (found by Sashiko AI review) v14: no changes v13: - queue the work on system_freezable_wq, so a re-probe pending across system suspend can neither detach a device the PM core has suspended nor race its late suspend callbacks; it runs after resume instead (found by Sashiko AI review) - record at scheduling time whether the parent needs locking, instead of reading dev->bus in the work, which may be gone with its module once the device has been unregistered (found by Sashiko AI review) - let __device_release_driver() report whether it released the driver, so an administrative unbind that wins the race inside the device links loop is not undone by the re-attach (found by Sashiko AI review) - use dev_err_probe() for the re-probe error path, so a re-probe deferred at resume no longer logs a spurious error (Hans de Goede, on the standalone posting of this helper) - describe the parent pinning and locking in the commit message, as in the standalone posting v12: - pin the parent device across the deferred work; a reference on the child alone left device_reprobe_work_fn() dereferencing a freed dev->parent under __device_driver_lock() when the device was unregistered before the work ran (found by Sashiko AI review) - take the parent lock across device_attach() on buses that require it, matching bus_rescan_devices_helper() (found by Sashiko AI review) v11: new patch: add device_schedule_reprobe() to the driver core (posted earlier as an RFC) so mxl862xx can schedule its post-flash and post-drain re-probe through the core instead of open-coding a work item --- drivers/base/dd.c | 115 +++++++++++++++++++++++++++++++++++++++++ include/linux/device.h | 2 + 2 files changed, 117 insertions(+) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index f6525a7ee8c5..a26a6b0eaef5 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1436,3 +1436,118 @@ void driver_detach(const struct device_driver *drv) put_device(dev); } } + +struct device_reprobe { + struct delayed_work work; + const struct device_driver *drv; + const char *drv_name; + struct device *dev; +}; + +static void device_reprobe_work_fn(struct work_struct *work) +{ + struct device_reprobe *rp =3D container_of(work, struct device_reprobe, + work.work); + struct device *dev =3D rp->dev; + bool detached =3D false; + int ret; + + device_lock(dev); + /* + * rp->drv is only compared, never dereferenced: the driver it points + * to may have been unregistered and freed. The saved name rejects a + * freed address the allocator has since handed to another driver. + */ + if (!defer_all_probes && !dev->p->dead && dev->driver =3D=3D rp->drv && + !strcmp(dev->driver->name, rp->drv_name)) { + __device_release_driver(dev, NULL); + detached =3D true; + } + device_unlock(dev); + + if (detached) { + ret =3D device_attach(dev); + if (ret < 0 && ret !=3D -EPROBE_DEFER) + dev_err_probe(dev, ret, + "re-probe failed, device left unbound\n"); + } + + put_device(dev); + kfree(rp->drv_name); + kfree(rp); +} + +/** + * device_schedule_reprobe - schedule a deferred detach and re-probe + * @dev: device to detach and re-probe + * @delay_ms: delay in milliseconds before the re-probe runs + * + * Schedule a detach and re-probe of @dev after @delay_ms milliseconds, + * from built-in driver-core work rather than a driver-owned work item, + * so the bound driver may call it without pinning its own module. The + * binding is recorded as the driver pointer plus a copy of its name; the + * pointer is only ever compared, never dereferenced, and the name copy + * guards against a freed &struct device_driver address the allocator + * later hands to a different driver. + * + * The re-probe is skipped when the work runs if @dev has since been + * removed, is no longer bound, is bound to a different driver, or probing + * has been blocked for a system shutdown. The work is freezable, so one + * pending across system suspend runs once the system has resumed. A + * failed re-probe leaves @dev unbound, as a failed initial probe would. + * + * This is device_reprobe() deferred, and shares its limitations; + * __device_release_driver() is unchanged. The detach and the re-attach + * are not one locked operation, so an administrative unbind arriving + * between them may be undone, and the attach half runs the normal probe + * path with no shutdown re-check of its own. If @dev has managed + * consumers, detaching it unbinds them as any driver release does, so a + * re-probe a concurrent device_shutdown() overtakes may run ->remove() + * in place of ->shutdown(). None of this is specific to this helper. + * + * Buses that take the parent lock to bind (only usb_bus_type) are refused + * with -EINVAL: the parent would have to be recorded before either lock + * is held, where device_move() can replace it. + * + * Context: May sleep (allocates with %GFP_KERNEL). May be called from any + * process context, @dev's own device lock held included, but not from + * @dev's ->probe(), which the scheduled work would detach. + * + * Returns: 0 on success, -EINVAL if @dev is not a registered device + * bound to a driver or sits on a bus which takes the parent lock to + * bind, -ENOMEM on allocation failure. + */ +int device_schedule_reprobe(struct device *dev, unsigned int delay_ms) +{ + const struct device_driver *drv; + struct device_reprobe *rp; + + drv =3D READ_ONCE(dev->driver); + /* + * A bus taking the parent lock would need @dev's parent pinned until + * the work runs, which device_move() can invalidate. + */ + if (!drv || !dev->bus || dev->bus->need_parent_lock || !dev->p || + dev->p->dead || !device_is_registered(dev)) + return -EINVAL; + + rp =3D kzalloc_obj(*rp); + if (!rp) + return -ENOMEM; + + rp->drv_name =3D kstrdup(drv->name, GFP_KERNEL); + if (!rp->drv_name) { + kfree(rp); + return -ENOMEM; + } + + rp->dev =3D get_device(dev); + rp->drv =3D drv; + + INIT_DELAYED_WORK(&rp->work, device_reprobe_work_fn); + queue_delayed_work(system_freezable_wq, &rp->work, + msecs_to_jiffies(delay_ms)); + + return 0; +} +EXPORT_SYMBOL_GPL(device_schedule_reprobe); diff --git a/include/linux/device.h b/include/linux/device.h index aee79fd6b32b..7a9916950577 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -1314,6 +1314,8 @@ int __must_check device_attach(struct device *dev); int __must_check driver_attach(const struct device_driver *drv); void device_initial_probe(struct device *dev); int __must_check device_reprobe(struct device *dev); +int __must_check device_schedule_reprobe(struct device *dev, + unsigned int delay_ms); =20 bool device_is_bound(struct device *dev); =20 --=20 2.55.0 From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4FBFB347505; Wed, 23 Sep 2026 02:35:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130950; cv=none; b=kNpWzHrjINLM83TQQShJiin4KyLDQ2RfyumdrhY5fXQ0LXYVhtYBuc/qQ/cDa2JN3ClWEn7Asrfdyzksg1i8FTHvVPXec//7lzwKRC/s1v7yXRFIx4/2GlVQcaOsK0q6A3DZtc2t5y/imsSndL4Yje29XqArJq55MMoN0Cs8zM4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130950; c=relaxed/simple; bh=7BCcbCcvsKm84NRRiR+j2vgyQOzsOoZUfcGughYhpDQ=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=jg0tKP6dm9BfXhqgW7E99/1UQ0IhS8kxw7UYXrURJE48gEnHQysN2FRptyhyVRC4VYJXxrmIw6sOnsCaV0nFuVcgxv8wQ2lQe8sVL+/QSUB2oE5P5Ih7Lip2AkrGe2m2PEnVZXDaoxDWkHmKpPH0ljK3OajN7/WJkmSbP0uZHi4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9Cpg-0000000031A-2RqH; Wed, 23 Sep 2026 02:35:40 +0000 Date: Wed, 23 Sep 2026 03:35:37 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 4/6] net: dsa: mxl862xx: add devlink flash_update and info_get Message-ID: <4e9a8b6062f8dccfa5564400b4f80dda983e4c33.1790130482.git.daniel@makrotopia.org> References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Implement "devlink dev flash" for firmware upgrade and "devlink dev info" for version reporting. info reports the chip part number and revision as the asic.id and asic.rev fixed versions, and the firmware version as both the running and the stored version, since the switch boots its firmware from its own flash; a zero part number or an all-zero version is omitted. flash validates the image and its payload CRCs, reboots the switch into its MCUboot loader with SYS_MISC_FW_UPDATE, transfers the image over the SB PDI clause-22 SMDIO protocol, and reboots into the new firmware once the loader has verified it. For the duration the driver closes and detaches the user ports so userspace cannot reopen them, closes the conduit, and blocks firmware API access under the MDIO bus lock so nothing reaches the bus after the switch has left for MCUboot. A blocked write reports success, so a bridge tearing its ports down over a flash does not see port_vlan_del() fail and leak its VLAN group. MCUboot offers no in-place path back, so the driver reinitialises with a deferred re-probe scheduled through device_schedule_reprobe() from the previous patch, which runs in the driver core off the devlink caller's locks and without holding a module or device reference. ->shutdown() and .remove() take the devlink instance lock up front, since neither goes through devlink and dsa_unregister_switch() frees the user netdevs before devlink_unregister() would: this waits out a transfer in flight rather than cutting it in half, and refuses a flash that starts once teardown has begun. Assisted-by: LLM Signed-off-by: Daniel Golle --- v17: - wait out a flash in flight in .remove() too, not only ->shutdown(): dsa_unregister_switch() frees the user netdevs before it reaches the devlink instance lock, so an unbind racing a flash could touch a freed netdev; .remove() now takes that lock up front like ->shutdown(), and both announce the wait with dev_info() (found by Sashiko AI review) - report success for a firmware read blocked by a flash (-EBUSY) as well as the teardown -ENODEV in port_mdb_add() and port_mdb_del(), so an MDB change racing a flash does not fail and leave the entry linked (found by Sashiko AI review) - clear the SB PDI ADDR and DATA latches with 0 rather than the CTRL reset mode value, which only happened to be 0 (found by Sashiko AI review) - log that an unbind and rebind is needed when the post-flash re-probe cannot be scheduled although the new firmware is already running (found by Sashiko AI review) - kernel-doc: describe the block_host and skip_teardown policy as it is, a host read failing while a write reports success (found by Sashiko AI review) - commit message: scheduling the re-probe can also fail with -EINVAL when the device is being unbound, not only -ENOMEM (found by Sashiko AI review) v16: - report success for a firmware write blocked by a running flash, instead of -EBUSY. Tearing a bridge down over a flash made port_vlan_del() fail, which leaves the VLAN on the bridge's list: __vlan_group_free() then warns and frees the group with the entries still linked - wait for a flash in flight in ->shutdown() and refuse one requested after it, so a reboot no longer cuts the image in half - state a fact in the -EBUSY extack of a pending reprobe, dropping the retry advice - the comment on mxl862xx_read_chip_id() no longer describes a rescue-mode cache that only the next patch creates - select CRC32, which nothing else selects for the crc32_le() calls validating the image checksums (found by Sashiko AI review) - Andrew's Reviewed-by is dropped for the ->shutdown() change v15: - treat the closing END write as advisory: the loader has left the receive loop and verified the image by then, so a failed write no longer reports a completed flash as a transfer failure (found by Sashiko AI review) - admit only the flash task's own firmware reads past block_host, instead of every read from any context, which let ethtool and FDB queries reach the freshly booted firmware and left an mdb_add() half executed (found by Sashiko AI review) v14: no changes, picked up Andrew's v13 Reviewed-by v13: stop the stats poll with disable_delayed_work_sync() in the flash path, so a racing get_stats64() re-arm is a no-op, and drop the early return in the work function with it; the v12 reordering of remove() is gone as well, since the race it addressed predates this series and needs a fix of its own (found by Sashiko AI review) v12: - cancel the stats poll after dsa_unregister_switch() in .remove(): a get_stats64() past its lockless WORK_STOPPED check could re-arm the work after the cancel and then run against the devres-freed priv (found by Sashiko AI review) - keep block_host set across the post-flash readiness poll and let only the flash path's own firmware reads through, so a concurrent bridge or STP write cannot reach the freshly booted firmware with stale pre-flash resource IDs while rtnl is dropped (found by Sashiko AI review) v11: - schedule the post-flash re-probe with device_schedule_reprobe() instead of a driver-owned work item; the module and device references and the drvdata bound-check go away with it, and with them both findings of the v10 AI review -- the work function no longer ends in module text behind a module_put(), and the bound-check and detach now run under one __device_driver_lock() hold in the core where ->shutdown() cannot interleave between them - the dsa_switch allocation returns to devres; keeping it out only defused the check-vs-detach window that the core helper now closes outright - scheduling the re-probe is now the one step that can fail after the switch was flashed, since the helper allocates its own work item; an -ENOMEM there is returned as-is, since unbind and rebind reinitialises the driver v10: - do not reprobe a device that has been shut down or unbound. The work cannot be cancelled from teardown: .shutdown() runs under device_lock(), which device_reprobe() takes as well, so a lock shared between the two would deadlock, and cancel_delayed_work_sync() would hang on the nested remove() the work triggers itself. It checks drvdata under device_lock() instead, which both .shutdown() and the driver core clear (found by Sashiko AI review of patch 4, but this is where the reprobe comes from) - allocate the dsa_switch outside devres and free it in .remove(), so losing the remaining race with .shutdown() cannot let devres free it while the DSA tree still holds dsa_ports pointing at it - lower the per-slice write timeout from 120 s to 60 s, so this path and the drain added in patch 4 agree on how long the same loader step may take (found by Sashiko AI review) v9: no changes v8: - refuse a second devlink dev flash while the previous one's reprobe is still pending: the firmware API is short-circuited by then, so the FW_UPDATE command faked success and the raw SB PDI writes ran against a switch the driver no longer tracks (found by Sashiko AI review) - omit the firmware version from devlink dev info while the cached copy is all-zero, so a failed transfer no longer publishes 0.0.0 as both running and stored, and clear asic_rev along with asic_id (found by Sashiko AI review) - only translate -ENODEV into success in port_mdb_del() during the post-flash teardown; outside it, a genuine bus error was reported to switchdev as a successful deletion (found by Sashiko AI review) - evaluate the image verification verdict the loader publishes after the last slice, instead of polling for a value it had already published, so a rejected image is no longer reported as a write timeout - rename the end_magic label to no_end, which is what it does, and correct the protocol comment: END is optional as the loader finalises on a 2 s timeout, the status register keeps the byte count while a chunk is programmed, and the half-bank switch does not clear DATA (found by Sashiko AI review) v7: - reprobe from a single delayed work item instead of a kthread spawned by a workqueue kickoff; the kthread existed only to drop the module reference from core code, but its creation-failure path did the racy module_put() from module text anyway and could strand the driver bound with skip_teardown set. The collapsed form matches iwl_trans_reprobe_wk(), and a failed reprobe now leaves the device unbound like a failed probe - only signal END on a successful transfer; a failure leaves the loader mid-payload, where a STAT write is a byte count and END (0x3cc3) is read as one, risking a receive-counter underflow, so return the error and let the reprobe recover - add cond_resched() to the payload loop so a long transfer over a bit-banged MDIO bus under CONFIG_PREEMPT_NONE does not trip the soft-lockup detector - drop the cached firmware version and chip id on a failed flash so devlink dev info stops reporting the pre-flash version until the reprobe re-reads it - report the firmware version under DEVLINK_INFO_VERSION_GENERIC_FW instead of a bare "fw" string - correct the SB PDI header comment's SMDIO register map (page in MII reg 0x1f, register from the low nibble) and expand the note on why closing the shared conduit is safe v6: - confirm the new firmware is running with mxl862xx_wait_ready() and lift the host block before reporting success, so devlink dev flash completes only once the update has taken effect instead of relying on the later reprobe to pick up the new version - run the post-flash reprobe from a kthread that drops the module reference with module_put_and_kthread_exit(), spawned from a workqueue kickoff, closing a use-after-free where a work item's trailing module_put() could return into module text a racing rmmod had already freed - jump to the end_magic teardown on every flash failure from the ready handshake onward, so an aborted transfer sends END and lets MCUboot reboot instead of leaving the loader waiting - poll the SB PDI status register with read_poll_timeout(), which evaluates the condition once more after the deadline, so a preempted poll cannot report a spurious -ETIMEDOUT - bail out of the periodic stats poll when the flash teardown has set WORK_STOPPED, closing a get_stats64() re-arm race - allocate the reprobe kickoff before disturbing the switch, so an -ENOMEM cannot leave it flashed but never reprobed with block_host and skip_teardown stuck set - omit asic.id/asic.rev when the CHIP ID read returned 0, instead of publishing a bogus "0000" for fwupd to match firmware against v5: - report the numeric chip part number and version read from the static CHIP ID registers as "asic.id" and "asic.rev" instead of a model-name string, which does not belong in a devlink version identifier (Jakub Kicinski) - report the running firmware version as the "stored" version too, since the switch boots it from its own flash, so userspace can tell a flash-backed part from a flashless one by the presence of "stored" without a future API change - run the post-flash reprobe from a self-contained work item again instead of the v4 kernel thread, which tripped the hung-task watchdog while parked across the flash and returned -EINTR from kthread_create() when the devlink command was interrupted - re-read the new firmware version through the reprobe's fresh probe and drop the SYS_MISC_FW_VERSION exemption from the host block - raise the firmware command poll timeout so the FW_UPDATE command that reboots into MCUboot is not cut short - move the devlink documentation into its own patch v4: - run the deferred reprobe from a kernel thread ending in module_put_and_kthread_exit() instead of a work item whose final module_put() raced against module unload - fail API read commands with -ENODEV after the update instead of faking success with an unfilled buffer, which sent port_fdb_dump() into an endless loop - keep block_host set across the post-update version query by exempting SYS_MISC_FW_VERSION instead of briefly lifting the block, and write the blocking flags under the MDIO bus lock - check the return value of every SB PDI control write; a failed address write during the half-bank switch could place the second half of the payload at the wrong flash offset undetected - report SMDIO write failures through one shared error path instead of per-site messages - initialise the progress notification deadline from jiffies so notifications are not suppressed on 32-bit shortly after boot - flush the switchdev deferred queue after closing the ports so the bridge's deferred STP DISABLED transitions reach the firmware while it is still running instead of failing with -EBUSY against the host block - treat -ENODEV as successful deletion in port_mdb_del() so the post-update teardown does not leave leftover host MDB entries behind for the DSA core to report v3: - validate the image, including both CRCs, before closing any ports so a malformed file no longer triggers a flash and reprobe cycle - reject images whose declared payload sizes overflow when summed (check_add_overflow) or sum up to zero; the latter used to erase the flash without writing anything back - allocate the reprobe work item and take the module and device references before disturbing the switch instead of silently skipping the reprobe when the allocation fails afterwards - check block_host/skip_teardown under the MDIO bus lock to close the window where a command already past the check could reach the bus after the switch rebooted into MCUboot - prevent the stats poll work from being re-armed and cancel the CRC error work before the transfer - check the return value of SB PDI data word writes; control writes are verified by the subsequent status polls - report a per-model chip name from the OF match data as "asic.id" instead of the devicetree compatible string whose comma is awkward for userspace consumers (Andrew Lunn) - commit message: the conduit is only closed, not detached v2: - factor out SB PDI slice flush and devlink status notification helpers, resolving checkpatch issues - use kzalloc_obj() (Manuel Ebner) - add kernel-doc for the new mxl862xx_priv members - trim comments and state the actual duration of a flash and reprobe cycle, just under a minute (Manuel Ebner) - reword commit message: split up run-on sentence, explain the dynamically allocated reprobe work item (Manuel Ebner), mention that closing the ports stops phylib polling (Andrew Lunn) --- drivers/net/dsa/mxl862xx/Kconfig | 1 + drivers/net/dsa/mxl862xx/Makefile | 2 +- drivers/net/dsa/mxl862xx/mxl862xx-api.h | 10 + drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 2 + drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 680 +++++++++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 18 + drivers/net/dsa/mxl862xx/mxl862xx-host.c | 20 + drivers/net/dsa/mxl862xx/mxl862xx.c | 77 ++- drivers/net/dsa/mxl862xx/mxl862xx.h | 25 + 9 files changed, 833 insertions(+), 2 deletions(-) create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h diff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kc= onfig index e51a67a3cf9b..8bb524fe28d1 100644 --- a/drivers/net/dsa/mxl862xx/Kconfig +++ b/drivers/net/dsa/mxl862xx/Kconfig @@ -3,6 +3,7 @@ config NET_DSA_MXL862 tristate "MaxLinear MxL862xx" depends on NET_DSA select CRC16 + select CRC32 select NET_DSA_TAG_MXL_862XX help This enables support for the MaxLinear MxL862xx switch family. diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/M= akefile index a7be0e6669df..bccac0d0f703 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) +=3D mxl862xx_dsa.o -mxl862xx_dsa-y :=3D mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o +mxl862xx_dsa-y :=3D 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/mxl8= 62xx/mxl862xx-api.h index a180a5decffc..6f771895984c 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; =20 +/** + * 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/mxl8= 62xx/mxl862xx-cmd.h index c87a955c13c4..a865425aa61e 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) =20 +#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) =20 #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/mxl86= 2xx/mxl862xx-fw.c new file mode 100644 index 000000000000..7a506d110c8d --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Firmware flash and devlink support for MaxLinear MxL862xx + * + * Copyright (C) 2025 Daniel Golle + * + * SB PDI - firmware download interface over clause-22 SMDIO + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * The MxL862xx MCUboot loader accepts a firmware image through four "SB P= DI" + * 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 f= ails + * 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 :=3D ( & 0xfff0) ; page latch + * MII reg ( & 0x000f) :=3D / =3D> + * 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=3D0x00 RD=3D0x01 WR=3D0x02 + * ADDR 0xe101 -> 0xc0938404 SB target word address (SB1 bank =3D 0x78= 00) + * 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) + * START 0xf48f host -> begin download session + * ACK 0xf490 loader -> START acknowledged (START + 1) + * END 0x3cc3 host -> finalise now (optional, see below) + * + * Console flash path (STAT=3D0xc55c) - mxl862xx_flash_firmware(): + * + * host loader + * ---- ------ + * reset (CTRL=3DADDR=3DDATA=3D0) + * read STAT ............................ 0xc55c (READY, idle) + * STAT :=3D START(0xf48f) --------------> + * <-------------- STAT =3D 0xf490 (ACK) + * CTRL :=3D WR + * DATA :=3D hdr[0..9] (20-byte header: type,size1,crc1,size2,crc2) + * reset; STAT :=3D 20 (header len) -----> parse hdr; r_remain=3Dsize1+= size2; + * ERASE target region(s) + * <-------------- STAT=3D21 (len+1), then STAT= =3D0 + * (erased) + * -- payload, streamed in slices: -- + * CTRL :=3D WR + * DATA :=3D word x N ... + * at word 16384: CTRL:=3DRST; ADDR:=3D0x7800; CTRL:=3DWR (half-bank = -> SB1) + * at word 32760: flush slice: + * reset; STAT :=3D ---> r_remain -=3D bytes; pr= ogram + * <------------------- STAT=3D0 (ready for next= slice) + * ... repeat until the whole payload is sent ... + * <------------------- STAT=3D0 image verified + * (STAT=3D1: image rejected) + * STAT :=3D END(0x3cc3) ---------------------> finalise and boot + * + * The r_remain =3D=3D 0 rule (critical): + * Every host STAT write in the payload phase is a byte count; the loader + * does r_remain -=3D count and stays in the receive loop while r_remain= !=3D 0. + * It leaves the loop ONLY when r_remain hits EXACTLY 0, and a count lar= ger + * than r_remain underflows the 32-bit counter and wedges the loader unt= il a + * power cycle. Having left it, the loader verifies the image, publishes= the + * verdict in STAT (0 good, 1 rejected) and waits 2 s for END before + * finalising regardless -- clearing its rescue-enable bit so boot_go bo= ots + * the new image -- so END only saves that wait. Hence: + * - never send a slice/chunk count larger than what is outstanding; + * - a STAT write is a command only once the loader has left the loop; + * - the loader leaves the count in STAT while it programs the chunk, = so + * a lingering count does not distinguish "busy" from "verdict". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_START 0xf48f +#define MXL862XX_SB_PDI_END 0x3cc3 + +/* Image verification verdict published in STAT once the receive loop ends= */ +#define MXL862XX_SB_PDI_VERIFY_OK 0 +#define MXL862XX_SB_PDI_VERIFY_BAD 1 + +/* Firmware transfer geometry */ +#define MXL862XX_FW_HDR_SIZE 20 +#define MXL862XX_FW_BANK_HALF 16384 /* words per half-bank */ +#define MXL862XX_FW_BANK_SLICE 32760 /* words per full slice */ +#define MXL862XX_FW_SB1_ADDR 0x7800 /* SB1 word address */ + +/* Timeouts (generous upper bounds) */ +#define MXL862XX_FW_READY_TIMEOUT_MS 3000 +#define MXL862XX_FW_ACK_TIMEOUT_MS 5000 +#define MXL862XX_FW_ERASE_TIMEOUT_MS 300000 +#define MXL862XX_FW_WRITE_TIMEOUT_MS 60000 +#define MXL862XX_FW_REBOOT_DELAY_MS 5000 +#define MXL862XX_FW_REPROBE_DELAY_MS 500 + +static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv) +{ + int ret; + + /* CTRL selects RST mode; ADDR and DATA are cleared to 0. */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_RST); + if (ret < 0) + return ret; + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR, 0x0000); + if (ret < 0) + return ret; + + return mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000); +} + +static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expec= ted, + unsigned long timeout_ms) +{ + int ret, val; + + ret =3D read_poll_timeout(mxl862xx_smdio_read, val, + val < 0 || (u16)val =3D=3D 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 =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written); + if (ret < 0) + return ret; + + return mxl862xx_sb_pdi_poll_stat(priv, 0, + MXL862XX_FW_WRITE_TIMEOUT_MS); +} + +/* Flush the last slice, which ends the receive loop: the loader verifies = the + * image and replaces the count in STAT with its verdict, so wait for the = count + * to go rather than for a fixed value. + */ +static int mxl862xx_sb_pdi_flush_last(struct mxl862xx_priv *priv, + u32 data_written) +{ + int ret, val; + + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written); + if (ret < 0) + return ret; + + ret =3D read_poll_timeout(mxl862xx_smdio_read, val, + val < 0 || (u16)val !=3D (u16)data_written, + 10000, MXL862XX_FW_WRITE_TIMEOUT_MS * 1000, + false, priv, MXL862XX_SB_PDI_STAT); + if (val < 0) + return val; + + if (!ret && (u16)val =3D=3D MXL862XX_SB_PDI_VERIFY_OK) + return 0; + + /* A final count of 1 is indistinguishable from the reject verdict, so + * a timeout still holding it lands here too. + */ + if ((u16)val =3D=3D MXL862XX_SB_PDI_VERIFY_BAD) { + dev_err(&priv->mdiodev->dev, + "flash: loader rejected the image\n"); + return -EBADMSG; + } + + return ret ? ret : -EPROTO; +} + +static void mxl862xx_flash_notify(struct devlink *dl, const char *status, + u32 done, u32 total) +{ + devlink_flash_update_status_notify(dl, status, NULL, done, total); +} + +/* 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 =3D (const struct mxl862xx_fw_hdr *)fw->data; + payload =3D fw->data + MXL862XX_FW_HDR_SIZE; + size1 =3D le32_to_cpu(hdr->image_size_1); + size2 =3D 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 =3D ~crc32_le(~0U, payload, size1); + if (crc !=3D 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 =3D ~crc32_le(~0U, payload + size1, size2); + if (crc !=3D 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 =3D 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 =3D fw->data + MXL862XX_FW_HDR_SIZE; + u32 word_idx =3D 0, data_written =3D 0, idx =3D 0; + unsigned long next_notify =3D jiffies - 1; + u16 word, fdata; + int ret, i; + + /* Step 1: reboot the firmware into MCUboot rescue mode */ + ret =3D 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 =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + goto write_err; + + /* Failures from here on end up at no_end, which returns the error + * without signalling END -- see there. + */ + ret =3D mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY, + MXL862XX_FW_READY_TIMEOUT_MS); + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: bootloader not ready: %pe\n", ERR_PTR(ret)); + goto no_end; + } + + /* Step 3: start handshake */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_SB_PDI_START); + if (ret < 0) + goto write_err; + + ret =3D mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1, + MXL862XX_FW_ACK_TIMEOUT_MS); + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: start handshake failed: %pe\n", ERR_PTR(ret)); + goto no_end; + } + + /* Step 4: transfer image header */ + mxl862xx_flash_notify(dl, "Erasing flash", 0, 0); + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_WR); + if (ret < 0) + goto write_err; + + for (i =3D 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) { + word =3D fw->data[i * 2] | + ((u16)fw->data[i * 2 + 1] << 8); + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word); + if (ret < 0) + goto write_err; + } + + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + goto write_err; + + /* the byte count in STAT triggers the erase */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_FW_HDR_SIZE); + if (ret < 0) + goto write_err; + + /* ACK is byte count + 1 */ + ret =3D mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1, + MXL862XX_FW_ACK_TIMEOUT_MS); + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: header ACK failed: %pe\n", ERR_PTR(ret)); + goto no_end; + } + + /* Step 5: wait for erase to complete */ + ret =3D mxl862xx_sb_pdi_poll_stat(priv, 0, + MXL862XX_FW_ERASE_TIMEOUT_MS); + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: erase timeout: %pe\n", ERR_PTR(ret)); + goto no_end; + } + + /* Step 6: transfer payload */ + ret =3D 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 =3D payload[idx] | + ((u16)payload[idx + 1] << 8); + idx +=3D 2; + data_written +=3D 2; + } else { + fdata =3D payload[idx]; + idx++; + data_written++; + } + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata); + if (ret < 0) + goto write_err; + word_idx++; + + if (idx >=3D payload_size) { + ret =3D mxl862xx_sb_pdi_flush_last(priv, data_written); + break; + } + + /* Half-bank boundary: switch to SB1 address */ + if (word_idx =3D=3D MXL862XX_FW_BANK_HALF) { + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_RST); + if (ret < 0) + goto write_err; + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR, + MXL862XX_FW_SB1_ADDR); + if (ret < 0) + goto write_err; + + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_WR); + if (ret < 0) + goto write_err; + } else if (word_idx >=3D MXL862XX_FW_BANK_SLICE) { + ret =3D mxl862xx_sb_pdi_flush_slice(priv, data_written); + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: write timeout at %u/%u: %pe\n", + idx, payload_size, ERR_PTR(ret)); + goto no_end; + } + word_idx =3D 0; + data_written =3D 0; + ret =3D 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 =3D jiffies + msecs_to_jiffies(500); + } + } + } + + if (ret) { + dev_err(&priv->mdiodev->dev, + "flash: final slice failed: %pe\n", ERR_PTR(ret)); + goto no_end; + } + + mxl862xx_flash_notify(dl, "Flashing", payload_size, payload_size); + + /* Success: the loader has left the receive loop at r_remain =3D=3D 0 and + * verified the image, so END(0x3cc3) is a finalise/boot request rather + * than a byte count. Signal it here -- and only here -- to boot the new + * image without waiting out the loader's 2 s END timeout. + */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_SB_PDI_END); + if (ret < 0) + dev_warn(&priv->mdiodev->dev, + "flash: END signalling failed, waiting the loader out: %pe\n", + ERR_PTR(ret)); + + msleep(MXL862XX_FW_REBOOT_DELAY_MS); + return 0; + +write_err: + dev_err(&priv->mdiodev->dev, "flash: SMDIO write failed: %pe\n", + ERR_PTR(ret)); +no_end: + /* A failure leaves the loader mid transfer; do not signal END (a STAT + * write is a byte count then, and END would be misread as one, risking + * a receive-counter underflow). Return the error; the caller reprobes. + */ + return ret; +} + +int mxl862xx_devlink_info_get(struct dsa_switch *ds, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct mxl862xx_priv *priv =3D ds->priv; + char buf[16]; + int ret; + + /* 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 =3D 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 =3D devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_REV, + buf); + if (ret) + return ret; + } + + /* An all-zero version is the cache a failed flash left behind, not a + * released firmware; omit it like the part number above. + */ + if (!priv->fw_version.major && !priv->fw_version.minor && + !priv->fw_version.revision) + return 0; + + snprintf(buf, sizeof(buf), "%u.%u.%u", + priv->fw_version.major, priv->fw_version.minor, + priv->fw_version.revision); + + ret =3D devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, buf); + if (ret) + return ret; + + /* boots this image from its own flash: stored =3D=3D running */ + return devlink_info_version_stored_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, buf); +} + +int mxl862xx_devlink_flash_update(struct dsa_switch *ds, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct mxl862xx_priv *priv =3D ds->priv; + struct dsa_port *dp; + u32 payload_size; + int ret, err, i; + + if (params->component) { + NL_SET_ERR_MSG_MOD(extack, "component is not supported"); + return -EOPNOTSUPP; + } + + /* Written under the instance lock this call is holding. */ + if (priv->shutting_down) { + NL_SET_ERR_MSG_MOD(extack, "device is shutting down"); + return -ENODEV; + } + + /* A previous flash is still waiting for its reprobe: the firmware API + * is short-circuited, so the raw SB PDI writes below would run against + * a switch this driver no longer tracks. + */ + if (priv->skip_teardown) { + NL_SET_ERR_MSG_MOD(extack, + "a previous flash awaits its reprobe"); + return -EBUSY; + } + + ret =3D mxl862xx_flash_validate(priv, params->fw, &payload_size); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, "firmware image validation failed"); + return ret; + } + + 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 =3D true; + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + disable_delayed_work_sync(&priv->stats_work); + cancel_work_sync(&priv->crc_err_work); + for (i =3D 0; i < ds->num_ports; i++) + cancel_work_sync(&priv->ports[i].host_flood_work); + + ret =3D mxl862xx_flash_firmware(priv, params->fw, payload_size, + ds->devlink); + if (ret) + NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed"); + + if (!ret) { + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, + MDIO_MUTEX_NESTED); + /* Keep block_host set so host writes stay blocked, but let the + * readiness poll below read the freshly booted firmware. + */ + priv->flash_owner =3D current; + 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 =3D 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 =3D 0; + priv->asic_rev =3D 0; + } + + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + priv->flash_owner =3D NULL; + priv->block_host =3D false; + priv->skip_teardown =3D true; + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + /* Reinitialise through a deferred re-probe: remove() runs with + * skip_teardown set, then a fresh probe() starts against whatever + * the switch now runs. The core skips the re-probe if the device + * is unbound or shut down before it fires. + */ + err =3D device_schedule_reprobe(ds->dev, MXL862XX_FW_REPROBE_DELAY_MS); + if (!ret && err) + dev_err(ds->dev, + "flash: new firmware is running but re-probe could not be scheduled (%p= e); unbind and rebind to reinitialise\n", + ERR_PTR(err)); + + return ret ? ret : err; +} + +/* The devlink core holds the instance lock across a flash, so taking it h= ere + * waits for a transfer in flight instead of cutting the image in half, and + * bars one that has not started yet. + */ +void mxl862xx_flash_shutdown(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv =3D ds->priv; + + if (!ds->devlink) + return; + + /* A flash holds the instance lock for its whole run. Announce the + * wait so the delay is not mistaken for a hang. + */ + if (!devl_trylock(ds->devlink)) { + dev_info(ds->dev, + "firmware update in progress, waiting for it to finish\n"); + devl_lock(ds->devlink); + } + priv->shutting_down =3D true; + devl_unlock(ds->devlink); +} diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl86= 2xx/mxl862xx-fw.h new file mode 100644 index 000000000000..15ed3a46bcfe --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_FW_H +#define __MXL862XX_FW_H + +#include + +struct mxl862xx_priv; + +int mxl862xx_devlink_info_get(struct dsa_switch *ds, + struct devlink_info_req *req, + struct netlink_ext_ack *extack); +int mxl862xx_devlink_flash_update(struct dsa_switch *ds, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack); +void mxl862xx_flash_shutdown(struct dsa_switch *ds); + +#endif /* __MXL862XX_FW_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl= 862xx/mxl862xx-host.c index 11759fa6069b..4b3956a518cf 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c @@ -12,9 +12,11 @@ #include #include #include +#include #include #include #include "mxl862xx.h" +#include "mxl862xx-cmd.h" #include "mxl862xx-host.h" =20 #define CTRL_BUSY_MASK BIT(15) @@ -340,6 +342,24 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 = cmd, void *_data, =20 mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); =20 + if (priv->skip_teardown) { + ret =3D read ? -ENODEV : 0; + goto out; + } + + /* During the post-flash readiness poll block_host stays set, but the + * flash path's own firmware version reads must reach the new image; + * host writes stay blocked so stale resource IDs cannot corrupt it. + * A blocked write reports success: the reprobe discards the switch + * configuration anyway, and a bridge tearing down over a flash must + * not see port_vlan_del() fail, which leaks its VLAN group. + */ + if (priv->block_host && cmd !=3D SYS_MISC_FW_UPDATE && + !(read && priv->flash_owner =3D=3D current)) { + ret =3D read ? -EBUSY : 0; + goto out; + } + max =3D (size + 1) / 2; =20 ret =3D mxl862xx_busy_wait(priv); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx= /mxl862xx.c index e05ad52cd297..33a7cdb8edd3 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" =20 @@ -71,6 +72,13 @@ static const struct ethtool_rmon_hist_range mxl862xx_rmo= n_ranges[] =3D { #define MXL862XX_READY_TIMEOUT_MS 10000 #define MXL862XX_READY_POLL_MS 100 =20 +/* Chip ID registers, read via SYS_MISC_REG_RD */ +#define MXL862XX_CHIPID_L 0xc0d28884 +#define MXL862XX_CHIPID_M 0xc0d28888 +#define MXL862XX_CHIPID_L_PNUML GENMASK(15, 12) +#define MXL862XX_CHIPID_M_PNUMM GENMASK(11, 0) +#define MXL862XX_CHIPID_M_VERSION GENMASK(14, 12) + #define MXL862XX_TCM_INST_SEL 0xe00 #define MXL862XX_TCM_CBS 0xe12 #define MXL862XX_TCM_EBS 0xe13 @@ -222,7 +230,45 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_b= us *bus, int addr, return mxl862xx_phy_write_mmd(bus->priv, addr, devadd, regnum, val); } =20 -static int mxl862xx_wait_ready(struct dsa_switch *ds) +/* The CHIP ID registers are only readable through the firmware mailbox, so + * the values are cached here and stay zero while no firmware answers. + */ +static int mxl862xx_read_chip_id(struct mxl862xx_priv *priv) +{ + struct mxl862xx_sys_reg_rw reg =3D {}; + u16 chipid_l, chipid_m; + int ret; + + reg.addr =3D cpu_to_le32(MXL862XX_CHIPID_L); + ret =3D MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg); + if (ret) + return ret; + chipid_l =3D le32_to_cpu(reg.val); + + reg.addr =3D cpu_to_le32(MXL862XX_CHIPID_M); + ret =3D MXL862XX_API_READ(priv, SYS_MISC_REG_RD, reg); + if (ret) + return ret; + chipid_m =3D le32_to_cpu(reg.val); + + priv->asic_id =3D FIELD_GET(MXL862XX_CHIPID_L_PNUML, chipid_l) | + FIELD_GET(MXL862XX_CHIPID_M_PNUMM, chipid_m) << 4; + priv->asic_rev =3D 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 =3D {}; unsigned long start =3D jiffies, timeout; @@ -254,6 +300,11 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds) priv->fw_version.major =3D ver.iv_major; priv->fw_version.minor =3D ver.iv_minor; priv->fw_version.revision =3D le16_to_cpu(ver.iv_revision); + + ret =3D mxl862xx_read_chip_id(priv); + if (ret) + dev_warn(ds->dev, "failed to read chip ID: %pe\n", + ERR_PTR(ret)); return 0; =20 not_ready_yet: @@ -1547,6 +1598,13 @@ static int mxl862xx_port_mdb_add(struct dsa_switch *= ds, int port, qparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); =20 ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam); + /* A flash blocks the API (-EBUSY) and its teardown drops the MAC + * table (-ENODEV); there is then nothing to program, and the reprobe + * rebuilds the configuration. See mxl862xx_port_mdb_del(). + */ + if ((ret =3D=3D -EBUSY && priv->block_host) || + (ret =3D=3D -ENODEV && priv->skip_teardown)) + return 0; if (ret) return ret; =20 @@ -1584,6 +1642,13 @@ static int mxl862xx_port_mdb_del(struct dsa_switch *= ds, int port, ether_addr_copy(qparam.mac, mdb->addr); =20 ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam); + /* A flash blocks the API (-EBUSY) and its teardown drops the MAC + * table (-ENODEV); a delete then has nothing to do. Outside these, + * both are bus errors and must be reported. + */ + if ((ret =3D=3D -EBUSY && priv->block_host) || + (ret =3D=3D -ENODEV && priv->skip_teardown)) + return 0; if (ret) return ret; =20 @@ -2097,6 +2162,8 @@ static const struct dsa_switch_ops mxl862xx_switch_op= s =3D { .get_pause_stats =3D mxl862xx_get_pause_stats, .get_rmon_stats =3D mxl862xx_get_rmon_stats, .get_stats64 =3D mxl862xx_get_stats64, + .devlink_info_get =3D mxl862xx_devlink_info_get, + .devlink_flash_update =3D mxl862xx_devlink_flash_update, }; =20 static int mxl862xx_probe(struct mdio_device *mdiodev) @@ -2161,6 +2228,12 @@ static void mxl862xx_remove(struct mdio_device *mdio= dev) =20 priv =3D ds->priv; =20 + /* Wait out a flash in flight and bar a new one before the DSA core + * frees the user netdevs; the devlink instance lock does not cover + * that free, which dsa_unregister_switch() reaches first. + */ + mxl862xx_flash_shutdown(ds); + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); =20 dsa_unregister_switch(ds); @@ -2187,6 +2260,8 @@ static void mxl862xx_shutdown(struct mdio_device *mdi= odev) =20 priv =3D ds->priv; =20 + mxl862xx_flash_shutdown(ds); + dsa_switch_shutdown(ds); =20 set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx= /mxl862xx.h index 432a5f3f2e08..054d0d35d3a5 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -303,6 +303,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 +323,19 @@ struct mxl862xx_fw_version { * @evlan_ingress_size: per-port ingress Extended VLAN block size * @evlan_egress_size: per-port egress Extended VLAN block size * @vf_block_size: per-port VLAN Filter block size + * @block_host: during a firmware flash, a host firmware read fails + * with -EBUSY and a write reports success without + * touching the bus, so a teardown racing the flash + * does not fail; FW_UPDATE and the flash owner's own + * reads still reach the bus + * @flash_owner: task running the post-flash readiness poll; only i= ts + * own firmware reads pass block_host + * @skip_teardown: during the post-flash reprobe teardown, a host + * firmware read fails with -ENODEV and a write repor= ts + * success without touching the bus + * @shutting_down: set under the devlink instance lock once ->shutdow= n() + * or .remove() has begun, so no flash starts while t= he + * switch is going away * @stats_work: periodic work item that polls RMON hardware counte= rs * and accumulates them into 64-bit per-port stats */ @@ -329,6 +346,8 @@ struct mxl862xx_priv { 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 +356,13 @@ struct mxl862xx_priv { u16 evlan_ingress_size; u16 evlan_egress_size; u16 vf_block_size; + struct task_struct *flash_owner; + bool block_host; + bool skip_teardown; + bool shutting_down; struct delayed_work stats_work; }; =20 +int mxl862xx_wait_ready(struct dsa_switch *ds); + #endif /* __MXL862XX_H */ --=20 2.55.0 From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB053347505; Wed, 23 Sep 2026 02:36:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130997; cv=none; b=ZkJUF2KxxE7tTS3odKHZzF5AND/QNwNFioPuTNW00C/S8DdVFbpDEu+W3ISvQeVr+fGJMQyh1ZbVFyszQcWIfj9oOSryo03iovA1VHisoRO0Iti8iH0tPY+JrdvFczkO0w9TE3fgkstpVhgOEXlngr8ZRp1+8fWdmV469Ei0UWY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790130997; c=relaxed/simple; bh=F9YAp1wCPP8c5BdU+QeGucbDgZQthwrf6CLH7TM0xwk=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=Rxf6r4dh7dF0wdPR6ToXq9r+XNPGquAoq2chVEmgETAUXEBzbKgHXjl8vNP4GYV1tLdaCtbADKKpswUWOlqeyWIIuwTzD+LR19np4MbA+qWHucliVgiQwmgqXL6uuTNddrhwbS4566t1LQ40seB9+/zalRq+HPprf5KgSid2jlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9CqR-0000000031s-2iw0; Wed, 23 Sep 2026 02:36:27 +0000 Date: Wed, 23 Sep 2026 03:36:24 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 5/6] net: dsa: mxl862xx: recover switch stuck in MCUboot rescue mode Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A broken or interrupted firmware image, or the sticky rescue bit, keeps the switch in its MCUboot loader, which exposes only the clause-22 SMDIO download interface. The clause-45 firmware API never comes up there, so an ordinary probe would spend its whole timeout on a mailbox nothing answers before failing. Detect the loader over SB PDI at setup, ahead of any clause-45 access, and in rescue mode register the switch without user interfaces so devlink stays available to reflash it: the user ports fail setup and the DSA core re-registers them as unused, while the CPU port comes up on its fixed link. devlink dev info then reports the firmware version as "0.0.0", which no released firmware carries, so fwupd offers every release as an upgrade and recovers the switch through the regular flash flow. An interrupted download can leave the loader wedged mid-payload with an outstanding byte count. A background work item off the devlink flash path drains it back to a clean ready state by feeding that count one byte at a time, since a larger step could underflow the loader's counter and wedge it until a power cycle, then re-probes so the probe-time detection reclassifies the switch. Until it finishes, devlink dev info reports no version and devlink dev flash returns -EBUSY. Assisted-by: LLM Signed-off-by: Daniel Golle --- v17: - bypass the firmware-version gate in mxl862xx_phylink_get_caps() in rescue mode, so a SerDes CPU port does not get an empty supported_interfaces mask and fail phylink_create(); the sibling mac_select_pcs() bypass was already there (found by Sashiko AI review) - extend the port_mdb_add()/port_mdb_del() flash-window tolerance to rescue mode as well (found by Sashiko AI review) - the @rescue_failed kernel-doc and the mxl862xx_setup_rescue() log message no longer prescribe a power cycle for every cause; the per-cause remedy is in the documentation (found by Sashiko AI review) v16: - drop the claim that the clause-45 API floods the log with CRC errors when no firmware answers, here and in the comments. The mailbox commands run into their timeouts instead, and testing the stuck-in-MCUboot paths produces no such message. What probing SB PDI first saves is the ten seconds of polling and the -ETIMEDOUT that ends probe - move the rescue-mode branch of mxl862xx_setup() into a function of its own, which brings its messages back inside 100 columns - state a fact in the -EBUSY extack of a running recovery, dropping the retry advice - trim the comment on mxl862xx_rescue_drain(), whose protocol detail is in the file header already, and order its declarations longest line first - treat a byte count that outlives the settle step as a busy loader rather than a running firmware, and wait out an erase from the dead session before draining, so a host that died during the loader's erase no longer fails probe with the -ETIMEDOUT this patch exists to avoid (found by Sashiko AI review) - poll the status register every 10 ms rather than every 50 us for the waits now measured in minutes - drop the loader's clean ready state along with the cached identity when a flash fails, so devlink dev info stops reporting 0.0.0, which means "ready to accept an image", for a loader left mid transfer (found by Sashiko AI review) - state facts in the extack for a failed recovery; the remedies, which differ per cause, are in the documentation (found by Sashiko AI review) - name -ECANCELED in the documented return sets that can produce it, and the opening handshake among the detection outcomes in the commit message (found by Sashiko AI review) v15: - classify a status register left in the download handshake as a loader needing a power cycle, rather than as a running firmware, which made probe fail with the CRC-error storm this patch avoids (found by Sashiko AI review) - give the loader one step to publish its next state when detection runs after a failed clause-45 wait, so the count of a chunk it is still programming is not read as a firmware status word (found by Sashiko AI review) - abort the drain polls as soon as teardown asks for it, instead of holding up unbind and shutdown for up to 17 s (found by Sashiko AI review) - pair the rescue_mode accesses with WRITE_ONCE()/READ_ONCE(), like the sibling rescue flags (found by Sashiko AI review) v14: - initialise the SerDes state once mxl862xx_wait_ready() has cached the firmware version, still before the rescue-mode early return, so PCS setup can depend on the running firmware v13: no changes v12: no changes v11: - schedule the post-drain re-probe with device_schedule_reprobe() too, instead of a driver-owned work item - a drain whose re-probe hand-off fails still marks recovery failed, so devlink does not keep promising a retry v10: - share the SB PDI timeouts with the flash path: one constant for the verify wait (15 s) and one for a single 1-byte mailbox step (2 s), the latter also replacing the separate detection timeout. The last-slice flush gets the sum of the write and verify budgets, since it cannot see the boundary between programming and verifying (found by Sashiko AI review) - report a reprobe hand-off that cannot be set up after a successful drain, instead of leaving devlink answering "retry shortly" for good for a loader sitting at a clean READY (found by Sashiko AI review) - drop heal_lock and mxl862xx_stop_work() with it: making the flag test and the queueing atomic was never the guarantee its comment claimed, and the reprobe now decides for itself whether it may still run (found by Sashiko AI review) - return -ENXIO rather than a propagated -ETIMEDOUT when the loader never re-arms READY for the register-read challenge, and correct the documented return sets of mxl862xx_rescue_mode_detect() and mxl862xx_rescue_drain_finish() (found by Sashiko AI review) - explain why STAT =3D=3D 0 during a drain is unambiguous: by the r_remain =3D=3D 0 rule the loader cannot be both inside the receive loop asking for a chunk and publishing a verdict (found by Sashiko AI review) - commit message: a running firmware is not "left untouched", the presence probe writes two mailbox scratch registers which are inert to it; an SB PDI window away from the OTP reset offsets also yields -ENODEV; and describe the -ENXIO outcome for a READY loader that never services the challenge (found by Sashiko AI review) v9: no changes v8: - never send END from the drain: the loader keeps the host's byte count in its status register while it programs a chunk, so a lingering count cannot be told from the "image rejected" verdict, and END written into the receive loop is consumed as a 15555-byte count and underflows the receive counter. Wait for the loader to ask for the next chunk or to return to its console loop instead, since it finalises on its own (found by Sashiko AI review) - initialise the SerDes state before the rescue-mode early return, so the window between a successful rescue-mode flash clearing rescue_mode and the reprobe cannot hand phylink a PCS with no ops and an uninitialised mutex (found by Sashiko AI review) - do not fail probe when the 1-byte slice-advance leaves the wedged loader somewhere other than asking for the next chunk: a download interrupted with exactly one byte outstanding completes on that byte, after which the loader verifies and returns to its console loop (found by Sashiko AI review) - report a failed drain and refuse further flashes with -EIO and an extack asking for a power cycle, instead of leaving devlink to answer "retry shortly" forever for a switch that never becomes ready (found by Sashiko AI review) - reset the mailbox before the presence probe: a download interrupted with the write latch armed made the scratch write land in switch memory instead, so detection returned -ENODEV for the very state it exists to recover (found by Sashiko AI review) - tell an SMDIO bus error apart from a loader failing the register-read challenge, and check the reset issued after it (found by Sashiko AI review) - reject DSA links before the rescue-mode shortcut, so an unsupported cascade topology fails probe in rescue mode too (found by Sashiko AI review) - serialise the self-heal's reprobe hand-off against teardown with a mutex (found by Sashiko AI review) - log the drain's progress, name its timeouts, and describe its real duration (found by Sashiko AI review) - WRITE_ONCE() the rescue_ready stores, document what orders rescue_mode, and correct the detection kernel-doc and the note on the OTP-configurable SB PDI register offsets (found by Sashiko AI review) v7: - queue the reprobe as a delayed work item from the background self-heal, following the previous patch's move off the reprobe kthread - report the rescue-mode firmware version under DEVLINK_INFO_VERSION_GENERIC_FW too - drop two redundant rescue-recovery log lines; the setup message ("switch in MCUboot with an interrupted download, recovering in background") already says it - return distinct errno from rescue_mode_detect() so an absent switch (-ENODEV), one strapped into flashless-download mode (-EOPNOTSUPP) and one that answers SB PDI READY but fails the register-read challenge, or wedges without draining (-ENXIO), are no longer all reported as -ENODEV v6: - after the background drain finalises the interrupted transfer, reprobe and let the probe-time detection re-classify the switch, so a valid image a last-moment interruption left bootable is picked up as running firmware; rescue_drain() no longer inspects or reports the outcome (its stale kernel-doc claiming a "return 1" case is gone) - poll the drain status register with read_poll_timeout() as well, which evaluates the condition once more after the deadline, matching the poll fix in the previous patch - treat the flashless-download loop (STAT 0xc33c) as an unsupported configuration and fail probe with -ENODEV, rather than advertising it as flashable when the console flash path cannot drive it v5: - detect the switch state from the value MCUboot publishes in the SB PDI STAT register (loader ready, wedged download, or running firmware), confirming a live console loader with a register-read challenge, instead of trusting a bare SMDIO scratch write - fail probe with -ENODEV over SB PDI when the switch does not respond at all -- absent, unpowered, or misdescribed in the device tree -- instead of letting the clause-45 API flood the log with CRC errors - drain a wedged interrupted download back to a clean ready state from a background work item so the multi-minute recovery never holds the devlink instance lock, and refuse devlink dev info and flash until it is ready - report the null firmware version as the stored version too, matching the running/stored reporting of the previous patch - do not report asic.id/asic.rev in rescue mode as the CHIP ID registers are unreadable without firmware; recovery tools match on the driver name and the "0.0.0" version instead (follows the numeric asic.id change in the previous patch) - move the devlink documentation into its own patch v4: - log a distinct diagnostic when rescue mode detection fails on an SMDIO bus error instead of silently treating it as "not in rescue mode" - clear the rescue_mode flag under the MDIO bus lock, following the flag write locking in the previous patch v3: - report the canonical null version "0.0.0" instead of "mcuboot-rescue" so that version-comparing update tools like fwupd offer any available release as an upgrade for recovery - check the rescue_mode flag under the MDIO bus lock, following the block_host/skip_teardown change in the previous patch v2: new patch, allowing recovery from a failed or interrupted update without having to use a special recovery OS image (Andrew Lunn) --- drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 491 +++++++++++++++++++- drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 3 + drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 + drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 9 +- drivers/net/dsa/mxl862xx/mxl862xx.c | 111 ++++- drivers/net/dsa/mxl862xx/mxl862xx.h | 21 + 6 files changed, 613 insertions(+), 30 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl86= 2xx/mxl862xx-fw.c index 7a506d110c8d..0761132120b1 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c @@ -29,9 +29,11 @@ * * STAT magics: * READY 0xc55c loader idle in the console loop (this driver) + * DL_RDY 0xc33c loader idle in the flashless loop * START 0xf48f host -> begin download session * ACK 0xf490 loader -> START acknowledged (START + 1) * END 0x3cc3 host -> finalise now (optional, see below) + * RDREG 0xe2c0 host -> register-read command (| index), see below * * Console flash path (STAT=3D0xc55c) - mxl862xx_flash_firmware(): * @@ -71,7 +73,40 @@ * - never send a slice/chunk count larger than what is outstanding; * - a STAT write is a command only once the loader has left the loop; * - the loader leaves the count in STAT while it programs the chunk, = so - * a lingering count does not distinguish "busy" from "verdict". + * a lingering count does not distinguish "busy" from "verdict"; + * - interrupted-download recovery feeds 1 byte at a time (see below). + * + * Interrupted-flash recovery (mxl862xx_rescue_drain): + * A host that dies mid-payload leaves the loader in the receive loop ho= lding + * STAT=3D0. Feed single 1-byte chunks (one DATA word + STAT=3D1) until = r_remain + * reaches 0; the loader then verifies the (now corrupt) image, publishe= s its + * verdict and comes back to READY by itself. END is never sent here: wh= ile + * r_remain is non-zero it would be consumed as a 15555-byte count, and a + * lingering STAT=3D1 cannot be told from a chunk still being programmed. + * + * Register-read challenge (non-destructive liveness proof): + * DATA :=3D 0x7c23 (marker); STAT :=3D 0xe2c0|idx + * -> loader returns a runtime word in DATA and re-arms STAT=3D0xc55c. + * The reply source is loader BSS, not a chip id; used only to prove a l= ive + * 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 n= ot + * use it. + * + * Rescue lifecycle (devlink): probe runs mxl862xx_rescue_mode_detect(); a + * wedged loader is drained back to READY by a background self-heal + * (rescue_heal_work), so the long recovery never holds the devlink lock. + * devlink dev info exposes the fw version (the "flashable" signal) only o= nce at + * READY; flash_update returns -EBUSY until then, and reprobes to WSP firm= ware + * 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 MCUboo= t. + * Rescue identity is by SB PDI behaviour only (mxl862xx_rescue_mode_d= etect). + * - The SMDIO PHY address comes from the device tree; the 0xe1xx regist= er + * offsets are the OTP reset defaults and the only layout supported he= re. */ =20 #include @@ -104,8 +139,15 @@ =20 /* 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 RDR= EG */ + +/* 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 =20 /* Image verification verdict published in STAT once the receive loop ends= */ #define MXL862XX_SB_PDI_VERIFY_OK 0 @@ -124,6 +166,13 @@ #define MXL862XX_FW_WRITE_TIMEOUT_MS 60000 #define MXL862XX_FW_REBOOT_DELAY_MS 5000 #define MXL862XX_FW_REPROBE_DELAY_MS 500 +/* One loader mailbox step: program a 1-byte chunk or service a command */ +#define MXL862XX_SB_PDI_STEP_MS 2000 +/* Covers the loader's END wait, verification and the reset into READY */ +#define MXL862XX_SB_PDI_VERIFY_MS 15000 +/* STAT poll intervals: a mailbox step is quick, an erase is not */ +#define MXL862XX_SB_PDI_POLL_US 50 +#define MXL862XX_SB_PDI_SLOW_POLL_US 10000 =20 static int mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv) { @@ -192,7 +241,8 @@ static int mxl862xx_sb_pdi_flush_last(struct mxl862xx_p= riv *priv, =20 ret =3D read_poll_timeout(mxl862xx_smdio_read, val, val < 0 || (u16)val !=3D (u16)data_written, - 10000, MXL862XX_FW_WRITE_TIMEOUT_MS * 1000, + 10000, (MXL862XX_FW_WRITE_TIMEOUT_MS + + MXL862XX_SB_PDI_VERIFY_MS) * 1000, false, priv, MXL862XX_SB_PDI_STAT); if (val < 0) return val; @@ -218,6 +268,379 @@ static void mxl862xx_flash_notify(struct devlink *dl,= const char *status, devlink_flash_update_status_notify(dl, status, NULL, done, total); } =20 +/* Byte-count of each chunk fed to the loader during drain. It MUST be 1: = the + * loader only lets us observe "counter =3D=3D 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). Steppi= ng by + * 1 walks the counter through every value and is guaranteed to land on ze= ro + * 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 f= or + * odd-sized images (see Step 6). + */ +#define MXL862XX_DRAIN_CHUNK_BYTES 1 + +/* Log the drain's progress every so many bytes; it can run for a long tim= e */ +#define MXL862XX_DRAIN_LOG_BYTES (128 * 1024) + +/* Wait for the loader to ask for the next chunk (STAT 0) or to come back = to its + * command loop (STAT READY), and return the STAT value either way, or + * -ECANCELED once teardown asks the caller to stop. On timeout the value = is + * whatever STAT still holds, which carries no further information: the + * loader keeps the count we wrote visible while it programs the chunk, an= d that + * is the same value it publishes as the "image rejected" verdict once the + * counter reaches zero. + * + * STAT 0 is unambiguous here even though it is also the "image verified" + * verdict: by the r_remain =3D=3D 0 rule the loader leaves the receive lo= op the + * moment the counter reaches zero, so it is never both inside the loop as= king + * for a chunk and publishing a verdict. Once it has left, the next STAT w= rite + * is a command rather than a count, so feeding one more chunk after a ver= dict + * cannot underflow anything either. + */ +static int mxl862xx_sb_pdi_poll_drain(struct mxl862xx_priv *priv, + unsigned long sleep_us, + unsigned long timeout_ms) +{ + int val; + + read_poll_timeout(mxl862xx_smdio_read, val, + val < 0 || (u16)val =3D=3D MXL862XX_SB_PDI_READY || + (u16)val =3D=3D 0 || + test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags), + sleep_us, timeout_ms * 1000, false, + priv, MXL862XX_SB_PDI_STAT); + if (val < 0) + return val; + if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) + return -ECANCELED; + return (u16)val; +} + +/* The loader is not asking for a chunk: it may still be programming the l= ast + * one, or the counter has reached zero and it is verifying the image and + * resetting into READY. Before the first chunk it may also still be erasi= ng + * for the session that died, which no verify window covers. Wait that out= -- + * STAT cannot tell the cases apart, and guessing would mean writing END i= nto a + * live receive loop. + * + * Return: 0 once the loader has left the loop, -EAGAIN if it asks for ano= ther + * chunk after all, -EIO for a loader still holding the count when the win= dow + * expires, -ECANCELED on teardown, or an SMDIO bus error. + */ +static int mxl862xx_rescue_drain_finish(struct mxl862xx_priv *priv, u32 ch= unk) +{ + struct device *dev =3D &priv->mdiodev->dev; + int stat; + + if (chunk) + stat =3D mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US, + MXL862XX_SB_PDI_VERIFY_MS); + else + stat =3D mxl862xx_sb_pdi_poll_drain(priv, + MXL862XX_SB_PDI_SLOW_POLL_US, + MXL862XX_FW_ERASE_TIMEOUT_MS); + if (stat < 0) + return stat; + if (stat =3D=3D MXL862XX_SB_PDI_READY) + return 0; + if (stat =3D=3D MXL862XX_SB_PDI_VERIFY_BAD) { + dev_err(dev, + "flash: loader stuck after %u chunks, power cycle it\n", + chunk); + return -EIO; + } + if (stat) { + if (!chunk) { + dev_err(dev, + "flash: loader still busy after the erase window\n"); + return -EIO; + } + /* A firmware is answering, not the loader: an image survived + * in flash and booted. + */ + dev_info(dev, "flash: firmware booted while draining\n"); + return 0; + } + + return -EAGAIN; +} + +/* Walk the loader's receive counter to zero (see the header): the image s= ize + * died with the host, and a chunk larger than what is outstanding underfl= ows + * the counter, so only single bytes are safe. Tens of minutes for a multi= -MiB + * remainder. Returns 0 once the loader has left the receive loop, <0 on e= rror; + * a counter an earlier oversized chunk underflowed needs a power cycle. + */ +static int mxl862xx_rescue_drain(struct mxl862xx_priv *priv) +{ + /* Bound: twice the loader's 16 MiB image cap, one byte per chunk. */ + u32 max_chunks =3D 2u * (16u << 20) / MXL862XX_DRAIN_CHUNK_BYTES; + struct device *dev =3D &priv->mdiodev->dev; + u32 chunk =3D 0; + int ret, stat; + + while (chunk < max_chunks) { + /* Teardown can interrupt this long drain. */ + if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) + return -ECANCELED; + + stat =3D mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US, + MXL862XX_SB_PDI_STEP_MS); + if (stat < 0) + return stat; + if (stat =3D=3D MXL862XX_SB_PDI_READY) + return 0; + + if (stat) { + ret =3D mxl862xx_rescue_drain_finish(priv, chunk); + if (ret !=3D -EAGAIN) + return ret; + } + + /* Feed one zero byte; reset cleared the write latch. */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_WR); + if (ret < 0) + return ret; + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000); + if (ret < 0) + return ret; + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_DRAIN_CHUNK_BYTES); + if (ret < 0) + return ret; + chunk++; + if (!(chunk % MXL862XX_DRAIN_LOG_BYTES)) + dev_info(dev, "flash: drained %u KiB so far\n", + chunk / 1024); + cond_resched(); + } + + dev_err(dev, + "flash: interrupted download did not drain after %u chunks\n", + chunk); + + return -ETIMEDOUT; +} + +/* Background self-heal: drain a wedged download off the devlink flash pat= h, so + * the long recovery never holds the devlink lock. Scheduled from probe; + * reprobes on success so the probe-time detection re-classifies the switc= h. + */ +void mxl862xx_rescue_heal_work_fn(struct work_struct *work) +{ + struct mxl862xx_priv *priv =3D + container_of(work, struct mxl862xx_priv, rescue_heal_work); + struct device *dev =3D &priv->mdiodev->dev; + int ret; + + ret =3D mxl862xx_rescue_drain(priv); + if (ret =3D=3D -ECANCELED) + return; + if (ret) { + /* Nothing retries this, so say so: rescue_ready stays clear + * and devlink dev flash reports why it refuses. + */ + dev_err(dev, "flash: download recovery failed: %pe\n", + ERR_PTR(ret)); + WRITE_ONCE(priv->rescue_failed, true); + return; + } + + /* The interrupted transfer is finalised; reprobe so the probe-time + * detection brings the driver up -- flashable in rescue mode if the + * loader is at READY, or normally if a valid image booted. The core + * skips the re-probe on its own if the device is unbound first; the + * flag test only avoids scheduling one certain to be skipped. A + * failed hand-off leaves nothing to reclassify the switch, so mark + * recovery failed rather than promise a retry that cannot succeed. + */ + if (test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) + return; + + if (device_schedule_reprobe(dev, MXL862XX_FW_REPROBE_DELAY_MS)) + WRITE_ONCE(priv->rescue_failed, true); +} + +/* Detect MCUboot rescue mode over clause-22 SMDIO alone, so the caller ca= n rule + * the loader out before any C45 API request (which only runs into its tim= eouts + * when no WSP firmware answers). A scratch write to ADDR/DATA must latch = or the + * chip is absent (-ENODEV); the mailbox is reset first, or a transfer + * interrupted with CTRL=3DWR would take that write as a payload word inst= ead of + * latching it. STAT then classifies the state, poked destructively only w= hen 0, + * the one value a running firmware never holds: + * + * - 0xc33c: flashless loop; recognised but not supported here. + * - 0xc55c: console loop, if the register-read challenge is serviced. + * - 0xf48f/0xf490: a download handshake nobody can finish; rescue, but o= nly + * a power cycle gets the loader out of it. + * - other non-zero: running firmware, left unpoked. With @settle the loa= der + * gets one step to publish 0 or READY first, and a count outliving tha= t is + * a busy loader, for a caller which has ruled a running firmware out. + * - 0: wedged receive loop; the 1-byte slice-advance then says whether it + * still needs draining or has just finished. + * + * The scratch write reaches a running firmware too, but lands in mailbox + * registers it does not read, so it is inert there. + * + * Return: MXL862XX_IN_RESCUE, MXL862XX_NOT_RESCUE, -ENODEV when the scrat= ch + * write does not latch, which is a switch that does not answer at all or = one + * whose SB PDI window is not at the offsets above, -EOPNOTSUPP for the + * flashless loop, -ENXIO for a READY loader whose mailbox fails the chall= enge, + * -ECANCELED when teardown interrupts a poll, or an SMDIO bus error. + */ +int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv, bool settle) +{ + int stat, dat, ret, rb, a, d; + + /* rescue_ready gates flashing; a wedged loader needs the drain first. */ + WRITE_ONCE(priv->rescue_ready, false); + + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + + /* Presence: a live chip latches the scratch write, an absent one floats.= */ + a =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR, + MXL862XX_SB_PDI_PROBE_A); + if (a < 0) + return a; + d =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, + MXL862XX_SB_PDI_PROBE_D); + if (d < 0) + return d; + a =3D mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_ADDR); + if (a < 0) + return a; + d =3D mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA); + if (d < 0) + return d; + if ((u16)a !=3D MXL862XX_SB_PDI_PROBE_A || + (u16)d !=3D MXL862XX_SB_PDI_PROBE_D) + return -ENODEV; + + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + + stat =3D 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 =3D=3D 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 =3D=3D MXL862XX_SB_PDI_READY) { + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, + MXL862XX_SB_PDI_RDREG_MARK); + if (ret < 0) + return ret; + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_SB_PDI_RDREG); + if (ret < 0) + return ret; + rb =3D mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY, + MXL862XX_SB_PDI_STEP_MS); + dat =3D mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_DATA); + ret =3D mxl862xx_sb_pdi_reset(priv); + /* Never re-arming READY fails the challenge like any other + * unserviced command; report it as such rather than as a bus + * timeout the bus never saw. + */ + if (rb =3D=3D -ETIMEDOUT) + rb =3D -ENXIO; + if (rb < 0) + return rb; + if (dat < 0) + return dat; + if (ret < 0) + return ret; + if ((u16)dat !=3D MXL862XX_SB_PDI_RDREG_MARK) { + WRITE_ONCE(priv->rescue_ready, true); + return MXL862XX_IN_RESCUE; + } + /* READY but the marker is untouched, so nothing is servicing the + * mailbox. A firmware publishing 0xc55c as its status word looks + * exactly like this, and flashing one would be far worse than + * refusing to bind, so treat it as unusable. + */ + return -ENXIO; + } + + /* The download handshake lives in STAT too and outlives a mailbox + * reset, so an aborted transfer leaves the loader waiting for a + * header no later session can supply: only a power cycle clears it. + */ + if ((u16)stat =3D=3D MXL862XX_SB_PDI_START || + (u16)stat =3D=3D MXL862XX_SB_PDI_START + 1) { + WRITE_ONCE(priv->rescue_failed, true); + return MXL862XX_IN_RESCUE; + } + + /* Any other non-zero value is a running firmware, not a loader -- but + * the loader also holds the count of a chunk it is programming or + * erasing for, so let a caller that has ruled the firmware out wait a + * step for the next state; a count outliving that is that busy loader. + */ + if (stat) { + if (!settle) + return MXL862XX_NOT_RESCUE; + + stat =3D mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US, + MXL862XX_SB_PDI_STEP_MS); + if (stat < 0) + return stat; + if (stat =3D=3D MXL862XX_SB_PDI_READY) { + WRITE_ONCE(priv->rescue_ready, true); + return MXL862XX_IN_RESCUE; + } + if (stat) + return MXL862XX_IN_RESCUE; + } + + /* STAT =3D=3D 0: a wedged receive loop takes a 1-byte slice-advance (feed + * one DATA word first, like a drain chunk) and asks for the next chunk + * by publishing 0 again. Had that byte been the last one outstanding, + * the loader leaves the loop instead and returns to READY, having + * consumed the advance -- proof enough of a live mailbox to skip the + * challenge. Anything else means it is still working on it. All three + * are rescue, so this never fails probe; only the drain does. + */ + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL, + MXL862XX_SB_PDI_CTRL_WR); + if (ret < 0) + return ret; + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, 0x0000); + if (ret < 0) + return ret; + ret =3D mxl862xx_sb_pdi_reset(priv); + if (ret < 0) + return ret; + ret =3D mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, + MXL862XX_DRAIN_CHUNK_BYTES); + if (ret < 0) + return ret; + + rb =3D mxl862xx_sb_pdi_poll_drain(priv, MXL862XX_SB_PDI_POLL_US, + MXL862XX_SB_PDI_STEP_MS); + if (rb < 0) + return rb; + if (rb =3D=3D MXL862XX_SB_PDI_READY) + WRITE_ONCE(priv->rescue_ready, true); + + return MXL862XX_IN_RESCUE; +} + /* MCUboot firmware image header */ struct mxl862xx_fw_hdr { __le32 image_type; @@ -293,13 +716,15 @@ static int mxl862xx_flash_firmware(struct mxl862xx_pr= iv *priv, int ret, i; =20 /* Step 1: reboot the firmware into MCUboot rescue mode */ - ret =3D 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; + if (!READ_ONCE(priv->rescue_mode)) { + ret =3D 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; + } } =20 /* Step 2: wait for bootloader ready */ @@ -486,6 +911,25 @@ int mxl862xx_devlink_info_get(struct dsa_switch *ds, char buf[16]; int ret; =20 + /* 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 (READ_ONCE(priv->rescue_mode)) { + if (!READ_ONCE(priv->rescue_ready)) + return 0; + + snprintf(buf, sizeof(buf), "%u.%u.%u", + priv->fw_version.major, priv->fw_version.minor, + priv->fw_version.revision); + ret =3D 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 @@ -564,9 +1008,27 @@ int mxl862xx_devlink_flash_update(struct dsa_switch *= ds, return ret; } =20 - dev_info(ds->dev, "flash: running firmware %u.%u.%u\n", - priv->fw_version.major, priv->fw_version.minor, - priv->fw_version.revision); + /* Refuse to flash while the background self-heal is still draining, and + * for good once it has given up on the loader. + */ + if (READ_ONCE(priv->rescue_failed)) { + NL_SET_ERR_MSG_MOD(extack, "download recovery failed"); + return -EIO; + } + + if (READ_ONCE(priv->rescue_mode) && !READ_ONCE(priv->rescue_ready)) { + NL_SET_ERR_MSG_MOD(extack, + "switch is recovering an interrupted download"); + return -EBUSY; + } + + if (READ_ONCE(priv->rescue_mode)) + dev_info(ds->dev, + "flash: flashing switch via MCUboot rescue mode\n"); + else + dev_info(ds->dev, "flash: running firmware %u.%u.%u\n", + priv->fw_version.major, priv->fw_version.minor, + priv->fw_version.revision); =20 /* Close ports while the firmware is still alive so the DSA core's * MDB/FDB tracking is drained, and detach user ports so userspace @@ -613,6 +1075,7 @@ int mxl862xx_devlink_flash_update(struct dsa_switch *d= s, * readiness poll below read the freshly booted firmware. */ priv->flash_owner =3D current; + WRITE_ONCE(priv->rescue_mode, false); mutex_unlock(&priv->mdiodev->bus->mdio_lock); =20 /* Refresh the cached versions so the flash update only @@ -629,11 +1092,13 @@ int mxl862xx_devlink_flash_update(struct dsa_switch = *ds, 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. + * the pre-flash version until the reprobe re-reads the truth, + * and with it the loader's clean READY state. */ memset(&priv->fw_version, 0, sizeof(priv->fw_version)); priv->asic_id =3D 0; priv->asic_rev =3D 0; + WRITE_ONCE(priv->rescue_ready, false); } =20 mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl86= 2xx/mxl862xx-fw.h index 15ed3a46bcfe..02e5a627e947 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h @@ -6,7 +6,10 @@ #include =20 struct mxl862xx_priv; +struct work_struct; =20 +int mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv, bool settle); +void mxl862xx_rescue_heal_work_fn(struct work_struct *work); int mxl862xx_devlink_info_get(struct dsa_switch *ds, struct devlink_info_req *req, struct netlink_ext_ack *extack); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl= 862xx/mxl862xx-host.c index 4b3956a518cf..694c22d2dd09 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c @@ -17,6 +17,7 @@ #include #include "mxl862xx.h" #include "mxl862xx-cmd.h" +#include "mxl862xx-fw.h" #include "mxl862xx-host.h" =20 #define CTRL_BUSY_MASK BIT(15) @@ -347,6 +348,11 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 = cmd, void *_data, goto out; } =20 + if (priv->rescue_mode) { + ret =3D -ENODEV; + goto out; + } + /* During the post-flash readiness poll block_host stays set, but the * flash path's own firmware version reads must reach the new image; * host writes stay blocked so stale resource IDs cannot corrupt it. @@ -558,9 +564,11 @@ int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u= 32 addr, u16 val) 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); } =20 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-phylink.c b/drivers/net/dsa/= mxl862xx/mxl862xx-phylink.c index b689652aa9b9..df77bbf108d5 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c @@ -47,7 +47,12 @@ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, in= t port, fallthrough; case 10 ... 12: case 14 ... 16: - if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) + /* Rescue mode has no firmware version, so bypass the gate and + * advertise the full set; a CPU port on a quad sub-interface + * would otherwise get an empty mask and fail phylink_create(). + */ + if (!READ_ONCE(priv->rescue_mode) && + !MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) break; __set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces); __set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces); @@ -406,6 +411,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *= config, =20 switch (port) { case 9 ... 16: + if (READ_ONCE(priv->rescue_mode)) + return NULL; if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) { dev_warn_once(dp->ds->dev, "SerDes PCS unsupported on old firmware.\n"); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx= /mxl862xx.c index 33a7cdb8edd3..911114579f10 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -667,27 +667,85 @@ static void mxl862xx_free_bridge(struct dsa_switch *d= s, priv->bridges[bridge->num] =3D 0; } =20 +static void mxl862xx_setup_rescue(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv =3D ds->priv; + + if (priv->rescue_ready) { + dev_warn(ds->dev, + "switch in MCUboot rescue mode, use devlink to flash new firmware\n"); + return; + } + + if (priv->rescue_failed) { + dev_warn(ds->dev, + "switch in MCUboot, download recovery gave up; see Documentation/netwo= rking/devlink/mxl862xx.rst\n"); + return; + } + + /* Drain the wedged download in the background so it never holds the + * devlink lock; info and flash become available once ready. + */ + dev_warn(ds->dev, + "switch in MCUboot with an interrupted download, recovering in backgrou= nd\n"); + queue_work(system_long_wq, &priv->rescue_heal_work); +} + static int mxl862xx_setup(struct dsa_switch *ds) { struct mxl862xx_priv *priv =3D ds->priv; int n_user_ports =3D 0, max_vlans; int ingress_finals, vid_rules; struct dsa_port *dp; - int ret, i; + int ret, i, rescue; =20 - ret =3D mxl862xx_reset(priv); - if (ret) - return ret; + /* Detect the loader over SB PDI first: it needs no firmware, unlike the + * C45 API (mxl862xx_reset/wait_ready), which spends its whole 10 s + * window on a mailbox nobody answers. Touch C45 only once rescue is + * ruled out. + */ + rescue =3D mxl862xx_rescue_mode_detect(priv, false); + if (rescue < 0) { + dev_err(ds->dev, "switch state detection failed: %pe\n", + ERR_PTR(rescue)); + return rescue; + } =20 - ret =3D mxl862xx_wait_ready(ds); - if (ret) - return ret; + if (rescue =3D=3D MXL862XX_NOT_RESCUE) { + ret =3D mxl862xx_reset(priv); + if (ret) + return ret; + + ret =3D mxl862xx_wait_ready(ds); + if (ret) { + /* the reset may only now have triggered rescue mode */ + rescue =3D mxl862xx_rescue_mode_detect(priv, true); + if (rescue < 0) { + dev_err(ds->dev, + "switch not responding after reset: %pe\n", + ERR_PTR(rescue)); + return rescue; + } + if (rescue =3D=3D MXL862XX_NOT_RESCUE) + return ret; + } + } + + priv->rescue_mode =3D rescue; =20 + /* Software-only SerDes state, needed before anything can reach phylink, + * including a rescue-mode flash clearing rescue_mode ahead of reprobe. + */ mutex_init(&priv->serdes_lock); for (i =3D 0; i < ARRAY_SIZE(priv->serdes_ports); i++) mxl862xx_setup_pcs(priv, &priv->serdes_ports[i], i + MXL862XX_FIRST_SERDES_PORT); =20 + if (priv->rescue_mode) { + mxl862xx_setup_rescue(ds); + return 0; + } + /* Calculate Extended VLAN block sizes. * With VLAN Filter handling VID membership checks: * Ingress: only final catchall rules (PVID insertion, 802.1Q @@ -778,11 +836,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds,= int port, bool enable) static int mxl862xx_port_enable(struct dsa_switch *ds, int port, struct phy_device *phydev) { + struct mxl862xx_priv *priv =3D ds->priv; + + if (READ_ONCE(priv->rescue_mode)) + return 0; + return mxl862xx_port_state(ds, port, true); } =20 static void mxl862xx_port_disable(struct dsa_switch *ds, int port) { + struct mxl862xx_priv *priv =3D ds->priv; + + if (READ_ONCE(priv->rescue_mode)) + return; + if (mxl862xx_port_state(ds, port, false)) dev_err(ds->dev, "failed to disable port %d\n", port); } @@ -1400,6 +1468,17 @@ static int mxl862xx_port_setup(struct dsa_switch *ds= , int port) bool is_cpu_port =3D dsa_port_is_cpu(dp); int ret; =20 + if (dsa_port_is_dsa(dp)) { + dev_err(ds->dev, "port %d: DSA links not supported\n", port); + return -EOPNOTSUPP; + } + + /* DSA reinits failed user ports as unused; shared ports must + * succeed for the tree to register. + */ + if (READ_ONCE(priv->rescue_mode)) + return dsa_port_is_user(dp) ? -ENODEV : 0; + ret =3D mxl862xx_port_state(ds, port, false); if (ret) return ret; @@ -1409,11 +1488,6 @@ static int mxl862xx_port_setup(struct dsa_switch *ds= , int port) if (dsa_port_is_unused(dp)) return 0; =20 - if (dsa_port_is_dsa(dp)) { - dev_err(ds->dev, "port %d: DSA links not supported\n", port); - return -EOPNOTSUPP; - } - ret =3D mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port); if (ret) return ret; @@ -1603,7 +1677,8 @@ static int mxl862xx_port_mdb_add(struct dsa_switch *d= s, int port, * rebuilds the configuration. See mxl862xx_port_mdb_del(). */ if ((ret =3D=3D -EBUSY && priv->block_host) || - (ret =3D=3D -ENODEV && priv->skip_teardown)) + (ret =3D=3D -ENODEV && + (priv->skip_teardown || READ_ONCE(priv->rescue_mode)))) return 0; if (ret) return ret; @@ -1642,12 +1717,13 @@ static int mxl862xx_port_mdb_del(struct dsa_switch = *ds, int port, ether_addr_copy(qparam.mac, mdb->addr); =20 ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam); - /* A flash blocks the API (-EBUSY) and its teardown drops the MAC - * table (-ENODEV); a delete then has nothing to do. Outside these, + /* A flash blocks the API (-EBUSY); its teardown or MCUboot drops the + * MAC table (-ENODEV); a delete then has nothing to do. Outside these, * both are bus errors and must be reported. */ if ((ret =3D=3D -EBUSY && priv->block_host) || - (ret =3D=3D -ENODEV && priv->skip_teardown)) + (ret =3D=3D -ENODEV && + (priv->skip_teardown || READ_ONCE(priv->rescue_mode)))) return 0; if (ret) return ret; @@ -1705,6 +1781,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_sw= itch *ds, int port, struct mxl862xx_priv *priv =3D ds->priv; int ret; =20 + if (READ_ONCE(priv->rescue_mode)) + return; + switch (state) { case BR_STATE_DISABLED: param.port_state =3D cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx= /mxl862xx.h index 054d0d35d3a5..70cab20a216a 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -4,7 +4,9 @@ #define __MXL862XX_H =20 #include +#include #include +#include #include #include =20 @@ -14,6 +16,10 @@ struct mxl862xx_priv; #define MXL862XX_FIRST_SERDES_PORT 9 #define MXL862XX_SERDES_SLOTS 4 =20 +/* 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 @@ -336,6 +342,17 @@ struct mxl862xx_fw_version { * @shutting_down: set under the devlink instance lock once ->shutdow= n() * or .remove() has begun, so no flash starts while t= he * switch is going away + * @rescue_mode: switch is in MCUboot; firmware API commands fail f= ast, + * only clause-22 SMDIO works. Set from setup() befor= e the + * switch is registered and cleared with WRITE_ONCE()= under + * the MDIO bus lock for the benefit of mxl862xx_api_= wrap(); + * readers outside that lock use READ_ONCE(). + * @rescue_ready: (rescue_mode) loader is at a clean READY and will = accept + * a flash; false while rescue_heal_work is draining + * @rescue_failed: (rescue_mode) the loader cannot accept a flash; the + * remedy depends on the cause and is described in + * Documentation/networking/devlink/mxl862xx.rst + * @rescue_heal_work: background self-heal draining a wedged download to= READY * @stats_work: periodic work item that polls RMON hardware counte= rs * and accumulates them into 64-bit per-port stats */ @@ -343,6 +360,7 @@ 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; @@ -360,6 +378,9 @@ struct mxl862xx_priv { bool block_host; bool skip_teardown; bool shutting_down; + bool rescue_mode; + bool rescue_ready; + bool rescue_failed; struct delayed_work stats_work; }; =20 --=20 2.55.0 From nobody Thu Sep 24 14:25:09 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED97737A840; Wed, 23 Sep 2026 02:36:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790131004; cv=none; b=DAD1cWJEnZpl6ZgPhlZxaLZYoH++RO8g+6xCS9DT120bDFUkymoHewklJ+r7a+Q4sc3zX48aZGCQCLMJ65kzFukrWiwA3Uld9NHSw3CTNgoyjLUEhp4MRIpgW5M11sj+ZUbYBXrBOOAOB9gBt66wiiUFhHeLM3morF+Yn/LbBiU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790131004; c=relaxed/simple; bh=8HSKNZVjdiIi+cplSdO+PtQgeeN5fbHnotSMEBFEFVM=; h=Date:From:To:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=oGk0OInd8NtIL4SYgAd/i/5IvN5d8iWQCL9CxGkthMJVdC9k8CWexRw53dRRHkYxrgKxnO2jFkLqEkyJ1d1HEwjFFbenVDE52CepajGnYilAyq5qejXMQS5i6z3b8O7zLSAnOGFugQMFXGAupyJZVQCUAB/E9UfqvHgnYwpLC4I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256:X25519MLKEM768) (Exim 4.100) (envelope-from ) id 1x9Cqc-0000000032G-25JD; Wed, 23 Sep 2026 02:36:38 +0000 Date: Wed, 23 Sep 2026 03:36:35 +0100 From: Daniel Golle To: Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Randy Dunlap , Daniel Golle , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich , Andrew Lunn , Vladimir Oltean , Russell King , netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev Subject: [PATCH net-next v17 6/6] net: dsa: mxl862xx: document devlink flash and info support Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Describe the devlink info versions and the flash update behaviour, including the MCUboot rescue mode recovery, in a dedicated file under Documentation/networking/devlink/ and link it from the index. Add the new file to the driver's MAINTAINERS entry. Assisted-by: LLM Reviewed-by: Andrew Lunn Signed-off-by: Daniel Golle --- v17: no changes v16: - document that a reboot waits for a running flash and that one requested afterwards is refused, and that a bus error ends the download recovery for good - title-case the "Flash Update" heading, as the other devlink driver documents do - say that the ports come back down from a flash, that a reprobe which cannot be scheduled needs a rebind, and which of the two recovery failures needs a power cycle and which a rebind (found by Sashiko AI review) v15: - asic.rev is read from the CHIP ID registers as well, not from one register word shared with asic.id (found by Sashiko AI review) - -EIO says the driver gave up on the recovery, which may need a driver rebind rather than a power cycle, and an interrupted opening handshake is reported the same way (found by Sashiko AI review) v14: no changes v13: no changes v12: no changes v11: no changes v10: document that a switch power cycled on its own needs the driver unbound and rebound before a failed recovery is re-examined v9: no changes, picked up Andrew's v5 Reviewed-by v8: - asic.id and asic.rev are omitted whenever the part number reads zero, not only in MCUboot rescue mode (found by Sashiko AI review) - drop the claim that "0.0.0" marks a switch that never ran firmware; rescue mode always reports it (found by Sashiko AI review) - document devlink dev flash as the signal that says whether a recovery is still running, including the -EIO it returns once the recovery has failed (found by Sashiko AI review) v7: no changes v6: no changes v5: new patch, splitting the devlink documentation out of the flash update and rescue mode recovery patches so each keeps to code (Jakub Kicinski asked for the documentation) --- Documentation/networking/devlink/index.rst | 1 + Documentation/networking/devlink/mxl862xx.rst | 91 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 93 insertions(+) create mode 100644 Documentation/networking/devlink/mxl862xx.rst diff --git a/Documentation/networking/devlink/index.rst b/Documentation/net= working/devlink/index.rst index 1af780c811ee..53d3ef16d13f 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -95,6 +95,7 @@ parameters, info versions, and other features it supports. mlx5 mlxsw mv88e6xxx + mxl862xx netdevsim nfp octeontx2 diff --git a/Documentation/networking/devlink/mxl862xx.rst b/Documentation/= networking/devlink/mxl862xx.rst new file mode 100644 index 000000000000..793f3521f505 --- /dev/null +++ b/Documentation/networking/devlink/mxl862xx.rst @@ -0,0 +1,91 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +mxl862xx devlink support +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +This document describes the devlink features implemented by the +``mxl862xx`` device driver. + +Info versions +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``mxl862xx`` driver reports the following versions + +.. list-table:: devlink info versions implemented + :widths: 5 5 5 85 + + * - Name + - Type + - Example + - Description + * - ``asic.id`` + - fixed + - 8628 + - The chip part number read from the CHIP ID registers. Omitted + when the part number reads as zero, which happens for a switch + sitting in MCUboot rescue mode (the registers need a running + firmware), for an unfused part, and after a failed flash. + * - ``asic.rev`` + - fixed + - 0 + - The chip version, read from the CHIP ID registers as well. Both + values are published behind the same check, so it is omitted + whenever ``asic.id`` is. + * - ``fw`` + - running, stored + - 1.0.70 + - Version of the firmware running on the switch, reported as both + running and stored since the switch boots it from its own flash. + It is omitted while no firmware version is known: after a failed + flash, and in MCUboot rescue mode while an interrupted download + is still being recovered in the background. Once the loader is + ready to accept a new image the version appears as "0.0.0", + which no released firmware reports, so version-comparing tools + offer any available release as an upgrade. Use ``devlink dev + flash`` to tell a recovering switch from a ready one, see below; + a missing version on its own does not say why. + +Flash Update +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``mxl862xx`` driver implements support for ``devlink dev flash``. +The signed firmware image is transferred to the switch over the same +MDIO bus which is also used to manage the switch, then verified and +installed by the MCUboot bootloader running on the switch. All ports +of the switch are closed for the duration of the update and the driver +reprobes the switch after it has rebooted into the new firmware; they +come back registered but down, so userspace brings them up again. A +complete flash and reprobe cycle takes about one minute. In the rare +case that the reprobe cannot be scheduled at all, ``devlink dev +flash`` reports that error and the driver stays bound to a switch it +no longer tracks until it is unbound and rebound. A reboot started +while an update is running waits for the transfer to finish, and an +update requested after the system has begun shutting down is refused +with ``-ENODEV``. + +A switch stuck in MCUboot rescue mode, e.g. after an interrupted +update, is registered without user ports. If the previous download was +interrupted mid-transfer the loader is wedged; the driver drains it +back to a clean ready state in the background, one byte at a time, +which takes tens of minutes for a large image and is reported through +the kernel log as it progresses. During that recovery ``devlink dev +flash`` returns ``-EBUSY`` with an extack message saying so, and +``devlink dev info`` reports no firmware version. Once the loader is +ready the firmware version appears and flashing a firmware image +through the regular update flow recovers the switch. + +If the driver gives up on the recovery, ``devlink dev flash`` returns +``-EIO`` and says so in its extack message. The drain runs once and is +never resumed, so a failed MDIO transaction ends it as well. A loader +that stops answering the drain needs a power cycle; a completed drain +whose reprobe could not be scheduled, and a drain a bus error cut +short, need only a driver rebind. The driver re-examines the switch +when it binds and at no other time, so a power cycle on a board where +the switch can be cycled on its own still has to be followed by an +unbind and rebind for the recovered switch to be recognised. + +A download interrupted during its opening handshake, before the image +header reached the loader, is reported the same way. The loader waits +for a header that no later session can supply, so that state needs a +power cycle. diff --git a/MAINTAINERS b/MAINTAINERS index e3ce77c839b0..db2b36581067 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16248,6 +16248,7 @@ M: Daniel Golle 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 =20 --=20 2.55.0