The Pedigree Project 0.1
concurrency-smoke/syscall-regressions.cc
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#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/PerProcessorScheduler.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/Semaphore.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/ProcessorInformation.h"
16#include "pedigree/kernel/processor/SyscallManager.h"
17
18namespace {
19class LifetimeHandler : public SyscallHandler {
20 public:
21 LifetimeHandler() : entered(0), release(0), own(nullptr), peer(nullptr), abandon(false) {}
22
23 uintptr_t syscall(SyscallState&) override {
24 if (own && (own->reset() || !*own)) {
25 FATAL("QEMU syscall self-removal lost ownership");
26 }
27 if (peer && (!peer->reset() || *peer)) {
28 FATAL("QEMU syscall idle peer could not retire");
29 }
30 if (abandon) {
31 Thread* thread = Processor::information().getCurrentThread();
33 PerProcessorScheduler::StackDiscardReason::HostedRegression);
34 }
35 if (own) {
36 entered.release();
37 if (!release.acquireForCompletion()) {
38 FATAL("QEMU syscall lifetime wait interrupted");
39 }
40 }
41 return 0x51;
42 }
43
44 Semaphore entered;
45 Semaphore release;
48 bool abandon;
49};
50
51uintptr_t fastEntry(SyscallHandler* handler, SyscallState& state) {
52 return handler->syscall(state) + 0x100;
53}
54
55class NestedRetirementHandler : public SyscallHandler {
56 public:
57 explicit NestedRetirementHandler(bool inner) : m_Inner(inner) {}
58
59 uintptr_t syscall(SyscallState&) override {
60 Thread* thread = Processor::information().getCurrentThread();
62 if (m_Inner) {
63 // Exec from a nested event abandons ancestor scopes while keeping the
64 // current syscall alive long enough to request its userspace transition.
66 if (!manager.requestUserJump(0x1000, 0x2000)) {
67 FATAL("QEMU ancestor retirement lost the nested syscall action");
68 }
69 return 0x62;
70 }
71 if (!thread->pushState()) {
72 FATAL("QEMU syscall nested state could not be created");
73 }
74 uintptr_t result = 0;
75 if (manager.dispatchHandlerForTest(native, result) || result != 0x62) {
76 FATAL("QEMU nested syscall action was not retained");
77 }
78 thread->popState();
79 if (manager.requestThreadExit()) {
80 FATAL("QEMU ancestor retirement left a stale syscall context");
81 }
82 return 0x63;
83 }
84
85 private:
86 bool m_Inner;
87};
88
89struct Invocation {
90 uintptr_t result = 0;
91 bool returned = false;
92};
93
94int invoke(void* parameter) {
95 Invocation* invocation = static_cast<Invocation*>(parameter);
96 invocation->returned = SyscallManager::instance().dispatchHandlerForTest(TUI, invocation->result);
97 return 0;
98}
99
100struct Removal {
101 explicit Removal(SyscallManager::Registration& token)
102 : registration(token), closed(0), retired(0) {}
103 SyscallManager::Registration& registration;
104 Semaphore closed;
105 Atomic<size_t> retired;
106};
107
108int remove(void* parameter) {
109 Removal* removal = static_cast<Removal*>(parameter);
110 if (!removal->registration.closeAdmission()) {
111 FATAL("QEMU syscall admission could not close");
112 }
113 removal->closed.release();
114 if (!removal->registration.reset()) {
115 FATAL("QEMU syscall admitted callback could not drain");
116 }
117 removal->retired = 1;
118 return 0;
119}
120} // namespace
121
122bool runSyscallLifetimeRegression() {
123 NOTICE("QEMU-CONCURRENCY-TEST: BEGIN syscall-handler-lifetime");
125 Process* process = Scheduler::instance().getKernelProcess();
126 LifetimeHandler handler, peer;
127 SyscallManager::Registration registration, peerRegistration;
128 if (!manager.registerSyscallHandler(TUI, &handler, registration, fastEntry) ||
129 !manager.registerSyscallHandler(native, &peer, peerRegistration)) {
130 return false;
131 }
132 handler.own = &registration;
133 handler.peer = &peerRegistration;
134 Invocation invocation;
135 Thread* callback = new Thread(process, invoke, &invocation);
136 if (!handler.entered.acquireForCompletion()) {
137 return false;
138 }
139 Removal removal(registration);
140 Thread* remover = new Thread(process, remove, &removal);
141 if (!removal.closed.acquireForCompletion()) {
142 return false;
143 }
144 Thread* thread = Processor::information().getCurrentThread();
145 void* dispatchContext = thread->getSyscallDispatchContext();
146 const bool terminationDeferred = thread->isTerminationDeferred();
147 uintptr_t result = 0;
148 if (manager.dispatchHandlerForTest(TUI, result) ||
149 thread->getSyscallDispatchContext() != dispatchContext ||
150 thread->isTerminationDeferred() != terminationDeferred) {
151 return false;
152 }
153 for (size_t i = 0; i < 32; ++i) {
155 if (removal.retired) {
156 return false;
157 }
158 }
159 handler.release.release();
160 if (!callback->joinForCompletion() || !remover->joinForCompletion() || !invocation.returned ||
161 invocation.result != 0x151 || !removal.retired || registration || peerRegistration) {
162 return false;
163 }
164
165 // Reuse the service with a different object and the virtual fallback. A stale
166 // fast entry from the prior generation would change the return value.
167 if (!manager.registerSyscallHandler(TUI, &peer, registration) ||
168 !manager.dispatchHandlerForTest(TUI, result) || result != 0x51 || !registration.reset()) {
169 return false;
170 }
171
172 peer.abandon = true;
173 if (!manager.registerSyscallHandler(TUI, &peer, registration)) {
174 return false;
175 }
176 Invocation abandoned;
177 Thread* discarded = new Thread(process, invoke, &abandoned);
178 if (!discarded->joinForCompletion() || abandoned.returned || !registration.reset()) {
179 return false;
180 }
181
182 NestedRetirementHandler outer(false), inner(true);
183 if (!manager.registerSyscallHandler(TUI, &outer, registration) ||
184 !manager.registerSyscallHandler(native, &inner, peerRegistration)) {
185 return false;
186 }
187 Invocation nested;
188 Thread* nesting = new Thread(process, invoke, &nested);
189 if (!nesting->joinForCompletion() || !nested.returned || nested.result != 0x63 ||
190 !registration.reset() || !peerRegistration.reset()) {
191 return false;
192 }
193 NOTICE("QEMU-CONCURRENCY-TEST: PASS syscall-handler-lifetime");
194 return true;
195}
void abandonCurrentThreadStack(StackDiscardReason reason, Spinlock *pLock=0) NORETURN
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
virtual uintptr_t syscall(SyscallState &State)=0
virtual bool registerSyscallHandler(Service_t Service, SyscallHandler *pHandler, Registration &registration, FastEntry entry=nullptr)=0
static EXPORTED_PUBLIC SyscallManager & instance()
EXPORTED_PUBLIC bool requestThreadExit()
bool joinForCompletion()
Definition Thread.cc:2750
void popState(bool clean=true)
Definition Thread.cc:913
void prepareSignalStateForExec()
Definition Thread.cc:2040
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:929
bool isTerminationDeferred() const
Definition Thread.h:569
SchedulerState * pushState()
Definition Thread.cc:841