xref: /linux/drivers/net/ethernet/amd/pds_core/fw.c (revision e96094c1)
149ce92fbSShannon Nelson // SPDX-License-Identifier: GPL-2.0
249ce92fbSShannon Nelson /* Copyright(c) 2023 Advanced Micro Devices, Inc */
349ce92fbSShannon Nelson 
449ce92fbSShannon Nelson #include "core.h"
549ce92fbSShannon Nelson 
649ce92fbSShannon Nelson /* The worst case wait for the install activity is about 25 minutes when
749ce92fbSShannon Nelson  * installing a new CPLD, which is very seldom.  Normal is about 30-35
849ce92fbSShannon Nelson  * seconds.  Since the driver can't tell if a CPLD update will happen we
949ce92fbSShannon Nelson  * set the timeout for the ugly case.
1049ce92fbSShannon Nelson  */
1149ce92fbSShannon Nelson #define PDSC_FW_INSTALL_TIMEOUT	(25 * 60)
1249ce92fbSShannon Nelson #define PDSC_FW_SELECT_TIMEOUT	30
1349ce92fbSShannon Nelson 
1449ce92fbSShannon Nelson /* Number of periodic log updates during fw file download */
1549ce92fbSShannon Nelson #define PDSC_FW_INTERVAL_FRACTION	32
1649ce92fbSShannon Nelson 
pdsc_devcmd_fw_download_locked(struct pdsc * pdsc,u64 addr,u32 offset,u32 length)1749ce92fbSShannon Nelson static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
1849ce92fbSShannon Nelson 					  u32 offset, u32 length)
1949ce92fbSShannon Nelson {
2049ce92fbSShannon Nelson 	union pds_core_dev_cmd cmd = {
2149ce92fbSShannon Nelson 		.fw_download.opcode = PDS_CORE_CMD_FW_DOWNLOAD,
2249ce92fbSShannon Nelson 		.fw_download.offset = cpu_to_le32(offset),
2349ce92fbSShannon Nelson 		.fw_download.addr = cpu_to_le64(addr),
2449ce92fbSShannon Nelson 		.fw_download.length = cpu_to_le32(length),
2549ce92fbSShannon Nelson 	};
2649ce92fbSShannon Nelson 	union pds_core_dev_comp comp;
2749ce92fbSShannon Nelson 
2849ce92fbSShannon Nelson 	return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
2949ce92fbSShannon Nelson }
3049ce92fbSShannon Nelson 
pdsc_devcmd_fw_install(struct pdsc * pdsc)3149ce92fbSShannon Nelson static int pdsc_devcmd_fw_install(struct pdsc *pdsc)
3249ce92fbSShannon Nelson {
3349ce92fbSShannon Nelson 	union pds_core_dev_cmd cmd = {
3449ce92fbSShannon Nelson 		.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
3549ce92fbSShannon Nelson 		.fw_control.oper = PDS_CORE_FW_INSTALL_ASYNC
3649ce92fbSShannon Nelson 	};
3749ce92fbSShannon Nelson 	union pds_core_dev_comp comp;
3849ce92fbSShannon Nelson 	int err;
3949ce92fbSShannon Nelson 
4049ce92fbSShannon Nelson 	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
4149ce92fbSShannon Nelson 	if (err < 0)
4249ce92fbSShannon Nelson 		return err;
4349ce92fbSShannon Nelson 
4449ce92fbSShannon Nelson 	return comp.fw_control.slot;
4549ce92fbSShannon Nelson }
4649ce92fbSShannon Nelson 
pdsc_devcmd_fw_activate(struct pdsc * pdsc,enum pds_core_fw_slot slot)4749ce92fbSShannon Nelson static int pdsc_devcmd_fw_activate(struct pdsc *pdsc,
4849ce92fbSShannon Nelson 				   enum pds_core_fw_slot slot)
4949ce92fbSShannon Nelson {
5049ce92fbSShannon Nelson 	union pds_core_dev_cmd cmd = {
5149ce92fbSShannon Nelson 		.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
5249ce92fbSShannon Nelson 		.fw_control.oper = PDS_CORE_FW_ACTIVATE_ASYNC,
5349ce92fbSShannon Nelson 		.fw_control.slot = slot
5449ce92fbSShannon Nelson 	};
5549ce92fbSShannon Nelson 	union pds_core_dev_comp comp;
5649ce92fbSShannon Nelson 
5749ce92fbSShannon Nelson 	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
5849ce92fbSShannon Nelson }
5949ce92fbSShannon Nelson 
pdsc_fw_status_long_wait(struct pdsc * pdsc,const char * label,unsigned long timeout,u8 fw_cmd,struct netlink_ext_ack * extack)6049ce92fbSShannon Nelson static int pdsc_fw_status_long_wait(struct pdsc *pdsc,
6149ce92fbSShannon Nelson 				    const char *label,
6249ce92fbSShannon Nelson 				    unsigned long timeout,
6349ce92fbSShannon Nelson 				    u8 fw_cmd,
6449ce92fbSShannon Nelson 				    struct netlink_ext_ack *extack)
6549ce92fbSShannon Nelson {
6649ce92fbSShannon Nelson 	union pds_core_dev_cmd cmd = {
6749ce92fbSShannon Nelson 		.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
6849ce92fbSShannon Nelson 		.fw_control.oper = fw_cmd,
6949ce92fbSShannon Nelson 	};
7049ce92fbSShannon Nelson 	union pds_core_dev_comp comp;
7149ce92fbSShannon Nelson 	unsigned long start_time;
7249ce92fbSShannon Nelson 	unsigned long end_time;
7349ce92fbSShannon Nelson 	int err;
7449ce92fbSShannon Nelson 
7549ce92fbSShannon Nelson 	/* Ping on the status of the long running async install
7649ce92fbSShannon Nelson 	 * command.  We get EAGAIN while the command is still
7749ce92fbSShannon Nelson 	 * running, else we get the final command status.
7849ce92fbSShannon Nelson 	 */
7949ce92fbSShannon Nelson 	start_time = jiffies;
8049ce92fbSShannon Nelson 	end_time = start_time + (timeout * HZ);
8149ce92fbSShannon Nelson 	do {
8249ce92fbSShannon Nelson 		err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
8349ce92fbSShannon Nelson 		msleep(20);
8449ce92fbSShannon Nelson 	} while (time_before(jiffies, end_time) &&
8549ce92fbSShannon Nelson 		 (err == -EAGAIN || err == -ETIMEDOUT));
8649ce92fbSShannon Nelson 
8749ce92fbSShannon Nelson 	if (err == -EAGAIN || err == -ETIMEDOUT) {
8849ce92fbSShannon Nelson 		NL_SET_ERR_MSG_MOD(extack, "Firmware wait timed out");
8949ce92fbSShannon Nelson 		dev_err(pdsc->dev, "DEV_CMD firmware wait %s timed out\n",
9049ce92fbSShannon Nelson 			label);
9149ce92fbSShannon Nelson 	} else if (err) {
9249ce92fbSShannon Nelson 		NL_SET_ERR_MSG_MOD(extack, "Firmware wait failed");
9349ce92fbSShannon Nelson 	}
9449ce92fbSShannon Nelson 
9549ce92fbSShannon Nelson 	return err;
9649ce92fbSShannon Nelson }
9749ce92fbSShannon Nelson 
pdsc_firmware_update(struct pdsc * pdsc,const struct firmware * fw,struct netlink_ext_ack * extack)9849ce92fbSShannon Nelson int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
9949ce92fbSShannon Nelson 			 struct netlink_ext_ack *extack)
10049ce92fbSShannon Nelson {
10149ce92fbSShannon Nelson 	u32 buf_sz, copy_sz, offset;
10249ce92fbSShannon Nelson 	struct devlink *dl;
10349ce92fbSShannon Nelson 	int next_interval;
10449ce92fbSShannon Nelson 	u64 data_addr;
10549ce92fbSShannon Nelson 	int err = 0;
10649ce92fbSShannon Nelson 	int fw_slot;
10749ce92fbSShannon Nelson 
10849ce92fbSShannon Nelson 	dev_info(pdsc->dev, "Installing firmware\n");
10949ce92fbSShannon Nelson 
110*e96094c1SBrett Creeley 	if (!pdsc->cmd_regs)
111*e96094c1SBrett Creeley 		return -ENXIO;
112*e96094c1SBrett Creeley 
11349ce92fbSShannon Nelson 	dl = priv_to_devlink(pdsc);
11449ce92fbSShannon Nelson 	devlink_flash_update_status_notify(dl, "Preparing to flash",
11549ce92fbSShannon Nelson 					   NULL, 0, 0);
11649ce92fbSShannon Nelson 
11749ce92fbSShannon Nelson 	buf_sz = sizeof(pdsc->cmd_regs->data);
11849ce92fbSShannon Nelson 
11949ce92fbSShannon Nelson 	dev_dbg(pdsc->dev,
12049ce92fbSShannon Nelson 		"downloading firmware - size %d part_sz %d nparts %lu\n",
12149ce92fbSShannon Nelson 		(int)fw->size, buf_sz, DIV_ROUND_UP(fw->size, buf_sz));
12249ce92fbSShannon Nelson 
12349ce92fbSShannon Nelson 	offset = 0;
12449ce92fbSShannon Nelson 	next_interval = 0;
12549ce92fbSShannon Nelson 	data_addr = offsetof(struct pds_core_dev_cmd_regs, data);
12649ce92fbSShannon Nelson 	while (offset < fw->size) {
12749ce92fbSShannon Nelson 		if (offset >= next_interval) {
12849ce92fbSShannon Nelson 			devlink_flash_update_status_notify(dl, "Downloading",
12949ce92fbSShannon Nelson 							   NULL, offset,
13049ce92fbSShannon Nelson 							   fw->size);
13149ce92fbSShannon Nelson 			next_interval = offset +
13249ce92fbSShannon Nelson 					(fw->size / PDSC_FW_INTERVAL_FRACTION);
13349ce92fbSShannon Nelson 		}
13449ce92fbSShannon Nelson 
13549ce92fbSShannon Nelson 		copy_sz = min_t(unsigned int, buf_sz, fw->size - offset);
13649ce92fbSShannon Nelson 		mutex_lock(&pdsc->devcmd_lock);
13749ce92fbSShannon Nelson 		memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz);
13849ce92fbSShannon Nelson 		err = pdsc_devcmd_fw_download_locked(pdsc, data_addr,
13949ce92fbSShannon Nelson 						     offset, copy_sz);
14049ce92fbSShannon Nelson 		mutex_unlock(&pdsc->devcmd_lock);
14149ce92fbSShannon Nelson 		if (err) {
14249ce92fbSShannon Nelson 			dev_err(pdsc->dev,
14349ce92fbSShannon Nelson 				"download failed offset 0x%x addr 0x%llx len 0x%x: %pe\n",
14449ce92fbSShannon Nelson 				offset, data_addr, copy_sz, ERR_PTR(err));
14549ce92fbSShannon Nelson 			NL_SET_ERR_MSG_MOD(extack, "Segment download failed");
14649ce92fbSShannon Nelson 			goto err_out;
14749ce92fbSShannon Nelson 		}
14849ce92fbSShannon Nelson 		offset += copy_sz;
14949ce92fbSShannon Nelson 	}
15049ce92fbSShannon Nelson 	devlink_flash_update_status_notify(dl, "Downloading", NULL,
15149ce92fbSShannon Nelson 					   fw->size, fw->size);
15249ce92fbSShannon Nelson 
15349ce92fbSShannon Nelson 	devlink_flash_update_timeout_notify(dl, "Installing", NULL,
15449ce92fbSShannon Nelson 					    PDSC_FW_INSTALL_TIMEOUT);
15549ce92fbSShannon Nelson 
15649ce92fbSShannon Nelson 	fw_slot = pdsc_devcmd_fw_install(pdsc);
15749ce92fbSShannon Nelson 	if (fw_slot < 0) {
15849ce92fbSShannon Nelson 		err = fw_slot;
15949ce92fbSShannon Nelson 		dev_err(pdsc->dev, "install failed: %pe\n", ERR_PTR(err));
16049ce92fbSShannon Nelson 		NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware install");
16149ce92fbSShannon Nelson 		goto err_out;
16249ce92fbSShannon Nelson 	}
16349ce92fbSShannon Nelson 
16449ce92fbSShannon Nelson 	err = pdsc_fw_status_long_wait(pdsc, "Installing",
16549ce92fbSShannon Nelson 				       PDSC_FW_INSTALL_TIMEOUT,
16649ce92fbSShannon Nelson 				       PDS_CORE_FW_INSTALL_STATUS,
16749ce92fbSShannon Nelson 				       extack);
16849ce92fbSShannon Nelson 	if (err)
16949ce92fbSShannon Nelson 		goto err_out;
17049ce92fbSShannon Nelson 
17149ce92fbSShannon Nelson 	devlink_flash_update_timeout_notify(dl, "Selecting", NULL,
17249ce92fbSShannon Nelson 					    PDSC_FW_SELECT_TIMEOUT);
17349ce92fbSShannon Nelson 
17449ce92fbSShannon Nelson 	err = pdsc_devcmd_fw_activate(pdsc, fw_slot);
17549ce92fbSShannon Nelson 	if (err) {
17649ce92fbSShannon Nelson 		NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware select");
17749ce92fbSShannon Nelson 		goto err_out;
17849ce92fbSShannon Nelson 	}
17949ce92fbSShannon Nelson 
18049ce92fbSShannon Nelson 	err = pdsc_fw_status_long_wait(pdsc, "Selecting",
18149ce92fbSShannon Nelson 				       PDSC_FW_SELECT_TIMEOUT,
18249ce92fbSShannon Nelson 				       PDS_CORE_FW_ACTIVATE_STATUS,
18349ce92fbSShannon Nelson 				       extack);
18449ce92fbSShannon Nelson 	if (err)
18549ce92fbSShannon Nelson 		goto err_out;
18649ce92fbSShannon Nelson 
18749ce92fbSShannon Nelson 	dev_info(pdsc->dev, "Firmware update completed, slot %d\n", fw_slot);
18849ce92fbSShannon Nelson 
18949ce92fbSShannon Nelson err_out:
19049ce92fbSShannon Nelson 	if (err)
19149ce92fbSShannon Nelson 		devlink_flash_update_status_notify(dl, "Flash failed",
19249ce92fbSShannon Nelson 						   NULL, 0, 0);
19349ce92fbSShannon Nelson 	else
19449ce92fbSShannon Nelson 		devlink_flash_update_status_notify(dl, "Flash done",
19549ce92fbSShannon Nelson 						   NULL, 0, 0);
19649ce92fbSShannon Nelson 	return err;
19749ce92fbSShannon Nelson }
198