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