8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Semaphore.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/MemoryRegion.h"
16#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
17#include "pedigree/kernel/processor/Processor.h"
18#include "pedigree/kernel/processor/VirtualAddressSpace.h"
19#include "pedigree/kernel/time/Time.h"
22constexpr uint64_t OldMarker = 0x13579BDF2468ACE0ULL;
23constexpr uint64_t NewMarker = 0x0FEDCBA987654321ULL;
24constexpr uint64_t WrittenMarker = 0xA55A123443215AA5ULL;
25constexpr uint64_t ReusedMarker = 0x76543210FEDCBA98ULL;
27struct TlbRemapContext {
28 explicit TlbRemapContext(
void* virtualAddress)
29 : address(virtualAddress),
36 successfulProcessors(0),
37 mutatorProcessor(static_cast<size_t>(-1)) {}
50int warmRemoteTranslation(
void* parameter) {
51 TlbRemapContext* context =
reinterpret_cast<TlbRemapContext*
>(parameter);
52 volatile uint64_t* value =
reinterpret_cast<volatile uint64_t*
>(context->address);
54 if (*value != OldMarker) {
55 context->failures += 1;
57 context->warmedProcessors |= uint64_t(1) << processorBefore;
58 context->warmed.release();
60 bool remapped =
false;
61 for (
size_t poll = 0; poll < 10000000; ++poll) {
62 if (context->remapped.value()) {
69 context->failures += 1;
73 __atomic_thread_fence(__ATOMIC_ACQUIRE);
74 const uint64_t observedAfter = *value;
76 if (observedAfter != NewMarker) {
77 context->failures += 1;
83 if (processorAfter != processorBefore) {
87 if (processorBefore != context->mutatorProcessor.value()) {
88 context->remoteReaders += 1;
89 context->remoteSuccesses += 1;
91 context->successfulProcessors |= uint64_t(1) << processorBefore;
95struct PrivateTlbContext {
108 asm volatile(
"rdmsr" :
"=a"(low),
"=d"(high) :
"c"(0x277));
109 return (uint64_t(high) << 32) | low;
113bool waitPrivateTlbStage(PrivateTlbContext& context,
size_t stage) {
114 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
115 while (!context.aborted && Time::getTicks() < deadline) {
116 if (context.stage == stage) {
124bool privateTlbValue(PrivateTlbContext& context, uint64_t expected) {
127 *
reinterpret_cast<volatile uint64_t*
>(context.address) == expected;
130bool waitPrivateTlbReady(PrivateTlbContext& context,
size_t ready) {
131 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
132 while (context.ready != ready && Time::getTicks() < deadline) {
135 return context.ready == ready;
138int readPrivateTranslations(
void* parameter) {
139 auto& context = *
static_cast<PrivateTlbContext*
>(parameter);
143 uint64_t cr0, savedCr4;
144 asm volatile(
"mov %%cr0, %0" :
"=r"(cr0));
145 asm volatile(
"mov %%cr4, %0" :
"=r"(savedCr4));
146 const bool cacheControlsMatch = !(cr0 & (3ULL << 29)) && readPat() == context.pat;
147 const uint64_t withGlobalPages = savedCr4 | (1ULL << 7);
148 asm volatile(
"mov %0, %%cr4" : :
"r"(withGlobalPages) :
"memory");
154 if (!cacheControlsMatch) {
155 ERROR(
"QEMU private TLB reader has inconsistent cache controls");
159 if (!privateTlbValue(context, OldMarker)) {
163 if (!waitPrivateTlbStage(context, 1) || !privateTlbValue(context, NewMarker)) {
167 if (!waitPrivateTlbStage(context, 2) || !privateTlbValue(context, NewMarker)) {
170 *
reinterpret_cast<volatile uint64_t*
>(context.address) = WrittenMarker;
173 if (!context.resume.acquireForCompletion(1, 5) || context.aborted) {
177 context.passed = privateTlbValue(context, ReusedMarker);
181 asm volatile(
"mov %0, %%cr4" : :
"r"(savedCr4) :
"memory");
184 if (!context.passed) {
185 context.aborted =
true;
187 return context.passed ? 0 : 1;
190struct KernelTlbWitness {
196int witnessKernelAddressSpace(
void* parameter) {
197 auto& context = *
static_cast<KernelTlbWitness*
>(parameter);
201 context.entered.release();
202 return context.release.acquireForCompletion(1, 5) ? 0 : 1;
205bool privateTlbMutator() {
206 NOTICE(
"QEMU-CONCURRENCY-TEST: BEGIN private-tlb-shootdown-smp");
209 size_t remote = CpuAffinityMask::MaximumCpus;
210 for (
size_t cpu = 0; cpu < CpuAffinityMask::MaximumCpus; ++cpu) {
211 if (cpu != mutator && online.contains(cpu)) {
216 if (remote == CpuAffinityMask::MaximumCpus) {
226 auto* original =
static_cast<volatile uint64_t*
>(backing.virtualAddress());
227 auto* replacement =
reinterpret_cast<volatile uint64_t*
>(
228 reinterpret_cast<uintptr_t
>(backing.virtualAddress()) + pageSize);
229 *original = OldMarker;
230 *replacement = NewMarker;
231 physical_uintptr_t originalPage = 0, replacementPage = 0;
234 if (!kernel.getMapping(backing.virtualAddress(), originalPage, flags) ||
235 !kernel.getMapping(
const_cast<uint64_t*
>(replacement), replacementPage, flags)) {
241 void* address =
reinterpret_cast<void*
>(space->
getDynamicStart() + 64 * pageSize);
247 !space->
map(originalPage, address, readFlags)) {
251 PrivateTlbContext context{space, address, remote};
253 context.pat = readPat();
256 placement.allowed.set(remote);
257 Thread*
reader =
new Thread(process, readPrivateTranslations, &context,
nullptr,
false,
false,
259 reader->setName(
"QEMU private TLB reader");
260 const bool started =
reader->start();
261 bool passed = started && waitPrivateTlbReady(context, 1);
263 space->
unmap(address);
265 *original = ReusedMarker;
266 passed = space->
map(replacementPage, address, readFlags);
269 passed = waitPrivateTlbReady(context, 2);
276 passed = waitPrivateTlbReady(context, 3) && *replacement == WrittenMarker;
280 KernelTlbWitness witness{remote};
281 Thread* witnessThread =
nullptr;
283 bool sleeping =
false;
284 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
285 while (!sleeping && Time::getTicks() < deadline) {
288 sleeping =
reader->getStatus() == Thread::Sleeping;
298 nullptr,
false,
false,
true, &placement);
299 witnessThread->setName(
"QEMU private TLB switch witness");
300 if (!witnessThread->
start()) {
304 passed = witness.entered.acquireForCompletion(1, 5) && witness.passed;
309 space->
unmap(address);
310 passed = space->
map(originalPage, address, writeFlags);
312 context.aborted = !passed;
313 context.resume.release();
314 witness.release.release();
319 FATAL(
"QEMU private TLB regression could not retire its workers");
321 passed = passed && context.passed;
323 space->
unmap(address);
327 ERROR(
"QEMU private TLB regression failed at stage " <<
Dec << context.stage.value());
330 NOTICE(
"QEMU-CONCURRENCY-TEST: private-tlb-shootdown reader=" <<
Dec << remote
331 <<
", mutator=" << mutator);
332 NOTICE(
"QEMU-CONCURRENCY-TEST: PASS private-tlb-shootdown-smp");
336int privateTlbMutatorEntry(
void* parameter) {
338 passed = privateTlbMutator();
339 return passed ? 0 : 1;
342bool privateTlbShootdownRegression() {
347 &passed,
nullptr,
false,
false,
true, &placement);
348 mutator->setName(
"QEMU private TLB mutator");
349 if (!mutator->
start()) {
353 FATAL(
"QEMU private TLB regression could not retire its mutator");
359bool runTlbShootdownConcurrencyRegression() {
360 NOTICE(
"QEMU-CONCURRENCY-TEST: BEGIN shared-kernel-tlb-shootdown-smp");
362 if (processorCount < 2 || processorCount > 64) {
363 ERROR(
"QEMU TLB shootdown regression requires at least two processors");
368 MemoryRegion reservation(
"QEMU TLB shootdown regression");
371 ERROR(
"QEMU TLB shootdown regression could not reserve a kernel address");
376 reservation.setNonRamMemory(
true);
377 reservation.setForced(
true);
379 const physical_uintptr_t originalPage = memory.
allocatePage();
380 const physical_uintptr_t replacementPage = memory.
allocatePage();
382 if (!originalPage || !replacementPage ||
383 !addressSpace.
map(originalPage, reservation.virtualAddress(),
388 if (replacementPage) {
392 ERROR(
"QEMU TLB shootdown regression could not prepare physical pages");
395 *
reinterpret_cast<volatile uint64_t*
>(reservation.virtualAddress()) = OldMarker;
397 TlbRemapContext context(reservation.virtualAddress());
400 const size_t readerTarget = processorCount <= 32 ? processorCount * 2 : processorCount;
401 size_t startedReaders = 0;
402 for (; startedReaders < readerTarget; ++startedReaders) {
403 readers[startedReaders] =
404 new Thread(process, warmRemoteTranslation, &context,
nullptr,
false,
false,
true);
405 readers[startedReaders]->setName(
"QEMU remote TLB reader");
406 if (!readers[startedReaders]->start()) {
411 for (
size_t i = 0; i < startedReaders; ++i) {
412 if (!context.warmed.acquireForCompletion()) {
413 context.failures += 1;
418 addressSpace.
unmap(reservation.virtualAddress());
419 const bool replacementMapped =
420 addressSpace.
map(replacementPage, reservation.virtualAddress(),
422 if (replacementMapped) {
423 *
reinterpret_cast<volatile uint64_t*
>(reservation.virtualAddress()) = NewMarker;
424 __atomic_thread_fence(__ATOMIC_RELEASE);
426 context.failures += 1;
428 context.remapped =
true;
430 bool readersJoined =
true;
431 for (
size_t i = 0; i < startedReaders; ++i) {
434 const bool mapped = addressSpace.
isMapped(reservation.virtualAddress());
436 addressSpace.
unmap(reservation.virtualAddress());
442 const bool passed = startedReaders == readerTarget && readersJoined && replacementMapped &&
443 mapped && !context.failures && context.remoteReaders &&
444 context.remoteSuccesses == context.remoteReaders;
446 ERROR(
"QEMU TLB shootdown regression did not observe the replacement mapping");
450 NOTICE(
"QEMU-CONCURRENCY-TEST: shared-kernel-tlb-shootdown mask="
451 <<
Hex << context.successfulProcessors.value() <<
Dec
452 <<
", mutator=" <<
static_cast<size_t>(context.mutatorProcessor));
453 NOTICE(
"QEMU-CONCURRENCY-TEST: PASS shared-kernel-tlb-shootdown-smp");
454 return privateTlbShootdownRegression();
Special memory entity in the kernel's virtual address space.
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() PURE
static const size_t virtualOnly
virtual void freePage(physical_uintptr_t page)=0
virtual bool allocateRegion(MemoryRegion &Region, size_t cPages, size_t pageConstraints, size_t Flags, physical_uintptr_t start=-1)=0
VirtualAddressSpace * getAddressSpace()
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
static const size_t Borrowed
virtual uintptr_t getDynamicStart() const
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool isMapped(void *virtualAddress)=0
static const size_t KernelMode
static const size_t Write
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
virtual uintptr_t getKernelStart() const =0
virtual MUST_USE_RESULT bool trySetFlags(void *virtualAddress, size_t newFlags)
virtual void unmap(void *virtualAddress)=0