| /* |
| * 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 */ |
| |
| |
| |