| // 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> | |
| /** | |
| * A handle to a queue endpoint. | |
| * | |
| * Dropping permissions can make this a receive-only or a send-only handle. | |
| */ | |
| struct QueueHandle | |
| { | |
| /** | |
| * The size of one element in this queue. | |
| */ | |
| size_t elementSize; | |
| /** | |
| * The size of the queue. | |
| */ | |
| size_t queueSize; | |
| /** | |
| * The buffer used for storing queue elements. | |
| */ | |
| void *buffer; | |
| /** | |
| * The producer counter. | |
| */ | |
| _Atomic(uint32_t) *producer; | |
| /** | |
| * The consumer counter. | |
| */ | |
| _Atomic(uint32_t) *consumer; | |
| }; | |
| __BEGIN_DECLS | |
| /** | |
| * Allocates space for a queue using `heapCapability` and stores a handle to it | |
| * via `outQueue`. The underlying allocation (which is necessary to free the | |
| * queue) is returned via `outAllocation`. | |
| * | |
| * The queue is has space for `elementCount` entries. Each entry is a fixed | |
| * size, `elementSize` bytes. | |
| */ | |
| int __cheri_libcall queue_create(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| struct QueueHandle *outQueue, | |
| void **outAllocation, | |
| 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 QueueHandle *handle); | |
| /** | |
| * Convert a queue handle returned from `queue_create` into one that can be | |
| * used *only* for receiving. | |
| * | |
| * Note: This is primarily defence in depth. A malicious holder of this queue | |
| * handle can still set the consumer counter to invalid values. | |
| */ | |
| struct QueueHandle __cheri_libcall | |
| queue_make_receive_handle(struct QueueHandle handle); | |
| /** | |
| * Convert a queue handle returned from `queue_create` into one that can be | |
| * used *only* for sending. | |
| * | |
| * Note: This is primarily defence in depth. A malicious holder of this queue | |
| * handle can still set the producer counter to invalid values and overwrite | |
| * arbitrary queue locations. | |
| */ | |
| struct QueueHandle __cheri_libcall | |
| queue_make_send_handle(struct QueueHandle 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 QueueHandle *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 QueueHandle *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 QueueHandle *handle, | |
| size_t *items); | |
| /** | |
| * Allocate a new message queue that is managed by the message queue | |
| * compartment. This is returned as two sealed pointers to send and receive | |
| * ends of the queue. | |
| */ | |
| int __cheri_compartment("message_queue") | |
| queue_create_sealed(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| struct SObjStruct **outQueueSend, | |
| struct SObjStruct **outQueReceive, | |
| size_t elementSize, | |
| size_t elementCount); | |
| /** | |
| * Destroy a queue using a sealed queue endpoint handle. The queue is not | |
| * actually freed until *both* endpoints are destroyed, which means that you | |
| * can safely call this from the sending end without the receiving end losing | |
| * access to messages held in 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 QueueHandle *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 QueueHandle *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); | |
| __END_DECLS |