// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2023 Advanced Micro Devices, Inc */ #include #include #include "core.h" /* The worst case wait for the install activity is about 25 minutes when * installing a new CPLD, which is very seldom. Normal is about 30-35 * seconds. Since the driver can't tell if a CPLD update will happen we * set the timeout for the ugly case. */ #define PDSC_FW_INSTALL_TIMEOUT (25 * 60) #define PDSC_FW_SELECT_TIMEOUT 30 /* Number of periodic log updates during fw file download */ #define PDSC_FW_INTERVAL_FRACTION 32 #define PDSC_FW_COMPONENT_PREFIX "fw." #define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \ (sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN) /* Driver-defined component type to name mapping. * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as "fw" without prefix. */ static const char * const pdsc_fw_type_names[] = { [PDS_CORE_FW_TYPE_MAIN] = NULL, [PDS_CORE_FW_TYPE_BOOT] = "bootloader", [PDS_CORE_FW_TYPE_CPLD] = "cpld", [PDS_CORE_FW_TYPE_SECURE] = "secure", [PDS_CORE_FW_TYPE_FPGA] = "fpga", [PDS_CORE_FW_TYPE_SUC_MAIN] = "suc", [PDS_CORE_FW_TYPE_SUC_BOOT] = "suc.bootloader", [PDS_CORE_FW_TYPE_UBOOT] = "uboot", }; const char *pdsc_fw_type_to_name(u8 type) { if (type < ARRAY_SIZE(pdsc_fw_type_names) && pdsc_fw_type_names[type]) return pdsc_fw_type_names[type]; return NULL; } void pdsc_fw_components_invalidate(struct pdsc *pdsc) { /* Pairs with READ_ONCE in pdsc_dl_component_info_get() */ WRITE_ONCE(pdsc->fw_components.num_components, 0); } static u8 pdsc_name_to_fw_type(const char *name) { size_t prefix_len; int i; /* "fw" without suffix maps to main firmware */ if (!strcmp(name, "fw")) return PDS_CORE_FW_TYPE_MAIN; prefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX); if (prefix_len) name += prefix_len; for (i = 1; i < ARRAY_SIZE(pdsc_fw_type_names); i++) { if (pdsc_fw_type_names[i] && !strcmp(name, pdsc_fw_type_names[i])) return i; } return 0; } static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr, u32 offset, u32 length) { union pds_core_dev_cmd cmd = { .fw_download.opcode = PDS_CORE_CMD_FW_DOWNLOAD, .fw_download.offset = cpu_to_le32(offset), .fw_download.addr = cpu_to_le64(addr), .fw_download.length = cpu_to_le32(length), }; union pds_core_dev_comp comp = {}; return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static int pdsc_devcmd_fw_install(struct pdsc *pdsc) { union pds_core_dev_cmd cmd = { .fw_control.opcode = PDS_CORE_CMD_FW_CONTROL, .fw_control.oper = PDS_CORE_FW_INSTALL_ASYNC }; union pds_core_dev_comp comp; int err; err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); if (err < 0) return err; return comp.fw_control.slot; } static int pdsc_devcmd_fw_activate(struct pdsc *pdsc, enum pds_core_fw_slot slot) { union pds_core_dev_cmd cmd = { .fw_control.opcode = PDS_CORE_CMD_FW_CONTROL, .fw_control.oper = PDS_CORE_FW_ACTIVATE_ASYNC, .fw_control.slot = slot }; union pds_core_dev_comp comp; return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static int pdsc_fw_status_long_wait(struct pdsc *pdsc, const char *label, unsigned long timeout, u8 fw_cmd, struct netlink_ext_ack *extack) { union pds_core_dev_cmd cmd = { .fw_control.opcode = PDS_CORE_CMD_FW_CONTROL, .fw_control.oper = fw_cmd, }; union pds_core_dev_comp comp; unsigned long start_time; unsigned long end_time; int err; /* Ping on the status of the long running async install * command. We get EAGAIN while the command is still * running, else we get the final command status. */ start_time = jiffies; end_time = start_time + (timeout * HZ); do { err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); msleep(20); } while (time_before(jiffies, end_time) && (err == -EAGAIN || err == -ETIMEDOUT)); if (err == -EAGAIN || err == -ETIMEDOUT) { NL_SET_ERR_MSG_MOD(extack, "Firmware wait timed out"); dev_err(pdsc->dev, "DEV_CMD firmware wait %s timed out\n", label); } else if (err) { NL_SET_ERR_MSG_MOD(extack, "Firmware wait failed"); } return err; } static int pdsc_legacy_firmware_update(struct pdsc *pdsc, struct devlink_flash_update_params *params, struct netlink_ext_ack *extack) { const struct firmware *fw = params->fw; u32 buf_sz, copy_sz, offset; struct devlink *dl; int next_interval; u64 data_addr; int err = 0; int fw_slot; if (params->component) { NL_SET_ERR_MSG_MOD(extack, "Component update not supported by this device"); return -EOPNOTSUPP; } dev_info(pdsc->dev, "Installing firmware\n"); if (!pdsc->cmd_regs) { NL_SET_ERR_MSG_MOD(extack, "BARs not mapped"); return -ENXIO; } dl = priv_to_devlink(pdsc); devlink_flash_update_status_notify(dl, "Preparing to flash", NULL, 0, 0); buf_sz = sizeof(pdsc->cmd_regs->data); dev_dbg(pdsc->dev, "downloading firmware - size %d part_sz %d nparts %lu\n", (int)fw->size, buf_sz, DIV_ROUND_UP(fw->size, buf_sz)); offset = 0; next_interval = 0; data_addr = offsetof(struct pds_core_dev_cmd_regs, data); while (offset < fw->size) { if (offset >= next_interval) { devlink_flash_update_status_notify(dl, "Downloading", NULL, offset, fw->size); next_interval = offset + (fw->size / PDSC_FW_INTERVAL_FRACTION); } copy_sz = min_t(unsigned int, buf_sz, fw->size - offset); mutex_lock(&pdsc->devcmd_lock); if (!pdsc->cmd_regs) { mutex_unlock(&pdsc->devcmd_lock); err = -ENXIO; NL_SET_ERR_MSG_MOD(extack, "Device reset during flash"); goto err_out; } memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz); err = pdsc_devcmd_fw_download_locked(pdsc, data_addr, offset, copy_sz); mutex_unlock(&pdsc->devcmd_lock); if (err) { dev_err(pdsc->dev, "download failed offset 0x%x addr 0x%llx len 0x%x: %pe\n", offset, data_addr, copy_sz, ERR_PTR(err)); NL_SET_ERR_MSG_MOD(extack, "Segment download failed"); goto err_out; } offset += copy_sz; } devlink_flash_update_status_notify(dl, "Downloading", NULL, fw->size, fw->size); devlink_flash_update_timeout_notify(dl, "Installing", NULL, PDSC_FW_INSTALL_TIMEOUT); fw_slot = pdsc_devcmd_fw_install(pdsc); if (fw_slot < 0) { err = fw_slot; dev_err(pdsc->dev, "install failed: %pe\n", ERR_PTR(err)); NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware install"); goto err_out; } err = pdsc_fw_status_long_wait(pdsc, "Installing", PDSC_FW_INSTALL_TIMEOUT, PDS_CORE_FW_INSTALL_STATUS, extack); if (err) goto err_out; devlink_flash_update_timeout_notify(dl, "Selecting", NULL, PDSC_FW_SELECT_TIMEOUT); err = pdsc_devcmd_fw_activate(pdsc, fw_slot); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware select"); goto err_out; } err = pdsc_fw_status_long_wait(pdsc, "Selecting", PDSC_FW_SELECT_TIMEOUT, PDS_CORE_FW_ACTIVATE_STATUS, extack); if (err) goto err_out; dev_info(pdsc->dev, "Firmware update completed, slot %d\n", fw_slot); err_out: if (err) devlink_flash_update_status_notify(dl, "Flash failed", NULL, 0, 0); else devlink_flash_update_status_notify(dl, "Flash done", NULL, 0, 0); return err; } struct pdsc_component_priv { u16 component_id; bool skip; struct list_head list_entry; }; struct pds_core_fwu_priv { struct pldmfw context; struct devlink_flash_update_params *params; struct netlink_ext_ack *extack; struct pdsc *pdsc; struct list_head components; bool component_found; }; static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv) { struct pdsc_component_priv *component_priv, *tmp; list_for_each_entry_safe(component_priv, tmp, &priv->components, list_entry) { list_del(&component_priv->list_entry); kfree(component_priv); } } static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type, u16 desc_size, const u8 *desc_data, u8 *match) { union pds_core_dev_cmd cmd = { .match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC, .match_record_desc.ver = 1, .match_record_desc.type = cpu_to_le16(desc_type), .match_record_desc.size = cpu_to_le16(desc_size), }; union pds_core_dev_comp comp = {}; int err; err = pdsc_devcmd_with_data(pdsc, &cmd, desc_data, desc_size, &comp, pdsc->devcmd_timeout); *match = comp.match_record_desc.match; return err; } static bool pdsc_match_record_descs(struct pldmfw *context, struct pldmfw_record *record) { struct pds_core_fwu_priv *priv = container_of(context, struct pds_core_fwu_priv, context); struct pdsc *pdsc = priv->pdsc; struct pldmfw_desc_tlv *desc; if (!pldmfw_op_pci_match_record(context, record)) return false; list_for_each_entry(desc, &record->descs, entry) { u8 match; int err; switch (desc->type) { /* skip types checked in pldmfw_op_pci_match_record */ case PLDM_DESC_ID_PCI_VENDOR_ID: case PLDM_DESC_ID_PCI_DEVICE_ID: case PLDM_DESC_ID_PCI_SUBVENDOR_ID: case PLDM_DESC_ID_PCI_SUBDEV_ID: continue; } if (!desc->size) return false; err = pdsc_devcmd_match_record_desc(pdsc, desc->type, desc->size, desc->data, &match); if (err) { dev_err(pdsc->dev, "match_record_desc failed type: 0x%04x size: %u, err %d\n", desc->type, desc->size, err); return false; } /* all record descriptors must match */ if (!match) return false; } return true; } static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr, u16 length, u16 offset, u16 total_len) { union pds_core_dev_cmd cmd = { .send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA, .send_pkg_data.ver = 1, .send_pkg_data.data_pa = cpu_to_le64(addr), .send_pkg_data.data_len = cpu_to_le16(length), .send_pkg_data.offset = cpu_to_le16(offset), .send_pkg_data.total_len = cpu_to_le16(total_len), }; union pds_core_dev_comp comp = {}; return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static int pdsc_send_package_data(struct pldmfw *context, const u8 *data, u16 length) { struct pds_core_fwu_priv *priv = container_of(context, struct pds_core_fwu_priv, context); struct pdsc_deferred_dma *deferred; struct device *dev = context->dev; struct pdsc *pdsc = priv->pdsc; dma_addr_t dma_addr; u8 *package_data; u32 offset; int err; if (!length) return 0; deferred = kmalloc_obj(*deferred); if (!deferred) return -ENOMEM; package_data = kmemdup(data, length, GFP_KERNEL); if (!package_data) { kfree(deferred); return -ENOMEM; } dma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE); if (dma_mapping_error(dev, dma_addr)) { dev_err(dev, "Failed to dma_map package_data length 0x%x\n", length); kfree(package_data); kfree(deferred); return -ENOMEM; } for (offset = 0; offset < length; offset += PDS_PAGE_SIZE) { u32 copy_sz; copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset); err = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset, copy_sz, offset, length); if (err) { NL_SET_ERR_MSG_MOD(priv->extack, "Failed to send package data"); break; } } if (err == -ETIMEDOUT || err == -EAGAIN) { pdsc_deferred_dma_add(pdsc, deferred, dma_addr, package_data, length, DMA_TO_DEVICE); return err; } kfree(deferred); dma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE); kfree(package_data); return err; } static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type) { int i; for (i = 0; i < pdsc->fw_components.num_components; i++) { if (pdsc->fw_components.info[i].component_type == type) return true; } return false; } static bool pdsc_component_id_matches_type(struct pdsc *pdsc, u8 component_id, u8 type) { int i; for (i = 0; i < pdsc->fw_components.num_components; i++) { struct pds_core_fw_component_info *info = &pdsc->fw_components.info[i]; if (info->identifier == component_id && info->component_type == type) return true; } return false; } static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id) { int i; for (i = 0; i < pdsc->fw_components.num_components; i++) { struct pds_core_fw_component_info *info = &pdsc->fw_components.info[i]; if (info->identifier == component_id) return info->component_type; } return 0; } static bool pdsc_skip_component(struct pds_core_fwu_priv *priv, u16 component_id) { struct pdsc_component_priv *component_priv; list_for_each_entry(component_priv, &priv->components, list_entry) { if (component_priv->component_id == component_id) return component_priv->skip; } return false; } static int pdsc_send_component_table(struct pldmfw *context, struct pldmfw_component *component, u8 transfer_flag) { struct pds_core_fwu_priv *priv = container_of(context, struct pds_core_fwu_priv, context); struct pds_core_component_tbl *component_tbl; struct pdsc_component_priv *component_priv; struct device *dev = context->dev; union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = {}; struct pdsc *pdsc = priv->pdsc; bool skip_component = false; u8 requested_type = 0; u16 buf_sz, tbl_sz; int err = 0; dev_dbg(dev, "component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\n", priv->params->component, component->classification, component->identifier, component->activation_method, component->version_len, component->version_len, component->version_string, component->index, component->component_size, transfer_flag); component_priv = kzalloc_obj(*component_priv); if (!component_priv) return -ENOMEM; if (priv->params->component) { requested_type = pdsc_name_to_fw_type(priv->params->component); if (component->identifier > U8_MAX || !pdsc_component_id_matches_type(pdsc, component->identifier, requested_type)) { skip_component = true; goto add_component_priv; } priv->component_found = true; } buf_sz = sizeof(pdsc->cmd_regs->data); tbl_sz = struct_size(component_tbl, version_str, component->version_len); if (tbl_sz > buf_sz) { dev_err(dev, "component_tbl size %d too big, max size: %d\n", tbl_sz, buf_sz); err = -ENOSPC; goto free_component_priv; } component_tbl = kzalloc(tbl_sz, GFP_KERNEL); if (!component_tbl) { err = -ENOMEM; goto free_component_priv; } component_tbl->comparison_stamp = cpu_to_le32(component->comparison_stamp); component_tbl->classification = cpu_to_le16(component->classification); component_tbl->identifier = cpu_to_le16(component->identifier); component_tbl->transfer_flag = transfer_flag; component_tbl->version_str_type = component->version_type; component_tbl->version_str_len = component->version_len; memcpy(component_tbl->version_str, component->version_string, component->version_len); cmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL; cmd.send_component_tbl.ver = 1; cmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID; err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz, &comp, pdsc->devcmd_timeout); kfree(component_tbl); if (err) { dev_err(dev, "Failed sending component table: %pe\n", ERR_PTR(err)); goto free_component_priv; } skip_component = comp.send_component_tbl.response == 1; add_component_priv: component_priv->skip = skip_component; component_priv->component_id = component->identifier; list_add(&component_priv->list_entry, &priv->components); return 0; free_component_priv: kfree(component_priv); return err; } int pdsc_get_component_info(struct pdsc *pdsc) { union pds_core_dev_cmd cmd = { .get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO, .get_component_info.ver = 1, }; struct pds_core_component_list_info *list_info; struct pdsc_deferred_dma *deferred; union pds_core_dev_comp comp = {}; dma_addr_t dma_addr; u8 num_components; int err, i; deferred = kmalloc_obj(*deferred); if (!deferred) return -ENOMEM; list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL); if (!list_info) { kfree(deferred); return -ENOMEM; } dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE, DMA_FROM_DEVICE); if (dma_mapping_error(pdsc->dev, dma_addr)) { dev_err(pdsc->dev, "Failed to dma_map component_list_info length %d\n", PDS_PAGE_SIZE); kfree(list_info); kfree(deferred); return -ENOMEM; } cmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE); cmd.get_component_info.data_pa = cpu_to_le64(dma_addr); err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2); if (err == -ETIMEDOUT || err == -EAGAIN) { pdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info, PDS_PAGE_SIZE, DMA_FROM_DEVICE); return err; } kfree(deferred); dma_unmap_single(pdsc->dev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE); if (err) goto out; if (comp.get_component_info.ver == 0) { /* Don't support backward compatibility as version 0 has * alignment issues, so give a hint to users to update * their firmware */ dev_warn_once(pdsc->dev, "Incompatible get_component_info version %u reported by firmware\n", comp.get_component_info.ver); err = 0; goto out; } num_components = list_info->num_components; if (num_components > PDS_CORE_FW_COMPONENT_LIST_LEN) { err = -ENOMEM; goto out; } pdsc->fw_components.num_components = num_components; for (i = 0; i < num_components; i++) { struct pds_core_fw_component_info *info = &pdsc->fw_components.info[i]; memcpy(info, &list_info->info[i], sizeof(*info)); info->version[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0; info->name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0; } out: kfree(list_info); return err; } static int pdsc_devcmd_send_component(struct pdsc *pdsc, struct pds_core_flash_component *info, u16 info_sz, dma_addr_t addr, u32 length, u32 offset, u16 slot_id, union pds_core_dev_comp *comp) { union pds_core_dev_cmd cmd = { .send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT, .send_component.ver = 1, .send_component.operation = PDS_CORE_SEND_COMPONENT_START, .send_component.data_pa = cpu_to_le64(addr), .send_component.data_len = cpu_to_le32(length), .send_component.offset = cpu_to_le32(offset), .send_component.slot_id = slot_id, }; unsigned long timeout = 300 * HZ; unsigned long start_time; unsigned long end_time; int err; start_time = jiffies; end_time = start_time + timeout; do { /* prevent noisy/benign devcmd failures */ err = pdsc_devcmd_with_data_nomsg(pdsc, &cmd, info, info_sz, comp, 60); if (err != -EAGAIN) break; /* if required, subsequent commands check status of * PDS_CORE_CMD_SEND_COMPONENT command, which returns * EAGAIN while the command is still running, * else we get the final command status. */ cmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS; msleep(20); } while (time_before(jiffies, end_time)); if (err == -EAGAIN || err == -ETIMEDOUT) dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n"); return err; } static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev, struct pds_core_flash_component *info, u16 info_sz, const u8 *data, u16 copy_sz, u32 offset, u8 slot_id, union pds_core_dev_comp *comp) { struct pdsc_deferred_dma *deferred; dma_addr_t dma_addr; u8 *component_data; int err; deferred = kmalloc_obj(*deferred); if (!deferred) return -ENOMEM; component_data = kmemdup(data, copy_sz, GFP_KERNEL); if (!component_data) { kfree(deferred); return -ENOMEM; } dma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE); if (dma_mapping_error(dev, dma_addr)) { dev_err(dev, "Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n", offset, copy_sz); kfree(component_data); kfree(deferred); return -ENOMEM; } err = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr, copy_sz, offset, slot_id, comp); if (err == -ETIMEDOUT || err == -EAGAIN) { pdsc_deferred_dma_add(pdsc, deferred, dma_addr, component_data, copy_sz, DMA_TO_DEVICE); return err; } kfree(deferred); dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE); kfree(component_data); return err; } static int pdsc_flash_component(struct pldmfw *context, struct pldmfw_component *component) { char component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN]; struct pds_core_fwu_priv *priv = container_of(context, struct pds_core_fwu_priv, context); struct pds_core_flash_component *component_info; const char *component_name = NULL; struct device *dev = context->dev; struct pdsc *pdsc = priv->pdsc; u16 buf_sz, info_sz; struct devlink *dl; u8 component_type; u32 total_len; u32 offset; int err; component_type = pdsc_get_component_type_by_id(pdsc, component->identifier); if (component_type) { const char *type_name = pdsc_fw_type_to_name(component_type); if (component_type == PDS_CORE_FW_TYPE_MAIN) { component_name = "fw"; } else if (type_name) { snprintf(component_name_buf, sizeof(component_name_buf), "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name); component_name = component_name_buf; } } dl = priv_to_devlink(pdsc); if (pdsc_skip_component(priv, component->identifier)) { devlink_flash_update_status_notify(dl, "Skipped", component_name, 0, 0); return 0; } total_len = component->component_size; dev_dbg(dev, "component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\n", component_name ?: "(unknown)", component->classification, component->identifier, component->activation_method, component->version_len, component->version_string, component->index, component->component_size); buf_sz = sizeof(pdsc->cmd_regs->data); info_sz = struct_size(component_info, version_str, component->version_len); if (info_sz > buf_sz) { dev_err(dev, "component_info size %d too big, max size: %d\n", info_sz, buf_sz); return -ENOSPC; } component_info = vzalloc(info_sz); if (!component_info) return -ENOMEM; component_info->comparison_stamp = cpu_to_le32(component->comparison_stamp); component_info->image_size = cpu_to_le32(total_len); component_info->classification = cpu_to_le16(component->classification); component_info->identifier = cpu_to_le16(component->identifier); component_info->options = cpu_to_le16(component->options); component_info->version_str_type = component->version_type; component_info->version_str_len = component->version_len; memcpy(component_info->version_str, component->version_string, component->version_len); offset = 0; while (offset < total_len) { union pds_core_dev_comp comp = {}; u16 copy_sz; copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, total_len - offset); err = pdsc_flash_component_chunk(pdsc, dev, component_info, info_sz, component->component_data + offset, copy_sz, offset, PDS_CORE_FW_SLOT_INVALID, &comp); if (err && comp.send_component.compat_response && (comp.send_component.compat_response_code == PDS_CORE_COMPONENT_STAMP_IDENTICAL || comp.send_component.compat_response_code == PDS_CORE_COMPONENT_STAMP_LOWER)) { err = 0; devlink_flash_update_status_notify(dl, "Skipped", component_name, 0, 0); goto skip_component; } if (err) { NL_SET_ERR_MSG_MOD(priv->extack, "Failed to flash component"); goto err_out; } offset += copy_sz; devlink_flash_update_status_notify(dl, "Erasing/Flashing", component_name, offset, total_len); } vfree(component_info); return 0; err_out: devlink_flash_update_status_notify(dl, "Erasing/Flashing Component Failed", component_name, 0, 0); skip_component: vfree(component_info); return err; } static int pdsc_devcmd_finalize_update(struct pdsc *pdsc) { union pds_core_dev_cmd cmd = { .finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE, .finalize_update.ver = 1, }; union pds_core_dev_comp comp = {}; return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static int pdsc_finalize_update(struct pldmfw *context) { struct pds_core_fwu_priv *priv = container_of(context, struct pds_core_fwu_priv, context); const char *component_name = priv->params->component; unsigned long start_time, end_time; struct device *dev = context->dev; struct pdsc *pdsc = priv->pdsc; struct devlink *dl; int err; dl = priv_to_devlink(pdsc); start_time = jiffies; end_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ); do { err = pdsc_devcmd_finalize_update(pdsc); if (err != -EAGAIN) break; dev_dbg(dev, "retrying finalize_update: %pe\n", ERR_PTR(err)); msleep(20); } while (time_before(jiffies, end_time) && err == -EAGAIN); if (err) { devlink_flash_update_status_notify(dl, "Finalize Update Failed", component_name, 0, 0); NL_SET_ERR_MSG_MOD(priv->extack, "Finalize update failed"); return err; } devlink_flash_update_status_notify(dl, "Finalized Update", component_name, 0, 0); return 0; } static const struct pldmfw_ops pdsc_pldmfw_ops = { .match_record = pdsc_match_record_descs, .send_package_data = pdsc_send_package_data, .send_component_table = pdsc_send_component_table, .flash_component = pdsc_flash_component, .finalize_update = pdsc_finalize_update }; static int pdsc_pldm_firmware_update(struct pdsc *pdsc, struct devlink_flash_update_params *params, struct netlink_ext_ack *extack, const struct firmware *fw) { struct pds_core_fwu_priv priv = {}; int err; if (!pdsc->fw_components.num_components) { err = pdsc_get_component_info(pdsc); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to get component info"); return err; } } if (params->component) { u8 type = pdsc_name_to_fw_type(params->component); if (!type || !pdsc_component_type_exists(pdsc, type)) { NL_SET_ERR_MSG_MOD(extack, "Unknown component name"); return -ENOENT; } } INIT_LIST_HEAD(&priv.components); priv.context.ops = &pdsc_pldmfw_ops; priv.context.dev = pdsc->dev; priv.params = params; priv.extack = extack; priv.pdsc = pdsc; err = pldmfw_flash_image(&priv.context, fw); if (!err && params->component && !priv.component_found) { NL_SET_ERR_MSG_MOD(extack, "Requested component not present in firmware package"); err = -ENOENT; } pdsc_free_fwu_priv(&priv); return err; } int pdsc_firmware_update(struct pdsc *pdsc, struct devlink_flash_update_params *params, struct netlink_ext_ack *extack) { int err; if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 && pdsc->dev_ident.capabilities & cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE)) err = pdsc_pldm_firmware_update(pdsc, params, extack, params->fw); else err = pdsc_legacy_firmware_update(pdsc, params, extack); /* Invalidate cached component info so next info_get refreshes */ pdsc_fw_components_invalidate(pdsc); return err; }