diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index f3d2924f07fc..ecf2cb7806d1 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -572,4 +572,16 @@ config QCOM_WATCHDOG deadlocks. It does not run during the bootup process, so it will not catch any early lockups. +config MSM_SPCOM + depends on QCOM_GLINK + bool "Secure Processor Communication over RPMSG" + help + SPCOM driver allows loading Secure Processor Applications and sending + messages to Secure Processor Applications. SPCOM implements logic of RPMSG + client of SPSS edge. SPCOM provides interface to both user space app and + kernel driver. It is using glink as the transport layer, which provides + multiple logical channels over single physical channel. The physical layer + is based on shared memory and interrupts. SPCOM provides clients/server API + although currently only one client/server is allowed per logical channel. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 43efcd69ff28..a911398e55ee 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -56,4 +56,5 @@ obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpmh_master_stat.o obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o +obj-$(CONFIG_MSM_SPCOM) += spcom.o obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o diff --git a/drivers/soc/qcom/spcom.c b/drivers/soc/qcom/spcom.c new file mode 100644 index 000000000000..8b0e7bacb5a5 --- /dev/null +++ b/drivers/soc/qcom/spcom.c @@ -0,0 +1,2545 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ + +/* + * Secure-Processor-Communication (SPCOM). + * + * This driver provides communication to Secure Processor (SP) + * over RPMSG framework. + * + * It provides interface to userspace spcomlib. + * + * Userspace application shall use spcomlib for communication with SP. + * Userspace application can be either client or server. spcomlib shall + * use write() file operation to send data, and read() file operation + * to read data. + * + * This driver uses RPMSG with glink-spss as a transport layer. + * This driver exposes "/dev/" file node for each rpmsg + * logical channel. + * This driver exposes "/dev/spcom" file node for some debug/control command. + * The predefined channel "/dev/sp_kernel" is used for loading SP application + * from HLOS. + * This driver exposes "/dev/sp_ssr" file node to allow user space poll for SSR. + * After the remote SP App is loaded, this driver exposes a new file node + * "/dev/" for the matching HLOS App to use. + * The access to predefined file nodes and dynamically allocated file nodes is + * restricted by using unix group and SELinux. + * + * No message routing is used, but using the rpmsg/G-Link "multiplexing" feature + * to use a dedicated logical channel for HLOS and SP Application-Pair. + * + * Each HLOS/SP Application can be either Client or Server or both, + * Messaging is allways point-to-point between 2 HLOS<=>SP applications. + * Each channel exclusevly used by single Client or Server. + * + * User Space Request & Response are synchronous. + * read() & write() operations are blocking until completed or terminated. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include /* min() */ +#include /* MODULE_LICENSE */ +#include /* class_create() */ +#include /* kzalloc() */ +#include /* file_operations */ +#include /* cdev_add() */ +#include /* EINVAL, ETIMEDOUT */ +#include /* pr_err() */ +#include /* BIT(x) */ +#include /* wait_for_completion_timeout() */ +#include /* POLLOUT */ +#include +#include /* of_property_count_strings() */ +#include +#include /* msleep() */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Request buffer size. + * Any large data (multiply of 4KB) is provided by temp buffer in DDR. + * Request shall provide the temp buffer physical address (align to 4KB). + * Maximum request/response size of 268 is used to accommodate APDU size. + * From kernel spcom driver perspective a PAGE_SIZE of 4K + * is the actual maximum size for a single read/write file operation. + */ +#define SPCOM_MAX_RESPONSE_SIZE 268 + +/* SPCOM driver name */ +#define DEVICE_NAME "spcom" + +/* maximum clients that can register over a single channel */ +#define SPCOM_MAX_CHANNEL_CLIENTS 2 + +/* maximum ION buffers should be >= SPCOM_MAX_CHANNELS */ +#define SPCOM_MAX_ION_BUF_PER_CH (SPCOM_MAX_CHANNELS + 4) + +/* maximum ION buffer per send request/response command */ +#define SPCOM_MAX_ION_BUF_PER_CMD SPCOM_MAX_ION_BUF + +/* Maximum command size */ +#define SPCOM_MAX_COMMAND_SIZE (PAGE_SIZE) + +/* Maximum input size */ +#define SPCOM_MAX_READ_SIZE (PAGE_SIZE) + +/* Current Process ID */ +#define current_pid() ((u32)(current->pid)) + +/* + * After both sides get CONNECTED, + * there is a race between one side queueing rx buffer and the other side + * trying to call glink_tx() , this race is only on the 1st tx. + * Do tx retry with some delay to allow the other side to queue rx buffer. + */ +#define TX_RETRY_DELAY_MSEC 100 + +/* SPCOM_MAX_REQUEST_SIZE-or-SPCOM_MAX_RESPONSE_SIZE + header */ +#define SPCOM_RX_BUF_SIZE 300 + +/* + * Initial transaction id, use non-zero nonce for debug. + * Incremented by client on request, and copied back by server on response. + */ +#define INITIAL_TXN_ID 0x12345678 + +/** + * struct spcom_msg_hdr - Request/Response message header between HLOS and SP. + * + * This header is proceeding any request specific parameters. + * The transaction id is used to match request with response. + * Note: rpmsg API provides the rx/tx data size, so user payload size is + * calculated by reducing the header size. + */ +struct spcom_msg_hdr { + uint32_t reserved; /* for future use */ + uint32_t txn_id; /* transaction id */ + char buf[0]; /* Variable buffer size, must be last field */ +} __packed; + +/** + * struct spcom_client - Client handle + */ +struct spcom_client { + struct spcom_channel *ch; +}; + +/** + * struct spcom_server - Server handle + */ +struct spcom_server { + struct spcom_channel *ch; +}; + +/** + * struct spcom_channel - channel context + */ +struct spcom_channel { + char name[SPCOM_CHANNEL_NAME_SIZE]; + struct mutex lock; + uint32_t txn_id; /* incrementing nonce per client request */ + bool is_server; /* for txn_id and response_timeout_msec */ + bool comm_role_undefined; /* is true on channel creation, before */ + /* first tx/rx on channel */ + uint32_t response_timeout_msec; /* for client only */ + + /* char dev */ + struct cdev *cdev; + struct device *dev; + struct device_attribute attr; + + /* rpmsg */ + struct rpmsg_driver *rpdrv; + struct rpmsg_device *rpdev; + + /* Events notification */ + struct completion rx_done; + struct completion connect; + + /** + * Only one client or server per non-sharable channel . + * SPCOM_MAX_CHANNEL_CLIENTS clients for sharable channel + * Only one tx-rx transaction at a time (request + response). + */ + bool is_busy; + bool is_sharable; /* channel's sharable property */ + u32 active_pid; /* current tx-rx transaction pid */ + uint8_t num_clients; /* current number of clients */ + struct mutex shared_sync_lock; + + u32 pid[SPCOM_MAX_CHANNEL_CLIENTS]; + + /* abort flags */ + bool rpmsg_abort; + + /* rx data info */ + size_t actual_rx_size; /* actual data size received */ + void *rpmsg_rx_buf; + + /** + * to track if rx_buf is read in the same session + * in which it is updated + */ + uint32_t rx_buf_txn_id; + + /* shared buffer lock/unlock support */ + int dmabuf_fd_table[SPCOM_MAX_ION_BUF_PER_CH]; + struct dma_buf *dmabuf_handle_table[SPCOM_MAX_ION_BUF_PER_CH]; +}; + +/** + * struct rx_buff_list - holds rx rpmsg data, before it will be consumed + * by spcom_signal_rx_done worker, item per rx packet + */ +struct rx_buff_list { + struct list_head list; + + void *rpmsg_rx_buf; + int rx_buf_size; + struct spcom_channel *ch; +}; + +/** + * struct spcom_device - device state structure. + */ +struct spcom_device { + char predefined_ch_name[SPCOM_MAX_CHANNELS][SPCOM_CHANNEL_NAME_SIZE]; + + /* char device info */ + struct cdev cdev; + dev_t device_no; + struct class *driver_class; + struct device *class_dev; + struct platform_device *pdev; + + /* rpmsg channels */ + struct spcom_channel channels[SPCOM_MAX_CHANNELS]; + atomic_t chdev_count; + + struct completion rpmsg_state_change; + atomic_t rpmsg_dev_count; + + /* rx data path */ + struct list_head rx_list_head; + spinlock_t rx_lock; + + int32_t nvm_ion_fd; + struct mutex ioctl_lock; +}; + +/* Device Driver State */ +static struct spcom_device *spcom_dev; + +/* Physical address of SP2SOC RMB shared register */ +/* SP_SCSR_RMB_SP2SOC_IRQ_SET_ADDR */ +static u32 spcom_sp2soc_rmb_reg_addr; +/* SP_SCSR_SP2SOC_IRQ_SET_SW_INIT_DONE_BMSK */ +static u32 spcom_sp2soc_initdone_mask; +/* SP_SCSR_SP2SOC_IRQ_SET_PBL_DONE_BMSK */ +static u32 spcom_sp2soc_pbldone_mask; + +/* Physical address of SOC2SP RMB shared register */ +/* SP_SCSR_RMB_SOC2SP_IRQ_SET_ADDR */ +static u32 spcom_soc2sp_rmb_reg_addr; +/* Bit used by spcom kernel for indicating SSR to SP */ +static u32 spcom_soc2sp_rmb_sp_ssr_mask; + +/* static functions declaration */ +static int spcom_create_channel_chardev(const char *name, bool is_sharable); +static struct spcom_channel *spcom_find_channel_by_name(const char *name); +static int spcom_register_rpmsg_drv(struct spcom_channel *ch); +static int spcom_unregister_rpmsg_drv(struct spcom_channel *ch); + +/* PIL's original SSR function*/ +int (*desc_powerup)(const struct subsys_desc *) = NULL; + +/** + * spcom_is_channel_open() - channel is open on this side. + * + * Channel is fully connected, when rpmsg driver is registered and + * rpmsg device probed + */ +static inline bool spcom_is_channel_open(struct spcom_channel *ch) +{ + return ch->rpdrv != NULL; +} + +/** + * spcom_is_channel_connected() - channel is fully connected by both sides. + */ +static inline bool spcom_is_channel_connected(struct spcom_channel *ch) +{ + /* Channel must be open before it gets connected */ + if (!spcom_is_channel_open(ch)) + return false; + + return ch->rpdev != NULL; +} + +/** + * spcom_create_predefined_channels_chardev() - expose predefined channels to + * user space. + * + * Predefined channels list is provided by device tree. Typically, it is for + * known servers on remote side that are not loaded by the HLOS + */ +static int spcom_create_predefined_channels_chardev(void) +{ + int i; + int ret; + static bool is_predefined_created; + + if (is_predefined_created) + return 0; + + for (i = 0; i < SPCOM_MAX_CHANNELS; i++) { + const char *name = spcom_dev->predefined_ch_name[i]; + + if (name[0] == 0) + break; + ret = spcom_create_channel_chardev(name, false); + if (ret) { + pr_err("failed to create chardev [%s], ret [%d]\n", + name, ret); + return -EFAULT; + } + } + + is_predefined_created = true; + + return 0; +} + +/*======================================================================*/ +/* UTILITIES */ +/*======================================================================*/ + +/** + * spcom_init_channel() - initialize channel state. + * + * @ch: channel state struct pointer + * @is_sharable: whether channel is sharable + * @name: channel name + */ +static int spcom_init_channel(struct spcom_channel *ch, + bool is_sharable, + const char *name) +{ + if (!ch || !name || !name[0]) { + pr_err("invalid parameters\n"); + return -EINVAL; + } + + strlcpy(ch->name, name, SPCOM_CHANNEL_NAME_SIZE); + + init_completion(&ch->rx_done); + init_completion(&ch->connect); + + mutex_init(&ch->lock); + ch->rpdrv = NULL; + ch->rpdev = NULL; + ch->actual_rx_size = 0; + ch->is_busy = false; + ch->txn_id = INITIAL_TXN_ID; /* use non-zero nonce for debug */ + ch->rx_buf_txn_id = ch->txn_id; + memset(ch->pid, 0, sizeof(ch->pid)); + ch->rpmsg_abort = false; + ch->rpmsg_rx_buf = NULL; + ch->comm_role_undefined = true; + ch->is_sharable = is_sharable; + ch->active_pid = 0; + ch->num_clients = 0; + mutex_init(&ch->shared_sync_lock); + return 0; +} + +/** + * spcom_find_channel_by_name() - find a channel by name. + * + * @name: channel name + * + * Return: a channel state struct. + */ +static struct spcom_channel *spcom_find_channel_by_name(const char *name) +{ + int i; + + for (i = 0 ; i < ARRAY_SIZE(spcom_dev->channels); i++) { + struct spcom_channel *ch = &spcom_dev->channels[i]; + + if (strcmp(ch->name, name) == 0) + return ch; + } + + return NULL; +} + +/** + * spcom_rx() - wait for received data until timeout, unless pending rx data is + * already ready + * + * @ch: channel state struct pointer + * @buf: buffer pointer + * @size: buffer size + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_rx(struct spcom_channel *ch, + void *buf, + uint32_t size, + uint32_t timeout_msec) +{ + unsigned long jiffies = msecs_to_jiffies(timeout_msec); + long timeleft = 1; + int ret = 0; + + mutex_lock(&ch->lock); + + if (ch->rx_buf_txn_id != ch->txn_id) { + pr_debug("rpmsg_rx_buf is updated in a different session\n"); + if (ch->rpmsg_rx_buf) { + memset(ch->rpmsg_rx_buf, 0, ch->actual_rx_size); + kfree((void *)ch->rpmsg_rx_buf); + ch->rpmsg_rx_buf = NULL; + ch->actual_rx_size = 0; + } + } + + /* check for already pending data */ + if (!ch->actual_rx_size) { + reinit_completion(&ch->rx_done); + + mutex_unlock(&ch->lock); /* unlock while waiting */ + /* wait for rx response */ + pr_debug("wait for rx done, timeout_msec=%d\n", timeout_msec); + if (timeout_msec) + timeleft = wait_for_completion_interruptible_timeout( + &ch->rx_done, jiffies); + else + ret = wait_for_completion_interruptible(&ch->rx_done); + + mutex_lock(&ch->lock); + if (timeout_msec && timeleft == 0) { + ch->txn_id++; /* to drop expired rx packet later */ + pr_err("rx_done timeout expired %d ms, set txn_id=%d\n", + timeout_msec, ch->txn_id); + ret = -ETIMEDOUT; + goto exit_err; + } else if (ch->rpmsg_abort) { + pr_warn("rpmsg channel is closing\n"); + ret = -ERESTART; + goto exit_err; + } else if (ret < 0 || timeleft == -ERESTARTSYS) { + pr_debug("wait interrupted: ret=%d, timeleft=%ld\n", + ret, timeleft); + if (timeleft == -ERESTARTSYS) + ret = -ERESTARTSYS; + goto exit_err; + } else if (ch->actual_rx_size) { + pr_debug("actual_rx_size is [%zu], txn_id %d\n", + ch->actual_rx_size, ch->txn_id); + } else { + pr_err("actual_rx_size is zero\n"); + ret = -EFAULT; + goto exit_err; + } + } else { + pr_debug("pending data size [%zu], requested size [%u], ch->txn_id %d\n", + ch->actual_rx_size, size, ch->txn_id); + } + if (!ch->rpmsg_rx_buf) { + pr_err("invalid rpmsg_rx_buf\n"); + ret = -ENOMEM; + goto exit_err; + } + + size = min_t(size_t, ch->actual_rx_size, size); + memcpy(buf, ch->rpmsg_rx_buf, size); + + pr_debug("copy size [%d]\n", (int) size); + + memset(ch->rpmsg_rx_buf, 0, ch->actual_rx_size); + kfree((void *)ch->rpmsg_rx_buf); + ch->rpmsg_rx_buf = NULL; + ch->actual_rx_size = 0; + + mutex_unlock(&ch->lock); + + return size; +exit_err: + mutex_unlock(&ch->lock); + return ret; +} + +/** + * spcom_get_next_request_size() - get request size. + * already ready + * + * @ch: channel state struct pointer + * + * Server needs the size of the next request to allocate a request buffer. + * Initially used intent-request, however this complicated the remote side, + * so both sides are not using glink_tx() with INTENT_REQ anymore. + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_get_next_request_size(struct spcom_channel *ch) +{ + int size = -1; + int ret = 0; + + /* NOTE: Remote clients might not be connected yet.*/ + mutex_lock(&ch->lock); + reinit_completion(&ch->rx_done); + + /* check if already got it via callback */ + if (ch->actual_rx_size) { + pr_debug("next-req-size already ready ch [%s] size [%zu]\n", + ch->name, ch->actual_rx_size); + ret = -EFAULT; + goto exit_ready; + } + mutex_unlock(&ch->lock); /* unlock while waiting */ + + pr_debug("Wait for Rx Done, ch [%s]\n", ch->name); + ret = wait_for_completion_interruptible(&ch->rx_done); + if (ret < 0) { + pr_debug("ch [%s]:interrupted wait ret=%d\n", + ch->name, ret); + goto exit_error; + } + + mutex_lock(&ch->lock); /* re-lock after waiting */ + + if (ch->actual_rx_size == 0) { + pr_err("invalid rx size [%zu] ch [%s]\n", + ch->actual_rx_size, ch->name); + mutex_unlock(&ch->lock); + ret = -EFAULT; + goto exit_error; + } + +exit_ready: + /* actual_rx_size not exeeds SPCOM_RX_BUF_SIZE*/ + size = (int)ch->actual_rx_size; + if (size > sizeof(struct spcom_msg_hdr)) { + size -= sizeof(struct spcom_msg_hdr); + } else { + pr_err("rx size [%d] too small\n", size); + ret = -EFAULT; + mutex_unlock(&ch->lock); + goto exit_error; + } + + mutex_unlock(&ch->lock); + return size; + +exit_error: + return ret; +} + +/*======================================================================*/ +/* USER SPACE commands handling */ +/*======================================================================*/ + +/** + * spcom_handle_create_channel_command() - Handle Create Channel command from + * user space. + * + * @cmd_buf: command buffer. + * @cmd_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_create_channel_command(void *cmd_buf, int cmd_size) +{ + int ret = 0; + struct spcom_user_create_channel_command *cmd = cmd_buf; + const size_t maxlen = sizeof(cmd->ch_name); + + if (cmd_size != sizeof(*cmd)) { + pr_err("cmd_size [%d] , expected [%d]\n", + (int) cmd_size, (int) sizeof(*cmd)); + return -EINVAL; + } + + if (strnlen(cmd->ch_name, maxlen) == maxlen) { + pr_err("channel name is not NULL terminated\n"); + return -EINVAL; + } + + pr_debug("ch_name [%s]\n", cmd->ch_name); + + ret = spcom_create_channel_chardev(cmd->ch_name, cmd->is_sharable); + + return ret; +} + +/** + * spcom_local_powerup() - Helper function that causes PIL boot to skip + * powerup. This function sets the INIT DONE register. + * + * @subsys: subsystem descriptor. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_local_powerup(const struct subsys_desc *subsys) +{ + void __iomem *regs; + + regs = ioremap_nocache(spcom_sp2soc_rmb_reg_addr, sizeof(u32)); + if (!regs) + return -ENOMEM; + + writel_relaxed(spcom_sp2soc_pbldone_mask|spcom_sp2soc_initdone_mask, + regs); + iounmap(regs); + pr_debug("spcom local powerup - SPSS cold boot\n"); + return 0; +} + +/** + * spcom_local_powerup_after_fota() - SSR is not allowed after FOTA - + * might cause cryptographic erase. Reset the device + * + * @subsys: subsystem descriptor. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_local_powerup_after_fota(const struct subsys_desc *subsys) +{ + (void)subsys; + + pr_err("SSR after firmware update before calling IAR update - panic\n"); + panic("SSR after SPU firmware update\n"); + + return 0; +} + +/** + * spcom_handle_restart_sp_command() - Handle Restart SP command from + * user space. + * + * @cmd_buf: command buffer. + * @cmd_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_restart_sp_command(void *cmd_buf, int cmd_size) +{ + void *subsystem_get_retval = NULL; + struct spcom_user_restart_sp_command *cmd = cmd_buf; + struct subsys_desc *desc_p = NULL; + + if (!cmd) { + pr_err("NULL cmd_buf\n"); + return -EINVAL; + } + + if (cmd_size != sizeof(*cmd)) { + pr_err("cmd_size [%d] , expected [%d]\n", + (int) cmd_size, (int) sizeof(*cmd)); + return -EINVAL; + } + + pr_debug("restart - PIL FW loading initiated: preloaded=%d\n", + cmd->arg); + + if (cmd->arg) { + subsystem_get_retval = find_subsys_device("spss"); + if (!subsystem_get_retval) { + pr_err("restart - no device\n"); + return -ENODEV; + } + + desc_p = *(struct subsys_desc **)subsystem_get_retval; + if (!desc_p) { + pr_err("restart - no device\n"); + return -ENODEV; + } + + pr_debug("restart - Name: %s FW name: %s Depends on: %s\n", + desc_p->name, desc_p->fw_name, desc_p->pon_depends_on); + desc_powerup = desc_p->powerup; + /** + * Overwrite the subsys PIL powerup function with an spcom + * internal function which causes PIL to skip calling the + * PIL boot function. This is done because SP is already + * loaded in UEFI state and we do not want PIL to start + * loading the SP again. We still want to let PIL perform + * everything else wrt SP - hence calling the subsystem_get + * API with a spcom internal function that only writes the + * INIT DONE register on behalf of SP. Once done with this, + * we shall reset the PIL subsys power up function so that + * we let the PIL subsys to load/boot SP upon SSR + */ + desc_p->powerup = spcom_local_powerup; + } + + subsystem_get_retval = subsystem_get("spss"); + if (!subsystem_get_retval) { + pr_err("restart - unable to trigger PIL process for FW loading\n"); + return -EINVAL; + } + + if (cmd->arg) { + + /* SPU got firmware update. Don't allow SSR*/ + if (cmd->is_updated) { + desc_p->powerup = spcom_local_powerup_after_fota; + } else { + /* Reset the PIL subsystem power up function */ + desc_p->powerup = desc_powerup; + } + } + pr_debug("restart - PIL FW loading process is complete\n"); + return 0; +} + +/** + * spcom_handle_send_command() - Handle send request/response from user space. + * + * @buf: command buffer. + * @buf_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_send_command(struct spcom_channel *ch, + void *cmd_buf, int size) +{ + int ret = 0; + struct spcom_send_command *cmd = cmd_buf; + uint32_t buf_size; + void *buf; + struct spcom_msg_hdr *hdr; + void *tx_buf; + int tx_buf_size; + uint32_t timeout_msec; + int time_msec = 0; + + pr_debug("send req/resp ch [%s] size [%d]\n", ch->name, size); + + /* + * check that cmd buf size is at least struct size, + * to allow access to struct fields. + */ + if (size < sizeof(*cmd)) { + pr_err("ch [%s] invalid cmd buf\n", + ch->name); + return -EINVAL; + } + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect\n", ch->name); + return -ENOTCONN; + } + + /* parse command buffer */ + buf = &cmd->buf; + buf_size = cmd->buf_size; + timeout_msec = cmd->timeout_msec; + + /* Check param validity */ + if (buf_size > SPCOM_MAX_RESPONSE_SIZE) { + pr_err("ch [%s] invalid buf size [%d]\n", + ch->name, buf_size); + return -EINVAL; + } + if (size != sizeof(*cmd) + buf_size) { + pr_err("ch [%s] invalid cmd size [%d]\n", + ch->name, size); + return -EINVAL; + } + + /* Allocate Buffers*/ + tx_buf_size = sizeof(*hdr) + buf_size; + tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + /* Prepare Tx Buf */ + hdr = tx_buf; + + mutex_lock(&ch->lock); + if (ch->comm_role_undefined) { + pr_debug("ch [%s] send first -> it is client\n", ch->name); + ch->comm_role_undefined = false; + ch->is_server = false; + } + + if (!ch->is_server) { + ch->txn_id++; /* client sets the request txn_id */ + ch->response_timeout_msec = timeout_msec; + } + hdr->txn_id = ch->txn_id; + + /* user buf */ + memcpy(hdr->buf, buf, buf_size); + + time_msec = 0; + do { + if (ch->rpmsg_abort) { + pr_err("ch [%s] aborted\n", ch->name); + ret = -ECANCELED; + break; + } + /* may fail when RX intent not queued by SP */ + ret = rpmsg_trysend(ch->rpdev->ept, tx_buf, tx_buf_size); + if (ret == 0) + break; + time_msec += TX_RETRY_DELAY_MSEC; + mutex_unlock(&ch->lock); + msleep(TX_RETRY_DELAY_MSEC); + mutex_lock(&ch->lock); + } while ((ret == -EBUSY || ret == -EAGAIN) && time_msec < timeout_msec); + if (ret) + pr_err("ch [%s] rpmsg_trysend() error (%d), timeout_msec=%d\n", + ch->name, ret, timeout_msec); + mutex_unlock(&ch->lock); + + kfree(tx_buf); + return ret; +} + +/** + * modify_ion_addr() - replace the ION buffer virtual address with physical + * address in a request or response buffer. + * + * @buf: buffer to modify + * @buf_size: buffer size + * @ion_info: ION buffer info such as FD and offset in buffer. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int modify_ion_addr(void *buf, + uint32_t buf_size, + struct spcom_ion_info ion_info) +{ + struct dma_buf *dma_buf; + struct dma_buf_attachment *attach; + struct sg_table *sg = NULL; + dma_addr_t phy_addr = 0; + int fd, ret = 0; + uint32_t buf_offset; + char *ptr = (char *)buf; + + fd = ion_info.fd; + buf_offset = ion_info.buf_offset; + ptr += buf_offset; + + if (fd < 0) { + pr_err("invalid fd [%d]\n", fd); + return -ENODEV; + } + + if (buf_size < sizeof(uint64_t)) { + pr_err("buf size too small [%d]\n", buf_size); + return -ENODEV; + } + + if (buf_offset % sizeof(uint64_t)) + pr_debug("offset [%d] is NOT 64-bit aligned\n", buf_offset); + else + pr_debug("offset [%d] is 64-bit aligned\n", buf_offset); + + if (buf_offset > buf_size - sizeof(uint64_t)) { + pr_err("invalid buf_offset [%d]\n", buf_offset); + return -ENODEV; + } + + dma_buf = dma_buf_get(fd); + if (IS_ERR_OR_NULL(dma_buf)) { + pr_err("fail to get dma buf handle\n"); + return -EINVAL; + } + pr_debug("dma_buf handle ok\n"); + attach = dma_buf_attach(dma_buf, &spcom_dev->pdev->dev); + if (IS_ERR_OR_NULL(attach)) { + ret = PTR_ERR(attach); + pr_err("fail to attach dma buf %d\n", ret); + dma_buf_put(dma_buf); + goto mem_map_table_failed; + } + + sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); + if (IS_ERR_OR_NULL(sg)) { + ret = PTR_ERR(sg); + pr_err("fail to get sg table of dma buf %d\n", ret); + goto mem_map_table_failed; + } + if (sg->sgl) { + phy_addr = sg->sgl->dma_address; + } else { + pr_err("sgl is NULL\n"); + ret = -ENOMEM; + goto mem_map_sg_failed; + } + + /* Set the physical address at the buffer offset */ + pr_debug("ion phys addr = [0x%lx]\n", (long) phy_addr); + memcpy(ptr, &phy_addr, sizeof(phy_addr)); + +mem_map_sg_failed: + dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL); +mem_map_table_failed: + dma_buf_detach(dma_buf, attach); + dma_buf_put(dma_buf); + + return ret; +} + +/** + * spcom_handle_send_modified_command() - send a request/response with ION + * buffer address. Modify the request/response by replacing the ION buffer + * virtual address with the physical address. + * + * @ch: channel pointer + * @cmd_buf: User space command buffer + * @size: size of user command buffer + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_send_modified_command(struct spcom_channel *ch, + void *cmd_buf, int size) +{ + int ret = 0; + struct spcom_user_send_modified_command *cmd = cmd_buf; + uint32_t buf_size; + void *buf; + struct spcom_msg_hdr *hdr; + void *tx_buf; + int tx_buf_size; + struct spcom_ion_info ion_info[SPCOM_MAX_ION_BUF_PER_CMD]; + int i; + uint32_t timeout_msec; + int time_msec = 0; + + pr_debug("send req/resp ch [%s] size [%d]\n", ch->name, size); + + /* + * check that cmd buf size is at least struct size, + * to allow access to struct fields. + */ + if (size < sizeof(*cmd)) { + pr_err("ch [%s] invalid cmd buf\n", + ch->name); + return -EINVAL; + } + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect\n", ch->name); + return -ENOTCONN; + } + + /* parse command buffer */ + buf = &cmd->buf; + buf_size = cmd->buf_size; + timeout_msec = cmd->timeout_msec; + memcpy(ion_info, cmd->ion_info, sizeof(ion_info)); + + /* Check param validity */ + if (buf_size > SPCOM_MAX_RESPONSE_SIZE) { + pr_err("ch [%s] invalid buf size [%d]\n", + ch->name, buf_size); + return -EINVAL; + } + if (size != sizeof(*cmd) + buf_size) { + pr_err("ch [%s] invalid cmd size [%d]\n", + ch->name, size); + return -EINVAL; + } + + /* Allocate Buffers*/ + tx_buf_size = sizeof(*hdr) + buf_size; + tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + /* Prepare Tx Buf */ + hdr = tx_buf; + + mutex_lock(&ch->lock); + if (ch->comm_role_undefined) { + pr_debug("ch [%s] send first -> it is client\n", ch->name); + ch->comm_role_undefined = false; + ch->is_server = false; + } + if (!ch->is_server) { + ch->txn_id++; /* client sets the request txn_id */ + ch->response_timeout_msec = timeout_msec; + } + hdr->txn_id = ch->txn_id; + + /* user buf */ + memcpy(hdr->buf, buf, buf_size); + + for (i = 0 ; i < ARRAY_SIZE(ion_info) ; i++) { + if (ion_info[i].fd >= 0) { + ret = modify_ion_addr(hdr->buf, buf_size, ion_info[i]); + if (ret < 0) { + mutex_unlock(&ch->lock); + pr_err("modify_ion_addr() error [%d]\n", ret); + memset(tx_buf, 0, tx_buf_size); + kfree(tx_buf); + return -EFAULT; + } + } + } + + time_msec = 0; + do { + if (ch->rpmsg_abort) { + pr_err("ch [%s] aborted\n", ch->name); + ret = -ECANCELED; + break; + } + /* may fail when RX intent not queued by SP */ + ret = rpmsg_trysend(ch->rpdev->ept, tx_buf, tx_buf_size); + if (ret == 0) + break; + time_msec += TX_RETRY_DELAY_MSEC; + mutex_unlock(&ch->lock); + msleep(TX_RETRY_DELAY_MSEC); + mutex_lock(&ch->lock); + } while ((ret == -EBUSY || ret == -EAGAIN) && time_msec < timeout_msec); + if (ret) + pr_err("ch [%s] rpmsg_trysend() error (%d), timeout_msec=%d\n", + ch->name, ret, timeout_msec); + + mutex_unlock(&ch->lock); + memset(tx_buf, 0, tx_buf_size); + kfree(tx_buf); + return ret; +} + + +/** + * spcom_handle_lock_ion_buf_command() - Lock an shared buffer. + * + * Lock an shared buffer, prevent it from being free if the userspace App crash, + * while it is used by the remote subsystem. + */ +static int spcom_handle_lock_ion_buf_command(struct spcom_channel *ch, + void *cmd_buf, int size) +{ + struct spcom_user_command *cmd = cmd_buf; + int fd; + int i; + struct dma_buf *dma_buf; + + if (size != sizeof(*cmd)) { + pr_err("cmd size [%d] , expected [%d]\n", + (int) size, (int) sizeof(*cmd)); + return -EINVAL; + } + + if (cmd->arg > (unsigned int)INT_MAX) { + pr_err("int overflow [%u]\n", cmd->arg); + return -EINVAL; + } + fd = cmd->arg; + + dma_buf = dma_buf_get(fd); + if (IS_ERR_OR_NULL(dma_buf)) { + pr_err("fail to get dma buf handle\n"); + return -EINVAL; + } + pr_debug("dma_buf referenced ok\n"); + + /* shared buf lock doesn't involve any rx/tx data to SP. */ + mutex_lock(&ch->lock); + + /* Check if this shared buffer is already locked */ + for (i = 0 ; i < ARRAY_SIZE(ch->dmabuf_handle_table) ; i++) { + if (ch->dmabuf_handle_table[i] == dma_buf) { + pr_debug("fd [%d] shared buf is already locked\n", fd); + /* decrement back the ref count */ + mutex_unlock(&ch->lock); + dma_buf_put(dma_buf); + return -EINVAL; + } + } + + /* Store the dma_buf handle */ + for (i = 0 ; i < ARRAY_SIZE(ch->dmabuf_handle_table) ; i++) { + if (ch->dmabuf_handle_table[i] == NULL) { + ch->dmabuf_handle_table[i] = dma_buf; + ch->dmabuf_fd_table[i] = fd; + pr_debug("ch [%s] locked ion buf #%d fd [%d] dma_buf=0x%pK\n", + ch->name, i, + ch->dmabuf_fd_table[i], + ch->dmabuf_handle_table[i]); + mutex_unlock(&ch->lock); + return 0; + } + } + + mutex_unlock(&ch->lock); + /* decrement back the ref count */ + dma_buf_put(dma_buf); + pr_err("no free entry to store ion handle of fd [%d]\n", fd); + + return -EFAULT; +} + +/** + * spcom_handle_unlock_ion_buf_command() - Unlock an ION buffer. + * + * Unlock an ION buffer, let it be free, when it is no longer being used by + * the remote subsystem. + */ +static int spcom_handle_unlock_ion_buf_command(struct spcom_channel *ch, + void *cmd_buf, int size) +{ + int i; + struct spcom_user_command *cmd = cmd_buf; + int fd; + bool found = false; + struct dma_buf *dma_buf; + + if (size != sizeof(*cmd)) { + pr_err("cmd size [%d], expected [%d]\n", + (int)size, (int)sizeof(*cmd)); + return -EINVAL; + } + if (cmd->arg > (unsigned int)INT_MAX) { + pr_err("int overflow [%u]\n", cmd->arg); + return -EINVAL; + } + fd = cmd->arg; + + pr_debug("Unlock ion buf ch [%s] fd [%d]\n", ch->name, fd); + + dma_buf = dma_buf_get(fd); + if (IS_ERR_OR_NULL(dma_buf)) { + pr_err("fail to get dma buf handle\n"); + return -EINVAL; + } + dma_buf_put(dma_buf); + pr_debug("dma_buf referenced ok\n"); + + /* shared buf unlock doesn't involve any rx/tx data to SP. */ + mutex_lock(&ch->lock); + if (fd == (int) SPCOM_ION_FD_UNLOCK_ALL) { + pr_debug("unlocked ALL ion buf ch [%s]\n", ch->name); + found = true; + /* unlock all buf */ + for (i = 0; i < ARRAY_SIZE(ch->dmabuf_handle_table); i++) { + if (ch->dmabuf_handle_table[i] != NULL) { + pr_debug("unlocked ion buf #%d fd [%d]\n", + i, ch->dmabuf_fd_table[i]); + dma_buf_put(ch->dmabuf_handle_table[i]); + ch->dmabuf_handle_table[i] = NULL; + ch->dmabuf_fd_table[i] = -1; + } + } + } else { + /* unlock specific buf */ + for (i = 0 ; i < ARRAY_SIZE(ch->dmabuf_handle_table) ; i++) { + if (!ch->dmabuf_handle_table[i]) + continue; + if (ch->dmabuf_handle_table[i] == dma_buf) { + pr_debug("ch [%s] unlocked ion buf #%d fd [%d] dma_buf=0x%pK\n", + ch->name, i, + ch->dmabuf_fd_table[i], + ch->dmabuf_handle_table[i]); + dma_buf_put(ch->dmabuf_handle_table[i]); + ch->dmabuf_handle_table[i] = NULL; + ch->dmabuf_fd_table[i] = -1; + found = true; + break; + } + } + } + mutex_unlock(&ch->lock); + + if (!found) { + pr_err("ch [%s] fd [%d] was not found\n", ch->name, fd); + return -ENODEV; + } + + return 0; +} + +/** + * spcom_handle_enable_ssr_command() - Handle user space request to enable ssr + * + * After FOTA SSR is disabled until IAR update occurs. + * Then - enable SSR again + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_handle_enable_ssr_command(void) +{ + struct subsys_desc *desc_p = NULL; + void *subsystem_get_retval = find_subsys_device("spss"); + + if (!subsystem_get_retval) { + pr_err("restart - no device\n"); + return -ENODEV; + } + + desc_p = *(struct subsys_desc **)subsystem_get_retval; + if (!desc_p) { + pr_err("restart - no device\n"); + return -ENODEV; + } + + if (!desc_powerup) { + pr_err("no original SSR function\n"); + return -ENODEV; + } + + desc_p->powerup = desc_powerup; + pr_info("SSR is enabled after FOTA\n"); + + return 0; +} + +/** + * spcom_handle_write() - Handle user space write commands. + * + * @buf: command buffer. + * @buf_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_write(struct spcom_channel *ch, + void *buf, + int buf_size) +{ + int ret = 0; + struct spcom_user_command *cmd = NULL; + int cmd_id = 0; + + /* Minimal command should have command-id and argument */ + if (buf_size < sizeof(struct spcom_user_command)) { + pr_err("Command buffer size [%d] too small\n", buf_size); + return -EINVAL; + } + + cmd = (struct spcom_user_command *)buf; + cmd_id = (int) cmd->cmd_id; + + pr_debug("cmd_id [0x%x]\n", cmd_id); + + if (!ch && cmd_id != SPCOM_CMD_CREATE_CHANNEL + && cmd_id != SPCOM_CMD_RESTART_SP + && cmd_id != SPCOM_CMD_ENABLE_SSR) { + pr_err("channel context is null\n"); + return -EINVAL; + } + + switch (cmd_id) { + case SPCOM_CMD_SEND: + if (ch->is_sharable) { + /* Channel shared, mutex protect TxRx */ + mutex_lock(&ch->shared_sync_lock); + /* pid indicates the current active ch */ + ch->active_pid = current_pid(); + } + ret = spcom_handle_send_command(ch, buf, buf_size); + break; + case SPCOM_CMD_SEND_MODIFIED: + if (ch->is_sharable) { + /* Channel shared, mutex protect TxRx */ + mutex_lock(&ch->shared_sync_lock); + /* pid indicates the current active ch */ + ch->active_pid = current_pid(); + } + ret = spcom_handle_send_modified_command(ch, buf, buf_size); + break; + case SPCOM_CMD_LOCK_ION_BUF: + ret = spcom_handle_lock_ion_buf_command(ch, buf, buf_size); + break; + case SPCOM_CMD_UNLOCK_ION_BUF: + ret = spcom_handle_unlock_ion_buf_command(ch, buf, buf_size); + break; + case SPCOM_CMD_CREATE_CHANNEL: + ret = spcom_handle_create_channel_command(buf, buf_size); + break; + case SPCOM_CMD_RESTART_SP: + ret = spcom_handle_restart_sp_command(buf, buf_size); + break; + case SPCOM_CMD_ENABLE_SSR: + ret = spcom_handle_enable_ssr_command(); + break; + default: + pr_err("Invalid Command Id [0x%x]\n", (int) cmd->cmd_id); + ret = -EINVAL; + } + + return ret; +} + +/** + * spcom_handle_get_req_size() - Handle user space get request size command + * + * @ch: channel handle + * @buf: command buffer. + * @size: command buffer size. + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_handle_get_req_size(struct spcom_channel *ch, + void *buf, + uint32_t size) +{ + int ret = -1; + uint32_t next_req_size = 0; + + if (size < sizeof(next_req_size)) { + pr_err("buf size [%d] too small\n", (int) size); + return -EINVAL; + } + + ret = spcom_get_next_request_size(ch); + if (ret < 0) + return ret; + next_req_size = (uint32_t) ret; + + memcpy(buf, &next_req_size, sizeof(next_req_size)); + pr_debug("next_req_size [%d]\n", next_req_size); + + return sizeof(next_req_size); /* can't exceed user buffer size */ +} + +/** + * spcom_handle_read_req_resp() - Handle user space get request/response command + * + * @ch: channel handle + * @buf: command buffer. + * @size: command buffer size. + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_handle_read_req_resp(struct spcom_channel *ch, + void *buf, + uint32_t size) +{ + int ret; + struct spcom_msg_hdr *hdr; + void *rx_buf; + int rx_buf_size; + uint32_t timeout_msec = 0; /* client only */ + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect\n", ch->name); + return -ENOTCONN; + } + + /* Check param validity */ + if (size > SPCOM_MAX_RESPONSE_SIZE) { + pr_err("ch [%s] invalid size [%d]\n", + ch->name, size); + return -EINVAL; + } + + /* Allocate Buffers*/ + rx_buf_size = sizeof(*hdr) + size; + rx_buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + /* + * client response timeout depends on the request + * handling time on the remote side . + */ + if (!ch->is_server) { + timeout_msec = ch->response_timeout_msec; + pr_debug("response_timeout_msec = %d\n", (int) timeout_msec); + } + + ret = spcom_rx(ch, rx_buf, rx_buf_size, timeout_msec); + if (ret < 0) { + pr_err("rx error %d\n", ret); + goto exit_err; + } else { + size = ret; /* actual_rx_size */ + } + + hdr = rx_buf; + + if (ch->is_server) { + ch->txn_id = hdr->txn_id; + pr_debug("request txn_id [0x%x]\n", ch->txn_id); + } + + /* copy data to user without the header */ + if (size > sizeof(*hdr)) { + size -= sizeof(*hdr); + memcpy(buf, hdr->buf, size); + } else { + pr_err("rx size [%d] too small\n", size); + ret = -EFAULT; + goto exit_err; + } + + kfree(rx_buf); + return size; +exit_err: + kfree(rx_buf); + return ret; +} + +/** + * spcom_handle_read() - Handle user space read request/response or + * request-size command + * + * @ch: channel handle + * @buf: command buffer. + * @size: command buffer size. + * + * A special size SPCOM_GET_NEXT_REQUEST_SIZE, which is bigger than the max + * response/request tells the kernel that user space only need the size. + * + * Return: size in bytes on success, negative value on failure. + */ +static int spcom_handle_read(struct spcom_channel *ch, + void *buf, + uint32_t size) +{ + int ret = -1; + + if (size == SPCOM_GET_NEXT_REQUEST_SIZE) { + pr_debug("get next request size, ch [%s]\n", ch->name); + ch->is_server = true; + ret = spcom_handle_get_req_size(ch, buf, size); + } else { + pr_debug("get request/response, ch [%s]\n", ch->name); + ret = spcom_handle_read_req_resp(ch, buf, size); + } + + pr_debug("ch [%s] , size = %d\n", ch->name, size); + + return ret; +} + +/*======================================================================*/ +/* CHAR DEVICE USER SPACE INTERFACE */ +/*======================================================================*/ + +/** + * file_to_filename() - get the filename from file pointer. + * + * @filp: file pointer + * + * it is used for debug prints. + * + * Return: filename string or "unknown". + */ +static char *file_to_filename(struct file *filp) +{ + struct dentry *dentry = NULL; + char *filename = NULL; + + if (!filp || !filp->f_path.dentry) + return "unknown"; + + dentry = filp->f_path.dentry; + filename = dentry->d_iname; + + return filename; +} + +/** + * spcom_device_open() - handle channel file open() from user space. + * + * @filp: file pointer + * + * The file name (without path) is the channel name. + * Register rpmsg driver matching with channel name. + * Store the channel context in the file private date pointer for future + * read/write/close operations. + */ +static int spcom_device_open(struct inode *inode, struct file *filp) +{ + struct spcom_channel *ch; + int ret; + const char *name = file_to_filename(filp); + u32 pid = current_pid(); + int i = 0; + + pr_debug("open file [%s]\n", name); + + if (strcmp(name, "unknown") == 0) { + pr_err("name is unknown\n"); + return -EINVAL; + } + + if (strcmp(name, DEVICE_NAME) == 0) { + pr_debug("root dir skipped\n"); + return 0; + } + + if (strcmp(name, "sp_ssr") == 0) { + pr_debug("sp_ssr dev node skipped\n"); + return 0; + } + + ch = spcom_find_channel_by_name(name); + if (!ch) { + pr_err("channel %s doesn't exist, load App first\n", name); + return -ENODEV; + } + + mutex_lock(&ch->lock); + if (!spcom_is_channel_open(ch)) { + reinit_completion(&ch->connect); + /* channel was closed need to register drv again */ + ret = spcom_register_rpmsg_drv(ch); + if (ret < 0) { + pr_err("register rpmsg driver failed %d\n", ret); + mutex_unlock(&ch->lock); + return ret; + } + } + /* max number of channel clients reached */ + if (ch->is_busy) { + pr_err("channel [%s] is BUSY and has %d of clients, already in use\n", + name, ch->num_clients); + mutex_unlock(&ch->lock); + return -EBUSY; + } + + /* + * if same client trying to register again, this will fail + */ + for (i = 0; i < SPCOM_MAX_CHANNEL_CLIENTS; i++) { + if (ch->pid[i] == pid) { + pr_err("client with pid [%d] is already registered with channel[%s]\n", + pid, name); + mutex_unlock(&ch->lock); + return -EINVAL; + } + } + + if (ch->is_sharable) { + ch->num_clients++; + if (ch->num_clients >= SPCOM_MAX_CHANNEL_CLIENTS) + ch->is_busy = true; + else + ch->is_busy = false; + /* pid array has pid of all the registered client. + * If we reach here, the is_busy flag check above guarantees + * that we have atleast one non-zero pid index + */ + for (i = 0; i < SPCOM_MAX_CHANNEL_CLIENTS; i++) { + if (ch->pid[i] == 0) { + ch->pid[i] = pid; + break; + } + } + } else { + ch->num_clients = 1; + ch->is_busy = true; + /* Only first index of pid is relevant in case of + * non-shareable + */ + ch->pid[0] = pid; + } + + mutex_unlock(&ch->lock); + + filp->private_data = ch; + return 0; +} + +/** + * spcom_device_release() - handle channel file close() from user space. + * + * @filp: file pointer + * + * The file name (without path) is the channel name. + * Open the relevant glink channel. + * Store the channel context in the file private + * date pointer for future read/write/close + * operations. + */ +static int spcom_device_release(struct inode *inode, struct file *filp) +{ + struct spcom_channel *ch; + const char *name = file_to_filename(filp); + int ret = 0; + int i = 0; + + if (strcmp(name, "unknown") == 0) { + pr_err("name is unknown\n"); + return -EINVAL; + } + + if (strcmp(name, DEVICE_NAME) == 0) { + pr_debug("root dir skipped\n"); + return 0; + } + + if (strcmp(name, "sp_ssr") == 0) { + pr_debug("sp_ssr dev node skipped\n"); + return 0; + } + + ch = filp->private_data; + if (!ch) { + pr_debug("ch is NULL, file name %s\n", file_to_filename(filp)); + return -ENODEV; + } + + mutex_lock(&ch->lock); + /* channel might be already closed or disconnected */ + if (!spcom_is_channel_open(ch)) { + pr_debug("ch [%s] already closed\n", name); + mutex_unlock(&ch->lock); + return 0; + } + + for (i = 0; i < SPCOM_MAX_CHANNEL_CLIENTS; i++) { + if (ch->pid[i] == current_pid()) { + ch->pid[i] = 0; + break; + } + } + + if (ch->num_clients > 1) { + /* + * Shared client is trying to close channel, + * release the sync_lock if applicable + */ + if (ch->active_pid == current_pid()) { + pr_debug("active_pid [%x] is releasing ch [%s] sync lock\n", + ch->active_pid, name); + /* No longer the current active user of the channel */ + ch->active_pid = 0; + mutex_unlock(&ch->shared_sync_lock); + } + ch->num_clients--; + ch->is_busy = false; + mutex_unlock(&ch->lock); + return 0; + } + + ch->is_busy = false; + ch->num_clients = 0; + ch->active_pid = 0; + + if (ch->rpmsg_rx_buf) { + pr_debug("ch [%s] discarting unconsumed rx packet actual_rx_size=%zd\n", + name, ch->actual_rx_size); + kfree(ch->rpmsg_rx_buf); + ch->rpmsg_rx_buf = NULL; + } + ch->actual_rx_size = 0; + mutex_unlock(&ch->lock); + filp->private_data = NULL; + return ret; +} + +/** + * spcom_device_write() - handle channel file write() from user space. + * + * @filp: file pointer + * + * Return: On Success - same size as number of bytes to write. + * On Failure - negative value. + */ +static ssize_t spcom_device_write(struct file *filp, + const char __user *user_buff, + size_t size, loff_t *f_pos) +{ + int ret; + char *buf; + struct spcom_channel *ch; + const char *name = file_to_filename(filp); + int buf_size = 0; + + if (!user_buff || !f_pos || !filp) { + pr_err("invalid null parameters\n"); + return -EINVAL; + } + + if (*f_pos != 0) { + pr_err("offset should be zero, no sparse buffer\n"); + return -EINVAL; + } + + if (!name) { + pr_err("name is NULL\n"); + return -EINVAL; + } + pr_debug("write file [%s] size [%d] pos [%d]\n", + name, (int) size, (int) *f_pos); + + if (strcmp(name, "unknown") == 0) { + pr_err("name is unknown\n"); + return -EINVAL; + } + + if (size > SPCOM_MAX_COMMAND_SIZE) { + pr_err("size [%d] > max size [%d]\n", + (int) size, (int) SPCOM_MAX_COMMAND_SIZE); + return -EINVAL; + } + + ch = filp->private_data; + if (!ch) { + if (strcmp(name, DEVICE_NAME) != 0) { + pr_err("invalid ch pointer, command not allowed\n"); + return -EINVAL; + } + pr_debug("control device - no channel context\n"); + } else { + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect\n", ch->name); + return -ENOTCONN; + } + } + buf_size = size; /* explicit casting size_t to int */ + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + ret = copy_from_user(buf, user_buff, size); + if (ret) { + pr_err("Unable to copy from user (err %d)\n", ret); + kfree(buf); + return -EFAULT; + } + + ret = spcom_handle_write(ch, buf, buf_size); + if (ret) { + pr_err("handle command error [%d]\n", ret); + kfree(buf); + if (ch && ch->active_pid == current_pid()) { + ch->active_pid = 0; + mutex_unlock(&ch->shared_sync_lock); + } + return ret; + } + + kfree(buf); + + return size; +} + +/** + * spcom_device_read() - handle channel file read() from user space. + * + * @filp: file pointer + * + * Return: number of bytes to read on success, negative value on + * failure. + */ +static ssize_t spcom_device_read(struct file *filp, char __user *user_buff, + size_t size, loff_t *f_pos) +{ + int ret = 0; + int actual_size = 0; + char *buf; + struct spcom_channel *ch; + const char *name = file_to_filename(filp); + uint32_t buf_size = 0; + u32 cur_pid = current_pid(); + + pr_debug("read file [%s], size = %d bytes\n", name, (int) size); + + if (strcmp(name, "unknown") == 0) { + pr_err("name is unknown\n"); + return -EINVAL; + } + + if (!user_buff || !f_pos || + (size == 0) || (size > SPCOM_MAX_READ_SIZE)) { + pr_err("invalid parameters\n"); + return -EINVAL; + } + buf_size = size; /* explicit casting size_t to uint32_t */ + + ch = filp->private_data; + + if (ch == NULL) { + pr_err("invalid ch pointer, file [%s]\n", name); + return -EINVAL; + } + + if (!spcom_is_channel_open(ch)) { + pr_err("ch is not open, file [%s]\n", name); + return -EINVAL; + } + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) { + ret = -ENOMEM; + goto exit_err; + } + + ret = spcom_handle_read(ch, buf, buf_size); + if (ret < 0) { + if (ret != -ERESTARTSYS) + pr_err("read error [%d]\n", ret); + goto exit_err; + } + actual_size = ret; + if ((actual_size == 0) || (actual_size > size)) { + pr_err("invalid actual_size [%d]\n", actual_size); + ret = -EFAULT; + goto exit_err; + } + + ret = copy_to_user(user_buff, buf, actual_size); + if (ret) { + pr_err("Unable to copy to user, err = %d\n", ret); + ret = -EFAULT; + goto exit_err; + } + + kfree(buf); + pr_debug("ch [%s] ret [%d]\n", name, (int) actual_size); + + if (ch->active_pid == cur_pid) { + ch->active_pid = 0; + mutex_unlock(&ch->shared_sync_lock); + } + return actual_size; + +exit_err: + kfree(buf); + if (ch->active_pid == cur_pid) { + ch->active_pid = 0; + mutex_unlock(&ch->shared_sync_lock); + } + return ret; +} + +static inline int handle_poll(struct file *file, + struct spcom_poll_param *op) +{ + struct spcom_channel *ch; + const char *name = file_to_filename(file); + int ready = 0; + int ret = 0; + void __iomem *regs; + + pr_debug("SPCOM_POLL_STATE - wait:%d, op:%d\n", op->wait, op->cmd_id); + + switch (op->cmd_id) { + case SPCOM_LINK_STATE_REQ: + if (op->wait) { + reinit_completion(&spcom_dev->rpmsg_state_change); + ready = wait_for_completion_interruptible( + &spcom_dev->rpmsg_state_change); + pr_debug("ch [%s] link state change signaled\n", name); + regs = ioremap_nocache(spcom_soc2sp_rmb_reg_addr, + sizeof(u32)); + if (regs) { + writel_relaxed(spcom_soc2sp_rmb_sp_ssr_mask, + regs); + iounmap(regs); + } else { + pr_err("failed to set register indicating SSR\n"); + } + } + op->retval = atomic_read(&spcom_dev->rpmsg_dev_count) > 0; + break; + case SPCOM_CH_CONN_STATE_REQ: + if (strcmp(name, DEVICE_NAME) == 0) { + pr_err("invalid control device is used: %s\n", name); + return -EINVAL; + } + /* + * ch is not expected to be NULL since user must call open() + * to get FD before it can call poll(). + * open() will fail if no ch related to the char-device. + */ + ch = file->private_data; + if (!ch) { + pr_err("invalid ch pointer, file [%s]\n", name); + ret = -EINVAL; + break; + } + if (op->wait) { + reinit_completion(&ch->connect); + ready = wait_for_completion_interruptible(&ch->connect); + pr_debug("ch [%s] connect signaled\n", name); + } + mutex_lock(&ch->lock); + op->retval = (ch->rpdev != NULL); + mutex_unlock(&ch->lock); + pr_debug("ch [%s] reported retval=%d\n", name, op->retval); + break; + default: + pr_err("ch [%s] unsupported ioctl:%u\n", op->cmd_id); + ret = -EINVAL; + } + pr_debug("name=%s, retval=%d, ready=%d\n", name, op->retval, ready); + if (ready < 0) { /* wait was interrupted */ + pr_info("interrupted wait retval=%d\n", op->retval); + ret = -EINTR; + } + return ret; +} + +static long spcom_device_ioctl(struct file *file, + unsigned int ioctl, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + const char *name = file_to_filename(file); + struct spcom_poll_param op = {0}; + int ret = 0; + + if (strcmp(name, "unknown") == 0) { + pr_err("name is unknown\n"); + return -EINVAL; + } + + switch (ioctl) { + case SPCOM_SET_IONFD: + ret = get_user(spcom_dev->nvm_ion_fd, (int32_t *)arg); + break; + case SPCOM_GET_IONFD: + ret = put_user(spcom_dev->nvm_ion_fd, (int32_t *)arg); + break; + case SPCOM_POLL_STATE: + ret = copy_from_user(&op, argp, + sizeof(struct spcom_poll_param)); + if (ret) { + pr_err("Unable to copy from user [%d]\n", ret); + return -EINVAL; + } + + ret = handle_poll(file, &op); + if (ret) + return ret; + + ret = copy_to_user(argp, &op, + sizeof(struct spcom_poll_param)); + if (ret) { + pr_err("Unable to copy to user [%d]\n", ret); + return -EINVAL; + } + break; + default: + pr_err("Unsupported ioctl:%d\n", ioctl); + ret = -EINVAL; + + } + return ret; +} + +/* file operation supported from user space */ +static const struct file_operations fops = { + .read = spcom_device_read, + .write = spcom_device_write, + .open = spcom_device_open, + .release = spcom_device_release, + .unlocked_ioctl = spcom_device_ioctl, +}; + +/** + * spcom_create_channel_chardev() - Create a channel char-dev node file + * for user space interface + */ +static int spcom_create_channel_chardev(const char *name, bool is_sharable) +{ + int ret; + struct device *dev; + struct spcom_channel *ch; + dev_t devt; + struct class *cls = spcom_dev->driver_class; + struct device *parent = spcom_dev->class_dev; + void *priv; + struct cdev *cdev; + + pr_debug("Add channel [%s]\n", name); + + ch = spcom_find_channel_by_name(name); + if (ch) { + pr_err("channel [%s] already exist\n", name); + return -EBUSY; + } + + ch = spcom_find_channel_by_name(""); /* find reserved channel */ + if (!ch) { + pr_err("no free channel\n"); + return -ENODEV; + } + + ret = spcom_init_channel(ch, is_sharable, name); + if (ret < 0) { + pr_err("can't init channel %d\n", ret); + return ret; + } + + ret = spcom_register_rpmsg_drv(ch); + if (ret < 0) { + pr_err("register rpmsg driver failed %d\n", ret); + goto exit_destroy_channel; + } + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) { + ret = -ENOMEM; + goto exit_unregister_drv; + } + + devt = spcom_dev->device_no + atomic_read(&spcom_dev->chdev_count); + priv = ch; + dev = device_create(cls, parent, devt, priv, name); + if (IS_ERR(dev)) { + pr_err("device_create failed\n"); + ret = -ENODEV; + goto exit_free_cdev; + } + + cdev_init(cdev, &fops); + cdev->owner = THIS_MODULE; + + ret = cdev_add(cdev, devt, 1); + if (ret < 0) { + pr_err("cdev_add failed %d\n", ret); + ret = -ENODEV; + goto exit_destroy_device; + } + atomic_inc(&spcom_dev->chdev_count); + mutex_lock(&ch->lock); + ch->cdev = cdev; + ch->dev = dev; + mutex_unlock(&ch->lock); + + return 0; + +exit_destroy_device: + device_destroy(spcom_dev->driver_class, devt); +exit_free_cdev: + kfree(cdev); +exit_unregister_drv: + ret = spcom_unregister_rpmsg_drv(ch); + if (ret != 0) + pr_err("can't unregister rpmsg drv %d\n", ret); +exit_destroy_channel: + /* empty channel leaves free slot for next time*/ + mutex_lock(&ch->lock); + memset(ch->name, 0, SPCOM_CHANNEL_NAME_SIZE); + mutex_unlock(&ch->lock); + return -EFAULT; +} + +static int spcom_register_chardev(void) +{ + int ret; + unsigned int baseminor = 0; + unsigned int count = 1; + void *priv = spcom_dev; + + ret = alloc_chrdev_region(&spcom_dev->device_no, baseminor, count, + DEVICE_NAME); + if (ret < 0) { + pr_err("alloc_chrdev_region failed %d\n", ret); + return ret; + } + + spcom_dev->driver_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(spcom_dev->driver_class)) { + ret = -ENOMEM; + pr_err("class_create failed %d\n", ret); + goto exit_unreg_chrdev_region; + } + + spcom_dev->class_dev = device_create(spcom_dev->driver_class, NULL, + spcom_dev->device_no, priv, + DEVICE_NAME); + + if (IS_ERR(spcom_dev->class_dev)) { + pr_err("class_device_create failed %d\n", ret); + ret = -ENOMEM; + goto exit_destroy_class; + } + + cdev_init(&spcom_dev->cdev, &fops); + spcom_dev->cdev.owner = THIS_MODULE; + + ret = cdev_add(&spcom_dev->cdev, + MKDEV(MAJOR(spcom_dev->device_no), 0), + SPCOM_MAX_CHANNELS); + if (ret < 0) { + pr_err("cdev_add failed %d\n", ret); + goto exit_destroy_device; + } + + pr_debug("char device created\n"); + + return 0; + +exit_destroy_device: + device_destroy(spcom_dev->driver_class, spcom_dev->device_no); +exit_destroy_class: + class_destroy(spcom_dev->driver_class); +exit_unreg_chrdev_region: + unregister_chrdev_region(spcom_dev->device_no, 1); + return ret; +} + +static void spcom_unregister_chrdev(void) +{ + cdev_del(&spcom_dev->cdev); + device_destroy(spcom_dev->driver_class, spcom_dev->device_no); + class_destroy(spcom_dev->driver_class); + unregister_chrdev_region(spcom_dev->device_no, + atomic_read(&spcom_dev->chdev_count)); + +} + +static int spcom_parse_dt(struct device_node *np) +{ + int ret; + const char *propname = "qcom,spcom-ch-names"; + int num_ch; + int i; + const char *name; + u32 sp2soc_rmb_pbldone_bit = 0; + u32 sp2soc_rmb_initdone_bit = 0; + u32 soc2sp_rmb_sp_ssr_bit = 0; + + /* Read SP HLOS SCSR RMB IRQ register address */ + ret = of_property_read_u32(np, "qcom,spcom-sp2soc-rmb-reg-addr", + &spcom_sp2soc_rmb_reg_addr); + if (ret < 0) { + pr_err("can't get sp2soc rmb reg addr\n"); + return ret; + } + + ret = of_property_read_u32(np, "qcom,spcom-sp2soc-rmb-pbldone-bit", + &sp2soc_rmb_pbldone_bit); + if (ret < 0) { + pr_err("can't get sp2soc rmb pbl done bit\n"); + return ret; + } + + ret = of_property_read_u32(np, "qcom,spcom-sp2soc-rmb-initdone-bit", + &sp2soc_rmb_initdone_bit); + if (ret < 0) { + pr_err("can't get sp2soc rmb sw init done bit\n"); + return ret; + } + + spcom_sp2soc_pbldone_mask = BIT(sp2soc_rmb_pbldone_bit); + spcom_sp2soc_initdone_mask = BIT(sp2soc_rmb_initdone_bit); + + /* Read SOC 2 SP SCSR RMB IRQ register address */ + ret = of_property_read_u32(np, "qcom,spcom-soc2sp-rmb-reg-addr", + &spcom_soc2sp_rmb_reg_addr); + if (ret < 0) { + pr_err("can't get soc2sp rmb reg addr\n"); + return ret; + } + + ret = of_property_read_u32(np, "qcom,spcom-soc2sp-rmb-sp-ssr-bit", + &soc2sp_rmb_sp_ssr_bit); + if (ret < 0) { + pr_err("can't get soc2sp rmb SP SSR bit\n"); + return ret; + } + + spcom_soc2sp_rmb_sp_ssr_mask = BIT(soc2sp_rmb_sp_ssr_bit); + + /* Get predefined channels info */ + num_ch = of_property_count_strings(np, propname); + if (num_ch < 0) { + pr_err("wrong format of predefined channels definition [%d]\n", + num_ch); + return num_ch; + } + if (num_ch > ARRAY_SIZE(spcom_dev->predefined_ch_name)) { + pr_err("too many predefined channels [%d]\n", num_ch); + return -EINVAL; + } + + pr_debug("num of predefined channels [%d]\n", num_ch); + for (i = 0; i < num_ch; i++) { + ret = of_property_read_string_index(np, propname, i, &name); + if (ret) { + pr_err("failed to read DT channel [%d] name\n", i); + return -EFAULT; + } + strlcpy(spcom_dev->predefined_ch_name[i], + name, + sizeof(spcom_dev->predefined_ch_name[i])); + + pr_debug("found ch [%s]\n", name); + } + + return num_ch; +} + +/* + * the function is running on system workqueue context, + * processes delayed (by rpmsg rx callback) packets: + * each packet belong to its destination spcom channel ch + */ +static void spcom_signal_rx_done(struct work_struct *ignored) +{ + struct spcom_channel *ch; + struct rx_buff_list *rx_item; + struct spcom_msg_hdr *hdr; + unsigned long flags; + + spin_lock_irqsave(&spcom_dev->rx_lock, flags); + while (!list_empty(&spcom_dev->rx_list_head)) { + /* detach last entry */ + rx_item = list_last_entry(&spcom_dev->rx_list_head, + struct rx_buff_list, list); + list_del(&rx_item->list); + spin_unlock_irqrestore(&spcom_dev->rx_lock, flags); + + if (!rx_item) { + pr_err("empty entry in pending rx list\n"); + spin_lock_irqsave(&spcom_dev->rx_lock, flags); + continue; + } + ch = rx_item->ch; + hdr = (struct spcom_msg_hdr *)rx_item->rpmsg_rx_buf; + mutex_lock(&ch->lock); + + if (ch->comm_role_undefined) { + ch->comm_role_undefined = false; + ch->is_server = true; + ch->txn_id = hdr->txn_id; + pr_debug("ch [%s] first packet txn_id=%d, it is server\n", + ch->name, ch->txn_id); + } + + if (ch->rpmsg_abort) { + if (ch->rpmsg_rx_buf) { + pr_debug("ch [%s] rx aborted free %zd bytes\n", + ch->name, ch->actual_rx_size); + kfree(ch->rpmsg_rx_buf); + ch->actual_rx_size = 0; + } + goto rx_aborted; + } + if (ch->rpmsg_rx_buf) { + pr_err("ch [%s] previous buffer not consumed %zd bytes\n", + ch->name, ch->actual_rx_size); + kfree(ch->rpmsg_rx_buf); + ch->rpmsg_rx_buf = NULL; + ch->actual_rx_size = 0; + } + if (!ch->is_server && (hdr->txn_id != ch->txn_id)) { + pr_err("ch [%s] rx dropped txn_id %d, ch->txn_id %d\n", + ch->name, hdr->txn_id, ch->txn_id); + goto rx_aborted; + } + ch->rpmsg_rx_buf = rx_item->rpmsg_rx_buf; + ch->actual_rx_size = rx_item->rx_buf_size; + ch->rx_buf_txn_id = ch->txn_id; + complete_all(&ch->rx_done); + mutex_unlock(&ch->lock); + + kfree(rx_item); + + /* lock for the next list entry */ + spin_lock_irqsave(&spcom_dev->rx_lock, flags); + } + spin_unlock_irqrestore(&spcom_dev->rx_lock, flags); + return; +rx_aborted: + mutex_unlock(&ch->lock); + kfree(rx_item->rpmsg_rx_buf); + kfree(rx_item); +} + +static int spcom_rpdev_cb(struct rpmsg_device *rpdev, + void *data, int len, void *priv, u32 src) +{ + struct spcom_channel *ch; + static DECLARE_WORK(rpmsg_rx_consumer, spcom_signal_rx_done); + struct rx_buff_list *rx_item; + unsigned long flags; + + if (!rpdev || !data) { + pr_err("rpdev or data is NULL\n"); + return -EINVAL; + } + pr_debug("incoming msg from %s\n", rpdev->id.name); + ch = dev_get_drvdata(&rpdev->dev); + if (!ch) { + pr_err("%s: invalid ch\n", __func__); + return -EINVAL; + } + if (len > SPCOM_RX_BUF_SIZE || len <= 0) { + pr_err("got msg size %d, max allowed %d\n", + len, SPCOM_RX_BUF_SIZE); + return -EINVAL; + } + + rx_item = kzalloc(sizeof(*rx_item), GFP_ATOMIC); + if (!rx_item) + return -ENOMEM; + + rx_item->rpmsg_rx_buf = kmemdup(data, len, GFP_ATOMIC); + if (!rx_item->rpmsg_rx_buf) + return -ENOMEM; + + rx_item->rx_buf_size = len; + rx_item->ch = ch; + + spin_lock_irqsave(&spcom_dev->rx_lock, flags); + list_add(&rx_item->list, &spcom_dev->rx_list_head); + spin_unlock_irqrestore(&spcom_dev->rx_lock, flags); + pr_debug("signaling rx item for %s, received %d bytes\n", + rpdev->id.name, len); + + schedule_work(&rpmsg_rx_consumer); + return 0; +} + +static int spcom_rpdev_probe(struct rpmsg_device *rpdev) +{ + const char *name; + struct spcom_channel *ch; + + if (!rpdev) { + pr_err("rpdev is NULL\n"); + return -EINVAL; + } + name = rpdev->id.name; + pr_debug("new channel %s rpmsg_device arrived\n", name); + ch = spcom_find_channel_by_name(name); + if (!ch) { + pr_err("channel %s not found\n", name); + return -ENODEV; + } + mutex_lock(&ch->lock); + ch->rpdev = rpdev; + ch->rpmsg_abort = false; + ch->txn_id = INITIAL_TXN_ID; + complete_all(&ch->connect); + mutex_unlock(&ch->lock); + + dev_set_drvdata(&rpdev->dev, ch); + + /* used to evaluate underlying transport link up/down */ + atomic_inc(&spcom_dev->rpmsg_dev_count); + if (atomic_read(&spcom_dev->rpmsg_dev_count) == 1) { + pr_err("Signal link up\n"); + complete_all(&spcom_dev->rpmsg_state_change); + } + + return 0; +} + +static void spcom_rpdev_remove(struct rpmsg_device *rpdev) +{ + struct spcom_channel *ch; + int i; + + if (!rpdev) { + pr_err("rpdev is NULL\n"); + return; + } + + dev_info(&rpdev->dev, "rpmsg device %s removed\n", rpdev->id.name); + ch = dev_get_drvdata(&rpdev->dev); + if (!ch) { + pr_err("channel %s not found\n", rpdev->id.name); + return; + } + + mutex_lock(&ch->lock); + /* unlock all ion buffers of sp_kernel channel*/ + if (strcmp(ch->name, "sp_kernel") == 0) { + for (i = 0; i < ARRAY_SIZE(ch->dmabuf_handle_table); i++) { + if (ch->dmabuf_handle_table[i] != NULL) { + pr_debug("unlocked ion buf #%d fd [%d]\n", + i, ch->dmabuf_fd_table[i]); + dma_buf_put(ch->dmabuf_handle_table[i]); + ch->dmabuf_handle_table[i] = NULL; + ch->dmabuf_fd_table[i] = -1; + } + } + } + + ch->rpdev = NULL; + ch->rpmsg_abort = true; + ch->txn_id = 0; + complete_all(&ch->rx_done); + mutex_unlock(&ch->lock); + + /* used to evaluate underlying transport link up/down */ + if (atomic_dec_and_test(&spcom_dev->rpmsg_dev_count)) { + pr_err("Signal link down\n"); + complete_all(&spcom_dev->rpmsg_state_change); + } +} + +/* register rpmsg driver to match with channel ch_name */ +static int spcom_register_rpmsg_drv(struct spcom_channel *ch) +{ + struct rpmsg_driver *rpdrv; + struct rpmsg_device_id *match; + char *drv_name; + int ret; + + if (ch->rpdrv) { + pr_err("ch:%s, rpmsg driver %s already registered\n", ch->name, + ch->rpdrv->id_table->name); + return -ENODEV; + } + + rpdrv = kzalloc(sizeof(*rpdrv), GFP_KERNEL); + if (!rpdrv) + return -ENOMEM; + + /* zalloc array of two to NULL terminate the match list */ + match = kzalloc(2 * sizeof(*match), GFP_KERNEL); + if (!match) { + kfree(rpdrv); + return -ENOMEM; + } + snprintf(match->name, RPMSG_NAME_SIZE, "%s", ch->name); + + drv_name = kasprintf(GFP_KERNEL, "%s_%s", "spcom_rpmsg_drv", ch->name); + if (!drv_name) { + pr_err("can't allocate drv_name for %s\n", ch->name); + kfree(rpdrv); + kfree(match); + return -ENOMEM; + } + + rpdrv->probe = spcom_rpdev_probe; + rpdrv->remove = spcom_rpdev_remove; + rpdrv->callback = spcom_rpdev_cb; + rpdrv->id_table = match; + rpdrv->drv.name = drv_name; + ret = register_rpmsg_driver(rpdrv); + if (ret) { + pr_err("can't register rpmsg_driver for %s\n", ch->name); + kfree(rpdrv); + kfree(match); + kfree(drv_name); + return ret; + } + mutex_lock(&ch->lock); + ch->rpdrv = rpdrv; + ch->rpmsg_abort = false; + mutex_unlock(&ch->lock); + + return 0; +} + +static int spcom_unregister_rpmsg_drv(struct spcom_channel *ch) +{ + if (!ch->rpdrv) + return -ENODEV; + unregister_rpmsg_driver(ch->rpdrv); + + mutex_lock(&ch->lock); + kfree(ch->rpdrv->drv.name); + kfree((void *)ch->rpdrv->id_table); + kfree(ch->rpdrv); + ch->rpdrv = NULL; + ch->rpmsg_abort = true; /* will unblock spcom_rx() */ + mutex_unlock(&ch->lock); + return 0; +} + +static int spcom_probe(struct platform_device *pdev) +{ + int ret; + struct spcom_device *dev = NULL; + struct device_node *np; + + if (!pdev) { + pr_err("invalid pdev\n"); + return -ENODEV; + } + + np = pdev->dev.of_node; + if (!np) { + pr_err("invalid DT node\n"); + return -EINVAL; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + spcom_dev = dev; + spcom_dev->pdev = pdev; + /* start counting exposed channel char devices from 1 */ + atomic_set(&spcom_dev->chdev_count, 1); + init_completion(&spcom_dev->rpmsg_state_change); + atomic_set(&spcom_dev->rpmsg_dev_count, 0); + + INIT_LIST_HEAD(&spcom_dev->rx_list_head); + spin_lock_init(&spcom_dev->rx_lock); + spcom_dev->nvm_ion_fd = -1; + mutex_init(&spcom_dev->ioctl_lock); + + ret = spcom_register_chardev(); + if (ret) { + pr_err("create character device failed\n"); + goto fail_while_chardev_reg; + } + + ret = spcom_parse_dt(np); + if (ret < 0) + goto fail_reg_chardev; + + ret = spcom_create_predefined_channels_chardev(); + if (ret < 0) { + pr_err("create character device failed\n"); + goto fail_reg_chardev; + } + pr_debug("Driver Initialization ok\n"); + return 0; + +fail_reg_chardev: + pr_err("failed to init driver\n"); + spcom_unregister_chrdev(); +fail_while_chardev_reg: + kfree(dev); + spcom_dev = NULL; + + return -ENODEV; +} + +static const struct of_device_id spcom_match_table[] = { + { .compatible = "qcom,spcom", }, + { }, +}; + +static struct platform_driver spcom_driver = { + .probe = spcom_probe, + .driver = { + .name = DEVICE_NAME, + .of_match_table = of_match_ptr(spcom_match_table), + }, +}; + +static int __init spcom_init(void) +{ + int ret; + + ret = platform_driver_register(&spcom_driver); + if (ret) + pr_err("spcom_driver register failed %d\n", ret); + + return ret; +} +module_init(spcom_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Secure Processor Communication"); diff --git a/include/uapi/linux/spcom.h b/include/uapi/linux/spcom.h new file mode 100644 index 000000000000..65c9e238c537 --- /dev/null +++ b/include/uapi/linux/spcom.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _UAPI_SPCOM_H_ +#define _UAPI_SPCOM_H_ + +#include /* uint32_t, bool */ +#ifndef BIT + #define BIT(x) (1 << x) +#endif + +#ifndef PAGE_SIZE + #define PAGE_SIZE 4096 +#endif + +/** + * @brief - Secure Processor Communication interface to user space spcomlib. + * + * Sending data and control commands by write() file operation. + * Receiving data by read() file operation. + * Getting the next request size by read() file operation, + * with special size SPCOM_GET_NEXT_REQUEST_SIZE. + */ + +/* + * Maximum number of channel between Secure Processor and HLOS. + * including predefined channels, like "sp_kernel". + */ +#define SPCOM_MAX_CHANNELS 0x20 + +/* Maximum size (including null) for channel names */ +#define SPCOM_CHANNEL_NAME_SIZE 32 +/* + * file read(fd, buf, size) with this size, + * hints the kernel that user space wants to read the next-req-size. + * This size is bigger than both SPCOM_MAX_REQUEST_SIZE and + * SPCOM_MAX_RESPONSE_SIZE , so it is not a valid data size. + */ +#define SPCOM_GET_NEXT_REQUEST_SIZE (PAGE_SIZE-1) + +/* Command Id between spcomlib and spcom driver, on write() */ +enum spcom_cmd_id { + SPCOM_CMD_LOAD_APP = 0x4C4F4144, /* "LOAD" = 0x4C4F4144 */ + SPCOM_CMD_RESET_SP = 0x52455354, /* "REST" = 0x52455354 */ + SPCOM_CMD_SEND = 0x53454E44, /* "SEND" = 0x53454E44 */ + SPCOM_CMD_SEND_MODIFIED = 0x534E444D, /* "SNDM" = 0x534E444D */ + SPCOM_CMD_LOCK_ION_BUF = 0x4C4F434B, /* "LOCK" = 0x4C4F434B */ + SPCOM_CMD_UNLOCK_ION_BUF = 0x554C434B, /* "ULCK" = 0x4C4F434B */ + SPCOM_CMD_FSSR = 0x46535352, /* "FSSR" = 0x46535352 */ + SPCOM_CMD_CREATE_CHANNEL = 0x43524554, /* "CRET" = 0x43524554 */ + +#define SPCOM_CMD_ENABLE_SSR \ + SPCOM_CMD_ENABLE_SSR + SPCOM_CMD_ENABLE_SSR = 0x45535352, /* "ESSR" =0x45535352*/ + +#define SPCOM_CMD_RESTART_SP \ + SPCOM_CMD_RESTART_SP + SPCOM_CMD_RESTART_SP = 0x52535452, /* "RSTR" = 0x52535452 */ +}; + +/* + * @note: Event types that are always implicitly polled: + * POLLERR=0x08 | POLLHUP=0x10 | POLLNVAL=0x20 + * so bits 3,4,5 can't be used + */ +enum spcom_poll_events { + SPCOM_POLL_LINK_STATE = BIT(1), + SPCOM_POLL_CH_CONNECT = BIT(2), + SPCOM_POLL_READY_FLAG = BIT(14), /* output */ + SPCOM_POLL_WAIT_FLAG = BIT(15), /* if set , wait for the event */ +}; + +/* Common Command structure between User Space and spcom driver, on write() */ +struct spcom_user_command { + enum spcom_cmd_id cmd_id; + uint32_t arg; +} __packed; + +/* Command structure between User Space and spcom driver, on write() */ +struct spcom_send_command { + enum spcom_cmd_id cmd_id; + uint32_t timeout_msec; + uint32_t buf_size; + char buf[0]; /* Variable buffer size - must be last field */ +} __packed; + +/* Command structure between userspace spcomlib and spcom driver, on write() */ +struct spcom_user_create_channel_command { + enum spcom_cmd_id cmd_id; + char ch_name[SPCOM_CHANNEL_NAME_SIZE]; +#define SPCOM_IS_SHARABLE_SUPPORTED + bool is_sharable; +} __packed; + +/* Command structure between userspace spcomlib and spcom driver, on write() */ +#define SPCOM_USER_RESTART_SP_CMD +struct spcom_user_restart_sp_command { + enum spcom_cmd_id cmd_id; + uint32_t arg; +#define SPCOM_IS_UPDATED_SUPPORETED + uint32_t is_updated; +} __packed; + +/* maximum ION buf for send-modfied-command */ +#define SPCOM_MAX_ION_BUF 4 + +struct spcom_ion_info { + int32_t fd; /* ION buffer File Descriptor, set -1 for invalid fd */ + uint32_t buf_offset; /* virtual address offset in request/response */ +}; + +/* Pass this FD to unlock all ION buffer for the specific channel */ +#define SPCOM_ION_FD_UNLOCK_ALL 0xFFFF + +struct spcom_ion_handle { + int32_t fd; /* File Descriptor associated with the buffer */ +}; + +/* Command structure between User Space and spcom driver, on write() */ +struct spcom_user_send_modified_command { + enum spcom_cmd_id cmd_id; + struct spcom_ion_info ion_info[SPCOM_MAX_ION_BUF]; + uint32_t timeout_msec; + uint32_t buf_size; + char buf[0]; /* Variable buffer size - must be last field */ +} __packed; + +enum { + SPCOM_IONFD_CMD, + SPCOM_POLL_CMD, +}; + +enum spcom_poll_cmd_id { + SPCOM_LINK_STATE_REQ, + SPCOM_CH_CONN_STATE_REQ, +}; + +struct spcom_poll_param { + /* input parameters */ + bool wait; + enum spcom_poll_cmd_id cmd_id; + /* output parameter */ + int retval; +} __packed; + +#define SPCOM_IOCTL_MAGIC 'S' +#define SPCOM_GET_IONFD _IOR(SPCOM_IOCTL_MAGIC, SPCOM_IONFD_CMD, \ + struct spcom_ion_handle) +#define SPCOM_SET_IONFD _IOW(SPCOM_IOCTL_MAGIC, SPCOM_IONFD_CMD, \ + struct spcom_ion_handle) +#define SPCOM_POLL_STATE _IOWR(SPCOM_IOCTL_MAGIC, SPCOM_POLL_CMD, \ + struct spcom_poll_param) + +#endif /* _UAPI_SPCOM_H_ */