blob: 68743da14ad5211ab24b42a81bbf395fd3436341 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
/**
* This file contains the interface for a simple message queue. This is split
* into two layers. The core functionality is implemented as a shared library.
* This allows message queues to be used for inter-thread communication without
* requiring cross-compartment calls except in the blocking cases (reading from
* an empty queue, writing to a full queue).
*
* These library interfaces are then wrapped in a compartment, which provides
* sealed capabilities that authorise sending or receiving messages via a
* queue. The compartment interface can be used between mutually distrusting
* compartments. Neither endpoint can corrupt the queue state, though there is
* of course no guarantee that the sender will send valid data.
*
* Both sets of queues support multiple senders and multiple receivers. This
* does *not* guarantee priority propagation and so a high-priority thread
* sending a message may be starved by a low-priority thread that attempts to
* send a message over the same queue and is preempted by a medium-priority
* thread.
*/
#pragma once
#include "cdefs.h"
#include <multiwaiter.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <timeout.h>
/**
* Structure representing a queue. This structure represents the queue
* metadata, the buffer is stored at the end.
*
* A queue is a ring buffer of fixed-sized elements with a producer and consumer
* counter.
*/
struct MessageQueue
{
/**
* The size of one element in this queue. This should not be modified after
* construction.
*/
size_t elementSize;
/**
* The size of the queue. This should not be modified after construction.
*/
size_t queueSize;
/**
* The producer counter.
*/
_Atomic(uint32_t) producer;
/**
* The consumer counter.
*/
_Atomic(uint32_t) consumer;
#ifdef __cplusplus
MessageQueue(size_t elementSize, size_t queueSize)
: elementSize(elementSize), queueSize(queueSize)
{
}
#endif
};
_Static_assert(sizeof(struct MessageQueue) % sizeof(void *) == 0,
"MessageQueue structure must end correctly aligned for storing "
"capabilities.");
__BEGIN_DECLS
/**
* Returns the allocation size needed for a queue with the specified number and
* size of elements. This can be used to statically allocate queues.
*
* Returns the allocation size on success, or `-EINVAL` if the arguments would
* cause an overflow.
*/
ssize_t __cheri_libcall queue_allocation_size(size_t elementSize,
size_t elementCount);
/**
* Allocates space for a queue using `heapCapability` and stores a handle to it
* via `outQueue`.
*
* The queue is has space for `elementCount` entries. Each entry is a fixed
* size, `elementSize` bytes.
*
* Returns 0 on success, `-ENOMEM` on allocation failure, and `-EINVAL` if the
* arguments are invalid (for example, if the requested number of elements
* multiplied by the element size would overflow).
*/
int __cheri_libcall queue_create(Timeout *timeout,
struct SObjStruct *heapCapability,
struct MessageQueue **outQueue,
size_t elementSize,
size_t elementCount);
/**
* Destroys a queue. This wakes up all threads waiting to produce or consume,
* and makes them fail to acquire the lock, before deallocating the underlying
* allocation.
*
* This must be called on an unrestricted queue handle (*not* one returned by
* `queue_make_receive_handle` or `queue_make_send_handle`).
*
* Returns 0 on success. On failure, returns `-EPERM` if the queue handle is
* restricted (see comment above).
*/
int __cheri_libcall queue_destroy(struct SObjStruct *heapCapability,
struct MessageQueue *handle);
/**
* Send a message to the queue specified by `handle`. This expects to be able
* to copy the number of bytes specified by `elementSize` when the queue was
* created from `src`.
*
* Returns 0 on success. On failure, returns `-ETIMEOUT` if the timeout was
* exhausted, `-EINVAL` on invalid arguments.
*
* This expected to be called with a valid queue handle. It does not validate
* that this is correct. It uses `safe_memcpy` and so will check the buffer.
*/
int __cheri_libcall queue_send(Timeout *timeout,
struct MessageQueue *handle,
const void *src);
/**
* Receive a message over a queue specified by `handle`. This expects to be
* able to copy the number of bytes specified by `elementSize`. The message is
* copied to `dst`, which must have sufficient permissions and space to hold
* the message.
*
* Returns 0 on success, `-ETIMEOUT` if the timeout was exhausted, `-EINVAL` on
* invalid arguments.
*/
int __cheri_libcall queue_receive(Timeout *timeout,
struct MessageQueue *handle,
void *dst);
/**
* Returns the number of items in the queue specified by `handle` via `items`.
*
* Returns 0 on success. This has no failure mechanisms, but is intended to
* have the same interface as the version that operates on a sealed queue
* handle.
*
* Note: This interface is inherently racy. The number of items in the queue
* may change in between the return of this function and the caller acting on
* the result.
*/
int __cheri_libcall queue_items_remaining(struct MessageQueue *handle,
size_t *items);
/**
* Allocate a new message queue that is managed by the message queue
* compartment. The resulting queue handle (returned in `outQueue`) is a
* sealed capability to a queue that can be used for both sending and
* receiving.
*/
int __cheri_compartment("message_queue")
queue_create_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct **outQueue,
size_t elementSize,
size_t elementCount);
/**
* Destroy a queue handle. If this is called on a restricted endpoint
* (returned from `queue_receive_handle_create_sealed` or
* `queue_send_handle_create_sealed`), this frees only the handle. If called
* with the queue handle returned from `queue_create_sealed`, this will destroy
* the queue.
*/
int __cheri_compartment("message_queue")
queue_destroy_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct *queueHandle);
/**
* Send a message via a sealed queue endpoint. This behaves in the same way as
* `queue_send`, except that it will return `-EINVAL` if the endpoint is not a
* valid sending endpoint and may return `-ECOMPARTMENTFAIL` if the queue is
* destroyed during the call.
*/
int __cheri_compartment("message_queue")
queue_send_sealed(Timeout *timeout,
struct SObjStruct *handle,
const void *src);
/**
* Receive a message via a sealed queue endpoint. This behaves in the same way
* as `queue_receive`, except that it will return `-EINVAL` if the endpoint is
* not a valid receiving endpoint and may return `-ECOMPARTMENTFAIL` if the
* queue is destroyed during the call.
*/
int __cheri_compartment("message_queue")
queue_receive_sealed(Timeout *timeout, struct SObjStruct *handle, void *dst);
/**
* Returns, via `items`, the number of items in the queue specified by `handle`.
* Returns 0 on success.
*
* This call is intended to be fast and so does minimal checking of arguments.
* It does not mutate state or acquire any locks and so may return
* `-ECOMPARTMENTFAIL` for any failure case.
*/
int __cheri_compartment("message_queue")
queue_items_remaining_sealed(struct SObjStruct *handle, size_t *items);
/**
* Initialise an event waiter source so that it will wait for the queue to be
* ready to receive. Note that this is inherently racy because another consumer
* may drain the queue before this consumer wakes up.
*/
void __cheri_libcall
multiwaiter_queue_receive_init(struct EventWaiterSource *source,
struct MessageQueue *handle);
/**
* Initialise an event waiter source so that it will wait for the queue to be
* ready to send. Note that this is inherently racy because another producer
* may fill the queue before this consumer wakes up.
*/
void __cheri_libcall
multiwaiter_queue_send_init(struct EventWaiterSource *source,
struct MessageQueue *handle);
/**
* Initialise an event waiter source as in `multiwaiter_queue_receive_init`,
* using a sealed queue endpoint. The `source` argument must be a receive
* endpoint.
*
* Returns 0 on success, `-EINVAL` on invalid arguments. May return
* `-ECOMPARTMENTFAIL` if the queue is deallocated in the middle of this call.
*/
int __cheri_compartment("message_queue")
multiwaiter_queue_receive_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle);
/**
* Initialise an event waiter source as in `multiwaiter_queue_send_init`,
* using a sealed queue endpoint. The `source` argument must be a send
* endpoint.
*
* Returns 0 on success, `-EINVAL` on invalid arguments. May return
* `-ECOMPARTMENTFAIL` if the queue is deallocated in the middle of this call.
*/
int __cheri_compartment("message_queue")
multiwaiter_queue_send_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle);
/**
* Convert a queue handle returned from `queue_create_sealed` into one that can
* be used *only* for receiving.
*
* Returns 0 on success and writes the resulting restricted handle via
* `outHandle`. Returns `-ENOMEM` on allocation failure or `-EINVAL` if the
* handle is not valid.
*/
int __cheri_compartment("message_queue")
queue_receive_handle_create_sealed(struct Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct *handle,
struct SObjStruct **outHandle);
/**
* Convert a queue handle returned from `queue_create_sealed` into one that can
* be used *only* for sending.
*
* Returns 0 on success and writes the resulting restricted handle via
* `outHandle`. Returns `-ENOMEM` on allocation failure or `-EINVAL` if the
* handle is not valid.
*/
int __cheri_compartment("message_queue")
queue_send_handle_create_sealed(struct Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct *handle,
struct SObjStruct **outHandle);
__END_DECLS