| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright(c) 2023 Advanced Micro Devices, Inc */ |
| |
| #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 |
| |
| 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; |
| } |
| |
| int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, |
| struct netlink_ext_ack *extack) |
| { |
| u32 buf_sz, copy_sz, offset; |
| struct devlink *dl; |
| int next_interval; |
| u64 data_addr; |
| int err = 0; |
| int fw_slot; |
| |
| dev_info(pdsc->dev, "Installing firmware\n"); |
| |
| if (!pdsc->cmd_regs) |
| 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); |
| 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; |
| } |