CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
platform-hardware_revoker.hh
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4#pragma once
5
6#include <cdefs.h>
8#include <futex.h>
9#include <interrupt.h>
10#include <limits>
11#include <riscvreg.h>
12#include <stddef.h>
13#include <stdint.h>
14
15#include <revoker/bitmap_direct.hh>
16
17#if !DEVICE_EXISTS(revoker) && !defined(CLANG_TIDY)
18# error Memory map was not configured with a revoker device
19#endif
20
21DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(revokerInterruptCapability,
22 RevokerInterrupt,
23 true,
24 true);
25namespace Ibex
26{
27 template<typename WordT, ptraddr_t TCMBaseAddr>
28 class HardwareRevoker : public Revocation::BitmapDirect<WordT, TCMBaseAddr>
29 {
30 private:
31 /**
32 * Layout of the revoker device.
33 */
34 struct RevokerInterface
35 {
36 /**
37 * The base address to scan.
38 */
39 uint32_t base;
40 /**
41 * The top address to scan.
42 */
43 uint32_t top;
44 /**
45 * The control word. The top 16 bits should be 0x5500. Writing to
46 * the low bit will start the revoker.
47 */
48 uint32_t control;
49 /**
50 * The revocation epoch. Low bit indicates that the revoker is
51 * running.
52 */
53 uint32_t epoch;
54 /**
55 * Interrupt status word. Reading this will return 0 if an
56 * interrupt has not been requested, 1 otherwise. Writing 1 clears
57 * any pending interrupt.
58 */
59 uint32_t interruptStatus;
60 /**
61 * Interrupt request word. Writing 1 here requests an interrupt to
62 * fire when the current revocation has completed.
63 */
64 uint32_t interruptRequested;
65 };
66
67 /**
68 * Get a reference to the revoker device.
69 */
70 __always_inline volatile RevokerInterface &revoker_device()
71 {
72 return *MMIO_CAPABILITY(RevokerInterface, revoker);
73 }
74
75 static inline const uint32_t *interruptFutex;
76
77 public:
78 /**
79 * This is an asynchronous hardware revoker.
80 */
81 static constexpr bool IsAsynchronous = true;
82
83 /**
84 * Initialise a revoker instance.
85 */
86 void init()
87 {
89
90 /**
91 * These two symbols mark the region that needs revocation. We
92 * revoke capabilities everywhere from the start of compartment
93 * globals to the end of the heap.
94 */
95 extern char __revoker_scan_start, __export_mem_heap_end;
96
97 auto base = LA_ABS(__revoker_scan_start);
98 auto top = LA_ABS(__export_mem_heap_end);
99 auto &device = revoker_device();
100 device.base = base;
101 device.top = top;
102 // Clang tidy is checking headers as stand-alone compilation units
103 // and so doesn't know what Debug is defined to.
104#ifndef CLANG_TIDY
105 Debug::Assert((device.control >> 16) == 0x5500,
106 "Device not present: {} (should be 0x55000000)",
107 device.control);
108 Debug::Invariant(base < top,
109 "Memory map has unexpected layout, base {} is "
110 "expected to be below top {}",
111 base,
112 top);
113#endif
114 // Get a pointer to the futex that we use to wait for interrupts.
115 interruptFutex = interrupt_futex_get(
116 STATIC_SEALED_VALUE(revokerInterruptCapability));
117
118#ifndef CLANG_TIDY
119 {
120 using namespace CHERI;
121
122 auto cap = Capability{interruptFutex};
123
124 Debug::Assert(cap.bounds() == sizeof(uint32_t),
125 "Interrupt futexes are not properly bounded: {}",
126 cap);
127 Debug::Assert(
128 cap.permissions() ==
129 PermissionSet{Permission::Global, Permission::Load},
130 "Interrupt futexes are not properly permissioned: {}",
131 cap);
132 }
133#endif
134 }
135
136 /**
137 * Returns the revocation epoch. This is the number of revocations
138 * that have started.
139 */
141 {
142 return revoker_device().epoch;
143 }
144
145 /**
146 * Queries whether the specified revocation epoch has finished, or,
147 * if `AllowPartial` is true, that it has (at least) started.
148 *
149 * `epoch` must be even, as memory leaves quarantine only when
150 * revocation is not in progress.
151 */
152 template<bool AllowPartial = false>
153 uint32_t has_revocation_finished_for_epoch(uint32_t epoch)
154 {
155 auto current = system_epoch_get();
156 // We want to know if current is greater than epoch, but current
157 // may have wrapped. Perform unsigned subtraction (guaranteed to
158 // wrap) and then coerce the result to a signed value. This will
159 // be correct unless we have more than 2^31 revocations in between
160 // checks
161 std::make_signed_t<decltype(current)> distance = current - epoch;
162 if (AllowPartial)
163 {
164 return distance >= 0;
165 }
166 // The allocator stores the epoch when things can be popped, which
167 // is always a complete (even) epoch.
168#ifndef CLANG_TIDY
169 Debug::Assert((epoch & 1) == 0, "Epoch must be even");
170#endif
171 // If the current epoch is odd then the epoch needs to be at least
172 // two more, to capture the fact that this is a complete epoch.
173 decltype(distance) minimumRequired = 1 + (epoch & 1);
174 return distance > minimumRequired;
175 }
176
177 /**
178 * Start a revocation sweep.
179 */
181 {
182 if (system_epoch_get() & 1)
183 {
184 return;
185 }
186 auto &device = revoker_device();
187 device.control = 0;
188 device.control = 1;
189 }
190
191 /**
192 * Block until the revocation epoch specified by `epoch` has completed.
193 */
194 bool wait_for_completion(TimeoutArgument timeout, uint32_t epoch)
195 {
196 uint32_t interruptValue;
197 do
198 {
199 // Read the current interrupt futex word. We want to retry if
200 // an interrupt happens after this point.
201 interruptValue = *interruptFutex;
202 // Make sure that the compiler doesn't reorder the read of the
203 // futex word with respect to the read of the revocation epoch.
204 __c11_atomic_signal_fence(__ATOMIC_SEQ_CST);
205 // If the requested epoch has finished, return success.
207 {
208 return true;
209 }
210 // Request the interrupt
211 revoker_device().interruptRequested = 1;
212 // There is a possible race: if the revocation pass finished
213 // before we requested the interrupt, we won't get the
214 // interrupt. Check again before we wait.
216 {
217 return true;
218 }
219 // Make sure that the revoker is running.
221 // If the epoch hasn't finished, wait for an interrupt to fire
222 // and retry.
223 } while (
224 futex_timed_wait(timeout, interruptFutex, interruptValue) == 0);
225 // Futex wait failed. This could be a timeout or an invalid
226 // timeout parameter, we fail either way.
227 return false;
228 }
229
230 /**
231 * Reveal the interrupt futex word.
232 *
233 * This is deliberately not part of the core/allocator/revoker.h
234 * IsHardwareRevokerDevice concept and should be used only in testing
235 * and other extenuating circumstances.
236 */
237 const uint32_t *interrupt_futex()
238 {
239 return interruptFutex;
240 }
241
242 /**
243 * Request the delivery of an IRQ when the revoker finishes its current
244 * scan.
245 *
246 * This is deliberately not part of the core/allocator/revoker.h
247 * IsHardwareRevokerDevice concept and should be used only in testing
248 * and other extenuating circumstances.
249 */
251 {
252 revoker_device().interruptRequested = 1;
253 }
254 };
255} // namespace Ibex
256
257template<typename WordT, ptraddr_t TCMBaseAddr>
258using HardwareRevoker = Ibex::HardwareRevoker<WordT, TCMBaseAddr>;
Helper class for accessing capability properties on pointers.
Definition cheri.hh:482
Class encapsulating a set of permissions.
Definition cheri.hh:90
void system_bg_revoker_kick()
Start a revocation sweep.
void init()
Initialise a revoker instance.
const uint32_t * interrupt_futex()
Reveal the interrupt futex word.
void request_interrupt()
Request the delivery of an IRQ when the revoker finishes its current scan.
uint32_t has_revocation_finished_for_epoch(uint32_t epoch)
Queries whether the specified revocation epoch has finished, or, if AllowPartial is true,...
static constexpr bool IsAsynchronous
This is an asynchronous hardware revoker.
uint32_t system_epoch_get()
Returns the revocation epoch.
bool wait_for_completion(TimeoutArgument timeout, uint32_t epoch)
Block until the revocation epoch specified by epoch has completed.
Class for interacting with the shadow bitmap.
void init()
Initialise this class with a capability to the shadow bitmap.
Macros for interacting with the compartmentalisation model in CHERIoT.
#define STATIC_SEALED_VALUE(name)
Returns a sealed capability to the named object created with DECLARE_STATIC_SEALED_VALUE.
#define MMIO_CAPABILITY(type, name)
Provide a capability of the type volatile type * referring to the MMIO region exported in the linker ...
Futex (compare-and-wait) APIs.
int futex_timed_wait(TimeoutArgument timeout, const volatile uint32_t *address, uint32_t expected, uint32_t flags)
Compare the value at address to expected and, if they match, sleep the thread until a wake event is s...
This file describes the interfaces for compartments to wait for interrupts.
#define DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY( name, number, mayWait, mayComplete)
Helper macro to define an interrupt capability without a separate declaration.
Definition interrupt.h:109
const uint32_t * interrupt_futex_get(InterruptCapability interruptcapability)
Request the futex associated with an interrupt.