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