Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: (GPL-2.0-only)
#
# Copyright (C) 2020-21 Intel Corporation.
#
iosm-y = \
iosm_ipc_task_queue.o \
iosm_ipc_imem.o \
iosm_ipc_imem_ops.o \
iosm_ipc_mmio.o \
iosm_ipc_port.o \
iosm_ipc_wwan.o \
iosm_ipc_uevent.o \
iosm_ipc_pm.o \
iosm_ipc_pcie.o \
iosm_ipc_irq.o \
iosm_ipc_chnl_cfg.o \
iosm_ipc_protocol.o \
iosm_ipc_protocol_ops.o \
iosm_ipc_mux.o \
iosm_ipc_mux_codec.o \
iosm_ipc_devlink.o \
iosm_ipc_flash.o \
iosm_ipc_coredump.o
iosm-$(CONFIG_WWAN_DEBUGFS) += \
iosm_ipc_debugfs.o \
iosm_ipc_trace.o
obj-$(CONFIG_IOSM) := iosm.o
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/wwan.h>
#include "iosm_ipc_chnl_cfg.h"
/* Max. sizes of a downlink buffers */
#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)
#define IPC_MEM_MAX_DL_MBIM_BUF_SIZE IPC_MEM_MAX_DL_RPC_BUF_SIZE
/* Max. transfer descriptors for a pipe. */
#define IPC_MEM_MAX_TDS_FLASH_DL 3
#define IPC_MEM_MAX_TDS_FLASH_UL 6
#define IPC_MEM_MAX_TDS_AT 4
#define IPC_MEM_MAX_TDS_RPC 4
#define IPC_MEM_MAX_TDS_MBIM IPC_MEM_MAX_TDS_RPC
#define IPC_MEM_MAX_TDS_LOOPBACK 11
/* Accumulation backoff usec */
#define IRQ_ACC_BACKOFF_OFF 0
/* MUX acc backoff 1ms */
#define IRQ_ACC_BACKOFF_MUX 1000
/* Modem channel configuration table
* Always reserve element zero for flash channel.
*/
static struct ipc_chnl_cfg modem_cfg[] = {
/* IP Mux */
{ IPC_MEM_IP_CHL_ID_0, IPC_MEM_PIPE_0, IPC_MEM_PIPE_1,
IPC_MEM_MAX_TDS_MUX_LITE_UL, IPC_MEM_MAX_TDS_MUX_LITE_DL,
IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE, WWAN_PORT_UNKNOWN },
/* RPC - 0 */
{ IPC_MEM_CTRL_CHL_ID_1, IPC_MEM_PIPE_2, IPC_MEM_PIPE_3,
IPC_MEM_MAX_TDS_RPC, IPC_MEM_MAX_TDS_RPC,
IPC_MEM_MAX_DL_RPC_BUF_SIZE, WWAN_PORT_XMMRPC },
/* IAT0 */
{ IPC_MEM_CTRL_CHL_ID_2, IPC_MEM_PIPE_4, IPC_MEM_PIPE_5,
IPC_MEM_MAX_TDS_AT, IPC_MEM_MAX_TDS_AT, IPC_MEM_MAX_DL_AT_BUF_SIZE,
WWAN_PORT_AT },
/* Trace */
{ IPC_MEM_CTRL_CHL_ID_3, IPC_MEM_PIPE_6, IPC_MEM_PIPE_7,
IPC_MEM_TDS_TRC, IPC_MEM_TDS_TRC, IPC_MEM_MAX_DL_TRC_BUF_SIZE,
WWAN_PORT_UNKNOWN },
/* IAT1 */
{ IPC_MEM_CTRL_CHL_ID_4, IPC_MEM_PIPE_8, IPC_MEM_PIPE_9,
IPC_MEM_MAX_TDS_AT, IPC_MEM_MAX_TDS_AT, IPC_MEM_MAX_DL_AT_BUF_SIZE,
WWAN_PORT_AT },
/* Loopback */
{ IPC_MEM_CTRL_CHL_ID_5, IPC_MEM_PIPE_10, IPC_MEM_PIPE_11,
IPC_MEM_MAX_TDS_LOOPBACK, IPC_MEM_MAX_TDS_LOOPBACK,
IPC_MEM_MAX_DL_LOOPBACK_SIZE, WWAN_PORT_UNKNOWN },
/* MBIM Channel */
{ 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)
{
if (index >= ARRAY_SIZE(modem_cfg)) {
pr_err("index: %d and array size %zu", index,
ARRAY_SIZE(modem_cfg));
return -ECHRNG;
}
if (index == IPC_MEM_MUX_IP_CH_IF_ID)
chnl_cfg->accumulation_backoff = IRQ_ACC_BACKOFF_MUX;
else
chnl_cfg->accumulation_backoff = IRQ_ACC_BACKOFF_OFF;
chnl_cfg->ul_nr_of_entries = modem_cfg[index].ul_nr_of_entries;
chnl_cfg->dl_nr_of_entries = modem_cfg[index].dl_nr_of_entries;
chnl_cfg->dl_buf_size = modem_cfg[index].dl_buf_size;
chnl_cfg->id = modem_cfg[index].id;
chnl_cfg->ul_pipe = modem_cfg[index].ul_pipe;
chnl_cfg->dl_pipe = modem_cfg[index].dl_pipe;
chnl_cfg->wwan_port_type = modem_cfg[index].wwan_port_type;
return 0;
}
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation
*/
#ifndef IOSM_IPC_CHNL_CFG_H
#define IOSM_IPC_CHNL_CFG_H
#include "iosm_ipc_mux.h"
/* Number of TDs on the trace channel */
#define IPC_MEM_TDS_TRC 32
/* Trace channel TD buffer size. */
#define IPC_MEM_MAX_DL_TRC_BUF_SIZE 8192
/* Channel ID */
enum ipc_channel_id {
IPC_MEM_IP_CHL_ID_0 = 0,
IPC_MEM_CTRL_CHL_ID_1,
IPC_MEM_CTRL_CHL_ID_2,
IPC_MEM_CTRL_CHL_ID_3,
IPC_MEM_CTRL_CHL_ID_4,
IPC_MEM_CTRL_CHL_ID_5,
IPC_MEM_CTRL_CHL_ID_6,
IPC_MEM_CTRL_CHL_ID_7,
};
/**
* struct ipc_chnl_cfg - IPC channel configuration structure
* @id: Interface ID
* @ul_pipe: Uplink datastream
* @dl_pipe: Downlink datastream
* @ul_nr_of_entries: Number of Transfer descriptor uplink pipe
* @dl_nr_of_entries: Number of Transfer descriptor downlink pipe
* @dl_buf_size: Downlink buffer size
* @wwan_port_type: Wwan subsystem port type
* @accumulation_backoff: Time in usec for data accumalation
*/
struct ipc_chnl_cfg {
u32 id;
u32 ul_pipe;
u32 dl_pipe;
u32 ul_nr_of_entries;
u32 dl_nr_of_entries;
u32 dl_buf_size;
u32 wwan_port_type;
u32 accumulation_backoff;
};
/**
* ipc_chnl_cfg_get - Get pipe configuration.
* @chnl_cfg: Array of ipc_chnl_cfg struct
* @index: Channel index (up to MAX_CHANNELS)
*
* Return: 0 on success and failure value on error
*/
int ipc_chnl_cfg_get(struct ipc_chnl_cfg *chnl_cfg, int index);
#endif
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#include <linux/vmalloc.h>
#include "iosm_ipc_coredump.h"
/**
* 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)
{
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 0;
get_cd_fail:
vfree(data_ptr);
return ret;
}
/**
* ipc_coredump_get_list - Get coredump list from modem
* @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)
{
u32 byte_read, num_entries, file_size;
struct iosm_cd_table *cd_table;
u8 size[MAX_SIZE_LEN], i;
char *filename;
int ret;
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;
}
@@ -0,0 +1,56 @@
/* 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 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[];
} __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;
int ipc_coredump_collect(struct iosm_devlink *devlink, u8 **data, int entry,
u32 region_size);
int ipc_coredump_get_list(struct iosm_devlink *devlink, u16 cmd);
#endif /* _IOSM_IPC_COREDUMP_H_ */
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#include <linux/debugfs.h>
#include <linux/wwan.h>
#include "iosm_ipc_imem.h"
#include "iosm_ipc_trace.h"
#include "iosm_ipc_debugfs.h"
void ipc_debugfs_init(struct iosm_imem *ipc_imem)
{
ipc_imem->debugfs_wwan_dir = wwan_get_debugfs_dir(ipc_imem->dev);
ipc_imem->debugfs_dir = debugfs_create_dir(KBUILD_MODNAME,
ipc_imem->debugfs_wwan_dir);
ipc_imem->trace = ipc_trace_init(ipc_imem);
if (!ipc_imem->trace)
dev_warn(ipc_imem->dev, "trace channel init failed");
}
void ipc_debugfs_deinit(struct iosm_imem *ipc_imem)
{
ipc_trace_deinit(ipc_imem->trace);
debugfs_remove_recursive(ipc_imem->debugfs_dir);
wwan_put_debugfs_dir(ipc_imem->debugfs_wwan_dir);
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#ifndef IOSM_IPC_DEBUGFS_H
#define IOSM_IPC_DEBUGFS_H
#ifdef CONFIG_WWAN_DEBUGFS
void ipc_debugfs_init(struct iosm_imem *ipc_imem);
void ipc_debugfs_deinit(struct iosm_imem *ipc_imem);
#else
static inline void ipc_debugfs_init(struct iosm_imem *ipc_imem) {}
static inline void ipc_debugfs_deinit(struct iosm_imem *ipc_imem) {}
#endif
#endif
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#include <linux/vmalloc.h>
#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 netlink_ext_ack *extack)
{
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);
}
@@ -0,0 +1,205 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#ifndef _IOSM_IPC_DEVLINK_H_
#define _IOSM_IPC_DEVLINK_H_
#include <net/devlink.h>
#include "iosm_ipc_imem.h"
#include "iosm_ipc_imem_ops.h"
#include "iosm_ipc_pcie.h"
/* Image ext max len */
#define IOSM_DEVLINK_MAX_IMG_LEN 3
/* Magic Header */
#define IOSM_DEVLINK_MAGIC_HEADER "IOSM_DEVLINK_HEADER"
/* Magic Header len */
#define IOSM_DEVLINK_MAGIC_HEADER_LEN 20
/* Devlink image type */
#define IOSM_DEVLINK_IMG_TYPE 4
/* Reserve header size */
#define IOSM_DEVLINK_RESERVED 34
/* Devlink Image Header size */
#define IOSM_DEVLINK_HDR_SIZE sizeof(struct iosm_devlink_image)
/* 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
*/
enum iosm_devlink_param_id {
IOSM_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
IOSM_DEVLINK_PARAM_ID_ERASE_FULL_FLASH,
};
/**
* 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
* @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 {
u8 erase_full_flash;
u8 erase_full_flash_done;
};
/**
* struct iosm_devlink_image - Structure with Fls file header info
* @magic_header: Header of the firmware image
* @image_type: Firmware image type
* @region_address: Address of the region to be flashed
* @download_region: Field to identify if it is a region
* @last_region: Field to identify if it is last region
* @reserved: Reserved field
*/
struct iosm_devlink_image {
char magic_header[IOSM_DEVLINK_MAGIC_HEADER_LEN];
char image_type[IOSM_DEVLINK_IMG_TYPE];
__le32 region_address;
u8 download_region;
u8 last_region;
u8 reserved[IOSM_DEVLINK_RESERVED];
} __packed;
/**
* 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;
};
struct iosm_devlink *ipc_devlink_init(struct iosm_imem *ipc_imem);
void ipc_devlink_deinit(struct iosm_devlink *ipc_devlink);
int ipc_devlink_send_cmd(struct iosm_devlink *ipc_devlink, u16 cmd, u32 entry);
#endif /* _IOSM_IPC_DEVLINK_H */
@@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#include "iosm_ipc_coredump.h"
#include "iosm_ipc_devlink.h"
#include "iosm_ipc_flash.h"
/* This function will pack the data to be sent to the modem using the
* payload, payload length and pack id
*/
static int ipc_flash_proc_format_ebl_pack(struct iosm_flash_data *flash_req,
u32 pack_length, u16 pack_id,
u8 *payload, u32 payload_length)
{
u16 checksum = pack_id;
u32 i;
if (payload_length + IOSM_EBL_HEAD_SIZE > pack_length)
return -EINVAL;
flash_req->pack_id = cpu_to_le16(pack_id);
flash_req->msg_length = cpu_to_le32(payload_length);
checksum += (payload_length >> IOSM_EBL_PAYL_SHIFT) +
(payload_length & IOSM_EBL_CKSM);
for (i = 0; i < payload_length; i++)
checksum += payload[i];
flash_req->checksum = cpu_to_le16(checksum);
return 0;
}
/* validate the response received from modem and
* check the type of errors received
*/
static int ipc_flash_proc_check_ebl_rsp(void *hdr_rsp, void *payload_rsp)
{
struct iosm_ebl_error *err_info = payload_rsp;
u16 *rsp_code = hdr_rsp;
u32 i;
if (*rsp_code == IOSM_EBL_RSP_BUFF) {
for (i = 0; i < IOSM_MAX_ERRORS; i++) {
if (!err_info->error[i].error_code) {
pr_err("EBL: error_class = %d, error_code = %d",
err_info->error[i].error_class,
err_info->error[i].error_code);
}
}
return -EINVAL;
}
return 0;
}
/* Send data to the modem */
static int ipc_flash_send_data(struct iosm_devlink *ipc_devlink, u32 size,
u16 pack_id, u8 *payload, u32 payload_length)
{
struct iosm_flash_data flash_req;
int ret;
ret = ipc_flash_proc_format_ebl_pack(&flash_req, size,
pack_id, payload, payload_length);
if (ret) {
dev_err(ipc_devlink->dev, "EBL2 pack failed for pack_id:%d",
pack_id);
goto ipc_free_payload;
}
ret = ipc_imem_sys_devlink_write(ipc_devlink, (u8 *)&flash_req,
IOSM_EBL_HEAD_SIZE);
if (ret) {
dev_err(ipc_devlink->dev, "EBL Header write failed for Id:%x",
pack_id);
goto ipc_free_payload;
}
ret = ipc_imem_sys_devlink_write(ipc_devlink, payload, payload_length);
if (ret) {
dev_err(ipc_devlink->dev, "EBL Payload write failed for Id:%x",
pack_id);
}
ipc_free_payload:
return ret;
}
/**
* ipc_flash_link_establish - Flash link establishment
* @ipc_imem: Pointer to struct iosm_imem
*
* Returns: 0 on success and failure value on error
*/
int ipc_flash_link_establish(struct iosm_imem *ipc_imem)
{
u8 ler_data[IOSM_LER_RSP_SIZE];
u32 bytes_read;
/* Allocate channel for flashing/cd collection */
ipc_imem->ipc_devlink->devlink_sio.channel =
ipc_imem_sys_devlink_open(ipc_imem);
if (!ipc_imem->ipc_devlink->devlink_sio.channel)
goto chl_open_fail;
if (ipc_imem_sys_devlink_read(ipc_imem->ipc_devlink, ler_data,
IOSM_LER_RSP_SIZE, &bytes_read))
goto devlink_read_fail;
if (bytes_read != IOSM_LER_RSP_SIZE)
goto devlink_read_fail;
return 0;
devlink_read_fail:
ipc_imem_sys_devlink_close(ipc_imem->ipc_devlink);
chl_open_fail:
return -EIO;
}
/* Receive data from the modem */
static int ipc_flash_receive_data(struct iosm_devlink *ipc_devlink, u32 size,
u8 *mdm_rsp)
{
u8 mdm_rsp_hdr[IOSM_EBL_HEAD_SIZE];
u32 bytes_read;
int ret;
ret = ipc_imem_sys_devlink_read(ipc_devlink, mdm_rsp_hdr,
IOSM_EBL_HEAD_SIZE, &bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "EBL rsp to read %d bytes failed",
IOSM_EBL_HEAD_SIZE);
goto ipc_flash_recv_err;
}
if (bytes_read != IOSM_EBL_HEAD_SIZE) {
ret = -EINVAL;
goto ipc_flash_recv_err;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink, mdm_rsp, size,
&bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "EBL rsp to read %d bytes failed",
size);
goto ipc_flash_recv_err;
}
if (bytes_read != size) {
ret = -EINVAL;
goto ipc_flash_recv_err;
}
ret = ipc_flash_proc_check_ebl_rsp(mdm_rsp_hdr + 2, mdm_rsp);
ipc_flash_recv_err:
return ret;
}
/* Function to send command to modem and receive response */
static int ipc_flash_send_receive(struct iosm_devlink *ipc_devlink, u16 pack_id,
u8 *payload, u32 payload_length, u8 *mdm_rsp)
{
size_t frame_len = IOSM_EBL_DW_PACK_SIZE;
int ret;
if (pack_id == FLASH_SET_PROT_CONF)
frame_len = IOSM_EBL_W_PACK_SIZE;
ret = ipc_flash_send_data(ipc_devlink, frame_len, pack_id, payload,
payload_length);
if (ret)
goto ipc_flash_send_rcv;
ret = ipc_flash_receive_data(ipc_devlink,
frame_len - IOSM_EBL_HEAD_SIZE, mdm_rsp);
ipc_flash_send_rcv:
return ret;
}
/**
* ipc_flash_boot_set_capabilities - Set modem boot capabilities in flash
* @ipc_devlink: Pointer to devlink structure
* @mdm_rsp: Pointer to modem response buffer
*
* Returns: 0 on success and failure value on error
*/
int ipc_flash_boot_set_capabilities(struct iosm_devlink *ipc_devlink,
u8 *mdm_rsp)
{
ipc_devlink->ebl_ctx.ebl_sw_info_version =
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_RSP_SW_INFO_VER];
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_SKIP_ERASE] = IOSM_CAP_NOT_ENHANCED;
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_SKIP_CRC] = IOSM_CAP_NOT_ENHANCED;
if (ipc_devlink->ebl_ctx.m_ebl_resp[EBL_CAPS_FLAG] &
IOSM_CAP_USE_EXT_CAP) {
if (ipc_devlink->param.erase_full_flash)
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_OOS_CONFIG] &=
~((u8)IOSM_EXT_CAP_ERASE_ALL);
else
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_OOS_CONFIG] &=
~((u8)IOSM_EXT_CAP_COMMIT_ALL);
ipc_devlink->ebl_ctx.m_ebl_resp[EBL_EXT_CAPS_HANDLED] =
IOSM_CAP_USE_EXT_CAP;
}
/* Write back the EBL capability to modem
* Request Set Protcnf command
*/
return ipc_flash_send_receive(ipc_devlink, FLASH_SET_PROT_CONF,
ipc_devlink->ebl_ctx.m_ebl_resp,
IOSM_EBL_RSP_SIZE, mdm_rsp);
}
/* Read the SWID type and SWID value from the EBL */
int ipc_flash_read_swid(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp)
{
struct iosm_flash_msg_control cmd_msg;
struct iosm_swid_table *swid;
char ebl_swid[IOSM_SWID_STR];
int ret;
if (ipc_devlink->ebl_ctx.ebl_sw_info_version !=
IOSM_EXT_CAP_SWID_OOS_PACK)
return -EINVAL;
cmd_msg.action = cpu_to_le32(FLASH_OOSC_ACTION_READ);
cmd_msg.type = cpu_to_le32(FLASH_OOSC_TYPE_SWID_TABLE);
cmd_msg.length = cpu_to_le32(IOSM_MSG_LEN_ARG);
cmd_msg.arguments = cpu_to_le32(IOSM_MSG_LEN_ARG);
ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_CONTROL,
(u8 *)&cmd_msg, IOSM_MDM_SEND_16, mdm_rsp);
if (ret)
goto ipc_swid_err;
cmd_msg.action = cpu_to_le32(*((u32 *)mdm_rsp));
ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_DATA_READ,
(u8 *)&cmd_msg, IOSM_MDM_SEND_4, mdm_rsp);
if (ret)
goto ipc_swid_err;
swid = (struct iosm_swid_table *)mdm_rsp;
dev_dbg(ipc_devlink->dev, "SWID %x RF_ENGINE_ID %x", swid->sw_id_val,
swid->rf_engine_id_val);
snprintf(ebl_swid, sizeof(ebl_swid), "SWID: %x, RF_ENGINE_ID: %x",
swid->sw_id_val, swid->rf_engine_id_val);
devlink_flash_update_status_notify(ipc_devlink->devlink_ctx, ebl_swid,
NULL, 0, 0);
ipc_swid_err:
return ret;
}
/* Function to check if full erase or conditional erase was successful */
static int ipc_flash_erase_check(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp)
{
int ret, count = 0;
u16 mdm_rsp_data;
/* Request Flash Erase Check */
do {
mdm_rsp_data = IOSM_MDM_SEND_DATA;
ret = ipc_flash_send_receive(ipc_devlink, FLASH_ERASE_CHECK,
(u8 *)&mdm_rsp_data,
IOSM_MDM_SEND_2, mdm_rsp);
if (ret)
goto ipc_erase_chk_err;
mdm_rsp_data = *((u16 *)mdm_rsp);
if (mdm_rsp_data > IOSM_MDM_ERASE_RSP) {
dev_err(ipc_devlink->dev,
"Flash Erase Check resp wrong 0x%04X",
mdm_rsp_data);
ret = -EINVAL;
goto ipc_erase_chk_err;
}
count++;
msleep(IOSM_FLASH_ERASE_CHECK_INTERVAL);
} while ((mdm_rsp_data != IOSM_MDM_ERASE_RSP) &&
(count < (IOSM_FLASH_ERASE_CHECK_TIMEOUT /
IOSM_FLASH_ERASE_CHECK_INTERVAL)));
if (mdm_rsp_data != IOSM_MDM_ERASE_RSP) {
dev_err(ipc_devlink->dev, "Modem erase check timeout failure!");
ret = -ETIMEDOUT;
}
ipc_erase_chk_err:
return ret;
}
/* Full erase function which will erase the nand flash through EBL command */
static int ipc_flash_full_erase(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp)
{
u32 erase_address = IOSM_ERASE_START_ADDR;
struct iosm_flash_msg_control cmd_msg;
u32 erase_length = IOSM_ERASE_LEN;
int ret;
dev_dbg(ipc_devlink->dev, "Erase full nand flash");
cmd_msg.action = cpu_to_le32(FLASH_OOSC_ACTION_ERASE);
cmd_msg.type = cpu_to_le32(FLASH_OOSC_TYPE_ALL_FLASH);
cmd_msg.length = cpu_to_le32(erase_length);
cmd_msg.arguments = cpu_to_le32(erase_address);
ret = ipc_flash_send_receive(ipc_devlink, FLASH_OOS_CONTROL,
(unsigned char *)&cmd_msg,
IOSM_MDM_SEND_16, mdm_rsp);
if (ret)
goto ipc_flash_erase_err;
ipc_devlink->param.erase_full_flash_done = IOSM_SET_FLAG;
ret = ipc_flash_erase_check(ipc_devlink, mdm_rsp);
ipc_flash_erase_err:
return ret;
}
/* Logic for flashing all the Loadmaps available for individual fls file */
static int ipc_flash_download_region(struct iosm_devlink *ipc_devlink,
const struct firmware *fw, u8 *mdm_rsp)
{
u32 raw_len, rest_len = fw->size - IOSM_DEVLINK_HDR_SIZE;
struct iosm_devlink_image *fls_data;
__le32 reg_info[2]; /* 0th position region address, 1st position size */
u32 nand_address;
char *file_ptr;
int ret;
fls_data = (struct iosm_devlink_image *)fw->data;
file_ptr = (void *)(fls_data + 1);
nand_address = le32_to_cpu(fls_data->region_address);
reg_info[0] = cpu_to_le32(nand_address);
if (!ipc_devlink->param.erase_full_flash_done) {
reg_info[1] = cpu_to_le32(nand_address + rest_len - 2);
ret = ipc_flash_send_receive(ipc_devlink, FLASH_ERASE_START,
(u8 *)reg_info, IOSM_MDM_SEND_8,
mdm_rsp);
if (ret)
goto dl_region_fail;
ret = ipc_flash_erase_check(ipc_devlink, mdm_rsp);
if (ret)
goto dl_region_fail;
}
/* Request Flash Set Address */
ret = ipc_flash_send_receive(ipc_devlink, FLASH_SET_ADDRESS,
(u8 *)reg_info, IOSM_MDM_SEND_4, mdm_rsp);
if (ret)
goto dl_region_fail;
/* Request Flash Write Raw Image */
ret = ipc_flash_send_data(ipc_devlink, IOSM_EBL_DW_PACK_SIZE,
FLASH_WRITE_IMAGE_RAW, (u8 *)&rest_len,
IOSM_MDM_SEND_4);
if (ret)
goto dl_region_fail;
do {
raw_len = (rest_len > IOSM_FLS_BUF_SIZE) ? IOSM_FLS_BUF_SIZE :
rest_len;
ret = ipc_imem_sys_devlink_write(ipc_devlink, file_ptr,
raw_len);
if (ret) {
dev_err(ipc_devlink->dev, "Image write failed");
goto dl_region_fail;
}
file_ptr += raw_len;
rest_len -= raw_len;
} while (rest_len);
ret = ipc_flash_receive_data(ipc_devlink, IOSM_EBL_DW_PAYL_SIZE,
mdm_rsp);
dl_region_fail:
return ret;
}
/**
* ipc_flash_send_fls - Inject Modem subsystem fls file to device
* @ipc_devlink: Pointer to devlink structure
* @fw: FW image
* @mdm_rsp: Pointer to modem response buffer
*
* Returns: 0 on success and failure value on error
*/
int ipc_flash_send_fls(struct iosm_devlink *ipc_devlink,
const struct firmware *fw, u8 *mdm_rsp)
{
u32 fw_size = fw->size - IOSM_DEVLINK_HDR_SIZE;
struct iosm_devlink_image *fls_data;
u16 flash_cmd;
int ret;
fls_data = (struct iosm_devlink_image *)fw->data;
if (ipc_devlink->param.erase_full_flash) {
ipc_devlink->param.erase_full_flash = false;
ret = ipc_flash_full_erase(ipc_devlink, mdm_rsp);
if (ret)
goto ipc_flash_err;
}
/* Request Sec Start */
if (!fls_data->download_region) {
ret = ipc_flash_send_receive(ipc_devlink, FLASH_SEC_START,
(u8 *)fw->data +
IOSM_DEVLINK_HDR_SIZE, fw_size,
mdm_rsp);
if (ret)
goto ipc_flash_err;
} else {
/* Download regions */
ret = ipc_flash_download_region(ipc_devlink, fw, mdm_rsp);
if (ret)
goto ipc_flash_err;
if (fls_data->last_region) {
/* Request Sec End */
flash_cmd = IOSM_MDM_SEND_DATA;
ret = ipc_flash_send_receive(ipc_devlink, FLASH_SEC_END,
(u8 *)&flash_cmd,
IOSM_MDM_SEND_2, mdm_rsp);
}
}
ipc_flash_err:
return ret;
}
/**
* ipc_flash_boot_psi - Inject PSI image
* @ipc_devlink: Pointer to devlink structure
* @fw: FW image
*
* Returns: 0 on success and failure value on error
*/
int ipc_flash_boot_psi(struct iosm_devlink *ipc_devlink,
const struct firmware *fw)
{
u32 bytes_read, psi_size = fw->size - IOSM_DEVLINK_HDR_SIZE;
u8 psi_ack_byte[IOSM_PSI_ACK], read_data[2];
u8 *psi_code;
int ret;
dev_dbg(ipc_devlink->dev, "Boot transfer PSI");
psi_code = kmemdup(fw->data + IOSM_DEVLINK_HDR_SIZE, psi_size,
GFP_KERNEL);
if (!psi_code)
return -ENOMEM;
ret = ipc_imem_sys_devlink_write(ipc_devlink, psi_code, psi_size);
if (ret) {
dev_err(ipc_devlink->dev, "RPSI Image write failed");
goto ipc_flash_psi_free;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data,
IOSM_LER_ACK_SIZE, &bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "ipc_devlink_sio_read ACK failed");
goto ipc_flash_psi_free;
}
if (bytes_read != IOSM_LER_ACK_SIZE) {
ret = -EINVAL;
goto ipc_flash_psi_free;
}
snprintf(psi_ack_byte, sizeof(psi_ack_byte), "%x%x", read_data[0],
read_data[1]);
devlink_flash_update_status_notify(ipc_devlink->devlink_ctx,
psi_ack_byte, "PSI ACK", 0, 0);
if (read_data[0] == 0x00 && read_data[1] == 0xCD) {
dev_dbg(ipc_devlink->dev, "Coredump detected");
ret = ipc_coredump_get_list(ipc_devlink,
rpsi_cmd_coredump_start);
if (ret)
dev_err(ipc_devlink->dev, "Failed to get cd list");
}
ipc_flash_psi_free:
kfree(psi_code);
return ret;
}
/**
* ipc_flash_boot_ebl - Inject EBL image
* @ipc_devlink: Pointer to devlink structure
* @fw: FW image
*
* Returns: 0 on success and failure value on error
*/
int ipc_flash_boot_ebl(struct iosm_devlink *ipc_devlink,
const struct firmware *fw)
{
u32 ebl_size = fw->size - IOSM_DEVLINK_HDR_SIZE;
u8 read_data[2];
u32 bytes_read;
int ret;
if (ipc_mmio_get_exec_stage(ipc_devlink->pcie->imem->mmio) !=
IPC_MEM_EXEC_STAGE_PSI) {
devlink_flash_update_status_notify(ipc_devlink->devlink_ctx,
"Invalid execution stage",
NULL, 0, 0);
return -EINVAL;
}
dev_dbg(ipc_devlink->dev, "Boot transfer EBL");
ret = ipc_devlink_send_cmd(ipc_devlink, rpsi_cmd_code_ebl,
IOSM_RPSI_LOAD_SIZE);
if (ret) {
dev_err(ipc_devlink->dev, "Sending rpsi_cmd_code_ebl failed");
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE,
&bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "rpsi_cmd_code_ebl read failed");
goto ipc_flash_ebl_err;
}
if (bytes_read != IOSM_READ_SIZE) {
ret = -EINVAL;
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_write(ipc_devlink, (u8 *)&ebl_size,
sizeof(ebl_size));
if (ret) {
dev_err(ipc_devlink->dev, "EBL length write failed");
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE,
&bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "EBL read failed");
goto ipc_flash_ebl_err;
}
if (bytes_read != IOSM_READ_SIZE) {
ret = -EINVAL;
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_write(ipc_devlink,
(u8 *)fw->data + IOSM_DEVLINK_HDR_SIZE,
ebl_size);
if (ret) {
dev_err(ipc_devlink->dev, "EBL data transfer failed");
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink, read_data, IOSM_READ_SIZE,
&bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "EBL read failed");
goto ipc_flash_ebl_err;
}
if (bytes_read != IOSM_READ_SIZE) {
ret = -EINVAL;
goto ipc_flash_ebl_err;
}
ret = ipc_imem_sys_devlink_read(ipc_devlink,
ipc_devlink->ebl_ctx.m_ebl_resp,
IOSM_EBL_RSP_SIZE, &bytes_read);
if (ret) {
dev_err(ipc_devlink->dev, "EBL response read failed");
goto ipc_flash_ebl_err;
}
if (bytes_read != IOSM_EBL_RSP_SIZE)
ret = -EINVAL;
ipc_flash_ebl_err:
return ret;
}
@@ -0,0 +1,229 @@
/* 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;
};
int ipc_flash_boot_psi(struct iosm_devlink *ipc_devlink,
const struct firmware *fw);
int ipc_flash_boot_ebl(struct iosm_devlink *ipc_devlink,
const struct firmware *fw);
int ipc_flash_boot_set_capabilities(struct iosm_devlink *ipc_devlink,
u8 *mdm_rsp);
int ipc_flash_link_establish(struct iosm_imem *ipc_imem);
int ipc_flash_read_swid(struct iosm_devlink *ipc_devlink, u8 *mdm_rsp);
int ipc_flash_send_fls(struct iosm_devlink *ipc_devlink,
const struct firmware *fw, u8 *mdm_rsp);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,590 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_IMEM_H
#define IOSM_IPC_IMEM_H
#include <linux/skbuff.h>
#include "iosm_ipc_mmio.h"
#include "iosm_ipc_pcie.h"
#include "iosm_ipc_uevent.h"
#include "iosm_ipc_wwan.h"
#include "iosm_ipc_task_queue.h"
struct ipc_chnl_cfg;
/* IRQ moderation in usec */
#define IRQ_MOD_OFF 0
#define IRQ_MOD_NET 1000
#define IRQ_MOD_TRC 4000
/* Either the PSI image is accepted by CP or the suspended flash tool is waken,
* informed that the CP ROM driver is not ready to process the PSI image.
* unit : milliseconds
*/
#define IPC_PSI_TRANSFER_TIMEOUT 3000
/* Timeout in 20 msec to wait for the modem to boot up to
* IPC_MEM_DEVICE_IPC_INIT state.
* unit : milliseconds (500 * ipc_util_msleep(20))
*/
#define IPC_MODEM_BOOT_TIMEOUT 500
/* Wait timeout for ipc status reflects IPC_MEM_DEVICE_IPC_UNINIT
* unit : milliseconds
*/
#define IPC_MODEM_UNINIT_TIMEOUT_MS 30
/* Pending time for processing data.
* unit : milliseconds
*/
#define IPC_PEND_DATA_TIMEOUT 500
/* The timeout in milliseconds for application to wait for remote time. */
#define IPC_REMOTE_TS_TIMEOUT_MS 10
/* Timeout for TD allocation retry.
* unit : milliseconds
*/
#define IPC_TD_ALLOC_TIMER_PERIOD_MS 100
/* Host sleep target is host */
#define IPC_HOST_SLEEP_HOST 0
/* Host sleep target is device */
#define IPC_HOST_SLEEP_DEVICE 1
/* Sleep message, target host: AP enters sleep / target device: CP is
* allowed to enter sleep and shall use the host sleep protocol
*/
#define IPC_HOST_SLEEP_ENTER_SLEEP 0
/* Sleep_message, target host: AP exits sleep / target device: CP is
* NOT allowed to enter sleep
*/
#define IPC_HOST_SLEEP_EXIT_SLEEP 1
#define IMEM_IRQ_DONT_CARE (-1)
#define IPC_MEM_MAX_CHANNELS 8
#define IPC_MEM_MUX_IP_SESSION_ENTRIES 8
#define IPC_MEM_MUX_IP_CH_IF_ID 0
#define TD_UPDATE_DEFAULT_TIMEOUT_USEC 1900
#define FORCE_UPDATE_DEFAULT_TIMEOUT_USEC 500
/* Sleep_message, target host: not applicable / target device: CP is
* allowed to enter sleep and shall NOT use the device sleep protocol
*/
#define IPC_HOST_SLEEP_ENTER_SLEEP_NO_PROTOCOL 2
/* in_band_crash_signal IPC_MEM_INBAND_CRASH_SIG
* Modem crash notification configuration. If this value is non-zero then
* FEATURE_SET message will be sent to the Modem as a result the Modem will
* signal Crash via Execution Stage register. If this value is zero then Modem
* will use out-of-band method to notify about it's Crash.
*/
#define IPC_MEM_INBAND_CRASH_SIG 1
/* Extra headroom to be allocated for DL SKBs to allow addition of Ethernet
* header
*/
#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
#define IOSM_DEVLINK_INIT 1
/* List of the supported UL/DL pipes. */
enum ipc_mem_pipes {
IPC_MEM_PIPE_0 = 0,
IPC_MEM_PIPE_1,
IPC_MEM_PIPE_2,
IPC_MEM_PIPE_3,
IPC_MEM_PIPE_4,
IPC_MEM_PIPE_5,
IPC_MEM_PIPE_6,
IPC_MEM_PIPE_7,
IPC_MEM_PIPE_8,
IPC_MEM_PIPE_9,
IPC_MEM_PIPE_10,
IPC_MEM_PIPE_11,
IPC_MEM_PIPE_12,
IPC_MEM_PIPE_13,
IPC_MEM_PIPE_14,
IPC_MEM_PIPE_15,
IPC_MEM_PIPE_16,
IPC_MEM_PIPE_17,
IPC_MEM_PIPE_18,
IPC_MEM_PIPE_19,
IPC_MEM_PIPE_20,
IPC_MEM_PIPE_21,
IPC_MEM_PIPE_22,
IPC_MEM_PIPE_23,
IPC_MEM_MAX_PIPES
};
/* Enum defining channel states. */
enum ipc_channel_state {
IMEM_CHANNEL_FREE,
IMEM_CHANNEL_RESERVED,
IMEM_CHANNEL_ACTIVE,
IMEM_CHANNEL_CLOSING,
};
/**
* enum ipc_ctype - Enum defining supported channel type needed for control
* /IP traffic.
* @IPC_CTYPE_WWAN: Used for IP traffic
* @IPC_CTYPE_CTRL: Used for Control Communication
*/
enum ipc_ctype {
IPC_CTYPE_WWAN,
IPC_CTYPE_CTRL,
};
/* Pipe direction. */
enum ipc_mem_pipe_dir {
IPC_MEM_DIR_UL,
IPC_MEM_DIR_DL,
};
/* HP update identifier. To be used as data for ipc_cp_irq_hpda_update() */
enum ipc_hp_identifier {
IPC_HP_MR = 0,
IPC_HP_PM_TRIGGER,
IPC_HP_WAKEUP_SPEC_TMR,
IPC_HP_TD_UPD_TMR_START,
IPC_HP_TD_UPD_TMR,
IPC_HP_FAST_TD_UPD_TMR,
IPC_HP_UL_WRITE_TD,
IPC_HP_DL_PROCESS,
IPC_HP_NET_CHANNEL_INIT,
IPC_HP_CDEV_OPEN,
};
/**
* struct ipc_pipe - Structure for Pipe.
* @tdr_start: Ipc private protocol Transfer Descriptor Ring
* @channel: Id of the sio device, set by imem_sio_open,
* needed to pass DL char to the user terminal
* @skbr_start: Circular buffer for skbuf and the buffer
* reference in a tdr_start entry.
* @phy_tdr_start: Transfer descriptor start address
* @old_head: last head pointer reported to CP.
* @old_tail: AP read position before CP moves the read
* position to write/head. If CP has consumed the
* buffers, AP has to freed the skbuf starting at
* tdr_start[old_tail].
* @nr_of_entries: Number of elements of skb_start and tdr_start.
* @max_nr_of_queued_entries: Maximum number of queued entries in TDR
* @accumulation_backoff: Accumulation in usec for accumulation
* backoff (0 = no acc backoff)
* @irq_moderation: timer in usec for irq_moderation
* (0=no irq moderation)
* @pipe_nr: Pipe identification number
* @irq: Interrupt vector
* @dir: Direction of data stream in pipe
* @buf_size: Buffer size (in bytes) for preallocated
* buffers (for DL pipes)
* @nr_of_queued_entries: Aueued number of entries
* @is_open: Check for open pipe status
*/
struct ipc_pipe {
struct ipc_protocol_td *tdr_start;
struct ipc_mem_channel *channel;
struct sk_buff **skbr_start;
dma_addr_t phy_tdr_start;
u32 old_head;
u32 old_tail;
u32 nr_of_entries;
u32 max_nr_of_queued_entries;
u32 accumulation_backoff;
u32 irq_moderation;
u32 pipe_nr;
u32 irq;
enum ipc_mem_pipe_dir dir;
u32 buf_size;
u16 nr_of_queued_entries;
u8 is_open:1;
};
/**
* struct ipc_mem_channel - Structure for Channel.
* @channel_id: Instance of the channel list and is return to the user
* at the end of the open operation.
* @ctype: Control or netif channel.
* @index: unique index per ctype
* @ul_pipe: pipe objects
* @dl_pipe: pipe objects
* @if_id: Interface ID
* @net_err_count: Number of downlink errors returned by ipc_wwan_receive
* interface at the entry point of the IP stack.
* @state: Free, reserved or busy (in use).
* @ul_sem: Needed for the blocking write or uplink transfer.
* @ul_list: Uplink accumulator which is filled by the uplink
* char app or IP stack. The socket buffer pointer are
* added to the descriptor list in the kthread context.
*/
struct ipc_mem_channel {
int channel_id;
enum ipc_ctype ctype;
int index;
struct ipc_pipe ul_pipe;
struct ipc_pipe dl_pipe;
int if_id;
u32 net_err_count;
enum ipc_channel_state state;
struct completion ul_sem;
struct sk_buff_head ul_list;
};
/**
* enum ipc_phase - Different AP and CP phases.
* The enums defined after "IPC_P_ROM" and before
* "IPC_P_RUN" indicates the operating state where CP can
* respond to any requests. So while introducing new phase
* this shall be taken into consideration.
* @IPC_P_OFF: On host PC, the PCIe device link settings are known
* about the combined power on. PC is running, the driver
* is loaded and CP is in power off mode. The PCIe bus
* driver call the device power mode D3hot. In this phase
* the driver the polls the device, until the device is in
* the power on state and signals the power mode D0.
* @IPC_P_OFF_REQ: The intermediate phase between cleanup activity starts
* and ends.
* @IPC_P_CRASH: The phase indicating CP crash
* @IPC_P_CD_READY: The phase indicating CP core dump is ready
* @IPC_P_ROM: After power on, CP starts in ROM mode and the IPC ROM
* driver is waiting 150 ms for the AP active notification
* saved in the PCI link status register.
* @IPC_P_PSI: Primary signed image download phase
* @IPC_P_EBL: Extended bootloader pahse
* @IPC_P_RUN: The phase after flashing to RAM is the RUNTIME phase.
*/
enum ipc_phase {
IPC_P_OFF,
IPC_P_OFF_REQ,
IPC_P_CRASH,
IPC_P_CD_READY,
IPC_P_ROM,
IPC_P_PSI,
IPC_P_EBL,
IPC_P_RUN,
};
/**
* struct iosm_imem - Current state of the IPC shared memory.
* @mmio: mmio instance to access CP MMIO area /
* doorbell scratchpad.
* @ipc_protocol: IPC Protocol instance
* @ipc_task: Task for entry into ipc task queue
* @wwan: WWAN device pointer
* @mux: IP Data multiplexing state.
* @sio: IPC SIO data structure pointer
* @ipc_port: IPC PORT data structure pointer
* @pcie: IPC PCIe
* @trace: IPC trace data structure pointer
* @dev: Pointer to device structure
* @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.
* @hrtimer_period: Hr timer period
* @tdupdate_timer: Delay the TD update doorbell.
* @fast_update_timer: forced head pointer update delay timer.
* @td_alloc_timer: Timer for DL pipe TD allocation retry
* @adb_timer: Timer for finishing the ADB.
* @rom_exit_code: Mapped boot rom exit code.
* @enter_runtime: 1 means the transition to runtime phase was
* executed.
* @ul_pend_sem: Semaphore to wait/complete of UL TDs
* before closing pipe.
* @app_notify_ul_pend: Signal app if UL TD is pending
* @dl_pend_sem: Semaphore to wait/complete of DL TDs
* before closing pipe.
* @app_notify_dl_pend: Signal app if DL TD is pending
* @phase: Operating phase like runtime.
* @pci_device_id: Device ID
* @cp_version: CP version
* @device_sleep: Device sleep state
* @run_state_worker: Pointer to worker component for device
* setup operations to be called when modem
* reaches RUN state
* @ev_irq_pending: 0 means inform the IPC tasklet to
* process the irq actions.
* @flag: Flag to monitor the state of driver
* @td_update_timer_suspended: if true then td update timer suspend
* @ev_mux_net_transmit_pending:0 means inform the IPC tasklet to pass
* @reset_det_n: Reset detect flag
* @pcie_wake_n: Pcie wake flag
* @debugfs_wwan_dir: WWAN Debug FS directory entry
* @debugfs_dir: Debug FS directory for driver-specific entries
*/
struct iosm_imem {
struct iosm_mmio *mmio;
struct iosm_protocol *ipc_protocol;
struct ipc_task *ipc_task;
struct iosm_wwan *wwan;
struct iosm_mux *mux;
struct iosm_cdev *ipc_port[IPC_MEM_MAX_CHANNELS];
struct iosm_pcie *pcie;
#ifdef CONFIG_WWAN_DEBUGFS
struct iosm_trace *trace;
#endif
struct device *dev;
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;
ktime_t hrtimer_period;
struct hrtimer tdupdate_timer;
struct hrtimer fast_update_timer;
struct hrtimer td_alloc_timer;
struct hrtimer adb_timer;
enum rom_exit_code rom_exit_code;
u32 enter_runtime;
struct completion ul_pend_sem;
u32 app_notify_ul_pend;
struct completion dl_pend_sem;
u32 app_notify_dl_pend;
enum ipc_phase phase;
u16 pci_device_id;
int cp_version;
int device_sleep;
struct work_struct run_state_worker;
u8 ev_irq_pending[IPC_IRQ_VECTORS];
unsigned long flag;
u8 td_update_timer_suspended:1,
ev_mux_net_transmit_pending:1,
reset_det_n:1,
pcie_wake_n:1;
#ifdef CONFIG_WWAN_DEBUGFS
struct dentry *debugfs_wwan_dir;
struct dentry *debugfs_dir;
#endif
};
/**
* ipc_imem_init - Initialize the shared memory region
* @pcie: Pointer to core driver data-struct
* @device_id: PCI device ID
* @mmio: Pointer to the mmio area
* @dev: Pointer to device structure
*
* Returns: Initialized imem pointer on success else NULL
*/
struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id,
void __iomem *mmio, struct device *dev);
/**
* ipc_imem_pm_s2idle_sleep - Set PM variables to sleep/active for
* s2idle sleep/active
* @ipc_imem: Pointer to imem data-struct
* @sleep: Set PM Variable to sleep/active
*/
void ipc_imem_pm_s2idle_sleep(struct iosm_imem *ipc_imem, bool sleep);
/**
* ipc_imem_pm_suspend - The HAL shall ask the shared memory layer
* whether D3 is allowed.
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_pm_suspend(struct iosm_imem *ipc_imem);
/**
* ipc_imem_pm_resume - The HAL shall inform the shared memory layer
* that the device is active.
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_pm_resume(struct iosm_imem *ipc_imem);
/**
* ipc_imem_cleanup - Inform CP and free the shared memory resources.
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_cleanup(struct iosm_imem *ipc_imem);
/**
* ipc_imem_irq_process - Shift the IRQ actions to the IPC thread.
* @ipc_imem: Pointer to imem data-struct
* @irq: Irq number
*/
void ipc_imem_irq_process(struct iosm_imem *ipc_imem, int irq);
/**
* imem_get_device_sleep_state - Get the device sleep state value.
* @ipc_imem: Pointer to imem instance
*
* Returns: device sleep state
*/
int imem_get_device_sleep_state(struct iosm_imem *ipc_imem);
/**
* ipc_imem_td_update_timer_suspend - Updates the TD Update Timer suspend flag.
* @ipc_imem: Pointer to imem data-struct
* @suspend: Flag to update. If TRUE then HP update doorbell is triggered to
* device without any wait. If FALSE then HP update doorbell is
* delayed until timeout.
*/
void ipc_imem_td_update_timer_suspend(struct iosm_imem *ipc_imem, bool suspend);
/**
* ipc_imem_channel_close - Release the channel resources.
* @ipc_imem: Pointer to imem data-struct
* @channel_id: Channel ID to be cleaned up.
*/
void ipc_imem_channel_close(struct iosm_imem *ipc_imem, int channel_id);
/**
* ipc_imem_channel_alloc - Reserves a channel
* @ipc_imem: Pointer to imem data-struct
* @index: ID to lookup from the preallocated list.
* @ctype: Channel type.
*
* Returns: Index on success and failure value on error
*/
int ipc_imem_channel_alloc(struct iosm_imem *ipc_imem, int index,
enum ipc_ctype ctype);
/**
* ipc_imem_channel_open - Establish the pipes.
* @ipc_imem: Pointer to imem data-struct
* @channel_id: Channel ID returned during alloc.
* @db_id: Doorbell ID for trigger identifier.
*
* Returns: Pointer of ipc_mem_channel on success and NULL on failure.
*/
struct ipc_mem_channel *ipc_imem_channel_open(struct iosm_imem *ipc_imem,
int channel_id, u32 db_id);
/**
* ipc_imem_td_update_timer_start - Starts the TD Update Timer if not running.
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_td_update_timer_start(struct iosm_imem *ipc_imem);
/**
* ipc_imem_ul_write_td - Pass the channel UL list to protocol layer for TD
* preparation and sending them to the device.
* @ipc_imem: Pointer to imem data-struct
*
* Returns: TRUE of HP Doorbell trigger is pending. FALSE otherwise.
*/
bool ipc_imem_ul_write_td(struct iosm_imem *ipc_imem);
/**
* ipc_imem_ul_send - Dequeue SKB from channel list and start with
* the uplink transfer.If HP Doorbell is pending to be
* triggered then starts the TD Update Timer.
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_ul_send(struct iosm_imem *ipc_imem);
/**
* ipc_imem_channel_update - Set or modify pipe config of an existing channel
* @ipc_imem: Pointer to imem data-struct
* @id: Channel config index
* @chnl_cfg: Channel config struct
* @irq_moderation: Timer in usec for irq_moderation
*/
void ipc_imem_channel_update(struct iosm_imem *ipc_imem, int id,
struct ipc_chnl_cfg chnl_cfg, u32 irq_moderation);
/**
* ipc_imem_channel_free -Free an IPC channel.
* @channel: Channel to be freed
*/
void ipc_imem_channel_free(struct ipc_mem_channel *channel);
/**
* ipc_imem_hrtimer_stop - Stop the hrtimer
* @hr_timer: Pointer to hrtimer instance
*/
void ipc_imem_hrtimer_stop(struct hrtimer *hr_timer);
/**
* ipc_imem_pipe_cleanup - Reset volatile pipe content for all channels
* @ipc_imem: Pointer to imem data-struct
* @pipe: Pipe to cleaned up
*/
void ipc_imem_pipe_cleanup(struct iosm_imem *ipc_imem, struct ipc_pipe *pipe);
/**
* ipc_imem_pipe_close - Send msg to device to close pipe
* @ipc_imem: Pointer to imem data-struct
* @pipe: Pipe to be closed
*/
void ipc_imem_pipe_close(struct iosm_imem *ipc_imem, struct ipc_pipe *pipe);
/**
* ipc_imem_phase_update - Get the CP execution state
* and map it to the AP phase.
* @ipc_imem: Pointer to imem data-struct
*
* Returns: Current ap updated phase
*/
enum ipc_phase ipc_imem_phase_update(struct iosm_imem *ipc_imem);
/**
* ipc_imem_phase_get_string - Return the current operation
* phase as string.
* @phase: AP phase
*
* Returns: AP phase string
*/
const char *ipc_imem_phase_get_string(enum ipc_phase phase);
/**
* ipc_imem_msg_send_feature_set - Send feature set message to modem
* @ipc_imem: Pointer to imem data-struct
* @reset_enable: 0 = out-of-band, 1 = in-band-crash notification
* @atomic_ctx: if disabled call in tasklet context
*
*/
void ipc_imem_msg_send_feature_set(struct iosm_imem *ipc_imem,
unsigned int reset_enable, bool atomic_ctx);
/**
* ipc_imem_ipc_init_check - Send the init event to CP, wait a certain time and
* set CP to runtime with the context information
* @ipc_imem: Pointer to imem data-struct
*/
void ipc_imem_ipc_init_check(struct iosm_imem *ipc_imem);
/**
* ipc_imem_channel_init - Initialize the channel list with UL/DL pipe pairs.
* @ipc_imem: Pointer to imem data-struct
* @ctype: Channel type
* @chnl_cfg: Channel configuration struct
* @irq_moderation: Timer in usec for irq_moderation
*/
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);
void ipc_imem_adb_timer_start(struct iosm_imem *ipc_imem);
#endif
@@ -0,0 +1,658 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/delay.h>
#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"
#include "iosm_ipc_task_queue.h"
/* Open a packet data online channel between the network layer and CP. */
int ipc_imem_sys_wwan_open(struct iosm_imem *ipc_imem, int if_id)
{
dev_dbg(ipc_imem->dev, "%s if id: %d",
ipc_imem_phase_get_string(ipc_imem->phase), if_id);
/* The network interface is only supported in the runtime phase. */
if (ipc_imem_phase_update(ipc_imem) != IPC_P_RUN) {
dev_err(ipc_imem->dev, "net:%d : refused phase %s", if_id,
ipc_imem_phase_get_string(ipc_imem->phase));
return -EIO;
}
return ipc_mux_open_session(ipc_imem->mux, if_id);
}
/* Release a net link to CP. */
void ipc_imem_sys_wwan_close(struct iosm_imem *ipc_imem, int if_id,
int channel_id)
{
if (ipc_imem->mux && if_id >= IP_MUX_SESSION_START &&
if_id <= IP_MUX_SESSION_END)
ipc_mux_close_session(ipc_imem->mux, if_id);
}
/* Tasklet call to do uplink transfer. */
static int ipc_imem_tq_cdev_write(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size)
{
ipc_imem_ul_send(ipc_imem);
return 0;
}
/* Through tasklet to do sio write. */
static int ipc_imem_call_cdev_write(struct iosm_imem *ipc_imem)
{
return ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_cdev_write, 0,
NULL, 0, false);
}
/* Function for transfer UL data */
int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem,
int if_id, int channel_id, struct sk_buff *skb)
{
int ret = -EINVAL;
if (!ipc_imem || channel_id < 0)
goto out;
/* Is CP Running? */
if (ipc_imem->phase != IPC_P_RUN) {
dev_dbg(ipc_imem->dev, "phase %s transmit",
ipc_imem_phase_get_string(ipc_imem->phase));
ret = -EIO;
goto out;
}
/* Route the UL packet through IP MUX Layer */
ret = ipc_mux_ul_trigger_encode(ipc_imem->mux, if_id, skb);
out:
return ret;
}
/* Initialize wwan channel */
int ipc_imem_wwan_channel_init(struct iosm_imem *ipc_imem,
enum ipc_mux_protocol mux_type)
{
struct ipc_chnl_cfg chnl_cfg = { 0 };
ipc_imem->cp_version = ipc_mmio_get_cp_version(ipc_imem->mmio);
/* If modem version is invalid (0xffffffff), do not initialize WWAN. */
if (ipc_imem->cp_version == -1) {
dev_err(ipc_imem->dev, "invalid CP version");
return -EIO;
}
ipc_chnl_cfg_get(&chnl_cfg, ipc_imem->nr_of_channels);
if (ipc_imem->mmio->mux_protocol == MUX_AGGREGATION &&
ipc_imem->nr_of_channels == IPC_MEM_IP_CHL_ID_0) {
chnl_cfg.ul_nr_of_entries = IPC_MEM_MAX_TDS_MUX_AGGR_UL;
chnl_cfg.dl_nr_of_entries = IPC_MEM_MAX_TDS_MUX_AGGR_DL;
chnl_cfg.dl_buf_size = IPC_MEM_MAX_ADB_BUF_SIZE;
}
ipc_imem_channel_init(ipc_imem, IPC_CTYPE_WWAN, chnl_cfg,
IRQ_MOD_OFF);
/* WWAN registration. */
ipc_imem->wwan = ipc_wwan_init(ipc_imem, ipc_imem->dev);
if (!ipc_imem->wwan) {
dev_err(ipc_imem->dev,
"failed to register the ipc_wwan interfaces");
return -ENOMEM;
}
return 0;
}
/* Map SKB to DMA for transfer */
static int ipc_imem_map_skb_to_dma(struct iosm_imem *ipc_imem,
struct sk_buff *skb)
{
struct iosm_pcie *ipc_pcie = ipc_imem->pcie;
char *buf = skb->data;
int len = skb->len;
dma_addr_t mapping;
int ret;
ret = ipc_pcie_addr_map(ipc_pcie, buf, len, &mapping, DMA_TO_DEVICE);
if (ret)
goto err;
BUILD_BUG_ON(sizeof(*IPC_CB(skb)) > sizeof(skb->cb));
IPC_CB(skb)->mapping = mapping;
IPC_CB(skb)->direction = DMA_TO_DEVICE;
IPC_CB(skb)->len = len;
IPC_CB(skb)->op_type = (u8)UL_DEFAULT;
err:
return ret;
}
/* return true if channel is ready for use */
static bool ipc_imem_is_channel_active(struct iosm_imem *ipc_imem,
struct ipc_mem_channel *channel)
{
enum ipc_phase phase;
/* Update the current operation phase. */
phase = ipc_imem->phase;
/* Select the operation depending on the execution stage. */
switch (phase) {
case IPC_P_RUN:
case IPC_P_PSI:
case IPC_P_EBL:
break;
case IPC_P_ROM:
/* Prepare the PSI image for the CP ROM driver and
* suspend the flash app.
*/
if (channel->state != IMEM_CHANNEL_RESERVED) {
dev_err(ipc_imem->dev,
"ch[%d]:invalid channel state %d,expected %d",
channel->channel_id, channel->state,
IMEM_CHANNEL_RESERVED);
goto channel_unavailable;
}
goto channel_available;
default:
/* Ignore uplink actions in all other phases. */
dev_err(ipc_imem->dev, "ch[%d]: confused phase %d",
channel->channel_id, phase);
goto channel_unavailable;
}
/* Check the full availability of the channel. */
if (channel->state != IMEM_CHANNEL_ACTIVE) {
dev_err(ipc_imem->dev, "ch[%d]: confused channel state %d",
channel->channel_id, channel->state);
goto channel_unavailable;
}
channel_available:
return true;
channel_unavailable:
return false;
}
/**
* ipc_imem_sys_port_close - Release a sio link to CP.
* @ipc_imem: Imem instance.
* @channel: Channel instance.
*/
void ipc_imem_sys_port_close(struct iosm_imem *ipc_imem,
struct ipc_mem_channel *channel)
{
enum ipc_phase curr_phase;
int status = 0;
u32 tail = 0;
curr_phase = ipc_imem->phase;
/* If current phase is IPC_P_OFF or SIO ID is -ve then
* channel is already freed. Nothing to do.
*/
if (curr_phase == IPC_P_OFF) {
dev_err(ipc_imem->dev,
"nothing to do. Current Phase: %s",
ipc_imem_phase_get_string(curr_phase));
return;
}
if (channel->state == IMEM_CHANNEL_FREE) {
dev_err(ipc_imem->dev, "ch[%d]: invalid channel state %d",
channel->channel_id, channel->state);
return;
}
/* 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) {
ipc_imem->app_notify_ul_pend = 1;
/* Suspend the user app and wait a certain time for processing
* UL Data.
*/
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,
"Pend data Timeout UL-Pipe:%d Head:%d Tail:%d",
channel->ul_pipe.pipe_nr,
channel->ul_pipe.old_head,
channel->ul_pipe.old_tail);
}
ipc_imem->app_notify_ul_pend = 0;
}
/* If there are any pending TDs then wait for Timeout/Completion before
* closing pipe.
*/
ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol,
&channel->dl_pipe, NULL, &tail);
if (tail != channel->dl_pipe.old_tail) {
ipc_imem->app_notify_dl_pend = 1;
/* Suspend the user app and wait a certain time for processing
* DL Data.
*/
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,
"Pend data Timeout DL-Pipe:%d Head:%d Tail:%d",
channel->dl_pipe.pipe_nr,
channel->dl_pipe.old_head,
channel->dl_pipe.old_tail);
}
ipc_imem->app_notify_dl_pend = 0;
}
/* 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;
ipc_imem_pipe_close(ipc_imem, &channel->ul_pipe);
ipc_imem_pipe_close(ipc_imem, &channel->dl_pipe);
ipc_imem_channel_free(channel);
}
/* Open a PORT link to CP and return the channel */
struct ipc_mem_channel *ipc_imem_sys_port_open(struct iosm_imem *ipc_imem,
int chl_id, int hp_id)
{
struct ipc_mem_channel *channel;
int ch_id;
/* The PORT interface is only supported in the runtime phase. */
if (ipc_imem_phase_update(ipc_imem) != IPC_P_RUN) {
dev_err(ipc_imem->dev, "PORT open refused, phase %s",
ipc_imem_phase_get_string(ipc_imem->phase));
return NULL;
}
ch_id = ipc_imem_channel_alloc(ipc_imem, chl_id, IPC_CTYPE_CTRL);
if (ch_id < 0) {
dev_err(ipc_imem->dev, "reservation of an PORT chnl id failed");
return NULL;
}
channel = ipc_imem_channel_open(ipc_imem, ch_id, hp_id);
if (!channel) {
dev_err(ipc_imem->dev, "PORT channel id open failed");
return NULL;
}
return channel;
}
/* transfer skb to modem */
int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb)
{
struct ipc_mem_channel *channel = ipc_cdev->channel;
struct iosm_imem *ipc_imem = ipc_cdev->ipc_imem;
int ret = -EIO;
if (!ipc_imem_is_channel_active(ipc_imem, channel) ||
ipc_imem->phase == IPC_P_OFF_REQ)
goto out;
ret = ipc_imem_map_skb_to_dma(ipc_imem, skb);
if (ret)
goto out;
/* Add skb to the uplink skbuf accumulator. */
skb_queue_tail(&channel->ul_list, skb);
ret = ipc_imem_call_cdev_write(ipc_imem);
if (ret) {
skb_dequeue_tail(&channel->ul_list);
dev_err(ipc_cdev->dev, "channel id[%d] write failed\n",
ipc_cdev->channel->channel_id);
}
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;
int status = 0;
u32 tail = 0;
channel = ipc_imem->ipc_devlink->devlink_sio.channel;
/* 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);
ipc_imem->nr_of_channels--;
}
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;
}
skb_put_data(skb, 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:
dev_kfree_skb(skb);
return rc;
}
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_IMEM_OPS_H
#define IOSM_IPC_IMEM_OPS_H
#include "iosm_ipc_mux_codec.h"
/* Maximum wait time for blocking read */
#define IPC_READ_TIMEOUT 3000
/* The delay in ms for defering the unregister */
#define SIO_UNREGISTER_DEFER_DELAY_MS 1
/* Default delay till CP PSI image is running and modem updates the
* execution stage.
* unit : milliseconds
*/
#define PSI_START_DEFAULT_TIMEOUT 3000
/* Default time out when closing SIO, till the modem is in
* running state.
* unit : milliseconds
*/
#define BOOT_CHECK_DEFAULT_TIMEOUT 400
/* IP MUX channel range */
#define IP_MUX_SESSION_START 0
#define IP_MUX_SESSION_END 7
/* Default IP MUX channel */
#define IP_MUX_SESSION_DEFAULT 0
/**
* ipc_imem_sys_port_open - Open a port link to CP.
* @ipc_imem: Imem instance.
* @chl_id: Channel Identifier.
* @hp_id: HP Identifier.
*
* Return: channel instance on success, NULL for failure
*/
struct ipc_mem_channel *ipc_imem_sys_port_open(struct iosm_imem *ipc_imem,
int chl_id, int hp_id);
void ipc_imem_sys_port_close(struct iosm_imem *ipc_imem,
struct ipc_mem_channel *channel);
/**
* ipc_imem_sys_cdev_write - Route the uplink buffer to CP.
* @ipc_cdev: iosm_cdev instance.
* @skb: Pointer to skb.
*
* Return: 0 on success and failure value on error
*/
int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb);
/**
* ipc_imem_sys_wwan_open - Open packet data online channel between network
* layer and CP.
* @ipc_imem: Imem instance.
* @if_id: ip link tag of the net device.
*
* Return: Channel ID on success and failure value on error
*/
int ipc_imem_sys_wwan_open(struct iosm_imem *ipc_imem, int if_id);
/**
* ipc_imem_sys_wwan_close - Close packet data online channel between network
* layer and CP.
* @ipc_imem: Imem instance.
* @if_id: IP link id net device.
* @channel_id: Channel ID to be closed.
*/
void ipc_imem_sys_wwan_close(struct iosm_imem *ipc_imem, int if_id,
int channel_id);
/**
* ipc_imem_sys_wwan_transmit - Function for transfer UL data
* @ipc_imem: Imem instance.
* @if_id: link ID of the device.
* @channel_id: Channel ID used
* @skb: Pointer to sk buffer
*
* Return: 0 on success and failure value on error
*/
int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem, int if_id,
int channel_id, struct sk_buff *skb);
/**
* ipc_imem_wwan_channel_init - Initializes WWAN channels and the channel for
* MUX.
* @ipc_imem: Pointer to iosm_imem struct.
* @mux_type: Type of mux protocol.
*
* Return: 0 on success and failure value on error
*/
int 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
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_pcie.h"
#include "iosm_ipc_protocol.h"
static void ipc_write_dbell_reg(struct iosm_pcie *ipc_pcie, int irq_n, u32 data)
{
void __iomem *write_reg;
/* Select the first doorbell register, which is only currently needed
* by CP.
*/
write_reg = (void __iomem *)((u8 __iomem *)ipc_pcie->ipc_regs +
ipc_pcie->doorbell_write +
(irq_n * ipc_pcie->doorbell_reg_offset));
/* Fire the doorbell irq by writing data on the doorbell write pointer
* register.
*/
iowrite32(data, write_reg);
}
void ipc_doorbell_fire(struct iosm_pcie *ipc_pcie, int irq_n, u32 data)
{
ipc_write_dbell_reg(ipc_pcie, irq_n, data);
}
/* Threaded Interrupt handler for MSI interrupts */
static irqreturn_t ipc_msi_interrupt(int irq, void *dev_id)
{
struct iosm_pcie *ipc_pcie = dev_id;
int instance = irq - ipc_pcie->pci->irq;
/* Shift the MSI irq actions to the IPC tasklet. IRQ_NONE means the
* irq was not from the IPC device or could not be served.
*/
if (instance >= ipc_pcie->nvec)
return IRQ_NONE;
if (!test_bit(0, &ipc_pcie->suspend))
ipc_imem_irq_process(ipc_pcie->imem, instance);
return IRQ_HANDLED;
}
void ipc_release_irq(struct iosm_pcie *ipc_pcie)
{
struct pci_dev *pdev = ipc_pcie->pci;
if (pdev->msi_enabled) {
while (--ipc_pcie->nvec >= 0)
free_irq(pdev->irq + ipc_pcie->nvec, ipc_pcie);
}
pci_free_irq_vectors(pdev);
}
int ipc_acquire_irq(struct iosm_pcie *ipc_pcie)
{
struct pci_dev *pdev = ipc_pcie->pci;
int i, rc = -EINVAL;
ipc_pcie->nvec = pci_alloc_irq_vectors(pdev, IPC_MSI_VECTORS,
IPC_MSI_VECTORS, PCI_IRQ_MSI);
if (ipc_pcie->nvec < 0) {
rc = ipc_pcie->nvec;
goto error;
}
if (!pdev->msi_enabled)
goto error;
for (i = 0; i < ipc_pcie->nvec; ++i) {
rc = request_threaded_irq(pdev->irq + i, NULL,
ipc_msi_interrupt, IRQF_ONESHOT,
KBUILD_MODNAME, ipc_pcie);
if (rc) {
dev_err(ipc_pcie->dev, "unable to grab IRQ, rc=%d", rc);
ipc_pcie->nvec = i;
ipc_release_irq(ipc_pcie);
goto error;
}
}
error:
return rc;
}
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_IRQ_H
#define IOSM_IPC_IRQ_H
struct iosm_pcie;
/**
* ipc_doorbell_fire - fire doorbell to CP
* @ipc_pcie: Pointer to iosm_pcie
* @irq_n: Doorbell type
* @data: ipc state
*/
void ipc_doorbell_fire(struct iosm_pcie *ipc_pcie, int irq_n, u32 data);
/**
* ipc_release_irq - Release the IRQ handler.
* @ipc_pcie: Pointer to iosm_pcie struct
*/
void ipc_release_irq(struct iosm_pcie *ipc_pcie);
/**
* ipc_acquire_irq - acquire IRQ & register IRQ handler.
* @ipc_pcie: Pointer to iosm_pcie struct
*
* Return: 0 on success and failure value on error
*/
int ipc_acquire_irq(struct iosm_pcie *ipc_pcie);
#endif
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/slab.h>
#include "iosm_ipc_mmio.h"
#include "iosm_ipc_mux.h"
/* Definition of MMIO offsets
* note that MMIO_CI offsets are relative to end of chip info structure
*/
/* MMIO chip info size in bytes */
#define MMIO_CHIP_INFO_SIZE 60
/* CP execution stage */
#define MMIO_OFFSET_EXECUTION_STAGE 0x00
/* Boot ROM Chip Info struct */
#define MMIO_OFFSET_CHIP_INFO 0x04
#define MMIO_OFFSET_ROM_EXIT_CODE 0x40
#define MMIO_OFFSET_PSI_ADDRESS 0x54
#define MMIO_OFFSET_PSI_SIZE 0x5C
#define MMIO_OFFSET_IPC_STATUS 0x60
#define MMIO_OFFSET_CONTEXT_INFO 0x64
#define MMIO_OFFSET_BASE_ADDR 0x6C
#define MMIO_OFFSET_END_ADDR 0x74
#define MMIO_OFFSET_CP_VERSION 0xF0
#define MMIO_OFFSET_CP_CAPABILITIES 0xF4
/* Timeout in 50 msec to wait for the modem boot code to write a valid
* execution stage into mmio area
*/
#define IPC_MMIO_EXEC_STAGE_TIMEOUT 50
/* check if exec stage has one of the valid values */
static bool ipc_mmio_is_valid_exec_stage(enum ipc_mem_exec_stage stage)
{
switch (stage) {
case IPC_MEM_EXEC_STAGE_BOOT:
case IPC_MEM_EXEC_STAGE_PSI:
case IPC_MEM_EXEC_STAGE_EBL:
case IPC_MEM_EXEC_STAGE_RUN:
case IPC_MEM_EXEC_STAGE_CRASH:
case IPC_MEM_EXEC_STAGE_CD_READY:
return true;
default:
return false;
}
}
void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio)
{
u32 cp_cap;
unsigned int ver;
ver = ipc_mmio_get_cp_version(ipc_mmio);
cp_cap = ioread32(ipc_mmio->base + ipc_mmio->offset.cp_capability);
ipc_mmio->mux_protocol = ((ver >= IOSM_CP_VERSION) && (cp_cap &
(UL_AGGR | DL_AGGR))) ? MUX_AGGREGATION
: MUX_LITE;
ipc_mmio->has_ul_flow_credit =
(ver >= IOSM_CP_VERSION) && (cp_cap & UL_FLOW_CREDIT);
}
struct iosm_mmio *ipc_mmio_init(void __iomem *mmio, struct device *dev)
{
struct iosm_mmio *ipc_mmio = kzalloc_obj(*ipc_mmio);
int retries = IPC_MMIO_EXEC_STAGE_TIMEOUT;
enum ipc_mem_exec_stage stage;
if (!ipc_mmio)
return NULL;
ipc_mmio->dev = dev;
ipc_mmio->base = mmio;
ipc_mmio->offset.exec_stage = MMIO_OFFSET_EXECUTION_STAGE;
/* Check for a valid execution stage to make sure that the boot code
* has correctly initialized the MMIO area.
*/
do {
stage = ipc_mmio_get_exec_stage(ipc_mmio);
if (ipc_mmio_is_valid_exec_stage(stage))
break;
msleep(20);
} while (--retries > 0);
if (!retries) {
dev_err(ipc_mmio->dev, "invalid exec stage %X", stage);
goto init_fail;
}
ipc_mmio->offset.chip_info = MMIO_OFFSET_CHIP_INFO;
/* read chip info size and version from chip info structure */
ipc_mmio->chip_info_version =
ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info);
/* Increment of 2 is needed as the size value in the chip info
* excludes the version and size field, which are always present
*/
ipc_mmio->chip_info_size =
ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info + 1) + 2;
if (ipc_mmio->chip_info_size != MMIO_CHIP_INFO_SIZE) {
dev_err(ipc_mmio->dev, "Unexpected Chip Info");
goto init_fail;
}
ipc_mmio->offset.rom_exit_code = MMIO_OFFSET_ROM_EXIT_CODE;
ipc_mmio->offset.psi_address = MMIO_OFFSET_PSI_ADDRESS;
ipc_mmio->offset.psi_size = MMIO_OFFSET_PSI_SIZE;
ipc_mmio->offset.ipc_status = MMIO_OFFSET_IPC_STATUS;
ipc_mmio->offset.context_info = MMIO_OFFSET_CONTEXT_INFO;
ipc_mmio->offset.ap_win_base = MMIO_OFFSET_BASE_ADDR;
ipc_mmio->offset.ap_win_end = MMIO_OFFSET_END_ADDR;
ipc_mmio->offset.cp_version = MMIO_OFFSET_CP_VERSION;
ipc_mmio->offset.cp_capability = MMIO_OFFSET_CP_CAPABILITIES;
return ipc_mmio;
init_fail:
kfree(ipc_mmio);
return NULL;
}
enum ipc_mem_exec_stage ipc_mmio_get_exec_stage(struct iosm_mmio *ipc_mmio)
{
if (!ipc_mmio)
return IPC_MEM_EXEC_STAGE_INVALID;
return (enum ipc_mem_exec_stage)ioread32(ipc_mmio->base +
ipc_mmio->offset.exec_stage);
}
void ipc_mmio_copy_chip_info(struct iosm_mmio *ipc_mmio, void *dest,
size_t size)
{
if (ipc_mmio && dest)
memcpy_fromio(dest, ipc_mmio->base + ipc_mmio->offset.chip_info,
size);
}
enum ipc_mem_device_ipc_state ipc_mmio_get_ipc_state(struct iosm_mmio *ipc_mmio)
{
if (!ipc_mmio)
return IPC_MEM_DEVICE_IPC_INVALID;
return (enum ipc_mem_device_ipc_state)ioread32(ipc_mmio->base +
ipc_mmio->offset.ipc_status);
}
enum rom_exit_code ipc_mmio_get_rom_exit_code(struct iosm_mmio *ipc_mmio)
{
if (!ipc_mmio)
return IMEM_ROM_EXIT_FAIL;
return (enum rom_exit_code)ioread32(ipc_mmio->base +
ipc_mmio->offset.rom_exit_code);
}
void ipc_mmio_config(struct iosm_mmio *ipc_mmio)
{
if (!ipc_mmio)
return;
/* AP memory window (full window is open and active so that modem checks
* each AP address) 0 means don't check on modem side.
*/
iowrite64(0, ipc_mmio->base + ipc_mmio->offset.ap_win_base);
iowrite64(0, ipc_mmio->base + ipc_mmio->offset.ap_win_end);
iowrite64(ipc_mmio->context_info_addr,
ipc_mmio->base + ipc_mmio->offset.context_info);
}
void ipc_mmio_set_psi_addr_and_size(struct iosm_mmio *ipc_mmio, dma_addr_t addr,
u32 size)
{
if (!ipc_mmio)
return;
iowrite64(addr, ipc_mmio->base + ipc_mmio->offset.psi_address);
iowrite32(size, ipc_mmio->base + ipc_mmio->offset.psi_size);
}
void ipc_mmio_set_contex_info_addr(struct iosm_mmio *ipc_mmio, phys_addr_t addr)
{
if (!ipc_mmio)
return;
/* store context_info address. This will be stored in the mmio area
* during IPC_MEM_DEVICE_IPC_INIT state via ipc_mmio_config()
*/
ipc_mmio->context_info_addr = addr;
}
int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio)
{
if (ipc_mmio)
return ioread32(ipc_mmio->base + ipc_mmio->offset.cp_version);
return -EFAULT;
}
@@ -0,0 +1,183 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_MMIO_H
#define IOSM_IPC_MMIO_H
/* Minimal IOSM CP VERSION which has valid CP_CAPABILITIES field */
#define IOSM_CP_VERSION 0x0100UL
/* DL dir Aggregation support mask */
#define DL_AGGR BIT(9)
/* UL dir Aggregation support mask */
#define UL_AGGR BIT(8)
/* UL flow credit support mask */
#define UL_FLOW_CREDIT BIT(21)
/* Possible states of the IPC finite state machine. */
enum ipc_mem_device_ipc_state {
IPC_MEM_DEVICE_IPC_UNINIT,
IPC_MEM_DEVICE_IPC_INIT,
IPC_MEM_DEVICE_IPC_RUNNING,
IPC_MEM_DEVICE_IPC_RECOVERY,
IPC_MEM_DEVICE_IPC_ERROR,
IPC_MEM_DEVICE_IPC_DONT_CARE,
IPC_MEM_DEVICE_IPC_INVALID = -1
};
/* Boot ROM exit status. */
enum rom_exit_code {
IMEM_ROM_EXIT_OPEN_EXT = 0x01,
IMEM_ROM_EXIT_OPEN_MEM = 0x02,
IMEM_ROM_EXIT_CERT_EXT = 0x10,
IMEM_ROM_EXIT_CERT_MEM = 0x20,
IMEM_ROM_EXIT_FAIL = 0xFF
};
/* Boot stages */
enum ipc_mem_exec_stage {
IPC_MEM_EXEC_STAGE_RUN = 0x600DF00D,
IPC_MEM_EXEC_STAGE_CRASH = 0x8BADF00D,
IPC_MEM_EXEC_STAGE_CD_READY = 0xBADC0DED,
IPC_MEM_EXEC_STAGE_BOOT = 0xFEEDB007,
IPC_MEM_EXEC_STAGE_PSI = 0xFEEDBEEF,
IPC_MEM_EXEC_STAGE_EBL = 0xFEEDCAFE,
IPC_MEM_EXEC_STAGE_INVALID = 0xFFFFFFFF
};
/* mmio scratchpad info */
struct mmio_offset {
int exec_stage;
int chip_info;
int rom_exit_code;
int psi_address;
int psi_size;
int ipc_status;
int context_info;
int ap_win_base;
int ap_win_end;
int cp_version;
int cp_capability;
};
/**
* struct iosm_mmio - MMIO region mapped to the doorbell scratchpad.
* @base: Base address of MMIO region
* @dev: Pointer to device structure
* @offset: Start offset
* @context_info_addr: Physical base address of context info structure
* @chip_info_version: Version of chip info structure
* @chip_info_size: Size of chip info structure
* @mux_protocol: mux protocol
* @has_ul_flow_credit: Ul flow credit support
* @has_slp_no_prot: Device sleep no protocol support
* @has_mcr_support: Usage of mcr support
*/
struct iosm_mmio {
unsigned char __iomem *base;
struct device *dev;
struct mmio_offset offset;
phys_addr_t context_info_addr;
unsigned int chip_info_version;
unsigned int chip_info_size;
u32 mux_protocol;
u8 has_ul_flow_credit:1,
has_slp_no_prot:1,
has_mcr_support:1;
};
/**
* ipc_mmio_init - Allocate mmio instance data
* @mmio_addr: Mapped AP base address of the MMIO area.
* @dev: Pointer to device structure
*
* Returns: address of mmio instance data or NULL if fails.
*/
struct iosm_mmio *ipc_mmio_init(void __iomem *mmio_addr, struct device *dev);
/**
* ipc_mmio_set_psi_addr_and_size - Set start address and size of the
* primary system image (PSI) for the
* FW dowload.
* @ipc_mmio: Pointer to mmio instance
* @addr: PSI address
* @size: PSI immage size
*/
void ipc_mmio_set_psi_addr_and_size(struct iosm_mmio *ipc_mmio, dma_addr_t addr,
u32 size);
/**
* ipc_mmio_set_contex_info_addr - Stores the Context Info Address in
* MMIO instance to share it with CP during
* mmio_init.
* @ipc_mmio: Pointer to mmio instance
* @addr: 64-bit address of AP context information.
*/
void ipc_mmio_set_contex_info_addr(struct iosm_mmio *ipc_mmio,
phys_addr_t addr);
/**
* ipc_mmio_get_cp_version - Get the CP IPC version
* @ipc_mmio: Pointer to mmio instance
*
* Returns: version number on success and failure value on error.
*/
int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio);
/**
* ipc_mmio_get_rom_exit_code - Get exit code from CP boot rom download app
* @ipc_mmio: Pointer to mmio instance
*
* Returns: exit code from CP boot rom download APP
*/
enum rom_exit_code ipc_mmio_get_rom_exit_code(struct iosm_mmio *ipc_mmio);
/**
* ipc_mmio_get_exec_stage - Query CP execution stage
* @ipc_mmio: Pointer to mmio instance
*
* Returns: CP execution stage
*/
enum ipc_mem_exec_stage ipc_mmio_get_exec_stage(struct iosm_mmio *ipc_mmio);
/**
* ipc_mmio_get_ipc_state - Query CP IPC state
* @ipc_mmio: Pointer to mmio instance
*
* Returns: CP IPC state
*/
enum ipc_mem_device_ipc_state
ipc_mmio_get_ipc_state(struct iosm_mmio *ipc_mmio);
/**
* ipc_mmio_copy_chip_info - Copy size bytes of CP chip info structure
* into caller provided buffer
* @ipc_mmio: Pointer to mmio instance
* @dest: Pointer to caller provided buff
* @size: Number of bytes to copy
*/
void ipc_mmio_copy_chip_info(struct iosm_mmio *ipc_mmio, void *dest,
size_t size);
/**
* ipc_mmio_config - Write context info and AP memory range addresses.
* This needs to be called when CP is in
* IPC_MEM_DEVICE_IPC_INIT state
*
* @ipc_mmio: Pointer to mmio instance
*/
void ipc_mmio_config(struct iosm_mmio *ipc_mmio);
/**
* ipc_mmio_update_cp_capability - Read and update modem capability, from mmio
* capability offset
*
* @ipc_mmio: Pointer to mmio instance
*/
void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio);
#endif
@@ -0,0 +1,489 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_mux_codec.h"
/* At the begin of the runtime phase the IP MUX channel shall created. */
static int ipc_mux_channel_create(struct iosm_mux *ipc_mux)
{
int channel_id;
channel_id = ipc_imem_channel_alloc(ipc_mux->imem, ipc_mux->instance_id,
IPC_CTYPE_WWAN);
if (channel_id < 0) {
dev_err(ipc_mux->dev,
"allocation of the MUX channel id failed");
ipc_mux->state = MUX_S_ERROR;
ipc_mux->event = MUX_E_NOT_APPLICABLE;
goto no_channel;
}
/* Establish the MUX channel in blocking mode. */
ipc_mux->channel = ipc_imem_channel_open(ipc_mux->imem, channel_id,
IPC_HP_NET_CHANNEL_INIT);
if (!ipc_mux->channel) {
dev_err(ipc_mux->dev, "ipc_imem_channel_open failed");
ipc_mux->state = MUX_S_ERROR;
ipc_mux->event = MUX_E_NOT_APPLICABLE;
return -ENODEV; /* MUX channel is not available. */
}
/* Define the MUX active state properties. */
ipc_mux->state = MUX_S_ACTIVE;
ipc_mux->event = MUX_E_NO_ORDERS;
no_channel:
return channel_id;
}
/* Reset the session/if id state. */
static void ipc_mux_session_free(struct iosm_mux *ipc_mux, int if_id)
{
struct mux_session *if_entry;
if_entry = &ipc_mux->session[if_id];
/* Reset the session state. */
if_entry->wwan = NULL;
}
/* Create and send the session open command. */
static struct mux_cmd_open_session_resp *
ipc_mux_session_open_send(struct iosm_mux *ipc_mux, int if_id)
{
struct mux_cmd_open_session_resp *open_session_resp;
struct mux_acb *acb = &ipc_mux->acb;
union mux_cmd_param param;
/* open_session commands to one ACB and start transmission. */
param.open_session.flow_ctrl = 0;
param.open_session.ipv4v6_hints = 0;
param.open_session.reserved2 = 0;
param.open_session.dl_head_pad_len = cpu_to_le32(IPC_MEM_DL_ETH_OFFSET);
/* Finish and transfer ACB. The user thread is suspended.
* It is a blocking function call, until CP responds or timeout.
*/
acb->wanted_response = MUX_CMD_OPEN_SESSION_RESP;
if (ipc_mux_dl_acb_send_cmds(ipc_mux, MUX_CMD_OPEN_SESSION, if_id, 0,
&param, sizeof(param.open_session), true,
false) ||
acb->got_response != MUX_CMD_OPEN_SESSION_RESP) {
dev_err(ipc_mux->dev, "if_id %d: OPEN_SESSION send failed",
if_id);
return NULL;
}
open_session_resp = &ipc_mux->acb.got_param.open_session_resp;
if (open_session_resp->response != cpu_to_le32(MUX_CMD_RESP_SUCCESS)) {
dev_err(ipc_mux->dev,
"if_id %d,session open failed,response=%d", if_id,
open_session_resp->response);
return NULL;
}
return open_session_resp;
}
/* Open the first IP session. */
static bool ipc_mux_session_open(struct iosm_mux *ipc_mux,
struct mux_session_open *session_open)
{
struct mux_cmd_open_session_resp *open_session_resp;
int if_id;
/* Search for a free session interface id. */
if_id = le32_to_cpu(session_open->if_id);
if (if_id < 0 || if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) {
dev_err(ipc_mux->dev, "invalid interface id=%d", if_id);
return false;
}
/* Create and send the session open command.
* It is a blocking function call, until CP responds or timeout.
*/
open_session_resp = ipc_mux_session_open_send(ipc_mux, if_id);
if (!open_session_resp) {
ipc_mux_session_free(ipc_mux, if_id);
session_open->if_id = cpu_to_le32(-1);
return false;
}
/* Initialize the uplink skb accumulator. */
skb_queue_head_init(&ipc_mux->session[if_id].ul_list);
ipc_mux->session[if_id].dl_head_pad_len = IPC_MEM_DL_ETH_OFFSET;
ipc_mux->session[if_id].ul_head_pad_len =
le32_to_cpu(open_session_resp->ul_head_pad_len);
ipc_mux->session[if_id].wwan = ipc_mux->wwan;
/* Reset the flow ctrl stats of the session */
ipc_mux->session[if_id].flow_ctl_en_cnt = 0;
ipc_mux->session[if_id].flow_ctl_dis_cnt = 0;
ipc_mux->session[if_id].ul_flow_credits = 0;
ipc_mux->session[if_id].net_tx_stop = false;
ipc_mux->session[if_id].flow_ctl_mask = 0;
/* Save and return the assigned if id. */
session_open->if_id = cpu_to_le32(if_id);
ipc_mux->nr_sessions++;
return true;
}
/* Free pending session UL packet. */
static void ipc_mux_session_reset(struct iosm_mux *ipc_mux, int if_id)
{
/* Reset the session/if id state. */
ipc_mux_session_free(ipc_mux, if_id);
/* Empty the uplink skb accumulator. */
skb_queue_purge(&ipc_mux->session[if_id].ul_list);
}
static void ipc_mux_session_close(struct iosm_mux *ipc_mux,
struct mux_session_close *msg)
{
int if_id;
/* Copy the session interface id. */
if_id = le32_to_cpu(msg->if_id);
if (if_id < 0 || if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) {
dev_err(ipc_mux->dev, "invalid session id %d", if_id);
return;
}
/* Create and send the session close command.
* It is a blocking function call, until CP responds or timeout.
*/
if (ipc_mux_dl_acb_send_cmds(ipc_mux, MUX_CMD_CLOSE_SESSION, if_id, 0,
NULL, 0, true, false))
dev_err(ipc_mux->dev, "if_id %d: CLOSE_SESSION send failed",
if_id);
/* Reset the flow ctrl stats of the session */
ipc_mux->session[if_id].flow_ctl_en_cnt = 0;
ipc_mux->session[if_id].flow_ctl_dis_cnt = 0;
ipc_mux->session[if_id].flow_ctl_mask = 0;
ipc_mux_session_reset(ipc_mux, if_id);
ipc_mux->nr_sessions--;
}
static void ipc_mux_channel_close(struct iosm_mux *ipc_mux,
struct mux_channel_close *channel_close_p)
{
int i;
/* Free pending session UL packet. */
for (i = 0; i < IPC_MEM_MUX_IP_SESSION_ENTRIES; i++)
if (ipc_mux->session[i].wwan)
ipc_mux_session_reset(ipc_mux, i);
ipc_imem_channel_close(ipc_mux->imem, ipc_mux->channel_id);
/* Reset the MUX object. */
ipc_mux->state = MUX_S_INACTIVE;
ipc_mux->event = MUX_E_INACTIVE;
}
/* CP has interrupted AP. If AP is in IP MUX mode, execute the pending ops. */
static int ipc_mux_schedule(struct iosm_mux *ipc_mux, union mux_msg *msg)
{
enum mux_event order;
bool success;
int ret = -EIO;
if (!ipc_mux->initialized) {
ret = -EAGAIN;
goto out;
}
order = msg->common.event;
switch (ipc_mux->state) {
case MUX_S_INACTIVE:
if (order != MUX_E_MUX_SESSION_OPEN)
goto out; /* Wait for the request to open a session */
if (ipc_mux->event == MUX_E_INACTIVE)
/* Establish the MUX channel and the new state. */
ipc_mux->channel_id = ipc_mux_channel_create(ipc_mux);
if (ipc_mux->state != MUX_S_ACTIVE) {
ret = ipc_mux->channel_id; /* Missing the MUX channel */
goto out;
}
/* Disable the TD update timer and open the first IP session. */
ipc_imem_td_update_timer_suspend(ipc_mux->imem, true);
ipc_mux->event = MUX_E_MUX_SESSION_OPEN;
success = ipc_mux_session_open(ipc_mux, &msg->session_open);
ipc_imem_td_update_timer_suspend(ipc_mux->imem, false);
if (success)
ret = ipc_mux->channel_id;
goto out;
case MUX_S_ACTIVE:
switch (order) {
case MUX_E_MUX_SESSION_OPEN:
/* Disable the TD update timer and open a session */
ipc_imem_td_update_timer_suspend(ipc_mux->imem, true);
ipc_mux->event = MUX_E_MUX_SESSION_OPEN;
success = ipc_mux_session_open(ipc_mux,
&msg->session_open);
ipc_imem_td_update_timer_suspend(ipc_mux->imem, false);
if (success)
ret = ipc_mux->channel_id;
goto out;
case MUX_E_MUX_SESSION_CLOSE:
/* Release an IP session. */
ipc_mux->event = MUX_E_MUX_SESSION_CLOSE;
ipc_mux_session_close(ipc_mux, &msg->session_close);
if (!ipc_mux->nr_sessions) {
ipc_mux->event = MUX_E_MUX_CHANNEL_CLOSE;
ipc_mux_channel_close(ipc_mux,
&msg->channel_close);
}
ret = ipc_mux->channel_id;
goto out;
case MUX_E_MUX_CHANNEL_CLOSE:
/* Close the MUX channel pipes. */
ipc_mux->event = MUX_E_MUX_CHANNEL_CLOSE;
ipc_mux_channel_close(ipc_mux, &msg->channel_close);
ret = ipc_mux->channel_id;
goto out;
default:
/* Invalid order. */
goto out;
}
default:
dev_err(ipc_mux->dev,
"unexpected MUX transition: state=%d, event=%d",
ipc_mux->state, ipc_mux->event);
}
out:
return ret;
}
struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg,
struct iosm_imem *imem)
{
struct iosm_mux *ipc_mux = kzalloc_obj(*ipc_mux);
int i, j, ul_tds, ul_td_size;
struct sk_buff_head *free_list;
struct sk_buff *skb;
int qlt_size;
if (!ipc_mux)
return NULL;
ipc_mux->protocol = mux_cfg->protocol;
ipc_mux->ul_flow = mux_cfg->ul_flow;
ipc_mux->instance_id = mux_cfg->instance_id;
ipc_mux->wwan_q_offset = 0;
ipc_mux->pcie = imem->pcie;
ipc_mux->imem = imem;
ipc_mux->ipc_protocol = imem->ipc_protocol;
ipc_mux->dev = imem->dev;
ipc_mux->wwan = imem->wwan;
/* Get the reference to the UL ADB list. */
free_list = &ipc_mux->ul_adb.free_list;
/* Initialize the list with free ADB. */
skb_queue_head_init(free_list);
ul_td_size = IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE;
ul_tds = IPC_MEM_MAX_TDS_MUX_LITE_UL;
ipc_mux->ul_adb.dest_skb = NULL;
ipc_mux->initialized = true;
ipc_mux->adb_prep_ongoing = false;
ipc_mux->size_needed = 0;
ipc_mux->ul_data_pend_bytes = 0;
ipc_mux->state = MUX_S_INACTIVE;
ipc_mux->ev_mux_net_transmit_pending = false;
ipc_mux->tx_transaction_id = 0;
ipc_mux->rr_next_session = 0;
ipc_mux->event = MUX_E_INACTIVE;
ipc_mux->channel_id = -1;
ipc_mux->channel = NULL;
if (ipc_mux->protocol != MUX_LITE) {
qlt_size = offsetof(struct mux_qlth, ql) +
MUX_QUEUE_LEVEL * sizeof(struct mux_qlth_ql);
for (i = 0; i < IPC_MEM_MUX_IP_SESSION_ENTRIES; i++) {
ipc_mux->ul_adb.pp_qlt[i] = kzalloc(qlt_size,
GFP_ATOMIC);
if (!ipc_mux->ul_adb.pp_qlt[i]) {
for (j = i - 1; j >= 0; j--)
kfree(ipc_mux->ul_adb.pp_qlt[j]);
kfree(ipc_mux);
return NULL;
}
}
ul_td_size = IPC_MEM_MAX_UL_ADB_BUF_SIZE;
ul_tds = IPC_MEM_MAX_TDS_MUX_AGGR_UL;
}
/* Allocate the list of UL ADB. */
for (i = 0; i < ul_tds; i++) {
dma_addr_t mapping;
skb = ipc_pcie_alloc_skb(ipc_mux->pcie, ul_td_size, GFP_ATOMIC,
&mapping, DMA_TO_DEVICE, 0);
if (!skb) {
ipc_mux_deinit(ipc_mux);
return NULL;
}
/* Extend the UL ADB list. */
skb_queue_tail(free_list, skb);
}
return ipc_mux;
}
/* Informs the network stack to restart transmission for all opened session if
* Flow Control is not ON for that session.
*/
static void ipc_mux_restart_tx_for_all_sessions(struct iosm_mux *ipc_mux)
{
struct mux_session *session;
int idx;
for (idx = 0; idx < IPC_MEM_MUX_IP_SESSION_ENTRIES; idx++) {
session = &ipc_mux->session[idx];
if (!session->wwan)
continue;
/* If flow control of the session is OFF and if there was tx
* stop then restart. Inform the network interface to restart
* sending data.
*/
if (session->flow_ctl_mask == 0) {
session->net_tx_stop = false;
ipc_mux_netif_tx_flowctrl(session, idx, false);
}
}
}
/* Informs the network stack to stop sending further pkt for all opened
* sessions
*/
static void ipc_mux_stop_netif_for_all_sessions(struct iosm_mux *ipc_mux)
{
struct mux_session *session;
int idx;
for (idx = 0; idx < IPC_MEM_MUX_IP_SESSION_ENTRIES; idx++) {
session = &ipc_mux->session[idx];
if (!session->wwan)
continue;
ipc_mux_netif_tx_flowctrl(session, session->if_id, true);
}
}
void ipc_mux_check_n_restart_tx(struct iosm_mux *ipc_mux)
{
if (ipc_mux->ul_flow == MUX_UL) {
int low_thresh = IPC_MEM_MUX_UL_FLOWCTRL_LOW_B;
if (ipc_mux->ul_data_pend_bytes < low_thresh)
ipc_mux_restart_tx_for_all_sessions(ipc_mux);
}
}
int ipc_mux_get_max_sessions(struct iosm_mux *ipc_mux)
{
return ipc_mux ? IPC_MEM_MUX_IP_SESSION_ENTRIES : -EFAULT;
}
enum ipc_mux_protocol ipc_mux_get_active_protocol(struct iosm_mux *ipc_mux)
{
return ipc_mux ? ipc_mux->protocol : MUX_UNKNOWN;
}
int ipc_mux_open_session(struct iosm_mux *ipc_mux, int session_nr)
{
struct mux_session_open *session_open;
union mux_msg mux_msg;
session_open = &mux_msg.session_open;
session_open->event = MUX_E_MUX_SESSION_OPEN;
session_open->if_id = cpu_to_le32(session_nr);
ipc_mux->session[session_nr].flags |= IPC_MEM_WWAN_MUX;
return ipc_mux_schedule(ipc_mux, &mux_msg);
}
int ipc_mux_close_session(struct iosm_mux *ipc_mux, int session_nr)
{
struct mux_session_close *session_close;
union mux_msg mux_msg;
int ret_val;
session_close = &mux_msg.session_close;
session_close->event = MUX_E_MUX_SESSION_CLOSE;
session_close->if_id = cpu_to_le32(session_nr);
ret_val = ipc_mux_schedule(ipc_mux, &mux_msg);
ipc_mux->session[session_nr].flags &= ~IPC_MEM_WWAN_MUX;
return ret_val;
}
void ipc_mux_deinit(struct iosm_mux *ipc_mux)
{
struct mux_channel_close *channel_close;
struct sk_buff_head *free_list;
union mux_msg mux_msg;
struct sk_buff *skb;
int i;
if (!ipc_mux->initialized)
return;
ipc_mux_stop_netif_for_all_sessions(ipc_mux);
if (ipc_mux->state == MUX_S_ACTIVE) {
channel_close = &mux_msg.channel_close;
channel_close->event = MUX_E_MUX_CHANNEL_CLOSE;
ipc_mux_schedule(ipc_mux, &mux_msg);
}
/* Empty the ADB free list. */
free_list = &ipc_mux->ul_adb.free_list;
/* Remove from the head of the downlink queue. */
while ((skb = skb_dequeue(free_list)))
ipc_pcie_kfree_skb(ipc_mux->pcie, skb);
if (ipc_mux->channel) {
ipc_mux->channel->ul_pipe.is_open = false;
ipc_mux->channel->dl_pipe.is_open = false;
}
if (ipc_mux->protocol != MUX_LITE) {
for (i = 0; i < IPC_MEM_MUX_IP_SESSION_ENTRIES; i++)
kfree(ipc_mux->ul_adb.pp_qlt[i]);
}
kfree(ipc_mux);
}
@@ -0,0 +1,442 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_MUX_H
#define IOSM_IPC_MUX_H
#include "iosm_ipc_protocol.h"
#define IPC_MEM_MAX_UL_DG_ENTRIES 100
#define IPC_MEM_MAX_TDS_MUX_AGGR_UL 60
#define IPC_MEM_MAX_TDS_MUX_AGGR_DL 60
#define IPC_MEM_MAX_ADB_BUF_SIZE (16 * 1024)
#define IPC_MEM_MAX_UL_ADB_BUF_SIZE IPC_MEM_MAX_ADB_BUF_SIZE
#define IPC_MEM_MAX_DL_ADB_BUF_SIZE IPC_MEM_MAX_ADB_BUF_SIZE
/* Size of the buffer for the IP MUX Lite data buffer. */
#define IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE (2 * 1024)
/* TD counts for IP MUX Lite */
#define IPC_MEM_MAX_TDS_MUX_LITE_UL 800
#define IPC_MEM_MAX_TDS_MUX_LITE_DL 1200
/* open session request (AP->CP) */
#define MUX_CMD_OPEN_SESSION 1
/* response to open session request (CP->AP) */
#define MUX_CMD_OPEN_SESSION_RESP 2
/* close session request (AP->CP) */
#define MUX_CMD_CLOSE_SESSION 3
/* response to close session request (CP->AP) */
#define MUX_CMD_CLOSE_SESSION_RESP 4
/* Flow control command with mask of the flow per queue/flow. */
#define MUX_LITE_CMD_FLOW_CTL 5
/* ACK the flow control command. Shall have the same Transaction ID as the
* matching FLOW_CTL command.
*/
#define MUX_LITE_CMD_FLOW_CTL_ACK 6
/* Command for report packet indicating link quality metrics. */
#define MUX_LITE_CMD_LINK_STATUS_REPORT 7
/* Response to a report packet */
#define MUX_LITE_CMD_LINK_STATUS_REPORT_RESP 8
/* Used to reset a command/response state. */
#define MUX_CMD_INVALID 255
/* command response : command processed successfully */
#define MUX_CMD_RESP_SUCCESS 0
/* MUX for route link devices */
#define IPC_MEM_WWAN_MUX BIT(0)
/* Initiated actions to change the state of the MUX object. */
enum mux_event {
MUX_E_INACTIVE, /* No initiated actions. */
MUX_E_MUX_SESSION_OPEN, /* Create the MUX channel and a session. */
MUX_E_MUX_SESSION_CLOSE, /* Release a session. */
MUX_E_MUX_CHANNEL_CLOSE, /* Release the MUX channel. */
MUX_E_NO_ORDERS, /* No MUX order. */
MUX_E_NOT_APPLICABLE, /* Defect IP MUX. */
};
/* MUX session open command. */
struct mux_session_open {
enum mux_event event;
__le32 if_id;
};
/* MUX session close command. */
struct mux_session_close {
enum mux_event event;
__le32 if_id;
};
/* MUX channel close command. */
struct mux_channel_close {
enum mux_event event;
};
/* Default message type to find out the right message type. */
struct mux_common {
enum mux_event event;
};
/* List of ops in MUX mode. */
union mux_msg {
struct mux_session_open session_open;
struct mux_session_close session_close;
struct mux_channel_close channel_close;
struct mux_common common;
};
/* Parameter definition of the open session command. */
struct mux_cmd_open_session {
u8 flow_ctrl; /* 0: Flow control disabled (flow allowed). */
/* 1: Flow control enabled (flow not allowed)*/
u8 ipv4v6_hints; /* 0: IPv4/IPv6 hints not supported.*/
/* 1: IPv4/IPv6 hints supported*/
__le16 reserved2; /* Reserved. Set to zero. */
__le32 dl_head_pad_len; /* Maximum length supported */
/* for DL head padding on a datagram. */
};
/* Parameter definition of the open session response. */
struct mux_cmd_open_session_resp {
__le32 response; /* Response code */
u8 flow_ctrl; /* 0: Flow control disabled (flow allowed). */
/* 1: Flow control enabled (flow not allowed) */
u8 ipv4v6_hints; /* 0: IPv4/IPv6 hints not supported */
/* 1: IPv4/IPv6 hints supported */
__le16 reserved2; /* Reserved. Set to zero. */
__le32 ul_head_pad_len; /* Actual length supported for */
/* UL head padding on adatagram.*/
};
/* Parameter definition of the close session response code */
struct mux_cmd_close_session_resp {
__le32 response;
};
/* Parameter definition of the flow control command. */
struct mux_cmd_flow_ctl {
__le32 mask; /* indicating the desired flow control */
/* state for various flows/queues */
};
/* Parameter definition of the link status report code*/
struct mux_cmd_link_status_report {
u8 payload;
};
/* Parameter definition of the link status report response code. */
struct mux_cmd_link_status_report_resp {
__le32 response;
};
/**
* union mux_cmd_param - Union-definition of the command parameters.
* @open_session: Inband command for open session
* @open_session_resp: Inband command for open session response
* @close_session_resp: Inband command for close session response
* @flow_ctl: In-band flow control on the opened interfaces
* @link_status: In-band Link Status Report
* @link_status_resp: In-band command for link status report response
*/
union mux_cmd_param {
struct mux_cmd_open_session open_session;
struct mux_cmd_open_session_resp open_session_resp;
struct mux_cmd_close_session_resp close_session_resp;
struct mux_cmd_flow_ctl flow_ctl;
struct mux_cmd_link_status_report link_status;
struct mux_cmd_link_status_report_resp link_status_resp;
};
/* States of the MUX object.. */
enum mux_state {
MUX_S_INACTIVE, /* IP MUX is unused. */
MUX_S_ACTIVE, /* IP MUX channel is available. */
MUX_S_ERROR, /* Defect IP MUX. */
};
/* Supported MUX protocols. */
enum ipc_mux_protocol {
MUX_UNKNOWN,
MUX_LITE,
MUX_AGGREGATION,
};
/* Supported UL data transfer methods. */
enum ipc_mux_ul_flow {
MUX_UL_UNKNOWN,
MUX_UL, /* Normal UL data transfer */
MUX_UL_ON_CREDITS, /* UL data transfer will be based on credits */
};
/* List of the MUX session. */
struct mux_session {
struct iosm_wwan *wwan; /*Network i/f used for communication*/
int if_id; /* i/f id for session open message.*/
u32 flags;
u32 ul_head_pad_len; /* Nr of bytes for UL head padding. */
u32 dl_head_pad_len; /* Nr of bytes for DL head padding. */
struct sk_buff_head ul_list; /* skb entries for an ADT. */
u32 flow_ctl_mask; /* UL flow control */
u32 flow_ctl_en_cnt; /* Flow control Enable cmd count */
u32 flow_ctl_dis_cnt; /* Flow Control Disable cmd count */
int ul_flow_credits; /* UL flow credits */
u8 net_tx_stop:1,
flush:1; /* flush net interface ? */
};
/**
* struct mux_adth_dg - Structure of the datagram in the Aggregated Datagram
* Table Header.
* @datagram_index : Index (in bytes) to the k-th datagram in the table.
* Index shall count from the start of the block including
* the 16-byte header. This value shall be non-zero.
* @datagram_length: Length of the k-th datagram including the head padding.
* This value shall be non-zero.
* @service_class: Service class identifier for the datagram.
* @reserved: Reserved bytes. Set to zero
*/
struct mux_adth_dg {
__le32 datagram_index;
__le16 datagram_length;
u8 service_class;
u8 reserved;
};
/**
* struct mux_qlth_ql - Structure of the queue level in the Aggregated
* Datagram Queue Level Table Header.
* @nr_of_bytes: Number of bytes available to transmit in the queue.
*/
struct mux_qlth_ql {
__le32 nr_of_bytes;
};
/**
* struct mux_qlth - Structure of Aggregated Datagram Queue Level Table
* Header.
* @signature: Signature of the Queue Level Table Header
* Value: 0x48544C51 (ASCII characters: 'Q' 'L' 'T' 'H')
* @table_length: Length (in bytes) of the datagram table. This length
* shall include the queue level table header size.
* Minimum value:0x10
* @if_id: ID of the interface the queue levels in the table
* belong to.
* @reserved: Reserved byte. Set to zero.
* @next_table_index: Index (in bytes) to the next table in the buffer. Index
* shall count from the start of the block including the
* 16-byte header. Value of zero indicates end of the list.
* @reserved2: Reserved bytes. Set to zero
* @ql: Queue level table with variable length
*/
struct mux_qlth {
__le32 signature;
__le16 table_length;
u8 if_id;
u8 reserved;
__le32 next_table_index;
__le32 reserved2;
struct mux_qlth_ql ql;
};
/**
* struct mux_adb - Structure of State of a single UL data block.
* @dest_skb: Current UL skb for the data block.
* @buf: ADB memory
* @adgh: ADGH pointer
* @qlth_skb: QLTH pointer
* @next_table_index: Pointer to next table index.
* @free_list: List of alloc. ADB for the UL sess.
* @size: Size of the ADB memory.
* @if_cnt: Statistic counter
* @dg_cnt_total: Datagram count total
* @payload_size: Payload Size
* @dg: Datagram table.
* @pp_qlt: Pointers to hold Queue Level Tables of session
* @adbh: ADBH pointer
* @qlt_updated: Queue level table updated
* @dg_count: Datagram count
*/
struct mux_adb {
struct sk_buff *dest_skb;
u8 *buf;
struct mux_adgh *adgh;
struct sk_buff *qlth_skb;
u32 *next_table_index;
struct sk_buff_head free_list;
int size;
u32 if_cnt;
u32 dg_cnt_total;
u32 payload_size;
struct mux_adth_dg
dg[IPC_MEM_MUX_IP_SESSION_ENTRIES][IPC_MEM_MAX_UL_DG_ENTRIES];
struct mux_qlth *pp_qlt[IPC_MEM_MUX_IP_SESSION_ENTRIES];
struct mux_adbh *adbh;
u32 qlt_updated[IPC_MEM_MUX_IP_SESSION_ENTRIES];
u32 dg_count[IPC_MEM_MUX_IP_SESSION_ENTRIES];
};
/**
* struct mux_acb - Structure of Temporary ACB state.
* @skb: Used UL skb.
* @if_id: Session id.
* @buf_p: Command buffer.
* @wanted_response: Wanted Response
* @got_response: Got response
* @cmd: command
* @got_param: Received command/response parameter
*/
struct mux_acb {
struct sk_buff *skb; /* Used UL skb. */
int if_id; /* Session id. */
u8 *buf_p;
u32 wanted_response;
u32 got_response;
u32 cmd;
union mux_cmd_param got_param; /* Received command/response parameter */
};
/**
* struct iosm_mux - Structure of the data multiplexing over an IP channel.
* @dev: Pointer to device structure
* @session: Array of the MUX sessions.
* @channel: Reference to the IP MUX channel
* @pcie: Pointer to iosm_pcie struct
* @imem: Pointer to iosm_imem
* @wwan: Poinetr to iosm_wwan
* @ipc_protocol: Pointer to iosm_protocol
* @channel_id: Channel ID for MUX
* @protocol: Type of the MUX protocol
* @ul_flow: UL Flow type
* @nr_sessions: Number of sessions
* @instance_id: Instance ID
* @state: States of the MUX object
* @event: Initiated actions to change the state of the MUX object
* @tx_transaction_id: Transaction id for the ACB command.
* @rr_next_session: Next session number for round robin.
* @ul_adb: State of the UL ADB/ADGH.
* @size_needed: Variable to store the size needed during ADB preparation
* @ul_data_pend_bytes: Pending UL data to be processed in bytes
* @acb: Temporary ACB state
* @wwan_q_offset: This will hold the offset of the given instance
* Useful while passing or receiving packets from
* wwan/imem layer.
* @acb_tx_sequence_nr: Sequence number for the ACB header.
* @adb_tx_sequence_nr: Sequence number for ADB header
* @acc_adb_size: Statistic data for logging
* @acc_payload_size: Statistic data for logging
* @initialized: MUX object is initialized
* @ev_mux_net_transmit_pending:
* 0 means inform the IPC tasklet to pass the
* accumulated uplink ADB to CP.
* @adb_prep_ongoing: Flag for ADB preparation status
*/
struct iosm_mux {
struct device *dev;
struct mux_session session[IPC_MEM_MUX_IP_SESSION_ENTRIES];
struct ipc_mem_channel *channel;
struct iosm_pcie *pcie;
struct iosm_imem *imem;
struct iosm_wwan *wwan;
struct iosm_protocol *ipc_protocol;
int channel_id;
enum ipc_mux_protocol protocol;
enum ipc_mux_ul_flow ul_flow;
int nr_sessions;
int instance_id;
enum mux_state state;
enum mux_event event;
u32 tx_transaction_id;
int rr_next_session;
struct mux_adb ul_adb;
int size_needed;
long long ul_data_pend_bytes;
struct mux_acb acb;
int wwan_q_offset;
u16 acb_tx_sequence_nr;
u16 adb_tx_sequence_nr;
unsigned long long acc_adb_size;
unsigned long long acc_payload_size;
u8 initialized:1,
ev_mux_net_transmit_pending:1,
adb_prep_ongoing;
} __packed;
/* MUX configuration structure */
struct ipc_mux_config {
enum ipc_mux_protocol protocol;
enum ipc_mux_ul_flow ul_flow;
int instance_id;
};
/**
* ipc_mux_init - Allocates and Init MUX instance
* @mux_cfg: Pointer to MUX configuration structure
* @ipc_imem: Pointer to imem data-struct
*
* Returns: Initialized mux pointer on success else NULL
*/
struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg,
struct iosm_imem *ipc_imem);
/**
* ipc_mux_deinit - Deallocates MUX instance
* @ipc_mux: Pointer to the MUX instance.
*/
void ipc_mux_deinit(struct iosm_mux *ipc_mux);
/**
* ipc_mux_check_n_restart_tx - Checks for pending UL date bytes and then
* it restarts the net interface tx queue if
* device has set flow control as off.
* @ipc_mux: Pointer to MUX data-struct
*/
void ipc_mux_check_n_restart_tx(struct iosm_mux *ipc_mux);
/**
* ipc_mux_get_active_protocol - Returns the active MUX protocol type.
* @ipc_mux: Pointer to MUX data-struct
*
* Returns: enum of type ipc_mux_protocol
*/
enum ipc_mux_protocol ipc_mux_get_active_protocol(struct iosm_mux *ipc_mux);
/**
* ipc_mux_open_session - Opens a MUX session for IP traffic.
* @ipc_mux: Pointer to MUX data-struct
* @session_nr: Interface ID or session number
*
* Returns: channel id on success, failure value on error
*/
int ipc_mux_open_session(struct iosm_mux *ipc_mux, int session_nr);
/**
* ipc_mux_close_session - Closes a MUX session.
* @ipc_mux: Pointer to MUX data-struct
* @session_nr: Interface ID or session number
*
* Returns: channel id on success, failure value on error
*/
int ipc_mux_close_session(struct iosm_mux *ipc_mux, int session_nr);
/**
* ipc_mux_get_max_sessions - Returns the maximum sessions supported on the
* provided MUX instance..
* @ipc_mux: Pointer to MUX data-struct
*
* Returns: Number of sessions supported on Success and failure value on error
*/
int ipc_mux_get_max_sessions(struct iosm_mux *ipc_mux);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,325 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_MUX_CODEC_H
#define IOSM_IPC_MUX_CODEC_H
#include "iosm_ipc_mux.h"
/* Queue level size and reporting
* >1 is enable, 0 is disable
*/
#define MUX_QUEUE_LEVEL 1
/* ADB finish timer value */
#define IOSM_AGGR_MUX_ADB_FINISH_TIMEOUT_NSEC (500 * 1000)
/* Enables the flow control (Flow is not allowed) */
#define IOSM_AGGR_MUX_CMD_FLOW_CTL_ENABLE 5
/* Disables the flow control (Flow is allowed) */
#define IOSM_AGGR_MUX_CMD_FLOW_CTL_DISABLE 6
/* ACK the flow control command. Shall have the same Transaction ID as the
* matching FLOW_CTL command
*/
#define IOSM_AGGR_MUX_CMD_FLOW_CTL_ACK 7
/* Aggregation Protocol Command for report packet indicating link quality
*/
#define IOSM_AGGR_MUX_CMD_LINK_STATUS_REPORT 8
/* Response to a report packet */
#define IOSM_AGGR_MUX_CMD_LINK_STATUS_REPORT_RESP 9
/* ACBH: Signature of the Aggregated Command Block Header. */
#define IOSM_AGGR_MUX_SIG_ACBH 0x48424341
/* ADTH: Signature of the Aggregated Datagram Table Header. */
#define IOSM_AGGR_MUX_SIG_ADTH 0x48544441
/* ADBH: Signature of the Aggregated Data Block Header. */
#define IOSM_AGGR_MUX_SIG_ADBH 0x48424441
/* ADGH: Signature of the Datagram Header. */
#define IOSM_AGGR_MUX_SIG_ADGH 0x48474441
/* Size of the buffer for the IP MUX commands. */
#define MUX_MAX_UL_ACB_BUF_SIZE 256
/* Maximum number of packets in a go per session */
#define MUX_MAX_UL_DG_ENTRIES 100
/* ADGH: Signature of the Datagram Header. */
#define MUX_SIG_ADGH 0x48474441
/* CMDH: Signature of the Command Header. */
#define MUX_SIG_CMDH 0x48444D43
/* QLTH: Signature of the Queue Level Table */
#define MUX_SIG_QLTH 0x48544C51
/* FCTH: Signature of the Flow Credit Table */
#define MUX_SIG_FCTH 0x48544346
/* MUX UL session threshold factor */
#define IPC_MEM_MUX_UL_SESS_FCOFF_THRESHOLD_FACTOR (4)
/* Size of the buffer for the IP MUX Lite data buffer. */
#define IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE (2 * 1024)
/* MUX UL session threshold in number of packets */
#define IPC_MEM_MUX_UL_SESS_FCON_THRESHOLD (64)
/* Default time out for sending IPC session commands like
* open session, close session etc
* unit : milliseconds
*/
#define IPC_MUX_CMD_RUN_DEFAULT_TIMEOUT 1000 /* 1 second */
/* MUX UL flow control lower threshold in bytes */
#define IPC_MEM_MUX_UL_FLOWCTRL_LOW_B 10240 /* 10KB */
/* MUX UL flow control higher threshold in bytes (5ms worth of data)*/
#define IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B (110 * 1024)
/**
* struct mux_cmdh - Structure of Command Header.
* @signature: Signature of the Command Header.
* @cmd_len: Length (in bytes) of the Aggregated Command Block.
* @if_id: ID of the interface the commands in the table belong to.
* @reserved: Reserved. Set to zero.
* @next_cmd_index: Index (in bytes) to the next command in the buffer.
* @command_type: Command Enum. See table Session Management chapter for
* details.
* @transaction_id: The Transaction ID shall be unique to the command
* @param: Optional parameters used with the command.
*/
struct mux_cmdh {
__le32 signature;
__le16 cmd_len;
u8 if_id;
u8 reserved;
__le32 next_cmd_index;
__le32 command_type;
__le32 transaction_id;
union mux_cmd_param param;
};
/**
* struct mux_acbh - Structure of the Aggregated Command Block Header.
* @signature: Signature of the Aggregated Command Block Header.
* @reserved: Reserved bytes. Set to zero.
* @sequence_nr: Block sequence number.
* @block_length: Length (in bytes) of the Aggregated Command Block.
* @first_cmd_index: Index (in bytes) to the first command in the buffer.
*/
struct mux_acbh {
__le32 signature;
__le16 reserved;
__le16 sequence_nr;
__le32 block_length;
__le32 first_cmd_index;
};
/**
* struct mux_adbh - Structure of the Aggregated Data Block Header.
* @signature: Signature of the Aggregated Data Block Header.
* @reserved: Reserved bytes. Set to zero.
* @sequence_nr: Block sequence number.
* @block_length: Length (in bytes) of the Aggregated Data Block.
* @first_table_index: Index (in bytes) to the first Datagram Table in
* the buffer.
*/
struct mux_adbh {
__le32 signature;
__le16 reserved;
__le16 sequence_nr;
__le32 block_length;
__le32 first_table_index;
};
/**
* struct mux_adth - Structure of the Aggregated Datagram Table Header.
* @signature: Signature of the Aggregated Datagram Table Header.
* @table_length: Length (in bytes) of the datagram table.
* @if_id: ID of the interface the datagrams in the table
* belong to.
* @opt_ipv4v6: Indicates IPv4(=0)/IPv6(=1) hint.
* @reserved: Reserved bits. Set to zero.
* @next_table_index: Index (in bytes) to the next Datagram Table in
* the buffer.
* @reserved2: Reserved bytes. Set to zero
* @dg: datagramm table with variable length
*/
struct mux_adth {
__le32 signature;
__le16 table_length;
u8 if_id;
u8 opt_ipv4v6;
__le32 next_table_index;
__le32 reserved2;
struct mux_adth_dg dg[];
};
/**
* struct mux_adgh - Aggregated Datagram Header.
* @signature: Signature of the Aggregated Datagram Header(0x48474441)
* @length: Length (in bytes) of the datagram header. This length
* shall include the header size. Min value: 0x10
* @if_id: ID of the interface the datagrams belong to
* @opt_ipv4v6: Indicates IPv4(=0)/IPv6(=1), It is optional if not
* used set it to zero.
* @reserved: Reserved bits. Set to zero.
* @service_class: Service class identifier for the datagram.
* @next_count: Count of the datagrams that shall be following this
* datagrams for this interface. A count of zero means
* the next datagram may not belong to this interface.
* @reserved1: Reserved bytes, Set to zero
*/
struct mux_adgh {
__le32 signature;
__le16 length;
u8 if_id;
u8 opt_ipv4v6;
u8 service_class;
u8 next_count;
u8 reserved1[6];
};
/**
* struct mux_lite_cmdh - MUX Lite Command Header
* @signature: Signature of the Command Header(0x48444D43)
* @cmd_len: Length (in bytes) of the command. This length shall
* include the header size. Minimum value: 0x10
* @if_id: ID of the interface the commands in the table belong to.
* @reserved: Reserved Set to zero.
* @command_type: Command Enum.
* @transaction_id: 4 byte value shall be generated and sent along with a
* command Responses and ACKs shall have the same
* Transaction ID as their commands. It shall be unique to
* the command transaction on the given interface.
* @param: Optional parameters used with the command.
*/
struct mux_lite_cmdh {
__le32 signature;
__le16 cmd_len;
u8 if_id;
u8 reserved;
__le32 command_type;
__le32 transaction_id;
union mux_cmd_param param;
};
/**
* struct mux_lite_vfl - value field in generic table
* @nr_of_bytes: Number of bytes available to transmit in the queue.
*/
struct mux_lite_vfl {
__le32 nr_of_bytes;
};
/**
* struct ipc_mem_lite_gen_tbl - Generic table format for Queue Level
* and Flow Credit
* @signature: Signature of the table
* @length: Length of the table
* @if_id: ID of the interface the table belongs to
* @vfl_length: Value field length
* @reserved: Reserved
* @vfl: Value field of variable length
*/
struct ipc_mem_lite_gen_tbl {
__le32 signature;
__le16 length;
u8 if_id;
u8 vfl_length;
u32 reserved[2];
struct mux_lite_vfl vfl;
};
/**
* struct mux_type_cmdh - Structure of command header for mux lite and aggr
* @ack_lite: MUX Lite Command Header pointer
* @ack_aggr: Command Header pointer
*/
union mux_type_cmdh {
struct mux_lite_cmdh *ack_lite;
struct mux_cmdh *ack_aggr;
};
/**
* struct mux_type_header - Structure of mux header type
* @adgh: Aggregated Datagram Header pointer
* @adbh: Aggregated Data Block Header pointer
*/
union mux_type_header {
struct mux_adgh *adgh;
struct mux_adbh *adbh;
};
void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb);
/**
* ipc_mux_dl_acb_send_cmds - Respond to the Command blocks.
* @ipc_mux: Pointer to MUX data-struct
* @cmd_type: Command
* @if_id: Session interface id.
* @transaction_id: Command transaction id.
* @param: Pointer to command params.
* @res_size: Response size
* @blocking: True for blocking send
* @respond: If true return transaction ID
*
* Returns: 0 in success and failure value on error
*/
int ipc_mux_dl_acb_send_cmds(struct iosm_mux *ipc_mux, u32 cmd_type, u8 if_id,
u32 transaction_id, union mux_cmd_param *param,
size_t res_size, bool blocking, bool respond);
/**
* ipc_mux_netif_tx_flowctrl - Enable/Disable TX flow control on MUX sessions.
* @session: Pointer to mux_session struct
* @idx: Session ID
* @on: true for Enable and false for disable flow control
*/
void ipc_mux_netif_tx_flowctrl(struct mux_session *session, int idx, bool on);
/**
* ipc_mux_ul_trigger_encode - Route the UL packet through the IP MUX layer
* for encoding.
* @ipc_mux: Pointer to MUX data-struct
* @if_id: Session ID.
* @skb: Pointer to ipc_skb.
*
* Returns: 0 if successfully encoded
* failure value on error
* -EBUSY if packet has to be retransmitted.
*/
int ipc_mux_ul_trigger_encode(struct iosm_mux *ipc_mux, int if_id,
struct sk_buff *skb);
/**
* ipc_mux_ul_data_encode - UL encode function for calling from Tasklet context.
* @ipc_mux: Pointer to MUX data-struct
*
* Returns: TRUE if any packet of any session is encoded FALSE otherwise.
*/
bool ipc_mux_ul_data_encode(struct iosm_mux *ipc_mux);
/**
* ipc_mux_ul_encoded_process - Handles the Modem processed UL data by adding
* the SKB to the UL free list.
* @ipc_mux: Pointer to MUX data-struct
* @skb: Pointer to ipc_skb.
*/
void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb);
void ipc_mux_ul_adb_finish(struct iosm_mux *ipc_mux);
void ipc_mux_ul_adb_update_ql(struct iosm_mux *ipc_mux, struct mux_adb *p_adb,
int session_id, int qlth_n_ql_size,
struct sk_buff_head *ul_list);
#endif
@@ -0,0 +1,586 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/suspend.h>
#include <net/rtnetlink.h>
#include "iosm_ipc_imem.h"
#include "iosm_ipc_pcie.h"
#include "iosm_ipc_protocol.h"
MODULE_DESCRIPTION("IOSM Driver");
MODULE_LICENSE("GPL v2");
/* WWAN GUID */
static guid_t wwan_acpi_guid = GUID_INIT(0xbad01b75, 0x22a8, 0x4f48, 0x87, 0x92,
0xbd, 0xde, 0x94, 0x67, 0x74, 0x7d);
static bool pci_registered;
static void ipc_pcie_resources_release(struct iosm_pcie *ipc_pcie)
{
/* Free the MSI resources. */
ipc_release_irq(ipc_pcie);
/* Free mapped doorbell scratchpad bus memory into CPU space. */
iounmap(ipc_pcie->scratchpad);
/* Free mapped IPC_REGS bus memory into CPU space. */
iounmap(ipc_pcie->ipc_regs);
/* Releases all PCI I/O and memory resources previously reserved by a
* successful call to pci_request_regions. Call this function only
* after all use of the PCI regions has ceased.
*/
pci_release_regions(ipc_pcie->pci);
}
static void ipc_pcie_cleanup(struct iosm_pcie *ipc_pcie)
{
/* Free the shared memory resources. */
ipc_imem_cleanup(ipc_pcie->imem);
ipc_pcie_resources_release(ipc_pcie);
/* Signal to the system that the PCI device is not in use. */
pci_disable_device(ipc_pcie->pci);
}
static void ipc_pcie_deinit(struct iosm_pcie *ipc_pcie)
{
kfree(ipc_pcie->imem);
kfree(ipc_pcie);
}
static void ipc_pcie_remove(struct pci_dev *pci)
{
struct iosm_pcie *ipc_pcie = pci_get_drvdata(pci);
ipc_pcie_cleanup(ipc_pcie);
ipc_pcie_deinit(ipc_pcie);
}
static int ipc_pcie_resources_request(struct iosm_pcie *ipc_pcie)
{
struct pci_dev *pci = ipc_pcie->pci;
u32 cap = 0;
int ret;
/* Reserved PCI I/O and memory resources.
* Mark all PCI regions associated with PCI device pci as
* being reserved by owner IOSM_IPC.
*/
ret = pci_request_regions(pci, "IOSM_IPC");
if (ret) {
dev_err(ipc_pcie->dev, "failed pci request regions");
goto pci_request_region_fail;
}
/* Reserve the doorbell IPC REGS memory resources.
* Remap the memory into CPU space. Arrange for the physical address
* (BAR) to be visible from this driver.
* pci_ioremap_bar() ensures that the memory is marked uncachable.
*/
ipc_pcie->ipc_regs = pci_ioremap_bar(pci, ipc_pcie->ipc_regs_bar_nr);
if (!ipc_pcie->ipc_regs) {
dev_err(ipc_pcie->dev, "IPC REGS ioremap error");
ret = -EBUSY;
goto ipc_regs_remap_fail;
}
/* Reserve the MMIO scratchpad memory resources.
* Remap the memory into CPU space. Arrange for the physical address
* (BAR) to be visible from this driver.
* pci_ioremap_bar() ensures that the memory is marked uncachable.
*/
ipc_pcie->scratchpad =
pci_ioremap_bar(pci, ipc_pcie->scratchpad_bar_nr);
if (!ipc_pcie->scratchpad) {
dev_err(ipc_pcie->dev, "doorbell scratchpad ioremap error");
ret = -EBUSY;
goto scratch_remap_fail;
}
/* Install the irq handler triggered by CP. */
ret = ipc_acquire_irq(ipc_pcie);
if (ret) {
dev_err(ipc_pcie->dev, "acquiring MSI irq failed!");
goto irq_acquire_fail;
}
/* Enable bus-mastering for the IOSM IPC device. */
pci_set_master(pci);
/* Enable LTR if possible
* This is needed for L1.2!
*/
pcie_capability_read_dword(ipc_pcie->pci, PCI_EXP_DEVCAP2, &cap);
if (cap & PCI_EXP_DEVCAP2_LTR)
pcie_capability_set_word(ipc_pcie->pci, PCI_EXP_DEVCTL2,
PCI_EXP_DEVCTL2_LTR_EN);
dev_dbg(ipc_pcie->dev, "link between AP and CP is fully on");
return ret;
irq_acquire_fail:
iounmap(ipc_pcie->scratchpad);
scratch_remap_fail:
iounmap(ipc_pcie->ipc_regs);
ipc_regs_remap_fail:
pci_release_regions(pci);
pci_request_region_fail:
return ret;
}
bool ipc_pcie_check_aspm_enabled(struct iosm_pcie *ipc_pcie,
bool parent)
{
struct pci_dev *pdev;
u16 value = 0;
u32 enabled;
if (parent)
pdev = ipc_pcie->pci->bus->self;
else
pdev = ipc_pcie->pci;
pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &value);
enabled = value & PCI_EXP_LNKCTL_ASPMC;
dev_dbg(ipc_pcie->dev, "ASPM L1: 0x%04X 0x%03X", pdev->device, value);
return (enabled == PCI_EXP_LNKCTL_ASPM_L1 ||
enabled == PCI_EXP_LNKCTL_ASPMC);
}
bool ipc_pcie_check_data_link_active(struct iosm_pcie *ipc_pcie)
{
struct pci_dev *parent;
u16 link_status = 0;
if (!ipc_pcie->pci->bus || !ipc_pcie->pci->bus->self) {
dev_err(ipc_pcie->dev, "root port not found");
return false;
}
parent = ipc_pcie->pci->bus->self;
pcie_capability_read_word(parent, PCI_EXP_LNKSTA, &link_status);
dev_dbg(ipc_pcie->dev, "Link status: 0x%04X", link_status);
return link_status & PCI_EXP_LNKSTA_DLLLA;
}
static bool ipc_pcie_check_aspm_supported(struct iosm_pcie *ipc_pcie,
bool parent)
{
struct pci_dev *pdev;
u32 support;
u32 cap = 0;
if (parent)
pdev = ipc_pcie->pci->bus->self;
else
pdev = ipc_pcie->pci;
pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &cap);
support = u32_get_bits(cap, PCI_EXP_LNKCAP_ASPMS);
if (support < PCI_EXP_LNKCTL_ASPM_L1) {
dev_dbg(ipc_pcie->dev, "ASPM L1 not supported: 0x%04X",
pdev->device);
return false;
}
return true;
}
void ipc_pcie_config_aspm(struct iosm_pcie *ipc_pcie)
{
bool parent_aspm_enabled, dev_aspm_enabled;
/* check if both root port and child supports ASPM L1 */
if (!ipc_pcie_check_aspm_supported(ipc_pcie, true) ||
!ipc_pcie_check_aspm_supported(ipc_pcie, false))
return;
parent_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, true);
dev_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, false);
dev_dbg(ipc_pcie->dev, "ASPM parent: %s device: %s",
parent_aspm_enabled ? "Enabled" : "Disabled",
dev_aspm_enabled ? "Enabled" : "Disabled");
}
/* Initializes PCIe endpoint configuration */
static void ipc_pcie_config_init(struct iosm_pcie *ipc_pcie)
{
/* BAR0 is used for doorbell */
ipc_pcie->ipc_regs_bar_nr = IPC_DOORBELL_BAR0;
/* update HW configuration */
ipc_pcie->scratchpad_bar_nr = IPC_SCRATCHPAD_BAR2;
ipc_pcie->doorbell_reg_offset = IPC_DOORBELL_CH_OFFSET;
ipc_pcie->doorbell_write = IPC_WRITE_PTR_REG_0;
ipc_pcie->doorbell_capture = IPC_CAPTURE_PTR_REG_0;
}
/* This will read the BIOS WWAN RTD3 settings:
* D0L1.2/D3L2/Disabled
*/
static enum ipc_pcie_sleep_state ipc_pcie_read_bios_cfg(struct device *dev)
{
enum ipc_pcie_sleep_state sleep_state = IPC_PCIE_D0L12;
union acpi_object *object;
acpi_handle handle_acpi;
handle_acpi = ACPI_HANDLE(dev);
if (!handle_acpi) {
pr_debug("pci device is NOT ACPI supporting device\n");
goto default_ret;
}
object = acpi_evaluate_dsm(handle_acpi, &wwan_acpi_guid, 0, 3, NULL);
if (!object)
goto default_ret;
if (object->integer.value == 3)
sleep_state = IPC_PCIE_D3L2;
ACPI_FREE(object);
default_ret:
return sleep_state;
}
static int ipc_pcie_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
struct iosm_pcie *ipc_pcie = kzalloc_obj(*ipc_pcie);
int ret;
pr_debug("Probing device 0x%X from the vendor 0x%X", pci_id->device,
pci_id->vendor);
if (!ipc_pcie)
goto ret_fail;
/* Initialize ipc dbg component for the PCIe device */
ipc_pcie->dev = &pci->dev;
/* Set the driver specific data. */
pci_set_drvdata(pci, ipc_pcie);
/* Save the address of the PCI device configuration. */
ipc_pcie->pci = pci;
/* Update platform configuration */
ipc_pcie_config_init(ipc_pcie);
/* Initialize the device before it is used. Ask low-level code
* to enable I/O and memory. Wake up the device if it was suspended.
*/
if (pci_enable_device(pci)) {
dev_err(ipc_pcie->dev, "failed to enable the AP PCIe device");
/* If enable of PCIe device has failed then calling
* ipc_pcie_cleanup will panic the system. More over
* ipc_pcie_cleanup() is required to be called after
* ipc_imem_mount()
*/
goto pci_enable_fail;
}
ret = dma_set_mask(ipc_pcie->dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(ipc_pcie->dev, "Could not set PCI DMA mask: %d", ret);
goto set_mask_fail;
}
ipc_pcie_config_aspm(ipc_pcie);
dev_dbg(ipc_pcie->dev, "PCIe device enabled.");
/* Read WWAN RTD3 BIOS Setting
*/
ipc_pcie->d3l2_support = ipc_pcie_read_bios_cfg(&pci->dev);
ipc_pcie->suspend = 0;
if (ipc_pcie_resources_request(ipc_pcie))
goto resources_req_fail;
/* Establish the link to the imem layer. */
ipc_pcie->imem = ipc_imem_init(ipc_pcie, pci->device,
ipc_pcie->scratchpad, ipc_pcie->dev);
if (!ipc_pcie->imem) {
dev_err(ipc_pcie->dev, "failed to init imem");
goto imem_init_fail;
}
return 0;
imem_init_fail:
ipc_pcie_resources_release(ipc_pcie);
resources_req_fail:
set_mask_fail:
pci_disable_device(pci);
pci_enable_fail:
kfree(ipc_pcie);
ret_fail:
return -EIO;
}
static const struct pci_device_id iosm_ipc_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7560_ID) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7360_ID) },
{}
};
MODULE_DEVICE_TABLE(pci, iosm_ipc_ids);
/* Enter sleep in s2idle case
*/
static int __maybe_unused ipc_pcie_suspend_s2idle(struct iosm_pcie *ipc_pcie)
{
ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_SLEEP);
/* Complete all memory stores before setting bit */
smp_mb__before_atomic();
set_bit(0, &ipc_pcie->suspend);
/* Complete all memory stores after setting bit */
smp_mb__after_atomic();
ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, true);
return 0;
}
/* Resume from sleep in s2idle case
*/
static int __maybe_unused ipc_pcie_resume_s2idle(struct iosm_pcie *ipc_pcie)
{
ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_ACTIVE);
ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, false);
/* Complete all memory stores before clearing bit. */
smp_mb__before_atomic();
clear_bit(0, &ipc_pcie->suspend);
/* Complete all memory stores after clearing bit. */
smp_mb__after_atomic();
return 0;
}
int __maybe_unused ipc_pcie_suspend(struct iosm_pcie *ipc_pcie)
{
/* The HAL shall ask the shared memory layer whether D3 is allowed. */
ipc_imem_pm_suspend(ipc_pcie->imem);
dev_dbg(ipc_pcie->dev, "SUSPEND done");
return 0;
}
int __maybe_unused ipc_pcie_resume(struct iosm_pcie *ipc_pcie)
{
/* The HAL shall inform the shared memory layer that the device is
* active.
*/
ipc_imem_pm_resume(ipc_pcie->imem);
dev_dbg(ipc_pcie->dev, "RESUME done");
return 0;
}
static int __maybe_unused ipc_pcie_suspend_cb(struct device *dev)
{
struct iosm_pcie *ipc_pcie;
struct pci_dev *pdev;
pdev = to_pci_dev(dev);
ipc_pcie = pci_get_drvdata(pdev);
switch (ipc_pcie->d3l2_support) {
case IPC_PCIE_D0L12:
ipc_pcie_suspend_s2idle(ipc_pcie);
break;
case IPC_PCIE_D3L2:
ipc_pcie_suspend(ipc_pcie);
break;
}
return 0;
}
static int __maybe_unused ipc_pcie_resume_cb(struct device *dev)
{
struct iosm_pcie *ipc_pcie;
struct pci_dev *pdev;
pdev = to_pci_dev(dev);
ipc_pcie = pci_get_drvdata(pdev);
switch (ipc_pcie->d3l2_support) {
case IPC_PCIE_D0L12:
ipc_pcie_resume_s2idle(ipc_pcie);
break;
case IPC_PCIE_D3L2:
ipc_pcie_resume(ipc_pcie);
break;
}
return 0;
}
static SIMPLE_DEV_PM_OPS(iosm_ipc_pm, ipc_pcie_suspend_cb, ipc_pcie_resume_cb);
static struct pci_driver iosm_ipc_driver = {
.name = KBUILD_MODNAME,
.probe = ipc_pcie_probe,
.remove = ipc_pcie_remove,
.driver = {
.pm = &iosm_ipc_pm,
},
.id_table = iosm_ipc_ids,
};
int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data,
size_t size, dma_addr_t *mapping, int direction)
{
if (ipc_pcie->pci) {
*mapping = dma_map_single(&ipc_pcie->pci->dev, data, size,
direction);
if (dma_mapping_error(&ipc_pcie->pci->dev, *mapping)) {
dev_err(ipc_pcie->dev, "dma mapping failed");
return -EINVAL;
}
}
return 0;
}
void ipc_pcie_addr_unmap(struct iosm_pcie *ipc_pcie, size_t size,
dma_addr_t mapping, int direction)
{
if (!mapping)
return;
if (ipc_pcie->pci)
dma_unmap_single(&ipc_pcie->pci->dev, mapping, size, direction);
}
struct sk_buff *ipc_pcie_alloc_local_skb(struct iosm_pcie *ipc_pcie,
gfp_t flags, size_t size)
{
struct sk_buff *skb;
if (!ipc_pcie || !size) {
pr_err("invalid pcie object or size");
return NULL;
}
skb = __netdev_alloc_skb(NULL, size, flags);
if (!skb)
return NULL;
IPC_CB(skb)->op_type = (u8)UL_DEFAULT;
IPC_CB(skb)->mapping = 0;
return skb;
}
struct sk_buff *ipc_pcie_alloc_skb(struct iosm_pcie *ipc_pcie, size_t size,
gfp_t flags, dma_addr_t *mapping,
int direction, size_t headroom)
{
struct sk_buff *skb = ipc_pcie_alloc_local_skb(ipc_pcie, flags,
size + headroom);
if (!skb)
return NULL;
if (headroom)
skb_reserve(skb, headroom);
if (ipc_pcie_addr_map(ipc_pcie, skb->data, size, mapping, direction)) {
dev_kfree_skb(skb);
return NULL;
}
BUILD_BUG_ON(sizeof(*IPC_CB(skb)) > sizeof(skb->cb));
/* Store the mapping address in skb scratch pad for later usage */
IPC_CB(skb)->mapping = *mapping;
IPC_CB(skb)->direction = direction;
IPC_CB(skb)->len = size;
return skb;
}
void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb)
{
if (!skb)
return;
ipc_pcie_addr_unmap(ipc_pcie, IPC_CB(skb)->len, IPC_CB(skb)->mapping,
IPC_CB(skb)->direction);
IPC_CB(skb)->mapping = 0;
dev_kfree_skb(skb);
}
static int pm_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
{
if (mode == PM_HIBERNATION_PREPARE || mode == PM_RESTORE_PREPARE) {
if (pci_registered) {
pci_unregister_driver(&iosm_ipc_driver);
pci_registered = false;
}
} else if (mode == PM_POST_HIBERNATION || mode == PM_POST_RESTORE) {
if (!pci_registered) {
int ret;
ret = pci_register_driver(&iosm_ipc_driver);
if (ret) {
pr_err(KBUILD_MODNAME ": unable to re-register PCI driver: %d\n",
ret);
} else {
pci_registered = true;
}
}
}
return 0;
}
static struct notifier_block pm_notifier = {
.notifier_call = pm_notify,
};
static int __init iosm_ipc_driver_init(void)
{
int ret;
ret = pci_register_driver(&iosm_ipc_driver);
if (ret)
return ret;
pci_registered = true;
register_pm_notifier(&pm_notifier);
return 0;
}
module_init(iosm_ipc_driver_init);
static void __exit iosm_ipc_driver_exit(void)
{
unregister_pm_notifier(&pm_notifier);
if (pci_registered)
pci_unregister_driver(&iosm_ipc_driver);
}
module_exit(iosm_ipc_driver_exit);
@@ -0,0 +1,210 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_PCIE_H
#define IOSM_IPC_PCIE_H
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/skbuff.h>
#include "iosm_ipc_irq.h"
/* Device ID */
#define INTEL_CP_DEVICE_7560_ID 0x7560
#define INTEL_CP_DEVICE_7360_ID 0x7360
/* Define for BAR area usage */
#define IPC_DOORBELL_BAR0 0
#define IPC_SCRATCHPAD_BAR2 2
/* Defines for DOORBELL registers information */
#define IPC_DOORBELL_CH_OFFSET BIT(5)
#define IPC_WRITE_PTR_REG_0 BIT(4)
#define IPC_CAPTURE_PTR_REG_0 BIT(3)
/* Number of MSI used for IPC */
#define IPC_MSI_VECTORS 1
/* Total number of Maximum IPC IRQ vectors used for IPC */
#define IPC_IRQ_VECTORS IPC_MSI_VECTORS
/**
* enum ipc_pcie_sleep_state - Enum type to different sleep state transitions
* @IPC_PCIE_D0L12: Put the sleep state in D0L12
* @IPC_PCIE_D3L2: Put the sleep state in D3L2
*/
enum ipc_pcie_sleep_state {
IPC_PCIE_D0L12,
IPC_PCIE_D3L2,
};
/**
* struct iosm_pcie - IPC_PCIE struct.
* @pci: Address of the device description
* @dev: Pointer to generic device structure
* @ipc_regs: Remapped CP doorbell address of the irq register
* set, to fire the doorbell irq.
* @scratchpad: Remapped CP scratchpad address, to send the
* configuration. tuple and the IPC descriptors
* to CP in the ROM phase. The config tuple
* information are saved on the MSI scratchpad.
* @imem: Pointer to imem data struct
* @ipc_regs_bar_nr: BAR number to be used for IPC doorbell
* @scratchpad_bar_nr: BAR number to be used for Scratchpad
* @nvec: number of requested irq vectors
* @doorbell_reg_offset: doorbell_reg_offset
* @doorbell_write: doorbell write register
* @doorbell_capture: doorbell capture resgister
* @suspend: S2IDLE sleep/active
* @d3l2_support: Read WWAN RTD3 BIOS setting for D3L2 support
*/
struct iosm_pcie {
struct pci_dev *pci;
struct device *dev;
void __iomem *ipc_regs;
void __iomem *scratchpad;
struct iosm_imem *imem;
int ipc_regs_bar_nr;
int scratchpad_bar_nr;
int nvec;
u32 doorbell_reg_offset;
u32 doorbell_write;
u32 doorbell_capture;
unsigned long suspend;
enum ipc_pcie_sleep_state d3l2_support;
};
/**
* struct ipc_skb_cb - Struct definition of the socket buffer which is mapped to
* the cb field of sbk
* @mapping: Store physical or IOVA mapped address of skb virtual add.
* @direction: DMA direction
* @len: Length of the DMA mapped region
* @op_type: Expected values are defined about enum ipc_ul_usr_op.
*/
struct ipc_skb_cb {
dma_addr_t mapping;
int direction;
int len;
u8 op_type;
};
/**
* enum ipc_ul_usr_op - Control operation to execute the right action on
* the user interface.
* @UL_USR_OP_BLOCKED: The uplink app was blocked until CP confirms that the
* uplink buffer was consumed triggered by the IRQ.
* @UL_MUX_OP_ADB: In MUX mode the UL ADB shall be addedd to the free list.
* @UL_DEFAULT: SKB in non muxing mode
*/
enum ipc_ul_usr_op {
UL_USR_OP_BLOCKED,
UL_MUX_OP_ADB,
UL_DEFAULT,
};
/**
* ipc_pcie_addr_map - Maps the kernel's virtual address to either IOVA
* address space or Physical address space, the mapping is
* stored in the skb's cb.
* @ipc_pcie: Pointer to struct iosm_pcie
* @data: Skb mem containing data
* @size: Data size
* @mapping: Dma mapping address
* @direction: Data direction
*
* Returns: 0 on success and failure value on error
*/
int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data,
size_t size, dma_addr_t *mapping, int direction);
/**
* ipc_pcie_addr_unmap - Unmaps the skb memory region from IOVA address space
* @ipc_pcie: Pointer to struct iosm_pcie
* @size: Data size
* @mapping: Dma mapping address
* @direction: Data direction
*/
void ipc_pcie_addr_unmap(struct iosm_pcie *ipc_pcie, size_t size,
dma_addr_t mapping, int direction);
/**
* ipc_pcie_alloc_skb - Allocate an uplink SKB for the given size.
* @ipc_pcie: Pointer to struct iosm_pcie
* @size: Size of the SKB required.
* @flags: Allocation flags
* @mapping: Copies either mapped IOVA add. or converted Phy address
* @direction: DMA data direction
* @headroom: Header data offset
*
* Returns: Pointer to ipc_skb on Success, NULL on failure.
*/
struct sk_buff *ipc_pcie_alloc_skb(struct iosm_pcie *ipc_pcie, size_t size,
gfp_t flags, dma_addr_t *mapping,
int direction, size_t headroom);
/**
* ipc_pcie_alloc_local_skb - Allocate a local SKB for the given size.
* @ipc_pcie: Pointer to struct iosm_pcie
* @flags: Allocation flags
* @size: Size of the SKB required.
*
* Returns: Pointer to ipc_skb on Success, NULL on failure.
*/
struct sk_buff *ipc_pcie_alloc_local_skb(struct iosm_pcie *ipc_pcie,
gfp_t flags, size_t size);
/**
* ipc_pcie_kfree_skb - Free skb allocated by ipc_pcie_alloc_*_skb().
* @ipc_pcie: Pointer to struct iosm_pcie
* @skb: Pointer to the skb
*/
void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb);
/**
* ipc_pcie_check_data_link_active - Check Data Link Layer Active
* @ipc_pcie: Pointer to struct iosm_pcie
*
* Returns: true if active, otherwise false
*/
bool ipc_pcie_check_data_link_active(struct iosm_pcie *ipc_pcie);
/**
* ipc_pcie_suspend - Callback invoked by pm_runtime_suspend. It decrements
* the device's usage count then, carry out a suspend,
* either synchronous or asynchronous.
* @ipc_pcie: Pointer to struct iosm_pcie
*
* Returns: 0 on success and failure value on error
*/
int ipc_pcie_suspend(struct iosm_pcie *ipc_pcie);
/**
* ipc_pcie_resume - Callback invoked by pm_runtime_resume. It increments
* the device's usage count then, carry out a resume,
* either synchronous or asynchronous.
* @ipc_pcie: Pointer to struct iosm_pcie
*
* Returns: 0 on success and failure value on error
*/
int ipc_pcie_resume(struct iosm_pcie *ipc_pcie);
/**
* ipc_pcie_check_aspm_enabled - Check if ASPM L1 is already enabled
* @ipc_pcie: Pointer to struct iosm_pcie
* @parent: True if checking ASPM L1 for parent else false
*
* Returns: true if ASPM is already enabled else false
*/
bool ipc_pcie_check_aspm_enabled(struct iosm_pcie *ipc_pcie,
bool parent);
/**
* ipc_pcie_config_aspm - Configure ASPM L1
* @ipc_pcie: Pointer to struct iosm_pcie
*/
void ipc_pcie_config_aspm(struct iosm_pcie *ipc_pcie);
#endif
@@ -0,0 +1,333 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_protocol.h"
/* Timeout value in MS for the PM to wait for device to reach active state */
#define IPC_PM_ACTIVE_TIMEOUT_MS (500)
/* Note that here "active" has the value 1, as compared to the enums
* ipc_mem_host_pm_state or ipc_mem_dev_pm_state, where "active" is 0
*/
#define IPC_PM_SLEEP (0)
#define CONSUME_STATE (0)
#define IPC_PM_ACTIVE (1)
void ipc_pm_signal_hpda_doorbell(struct iosm_pm *ipc_pm, u32 identifier,
bool host_slp_check)
{
if (host_slp_check && ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE &&
ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE_WAIT) {
ipc_pm->pending_hpda_update = true;
dev_dbg(ipc_pm->dev,
"Pend HPDA update set. Host PM_State: %d identifier:%d",
ipc_pm->host_pm_state, identifier);
return;
}
if (!ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, true)) {
ipc_pm->pending_hpda_update = true;
dev_dbg(ipc_pm->dev, "Pending HPDA update set. identifier:%d",
identifier);
return;
}
ipc_pm->pending_hpda_update = false;
/* Trigger the irq towards CP */
ipc_cp_irq_hpda_update(ipc_pm->pcie, identifier);
ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, false);
}
/* Wake up the device if it is in low power mode. */
static bool ipc_pm_link_activate(struct iosm_pm *ipc_pm)
{
if (ipc_pm->cp_state == IPC_MEM_DEV_PM_ACTIVE)
return true;
if (ipc_pm->cp_state == IPC_MEM_DEV_PM_SLEEP) {
if (ipc_pm->ap_state == IPC_MEM_DEV_PM_SLEEP) {
/* Wake up the device. */
ipc_cp_irq_sleep_control(ipc_pm->pcie,
IPC_MEM_DEV_PM_WAKEUP);
ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE_WAIT;
goto not_active;
}
if (ipc_pm->ap_state == IPC_MEM_DEV_PM_ACTIVE_WAIT)
goto not_active;
return true;
}
not_active:
/* link is not ready */
return false;
}
bool ipc_pm_wait_for_device_active(struct iosm_pm *ipc_pm)
{
bool ret_val = false;
if (ipc_pm->ap_state != IPC_MEM_DEV_PM_ACTIVE) {
/* Complete all memory stores before setting bit */
smp_mb__before_atomic();
/* Wait for IPC_PM_ACTIVE_TIMEOUT_MS for Device sleep state
* machine to enter ACTIVE state.
*/
set_bit(0, &ipc_pm->host_sleep_pend);
/* Complete all memory stores after setting bit */
smp_mb__after_atomic();
if (!wait_for_completion_interruptible_timeout
(&ipc_pm->host_sleep_complete,
msecs_to_jiffies(IPC_PM_ACTIVE_TIMEOUT_MS))) {
dev_err(ipc_pm->dev,
"PM timeout. Expected State:%d. Actual: %d",
IPC_MEM_DEV_PM_ACTIVE, ipc_pm->ap_state);
goto active_timeout;
}
}
ret_val = true;
active_timeout:
/* Complete all memory stores before clearing bit */
smp_mb__before_atomic();
/* Reset the atomic variable in any case as device sleep
* state machine change is no longer of interest.
*/
clear_bit(0, &ipc_pm->host_sleep_pend);
/* Complete all memory stores after clearing bit */
smp_mb__after_atomic();
return ret_val;
}
static void ipc_pm_on_link_sleep(struct iosm_pm *ipc_pm)
{
/* pending sleep ack and all conditions are cleared
* -> signal SLEEP__ACK to CP
*/
ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;
ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_SLEEP);
}
static void ipc_pm_on_link_wake(struct iosm_pm *ipc_pm, bool ack)
{
ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
if (ack) {
ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_ACTIVE);
/* check the consume state !!! */
if (test_bit(CONSUME_STATE, &ipc_pm->host_sleep_pend))
complete(&ipc_pm->host_sleep_complete);
}
/* Check for pending HPDA update.
* Pending HP update could be because of sending message was
* put on hold due to Device sleep state or due to TD update
* which could be because of Device Sleep and Host Sleep
* states.
*/
if (ipc_pm->pending_hpda_update &&
ipc_pm->host_pm_state == IPC_MEM_HOST_PM_ACTIVE)
ipc_pm_signal_hpda_doorbell(ipc_pm, IPC_HP_PM_TRIGGER, true);
}
bool ipc_pm_trigger(struct iosm_pm *ipc_pm, enum ipc_pm_unit unit, bool active)
{
union ipc_pm_cond old_cond;
union ipc_pm_cond new_cond;
bool link_active;
/* Save the current D3 state. */
new_cond = ipc_pm->pm_cond;
old_cond = ipc_pm->pm_cond;
/* Calculate the power state only in the runtime phase. */
switch (unit) {
case IPC_PM_UNIT_IRQ: /* CP irq */
new_cond.irq = active;
break;
case IPC_PM_UNIT_LINK: /* Device link state. */
new_cond.link = active;
break;
case IPC_PM_UNIT_HS: /* Host sleep trigger requires Link. */
new_cond.hs = active;
break;
default:
break;
}
/* Something changed ? */
if (old_cond.raw == new_cond.raw) {
/* Stay in the current PM state. */
link_active = old_cond.link == IPC_PM_ACTIVE;
goto ret;
}
ipc_pm->pm_cond = new_cond;
if (new_cond.link)
ipc_pm_on_link_wake(ipc_pm, unit == IPC_PM_UNIT_LINK);
else if (unit == IPC_PM_UNIT_LINK)
ipc_pm_on_link_sleep(ipc_pm);
if (old_cond.link == IPC_PM_SLEEP && new_cond.raw) {
link_active = ipc_pm_link_activate(ipc_pm);
goto ret;
}
link_active = old_cond.link == IPC_PM_ACTIVE;
ret:
return link_active;
}
bool ipc_pm_prepare_host_sleep(struct iosm_pm *ipc_pm)
{
/* suspend not allowed if host_pm_state is not IPC_MEM_HOST_PM_ACTIVE */
if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE) {
dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
ipc_pm->host_pm_state, IPC_MEM_HOST_PM_ACTIVE);
return false;
}
ipc_pm->host_pm_state = IPC_MEM_HOST_PM_SLEEP_WAIT_D3;
return true;
}
bool ipc_pm_prepare_host_active(struct iosm_pm *ipc_pm)
{
if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_SLEEP) {
dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
ipc_pm->host_pm_state, IPC_MEM_HOST_PM_SLEEP);
return false;
}
/* Sending Sleep Exit message to CP. Update the state */
ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE_WAIT;
return true;
}
void ipc_pm_set_s2idle_sleep(struct iosm_pm *ipc_pm, bool sleep)
{
if (sleep) {
ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;
ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_SLEEP;
} else {
ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_ACTIVE;
ipc_pm->pm_cond.link = IPC_PM_ACTIVE;
}
}
bool ipc_pm_dev_slp_notification(struct iosm_pm *ipc_pm, u32 cp_pm_req)
{
if (cp_pm_req == ipc_pm->device_sleep_notification)
return false;
ipc_pm->device_sleep_notification = cp_pm_req;
/* Evaluate the PM request. */
switch (ipc_pm->cp_state) {
case IPC_MEM_DEV_PM_ACTIVE:
switch (cp_pm_req) {
case IPC_MEM_DEV_PM_ACTIVE:
break;
case IPC_MEM_DEV_PM_SLEEP:
/* Inform the PM that the device link can go down. */
ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, false);
return true;
default:
dev_err(ipc_pm->dev,
"loc-pm=%d active: confused req-pm=%d",
ipc_pm->cp_state, cp_pm_req);
break;
}
break;
case IPC_MEM_DEV_PM_SLEEP:
switch (cp_pm_req) {
case IPC_MEM_DEV_PM_ACTIVE:
/* Inform the PM that the device link is active. */
ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, true);
break;
case IPC_MEM_DEV_PM_SLEEP:
break;
default:
dev_err(ipc_pm->dev,
"loc-pm=%d sleep: confused req-pm=%d",
ipc_pm->cp_state, cp_pm_req);
break;
}
break;
default:
dev_err(ipc_pm->dev, "confused loc-pm=%d, req-pm=%d",
ipc_pm->cp_state, cp_pm_req);
break;
}
return false;
}
void ipc_pm_init(struct iosm_protocol *ipc_protocol)
{
struct iosm_imem *ipc_imem = ipc_protocol->imem;
struct iosm_pm *ipc_pm = &ipc_protocol->pm;
ipc_pm->pcie = ipc_imem->pcie;
ipc_pm->dev = ipc_imem->dev;
ipc_pm->pm_cond.irq = IPC_PM_SLEEP;
ipc_pm->pm_cond.hs = IPC_PM_SLEEP;
ipc_pm->pm_cond.link = IPC_PM_ACTIVE;
ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE;
/* Create generic wait-for-completion handler for Host Sleep
* and device sleep coordination.
*/
init_completion(&ipc_pm->host_sleep_complete);
/* Complete all memory stores before clearing bit */
smp_mb__before_atomic();
clear_bit(0, &ipc_pm->host_sleep_pend);
/* Complete all memory stores after clearing bit */
smp_mb__after_atomic();
}
void ipc_pm_deinit(struct iosm_protocol *proto)
{
struct iosm_pm *ipc_pm = &proto->pm;
complete(&ipc_pm->host_sleep_complete);
}
@@ -0,0 +1,207 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_PM_H
#define IOSM_IPC_PM_H
/* Trigger the doorbell interrupt on cp to change the PM sleep/active status */
#define ipc_cp_irq_sleep_control(ipc_pcie, data) \
ipc_doorbell_fire(ipc_pcie, IPC_DOORBELL_IRQ_SLEEP, data)
/* Trigger the doorbell interrupt on CP to do hpda update */
#define ipc_cp_irq_hpda_update(ipc_pcie, data) \
ipc_doorbell_fire(ipc_pcie, IPC_DOORBELL_IRQ_HPDA, 0xFF & (data))
/**
* union ipc_pm_cond - Conditions for D3 and the sleep message to CP.
* @raw: raw/combined value for faster check
* @irq: IRQ towards CP
* @hs: Host Sleep
* @link: Device link state.
*/
union ipc_pm_cond {
unsigned int raw;
struct {
unsigned int irq:1,
hs:1,
link:1;
};
};
/**
* enum ipc_mem_host_pm_state - Possible states of the HOST SLEEP finite state
* machine.
* @IPC_MEM_HOST_PM_ACTIVE: Host is active
* @IPC_MEM_HOST_PM_ACTIVE_WAIT: Intermediate state before going to
* active
* @IPC_MEM_HOST_PM_SLEEP_WAIT_IDLE: Intermediate state to wait for idle
* before going into sleep
* @IPC_MEM_HOST_PM_SLEEP_WAIT_D3: Intermediate state to wait for D3
* before going to sleep
* @IPC_MEM_HOST_PM_SLEEP: after this state the interface is not
* accessible host is in suspend to RAM
* @IPC_MEM_HOST_PM_SLEEP_WAIT_EXIT_SLEEP: Intermediate state before exiting
* sleep
*/
enum ipc_mem_host_pm_state {
IPC_MEM_HOST_PM_ACTIVE,
IPC_MEM_HOST_PM_ACTIVE_WAIT,
IPC_MEM_HOST_PM_SLEEP_WAIT_IDLE,
IPC_MEM_HOST_PM_SLEEP_WAIT_D3,
IPC_MEM_HOST_PM_SLEEP,
IPC_MEM_HOST_PM_SLEEP_WAIT_EXIT_SLEEP,
};
/**
* enum ipc_mem_dev_pm_state - Possible states of the DEVICE SLEEP finite state
* machine.
* @IPC_MEM_DEV_PM_ACTIVE: IPC_MEM_DEV_PM_ACTIVE is the initial
* power management state.
* IRQ(struct ipc_mem_device_info:
* device_sleep_notification)
* and DOORBELL-IRQ-HPDA(data) values.
* @IPC_MEM_DEV_PM_SLEEP: IPC_MEM_DEV_PM_SLEEP is PM state for
* sleep.
* @IPC_MEM_DEV_PM_WAKEUP: DOORBELL-IRQ-DEVICE_WAKE(data).
* @IPC_MEM_DEV_PM_HOST_SLEEP: DOORBELL-IRQ-HOST_SLEEP(data).
* @IPC_MEM_DEV_PM_ACTIVE_WAIT: Local intermediate states.
* @IPC_MEM_DEV_PM_FORCE_SLEEP: DOORBELL-IRQ-FORCE_SLEEP.
* @IPC_MEM_DEV_PM_FORCE_ACTIVE: DOORBELL-IRQ-FORCE_ACTIVE.
*/
enum ipc_mem_dev_pm_state {
IPC_MEM_DEV_PM_ACTIVE,
IPC_MEM_DEV_PM_SLEEP,
IPC_MEM_DEV_PM_WAKEUP,
IPC_MEM_DEV_PM_HOST_SLEEP,
IPC_MEM_DEV_PM_ACTIVE_WAIT,
IPC_MEM_DEV_PM_FORCE_SLEEP = 7,
IPC_MEM_DEV_PM_FORCE_ACTIVE,
};
/**
* struct iosm_pm - Power management instance
* @pcie: Pointer to iosm_pcie structure
* @dev: Pointer to device structure
* @host_pm_state: PM states for host
* @host_sleep_pend: Variable to indicate Host Sleep Pending
* @host_sleep_complete: Generic wait-for-completion used in
* case of Host Sleep
* @pm_cond: Conditions for power management
* @ap_state: Current power management state, the
* initial state is IPC_MEM_DEV_PM_ACTIVE eq. 0.
* @cp_state: PM State of CP
* @device_sleep_notification: last handled device_sleep_notfication
* @pending_hpda_update: is a HPDA update pending?
*/
struct iosm_pm {
struct iosm_pcie *pcie;
struct device *dev;
enum ipc_mem_host_pm_state host_pm_state;
unsigned long host_sleep_pend;
struct completion host_sleep_complete;
union ipc_pm_cond pm_cond;
enum ipc_mem_dev_pm_state ap_state;
enum ipc_mem_dev_pm_state cp_state;
u32 device_sleep_notification;
u8 pending_hpda_update:1;
};
/**
* enum ipc_pm_unit - Power management units.
* @IPC_PM_UNIT_IRQ: IRQ towards CP
* @IPC_PM_UNIT_HS: Host Sleep for converged protocol
* @IPC_PM_UNIT_LINK: Link state controlled by CP.
*/
enum ipc_pm_unit {
IPC_PM_UNIT_IRQ,
IPC_PM_UNIT_HS,
IPC_PM_UNIT_LINK,
};
/**
* ipc_pm_init - Allocate power management component
* @ipc_protocol: Pointer to iosm_protocol structure
*/
void ipc_pm_init(struct iosm_protocol *ipc_protocol);
/**
* ipc_pm_deinit - Free power management component, invalidating its pointer.
* @ipc_protocol: Pointer to iosm_protocol structure
*/
void ipc_pm_deinit(struct iosm_protocol *ipc_protocol);
/**
* ipc_pm_dev_slp_notification - Handle a sleep notification message from the
* device. This can be called from interrupt state
* This function handles Host Sleep requests too
* if the Host Sleep protocol is register based.
* @ipc_pm: Pointer to power management component
* @sleep_notification: Actual notification from device
*
* Returns: true if dev sleep state has to be checked, false otherwise.
*/
bool ipc_pm_dev_slp_notification(struct iosm_pm *ipc_pm,
u32 sleep_notification);
/**
* ipc_pm_set_s2idle_sleep - Set PM variables to sleep/active
* @ipc_pm: Pointer to power management component
* @sleep: true to enter sleep/false to exit sleep
*/
void ipc_pm_set_s2idle_sleep(struct iosm_pm *ipc_pm, bool sleep);
/**
* ipc_pm_prepare_host_sleep - Prepare the PM for sleep by entering
* IPC_MEM_HOST_PM_SLEEP_WAIT_D3 state.
* @ipc_pm: Pointer to power management component
*
* Returns: true on success, false if the host was not active.
*/
bool ipc_pm_prepare_host_sleep(struct iosm_pm *ipc_pm);
/**
* ipc_pm_prepare_host_active - Prepare the PM for wakeup by entering
* IPC_MEM_HOST_PM_ACTIVE_WAIT state.
* @ipc_pm: Pointer to power management component
*
* Returns: true on success, false if the host was not sleeping.
*/
bool ipc_pm_prepare_host_active(struct iosm_pm *ipc_pm);
/**
* ipc_pm_wait_for_device_active - Wait up to IPC_PM_ACTIVE_TIMEOUT_MS ms
* for the device to reach active state
* @ipc_pm: Pointer to power management component
*
* Returns: true if device is active, false on timeout
*/
bool ipc_pm_wait_for_device_active(struct iosm_pm *ipc_pm);
/**
* ipc_pm_signal_hpda_doorbell - Wake up the device if it is in low power mode
* and trigger a head pointer update interrupt.
* @ipc_pm: Pointer to power management component
* @identifier: specifies what component triggered hpda update irq
* @host_slp_check: if set to true then Host Sleep state machine check will
* be performed. If Host Sleep state machine allows HP
* update then only doorbell is triggered otherwise pending
* flag will be set. If set to false then Host Sleep check
* will not be performed. This is helpful for Host Sleep
* negotiation through message ring.
*/
void ipc_pm_signal_hpda_doorbell(struct iosm_pm *ipc_pm, u32 identifier,
bool host_slp_check);
/**
* ipc_pm_trigger - Update power manager and wake up the link if needed
* @ipc_pm: Pointer to power management component
* @unit: Power management units
* @active: Device link state
*
* Returns: true if link is unchanged or active, false otherwise
*/
bool ipc_pm_trigger(struct iosm_pm *ipc_pm, enum ipc_pm_unit unit, bool active);
#endif
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_chnl_cfg.h"
#include "iosm_ipc_imem_ops.h"
#include "iosm_ipc_port.h"
/* open logical channel for control communication */
static int ipc_port_ctrl_start(struct wwan_port *port)
{
struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port);
int ret = 0;
ipc_port->channel = ipc_imem_sys_port_open(ipc_port->ipc_imem,
ipc_port->chl_id,
IPC_HP_CDEV_OPEN);
if (!ipc_port->channel)
ret = -EIO;
return ret;
}
/* close logical channel */
static void ipc_port_ctrl_stop(struct wwan_port *port)
{
struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port);
ipc_imem_sys_port_close(ipc_port->ipc_imem, ipc_port->channel);
}
/* transfer control data to modem */
static int ipc_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
{
struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port);
return ipc_imem_sys_cdev_write(ipc_port, skb);
}
static const struct wwan_port_ops ipc_wwan_ctrl_ops = {
.start = ipc_port_ctrl_start,
.stop = ipc_port_ctrl_stop,
.tx = ipc_port_ctrl_tx,
};
/* Port init func */
struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem,
struct ipc_chnl_cfg ipc_port_cfg)
{
struct iosm_cdev *ipc_port = kzalloc_obj(*ipc_port);
enum wwan_port_type port_type = ipc_port_cfg.wwan_port_type;
enum ipc_channel_id chl_id = ipc_port_cfg.id;
if (!ipc_port)
return NULL;
ipc_port->dev = ipc_imem->dev;
ipc_port->pcie = ipc_imem->pcie;
ipc_port->port_type = port_type;
ipc_port->chl_id = chl_id;
ipc_port->ipc_imem = ipc_imem;
ipc_port->iosm_port = wwan_create_port(ipc_port->dev, port_type,
&ipc_wwan_ctrl_ops, NULL,
ipc_port);
return ipc_port;
}
/* Port deinit func */
void ipc_port_deinit(struct iosm_cdev *port[])
{
struct iosm_cdev *ipc_port;
u8 ctrl_chl_nr;
for (ctrl_chl_nr = 0; ctrl_chl_nr < IPC_MEM_MAX_CHANNELS;
ctrl_chl_nr++) {
if (port[ctrl_chl_nr]) {
ipc_port = port[ctrl_chl_nr];
wwan_remove_port(ipc_port->iosm_port);
kfree(ipc_port);
}
}
}
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_PORT_H
#define IOSM_IPC_PORT_H
#include <linux/wwan.h>
#include "iosm_ipc_imem_ops.h"
/**
* struct iosm_cdev - State of the char driver layer.
* @iosm_port: Pointer of type wwan_port
* @ipc_imem: imem instance
* @dev: Pointer to device struct
* @pcie: PCIe component
* @port_type: WWAN port type
* @channel: Channel instance
* @chl_id: Channel Identifier
*/
struct iosm_cdev {
struct wwan_port *iosm_port;
struct iosm_imem *ipc_imem;
struct device *dev;
struct iosm_pcie *pcie;
enum wwan_port_type port_type;
struct ipc_mem_channel *channel;
enum ipc_channel_id chl_id;
};
/**
* ipc_port_init - Allocate IPC port & register to wwan subsystem for AT/MBIM
* communication.
* @ipc_imem: Pointer to iosm_imem structure
* @ipc_port_cfg: IPC Port Config
*
* Returns: 0 on success & NULL on failure
*/
struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem,
struct ipc_chnl_cfg ipc_port_cfg);
/**
* ipc_port_deinit - Free IPC port & unregister port with wwan subsystem.
* @ipc_port: Array of pointer to the ipc port data-struct
*/
void ipc_port_deinit(struct iosm_cdev *ipc_port[]);
#endif
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_imem.h"
#include "iosm_ipc_protocol.h"
#include "iosm_ipc_protocol_ops.h"
#include "iosm_ipc_pm.h"
#include "iosm_ipc_task_queue.h"
int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol,
enum ipc_msg_prep_type msg_type,
union ipc_msg_prep_args *prep_args,
struct ipc_rsp *response)
{
int index = ipc_protocol_msg_prep(ipc_protocol->imem, msg_type,
prep_args);
/* Store reference towards caller specified response in response ring
* and signal CP
*/
if (index >= 0 && index < IPC_MEM_MSG_ENTRIES) {
ipc_protocol->rsp_ring[index] = response;
ipc_protocol_msg_hp_update(ipc_protocol->imem);
}
return index;
}
/* Callback for message send */
static int ipc_protocol_tq_msg_send_cb(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size)
{
struct ipc_call_msg_send_args *send_args = msg;
struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;
return ipc_protocol_tq_msg_send(ipc_protocol, send_args->msg_type,
send_args->prep_args,
send_args->response);
}
/* Remove reference to a response. This is typically used when a requestor timed
* out and is no longer interested in the response.
*/
static int ipc_protocol_tq_msg_remove(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size)
{
struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;
ipc_protocol->rsp_ring[arg] = NULL;
return 0;
}
int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol,
enum ipc_msg_prep_type prep,
union ipc_msg_prep_args *prep_args)
{
struct ipc_call_msg_send_args send_args;
unsigned int exec_timeout;
struct ipc_rsp response;
int index;
exec_timeout = (ipc_protocol_get_ap_exec_stage(ipc_protocol) ==
IPC_MEM_EXEC_STAGE_RUN ?
IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT :
IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT);
/* Trap if called from non-preemptible context */
might_sleep();
response.status = IPC_MEM_MSG_CS_INVALID;
init_completion(&response.completion);
send_args.msg_type = prep;
send_args.prep_args = prep_args;
send_args.response = &response;
/* Allocate and prepare message to be sent in tasklet context.
* A positive index returned form tasklet_call references the message
* in case it needs to be cancelled when there is a timeout.
*/
index = ipc_task_queue_send_task(ipc_protocol->imem,
ipc_protocol_tq_msg_send_cb, 0,
&send_args, 0, true);
if (index < 0) {
dev_err(ipc_protocol->dev, "msg %d failed", prep);
return index;
}
/* Wait for the device to respond to the message */
switch (wait_for_completion_timeout(&response.completion,
msecs_to_jiffies(exec_timeout))) {
case 0:
/* Timeout, there was no response from the device.
* Remove the reference to the local response completion
* object as we are no longer interested in the response.
*/
ipc_task_queue_send_task(ipc_protocol->imem,
ipc_protocol_tq_msg_remove, index,
NULL, 0, true);
dev_err(ipc_protocol->dev, "msg timeout");
ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT);
break;
default:
/* We got a response in time; check completion status: */
if (response.status != IPC_MEM_MSG_CS_SUCCESS) {
dev_err(ipc_protocol->dev,
"msg completion status error %d",
response.status);
return -EIO;
}
}
return 0;
}
static int ipc_protocol_msg_send_host_sleep(struct iosm_protocol *ipc_protocol,
u32 state)
{
union ipc_msg_prep_args prep_args = {
.sleep.target = 0,
.sleep.state = state,
};
return ipc_protocol_msg_send(ipc_protocol, IPC_MSG_PREP_SLEEP,
&prep_args);
}
void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol,
u32 identifier)
{
ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, identifier, true);
}
bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol)
{
u32 ipc_status = ipc_protocol_get_ipc_status(ipc_protocol);
u32 requested;
if (ipc_status != IPC_MEM_DEVICE_IPC_RUNNING) {
dev_err(ipc_protocol->dev,
"irq ignored, CP IPC state is %d, should be RUNNING",
ipc_status);
/* Stop further processing. */
return false;
}
/* Get a copy of the requested PM state by the device and the local
* device PM state.
*/
requested = ipc_protocol_pm_dev_get_sleep_notification(ipc_protocol);
return ipc_pm_dev_slp_notification(&ipc_protocol->pm, requested);
}
static int ipc_protocol_tq_wakeup_dev_slp(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size)
{
struct iosm_pm *ipc_pm = &ipc_imem->ipc_protocol->pm;
/* Wakeup from device sleep if it is not ACTIVE */
ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, true);
ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, false);
return 0;
}
void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep)
{
ipc_pm_set_s2idle_sleep(&ipc_protocol->pm, sleep);
}
bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol)
{
if (!ipc_pm_prepare_host_sleep(&ipc_protocol->pm))
goto err;
ipc_task_queue_send_task(ipc_protocol->imem,
ipc_protocol_tq_wakeup_dev_slp, 0, NULL, 0,
true);
if (!ipc_pm_wait_for_device_active(&ipc_protocol->pm)) {
ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT);
goto err;
}
/* Send the sleep message for sync sys calls. */
dev_dbg(ipc_protocol->dev, "send TARGET_HOST, ENTER_SLEEP");
if (ipc_protocol_msg_send_host_sleep(ipc_protocol,
IPC_HOST_SLEEP_ENTER_SLEEP)) {
/* Sending ENTER_SLEEP message failed, we are still active */
ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE;
goto err;
}
ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP;
return true;
err:
return false;
}
bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol)
{
if (!ipc_pm_prepare_host_active(&ipc_protocol->pm))
return false;
dev_dbg(ipc_protocol->dev, "send TARGET_HOST, EXIT_SLEEP");
if (ipc_protocol_msg_send_host_sleep(ipc_protocol,
IPC_HOST_SLEEP_EXIT_SLEEP)) {
ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP;
return false;
}
ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE;
return true;
}
struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem)
{
struct iosm_protocol *ipc_protocol = kzalloc_obj(*ipc_protocol);
struct ipc_protocol_context_info *p_ci;
u64 addr;
if (!ipc_protocol)
return NULL;
ipc_protocol->dev = ipc_imem->dev;
ipc_protocol->pcie = ipc_imem->pcie;
ipc_protocol->imem = ipc_imem;
ipc_protocol->p_ap_shm = NULL;
ipc_protocol->phy_ap_shm = 0;
ipc_protocol->old_msg_tail = 0;
ipc_protocol->p_ap_shm =
dma_alloc_coherent(&ipc_protocol->pcie->pci->dev,
sizeof(*ipc_protocol->p_ap_shm),
&ipc_protocol->phy_ap_shm, GFP_KERNEL);
if (!ipc_protocol->p_ap_shm) {
dev_err(ipc_protocol->dev, "pci shm alloc error");
kfree(ipc_protocol);
return NULL;
}
/* Prepare the context info for CP. */
addr = ipc_protocol->phy_ap_shm;
p_ci = &ipc_protocol->p_ap_shm->ci;
p_ci->device_info_addr =
addr + offsetof(struct ipc_protocol_ap_shm, device_info);
p_ci->head_array =
addr + offsetof(struct ipc_protocol_ap_shm, head_array);
p_ci->tail_array =
addr + offsetof(struct ipc_protocol_ap_shm, tail_array);
p_ci->msg_head = addr + offsetof(struct ipc_protocol_ap_shm, msg_head);
p_ci->msg_tail = addr + offsetof(struct ipc_protocol_ap_shm, msg_tail);
p_ci->msg_ring_addr =
addr + offsetof(struct ipc_protocol_ap_shm, msg_ring);
p_ci->msg_ring_entries = cpu_to_le16(IPC_MEM_MSG_ENTRIES);
p_ci->msg_irq_vector = IPC_MSG_IRQ_VECTOR;
p_ci->device_info_irq_vector = IPC_DEVICE_IRQ_VECTOR;
ipc_mmio_set_contex_info_addr(ipc_imem->mmio, addr);
ipc_pm_init(ipc_protocol);
return ipc_protocol;
}
void ipc_protocol_deinit(struct iosm_protocol *proto)
{
dma_free_coherent(&proto->pcie->pci->dev, sizeof(*proto->p_ap_shm),
proto->p_ap_shm, proto->phy_ap_shm);
ipc_pm_deinit(proto);
kfree(proto);
}
@@ -0,0 +1,237 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_PROTOCOL_H
#define IOSM_IPC_PROTOCOL_H
#include "iosm_ipc_imem.h"
#include "iosm_ipc_pm.h"
#include "iosm_ipc_protocol_ops.h"
/* Trigger the doorbell interrupt on CP. */
#define IPC_DOORBELL_IRQ_HPDA 0
#define IPC_DOORBELL_IRQ_IPC 1
#define IPC_DOORBELL_IRQ_SLEEP 2
/* IRQ vector number. */
#define IPC_DEVICE_IRQ_VECTOR 0
#define IPC_MSG_IRQ_VECTOR 0
#define IPC_UL_PIPE_IRQ_VECTOR 0
#define IPC_DL_PIPE_IRQ_VECTOR 0
#define IPC_MEM_MSG_ENTRIES 128
/* Default time out for sending IPC messages like open pipe, close pipe etc.
* during run mode.
*
* If the message interface lock to CP times out, the link to CP is broken.
* mode : run mode (IPC_MEM_EXEC_STAGE_RUN)
* unit : milliseconds
*/
#define IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT 500 /* 0.5 seconds */
/* Default time out for sending IPC messages like open pipe, close pipe etc.
* during boot mode.
*
* If the message interface lock to CP times out, the link to CP is broken.
* mode : boot mode
* (IPC_MEM_EXEC_STAGE_BOOT | IPC_MEM_EXEC_STAGE_PSI | IPC_MEM_EXEC_STAGE_EBL)
* unit : milliseconds
*/
#define IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT 500 /* 0.5 seconds */
/**
* struct ipc_protocol_context_info - Structure of the context info
* @device_info_addr: 64 bit address to device info
* @head_array: 64 bit address to head pointer arr for the pipes
* @tail_array: 64 bit address to tail pointer arr for the pipes
* @msg_head: 64 bit address to message head pointer
* @msg_tail: 64 bit address to message tail pointer
* @msg_ring_addr: 64 bit pointer to the message ring buffer
* @msg_ring_entries: This field provides the number of entries which
* the MR can hold
* @msg_irq_vector: This field provides the IRQ which shall be
* generated by the EP device when generating
* completion for Messages.
* @device_info_irq_vector: This field provides the IRQ which shall be
* generated by the EP dev after updating Dev. Info
*/
struct ipc_protocol_context_info {
phys_addr_t device_info_addr;
phys_addr_t head_array;
phys_addr_t tail_array;
phys_addr_t msg_head;
phys_addr_t msg_tail;
phys_addr_t msg_ring_addr;
__le16 msg_ring_entries;
u8 msg_irq_vector;
u8 device_info_irq_vector;
};
/**
* struct ipc_protocol_device_info - Structure for the device information
* @execution_stage: CP execution stage
* @ipc_status: IPC states
* @device_sleep_notification: Requested device pm states
*/
struct ipc_protocol_device_info {
__le32 execution_stage;
__le32 ipc_status;
__le32 device_sleep_notification;
};
/**
* struct ipc_protocol_ap_shm - Protocol Shared Memory Structure
* @ci: Context information struct
* @device_info: Device information struct
* @msg_head: Point to msg head
* @head_array: Array of head pointer
* @msg_tail: Point to msg tail
* @tail_array: Array of tail pointer
* @msg_ring: Circular buffers for the read/tail and write/head
* indeces.
*/
struct ipc_protocol_ap_shm {
struct ipc_protocol_context_info ci;
struct ipc_protocol_device_info device_info;
__le32 msg_head;
__le32 head_array[IPC_MEM_MAX_PIPES];
__le32 msg_tail;
__le32 tail_array[IPC_MEM_MAX_PIPES];
union ipc_mem_msg_entry msg_ring[IPC_MEM_MSG_ENTRIES];
};
/**
* struct iosm_protocol - Structure for IPC protocol.
* @p_ap_shm: Pointer to Protocol Shared Memory Structure
* @pm: Instance to struct iosm_pm
* @pcie: Pointer to struct iosm_pcie
* @imem: Pointer to struct iosm_imem
* @rsp_ring: Array of OS completion objects to be triggered once CP
* acknowledges a request in the message ring
* @dev: Pointer to device structure
* @phy_ap_shm: Physical/Mapped representation of the shared memory info
* @old_msg_tail: Old msg tail ptr, until AP has handled ACK's from CP
*/
struct iosm_protocol {
struct ipc_protocol_ap_shm *p_ap_shm;
struct iosm_pm pm;
struct iosm_pcie *pcie;
struct iosm_imem *imem;
struct ipc_rsp *rsp_ring[IPC_MEM_MSG_ENTRIES];
struct device *dev;
dma_addr_t phy_ap_shm;
u32 old_msg_tail;
};
/**
* struct ipc_call_msg_send_args - Structure for message argument for
* tasklet function.
* @prep_args: Arguments for message preparation function
* @response: Can be NULL if result can be ignored
* @msg_type: Message Type
*/
struct ipc_call_msg_send_args {
union ipc_msg_prep_args *prep_args;
struct ipc_rsp *response;
enum ipc_msg_prep_type msg_type;
};
/**
* ipc_protocol_tq_msg_send - prepare the msg and send to CP
* @ipc_protocol: Pointer to ipc_protocol instance
* @msg_type: Message type
* @prep_args: Message arguments
* @response: Pointer to a response object which has a
* completion object and return code.
*
* Returns: 0 on success and failure value on error
*/
int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol,
enum ipc_msg_prep_type msg_type,
union ipc_msg_prep_args *prep_args,
struct ipc_rsp *response);
/**
* ipc_protocol_msg_send - Send ipc control message to CP and wait for response
* @ipc_protocol: Pointer to ipc_protocol instance
* @prep: Message type
* @prep_args: Message arguments
*
* Returns: 0 on success and failure value on error
*/
int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol,
enum ipc_msg_prep_type prep,
union ipc_msg_prep_args *prep_args);
/**
* ipc_protocol_suspend - Signal to CP that host wants to go to sleep (suspend).
* @ipc_protocol: Pointer to ipc_protocol instance
*
* Returns: true if host can suspend, false if suspend must be aborted.
*/
bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol);
/**
* ipc_protocol_s2idle_sleep - Call PM function to set PM variables in s2idle
* sleep/active case
* @ipc_protocol: Pointer to ipc_protocol instance
* @sleep: True for sleep/False for active
*/
void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep);
/**
* ipc_protocol_resume - Signal to CP that host wants to resume operation.
* @ipc_protocol: Pointer to ipc_protocol instance
*
* Returns: true if host can resume, false if there is a problem.
*/
bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol);
/**
* ipc_protocol_pm_dev_sleep_handle - Handles the Device Sleep state change
* notification.
* @ipc_protocol: Pointer to ipc_protocol instance.
*
* Returns: true if sleep notification handled, false otherwise.
*/
bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol);
/**
* ipc_protocol_doorbell_trigger - Wrapper for PM function which wake up the
* device if it is in low power mode
* and trigger a head pointer update interrupt.
* @ipc_protocol: Pointer to ipc_protocol instance.
* @identifier: Specifies what component triggered hpda
* update irq
*/
void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol,
u32 identifier);
/**
* ipc_protocol_sleep_notification_string - Returns last Sleep Notification as
* string.
* @ipc_protocol: Instance pointer of Protocol module.
*
* Returns: Pointer to string.
*/
const char *
ipc_protocol_sleep_notification_string(struct iosm_protocol *ipc_protocol);
/**
* ipc_protocol_init - Allocates IPC protocol instance
* @ipc_imem: Pointer to iosm_imem structure
*
* Returns: Address of IPC protocol instance on success & NULL on failure.
*/
struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem);
/**
* ipc_protocol_deinit - Deallocates IPC protocol instance
* @ipc_protocol: pointer to the IPC protocol instance
*/
void ipc_protocol_deinit(struct iosm_protocol *ipc_protocol);
#endif
@@ -0,0 +1,541 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_protocol.h"
#include "iosm_ipc_protocol_ops.h"
/* Get the next free message element.*/
static union ipc_mem_msg_entry *
ipc_protocol_free_msg_get(struct iosm_protocol *ipc_protocol, int *index)
{
u32 head = le32_to_cpu(ipc_protocol->p_ap_shm->msg_head);
u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES;
union ipc_mem_msg_entry *msg;
if (new_head == le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail)) {
dev_err(ipc_protocol->dev, "message ring is full");
return NULL;
}
/* Get the pointer to the next free message element,
* reset the fields and mark is as invalid.
*/
msg = &ipc_protocol->p_ap_shm->msg_ring[head];
memset(msg, 0, sizeof(*msg));
/* return index in message ring */
*index = head;
return msg;
}
/* Updates the message ring Head pointer */
void ipc_protocol_msg_hp_update(struct iosm_imem *ipc_imem)
{
struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;
u32 head = le32_to_cpu(ipc_protocol->p_ap_shm->msg_head);
u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES;
/* Update head pointer and fire doorbell. */
ipc_protocol->p_ap_shm->msg_head = cpu_to_le32(new_head);
ipc_protocol->old_msg_tail =
le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail);
ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, IPC_HP_MR, false);
}
/* Allocate and prepare a OPEN_PIPE message.
* This also allocates the memory for the new TDR structure and
* updates the pipe structure referenced in the preparation arguments.
*/
static int ipc_protocol_msg_prepipe_open(struct iosm_protocol *ipc_protocol,
union ipc_msg_prep_args *args)
{
int index;
union ipc_mem_msg_entry *msg =
ipc_protocol_free_msg_get(ipc_protocol, &index);
struct ipc_pipe *pipe = args->pipe_open.pipe;
struct ipc_protocol_td *tdr;
struct sk_buff **skbr;
if (!msg) {
dev_err(ipc_protocol->dev, "failed to get free message");
return -EIO;
}
/* Allocate the skbuf elements for the skbuf which are on the way.
* SKB ring is internal memory allocation for driver. No need to
* re-calculate the start and end addresses.
*/
skbr = kzalloc_objs(*skbr, pipe->nr_of_entries, GFP_ATOMIC);
if (!skbr)
return -ENOMEM;
/* Allocate the transfer descriptors for the pipe. */
tdr = dma_alloc_coherent(&ipc_protocol->pcie->pci->dev,
pipe->nr_of_entries * sizeof(*tdr),
&pipe->phy_tdr_start, GFP_ATOMIC);
if (!tdr) {
kfree(skbr);
dev_err(ipc_protocol->dev, "tdr alloc error");
return -ENOMEM;
}
pipe->max_nr_of_queued_entries = pipe->nr_of_entries - 1;
pipe->nr_of_queued_entries = 0;
pipe->tdr_start = tdr;
pipe->skbr_start = skbr;
pipe->old_tail = 0;
ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = 0;
msg->open_pipe.type_of_message = IPC_MEM_MSG_OPEN_PIPE;
msg->open_pipe.pipe_nr = pipe->pipe_nr;
msg->open_pipe.tdr_addr = cpu_to_le64(pipe->phy_tdr_start);
msg->open_pipe.tdr_entries = cpu_to_le16(pipe->nr_of_entries);
msg->open_pipe.accumulation_backoff =
cpu_to_le32(pipe->accumulation_backoff);
msg->open_pipe.irq_vector = cpu_to_le32(pipe->irq);
return index;
}
static int ipc_protocol_msg_prepipe_close(struct iosm_protocol *ipc_protocol,
union ipc_msg_prep_args *args)
{
int index = -1;
union ipc_mem_msg_entry *msg =
ipc_protocol_free_msg_get(ipc_protocol, &index);
struct ipc_pipe *pipe = args->pipe_close.pipe;
if (!msg)
return -EIO;
msg->close_pipe.type_of_message = IPC_MEM_MSG_CLOSE_PIPE;
msg->close_pipe.pipe_nr = pipe->pipe_nr;
dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_CLOSE_PIPE(pipe_nr=%d)",
msg->close_pipe.pipe_nr);
return index;
}
static int ipc_protocol_msg_prep_sleep(struct iosm_protocol *ipc_protocol,
union ipc_msg_prep_args *args)
{
int index = -1;
union ipc_mem_msg_entry *msg =
ipc_protocol_free_msg_get(ipc_protocol, &index);
if (!msg) {
dev_err(ipc_protocol->dev, "failed to get free message");
return -EIO;
}
/* Prepare and send the host sleep message to CP to enter or exit D3. */
msg->host_sleep.type_of_message = IPC_MEM_MSG_SLEEP;
msg->host_sleep.target = args->sleep.target; /* 0=host, 1=device */
/* state; 0=enter, 1=exit 2=enter w/o protocol */
msg->host_sleep.state = args->sleep.state;
dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_SLEEP(target=%d; state=%d)",
msg->host_sleep.target, msg->host_sleep.state);
return index;
}
static int ipc_protocol_msg_prep_feature_set(struct iosm_protocol *ipc_protocol,
union ipc_msg_prep_args *args)
{
int index = -1;
union ipc_mem_msg_entry *msg =
ipc_protocol_free_msg_get(ipc_protocol, &index);
if (!msg) {
dev_err(ipc_protocol->dev, "failed to get free message");
return -EIO;
}
msg->feature_set.type_of_message = IPC_MEM_MSG_FEATURE_SET;
msg->feature_set.reset_enable = args->feature_set.reset_enable <<
RESET_BIT;
dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_FEATURE_SET(reset_enable=%d)",
msg->feature_set.reset_enable >> RESET_BIT);
return index;
}
/* Processes the message consumed by CP. */
bool ipc_protocol_msg_process(struct iosm_imem *ipc_imem, int irq)
{
struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;
struct ipc_rsp **rsp_ring = ipc_protocol->rsp_ring;
bool msg_processed = false;
u32 i;
if (le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail) >=
IPC_MEM_MSG_ENTRIES) {
dev_err(ipc_protocol->dev, "msg_tail out of range: %d",
le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail));
return msg_processed;
}
if (irq != IMEM_IRQ_DONT_CARE &&
irq != ipc_protocol->p_ap_shm->ci.msg_irq_vector)
return msg_processed;
for (i = ipc_protocol->old_msg_tail;
i != le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail);
i = (i + 1) % IPC_MEM_MSG_ENTRIES) {
union ipc_mem_msg_entry *msg =
&ipc_protocol->p_ap_shm->msg_ring[i];
dev_dbg(ipc_protocol->dev, "msg[%d]: type=%u status=%d", i,
msg->common.type_of_message,
msg->common.completion_status);
/* Update response with status and wake up waiting requestor */
if (rsp_ring[i]) {
rsp_ring[i]->status =
le32_to_cpu(msg->common.completion_status);
complete(&rsp_ring[i]->completion);
rsp_ring[i] = NULL;
}
msg_processed = true;
}
ipc_protocol->old_msg_tail = i;
return msg_processed;
}
/* Sends data from UL list to CP for the provided pipe by updating the Head
* pointer of given pipe.
*/
bool ipc_protocol_ul_td_send(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe,
struct sk_buff_head *p_ul_list)
{
struct ipc_protocol_td *td;
bool hpda_pending = false;
struct sk_buff *skb;
s32 free_elements;
u32 head;
u32 tail;
if (!ipc_protocol->p_ap_shm) {
dev_err(ipc_protocol->dev, "driver is not initialized");
return false;
}
/* Get head and tail of the td list and calculate
* the number of free elements.
*/
head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]);
tail = pipe->old_tail;
while (!skb_queue_empty(p_ul_list)) {
if (head < tail)
free_elements = tail - head - 1;
else
free_elements =
pipe->nr_of_entries - head + ((s32)tail - 1);
if (free_elements <= 0) {
dev_dbg(ipc_protocol->dev,
"no free td elements for UL pipe %d",
pipe->pipe_nr);
break;
}
/* Get the td address. */
td = &pipe->tdr_start[head];
/* Take the first element of the uplink list and add it
* to the td list.
*/
skb = skb_dequeue(p_ul_list);
if (WARN_ON(!skb))
break;
/* Save the reference to the uplink skbuf. */
pipe->skbr_start[head] = skb;
td->buffer.address = IPC_CB(skb)->mapping;
td->scs = cpu_to_le32(skb->len) & cpu_to_le32(SIZE_MASK);
td->next = 0;
pipe->nr_of_queued_entries++;
/* Calculate the new head and save it. */
head++;
if (head >= pipe->nr_of_entries)
head = 0;
ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] =
cpu_to_le32(head);
}
if (pipe->old_head != head) {
dev_dbg(ipc_protocol->dev, "New UL TDs Pipe:%d", pipe->pipe_nr);
pipe->old_head = head;
/* Trigger doorbell because of pending UL packets. */
hpda_pending = true;
}
return hpda_pending;
}
/* Checks for Tail pointer update from CP and returns the data as SKB. */
struct sk_buff *ipc_protocol_ul_td_process(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe)
{
struct ipc_protocol_td *p_td = &pipe->tdr_start[pipe->old_tail];
struct sk_buff *skb = pipe->skbr_start[pipe->old_tail];
pipe->nr_of_queued_entries--;
pipe->old_tail++;
if (pipe->old_tail >= pipe->nr_of_entries)
pipe->old_tail = 0;
if (!p_td->buffer.address) {
dev_err(ipc_protocol->dev, "Td buffer address is NULL");
return NULL;
}
if (p_td->buffer.address != IPC_CB(skb)->mapping) {
dev_err(ipc_protocol->dev,
"pipe %d: invalid buf_addr or skb_data",
pipe->pipe_nr);
return NULL;
}
return skb;
}
/* Allocates an SKB for CP to send data and updates the Head Pointer
* of the given Pipe#.
*/
bool ipc_protocol_dl_td_prepare(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe)
{
struct ipc_protocol_td *td;
dma_addr_t mapping = 0;
u32 head, new_head;
struct sk_buff *skb;
u32 tail;
/* Get head and tail of the td list and calculate
* the number of free elements.
*/
head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]);
tail = le32_to_cpu(ipc_protocol->p_ap_shm->tail_array[pipe->pipe_nr]);
new_head = head + 1;
if (new_head >= pipe->nr_of_entries)
new_head = 0;
if (new_head == tail)
return false;
/* Get the td address. */
td = &pipe->tdr_start[head];
/* Allocate the skbuf for the descriptor. */
skb = ipc_pcie_alloc_skb(ipc_protocol->pcie, pipe->buf_size, GFP_ATOMIC,
&mapping, DMA_FROM_DEVICE,
IPC_MEM_DL_ETH_OFFSET);
if (!skb)
return false;
td->buffer.address = mapping;
td->scs = cpu_to_le32(pipe->buf_size) & cpu_to_le32(SIZE_MASK);
td->next = 0;
/* store the new head value. */
ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] =
cpu_to_le32(new_head);
/* Save the reference to the skbuf. */
pipe->skbr_start[head] = skb;
pipe->nr_of_queued_entries++;
return true;
}
/* Processes DL TD's */
struct sk_buff *ipc_protocol_dl_td_process(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe)
{
struct ipc_protocol_td *p_td;
struct sk_buff *skb;
if (!pipe->tdr_start)
return NULL;
/* Copy the reference to the downlink buffer. */
p_td = &pipe->tdr_start[pipe->old_tail];
skb = pipe->skbr_start[pipe->old_tail];
/* Reset the ring elements. */
pipe->skbr_start[pipe->old_tail] = NULL;
pipe->nr_of_queued_entries--;
pipe->old_tail++;
if (pipe->old_tail >= pipe->nr_of_entries)
pipe->old_tail = 0;
if (!skb) {
dev_err(ipc_protocol->dev, "skb is null");
goto ret;
} else if (!p_td->buffer.address) {
dev_err(ipc_protocol->dev, "td/buffer address is null");
ipc_pcie_kfree_skb(ipc_protocol->pcie, skb);
skb = NULL;
goto ret;
}
if (p_td->buffer.address != IPC_CB(skb)->mapping) {
dev_err(ipc_protocol->dev, "invalid buf=%llx or skb=%p",
(unsigned long long)p_td->buffer.address, skb->data);
ipc_pcie_kfree_skb(ipc_protocol->pcie, skb);
skb = NULL;
goto ret;
} else if ((le32_to_cpu(p_td->scs) & SIZE_MASK) > pipe->buf_size) {
dev_err(ipc_protocol->dev, "invalid buffer size %d > %d",
le32_to_cpu(p_td->scs) & SIZE_MASK,
pipe->buf_size);
ipc_pcie_kfree_skb(ipc_protocol->pcie, skb);
skb = NULL;
goto ret;
} else if (le32_to_cpu(p_td->scs) >> COMPLETION_STATUS ==
IPC_MEM_TD_CS_ABORT) {
/* Discard aborted buffers. */
dev_dbg(ipc_protocol->dev, "discard 'aborted' buffers");
ipc_pcie_kfree_skb(ipc_protocol->pcie, skb);
skb = NULL;
goto ret;
}
/* Set the length field in skbuf. */
skb_put(skb, le32_to_cpu(p_td->scs) & SIZE_MASK);
ret:
return skb;
}
void ipc_protocol_get_head_tail_index(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe, u32 *head,
u32 *tail)
{
struct ipc_protocol_ap_shm *ipc_ap_shm = ipc_protocol->p_ap_shm;
if (head)
*head = le32_to_cpu(ipc_ap_shm->head_array[pipe->pipe_nr]);
if (tail)
*tail = le32_to_cpu(ipc_ap_shm->tail_array[pipe->pipe_nr]);
}
/* Frees the TDs given to CP. */
void ipc_protocol_pipe_cleanup(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe)
{
struct sk_buff *skb;
u32 head;
u32 tail;
/* Get the start and the end of the buffer list. */
head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]);
tail = pipe->old_tail;
/* Reset tail and head to 0. */
ipc_protocol->p_ap_shm->tail_array[pipe->pipe_nr] = 0;
ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = 0;
/* Free pending uplink and downlink buffers. */
if (pipe->skbr_start) {
while (head != tail) {
/* Get the reference to the skbuf,
* which is on the way and free it.
*/
skb = pipe->skbr_start[tail];
if (skb)
ipc_pcie_kfree_skb(ipc_protocol->pcie, skb);
tail++;
if (tail >= pipe->nr_of_entries)
tail = 0;
}
kfree(pipe->skbr_start);
pipe->skbr_start = NULL;
}
pipe->old_tail = 0;
/* Free and reset the td and skbuf circular buffers. kfree is save! */
if (pipe->tdr_start) {
dma_free_coherent(&ipc_protocol->pcie->pci->dev,
sizeof(*pipe->tdr_start) * pipe->nr_of_entries,
pipe->tdr_start, pipe->phy_tdr_start);
pipe->tdr_start = NULL;
}
}
enum ipc_mem_device_ipc_state ipc_protocol_get_ipc_status(struct iosm_protocol
*ipc_protocol)
{
return (enum ipc_mem_device_ipc_state)
le32_to_cpu(ipc_protocol->p_ap_shm->device_info.ipc_status);
}
enum ipc_mem_exec_stage
ipc_protocol_get_ap_exec_stage(struct iosm_protocol *ipc_protocol)
{
return le32_to_cpu(ipc_protocol->p_ap_shm->device_info.execution_stage);
}
int ipc_protocol_msg_prep(struct iosm_imem *ipc_imem,
enum ipc_msg_prep_type msg_type,
union ipc_msg_prep_args *args)
{
struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;
switch (msg_type) {
case IPC_MSG_PREP_SLEEP:
return ipc_protocol_msg_prep_sleep(ipc_protocol, args);
case IPC_MSG_PREP_PIPE_OPEN:
return ipc_protocol_msg_prepipe_open(ipc_protocol, args);
case IPC_MSG_PREP_PIPE_CLOSE:
return ipc_protocol_msg_prepipe_close(ipc_protocol, args);
case IPC_MSG_PREP_FEATURE_SET:
return ipc_protocol_msg_prep_feature_set(ipc_protocol, args);
/* Unsupported messages in protocol */
case IPC_MSG_PREP_MAP:
case IPC_MSG_PREP_UNMAP:
default:
dev_err(ipc_protocol->dev,
"unsupported message type: %d in protocol", msg_type);
return -EINVAL;
}
}
u32
ipc_protocol_pm_dev_get_sleep_notification(struct iosm_protocol *ipc_protocol)
{
struct ipc_protocol_ap_shm *ipc_ap_shm = ipc_protocol->p_ap_shm;
return le32_to_cpu(ipc_ap_shm->device_info.device_sleep_notification);
}
@@ -0,0 +1,444 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_PROTOCOL_OPS_H
#define IOSM_IPC_PROTOCOL_OPS_H
#define SIZE_MASK 0x00FFFFFF
#define COMPLETION_STATUS 24
#define RESET_BIT 7
/**
* enum ipc_mem_td_cs - Completion status of a TD
* @IPC_MEM_TD_CS_INVALID: Initial status - td not yet used.
* @IPC_MEM_TD_CS_PARTIAL_TRANSFER: More data pending -> next TD used for this
* @IPC_MEM_TD_CS_END_TRANSFER: IO transfer is complete.
* @IPC_MEM_TD_CS_OVERFLOW: IO transfer to small for the buff to write
* @IPC_MEM_TD_CS_ABORT: TD marked as abort and shall be discarded
* by AP.
* @IPC_MEM_TD_CS_ERROR: General error.
*/
enum ipc_mem_td_cs {
IPC_MEM_TD_CS_INVALID,
IPC_MEM_TD_CS_PARTIAL_TRANSFER,
IPC_MEM_TD_CS_END_TRANSFER,
IPC_MEM_TD_CS_OVERFLOW,
IPC_MEM_TD_CS_ABORT,
IPC_MEM_TD_CS_ERROR,
};
/**
* enum ipc_mem_msg_cs - Completion status of IPC Message
* @IPC_MEM_MSG_CS_INVALID: Initial status.
* @IPC_MEM_MSG_CS_SUCCESS: IPC Message completion success.
* @IPC_MEM_MSG_CS_ERROR: Message send error.
*/
enum ipc_mem_msg_cs {
IPC_MEM_MSG_CS_INVALID,
IPC_MEM_MSG_CS_SUCCESS,
IPC_MEM_MSG_CS_ERROR,
};
/**
* struct ipc_msg_prep_args_pipe - struct for pipe args for message preparation
* @pipe: Pipe to open/close
*/
struct ipc_msg_prep_args_pipe {
struct ipc_pipe *pipe;
};
/**
* struct ipc_msg_prep_args_sleep - struct for sleep args for message
* preparation
* @target: 0=host, 1=device
* @state: 0=enter sleep, 1=exit sleep
*/
struct ipc_msg_prep_args_sleep {
unsigned int target;
unsigned int state;
};
/**
* struct ipc_msg_prep_feature_set - struct for feature set argument for
* message preparation
* @reset_enable: 0=out-of-band, 1=in-band-crash notification
*/
struct ipc_msg_prep_feature_set {
u8 reset_enable;
};
/**
* struct ipc_msg_prep_map - struct for map argument for message preparation
* @region_id: Region to map
* @addr: Pcie addr of region to map
* @size: Size of the region to map
*/
struct ipc_msg_prep_map {
unsigned int region_id;
unsigned long addr;
size_t size;
};
/**
* struct ipc_msg_prep_unmap - struct for unmap argument for message preparation
* @region_id: Region to unmap
*/
struct ipc_msg_prep_unmap {
unsigned int region_id;
};
/**
* struct ipc_msg_prep_args - Union to handle different message types
* @pipe_open: Pipe open message preparation struct
* @pipe_close: Pipe close message preparation struct
* @sleep: Sleep message preparation struct
* @feature_set: Feature set message preparation struct
* @map: Memory map message preparation struct
* @unmap: Memory unmap message preparation struct
*/
union ipc_msg_prep_args {
struct ipc_msg_prep_args_pipe pipe_open;
struct ipc_msg_prep_args_pipe pipe_close;
struct ipc_msg_prep_args_sleep sleep;
struct ipc_msg_prep_feature_set feature_set;
struct ipc_msg_prep_map map;
struct ipc_msg_prep_unmap unmap;
};
/**
* enum ipc_msg_prep_type - Enum for message prepare actions
* @IPC_MSG_PREP_SLEEP: Sleep message preparation type
* @IPC_MSG_PREP_PIPE_OPEN: Pipe open message preparation type
* @IPC_MSG_PREP_PIPE_CLOSE: Pipe close message preparation type
* @IPC_MSG_PREP_FEATURE_SET: Feature set message preparation type
* @IPC_MSG_PREP_MAP: Memory map message preparation type
* @IPC_MSG_PREP_UNMAP: Memory unmap message preparation type
*/
enum ipc_msg_prep_type {
IPC_MSG_PREP_SLEEP,
IPC_MSG_PREP_PIPE_OPEN,
IPC_MSG_PREP_PIPE_CLOSE,
IPC_MSG_PREP_FEATURE_SET,
IPC_MSG_PREP_MAP,
IPC_MSG_PREP_UNMAP,
};
/**
* struct ipc_rsp - Response to sent message
* @completion: For waking up requestor
* @status: Completion status
*/
struct ipc_rsp {
struct completion completion;
enum ipc_mem_msg_cs status;
};
/**
* enum ipc_mem_msg - Type-definition of the messages.
* @IPC_MEM_MSG_OPEN_PIPE: AP ->CP: Open a pipe
* @IPC_MEM_MSG_CLOSE_PIPE: AP ->CP: Close a pipe
* @IPC_MEM_MSG_ABORT_PIPE: AP ->CP: wait for completion of the
* running transfer and abort all pending
* IO-transfers for the pipe
* @IPC_MEM_MSG_SLEEP: AP ->CP: host enter or exit sleep
* @IPC_MEM_MSG_FEATURE_SET: AP ->CP: Intel feature configuration
*/
enum ipc_mem_msg {
IPC_MEM_MSG_OPEN_PIPE = 0x01,
IPC_MEM_MSG_CLOSE_PIPE = 0x02,
IPC_MEM_MSG_ABORT_PIPE = 0x03,
IPC_MEM_MSG_SLEEP = 0x04,
IPC_MEM_MSG_FEATURE_SET = 0xF0,
};
/**
* struct ipc_mem_msg_open_pipe - Message structure for open pipe
* @tdr_addr: Tdr address
* @tdr_entries: Tdr entries
* @pipe_nr: Pipe number
* @type_of_message: Message type
* @irq_vector: MSI vector number
* @accumulation_backoff: Time in usec for data accumalation
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_open_pipe {
__le64 tdr_addr;
__le16 tdr_entries;
u8 pipe_nr;
u8 type_of_message;
__le32 irq_vector;
__le32 accumulation_backoff;
__le32 completion_status;
};
/**
* struct ipc_mem_msg_close_pipe - Message structure for close pipe
* @reserved1: Reserved
* @reserved2: Reserved
* @pipe_nr: Pipe number
* @type_of_message: Message type
* @reserved3: Reserved
* @reserved4: Reserved
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_close_pipe {
__le32 reserved1[2];
__le16 reserved2;
u8 pipe_nr;
u8 type_of_message;
__le32 reserved3;
__le32 reserved4;
__le32 completion_status;
};
/**
* struct ipc_mem_msg_abort_pipe - Message structure for abort pipe
* @reserved1: Reserved
* @reserved2: Reserved
* @pipe_nr: Pipe number
* @type_of_message: Message type
* @reserved3: Reserved
* @reserved4: Reserved
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_abort_pipe {
__le32 reserved1[2];
__le16 reserved2;
u8 pipe_nr;
u8 type_of_message;
__le32 reserved3;
__le32 reserved4;
__le32 completion_status;
};
/**
* struct ipc_mem_msg_host_sleep - Message structure for sleep message.
* @reserved1: Reserved
* @target: 0=host, 1=device, host or EP devie
* is the message target
* @state: 0=enter sleep, 1=exit sleep,
* 2=enter sleep no protocol
* @reserved2: Reserved
* @type_of_message: Message type
* @reserved3: Reserved
* @reserved4: Reserved
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_host_sleep {
__le32 reserved1[2];
u8 target;
u8 state;
u8 reserved2;
u8 type_of_message;
__le32 reserved3;
__le32 reserved4;
__le32 completion_status;
};
/**
* struct ipc_mem_msg_feature_set - Message structure for feature_set message
* @reserved1: Reserved
* @reserved2: Reserved
* @reset_enable: 0=out-of-band, 1=in-band-crash notification
* @type_of_message: Message type
* @reserved3: Reserved
* @reserved4: Reserved
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_feature_set {
__le32 reserved1[2];
__le16 reserved2;
u8 reset_enable;
u8 type_of_message;
__le32 reserved3;
__le32 reserved4;
__le32 completion_status;
};
/**
* struct ipc_mem_msg_common - Message structure for completion status update.
* @reserved1: Reserved
* @reserved2: Reserved
* @type_of_message: Message type
* @reserved3: Reserved
* @reserved4: Reserved
* @completion_status: Message Completion Status
*/
struct ipc_mem_msg_common {
__le32 reserved1[2];
u8 reserved2[3];
u8 type_of_message;
__le32 reserved3;
__le32 reserved4;
__le32 completion_status;
};
/**
* union ipc_mem_msg_entry - Union with all possible messages.
* @open_pipe: Open pipe message struct
* @close_pipe: Close pipe message struct
* @abort_pipe: Abort pipe message struct
* @host_sleep: Host sleep message struct
* @feature_set: Featuer set message struct
* @common: Used to access msg_type and to set the completion status
*/
union ipc_mem_msg_entry {
struct ipc_mem_msg_open_pipe open_pipe;
struct ipc_mem_msg_close_pipe close_pipe;
struct ipc_mem_msg_abort_pipe abort_pipe;
struct ipc_mem_msg_host_sleep host_sleep;
struct ipc_mem_msg_feature_set feature_set;
struct ipc_mem_msg_common common;
};
/* Transfer descriptor definition. */
struct ipc_protocol_td {
union {
/* 0 : 63 - 64-bit address of a buffer in host memory. */
dma_addr_t address;
struct {
/* 0 : 31 - 32 bit address */
__le32 address;
/* 32 : 63 - corresponding descriptor */
__le32 desc;
} __packed shm;
} buffer;
/* 0 - 2nd byte - Size of the buffer.
* The host provides the size of the buffer queued.
* The EP device reads this value and shall update
* it for downlink transfers to indicate the
* amount of data written in buffer.
* 3rd byte - This field provides the completion status
* of the TD. When queuing the TD, the host sets
* the status to 0. The EP device updates this
* field when completing the TD.
*/
__le32 scs;
/* 0th - nr of following descriptors
* 1 - 3rd byte - reserved
*/
__le32 next;
} __packed;
/**
* ipc_protocol_msg_prep - Prepare message based upon message type
* @ipc_imem: iosm_protocol instance
* @msg_type: message prepare type
* @args: message arguments
*
* Return: 0 on success and failure value on error
*/
int ipc_protocol_msg_prep(struct iosm_imem *ipc_imem,
enum ipc_msg_prep_type msg_type,
union ipc_msg_prep_args *args);
/**
* ipc_protocol_msg_hp_update - Function for head pointer update
* of message ring
* @ipc_imem: iosm_protocol instance
*/
void ipc_protocol_msg_hp_update(struct iosm_imem *ipc_imem);
/**
* ipc_protocol_msg_process - Function for processing responses
* to IPC messages
* @ipc_imem: iosm_protocol instance
* @irq: IRQ vector
*
* Return: True on success, false if error
*/
bool ipc_protocol_msg_process(struct iosm_imem *ipc_imem, int irq);
/**
* ipc_protocol_ul_td_send - Function for sending the data to CP
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe instance
* @p_ul_list: uplink sk_buff list
*
* Return: true in success, false in case of error
*/
bool ipc_protocol_ul_td_send(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe,
struct sk_buff_head *p_ul_list);
/**
* ipc_protocol_ul_td_process - Function for processing the sent data
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe instance
*
* Return: sk_buff instance
*/
struct sk_buff *ipc_protocol_ul_td_process(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe);
/**
* ipc_protocol_dl_td_prepare - Function for providing DL TDs to CP
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe instance
*
* Return: true in success, false in case of error
*/
bool ipc_protocol_dl_td_prepare(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe);
/**
* ipc_protocol_dl_td_process - Function for processing the DL data
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe instance
*
* Return: sk_buff instance
*/
struct sk_buff *ipc_protocol_dl_td_process(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe);
/**
* ipc_protocol_get_head_tail_index - Function for getting Head and Tail
* pointer index of given pipe
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe Instance
* @head: head pointer index of the given pipe
* @tail: tail pointer index of the given pipe
*/
void ipc_protocol_get_head_tail_index(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe, u32 *head,
u32 *tail);
/**
* ipc_protocol_get_ipc_status - Function for getting the IPC Status
* @ipc_protocol: iosm_protocol instance
*
* Return: Returns IPC State
*/
enum ipc_mem_device_ipc_state ipc_protocol_get_ipc_status(struct iosm_protocol
*ipc_protocol);
/**
* ipc_protocol_pipe_cleanup - Function to cleanup pipe resources
* @ipc_protocol: iosm_protocol instance
* @pipe: Pipe instance
*/
void ipc_protocol_pipe_cleanup(struct iosm_protocol *ipc_protocol,
struct ipc_pipe *pipe);
/**
* ipc_protocol_get_ap_exec_stage - Function for getting AP Exec Stage
* @ipc_protocol: pointer to struct iosm protocol
*
* Return: returns BOOT Stages
*/
enum ipc_mem_exec_stage
ipc_protocol_get_ap_exec_stage(struct iosm_protocol *ipc_protocol);
/**
* ipc_protocol_pm_dev_get_sleep_notification - Function for getting Dev Sleep
* notification
* @ipc_protocol: iosm_protocol instance
*
* Return: Returns dev PM State
*/
u32 ipc_protocol_pm_dev_get_sleep_notification(struct iosm_protocol
*ipc_protocol);
#endif
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include "iosm_ipc_imem.h"
#include "iosm_ipc_task_queue.h"
/* Actual tasklet function, will be called whenever tasklet is scheduled.
* Calls event handler involves callback for each element in the message queue
*/
static void ipc_task_queue_handler(unsigned long data)
{
struct ipc_task_queue *ipc_task = (struct ipc_task_queue *)data;
unsigned int q_rpos = ipc_task->q_rpos;
/* Loop over the input queue contents. */
while (q_rpos != ipc_task->q_wpos) {
/* Get the current first queue element. */
struct ipc_task_queue_args *args = &ipc_task->args[q_rpos];
/* Process the input message. */
if (args->func)
args->response = args->func(args->ipc_imem, args->arg,
args->msg, args->size);
/* Signal completion for synchronous calls */
if (args->completion)
complete(args->completion);
/* Free message if copy was allocated. */
if (args->is_copy)
kfree(args->msg);
/* Set invalid queue element. Technically
* spin_lock_irqsave is not required here as
* the array element has been processed already
* so we can assume that immediately after processing
* ipc_task element, queue will not rotate again to
* ipc_task same element within such short time.
*/
args->completion = NULL;
args->func = NULL;
args->msg = NULL;
args->size = 0;
args->is_copy = false;
/* calculate the new read ptr and update the volatile read
* ptr
*/
q_rpos = (q_rpos + 1) % IPC_THREAD_QUEUE_SIZE;
ipc_task->q_rpos = q_rpos;
}
}
/* Free memory alloc and trigger completions left in the queue during dealloc */
static void ipc_task_queue_cleanup(struct ipc_task_queue *ipc_task)
{
unsigned int q_rpos = ipc_task->q_rpos;
while (q_rpos != ipc_task->q_wpos) {
struct ipc_task_queue_args *args = &ipc_task->args[q_rpos];
if (args->completion)
complete(args->completion);
if (args->is_copy)
kfree(args->msg);
q_rpos = (q_rpos + 1) % IPC_THREAD_QUEUE_SIZE;
ipc_task->q_rpos = q_rpos;
}
}
/* Add a message to the queue and trigger the ipc_task. */
static int
ipc_task_queue_add_task(struct iosm_imem *ipc_imem,
int arg, void *msg,
int (*func)(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size),
size_t size, bool is_copy, bool wait)
{
struct tasklet_struct *ipc_tasklet = ipc_imem->ipc_task->ipc_tasklet;
struct ipc_task_queue *ipc_task = &ipc_imem->ipc_task->ipc_queue;
struct completion completion;
unsigned int pos, nextpos;
unsigned long flags;
int result = -EIO;
init_completion(&completion);
/* tasklet send may be called from both interrupt or thread
* context, therefore protect queue operation by spinlock
*/
spin_lock_irqsave(&ipc_task->q_lock, flags);
pos = ipc_task->q_wpos;
nextpos = (pos + 1) % IPC_THREAD_QUEUE_SIZE;
/* Get next queue position. */
if (nextpos != ipc_task->q_rpos) {
/* Get the reference to the queue element and save the passed
* values.
*/
ipc_task->args[pos].arg = arg;
ipc_task->args[pos].msg = msg;
ipc_task->args[pos].func = func;
ipc_task->args[pos].ipc_imem = ipc_imem;
ipc_task->args[pos].size = size;
ipc_task->args[pos].is_copy = is_copy;
ipc_task->args[pos].completion = wait ? &completion : NULL;
ipc_task->args[pos].response = -1;
/* apply write barrier so that ipc_task->q_rpos elements
* are updated before ipc_task->q_wpos is being updated.
*/
smp_wmb();
/* Update the status of the free queue space. */
ipc_task->q_wpos = nextpos;
result = 0;
}
spin_unlock_irqrestore(&ipc_task->q_lock, flags);
if (result == 0) {
tasklet_schedule(ipc_tasklet);
if (wait) {
wait_for_completion(&completion);
result = ipc_task->args[pos].response;
}
} else {
dev_err(ipc_imem->ipc_task->dev, "queue is full");
}
return result;
}
int ipc_task_queue_send_task(struct iosm_imem *imem,
int (*func)(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size),
int arg, void *msg, size_t size, bool wait)
{
bool is_copy = false;
void *copy = msg;
int ret = -ENOMEM;
if (size > 0) {
copy = kmemdup(msg, size, GFP_ATOMIC);
if (!copy)
goto out;
is_copy = true;
}
ret = ipc_task_queue_add_task(imem, arg, copy, func,
size, is_copy, wait);
if (ret < 0) {
dev_err(imem->ipc_task->dev,
"add task failed for %ps %d, %p, %zu, %d", func, arg,
copy, size, is_copy);
if (is_copy)
kfree(copy);
goto out;
}
ret = 0;
out:
return ret;
}
int ipc_task_init(struct ipc_task *ipc_task)
{
struct ipc_task_queue *ipc_queue = &ipc_task->ipc_queue;
ipc_task->ipc_tasklet = kzalloc_obj(*ipc_task->ipc_tasklet);
if (!ipc_task->ipc_tasklet)
return -ENOMEM;
/* Initialize the spinlock needed to protect the message queue of the
* ipc_task
*/
spin_lock_init(&ipc_queue->q_lock);
tasklet_init(ipc_task->ipc_tasklet, ipc_task_queue_handler,
(unsigned long)ipc_queue);
return 0;
}
void ipc_task_deinit(struct ipc_task *ipc_task)
{
tasklet_kill(ipc_task->ipc_tasklet);
kfree(ipc_task->ipc_tasklet);
/* This will free/complete any outstanding messages,
* without calling the actual handler
*/
ipc_task_queue_cleanup(&ipc_task->ipc_queue);
}
@@ -0,0 +1,97 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_TASK_QUEUE_H
#define IOSM_IPC_TASK_QUEUE_H
/* Number of available element for the input message queue of the IPC
* ipc_task
*/
#define IPC_THREAD_QUEUE_SIZE 256
/**
* struct ipc_task_queue_args - Struct for Task queue elements
* @ipc_imem: Pointer to struct iosm_imem
* @msg: Message argument for tasklet function. (optional, can be NULL)
* @completion: OS object used to wait for the tasklet function to finish for
* synchronous calls
* @func: Function to be called in tasklet (tl) context
* @arg: Generic integer argument for tasklet function (optional)
* @size: Message size argument for tasklet function (optional)
* @response: Return code of tasklet function for synchronous calls
* @is_copy: Is true if msg contains a pointer to a copy of the original msg
* for async. calls that needs to be freed once the tasklet returns
*/
struct ipc_task_queue_args {
struct iosm_imem *ipc_imem;
void *msg;
struct completion *completion;
int (*func)(struct iosm_imem *ipc_imem, int arg, void *msg,
size_t size);
int arg;
size_t size;
int response;
u8 is_copy:1;
};
/**
* struct ipc_task_queue - Struct for Task queue
* @q_lock: Protect the message queue of the ipc ipc_task
* @args: Message queue of the IPC ipc_task
* @q_rpos: First queue element to process.
* @q_wpos: First free element of the input queue.
*/
struct ipc_task_queue {
spinlock_t q_lock; /* for atomic operation on queue */
struct ipc_task_queue_args args[IPC_THREAD_QUEUE_SIZE];
unsigned int q_rpos;
unsigned int q_wpos;
};
/**
* struct ipc_task - Struct for Task
* @dev: Pointer to device structure
* @ipc_tasklet: Tasklet for serialized work offload
* from interrupts and OS callbacks
* @ipc_queue: Task for entry into ipc task queue
*/
struct ipc_task {
struct device *dev;
struct tasklet_struct *ipc_tasklet;
struct ipc_task_queue ipc_queue;
};
/**
* ipc_task_init - Allocate a tasklet
* @ipc_task: Pointer to ipc_task structure
* Returns: 0 on success and failure value on error.
*/
int ipc_task_init(struct ipc_task *ipc_task);
/**
* ipc_task_deinit - Free a tasklet, invalidating its pointer.
* @ipc_task: Pointer to ipc_task structure
*/
void ipc_task_deinit(struct ipc_task *ipc_task);
/**
* ipc_task_queue_send_task - Synchronously/Asynchronously call a function in
* tasklet context.
* @imem: Pointer to iosm_imem struct
* @func: Function to be called in tasklet context
* @arg: Integer argument for func
* @msg: Message pointer argument for func
* @size: Size argument for func
* @wait: if true wait for result
*
* Returns: Result value returned by func or failure value if func could not
* be called.
*/
int ipc_task_queue_send_task(struct iosm_imem *imem,
int (*func)(struct iosm_imem *ipc_imem, int arg,
void *msg, size_t size),
int arg, void *msg, size_t size, bool wait);
#endif
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#include <linux/wwan.h>
#include "iosm_ipc_trace.h"
/* sub buffer size and number of sub buffer */
#define IOSM_TRC_SUB_BUFF_SIZE 131072
#define IOSM_TRC_N_SUB_BUFF 32
#define IOSM_TRC_FILE_PERM 0600
#define IOSM_TRC_DEBUGFS_TRACE "trace"
#define IOSM_TRC_DEBUGFS_TRACE_CTRL "trace_ctrl"
/**
* ipc_trace_port_rx - Receive trace packet from cp and write to relay buffer
* @ipc_imem: Pointer to iosm_imem structure
* @skb: Pointer to struct sk_buff
*/
void ipc_trace_port_rx(struct iosm_imem *ipc_imem, struct sk_buff *skb)
{
struct iosm_trace *ipc_trace = ipc_imem->trace;
if (ipc_trace->ipc_rchan)
relay_write(ipc_trace->ipc_rchan, skb->data, skb->len);
dev_kfree_skb(skb);
}
/* Creates relay file in debugfs. */
static struct dentry *
ipc_trace_create_buf_file_handler(const char *filename,
struct dentry *parent,
umode_t mode,
struct rchan_buf *buf,
int *is_global)
{
*is_global = 1;
return debugfs_create_file(filename, mode, parent, buf,
&relay_file_operations);
}
/* Removes relay file from debugfs. */
static int ipc_trace_remove_buf_file_handler(struct dentry *dentry)
{
debugfs_remove(dentry);
return 0;
}
static int ipc_trace_subbuf_start_handler(struct rchan_buf *buf, void *subbuf,
void *prev_subbuf)
{
if (relay_buf_full(buf)) {
pr_err_ratelimited("Relay_buf full dropping traces");
return 0;
}
return 1;
}
/* Relay interface callbacks */
static struct rchan_callbacks relay_callbacks = {
.subbuf_start = ipc_trace_subbuf_start_handler,
.create_buf_file = ipc_trace_create_buf_file_handler,
.remove_buf_file = ipc_trace_remove_buf_file_handler,
};
/* Copy the trace control mode to user buffer */
static ssize_t ipc_trace_ctrl_file_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct iosm_trace *ipc_trace = filp->private_data;
char buf[16];
int len;
mutex_lock(&ipc_trace->trc_mutex);
len = snprintf(buf, sizeof(buf), "%d\n", ipc_trace->mode);
mutex_unlock(&ipc_trace->trc_mutex);
return simple_read_from_buffer(buffer, count, ppos, buf, len);
}
/* Open and close the trace channel depending on user input */
static ssize_t ipc_trace_ctrl_file_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct iosm_trace *ipc_trace = filp->private_data;
unsigned long val;
int ret;
ret = kstrtoul_from_user(buffer, count, 10, &val);
if (ret)
return ret;
mutex_lock(&ipc_trace->trc_mutex);
if (val == TRACE_ENABLE && ipc_trace->mode != TRACE_ENABLE) {
ipc_trace->channel = ipc_imem_sys_port_open(ipc_trace->ipc_imem,
ipc_trace->chl_id,
IPC_HP_CDEV_OPEN);
if (!ipc_trace->channel) {
ret = -EIO;
goto unlock;
}
ipc_trace->mode = TRACE_ENABLE;
} else if (val == TRACE_DISABLE && ipc_trace->mode != TRACE_DISABLE) {
ipc_trace->mode = TRACE_DISABLE;
/* close trace channel */
ipc_imem_sys_port_close(ipc_trace->ipc_imem,
ipc_trace->channel);
relay_flush(ipc_trace->ipc_rchan);
}
ret = count;
unlock:
mutex_unlock(&ipc_trace->trc_mutex);
return ret;
}
static const struct file_operations ipc_trace_fops = {
.open = simple_open,
.write = ipc_trace_ctrl_file_write,
.read = ipc_trace_ctrl_file_read,
};
/**
* ipc_trace_init - Create trace interface & debugfs entries
* @ipc_imem: Pointer to iosm_imem structure
*
* Returns: Pointer to trace instance on success else NULL
*/
struct iosm_trace *ipc_trace_init(struct iosm_imem *ipc_imem)
{
struct ipc_chnl_cfg chnl_cfg = { 0 };
struct iosm_trace *ipc_trace;
ipc_chnl_cfg_get(&chnl_cfg, IPC_MEM_CTRL_CHL_ID_3);
ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, chnl_cfg,
IRQ_MOD_OFF);
ipc_trace = kzalloc_obj(*ipc_trace);
if (!ipc_trace)
return NULL;
ipc_trace->mode = TRACE_DISABLE;
ipc_trace->dev = ipc_imem->dev;
ipc_trace->ipc_imem = ipc_imem;
ipc_trace->chl_id = IPC_MEM_CTRL_CHL_ID_3;
mutex_init(&ipc_trace->trc_mutex);
ipc_trace->ctrl_file = debugfs_create_file(IOSM_TRC_DEBUGFS_TRACE_CTRL,
IOSM_TRC_FILE_PERM,
ipc_imem->debugfs_dir,
ipc_trace, &ipc_trace_fops);
ipc_trace->ipc_rchan = relay_open(IOSM_TRC_DEBUGFS_TRACE,
ipc_imem->debugfs_dir,
IOSM_TRC_SUB_BUFF_SIZE,
IOSM_TRC_N_SUB_BUFF,
&relay_callbacks, NULL);
return ipc_trace;
}
/**
* ipc_trace_deinit - Closing relayfs, removing debugfs entries
* @ipc_trace: Pointer to the iosm_trace data struct
*/
void ipc_trace_deinit(struct iosm_trace *ipc_trace)
{
if (!ipc_trace)
return;
debugfs_remove(ipc_trace->ctrl_file);
relay_close(ipc_trace->ipc_rchan);
mutex_destroy(&ipc_trace->trc_mutex);
kfree(ipc_trace);
}
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-2021 Intel Corporation.
*/
#ifndef IOSM_IPC_TRACE_H
#define IOSM_IPC_TRACE_H
#include <linux/debugfs.h>
#include <linux/relay.h>
#include "iosm_ipc_chnl_cfg.h"
#include "iosm_ipc_imem_ops.h"
/**
* enum trace_ctrl_mode - State of trace channel
* @TRACE_DISABLE: mode for disable trace
* @TRACE_ENABLE: mode for enable trace
*/
enum trace_ctrl_mode {
TRACE_DISABLE = 0,
TRACE_ENABLE,
};
/**
* struct iosm_trace - Struct for trace interface
* @ipc_rchan: Pointer to relay channel
* @ctrl_file: Pointer to trace control file
* @ipc_imem: Imem instance
* @dev: Pointer to device struct
* @channel: Channel instance
* @chl_id: Channel Identifier
* @trc_mutex: Mutex used for read and write mode
* @mode: Mode for enable and disable trace
*/
struct iosm_trace {
struct rchan *ipc_rchan;
struct dentry *ctrl_file;
struct iosm_imem *ipc_imem;
struct device *dev;
struct ipc_mem_channel *channel;
enum ipc_channel_id chl_id;
struct mutex trc_mutex; /* Mutex used for read and write mode */
enum trace_ctrl_mode mode;
};
#ifdef CONFIG_WWAN_DEBUGFS
static inline bool ipc_is_trace_channel(struct iosm_imem *ipc_mem, u16 chl_id)
{
return ipc_mem->trace && ipc_mem->trace->chl_id == chl_id;
}
struct iosm_trace *ipc_trace_init(struct iosm_imem *ipc_imem);
void ipc_trace_deinit(struct iosm_trace *ipc_trace);
void ipc_trace_port_rx(struct iosm_imem *ipc_imem, struct sk_buff *skb);
#else
static inline bool ipc_is_trace_channel(struct iosm_imem *ipc_mem, u16 chl_id)
{
return false;
}
static inline void ipc_trace_port_rx(struct iosm_imem *ipc_imem,
struct sk_buff *skb)
{
dev_kfree_skb(skb);
}
#endif
#endif
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/slab.h>
#include "iosm_ipc_uevent.h"
/* Update the uevent in work queue context */
static void ipc_uevent_work(struct work_struct *data)
{
struct ipc_uevent_info *info;
char *envp[2] = { NULL, NULL };
info = container_of(data, struct ipc_uevent_info, work);
envp[0] = info->uevent;
if (kobject_uevent_env(&info->dev->kobj, KOBJ_CHANGE, envp))
pr_err("uevent %s failed to sent", info->uevent);
kfree(info);
}
void ipc_uevent_send(struct device *dev, char *uevent)
{
struct ipc_uevent_info *info = kzalloc_obj(*info, GFP_ATOMIC);
if (!info)
return;
/* Initialize the kernel work queue */
INIT_WORK(&info->work, ipc_uevent_work);
/* Store the device and event information */
info->dev = dev;
snprintf(info->uevent, MAX_UEVENT_LEN, "IOSM_EVENT=%s", uevent);
/* Schedule uevent in process context using work queue */
schedule_work(&info->work);
}
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_UEVENT_H
#define IOSM_IPC_UEVENT_H
/* Baseband event strings */
#define UEVENT_MDM_NOT_READY "MDM_NOT_READY"
#define UEVENT_ROM_READY "ROM_READY"
#define UEVENT_MDM_READY "MDM_READY"
#define UEVENT_CRASH "CRASH"
#define UEVENT_CD_READY "CD_READY"
#define UEVENT_CD_READY_LINK_DOWN "CD_READY_LINK_DOWN"
#define UEVENT_MDM_TIMEOUT "MDM_TIMEOUT"
/* Maximum length of user events */
#define MAX_UEVENT_LEN 64
/**
* struct ipc_uevent_info - Uevent information structure.
* @dev: Pointer to device structure
* @uevent: Uevent information
* @work: Uevent work struct
*/
struct ipc_uevent_info {
struct device *dev;
char uevent[MAX_UEVENT_LEN];
struct work_struct work;
};
/**
* ipc_uevent_send - Send modem event to user space.
* @dev: Generic device pointer
* @uevent: Uevent information
*
*/
void ipc_uevent_send(struct device *dev, char *uevent);
#endif
@@ -0,0 +1,316 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-21 Intel Corporation.
*/
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/if_link.h>
#include <linux/rtnetlink.h>
#include <linux/wwan.h>
#include <net/pkt_sched.h>
#include "iosm_ipc_chnl_cfg.h"
#include "iosm_ipc_imem_ops.h"
#include "iosm_ipc_wwan.h"
#define IOSM_IP_TYPE_MASK 0xF0
#define IOSM_IP_TYPE_IPV4 0x40
#define IOSM_IP_TYPE_IPV6 0x60
/**
* struct iosm_netdev_priv - netdev WWAN driver specific private data
* @ipc_wwan: Pointer to iosm_wwan struct
* @netdev: Pointer to network interface device structure
* @if_id: Interface id for device.
* @ch_id: IPC channel number for which interface device is created.
*/
struct iosm_netdev_priv {
struct iosm_wwan *ipc_wwan;
struct net_device *netdev;
int if_id;
int ch_id;
};
/**
* struct iosm_wwan - This structure contains information about WWAN root device
* and interface to the IPC layer.
* @ipc_imem: Pointer to imem data-struct
* @sub_netlist: List of active netdevs
* @dev: Pointer device structure
*/
struct iosm_wwan {
struct iosm_imem *ipc_imem;
struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1];
struct device *dev;
};
/* Bring-up the wwan net link */
static int ipc_wwan_link_open(struct net_device *netdev)
{
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
int if_id = priv->if_id;
if (if_id < IP_MUX_SESSION_START ||
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
return -EINVAL;
/* get channel id */
priv->ch_id = ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id);
if (priv->ch_id < 0) {
dev_err(ipc_wwan->dev,
"cannot connect wwan0 & id %d to the IPC mem layer",
if_id);
return -ENODEV;
}
/* enable tx path, DL data may follow */
netif_start_queue(netdev);
dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d",
priv->ch_id, priv->if_id);
return 0;
}
/* Bring-down the wwan net link */
static int ipc_wwan_link_stop(struct net_device *netdev)
{
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
netif_stop_queue(netdev);
ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id,
priv->ch_id);
priv->ch_id = -1;
return 0;
}
/* Transmit a packet */
static netdev_tx_t ipc_wwan_link_transmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
unsigned int len = skb->len;
int if_id = priv->if_id;
int ret;
/* Interface IDs from 1 to 8 are for IP data
* & from 257 to 261 are for non-IP data
*/
if (if_id < IP_MUX_SESSION_START ||
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
return -EINVAL;
/* Send the SKB to device for transmission */
ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem,
if_id, priv->ch_id, skb);
/* Return code of zero is success */
if (ret == 0) {
netdev->stats.tx_packets++;
netdev->stats.tx_bytes += len;
ret = NETDEV_TX_OK;
} else if (ret == -EBUSY) {
ret = NETDEV_TX_BUSY;
dev_err(ipc_wwan->dev, "unable to push packets");
} else {
goto exit;
}
return ret;
exit:
/* Log any skb drop */
if (if_id)
dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id,
ret);
dev_kfree_skb_any(skb);
netdev->stats.tx_dropped++;
return NETDEV_TX_OK;
}
/* Ops structure for wwan net link */
static const struct net_device_ops ipc_inm_ops = {
.ndo_open = ipc_wwan_link_open,
.ndo_stop = ipc_wwan_link_stop,
.ndo_start_xmit = ipc_wwan_link_transmit,
};
/* Setup function for creating new net link */
static void ipc_wwan_setup(struct net_device *iosm_dev)
{
iosm_dev->header_ops = NULL;
iosm_dev->hard_header_len = 0;
iosm_dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
iosm_dev->type = ARPHRD_NONE;
iosm_dev->mtu = ETH_DATA_LEN;
iosm_dev->min_mtu = ETH_MIN_MTU;
iosm_dev->max_mtu = ETH_MAX_MTU;
iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP;
iosm_dev->needs_free_netdev = true;
iosm_dev->netdev_ops = &ipc_inm_ops;
}
/* Create new wwan net link */
static int ipc_wwan_newlink(void *ctxt, struct net_device *dev,
u32 if_id, struct netlink_ext_ack *extack)
{
struct iosm_wwan *ipc_wwan = ctxt;
struct iosm_netdev_priv *priv;
int err;
if (if_id < IP_MUX_SESSION_START ||
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
return -EINVAL;
priv = wwan_netdev_drvpriv(dev);
priv->if_id = if_id;
priv->netdev = dev;
priv->ipc_wwan = ipc_wwan;
if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id]))
return -EBUSY;
err = register_netdevice(dev);
if (err)
return err;
rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv);
netif_device_attach(dev);
return 0;
}
static void ipc_wwan_dellink(void *ctxt, struct net_device *dev,
struct list_head *head)
{
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev);
struct iosm_wwan *ipc_wwan = ctxt;
int if_id = priv->if_id;
if (WARN_ON(if_id < IP_MUX_SESSION_START ||
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)))
return;
if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv))
return;
RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL);
/* unregistering includes synchronize_net() */
unregister_netdevice_queue(dev, head);
}
static const struct wwan_ops iosm_wwan_ops = {
.priv_size = sizeof(struct iosm_netdev_priv),
.setup = ipc_wwan_setup,
.newlink = ipc_wwan_newlink,
.dellink = ipc_wwan_dellink,
};
int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg,
bool dss, int if_id)
{
struct sk_buff *skb = skb_arg;
struct net_device_stats *stats;
struct iosm_netdev_priv *priv;
int ret;
if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4)
skb->protocol = htons(ETH_P_IP);
else if ((skb->data[0] & IOSM_IP_TYPE_MASK) ==
IOSM_IP_TYPE_IPV6)
skb->protocol = htons(ETH_P_IPV6);
skb->pkt_type = PACKET_HOST;
if (if_id < IP_MUX_SESSION_START ||
if_id > IP_MUX_SESSION_END) {
ret = -EINVAL;
goto free;
}
rcu_read_lock();
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]);
if (!priv) {
ret = -EINVAL;
goto unlock;
}
skb->dev = priv->netdev;
stats = &priv->netdev->stats;
stats->rx_packets++;
stats->rx_bytes += skb->len;
ret = netif_rx(skb);
skb = NULL;
unlock:
rcu_read_unlock();
free:
dev_kfree_skb(skb);
return ret;
}
void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on)
{
struct net_device *netdev;
struct iosm_netdev_priv *priv;
bool is_tx_blk;
rcu_read_lock();
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]);
if (!priv) {
rcu_read_unlock();
return;
}
netdev = priv->netdev;
is_tx_blk = netif_queue_stopped(netdev);
if (on)
dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable",
if_id);
if (on && !is_tx_blk)
netif_stop_queue(netdev);
else if (!on && is_tx_blk)
netif_wake_queue(netdev);
rcu_read_unlock();
}
struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev)
{
struct iosm_wwan *ipc_wwan;
ipc_wwan = kzalloc_obj(*ipc_wwan);
if (!ipc_wwan)
return NULL;
ipc_wwan->dev = dev;
ipc_wwan->ipc_imem = ipc_imem;
/* WWAN core will create a netdev for the default IP MUX channel */
if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan,
IP_MUX_SESSION_DEFAULT)) {
kfree(ipc_wwan);
return NULL;
}
return ipc_wwan;
}
void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan)
{
/* This call will remove all child netdev(s) */
wwan_unregister_ops(ipc_wwan->dev);
kfree(ipc_wwan);
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2020-21 Intel Corporation.
*/
#ifndef IOSM_IPC_WWAN_H
#define IOSM_IPC_WWAN_H
/**
* ipc_wwan_init - Allocate, Init and register WWAN device
* @ipc_imem: Pointer to imem data-struct
* @dev: Pointer to device structure
*
* Returns: Pointer to instance on success else NULL
*/
struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev);
/**
* ipc_wwan_deinit - Unregister and free WWAN device, clear pointer
* @ipc_wwan: Pointer to wwan instance data
*/
void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan);
/**
* ipc_wwan_receive - Receive a downlink packet from CP.
* @ipc_wwan: Pointer to wwan instance
* @skb_arg: Pointer to struct sk_buff
* @dss: Set to true if interafce id is from 257 to 261,
* else false
* @if_id: Interface ID
*
* Return: 0 on success and failure value on error
*/
int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg,
bool dss, int if_id);
/**
* ipc_wwan_tx_flowctrl - Enable/Disable TX flow control
* @ipc_wwan: Pointer to wwan instance
* @id: Ipc mux channel session id
* @on: if true then flow ctrl would be enabled else disable
*
*/
void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int id, bool on);
#endif