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]>

Reply via email to