| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- // 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;
- }
- /* Limit the number of status updates during the erase or write phases */
- #define EFX_DEVLINK_STATUS_UPDATE_COUNT 50
- /* Expected timeout for the efx_mcdi_nvram_update_finish_polled() */
- #define EFX_DEVLINK_UPDATE_FINISH_TIMEOUT 900
- /* Ideal erase chunk size. This is a balance between minimising the number of
- * MCDI requests to erase an entire partition whilst avoiding tripping the MCDI
- * RPC timeout.
- */
- #define EFX_NVRAM_ERASE_IDEAL_CHUNK_SIZE (64 * 1024)
- static int efx_reflash_erase_partition(struct efx_nic *efx,
- struct netlink_ext_ack *extack,
- struct devlink *devlink, u32 type,
- size_t partition_size,
- size_t align)
- {
- size_t chunk, offset, next_update;
- int rc;
- /* Partitions that cannot be erased or do not require erase before
- * write are advertised with a erase alignment/sector size of zero.
- */
- if (align == 0)
- /* Nothing to do */
- return 0;
- if (partition_size % align)
- return -EINVAL;
- /* Erase the entire NVRAM partition a chunk at a time to avoid
- * potentially tripping the MCDI RPC timeout.
- */
- if (align >= EFX_NVRAM_ERASE_IDEAL_CHUNK_SIZE)
- chunk = align;
- else
- chunk = rounddown(EFX_NVRAM_ERASE_IDEAL_CHUNK_SIZE, align);
- for (offset = 0, next_update = 0; offset < partition_size; offset += chunk) {
- if (offset >= next_update) {
- devlink_flash_update_status_notify(devlink, "Erasing",
- NULL, offset,
- partition_size);
- next_update += partition_size / EFX_DEVLINK_STATUS_UPDATE_COUNT;
- }
- chunk = min_t(size_t, partition_size - offset, chunk);
- rc = efx_mcdi_nvram_erase(efx, type, offset, chunk);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Erase failed for NVRAM partition %#x at %#zx-%#zx",
- type, offset, offset + chunk - 1);
- return rc;
- }
- }
- devlink_flash_update_status_notify(devlink, "Erasing", NULL,
- partition_size, partition_size);
- return 0;
- }
- static int efx_reflash_write_partition(struct efx_nic *efx,
- struct netlink_ext_ack *extack,
- struct devlink *devlink, u32 type,
- const u8 *data, size_t data_size,
- size_t align)
- {
- size_t write_max, chunk, offset, next_update;
- int rc;
- if (align == 0)
- return -EINVAL;
- /* Write the NVRAM partition in chunks that are the largest multiple
- * of the partition's required write alignment that will fit into the
- * MCDI NVRAM_WRITE RPC payload.
- */
- if (efx->type->mcdi_max_ver < 2)
- write_max = MC_CMD_NVRAM_WRITE_IN_WRITE_BUFFER_LEN *
- MC_CMD_NVRAM_WRITE_IN_WRITE_BUFFER_MAXNUM;
- else
- write_max = MC_CMD_NVRAM_WRITE_IN_WRITE_BUFFER_LEN *
- MC_CMD_NVRAM_WRITE_IN_WRITE_BUFFER_MAXNUM_MCDI2;
- chunk = rounddown(write_max, align);
- for (offset = 0, next_update = 0; offset + chunk <= data_size; offset += chunk) {
- if (offset >= next_update) {
- devlink_flash_update_status_notify(devlink, "Writing",
- NULL, offset,
- data_size);
- next_update += data_size / EFX_DEVLINK_STATUS_UPDATE_COUNT;
- }
- rc = efx_mcdi_nvram_write(efx, type, offset, data + offset, chunk);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Write failed for NVRAM partition %#x at %#zx-%#zx",
- type, offset, offset + chunk - 1);
- return rc;
- }
- }
- /* Round up left over data to satisfy write alignment */
- if (offset < data_size) {
- size_t remaining = data_size - offset;
- u8 *buf;
- if (offset >= next_update)
- devlink_flash_update_status_notify(devlink, "Writing",
- NULL, offset,
- data_size);
- chunk = roundup(remaining, align);
- buf = kmalloc(chunk, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
- memcpy(buf, data + offset, remaining);
- memset(buf + remaining, 0xFF, chunk - remaining);
- rc = efx_mcdi_nvram_write(efx, type, offset, buf, chunk);
- kfree(buf);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Write failed for NVRAM partition %#x at %#zx-%#zx",
- type, offset, offset + chunk - 1);
- return rc;
- }
- }
- devlink_flash_update_status_notify(devlink, "Writing", NULL, data_size,
- data_size);
- return 0;
- }
- int efx_reflash_flash_firmware(struct efx_nic *efx, const struct firmware *fw,
- struct netlink_ext_ack *extack)
- {
- size_t data_size, size, erase_align, write_align;
- struct devlink *devlink = efx->devlink;
- u32 type, data_subtype, subtype;
- const u8 *data;
- bool protected;
- 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;
- }
- mutex_lock(&efx->reflash_mutex);
- devlink_flash_update_status_notify(devlink, "Checking update", NULL, 0, 0);
- if (efx->type->flash_auto_partition) {
- /* NIC wants entire FW file including headers;
- * FW will validate 'subtype' if there is one
- */
- type = NVRAM_PARTITION_TYPE_AUTO;
- data = fw->data;
- data_size = fw->size;
- } else {
- 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_unlock;
- }
- rc = efx_mcdi_nvram_metadata(efx, type, &subtype, NULL, NULL, 0);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Metadata query for NVRAM partition %#x failed",
- type);
- goto out_unlock;
- }
- if (subtype != data_subtype) {
- NL_SET_ERR_MSG_MOD(extack,
- "Firmware image is not appropriate for this adapter");
- rc = -EINVAL;
- goto out_unlock;
- }
- }
- rc = efx_mcdi_nvram_info(efx, type, &size, &erase_align, &write_align,
- &protected);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Info query for NVRAM partition %#x failed",
- type);
- goto out_unlock;
- }
- if (protected) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "NVRAM partition %#x is protected",
- type);
- rc = -EPERM;
- goto out_unlock;
- }
- if (write_align == 0) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "NVRAM partition %#x is not writable",
- type);
- rc = -EACCES;
- goto out_unlock;
- }
- if (erase_align != 0 && size % erase_align) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "NVRAM partition %#x has a bad partition table entry, can't erase it",
- type);
- rc = -EACCES;
- goto out_unlock;
- }
- if (data_size > size) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Firmware image is too big for NVRAM partition %#x",
- type);
- rc = -EFBIG;
- goto out_unlock;
- }
- devlink_flash_update_status_notify(devlink, "Starting update", NULL, 0, 0);
- rc = efx_mcdi_nvram_update_start(efx, type);
- if (rc) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Update start request for NVRAM partition %#x failed",
- type);
- goto out_unlock;
- }
- rc = efx_reflash_erase_partition(efx, extack, devlink, type, size,
- erase_align);
- if (rc)
- goto out_update_finish;
- rc = efx_reflash_write_partition(efx, extack, devlink, type, data,
- data_size, write_align);
- if (rc)
- goto out_update_finish;
- devlink_flash_update_timeout_notify(devlink, "Finishing update", NULL,
- EFX_DEVLINK_UPDATE_FINISH_TIMEOUT);
- out_update_finish:
- if (rc)
- /* Don't obscure the return code from an earlier failure */
- efx_mcdi_nvram_update_finish(efx, type, EFX_UPDATE_FINISH_ABORT);
- else
- rc = efx_mcdi_nvram_update_finish_polled(efx, type);
- out_unlock:
- mutex_unlock(&efx->reflash_mutex);
- devlink_flash_update_status_notify(devlink, rc ? "Update failed" :
- "Update complete",
- NULL, 0, 0);
- return rc;
- }
|