]> git.99rst.org Git - openwrt-packages.git/commitdiff
fwupd: backport upstream support for MxL862xx switches
authorDaniel Golle <redacted>
Mon, 7 Sep 2026 18:26:54 +0000 (19:26 +0100)
committerJosef Schlehofer <redacted>
Tue, 8 Sep 2026 10:25:52 +0000 (12:25 +0200)
Backport two patches from upstream implementing devlink-based firmware
update support for the MaxLinear MxL862xx DSA switch family, found eg.
on the BananaPi R4 Pro (8E/4E).

Users can update the switch firmware using `fwupdmgr update`.

Signed-off-by: Daniel Golle <redacted>
utils/fwupd/Makefile
utils/fwupd/patches/0001-devlink-Add-MDIO-bus-support.patch [new file with mode: 0644]
utils/fwupd/patches/0002-devlink-Add-quirks-for-MaxLinear-MxL862xx-Ethernet-s.patch [new file with mode: 0644]

index a88fa10a1ae90411d2523f6b3d2883277edf77b2..dfff2e607d30d192b75a276cc9ea73a3b59736ce 100644 (file)
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=fwupd
 PKG_VERSION:=2.1.7
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
 PKG_SOURCE_URL:=https://github.com/fwupd/fwupd/releases/download/$(PKG_VERSION)
diff --git a/utils/fwupd/patches/0001-devlink-Add-MDIO-bus-support.patch b/utils/fwupd/patches/0001-devlink-Add-MDIO-bus-support.patch
new file mode 100644 (file)
index 0000000..f390abf
--- /dev/null
@@ -0,0 +1,253 @@
+From 5298944fa6b14e9c6a36bc61af7e4a495033b7af Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 13 Jul 2026 22:55:10 +0100
+Subject: [PATCH 1/2] devlink: Add MDIO bus support
+
+The backend refused to create devices for anything but the "pci" and
+"netdevsim" buses. Ethernet switches attached via MDIO (bus name
+"mdio_bus"), such as the MaxLinear MxL862xx family, were rejected
+before their versions were ever queried:
+
+  failed to add devlink device mdio_bus/mdio-bus:10: unsupported bus
+  type: mdio_bus (only 'pci' and 'netdevsim' are supported)
+
+Create a parent device via the udev backend like it is already done
+for PCI devices. MDIO devices expose no vendor or device IDs in sysfs,
+so use the first "compatible" string of the associated OF node to
+identify the device where available: the vendor prefix becomes a
+devicetree vendor ID, e.g. DT:maxlinear, and the two halves of the
+compatible are used as the VEN and DEV instance strings, so the usual
+quirk instance ID, e.g. MDIO_BUS\VEN_maxlinear&DEV_mxl86252, and
+component GUIDs, e.g. MDIO_BUS\VEN_maxlinear&DEV_mxl86252&COMPONENT_fw,
+are built just like for PCI devices.
+
+Unlike the versions reported by the kernel, the devicetree compatible
+is constant and always available, even when the device cannot report
+any version information, for example while it is stuck in a bootloader
+rescue mode. Firmware targeting the compatible-based component GUID
+can therefore still be matched and flashed to recover such a device.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ plugins/devlink/README.md            | 18 ++++++-
+ plugins/devlink/fu-devlink-backend.c | 81 +++++++++++++++++++++++++++-
+ plugins/devlink/fu-devlink-device.c  | 22 ++++++++
+ 3 files changed, 117 insertions(+), 4 deletions(-)
+
+--- a/plugins/devlink/README.md
++++ b/plugins/devlink/README.md
+@@ -9,7 +9,8 @@ This is a generic plugin that can work w
+ ## Supported Devices
+-The plugin supports any device that implements the devlink interface, regardless the bus it resides on.
++The plugin supports devices implementing the devlink interface on the PCI and
++MDIO buses, as well as emulated netdevsim devices for testing.
+ ## Firmware Format
+@@ -25,9 +26,21 @@ This plugin supports the following proto
+ These devices use custom instance IDs consisting of the component name.
+ * `PCI\VEN_15B3&DEV_1021&COMPONENT_fw`
++* `MDIO_BUS\COMPONENT_fw`
++* `MDIO_BUS\VEN_maxlinear&DEV_mxl86252&COMPONENT_fw`
+ Optionally, additional GUID might get generated as specified in the squirk file, see below.
++For devices attached via MDIO with an associated devicetree node, the vendor
++prefix and device name from the first "compatible" string are used as the
++`VEN` and `DEV` instance ID components, resulting in a quirk-only
++`MDIO_BUS\VEN_maxlinear&DEV_mxl86252` instance ID on the devlink device and
++component GUIDs just like for PCI devices. Unlike the versions reported by
++the kernel, the devicetree compatible is available even when the device
++cannot report any version information, for example while it is stuck in a
++bootloader rescue mode, so firmware targeting the compatible-based GUID can
++still be matched and flashed to recover such a device.
++
+ ### Device Identification
+ Devices are identified using their in the format:
+@@ -131,7 +144,8 @@ The plugin handles various error conditi
+ ## Vendor ID Security
+-The vendor ID is set from the PCI vendor.
++The vendor ID is set from the PCI vendor, or for devices on the MDIO bus, from
++the devicetree vendor prefix, e.g. `DT:maxlinear`.
+ ## External Interface Access
+--- a/plugins/devlink/fu-devlink-backend.c
++++ b/plugins/devlink/fu-devlink-backend.c
+@@ -67,6 +67,71 @@ fu_devlink_backend_create_pci_parent(FuD
+ }
+ static FuDevice *
++fu_devlink_backend_create_mdio_parent(FuDevlinkBackend *self,
++                                    const gchar *bus_name,
++                                    const gchar *dev_name,
++                                    GError **error)
++{
++      FuContext *ctx = fu_backend_get_context(FU_BACKEND(self));
++      FuBackend *udev_backend = NULL;
++      g_autofree gchar *mdio_sysfs_path = NULL;
++      g_autofree gchar *mdio_sysfs_real = NULL;
++      g_autoptr(FuDevice) mdio_device = NULL;
++      g_autoptr(GBytes) compatible_blob = NULL;
++      g_autoptr(GError) error_local = NULL;
++
++      udev_backend = fu_context_get_backend_by_name(ctx, "udev", &error_local);
++      if (udev_backend == NULL) {
++              g_set_error(error,
++                          FWUPD_ERROR,
++                          FWUPD_ERROR_NOT_SUPPORTED,
++                          "udev backend not available: %s",
++                          error_local->message);
++              return NULL;
++      }
++
++      mdio_sysfs_path = g_strdup_printf("/sys/bus/mdio_bus/devices/%s", dev_name);
++      mdio_sysfs_real = fu_path_make_absolute(mdio_sysfs_path, error);
++      if (mdio_sysfs_real == NULL)
++              return NULL;
++
++      mdio_device = fu_backend_create_device(udev_backend, mdio_sysfs_real, &error_local);
++      if (mdio_device == NULL) {
++              g_set_error(error,
++                          FWUPD_ERROR,
++                          FWUPD_ERROR_NOT_FOUND,
++                          "failed to create MDIO device for %s: %s",
++                          mdio_sysfs_path,
++                          error_local->message);
++              return NULL;
++      }
++
++      if (!fu_device_probe(mdio_device, error)) {
++              g_prefix_error_literal(error, "failed to probe MDIO device: ");
++              return NULL;
++      }
++
++      /* of_node/compatible is a NUL-separated string list, so read it as raw bytes */
++      compatible_blob = fu_udev_device_read_sysfs_bytes(FU_UDEV_DEVICE(mdio_device),
++                                                        "of_node/compatible",
++                                                        -1,
++                                                        FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
++                                                        NULL);
++      if (compatible_blob != NULL && g_bytes_get_size(compatible_blob) > 0) {
++              g_autofree gchar *compatible = g_strndup(g_bytes_get_data(compatible_blob, NULL),
++                                                       g_bytes_get_size(compatible_blob));
++              g_auto(GStrv) parts = g_strsplit(compatible, ",", 2);
++              if (g_strv_length(parts) == 2) {
++                      fu_device_build_vendor_id(mdio_device, "DT", parts[0]);
++                      fu_device_add_instance_strsafe(mdio_device, "VEN", parts[0]);
++                      fu_device_add_instance_strsafe(mdio_device, "DEV", parts[1]);
++              }
++      }
++
++      return g_steal_pointer(&mdio_device);
++}
++
++static FuDevice *
+ fu_devlink_backend_create_netdevsim_parent(FuDevlinkBackend *self,
+                                          const gchar *bus_name,
+                                          const gchar *dev_name,
+@@ -96,6 +161,7 @@ fu_devlink_backend_device_added(FuDevlin
+ {
+       FuContext *ctx = fu_backend_get_context(FU_BACKEND(self));
+       FuDevice *old_devlink_device;
++      const gchar *instance_keys[] = {"VEN", "DEV", NULL};
+       g_autoptr(FuDevice) devlink_device = NULL;
+       g_autoptr(FuDevice) parent_device = NULL;
+@@ -104,12 +170,17 @@ fu_devlink_backend_device_added(FuDevlin
+       g_return_val_if_fail(dev_name != NULL, NULL);
+       g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+-      /* only support PCI and netdevsim buses */
++      /* create a bus-specific parent device */
+       if (g_strcmp0(bus_name, "pci") == 0) {
+               parent_device =
+                   fu_devlink_backend_create_pci_parent(self, bus_name, dev_name, error);
+               if (parent_device == NULL)
+                       return NULL;
++      } else if (g_strcmp0(bus_name, "mdio_bus") == 0) {
++              parent_device =
++                  fu_devlink_backend_create_mdio_parent(self, bus_name, dev_name, error);
++              if (parent_device == NULL)
++                      return NULL;
+       } else if (g_strcmp0(bus_name, "netdevsim") == 0) {
+               parent_device =
+                   fu_devlink_backend_create_netdevsim_parent(self, bus_name, dev_name, error);
+@@ -119,7 +190,8 @@ fu_devlink_backend_device_added(FuDevlin
+               g_set_error(error,
+                           FWUPD_ERROR,
+                           FWUPD_ERROR_NOT_SUPPORTED,
+-                          "unsupported bus type: %s (only 'pci' and 'netdevsim' are supported)",
++                          "unsupported bus type: %s (only 'pci', 'mdio_bus' and 'netdevsim' are "
++                          "supported)",
+                           bus_name);
+               return NULL;
+       }
+@@ -157,6 +229,11 @@ fu_devlink_backend_device_added(FuDevlin
+                                 FU_DEVICE_INCORPORATE_FLAG_VENDOR |
+                                 FU_DEVICE_INCORPORATE_FLAG_VENDOR_IDS |
+                                 FU_DEVICE_INCORPORATE_FLAG_VID | FU_DEVICE_INCORPORATE_FLAG_PID);
++      for (guint i = 0; instance_keys[i] != NULL; i++) {
++              const gchar *value = fu_device_get_instance_str(parent_device, instance_keys[i]);
++              if (value != NULL)
++                      fu_device_add_instance_str(devlink_device, instance_keys[i], value);
++      }
+       /* only add the devlink device to the backend - parent is managed by its own backend */
+       fu_backend_device_added(FU_BACKEND(self), devlink_device);
+--- a/plugins/devlink/fu-devlink-device.c
++++ b/plugins/devlink/fu-devlink-device.c
+@@ -699,6 +699,9 @@ fu_devlink_device_setup(FuDevice *device
+       if (fu_device_get_vid(device) != 0 && fu_device_get_pid(device) != 0) {
+               fu_device_add_instance_u16(device, "VEN", fu_device_get_vid(device));
+               fu_device_add_instance_u16(device, "DEV", fu_device_get_pid(device));
++      }
++      if (fu_device_get_instance_str(device, "VEN") != NULL &&
++          fu_device_get_instance_str(device, "DEV") != NULL) {
+               if (!fu_device_build_instance_id_full(device,
+                                                     FU_DEVICE_INSTANCE_FLAG_QUIRKS,
+                                                     error,
+@@ -864,6 +867,17 @@ fu_devlink_device_add_json(FuDevice *dev
+               fwupd_json_object_add_string(json_obj, "BusName", self->bus_name);
+       if (self->dev_name != NULL)
+               fwupd_json_object_add_string(json_obj, "DevName", self->dev_name);
++
++      /* instance strings inherited from the parent device, which does not exist under
++       * emulation */
++      if (fu_device_get_instance_str(device, "VEN") != NULL)
++              fwupd_json_object_add_string(json_obj,
++                                           "Ven",
++                                           fu_device_get_instance_str(device, "VEN"));
++      if (fu_device_get_instance_str(device, "DEV") != NULL)
++              fwupd_json_object_add_string(json_obj,
++                                           "Dev",
++                                           fu_device_get_instance_str(device, "DEV"));
+ }
+ static gboolean
+@@ -872,11 +886,19 @@ fu_devlink_device_from_json(FuDevice *de
+       FuDevlinkDevice *self = FU_DEVLINK_DEVICE(device);
+       const gchar *bus_name;
+       const gchar *dev_name;
++      const gchar *ven;
++      const gchar *dev;
+       g_autofree gchar *device_id = NULL;
+       /* devlink-specific properties */
+       bus_name = fwupd_json_object_get_string(json_obj, "BusName", NULL);
+       dev_name = fwupd_json_object_get_string(json_obj, "DevName", NULL);
++      ven = fwupd_json_object_get_string(json_obj, "Ven", NULL);
++      if (ven != NULL)
++              fu_device_add_instance_str(device, "VEN", ven);
++      dev = fwupd_json_object_get_string(json_obj, "Dev", NULL);
++      if (dev != NULL)
++              fu_device_add_instance_str(device, "DEV", dev);
+       if (bus_name == NULL || dev_name == NULL) {
+               g_set_error_literal(error,
diff --git a/utils/fwupd/patches/0002-devlink-Add-quirks-for-MaxLinear-MxL862xx-Ethernet-s.patch b/utils/fwupd/patches/0002-devlink-Add-quirks-for-MaxLinear-MxL862xx-Ethernet-s.patch
new file mode 100644 (file)
index 0000000..0a801f7
--- /dev/null
@@ -0,0 +1,228 @@
+From e50c9e5ab39d31242e664efbbf441fd46d15a0cd Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 13 Jul 2026 22:57:10 +0100
+Subject: [PATCH 2/2] devlink: Add quirks for MaxLinear MxL862xx Ethernet
+ switches
+
+The mxl862xx DSA driver implements devlink info_get and flash_update
+for these switches attached via MDIO. The chip ID registers are only
+readable with a running firmware, so a switch stuck in MCUboot rescue
+mode reports no fixed versions at all and cannot be identified by its
+numeric "asic.id". Match the device and its firmware component on the
+devicetree compatible instead: it is available in both modes, so
+firmware targeting the compatible-based component GUID can also be
+flashed to recover a switch from rescue mode.
+
+The vendor ID derived from the devicetree vendor prefix by the MDIO
+parent device only reaches the devlink device itself, so it is
+repeated here for the component.
+
+The firmware image contains both application slots and is flashed as a
+whole, so component-specific updates are rejected by the driver and
+omit-component-name is required. All ports go down for the duration of
+the flash process and the new image boots immediately afterwards,
+hence neither usable-during-update nor dual-image apply.
+
+Also, add emulations recorded on a Bananapi BPI-R4 Pro 8X with a MaxLinear
+MxL86252 Ethernet switch attached via MDIO.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ plugins/devlink/README.md                   |  2 +-
+ plugins/devlink/devlink.quirk               | 18 +++++++++
+ plugins/devlink/fu-devlink-backend.c        | 41 +++++++++++++++------
+ plugins/devlink/fu-devlink-device.c         | 21 +++++++----
+ plugins/devlink/meson.build                 |  5 ++-
+ plugins/devlink/tests/devlink-mxl86252.json | 20 ++++++++++
+ 6 files changed, 86 insertions(+), 21 deletions(-)
+ create mode 100644 plugins/devlink/tests/devlink-mxl86252.json
+
+--- a/plugins/devlink/README.md
++++ b/plugins/devlink/README.md
+@@ -29,7 +29,7 @@ These devices use custom instance IDs co
+ * `MDIO_BUS\COMPONENT_fw`
+ * `MDIO_BUS\VEN_maxlinear&DEV_mxl86252&COMPONENT_fw`
+-Optionally, additional GUID might get generated as specified in the squirk file, see below.
++Optionally, additional GUIDs might be generated as specified in the quirk file, see below.
+ For devices attached via MDIO with an associated devicetree node, the vendor
+ prefix and device name from the first "compatible" string are used as the
+--- a/plugins/devlink/devlink.quirk
++++ b/plugins/devlink/devlink.quirk
+@@ -27,3 +27,21 @@ DevlinkFixedVersions = fw.psid
+ # NVIDIA ConnectX-8
+ [PCI\VEN_15B3&DEV_1023]
+ DevlinkFixedVersions = fw.psid
++
++# MaxLinear MxL86252 Ethernet switch
++[MDIO_BUS\VEN_maxlinear&DEV_mxl86252]
++Name = MxL86252 Ethernet Switch
++Vendor = MaxLinear
++[MDIO_BUS\VEN_maxlinear&DEV_mxl86252&COMPONENT_fw]
++Vendor = MaxLinear
++VendorId = DT:maxlinear
++Flags = omit-component-name,~usable-during-update,~dual-image
++
++# MaxLinear MxL86282 Ethernet switch
++[MDIO_BUS\VEN_maxlinear&DEV_mxl86282]
++Name = MxL86282 Ethernet Switch
++Vendor = MaxLinear
++[MDIO_BUS\VEN_maxlinear&DEV_mxl86282&COMPONENT_fw]
++Vendor = MaxLinear
++VendorId = DT:maxlinear
++Flags = omit-component-name,~usable-during-update,~dual-image
+--- a/plugins/devlink/fu-devlink-backend.c
++++ b/plugins/devlink/fu-devlink-backend.c
+@@ -40,7 +40,16 @@ fu_devlink_backend_create_pci_parent(FuD
+       }
+       /* construct PCI sysfs path from bus_name (e.g., "pci/0000:01:00.0") */
+-      pci_sysfs_path = g_strdup_printf("/sys/bus/pci/devices/%s", dev_name);
++      pci_sysfs_path = fu_context_build_filename(ctx,
++                                                 error,
++                                                 FU_PATH_KIND_SYSFSDIR,
++                                                 "bus",
++                                                 "pci",
++                                                 "devices",
++                                                 dev_name,
++                                                 NULL);
++      if (pci_sysfs_path == NULL)
++              return NULL;
+       pci_sysfs_real = fu_path_make_absolute(pci_sysfs_path, error);
+       if (pci_sysfs_real == NULL)
+               return NULL;
+@@ -90,7 +99,16 @@ fu_devlink_backend_create_mdio_parent(Fu
+               return NULL;
+       }
+-      mdio_sysfs_path = g_strdup_printf("/sys/bus/mdio_bus/devices/%s", dev_name);
++      mdio_sysfs_path = fu_context_build_filename(ctx,
++                                                  error,
++                                                  FU_PATH_KIND_SYSFSDIR,
++                                                  "bus",
++                                                  "mdio_bus",
++                                                  "devices",
++                                                  dev_name,
++                                                  NULL);
++      if (mdio_sysfs_path == NULL)
++              return NULL;
+       mdio_sysfs_real = fu_path_make_absolute(mdio_sysfs_path, error);
+       if (mdio_sysfs_real == NULL)
+               return NULL;
+@@ -118,13 +136,14 @@ fu_devlink_backend_create_mdio_parent(Fu
+                                                         FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
+                                                         NULL);
+       if (compatible_blob != NULL && g_bytes_get_size(compatible_blob) > 0) {
+-              g_autofree gchar *compatible = g_strndup(g_bytes_get_data(compatible_blob, NULL),
+-                                                       g_bytes_get_size(compatible_blob));
+-              g_auto(GStrv) parts = g_strsplit(compatible, ",", 2);
+-              if (g_strv_length(parts) == 2) {
+-                      fu_device_build_vendor_id(mdio_device, "DT", parts[0]);
+-                      fu_device_add_instance_strsafe(mdio_device, "VEN", parts[0]);
+-                      fu_device_add_instance_strsafe(mdio_device, "DEV", parts[1]);
++              g_autofree gchar *compatible = fu_strsafe_bytes(compatible_blob, G_MAXSIZE);
++              if (compatible != NULL) {
++                      g_auto(GStrv) parts = g_strsplit(compatible, ",", 2);
++                      if (g_strv_length(parts) == 2) {
++                              fu_device_build_vendor_id(mdio_device, "DT", parts[0]);
++                              fu_device_add_instance_strsafe(mdio_device, "VEN", parts[0]);
++                              fu_device_add_instance_strsafe(mdio_device, "DEV", parts[1]);
++                      }
+               }
+       }
+@@ -161,7 +180,7 @@ fu_devlink_backend_device_added(FuDevlin
+ {
+       FuContext *ctx = fu_backend_get_context(FU_BACKEND(self));
+       FuDevice *old_devlink_device;
+-      const gchar *instance_keys[] = {"VEN", "DEV", NULL};
++      const gchar *instance_keys[] = {"VEN", "DEV"};
+       g_autoptr(FuDevice) devlink_device = NULL;
+       g_autoptr(FuDevice) parent_device = NULL;
+@@ -229,7 +248,7 @@ fu_devlink_backend_device_added(FuDevlin
+                                 FU_DEVICE_INCORPORATE_FLAG_VENDOR |
+                                 FU_DEVICE_INCORPORATE_FLAG_VENDOR_IDS |
+                                 FU_DEVICE_INCORPORATE_FLAG_VID | FU_DEVICE_INCORPORATE_FLAG_PID);
+-      for (guint i = 0; instance_keys[i] != NULL; i++) {
++      for (guint i = 0; i < G_N_ELEMENTS(instance_keys); i++) {
+               const gchar *value = fu_device_get_instance_str(parent_device, instance_keys[i]);
+               if (value != NULL)
+                       fu_device_add_instance_str(devlink_device, instance_keys[i], value);
+--- a/plugins/devlink/fu-devlink-device.c
++++ b/plugins/devlink/fu-devlink-device.c
+@@ -868,16 +868,17 @@ fu_devlink_device_add_json(FuDevice *dev
+       if (self->dev_name != NULL)
+               fwupd_json_object_add_string(json_obj, "DevName", self->dev_name);
+-      /* instance strings inherited from the parent device, which does not exist under
+-       * emulation */
+-      if (fu_device_get_instance_str(device, "VEN") != NULL)
++      /* instance strings inherited from the parent, which does not exist under emulation */
++      if (fu_device_get_instance_str(device, "VEN") != NULL) {
+               fwupd_json_object_add_string(json_obj,
+-                                           "Ven",
++                                           "Vid",
+                                            fu_device_get_instance_str(device, "VEN"));
+-      if (fu_device_get_instance_str(device, "DEV") != NULL)
++      }
++      if (fu_device_get_instance_str(device, "DEV") != NULL) {
+               fwupd_json_object_add_string(json_obj,
+-                                           "Dev",
++                                           "Pid",
+                                            fu_device_get_instance_str(device, "DEV"));
++      }
+ }
+ static gboolean
+@@ -893,10 +894,14 @@ fu_devlink_device_from_json(FuDevice *de
+       /* devlink-specific properties */
+       bus_name = fwupd_json_object_get_string(json_obj, "BusName", NULL);
+       dev_name = fwupd_json_object_get_string(json_obj, "DevName", NULL);
+-      ven = fwupd_json_object_get_string(json_obj, "Ven", NULL);
++      ven = fwupd_json_object_get_string(json_obj, "Vid", NULL);
++      if (ven == NULL)
++              ven = fwupd_json_object_get_string(json_obj, "Ven", NULL);
+       if (ven != NULL)
+               fu_device_add_instance_str(device, "VEN", ven);
+-      dev = fwupd_json_object_get_string(json_obj, "Dev", NULL);
++      dev = fwupd_json_object_get_string(json_obj, "Pid", NULL);
++      if (dev == NULL)
++              dev = fwupd_json_object_get_string(json_obj, "Dev", NULL);
+       if (dev != NULL)
+               fu_device_add_instance_str(device, "DEV", dev);
+--- a/plugins/devlink/meson.build
++++ b/plugins/devlink/meson.build
+@@ -29,6 +29,7 @@ plugin_builtin_devlink = static_library(
+ plugin_builtins += plugin_builtin_devlink
+ device_tests += files(
++  'tests/devlink-mxl86252.json',
+   'tests/devlink-netdevsim.json',
+ )
+--- /dev/null
++++ b/plugins/devlink/tests/devlink-mxl86252.json
+@@ -0,0 +1,20 @@
++{
++  "name": "MaxLinear MxL86252 Ethernet Switch Update",
++  "interactive": false,
++  "steps": [
++    {
++      "url": "972ec047263bc0e3e06a9fa929c65923f1cae6ead59e0904d4fe7440c5e6847a-mxl862xxc-1085-signed-upgrade-dsa.cab",
++      "emulation-url": "c2973f267c338b9904238c9964f612260f789248d711247bda6485bf4750d2b4-mxl86252-emulation.zip",
++      "components": [
++        {
++          "name": "fw",
++          "protocol": "org.kernel.devlink",
++          "version": "1.0.85",
++          "guids": [
++            "d2a6f602-d988-5425-b686-5c1081576cce"
++          ]
++        }
++      ]
++    }
++  ]
++}
git clone https://git.99rst.org/PROJECT