diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 45b5f8b341df..19ffd561cf4f 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -47,3 +47,4 @@ parameters, info versions, and other features it supports. ti-cpsw-switch am65-nuss-cpsw-switch prestera + iosm diff --git a/Documentation/networking/devlink/iosm.rst b/Documentation/networking/devlink/iosm.rst new file mode 100644 index 000000000000..dd460c1c15cd --- /dev/null +++ b/Documentation/networking/devlink/iosm.rst @@ -0,0 +1,182 @@ +.. SPDX-License-Identifier: GPL-2.0 + +==================== +iosm devlink support +==================== + +This document describes the devlink features implemented by the ``iosm`` +device driver. + +Parameters +========== + +The ``iosm`` driver implements the following driver-specific parameters. + +.. list-table:: Driver-specific parameters implemented + :widths: 5 5 5 85 + + * - Name + - Type + - Mode + - Description + * - ``erase_full_flash`` + - u8 + - runtime + - erase_full_flash parameter is used to check if full erase is required for + the device during firmware flashing. + If set, Full nand erase command will be sent to the device. By default, + only conditional erase support is enabled. + * - ``download_region`` + - u8 + - runtime + - download_region parameter is used to identify if we are flashing the + loadmap/region file during the firmware flashing. + * - ``address`` + - u32 + - runtime + - address parameter is used to send the address information of the + loadmap/region file which is required during the firmware flashing + process. Each region file has be flashed to its respective flash address. + * - ``region_count`` + - u8 + - runtime + - region_count parameter is used to inform the driver on how many total + loadmap/region files are present in modem firmware image that has to be + flashed. + + +Flash Update +============ + +The ``iosm`` driver implements support for flash update using the +``devlink-flash`` interface. + +It supports updating the device flash using a combined flash image which contains +the Bootloader images and other modem software images. + +The driver uses DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT to identify type of +firmware image that need to be flashed as requested by user space application. +Supported firmware image types. + +.. list-table:: Firmware Image types + :widths: 15 85 + + * - Name + - Description + * - ``PSI RAM`` + - Primary Signed Image + * - ``EBL`` + - External Bootloader + * - ``FLS`` + - Modem Software Image + +PSI RAM and EBL are the RAM images which are injected to the device when the +device is in BOOT ROM stage. Once this is successful, the actual modem firmware +image is flashed to the device. The modem software image contains multiple files +each having one secure bin file and at least one Loadmap/Region file. For flashing +these files, appropriate commands are sent to the modem device along with the +data required for flashing. The data like region count and address of each region +has to be passed to the driver using the devlink param command. + +If the device has to be fully erased before firmware flashing, user application +need to set the erase_full_flash parameter using devlink param command. +By default, conditional erase feature is supported. + +Flash Commands: +=============== +1) When modem is in Boot ROM stage, user can use below command to inject PSI RAM +image using devlink flash command. + +$ devlink dev flash pci/0000:02:00.0 file component PSI + +2) If user want to do a full erase, below command need to be issued to set the +erase full flash param (To be set only if full erase required). + +$ devlink dev param set pci/0000:02:00.0 name erase_full_flash value true cmode runtime + +3) Inject EBL after the modem is in PSI stage. +$ devlink dev flash pci/0000:02:00.0 file component EBL + +4) Once EBL is injected successfully, then the actual firmware flashing takes +place. Below is the sequence of commands used for each of the firmware images. + +a) Flash secure bin file. +$ devlink dev flash pci/0000:02:00.0 file component FLS + +b) Flashing the Loadmap/Region file +$ devlink dev param set pci/0000:02:00.0 name region_count value 1 cmode runtime + +$ devlink dev param set pci/0000:02:00.0 name download_region value true cmode runtime + +$ devlink dev param set pci/0000:02:00.0 name address value cmode runtime + +$ devlink dev flash pci/0000:02:00.0 file component FLS + +Regions +======= + +The ``iosm`` driver supports dumping the coredump logs. + +In case a firmware encounters an exception, a snapshot will be taken by the +driver. Following regions are accessed for device internal data. + +.. list-table:: Regions implemented + :widths: 15 85 + + * - Name + - Description + * - ``report.json`` + - The summary of exception details logged as part of this region. + * - ``coredump.fcd`` + - This region contains the details related to the exception occurred in the + device (RAM dump). + * - ``cdd.log`` + - This region contains the logs related to the modem CDD driver. + * - ``eeprom.bin`` + - This region contains the eeprom logs. + * - ``bootcore_trace.bin`` + - This region contains the current instance of bootloader logs. + * - ``bootcore_prev_trace.bin`` + - This region contains the previous instance of bootloader logs. + + +Region commands +=============== + +$ devlink region show + +$ devlink region new pci/0000:02:00.0/report.json + +$ devlink region dump pci/0000:02:00.0/report.json snapshot 0 + +$ devlink region del pci/0000:02:00.0/report.json snapshot 0 + +$ devlink region new pci/0000:02:00.0/coredump.fcd + +$ devlink region dump pci/0000:02:00.0/coredump.fcd snapshot 1 + +$ devlink region del pci/0000:02:00.0/coredump.fcd snapshot 1 + +$ devlink region new pci/0000:02:00.0/cdd.log + +$ devlink region dump pci/0000:02:00.0/cdd.log snapshot 2 + +$ devlink region del pci/0000:02:00.0/cdd.log snapshot 2 + +$ devlink region new pci/0000:02:00.0/eeprom.bin + +$ devlink region dump pci/0000:02:00.0/eeprom.bin snapshot 3 + +$ devlink region del pci/0000:02:00.0/eeprom.bin snapshot 3 + +$ devlink region new pci/0000:02:00.0/bootcore_trace.bin + +$ devlink region dump pci/0000:02:00.0/bootcore_trace.bin snapshot 4 + +$ devlink region del pci/0000:02:00.0/bootcore_trace.bin snapshot 4 + +$ devlink region new pci/0000:02:00.0/bootcore_prev_trace.bin + +$ devlink region dump pci/0000:02:00.0/bootcore_prev_trace.bin snapshot 5 + +$ devlink region del pci/0000:02:00.0/bootcore_prev_trace.bin snapshot 5 diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 77dbfc418bce..17543be14665 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -71,6 +71,7 @@ config RPMSG_WWAN_CTRL config IOSM tristate "IOSM Driver for Intel M.2 WWAN Device" depends on INTEL_IOMMU + select NET_DEVLINK help This driver enables Intel M.2 WWAN Device communication. diff --git a/drivers/net/wwan/iosm/Makefile b/drivers/net/wwan/iosm/Makefile index 4f9f0ae398e1..b838034bb120 100644 --- a/drivers/net/wwan/iosm/Makefile +++ b/drivers/net/wwan/iosm/Makefile @@ -18,6 +18,9 @@ iosm-y = \ iosm_ipc_protocol.o \ iosm_ipc_protocol_ops.o \ iosm_ipc_mux.o \ - iosm_ipc_mux_codec.o + iosm_ipc_mux_codec.o \ + iosm_ipc_devlink.o \ + iosm_ipc_flash.o \ + iosm_ipc_coredump.o obj-$(CONFIG_IOSM) := iosm.o diff --git a/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.c b/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.c index 519361ec40df..128c999e08bb 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.c +++ b/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.c @@ -8,7 +8,7 @@ #include "iosm_ipc_chnl_cfg.h" /* Max. sizes of a downlink buffers */ -#define IPC_MEM_MAX_DL_FLASH_BUF_SIZE (16 * 1024) +#define IPC_MEM_MAX_DL_FLASH_BUF_SIZE (64 * 1024) #define IPC_MEM_MAX_DL_LOOPBACK_SIZE (1 * 1024 * 1024) #define IPC_MEM_MAX_DL_AT_BUF_SIZE 2048 #define IPC_MEM_MAX_DL_RPC_BUF_SIZE (32 * 1024) @@ -60,6 +60,10 @@ static struct ipc_chnl_cfg modem_cfg[] = { { IPC_MEM_CTRL_CHL_ID_6, IPC_MEM_PIPE_12, IPC_MEM_PIPE_13, IPC_MEM_MAX_TDS_MBIM, IPC_MEM_MAX_TDS_MBIM, IPC_MEM_MAX_DL_MBIM_BUF_SIZE, WWAN_PORT_MBIM }, + /* Flash Channel/Coredump Channel */ + { IPC_MEM_CTRL_CHL_ID_7, IPC_MEM_PIPE_0, IPC_MEM_PIPE_1, + IPC_MEM_MAX_TDS_FLASH_UL, IPC_MEM_MAX_TDS_FLASH_DL, + IPC_MEM_MAX_DL_FLASH_BUF_SIZE, WWAN_PORT_UNKNOWN }, }; int ipc_chnl_cfg_get(struct ipc_chnl_cfg *chnl_cfg, int index) diff --git a/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.h b/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.h index 422471367f78..e77084e76718 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.h +++ b/drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.h @@ -23,6 +23,7 @@ enum ipc_channel_id { IPC_MEM_CTRL_CHL_ID_4, IPC_MEM_CTRL_CHL_ID_5, IPC_MEM_CTRL_CHL_ID_6, + IPC_MEM_CTRL_CHL_ID_7, }; /** diff --git a/drivers/net/wwan/iosm/iosm_ipc_coredump.c b/drivers/net/wwan/iosm/iosm_ipc_coredump.c new file mode 100644 index 000000000000..fba3c3454e80 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_coredump.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#include "iosm_ipc_coredump.h" + +/* Collect coredump data from modem */ +int ipc_coredump_collect(struct iosm_devlink *devlink, u8 **data, int entry, + u32 region_size) +{ + int ret, bytes_to_read, bytes_read = 0, i = 0; + s32 remaining; + u8 *data_ptr; + + data_ptr = vmalloc(region_size); + if (!data_ptr) + return -ENOMEM; + + remaining = devlink->cd_file_info[entry].actual_size; + ret = ipc_devlink_send_cmd(devlink, rpsi_cmd_coredump_get, entry); + if (ret) { + dev_err(devlink->dev, "Send coredump_get cmd failed"); + goto get_cd_fail; + } + while (remaining > 0) { + bytes_to_read = min(remaining, MAX_DATA_SIZE); + bytes_read = 0; + ret = ipc_imem_sys_devlink_read(devlink, data_ptr + i, + bytes_to_read, &bytes_read); + if (ret) { + dev_err(devlink->dev, "CD data read failed"); + goto get_cd_fail; + } + remaining -= bytes_read; + i += bytes_read; + } + + *data = data_ptr; + + return ret; +get_cd_fail: + vfree(data_ptr); + return ret; +} + +/* Get coredump list to be collected from modem */ +int ipc_coredump_get_list(struct iosm_devlink *devlink, u16 cmd) +{ + u32 byte_read, num_entries, file_size; + struct iosm_cd_table *cd_table; + u8 size[MAX_SIZE_LEN], i; + char *filename; + int ret = 0; + + cd_table = kzalloc(MAX_CD_LIST_SIZE, GFP_KERNEL); + if (!cd_table) { + ret = -ENOMEM; + goto cd_init_fail; + } + + ret = ipc_devlink_send_cmd(devlink, cmd, MAX_CD_LIST_SIZE); + if (ret) { + dev_err(devlink->dev, "rpsi_cmd_coredump_start failed"); + goto cd_init_fail; + } + + ret = ipc_imem_sys_devlink_read(devlink, (u8 *)cd_table, + MAX_CD_LIST_SIZE, &byte_read); + if (ret) { + dev_err(devlink->dev, "Coredump data is invalid"); + goto cd_init_fail; + } + + if (byte_read != MAX_CD_LIST_SIZE) + goto cd_init_fail; + + if (cmd == rpsi_cmd_coredump_start) { + num_entries = le32_to_cpu(cd_table->list.num_entries); + if (num_entries == 0 || num_entries > IOSM_NOF_CD_REGION) { + ret = -EINVAL; + goto cd_init_fail; + } + + for (i = 0; i < num_entries; i++) { + file_size = le32_to_cpu(cd_table->list.entry[i].size); + filename = cd_table->list.entry[i].filename; + + if (file_size > devlink->cd_file_info[i].default_size) { + ret = -EINVAL; + goto cd_init_fail; + } + + devlink->cd_file_info[i].actual_size = file_size; + dev_dbg(devlink->dev, "file: %s actual size %d", + filename, file_size); + devlink_flash_update_status_notify(devlink->devlink_ctx, + filename, + "FILENAME", 0, 0); + snprintf(size, sizeof(size), "%d", file_size); + devlink_flash_update_status_notify(devlink->devlink_ctx, + size, "FILE SIZE", + 0, 0); + } + } + +cd_init_fail: + kfree(cd_table); + return ret; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_coredump.h b/drivers/net/wwan/iosm/iosm_ipc_coredump.h new file mode 100644 index 000000000000..d5028153c8d1 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_coredump.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#ifndef _IOSM_IPC_COREDUMP_H_ +#define _IOSM_IPC_COREDUMP_H_ + +#include "iosm_ipc_devlink.h" + +/* Max number of bytes to receive for Coredump list structure */ +#define MAX_CD_LIST_SIZE 0x1000 + +/* Max buffer allocated to receive coredump data */ +#define MAX_DATA_SIZE 0x00010000 + +/* Max number of file entries */ +#define MAX_NOF_ENTRY 256 + +/* Max length */ +#define MAX_SIZE_LEN 32 + +/** + * struct iosm_cd_list_entry - Structure to hold coredump file info. + * @size: Number of bytes for the entry + * @filename: Coredump filename to be generated on host + */ +struct iosm_cd_list_entry { + __le32 size; + char filename[IOSM_MAX_FILENAME_LEN]; +} __packed; + +/** + * struct iosm_cd_list - Structure to hold list of coredump files + * to be collected. + * @num_entries: Number of entries to be received + * @entry: Contains File info + */ +struct iosm_cd_list { + __le32 num_entries; + struct iosm_cd_list_entry entry[MAX_NOF_ENTRY]; +} __packed; + +/** + * struct iosm_cd_table - Common Coredump table + * @version: Version of coredump structure + * @list: Coredump list structure + */ +struct iosm_cd_table { + __le32 version; + struct iosm_cd_list list; +} __packed; + +/** + * ipc_coredump_collect - To collect coredump + * @devlink: Pointer to devlink instance. + * @data: Pointer to snapshot + * @entry: ID of requested snapshot + * @region_size: Region size + * + * Returns: 0 on success, error on failure + */ +int ipc_coredump_collect(struct iosm_devlink *devlink, u8 **data, int entry, + u32 region_size); + +/** + * ipc_coredump_get_list - Get coredump list + * @devlink: Pointer to devlink instance. + * @cmd: RPSI command to be sent + * + * Returns: 0 on success, error on failure + */ +int ipc_coredump_get_list(struct iosm_devlink *devlink, u16 cmd); + +#endif /* _IOSM_IPC_COREDUMP_H_ */ diff --git a/drivers/net/wwan/iosm/iosm_ipc_devlink.c b/drivers/net/wwan/iosm/iosm_ipc_devlink.c new file mode 100644 index 000000000000..7fd7956cc61e --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_devlink.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#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); + int rc = 0; + + switch (id) { + case IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH: + ctx->val.vu8 = ipc_devlink->param.erase_full_flash; + break; + + case IOSM_DEVLINK_PARAM_ID_DOWNLOAD_REGION: + ctx->val.vu8 = ipc_devlink->param.download_region; + break; + + case IOSM_DEVLINK_PARAM_ID_ADDRESS: + ctx->val.vu32 = ipc_devlink->param.address; + break; + + case IOSM_DEVLINK_PARAM_ID_REGION_COUNT: + ctx->val.vu8 = ipc_devlink->param.region_count; + break; + + default: + rc = -EOPNOTSUPP; + break; + } + + return rc; +} + +/* 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 iosm_devlink *ipc_devlink = devlink_priv(dl); + int rc = 0; + + switch (id) { + case IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH: + ipc_devlink->param.erase_full_flash = ctx->val.vu8; + break; + + case IOSM_DEVLINK_PARAM_ID_DOWNLOAD_REGION: + ipc_devlink->param.download_region = ctx->val.vu8; + break; + + case IOSM_DEVLINK_PARAM_ID_ADDRESS: + ipc_devlink->param.address = ctx->val.vu32; + break; + + case IOSM_DEVLINK_PARAM_ID_REGION_COUNT: + ipc_devlink->param.region_count = ctx->val.vu8; + break; + + default: + rc = -EOPNOTSUPP; + break; + } + + return rc; +} + +/* 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), + DEVLINK_PARAM_DRIVER(IOSM_DEVLINK_PARAM_ID_DOWNLOAD_REGION, + "download_region", DEVLINK_PARAM_TYPE_BOOL, + BIT(DEVLINK_PARAM_CMODE_RUNTIME), + ipc_devlink_get_param, ipc_devlink_set_param, + NULL), + DEVLINK_PARAM_DRIVER(IOSM_DEVLINK_PARAM_ID_ADDRESS, + "address", DEVLINK_PARAM_TYPE_U32, + BIT(DEVLINK_PARAM_CMODE_RUNTIME), + ipc_devlink_get_param, ipc_devlink_set_param, + NULL), + DEVLINK_PARAM_DRIVER(IOSM_DEVLINK_PARAM_ID_REGION_COUNT, + "region_count", DEVLINK_PARAM_TYPE_U8, + 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; + u32 rc = -EINVAL; + u8 *mdm_rsp; + + if (!params->component) + return rc; + + mdm_rsp = kzalloc(IOSM_EBL_DW_PACK_SIZE, GFP_KERNEL); + if (!mdm_rsp) + return -ENOMEM; + + fls_type = ipc_devlink_get_flash_comp_type(params->component, + strlen(params->component)); + + 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) + rc = ipc_flash_boot_set_capabilities(ipc_devlink, + mdm_rsp); + if (!rc) + 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", + params->component, 0, 0); + break; + } + + if (!rc) + devlink_flash_update_status_notify(devlink, "Flashing success", + params->component, 0, 0); + else + devlink_flash_update_status_notify(devlink, "Flashing failed", + params->component, 0, 0); + + kfree(mdm_rsp); + return rc; +} + +/* Call back function for devlink ops */ +static const struct devlink_ops devlink_flash_ops = { + .supported_flash_update_params = DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT, + .flash_update = ipc_devlink_flash_update, +}; + +/* Send command to modem to collect data */ +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)); +} + +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 rc; +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 >= 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]); +} + +/* Handle registration to devlink framework */ +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_register(devlink_ctx); + if (rc) { + dev_err(ipc_devlink->dev, "devlink_register failed rc %d", rc); + goto free_dl; + } + + 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; + } + + devlink_params_publish(devlink_ctx); + 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); + + 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_unpublish(devlink_ctx); + devlink_params_unregister(devlink_ctx, iosm_devlink_params, + ARRAY_SIZE(iosm_devlink_params)); +param_reg_fail: + devlink_unregister(devlink_ctx); +free_dl: + devlink_free(devlink_ctx); +devlink_alloc_fail: + return NULL; +} + +/* Handle unregistration of devlink */ +void ipc_devlink_deinit(struct iosm_devlink *ipc_devlink) +{ + struct devlink *devlink_ctx = ipc_devlink->devlink_ctx; + + ipc_devlink_destroy_region(ipc_devlink); + devlink_params_unpublish(devlink_ctx); + 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_unregister(devlink_ctx); + devlink_free(devlink_ctx); +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_devlink.h b/drivers/net/wwan/iosm/iosm_ipc_devlink.h new file mode 100644 index 000000000000..392735080cb3 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_devlink.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#ifndef _IOSM_IPC_DEVLINK_H_ +#define _IOSM_IPC_DEVLINK_H_ + +#include + +#include "iosm_ipc_imem.h" +#include "iosm_ipc_imem_ops.h" +#include "iosm_ipc_pcie.h" + +/* MAX file name length */ +#define IOSM_MAX_FILENAME_LEN 32 +/* EBL response size */ +#define IOSM_EBL_RSP_SIZE 76 +/* MAX number of regions supported */ +#define IOSM_NOF_CD_REGION 6 +/* MAX number of SNAPSHOTS supported */ +#define MAX_SNAPSHOTS 1 +/* Default Coredump file size */ +#define REPORT_JSON_SIZE 0x800 +#define COREDUMP_FCD_SIZE 0x10E00000 +#define CDD_LOG_SIZE 0x30000 +#define EEPROM_BIN_SIZE 0x10000 +#define BOOTCORE_TRC_BIN_SIZE 0x8000 +#define BOOTCORE_PREV_TRC_BIN_SIZE 0x20000 + +/** + * enum iosm_devlink_param_id - Enum type to different devlink params + * @IOSM_DEVLINK_PARAM_ID_BASE: Devlink param base ID + * @IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH: Set if full erase required + * @IOSM_DEVLINK_PARAM_ID_DOWNLOAD_REGION: Set if fls file to be + * flashed is Loadmap/region file + * @IOSM_DEVLINK_PARAM_ID_ADDRESS: Address of the region to be + * flashed + * @IOSM_DEVLINK_PARAM_ID_REGION_COUNT: Max region count + */ + +enum iosm_devlink_param_id { + IOSM_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, + IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH, + IOSM_DEVLINK_PARAM_ID_DOWNLOAD_REGION, + IOSM_DEVLINK_PARAM_ID_ADDRESS, + IOSM_DEVLINK_PARAM_ID_REGION_COUNT, +}; + +/** + * enum iosm_rpsi_cmd_code - Enum type for RPSI command list + * @rpsi_cmd_code_ebl: Command to load ebl + * @rpsi_cmd_coredump_start: Command to get list of files and + * file size info from PSI + * @rpsi_cmd_coredump_get: Command to get the coredump data + * @rpsi_cmd_coredump_end: Command to stop receiving the coredump + */ +enum iosm_rpsi_cmd_code { + rpsi_cmd_code_ebl = 0x02, + rpsi_cmd_coredump_start = 0x10, + rpsi_cmd_coredump_get = 0x11, + rpsi_cmd_coredump_end = 0x12, +}; + +/** + * enum iosm_flash_comp_type - Enum for different flash component types + * @FLASH_COMP_TYPE_PSI: PSI flash comp type + * @FLASH_COMP_TYPE_EBL: EBL flash comp type + * @FLASH_COMP_TYPE_FLS: FLS flash comp type + * @FLASH_COMP_TYPE_INVAL: Invalid flash comp type + */ +enum iosm_flash_comp_type { + FLASH_COMP_TYPE_PSI, + FLASH_COMP_TYPE_EBL, + FLASH_COMP_TYPE_FLS, + FLASH_COMP_TYPE_INVAL, +}; + +/** + * struct iosm_devlink_sio - SIO instance + * @rx_list: Downlink skbuf list received from CP + * @read_sem: Needed for the blocking read or downlink transfer + * @channel_id: Reserved channel id for flashing/CD collection to RAM + * @channel: Channel instance for flashing and coredump + * @devlink_read_pend: Check if read is pending + */ +struct iosm_devlink_sio { + struct sk_buff_head rx_list; + struct completion read_sem; + int channel_id; + struct ipc_mem_channel *channel; + u32 devlink_read_pend; +}; + +/** + * struct iosm_flash_params - List of flash params required for flashing + * @address: Address of the region file to be flashed + * @region_count: Maximum no of regions for each fls file + * @download_region: To be set if region is being flashed + * @erase_full_flash: To set the flashing mode + * erase_full_flash = 1; full erase + * erase_full_flash = 0; no erase + * @erase_full_flash_done: Flag to check if it is a full erase + */ +struct iosm_flash_params { + u32 address; + u8 region_count; + u8 download_region; + u8 erase_full_flash; + u8 erase_full_flash_done; +}; + +/** + * struct iosm_ebl_ctx_data - EBL ctx data used during flashing + * @ebl_sw_info_version: SWID version info obtained from EBL + * @m_ebl_resp: Buffer used to read and write the ebl data + */ +struct iosm_ebl_ctx_data { + u8 ebl_sw_info_version; + u8 m_ebl_resp[IOSM_EBL_RSP_SIZE]; +}; + +/** + * struct iosm_coredump_file_info - Coredump file info + * @filename: Name of coredump file + * @default_size: Default size of coredump file + * @actual_size: Actual size of coredump file + * @entry: Index of the coredump file + */ +struct iosm_coredump_file_info { + char filename[IOSM_MAX_FILENAME_LEN]; + u32 default_size; + u32 actual_size; + u32 entry; +}; + +/** + * struct iosm_devlink - IOSM Devlink structure + * @devlink_sio: SIO instance for read/write functionality + * @pcie: Pointer to PCIe component + * @dev: Pointer to device struct + * @devlink_ctx: Pointer to devlink context + * @param: Params required for flashing + * @ebl_ctx: Data to be read and written to Modem + * @cd_file_info: coredump file info + * @iosm_devlink_mdm_coredump: region ops for coredump collection + * @cd_regions: coredump regions + */ +struct iosm_devlink { + struct iosm_devlink_sio devlink_sio; + struct iosm_pcie *pcie; + struct device *dev; + struct devlink *devlink_ctx; + struct iosm_flash_params param; + struct iosm_ebl_ctx_data ebl_ctx; + struct iosm_coredump_file_info *cd_file_info; + struct devlink_region_ops iosm_devlink_mdm_coredump[IOSM_NOF_CD_REGION]; + struct devlink_region *cd_regions[IOSM_NOF_CD_REGION]; +}; + +/** + * union iosm_rpsi_param_u - RPSI cmd param for CRC calculation + * @word: Words member used in CRC calculation + * @dword: Actual data + */ +union iosm_rpsi_param_u { + __le16 word[2]; + __le32 dword; +}; + +/** + * struct iosm_rpsi_cmd - Structure for RPSI Command + * @param: Used to calculate CRC + * @cmd: Stores the RPSI command + * @crc: Stores the CRC value + */ +struct iosm_rpsi_cmd { + union iosm_rpsi_param_u param; + __le16 cmd; + __le16 crc; +}; + +/** + * ipc_devlink_init - To initialize the 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); + +/** + * ipc_devlink_deinit - To unintialize the devlink from IOSM driver. + * @ipc_devlink: Devlink instance + */ +void ipc_devlink_deinit(struct iosm_devlink *ipc_devlink); + +/** + * 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); + +#endif /* _IOSM_IPC_DEVLINK_H */ 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..3d2f1ec6da00 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_flash.c @@ -0,0 +1,561 @@ +// 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; + int res = 0; + 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); + } + } + res = -EINVAL; + } + + return res; +} + +/* 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; +} + +/* Allocate flash channel and read LER data from modem */ +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; +} + +/* Set the capabilities for the EBL */ +int ipc_flash_boot_set_capabilities(struct iosm_devlink *ipc_devlink, + u8 *mdm_rsp) +{ + int ret; + + 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 + */ + ret = ipc_flash_send_receive(ipc_devlink, FLASH_SET_PROT_CONF, + ipc_devlink->ebl_ctx.m_ebl_resp, + IOSM_EBL_RSP_SIZE, mdm_rsp); + return ret; +} + +/* 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) +{ + __le32 reg_info[2]; /* 0th position region address, 1st position size */ + char *file_ptr; + u32 rest_len; + u32 raw_len; + int ret; + + file_ptr = (char *)fw->data; + reg_info[0] = cpu_to_le32(ipc_devlink->param.address); + + if (!ipc_devlink->param.erase_full_flash_done) { + reg_info[1] = cpu_to_le32(ipc_devlink->param.address + + fw->size - 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; + + rest_len = fw->size; + + /* 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; +} + +/* Flash the individual fls files */ +int ipc_flash_send_fls(struct iosm_devlink *ipc_devlink, + const struct firmware *fw, u8 *mdm_rsp) +{ + u16 flash_cmd; + int ret; + + 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 (!ipc_devlink->param.download_region) { + ret = ipc_flash_send_receive(ipc_devlink, FLASH_SEC_START, + (u8 *)fw->data, fw->size, mdm_rsp); + if (ret) + goto ipc_flash_err; + } else { + /* Download regions */ + ipc_devlink->param.region_count -= IOSM_SET_FLAG; + ret = ipc_flash_download_region(ipc_devlink, fw, mdm_rsp); + if (ret) + goto ipc_flash_err; + + if (!ipc_devlink->param.region_count) { + /* 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; +} + +/* Inject RPSI */ +int ipc_flash_boot_psi(struct iosm_devlink *ipc_devlink, + const struct firmware *fw) +{ + u8 psi_ack_byte[IOSM_PSI_ACK], read_data[2]; + u32 bytes_read; + u8 *psi_code; + int ret; + + dev_dbg(ipc_devlink->dev, "Boot transfer PSI"); + psi_code = kmemdup(fw->data, fw->size, GFP_KERNEL); + if (!psi_code) + return -ENOMEM; + + ret = ipc_imem_sys_devlink_write(ipc_devlink, psi_code, fw->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; +} + +/* Inject EBL */ +int ipc_flash_boot_ebl(struct iosm_devlink *ipc_devlink, + const struct firmware *fw) +{ + u32 ebl_size = fw->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, (unsigned char *)fw->data, + fw->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; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_flash.h b/drivers/net/wwan/iosm/iosm_ipc_flash.h new file mode 100644 index 000000000000..aee848927228 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_flash.h @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-2021 Intel Corporation. + */ + +#ifndef _IOSM_IPC_FLASH_H +#define _IOSM_IPC_FLASH_H + +/* Buffer size used to read the fls image */ +#define IOSM_FLS_BUF_SIZE 0x00100000 +/* Full erase start address */ +#define IOSM_ERASE_START_ADDR 0x00000000 +/* Erase length for NAND flash */ +#define IOSM_ERASE_LEN 0xFFFFFFFF +/* EBL response Header size */ +#define IOSM_EBL_HEAD_SIZE 8 +/* EBL payload size */ +#define IOSM_EBL_W_PAYL_SIZE 2048 +/* Total EBL pack size */ +#define IOSM_EBL_W_PACK_SIZE (IOSM_EBL_HEAD_SIZE + IOSM_EBL_W_PAYL_SIZE) +/* EBL payload size */ +#define IOSM_EBL_DW_PAYL_SIZE 16384 +/* Total EBL pack size */ +#define IOSM_EBL_DW_PACK_SIZE (IOSM_EBL_HEAD_SIZE + IOSM_EBL_DW_PAYL_SIZE) +/* EBL name size */ +#define IOSM_EBL_NAME 32 +/* Maximum supported error types */ +#define IOSM_MAX_ERRORS 8 +/* Read size for RPSI/EBL response */ +#define IOSM_READ_SIZE 2 +/* Link establishment response ack size */ +#define IOSM_LER_ACK_SIZE 2 +/* PSI ACK len */ +#define IOSM_PSI_ACK 8 +/* SWID capability for packed swid type */ +#define IOSM_EXT_CAP_SWID_OOS_PACK 0x02 +/* EBL error response buffer */ +#define IOSM_EBL_RSP_BUFF 0x0041 +/* SWID string length */ +#define IOSM_SWID_STR 64 +/* Load EBL command size */ +#define IOSM_RPSI_LOAD_SIZE 0 +/* EBL payload checksum */ +#define IOSM_EBL_CKSM 0x0000FFFF +/* SWID msg len and argument */ +#define IOSM_MSG_LEN_ARG 0 +/* Data to be sent to modem */ +#define IOSM_MDM_SEND_DATA 0x0000 +/* Data received from modem as part of erase check */ +#define IOSM_MDM_ERASE_RSP 0x0001 +/* Bit shift to calculate Checksum */ +#define IOSM_EBL_PAYL_SHIFT 16 +/* Flag To be set */ +#define IOSM_SET_FLAG 1 +/* Set flash erase check timeout to 100 msec */ +#define IOSM_FLASH_ERASE_CHECK_TIMEOUT 100 +/* Set flash erase check interval to 20 msec */ +#define IOSM_FLASH_ERASE_CHECK_INTERVAL 20 +/* Link establishment response ack size */ +#define IOSM_LER_RSP_SIZE 60 + +/** + * enum iosm_flash_package_type - Enum for the flashing operations + * @FLASH_SET_PROT_CONF: Write EBL capabilities + * @FLASH_SEC_START: Start writing the secpack + * @FLASH_SEC_END: Validate secpack end + * @FLASH_SET_ADDRESS: Set the address for flashing + * @FLASH_ERASE_START: Start erase before flashing + * @FLASH_ERASE_CHECK: Validate the erase functionality + * @FLASH_OOS_CONTROL: Retrieve data based on oos actions + * @FLASH_OOS_DATA_READ: Read data from EBL + * @FLASH_WRITE_IMAGE_RAW: Write the raw image to flash + */ +enum iosm_flash_package_type { + FLASH_SET_PROT_CONF = 0x0086, + FLASH_SEC_START = 0x0204, + FLASH_SEC_END, + FLASH_SET_ADDRESS = 0x0802, + FLASH_ERASE_START = 0x0805, + FLASH_ERASE_CHECK, + FLASH_OOS_CONTROL = 0x080C, + FLASH_OOS_DATA_READ = 0x080E, + FLASH_WRITE_IMAGE_RAW, +}; + +/** + * enum iosm_out_of_session_action - Actions possible over the + * OutOfSession command interface + * @FLASH_OOSC_ACTION_READ: Read data according to its type + * @FLASH_OOSC_ACTION_ERASE: Erase data according to its type + */ +enum iosm_out_of_session_action { + FLASH_OOSC_ACTION_READ = 2, + FLASH_OOSC_ACTION_ERASE = 3, +}; + +/** + * enum iosm_out_of_session_type - Data types that can be handled over the + * Out Of Session command Interface + * @FLASH_OOSC_TYPE_ALL_FLASH: The whole flash area + * @FLASH_OOSC_TYPE_SWID_TABLE: Read the swid table from the target + */ +enum iosm_out_of_session_type { + FLASH_OOSC_TYPE_ALL_FLASH = 8, + FLASH_OOSC_TYPE_SWID_TABLE = 16, +}; + +/** + * enum iosm_ebl_caps - EBL capability settings + * @IOSM_CAP_NOT_ENHANCED: If capability not supported + * @IOSM_CAP_USE_EXT_CAP: To be set if extended capability is set + * @IOSM_EXT_CAP_ERASE_ALL: Set Erase all capability + * @IOSM_EXT_CAP_COMMIT_ALL: Set the commit all capability + */ +enum iosm_ebl_caps { + IOSM_CAP_NOT_ENHANCED = 0x00, + IOSM_CAP_USE_EXT_CAP = 0x01, + IOSM_EXT_CAP_ERASE_ALL = 0x08, + IOSM_EXT_CAP_COMMIT_ALL = 0x20, +}; + +/** + * enum iosm_ebl_rsp - EBL response field + * @EBL_CAPS_FLAG: EBL capability flag + * @EBL_SKIP_ERASE: EBL skip erase flag + * @EBL_SKIP_CRC: EBL skip wr_pack crc + * @EBL_EXT_CAPS_HANDLED: EBL extended capability handled flag + * @EBL_OOS_CONFIG: EBL oos configuration + * @EBL_RSP_SW_INFO_VER: EBL SW info version + */ +enum iosm_ebl_rsp { + EBL_CAPS_FLAG = 50, + EBL_SKIP_ERASE = 54, + EBL_SKIP_CRC = 55, + EBL_EXT_CAPS_HANDLED = 57, + EBL_OOS_CONFIG = 64, + EBL_RSP_SW_INFO_VER = 70, +}; + +/** + * enum iosm_mdm_send_recv_data - Data to send to modem + * @IOSM_MDM_SEND_2: Send 2 bytes of payload + * @IOSM_MDM_SEND_4: Send 4 bytes of payload + * @IOSM_MDM_SEND_8: Send 8 bytes of payload + * @IOSM_MDM_SEND_16: Send 16 bytes of payload + */ +enum iosm_mdm_send_recv_data { + IOSM_MDM_SEND_2 = 2, + IOSM_MDM_SEND_4 = 4, + IOSM_MDM_SEND_8 = 8, + IOSM_MDM_SEND_16 = 16, +}; + +/** + * struct iosm_ebl_one_error - Structure containing error details + * @error_class: Error type- standard, security and text error + * @error_code: Specific error from error type + */ +struct iosm_ebl_one_error { + u16 error_class; + u16 error_code; +}; + +/** + * struct iosm_ebl_error- Structure with max error type supported + * @error: Array of one_error structure with max errors + */ +struct iosm_ebl_error { + struct iosm_ebl_one_error error[IOSM_MAX_ERRORS]; +}; + +/** + * struct iosm_swid_table - SWID table data for modem + * @number_of_data_sets: Number of swid types + * @sw_id_type: SWID type - SWID + * @sw_id_val: SWID value + * @rf_engine_id_type: RF engine ID type - RF_ENGINE_ID + * @rf_engine_id_val: RF engine ID value + */ +struct iosm_swid_table { + u32 number_of_data_sets; + char sw_id_type[IOSM_EBL_NAME]; + u32 sw_id_val; + char rf_engine_id_type[IOSM_EBL_NAME]; + u32 rf_engine_id_val; +}; + +/** + * struct iosm_flash_msg_control - Data sent to modem + * @action: Action to be performed + * @type: Type of action + * @length: Length of the action + * @arguments: Argument value sent to modem + */ +struct iosm_flash_msg_control { + __le32 action; + __le32 type; + __le32 length; + __le32 arguments; +}; + +/** + * struct iosm_flash_data - Header Data to be sent to modem + * @checksum: Checksum value calculated for the payload data + * @pack_id: Flash Action type + * @msg_length: Payload length + */ +struct iosm_flash_data { + __le16 checksum; + __le16 pack_id; + __le32 msg_length; +}; + +/** + * 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); + +/** + * 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); + +/** + * ipc_flash_boot_set_capabilities - Set modem bool 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_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); + +/** + * ipc_flash_read_swid - Get swid during flash phase + * @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_read_swid(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp); + +/** + * 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); +#endif diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem.c b/drivers/net/wwan/iosm/iosm_ipc_imem.c index 9f00e36b7f79..1cf49e9959f4 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem.c +++ b/drivers/net/wwan/iosm/iosm_ipc_imem.c @@ -6,6 +6,8 @@ #include #include "iosm_ipc_chnl_cfg.h" +#include "iosm_ipc_devlink.h" +#include "iosm_ipc_flash.h" #include "iosm_ipc_imem.h" #include "iosm_ipc_port.h" @@ -263,9 +265,12 @@ static void ipc_imem_dl_skb_process(struct iosm_imem *ipc_imem, switch (pipe->channel->ctype) { case IPC_CTYPE_CTRL: port_id = pipe->channel->channel_id; - - /* Pass the packet to the wwan layer. */ - wwan_port_rx(ipc_imem->ipc_port[port_id]->iosm_port, skb); + if (port_id == IPC_MEM_CTRL_CHL_ID_7) + ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink, + skb); + else + wwan_port_rx(ipc_imem->ipc_port[port_id]->iosm_port, + skb); break; case IPC_CTYPE_WWAN: @@ -399,19 +404,8 @@ static void ipc_imem_rom_irq_exec(struct iosm_imem *ipc_imem) { struct ipc_mem_channel *channel; - if (ipc_imem->flash_channel_id < 0) { - ipc_imem->rom_exit_code = IMEM_ROM_EXIT_FAIL; - dev_err(ipc_imem->dev, "Missing flash app:%d", - ipc_imem->flash_channel_id); - return; - } - + channel = ipc_imem->ipc_devlink->devlink_sio.channel; ipc_imem->rom_exit_code = ipc_mmio_get_rom_exit_code(ipc_imem->mmio); - - /* Wake up the flash app to continue or to terminate depending - * on the CP ROM exit code. - */ - channel = &ipc_imem->channels[ipc_imem->flash_channel_id]; complete(&channel->ul_sem); } @@ -572,7 +566,7 @@ static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq) enum ipc_phase old_phase, phase; bool retry_allocation = false; bool ul_pending = false; - int ch_id, i; + int i; if (irq != IMEM_IRQ_DONT_CARE) ipc_imem->ev_irq_pending[irq] = false; @@ -696,11 +690,8 @@ static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq) if ((phase == IPC_P_PSI || phase == IPC_P_EBL) && ipc_imem->ipc_requested_state == IPC_MEM_DEVICE_IPC_RUNNING && ipc_mmio_get_ipc_state(ipc_imem->mmio) == - IPC_MEM_DEVICE_IPC_RUNNING && - ipc_imem->flash_channel_id >= 0) { - /* Wake up the flash app to open the pipes. */ - ch_id = ipc_imem->flash_channel_id; - complete(&ipc_imem->channels[ch_id].ul_sem); + IPC_MEM_DEVICE_IPC_RUNNING) { + complete(&ipc_imem->ipc_devlink->devlink_sio.channel->ul_sem); } /* Reset the expected CP state. */ @@ -1176,6 +1167,9 @@ void ipc_imem_cleanup(struct iosm_imem *ipc_imem) ipc_port_deinit(ipc_imem->ipc_port); } + if (ipc_imem->ipc_devlink) + ipc_devlink_deinit(ipc_imem->ipc_devlink); + ipc_imem_device_ipc_uninit(ipc_imem); ipc_imem_channel_reset(ipc_imem); @@ -1258,6 +1252,7 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id, void __iomem *mmio, struct device *dev) { struct iosm_imem *ipc_imem = kzalloc(sizeof(*pcie->imem), GFP_KERNEL); + enum ipc_mem_exec_stage stage; if (!ipc_imem) return NULL; @@ -1272,9 +1267,6 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id, ipc_imem->cp_version = 0; ipc_imem->device_sleep = IPC_HOST_SLEEP_ENTER_SLEEP; - /* Reset the flash channel id. */ - ipc_imem->flash_channel_id = -1; - /* Reset the max number of configured channels */ ipc_imem->nr_of_channels = 0; @@ -1328,8 +1320,21 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id, goto imem_config_fail; } - return ipc_imem; + stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); + if (stage == IPC_MEM_EXEC_STAGE_BOOT) { + /* Alloc and Register devlink */ + ipc_imem->ipc_devlink = ipc_devlink_init(ipc_imem); + if (!ipc_imem->ipc_devlink) { + dev_err(ipc_imem->dev, "Devlink register failed"); + goto imem_config_fail; + } + if (ipc_flash_link_establish(ipc_imem)) + goto devlink_channel_fail; + } + return ipc_imem; +devlink_channel_fail: + ipc_devlink_deinit(ipc_imem->ipc_devlink); imem_config_fail: hrtimer_cancel(&ipc_imem->td_alloc_timer); hrtimer_cancel(&ipc_imem->fast_update_timer); @@ -1361,3 +1366,51 @@ void ipc_imem_td_update_timer_suspend(struct iosm_imem *ipc_imem, bool suspend) { ipc_imem->td_update_timer_suspended = suspend; } + +/* Verify the CP execution state, copy the chip info, + * change the execution phase to ROM + */ +static int ipc_imem_devlink_trigger_chip_info_cb(struct iosm_imem *ipc_imem, + int arg, void *msg, + size_t msgsize) +{ + enum ipc_mem_exec_stage stage; + struct sk_buff *skb; + int rc = -EINVAL; + size_t size; + + /* Test the CP execution state. */ + stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); + if (stage != IPC_MEM_EXEC_STAGE_BOOT) { + dev_err(ipc_imem->dev, + "Execution_stage: expected BOOT, received = %X", stage); + goto trigger_chip_info_fail; + } + /* Allocate a new sk buf for the chip info. */ + size = ipc_imem->mmio->chip_info_size; + if (size > IOSM_CHIP_INFO_SIZE_MAX) + goto trigger_chip_info_fail; + + skb = ipc_pcie_alloc_local_skb(ipc_imem->pcie, GFP_ATOMIC, size); + if (!skb) { + dev_err(ipc_imem->dev, "exhausted skbuf kernel DL memory"); + rc = -ENOMEM; + goto trigger_chip_info_fail; + } + /* Copy the chip info characters into the ipc_skb. */ + ipc_mmio_copy_chip_info(ipc_imem->mmio, skb_put(skb, size), size); + /* First change to the ROM boot phase. */ + dev_dbg(ipc_imem->dev, "execution_stage[%X] eq. BOOT", stage); + ipc_imem->phase = ipc_imem_phase_update(ipc_imem); + ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink, skb); + rc = 0; +trigger_chip_info_fail: + return rc; +} + +int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem) +{ + return ipc_task_queue_send_task(ipc_imem, + ipc_imem_devlink_trigger_chip_info_cb, + 0, NULL, 0, true); +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem.h b/drivers/net/wwan/iosm/iosm_ipc_imem.h index dc65b0712261..6be6708b4eec 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem.h +++ b/drivers/net/wwan/iosm/iosm_ipc_imem.h @@ -69,7 +69,7 @@ struct ipc_chnl_cfg; #define IMEM_IRQ_DONT_CARE (-1) -#define IPC_MEM_MAX_CHANNELS 7 +#define IPC_MEM_MAX_CHANNELS 8 #define IPC_MEM_MUX_IP_SESSION_ENTRIES 8 @@ -98,6 +98,7 @@ struct ipc_chnl_cfg; #define IPC_MEM_DL_ETH_OFFSET 16 #define IPC_CB(skb) ((struct ipc_skb_cb *)((skb)->cb)) +#define IOSM_CHIP_INFO_SIZE_MAX 100 #define FULLY_FUNCTIONAL 0 @@ -304,9 +305,9 @@ enum ipc_phase { * @ipc_port: IPC PORT data structure pointer * @pcie: IPC PCIe * @dev: Pointer to device structure - * @flash_channel_id: Reserved channel id for flashing to RAM. * @ipc_requested_state: Expected IPC state on CP. * @channels: Channel list with UL/DL pipe pairs. + * @ipc_devlink: IPC Devlink data structure pointer * @ipc_status: local ipc_status * @nr_of_channels: number of configured channels * @startup_timer: startup timer for NAND support. @@ -349,9 +350,9 @@ struct iosm_imem { struct iosm_cdev *ipc_port[IPC_MEM_MAX_CHANNELS]; struct iosm_pcie *pcie; struct device *dev; - int flash_channel_id; enum ipc_mem_device_ipc_state ipc_requested_state; struct ipc_mem_channel channels[IPC_MEM_MAX_CHANNELS]; + struct iosm_devlink *ipc_devlink; u32 ipc_status; u32 nr_of_channels; struct hrtimer startup_timer; @@ -575,4 +576,15 @@ void ipc_imem_ipc_init_check(struct iosm_imem *ipc_imem); */ void ipc_imem_channel_init(struct iosm_imem *ipc_imem, enum ipc_ctype ctype, struct ipc_chnl_cfg chnl_cfg, u32 irq_moderation); + +/** + * ipc_imem_devlink_trigger_chip_info - Inform devlink that the chip + * information are available if the + * flashing to RAM interworking shall be + * executed. + * @ipc_imem: Pointer to imem structure + * + * Returns: 0 on success, -1 on failure + */ +int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem); #endif diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c index 0a472ce77370..b885a6570235 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c +++ b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c @@ -6,6 +6,7 @@ #include #include "iosm_ipc_chnl_cfg.h" +#include "iosm_ipc_devlink.h" #include "iosm_ipc_imem.h" #include "iosm_ipc_imem_ops.h" #include "iosm_ipc_port.h" @@ -331,3 +332,319 @@ int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb) out: return ret; } + +/* Open a SIO link to CP and return the channel instance */ +struct ipc_mem_channel *ipc_imem_sys_devlink_open(struct iosm_imem *ipc_imem) +{ + struct ipc_mem_channel *channel; + enum ipc_phase phase; + int channel_id; + + phase = ipc_imem_phase_update(ipc_imem); + switch (phase) { + case IPC_P_OFF: + case IPC_P_ROM: + /* Get a channel id as flash id and reserve it. */ + channel_id = ipc_imem_channel_alloc(ipc_imem, + IPC_MEM_CTRL_CHL_ID_7, + IPC_CTYPE_CTRL); + + if (channel_id < 0) { + dev_err(ipc_imem->dev, + "reservation of a flash channel id failed"); + goto error; + } + + ipc_imem->ipc_devlink->devlink_sio.channel_id = channel_id; + channel = &ipc_imem->channels[channel_id]; + + /* Enqueue chip info data to be read */ + if (ipc_imem_devlink_trigger_chip_info(ipc_imem)) { + dev_err(ipc_imem->dev, "Enqueue of chip info failed"); + channel->state = IMEM_CHANNEL_FREE; + goto error; + } + + return channel; + + case IPC_P_PSI: + case IPC_P_EBL: + ipc_imem->cp_version = ipc_mmio_get_cp_version(ipc_imem->mmio); + if (ipc_imem->cp_version == -1) { + dev_err(ipc_imem->dev, "invalid CP version"); + goto error; + } + + channel_id = ipc_imem->ipc_devlink->devlink_sio.channel_id; + return ipc_imem_channel_open(ipc_imem, channel_id, + IPC_HP_CDEV_OPEN); + + default: + /* CP is in the wrong state (e.g. CRASH or CD_READY) */ + dev_err(ipc_imem->dev, "SIO open refused, phase %d", phase); + } +error: + return NULL; +} + +/* Release a SIO channel link to CP. */ +void ipc_imem_sys_devlink_close(struct iosm_devlink *ipc_devlink) +{ + struct iosm_imem *ipc_imem = ipc_devlink->pcie->imem; + int boot_check_timeout = BOOT_CHECK_DEFAULT_TIMEOUT; + enum ipc_mem_exec_stage exec_stage; + struct ipc_mem_channel *channel; + enum ipc_phase curr_phase; + int status = 0; + u32 tail = 0; + + channel = ipc_imem->ipc_devlink->devlink_sio.channel; + curr_phase = ipc_imem->phase; + /* Increase the total wait time to boot_check_timeout */ + do { + exec_stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); + if (exec_stage == IPC_MEM_EXEC_STAGE_RUN || + exec_stage == IPC_MEM_EXEC_STAGE_PSI) + break; + msleep(20); + boot_check_timeout -= 20; + } while (boot_check_timeout > 0); + + /* If there are any pending TDs then wait for Timeout/Completion before + * closing pipe. + */ + if (channel->ul_pipe.old_tail != channel->ul_pipe.old_head) { + status = wait_for_completion_interruptible_timeout + (&ipc_imem->ul_pend_sem, + msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT)); + if (status == 0) { + dev_dbg(ipc_imem->dev, + "Data Timeout on UL-Pipe:%d Head:%d Tail:%d", + channel->ul_pipe.pipe_nr, + channel->ul_pipe.old_head, + channel->ul_pipe.old_tail); + } + } + + ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol, + &channel->dl_pipe, NULL, &tail); + + if (tail != channel->dl_pipe.old_tail) { + status = wait_for_completion_interruptible_timeout + (&ipc_imem->dl_pend_sem, + msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT)); + if (status == 0) { + dev_dbg(ipc_imem->dev, + "Data Timeout on DL-Pipe:%d Head:%d Tail:%d", + channel->dl_pipe.pipe_nr, + channel->dl_pipe.old_head, + channel->dl_pipe.old_tail); + } + } + + /* Due to wait for completion in messages, there is a small window + * between closing the pipe and updating the channel is closed. In this + * small window there could be HP update from Host Driver. Hence update + * the channel state as CLOSING to aviod unnecessary interrupt + * towards CP. + */ + channel->state = IMEM_CHANNEL_CLOSING; + /* Release the pipe resources */ + ipc_imem_pipe_cleanup(ipc_imem, &channel->ul_pipe); + ipc_imem_pipe_cleanup(ipc_imem, &channel->dl_pipe); +} + +void ipc_imem_sys_devlink_notify_rx(struct iosm_devlink *ipc_devlink, + struct sk_buff *skb) +{ + skb_queue_tail(&ipc_devlink->devlink_sio.rx_list, skb); + complete(&ipc_devlink->devlink_sio.read_sem); +} + +/* PSI transfer */ +static int ipc_imem_sys_psi_transfer(struct iosm_imem *ipc_imem, + struct ipc_mem_channel *channel, + unsigned char *buf, int count) +{ + int psi_start_timeout = PSI_START_DEFAULT_TIMEOUT; + enum ipc_mem_exec_stage exec_stage; + + dma_addr_t mapping = 0; + int ret; + + ret = ipc_pcie_addr_map(ipc_imem->pcie, buf, count, &mapping, + DMA_TO_DEVICE); + if (ret) + goto pcie_addr_map_fail; + + /* Save the PSI information for the CP ROM driver on the doorbell + * scratchpad. + */ + ipc_mmio_set_psi_addr_and_size(ipc_imem->mmio, mapping, count); + ipc_doorbell_fire(ipc_imem->pcie, 0, IPC_MEM_EXEC_STAGE_BOOT); + + ret = wait_for_completion_interruptible_timeout + (&channel->ul_sem, + msecs_to_jiffies(IPC_PSI_TRANSFER_TIMEOUT)); + + if (ret <= 0) { + dev_err(ipc_imem->dev, "Failed PSI transfer to CP, Error-%d", + ret); + goto psi_transfer_fail; + } + /* If the PSI download fails, return the CP boot ROM exit code */ + if (ipc_imem->rom_exit_code != IMEM_ROM_EXIT_OPEN_EXT && + ipc_imem->rom_exit_code != IMEM_ROM_EXIT_CERT_EXT) { + ret = (-1) * ((int)ipc_imem->rom_exit_code); + goto psi_transfer_fail; + } + + dev_dbg(ipc_imem->dev, "PSI image successfully downloaded"); + + /* Wait psi_start_timeout milliseconds until the CP PSI image is + * running and updates the execution_stage field with + * IPC_MEM_EXEC_STAGE_PSI. Verify the execution stage. + */ + do { + exec_stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); + + if (exec_stage == IPC_MEM_EXEC_STAGE_PSI) + break; + + msleep(20); + psi_start_timeout -= 20; + } while (psi_start_timeout > 0); + + if (exec_stage != IPC_MEM_EXEC_STAGE_PSI) + goto psi_transfer_fail; /* Unknown status of CP PSI process. */ + + ipc_imem->phase = IPC_P_PSI; + + /* Enter the PSI phase. */ + dev_dbg(ipc_imem->dev, "execution_stage[%X] eq. PSI", exec_stage); + + /* Request the RUNNING state from CP and wait until it was reached + * or timeout. + */ + ipc_imem_ipc_init_check(ipc_imem); + + ret = wait_for_completion_interruptible_timeout + (&channel->ul_sem, msecs_to_jiffies(IPC_PSI_TRANSFER_TIMEOUT)); + if (ret <= 0) { + dev_err(ipc_imem->dev, + "Failed PSI RUNNING state on CP, Error-%d", ret); + goto psi_transfer_fail; + } + + if (ipc_mmio_get_ipc_state(ipc_imem->mmio) != + IPC_MEM_DEVICE_IPC_RUNNING) { + dev_err(ipc_imem->dev, + "ch[%d] %s: unexpected CP IPC state %d, not RUNNING", + channel->channel_id, + ipc_imem_phase_get_string(ipc_imem->phase), + ipc_mmio_get_ipc_state(ipc_imem->mmio)); + + goto psi_transfer_fail; + } + + /* Create the flash channel for the transfer of the images. */ + if (!ipc_imem_sys_devlink_open(ipc_imem)) { + dev_err(ipc_imem->dev, "can't open flash_channel"); + goto psi_transfer_fail; + } + + ret = 0; +psi_transfer_fail: + ipc_pcie_addr_unmap(ipc_imem->pcie, count, mapping, DMA_TO_DEVICE); +pcie_addr_map_fail: + return ret; +} + +int ipc_imem_sys_devlink_write(struct iosm_devlink *ipc_devlink, + unsigned char *buf, int count) +{ + struct iosm_imem *ipc_imem = ipc_devlink->pcie->imem; + struct ipc_mem_channel *channel; + struct sk_buff *skb; + dma_addr_t mapping; + int ret; + + channel = ipc_imem->ipc_devlink->devlink_sio.channel; + + /* In the ROM phase the PSI image is passed to CP about a specific + * shared memory area and doorbell scratchpad directly. + */ + if (ipc_imem->phase == IPC_P_ROM) { + ret = ipc_imem_sys_psi_transfer(ipc_imem, channel, buf, count); + /* If the PSI transfer fails then send crash + * Signature. + */ + if (ret > 0) + ipc_imem_msg_send_feature_set(ipc_imem, + IPC_MEM_INBAND_CRASH_SIG, + false); + goto out; + } + + /* Allocate skb memory for the uplink buffer. */ + skb = ipc_pcie_alloc_skb(ipc_devlink->pcie, count, GFP_KERNEL, &mapping, + DMA_TO_DEVICE, 0); + if (!skb) { + ret = -ENOMEM; + goto out; + } + + memcpy(skb_put(skb, count), buf, count); + + IPC_CB(skb)->op_type = UL_USR_OP_BLOCKED; + + /* Add skb to the uplink skbuf accumulator. */ + skb_queue_tail(&channel->ul_list, skb); + + /* Inform the IPC tasklet to pass uplink IP packets to CP. */ + if (!ipc_imem_call_cdev_write(ipc_imem)) { + ret = wait_for_completion_interruptible(&channel->ul_sem); + + if (ret < 0) { + dev_err(ipc_imem->dev, + "ch[%d] no CP confirmation, status = %d", + channel->channel_id, ret); + ipc_pcie_kfree_skb(ipc_devlink->pcie, skb); + goto out; + } + } + ret = 0; +out: + return ret; +} + +int ipc_imem_sys_devlink_read(struct iosm_devlink *devlink, u8 *data, + u32 bytes_to_read, u32 *bytes_read) +{ + struct sk_buff *skb = NULL; + int rc = 0; + + /* check skb is available in rx_list or wait for skb */ + devlink->devlink_sio.devlink_read_pend = 1; + while (!skb && !(skb = skb_dequeue(&devlink->devlink_sio.rx_list))) { + if (!wait_for_completion_interruptible_timeout + (&devlink->devlink_sio.read_sem, + msecs_to_jiffies(IPC_READ_TIMEOUT))) { + dev_err(devlink->dev, "Read timedout"); + rc = -ETIMEDOUT; + goto devlink_read_fail; + } + } + devlink->devlink_sio.devlink_read_pend = 0; + if (bytes_to_read < skb->len) { + dev_err(devlink->dev, "Invalid size,expected len %d", skb->len); + rc = -EINVAL; + goto devlink_read_fail; + } + *bytes_read = skb->len; + memcpy(data, skb->data, skb->len); + +devlink_read_fail: + ipc_pcie_kfree_skb(devlink->pcie, skb); + return rc; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h index 2007fe23e9a5..f0c88ac5643c 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h +++ b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h @@ -9,7 +9,7 @@ #include "iosm_ipc_mux_codec.h" /* Maximum wait time for blocking read */ -#define IPC_READ_TIMEOUT 500 +#define IPC_READ_TIMEOUT 3000 /* The delay in ms for defering the unregister */ #define SIO_UNREGISTER_DEFER_DELAY_MS 1 @@ -98,4 +98,51 @@ int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem, int if_id, */ void ipc_imem_wwan_channel_init(struct iosm_imem *ipc_imem, enum ipc_mux_protocol mux_type); + +/** + * ipc_imem_sys_devlink_open - Open a Flash/CD Channel link to CP + * @ipc_imem: iosm_imem instance + * + * Return: channel instance on success, NULL for failure + */ +struct ipc_mem_channel *ipc_imem_sys_devlink_open(struct iosm_imem *ipc_imem); + +/** + * ipc_imem_sys_devlink_close - Release a Flash/CD channel link to CP + * @ipc_devlink: Pointer to ipc_devlink data-struct + * + */ +void ipc_imem_sys_devlink_close(struct iosm_devlink *ipc_devlink); + +/** + * ipc_imem_sys_devlink_notify_rx - Receive downlink characters from CP, + * the downlink skbuf is added at the end of the + * downlink or rx list + * @ipc_devlink: Pointer to ipc_devlink data-struct + * @skb: Pointer to sk buffer + */ +void ipc_imem_sys_devlink_notify_rx(struct iosm_devlink *ipc_devlink, + struct sk_buff *skb); + +/** + * ipc_imem_sys_devlink_read - Copy the rx data and free the skbuf + * @ipc_devlink: Devlink instance + * @data: Buffer to read the data from modem + * @bytes_to_read: Size of destination buffer + * @bytes_read: Number of bytes read + * + * Return: 0 on success and failure value on error + */ +int ipc_imem_sys_devlink_read(struct iosm_devlink *ipc_devlink, u8 *data, + u32 bytes_to_read, u32 *bytes_read); + +/** + * ipc_imem_sys_devlink_write - Route the uplink buffer to CP + * @ipc_devlink: Devlink_sio instance + * @buf: Pointer to buffer + * @count: Number of data bytes to write + * Return: 0 on success and failure value on error + */ +int ipc_imem_sys_devlink_write(struct iosm_devlink *ipc_devlink, + unsigned char *buf, int count); #endif