8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/Semaphore.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/time/Time.h"
15#include "pedigree/kernel/utilities/new"
20#include "modules/system/network-stack/NetworkStack.h"
21#include "system/kernel/core/processor/DeviceHardIrqContext.h"
24class HostedNetworkDevice final :
public Network {
26 bool send(
size_t, uintptr_t)
override {
35struct ReceivePublicationContext {
36 static constexpr size_t MaxTrackedBuffers = 257;
38 ReceivePublicationContext(
Network* device,
bool holdFirst)
44 queuedWithInterruptsDisabled(0),
62 uintptr_t buffers[MaxTrackedBuffers] = {};
63 uint8_t terminalCounts[MaxTrackedBuffers] = {};
65 bool everyBufferTerminatedOnce(
size_t expected)
const {
66 if (expected > MaxTrackedBuffers) {
69 for (
size_t i = 0; i < expected; ++i) {
70 if (!buffers[i] || terminalCounts[i] != 1) {
78ReceivePublicationContext* g_ReceivePublicationContext =
nullptr;
80void receivePublicationHook(NetworkStack::HostedReceiveEvent event, uintptr_t buffer,
82 ReceivePublicationContext* context = g_ReceivePublicationContext;
83 if (!context || device != context->device) {
87 bool terminalEvent =
false;
89 case NetworkStack::HostedReceiveEvent::Queued: {
90 const size_t index = (context->queued += 1) - 1;
91 if (index >= ReceivePublicationContext::MaxTrackedBuffers) {
92 context->failures += 1;
95 context->buffers[index] = buffer;
97 context->queuedWithInterruptsDisabled += 1;
101 case NetworkStack::HostedReceiveEvent::BeforeDispatch:
102 if ((context->beforeDispatch += 1) == 1 && context->holdFirst) {
103 context->workerEntered.release();
104 if (!context->allowDispatch.acquireForCompletion()) {
105 context->failures += 1;
109 case NetworkStack::HostedReceiveEvent::Delivered:
110 context->delivered += 1;
111 terminalEvent =
true;
113 case NetworkStack::HostedReceiveEvent::DiscardedStale:
114 context->staleDiscards += 1;
115 terminalEvent =
true;
117 case NetworkStack::HostedReceiveEvent::Cancelled:
118 context->cancellations += 1;
119 terminalEvent =
true;
123 if (!terminalEvent) {
128 for (
size_t i = 0; i < context->queued; ++i) {
129 if (context->buffers[i] != buffer) {
133 if (++context->terminalCounts[i] != 1) {
134 context->failures += 1;
139 context->failures += 1;
143struct ReceiveAbaContext {
144 explicit ReceiveAbaContext(
Network* device)
170ReceiveAbaContext* g_ReceiveAbaContext =
nullptr;
172void receiveHook(NetworkStack::HostedReceiveEvent event, uintptr_t buffer,
Network* device,
174 ReceiveAbaContext* context = g_ReceiveAbaContext;
175 if (!context || device != context->device) {
179 if (event == NetworkStack::HostedReceiveEvent::Queued) {
180 context->queued += 1;
181 if (!context->buffer.compareAndSwap(0, buffer)) {
182 context->failures += 1;
184 context->generation = generation;
188 if (buffer !=
static_cast<uintptr_t
>(context->buffer)) {
193 case NetworkStack::HostedReceiveEvent::BeforeDispatch:
194 if ((context->beforeDispatch += 1) != 1) {
195 context->failures += 1;
198 context->workerEntered.release();
199 if (!context->allowDispatch.acquireForCompletion()) {
200 context->failures += 1;
203 case NetworkStack::HostedReceiveEvent::Delivered:
204 context->delivered += 1;
206 case NetworkStack::HostedReceiveEvent::DiscardedStale:
207 context->staleDiscards += 1;
209 case NetworkStack::HostedReceiveEvent::Cancelled:
210 context->cancellations += 1;
212 case NetworkStack::HostedReceiveEvent::Queued:
217bool check(
bool condition,
const char* detail) {
222 ERROR(
"HOSTED-NETWORK-TEST: FAIL receive-generation-aba: " << detail);
226bool check(
bool condition,
const char* test,
const char* detail) {
231 ERROR(
"HOSTED-NETWORK-TEST: FAIL " << test <<
": " << detail);
240bool waitUntilQueued(
Thread* thread,
size_t debugState) {
241 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
242 while (Time::getTicks() < deadline) {
244 uintptr_t debugAddress = 0;
254struct DeviceDeregisterContext {
256 : stack(stack), device(device), entered(0), returned(0) {}
264int deregisterLeasedDevice(
void* parameter) {
265 DeviceDeregisterContext* context =
reinterpret_cast<DeviceDeregisterContext*
>(parameter);
266 context->entered += 1;
267 context->stack->deRegisterDevice(context->device);
268 context->returned += 1;
272bool deviceLeaseDeregisterDrain() {
273 static const char* Test =
"device-lease-deregister-drain";
276 alignas(HostedNetworkDevice) uint8_t deviceStorage[
sizeof(HostedNetworkDevice)];
277 HostedNetworkDevice* original =
new (deviceStorage) HostedNetworkDevice();
279 const size_t originalGeneration = NetworkStack::getHostedRegistrationGeneration(original);
283 struct netif* originalInterface = acquired ? held.interface() :
nullptr;
284 DeviceDeregisterContext context(&stack, original);
285 Thread* remover =
nullptr;
286 bool drainPublished =
false;
287 bool lateRejected =
false;
288 bool usableWhileDraining =
false;
292 nullptr,
false,
true);
293 remover->setName(
"hosted network device deregister");
294 drainPublished = waitUntilQueued(remover, Thread::CallbackDrain);
302 usableWhileDraining = held.device() == original && held.interface() == originalInterface &&
303 originalInterface && originalInterface->
state == original &&
308 const bool removerJoined = remover && remover->
join();
309 const bool unregistered =
310 !isRegistered(stack, original) && !NetworkStack::getHostedRegistrationGeneration(original);
314 original->~HostedNetworkDevice();
316 HostedNetworkDevice* replacement =
new (deviceStorage) HostedNetworkDevice();
318 const size_t replacementGeneration = NetworkStack::getHostedRegistrationGeneration(replacement);
320 const bool replacementRegistered = stack.
acquireDevice(replacement, replacementLease);
323 replacement->~HostedNetworkDevice();
326 check(acquired && originalGeneration && context.entered == 1 && drainPublished &&
327 context.returned == 1 && lateRejected && usableWhileDraining && removerJoined &&
328 unregistered && replacementRegistered && replacementGeneration &&
329 replacementGeneration != originalGeneration,
330 Test,
"deregistration did not unpublish, drain, and retire one held registration");
332 NOTICE(
"HOSTED-NETWORK-TEST: PASS " << Test);
337bool mailboxTryPostRejectsHardIrq() {
338 static const char* Test =
"mailbox-hard-irq-trypost";
342 return check(
false, Test,
"mailbox creation failed");
347 size_t previousDepth = 0;
348 bool restorationArmed =
false;
354 void* fetched =
nullptr;
360 "the ISR call enqueued data or entered a blocking notification path");
362 NOTICE(
"HOSTED-NETWORK-TEST: PASS " << Test);
367bool interruptReceivePublication() {
368 static const char* Test =
"receive-interrupt-publication";
371 HostedNetworkDevice device;
374 ReceivePublicationContext context(&device,
true);
375 g_ReceivePublicationContext = &context;
376 NetworkStack::setHostedReceiveHook(receivePublicationHook);
378 uint8_t packet[64] = {};
381 stack.
receive(
sizeof(packet),
reinterpret_cast<uintptr_t
>(packet), &device, 0);
385 const bool workerHeld = context.workerEntered.acquire(1, 2);
386 bool unregistered =
false;
390 !isRegistered(stack, &device) && !NetworkStack::getHostedRegistrationGeneration(&device);
392 context.allowDispatch.release();
393 const bool drained = stack.
drain();
394 NetworkStack::setHostedReceiveHook(
nullptr);
395 g_ReceivePublicationContext =
nullptr;
400 const size_t terminalOwnershipEvents =
static_cast<size_t>(context.delivered) +
401 static_cast<size_t>(context.staleDiscards) +
402 static_cast<size_t>(context.cancellations);
405 passed &= check(interrupts && remainedDisabled && context.queued == 1 &&
406 context.queuedWithInterruptsDisabled == 1,
407 Test,
"receive did not publish while preserving IF=0");
408 passed &= check(workerHeld && unregistered && drained && context.beforeDispatch == 1 &&
409 !context.delivered && context.staleDiscards == 1 && !context.cancellations &&
410 terminalOwnershipEvents == 1 && context.everyBufferTerminatedOnce(1) &&
412 Test,
"the interrupt-published pbuf did not transfer exactly once");
413 passed &= check(NetworkStack::getHostedReceiveRequestCapacity() == 256, Test,
414 "the preallocated bank no longer preserves receive capacity");
417 NOTICE(
"HOSTED-NETWORK-TEST: PASS " << Test);
422bool boundedReceiveBurst() {
423 static const char* Test =
"receive-bounded-burst";
426 HostedNetworkDevice device;
429 ReceivePublicationContext context(&device,
true);
430 g_ReceivePublicationContext = &context;
431 NetworkStack::setHostedReceiveHook(receivePublicationHook);
433 uint8_t packet[64] = {};
434 stack.
receive(
sizeof(packet),
reinterpret_cast<uintptr_t
>(packet), &device, 0);
435 const bool workerHeld = context.workerEntered.acquire(1, 2);
436 const size_t capacity = NetworkStack::getHostedReceiveRequestCapacity();
440 for (
size_t i = 1; i <= capacity; ++i) {
441 stack.
receive(
sizeof(packet),
reinterpret_cast<uintptr_t
>(packet), &device, 0);
444 const bool rejectedWhileHeld = context.cancellations == 1;
446 bool unregistered =
false;
450 !isRegistered(stack, &device) && !NetworkStack::getHostedRegistrationGeneration(&device);
454 context.allowDispatch.release();
455 const bool drained = stack.
drain();
457 NetworkStack::setHostedReceiveHook(
nullptr);
458 g_ReceivePublicationContext =
nullptr;
463 const size_t terminalOwnershipEvents =
static_cast<size_t>(context.delivered) +
464 static_cast<size_t>(context.staleDiscards) +
465 static_cast<size_t>(context.cancellations);
468 passed &= check(workerHeld && rejectedWhileHeld && unregistered && !context.failures, Test,
469 "the full token bank did not reject one pbuf synchronously");
470 passed &= check(drained && context.queued == capacity + 1 && context.beforeDispatch == capacity &&
471 !context.delivered && context.staleDiscards == capacity &&
472 context.cancellations == 1,
473 Test,
"the accepted/rejected burst counts were inconsistent");
475 terminalOwnershipEvents == capacity + 1 && context.everyBufferTerminatedOnce(capacity + 1),
476 Test,
"a burst pbuf did not have exactly one terminal owner");
479 NOTICE(
"HOSTED-NETWORK-TEST: PASS " << Test);
484bool queuedReceiveGenerationAba() {
486 alignas(HostedNetworkDevice) uint8_t deviceStorage[
sizeof(HostedNetworkDevice)];
487 HostedNetworkDevice* original =
new (deviceStorage) HostedNetworkDevice();
490 const size_t originalGeneration = NetworkStack::getHostedRegistrationGeneration(original);
492 passed &= check(isRegistered(stack, original) && originalGeneration,
493 "the original device was not registered");
495 ReceiveAbaContext context(original);
496 g_ReceiveAbaContext = &context;
497 NetworkStack::setHostedReceiveHook(receiveHook);
499 uint8_t packet[64] = {};
500 stack.
receive(
sizeof(packet),
reinterpret_cast<uintptr_t
>(packet), original, 0);
502 const bool workerHeld = context.workerEntered.acquire(1, 2);
503 HostedNetworkDevice* replacement =
nullptr;
504 size_t replacementGeneration = 0;
505 bool unregistered =
false;
506 bool reusedAddress =
false;
507 bool replacementRegistered =
false;
512 !isRegistered(stack, original) && !NetworkStack::getHostedRegistrationGeneration(original);
513 original->~HostedNetworkDevice();
515 replacement =
new (deviceStorage) HostedNetworkDevice();
516 reusedAddress = replacement == original;
518 replacementGeneration = NetworkStack::getHostedRegistrationGeneration(replacement);
519 replacementRegistered = isRegistered(stack, replacement) && replacementGeneration;
523 context.allowDispatch.release();
524 const bool drained = stack.
drain();
526 NetworkStack::setHostedReceiveHook(
nullptr);
527 g_ReceiveAbaContext =
nullptr;
531 replacement->~HostedNetworkDevice();
534 original->~HostedNetworkDevice();
537 const size_t terminalOwnershipEvents =
static_cast<size_t>(context.delivered) +
538 static_cast<size_t>(context.staleDiscards) +
539 static_cast<size_t>(context.cancellations);
541 passed &= check(workerHeld && drained && !context.failures,
542 "the queued receive could not be held and drained deterministically");
543 passed &= check(context.queued == 1 && context.beforeDispatch == 1 &&
544 context.generation == originalGeneration,
545 "the queued request did not retain the original registration");
546 passed &= check(unregistered && reusedAddress && replacementRegistered &&
547 replacementGeneration != originalGeneration,
548 "unregister/re-register did not create a new identity at one address");
549 passed &= check(context.staleDiscards == 1 && !context.delivered && !context.cancellations,
550 "stale queued work reached the replacement or the wrong release path");
551 passed &= check(terminalOwnershipEvents == 1,
552 "the receive buffer did not have exactly one terminal owner");
555 NOTICE(
"HOSTED-NETWORK-TEST: PASS receive-generation-aba");
561bool runHostedNetworkStackRegressions() {
562 bool passed = mailboxTryPostRejectsHardIrq();
563 passed &= deviceLeaseDeregisterDrain();
564 passed &= interruptReceivePublication();
565 passed &= boundedReceiveBurst();
566 passed &= queuedReceiveGenerationAba();
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()
virtual bool send(size_t nBytes, uintptr_t buffer)=0
virtual void getName(String &str)
static bool getInterrupts()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
void sys_mbox_free(sys_mbox_t *mbox)
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)