The Pedigree Project 0.1
network-stack-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/machine/IrqHandler.h"
11#include "pedigree/kernel/machine/IrqHandlerRegistry.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/Processor.h"
16#include "pedigree/kernel/processor/state.h"
17#include "pedigree/kernel/time/Time.h"
18#include "pedigree/kernel/utilities/new"
19
25#include "modules/system/network-stack/NetworkStack.h"
26
27namespace {
28class HostedNetworkDevice final : public Network {
29 public:
30 bool send(size_t, uintptr_t) override {
31 return true;
32 }
33
34 const StationInfo& getStationInfo() override {
35 return m_StationInfo;
36 }
37};
38
39bool check(bool condition, const char* test, const char* detail) {
40 if (condition) {
41 return true;
42 }
43
44 ERROR("HOSTED-NETWORK-TEST: FAIL " << test << ": " << detail);
45 return false;
46}
47
48bool isRegistered(NetworkStack& stack, Network* device) {
50 return stack.acquireDevice(device, lease);
51}
52
53bool waitUntilQueued(Thread* thread, size_t debugState) {
54 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
55 while (Time::getTicks() < deadline) {
56 Thread::WaitDebugInfo info = {};
57 uintptr_t debugAddress = 0;
58 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
59 thread->getDebugState(debugAddress) == debugState) {
60 return true;
61 }
63 }
64 return false;
65}
66
67struct DeviceDeregisterContext {
68 DeviceDeregisterContext(NetworkStack* stack, Network* device)
69 : stack(stack), device(device), entered(0), returned(0) {}
70
71 NetworkStack* stack;
72 Network* device;
73 Atomic<size_t> entered;
74 Atomic<size_t> returned;
75};
76
77int deregisterLeasedDevice(void* parameter) {
78 DeviceDeregisterContext* context = reinterpret_cast<DeviceDeregisterContext*>(parameter);
79 context->entered += 1;
80 context->stack->deRegisterDevice(context->device);
81 context->returned += 1;
82 return 0;
83}
84
85bool deviceLeaseDeregisterDrain() {
86 static const char* Test = "device-lease-deregister-drain";
87
89 alignas(HostedNetworkDevice) uint8_t deviceStorage[sizeof(HostedNetworkDevice)];
90 HostedNetworkDevice* original = new (deviceStorage) HostedNetworkDevice();
91 stack.registerDevice(original);
92
94 const bool acquired = stack.acquireDevice(original, held);
95 struct netif* originalInterface = acquired ? held.interface() : nullptr;
96 DeviceDeregisterContext context(&stack, original);
97 Thread* remover = nullptr;
98 bool drainPublished = false;
99 bool lateRejected = false;
100 bool usableWhileDraining = false;
101
102 if (acquired) {
103 remover = new Thread(Scheduler::instance().getKernelProcess(), deregisterLeasedDevice, &context,
104 nullptr, false, true);
105 remover->setName("hosted network device deregister");
106 drainPublished = waitUntilQueued(remover, Thread::CallbackDrain);
107
109 lateRejected = !stack.acquireDevice(original, late);
110
111 String name;
112 held.device()->getName(name);
113 const StationInfo& info = held.device()->getStationInfo();
114 usableWhileDraining = held.device() == original && held.interface() == originalInterface &&
115 originalInterface && originalInterface->state == original &&
116 info.nPackets == 0;
118 }
119
120 const bool removerJoined = remover && remover->join();
121 const bool unregistered = !isRegistered(stack, original);
122 if (!remover) {
123 stack.deRegisterDevice(original);
124 }
125 original->~HostedNetworkDevice();
126
127 HostedNetworkDevice* replacement = new (deviceStorage) HostedNetworkDevice();
128 stack.registerDevice(replacement);
129 NetworkStack::DeviceLease replacementLease;
130 const bool replacementRegistered = stack.acquireDevice(replacement, replacementLease);
131 replacementLease = NetworkStack::DeviceLease();
132 stack.deRegisterDevice(replacement);
133 replacement->~HostedNetworkDevice();
134
135 const bool passed = check(
136 acquired && context.entered == 1 && drainPublished && context.returned == 1 && lateRejected &&
137 usableWhileDraining && removerJoined && unregistered && replacementRegistered,
138 Test, "deregistration did not unpublish, drain, and retire one held registration");
139 if (passed) {
140 NOTICE("HOSTED-NETWORK-TEST: PASS " << Test);
141 }
142 return passed;
143}
144
145class MailboxHardIrqProbe final : public HardIrqHandler {
146 public:
147 MailboxHardIrqProbe(sys_mbox_t* mailbox, void* payload)
148 : mailbox(mailbox), payload(payload), result(ERR_OK) {}
149
150 HardIrqDisposition irq(irq_id_t, InterruptState&) override {
151 result = sys_mbox_trypost(mailbox, payload);
152 return HardIrqDisposition::Handled;
153 }
154
155 sys_mbox_t* mailbox;
156 void* payload;
157 err_t result;
158};
159
160bool mailboxTryPostRejectsHardIrq() {
161 static const char* Test = "mailbox-hard-irq-trypost";
162 static IrqHandlerRegistry registry;
163 constexpr uint8_t TestIrq = 42;
164
165 sys_mbox_t mailbox = nullptr;
166 if (sys_mbox_new(&mailbox, 1) != ERR_OK) {
167 return check(false, Test, "mailbox creation failed");
168 }
169
170 int payload = 0;
171 MailboxHardIrqProbe probe(&mailbox, &payload);
172 if (!registry.registerHardHandler(TestIrq, &probe)) {
173 sys_mbox_free(&mailbox);
174 return check(false, Test, "hard IRQ registration failed");
175 }
176
177 alignas(InterruptState) uint8_t stateStorage[sizeof(InterruptState)] = {};
178 InterruptState& state = *reinterpret_cast<InterruptState*>(stateStorage);
179 HardIrqDisposition disposition = HardIrqDisposition::NotHandled;
180 const bool interruptsWereEnabled = Processor::getInterrupts();
182 const bool dispatched = registry.dispatchHard(TestIrq, state, disposition);
183 Processor::setInterrupts(interruptsWereEnabled);
184 const bool removed = registry.unregisterHandler(TestIrq, &probe) ==
185 IrqHandlerRegistry::UnregisterResult::Completed;
186
187 void* fetched = nullptr;
188 const u32_t fetchResult = sys_arch_mbox_tryfetch(&mailbox, &fetched);
189 sys_mbox_free(&mailbox);
190
191 const bool passed =
192 check(dispatched && disposition == HardIrqDisposition::Handled && removed &&
193 probe.result == ERR_WOULDBLOCK && fetchResult == SYS_MBOX_EMPTY && !fetched,
194 Test, "the ISR call enqueued data or entered a blocking notification path");
195 if (passed) {
196 NOTICE("HOSTED-NETWORK-TEST: PASS " << Test);
197 }
198 return passed;
199}
200
201struct ReceiveContext {
202 ReceiveContext(NetworkStack* stack, Network* device, struct netif* interface)
203 : stack(stack),
204 device(device),
205 interface(interface),
206 removal(stack, device),
207 inputEntered(0),
208 allowInput(0),
209 coreEntered(0),
210 allowCore(0),
211 admitInput(0),
212 inputs(0),
213 enqueued(0),
214 delivered(0),
215 failures(0) {}
216
217 NetworkStack* stack;
218 Network* device;
219 struct netif* interface;
220 DeviceDeregisterContext removal;
221 Semaphore inputEntered;
222 Semaphore allowInput;
223 Semaphore coreEntered;
224 Semaphore allowCore;
225 Atomic<size_t> admitInput;
226 Atomic<size_t> inputs;
227 Atomic<size_t> enqueued;
228 Atomic<size_t> delivered;
229 Atomic<size_t> failures;
230};
231
232ReceiveContext* g_ReceiveContext = nullptr;
233
234void holdTcpipWorker(void* parameter) {
235 ReceiveContext* context = reinterpret_cast<ReceiveContext*>(parameter);
236 context->coreEntered.release();
237 if (!context->allowCore.acquireForCompletion()) {
238 context->failures += 1;
239 }
240}
241
242err_t receiveQueuedPacket(struct pbuf* packet, struct netif* interface) {
243 ReceiveContext* context = g_ReceiveContext;
244 if (context->removal.returned || interface != context->interface ||
245 interface->state != context->device || packet->tot_len != 64 ||
246 pbuf_get_at(packet, 0) != 0x5a || pbuf_get_at(packet, 63) != 0xa5) {
247 context->failures += 1;
248 }
249 pbuf_free(packet);
250 context->delivered += 1;
251 return ERR_OK;
252}
253
254err_t holdReceiveInput(struct pbuf* packet, struct netif* interface) {
255 ReceiveContext* context = g_ReceiveContext;
256 if ((context->inputs += 1) == 1) {
257 context->inputEntered.release();
258 if (!context->allowInput.acquireForCompletion()) {
259 context->failures += 1;
260 }
261 }
262 if (!context->admitInput) {
263 pbuf_free(packet);
264 return ERR_OK;
265 }
266
267 const err_t result = tcpip_inpkt(packet, interface, receiveQueuedPacket);
268 if (result == ERR_OK) {
269 context->enqueued += 1;
270 } else {
271 context->failures += 1;
272 }
273 return result;
274}
275
276int receivePacketBatch(void* parameter) {
277 ReceiveContext* context = reinterpret_cast<ReceiveContext*>(parameter);
279 if (!context->stack->acquireDevice(context->device, batch)) {
280 context->failures += 1;
281 return 0;
282 }
283 uint8_t packet[68] = {};
284 packet[4] = 0x5a;
285 packet[67] = 0xa5;
286 for (size_t i = 0; i < 2; ++i) {
287 context->stack->receive(64, reinterpret_cast<uintptr_t>(packet), batch, 4);
288 }
289 return 0;
290}
291
292bool receiveInterfaceRetirement() {
293 static const char* Test = "receive-interface-retirement";
294
296 HostedNetworkDevice device;
297 stack.registerDevice(&device);
299 if (!stack.acquireDevice(&device, setup)) {
300 stack.deRegisterDevice(&device);
301 return check(false, Test, "could not acquire the registered interface");
302 }
303
304 ReceiveContext context(&stack, &device, setup.interface());
305 setup.interface()->input = holdReceiveInput;
307 g_ReceiveContext = &context;
308
309 const bool corePosted = tcpip_callback(holdTcpipWorker, &context) == ERR_OK;
310 const bool coreHeld = corePosted && context.coreEntered.acquire(1, 2);
311 Thread* receiver = nullptr;
312 Thread* remover = nullptr;
313 bool inputHeld = false;
314 bool leaseDraining = false;
315 bool lateRejected = false;
316 bool receiverJoined = false;
317 bool queueDraining = false;
318
319 if (coreHeld) {
320 receiver = new Thread(Scheduler::instance().getKernelProcess(), receivePacketBatch, &context,
321 nullptr, false, true);
322 receiver->setName("hosted network receive");
323 inputHeld = context.inputEntered.acquire(1, 2);
324 if (inputHeld) {
325 remover = new Thread(Scheduler::instance().getKernelProcess(), deregisterLeasedDevice,
326 &context.removal, nullptr, false, true);
327 remover->setName("hosted network receive retirement");
328 leaseDraining = waitUntilQueued(remover, Thread::CallbackDrain);
329 if (leaseDraining) {
330 uint8_t latePacket[64] = {};
331 stack.receive(sizeof(latePacket), reinterpret_cast<uintptr_t>(latePacket), &device, 0);
332 lateRejected = !isRegistered(stack, &device) && context.inputs == 1;
333 context.admitInput = 1;
334 }
335 }
336 }
337
338 // Release a late receiver as well, but only enqueue when retirement is
339 // demonstrably waiting for its lease; a failed assertion must not use a
340 // possibly retired interface.
341 context.allowInput.release();
342 receiverJoined = receiver && receiver->join();
343 if (remover && leaseDraining) {
344 queueDraining = waitUntilQueued(remover, Thread::SemWait) && !context.removal.returned &&
345 !context.delivered && context.enqueued == 2;
346 }
347
348 context.allowCore.release();
349 const bool removerJoined = remover && remover->join();
350 if (!remover) {
351 stack.deRegisterDevice(&device);
352 }
353 g_ReceiveContext = nullptr;
354
355 bool passed = true;
356 passed &= check(coreHeld && inputHeld && leaseDraining && lateRejected && receiverJoined, Test,
357 "receive batch did not pin its interface through input admission");
358 passed &= check(queueDraining && removerJoined && context.removal.returned == 1 &&
359 context.delivered == 2 && !context.failures && !isRegistered(stack, &device),
360 Test, "queued input did not finish before interface retirement");
361 if (passed) {
362 NOTICE("HOSTED-NETWORK-TEST: PASS " << Test);
363 }
364 return passed;
365}
366} // namespace
367
368bool runHostedNetworkStackRegressions() {
369 bool passed = mailboxTryPostRejectsHardIrq();
370 passed &= deviceLeaseDeregisterDrain();
371 passed &= receiveInterfaceRetirement();
372 return passed;
373}
virtual HardIrqDisposition irq(irq_id_t number, InterruptState &state)=0
UnregisterResult unregisterHandler(uint8_t irq, IrqHandlerBase *handler)
bool dispatchHard(uint8_t irq, InterruptState &state, HardIrqDisposition &disposition, HardIrqHandler *onlyHandler=nullptr, size_t dispatchGeneration=0, irq_id_t callbackId=0)
bool registerHardHandler(uint8_t irq, HardIrqHandler *handler)
void deRegisterDevice(Network *pDevice)
static NetworkStack & instance()
void receive(size_t nBytes, uintptr_t packet, Network *pCard, uint32_t offset)
MUST_USE_RESULT bool acquireDevice(size_t n, DeviceLease &lease)
void registerDevice(Network *pDevice)
virtual const StationInfo & getStationInfo()
Definition Network.cc:79
virtual bool send(size_t nBytes, uintptr_t buffer)=0
virtual void getName(String &str)
Definition Network.cc:67
static bool getInterrupts()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
bool join()
Definition Thread.cc:2746
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
s8_t err_t
Definition err.h:76
@ ERR_OK
Definition err.h:82
@ ERR_WOULDBLOCK
Definition err.h:96
HardIrqDisposition
Definition IrqHandler.h:44
u8_t pbuf_get_at(const struct pbuf *p, u16_t offset)
Definition pbuf.c:1318
u8_t pbuf_free(struct pbuf *p)
Definition pbuf.c:734
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
Definition sys_arch.cc:313
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
Definition sys_arch.cc:250
void sys_mbox_free(sys_mbox_t *mbox)
Definition sys_arch.cc:255
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
Definition sys_arch.cc:279
Definition netif.h:244
char name[2]
Definition netif.h:326
void * state
Definition netif.h:298
netif_input_fn input
Definition netif.h:263
Definition pbuf.h:161
u16_t tot_len
Definition pbuf.h:175
#define SYS_MBOX_EMPTY
Definition sys.h:111
err_t tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
Definition tcpip.c:236
#define tcpip_callback(f, ctx)
Definition tcpip.h:113