| // 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; |
| } |