blob: f381f16683d6be8e50e6328f37a12a595c9a8f7b [file] [log] [blame]
/*
* Copyright (c) 2015-2017, 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/<sp-channel-name>" 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/<ch-name>" 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 used, 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/printk.h>
#include <linux/bitops.h>
#include <linux/completion.h>
#include <linux/poll.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/msm_ion.h>
#include <soc/qcom/glink.h>
#include <soc/qcom/smem.h>
#include <soc/qcom/spcom.h>
#include <uapi/linux/spcom.h>
#include "glink_private.h"
/* "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 0x20
/* 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))
/* 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 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
/* 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.
* Normally, it takes few msec for SPSS to respond with ACK for TX data.
* However, due to SPSS HW issue, the SPSS might disable interrupts
* for a very long time.
*/
#define TX_DONE_TIMEOUT_MSEC 5000
/*
* 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_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 int 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;
/* ION lock/unlock support */
int ion_fd_table[SPCOM_MAX_ION_BUF_PER_CH];
struct ion_handle *ion_handle_table[SPCOM_MAX_ION_BUF_PER_CH];
};
/**
* 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;
/* ION support */
struct ion_client *ion_client;
};
#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);
static void spcom_notify_rx_abort(void *handle, const void *priv,
const void *pkt_priv);
static struct spcom_channel *spcom_find_channel_by_name(const char *name);
static int spcom_unlock_ion_buf(struct spcom_channel *ch, int fd);
/**
* 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)
{
struct spcom_channel *ch = NULL;
const char *ch_name = "sp_kernel";
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_info("GLINK_LINK_STATE_UP.\n");
spcom_create_predefined_channels_chardev();
break;
case GLINK_LINK_STATE_DOWN:
pr_err("GLINK_LINK_STATE_DOWN.\n");
/*
* Free all the SKP ION buffers that were locked
* for SPSS app swapping, when remote subsystem reset.
*/
pr_debug("Free all SKP ION buffers on SSR.\n");
ch = spcom_find_channel_by_name(ch_name);
if (!ch)
pr_err("failed to find channel [%s].\n", ch_name);
else
spcom_unlock_ion_buf(ch, SPCOM_ION_FD_UNLOCK_ALL);
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 int 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_err("GLINK_REMOTE_DISCONNECTED, ch [%s].\n", ch->name);
/*
* Abort any blocking read() operation.
* The glink notification might be after REMOTE_DISCONNECT.
*/
spcom_notify_rx_abort(NULL, ch, NULL);
/*
* 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)) {
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) && (!ch->tx_abort)) {
ch->tx_abort = true;
complete_all(&ch->tx_done);
}
}
/*======================================================================*/
/* 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)
return ch;
}
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->rx_abort = false;
ch->tx_abort = false;
ch->glink_state = GLINK_LOCAL_DISCONNECTED;
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;
}
/*======================================================================*/
/* General API for kernel drivers */
/*======================================================================*/
/**
* 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)
{
return (spcom_dev->link_state == GLINK_LINK_STATE_UP);
}
EXPORT_SYMBOL(spcom_is_sp_subsystem_link_up);
/*======================================================================*/
/* 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_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_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 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;
/* 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;
}
/**
* 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 ion_handle *handle = NULL;
ion_phys_addr_t ion_phys_addr;
size_t len;
int fd;
uint32_t buf_offset;
char *ptr = (char *)buf;
int ret;
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 > buf_size - sizeof(uint64_t)) {
pr_err("invalid buf_offset [%d].\n", buf_offset);
return -ENODEV;
}
/* Get ION handle from fd */
handle = ion_import_dma_buf_fd(spcom_dev->ion_client, fd);
if (handle == NULL) {
pr_err("fail to get ion handle.\n");
return -EINVAL;
}
pr_debug("ion handle ok.\n");
/* Get the ION buffer Physical Address */
ret = ion_phys(spcom_dev->ion_client, handle, &ion_phys_addr, &len);
if (ret < 0) {
pr_err("fail to get ion phys addr.\n");
ion_free(spcom_dev->ion_client, handle);
return -EINVAL;
}
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);
/* Set the ION Physical Address at the buffer offset */
pr_debug("ion phys addr = [0x%lx].\n", (long int) ion_phys_addr);
memcpy(ptr, &ion_phys_addr, sizeof(uint64_t));
/* Release the ION handle */
ion_free(spcom_dev->ion_client, handle);
return 0;
}
/**
* 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;
uint32_t timeout_msec;
struct spcom_ion_info ion_info[SPCOM_MAX_ION_BUF_PER_CMD];
int i;
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;
/* 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);
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) {
pr_err("modify_ion_addr() error [%d].\n", ret);
kfree(tx_buf);
return -EFAULT;
}
}
}
/*
* 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_lock_ion_buf_command() - Lock an ION buffer.
*
* Lock an ION buffer, prevent it from being free if the user space 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 = cmd->arg;
struct ion_handle *ion_handle;
int i;
if (size != sizeof(*cmd)) {
pr_err("cmd size [%d] , expected [%d].\n",
(int) size, (int) sizeof(*cmd));
return -EINVAL;
}
/* Check ION client */
if (spcom_dev->ion_client == NULL) {
pr_err("invalid ion client.\n");
return -ENODEV;
}
/* Get ION handle from fd - this increments the ref count */
ion_handle = ion_import_dma_buf_fd(spcom_dev->ion_client, fd);
if (ion_handle == NULL) {
pr_err("fail to get ion handle.\n");
return -EINVAL;
}
pr_debug("ion handle ok.\n");
/* Check if this ION buffer is already locked */
for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) {
if (ch->ion_handle_table[i] == ion_handle) {
pr_debug("fd [%d] ion buf is already locked.\n", fd);
/* decrement back the ref count */
ion_free(spcom_dev->ion_client, ion_handle);
return -EINVAL;
}
}
/* Store the ION handle */
for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) {
if (ch->ion_handle_table[i] == NULL) {
ch->ion_handle_table[i] = ion_handle;
ch->ion_fd_table[i] = fd;
pr_debug("locked ion buf#[%d], fd [%d].\n", i, fd);
return 0;
}
}
return -EFAULT;
}
/**
* spcom_unlock_ion_buf() - 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_unlock_ion_buf(struct spcom_channel *ch, int fd)
{
struct ion_client *ion_client = spcom_dev->ion_client;
int i;
bool found = false;
pr_debug("Unlock ion buf ch [%s] fd [%d].\n", ch->name, fd);
/* Check ION client */
if (ion_client == NULL) {
pr_err("fail to create ion client.\n");
return -ENODEV;
}
if (fd == (int) SPCOM_ION_FD_UNLOCK_ALL) {
pr_debug("unlocked ALL ion buf ch [%s].\n", ch->name);
found = true;
/* unlock all ION buf */
for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) {
if (ch->ion_handle_table[i] != NULL) {
ion_free(ion_client, ch->ion_handle_table[i]);
ch->ion_handle_table[i] = NULL;
ch->ion_fd_table[i] = -1;
pr_debug("unlocked ion buf#[%d].\n", i);
}
}
} else {
/* unlock specific ION buf */
for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) {
if (ch->ion_fd_table[i] == fd) {
ion_free(ion_client, ch->ion_handle_table[i]);
ch->ion_handle_table[i] = NULL;
ch->ion_fd_table[i] = -1;
pr_debug("unlocked ion buf#[%d].\n", i);
found = true;
break;
}
}
}
if (!found) {
pr_err("ch [%s] fd [%d] was not found.\n", ch->name, fd);
return -ENODEV;
}
return 0;
}
/**
* 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 ret;
struct spcom_user_command *cmd = cmd_buf;
int fd = cmd->arg;
if (size != sizeof(*cmd)) {
pr_err("cmd size [%d] , expected [%d].\n",
(int) size, (int) sizeof(*cmd));
return -EINVAL;
}
ret = spcom_unlock_ion_buf(ch, fd);
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, sizeof(int));
pr_debug("cmd_id [0x%x] cmd_name [%s].\n", cmd_id, cmd_name);
switch (cmd_id) {
case SPCOM_CMD_SEND:
ret = spcom_handle_send_command(ch, buf, buf_size);
break;
case SPCOM_CMD_SEND_MODIFIED:
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_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;
}
/* Check param validity */
if (size > SPCOM_MAX_RESPONSE_SIZE) {
pr_err("ch [%s] inavlid 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);
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_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.
* 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);
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;
}
/* 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);
if ((actual_size <= 0) || (actual_size > size)) {
pr_err("invalid actual_size [%d].\n", actual_size);
kfree(buf);
return -EFAULT;
}
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_debug("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 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 (!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, 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;
}
spcom_dev->ion_client = msm_ion_client_create(DEVICE_NAME);
if (spcom_dev->ion_client == NULL) {
pr_err("fail to create ion client.\n");
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");