]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
sfc: parse headers of devlink flash images
authorEdward Cree <ecree.xilinx@gmail.com>
Mon, 10 Feb 2025 11:25:42 +0000 (11:25 +0000)
committerJakub Kicinski <kuba@kernel.org>
Wed, 12 Feb 2025 01:12:17 +0000 (17:12 -0800)
This parsing is necessary to obtain the metadata which will be
 used in a subsequent patch to write the image to the device.

Signed-off-by: Edward Cree <ecree.xilinx@gmail.com>
Link: https://patch.msgid.link/65318300f3f1b1462925f917f7c0d0ac833955ae.1739186253.git.ecree.xilinx@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/sfc/Makefile
drivers/net/ethernet/sfc/efx_devlink.c
drivers/net/ethernet/sfc/efx_reflash.c [new file with mode: 0644]
drivers/net/ethernet/sfc/efx_reflash.h [new file with mode: 0644]
drivers/net/ethernet/sfc/fw_formats.h [new file with mode: 0644]

index 8f446b9bd5ee3c168d571f666fdb6f1ce6584584..d99039ec468d67a7dffeb8a33653ff3e1b6c64e7 100644 (file)
@@ -7,7 +7,7 @@ sfc-y                   += efx.o efx_common.o efx_channels.o nic.o \
                           mcdi_functions.o mcdi_filters.o mcdi_mon.o \
                           ef100.o ef100_nic.o ef100_netdev.o \
                           ef100_ethtool.o ef100_rx.o ef100_tx.o \
-                          efx_devlink.o
+                          efx_devlink.o efx_reflash.o
 sfc-$(CONFIG_SFC_MTD)  += mtd.o
 sfc-$(CONFIG_SFC_SRIOV)        += sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \
                            mae.o tc.o tc_bindings.o tc_counters.o \
index 3cd750820fdde327b819db6adb83b9350826d49c..d842c60dfc100d0bb6c364a152b89442cac00344 100644 (file)
@@ -19,6 +19,7 @@
 #include "mae.h"
 #include "ef100_rep.h"
 #endif
+#include "efx_reflash.h"
 
 struct efx_devlink {
        struct efx_nic *efx;
@@ -615,7 +616,19 @@ static int efx_devlink_info_get(struct devlink *devlink,
        return 0;
 }
 
+static int efx_devlink_flash_update(struct devlink *devlink,
+                                   struct devlink_flash_update_params *params,
+                                   struct netlink_ext_ack *extack)
+{
+       struct efx_devlink *devlink_private = devlink_priv(devlink);
+       struct efx_nic *efx = devlink_private->efx;
+
+       return efx_reflash_flash_firmware(efx, params->fw, extack);
+}
+
 static const struct devlink_ops sfc_devlink_ops = {
+       .supported_flash_update_params  = 0,
+       .flash_update                   = efx_devlink_flash_update,
        .info_get                       = efx_devlink_info_get,
 };
 
diff --git a/drivers/net/ethernet/sfc/efx_reflash.c b/drivers/net/ethernet/sfc/efx_reflash.c
new file mode 100644 (file)
index 0000000..9a8d821
--- /dev/null
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/****************************************************************************
+ * Driver for AMD network controllers and boards
+ * Copyright (C) 2025, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#include <linux/crc32.h>
+#include <net/devlink.h>
+#include "efx_reflash.h"
+#include "net_driver.h"
+#include "fw_formats.h"
+#include "mcdi_pcol.h"
+#include "mcdi.h"
+
+/* Try to parse a Reflash header at the specified offset */
+static bool efx_reflash_parse_reflash_header(const struct firmware *fw,
+                                            size_t header_offset, u32 *type,
+                                            u32 *subtype, const u8 **data,
+                                            size_t *data_size)
+{
+       size_t header_end, trailer_offset, trailer_end;
+       u32 magic, version, payload_size, header_len;
+       const u8 *header, *trailer;
+       u32 expected_crc, crc;
+
+       if (check_add_overflow(header_offset, EFX_REFLASH_HEADER_LENGTH_OFST +
+                                             EFX_REFLASH_HEADER_LENGTH_LEN,
+                              &header_end))
+               return false;
+       if (fw->size < header_end)
+               return false;
+
+       header = fw->data + header_offset;
+       magic = get_unaligned_le32(header + EFX_REFLASH_HEADER_MAGIC_OFST);
+       if (magic != EFX_REFLASH_HEADER_MAGIC_VALUE)
+               return false;
+
+       version = get_unaligned_le32(header + EFX_REFLASH_HEADER_VERSION_OFST);
+       if (version != EFX_REFLASH_HEADER_VERSION_VALUE)
+               return false;
+
+       payload_size = get_unaligned_le32(header + EFX_REFLASH_HEADER_PAYLOAD_SIZE_OFST);
+       header_len = get_unaligned_le32(header + EFX_REFLASH_HEADER_LENGTH_OFST);
+       if (check_add_overflow(header_offset, header_len, &trailer_offset) ||
+           check_add_overflow(trailer_offset, payload_size, &trailer_offset) ||
+           check_add_overflow(trailer_offset, EFX_REFLASH_TRAILER_LEN,
+                              &trailer_end))
+               return false;
+       if (fw->size < trailer_end)
+               return false;
+
+       trailer = fw->data + trailer_offset;
+       expected_crc = get_unaligned_le32(trailer + EFX_REFLASH_TRAILER_CRC_OFST);
+       /* Addition could overflow u32, but not size_t since we already
+        * checked trailer_offset didn't overflow.  So cast to size_t first.
+        */
+       crc = crc32_le(0, header, (size_t)header_len + payload_size);
+       if (crc != expected_crc)
+               return false;
+
+       *type = get_unaligned_le32(header + EFX_REFLASH_HEADER_FIRMWARE_TYPE_OFST);
+       *subtype = get_unaligned_le32(header + EFX_REFLASH_HEADER_FIRMWARE_SUBTYPE_OFST);
+       if (*type == EFX_REFLASH_FIRMWARE_TYPE_BUNDLE) {
+               /* All the bundle data is written verbatim to NVRAM */
+               *data = fw->data;
+               *data_size = fw->size;
+       } else {
+               /* Other payload types strip the reflash header and trailer
+                * from the data written to NVRAM
+                */
+               *data = header + header_len;
+               *data_size = payload_size;
+       }
+
+       return true;
+}
+
+/* Map from FIRMWARE_TYPE to NVRAM_PARTITION_TYPE */
+static int efx_reflash_partition_type(u32 type, u32 subtype,
+                                     u32 *partition_type,
+                                     u32 *partition_subtype)
+{
+       int rc = 0;
+
+       switch (type) {
+       case EFX_REFLASH_FIRMWARE_TYPE_BOOTROM:
+               *partition_type = NVRAM_PARTITION_TYPE_EXPANSION_ROM;
+               *partition_subtype = subtype;
+               break;
+       case EFX_REFLASH_FIRMWARE_TYPE_BUNDLE:
+               *partition_type = NVRAM_PARTITION_TYPE_BUNDLE;
+               *partition_subtype = subtype;
+               break;
+       default:
+               /* Not supported */
+               rc = -EINVAL;
+       }
+
+       return rc;
+}
+
+/* Try to parse a SmartNIC image header at the specified offset */
+static bool efx_reflash_parse_snic_header(const struct firmware *fw,
+                                         size_t header_offset,
+                                         u32 *partition_type,
+                                         u32 *partition_subtype,
+                                         const u8 **data, size_t *data_size)
+{
+       u32 magic, version, payload_size, header_len, expected_crc, crc;
+       size_t header_end, payload_end;
+       const u8 *header;
+
+       if (check_add_overflow(header_offset, EFX_SNICIMAGE_HEADER_MINLEN,
+                              &header_end) ||
+           fw->size < header_end)
+               return false;
+
+       header = fw->data + header_offset;
+       magic = get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_MAGIC_OFST);
+       if (magic != EFX_SNICIMAGE_HEADER_MAGIC_VALUE)
+               return false;
+
+       version = get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_VERSION_OFST);
+       if (version != EFX_SNICIMAGE_HEADER_VERSION_VALUE)
+               return false;
+
+       header_len = get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_LENGTH_OFST);
+       if (check_add_overflow(header_offset, header_len, &header_end))
+               return false;
+       payload_size = get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_PAYLOAD_SIZE_OFST);
+       if (check_add_overflow(header_end, payload_size, &payload_end) ||
+           fw->size < payload_end)
+               return false;
+
+       expected_crc = get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_CRC_OFST);
+
+       /* Calculate CRC omitting the expected CRC field itself */
+       crc = crc32_le(~0, header, EFX_SNICIMAGE_HEADER_CRC_OFST);
+       crc = ~crc32_le(crc,
+                       header + EFX_SNICIMAGE_HEADER_CRC_OFST +
+                       EFX_SNICIMAGE_HEADER_CRC_LEN,
+                       header_len + payload_size - EFX_SNICIMAGE_HEADER_CRC_OFST -
+                       EFX_SNICIMAGE_HEADER_CRC_LEN);
+       if (crc != expected_crc)
+               return false;
+
+       *partition_type =
+               get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_PARTITION_TYPE_OFST);
+       *partition_subtype =
+               get_unaligned_le32(header + EFX_SNICIMAGE_HEADER_PARTITION_SUBTYPE_OFST);
+       *data = fw->data;
+       *data_size = fw->size;
+       return true;
+}
+
+/* Try to parse a SmartNIC bundle header at the specified offset */
+static bool efx_reflash_parse_snic_bundle_header(const struct firmware *fw,
+                                                size_t header_offset,
+                                                u32 *partition_type,
+                                                u32 *partition_subtype,
+                                                const u8 **data,
+                                                size_t *data_size)
+{
+       u32 magic, version, bundle_type, header_len, expected_crc, crc;
+       size_t header_end;
+       const u8 *header;
+
+       if (check_add_overflow(header_offset, EFX_SNICBUNDLE_HEADER_LEN,
+                              &header_end))
+               return false;
+       if (fw->size < header_end)
+               return false;
+
+       header = fw->data + header_offset;
+       magic = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_MAGIC_OFST);
+       if (magic != EFX_SNICBUNDLE_HEADER_MAGIC_VALUE)
+               return false;
+
+       version = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_VERSION_OFST);
+       if (version != EFX_SNICBUNDLE_HEADER_VERSION_VALUE)
+               return false;
+
+       bundle_type = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_BUNDLE_TYPE_OFST);
+       if (bundle_type != NVRAM_PARTITION_TYPE_BUNDLE)
+               return false;
+
+       header_len = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_LENGTH_OFST);
+       if (header_len != EFX_SNICBUNDLE_HEADER_LEN)
+               return false;
+
+       expected_crc = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_CRC_OFST);
+       crc = ~crc32_le(~0, header, EFX_SNICBUNDLE_HEADER_CRC_OFST);
+       if (crc != expected_crc)
+               return false;
+
+       *partition_type = NVRAM_PARTITION_TYPE_BUNDLE;
+       *partition_subtype = get_unaligned_le32(header + EFX_SNICBUNDLE_HEADER_BUNDLE_SUBTYPE_OFST);
+       *data = fw->data;
+       *data_size = fw->size;
+       return true;
+}
+
+/* Try to find a valid firmware payload in the firmware data.
+ * When we recognise a valid header, we parse it for the partition type
+ * (so we know where to ask the MC to write it to) and the location of
+ * the data blob to write.
+ */
+static int efx_reflash_parse_firmware_data(const struct firmware *fw,
+                                          u32 *partition_type,
+                                          u32 *partition_subtype,
+                                          const u8 **data, size_t *data_size)
+{
+       size_t header_offset;
+       u32 type, subtype;
+
+       /* Some packaging formats (such as CMS/PKCS#7 signed images)
+        * prepend a header for which finding the size is a non-trivial
+        * task, so step through the firmware data until we find a valid
+        * header.
+        *
+        * The checks are intended to reject firmware data that is clearly not
+        * in the expected format.  They do not need to be exhaustive as the
+        * running firmware will perform its own comprehensive validity and
+        * compatibility checks during the update procedure.
+        *
+        * Firmware packages may contain multiple reflash images, e.g. a
+        * bundle containing one or more other images.  Only check the
+        * outermost container by stopping after the first candidate image
+        * found even it is for an unsupported partition type.
+        */
+       for (header_offset = 0; header_offset < fw->size; header_offset++) {
+               if (efx_reflash_parse_snic_bundle_header(fw, header_offset,
+                                                        partition_type,
+                                                        partition_subtype,
+                                                        data, data_size))
+                       return 0;
+
+               if (efx_reflash_parse_snic_header(fw, header_offset,
+                                                 partition_type,
+                                                 partition_subtype, data,
+                                                 data_size))
+                       return 0;
+
+               if (efx_reflash_parse_reflash_header(fw, header_offset, &type,
+                                                    &subtype, data, data_size))
+                       return efx_reflash_partition_type(type, subtype,
+                                                         partition_type,
+                                                         partition_subtype);
+       }
+
+       return -EINVAL;
+}
+
+int efx_reflash_flash_firmware(struct efx_nic *efx, const struct firmware *fw,
+                              struct netlink_ext_ack *extack)
+{
+       struct devlink *devlink = efx->devlink;
+       u32 type, data_subtype;
+       size_t data_size;
+       const u8 *data;
+       int rc;
+
+       if (!efx_has_cap(efx, BUNDLE_UPDATE)) {
+               NL_SET_ERR_MSG_MOD(extack, "NVRAM bundle updates are not supported by the firmware");
+               return -EOPNOTSUPP;
+       }
+
+       devlink_flash_update_status_notify(devlink, "Checking update", NULL, 0, 0);
+
+       rc = efx_reflash_parse_firmware_data(fw, &type, &data_subtype, &data,
+                                            &data_size);
+       if (rc) {
+               NL_SET_ERR_MSG_MOD(extack,
+                                  "Firmware image validation check failed");
+               goto out;
+       }
+
+       rc = -EOPNOTSUPP;
+
+out:
+       devlink_flash_update_status_notify(devlink, rc ? "Update failed" :
+                                                        "Update complete",
+                                          NULL, 0, 0);
+       return rc;
+}
diff --git a/drivers/net/ethernet/sfc/efx_reflash.h b/drivers/net/ethernet/sfc/efx_reflash.h
new file mode 100644 (file)
index 0000000..3dffac5
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/****************************************************************************
+ * Driver for AMD network controllers and boards
+ * Copyright (C) 2025, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#ifndef _EFX_REFLASH_H
+#define _EFX_REFLASH_H
+
+#include "net_driver.h"
+#include <linux/firmware.h>
+
+int efx_reflash_flash_firmware(struct efx_nic *efx, const struct firmware *fw,
+                              struct netlink_ext_ack *extack);
+
+#endif /* _EFX_REFLASH_H */
diff --git a/drivers/net/ethernet/sfc/fw_formats.h b/drivers/net/ethernet/sfc/fw_formats.h
new file mode 100644 (file)
index 0000000..cbc350c
--- /dev/null
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/****************************************************************************
+ * Driver for AMD network controllers and boards
+ * Copyright (C) 2025, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#ifndef _EFX_FW_FORMATS_H
+#define _EFX_FW_FORMATS_H
+
+/* Header layouts of firmware update images recognised by Efx NICs.
+ * The sources-of-truth for these layouts are AMD internal documents
+ * and sfregistry headers, neither of which are available externally
+ * nor usable directly by the driver.
+ *
+ * While each format includes a 'magic number', these are at different
+ * offsets in the various formats, and a legal header for one format
+ * could have the right value in whichever field occupies that offset
+ * to match another format's magic.
+ * Besides, some packaging formats (such as CMS/PKCS#7 signed images)
+ * prepend a header for which finding the size is a non-trivial task;
+ * rather than trying to parse those headers, we search byte-by-byte
+ * through the provided firmware image looking for a valid header.
+ * Thus, format recognition has to include validation of the checksum
+ * field, even though the firmware will validate that itself before
+ * applying the image.
+ */
+
+/* EF10 (Medford2, X2) "reflash" header format.  Defined in SF-121352-AN */
+#define EFX_REFLASH_HEADER_MAGIC_OFST 0
+#define EFX_REFLASH_HEADER_MAGIC_LEN 4
+#define EFX_REFLASH_HEADER_MAGIC_VALUE 0x106F1A5
+
+#define EFX_REFLASH_HEADER_VERSION_OFST 4
+#define EFX_REFLASH_HEADER_VERSION_LEN 4
+#define EFX_REFLASH_HEADER_VERSION_VALUE 4
+
+#define EFX_REFLASH_HEADER_FIRMWARE_TYPE_OFST 8
+#define EFX_REFLASH_HEADER_FIRMWARE_TYPE_LEN 4
+#define EFX_REFLASH_FIRMWARE_TYPE_BOOTROM 0x2
+#define EFX_REFLASH_FIRMWARE_TYPE_BUNDLE 0xd
+
+#define EFX_REFLASH_HEADER_FIRMWARE_SUBTYPE_OFST 12
+#define EFX_REFLASH_HEADER_FIRMWARE_SUBTYPE_LEN 4
+
+#define EFX_REFLASH_HEADER_PAYLOAD_SIZE_OFST 16
+#define EFX_REFLASH_HEADER_PAYLOAD_SIZE_LEN 4
+
+#define EFX_REFLASH_HEADER_LENGTH_OFST 20
+#define EFX_REFLASH_HEADER_LENGTH_LEN 4
+
+/* Reflash trailer */
+#define EFX_REFLASH_TRAILER_CRC_OFST 0
+#define EFX_REFLASH_TRAILER_CRC_LEN 4
+
+#define EFX_REFLASH_TRAILER_LEN        \
+       (EFX_REFLASH_TRAILER_CRC_OFST + EFX_REFLASH_TRAILER_CRC_LEN)
+
+/* EF100 "SmartNIC image" header format.
+ * Defined in sfregistry "src/layout/snic_image_hdr.h".
+ */
+#define EFX_SNICIMAGE_HEADER_MAGIC_OFST 16
+#define EFX_SNICIMAGE_HEADER_MAGIC_LEN 4
+#define EFX_SNICIMAGE_HEADER_MAGIC_VALUE 0x541C057A
+
+#define EFX_SNICIMAGE_HEADER_VERSION_OFST 20
+#define EFX_SNICIMAGE_HEADER_VERSION_LEN 4
+#define EFX_SNICIMAGE_HEADER_VERSION_VALUE 1
+
+#define EFX_SNICIMAGE_HEADER_LENGTH_OFST 24
+#define EFX_SNICIMAGE_HEADER_LENGTH_LEN 4
+
+#define EFX_SNICIMAGE_HEADER_PARTITION_TYPE_OFST 36
+#define EFX_SNICIMAGE_HEADER_PARTITION_TYPE_LEN 4
+
+#define EFX_SNICIMAGE_HEADER_PARTITION_SUBTYPE_OFST 40
+#define EFX_SNICIMAGE_HEADER_PARTITION_SUBTYPE_LEN 4
+
+#define EFX_SNICIMAGE_HEADER_PAYLOAD_SIZE_OFST 60
+#define EFX_SNICIMAGE_HEADER_PAYLOAD_SIZE_LEN 4
+
+#define EFX_SNICIMAGE_HEADER_CRC_OFST 64
+#define EFX_SNICIMAGE_HEADER_CRC_LEN 4
+
+#define EFX_SNICIMAGE_HEADER_MINLEN 256
+
+/* EF100 "SmartNIC bundle" header format.  Defined in SF-122606-TC */
+#define EFX_SNICBUNDLE_HEADER_MAGIC_OFST 0
+#define EFX_SNICBUNDLE_HEADER_MAGIC_LEN 4
+#define EFX_SNICBUNDLE_HEADER_MAGIC_VALUE 0xB1001001
+
+#define EFX_SNICBUNDLE_HEADER_VERSION_OFST 4
+#define EFX_SNICBUNDLE_HEADER_VERSION_LEN 4
+#define EFX_SNICBUNDLE_HEADER_VERSION_VALUE 1
+
+#define EFX_SNICBUNDLE_HEADER_BUNDLE_TYPE_OFST 8
+#define EFX_SNICBUNDLE_HEADER_BUNDLE_TYPE_LEN 4
+
+#define EFX_SNICBUNDLE_HEADER_BUNDLE_SUBTYPE_OFST 12
+#define EFX_SNICBUNDLE_HEADER_BUNDLE_SUBTYPE_LEN 4
+
+#define EFX_SNICBUNDLE_HEADER_LENGTH_OFST 20
+#define EFX_SNICBUNDLE_HEADER_LENGTH_LEN 4
+
+#define EFX_SNICBUNDLE_HEADER_CRC_OFST 224
+#define EFX_SNICBUNDLE_HEADER_CRC_LEN 4
+
+#define EFX_SNICBUNDLE_HEADER_LEN      \
+       (EFX_SNICBUNDLE_HEADER_CRC_OFST + EFX_SNICBUNDLE_HEADER_CRC_LEN)
+
+#endif /* _EFX_FW_FORMATS_H */