blob: 85425454627abe38086ece0c649445da03ac0501 [file]
/*
* Copyright (c) 2019, 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.
*/
#include <assert.h>
#include <err.h>
#include <kernel/mutex.h>
#include <kernel/usercopy.h>
#include <lib/binary_search_tree.h>
#include <lib/syscall.h>
#include <list.h>
#include <platform/interrupts.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <trace.h>
#define LOCAL_TRACE 0
#include <lib/trusty/event.h>
#include <lib/trusty/handle.h>
#include <lib/trusty/trusty_app.h>
#include <lib/trusty/uctx.h>
#include <lib/trusty/uio.h>
/**
* enum event_state - event states
* @EVENT_STATE_UNSIGNALED:
* state is an initial state of any event object. An event object leaves
* @EVENT_STATE_UNSIGNALED state when it becomes signaled. An event object
* might return to @EVENT_STATE_UNSIGNALED state under certain conditions
* (see below).
* @EVENT_STATE_SIGNALED:
* state is entered by event source object when event_source_signal() routine
* is invoked. The @EVENT_STATE_SIGNALED state is entered by event client
* object from @EVENT_STATE_UNSIGNALED state in when event_source_signal()
* routine if invoke on event source or from @EVENT_STATE_NOTIFIED_SIGNALED
* state when client acknowledges that previously delivered event has been
* handled. Waiting on event client object in @EVENT_STATE_SIGNALED state
* generates the %IPC_HANDLE_POLL_MSG event for its waiters.
* @EVENT_STATE_NOTIFIED:
* is entered by an event client object from @EVENT_STATE_SIGNALED state
* after %IPC_HANDLE_POLL_MSG event has been delivered to the client. In
* this state, the event client object stops generating %IPC_HANDLE_POLL_MSG
* event for its waiters. The client should handle received event and
* acknowledge that by invoking event_client_notify_handled() call on client
* event object. Upon receiving such acknowledgment the client event object
* is transitioning back to @EVENT_STATE_UNSIGNALED state.
* @EVENT_STATE_NOTIFIED_SIGNALED:
* is entered by an event client object from @EVENT_STATE_NOTIFIED state
* when another signal is received which is possible for events sources that
* support edge triggering semantic. Receiving acknowledgment for event
* client in this state transition event object into @EVENT_STATE_SIGNALED
* state which generates new %IPC_HANDLE_POLL_MSG event for its waiters.
* @EVENT_STATE_HANDLED:
* is entered by an event source object from @EVENT_STATE_SIGNALED state
* when all registered clients has finished handling an event and
* acknowledged that by invoking event_client_notify_handled() routine.
* Waiting on event source object when it is in @EVENT_STATE_HANDLED state
* generates %IPC_HANDLE_POLL_MSG event for its waiters and transition
* object back to @EVENT_STATE_UNSIGNALED state.
* @EVENT_STATE_CLOSED:
* is entered when the last reference to event source object handle goes
* away. This state is applicable for both event source and event client
* objects. In this state, the %IPC_HANDLE_POLL_HUP event is triggered to
* handle waiters.
*/
enum event_state {
EVENT_STATE_UNSIGNALED = 0,
EVENT_STATE_SIGNALED,
EVENT_STATE_NOTIFIED,
EVENT_STATE_NOTIFIED_SIGNALED,
EVENT_STATE_HANDLED,
EVENT_STATE_CLOSED,
};
/**
* struct event_source - represents event source object
* @name: event name
* @ops: pointed to @struct event_source_ops
* @ops_arg: pointer passes as &priv parameters of all ops callbacks
* @uuids: pointer to array of &struct uuid items that are allowed to open
* this event source object
* @uuids_num: number of items in @uuids array
* @refobj: ref object
* @handle_ref: self reference from @handle
* @handle: embedded @struct handle
* @tree_node: tracking @struct bst node
* @client_list: list of attached clients
* @client_cnt: number of attached clients
* @slock: spinlock protecting internal state
* @ack_cnt: required ack count
* @state: event source state
*
* Note: the event object internal state and state transitions are protected by
* two locks: the global mutex (&es_lock) and a spin lock (@slock) private to
* event source object. The global mutex is held to protect operations related
* to global event object list (insert, remove and lookup) and ref object.
* In addition, it is held to synchronize invocation of &open and &close
* callbacks which is happening in context of creating and destroying event
* objects. All other state transitions are protected by the spin lock.
*/
struct event_source {
const char* name;
const struct event_source_ops* ops;
const void* ops_arg;
const uuid_t* uuids;
unsigned int uuids_num;
struct obj refobj;
/* handle_ref is a self reference when there are
* outstanding handles out there. It is removed
* when last handle ref goes away.
*/
struct obj_ref handle_ref;
struct handle handle;
struct bst_node tree_node;
struct list_node client_list;
unsigned int client_cnt;
spin_lock_t slock;
unsigned int ack_cnt;
volatile int state;
};
struct event_client {
struct handle handle;
struct list_node node;
struct event_source* es;
struct obj_ref es_ref;
volatile int state;
};
#define SLOCK_FLAGS SPIN_LOCK_FLAG_INTERRUPTS
static uint32_t event_source_poll(struct handle* handle,
uint32_t emask,
bool finalize);
static void event_source_destroy(struct handle* handle);
static uint32_t event_client_poll(struct handle* handle,
uint32_t emask,
bool finalize);
static void event_client_destroy(struct handle* handle);
static ssize_t event_client_user_readv(struct handle* h,
user_addr_t iov_uaddr,
uint32_t iov_cnt);
static ssize_t event_client_user_writev(struct handle* handle,
user_addr_t iov_uaddr,
uint32_t iov_cnt);
static mutex_t es_lock = MUTEX_INITIAL_VALUE(es_lock);
static struct bst_root es_tree_root = BST_ROOT_INITIAL_VALUE;
static struct handle_ops event_source_handle_ops = {
.poll = event_source_poll,
.destroy = event_source_destroy,
};
static struct handle_ops event_client_handle_ops = {
.poll = event_client_poll,
.destroy = event_client_destroy,
.user_readv = event_client_user_readv,
.user_writev = event_client_user_writev,
};
/******************************************************************************/
static struct event_source* handle_to_event_source(struct handle* h) {
ASSERT(h);
ASSERT(h->ops == &event_source_handle_ops);
return containerof(h, struct event_source, handle);
}
static int event_source_bst_compare(struct bst_node* a, struct bst_node* b) {
struct event_source* es_a = containerof(a, struct event_source, tree_node);
struct event_source* es_b = containerof(b, struct event_source, tree_node);
return strcmp(es_a->name, es_b->name);
}
static uint32_t event_source_poll(struct handle* h,
uint32_t emask,
bool finalize) {
int oldstate;
spin_lock_saved_state_t state;
struct event_source* es = handle_to_event_source(h);
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
oldstate = es->state;
if (finalize && (oldstate == EVENT_STATE_HANDLED)) {
es->state = EVENT_STATE_UNSIGNALED;
}
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
if (oldstate == EVENT_STATE_HANDLED) {
return IPC_HANDLE_POLL_MSG;
}
return 0;
}
static void event_source_obj_destroy(struct obj* obj) {
struct event_source* es = containerof(obj, struct event_source, refobj);
free(es);
}
static void event_source_destroy(struct handle* h) {
struct event_client* ec;
struct event_source* es;
spin_lock_saved_state_t state;
/* called when the last reference to handle goes away */
es = handle_to_event_source(h);
mutex_acquire(&es_lock);
/* if event source in global list : remove it */
if (es->tree_node.rank) {
bst_delete(&es_tree_root, &es->tree_node);
/* notify observers that event source is closed */
if (es->ops)
es->ops->close(es->ops_arg);
}
/* mark all clients still connected as closed */
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
es->state = EVENT_STATE_CLOSED;
list_for_every_entry(&es->client_list, ec, struct event_client, node) {
ec->state = EVENT_STATE_CLOSED;
handle_notify(&ec->handle);
}
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
/* clear pointers that should not be accessed past this point */
es->ops = NULL;
es->ops_arg = NULL;
es->uuids = NULL;
es->name = NULL;
/* remove self reference */
obj_del_ref(&es->refobj, &es->handle_ref, event_source_obj_destroy);
mutex_release(&es_lock);
}
static struct event_source* event_source_lookup_locked(const char* name,
const uuid_t* uuid,
struct obj_ref* ref) {
struct bst_node* tn;
struct event_source* es;
struct event_source unused;
/* only init .name */
unused.name = name;
DEBUG_ASSERT(is_mutex_held(&es_lock));
tn = bst_search(&es_tree_root, &unused.tree_node, event_source_bst_compare);
if (!tn) {
/* Object not found */
return NULL;
}
/* Object found: check if we are allowed to connect */
es = containerof(tn, struct event_source, tree_node);
if (!es->uuids_num) {
/* No uuids are configured: allow anybody */
obj_add_ref(&es->refobj, ref);
return es;
}
/* check client */
for (uint32_t i = 0; i < es->uuids_num; i++) {
if (memcmp(uuid, &es->uuids[i], sizeof(*uuid)) == 0) {
obj_add_ref(&es->refobj, ref);
return es;
}
}
return NULL;
}
static void event_source_attach_client_locked(struct event_source* es,
struct event_client* ec) {
spin_lock_saved_state_t state;
DEBUG_ASSERT(is_mutex_held(&es_lock));
DEBUG_ASSERT(!spin_lock_held(&es->slock));
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
/* add ref to es and attach client to tracking list */
ec->es = es;
obj_add_ref(&es->refobj, &ec->es_ref);
list_add_tail(&es->client_list, &ec->node);
/* client starts in EVENT_STATE_UNSIGNALED state */
ec->state = EVENT_STATE_UNSIGNALED;
es->client_cnt++;
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
if (es->client_cnt == 1) {
/* if first client (invokes open) */
if (es->ops && es->ops->open) {
es->ops->open(es->ops_arg);
}
}
}
static void event_source_notify_done_slocked(struct event_source* es) {
DEBUG_ASSERT(spin_lock_held(&es->slock));
ASSERT(es->ack_cnt > 0);
/* decrement ack count of event source */
if (--es->ack_cnt == 0) {
/* All clients notified */
es->state = EVENT_STATE_HANDLED;
handle_notify(&es->handle);
if (es->ops && es->ops->unmask) {
es->ops->unmask(es->ops_arg);
}
}
}
int event_source_signal(struct handle* h) {
struct event_client* ec;
struct event_source* es;
spin_lock_saved_state_t state;
es = handle_to_event_source(h);
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
if (es->ops && es->ops->mask) {
/*
* If we have mask method we are in "level triggered" mode. It is
* expected that event should be signaled only if the event source is
* in EVENT_STATE_UNSIGNALED or EVENT_STATE_HANDLED state.
*/
ASSERT(es->state == EVENT_STATE_UNSIGNALED ||
es->state == EVENT_STATE_HANDLED);
/* mask source */
es->ops->mask(es->ops_arg);
}
if (es->client_cnt) {
/* we have clients */
es->ack_cnt = es->client_cnt;
es->state = EVENT_STATE_SIGNALED;
list_for_every_entry(&es->client_list, ec, struct event_client, node) {
if (ec->state == EVENT_STATE_UNSIGNALED) {
/* enter signaled state and pet handle */
ec->state = EVENT_STATE_SIGNALED;
handle_notify(&ec->handle);
} else if (ec->state == EVENT_STATE_NOTIFIED) {
/* enter signaled notify state */
ec->state = EVENT_STATE_NOTIFIED_SIGNALED;
}
}
} else {
/* no clients: mark source as handled and notify source handle */
es->state = EVENT_STATE_HANDLED;
handle_notify(&es->handle);
}
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
return NO_ERROR;
}
int event_source_publish(struct handle* h) {
bool inserted;
struct event_source* es = handle_to_event_source(h);
mutex_acquire(&es_lock);
inserted =
bst_insert(&es_tree_root, &es->tree_node, event_source_bst_compare);
mutex_release(&es_lock);
return inserted ? NO_ERROR : ERR_ALREADY_EXISTS;
}
int event_source_create(const char* name,
const struct event_source_ops* ops,
const void* ops_arg,
const struct uuid* uuids,
unsigned int uuids_num,
unsigned int flags,
struct handle** ph) {
struct event_source* es;
if (!name || *name == 0)
return ERR_INVALID_ARGS;
es = calloc(1, sizeof(*es));
if (!es) {
return ERR_NO_MEMORY;
}
es->name = name;
if (ops) {
ASSERT(ops->open);
ASSERT(ops->close);
/* mask and unmask must be set together */
ASSERT(!ops->mask == !ops->unmask);
}
es->ops = ops;
es->ops_arg = ops_arg;
es->uuids = uuids;
es->uuids_num = uuids_num;
spin_lock_init(&es->slock);
list_initialize(&es->client_list);
bst_node_initialize(&es->tree_node);
obj_init(&es->refobj, &es->handle_ref);
handle_init(&es->handle, &event_source_handle_ops);
*ph = &es->handle;
return NO_ERROR;
}
int event_source_open(const uuid_t* cid,
const char* name,
size_t max_name,
uint flags,
struct handle** ph) {
int ret;
struct event_source* es;
struct event_client* ec = NULL;
struct obj_ref es_tmp_ref = OBJ_REF_INITIAL_VALUE(es_tmp_ref);
if (!name) {
return ERR_INVALID_ARGS;
}
size_t len = strnlen(name, max_name);
if (len == 0 || len >= max_name) {
/* empty or unterminated string */
LTRACEF("invalid path specified\n");
return ERR_INVALID_ARGS;
}
/* After this point name is zero terminated */
mutex_acquire(&es_lock);
/* lookup event source */
es = event_source_lookup_locked(name, cid, &es_tmp_ref);
if (!es) {
ret = ERR_NOT_FOUND;
goto err_not_found;
}
/* allocate handle and tracking structure */
ec = calloc(1, sizeof(*ec));
if (!ec) {
ret = ERR_NO_MEMORY;
goto err_alloc;
}
obj_ref_init(&ec->es_ref);
handle_init(&ec->handle, &event_client_handle_ops);
/* attach it to event source */
event_source_attach_client_locked(es, ec);
/* Looks OK */
handle_incref(&ec->handle);
*ph = &ec->handle;
ret = NO_ERROR;
err_attach:
err_alloc:
obj_del_ref(&es->refobj, &es_tmp_ref, event_source_obj_destroy);
err_not_found:
mutex_release(&es_lock);
if (ec) {
handle_decref(&ec->handle);
}
return ret;
}
/******************************************************************************/
static bool handle_is_client(struct handle* handle) {
ASSERT(handle);
return likely(handle->ops == &event_client_handle_ops);
}
static uint32_t event_client_poll(struct handle* h,
uint32_t emask,
bool finalize) {
int oldstate;
spin_lock_saved_state_t state;
ASSERT(handle_is_client(h));
struct event_client* ec = containerof(h, struct event_client, handle);
spin_lock_save(&ec->es->slock, &state, SLOCK_FLAGS);
oldstate = ec->state;
if (finalize && (oldstate == EVENT_STATE_SIGNALED)) {
ec->state = EVENT_STATE_NOTIFIED;
}
spin_unlock_restore(&ec->es->slock, state, SLOCK_FLAGS);
if (oldstate == EVENT_STATE_CLOSED) {
return IPC_HANDLE_POLL_HUP;
}
if (oldstate == EVENT_STATE_SIGNALED) {
return IPC_HANDLE_POLL_MSG;
}
return 0;
}
static void event_client_notify_done_slocked(struct event_client* ec) {
struct event_source* es = ec->es;
/* event source spinlock must be held. Global es_lock is not required */
DEBUG_ASSERT(spin_lock_held(&es->slock));
if (ec->state == EVENT_STATE_NOTIFIED_SIGNALED) {
/* back to signaled state and pet handle */
ec->state = EVENT_STATE_SIGNALED;
handle_notify(&ec->handle);
} else if (ec->state == EVENT_STATE_NOTIFIED) {
/* back to unsignaled state and update source */
ec->state = EVENT_STATE_UNSIGNALED;
event_source_notify_done_slocked(es);
}
}
static void event_client_destroy(struct handle* h) {
int oldstate;
struct event_client* ec;
struct event_source* es;
spin_lock_saved_state_t state;
ASSERT(handle_is_client(h));
ec = containerof(h, struct event_client, handle);
mutex_acquire(&es_lock);
es = ec->es;
ASSERT(es);
/* detach client */
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
oldstate = ec->state;
if (oldstate != EVENT_STATE_CLOSED) {
/* if source is not closed */
if (oldstate == EVENT_STATE_SIGNALED ||
oldstate == EVENT_STATE_NOTIFIED ||
oldstate == EVENT_STATE_NOTIFIED_SIGNALED) {
/* then invoke notify done */
event_source_notify_done_slocked(es);
}
ec->state = EVENT_STATE_CLOSED;
}
ASSERT(list_in_list(&ec->node));
list_delete(&ec->node);
es->client_cnt--;
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
if (oldstate != EVENT_STATE_CLOSED) {
if (es->client_cnt == 0) {
/* last client: invoke close */
if (es->ops && es->ops->close) {
es->ops->close(es->ops_arg);
}
}
}
/* Remove reference to source object */
obj_del_ref(&es->refobj, &ec->es_ref, event_source_obj_destroy);
/* free client */
free(ec);
mutex_release(&es_lock);
}
int event_client_notify_handled(struct handle* h) {
int ret = NO_ERROR;
struct event_client* ec;
struct event_source* es;
spin_lock_saved_state_t state;
if (!handle_is_client(h)) {
return ERR_INVALID_ARGS;
}
ec = containerof(h, struct event_client, handle);
es = ec->es;
ASSERT(es);
spin_lock_save(&es->slock, &state, SLOCK_FLAGS);
switch (ec->state) {
case EVENT_STATE_NOTIFIED:
case EVENT_STATE_NOTIFIED_SIGNALED:
event_client_notify_done_slocked(ec);
break;
case EVENT_STATE_CLOSED:
ret = ERR_CHANNEL_CLOSED;
break;
default:
ret = ERR_BAD_STATE;
}
spin_unlock_restore(&es->slock, state, SLOCK_FLAGS);
return ret;
}
static ssize_t event_client_user_writev(struct handle* h,
user_addr_t iov_uaddr,
uint32_t iov_cnt) {
int ret;
ssize_t len;
uint32_t cmd;
DEBUG_ASSERT(h);
if (iov_cnt != 1) {
/* we expect exactly one iov here */
return ERR_INVALID_ARGS;
}
len = user_iovec_to_membuf((uint8_t*)&cmd, sizeof(cmd), iov_uaddr, iov_cnt);
if (len < 0) {
/* most likely FAULT */
return (int32_t)len;
}
if (len != sizeof(cmd)) {
/* partial write */
return ERR_INVALID_ARGS;
}
switch (cmd) {
case EVENT_NOTIFY_CMD_HANDLED:
ret = event_client_notify_handled(h);
break;
default:
ret = ERR_INVALID_ARGS;
}
return ret;
}
static ssize_t event_client_user_readv(struct handle* h,
user_addr_t iov_uaddr,
uint32_t iov_cnt) {
return 0;
}