blob: c039cce81d2c8d9ca192eaf17b90ed5a33e24580 [file] [log] [blame]
// 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 <limits>
#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();
// We want to know if current is greater than epoch, but current
// may have wrapped. Perform unsigned subtraction (guaranteed to
// wrap) and then coerce the result to a signed value. This will
// be correct unless we have more than 2^31 revocations in between
// checks
std::make_signed_t<decltype(current)> distance = current - epoch;
if (AllowPartial)
{
return distance >= 0;
}
// The allocator stores the epoch when things can be popped, which
// is always a complete (even) epoch.
#ifndef CLANG_TIDY
Debug::Assert((epoch & 1) == 0, "Epoch must be even");
#endif
// If the current epoch is odd then the epoch needs to be at least
// two more, to capture the fact that this is a complete epoch.
decltype(distance) minimumRequired = 1 + (epoch & 1);
return distance > minimumRequired;
}
/**
* 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;