8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/process/WaitQueue.h"
15#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
16#include "pedigree/kernel/time/Time.h"
22#include "modules/subsys/posix/PosixProcess.h"
23#include "modules/subsys/posix/PosixSubsystem.h"
24#include "modules/subsys/posix/pthread-syscalls.h"
25#include "modules/subsys/posix/signal-syscalls.h"
26#include "modules/subsys/posix/system-syscalls.h"
30constexpr int PrivateWait = 128;
31constexpr int PrivateWake = 129;
32constexpr int PrivateRequeue = 131;
33constexpr int RealtimeBitsetWait = 393;
34constexpr int PreservedErrno = 173;
35constexpr uint32_t OwnerDied = 0x40000000;
36constexpr uint32_t Waiters = 0x80000000;
38struct CrossOwnerContext {
40 size_t firstOwner = 0;
41 size_t secondOwner = 0;
44CrossOwnerContext* g_CrossOwner =
nullptr;
47 CrossOwnerContext* context = g_CrossOwner;
48 if (!context || queue != &context->queue) {
51 WaitQueue::setBeforeBlockHook(
nullptr);
54 auto guard = queue->acquire();
55 bool passed = guard.
wakeAndRequeue(source, 0, destination, 1) == 1;
57 passed &= thread->
getWaitDebugInfo(info) && info.queue == queue && info.queued &&
58 info.channelOwner == destination.owner && info.channelValue == destination.value;
59 passed &= !guard.wakeOne(WaitQueue::WakeReason::Signalled, source);
60 passed &= guard.wakeOne(WaitQueue::WakeReason::Signalled, destination);
61 context->passed = passed;
64bool crossOwnerRequeue() {
65 CrossOwnerContext context;
66 g_CrossOwner = &context;
67 WaitQueue::setBeforeBlockHook(requeueAcrossOwners);
68 auto guard = context.queue.acquire();
69 const auto reason = guard.wait(
WaitQueue::Channel(&context.firstOwner, 11), Thread::FutexWait);
70 WaitQueue::setBeforeBlockHook(
nullptr);
71 g_CrossOwner =
nullptr;
72 return context.passed && reason == WaitQueue::WakeReason::Signalled &&
73 context.queue.waiterCount() == 0;
83int peerWorker(
void* parameter) {
84 PeerContext* context =
reinterpret_cast<PeerContext*
>(parameter);
86 thread->setRobustList(context->head, thread->
getTaskId());
89 auto guard = context->queue.acquire();
90 if (context->release) {
93 const auto reason = guard.wait();
98void releasePeer(PeerContext& context) {
99 auto guard = context.queue.acquire();
100 context.release =
true;
105 const Time::Timestamp deadline = Time::getTicks() + Time::Multiplier::Second * 2;
106 while (!ready && Time::getTicks() < deadline) {
124 RobustNode acquiring;
125 uintptr_t peerHead[3];
126 uintptr_t returnedHead;
127 size_t returnedLength;
131 timespec zeroTimeout;
139struct AbsoluteWaitContext {
141 UserFixture* fixture;
142 bool entered =
false;
145AbsoluteWaitContext* g_AbsoluteWait =
nullptr;
148 auto* context = g_AbsoluteWait;
149 if (!context || context->thread != thread || state != Thread::FutexWait)
151 WaitQueue::setBeforeBlockHook(
nullptr);
152 context->entered =
true;
153 auto* fixture = context->fixture;
155 posix_futex(&fixture->source, PrivateRequeue, 0, 1, &fixture->destination, 0) == 1;
156 const int changed = 1;
160 posix_futex_clock_changed();
161 context->passed &= posix_futex(&fixture->source, PrivateWake, 1, 0,
nullptr, 0) == 0;
162 context->passed &= posix_futex(&fixture->destination, PrivateWake, 1, 0,
nullptr, 0) == 1;
165int futexWaiter(
void* parameter) {
166 FutexContext* context =
reinterpret_cast<FutexContext*
>(parameter);
167 auto* fixture =
reinterpret_cast<UserFixture*
>(context->address);
168 context->passed = posix_futex(&fixture->source, PrivateWait, 0,
169 reinterpret_cast<uintptr_t
>(&fixture->timeout),
nullptr, 0) == 0;
170 return context->passed ? 0 : 1;
173struct ContractContext {
175 uintptr_t address = 0;
176 uint32_t expectedExitOwner = 0;
180int contractWorker(
void* parameter) {
181 ContractContext* context =
reinterpret_cast<ContractContext*
>(parameter);
187 uintptr_t address = 0;
188 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
191 uintptr_t mappedAddress = address;
192 if (!mappings.
mapAnon(mappedAddress, pageSize,
193 MemoryMappedObject::Read | MemoryMappedObject::Write) ||
194 mappedAddress != address) {
197 context->address = address;
198 auto* fixture =
reinterpret_cast<UserFixture*
>(address);
199 bool passed = thread->
getId() == 1 && context->foreign->getId() == 1 &&
201 thread->
getTaskId() != context->foreign->getTaskId() &&
202 posix_gettid() ==
static_cast<pid_t
>(thread->
getId()) &&
203 posix_gettid(
true) ==
static_cast<pid_t
>(thread->
getTaskId()) &&
204 posix_set_tid_address(
nullptr,
true) ==
static_cast<pid_t
>(thread->
getTaskId());
209 passed &= posix_futex(&fixture->source, PrivateWait, 1, 0,
nullptr, 0) == -1 &&
210 thread->
getErrno() == Error::NoMoreProcesses;
212 UserFixture initial = {};
213 initial.next =
reinterpret_cast<uintptr_t
>(&fixture->held.next);
214 initial.offset = -
static_cast<intptr_t
>(offsetof(RobustNode, next));
215 initial.pending =
reinterpret_cast<uintptr_t
>(&fixture->acquiring.next);
216 initial.held = {
static_cast<uint32_t
>(thread->
getTaskId()) | Waiters, 0,
217 reinterpret_cast<uintptr_t
>(&fixture->foreign.next)};
218 initial.foreign = {
static_cast<uint32_t
>(context->foreign->getTaskId()), 0, address};
219 initial.acquiring = {
static_cast<uint32_t
>(thread->
getTaskId()), 0, 0};
220 initial.peerHead[0] =
reinterpret_cast<uintptr_t
>(fixture->peerHead);
221 initial.timeout.tv_sec = 2;
225 passed &= posix_futex(&fixture->source, PrivateWait, 0,
226 reinterpret_cast<uintptr_t
>(&fixture->zeroTimeout),
nullptr, 0) == -1 &&
227 thread->
getErrno() == Error::TimedOut;
228 uint32_t observed = 0;
229 bool exchanged =
false;
230 passed &= space->
tryReadUser32(
reinterpret_cast<uintptr_t
>(&fixture->source), observed) &&
232 passed &= mappings.setPermissions(address, pageSize, MemoryMappedObject::Read) == 1;
234 observed, 7, exchanged) &&
236 passed &= mappings.setPermissions(address, pageSize, MemoryMappedObject::None) == 1;
237 passed &= !space->
tryReadUser32(
reinterpret_cast<uintptr_t
>(&fixture->source), observed);
238 passed &= mappings.setPermissions(address, pageSize,
239 MemoryMappedObject::Read | MemoryMappedObject::Write) == 1;
240 passed &= space->
tryReadUser32(
reinterpret_cast<uintptr_t
>(&fixture->source), observed) &&
243 FutexContext futex{address};
244 Thread*
waiter =
new Thread(process, futexWaiter, &futex,
nullptr,
false,
true,
true);
245 bool waiterStarted =
waiter->start();
246 bool enrolled =
false;
247 const Time::Timestamp deadline = Time::getTicks() + Time::Multiplier::Second;
248 while (waiterStarted && Time::getTicks() < deadline) {
250 if (
waiter->getWaitDebugInfo(info) && info.queued && info.channelOwner == space &&
251 info.channelValue ==
reinterpret_cast<uintptr_t
>(&fixture->source)) {
257 passed &= enrolled &&
258 posix_futex(&fixture->source, PrivateRequeue, 0, 1, &fixture->destination, 0) == 1;
259 passed &= posix_futex(&fixture->source, PrivateWake, 1, 0,
nullptr, 0) == 0;
260 const int destinationWoken = posix_futex(&fixture->destination, PrivateWake, 1, 0,
nullptr, 0);
261 passed &= destinationWoken == 1;
262 if (!destinationWoken) {
263 posix_futex(&fixture->source, PrivateWake, 1, 0,
nullptr, 0);
265 const bool waiterJoined = waiterStarted &&
waiter->joinForCompletion();
266 if (!waiterStarted) {
269 passed &= waiterJoined && futex.passed;
271 const Time::Timestamp future = Time::getTimeNanoseconds() + 10 * Time::Multiplier::Second;
272 const struct timespec absolute = {
static_cast<time_t
>(future / Time::Multiplier::Second),
273 static_cast<long>(future % Time::Multiplier::Second)};
275 AbsoluteWaitContext absoluteContext{thread, fixture};
276 g_AbsoluteWait = &absoluteContext;
277 const size_t alarmCreates = Time::getHostedAlarmCreateCount();
278 const size_t alarmDestroys = Time::getHostedAlarmDestroyCount();
279 WaitQueue::setBeforeBlockHook(&wakeRequeuedAbsoluteWait);
280 const int absoluteResult = posix_futex(&fixture->source, RealtimeBitsetWait, 0,
281 reinterpret_cast<uintptr_t
>(&fixture->timeout),
nullptr,
282 static_cast<int>(UINT32_MAX));
283 WaitQueue::setBeforeBlockHook(
nullptr);
284 g_AbsoluteWait =
nullptr;
285 passed &= absoluteResult == 0 && absoluteContext.entered && absoluteContext.passed &&
286 Time::getHostedAlarmCreateCount() == alarmCreates + 1 &&
287 Time::getHostedAlarmDestroyCount() == alarmDestroys + 1;
288 passed &= posix_futex(&fixture->destination, PrivateWake, 1, 0,
nullptr, 0) == 0;
289 posix_futex_clock_changed();
291 auto** outputHead =
reinterpret_cast<robust_list_head**
>(&fixture->returnedHead);
293 passed &= posix_get_robust_list(0, outputHead, &fixture->returnedLength,
true) == 0 &&
294 thread->
getErrno() == PreservedErrno && fixture->returnedHead == 0 &&
295 fixture->returnedLength == 3 *
sizeof(uintptr_t);
297 passed &= posix_set_robust_list(
reinterpret_cast<robust_list_head*
>(address), 0,
true) == -1 &&
298 thread->
getErrno() == Error::InvalidArgument;
299 passed &= posix_set_robust_list(
reinterpret_cast<robust_list_head*
>(address),
300 3 *
sizeof(uintptr_t),
true) == 0;
303 peer.head =
reinterpret_cast<uintptr_t
>(fixture->peerHead);
304 Thread* buddy =
new Thread(process, peerWorker, &peer,
nullptr,
false,
true,
true);
305 const bool buddyStarted = buddy->
start();
306 const bool buddyReady = buddyStarted && awaitReady(peer.ready);
308 buddy->
getTaskId() != context->foreign->getTaskId();
309 passed &= posix_get_robust_list(
static_cast<int>(buddy->
getTaskId()), outputHead,
310 &fixture->returnedLength,
true) == 0 &&
311 fixture->returnedHead == peer.head && thread->getRobustList() == address;
313 passed &= posix_get_robust_list(
static_cast<int>(context->foreign->getTaskId()), outputHead,
314 &fixture->returnedLength,
true) == -1 &&
315 thread->
getErrno() == Error::NotEnoughPermissions;
317 passed &= posix_get_robust_list(INT_MAX, outputHead, &fixture->returnedLength,
true) == -1 &&
318 thread->
getErrno() == Error::NoSuchProcess;
320 passed &= posix_get_robust_list(0,
nullptr, &fixture->returnedLength,
true) == -1 &&
321 thread->
getErrno() == Error::BadAddress;
322 passed &= posix_tkill(
static_cast<int>(buddy->
getTaskId()), 0,
true) == 0 &&
323 posix_tgkill(
static_cast<int>(process->
getId()),
static_cast<int>(buddy->
getTaskId()),
325 posix_tkill(
static_cast<int>(context->foreign->getTaskId()), 0,
true) == 0;
327 passed &= posix_tgkill(
static_cast<int>(process->
getId()),
328 static_cast<int>(context->foreign->getTaskId()), 0,
true) == -1 &&
329 thread->
getErrno() == Error::NoSuchProcess;
335 passed &= buddyJoined;
337 posix_robust_list_exit(thread);
338 passed &= thread->getRobustList() == 0 && fixture->held.owner == (OwnerDied | Waiters) &&
339 fixture->acquiring.owner == OwnerDied &&
340 fixture->foreign.owner == context->foreign->getTaskId();
343 fixture->held.owner =
static_cast<uint32_t
>(thread->
getTaskId());
344 fixture->held.next = address;
345 fixture->pending = 0;
346 context->expectedExitOwner = OwnerDied;
347 passed &= posix_set_robust_list(
reinterpret_cast<robust_list_head*
>(address),
348 3 *
sizeof(uintptr_t),
true) == 0;
349 context->passed = passed;
350 return passed ? 0 : 1;
353int cleanupWorker(
void* parameter) {
354 ContractContext* context =
reinterpret_cast<ContractContext*
>(parameter);
356 if (context->address) {
359 Process::UserRegion::Normal, context->address, pageSize);
365bool runHostedFutexRobustRegressions(
Process* kernelProcess) {
366 bool passed = crossOwnerRequeue();
373 PeerContext foreignContext;
375 new Thread(foreign, peerWorker, &foreignContext,
nullptr,
false,
true,
true);
376 const bool foreignStarted = foreignThread->
start();
377 passed &= foreignStarted && awaitReady(foreignContext.ready);
378 ContractContext context{foreignThread};
379 Thread*
worker =
new Thread(process, contractWorker, &context,
nullptr,
false,
true,
true);
380 const bool started =
worker->start();
381 const bool joined = started &&
worker->joinForCompletion();
385 uint32_t exitOwner = 0;
386 const bool recovered =
389 context.address + offsetof(UserFixture, held) + offsetof(RobustNode, owner), exitOwner) &&
390 exitOwner == context.expectedExitOwner;
391 passed &= joined && context.passed && recovered;
392 releasePeer(foreignContext);
394 if (!foreignStarted) {
395 delete foreignThread;
397 passed &= foreignJoined;
398 Thread* cleanup =
new Thread(process, cleanupWorker, &context,
nullptr,
false,
true);
403 NOTICE(
"HOSTED-SYSCALL-TEST: PASS futex-robust-contracts");
405 ERROR(
"HOSTED-SYSCALL-TEST: FAIL futex-robust-contracts: queries=" << context.passed
406 <<
", exit=" << recovered);
Memory-mapped file interface.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
static constexpr size_t getPageSize() PURE
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
VirtualAddressSpace * getAddressSpace()
static ProcessorInformation & information()
static Scheduler & instance()
void setErrno(size_t err)
bool getWaitDebugInfo(WaitDebugInfo &info)
Process * getParent() const
virtual bool tryCompareExchangeUser32(uintptr_t address, uint32_t &expected, uint32_t desired, bool &exchanged)
virtual bool tryReadUser32(uintptr_t address, uint32_t &value)
size_t wakeAndRequeue(const Channel &source, size_t wakeCount, const Channel &destination, size_t requeueCount)