// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020-2021 Intel Corporation. */ #include #include "iosm_ipc_chnl_cfg.h" #include "iosm_ipc_coredump.h" #include "iosm_ipc_devlink.h" #include "iosm_ipc_flash.h" /* Coredump list */ static struct iosm_coredump_file_info list[IOSM_NOF_CD_REGION] = { {"report.json", REPORT_JSON_SIZE,}, {"coredump.fcd", COREDUMP_FCD_SIZE,}, {"cdd.log", CDD_LOG_SIZE,}, {"eeprom.bin", EEPROM_BIN_SIZE,}, {"bootcore_trace.bin", BOOTCORE_TRC_BIN_SIZE,}, {"bootcore_prev_trace.bin", BOOTCORE_PREV_TRC_BIN_SIZE,}, }; /* Get the param values for the specific param ID's */ static int ipc_devlink_get_param(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct iosm_devlink *ipc_devlink = devlink_priv(dl); if (id == IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH) ctx->val.vu8 = ipc_devlink->param.erase_full_flash; return 0; } /* Set the param values for the specific param ID's */ static int ipc_devlink_set_param(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx, struct netlink_ext_ack *extack) { struct iosm_devlink *ipc_devlink = devlink_priv(dl); if (id == IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH) ipc_devlink->param.erase_full_flash = ctx->val.vu8; return 0; } /* Devlink param structure array */ static const struct devlink_param iosm_devlink_params[] = { DEVLINK_PARAM_DRIVER(IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH, "erase_full_flash", DEVLINK_PARAM_TYPE_BOOL, BIT(DEVLINK_PARAM_CMODE_RUNTIME), ipc_devlink_get_param, ipc_devlink_set_param, NULL), }; /* Get devlink flash component type */ static enum iosm_flash_comp_type ipc_devlink_get_flash_comp_type(const char comp_str[], u32 len) { enum iosm_flash_comp_type fls_type; if (!strncmp("PSI", comp_str, len)) fls_type = FLASH_COMP_TYPE_PSI; else if (!strncmp("EBL", comp_str, len)) fls_type = FLASH_COMP_TYPE_EBL; else if (!strncmp("FLS", comp_str, len)) fls_type = FLASH_COMP_TYPE_FLS; else fls_type = FLASH_COMP_TYPE_INVAL; return fls_type; } /* Function triggered on devlink flash command * Flash update function which calls multiple functions based on * component type specified in the flash command */ static int ipc_devlink_flash_update(struct devlink *devlink, struct devlink_flash_update_params *params, struct netlink_ext_ack *extack) { struct iosm_devlink *ipc_devlink = devlink_priv(devlink); enum iosm_flash_comp_type fls_type; struct iosm_devlink_image *header; int rc = -EINVAL; u8 *mdm_rsp; header = (struct iosm_devlink_image *)params->fw->data; if (!header || params->fw->size <= IOSM_DEVLINK_HDR_SIZE || (memcmp(header->magic_header, IOSM_DEVLINK_MAGIC_HEADER, IOSM_DEVLINK_MAGIC_HEADER_LEN) != 0)) return -EINVAL; mdm_rsp = kzalloc(IOSM_EBL_DW_PACK_SIZE, GFP_KERNEL); if (!mdm_rsp) return -ENOMEM; fls_type = ipc_devlink_get_flash_comp_type(header->image_type, IOSM_DEVLINK_MAX_IMG_LEN); switch (fls_type) { case FLASH_COMP_TYPE_PSI: rc = ipc_flash_boot_psi(ipc_devlink, params->fw); break; case FLASH_COMP_TYPE_EBL: rc = ipc_flash_boot_ebl(ipc_devlink, params->fw); if (rc) break; rc = ipc_flash_boot_set_capabilities(ipc_devlink, mdm_rsp); if (rc) break; rc = ipc_flash_read_swid(ipc_devlink, mdm_rsp); break; case FLASH_COMP_TYPE_FLS: rc = ipc_flash_send_fls(ipc_devlink, params->fw, mdm_rsp); break; default: devlink_flash_update_status_notify(devlink, "Invalid component", NULL, 0, 0); break; } if (!rc) devlink_flash_update_status_notify(devlink, "Flashing success", header->image_type, 0, 0); else devlink_flash_update_status_notify(devlink, "Flashing failed", header->image_type, 0, 0); kfree(mdm_rsp); return rc; } /* Call back function for devlink ops */ static const struct devlink_ops devlink_flash_ops = { .flash_update = ipc_devlink_flash_update, }; /** * ipc_devlink_send_cmd - Send command to Modem * @ipc_devlink: Pointer to struct iosm_devlink * @cmd: Command to be sent to modem * @entry: Command entry number * * Returns: 0 on success and failure value on error */ int ipc_devlink_send_cmd(struct iosm_devlink *ipc_devlink, u16 cmd, u32 entry) { struct iosm_rpsi_cmd rpsi_cmd; rpsi_cmd.param.dword = cpu_to_le32(entry); rpsi_cmd.cmd = cpu_to_le16(cmd); rpsi_cmd.crc = rpsi_cmd.param.word[0] ^ rpsi_cmd.param.word[1] ^ rpsi_cmd.cmd; return ipc_imem_sys_devlink_write(ipc_devlink, (u8 *)&rpsi_cmd, sizeof(rpsi_cmd)); } /* Function to create snapshot */ static int ipc_devlink_coredump_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct iosm_devlink *ipc_devlink = devlink_priv(dl); struct iosm_coredump_file_info *cd_list = ops->priv; u32 region_size; int rc; dev_dbg(ipc_devlink->dev, "Region:%s, ID:%d", ops->name, cd_list->entry); region_size = cd_list->default_size; rc = ipc_coredump_collect(ipc_devlink, data, cd_list->entry, region_size); if (rc) { dev_err(ipc_devlink->dev, "Fail to create snapshot,err %d", rc); goto coredump_collect_err; } /* Send coredump end cmd indicating end of coredump collection */ if (cd_list->entry == (IOSM_NOF_CD_REGION - 1)) ipc_coredump_get_list(ipc_devlink, rpsi_cmd_coredump_end); return 0; coredump_collect_err: ipc_coredump_get_list(ipc_devlink, rpsi_cmd_coredump_end); return rc; } /* To create regions for coredump files */ static int ipc_devlink_create_region(struct iosm_devlink *devlink) { struct devlink_region_ops *mdm_coredump; int rc = 0; int i; mdm_coredump = devlink->iosm_devlink_mdm_coredump; for (i = 0; i < IOSM_NOF_CD_REGION; i++) { mdm_coredump[i].name = list[i].filename; mdm_coredump[i].snapshot = ipc_devlink_coredump_snapshot; mdm_coredump[i].destructor = vfree; devlink->cd_regions[i] = devlink_region_create(devlink->devlink_ctx, &mdm_coredump[i], MAX_SNAPSHOTS, list[i].default_size); if (IS_ERR(devlink->cd_regions[i])) { rc = PTR_ERR(devlink->cd_regions[i]); dev_err(devlink->dev, "Devlink region fail,err %d", rc); /* Delete previously created regions */ for (i--; i >= 0; i--) devlink_region_destroy(devlink->cd_regions[i]); goto region_create_fail; } list[i].entry = i; mdm_coredump[i].priv = list + i; } region_create_fail: return rc; } /* To Destroy devlink regions */ static void ipc_devlink_destroy_region(struct iosm_devlink *ipc_devlink) { u8 i; for (i = 0; i < IOSM_NOF_CD_REGION; i++) devlink_region_destroy(ipc_devlink->cd_regions[i]); } /** * ipc_devlink_init - Initialize/register devlink to IOSM driver * @ipc_imem: Pointer to struct iosm_imem * * Returns: Pointer to iosm_devlink on success and NULL on failure */ struct iosm_devlink *ipc_devlink_init(struct iosm_imem *ipc_imem) { struct ipc_chnl_cfg chnl_cfg_flash = { 0 }; struct iosm_devlink *ipc_devlink; struct devlink *devlink_ctx; int rc; devlink_ctx = devlink_alloc(&devlink_flash_ops, sizeof(struct iosm_devlink), ipc_imem->dev); if (!devlink_ctx) { dev_err(ipc_imem->dev, "devlink_alloc failed"); goto devlink_alloc_fail; } ipc_devlink = devlink_priv(devlink_ctx); ipc_devlink->devlink_ctx = devlink_ctx; ipc_devlink->pcie = ipc_imem->pcie; ipc_devlink->dev = ipc_imem->dev; rc = devlink_params_register(devlink_ctx, iosm_devlink_params, ARRAY_SIZE(iosm_devlink_params)); if (rc) { dev_err(ipc_devlink->dev, "devlink_params_register failed. rc %d", rc); goto param_reg_fail; } ipc_devlink->cd_file_info = list; rc = ipc_devlink_create_region(ipc_devlink); if (rc) { dev_err(ipc_devlink->dev, "Devlink Region create failed, rc %d", rc); goto region_create_fail; } if (ipc_chnl_cfg_get(&chnl_cfg_flash, IPC_MEM_CTRL_CHL_ID_7) < 0) goto chnl_get_fail; ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, chnl_cfg_flash, IRQ_MOD_OFF); init_completion(&ipc_devlink->devlink_sio.read_sem); skb_queue_head_init(&ipc_devlink->devlink_sio.rx_list); devlink_register(devlink_ctx); dev_dbg(ipc_devlink->dev, "iosm devlink register success"); return ipc_devlink; chnl_get_fail: ipc_devlink_destroy_region(ipc_devlink); region_create_fail: devlink_params_unregister(devlink_ctx, iosm_devlink_params, ARRAY_SIZE(iosm_devlink_params)); param_reg_fail: devlink_free(devlink_ctx); devlink_alloc_fail: return NULL; } /** * ipc_devlink_deinit - To unintialize the devlink from IOSM driver. * @ipc_devlink: Devlink instance */ void ipc_devlink_deinit(struct iosm_devlink *ipc_devlink) { struct devlink *devlink_ctx = ipc_devlink->devlink_ctx; devlink_unregister(devlink_ctx); ipc_devlink_destroy_region(ipc_devlink); devlink_params_unregister(devlink_ctx, iosm_devlink_params, ARRAY_SIZE(iosm_devlink_params)); if (ipc_devlink->devlink_sio.devlink_read_pend) { complete(&ipc_devlink->devlink_sio.read_sem); complete(&ipc_devlink->devlink_sio.channel->ul_sem); } if (!ipc_devlink->devlink_sio.devlink_read_pend) skb_queue_purge(&ipc_devlink->devlink_sio.rx_list); ipc_imem_sys_devlink_close(ipc_devlink); devlink_free(devlink_ctx); }