blob: 7441566e372eccba3974f7eab090f84f1be7b459 [file] [log] [blame]
/*
* Copyright (c) 2013, Google, Inc. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define LOCAL_TRACE 0
/**
* @file
* @brief IPC message management primitives
* @defgroup ipc IPC
*
* Provides low level data structures for managing message
* areas for the ipc contexts.
*
* Also provides user syscall implementations for message
* send/receive mechanism.
*
* @{
*/
#include <assert.h>
#include <err.h>
#include <list.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <trace.h>
#include <uthread.h>
#include <lib/syscall.h>
#if WITH_TRUSTY_IPC
#include <lib/trusty/handle.h>
#include <lib/trusty/ipc.h>
#include <lib/trusty/ipc_msg.h>
#include <lib/trusty/trusty_app.h>
#include <lib/trusty/uctx.h>
extern mutex_t ipc_lock;
enum {
MSG_ITEM_STATE_FREE = 0,
MSG_ITEM_STATE_FILLED = 1,
MSG_ITEM_STATE_READ = 2,
};
typedef struct msg_item {
uint8_t id;
uint8_t state;
uint num_handles;
handle_id_t handles[MAX_MSG_HANDLES];
size_t len;
struct list_node node;
} msg_item_t;
struct ipc_msg_queue {
struct list_node free_list;
struct list_node filled_list;
struct list_node read_list;
uint num_items;
size_t item_sz;
uint8_t *buf;
/* store the message descriptors in the queue,
* and the buffer separately. The buffer could
* eventually move to a separate area that can
* be mapped into the process directly.
*/
msg_item_t items[0];
};
enum {
IPC_MSG_BUFFER_USER = 0,
IPC_MSG_BUFFER_KERNEL = 1,
};
typedef struct msg_desc {
int type;
union {
ipc_msg_kern_t kern;
ipc_msg_user_t user;
};
} msg_desc_t;
/**
* @brief Create IPC message queue
*
* Stores up-to a predefined number of equal-sized items in a circular
* buffer (FIFO).
*
* @param num_items Number of messages we need to store.
* @param item_sz Size of each message item.
* @param mq Pointer where to store the ptr to the newly allocated
* message queue.
*
* @return Returns NO_ERROR on success, ERR_NO_MEMORY on error.
*/
int ipc_msg_queue_create(uint num_items, size_t item_sz, ipc_msg_queue_t **mq)
{
ipc_msg_queue_t *tmp_mq;
int ret;
tmp_mq = calloc(1, (sizeof(ipc_msg_queue_t) +
num_items * sizeof(msg_item_t)));
if (!tmp_mq) {
dprintf(CRITICAL, "cannot allocate memory for message queue\n");
return ERR_NO_MEMORY;
}
tmp_mq->buf = malloc(num_items * item_sz);
if (!tmp_mq->buf) {
dprintf(CRITICAL,
"cannot allocate memory for message queue buf\n");
ret = ERR_NO_MEMORY;
goto err_alloc_buf;
}
tmp_mq->num_items = num_items;
tmp_mq->item_sz = item_sz;
list_initialize(&tmp_mq->free_list);
list_initialize(&tmp_mq->filled_list);
list_initialize(&tmp_mq->read_list);
for (uint i = 0; i < num_items; i++) {
tmp_mq->items[i].id = i;
list_add_tail(&tmp_mq->free_list, &tmp_mq->items[i].node);
}
*mq = tmp_mq;
return 0;
err_alloc_buf:
free(tmp_mq);
return ret;
}
void ipc_msg_queue_destroy(ipc_msg_queue_t *mq)
{
free(mq->buf);
free(mq);
}
bool ipc_msg_queue_is_empty(ipc_msg_queue_t *mq)
{
return list_is_empty(&mq->filled_list);
}
bool ipc_msg_queue_is_full(ipc_msg_queue_t *mq)
{
return list_is_empty(&mq->free_list);
}
static inline uint8_t *msg_queue_get_buf(ipc_msg_queue_t *mq, msg_item_t *item)
{
return mq->buf + item->id * mq->item_sz;
}
static inline msg_item_t *msg_queue_get_item(ipc_msg_queue_t *mq, uint32_t id)
{
return id < mq->num_items ? &mq->items[id] : NULL;
}
static int check_channel_locked(handle_t *chandle)
{
if (unlikely(!chandle))
return ERR_INVALID_ARGS;
if (unlikely(!ipc_is_channel(chandle)))
return ERR_INVALID_ARGS;
return NO_ERROR;
}
static int check_channel_connected_locked(handle_t *chandle)
{
int ret = check_channel_locked(chandle);
if (unlikely(ret != NO_ERROR))
return ret;
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
if (likely(chan->state == IPC_CHAN_STATE_CONNECTED)) {
DEBUG_ASSERT(chan->peer); /* there should be peer */
return NO_ERROR;
}
if (likely(chan->state == IPC_CHAN_STATE_DISCONNECTING))
return ERR_CHANNEL_CLOSED;
else
return ERR_NOT_READY;
}
static int msg_write_locked(ipc_chan_t *chan, msg_desc_t *msg)
{
ssize_t ret;
msg_item_t *item;
ipc_msg_queue_t *mq = chan->peer->msg_queue;
item = list_peek_head_type(&mq->free_list, msg_item_t, node);
if (item == NULL) {
chan->aux_state |= IPC_CHAN_AUX_STATE_SEND_BLOCKED;
return ERR_NOT_ENOUGH_BUFFER;
}
DEBUG_ASSERT(item->state == MSG_ITEM_STATE_FREE);
/* TODO: figure out what to do about handles */
item->num_handles = 0;
item->len = 0;
uint8_t *buf = msg_queue_get_buf(mq, item);
if (msg->type == IPC_MSG_BUFFER_KERNEL) {
if (msg->kern.num_handles) {
LTRACEF("handles are not supported yet\n");
return ERR_NOT_SUPPORTED;
}
ret = kern_iovec_to_membuf(buf, mq->item_sz,
(const iovec_kern_t *)msg->kern.iov,
msg->kern.num_iov);
} else if (msg->type == IPC_MSG_BUFFER_USER) {
if (msg->user.num_handles) {
LTRACEF("handles are not supported yet\n");
return ERR_NOT_SUPPORTED;
}
ret = user_iovec_to_membuf(buf, mq->item_sz,
msg->user.iov, msg->user.num_iov);
} else {
return ERR_INVALID_ARGS;
}
if (ret < 0)
return ret;
item->len = (size_t) ret;
list_delete(&item->node);
list_add_tail(&mq->filled_list, &item->node);
item->state = MSG_ITEM_STATE_FILLED;
return item->len;
}
/*
* reads the specified message by copying the data into the iov list
* provided by msg. The message must have been previously moved
* to the read list (and thus put into READ state).
*/
static int msg_read_locked(ipc_msg_queue_t *mq, uint32_t msg_id,
uint32_t offset, msg_desc_t *msg)
{
msg_item_t *item;
item = msg_queue_get_item(mq, msg_id);
if (!item) {
LTRACEF("invalid message id %d\n", msg_id);
return ERR_INVALID_ARGS;
}
if (item->state != MSG_ITEM_STATE_READ) {
LTRACEF("message %d is not in READ state (0x%x)\n",
item->id, item->state);
return ERR_INVALID_ARGS;
}
if (item->num_handles) {
LTRACEF("handles are not supported yet\n");
return ERR_NOT_SUPPORTED;
}
if (offset > item->len) {
LTRACEF("invalid offset %d\n", offset);
return ERR_INVALID_ARGS;
}
const uint8_t *buf = msg_queue_get_buf(mq, item) + offset;
size_t bytes_left = item->len - offset;
if (msg->type == IPC_MSG_BUFFER_KERNEL) {
if (msg->kern.num_handles) {
LTRACEF("handles are not supported yet\n");
return ERR_NOT_SUPPORTED;
}
return membuf_to_kern_iovec((const iovec_kern_t *)msg->kern.iov,
msg->kern.num_iov,
buf, bytes_left);
} else if (msg->type == IPC_MSG_BUFFER_USER) {
if (msg->user.num_handles) {
LTRACEF("handles are not supported yet\n");
return ERR_NOT_SUPPORTED;
}
return membuf_to_user_iovec(msg->user.iov, msg->user.num_iov,
buf, bytes_left);
} else {
return ERR_INVALID_ARGS;
}
}
/*
* Is called to look at the head of the filled messages list. It should be followed by
* calling msg_get_filled_locked call to actually move message to readable list.
*/
static int msg_peek_next_filled_locked(ipc_msg_queue_t *mq, ipc_msg_info_t *info)
{
msg_item_t *item;
item = list_peek_head_type(&mq->filled_list, msg_item_t, node);
if (!item)
return ERR_NO_MSG;
info->len = item->len;
info->id = item->id;
return NO_ERROR;
}
/*
* Is called to move top of the queue item to readable list.
*/
static void msg_get_filled_locked(ipc_msg_queue_t *mq)
{
msg_item_t *item;
item = list_peek_head_type(&mq->filled_list, msg_item_t, node);
DEBUG_ASSERT(item);
list_delete(&item->node);
list_add_tail(&mq->read_list, &item->node);
item->state = MSG_ITEM_STATE_READ;
}
static int msg_put_read_locked(ipc_chan_t *chan, uint32_t msg_id)
{
DEBUG_ASSERT(chan);
DEBUG_ASSERT(chan->msg_queue);
ipc_msg_queue_t *mq = chan->msg_queue;
msg_item_t *item = msg_queue_get_item(mq, msg_id);
if (!item || item->state != MSG_ITEM_STATE_READ)
return ERR_INVALID_ARGS;
list_delete(&item->node);
/* put it on the head since it was just taken off here */
list_add_head(&mq->free_list, &item->node);
item->state = MSG_ITEM_STATE_FREE;
ipc_chan_t *peer = chan->peer;
if (peer && (peer->aux_state & IPC_CHAN_AUX_STATE_SEND_BLOCKED)) {
peer->aux_state &= ~IPC_CHAN_AUX_STATE_SEND_BLOCKED;
peer->aux_state |= IPC_CHAN_AUX_STATE_SEND_UNBLOCKED;
handle_notify(&peer->handle);
}
return NO_ERROR;
}
long __SYSCALL sys_send_msg(uint32_t handle_id, user_addr_t user_msg)
{
handle_t *chandle;
msg_desc_t tmp_msg;
int ret;
/* copy message descriptor from user space */
tmp_msg.type = IPC_MSG_BUFFER_USER;
ret = copy_from_user(&tmp_msg.user, user_msg, sizeof(ipc_msg_user_t));
if (unlikely(ret != NO_ERROR))
return (long) ret;
/* grab handle */
ret = uctx_handle_get(current_uctx(), handle_id, &chandle);
if (unlikely(ret != NO_ERROR))
return (long) ret;
mutex_acquire(&ipc_lock);
/* check if it is avalid channel to call send_msg */
ret = check_channel_connected_locked(chandle);
if (likely(ret == NO_ERROR)) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
/* do write message to target channel */
ret = msg_write_locked(chan, &tmp_msg);
if (ret >= 0) {
/* and notify target */
handle_notify(&chan->peer->handle);
}
}
mutex_release(&ipc_lock);
handle_decref(chandle);
return (long) ret;
}
int ipc_send_msg(handle_t *chandle, ipc_msg_kern_t *msg)
{
int ret;
msg_desc_t tmp_msg;
if (!msg)
return ERR_INVALID_ARGS;
tmp_msg.type = IPC_MSG_BUFFER_KERNEL;
memcpy(&tmp_msg.kern, msg, sizeof(ipc_msg_kern_t));
mutex_acquire(&ipc_lock);
ret = check_channel_connected_locked(chandle);
if (likely(ret == NO_ERROR)) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
ret = msg_write_locked(chan, &tmp_msg);
if (ret >= 0) {
handle_notify(&chan->peer->handle);
}
}
mutex_release(&ipc_lock);
return ret;
}
long __SYSCALL sys_get_msg(uint32_t handle_id, user_addr_t user_msg_info)
{
handle_t *chandle;
ipc_msg_info_t msg_info;
int ret;
/* grab handle */
ret = uctx_handle_get(current_uctx(), handle_id, &chandle);
if (ret != NO_ERROR)
return (long) ret;
mutex_acquire(&ipc_lock);
/* check if channel handle is a valid one */
ret = check_channel_locked(chandle);
if (likely(ret == NO_ERROR)) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
/* peek next filled message */
ret = msg_peek_next_filled_locked(chan->msg_queue, &msg_info);
if (likely(ret == NO_ERROR)) {
/* copy it to user space */
ret = copy_to_user(user_msg_info,
&msg_info, sizeof(ipc_msg_info_t));
if (likely(ret == NO_ERROR)) {
/* and make it readable */
msg_get_filled_locked(chan->msg_queue);
}
}
}
mutex_release(&ipc_lock);
handle_decref(chandle);
return (long) ret;
}
int ipc_get_msg(handle_t *chandle, ipc_msg_info_t *msg_info)
{
int ret;
mutex_acquire(&ipc_lock);
/* check if channel handle */
ret = check_channel_locked(chandle);
if (likely(ret == NO_ERROR)) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
/* peek next filled message */
ret = msg_peek_next_filled_locked(chan->msg_queue, msg_info);
if (likely(ret == NO_ERROR)) {
/* and make it readable */
msg_get_filled_locked(chan->msg_queue);
}
}
mutex_release(&ipc_lock);
return ret;
}
long __SYSCALL sys_put_msg(uint32_t handle_id, uint32_t msg_id)
{
handle_t *chandle;
/* grab handle */
int ret = uctx_handle_get(current_uctx(), handle_id, &chandle);
if (unlikely(ret != NO_ERROR))
return (long) ret;
/* and put it to rest */
ret = ipc_put_msg(chandle, msg_id);
handle_decref(chandle);
return (long) ret;
}
int ipc_put_msg(handle_t *chandle, uint32_t msg_id)
{
int ret;
mutex_acquire(&ipc_lock);
/* check is channel handle is a valid one */
ret = check_channel_locked(chandle);
if (likely(ret == NO_ERROR)) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
/* retire message */
ret = msg_put_read_locked(chan, msg_id);
}
mutex_release(&ipc_lock);
return ret;
}
long __SYSCALL sys_read_msg(uint32_t handle_id, uint32_t msg_id, uint32_t offset,
user_addr_t user_msg)
{
handle_t *chandle;
msg_desc_t tmp_msg;
int ret;
/* get msg descriptor form user space */
tmp_msg.type = IPC_MSG_BUFFER_USER;
ret = copy_from_user(&tmp_msg.user, user_msg, sizeof(ipc_msg_user_t));
if (unlikely(ret != NO_ERROR))
return (long) ret;
/* grab handle */
ret = uctx_handle_get(current_uctx(), handle_id, &chandle);
if (unlikely(ret != NO_ERROR))
return (long) ret;
mutex_acquire(&ipc_lock);
/* check if channel handle is a valid one */
ret = check_channel_locked (chandle);
if (ret == NO_ERROR) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
/* read message content */
ret = msg_read_locked(chan->msg_queue, msg_id,
offset, &tmp_msg);
}
mutex_release(&ipc_lock);
handle_decref(chandle);
return (long) ret;
}
int ipc_read_msg(handle_t *chandle, uint32_t msg_id, uint32_t offset,
ipc_msg_kern_t *msg)
{
int ret;
msg_desc_t tmp_msg;
if (!msg)
return ERR_INVALID_ARGS;
tmp_msg.type = IPC_MSG_BUFFER_KERNEL;
memcpy(&tmp_msg.kern, msg, sizeof(ipc_msg_kern_t));
mutex_acquire(&ipc_lock);
ret = check_channel_locked (chandle);
if (ret == NO_ERROR) {
ipc_chan_t *chan = containerof(chandle, ipc_chan_t, handle);
ret = msg_read_locked(chan->msg_queue, msg_id,
offset, &tmp_msg);
}
mutex_release(&ipc_lock);
return ret;
}
#else /* WITH_TRUSTY_IPC */
long __SYSCALL sys_send_msg(uint32_t handle_id, user_addr_t user_msg)
{
return (long) ERR_NOT_SUPPORTED;
}
long __SYSCALL sys_get_msg(uint32_t handle_id, user_addr_t user_msg_info)
{
return (long) ERR_NOT_SUPPORTED;
}
long __SYSCALL sys_put_msg(uint32_t handle_id, uint32_t msg_id)
{
return (long) ERR_NOT_SUPPORTED;
}
long __SYSCALL sys_read_msg(uint32_t handle_id, uint32_t msg_id, uint32_t offset,
user_addr_t user_msg)
{
return (long) ERR_NOT_SUPPORTED;
}
#endif /* WITH_TRUSTY_IPC */