diff options
Diffstat (limited to 'drivers/net/wwan/iosm/iosm_ipc_flash.c')
| -rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_flash.c | 594 | 
1 files changed, 594 insertions, 0 deletions
| diff --git a/drivers/net/wwan/iosm/iosm_ipc_flash.c b/drivers/net/wwan/iosm/iosm_ipc_flash.c new file mode 100644 index 000000000000..d890914aa349 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_flash.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#include "iosm_ipc_coredump.h" +#include "iosm_ipc_devlink.h" +#include "iosm_ipc_flash.h" + +/* This function will pack the data to be sent to the modem using the + * payload, payload length and pack id + */ +static int ipc_flash_proc_format_ebl_pack(struct iosm_flash_data *flash_req, +					  u32 pack_length, u16 pack_id, +					  u8 *payload, u32 payload_length) +{ +	u16 checksum = pack_id; +	u32 i; + +	if (payload_length + IOSM_EBL_HEAD_SIZE > pack_length) +		return -EINVAL; + +	flash_req->pack_id = cpu_to_le16(pack_id); +	flash_req->msg_length = cpu_to_le32(payload_length); +	checksum += (payload_length >> IOSM_EBL_PAYL_SHIFT) + +		     (payload_length & IOSM_EBL_CKSM); + +	for (i = 0; i < payload_length; i++) +		checksum += payload[i]; + +	flash_req->checksum = cpu_to_le16(checksum); + +	return 0; +} + +/* validate the response received from modem and + * check the type of errors received + */ +static int ipc_flash_proc_check_ebl_rsp(void *hdr_rsp, void *payload_rsp) +{ +	struct iosm_ebl_error  *err_info = payload_rsp; +	u16 *rsp_code = hdr_rsp; +	u32 i; + +	if (*rsp_code == IOSM_EBL_RSP_BUFF) { +		for (i = 0; i < IOSM_MAX_ERRORS; i++) { +			if (!err_info->error[i].error_code) { +				pr_err("EBL: error_class = %d, error_code = %d", +				       err_info->error[i].error_class, +				       err_info->error[i].error_code); +			} +		} +		return -EINVAL; +	} + +	return 0; +} + +/* Send data to the modem */ +static int ipc_flash_send_data(struct iosm_devlink *ipc_devlink, u32 size, +			       u16 pack_id, u8 *payload, u32 payload_length) +{ +	struct iosm_flash_data flash_req; +	int ret; + +	ret = ipc_flash_proc_format_ebl_pack(&flash_req, size, +					     pack_id, payload, payload_length); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL2 pack failed for pack_id:%d", +			pack_id); +		goto ipc_free_payload; +	} + +	ret = ipc_imem_sys_devlink_write(ipc_devlink, (u8 *)&flash_req, +					 IOSM_EBL_HEAD_SIZE); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL Header write failed for Id:%x", +			pack_id); +		goto ipc_free_payload; +	} + +	ret = ipc_imem_sys_devlink_write(ipc_devlink, payload, payload_length); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL Payload write failed for Id:%x", +			pack_id); +	} + +ipc_free_payload: +	return ret; +} + +/** + * ipc_flash_link_establish - Flash link establishment + * @ipc_imem:           Pointer to struct iosm_imem + * + * Returns:     0 on success and failure value on error + */ +int ipc_flash_link_establish(struct iosm_imem *ipc_imem) +{ +	u8 ler_data[IOSM_LER_RSP_SIZE]; +	u32 bytes_read; + +	/* Allocate channel for flashing/cd collection */ +	ipc_imem->ipc_devlink->devlink_sio.channel = +					ipc_imem_sys_devlink_open(ipc_imem); + +	if (!ipc_imem->ipc_devlink->devlink_sio.channel) +		goto chl_open_fail; + +	if (ipc_imem_sys_devlink_read(ipc_imem->ipc_devlink, ler_data, +				      IOSM_LER_RSP_SIZE, &bytes_read)) +		goto devlink_read_fail; + +	if (bytes_read != IOSM_LER_RSP_SIZE) +		goto devlink_read_fail; + +	return 0; + +devlink_read_fail: +	ipc_imem_sys_devlink_close(ipc_imem->ipc_devlink); +chl_open_fail: +	return -EIO; +} + +/* Receive data from the modem */ +static int ipc_flash_receive_data(struct iosm_devlink *ipc_devlink, u32 size, +				  u8 *mdm_rsp) +{ +	u8 mdm_rsp_hdr[IOSM_EBL_HEAD_SIZE]; +	u32 bytes_read; +	int ret; + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, mdm_rsp_hdr, +					IOSM_EBL_HEAD_SIZE, &bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL rsp to read %d bytes failed", +			IOSM_EBL_HEAD_SIZE); +		goto ipc_flash_recv_err; +	} + +	if (bytes_read != IOSM_EBL_HEAD_SIZE) { +		ret = -EINVAL; +		goto ipc_flash_recv_err; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, mdm_rsp, size, +					&bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL rsp to read %d bytes failed", +			size); +		goto ipc_flash_recv_err; +	} + +	if (bytes_read != size) { +		ret = -EINVAL; +		goto ipc_flash_recv_err; +	} + +	ret = ipc_flash_proc_check_ebl_rsp(mdm_rsp_hdr + 2, mdm_rsp); + +ipc_flash_recv_err: +	return ret; +} + +/* Function to send command to modem and receive response */ +static int ipc_flash_send_receive(struct iosm_devlink *ipc_devlink, u16 pack_id, +				  u8 *payload, u32 payload_length, u8 *mdm_rsp) +{ +	size_t frame_len = IOSM_EBL_DW_PACK_SIZE; +	int ret; + +	if (pack_id == FLASH_SET_PROT_CONF) +		frame_len = IOSM_EBL_W_PACK_SIZE; + +	ret = ipc_flash_send_data(ipc_devlink, frame_len, pack_id, payload, +				  payload_length); +	if (ret) +		goto ipc_flash_send_rcv; + +	ret = ipc_flash_receive_data(ipc_devlink, +				     frame_len - IOSM_EBL_HEAD_SIZE, mdm_rsp); + +ipc_flash_send_rcv: +	return ret; +} + +/** + * ipc_flash_boot_set_capabilities  - Set modem boot capabilities in flash + * @ipc_devlink:        Pointer to devlink structure + * @mdm_rsp:            Pointer to modem response buffer + * + * Returns:             0 on success and failure value on error + */ +int ipc_flash_boot_set_capabilities(struct iosm_devlink *ipc_devlink, +				    u8 *mdm_rsp) +{ +	ipc_devlink->ebl_ctx.ebl_sw_info_version = +			ipc_devlink->ebl_ctx.m_ebl_resp[EBL_RSP_SW_INFO_VER]; +	ipc_devlink->ebl_ctx.m_ebl_resp[EBL_SKIP_ERASE] = IOSM_CAP_NOT_ENHANCED; +	ipc_devlink->ebl_ctx.m_ebl_resp[EBL_SKIP_CRC] = IOSM_CAP_NOT_ENHANCED; + +	if (ipc_devlink->ebl_ctx.m_ebl_resp[EBL_CAPS_FLAG] & +							IOSM_CAP_USE_EXT_CAP) { +		if (ipc_devlink->param.erase_full_flash) +			ipc_devlink->ebl_ctx.m_ebl_resp[EBL_OOS_CONFIG] &= +				~((u8)IOSM_EXT_CAP_ERASE_ALL); +		else +			ipc_devlink->ebl_ctx.m_ebl_resp[EBL_OOS_CONFIG] &= +				~((u8)IOSM_EXT_CAP_COMMIT_ALL); +		ipc_devlink->ebl_ctx.m_ebl_resp[EBL_EXT_CAPS_HANDLED] = +				IOSM_CAP_USE_EXT_CAP; +	} + +	/* Write back the EBL capability to modem +	 * Request Set Protcnf command +	 */ +	return ipc_flash_send_receive(ipc_devlink, FLASH_SET_PROT_CONF, +				     ipc_devlink->ebl_ctx.m_ebl_resp, +				     IOSM_EBL_RSP_SIZE, mdm_rsp); +} + +/* Read the SWID type and SWID value from the EBL */ +int ipc_flash_read_swid(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp) +{ +	struct iosm_flash_msg_control cmd_msg; +	struct iosm_swid_table *swid; +	char ebl_swid[IOSM_SWID_STR]; +	int ret; + +	if (ipc_devlink->ebl_ctx.ebl_sw_info_version != +			IOSM_EXT_CAP_SWID_OOS_PACK) +		return -EINVAL; + +	cmd_msg.action = cpu_to_le32(FLASH_OOSC_ACTION_READ); +	cmd_msg.type = cpu_to_le32(FLASH_OOSC_TYPE_SWID_TABLE); +	cmd_msg.length = cpu_to_le32(IOSM_MSG_LEN_ARG); +	cmd_msg.arguments = cpu_to_le32(IOSM_MSG_LEN_ARG); + +	ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_CONTROL, +				     (u8 *)&cmd_msg, IOSM_MDM_SEND_16, mdm_rsp); +	if (ret) +		goto ipc_swid_err; + +	cmd_msg.action = cpu_to_le32(*((u32 *)mdm_rsp)); + +	ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_DATA_READ, +				     (u8 *)&cmd_msg, IOSM_MDM_SEND_4, mdm_rsp); +	if (ret) +		goto ipc_swid_err; + +	swid = (struct iosm_swid_table *)mdm_rsp; +	dev_dbg(ipc_devlink->dev, "SWID %x RF_ENGINE_ID %x", swid->sw_id_val, +		swid->rf_engine_id_val); + +	snprintf(ebl_swid, sizeof(ebl_swid), "SWID: %x, RF_ENGINE_ID: %x", +		 swid->sw_id_val, swid->rf_engine_id_val); + +	devlink_flash_update_status_notify(ipc_devlink->devlink_ctx, ebl_swid, +					   NULL, 0, 0); +ipc_swid_err: +	return ret; +} + +/* Function to check if full erase or conditional erase was successful */ +static int ipc_flash_erase_check(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp) +{ +	int ret, count = 0; +	u16 mdm_rsp_data; + +	/* Request Flash Erase Check */ +	do { +		mdm_rsp_data = IOSM_MDM_SEND_DATA; +		ret = ipc_flash_send_receive(ipc_devlink, FLASH_ERASE_CHECK, +					     (u8 *)&mdm_rsp_data, +					     IOSM_MDM_SEND_2, mdm_rsp); +		if (ret) +			goto ipc_erase_chk_err; + +		mdm_rsp_data = *((u16 *)mdm_rsp); +		if (mdm_rsp_data > IOSM_MDM_ERASE_RSP) { +			dev_err(ipc_devlink->dev, +				"Flash Erase Check resp wrong 0x%04X", +				mdm_rsp_data); +			ret = -EINVAL; +			goto ipc_erase_chk_err; +		} +		count++; +		msleep(IOSM_FLASH_ERASE_CHECK_INTERVAL); +	} while ((mdm_rsp_data != IOSM_MDM_ERASE_RSP) && +		(count < (IOSM_FLASH_ERASE_CHECK_TIMEOUT / +		IOSM_FLASH_ERASE_CHECK_INTERVAL))); + +	if (mdm_rsp_data != IOSM_MDM_ERASE_RSP) { +		dev_err(ipc_devlink->dev, "Modem erase check timeout failure!"); +		ret = -ETIMEDOUT; +	} + +ipc_erase_chk_err: +	return ret; +} + +/* Full erase function which will erase the nand flash through EBL command */ +static int ipc_flash_full_erase(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp) +{ +	u32 erase_address = IOSM_ERASE_START_ADDR; +	struct iosm_flash_msg_control cmd_msg; +	u32 erase_length = IOSM_ERASE_LEN; +	int ret; + +	dev_dbg(ipc_devlink->dev, "Erase full nand flash"); +	cmd_msg.action = cpu_to_le32(FLASH_OOSC_ACTION_ERASE); +	cmd_msg.type = cpu_to_le32(FLASH_OOSC_TYPE_ALL_FLASH); +	cmd_msg.length = cpu_to_le32(erase_length); +	cmd_msg.arguments = cpu_to_le32(erase_address); + +	ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_CONTROL, +				     (unsigned char *)&cmd_msg, +				     IOSM_MDM_SEND_16, mdm_rsp); +	if (ret) +		goto ipc_flash_erase_err; + +	ipc_devlink->param.erase_full_flash_done = IOSM_SET_FLAG; +	ret = ipc_flash_erase_check(ipc_devlink, mdm_rsp); + +ipc_flash_erase_err: +	return ret; +} + +/* Logic for flashing all the Loadmaps available for individual fls file */ +static int ipc_flash_download_region(struct iosm_devlink *ipc_devlink, +				     const struct firmware *fw, u8 *mdm_rsp) +{ +	u32 raw_len, rest_len = fw->size - IOSM_DEVLINK_HDR_SIZE; +	struct iosm_devlink_image *fls_data; +	__le32 reg_info[2]; /* 0th position region address, 1st position size */ +	u32 nand_address; +	char *file_ptr; +	int ret; + +	fls_data = (struct iosm_devlink_image *)fw->data; +	file_ptr = (void *)(fls_data + 1); +	nand_address = le32_to_cpu(fls_data->region_address); +	reg_info[0] = cpu_to_le32(nand_address); + +	if (!ipc_devlink->param.erase_full_flash_done) { +		reg_info[1] = cpu_to_le32(nand_address + rest_len - 2); +		ret = ipc_flash_send_receive(ipc_devlink, FLASH_ERASE_START, +					     (u8 *)reg_info, IOSM_MDM_SEND_8, +					     mdm_rsp); +		if (ret) +			goto dl_region_fail; + +		ret = ipc_flash_erase_check(ipc_devlink, mdm_rsp); +		if (ret) +			goto dl_region_fail; +	} + +	/* Request Flash Set Address */ +	ret = ipc_flash_send_receive(ipc_devlink, FLASH_SET_ADDRESS, +				     (u8 *)reg_info, IOSM_MDM_SEND_4, mdm_rsp); +	if (ret) +		goto dl_region_fail; + +	/* Request Flash Write Raw Image */ +	ret = ipc_flash_send_data(ipc_devlink, IOSM_EBL_DW_PACK_SIZE, +				  FLASH_WRITE_IMAGE_RAW, (u8 *)&rest_len, +				  IOSM_MDM_SEND_4); +	if (ret) +		goto dl_region_fail; + +	do { +		raw_len = (rest_len > IOSM_FLS_BUF_SIZE) ? IOSM_FLS_BUF_SIZE : +				rest_len; +		ret = ipc_imem_sys_devlink_write(ipc_devlink, file_ptr, +						 raw_len); +		if (ret) { +			dev_err(ipc_devlink->dev, "Image write failed"); +			goto dl_region_fail; +		} +		file_ptr += raw_len; +		rest_len -= raw_len; +	} while (rest_len); + +	ret = ipc_flash_receive_data(ipc_devlink, IOSM_EBL_DW_PAYL_SIZE, +				     mdm_rsp); + +dl_region_fail: +	return ret; +} + +/** + * ipc_flash_send_fls  - Inject Modem subsystem fls file to device + * @ipc_devlink:        Pointer to devlink structure + * @fw:                 FW image + * @mdm_rsp:            Pointer to modem response buffer + * + * Returns:             0 on success and failure value on error + */ +int ipc_flash_send_fls(struct iosm_devlink *ipc_devlink, +		       const struct firmware *fw, u8 *mdm_rsp) +{ +	u32 fw_size = fw->size - IOSM_DEVLINK_HDR_SIZE; +	struct iosm_devlink_image *fls_data; +	u16 flash_cmd; +	int ret; + +	fls_data = (struct iosm_devlink_image *)fw->data; +	if (ipc_devlink->param.erase_full_flash) { +		ipc_devlink->param.erase_full_flash = false; +		ret = ipc_flash_full_erase(ipc_devlink, mdm_rsp); +		if (ret) +			goto ipc_flash_err; +	} + +	/* Request Sec Start */ +	if (!fls_data->download_region) { +		ret = ipc_flash_send_receive(ipc_devlink, FLASH_SEC_START, +					     (u8 *)fw->data + +					     IOSM_DEVLINK_HDR_SIZE, fw_size, +					     mdm_rsp); +		if (ret) +			goto ipc_flash_err; +	} else { +		/* Download regions */ +		ret = ipc_flash_download_region(ipc_devlink, fw, mdm_rsp); +		if (ret) +			goto ipc_flash_err; + +		if (fls_data->last_region) { +			/* Request Sec End */ +			flash_cmd = IOSM_MDM_SEND_DATA; +			ret = ipc_flash_send_receive(ipc_devlink, FLASH_SEC_END, +						     (u8 *)&flash_cmd, +						     IOSM_MDM_SEND_2, mdm_rsp); +		} +	} + +ipc_flash_err: +	return ret; +} + +/** + * ipc_flash_boot_psi - Inject PSI image + * @ipc_devlink:        Pointer to devlink structure + * @fw:                 FW image + * + * Returns:             0 on success and failure value on error + */ +int ipc_flash_boot_psi(struct iosm_devlink *ipc_devlink, +		       const struct firmware *fw) +{ +	u32 bytes_read, psi_size = fw->size - IOSM_DEVLINK_HDR_SIZE; +	u8 psi_ack_byte[IOSM_PSI_ACK], read_data[2]; +	u8 *psi_code; +	int ret; + +	dev_dbg(ipc_devlink->dev, "Boot transfer PSI"); +	psi_code = kmemdup(fw->data + IOSM_DEVLINK_HDR_SIZE, psi_size, +			   GFP_KERNEL); +	if (!psi_code) +		return -ENOMEM; + +	ret = ipc_imem_sys_devlink_write(ipc_devlink, psi_code, psi_size); +	if (ret) { +		dev_err(ipc_devlink->dev, "RPSI Image write failed"); +		goto ipc_flash_psi_free; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, +					IOSM_LER_ACK_SIZE, &bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "ipc_devlink_sio_read ACK failed"); +		goto ipc_flash_psi_free; +	} + +	if (bytes_read != IOSM_LER_ACK_SIZE) { +		ret = -EINVAL; +		goto ipc_flash_psi_free; +	} + +	snprintf(psi_ack_byte, sizeof(psi_ack_byte), "%x%x", read_data[0], +		 read_data[1]); +	devlink_flash_update_status_notify(ipc_devlink->devlink_ctx, +					   psi_ack_byte, "PSI ACK", 0, 0); + +	if (read_data[0] == 0x00 && read_data[1] == 0xCD) { +		dev_dbg(ipc_devlink->dev, "Coredump detected"); +		ret = ipc_coredump_get_list(ipc_devlink, +					    rpsi_cmd_coredump_start); +		if (ret) +			dev_err(ipc_devlink->dev, "Failed to get cd list"); +	} + +ipc_flash_psi_free: +	kfree(psi_code); +	return ret; +} + +/** + * ipc_flash_boot_ebl  - Inject EBL image + * @ipc_devlink:        Pointer to devlink structure + * @fw:                 FW image + * + * Returns:             0 on success and failure value on error + */ +int ipc_flash_boot_ebl(struct iosm_devlink *ipc_devlink, +		       const struct firmware *fw) +{ +	u32 ebl_size = fw->size - IOSM_DEVLINK_HDR_SIZE; +	u8 read_data[2]; +	u32 bytes_read; +	int ret; + +	if (ipc_mmio_get_exec_stage(ipc_devlink->pcie->imem->mmio) != +				    IPC_MEM_EXEC_STAGE_PSI) { +		devlink_flash_update_status_notify(ipc_devlink->devlink_ctx, +						   "Invalid execution stage", +						   NULL, 0, 0); +		return -EINVAL; +	} + +	dev_dbg(ipc_devlink->dev, "Boot transfer EBL"); +	ret = ipc_devlink_send_cmd(ipc_devlink, rpsi_cmd_code_ebl, +				   IOSM_RPSI_LOAD_SIZE); +	if (ret) { +		dev_err(ipc_devlink->dev, "Sending rpsi_cmd_code_ebl failed"); +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE, +					&bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "rpsi_cmd_code_ebl read failed"); +		goto ipc_flash_ebl_err; +	} + +	if (bytes_read != IOSM_READ_SIZE) { +		ret = -EINVAL; +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_write(ipc_devlink, (u8 *)&ebl_size, +					 sizeof(ebl_size)); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL length write failed"); +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE, +					&bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL read failed"); +		goto ipc_flash_ebl_err; +	} + +	if (bytes_read != IOSM_READ_SIZE) { +		ret = -EINVAL; +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_write(ipc_devlink, +					 (u8 *)fw->data + IOSM_DEVLINK_HDR_SIZE, +					 ebl_size); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL data transfer failed"); +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE, +					&bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL read failed"); +		goto ipc_flash_ebl_err; +	} + +	if (bytes_read != IOSM_READ_SIZE) { +		ret = -EINVAL; +		goto ipc_flash_ebl_err; +	} + +	ret = ipc_imem_sys_devlink_read(ipc_devlink, +					ipc_devlink->ebl_ctx.m_ebl_resp, +					IOSM_EBL_RSP_SIZE, &bytes_read); +	if (ret) { +		dev_err(ipc_devlink->dev, "EBL response read failed"); +		goto ipc_flash_ebl_err; +	} + +	if (bytes_read != IOSM_EBL_RSP_SIZE) +		ret = -EINVAL; + +ipc_flash_ebl_err: +	return ret; +} |