Update driver to be ready for the upcoming firmware release. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
569 lines
18 KiB
Diff
569 lines
18 KiB
Diff
From 099e70920b16f5da14d34bc00ac58a5ca95c1e33 Mon Sep 17 00:00:00 2001
|
|
From: Daniel Golle <daniel@makrotopia.org>
|
|
Date: Tue, 24 Mar 2026 16:30:31 +0000
|
|
Subject: [PATCH 13/19] net: dsa: mxl862xx: add devlink flash_update and
|
|
info_get
|
|
|
|
Implement runtime firmware upgrade via "devlink dev flash" and version
|
|
reporting via "devlink dev info":
|
|
|
|
devlink dev info mdio_bus/<bus>/<addr>
|
|
devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
|
|
|
|
The driver sends SYS_MISC_FW_UPDATE to enter MCUboot rescue mode,
|
|
transfers the signed image over the SB PDI bulk-transfer protocol
|
|
(clause-22 SMDIO), waits for the switch to reboot, then schedules
|
|
device_reprobe() for a clean remove()+probe() cycle.
|
|
|
|
Before the transfer begins the driver closes all conduit interfaces
|
|
and marks every netdev (user and conduit) not-present via
|
|
netif_device_detach() so that userspace cannot bring ports back up
|
|
during the ~15 minute flash process. Progress is reported through
|
|
devlink status notifications. Once the FW_UPDATE command has been
|
|
sent the switch is in MCUboot mode and normal operation can only be
|
|
restored by a reprobe, so the driver always schedules one regardless
|
|
of transfer outcome.
|
|
|
|
The reprobe work item is dynamically allocated (following the iwlwifi
|
|
pattern) because device_reprobe() triggers remove() which frees the
|
|
devm-managed priv while the work is still executing.
|
|
|
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
|
---
|
|
drivers/net/dsa/mxl862xx/Makefile | 2 +-
|
|
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
|
|
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 434 +++++++++++++++++++++++
|
|
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 15 +
|
|
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 7 +
|
|
drivers/net/dsa/mxl862xx/mxl862xx.c | 3 +
|
|
drivers/net/dsa/mxl862xx/mxl862xx.h | 2 +
|
|
7 files changed, 463 insertions(+), 1 deletion(-)
|
|
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
|
|
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
|
|
|
|
--- a/drivers/net/dsa/mxl862xx/Makefile
|
|
+++ b/drivers/net/dsa/mxl862xx/Makefile
|
|
@@ -1,3 +1,3 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
|
|
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
|
|
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
|
|
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
|
|
@@ -83,6 +83,7 @@
|
|
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
|
|
#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
|
|
|
|
+#define SYS_MISC_FW_UPDATE (SYS_MISC_MAGIC + 0x1)
|
|
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
|
|
|
|
#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
|
|
--- /dev/null
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
|
|
@@ -0,0 +1,434 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+/*
|
|
+ * Firmware flash and devlink support for MaxLinear MxL862xx
|
|
+ *
|
|
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
|
|
+ *
|
|
+ * Usage:
|
|
+ * # Query running firmware version:
|
|
+ * devlink dev info mdio_bus/<bus>/<addr>
|
|
+ *
|
|
+ * # Flash new firmware (all ports are taken down automatically):
|
|
+ * devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
|
|
+ *
|
|
+ * The flash process takes approximately 15 minutes. Progress is
|
|
+ * reported via devlink status notifications. After a successful (or
|
|
+ * failed) flash the driver reprobes the device automatically.
|
|
+ */
|
|
+
|
|
+#include <linux/crc32.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <net/dsa.h>
|
|
+
|
|
+#include "mxl862xx.h"
|
|
+#include "mxl862xx-api.h"
|
|
+#include "mxl862xx-cmd.h"
|
|
+#include "mxl862xx-fw.h"
|
|
+#include "mxl862xx-host.h"
|
|
+
|
|
+/* SB PDI registers (clause-22 SMDIO address space) */
|
|
+#define MXL862XX_SB_PDI_CTRL 0xe100
|
|
+#define MXL862XX_SB_PDI_ADDR 0xe101
|
|
+#define MXL862XX_SB_PDI_DATA 0xe102
|
|
+#define MXL862XX_SB_PDI_STAT 0xe103
|
|
+
|
|
+/* SB PDI CTRL modes */
|
|
+#define MXL862XX_SB_PDI_CTRL_RST 0x00
|
|
+#define MXL862XX_SB_PDI_CTRL_WR 0x02
|
|
+
|
|
+/* SB PDI handshake magic */
|
|
+#define MXL862XX_SB_PDI_READY 0xc55c
|
|
+#define MXL862XX_SB_PDI_START 0xf48f
|
|
+#define MXL862XX_SB_PDI_END 0x3cc3
|
|
+
|
|
+/* Firmware transfer geometry */
|
|
+#define MXL862XX_FW_HDR_SIZE 20
|
|
+#define MXL862XX_FW_BANK_HALF 16384 /* words per half-bank */
|
|
+#define MXL862XX_FW_BANK_SLICE 32760 /* words per full slice */
|
|
+#define MXL862XX_FW_SB1_ADDR 0x7800 /* SB1 word address */
|
|
+
|
|
+/* Timeouts */
|
|
+#define MXL862XX_FW_READY_TIMEOUT_MS 30000
|
|
+#define MXL862XX_FW_ACK_TIMEOUT_MS 5000
|
|
+#define MXL862XX_FW_ERASE_TIMEOUT_MS 300000 /* flash erase is very slow */
|
|
+#define MXL862XX_FW_WRITE_TIMEOUT_MS 120000 /* per-slice program timeout */
|
|
+#define MXL862XX_FW_REBOOT_DELAY_MS 5000
|
|
+
|
|
+static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
|
|
+{
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_RST);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
|
|
+ MXL862XX_SB_PDI_CTRL_RST);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
|
|
+ MXL862XX_SB_PDI_CTRL_RST);
|
|
+}
|
|
+
|
|
+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
|
|
+ unsigned long timeout_ms)
|
|
+{
|
|
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
|
+ int ret;
|
|
+
|
|
+ do {
|
|
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ if ((u16)ret == expected)
|
|
+ return 0;
|
|
+ usleep_range(10000, 11000);
|
|
+ } while (time_before(jiffies, timeout));
|
|
+
|
|
+ return -ETIMEDOUT;
|
|
+}
|
|
+
|
|
+/* Reprobe work -- dynamically allocated so it survives remove().
|
|
+ * device_reprobe() -> remove() frees priv (devm) while work is executing,
|
|
+ * so the work struct must not live in mxl862xx_priv.
|
|
+ */
|
|
+struct mxl862xx_reprobe {
|
|
+ struct device *dev;
|
|
+ struct delayed_work dwork;
|
|
+};
|
|
+
|
|
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
|
|
+{
|
|
+ struct mxl862xx_reprobe *reprobe =
|
|
+ container_of(work, struct mxl862xx_reprobe, dwork.work);
|
|
+
|
|
+ if (device_reprobe(reprobe->dev))
|
|
+ dev_err(reprobe->dev, "reprobe failed\n");
|
|
+ put_device(reprobe->dev);
|
|
+ kfree(reprobe);
|
|
+ module_put(THIS_MODULE);
|
|
+}
|
|
+
|
|
+/* MCUboot firmware image header (20 bytes) */
|
|
+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_firmware(struct mxl862xx_priv *priv,
|
|
+ const struct firmware *fw,
|
|
+ struct devlink *dl)
|
|
+{
|
|
+ const struct mxl862xx_fw_hdr *hdr;
|
|
+ u32 word_idx = 0, data_written = 0, idx = 0;
|
|
+ unsigned long next_notify = 0;
|
|
+ const u8 *payload;
|
|
+ u32 payload_size;
|
|
+ u16 word, fdata;
|
|
+ int ret, i;
|
|
+ u32 crc;
|
|
+
|
|
+ if (fw->size < MXL862XX_FW_HDR_SIZE)
|
|
+ return -EINVAL;
|
|
+
|
|
+ hdr = (const struct mxl862xx_fw_hdr *)fw->data;
|
|
+ payload = fw->data + MXL862XX_FW_HDR_SIZE;
|
|
+ payload_size = le32_to_cpu(hdr->image_size_1) +
|
|
+ le32_to_cpu(hdr->image_size_2);
|
|
+
|
|
+ if (payload_size > fw->size - MXL862XX_FW_HDR_SIZE) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: firmware file too small for declared size\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Validate CRC-32 of both image slots before touching hardware */
|
|
+ if (le32_to_cpu(hdr->image_size_1)) {
|
|
+ crc = ~crc32_le(~0U, payload,
|
|
+ le32_to_cpu(hdr->image_size_1));
|
|
+ if (crc != le32_to_cpu(hdr->image_checksum_1)) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
|
|
+ crc, le32_to_cpu(hdr->image_checksum_1));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (le32_to_cpu(hdr->image_size_2)) {
|
|
+ crc = ~crc32_le(~0U,
|
|
+ payload + le32_to_cpu(hdr->image_size_1),
|
|
+ le32_to_cpu(hdr->image_size_2));
|
|
+ if (crc != le32_to_cpu(hdr->image_checksum_2)) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
|
|
+ crc, le32_to_cpu(hdr->image_checksum_2));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Step 1: Tell firmware to enter MCUboot rescue mode.
|
|
+ * The FW_UPDATE command takes no payload (size 0).
|
|
+ */
|
|
+ ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
|
|
+ false, false);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: FW_UPDATE command failed: %pe\n",
|
|
+ ERR_PTR(ret));
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* From this point on, the switch is in MCUboot rescue mode.
|
|
+ * Any failure must go through the end_magic label to tell
|
|
+ * MCUboot to reboot rather than leaving it stuck waiting.
|
|
+ */
|
|
+
|
|
+ /* Step 2: Reset PDI and wait for bootloader ready */
|
|
+ devlink_flash_update_status_notify(dl, "Waiting for bootloader",
|
|
+ NULL, 0, 0);
|
|
+ mxl862xx_sb_pdi_reset(priv);
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
|
|
+ MXL862XX_FW_READY_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: bootloader not ready: %pe\n", ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+
|
|
+ /* Step 3: Start handshake */
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
|
|
+ MXL862XX_SB_PDI_START);
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
|
|
+ MXL862XX_FW_ACK_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: start handshake failed: %pe\n", ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+
|
|
+ /* Step 4: Transfer 20-byte header using auto-increment write mode */
|
|
+ devlink_flash_update_status_notify(dl, "Erasing flash", NULL, 0, 0);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_WR);
|
|
+ for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
|
|
+ word = fw->data[i * 2] |
|
|
+ ((u16)fw->data[i * 2 + 1] << 8);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
|
|
+ }
|
|
+ mxl862xx_sb_pdi_reset(priv);
|
|
+
|
|
+ /* Write header byte count to STAT to trigger erase */
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
|
|
+ MXL862XX_FW_HDR_SIZE);
|
|
+
|
|
+ /* Wait for header ACK (header_size + 1) */
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
|
|
+ MXL862XX_FW_ACK_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: header ACK failed: %pe\n", ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+
|
|
+ /* Step 5: Wait for erase to complete (STAT goes to 0) */
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
|
|
+ MXL862XX_FW_ERASE_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: erase timeout: %pe\n", ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+
|
|
+ /* Step 6: Transfer payload using dual-bank auto-increment writes */
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_WR);
|
|
+
|
|
+ while (idx < payload_size) {
|
|
+ if (idx + 1 < payload_size) {
|
|
+ fdata = payload[idx] |
|
|
+ ((u16)payload[idx + 1] << 8);
|
|
+ idx += 2;
|
|
+ data_written += 2;
|
|
+ } else {
|
|
+ fdata = payload[idx];
|
|
+ idx++;
|
|
+ data_written++;
|
|
+ }
|
|
+
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
|
|
+ word_idx++;
|
|
+
|
|
+ /* Last byte(s): flush final partial slice */
|
|
+ if (idx >= payload_size) {
|
|
+ mxl862xx_sb_pdi_reset(priv);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
|
|
+ data_written);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Half-bank boundary: switch to SB1 address */
|
|
+ if (word_idx == MXL862XX_FW_BANK_HALF) {
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_RST);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
|
|
+ MXL862XX_FW_SB1_ADDR);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_WR);
|
|
+ } else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
|
|
+ /* Full slice: flush and wait for program */
|
|
+ mxl862xx_sb_pdi_reset(priv);
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
|
|
+ data_written);
|
|
+ word_idx = 0;
|
|
+ data_written = 0;
|
|
+
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(
|
|
+ priv, 0, MXL862XX_FW_WRITE_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: write timeout at %u/%u: %pe\n",
|
|
+ idx, payload_size, ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
|
|
+ MXL862XX_SB_PDI_CTRL_WR);
|
|
+
|
|
+ if (time_after(jiffies, next_notify)) {
|
|
+ devlink_flash_update_status_notify(
|
|
+ dl, "Flashing", NULL,
|
|
+ idx, payload_size);
|
|
+ next_notify = jiffies +
|
|
+ msecs_to_jiffies(500);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Wait for final slice to be programmed */
|
|
+ ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
|
|
+ MXL862XX_FW_WRITE_TIMEOUT_MS);
|
|
+ if (ret) {
|
|
+ dev_err(&priv->mdiodev->dev,
|
|
+ "flash: final write timeout: %pe\n", ERR_PTR(ret));
|
|
+ goto end_magic;
|
|
+ }
|
|
+
|
|
+ devlink_flash_update_status_notify(dl, "Flashing", NULL,
|
|
+ payload_size, payload_size);
|
|
+
|
|
+end_magic:
|
|
+ /* Always send end magic so MCUboot reboots instead of sitting
|
|
+ * idle. The hardware reset during reprobe recovers the switch
|
|
+ * regardless of whether the transfer succeeded or failed.
|
|
+ */
|
|
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
|
|
+ MXL862XX_SB_PDI_END);
|
|
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
|
|
+ struct devlink_info_req *req,
|
|
+ struct netlink_ext_ack *extack)
|
|
+{
|
|
+ struct mxl862xx_priv *priv = ds->priv;
|
|
+ char ver_str[32];
|
|
+
|
|
+ snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
|
|
+ priv->fw_version.major, priv->fw_version.minor,
|
|
+ priv->fw_version.revision);
|
|
+
|
|
+ return devlink_info_version_running_put(req, "fw", ver_str);
|
|
+}
|
|
+
|
|
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
|
|
+ struct devlink_flash_update_params *params,
|
|
+ struct netlink_ext_ack *extack)
|
|
+{
|
|
+ struct mxl862xx_priv *priv = ds->priv;
|
|
+ struct mxl862xx_sys_fw_image_version ver = {};
|
|
+ struct mxl862xx_reprobe *reprobe;
|
|
+ struct dsa_port *dp;
|
|
+ int ret, i;
|
|
+
|
|
+ if (params->component) {
|
|
+ NL_SET_ERR_MSG_MOD(extack, "component is not supported");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
|
|
+ priv->fw_version.major, priv->fw_version.minor,
|
|
+ priv->fw_version.revision);
|
|
+
|
|
+ /* Close all user and CPU ports while the firmware is still
|
|
+ * alive. dev_close() on user ports triggers multicast group
|
|
+ * leave and host MDB/FDB removal on the CPU port through the
|
|
+ * normal DSA callbacks so the core's tracking lists are
|
|
+ * drained before we enter MCUboot. Then mark user ports
|
|
+ * not-present so userspace cannot bring them back up during
|
|
+ * the (slow) flash process. The conduit is only closed, not
|
|
+ * detached -- it is owned by the Ethernet MAC driver and
|
|
+ * dev_open() during reprobe must be able to bring it back.
|
|
+ */
|
|
+ rtnl_lock();
|
|
+ dsa_switch_for_each_user_port(dp, ds) {
|
|
+ if (dp->user) {
|
|
+ dev_close(dp->user);
|
|
+ netif_device_detach(dp->user);
|
|
+ }
|
|
+ }
|
|
+ dsa_switch_for_each_cpu_port(dp, ds)
|
|
+ dev_close(dp->conduit);
|
|
+ rtnl_unlock();
|
|
+
|
|
+ /* Block all firmware API commands while the switch is being
|
|
+ * reflashed. The conduit is intentionally kept open -- it is
|
|
+ * owned by the Ethernet MAC driver and would not recover on
|
|
+ * reprobe if we closed it here.
|
|
+ */
|
|
+ priv->block_host = true;
|
|
+
|
|
+ /* Stop stats polling and pending host-flood work */
|
|
+ cancel_delayed_work_sync(&priv->stats_work);
|
|
+ for (i = 0; i < ds->num_ports; i++)
|
|
+ cancel_work_sync(&priv->ports[i].host_flood_work);
|
|
+
|
|
+ ret = mxl862xx_flash_firmware(priv, params->fw, ds->devlink);
|
|
+ if (ret)
|
|
+ NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed");
|
|
+
|
|
+ if (!ret) {
|
|
+ /* Read new firmware version (switch just rebooted).
|
|
+ * Temporarily lift the block for this single query.
|
|
+ */
|
|
+ priv->block_host = false;
|
|
+ memset(&ver, 0, sizeof(ver));
|
|
+ if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver)
|
|
+ && ver.iv_major)
|
|
+ dev_info(ds->dev, "flash: new firmware %u.%u.%u\n",
|
|
+ ver.iv_major, ver.iv_minor,
|
|
+ le16_to_cpu(ver.iv_revision));
|
|
+ }
|
|
+
|
|
+ /* Silently discard all API commands during the teardown that
|
|
+ * reprobe triggers -- the switch firmware has been reset and
|
|
+ * has no knowledge of the old configuration.
|
|
+ */
|
|
+ priv->skip_teardown = true;
|
|
+
|
|
+ reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
|
|
+ if (!reprobe)
|
|
+ return ret;
|
|
+
|
|
+ if (!try_module_get(THIS_MODULE)) {
|
|
+ kfree(reprobe);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ reprobe->dev = get_device(ds->dev);
|
|
+ INIT_DELAYED_WORK(&reprobe->dwork, mxl862xx_reprobe_work_fn);
|
|
+ schedule_delayed_work(&reprobe->dwork, msecs_to_jiffies(500));
|
|
+
|
|
+ return ret;
|
|
+}
|
|
--- /dev/null
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
|
|
@@ -0,0 +1,15 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+
|
|
+#ifndef __MXL862XX_FW_H
|
|
+#define __MXL862XX_FW_H
|
|
+
|
|
+#include <net/dsa.h>
|
|
+
|
|
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
|
|
+ struct devlink_info_req *req,
|
|
+ struct netlink_ext_ack *extack);
|
|
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
|
|
+ struct devlink_flash_update_params *params,
|
|
+ struct netlink_ext_ack *extack);
|
|
+
|
|
+#endif /* __MXL862XX_FW_H */
|
|
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
|
|
@@ -14,6 +14,7 @@
|
|
#include <linux/limits.h>
|
|
#include <net/dsa.h>
|
|
#include "mxl862xx.h"
|
|
+#include "mxl862xx-cmd.h"
|
|
#include "mxl862xx-host.h"
|
|
|
|
#define CTRL_BUSY_MASK BIT(15)
|
|
@@ -334,6 +335,12 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
|
|
int ret, cmd_ret;
|
|
u16 max, crc, i;
|
|
|
|
+ if (priv->skip_teardown)
|
|
+ return 0;
|
|
+
|
|
+ if (priv->block_host && cmd != SYS_MISC_FW_UPDATE)
|
|
+ return -EBUSY;
|
|
+
|
|
dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
|
|
|
|
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
|
|
@@ -23,6 +23,7 @@
|
|
#include "mxl862xx.h"
|
|
#include "mxl862xx-api.h"
|
|
#include "mxl862xx-cmd.h"
|
|
+#include "mxl862xx-fw.h"
|
|
#include "mxl862xx-host.h"
|
|
#include "mxl862xx-phylink.h"
|
|
|
|
@@ -4248,6 +4249,8 @@ static const struct dsa_switch_ops mxl86
|
|
.get_pause_stats = mxl862xx_get_pause_stats,
|
|
.get_rmon_stats = mxl862xx_get_rmon_stats,
|
|
.get_stats64 = mxl862xx_get_stats64,
|
|
+ .devlink_info_get = mxl862xx_devlink_info_get,
|
|
+ .devlink_flash_update = mxl862xx_devlink_flash_update,
|
|
};
|
|
|
|
static int mxl862xx_probe(struct mdio_device *mdiodev)
|
|
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
|
|
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
|
|
@@ -394,6 +394,8 @@ struct mxl862xx_priv {
|
|
u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
|
|
u8 trunk_hash;
|
|
int mirror_dest;
|
|
+ bool block_host;
|
|
+ bool skip_teardown;
|
|
struct delayed_work stats_work;
|
|
};
|
|
|