| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include <cdefs.h> | |
| #include <compartment-macros.h> | |
| #include <futex.h> | |
| #include <interrupt.h> | |
| #include <riscvreg.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #if !DEVICE_EXISTS(revoker) && !defined(CLANG_TIDY) | |
| # error Memory map was not configured with a revoker device | |
| #endif | |
| DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(revokerInterruptCapability, | |
| RevokerInterrupt, | |
| true, | |
| true); | |
| namespace Ibex | |
| { | |
| class HardwareRevoker | |
| { | |
| private: | |
| /** | |
| * Layout of the revoker device. | |
| */ | |
| struct RevokerInterface | |
| { | |
| /** | |
| * The base address to scan. | |
| */ | |
| uint32_t base; | |
| /** | |
| * The top address to scan. | |
| */ | |
| uint32_t top; | |
| /** | |
| * The control word. The top 16 bits should be 0x5500. Writing to | |
| * the low bit will start the revoker. | |
| */ | |
| uint32_t control; | |
| /** | |
| * The revocation epoch. Low bit indicates that the revoker is | |
| * running. | |
| */ | |
| uint32_t epoch; | |
| /** | |
| * Interrupt status word. Reading this will return 0 if an | |
| * interrupt has not been requested, 1 otherwise. Writing 1 clears | |
| * any pending interrupt. | |
| */ | |
| uint32_t interruptStatus; | |
| /** | |
| * Interrupt request word. Writing 1 here requests an interrupt to | |
| * fire when the current revocation has completed. | |
| */ | |
| uint32_t interruptRequested; | |
| }; | |
| /** | |
| * Get a reference to the revoker device. | |
| */ | |
| __always_inline volatile RevokerInterface &revoker_device() | |
| { | |
| return *MMIO_CAPABILITY(RevokerInterface, revoker); | |
| } | |
| static inline const uint32_t *interruptFutex; | |
| public: | |
| /** | |
| * This is an asynchronous hardware revoker. | |
| */ | |
| static constexpr bool IsAsynchronous = true; | |
| /** | |
| * Initialise a revoker instance. | |
| */ | |
| void init() | |
| { | |
| /** | |
| * These two symbols mark the region that needs revocation. We | |
| * revoke capabilities everywhere from the start of compartment | |
| * globals to the end of the heap. | |
| */ | |
| extern char __compart_cgps, __export_mem_heap_end; | |
| auto base = LA_ABS(__compart_cgps); | |
| auto top = LA_ABS(__export_mem_heap_end); | |
| auto &device = revoker_device(); | |
| device.base = base; | |
| device.top = top; | |
| // Clang tidy is checking headers as stand-alone compilation units | |
| // and so doesn't know what Debug is defined to. | |
| #ifndef CLANG_TIDY | |
| Debug::Assert((device.control >> 16) == 0x5500, | |
| "Device not present: {} (should be 0x55000000)", | |
| device.control); | |
| Debug::Invariant(base < top, | |
| "Memory map has unexpected layout, base {} is " | |
| "expected to be below top {}", | |
| base, | |
| top); | |
| #endif | |
| // Get a pointer to the futex that we use to wait for interrupts. | |
| interruptFutex = interrupt_futex_get( | |
| STATIC_SEALED_VALUE(revokerInterruptCapability)); | |
| } | |
| /** | |
| * Returns the revocation epoch. This is the number of revocations | |
| * that have started. | |
| */ | |
| uint32_t system_epoch_get() | |
| { | |
| return revoker_device().epoch; | |
| } | |
| /** | |
| * Queries whether the specified revocation epoch has finished. | |
| */ | |
| template<bool AllowPartial = false> | |
| uint32_t has_revocation_finished_for_epoch(uint32_t epoch) | |
| { | |
| auto current = system_epoch_get(); | |
| if (AllowPartial) | |
| { | |
| return current > epoch; | |
| } | |
| return current - epoch >= (2 + (epoch & 1)); | |
| } | |
| /** | |
| * Start a revocation sweep. | |
| */ | |
| void system_bg_revoker_kick() | |
| { | |
| if (system_epoch_get() & 1) | |
| { | |
| return; | |
| } | |
| auto &device = revoker_device(); | |
| device.control = 0; | |
| device.control = 1; | |
| } | |
| /** | |
| * Block until the revocation epoch specified by `epoch` has completed. | |
| */ | |
| bool wait_for_completion(Timeout *timeout, uint32_t epoch) | |
| { | |
| uint32_t interruptValue; | |
| do | |
| { | |
| // Read the current interrupt futex word. We want to retry if | |
| // an interrupt happens after this point. | |
| interruptValue = *interruptFutex; | |
| // Make sure that the compiler doesn't reorder the read of the | |
| // futex word with respect to the read of the revocation epoch. | |
| __c11_atomic_signal_fence(__ATOMIC_SEQ_CST); | |
| // If the requested epoch has finished, return success. | |
| if (has_revocation_finished_for_epoch<true>(epoch)) | |
| { | |
| return true; | |
| } | |
| // Request the interrupt | |
| revoker_device().interruptRequested = 1; | |
| // There is a possible race: if the revocation pass finished | |
| // before we requested the interrupt, we won't get the | |
| // interrupt. Check again before we wait. | |
| if (has_revocation_finished_for_epoch<true>(epoch)) | |
| { | |
| return true; | |
| } | |
| // If the epoch hasn't finished, wait for an interrupt to fire | |
| // and retry. | |
| } while ( | |
| futex_timed_wait(timeout, interruptFutex, interruptValue) == 0); | |
| // Futex wait failed. This could be a timeout or an invalid | |
| // timeout parameter, we fail either way. | |
| return false; | |
| } | |
| }; | |
| } // namespace Ibex | |
| template<typename WordT, size_t TCMBaseAddr> | |
| using HardwareRevoker = Ibex::HardwareRevoker; |