| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include "loaderinfo.h" | |
| #include <cdefs.h> | |
| #include <cheri.hh> | |
| #include <debug.hh> | |
| #include <stdlib.h> | |
| #include <type_traits> | |
| namespace | |
| { | |
| constexpr bool DebugScheduler = | |
| #ifdef DEBUG_SCHEDULER | |
| DEBUG_SCHEDULER | |
| #else | |
| false | |
| #endif | |
| ; | |
| /// Is scheduler accounting enabled? | |
| constexpr bool Accounting = | |
| #ifdef SCHEDULER_ACCOUNTING | |
| SCHEDULER_ACCOUNTING | |
| #else | |
| false | |
| #endif | |
| ; | |
| using Debug = ConditionalDebug<DebugScheduler, "Scheduler">; | |
| /** | |
| * Base class for types that are exported from the scheduler with a common | |
| * sealing type. Includes an inline type marker. | |
| */ | |
| struct Handle | |
| { | |
| protected: | |
| /** | |
| * The real type of this subclass. | |
| * | |
| * This must be 32 bits and must be the first word of the class, so that | |
| * we are layout-compatible with the static sealing capabilities. We | |
| * use low-value numbers for things that we dynamically allocate to | |
| * check types. Anything that is statically allocated will have this | |
| * field initialised by the loader. | |
| * | |
| * Note that allocator-allocated and static types have a word of | |
| * padding here. This is necessary to ensure that the base of the sub | |
| * object is capability-aligned. In cases created with subclassing, | |
| * this is not required - the compiler will insert padding if it needs | |
| * to, but we can also put small fields in this space. For anything | |
| * statically allocated, we will need to handle this separately. | |
| */ | |
| enum class Type : uint32_t | |
| { | |
| Invalid = 0, | |
| /** | |
| * Multiwaiter type. | |
| */ | |
| MultiWaiter, | |
| /** | |
| * Used only as the type marker, not for comparisons. This | |
| * indicates that the type uses a dynamically allocated type value | |
| * that is provided in the `dynamic_type_marker()` static function. | |
| */ | |
| Dynamic, | |
| } type; | |
| /** | |
| * Constructor, takes the type of the subclass. | |
| */ | |
| Handle(Type type) : type(type) {} | |
| ~Handle() | |
| { | |
| __clang_ignored_warning_push("-Watomic-alignment") __atomic_store_n( | |
| reinterpret_cast<uint8_t *>(&type), 0, __ATOMIC_RELAXED); | |
| __clang_ignored_warning_pop() | |
| } | |
| public: | |
| /** | |
| * Unseal `unsafePointer` as a pointer to an object of the specified | |
| * type. Returns nullptr if `unsafePointer` is not a valid sealed | |
| * pointer to an object of the correct type. | |
| */ | |
| template<typename T> | |
| static T *unseal(void *unsafePointer) | |
| { | |
| return static_cast<Handle *>(unsafePointer)->unseal_as<T>(); | |
| } | |
| /** | |
| * Unseal this object as the specified type. Returns nullptr if this | |
| * is not a valid sealed object of the correct type. | |
| */ | |
| template<typename T> | |
| T *unseal_as() | |
| { | |
| static_assert(std::is_base_of_v<Handle, T>, | |
| "Cannot down-cast something that is not a subclass " | |
| "of Handle"); | |
| static_assert(offsetof(T, type) == 0, | |
| "Type field must be at the start of the object"); | |
| auto unsealed = compart_unseal(this); | |
| if constexpr (T::TypeMarker == Type::Dynamic) | |
| { | |
| if (unsealed.is_valid() && uint32_t(unsealed->type) == | |
| T::dynamic_type_marker().address()) | |
| { | |
| return unsealed.cast<T>(); | |
| } | |
| } | |
| else | |
| { | |
| if (unsealed.is_valid() && unsealed->type == T::TypeMarker) | |
| { | |
| return unsealed.cast<T>(); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| }; | |
| /** | |
| * RAII class for preventing nested exceptions. | |
| */ | |
| struct ExceptionGuard | |
| { | |
| /// The number of exception's currently being handled. | |
| static inline uint8_t exceptionLevel; | |
| /// RAII type, no copy constructor. | |
| ExceptionGuard(const ExceptionGuard &) = delete; | |
| /// RAII type, no move constructor. | |
| ExceptionGuard(ExceptionGuard &&) = delete; | |
| /** | |
| * Constructor. Increments the exception level and calls the handler | |
| * if we are in a nested exception. | |
| */ | |
| __always_inline ExceptionGuard(auto &&errorHandler) | |
| { | |
| exceptionLevel++; | |
| if (exceptionLevel > 1) | |
| { | |
| errorHandler(); | |
| } | |
| } | |
| /** | |
| * Destructor, decrements the current exception level. | |
| */ | |
| __always_inline ~ExceptionGuard() | |
| { | |
| exceptionLevel--; | |
| } | |
| /** | |
| * Panic if we are in a trying to block in an interrupt handler. | |
| * | |
| * In debug builds, this will report an error message with the caller's | |
| * source location. | |
| */ | |
| static void | |
| assert_safe_to_block(SourceLocation loc = SourceLocation::current()) | |
| { | |
| Debug::Invariant<>(exceptionLevel == 0, | |
| "Trying to block in an interrupt context.", | |
| loc); | |
| } | |
| }; | |
| __BEGIN_DECLS | |
| void exception_entry_asm(void); | |
| __END_DECLS | |
| /** | |
| * Wrapper around `std::unique_ptr` for objects allocated with a specific | |
| * heap capability. The scheduler is not authorised to allocate memory | |
| * except on behalf of callers. | |
| */ | |
| template<typename T> | |
| class HeapObject | |
| { | |
| /** | |
| * Deleter for use with `std::unique_ptr`, for memory allocated with an | |
| * explicit capability. | |
| */ | |
| class Deleter | |
| { | |
| /** | |
| * The capability that should authorise access to the heap. | |
| */ | |
| struct SObjStruct *heapCapability; | |
| public: | |
| /** | |
| * Constructor, captures the capability to use for deallocation. | |
| */ | |
| __always_inline Deleter(struct SObjStruct *heapCapability) | |
| : heapCapability(heapCapability) | |
| { | |
| } | |
| /** | |
| * Apply function, calls the destructor and cleans up the underlying | |
| * memory. | |
| */ | |
| __always_inline void operator()(T *object) | |
| { | |
| object->~T(); | |
| heap_free(heapCapability, object); | |
| } | |
| }; | |
| protected: | |
| /** | |
| * The underlying unique pointer. | |
| */ | |
| std::unique_ptr<T, Deleter> pointer = {nullptr, nullptr}; | |
| public: | |
| /// Default constructor, creates a null object. | |
| HeapObject() = default; | |
| /** | |
| * Constructor for use with externally heap-allocated objects and | |
| * placement new. Takes ownership of `allocatedObject`. The heap | |
| * capability will be used to free the object on destruction. | |
| */ | |
| HeapObject(struct SObjStruct *heapCapability, T *allocatedObject) | |
| : pointer(allocatedObject, heapCapability) | |
| { | |
| } | |
| /** | |
| * Attempt to allocate memory for, and construct, an instance of `T` | |
| * with the specified arguments. If memory allocation fails, this will | |
| * construct an object wrapping a null pointer and not call the | |
| * constructor. The bool-conversion operator can be used to check for | |
| * success. | |
| */ | |
| template<typename... Args> | |
| HeapObject(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| Args... args) | |
| : pointer(static_cast<T *>( | |
| heap_allocate(timeout, heapCapability, sizeof(T))), | |
| {heapCapability}) | |
| { | |
| if (pointer) | |
| { | |
| new (pointer.get()) T(std::forward<Args>(args)...); | |
| } | |
| } | |
| /** | |
| * Convert to bool. Returns true if the unique pointer owns a non-null | |
| * pointer. | |
| */ | |
| operator bool() | |
| { | |
| return !!pointer; | |
| } | |
| /** | |
| * Returns the wrapped pointer without transferring ownership. | |
| */ | |
| T *get() | |
| { | |
| return pointer.get(); | |
| } | |
| /** | |
| * Returns the wrapped pointer, transferring ownership to the caller. | |
| */ | |
| T *release() | |
| { | |
| return pointer.release(); | |
| } | |
| }; | |
| /** | |
| * Wrapper for a buffer of a dynamic length, allocated on the heap owned by | |
| * another compartment. | |
| */ | |
| struct HeapBuffer : public HeapObject<char> | |
| { | |
| HeapBuffer() = default; | |
| /** | |
| * Construct an array of `count` elemets, each of which is `size` bytes | |
| * long, using `heapCapability` to authorise allocation. | |
| */ | |
| HeapBuffer(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| size_t size, | |
| size_t count) | |
| : HeapObject( | |
| heapCapability, | |
| static_cast<char *>( | |
| heap_allocate_array(timeout, heapCapability, size, count))) | |
| { | |
| } | |
| /** | |
| * Update the address of the wrapped pointer. | |
| */ | |
| void set_address(ptraddr_t address) | |
| { | |
| CHERI::Capability old{pointer.release()}; | |
| old.address() = address; | |
| pointer.reset(old); | |
| } | |
| }; | |
| } // namespace |