The Pedigree Project 0.1
LocalApicTlbShootdown.h
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#ifndef KERNEL_MACHINE_MACH_PC_LOCALAPICTLBSHOOTDOWN_H
9#define KERNEL_MACHINE_MACH_PC_LOCALAPICTLBSHOOTDOWN_H
10#include "pedigree/kernel/Atomic.h"
11#include "pedigree/kernel/process/ExecutionContext.h"
12#include "pedigree/kernel/processor/types.h"
13
14#include <config.h>
15
23 public:
24 static constexpr size_t MaxProcessors = 64;
25
26 LocalApicProcessorControlOwner() : m_State(0) {}
27
28 bool tryAcquire(size_t processor) {
29 return processor < MaxProcessors && m_State.compareAndSwap(0, processor + 1);
30 }
31
32 bool ownedBy(size_t processor) const {
33 return processor < MaxProcessors && (m_State.value() & ~PhaseMask) == processor + 1;
34 }
35
36 bool activeBy(size_t processor) const {
37 return processor < MaxProcessors && m_State.value() == processor + 1;
38 }
39
40 bool markQuiesced(size_t processor) {
41 return processor < MaxProcessors &&
42 m_State.compareAndSwap(processor + 1, QuiescedBit | (processor + 1));
43 }
44
45 bool claimQuiesced(size_t processor) {
46 return processor < MaxProcessors &&
47 m_State.compareAndSwap(QuiescedBit | (processor + 1), processor + 1);
48 }
49
51 bool claimAnyQuiesced(size_t processor) {
52 if (processor >= MaxProcessors) {
53 return false;
54 }
55
56 size_t state = m_State.value();
57 while ((state & QuiescedBit) && !(state & TerminalBit)) {
58 if (m_State.compareAndSwap(state, processor + 1)) {
59 return true;
60 }
61 state = m_State.value();
62 }
63 return false;
64 }
65
67 bool claimAnyFailed(size_t processor) {
68 if (processor >= MaxProcessors) {
69 return false;
70 }
71
72 size_t state = m_State.value();
73 while ((state & FailedBit) && !(state & (QuiescedBit | TerminalBit))) {
74 if (m_State.compareAndSwap(state, processor + 1)) {
75 return true;
76 }
77 state = m_State.value();
78 }
79 return false;
80 }
81
82 bool quiescedBy(size_t processor) const {
83 return processor < MaxProcessors && m_State.value() == (QuiescedBit | (processor + 1));
84 }
85
86 bool markTerminal(size_t processor) {
87 return processor < MaxProcessors &&
88 m_State.compareAndSwap(processor + 1, TerminalBit | (processor + 1));
89 }
90
91 bool claimTerminal(size_t processor) {
92 return processor < MaxProcessors &&
93 m_State.compareAndSwap(TerminalBit | (processor + 1), processor + 1);
94 }
95
96 bool terminalBy(size_t processor) const {
97 return processor < MaxProcessors && m_State.value() == (TerminalBit | (processor + 1));
98 }
99
100 bool markFailed(size_t processor) {
101 return processor < MaxProcessors &&
102 m_State.compareAndSwap(processor + 1, FailedBit | (processor + 1));
103 }
104
105 bool failedBy(size_t processor) const {
106 return processor < MaxProcessors && m_State.value() == (FailedBit | (processor + 1));
107 }
108
109 bool release(size_t processor) {
110 return processor < MaxProcessors && m_State.compareAndSwap(processor + 1, 0);
111 }
112
113 bool owned() const {
114 return m_State.value() != 0;
115 }
116
117 private:
118 static constexpr size_t QuiescedBit = size_t(1) << (sizeof(size_t) * 8 - 1);
119 static constexpr size_t TerminalBit = QuiescedBit >> 1;
120 static constexpr size_t FailedBit = TerminalBit >> 1;
121 static constexpr size_t PhaseMask = QuiescedBit | TerminalBit | FailedBit;
122 Atomic<size_t> m_State;
123};
124
133 public:
134 enum class State : size_t { Open, ReversibleClosed, TerminalClosed };
135
136 LocalApicTlbMutationGate() : m_State(static_cast<size_t>(State::Open)), m_Active(0) {}
137
138 bool tryEnter() {
139 if (state() != State::Open) {
140 return false;
141 }
142
143 m_Active += 1;
144 if (state() != State::Open) {
145 leave();
146 return false;
147 }
148 return true;
149 }
150
151 bool leave() {
152 size_t active = m_Active.value();
153 while (active) {
154 if (m_Active.compareAndSwap(active, active - 1)) {
155 return true;
156 }
157 active = m_Active.value();
158 }
159 return false;
160 }
161
162 bool closeReversible() {
163 return m_State.compareAndSwap(static_cast<size_t>(State::Open),
164 static_cast<size_t>(State::ReversibleClosed));
165 }
166
169 size_t current = m_State.value();
170 while (current != static_cast<size_t>(State::TerminalClosed)) {
171 if (m_State.compareAndSwap(current, static_cast<size_t>(State::TerminalClosed))) {
172 return true;
173 }
174 current = m_State.value();
175 }
176 return true;
177 }
178
179 bool reopen() {
180 return !m_Active && m_State.compareAndSwap(static_cast<size_t>(State::ReversibleClosed),
181 static_cast<size_t>(State::Open));
182 }
183
192 bool cancelClose() {
193 return m_State.compareAndSwap(static_cast<size_t>(State::ReversibleClosed),
194 static_cast<size_t>(State::Open));
195 }
196
197 bool closed() const {
198 return state() != State::Open;
199 }
200
201 bool reversibleClosed() const {
202 return state() == State::ReversibleClosed;
203 }
204
205 bool terminalClosed() const {
206 return state() == State::TerminalClosed;
207 }
208
209 State state() const {
210 return static_cast<State>(m_State.value());
211 }
212
213 bool drained() const {
214 return !m_Active;
215 }
216
217 size_t active() const {
218 return m_Active.value();
219 }
220
221 private:
222 Atomic<size_t> m_State;
223 Atomic<size_t> m_Active;
224};
225
228 public:
229 static constexpr size_t MaxProcessors = 64;
230
231 LocalApicTlbTerminalFailure() : m_State(0) {}
232
233 bool elect(size_t processor, size_t reason) {
234 if (processor >= MaxProcessors) {
235 return false;
236 }
237
238 const size_t proposed = ((reason + 1) << ProcessorBits) | (processor + 1);
239 if (m_State.compareAndSwap(0, proposed)) {
240 return true;
241 }
242 return coordinator(processor);
243 }
244
245 bool active() const {
246 return m_State.value() != 0;
247 }
248
249 bool coordinator(size_t processor) const {
250 return processor < MaxProcessors && (m_State.value() & ProcessorMask) == processor + 1;
251 }
252
253 size_t reason() const {
254 const size_t state = m_State.value();
255 return state ? (state >> ProcessorBits) - 1 : 0;
256 }
257
258 private:
259 static constexpr size_t ProcessorBits = 8;
260 static constexpr size_t ProcessorMask = (size_t(1) << ProcessorBits) - 1;
261 Atomic<size_t> m_State;
262};
263
273 public:
274 static constexpr size_t MaxProcessors = 64;
275
276 struct Service {
277 constexpr Service()
278 : generation(0),
279 address(0),
280 processor(MaxProcessors),
281 servingToken(0),
282 acknowledgedToken(0) {}
283
284 size_t generation;
285 uintptr_t address;
286 size_t processor;
287 size_t servingToken;
288 size_t acknowledgedToken;
289 };
290
292 : m_Owner(false), m_Generation(0), m_NextGeneration(0), m_Address(0), m_Expected(0) {}
293
294 static constexpr bool supportsContext(ExecutionContext context) {
295 return context == ExecutionContext::WaitableThread || context == ExecutionContext::AtomicThread;
296 }
297
298 static constexpr bool onlyCurrentProcessorServiceable(size_t processorCount,
299 size_t terminalProcessors) {
300 return processorCount && terminalProcessors >= (processorCount - 1);
301 }
302
303 bool tryAcquire() {
304 return m_Owner.compareAndSwap(false, true);
305 }
306
307 bool publish(uintptr_t address, size_t processor, size_t processorCount,
308 uint64_t expected = ~uint64_t(0)) {
309 uint64_t available = 0;
310 if (!m_Owner || m_Generation || processor >= processorCount ||
311 !processorMask(processorCount, available)) {
312 return false;
313 }
314 if (expected == ~uint64_t(0)) {
315 expected = available;
316 }
317 if (!expected || (expected & ~available) || !(expected & (uint64_t(1) << processor))) {
318 return false;
319 }
320
321 const size_t initiatorState = m_ProcessorState[processor].value();
322 if (isServing(initiatorState)) {
323 return false;
324 }
325
326 size_t generation = 0;
327 if (!selectNextGeneration(generation)) {
328 return false;
329 }
330
331 m_Address = address;
332 m_Expected = expected;
333 m_ProcessorState[processor] = acknowledgedToken(generation);
334 m_NextGeneration = generation;
335 // Publishing the generation last makes the complete request visible to
336 // interrupt and cooperative service paths in one step.
337 m_Generation = generation;
338 return true;
339 }
340
341 bool beginService(size_t processor, Service& service) {
342 if (processor >= MaxProcessors || service.generation) {
343 return false;
344 }
345
346 const size_t generation = m_Generation.value();
347 if (!generation || !(m_Expected.value() & (uint64_t(1) << processor))) {
348 return false;
349 }
350
351 const size_t serving = servingToken(generation);
352 const size_t acknowledged = acknowledgedToken(generation);
353 const size_t previous = m_ProcessorState[processor].value();
354 if (isServing(previous) || previous == acknowledged ||
355 !m_ProcessorState[processor].compareAndSwap(previous, serving)) {
356 return false;
357 }
358
359 // Copy the request before revalidating its last-published generation. A
360 // publisher may replace the shared address while an older reader retires;
361 // that reader either copied the matching address or rejects below.
362 const uintptr_t address = m_Address.value();
363 return revalidateServiceClaim(processor, generation, address, serving, acknowledged, service);
364 }
365
366 bool finishService(Service& service) {
367 if (!service.generation || service.processor >= MaxProcessors) {
368 return false;
369 }
370
371 // Retirement and acknowledgement are one atomic transition. The owner
372 // can therefore never observe the final ACK while the service lease is
373 // still live.
374 const bool acknowledged = m_ProcessorState[service.processor].compareAndSwap(
375 service.servingToken, service.acknowledgedToken);
376 const bool current = acknowledged && m_Generation.value() == service.generation;
377 service = Service();
378 return current;
379 }
380
381 bool complete() const {
382 const size_t generation = m_Generation.value();
383 const uint64_t expected = m_Expected.value();
384 return generation && expected && allAcknowledged(generation, expected) &&
385 m_Generation.value() == generation;
386 }
387
388 void close() {
389 m_Generation = 0;
390 }
391
392 bool drained() const {
393 for (size_t processor = 0; processor < MaxProcessors; ++processor) {
394 if (isServing(m_ProcessorState[processor].value())) {
395 return false;
396 }
397 }
398 return true;
399 }
400
401 bool release() {
402 if (m_Generation || !drained()) {
403 return false;
404 }
405 return m_Owner.compareAndSwap(true, false);
406 }
407
408 bool owned() const {
409 return m_Owner;
410 }
411
412 bool retainedClosed() const {
413 return m_Owner && !m_Generation && drained();
414 }
415
416 size_t generation() const {
417 return m_Generation.value();
418 }
419
420 uintptr_t address() const {
421 return m_Address.value();
422 }
423
424 uint64_t expectedMask() const {
425 return m_Expected.value();
426 }
427
428 uint64_t acknowledgedMask() const {
429 const size_t generation = m_Generation.value();
430 return generation ? acknowledgedMask(generation) : 0;
431 }
432
433 size_t servicing() const {
434 size_t count = 0;
435 for (size_t processor = 0; processor < MaxProcessors; ++processor) {
436 if (isServing(m_ProcessorState[processor].value())) {
437 ++count;
438 }
439 }
440 return count;
441 }
442
443 private:
444 friend class LocalApicTlbShootdownTestPeer;
445
446 static constexpr size_t ServingBit = 1;
447 static constexpr size_t MaxGeneration = ~size_t(0) >> 1;
448
449 static constexpr bool isServing(size_t token) {
450 return token & ServingBit;
451 }
452
453 static constexpr size_t acknowledgedToken(size_t generation) {
454 return generation << 1;
455 }
456
457 static constexpr size_t servingToken(size_t generation) {
458 return acknowledgedToken(generation) | ServingBit;
459 }
460
461 bool revalidateServiceClaim(size_t processor, size_t generation, uintptr_t address,
462 size_t serving, size_t acknowledged, Service& service) {
463 if (m_Generation.value() != generation) {
464 m_ProcessorState[processor].compareAndSwap(serving, acknowledged);
465 return false;
466 }
467
468 service.generation = generation;
469 service.address = address;
470 service.processor = processor;
471 service.servingToken = serving;
472 service.acknowledgedToken = acknowledged;
473 return true;
474 }
475
476 bool selectNextGeneration(size_t& generation) const {
477 size_t candidate = m_NextGeneration.value();
478 for (size_t attempt = 0; attempt <= MaxProcessors; ++attempt) {
479 candidate = candidate == MaxGeneration ? 1 : candidate + 1;
480 bool present = false;
481 for (size_t processor = 0; processor < MaxProcessors; ++processor) {
482 if ((m_ProcessorState[processor].value() >> 1) == candidate) {
483 present = true;
484 break;
485 }
486 }
487 if (!present) {
488 generation = candidate;
489 return true;
490 }
491 }
492 return false;
493 }
494
495 uint64_t acknowledgedMask(size_t generation) const {
496 uint64_t acknowledged = 0;
497 const size_t token = acknowledgedToken(generation);
498 for (size_t processor = 0; processor < MaxProcessors; ++processor) {
499 if (m_ProcessorState[processor].value() == token) {
500 acknowledged |= uint64_t(1) << processor;
501 }
502 }
503 return acknowledged;
504 }
505
506 bool allAcknowledged(size_t generation, uint64_t expected) const {
507 const size_t token = acknowledgedToken(generation);
508 for (size_t processor = 0; expected; ++processor, expected >>= 1) {
509 if ((expected & 1) && m_ProcessorState[processor].value() != token) {
510 return false;
511 }
512 }
513 return true;
514 }
515
516 static bool processorMask(size_t processorCount, uint64_t& mask) {
517 if (!processorCount || processorCount > MaxProcessors) {
518 return false;
519 }
520 mask = processorCount == MaxProcessors ? ~uint64_t(0) : (uint64_t(1) << processorCount) - 1;
521 return true;
522 }
523
524 Atomic<bool> m_Owner;
525 Atomic<size_t> m_Generation;
526 Atomic<size_t> m_NextGeneration;
527 Atomic<uintptr_t> m_Address;
528 Atomic<uint64_t> m_Expected;
529 Atomic<size_t> m_ProcessorState[MaxProcessors];
530};
531
532#endif
bool claimAnyFailed(size_t processor)
bool claimAnyQuiesced(size_t processor)