blob: 18428119d28dfdf1b1fc757440e036d972ca988f [file]
/*
* 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 <kernel/usercopy.h>
#include <list.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <trace.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>
enum {
MSG_ITEM_STATE_FREE = 0,
MSG_ITEM_STATE_FILLED = 1,
MSG_ITEM_STATE_READ = 2,
};
struct msg_item {
uint8_t id;
uint8_t state;
uint num_handles;
struct handle* handles[MAX_MSG_HANDLES];
size_t len;
struct list_node node;
};
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.
*/
struct msg_item items[0];
};
/**
* @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,
struct ipc_msg_queue** mq) {
struct ipc_msg_queue* tmp_mq;
int ret;
tmp_mq = calloc(1, (sizeof(struct ipc_msg_queue) +
num_items * sizeof(struct msg_item)));
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(struct ipc_msg_queue* mq) {
/* release handles if any */
for (uint i = 0; i < mq->num_items; i++) {
struct msg_item* item = &mq->items[i];
if (item->num_handles) {
for (uint j = 0; j < item->num_handles; j++) {
handle_decref(item->handles[j]);
}
}
}
free(mq->buf);
free(mq);
}
bool ipc_msg_queue_is_empty(struct ipc_msg_queue* mq) {
return list_is_empty(&mq->filled_list);
}
bool ipc_msg_queue_is_full(struct ipc_msg_queue* mq) {
return list_is_empty(&mq->free_list);
}
static inline uint8_t* msg_queue_get_buf(struct ipc_msg_queue* mq,
struct msg_item* item) {
return mq->buf + item->id * mq->item_sz;
}
static inline struct msg_item* msg_queue_get_item(struct ipc_msg_queue* mq,
uint32_t id) {
return id < mq->num_items ? &mq->items[id] : NULL;
}
static int check_channel(struct handle* chandle) {
if (unlikely(!chandle))
return ERR_INVALID_ARGS;
if (unlikely(!ipc_is_channel(chandle)))
return ERR_INVALID_ARGS;
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
if (unlikely(!chan->peer))
return ERR_NOT_READY;
return NO_ERROR;
}
static ssize_t kern_msg_write_locked(struct ipc_msg_queue* mq,
struct msg_item* item,
const struct ipc_msg_kern* msg) {
ssize_t ret = NO_ERROR;
uint8_t* buf = msg_queue_get_buf(mq, item);
if (msg->num_handles) {
if (msg->num_handles > MAX_MSG_HANDLES) {
LTRACEF("sending too many (%u) handles\n", msg->num_handles);
return ERR_TOO_BIG;
}
if (!msg->handles)
return ERR_INVALID_ARGS;
}
/* copy any message body */
if (likely(msg->num_iov)) {
ret = kern_iovec_to_membuf(buf, mq->item_sz,
(const struct iovec_kern*)msg->iov,
msg->num_iov);
if (ret < 0)
return ret;
}
/* copy attached handles */
for (uint i = 0; i < msg->num_handles; i++) {
if (!msg->handles[i]) {
ret = ERR_BAD_HANDLE;
goto err_bad_handle;
}
if (!handle_is_sendable(msg->handles[i])) {
ret = ERR_NOT_ALLOWED;
goto err_bad_handle;
}
/* grab an additional reference */
handle_incref(msg->handles[i]);
item->handles[i] = msg->handles[i];
item->num_handles++;
}
return ret;
err_bad_handle:
for (uint i = 0; i < item->num_handles; i++) {
handle_decref(item->handles[i]);
item->handles[i] = NULL;
}
item->num_handles = 0;
return ret;
}
static ssize_t user_msg_write_locked(struct ipc_msg_queue* mq,
struct msg_item* item,
const struct ipc_msg_user* msg,
struct uctx* uctx) {
int rc;
ssize_t ret;
uint8_t* buf = msg_queue_get_buf(mq, item);
if (msg->num_handles > MAX_MSG_HANDLES) {
LTRACEF("sending too many (%u) handles\n", msg->num_handles);
return ERR_TOO_BIG;
}
/* copy message body */
ret = user_iovec_to_membuf(buf, mq->item_sz, msg->iov, msg->num_iov);
if (ret < 0)
return ret;
if (!msg->num_handles)
return ret; /* no handles, just return body */
/* copy handle ids from user space */
handle_id_t ids[MAX_MSG_HANDLES];
rc = copy_from_user(&ids, msg->handles,
msg->num_handles * sizeof(handle_id_t));
if (unlikely(rc != NO_ERROR))
return rc;
/* Need to send all or none */
for (uint i = 0; i < msg->num_handles; i++) {
rc = uctx_handle_get(uctx, ids[i], &item->handles[i]);
if (unlikely(rc != NO_ERROR)) {
goto err_get;
}
item->num_handles++;
if (!handle_is_sendable(item->handles[i])) {
rc = ERR_NOT_ALLOWED;
goto err_send;
}
}
return ret;
err_send:
err_get:
for (uint i = 0; i < item->num_handles; i++) {
handle_decref(item->handles[i]);
item->handles[i] = NULL;
}
item->num_handles = 0;
return rc;
}
static int msg_write_locked(struct ipc_chan* chan,
const void* msg,
struct uctx* uctx) {
ssize_t ret;
struct msg_item* item;
struct ipc_chan* peer = chan->peer;
if (peer->state != IPC_CHAN_STATE_CONNECTED) {
if (likely(peer->state == IPC_CHAN_STATE_DISCONNECTING))
return ERR_CHANNEL_CLOSED;
else
return ERR_NOT_READY;
}
struct ipc_msg_queue* mq = peer->msg_queue;
item = list_peek_head_type(&mq->free_list, struct msg_item, node);
if (item == NULL) {
peer->aux_state |= IPC_CHAN_AUX_STATE_PEER_SEND_BLOCKED;
return ERR_NOT_ENOUGH_BUFFER;
}
if (!list_is_empty(&chan->msg_queue->read_list)) {
/**
* Application shall retire read messages (via put_msg api)
* before sending responses. This not only allows to not waste
* receive message queue spots, but more importantly prevents
* a hard-to-debug race condition where incoming messages from linux
* are silently dropped due to the receive queue being freed
* only after the response is sent.
*/
TRACEF("WARNING: sending outgoing messages while incoming messages are in read state is not allowed\n");
/**
* todo: return an error after sufficient soak time of the warning log
* return ERR_NOT_ALLOWED;
*/
}
DEBUG_ASSERT(item->state == MSG_ITEM_STATE_FREE);
item->num_handles = 0;
item->len = 0;
if (uctx)
ret = user_msg_write_locked(mq, item, msg, uctx);
else
ret = kern_msg_write_locked(mq, item, msg);
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;
}
/*
* Check if specified message id is valid, message is in read state
* and provided offset is within message bounds.
*/
static struct msg_item* msg_check_read_item(struct ipc_msg_queue* mq,
uint32_t msg_id,
uint32_t offset) {
struct msg_item* item;
item = msg_queue_get_item(mq, msg_id);
if (!item) {
LTRACEF("invalid message id %d\n", msg_id);
return NULL;
}
if (item->state != MSG_ITEM_STATE_READ) {
LTRACEF("message %d is not in READ state (0x%x)\n", item->id,
item->state);
return NULL;
}
if (offset > item->len) {
LTRACEF("invalid offset %d\n", offset);
return NULL;
}
return item;
}
/*
* Reads the specified message by copying message data into the iov list
* and associated handles to destination handle array provided by kmsg.
* The message must have been previously moved to the read list (and thus
* put into READ state).
*/
static int kern_msg_read_locked(struct ipc_msg_queue* mq,
int32_t msg_id,
uint32_t offset,
struct ipc_msg_kern* kmsg) {
int ret = 0;
struct msg_item* item;
item = msg_check_read_item(mq, msg_id, offset);
if (!item)
return ERR_INVALID_ARGS;
const uint8_t* buf = msg_queue_get_buf(mq, item) + offset;
size_t bytes_left = item->len - offset;
if (likely(kmsg->num_iov)) {
ret = membuf_to_kern_iovec((const struct iovec_kern*)kmsg->iov,
kmsg->num_iov, buf, bytes_left);
if (ret < 0)
return ret;
}
uint hcnt = MIN(kmsg->num_handles, item->num_handles);
for (uint i = 0; i < hcnt; i++) {
handle_incref(item->handles[i]);
kmsg->handles[i] = item->handles[i];
}
return ret;
}
/*
* Reads the specified message by copying message data to user space (iov list
* is provided by umsg) and associated handles to destination handle array
* provided by caller. The message must have been previously moved to the read
* list (and thus put into READ state).
*/
static int user_msg_read_locked(struct ipc_msg_queue* mq,
uint32_t msg_id,
uint32_t offset,
struct ipc_msg_user* umsg,
struct handle** ph,
uint* phcnt) {
int ret;
struct msg_item* item;
item = msg_check_read_item(mq, msg_id, offset);
if (!item)
return ERR_INVALID_ARGS;
const uint8_t* buf = msg_queue_get_buf(mq, item) + offset;
size_t bytes_left = item->len - offset;
ret = membuf_to_user_iovec(umsg->iov, umsg->num_iov, buf, bytes_left);
if (ret < 0)
return ret;
/* return out handles with additional refs */
uint hcnt = MIN(umsg->num_handles, item->num_handles);
for (uint i = 0; i < hcnt; i++) {
handle_incref(item->handles[i]);
ph[i] = item->handles[i];
}
*phcnt = hcnt;
return ret;
}
/*
* 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(struct ipc_msg_queue* mq,
struct ipc_msg_info* info) {
struct msg_item* item;
item = list_peek_head_type(&mq->filled_list, struct msg_item, node);
if (!item)
return ERR_NO_MSG;
info->len = item->len;
info->id = item->id;
info->num_handles = item->num_handles;
return NO_ERROR;
}
/*
* Is called to move top of the queue item to readable list.
*/
static void msg_get_filled_locked(struct ipc_msg_queue* mq) {
struct msg_item* item;
item = list_peek_head_type(&mq->filled_list, struct msg_item, 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(struct ipc_chan* chan,
uint32_t msg_id,
struct handle** ph,
uint* phcnt) {
DEBUG_ASSERT(chan);
DEBUG_ASSERT(chan->msg_queue);
DEBUG_ASSERT(ph);
DEBUG_ASSERT(phcnt);
struct ipc_msg_queue* mq = chan->msg_queue;
struct msg_item* item = msg_queue_get_item(mq, msg_id);
if (!item || item->state != MSG_ITEM_STATE_READ)
return ERR_INVALID_ARGS;
list_delete(&item->node);
/* detach handles from table if any */
for (uint j = 0; j < item->num_handles; j++) {
ph[j] = item->handles[j];
item->handles[j] = NULL;
}
*phcnt = item->num_handles;
item->num_handles = 0;
/* 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;
return NO_ERROR;
}
long __SYSCALL sys_send_msg(uint32_t handle_id, user_addr_t user_msg) {
struct handle* chandle;
struct ipc_msg_user tmp_msg;
int ret;
struct uctx* uctx = current_uctx();
/* copy message descriptor from user space */
ret = copy_from_user(&tmp_msg, user_msg, sizeof(struct ipc_msg_user));
if (unlikely(ret != NO_ERROR))
return (long)ret;
/* grab handle */
ret = uctx_handle_get(uctx, handle_id, &chandle);
if (unlikely(ret != NO_ERROR))
return (long)ret;
ret = check_channel(chandle);
if (likely(ret == NO_ERROR)) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
mutex_acquire(&chan->peer->mlock);
ret = msg_write_locked(chan, &tmp_msg, uctx);
mutex_release(&chan->peer->mlock);
if (ret >= 0) {
/* and notify target */
handle_notify(&chan->peer->handle);
}
}
handle_decref(chandle);
return (long)ret;
}
int ipc_send_msg(struct handle* chandle, struct ipc_msg_kern* msg) {
int ret;
if (!msg)
return ERR_INVALID_ARGS;
ret = check_channel(chandle);
if (likely(ret == NO_ERROR)) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
mutex_acquire(&chan->peer->mlock);
ret = msg_write_locked(chan, msg, NULL);
mutex_release(&chan->peer->mlock);
if (ret >= 0) {
handle_notify(&chan->peer->handle);
}
}
return ret;
}
long __SYSCALL sys_get_msg(uint32_t handle_id, user_addr_t user_msg_info) {
struct handle* chandle;
struct ipc_msg_info mi_kern;
int ret;
/* grab handle */
ret = uctx_handle_get(current_uctx(), handle_id, &chandle);
if (ret != NO_ERROR)
return (long)ret;
/* check if channel handle is a valid one */
ret = check_channel(chandle);
if (likely(ret == NO_ERROR)) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
mutex_acquire(&chan->mlock);
/* peek next filled message */
ret = msg_peek_next_filled_locked(chan->msg_queue, &mi_kern);
if (likely(ret == NO_ERROR)) {
/* copy it to user space */
struct ipc_msg_info_user mi_user;
memset(&mi_user, 0, sizeof(mi_user));
mi_user.len = (user_size_t)mi_kern.len;
mi_user.id = mi_kern.id;
mi_user.num_handles = mi_kern.num_handles;
ret = copy_to_user(user_msg_info, &mi_user, sizeof(mi_user));
if (likely(ret == NO_ERROR)) {
/* and make it readable */
msg_get_filled_locked(chan->msg_queue);
}
}
mutex_release(&chan->mlock);
}
handle_decref(chandle);
return (long)ret;
}
int ipc_get_msg(struct handle* chandle, struct ipc_msg_info* msg_info) {
int ret;
/* check if channel handle */
ret = check_channel(chandle);
if (likely(ret == NO_ERROR)) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
mutex_acquire(&chan->mlock);
/* 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(&chan->mlock);
}
return ret;
}
long __SYSCALL sys_put_msg(uint32_t handle_id, uint32_t msg_id) {
struct handle* 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(struct handle* chandle, uint32_t msg_id) {
int ret;
/* check is channel handle is a valid one */
ret = check_channel(chandle);
if (unlikely(ret != NO_ERROR))
return ret;
struct handle* h[MAX_MSG_HANDLES];
uint hcnt = 0;
bool need_notify = false;
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
/* retire message */
mutex_acquire(&chan->mlock);
ret = msg_put_read_locked(chan, msg_id, h, &hcnt);
if (ret == NO_ERROR &&
(chan->aux_state & IPC_CHAN_AUX_STATE_PEER_SEND_BLOCKED)) {
chan->aux_state &= ~IPC_CHAN_AUX_STATE_PEER_SEND_BLOCKED;
need_notify = true;
}
mutex_release(&chan->mlock);
/* drop handle references outside of the lock */
for (uint i = 0; i < hcnt; i++) {
handle_decref(h[i]);
}
if (need_notify) {
mutex_acquire(&chan->peer->mlock);
chan->peer->aux_state |= IPC_CHAN_AUX_STATE_SEND_UNBLOCKED;
mutex_release(&chan->peer->mlock);
handle_notify(&chan->peer->handle);
}
return ret;
}
static void user_remove_multiple(struct uctx* uctx,
handle_id_t* hids,
uint hcnt) {
for (uint i = 0; i < hcnt; i++)
uctx_handle_remove(uctx, hids[i], NULL);
}
static int user_install_multiple(struct uctx* uctx,
struct handle** hptrs,
handle_id_t* hids,
uint hcnt) {
for (uint i = 0; i < hcnt; i++) {
int rc = uctx_handle_install(uctx, hptrs[i], &hids[i]);
if (rc) {
user_remove_multiple(uctx, hids, i);
return rc;
}
}
return 0;
}
static int user_return_handles(struct uctx* uctx,
user_addr_t uhptrs,
struct handle** hptrs,
uint hcnt) {
int rc;
handle_id_t hids[MAX_MSG_HANDLES];
if (hcnt > MAX_MSG_HANDLES) {
LTRACEF("returning too many (%u) handles\n", hcnt);
return ERR_TOO_BIG;
}
/* install handles */
rc = user_install_multiple(uctx, hptrs, hids, hcnt);
if (rc < 0)
return rc;
/* copy out handle ids */
rc = copy_to_user(uhptrs, hids, hcnt * sizeof(handle_id_t));
if (rc < 0) {
/* remove installed handles in case of error */
user_remove_multiple(uctx, hids, hcnt);
return rc;
}
return 0;
}
long __SYSCALL sys_read_msg(uint32_t handle_id,
uint32_t msg_id,
uint32_t offset,
user_addr_t user_msg) {
struct handle* chandle;
struct ipc_msg_user msg;
int ret;
struct uctx* uctx = current_uctx();
/* get msg descriptor from user space */
ret = copy_from_user(&msg, user_msg, sizeof(struct ipc_msg_user));
if (unlikely(ret != NO_ERROR))
return (long)ret;
/* grab handle */
ret = uctx_handle_get(uctx, handle_id, &chandle);
if (unlikely(ret != NO_ERROR))
return (long)ret;
/* check if channel handle is a valid one */
ret = check_channel(chandle);
if (ret == NO_ERROR) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
struct handle* h[MAX_MSG_HANDLES];
uint hcnt = 0;
mutex_acquire(&chan->mlock);
ret = user_msg_read_locked(chan->msg_queue, msg_id, offset, &msg, h,
&hcnt);
mutex_release(&chan->mlock);
if (ret >= 0 && hcnt) {
/* install into caller handle table and copy them out */
int rc = user_return_handles(uctx, msg.handles, h, hcnt);
if (rc < 0) {
ret = rc;
}
/* drop references obtained in user_msg_read_locked */
for (uint i = 0; i < hcnt; i++)
handle_decref(h[i]);
}
}
handle_decref(chandle);
return (long)ret;
}
int ipc_read_msg(struct handle* chandle,
uint32_t msg_id,
uint32_t offset,
struct ipc_msg_kern* msg) {
int ret;
if (!msg)
return ERR_INVALID_ARGS;
ret = check_channel(chandle);
if (ret == NO_ERROR) {
struct ipc_chan* chan = containerof(chandle, struct ipc_chan, handle);
mutex_acquire(&chan->mlock);
ret = kern_msg_read_locked(chan->msg_queue, msg_id, offset, msg);
mutex_release(&chan->mlock);
}
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 */