From 09e5b188695cf2eb58a8fdac761e40d596d48840 Mon Sep 17 00:00:00 2001 From: Amir Samuelov Date: Wed, 25 Nov 2015 11:34:17 +0200 Subject: [PATCH] soc: qcom: add secure processor communication (spcom) driver This driver supports communication with secure processor subsystem over glink transport layer. The communication is based on using shared memory and interrupts. This driver exposes interface to both kernel and user space. Change-Id: Iec5fc78c8370002643b549e43015c06b09d8ab8b Signed-off-by: Amir Samuelov --- .../devicetree/bindings/arm/msm/spcom.txt | 11 + drivers/soc/qcom/Kconfig | 13 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/spcom.c | 2423 +++++++++++++++++ include/soc/qcom/spcom.h | 266 ++ include/uapi/linux/spcom.h | 89 + 6 files changed, 2803 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/msm/spcom.txt create mode 100644 drivers/soc/qcom/spcom.c create mode 100644 include/soc/qcom/spcom.h create mode 100644 include/uapi/linux/spcom.h diff --git a/Documentation/devicetree/bindings/arm/msm/spcom.txt b/Documentation/devicetree/bindings/arm/msm/spcom.txt new file mode 100644 index 000000000000..36a07ec686ad --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/spcom.txt @@ -0,0 +1,11 @@ +Qualcomm Technologies, Inc. Secure Proccessor Communication (spcom) + +Required properties: +-compatible : should be "qcom,spcom" +-qcom,spcom-ch-names: predefined channels name string + +Example: + qcom,spcom { + compatible = "qcom,spcom"; + qcom,spcom-ch-names = "sp_kernel" , "sp_ssr"; + }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index abbcba0e9168..a24930fa7399 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -86,6 +86,19 @@ config MSM_GLINK_SMEM_NATIVE_XPRT transport to only connecting with entities internal to the System-on-Chip. +config MSM_SPCOM + depends on MSM_GLINK + bool "Secure Processor Communication over GLINK" + help + spcom driver allows loading Secure Processor Applications and + sending messages to Secure Processor Applications. + 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 signle physical channel. + The physical layer is based on shared memory and interrupts. + spcom provides clients/server API, although currently only one client + or server is allowed per logical channel. + config MSM_SMEM_LOGGING depends on MSM_SMEM bool "MSM Shared Memory Logger" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 9c3787035eda..aa3bab43af2d 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_GLINK_XPRT) += ipc_router_glink_xprt.o +obj-$(CONFIG_MSM_SPCOM) += spcom.o obj-y += qdsp6v2/ obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor_v01.o obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor.o diff --git a/drivers/soc/qcom/spcom.c b/drivers/soc/qcom/spcom.c new file mode 100644 index 000000000000..cc90f18cdd58 --- /dev/null +++ b/drivers/soc/qcom/spcom.c @@ -0,0 +1,2423 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Secure-Processor-Communication (SPCOM). + * + * This driver provides communication to Secure Processor (SP) + * over G-Link transport layer. + * + * It provides interface to both User Space spcomlib and kernel drivers. + * + * User Space App shall use spcomlib for communication with SP. + * User Space App 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 glink as the transport layer. + * This driver exposes "/dev/" file node for each glink + * 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 node is restricted by using unix group + * and SELinux. + * + * No message routing is use, but using the 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 allays point-to-point between 2 HLOS<=>SP applications. + * + * User Space Request & Response are synchronous. + * read() & write() operations are blocking until completed or terminated. + * + * This driver registers to G-Link callbacks to be aware on channel state. + * A notify callback is called upon channel connect/disconnect. + * + */ + +/* Uncomment the line below to test spcom against modem rather than SP */ +/* #define SPCOM_TEST_HLOS_WITH_MODEM 1 */ + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ + +#define pr_fmt(fmt) "spcom [%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 /* dma_alloc_coherent() */ +#include +#include /* of_property_count_strings() */ +#include +#include /* msleep() */ + +#include +#include +#include + +#include + +#include "glink_private.h" /* glink_ssr() */ + +/* "SPCM" string */ +#define SPCOM_MAGIC_ID ((uint32_t)(0x5350434D)) + +/* Request/Response */ +#define SPCOM_FLAG_REQ BIT(0) +#define SPCOM_FLAG_RESP BIT(1) +#define SPCOM_FLAG_ENCODED BIT(2) +#define SPCOM_FLAG_NON_ENCODED BIT(3) + +/* SPCOM driver name */ +#define DEVICE_NAME "spcom" + +#define SPCOM_MAX_CHANNELS 20 + +/* 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)) + +/* Maximum channel name size (including null) - matching GLINK_NAME_SIZE */ +#define MAX_CH_NAME_LEN 32 + +/* Connection negotiation timeout, if remote channel is open */ +#define OPEN_CHANNEL_TIMEOUT_MSEC 100 + +/* + * After both sides get CONNECTED, + * there is a race between once 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 + +/* number of tx retries */ +#define TX_MAX_RETRY 3 + +/* SPCOM_MAX_REQUEST_SIZE-or-SPCOM_MAX_RESPONSE_SIZE + header */ +#define SPCOM_RX_BUF_SIZE 300 + +/* The SPSS RAM size is 256 KB so SP App must fit into it */ +#define SPCOM_MAX_APP_SIZE SZ_256K + +/* ACK timeout from remote side for TX data */ +#define TX_DONE_TIMEOUT_MSEC 100 + +/* + * 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: glink 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_load_app_req - Load App Request sent to SP + * + * The application image binary is placed on DDR buffer. + * The application image is encrypted and signed. + * The DDR buffer address and size should be 4K aligned for XPU protection. + * The size of the app DDR buffer is larger than the image size to allow saving + * the app heap and stack on swap in/out. + */ +struct spcom_load_app_req { + uint32_t cmd_id; /* SPCOM_CMD_LOAD_APP */ + uint32_t image_size; + uint64_t buf_phys_addr; + char ch_name[16]; + uint32_t buf_size; +} __packed; + +/** + * struct spcom_load_app_resp - Load App Response from SP. + */ +struct spcom_load_app_resp { + uint32_t error_code; /* 0 for success, errors based on errno.h */ +} __packed; + +/** + * struct spcom_reset_cmd_req - Reset Request sent to SP + */ +struct spcom_reset_cmd_req { + uint32_t cmd_id; +} __packed; + +/** + * struct spcom_reset_resp - Reset Response from SP. + */ +struct spcom_reset_resp { + uint32_t error_code; /* 0 for success, errors based on errno.h */ +} __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[MAX_CH_NAME_LEN]; + struct mutex lock; + void *glink_handle; + uint32_t txn_id; /* incrementing nonce per channel */ + bool is_server; /* for txn_id and response_timeout_msec */ + uint32_t response_timeout_msec; /* for client only */ + + /* char dev */ + struct cdev *cdev; + struct device *dev; + struct device_attribute attr; + + /* + * glink state: CONNECTED / LOCAL_DISCONNECTED, REMOTE_DISCONNECTED + */ + unsigned glink_state; + + /* Events notification */ + struct completion connect; + struct completion disconnect; + struct completion tx_done; + struct completion rx_done; + + /* + * Only one client or server per channel. + * Only one rx/tx transaction at a time (request + response). + */ + int ref_count; + u32 pid; + + /* link UP/DOWN callback */ + void (*notify_link_state_cb)(bool up); + + /* abort flags */ + bool rx_abort; + bool tx_abort; + + /* rx data info */ + int rx_buf_size; /* allocated rx buffer size */ + bool rx_buf_ready; + int actual_rx_size; /* actual data size received */ + const void *glink_rx_buf; +}; + +/** + * struct spcom_device - device state structure. + */ +struct spcom_device { + char predefined_ch_name[SPCOM_MAX_CHANNELS][MAX_CH_NAME_LEN]; + + /* char device info */ + struct cdev cdev; + dev_t device_no; + struct class *driver_class; + struct device *class_dev; + + /* G-Link channels */ + struct spcom_channel channels[SPCOM_MAX_CHANNELS]; + int channel_count; + + /* private */ + struct mutex lock; + + /* Link state */ + struct completion link_state_changed; + enum glink_link_state link_state; +}; + +#ifdef SPCOM_TEST_HLOS_WITH_MODEM + static const char *spcom_edge = "mpss"; + static const char *spcom_transport = "smem"; +#else + static const char *spcom_edge = "spss"; + static const char *spcom_transport = "mailbox"; +#endif + +/* Device Driver State */ +static struct spcom_device *spcom_dev; + +/* static functions declaration */ +static int spcom_create_channel_chardev(const char *name); +static int spcom_open(struct spcom_channel *ch, unsigned int timeout_msec); +static int spcom_close(struct spcom_channel *ch); + +/** + * spcom_is_ready() - driver is initialized and ready. + */ +static inline bool spcom_is_ready(void) +{ + return spcom_dev != NULL; +} + +/** + * spcom_is_channel_open() - channel is open on this side. + * + * Channel might not be fully connected if remote side didn't open the channel + * yet. + */ +static inline bool spcom_is_channel_open(struct spcom_channel *ch) +{ + return ch->glink_handle != NULL; +} + +/** + * spcom_is_channel_connected() - channel is fully connected by both sides. + */ +static inline bool spcom_is_channel_connected(struct spcom_channel *ch) +{ + return (ch->glink_state == GLINK_CONNECTED); +} + +/** + * 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; + + 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); + if (ret) { + pr_err("failed to create chardev [%s], ret [%d].\n", + name, ret); + return -EFAULT; + } + } + + return 0; +} + +/*======================================================================*/ +/* GLINK CALLBACKS */ +/*======================================================================*/ + +/** + * spcom_link_state_notif_cb() - glink callback for link state change. + * + * glink notifies link layer is up, before any channel opened on remote side. + * Calling glink_open() locally allowed only after link is up. + * Notify link down, normally upon Remote Subsystem Reset (SSR). + * Note: upon SSR, glink will also notify each channel about remote disconnect, + * and abort any pending rx buffer. + */ +static void spcom_link_state_notif_cb(struct glink_link_state_cb_info *cb_info, + void *priv) +{ + spcom_dev->link_state = cb_info->link_state; + + pr_debug("spcom_link_state_notif_cb called. transport = %s edge = %s\n", + cb_info->transport, cb_info->edge); + + switch (cb_info->link_state) { + case GLINK_LINK_STATE_UP: + pr_debug("GLINK_LINK_STATE_UP.\n"); + spcom_create_predefined_channels_chardev(); + break; + case GLINK_LINK_STATE_DOWN: + pr_debug("GLINK_LINK_STATE_DOWN.\n"); + break; + default: + pr_err("unknown link_state [%d].\n", cb_info->link_state); + break; + } + complete_all(&spcom_dev->link_state_changed); +} + +/** + * spcom_notify_rx() - glink callback on receiving data. + * + * Glink notify rx data is ready. The glink internal rx buffer was + * allocated upon glink_queue_rx_intent(). + */ +static void spcom_notify_rx(void *handle, + const void *priv, const void *pkt_priv, + const void *buf, size_t size) +{ + struct spcom_channel *ch = (struct spcom_channel *) priv; + + if (!ch) { + pr_err("invalid ch parameter.\n"); + return; + } + + pr_debug("ch [%s] rx size [%d].\n", ch->name, (int) size); + + ch->actual_rx_size = (int) size; + ch->glink_rx_buf = (void *) buf; + + complete_all(&ch->rx_done); +} + +/** + * spcom_notify_tx_done() - glink callback on ACK sent data. + * + * after calling glink_tx() the remote side ACK receiving the data. + */ +static void spcom_notify_tx_done(void *handle, + const void *priv, const void *pkt_priv, + const void *buf) +{ + struct spcom_channel *ch = (struct spcom_channel *) priv; + int *tx_buf = (int *) buf; + + if (!ch) { + pr_err("invalid ch parameter.\n"); + return; + } + + pr_debug("ch [%s] buf[0] = [0x%x].\n", ch->name, tx_buf[0]); + + complete_all(&ch->tx_done); +} + +/** + * spcom_notify_state() - glink callback on channel connect/disconnect. + * + * Channel is fully CONNECTED after both sides opened the channel. + * Channel is LOCAL_DISCONNECTED after both sides closed the channel. + * If the remote side closed the channel, it is expected that the local side + * will also close the channel. + * Upon connection, rx buffer is allocated to receive data, + * the maximum transfer size is agreed by both sides. + */ +static void spcom_notify_state(void *handle, const void *priv, unsigned event) +{ + int ret; + struct spcom_channel *ch = (struct spcom_channel *) priv; + + switch (event) { + case GLINK_CONNECTED: + pr_debug("GLINK_CONNECTED, ch name [%s].\n", ch->name); + complete_all(&ch->connect); + + /* + * if spcom_notify_state() is called within glink_open() + * then ch->glink_handle is not updated yet. + */ + if (!ch->glink_handle) { + pr_debug("update glink_handle, ch [%s].\n", ch->name); + ch->glink_handle = handle; + } + + /* prepare default rx buffer after connected */ + ret = glink_queue_rx_intent(ch->glink_handle, + ch, ch->rx_buf_size); + if (ret) { + pr_err("glink_queue_rx_intent() err [%d]\n", ret); + } else { + pr_debug("rx buf is ready, size [%d].\n", + ch->rx_buf_size); + ch->rx_buf_ready = true; + } + break; + case GLINK_LOCAL_DISCONNECTED: + /* + * Channel state is GLINK_LOCAL_DISCONNECTED + * only after *both* sides closed the channel. + */ + pr_debug("GLINK_LOCAL_DISCONNECTED, ch [%s].\n", ch->name); + complete_all(&ch->disconnect); + break; + case GLINK_REMOTE_DISCONNECTED: + /* + * Remote side initiates glink_close(). + * This is not expected on normal operation. + * This may happen upon remote SSR. + */ + pr_debug("GLINK_REMOTE_DISCONNECTED, ch [%s].\n", ch->name); + /* + * after glink_close(), + * expecting notify GLINK_LOCAL_DISCONNECTED + */ + spcom_close(ch); + break; + default: + pr_err("unknown event id = %d, ch name [%s].\n", + (int) event, ch->name); + return; + } + + ch->glink_state = event; +} + +/** + * spcom_notify_rx_intent_req() - glink callback on intent request. + * + * glink allows the remote side to request for a local rx buffer if such + * buffer is not ready. + * However, for spcom simplicity on SP, and to reduce latency, we decided + * that glink_tx() on both side is not using INTENT_REQ flag, so this + * callback should not be called. + * Anyhow, return "false" to reject the request. + */ +static bool spcom_notify_rx_intent_req(void *handle, const void *priv, + size_t req_size) +{ + struct spcom_channel *ch = (struct spcom_channel *) priv; + + pr_err("Unexpected intent request for ch [%s].\n", ch->name); + + return false; +} + +/** + * spcom_notify_rx_abort() - glink callback on aborting rx pending buffer. + * + * Rx abort may happen if channel is closed by remote side, while rx buffer is + * pending in the queue. + */ +static void spcom_notify_rx_abort(void *handle, const void *priv, + const void *pkt_priv) +{ + struct spcom_channel *ch = (struct spcom_channel *) priv; + + pr_debug("ch [%s] pending rx aborted.\n", ch->name); + + if (spcom_is_channel_connected(ch)) { + ch->rx_abort = true; + complete_all(&ch->rx_done); + } +} + +/** + * spcom_notify_tx_abort() - glink callback on aborting tx data. + * + * This is probably not relevant, since glink_txv() is not used. + * Tx abort may happen if channel is closed by remote side, + * while multiple tx buffers are in a middle of tx operation. + */ +static void spcom_notify_tx_abort(void *handle, const void *priv, + const void *pkt_priv) +{ + struct spcom_channel *ch = (struct spcom_channel *) priv; + + pr_debug("ch [%s] pending tx aborted.\n", ch->name); + + if (spcom_is_channel_connected(ch)) { + complete_all(&ch->tx_done); + ch->tx_abort = true; + } +} + +/*======================================================================*/ +/* UTILITIES */ +/*======================================================================*/ + +/** + * spcom_init_open_config() - Fill glink_open() configuration parameters. + * + * @cfg: glink configuration struct pointer + * @name: channel name + * @priv: private caller data, provided back by callbacks, channel state. + * + * specify callbacks and other parameters for glink open channel. + */ +static void spcom_init_open_config(struct glink_open_config *cfg, + const char *name, void *priv) +{ + cfg->notify_rx = spcom_notify_rx; + cfg->notify_rxv = NULL; + cfg->notify_tx_done = spcom_notify_tx_done; + cfg->notify_state = spcom_notify_state; + cfg->notify_rx_intent_req = spcom_notify_rx_intent_req; + cfg->notify_rx_sigs = NULL; + cfg->notify_rx_abort = spcom_notify_rx_abort; + cfg->notify_tx_abort = spcom_notify_tx_abort; + + cfg->options = 0; /* not using GLINK_OPT_INITIAL_XPORT */ + cfg->priv = priv; /* provided back by callbacks */ + + cfg->name = name; + + cfg->transport = spcom_transport; + cfg->edge = spcom_edge; +} + +/** + * spcom_init_channel() - initialize channel state. + * + * @ch: channel state struct pointer + * @name: channel name + */ +static int spcom_init_channel(struct spcom_channel *ch, const char *name) +{ + if (!ch || !name || !name[0]) { + pr_err("invalid parameters.\n"); + return -EINVAL; + } + + strlcpy(ch->name, name, sizeof(ch->name)); + + init_completion(&ch->connect); + init_completion(&ch->disconnect); + init_completion(&ch->tx_done); + init_completion(&ch->rx_done); + + mutex_init(&ch->lock); + ch->glink_state = GLINK_LOCAL_DISCONNECTED; + ch->actual_rx_size = 0; + ch->rx_buf_size = SPCOM_RX_BUF_SIZE; + + 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) { + pr_debug("channel [%s] found.\n", name); + return ch; + } + } + + pr_err("failed to find channel [%s].\n", name); + + return NULL; +} + +/** + * spcom_open() - Open glink channel and wait for connection ACK. + * + * @ch: channel state struct pointer + * + * Normally, a local client opens a channel after remote server has opened + * the channel. + * A local server may open the channel before remote client is running. + */ +static int spcom_open(struct spcom_channel *ch, unsigned int timeout_msec) +{ + struct glink_open_config cfg = {0}; + unsigned long jiffies = msecs_to_jiffies(timeout_msec); + long timeleft; + const char *name; + void *handle; + + mutex_lock(&ch->lock); + name = ch->name; + + /* only one client/server may use the channel */ + if (ch->ref_count) { + pr_err("channel [%s] already in use.\n", name); + goto exit_err; + } + ch->ref_count++; + ch->pid = current_pid(); + ch->txn_id = INITIAL_TXN_ID; + + pr_debug("ch [%s] opened by PID [%d], count [%d]\n", + name, ch->pid, ch->ref_count); + + pr_debug("Open channel [%s] timeout_msec [%d].\n", name, timeout_msec); + + if (spcom_is_channel_open(ch)) { + pr_debug("channel [%s] already open.\n", name); + mutex_unlock(&ch->lock); + return 0; + } + + spcom_init_open_config(&cfg, name, ch); + + /* init completion before calling glink_open() */ + reinit_completion(&ch->connect); + + handle = glink_open(&cfg); + if (IS_ERR_OR_NULL(handle)) { + pr_err("glink_open failed.\n"); + goto exit_err; + } else { + pr_debug("glink_open [%s] ok.\n", name); + } + ch->glink_handle = handle; + + pr_debug("Wait for connection on channel [%s] timeout_msec [%d].\n", + name, timeout_msec); + + /* Wait for remote side to connect */ + if (timeout_msec) { + timeleft = wait_for_completion_timeout(&(ch->connect), jiffies); + if (timeleft == 0) + pr_debug("Channel [%s] is NOT connected.\n", name); + else + pr_debug("Channel [%s] fully connect.\n", name); + } else { + pr_debug("wait for connection ch [%s] no timeout.\n", name); + wait_for_completion(&(ch->connect)); + pr_debug("Channel [%s] opened, no timeout.\n", name); + } + + mutex_unlock(&ch->lock); + + return 0; +exit_err: + mutex_unlock(&ch->lock); + + return -EFAULT; +} + +/** + * spcom_close() - Close glink channel. + * + * @ch: channel state struct pointer + * + * A calling API functions should wait for disconnecting by both sides. + */ +static int spcom_close(struct spcom_channel *ch) +{ + int ret = 0; + + mutex_lock(&ch->lock); + + if (!spcom_is_channel_open(ch)) { + pr_err("ch already closed.\n"); + mutex_unlock(&ch->lock); + return 0; + } + + ret = glink_close(ch->glink_handle); + if (ret) + pr_err("glink_close() fail, ret [%d].\n", ret); + else + pr_debug("glink_close() ok.\n"); + + ch->glink_handle = NULL; + ch->ref_count = 0; + + ch->txn_id = INITIAL_TXN_ID; /* use non-zero nonce for debug */ + ch->pid = 0; + + pr_debug("Channel closed [%s].\n", ch->name); + mutex_unlock(&ch->lock); + + return 0; +} + +/** + * spcom_tx() - Send data and wait for ACK or timeout. + * + * @ch: channel state struct pointer + * @buf: buffer pointer + * @size: buffer size + * + * ACK is expected within a very short time (few msec). + */ +static int spcom_tx(struct spcom_channel *ch, + void *buf, + uint32_t size, + uint32_t timeout_msec) +{ + int ret; + void *pkt_priv = NULL; + uint32_t tx_flags = 0 ; /* don't use GLINK_TX_REQ_INTENT */ + unsigned long jiffies = msecs_to_jiffies(timeout_msec); + long timeleft; + int retry = 0; + + mutex_lock(&ch->lock); + + /* reset completion before calling glink */ + reinit_completion(&ch->tx_done); + + for (retry = 0; retry < TX_MAX_RETRY ; retry++) { + ret = glink_tx(ch->glink_handle, pkt_priv, buf, size, tx_flags); + if (ret == -EAGAIN) { + pr_err("glink_tx() fail, try again.\n"); + /* + * Delay to allow remote side to queue rx buffer. + * This may happen after the first channel connection. + */ + msleep(TX_RETRY_DELAY_MSEC); + } else if (ret < 0) { + pr_err("glink_tx() error %d.\n", ret); + goto exit_err; + } else { + break; /* no retry needed */ + } + } + + pr_debug("Wait for Tx done.\n"); + + /* Wait for Tx Completion */ + timeleft = wait_for_completion_timeout(&ch->tx_done, jiffies); + if (timeleft == 0) { + pr_err("tx_done timeout %d msec expired.\n", timeout_msec); + goto exit_err; + } else if (ch->tx_abort) { + pr_err("tx aborted.\n"); + goto exit_err; + } + + mutex_unlock(&ch->lock); + + return ret; +exit_err: + mutex_unlock(&ch->lock); + return -EFAULT; +} + +/** + * 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 + * + * ACK is expected within a very short time (few msec). + */ +static int spcom_rx(struct spcom_channel *ch, + void *buf, + uint32_t size, + uint32_t timeout_msec) +{ + int ret; + unsigned long jiffies = msecs_to_jiffies(timeout_msec); + long timeleft = 1; + + mutex_lock(&ch->lock); + + /* check for already pending data */ + if (ch->actual_rx_size) { + pr_debug("already pending data size [%d].\n", + ch->actual_rx_size); + goto copy_buf; + } + + /* reset completion before calling glink */ + reinit_completion(&ch->rx_done); + + /* Wait for Rx response */ + pr_debug("Wait for Rx done.\n"); + if (timeout_msec) + timeleft = wait_for_completion_timeout(&ch->rx_done, jiffies); + else + wait_for_completion(&ch->rx_done); + + if (timeleft == 0) { + pr_err("rx_done timeout [%d] msec expired.\n", timeout_msec); + goto exit_err; + } else if (ch->rx_abort) { + pr_err("rx aborted.\n"); + goto exit_err; + } else if (ch->actual_rx_size) { + pr_debug("actual_rx_size is [%d].\n", ch->actual_rx_size); + } else { + pr_err("actual_rx_size is zero.\n"); + goto exit_err; + } + + if (!ch->glink_rx_buf) { + pr_err("invalid glink_rx_buf.\n"); + goto exit_err; + } + +copy_buf: + /* Copy from glink buffer to spcom buffer */ + size = min_t(int, ch->actual_rx_size, size); + memcpy(buf, ch->glink_rx_buf, size); + + pr_debug("copy size [%d].\n", (int) size); + + /* free glink buffer after copy to spcom buffer */ + glink_rx_done(ch->glink_handle, ch->glink_rx_buf, false); + ch->glink_rx_buf = NULL; + ch->actual_rx_size = 0; + + /* queue rx buffer for the next time */ + ret = glink_queue_rx_intent(ch->glink_handle, ch, ch->rx_buf_size); + if (ret) { + pr_err("glink_queue_rx_intent() failed, ret [%d]", ret); + goto exit_err; + } else { + pr_debug("queue rx_buf, size [%d].\n", ch->rx_buf_size); + } + + mutex_unlock(&ch->lock); + + return size; +exit_err: + mutex_unlock(&ch->lock); + return -EFAULT; +} + +/** + * 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. + */ +static int spcom_get_next_request_size(struct spcom_channel *ch) +{ + int size = -1; + + /* 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 [%d].\n", + ch->name, ch->actual_rx_size); + goto exit_ready; + } + + pr_debug("Wait for Rx Done, ch [%s].\n", ch->name); + wait_for_completion(&ch->rx_done); + if (ch->actual_rx_size <= 0) { + pr_err("invalid rx size [%d] ch [%s].\n", + ch->actual_rx_size, ch->name); + goto exit_error; + } + +exit_ready: + size = 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); + goto exit_error; + } + + mutex_unlock(&ch->lock); + return size; + +exit_error: + mutex_unlock(&ch->lock); + return -EFAULT; + + +} + +/*======================================================================*/ +/* Client API for kernel drivers */ +/*======================================================================*/ + +/** + * spcom_register_client() - register a client. + * + * @info: channel name and ssr callback. + * + * Return: client handle + */ +struct spcom_client *spcom_register_client(struct spcom_client_info *info) +{ + int ret; + const char *name; + struct spcom_channel *ch; + struct spcom_client *client; + + if (!info) { + pr_err("Invalid parameter.\n"); + return NULL; + } + name = info->ch_name; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return NULL; + + ch = spcom_find_channel_by_name(name); + if (!ch) { + pr_err("channel %s doesn't exist, load App first.\n", name); + return NULL; + } + + client->ch = ch; /* backtrack */ + + ret = spcom_open(ch, OPEN_CHANNEL_TIMEOUT_MSEC); + if (ret) { + pr_err("failed to open channel [%s].\n", name); + kfree(client); + client = NULL; + } else { + pr_info("remote side connect to channel [%s].\n", name); + } + + return client; +} +EXPORT_SYMBOL(spcom_register_client); + + +/** + * spcom_unregister_client() - unregister a client. + * + * @client: client handle + */ +int spcom_unregister_client(struct spcom_client *client) +{ + struct spcom_channel *ch; + + if (!client) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = client->ch; + + kfree(client); + + spcom_close(ch); + + return 0; +} +EXPORT_SYMBOL(spcom_unregister_client); + + +/** + * spcom_client_send_message_sync() - send request and wait for response. + * + * @client: client handle + * @req_ptr: request pointer + * @req_size: request size + * @resp_ptr: response pointer + * @resp_size: response size + * @timeout_msec: timeout waiting for response. + * + * The timeout depends on the specific request handling time at the remote side. + */ +int spcom_client_send_message_sync(struct spcom_client *client, + void *req_ptr, + uint32_t req_size, + void *resp_ptr, + uint32_t resp_size, + uint32_t timeout_msec) +{ + int ret; + struct spcom_channel *ch; + + if (!client || !req_ptr || !resp_ptr) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = client->ch; + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect.\n", ch->name); + return -ENOTCONN; + } + + ret = spcom_tx(ch, req_ptr, req_size, TX_DONE_TIMEOUT_MSEC); + if (ret < 0) { + pr_err("tx error %d.\n", ret); + return ret; + } + + ret = spcom_rx(ch, resp_ptr, resp_size, timeout_msec); + if (ret < 0) { + pr_err("rx error %d.\n", ret); + return ret; + } + + /* @todo verify response transaction id match the request */ + + return ret; +} +EXPORT_SYMBOL(spcom_client_send_message_sync); + + +/** + * spcom_client_is_server_connected() - is remote server connected. + * + * @client: client handle + */ +bool spcom_client_is_server_connected(struct spcom_client *client) +{ + bool connected; + + if (!client) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + connected = spcom_is_channel_connected(client->ch); + + return connected; +} +EXPORT_SYMBOL(spcom_client_is_server_connected); + +/*======================================================================*/ +/* Server API for kernel drivers */ +/*======================================================================*/ + +/** + * spcom_register_service() - register a server. + * + * @info: channel name and ssr callback. + * + * Return: server handle + */ +struct spcom_server *spcom_register_service(struct spcom_service_info *info) +{ + int ret; + const char *name; + struct spcom_channel *ch; + struct spcom_server *server; + + if (!info) { + pr_err("Invalid parameter.\n"); + return NULL; + } + name = info->ch_name; + + server = kzalloc(sizeof(*server), GFP_KERNEL); + if (!server) + return NULL; + + ch = spcom_find_channel_by_name(name); + if (!ch) { + pr_err("channel %s doesn't exist, load App first.\n", name); + return NULL; + } + + server->ch = ch; /* backtrack */ + + ret = spcom_open(ch, 0); + if (ret) { + pr_err("failed to open channel [%s].\n", name); + kfree(server); + server = NULL; + } + + return server; +} +EXPORT_SYMBOL(spcom_register_service); + +/** + * spcom_unregister_service() - unregister a server. + * + * @server: server handle + */ +int spcom_unregister_service(struct spcom_server *server) +{ + struct spcom_channel *ch; + + if (!server) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = server->ch; + + kfree(server); + + spcom_close(ch); + + return 0; +} +EXPORT_SYMBOL(spcom_unregister_service); + +/** + * spcom_server_get_next_request_size() - get request size. + * + * @server: server handle + * + * Return: request size in bytes. + */ +int spcom_server_get_next_request_size(struct spcom_server *server) +{ + int size; + struct spcom_channel *ch; + + if (!server) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = server->ch; + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect.\n", ch->name); + return -ENOTCONN; + } + + size = spcom_get_next_request_size(ch); + + pr_debug("next_request_size [%d].\n", size); + + return size; +} +EXPORT_SYMBOL(spcom_server_get_next_request_size); + +/** + * spcom_server_wait_for_request() - wait for request. + * + * @server: server handle + * @req_ptr: request buffer pointer + * @req_size: max request size + * + * Return: request size in bytes. + */ +int spcom_server_wait_for_request(struct spcom_server *server, + void *req_ptr, + uint32_t req_size) +{ + int ret; + struct spcom_channel *ch; + + if (!server || !req_ptr) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = server->ch; + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect.\n", ch->name); + return -ENOTCONN; + } + + ret = spcom_rx(ch, req_ptr, req_size, 0); + + return ret; +} +EXPORT_SYMBOL(spcom_server_wait_for_request); + +/** + * spcom_server_send_response() - Send response + * + * @server: server handle + * @resp_ptr: response buffer pointer + * @resp_size: response size + */ +int spcom_server_send_response(struct spcom_server *server, + void *resp_ptr, + uint32_t resp_size) +{ + int ret; + struct spcom_channel *ch; + + if (!server || !resp_ptr) { + pr_err("Invalid parameter.\n"); + return -EINVAL; + } + + ch = server->ch; + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect.\n", ch->name); + return -ENOTCONN; + } + + ret = spcom_tx(ch, resp_ptr, resp_size, TX_DONE_TIMEOUT_MSEC); + + return ret; +} +EXPORT_SYMBOL(spcom_server_send_response); + +/*======================================================================*/ +/* USER SPACE commands handling */ +/*======================================================================*/ + +/** + * spcom_handle_load_app_command() - Handle Load app 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_load_app_command(struct spcom_channel *ch, + void *cmd_buf, + int cmd_size) +{ + int ret = 0; + struct spcom_msg_hdr *hdr; + struct spcom_load_app_req *req; + struct spcom_load_app_resp *resp; + uint32_t rx_timeout_msec = 0; /* Block until data ready */ + void *tx_buf; + int tx_buf_size; + void *rx_buf; + int rx_buf_size; + struct spcom_user_load_app_command *cmd = cmd_buf; + const char *ch_name; + uint32_t app_buf_size; + uint32_t app_image_size; + char *app_buf; + int ddr_buf_size = 0; + char *ddr_buf = NULL; + uint64_t phys_addr = 0; + dma_addr_t dma_addr = 0; + uint32_t txn_id = 0; + uint32_t offset = 0; + + /* parse command buffer */ + ch_name = cmd->ch_name; + app_image_size = cmd->app_image_size; + app_buf_size = cmd->app_buf_size; + app_buf = cmd->app_buf_ptr; + + pr_debug("Load app [%s], app_image_size [%d].\n", + ch_name, app_image_size); + + if (cmd_size != sizeof(*cmd)) { + pr_err("Load app cmd size [%d] expected size [%d].\n", + cmd_size, (int) sizeof(*cmd)); + return -EINVAL; + } + + if (app_buf_size > SPCOM_MAX_APP_SIZE) { + pr_err("app_buf_size [%d] > max size [%d].\n", + app_buf_size, SPCOM_MAX_APP_SIZE); + 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; + } + + /* Allocate Buffers*/ + tx_buf_size = sizeof(*hdr) + sizeof(*req); + rx_buf_size = sizeof(*hdr) + sizeof(*resp); + tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + rx_buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!rx_buf) { + kfree(tx_buf); + return -ENOMEM; + } + + /* Allocate DDR buffer for the App binary */ + ddr_buf_size = round_up(app_buf_size, PAGE_SIZE); + ddr_buf = dma_alloc_coherent(spcom_dev->class_dev, + ddr_buf_size, + &dma_addr, + GFP_KERNEL); + if (!ddr_buf) { + pr_err("fail to allocate DDR buffer.\n"); + return -ENOMEM; + } + + phys_addr = dma_to_phys(spcom_dev->class_dev, dma_addr); + + /* Align DDR buf to 4K, for SPSS XPU */ + offset = ((int) phys_addr) % PAGE_SIZE; + if (offset != 0) { + ddr_buf += (PAGE_SIZE - offset); /* round up*/ + phys_addr += (PAGE_SIZE - offset); /* round up*/ + } + + memcpy(ddr_buf, app_buf, app_image_size); + + /* Prepare Tx Buf */ + hdr = tx_buf; + req = (void *) &hdr->buf[0]; + + /* Header */ + txn_id = ch->txn_id++; + hdr->txn_id = txn_id; + + /* Request */ + req->cmd_id = SPCOM_CMD_LOAD_APP; + req->buf_phys_addr = phys_addr; + req->image_size = app_image_size; + req->buf_size = app_buf_size; + strlcpy(req->ch_name, cmd->ch_name, sizeof(req->ch_name)); + + pr_debug("send request.\n"); + + ret = spcom_tx(ch, tx_buf, tx_buf_size, TX_DONE_TIMEOUT_MSEC); + if (ret < 0) { + pr_err("tx error %d.\n", ret); + goto exit_err; + } + + pr_debug("get response.\n"); + + ret = spcom_rx(ch, rx_buf, rx_buf_size, rx_timeout_msec); + if (ret < 0) { + pr_err("rx error %d.\n", ret); + goto exit_err; + } + + /* check response from SP */ + hdr = rx_buf; + resp = (void *) &hdr->buf[0]; + + if (resp->error_code != 0) { + pr_err("response error code [%d].\n", (int) resp->error_code); + goto exit_err; + } + pr_debug("rx txn_id = 0x%x .\n", hdr->txn_id); + if (hdr->txn_id != txn_id) { + pr_err("request txn_id [0x%x] != response txn_id [0x%x].\n", + (int) txn_id, (int) hdr->txn_id); + goto exit_err; + } + + kfree(tx_buf); + kfree(rx_buf); + + /* HACK !! Don't free DDR buf, required by SPSS for swap in/out */ + + pr_debug("Load app completed OK.\n"); + + /* After remote app is loaded, create /dev/ */ + spcom_create_channel_chardev(ch_name); + + return 0; +exit_err: + kfree(tx_buf); + kfree(rx_buf); + + memset(ddr_buf, 0xDEADBEEF, ddr_buf_size); + dmam_free_coherent(spcom_dev->class_dev, ddr_buf_size, + ddr_buf, dma_addr); + + return -EFAULT; +} + +/** + * 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 char *ch_name; + + if (cmd_size != sizeof(*cmd)) { + pr_err("cmd_size [%d] , expected [%d].\n", + (int) cmd_size, (int) sizeof(*cmd)); + return -EINVAL; + } + + ch_name = cmd->ch_name; + + pr_debug("ch_name [%s].\n", ch_name); + + ret = spcom_create_channel_chardev(ch_name); + + return ret; +} + +/** + * spcom_handle_reset_command() - Handle RESET-SP command from user space. + * + * @buf: command buffer. + * @buf_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int spcom_handle_reset_command(struct spcom_channel *ch, + void *cmd_buf, int cmd_size) +{ + int ret = 0; + struct spcom_msg_hdr *hdr; + struct spcom_reset_cmd_req *req; + struct spcom_reset_resp *resp; + uint32_t rx_timeout_msec = 0; /* Block until data ready */ + void *tx_buf; + int tx_buf_size; + void *rx_buf; + int rx_buf_size; + + /* Check if remote side connect */ + if (!spcom_is_channel_connected(ch)) { + pr_err("ch [%s] remote side not connect.\n", ch->name); + return -ENOTCONN; + } + + /* Allocate Buffers*/ + tx_buf_size = sizeof(*hdr) + sizeof(*req); + rx_buf_size = sizeof(*hdr) + sizeof(*resp); + tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + rx_buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!rx_buf) { + kfree(tx_buf); + return -ENOMEM; + } + + /* Prepare Tx Buf */ + hdr = tx_buf; + req = (void *) &hdr->buf[0]; + + /* Header */ + hdr->txn_id = ch->txn_id++; + + /* Request */ + req->cmd_id = SPCOM_CMD_RESET_SP; + + pr_debug("send request.\n"); + + ret = spcom_tx(ch, tx_buf, tx_buf_size, TX_DONE_TIMEOUT_MSEC); + if (ret < 0) { + pr_err("tx error %d.\n", ret); + goto exit_err; + } + + pr_debug("get response.\n"); + + ret = spcom_rx(ch, rx_buf, rx_buf_size, rx_timeout_msec); + if (ret < 0) { + pr_err("rx error %d.\n", ret); + goto exit_err; + } + + /* check response from SP */ + hdr = rx_buf; + resp = (void *) &hdr->buf[0]; + + if (resp->error_code != 0) { + pr_err("response error code [%d].\n", (int) resp->error_code); + goto exit_err; + } + + kfree(tx_buf); + kfree(rx_buf); + + return 0; +exit_err: + kfree(tx_buf); + kfree(rx_buf); + return -EFAULT; +} + +/** + * 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; + + pr_debug("send req/resp ch [%s] size [%d] .\n", ch->name, size); + + /* 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; + + /* 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; + + /* Header */ + hdr->txn_id = ch->txn_id; + if (!ch->is_server) { + ch->txn_id++; /* client sets the request txn_id */ + ch->response_timeout_msec = timeout_msec; + } + + /* user buf */ + memcpy(hdr->buf, buf, buf_size); + + /* + * remote side should have rx buffer ready. + * tx_done is expected to be received quickly. + */ + ret = spcom_tx(ch, tx_buf, tx_buf_size, TX_DONE_TIMEOUT_MSEC); + if (ret < 0) + pr_err("tx error %d.\n", ret); + + kfree(tx_buf); + + return ret; +} + +/** + * spcom_handle_fake_ssr_command() - Handle fake ssr command from user space. + */ +static int spcom_handle_fake_ssr_command(struct spcom_channel *ch, int arg) +{ + pr_debug("Start Fake glink SSR subsystem [%s].\n", spcom_edge); + glink_ssr(spcom_edge); + pr_debug("Fake glink SSR subsystem [%s] done.\n", spcom_edge); + + 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; + int swap_id; + char cmd_name[5] = {0}; /* debug only */ + + /* opcode field is the minimum length of cmd */ + if (buf_size < sizeof(cmd->cmd_id)) { + pr_err("Invalid argument user buffer size %d.\n", buf_size); + return -EINVAL; + } + + cmd = (struct spcom_user_command *)buf; + cmd_id = (int) cmd->cmd_id; + swap_id = htonl(cmd->cmd_id); + memcpy(cmd_name, &swap_id, 4); + + pr_debug("cmd_id [0x%x] cmd_name [%s].\n", cmd_id, cmd_name); + + switch (cmd_id) { + case SPCOM_CMD_LOAD_APP: + ret = spcom_handle_load_app_command(ch, buf, buf_size); + break; + case SPCOM_CMD_RESET_SP: + ret = spcom_handle_reset_command(ch, buf, buf_size); + break; + case SPCOM_CMD_SEND: + ret = spcom_handle_send_command(ch, buf, buf_size); + break; + case SPCOM_CMD_FSSR: + ret = spcom_handle_fake_ssr_command(ch, cmd->arg); + break; + case SPCOM_CMD_CREATE_CHANNEL: + ret = spcom_handle_create_channel_command(buf, buf_size); + break; + default: + pr_err("Invalid Command Id [0x%x].\n", (int) cmd->cmd_id); + return -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. + */ +static int spcom_handle_get_req_size(struct spcom_channel *ch, + void *buf, + uint32_t size) +{ + uint32_t next_req_size = 0; + + if (size < sizeof(next_req_size)) { + pr_err("buf size [%d] too small.\n", (int) size); + return -EINVAL; + } + + next_req_size = spcom_get_next_request_size(ch); + + 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. + */ +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; + } + + /* 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_err("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); + goto exit_err; + } + + kfree(rx_buf); + return size; +exit_err: + kfree(rx_buf); + return -EFAULT; + +} + +/** + * 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. + */ +static int spcom_handle_read(struct spcom_channel *ch, + void *buf, + uint32_t size) +{ + if (size == SPCOM_GET_NEXT_REQUEST_SIZE) { + pr_debug("get next request size, ch [%s].\n", ch->name); + size = spcom_handle_get_req_size(ch, buf, size); + ch->is_server = true; + } else { + pr_debug("get request/response, ch [%s].\n", ch->name); + size = spcom_handle_read_req_resp(ch, buf, size); + } + + pr_debug("ch [%s] , size = %d.\n", ch->name, size); + + return size; +} + +/*======================================================================*/ +/* 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_dentry) + return "unknown"; + + dentry = filp->f_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. + * 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_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct spcom_channel *ch; + const char *name = file_to_filename(filp); + + pr_debug("Open file [%s].\n", name); + + 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; + } + + filp->private_data = ch; + + ret = spcom_open(ch, OPEN_CHANNEL_TIMEOUT_MSEC); + if (ret == -ETIMEDOUT) { + pr_err("Connection timeout channel [%s].\n", name); + } else if (ret) { + pr_err("failed to open channel [%s] , err=%d.\n", name, ret); + return ret; + } + + pr_debug("finished.\n"); + + 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); + bool connected = false; + + pr_debug("Close file [%s].\n", name); + + ch = filp->private_data; + + if (!ch) { + pr_err("ch is NULL, file name %s.\n", file_to_filename(filp)); + return -ENODEV; + } + + /* channel might be already closed or disconnected */ + if (spcom_is_channel_open(ch) && spcom_is_channel_connected(ch)) + connected = true; + + reinit_completion(&ch->disconnect); + + spcom_close(ch); + + if (connected) { + pr_debug("Wait for event GLINK_LOCAL_DISCONNECTED, ch [%s].\n", + name); + wait_for_completion(&ch->disconnect); + pr_debug("GLINK_LOCAL_DISCONNECTED signaled, ch [%s].\n", name); + } + + return 0; +} + +/** + * 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); + + pr_debug("Write file [%s] size [%d] pos [%d].\n", + name, (int) size, (int) *f_pos); + + if (!user_buff || !f_pos || !filp) { + pr_err("invalid null parameters.\n"); + return -EINVAL; + } + + ch = filp->private_data; + if (!ch) { + pr_debug("invalid ch pointer.\n"); + /* Allow some special commands via /dev/spcom and /dev/sp_ssr */ + } 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; + } + } + + if (size > SPCOM_MAX_COMMAND_SIZE) { + pr_err("size [%d] > max size [%d].\n", + (int) size , (int) SPCOM_MAX_COMMAND_SIZE); + return -EINVAL; + } + + if (*f_pos != 0) { + pr_err("offset should be zero, no sparse buffer.\n"); + return -EINVAL; + } + + 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, size); + if (ret) { + pr_err("handle command error [%d].\n", ret); + kfree(buf); + return -EFAULT; + } + + kfree(buf); + + return size; +} + +/** + * spcom_device_read() - handle channel file write() 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); + + pr_debug("Read file [%s], size = %d bytes.\n", name, (int) size); + + if (!filp || !user_buff || !f_pos || + (size == 0) || (size > SPCOM_MAX_READ_SIZE)) { + pr_err("invalid parameters.\n"); + return -EINVAL; + } + + ch = filp->private_data; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + actual_size = spcom_handle_read(ch, buf, size); + + ret = copy_to_user(user_buff, buf, actual_size); + + if (ret) { + pr_err("Unable to copy to user, err = %d.\n", ret); + kfree(buf); + return -EFAULT; + } + + kfree(buf); + + pr_debug("ch [%s] ret [%d].\n", name, (int) actual_size); + + return actual_size; +} + +/** + * spcom_device_poll() - handle channel file poll() from user space. + * + * @filp: file pointer + * + * This allows user space to wait/check for channel connection, + * or wait for SSR event. + * + * Return: event bitmask on success, set POLLERR on failure. + */ +static unsigned int spcom_device_poll(struct file *filp, + struct poll_table_struct *poll_table) +{ + /* + * when user call with timeout -1 for blocking mode, + * any bit must be set in response + */ + unsigned int ret = SPCOM_POLL_READY_FLAG; + unsigned long mask; + struct spcom_channel *ch; + const char *name = file_to_filename(filp); + bool wait = false; + bool done = false; + /* Event types always implicitly polled for */ + unsigned long reserved = POLLERR | POLLHUP | POLLNVAL; + int ready = 0; + + ch = filp->private_data; + + mask = poll_requested_events(poll_table); + + pr_debug("== ch [%s] mask [0x%x] ==.\n", name, (int) mask); + + /* user space API has poll use "short" and not "long" */ + mask &= 0x0000FFFF; + + wait = mask & SPCOM_POLL_WAIT_FLAG; + if (wait) + pr_debug("ch [%s] wait for event flag is ON.\n", name); + mask &= ~SPCOM_POLL_WAIT_FLAG; /* clear the wait flag */ + mask &= ~SPCOM_POLL_READY_FLAG; /* clear the ready flag */ + mask &= ~reserved; /* clear the implicitly set reserved bits */ + + switch (mask) { + case SPCOM_POLL_LINK_STATE: + pr_debug("ch [%s] SPCOM_POLL_LINK_STATE.\n", name); + if (wait) { + reinit_completion(&spcom_dev->link_state_changed); + ready = wait_for_completion_interruptible( + &spcom_dev->link_state_changed); + pr_debug("ch [%s] poll LINK_STATE signaled.\n", name); + } + done = (spcom_dev->link_state == GLINK_LINK_STATE_UP); + break; + case SPCOM_POLL_CH_CONNECT: + pr_debug("ch [%s] SPCOM_POLL_CH_CONNECT.\n", name); + if (wait) { + reinit_completion(&ch->connect); + ready = wait_for_completion_interruptible(&ch->connect); + pr_debug("ch [%s] poll CH_CONNECT signaled.\n", name); + } + done = completion_done(&ch->connect); + break; + default: + pr_err("ch [%s] poll, invalid mask [0x%x].\n", + name, (int) mask); + ret = POLLERR; + break; + } + + if (ready < 0) { /* wait was interrupted */ + pr_err("ch [%s] poll interrupted, ret [%d].\n", name, ready); + ret = POLLERR | SPCOM_POLL_READY_FLAG | mask; + } + if (done) + ret |= mask; + + pr_debug("ch [%s] poll, mask = 0x%x, ret=0x%x.\n", + name, (int) mask, ret); + + return ret; +} + +/* file operation supported from user space */ +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = spcom_device_read, + .poll = spcom_device_poll, + .write = spcom_device_write, + .open = spcom_device_open, + .release = spcom_device_release, +}; + +/** + * spcom_create_channel_chardev() - Create a channel char-dev node file + * for user space interface + */ +static int spcom_create_channel_chardev(const char *name) +{ + 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 -EINVAL; + } + + ch = spcom_find_channel_by_name(""); /* find reserved channel */ + if (!ch) { + pr_err("no free channel.\n"); + return -ENODEV; + } + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + spcom_dev->channel_count++; + devt = spcom_dev->device_no + spcom_dev->channel_count; + priv = ch; + dev = device_create(cls, parent, devt, priv, name); + if (!dev) { + pr_err("device_create failed.\n"); + kfree(cdev); + return -ENODEV; + } + + 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); + goto exit_destroy_device; + } + + spcom_init_channel(ch, name); + + ch->cdev = cdev; + ch->dev = dev; + + return 0; + +exit_destroy_device: + device_destroy(spcom_dev->driver_class, devt); + kfree(cdev); + return -EFAULT; +} + +static int __init spcom_register_chardev(void) +{ + int ret; + unsigned baseminor = 0; + unsigned 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 (!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_info("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, 1); + +} + +/*======================================================================*/ +/* Device Tree */ +/*======================================================================*/ + +static int spcom_parse_dt(struct device_node *np) +{ + int ret; + const char *propname = "qcom,spcom-ch-names"; + int num_ch = of_property_count_strings(np, propname); + int i; + const char *name; + + 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; +} + +static int spcom_probe(struct platform_device *pdev) +{ + int ret; + struct spcom_device *dev = NULL; + struct glink_link_info link_info; + struct device_node *np; + struct link_state_notifier_info *notif_handle; + + 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; + mutex_init(&dev->lock); + init_completion(&dev->link_state_changed); + spcom_dev->link_state = GLINK_LINK_STATE_DOWN; + + ret = spcom_register_chardev(); + if (ret) { + pr_err("create character device failed.\n"); + goto fail_reg_chardev; + } + + link_info.glink_link_state_notif_cb = spcom_link_state_notif_cb; + link_info.transport = spcom_transport; + link_info.edge = spcom_edge; + + ret = spcom_parse_dt(np); + if (ret < 0) + goto fail_reg_chardev; + + /* + * Register for glink link up/down notification. + * glink channels can't be opened before link is up. + */ + pr_debug("register_link_state_cb(), transport [%s] edge [%s]\n", + link_info.transport, link_info.edge); + notif_handle = glink_register_link_state_cb(&link_info, spcom_dev); + if (!notif_handle) { + pr_err("glink_register_link_state_cb(), err [%d]\n", ret); + goto fail_reg_chardev; + } + + pr_info("Driver Initialization ok.\n"); + + return 0; + +fail_reg_chardev: + pr_err("Failed to init driver.\n"); + spcom_unregister_chrdev(); + 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, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spcom_match_table), + }, +}; + +/*======================================================================*/ +/* Driver Init/Exit */ +/*======================================================================*/ + +static int __init spcom_init(void) +{ + int ret; + + pr_info("spcom driver Ver 1.0 23-Nov-2015.\n"); + + ret = platform_driver_register(&spcom_driver); + if (ret) + pr_err("spcom_driver register failed %d\n", ret); + + return 0; +} +module_init(spcom_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Secure Processor Communication"); diff --git a/include/soc/qcom/spcom.h b/include/soc/qcom/spcom.h new file mode 100644 index 000000000000..e7302cad39cd --- /dev/null +++ b/include/soc/qcom/spcom.h @@ -0,0 +1,266 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _SPCOM_H_ +#define _SPCOM_H_ + +#include /* uint32_t ,bool */ + +/** + * @brief - Secure Processor Communication API + * + * This API should be used by Linux Kernel drivers, + * similar API is provided to user space applications + * via spcomlib.h API file. + * Sending Request and receiving Response is synchronous, only one at a time. + * The API is based on Client/Server model. + * The API resemble the trustzone QSEECOM API. + * In most cases, the Secure Processor side has servers and the HLOS + * side has clients. Request is initiated by the client and responded by the + * server. + */ + +/*===========================================================================*/ +/* defines, enums , types */ +/*===========================================================================*/ + +/* Maximum size (including null) for channel names - match glink */ +#define SPCOM_CHANNEL_NAME_SIZE 32 + +/** + * 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_REQUEST_SIZE 268 +#define SPCOM_MAX_RESPONSE_SIZE 268 + +/** + * Abstract spcom handle. + * The actual struct definition is internal to the spcom driver. + */ +struct spcom_client; /* Forward declaration */ +struct spcom_server; /* Forward declaration */ + +/** + * Client registration info + * + * @ch_name: glink logical channel name + * @notify_ssr_cb: callback when the remote SP side reset (power down). + * This is likely to happen due to remote subsystem restart (SSR). + * NULL callback means no notification required. + * Upon ssr callback, the user should unregister, + * Poll for link up and then register again. + */ +struct spcom_client_info { + const char *ch_name; + void (*notify_ssr_cb)(void); +}; + +/** + * Server registration info + * + * @ch_name: glink logical channel name + * @notify_ssr_cb: callback when the remote SP side reset (power down). + * This is likely to happen due to remote subsystem restart (SSR). + * NULL callback means no notification required. + * Upon ssr callback, the user should unregister, + * Poll for link up and then register again. + */ +struct spcom_service_info { + const char *ch_name; + void (*notify_ssr_cb)(void); +}; + +/*===========================================================================*/ +/* RESET */ +/*===========================================================================*/ + + +/** + * spcom_reset_sp_subsystem() - send reset command to secure processor. + * + * Gracefully ask the remote SP to reset itself. + * SP will probably initiate a Watch-Dog-Bite. + * + * return: 0 on success, negative error code on failure. + */ +int spcom_reset_sp_subsystem(void); + +/** + * spcom_is_sp_subsystem_link_up() - check if SPSS link is up. + * + * return: true if link is up, false if link is down. + */ +bool spcom_is_sp_subsystem_link_up(void); + +/*===========================================================================*/ +/* Client LOAD SP Application */ +/*===========================================================================*/ + +/** + * spcom_is_app_loaded() - check if the SP App is already loaded. + * + * This shall be useful when the HLOS app restarts. + * This API will check if logical channel node has been created. + * + * @ch_name: glink logical channel name + * + * @note: This API is available only on HLOS. + * + * return: true if loaded,false otherwise. + */ +bool spcom_is_app_loaded(const char *ch_name); + +/** + * spcom_load_app() - Load Secure Processor Application. + * + * @ch_name: glink logical channel name + * spcom shall open channel file node after application is loaded. + * + * @file_path: Path to the encrypted file containing the application. + * + * @note: This API is available only on HLOS. + * + * return: 0 on success, negative error code on failure. + */ +int spcom_load_app(const char *ch_name, const char *file_path); + +/*===========================================================================*/ +/* Client Send Message */ +/*===========================================================================*/ +/** + * spcom_register_client() - register client for channel + * + * Only one client/Server can register on each side of a channel. + * Server on remote side is expected to be running and connected, + * therefore connection expected within the provided timeout. + * Handle is returned even if timeout expired. + * use spcom_client_is_server_connected() to check fully connected. + * + * @info: Client configuration info (input). + * + * return: client handle on success, NULL on failure. + */ +struct spcom_client *spcom_register_client(struct spcom_client_info *info); + +/** + * spcom_unregister_client() - unregister client for channel + * + * @client: Client Handle. + * + * return: 0 on success, negative error code on failure (see errno.h) + */ +int spcom_unregister_client(struct spcom_client *client); + +/** + * spcom_client_send_message_sync() - Send a synchronous request and response + * + * @client: a pointer to spcom client + * @req_ptr: a pointer to the request C struct representation + * @req_size: size of the request C struct + * @resp_ptr: a pointer to the response C struct representation + * @resp_size: size of the response C struct + * @timeout_msec: Timeout in msec between command and response, 0=no timeout. + * + * return: number of rx bytes on success, negative value on failure. + */ +int spcom_client_send_message_sync(struct spcom_client *client, + void *req_ptr, + uint32_t req_size, + void *resp_ptr, + uint32_t resp_size, + uint32_t timeout_msec); + +/** + * spcom_client_is_server_connected() - Check if remote server connected. + * + * This API checks that the logical channel is fully connected between + * the client and the server. + * Normally, the server should be up first and connect first. + * + * @client: a pointer to spcom client + * + * return: true if server connected, false otherwise. + */ +bool spcom_client_is_server_connected(struct spcom_client *client); + +/*===========================================================================*/ +/* Service */ +/*===========================================================================*/ + +/** + * spcom_register_service() - register server for channel + * + * Only one client/Server can register on each side of a channel. + * + * @info: Server configuration info (input). + * + * return: server handle on success, NULL on failure. + */ +struct spcom_server *spcom_register_service(struct spcom_service_info *info); + +/** + * spcom_unregister_service() - unregister server for channel + * + * @server: server Handle. + * + * return: 0 on success, negative error code on failure (see errno.h) + */ +int spcom_unregister_service(struct spcom_server *server); + +/** + * spcom_server_get_next_request_size() - get the size of the + * next request + * + * This API MUST be called before calling spcom_server_wait_for_request(). + * The server should allocate the relevant buffer size. + * + * @server: a pointer to spcom server + * + * return: size of request in bytes on success, negative value on failure. + */ +int spcom_server_get_next_request_size(struct spcom_server *server); + +/** + * spcom_server_wait_for_request() - server wait for request + * + * @server: a pointer to spcom server + * @req_ptr: a pointer to the request buffer + * @req_size: size of the buffer provided. + * The server should provide a buffer of at least the size + * returned by spcom_server_get_next_request_size() and up to + * SPCOM_MAX_REQUEST_SIZE. + * + * return: size of request on success, negative value on failure (see errno.h) + */ +int spcom_server_wait_for_request(struct spcom_server *server, + void *req_ptr, + uint32_t req_size); + +/** + * spcom_server_send_response() - Send a the response to request + * + * @server: a pointer to spcom server + * @resp_ptr: a pointer to the response C struct representation + * @resp_size: size of the response C struct + * + * return: sent data size on success, negative value on failure (see errno.h) + */ +int spcom_server_send_response(struct spcom_server *server, + void *resp_ptr, + uint32_t resp_size); + +#endif /* _SPCOM_H_ */ diff --git a/include/uapi/linux/spcom.h b/include/uapi/linux/spcom.h new file mode 100644 index 000000000000..eb09d72ce21b --- /dev/null +++ b/include/uapi/linux/spcom.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _UAPI_SPCOM_H_ +#define _UAPI_SPCOM_H_ + +#include /* uint32_t, bool */ +#include /* BIT() */ + +/** + * @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 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_FSSR = 0x46535352, /* "FSSR" = 0x46535352 */ + SPCOM_CMD_CREATE_CHANNEL = 0x43524554, /* "CRET" = 0x43524554 */ +}; + +/* + * @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 userspace spcomlib and spcom driver, on write() */ +struct spcom_user_load_app_command { + enum spcom_cmd_id cmd_id; + char ch_name[SPCOM_CHANNEL_NAME_SIZE]; + uint32_t app_image_size; + char *app_buf_ptr; + uint32_t app_buf_size; +} __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]; +} __packed; + +#endif /* _UAPI_SPCOM_H_ */