2424 lines
58 KiB
C
2424 lines
58 KiB
C
/*
|
|
* 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/<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 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 <linux/kernel.h> /* min() */
|
|
#include <linux/module.h> /* MODULE_LICENSE */
|
|
#include <linux/device.h> /* class_create() */
|
|
#include <linux/slab.h> /* kzalloc() */
|
|
#include <linux/fs.h> /* file_operations */
|
|
#include <linux/cdev.h> /* cdev_add() */
|
|
#include <linux/errno.h> /* EINVAL, ETIMEDOUT */
|
|
#include <linux/printk.h> /* pr_err() */
|
|
#include <linux/bitops.h> /* BIT(x) */
|
|
#include <linux/completion.h> /* wait_for_completion_timeout() */
|
|
#include <linux/poll.h> /* POLLOUT */
|
|
#include <linux/dma-mapping.h> /* dma_alloc_coherent() */
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h> /* of_property_count_strings() */
|
|
#include <linux/workqueue.h>
|
|
#include <linux/delay.h> /* msleep() */
|
|
|
|
#include <soc/qcom/glink.h>
|
|
#include <soc/qcom/smem.h>
|
|
#include <soc/qcom/spcom.h>
|
|
|
|
#include <uapi/linux/spcom.h>
|
|
|
|
#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/<ch_name> */
|
|
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");
|