|  | #include <cheri.hh> | 
|  | #include <cstdlib> | 
|  | #include <errno.h> | 
|  | #include <locks.hh> | 
|  | #include <queue.h> | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  | #include <timeout.h> | 
|  | #include <type_traits> | 
|  | #include <unwind.h> | 
|  |  | 
|  | using namespace CHERI; | 
|  | using cheriot::atomic; | 
|  |  | 
|  | using Debug = ConditionalDebug<false, "MessageQueue library">; | 
|  |  | 
|  | #ifdef __cplusplus | 
|  | using MessageQueueCounter = atomic<uint32_t>; | 
|  | #else | 
|  | typedef uint32_t MessageQueueCounter; | 
|  | #endif | 
|  |  | 
|  | #include <assembly-helpers.h> | 
|  |  | 
|  | namespace | 
|  | { | 
|  | /** | 
|  | * Helpers for the queue.  The queue uses two counters that wrap on double | 
|  | * the number of elements.  This ensures that full and empty conditions | 
|  | * have different values: a queue is full when the producer is one queue | 
|  | * length ahead of the consumer, and empty when the producer and consumer | 
|  | * are equal. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Helper for wrapping increment.  Increments `counter`, wrapping to zero | 
|  | * if it reaches double `size`. | 
|  | */ | 
|  | constexpr uint32_t increment_and_wrap(uint32_t size, uint32_t counter) | 
|  | { | 
|  | counter++; | 
|  | if (2 * size == counter) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | return counter; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the number of items in the queue for the given size with the | 
|  | * specified producer and consumer counters. | 
|  | */ | 
|  | constexpr uint32_t | 
|  | items_remaining(uint32_t size, uint32_t producer, uint32_t consumer) | 
|  | { | 
|  | // If the consumer is ahead of the producer then the producer has | 
|  | // wrapped.  In this case, treat the consumer as a negative offset | 
|  | if (consumer > producer) | 
|  | { | 
|  | return (2 * size) - consumer + producer; | 
|  | } | 
|  | return producer - consumer; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if and only if the `size`, `producer`, and `consumer` | 
|  | * counters indicate a full queue. | 
|  | */ | 
|  | constexpr bool is_full(uint32_t size, uint32_t producer, uint32_t consumer) | 
|  | { | 
|  | return items_remaining(size, producer, consumer) == size; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if and only if the `producer`, and `consumer` counters | 
|  | * indicate an empty queue. | 
|  | */ | 
|  | constexpr bool is_empty(uint32_t producer, uint32_t consumer) | 
|  | { | 
|  | return producer == consumer; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper that exhaustively checks the correctness of the is-full | 
|  | * calculation for a size. | 
|  | * | 
|  | * This is purely constexpr logic for compile-time checks, it generates no | 
|  | * code. | 
|  | */ | 
|  | template<uint32_t Size> | 
|  | struct CheckIsFull | 
|  | { | 
|  | /** | 
|  | * Check that the template arguments return false for is-full.  This is | 
|  | * a separate template so that we see the values in a compiler error. | 
|  | */ | 
|  | template<uint32_t Producer, uint32_t Consumer> | 
|  | static constexpr void check_not_full() | 
|  | { | 
|  | static_assert(!is_full(Size, Producer, Consumer), | 
|  | "is-full calculation is incorrect"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check that the template arguments return true for is-full.  This is a | 
|  | * separate template so that we see the values in a compiler error. | 
|  | */ | 
|  | template<uint32_t Producer, uint32_t Consumer> | 
|  | static constexpr void check_full() | 
|  | { | 
|  | static_assert(is_full(Size, Producer, Consumer), | 
|  | "is-full calculation is incorrect"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper that uses `increment_and_wrap` to add `displacement` to | 
|  | * `start`, giving the value of a counter after `displacement` | 
|  | * increments. | 
|  | */ | 
|  | static constexpr uint32_t add(uint32_t start, uint32_t displacement) | 
|  | { | 
|  | for (uint32_t i = 0; i < displacement; i++) | 
|  | { | 
|  | start = increment_and_wrap(Size, start); | 
|  | } | 
|  | return start; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check that items-remaining returns the correct value for the given | 
|  | * counter values. | 
|  | */ | 
|  | template<uint32_t Producer, uint32_t Consumer, uint32_t Displacement> | 
|  | static constexpr void check_items_remaining() | 
|  | { | 
|  | static_assert(items_remaining(Size, Producer, Consumer) == | 
|  | Displacement, | 
|  | "items-remaining calculation is incorrect"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * For every producer counter value from 0 up to `Size` after a consumer | 
|  | * counter value, check that it returns the correct value for the | 
|  | * items-remaining, is-empty, and is-full calculations. | 
|  | */ | 
|  | template<uint32_t Consumer, uint32_t Displacement = 0> | 
|  | static constexpr void check_offsets() | 
|  | { | 
|  | constexpr auto Producer = add(Consumer, Displacement); | 
|  | check_items_remaining<Producer, Consumer, Displacement>(); | 
|  | if constexpr (Displacement == 0) | 
|  | { | 
|  | static_assert(is_empty(Producer, Consumer), | 
|  | "is-empty calculation is incorrect"); | 
|  | } | 
|  | else | 
|  | { | 
|  | static_assert(!is_empty(Producer, Consumer), | 
|  | "is-empty calculation is incorrect"); | 
|  | } | 
|  | if constexpr (Displacement == Size) | 
|  | { | 
|  | check_full<Producer, Consumer>(); | 
|  | } | 
|  | else | 
|  | { | 
|  | static_assert(Displacement < Size, | 
|  | "Displacement overflowed somehow"); | 
|  | check_not_full<Producer, Consumer>(); | 
|  | check_offsets<Consumer, Displacement + 1>(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check every valid consumer value for the given size, and every valid | 
|  | * producer value for each consumer value. | 
|  | */ | 
|  | template<uint32_t Consumer = 0> | 
|  | static constexpr bool check_sizes() | 
|  | { | 
|  | check_offsets<Consumer>(); | 
|  | if constexpr (Consumer < Size * 2) | 
|  | { | 
|  | check_sizes<Consumer + 1>(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static constexpr bool Value = check_sizes(); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Helper.  This is never false, it is always either true or compile | 
|  | * failure. | 
|  | */ | 
|  | template<uint32_t Size> | 
|  | constexpr bool CheckIsFullValue = CheckIsFull<Size>::Value; | 
|  |  | 
|  | // Check some sizes that are likely to be wrong (powers of two, primes, and | 
|  | // some other values) | 
|  | static_assert(CheckIsFullValue<1>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<3>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<4>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<10>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<17>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<32>, "CheckIsFull failed"); | 
|  | static_assert(CheckIsFullValue<33>, "CheckIsFull failed"); | 
|  |  | 
|  | /** | 
|  | * Returns a pointer to the element in the queue indicated by `counter`. | 
|  | */ | 
|  | Capability<void> buffer_at_counter(struct MessageQueue &handle, | 
|  | uint32_t             counter) | 
|  | { | 
|  | // Handle wrap for the second run around the counter. | 
|  | size_t index = | 
|  | counter >= handle.queueSize ? counter - handle.queueSize : counter; | 
|  | auto             offset = index * handle.elementSize; | 
|  | Capability<void> pointer{&handle}; | 
|  | pointer.address() += sizeof(MessageQueue) + offset; | 
|  | return pointer; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Flag lock that uses the two high bits of a word for the lock. | 
|  | * | 
|  | * The remaining bits are free for other uses, but should not be modified | 
|  | * while the lock is not held. | 
|  | */ | 
|  | struct HighBitFlagLock | 
|  | { | 
|  | /** | 
|  | * A reference to the word that holds the lock. | 
|  | */ | 
|  | atomic<uint32_t> &lockWord; | 
|  |  | 
|  | /** | 
|  | * The bit to use for the lock. | 
|  | */ | 
|  | static constexpr uint32_t LockBit                 = 1U << 31; | 
|  | static constexpr uint32_t WaitersBit              = 1U << 30; | 
|  | static constexpr uint32_t LockedInDestructModeBit = 1U << 29; | 
|  |  | 
|  | // Function required to conform to the Lock concept. | 
|  | void lock() | 
|  | { | 
|  | __builtin_unreachable(); | 
|  | } | 
|  |  | 
|  | static constexpr uint32_t reserved_bits() | 
|  | { | 
|  | return LockBit | WaitersBit | LockedInDestructModeBit; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Try to acquire the lock.  Returns true on success, false on failure. | 
|  | */ | 
|  | bool try_lock(Timeout *t) | 
|  | { | 
|  | uint32_t value; | 
|  | do | 
|  | { | 
|  | value = lockWord.load(); | 
|  | if ((value & LockedInDestructModeBit) != 0) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (value & LockBit) | 
|  | { | 
|  | // If the lock is held, set the flag that indicates that | 
|  | // there are waiters. | 
|  | if ((value & WaitersBit) == 0) | 
|  | { | 
|  | if (!lockWord.compare_exchange_strong( | 
|  | value, value | WaitersBit)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | } | 
|  | if (lockWord.wait(t, value) == -ETIMEDOUT) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | continue; | 
|  | } | 
|  | } while ( | 
|  | !lockWord.compare_exchange_strong(value, (value | LockBit))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Release the lock. | 
|  | */ | 
|  | void unlock() | 
|  | { | 
|  | uint32_t value; | 
|  | // Clear the lock bit. | 
|  | value = lockWord.load(); | 
|  | // If we're releasing the lock with waiters, wake them up. | 
|  | if (lockWord.exchange(value & ~LockBit) & WaitersBit) | 
|  | { | 
|  | lockWord.notify_all(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set the lock in destruction mode. This has the same | 
|  | * semantics as `flaglock_upgrade_for_destruction`. | 
|  | */ | 
|  | void upgrade_for_destruction() | 
|  | { | 
|  | // Atomically set the destruction bit. | 
|  | lockWord |= LockedInDestructModeBit; | 
|  | if (lockWord & WaitersBit) | 
|  | { | 
|  | lockWord.notify_all(); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | uint32_t counter_load(std::atomic<uint32_t> *counter) | 
|  | { | 
|  | return counter->load() & ~(HighBitFlagLock::reserved_bits()); | 
|  | } | 
|  |  | 
|  | void counter_store(std::atomic<uint32_t> *counter, uint32_t value) | 
|  | { | 
|  | uint32_t old; | 
|  | do | 
|  | { | 
|  | old = counter->load(); | 
|  | } while (!counter->compare_exchange_strong( | 
|  | old, (old & HighBitFlagLock::reserved_bits()) | value)); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | int queue_destroy(struct SObjStruct   *heapCapability, | 
|  | struct MessageQueue *handle) | 
|  | { | 
|  | int ret = 0; | 
|  | // Only upgrade the locks for destruction if we know that we will be | 
|  | // able to free the queue at the end. This will fail if passed a | 
|  | // restricted buffer, which will happen if `queue_destroy` is called on | 
|  | // a restricted queue. | 
|  | if (ret = heap_can_free(heapCapability, handle); ret != 0) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | HighBitFlagLock producerLock{handle->producer}; | 
|  | producerLock.upgrade_for_destruction(); | 
|  |  | 
|  | HighBitFlagLock consumerLock{handle->consumer}; | 
|  | consumerLock.upgrade_for_destruction(); | 
|  |  | 
|  | // This should not fail because of the `heap_can_free` check, unless we | 
|  | // run out of stack. | 
|  | if (ret = heap_free(heapCapability, handle); ret != 0) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ssize_t queue_allocation_size(size_t elementSize, size_t elementCount) | 
|  | { | 
|  | size_t bufferSize; | 
|  | size_t allocSize; | 
|  | bool   overflow = | 
|  | __builtin_mul_overflow(elementCount, elementSize, &bufferSize); | 
|  | static constexpr size_t CounterSize = sizeof(uint32_t); | 
|  | // We also need space for the header | 
|  | overflow |= | 
|  | __builtin_add_overflow(sizeof(MessageQueue), bufferSize, &allocSize); | 
|  | if (overflow) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  | // We need the counters to be able to run to double the queue size without | 
|  | // hitting the high bits.  Error if this is the case. | 
|  | // | 
|  | // This should never be reached: a queue needs to be at least 256 MiB | 
|  | // (assuming one-byte elements) to hit this limit. | 
|  | if (((elementCount | (elementCount * 2)) & | 
|  | HighBitFlagLock::reserved_bits()) != 0) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return allocSize; | 
|  | } | 
|  |  | 
|  | int queue_create(Timeout              *timeout, | 
|  | struct SObjStruct    *heapCapability, | 
|  | struct MessageQueue **outQueue, | 
|  | size_t                elementSize, | 
|  | size_t                elementCount) | 
|  | { | 
|  | ssize_t allocSize = queue_allocation_size(elementSize, elementCount); | 
|  | if (allocSize < 0) | 
|  | { | 
|  | return allocSize; | 
|  | } | 
|  |  | 
|  | // Allocate the space for the queue. | 
|  | Capability buffer{heap_allocate(timeout, heapCapability, allocSize)}; | 
|  | if (!buffer.is_valid()) | 
|  | { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | *outQueue = new (buffer.get()) MessageQueue(elementSize, elementCount); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int queue_send(Timeout *timeout, struct MessageQueue *handle, const void *src) | 
|  | { | 
|  | Debug::log("Send called on: {}", handle); | 
|  | auto *producer   = &handle->producer; | 
|  | auto *consumer   = &handle->consumer; | 
|  | bool  shouldWake = false; | 
|  | { | 
|  | Debug::log("Lock word: {}", producer->load()); | 
|  | HighBitFlagLock l{*producer}; | 
|  | if (LockGuard g{l, timeout}) | 
|  | { | 
|  | volatile int ret = 0; | 
|  | // In an error-handling context, try to add the element to the | 
|  | // queue.  If the permissions on src are invalid, or either `handle` | 
|  | // or `src` is freed concurrently, we will hit the path that returns | 
|  | // -EPERM.  The counter update happens last, so any failure will | 
|  | // simply leave the queue in the old state. | 
|  | on_error( | 
|  | [&] { | 
|  | uint32_t producerCounter = counter_load(producer); | 
|  | uint32_t consumerValue   = consumer->load(); | 
|  | uint32_t consumerCounter = | 
|  | consumerValue & ~(HighBitFlagLock::reserved_bits()); | 
|  | Debug::log( | 
|  | "Producer counter: {}, consumer counter: {}, Size: {}", | 
|  | producerCounter, | 
|  | consumerCounter, | 
|  | handle->queueSize); | 
|  | while (is_full( | 
|  | handle->queueSize, producerCounter, consumerCounter)) | 
|  | { | 
|  | // Wait on the value to change.  If we hit this path while | 
|  | // the consumer lock is held, then the high bits will be | 
|  | // set.  Make sure that we yield. | 
|  | if (consumer->wait(timeout, consumerValue) == -ETIMEDOUT) | 
|  | { | 
|  | Debug::log("Timed out on futex"); | 
|  | ret = -ETIMEDOUT; | 
|  | return; | 
|  | } | 
|  | consumerValue = consumer->load(); | 
|  | consumerCounter = | 
|  | consumerValue & ~(HighBitFlagLock::reserved_bits()); | 
|  | } | 
|  | auto entry = buffer_at_counter(*handle, producerCounter); | 
|  | Debug::log("Send copying {} bytes from {} to {}", | 
|  | handle->elementSize, | 
|  | src, | 
|  | entry); | 
|  | memcpy(entry, src, handle->elementSize); | 
|  | counter_store( | 
|  | &handle->producer, | 
|  | increment_and_wrap(handle->queueSize, producerCounter)); | 
|  | // Check if the queue was empty before we updated the producer | 
|  | // counter.  By the time that we reach this point, anything on | 
|  | // the consumer side will be on the path to a futex_wait with | 
|  | // the old version of the producer counter and so will bounce | 
|  | // out again. | 
|  | shouldWake = | 
|  | is_empty(producerCounter, counter_load(consumer)); | 
|  | }, | 
|  | [&]() { | 
|  | ret = -EPERM; | 
|  | Debug::log("Error in send"); | 
|  | }); | 
|  | if (ret != 0) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | Debug::log("Timed out on lock"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  | if (shouldWake) | 
|  | { | 
|  | handle->producer.notify_all(); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int queue_receive(Timeout *timeout, struct MessageQueue *handle, void *dst) | 
|  | { | 
|  | Debug::log("Receive called on: {}", handle); | 
|  | auto *producer   = &handle->producer; | 
|  | auto *consumer   = &handle->consumer; | 
|  | bool  shouldWake = false; | 
|  | { | 
|  | HighBitFlagLock l{*consumer}; | 
|  | if (LockGuard g{l, timeout}) | 
|  | { | 
|  | volatile int ret = 0; | 
|  | // In an error-handling context, try to add the element to the | 
|  | // queue.  If the permissions on `dst` are invalid, or either | 
|  | // `handle` or `dst` is freed concurrently, we will hit the path | 
|  | // that returns `-EPERM`.  The counter update happens last, so any | 
|  | // failure will simply leave the queue in the old state. | 
|  | on_error( | 
|  | [&] { | 
|  | uint32_t producerValue = producer->load(); | 
|  | uint32_t producerCounter = | 
|  | producerValue & ~(HighBitFlagLock::reserved_bits()); | 
|  | uint32_t consumerCounter = counter_load(consumer); | 
|  | Debug::log( | 
|  | "Producer counter: {}, consumer counter: {}, Size: {}", | 
|  | producerCounter, | 
|  | consumerCounter, | 
|  | handle->queueSize); | 
|  | while (is_empty(producerCounter, consumerCounter)) | 
|  | { | 
|  | // Wait on the value to change.  If we hit this path while | 
|  | // the producer lock is held, then the high bits will be | 
|  | // set.  Make sure that we yield. | 
|  | if (producer->wait(timeout, producerValue) == -ETIMEDOUT) | 
|  | { | 
|  | ret = -ETIMEDOUT; | 
|  | return; | 
|  | } | 
|  | producerValue = producer->load(); | 
|  | producerCounter = | 
|  | producerValue & ~(HighBitFlagLock::reserved_bits()); | 
|  | } | 
|  | auto entry = buffer_at_counter(*handle, consumerCounter); | 
|  | Debug::log("Receive copying {} bytes from {} to {}", | 
|  | handle->elementSize, | 
|  | entry, | 
|  | dst); | 
|  | memcpy(dst, entry, handle->elementSize); | 
|  | counter_store( | 
|  | consumer, | 
|  | increment_and_wrap(handle->queueSize, consumerCounter)); | 
|  | // Check if the queue was full before we updated the consumer | 
|  | // counter.  By the time that we reach this point, anything on | 
|  | // the producer side will be on the path to a futex_wait with | 
|  | // the old version of the consumer counter and so will bounce | 
|  | // out again. | 
|  | shouldWake = is_full( | 
|  | handle->queueSize, counter_load(producer), consumerCounter); | 
|  | }, | 
|  | [&]() { | 
|  | ret = -EPERM; | 
|  | Debug::log("Error in receive"); | 
|  | }); | 
|  | if (ret != 0) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | Debug::log("Timed out on lock"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  | // If the queue is concurrently freed, this can trap, but we won't leak any | 
|  | // locks. | 
|  | if (shouldWake) | 
|  | { | 
|  | handle->consumer.notify_all(); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int queue_items_remaining(struct MessageQueue *handle, size_t *items) | 
|  | { | 
|  | auto producerCounter = counter_load(&handle->producer); | 
|  | auto consumerCounter = counter_load(&handle->consumer); | 
|  | *items = | 
|  | items_remaining(handle->queueSize, producerCounter, consumerCounter); | 
|  | Debug::log("Producer counter: {}, consumer counter: {}, items: {}", | 
|  | producerCounter, | 
|  | consumerCounter, | 
|  | *items); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void multiwaiter_queue_send_init(struct EventWaiterSource *source, | 
|  | struct MessageQueue      *handle) | 
|  | { | 
|  | uint32_t producer   = counter_load(&handle->producer); | 
|  | uint32_t consumer   = counter_load(&handle->consumer); | 
|  | source->eventSource = &handle->consumer; | 
|  | source->value = | 
|  | is_full(handle->queueSize, producer, consumer) ? consumer : -1; | 
|  | } | 
|  |  | 
|  | void multiwaiter_queue_receive_init(struct EventWaiterSource *source, | 
|  | struct MessageQueue      *handle) | 
|  | { | 
|  | uint32_t producer   = counter_load(&handle->producer); | 
|  | uint32_t consumer   = counter_load(&handle->consumer); | 
|  | source->eventSource = &handle->producer; | 
|  | source->value       = is_empty(producer, consumer) ? producer : -1; | 
|  | } |