On 27.10.2026 18:45, Aleksandr Loktionov wrote:
> An eRoT authenticates the complete NVM image, so a PLDM capsule that
> carries only part of the components is not a valid update on such an
> adapter. Report that from the driver: it knows the components an eRoT
> adapter needs, so it can reject the image with a reason in the extended
> ACK before anything is written to the device.
>
> Reject a repeated component and a component without data while the
> component tables are sent, and require all four components before the
> first bank is erased. Check the component set again before activation,
> because an image whose device record selects no component at all skips
> the flash path and still reaches that step. Reject a single component
> request up front, and accept the Manifest component only when firmware
> reports eRoT presence. Adapters whose firmware does not report eRoT
> keep their current handling.
>
> Read the device capabilities once per flash attempt and act on that
> snapshot, then reuse it for the pending update check the attempt
> already makes, so an update still costs one List Device Capabilities
> round trip. The cached copy in hw->dev_caps cannot decide this: a
> driver loaded while firmware was already in recovery mode never reads
> it, and a reset re-parses it in place. An explicit capability value
> other than 0 or 1 rejects every update, because an answer the driver
> cannot interpret is not evidence that the image is complete.
>
> No activation is requested for the Manifest: the AQ activation select
> mask and the pending update capabilities have no Manifest entry, so the
> driver writes the bank and leaves the activation flag zero. It can
> neither activate nor roll back that bank, which is documented along
> with the other eRoT update limits.
>
> Signed-off-by: Aleksandr Loktionov <[email protected]>
> ---
> Documentation/networking/devlink/ice.rst | 24 +++
> .../net/ethernet/intel/ice/ice_adminq_cmd.h | 1 +
> .../net/ethernet/intel/ice/ice_fw_update.c | 193 +++++++++++++++---
> drivers/net/ethernet/intel/ice/ice_type.h | 1 +
> 4 files changed, 192 insertions(+), 27 deletions(-)
>
> diff --git a/Documentation/networking/devlink/ice.rst
> b/Documentation/networking/devlink/ice.rst
> index 792e9f8..a8d8cb5 100644
> --- a/Documentation/networking/devlink/ice.rst
> +++ b/Documentation/networking/devlink/ice.rst
> @@ -205,6 +205,30 @@ The ``ice`` driver implements support for flash update
> using the
> combined flash image that contains the ``fw.mgmt``, ``fw.undi``, and
> ``fw.netlist`` components.
>
> +When firmware reports an external Root of Trust (eRoT), the image must
> +also contain the ``fw.pqc_manifest`` component and all four components
> +must be updated together. A single component request is rejected before
> +anything is written to the device. An image that misses one of the four
> +components, repeats one, or carries a component without data is
> +rejected before any flash bank is erased, but by then any previously
> +pending update has already been canceled. The driver queries the device
> +capabilities for every flash attempt, so these restrictions follow what
> +the firmware reports at that moment, including on a driver loaded while
> +the firmware was already in recovery mode. Adapters whose firmware does
> +not report the eRoT capability keep their previous handling, and a
> +``fw.pqc_manifest`` component always requires a reported eRoT
> +capability. A reported eRoT capability value other than 0 or 1 makes
> +every flash update fail with ``-EIO``; update such firmware out of band.
> +
> +These checks reject images the adapter cannot use. They do not
> +authenticate image contents and they do not make the update atomic. The
> +driver cannot activate or roll back the ``fw.pqc_manifest`` component:
> +the AQ activation select mask names only ``fw.mgmt``, ``fw.undi`` and
> +``fw.netlist``, and no pending update capability describes a Manifest.
> +If an update fails after the Manifest bank has been written, the state
> +of that bank is defined by the firmware, not by the driver, and
> +recovering the adapter then needs out-of-band tooling.
> +
> .. list-table:: List of supported overwrite modes
> :widths: 5 95
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> index 848ac58..072cd0b 100644
> --- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> +++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> @@ -1748,6 +1748,7 @@ struct ice_aqc_nvm_comp_tbl {
> #define NVM_COMP_ID_OROM 0x5
> #define NVM_COMP_ID_NVM 0x6
> #define NVM_COMP_ID_NETLIST 0x8
> +#define NVM_COMP_ID_MANIFEST 0x12
>
> u8 comp_class_idx;
> #define FWU_COMP_CLASS_IDX_NOT_USE 0x0
> diff --git a/drivers/net/ethernet/intel/ice/ice_fw_update.c
> b/drivers/net/ethernet/intel/ice/ice_fw_update.c
> index 3631461..93ddcbe 100644
> --- a/drivers/net/ethernet/intel/ice/ice_fw_update.c
> +++ b/drivers/net/ethernet/intel/ice/ice_fw_update.c
> @@ -9,6 +9,15 @@
> #include "ice_lib.h"
> #include "ice_fw_update.h"
>
> +/* Bitmask values for ice_fwu_priv::seen_components */
> +#define ICE_FWU_COMP_NVM BIT(0)
> +#define ICE_FWU_COMP_OROM BIT(1)
> +#define ICE_FWU_COMP_NETLIST BIT(2)
> +#define ICE_FWU_COMP_MANIFEST BIT(3)
> +/* Components an eRoT adapter requires in a single update */
> +#define ICE_FWU_COMP_ALL_EROT (ICE_FWU_COMP_NVM |
> ICE_FWU_COMP_OROM | \
> + ICE_FWU_COMP_NETLIST |
> ICE_FWU_COMP_MANIFEST)
> +
> struct ice_fwu_priv {
> struct pldmfw context;
>
> @@ -29,6 +38,10 @@ struct ice_fwu_priv {
>
> /* Track if EMP reset is available */
> u8 emp_reset_available;
> +
> + /* Bitmask of components seen in this image (ICE_FWU_COMP_*) */
> + u8 seen_components;
> + bool erot_present;
> };
>
> /**
> @@ -108,6 +121,9 @@ ice_check_component_response(struct ice_pf *pf, u16 id,
> u8 response, u8 code,
> case NVM_COMP_ID_NETLIST:
> component = "fw.netlist";
> break;
> + case NVM_COMP_ID_MANIFEST:
> + component = "fw.pqc_manifest";
> + break;
> default:
> WARN(1, "Unexpected unknown component identifier 0x%02x", id);
> return -EINVAL;
> @@ -220,12 +236,26 @@ ice_send_component_table(struct pldmfw *context, struct
> pldmfw_component *compon
> struct ice_pf *pf = priv->pf;
> struct ice_hw *hw = &pf->hw;
> size_t length;
> + u8 comp_bit;
> int status;
>
> switch (component->identifier) {
> case NVM_COMP_ID_OROM:
> + comp_bit = ICE_FWU_COMP_OROM;
> + break;
> case NVM_COMP_ID_NVM:
> + comp_bit = ICE_FWU_COMP_NVM;
> + break;
> case NVM_COMP_ID_NETLIST:
> + comp_bit = ICE_FWU_COMP_NETLIST;
> + break;
> + case NVM_COMP_ID_MANIFEST:
> + if (!priv->erot_present) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Manifest component requires an eRoT
> capable adapter");
> + return -EOPNOTSUPP;
> + }
> + comp_bit = ICE_FWU_COMP_MANIFEST;
> break;
> default:
> dev_err(dev, "Unable to update due to a firmware component with
> unknown ID %u\n",
> @@ -234,6 +264,19 @@ ice_send_component_table(struct pldmfw *context, struct
> pldmfw_component *compon
> return -EOPNOTSUPP;
> }
>
> + if (priv->erot_present) {
> + if (priv->seen_components & comp_bit) {
> + NL_SET_ERR_MSG_MOD(extack, "Duplicate component in PLDM
> image");
> + return -EINVAL;
> + }
> + /* the PLDM parser accepts a component without any data */
> + if (!component->component_size) {
> + NL_SET_ERR_MSG_MOD(extack, "Firmware component carries
> no data to flash");
> + return -EINVAL;
> + }
> + priv->seen_components |= comp_bit;
> + }
> +
> length = struct_size(comp_tbl, cvs, component->version_len);
> comp_tbl = kzalloc(length, GFP_KERNEL);
> if (!comp_tbl)
> @@ -623,6 +666,25 @@ ice_switch_flash_banks(struct ice_pf *pf, u8
> activate_flags,
> return 0;
> }
>
> +/**
> + * ice_fwu_check_erot - reject an eRoT update that misses a component
> + * @priv: PLDM firmware update private data
> + *
> + * An eRoT authenticates the complete NVM image, so all four components
> + * must be updated together.
> + *
> + * Returns: zero when the update may proceed, -EINVAL otherwise.
> + */
> +static int ice_fwu_check_erot(struct ice_fwu_priv *priv)
> +{
> + if (!priv->erot_present || priv->seen_components ==
> ICE_FWU_COMP_ALL_EROT)
> + return 0;
> +
> + NL_SET_ERR_MSG_MOD(priv->extack,
> + "eRoT adapter requires fw.mgmt, fw.undi, fw.netlist
> and fw.pqc_manifest");
> + return -EINVAL;
> +}
> +
> /**
> * ice_flash_component - Flash a component of the NVM
> * @context: PLDM fw update structure
> @@ -667,6 +729,13 @@ ice_flash_component(struct pldmfw *context, struct
> pldmfw_component *component)
> reset_level = NULL;
> name = "fw.netlist";
> break;
> + case NVM_COMP_ID_MANIFEST:
> + module = ICE_SR_PQC_MANIFEST_BANK_PTR;
> + /* the activation select mask has no bit for the manifest */
> + flag = 0;
> + reset_level = NULL;
> + name = "fw.pqc_manifest";
> + break;
> default:
> /* This should not trigger, since we check the id before
> * sending the component table to firmware.
> @@ -676,6 +745,13 @@ ice_flash_component(struct pldmfw *context, struct
> pldmfw_component *component)
> return -EINVAL;
> }
>
> + /* every component table is sent before the first component is flashed,
> + * so all components of the image are already known here
> + */
> + err = ice_fwu_check_erot(priv);
> + if (err)
> + return err;
> +
> /* Mark this component for activating at the end */
> priv->activate_flags |= flag;
>
> @@ -706,6 +782,11 @@ static int ice_finalize_update(struct pldmfw *context)
> struct devlink *devlink;
> int err;
>
> + /* an image without any applicable component still reaches this point */
> + err = ice_fwu_check_erot(priv);
> + if (err)
> + return err;
> +
> /* Finally, notify firmware to activate the written NVM banks */
> err = ice_switch_flash_banks(pf, priv->activate_flags,
> &priv->emp_reset_available, extack);
> @@ -843,6 +924,39 @@ static const struct pldmfw_ops ice_fwu_ops_e822 = {
> .finalize_update = &ice_finalize_update,
> };
>
> +/**
> + * ice_pending_updates_from_caps - Report the pending updates of a caps read
> + * @pf: the PF driver structure
> + * @pending: on return, bitmap of updates pending
> + * @dev_caps: device capabilities holding the pending flags
> + *
> + * Build the bitmap of flash components with an update pending, from an
> + * already read set of device capabilities.
> + */
> +static void
> +ice_pending_updates_from_caps(struct ice_pf *pf, u8 *pending,
> + const struct ice_hw_dev_caps *dev_caps)
> +{
> + struct device *dev = ice_pf_to_dev(pf);
> +
> + *pending = 0;
> +
> + if (dev_caps->common_cap.nvm_update_pending_nvm) {
> + dev_info(dev, "The fw.mgmt flash component has a pending
> update\n");
> + *pending |= ICE_AQC_NVM_ACTIV_SEL_NVM;
> + }
> +
> + if (dev_caps->common_cap.nvm_update_pending_orom) {
> + dev_info(dev, "The fw.undi flash component has a pending
> update\n");
> + *pending |= ICE_AQC_NVM_ACTIV_SEL_OROM;
> + }
> +
> + if (dev_caps->common_cap.nvm_update_pending_netlist) {
> + dev_info(dev, "The fw.netlist flash component has a pending
> update\n");
> + *pending |= ICE_AQC_NVM_ACTIV_SEL_NETLIST;
> + }
> +}
> +
> /**
> * ice_get_pending_updates - Check if the component has a pending update
> * @pf: the PF driver structure
> @@ -857,7 +971,6 @@ static const struct pldmfw_ops ice_fwu_ops_e822 = {
> int ice_get_pending_updates(struct ice_pf *pf, u8 *pending,
> struct netlink_ext_ack *extack)
> {
> - struct device *dev = ice_pf_to_dev(pf);
> struct ice_hw_dev_caps *dev_caps;
> struct ice_hw *hw = &pf->hw;
> int err;
> @@ -878,22 +991,7 @@ int ice_get_pending_updates(struct ice_pf *pf, u8
> *pending,
> return err;
> }
>
> - *pending = 0;
> -
> - if (dev_caps->common_cap.nvm_update_pending_nvm) {
> - dev_info(dev, "The fw.mgmt flash component has a pending
> update\n");
> - *pending |= ICE_AQC_NVM_ACTIV_SEL_NVM;
> - }
> -
> - if (dev_caps->common_cap.nvm_update_pending_orom) {
> - dev_info(dev, "The fw.undi flash component has a pending
> update\n");
> - *pending |= ICE_AQC_NVM_ACTIV_SEL_OROM;
> - }
> -
> - if (dev_caps->common_cap.nvm_update_pending_netlist) {
> - dev_info(dev, "The fw.netlist flash component has a pending
> update\n");
> - *pending |= ICE_AQC_NVM_ACTIV_SEL_NETLIST;
> - }
> + ice_pending_updates_from_caps(pf, pending, dev_caps);
>
> kfree(dev_caps);
>
> @@ -904,6 +1002,7 @@ int ice_get_pending_updates(struct ice_pf *pf, u8
> *pending,
> * ice_cancel_pending_update - Cancel any pending update for a component
> * @pf: the PF driver structure
> * @component: if not NULL, the name of the component being updated
> + * @dev_caps: device capabilities to read the pending updates from
> * @extack: Netlink extended ACK structure
> *
> * Cancel any pending update for the specified component. If component is
> @@ -913,6 +1012,7 @@ int ice_get_pending_updates(struct ice_pf *pf, u8
> *pending,
> */
> static int
> ice_cancel_pending_update(struct ice_pf *pf, const char *component,
> + const struct ice_hw_dev_caps *dev_caps,
> struct netlink_ext_ack *extack)
> {
> struct devlink *devlink = priv_to_devlink(pf);
> @@ -921,9 +1021,7 @@ ice_cancel_pending_update(struct ice_pf *pf, const char
> *component,
> u8 pending;
> int err;
>
> - err = ice_get_pending_updates(pf, &pending, extack);
> - if (err)
> - return err;
> + ice_pending_updates_from_caps(pf, &pending, dev_caps);
>
> /* If the flash_update request is for a specific component, ignore all
> * of the other components.
> @@ -981,9 +1079,10 @@ ice_cancel_pending_update(struct ice_pf *pf, const char
> *component,
> * formatted image that matches this device.
> *
> * Extract the device record Package Data and Component Tables and send them
> - * to the firmware. Extract and write the flash data for each of the three
> - * main flash components, "fw.mgmt", "fw.undi", and "fw.netlist". Notify
> - * firmware once the data is written to the inactive banks.
> + * to the firmware. Extract and write the flash data for each of the main
> + * flash components, "fw.mgmt", "fw.undi", and "fw.netlist", plus
> + * "fw.pqc_manifest" on eRoT adapters. Notify firmware once the data is
> + * written to the inactive banks.
> *
> * Returns: zero on success or a negative error code on failure.
> */
> @@ -994,6 +1093,7 @@ int ice_devlink_flash_update(struct devlink *devlink,
> struct ice_pf *pf = devlink_priv(devlink);
> struct device *dev = ice_pf_to_dev(pf);
> struct ice_hw *hw = &pf->hw;
> + struct ice_hw_dev_caps *dev_caps;
> struct ice_fwu_priv priv;
> u8 preservation;
> int err;
> @@ -1013,18 +1113,52 @@ int ice_devlink_flash_update(struct devlink *devlink,
> return -EOPNOTSUPP;
> }
>
> - if (!hw->dev_caps.common_cap.nvm_unified_update &&
> !ice_is_recovery_mode(hw)) {
> + dev_caps = kzalloc_obj(*dev_caps);
> + if (!dev_caps)
> + return -ENOMEM;
> +
> + /* Read the most recent device capabilities from firmware. Do not use
> + * the cached values in hw->dev_caps: a driver loaded while firmware
> + * was already in recovery mode never reads them, and a reset may be
> + * rebuilding them while this request runs. The same read serves the
> + * pending update check below, so this costs one AQ command per flash
> + * attempt, as it did before.
> + */
> + err = ice_discover_dev_caps(hw, dev_caps);
> + if (err) {
> + NL_SET_ERR_MSG_MOD(extack, "Unable to read device
> capabilities");
> + kfree(dev_caps);
> + return err;
> + }
> +
> + if (dev_caps->common_cap.external_pqc_rot_invalid) {
> + NL_SET_ERR_MSG_MOD(extack, "Invalid eRoT capability reported by
> firmware");
> + err = -EIO;
> + goto free_caps;
> + }
> +
> + if (!dev_caps->common_cap.nvm_unified_update &&
> !ice_is_recovery_mode(hw)) {
> NL_SET_ERR_MSG_MOD(extack, "Current firmware does not support
> unified update");
> - return -EOPNOTSUPP;
> + err = -EOPNOTSUPP;
> + goto free_caps;
> }
>
> memset(&priv, 0, sizeof(priv));
> + priv.erot_present = dev_caps->common_cap.external_pqc_rot_present;
> +
> + if (params->component && priv.erot_present) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "eRoT adapter does not support single
> component update");
> + err = -EOPNOTSUPP;
> + goto free_caps;
> + }
>
> if (params->component && strcmp(params->component, "fw.mgmt") == 0) {
> priv.context.mode = PLDMFW_UPDATE_MODE_SINGLE_COMPONENT;
> priv.context.component_identifier = NVM_COMP_ID_NVM;
> } else if (params->component) {
> - return -EOPNOTSUPP;
> + err = -EOPNOTSUPP;
> + goto free_caps;
> }
>
> /* the E822 device needs a slightly different ops */
> @@ -1039,7 +1173,8 @@ int ice_devlink_flash_update(struct devlink *devlink,
>
> devlink_flash_update_status_notify(devlink, "Preparing to flash", NULL,
> 0, 0);
>
> - err = ice_cancel_pending_update(pf, NULL, extack);
> + err = ice_cancel_pending_update(pf, NULL, dev_caps, extack);
> + kfree(dev_caps);
> if (err)
> return err;
>
> @@ -1066,4 +1201,8 @@ int ice_devlink_flash_update(struct devlink *devlink,
> ice_release_nvm(hw);
>
> return err;
> +
> +free_caps:
> + kfree(dev_caps);
> + return err;
> }
> diff --git a/drivers/net/ethernet/intel/ice/ice_type.h
> b/drivers/net/ethernet/intel/ice/ice_type.h
> index 1ff69f7..0fed29d 100644
> --- a/drivers/net/ethernet/intel/ice/ice_type.h
> +++ b/drivers/net/ethernet/intel/ice/ice_type.h
> @@ -1152,6 +1152,7 @@ struct ice_aq_get_set_rss_lut_params {
> #define ICE_SR_OROM_BANK_SIZE 0x45
> #define ICE_SR_NETLIST_BANK_PTR 0x46
> #define ICE_SR_NETLIST_BANK_SIZE 0x47
> +#define ICE_SR_PQC_MANIFEST_BANK_PTR 0x4E
> #define ICE_SR_SECTOR_SIZE_IN_WORDS 0x800
>
> /* CSS Header words */
Reviewed-by: Tomasz Lichwala <[email protected]>