The Pedigree Project 0.1
usb-hcd-port-change-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/panic.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/Semaphore.h"
13#include "pedigree/kernel/process/Thread.h"
14
15#include "modules/drivers/common/usb-hcd/PortChangeRequest.h"
16#include "modules/system/usb/UsbHub.h"
17
18namespace {
19bool check(bool condition, const char* detail) {
20 if (condition) {
21 return true;
22 }
23
24 ERROR("HOSTED-WAIT-TEST: FAIL usb-hcd-port-change-publication: " << detail);
25 return false;
26}
27
28class HostedConnectionChangeHub final : public UsbHub {
29 public:
30 using Suppression = UsbHub::ConnectionChangeSuppression;
31
32 HostedConnectionChangeHub()
33 : UsbHub(),
34 replayTotal(0),
35 publicationClosing(false),
36 closedReplayNoops(0),
37 rootGenerationBase(100),
38 replayHoldPort(16),
39 replayEntered(0),
40 allowReplay(0) {
41 for (size_t i = 0; i < 16; ++i) {
42 replays[i] = 0;
43 }
44 }
45
46 explicit HostedConnectionChangeHub(Device* device)
47 : UsbHub(device),
48 replayTotal(0),
49 publicationClosing(false),
50 closedReplayNoops(0),
51 rootGenerationBase(100),
52 replayHoldPort(16),
53 replayEntered(0),
54 allowReplay(0) {
55 for (size_t i = 0; i < 16; ++i) {
56 replays[i] = 0;
57 }
58 }
59
60 void attach(UsbHub* upstream, uint8_t upstreamPort) {
61 attachToUpstreamHub(upstream, upstream->rootConnectionForChild(upstreamPort));
62 }
63
64 UsbHub* root() const {
65 return rootHub();
66 }
67
68 bool suppress(size_t port, Suppression& suppression) {
69 return suppressConnectionChanges(port, suppression);
70 }
71
72 bool defer(size_t port) {
74 }
75
76 void resetReplays() {
77 replayTotal = 0;
78 publicationClosing = false;
79 closedReplayNoops = 0;
80 for (size_t i = 0; i < 16; ++i) {
81 replays[i] = 0;
82 }
83 }
84
85 void addTransferToTransaction(uintptr_t, bool, UsbPid, uintptr_t, size_t) override {}
86
87 uintptr_t createTransaction(UsbEndpoint) override {
88 return 0;
89 }
90
91 bool doAsync(uintptr_t, void (*)(uintptr_t, ssize_t), uintptr_t) override {
92 return false;
93 }
94
95 void cancelAsyncAndDrain(uintptr_t, void (*)(uintptr_t, ssize_t), uintptr_t) override {}
96
97 bool addInterruptInHandler(UsbEndpoint, uintptr_t, uint16_t, void (*)(uintptr_t, ssize_t),
98 UsbInterruptInHandle&, uintptr_t) override {
99 return false;
100 }
101
102 bool portReset(uint8_t, bool) override {
103 return true;
104 }
105
106 Atomic<size_t> replayTotal;
107 Atomic<size_t> replays[16];
108 Atomic<bool> publicationClosing;
109 Atomic<size_t> closedReplayNoops;
110 Atomic<size_t> rootGenerationBase;
111 Atomic<size_t> replayHoldPort;
112 Semaphore replayEntered;
113 Semaphore allowReplay;
114
115 protected:
116 size_t currentRootPortGeneration(size_t port) const override {
117 return port + rootGenerationBase;
118 }
119
120 bool cancelInterruptInAndDrain(const UsbInterruptInToken&, void (*)(uintptr_t, ssize_t),
121 uintptr_t, bool) override {
122 panic("hosted port-change hub unexpectedly owned an interrupt subscription");
123 return false;
124 }
125
126 void replaySuppressedConnectionChange(size_t port) override {
127 if (publicationClosing) {
128 closedReplayNoops += 1;
129 return;
130 }
131 if (replayHoldPort == port) {
132 replayEntered.release();
133 const bool released = allowReplay.acquireForCompletion();
134 (void)released;
135 replayHoldPort = 16;
136 }
137 replayTotal += 1;
138 if (port < 16) {
139 replays[port] += 1;
140 }
141 }
142};
143
144struct ReplayOrderingContext {
145 ReplayOrderingContext(HostedConnectionChangeHub* hub, size_t port,
147 : hub(hub),
148 port(port),
149 endingSuppression(endingSuppression),
150 acquireWaitEntered(0),
151 releaseFinished(false),
152 acquisitionFinished(false),
153 acquisitionSucceeded(false) {}
154
155 HostedConnectionChangeHub* hub;
156 size_t port;
158 Semaphore acquireWaitEntered;
159 Atomic<bool> releaseFinished;
160 Atomic<bool> acquisitionFinished;
161 Atomic<bool> acquisitionSucceeded;
162};
163
164ReplayOrderingContext* g_ReplayOrderingContext = nullptr;
165
166void connectionChangeReplayWait(UsbHub* hub, size_t port) {
167 ReplayOrderingContext* context = g_ReplayOrderingContext;
168 if (context && context->hub == hub && context->port == port) {
169 context->acquireWaitEntered.release();
170 }
171}
172
173int releaseSuppressionForReplay(void* parameter) {
174 auto* context = reinterpret_cast<ReplayOrderingContext*>(parameter);
175 context->endingSuppression->reset();
176 context->releaseFinished = true;
177 return 0;
178}
179
180int acquireSuppressionBehindReplay(void* parameter) {
181 auto* context = reinterpret_cast<ReplayOrderingContext*>(parameter);
183 context->acquisitionSucceeded = context->hub->suppress(context->port, suppression);
184 context->acquisitionFinished = true;
185 return 0;
186}
187
188struct SuppressionHolderContext {
189 SuppressionHolderContext(HostedConnectionChangeHub* hub, size_t port)
190 : hub(hub), port(port), entered(0), release(0), acquired(false) {}
191
192 HostedConnectionChangeHub* hub;
193 size_t port;
194 Semaphore entered;
195 Semaphore release;
196 Atomic<bool> acquired;
197};
198
199int holdConnectionChangeSuppression(void* parameter) {
200 auto* context = reinterpret_cast<SuppressionHolderContext*>(parameter);
202 context->acquired = context->hub->suppress(context->port, suppression);
203 context->entered.release();
204 if (context->acquired) {
205 const bool released = context->release.acquireForCompletion();
206 (void)released;
207 }
208 return 0;
209}
210
211bool suppressAndReturnEarly(HostedConnectionChangeHub& hub, size_t port) {
213 if (!hub.suppress(port, suppression)) {
214 return false;
215 }
216 return hub.defer(port);
217}
218
219struct SuppressionBoundaryContext {
220 SuppressionBoundaryContext(HostedConnectionChangeHub* hub, size_t port)
221 : hub(hub),
222 port(port),
223 hookEntered(0),
224 allowCompareExchange(0),
225 hookCalls(0),
226 deferred(true) {}
227
228 HostedConnectionChangeHub* hub;
229 size_t port;
230 Semaphore hookEntered;
231 Semaphore allowCompareExchange;
232 Atomic<size_t> hookCalls;
233 Atomic<bool> deferred;
234};
235
236SuppressionBoundaryContext* g_SuppressionBoundaryContext = nullptr;
237
238void holdBeforeConnectionChangePendingCas(UsbHub* hub, size_t port, size_t observedState) {
239 SuppressionBoundaryContext* context = g_SuppressionBoundaryContext;
240 if (!context || context->hub != hub || context->port != port) {
241 return;
242 }
243
244 context->hookCalls += 1;
245 (void)observedState;
246 context->hookEntered.release();
247 const bool released = context->allowCompareExchange.acquireForCompletion();
248 (void)released;
249}
250
251int raceSuppressionFinalRelease(void* parameter) {
252 auto* context = reinterpret_cast<SuppressionBoundaryContext*>(parameter);
253 context->deferred = context->hub->defer(context->port);
254 return 0;
255}
256
257bool runConnectionChangeSuppressionRegressions() {
258 Device controllerDevice;
259 HostedConnectionChangeHub hub(&controllerDevice);
260 bool passed = true;
261
262 HostedConnectionChangeHub downstream;
263 HostedConnectionChangeHub nested;
264 downstream.attach(&hub, 5);
265 hub.rootGenerationBase = 200;
266 nested.attach(&downstream, 7);
267 const auto rootConnection = hub.rootConnectionForChild(5);
268 const auto downstreamConnection = downstream.rootConnectionForChild(9);
269 const auto nestedConnection = nested.rootConnectionForChild(11);
270 passed &= check(hub.root() == &hub && downstream.root() == &hub && nested.root() == &hub,
271 "nested hubs did not retain their root-controller association");
272 passed &= check(rootConnection.port == 5 && rootConnection.generation == 205 &&
273 downstreamConnection.port == 5 && downstreamConnection.generation == 105 &&
274 nestedConnection.port == 5 && nestedConnection.generation == 105,
275 "nested hubs did not preserve root-port generation provenance");
276
279 passed &= check(hub.suppress(0, outer) && hub.suppress(0, inner) && hub.defer(0) &&
280 hub.defer(0) && hub.defer(0),
281 "nested root-port suppression did not coalesce observations");
282 inner.reset();
283 passed &=
284 check(hub.replayTotal == 0, "an inner root-port suppression release replayed too early");
285 outer.reset();
286 passed &= check(hub.replayTotal == 1 && hub.replays[0] == 1,
287 "nested root-port suppression did not replay exactly once");
288
289 hub.resetReplays();
292 passed &= check(
293 hub.suppress(1, firstPort) && hub.suppress(2, secondPort) && hub.defer(1) && hub.defer(2),
294 "independent root-port suppression could not retain observations");
295 firstPort.reset();
296 passed &= check(hub.replays[1] == 1 && hub.replays[2] == 0,
297 "releasing one root port replayed another port");
298 secondPort.reset();
299 passed &= check(hub.replayTotal == 2 && hub.replays[2] == 1,
300 "independent root-port observations did not replay independently");
301
302 hub.resetReplays();
303 passed &= check(suppressAndReturnEarly(hub, 3) && hub.replayTotal == 1 && hub.replays[3] == 1,
304 "early return leaked a root-port suppression lease");
306 passed &= check(hub.suppress(3, reacquired),
307 "an early-return lease left its root port permanently suppressed");
308 reacquired.reset();
309
310 hub.resetReplays();
312 passed &= check(hub.suppress(4, threadOverlap),
313 "same-port overlap could not acquire its first suppression");
314 SuppressionHolderContext holderContext(&hub, 4);
315 Thread* holder =
316 new Thread(Scheduler::instance().getKernelProcess(), holdConnectionChangeSuppression,
317 &holderContext, nullptr, false, true);
318 holder->setName("hosted USB suppression holder");
319 const bool holderEntered = holderContext.entered.acquireForCompletion();
320 passed &= check(holderEntered && holderContext.acquired && hub.defer(4),
321 "two-thread same-port suppression did not overlap");
322 threadOverlap.reset();
323 passed &=
324 check(hub.replayTotal == 0, "same-port overlap replayed before the second thread released");
325 holderContext.release.release();
326 passed &= check(holder->join() && hub.replayTotal == 1 && hub.replays[4] == 1,
327 "same-port overlap did not replay at the outermost release");
328
329 hub.resetReplays();
330 HostedConnectionChangeHub::Suppression boundarySuppression;
331 const bool boundaryAcquired = hub.suppress(5, boundarySuppression);
332 SuppressionBoundaryContext boundaryContext(&hub, 5);
333 bool boundaryEntered = false;
334 bool releaseWonWithoutReplay = false;
335 bool boundaryJoined = false;
336 if (boundaryAcquired) {
337 g_SuppressionBoundaryContext = &boundaryContext;
338 UsbHub::setConnectionChangePendingHookForTest(holdBeforeConnectionChangePendingCas);
339 Thread* boundaryReader =
340 new Thread(Scheduler::instance().getKernelProcess(), raceSuppressionFinalRelease,
341 &boundaryContext, nullptr, false, true);
342 boundaryReader->setName("hosted USB suppression boundary reader");
343 boundaryEntered = boundaryContext.hookEntered.acquireForCompletion();
344 boundarySuppression.reset();
345 releaseWonWithoutReplay = hub.replayTotal == 0;
346 boundaryContext.allowCompareExchange.release();
347 boundaryJoined = boundaryReader->join();
348 UsbHub::setConnectionChangePendingHookForTest(nullptr);
349 g_SuppressionBoundaryContext = nullptr;
350 }
351 passed &=
352 check(boundaryAcquired && boundaryEntered && releaseWonWithoutReplay && boundaryJoined &&
353 boundaryContext.hookCalls == 1 && !boundaryContext.deferred && hub.replayTotal == 0,
354 "a reader behind final release acknowledged an unreplayed change");
355
356 hub.resetReplays();
358 const bool replayAcquired = hub.suppress(6, replaySuppression);
359 const bool replayDeferred = replayAcquired && hub.defer(6);
360 hub.replayHoldPort = 6;
361 ReplayOrderingContext replayContext(&hub, 6, &replaySuppression);
362 bool replayStarted = false;
363 bool acquisitionWaited = false;
364 bool replayNotOvertaken = false;
365 bool releaseJoined = false;
366 bool acquisitionJoined = false;
367 if (replayAcquired && replayDeferred) {
368 Thread* releaser =
369 new Thread(Scheduler::instance().getKernelProcess(), releaseSuppressionForReplay,
370 &replayContext, nullptr, false, true);
371 releaser->setName("hosted USB suppression replay publisher");
372 replayStarted = hub.replayEntered.acquireForCompletion();
373
374 g_ReplayOrderingContext = &replayContext;
375 UsbHub::setConnectionChangeReplayWaitHookForTest(connectionChangeReplayWait);
376 Thread* acquirer =
377 new Thread(Scheduler::instance().getKernelProcess(), acquireSuppressionBehindReplay,
378 &replayContext, nullptr, false, true);
379 acquirer->setName("hosted USB suppression replay waiter");
380 acquisitionWaited = replayContext.acquireWaitEntered.acquireForCompletion();
381 replayNotOvertaken = !replayContext.releaseFinished && !replayContext.acquisitionFinished &&
382 hub.replayTotal == 0;
383 hub.allowReplay.release();
384 releaseJoined = releaser->joinForCompletion();
385 acquisitionJoined = acquirer->joinForCompletion();
386 UsbHub::setConnectionChangeReplayWaitHookForTest(nullptr);
387 g_ReplayOrderingContext = nullptr;
388 }
389 passed &=
390 check(replayAcquired && replayDeferred && replayStarted && acquisitionWaited &&
391 replayNotOvertaken && releaseJoined && acquisitionJoined &&
392 replayContext.releaseFinished && replayContext.acquisitionFinished &&
393 replayContext.acquisitionSucceeded && hub.replayTotal == 1 && hub.replays[6] == 1,
394 "a new same-port suppression overtook retained-change replay");
395
396 hub.resetReplays();
398 const bool closingAcquired = hub.suppress(7, closingSuppression);
399 const bool closingDeferred = closingAcquired && hub.defer(7);
400 hub.publicationClosing = true;
401 closingSuppression.reset();
403 const bool reacquiredAfterClose = hub.suppress(7, afterClosedReplay);
404 afterClosedReplay.reset();
405 passed &= check(closingAcquired && closingDeferred && reacquiredAfterClose &&
406 hub.replayTotal == 0 && hub.replays[7] == 0 && hub.closedReplayNoops == 1,
407 "suppression release after publication closure did not no-op and "
408 "clear replay state");
409 hub.publicationClosing = false;
410
412 passed &= check(!hub.suppress(16, invalid) && !invalid && !hub.defer(16),
413 "out-of-range root port entered suppression state");
414
415 if (passed) {
416 NOTICE(
417 "HOSTED-WAIT-TEST: PASS "
418 "usb-hcd-port-change-suppression-state");
419 }
420 return passed;
421}
422
423class HostedUsbPortQueue final : public RequestQueue {
424 public:
425 enum Controller {
426 Ehci,
427 Ohci,
428 Uhci,
429 };
430
431 HostedUsbPortQueue()
432 : RequestQueue(MakeConstantString("Hosted USB port changes")),
433 workerEntered(0),
434 holdEntered(0),
435 releaseHold(0),
436 executions(0),
437 suppressed(0),
438 failures(0),
439 cancellations(0) {
440 seen[Ehci] = 0;
441 seen[Ohci] = 0;
442 seen[Uhci] = 0;
443 }
444
445 ~HostedUsbPortQueue() override {
446 destroy();
447 }
448
449 void setMaximum(size_t maximum) {
450 m_nMaxAsyncRequests = maximum;
451 }
452
453 Thread* workerThread() const {
454 return m_pThread;
455 }
456
457 Semaphore workerEntered;
458 Semaphore holdEntered;
459 Semaphore releaseHold;
460 Atomic<size_t> executions;
461 Atomic<size_t> suppressed;
462 Atomic<size_t> failures;
463 Atomic<size_t> cancellations;
464 Atomic<uint64_t> seen[3];
465
466 protected:
467 uint64_t executeRequest(uint64_t controller, uint64_t port, uint64_t publication,
468 uint64_t signalWorker, uint64_t holdWorker, uint64_t returnEarly,
469 uint64_t, uint64_t generation) override {
470 auto* request = reinterpret_cast<UsbHcd::PortChangeRequest*>(publication);
471 if (signalWorker) {
472 workerEntered.release();
473 }
474
475 PublicationCompletion completion(*request, generation);
476 if (!completion) {
477 suppressed += 1;
478 return 0;
479 }
480 if (holdWorker && generation == 1) {
481 holdEntered.release();
482 const bool released = releaseHold.acquireForCompletion();
483 if (!released) {
484 failures += 1;
485 }
486 }
487 if (returnEarly) {
488 return 0;
489 }
490 if (controller > Uhci || port >= 64) {
491 failures += 1;
492 } else {
493 const uint64_t bit = 1ULL << port;
494 seen[controller] |= bit;
495 }
496 executions += 1;
497 return 0;
498 }
499
500 void cancelRequest(const Request& request) override {
501 auto* publication = reinterpret_cast<UsbHcd::PortChangeRequest*>(request.p3);
502 publication->cancel(request.p8);
503 cancellations += 1;
504 }
505
506 private:
507 using PublicationCompletion = UsbHcd::PortChangeRequest::Completion;
508};
509
510struct AcknowledgeWaitContext {
511 AcknowledgeWaitContext(UsbHcd::PortChangeRequest* request, Thread* worker, size_t generation)
512 : request(request),
513 worker(worker),
514 generation(generation),
515 hookCalls(0),
516 hookFailures(0),
517 wakeBeforeBlock(0) {}
518
520 Thread* worker;
521 size_t generation;
522 Atomic<size_t> hookCalls;
523 Atomic<size_t> hookFailures;
524 Atomic<size_t> wakeBeforeBlock;
525};
526
527AcknowledgeWaitContext* g_AcknowledgeWaitContext = nullptr;
528
529void acknowledgeBeforeBlockHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
530 size_t debugState) {
531 AcknowledgeWaitContext* context = g_AcknowledgeWaitContext;
532 if (!context || thread != context->worker || channel.owner != context->request) {
533 return;
534 }
535
536 context->hookCalls += 1;
537 Thread::WaitDebugInfo wait = {};
538 uintptr_t debugAddress = 0;
539 const bool validPublication = queue && !channel.value && debugState == Thread::CallbackDrain &&
540 thread->getWaitDebugInfo(wait) && wait.queue == queue &&
541 wait.channelOwner == channel.owner &&
542 wait.channelValue == channel.value && wait.queued &&
543 wait.reason == WaitQueue::WakeReason::Waiting &&
544 thread->getDebugState(debugAddress) == Thread::CallbackDrain &&
545 debugAddress == reinterpret_cast<uintptr_t>(context->request);
546
547 // Always release the worker so a failed assertion cannot strand the
548 // RequestQueue thread inside the test.
549 context->request->acknowledge(context->generation);
550
551 Thread::WaitDebugInfo signalledWait = {};
552 if (validPublication && thread->getWaitDebugInfo(signalledWait) && signalledWait.queue == queue &&
553 signalledWait.channelOwner == channel.owner && signalledWait.channelValue == channel.value &&
554 signalledWait.queued && signalledWait.reason == WaitQueue::WakeReason::Signalled) {
555 context->wakeBeforeBlock += 1;
556 } else {
557 context->hookFailures += 1;
558 }
559}
560
561struct DestroyContext {
562 explicit DestroyContext(HostedUsbPortQueue* queue) : queue(queue), finished(0) {}
563
564 HostedUsbPortQueue* queue;
565 Atomic<size_t> finished;
566};
567
568int destroyQueue(void* parameter) {
569 auto* context = reinterpret_cast<DestroyContext*>(parameter);
570 context->queue->destroy();
571 context->finished += 1;
572 return 0;
573}
574
575bool allIdle(UsbHcd::PortChangeRequest* requests, size_t count) {
576 for (size_t i = 0; i < count; ++i) {
577 if (!requests[i].isIdle()) {
578 return false;
579 }
580 }
581 return true;
582}
583bool runPortChangePublicationRegressions() {
584 using Publication = UsbHcd::PortChangeRequest;
585 using Result = Publication::Result;
586
587 bool passed = true;
588 passed &= check(UsbHcd::EhciRootPortCount == 15 && UsbHcd::OhciRootPortCount == 15 &&
589 UsbHcd::UhciRootPortCount == 8 && UsbHcd::validEhciRootPortCount(15) &&
590 !UsbHcd::validEhciRootPortCount(16) && !UsbHcd::validOhciRootPortCount(0) &&
591 UsbHcd::validOhciRootPortCount(15) && !UsbHcd::validOhciRootPortCount(16) &&
592 UsbHcd::validUhciRootPortCount(8) && !UsbHcd::validUhciRootPortCount(9),
593 "root-port token bounds do not match controller limits");
594
595 constexpr uint16_t Status = 0x123f;
596 constexpr uint16_t ChangeMask = 0x2a;
597 constexpr uint16_t Acknowledge = 0x02;
598 const uint16_t w1c = UsbHcd::selectiveW1cValue(Status, ChangeMask, Acknowledge);
599 passed &=
600 check((w1c & ChangeMask) == Acknowledge && (w1c & ~ChangeMask) == (Status & ~ChangeMask),
601 "selective W1C echoed an unrelated change bit");
602 passed &= check(Publication::canAcknowledge(Result::Accepted) &&
603 Publication::canAcknowledge(Result::Coalesced) &&
604 !Publication::canAcknowledge(Result::TokenBusy) &&
605 !Publication::canAcknowledge(Result::QueueFull) &&
606 !Publication::canAcknowledge(Result::QueueStopped) &&
607 !Publication::canAcknowledge(Result::InvalidPriority),
608 "a rejected publication was treated as safe to ACK");
609
611 deferred.defer(0, 1);
612 deferred.defer(2, 4);
613 deferred.defer(0, 3);
614 passed &= check(
615 !deferred.empty() && deferred.release(0) == 3 && deferred.release(2) == 4 && deferred.empty(),
616 "mixed port publication passes lost a deferred generation");
617
618 HostedUsbPortQueue queue;
619 Publication recovered;
620 passed &= check(recovered.configure(queue, 0, HostedUsbPortQueue::Ehci, 0,
621 reinterpret_cast<uintptr_t>(&recovered)),
622 "port token configuration was rejected");
623 const Publication::Observation stopped = recovered.observe();
624 passed &= check(
625 stopped.result == Result::QueueStopped && stopped.generation == 1 && !recovered.isIdle(),
626 "stopped queue discarded the pending hardware observation");
627
628 queue.initialise();
629 queue.setMaximum(0);
630 const Publication::Observation reservedAdmission = recovered.observe();
631 passed &= check(reservedAdmission.result == Result::Accepted && reservedAdmission.generation == 2,
632 "preallocated port token depended on allocation admission");
633 if (Publication::canAcknowledge(reservedAdmission.result)) {
634 recovered.acknowledge(reservedAdmission.generation);
635 }
636 passed &= check(queue.drain() && recovered.isIdle() && !recovered.hasPublicationFailure(),
637 "over-capacity port token did not drain");
638 queue.setMaximum(256);
639 queue.executions = 0;
640 queue.seen[HostedUsbPortQueue::Ehci] = 0;
641
642 Publication invalid;
643 passed &= check(!invalid.configure(queue, REQUEST_QUEUE_NUM_PRIORITIES),
644 "invalid priority configured a port token");
645
646 Publication ordered;
647 passed &= check(ordered.configure(queue, 0, HostedUsbPortQueue::Ehci, 0,
648 reinterpret_cast<uintptr_t>(&ordered), 1, 1),
649 "ordered port token configuration was rejected");
650
651 AcknowledgeWaitContext acknowledgeWait(&ordered, queue.workerThread(), 1);
652 g_AcknowledgeWaitContext = &acknowledgeWait;
653 WaitQueue::setBeforeBlockHook(acknowledgeBeforeBlockHook);
654 const Publication::Observation first = ordered.observe();
655 const bool workerEntered = first.result == Result::Accepted && queue.workerEntered.acquire();
656 constexpr size_t AcknowledgeWaitAttempts = 10000;
657 for (size_t i = 0; i < AcknowledgeWaitAttempts && !acknowledgeWait.wakeBeforeBlock &&
658 !acknowledgeWait.hookFailures;
659 ++i) {
661 }
662 if (!acknowledgeWait.wakeBeforeBlock) {
663 ordered.acknowledge(first.generation);
664 }
665 WaitQueue::setBeforeBlockHook(nullptr);
666 g_AcknowledgeWaitContext = nullptr;
667
668 passed &= check(workerEntered, "worker did not reach the pre-ACK publication barrier");
669 const bool waitQueueAcknowledgementPassed =
670 first.generation == 1 && acknowledgeWait.hookCalls == 1 &&
671 acknowledgeWait.hookFailures == 0 && acknowledgeWait.wakeBeforeBlock == 1;
672 passed &= check(waitQueueAcknowledgementPassed,
673 "hardware ACK did not signal the published pre-block waiter");
674 if (waitQueueAcknowledgementPassed) {
675 NOTICE("HOSTED-WAIT-TEST: PASS usb-hcd-port-change-waitqueue-ack");
676 }
677 passed &=
678 check(queue.holdEntered.acquire(), "acknowledged worker did not reach the execution hold");
679 const Publication::Observation second = ordered.observe();
680 const Publication::Observation third = ordered.observe();
681 passed &= check(second.result == Result::Coalesced && third.result == Result::Coalesced &&
682 queue.executions == 0,
683 "active port observations did not coalesce behind the worker");
684 queue.releaseHold.release();
685 passed &= check(queue.workerEntered.acquire() && queue.executions == 1,
686 "follow-up worker crossed a deferred controller ACK");
687 ordered.acknowledge(third.generation);
688 passed &= check(queue.drain() && ordered.isIdle() && queue.executions == 2 &&
689 !ordered.hasPublicationFailure(),
690 "latest coalesced generation was not consumed exactly once");
691 queue.seen[HostedUsbPortQueue::Ehci] = 0;
692 queue.executions = 0;
693
694 Publication earlyReturn;
695 passed &= check(earlyReturn.configure(queue, 0, HostedUsbPortQueue::Uhci, 0,
696 reinterpret_cast<uintptr_t>(&earlyReturn), 0, 0, 1),
697 "early-return port token configuration was rejected");
698 const Publication::Observation early = earlyReturn.observe();
699 passed &= check(early.result == Result::Accepted, "early-return publication was rejected");
700 earlyReturn.acknowledge(early.generation);
701 passed &=
702 check(queue.drain() && earlyReturn.isIdle(), "early worker return retained its port token");
703
704 Publication ehci[UsbHcd::EhciRootPortCount];
705 Publication ohci[UsbHcd::OhciRootPortCount];
706 Publication uhci[UsbHcd::UhciRootPortCount];
707
708 for (size_t i = 0; i < UsbHcd::EhciRootPortCount; ++i) {
709 passed &= check(ehci[i].configure(queue, 0, HostedUsbPortQueue::Ehci, i,
710 reinterpret_cast<uintptr_t>(&ehci[i])),
711 "EHCI token configuration was rejected");
712 const Publication::Observation observation = ehci[i].observe();
713 passed &= check(observation.result == Result::Accepted, "EHCI token was rejected");
714 if (Publication::canAcknowledge(observation.result)) {
715 ehci[i].acknowledge(observation.generation);
716 }
717 }
718 for (size_t i = 0; i < UsbHcd::OhciRootPortCount; ++i) {
719 passed &= check(ohci[i].configure(queue, 0, HostedUsbPortQueue::Ohci, i,
720 reinterpret_cast<uintptr_t>(&ohci[i])),
721 "OHCI token configuration was rejected");
722 const Publication::Observation observation = ohci[i].observe();
723 passed &= check(observation.result == Result::Accepted, "OHCI token was rejected");
724 if (Publication::canAcknowledge(observation.result)) {
725 ohci[i].acknowledge(observation.generation);
726 }
727 }
728 for (size_t i = 0; i < UsbHcd::UhciRootPortCount; ++i) {
729 passed &= check(uhci[i].configure(queue, 0, HostedUsbPortQueue::Uhci, i,
730 reinterpret_cast<uintptr_t>(&uhci[i])),
731 "UHCI token configuration was rejected");
732 const Publication::Observation observation = uhci[i].observe();
733 passed &= check(observation.result == Result::Accepted, "UHCI token was rejected");
734 if (Publication::canAcknowledge(observation.result)) {
735 uhci[i].acknowledge(observation.generation);
736 }
737 }
738
739 const uint64_t ehciMask = (1ULL << UsbHcd::EhciRootPortCount) - 1;
740 const uint64_t ohciMask = (1ULL << UsbHcd::OhciRootPortCount) - 1;
741 const uint64_t uhciMask = (1ULL << UsbHcd::UhciRootPortCount) - 1;
742 passed &= check(queue.drain() && queue.seen[HostedUsbPortQueue::Ehci] == ehciMask &&
743 queue.seen[HostedUsbPortQueue::Ohci] == ohciMask &&
744 queue.seen[HostedUsbPortQueue::Uhci] == uhciMask && queue.failures == 0 &&
745 queue.executions == UsbHcd::EhciRootPortCount + UsbHcd::OhciRootPortCount +
746 UsbHcd::UhciRootPortCount &&
747 allIdle(ehci, UsbHcd::EhciRootPortCount) &&
748 allIdle(ohci, UsbHcd::OhciRootPortCount) &&
749 allIdle(uhci, UsbHcd::UhciRootPortCount),
750 "controller port tokens did not map and complete exactly once");
751 queue.destroy();
752
753 HostedUsbPortQueue stopQueue;
754 stopQueue.initialise();
755 Publication unacknowledged;
756 passed &= check(unacknowledged.configure(stopQueue, 0, HostedUsbPortQueue::Ehci, 0,
757 reinterpret_cast<uintptr_t>(&unacknowledged), 1),
758 "stop-wait port token configuration was rejected");
759 const Publication::Observation waiting = unacknowledged.observe();
760 const bool stopWorkerEntered =
761 waiting.result == Result::Accepted && stopQueue.workerEntered.acquire();
762 bool stopWaitPublished = false;
763 for (size_t i = 0; i < 10000 && !stopWaitPublished; ++i) {
764 Thread::WaitDebugInfo wait = {};
765 uintptr_t debugAddress = 0;
766 stopWaitPublished =
767 stopQueue.workerThread()->getWaitDebugInfo(wait) && wait.queue &&
768 wait.channelOwner == &unacknowledged && !wait.channelValue && wait.queued &&
769 wait.reason == WaitQueue::WakeReason::Waiting &&
770 stopQueue.workerThread()->getDebugState(debugAddress) == Thread::CallbackDrain &&
771 debugAddress == reinterpret_cast<uintptr_t>(&unacknowledged);
772 if (!stopWaitPublished) {
774 }
775 }
776 passed &= check(stopWorkerEntered && stopWaitPublished,
777 "worker did not enter its unacknowledged generation wait");
778 const Publication::Observation stopFollowUp = unacknowledged.observe();
779 const bool stopFollowUpRetained =
780 stopFollowUp.result == Result::Coalesced && stopFollowUp.generation == 2;
781 passed &=
782 check(stopFollowUpRetained, "stop-wait port token did not retain its pending follow-up");
783 unacknowledged.stopAfterQuiesce();
784 const bool stopWakePassed = stopQueue.drain() && unacknowledged.isIdle() &&
785 stopQueue.suppressed == 1 && !stopQueue.workerEntered.tryAcquire() &&
786 !unacknowledged.hasPublicationFailure();
787 passed &= check(stopWakePassed, "stop did not release an unacknowledged active worker");
788 if (stopWorkerEntered && stopWaitPublished && stopFollowUpRetained && stopWakePassed) {
789 NOTICE("HOSTED-WAIT-TEST: PASS usb-hcd-port-change-waitqueue-stop");
790 NOTICE(
791 "HOSTED-WAIT-TEST: PASS "
792 "usb-hcd-port-change-stop-suppresses-republish");
793 }
794 stopQueue.destroy();
795
796 HostedUsbPortQueue cancellationQueue;
797 cancellationQueue.initialise();
798 Publication blocker;
799 Publication cancelled;
800 passed &= check(blocker.configure(cancellationQueue, 0, HostedUsbPortQueue::Ehci, 0,
801 reinterpret_cast<uintptr_t>(&blocker), 0, 1) &&
802 cancelled.configure(cancellationQueue, 0, HostedUsbPortQueue::Ohci, 0,
803 reinterpret_cast<uintptr_t>(&cancelled)),
804 "cancellation port token configuration was rejected");
805 const Publication::Observation blocking = blocker.observe();
806 passed &= check(blocking.result == Result::Accepted, "cancellation blocker was rejected");
807 blocker.acknowledge(blocking.generation);
808 passed &= check(cancellationQueue.holdEntered.acquire(),
809 "cancellation blocker did not enter the worker");
810 const Publication::Observation cancellation = cancelled.observe();
811 passed &= check(cancellation.result == Result::Accepted, "cancellation target was rejected");
812 cancelled.acknowledge(cancellation.generation);
813 const Publication::Observation cancellationFollowUp = cancelled.observe();
814 passed &= check(cancellationFollowUp.result == Result::Coalesced,
815 "cancellation target did not retain a coalesced follow-up");
816
817 blocker.stopAfterQuiesce();
818 cancelled.stopAfterQuiesce();
819 DestroyContext destroyContext(&cancellationQueue);
820 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyQueue,
821 &destroyContext, nullptr, false, true);
822 destroyer->setName("hosted USB port-change destroy regression");
823 while (cancellationQueue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
825 }
826 cancellationQueue.releaseHold.release();
827 passed &= check(destroyer->join() && destroyContext.finished == 1 && blocker.isIdle() &&
828 cancelled.isIdle() && cancellationQueue.cancellations == 1 &&
829 !cancelled.hasPublicationFailure(),
830 "destroy did not cancel and release a queued port token");
831
832 if (passed) {
833 NOTICE("HOSTED-WAIT-TEST: PASS usb-hcd-port-change-publication");
834 }
835 return passed;
836}
837} // namespace
838
839bool runHostedUsbHcdPortChangeRegressions() {
840 // Keep these two stack-heavy fixtures sequential. Nesting the suppression
841 // fixture under the publication fixture exhausts the bounded kernel stack
842 // in ASan hosted builds before either fixture reports its result.
843 const bool suppressionPassed = runConnectionChangeSuppressionRegressions();
844 const bool publicationPassed = runPortChangePublicationRegressions();
845 return suppressionPassed && publicationPassed;
846}
static Device & root()
Definition Device.h:327
Definition Ehci.h:57
Definition Ohci.h:54
virtual void destroy()
virtual void cancelRequest(const Request &request)
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)=0
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void release(size_t n=1)
Definition Semaphore.cc:546
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:369
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
bool join()
Definition Thread.cc:2767
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
Definition Uhci.h:54
virtual void addTransferToTransaction(uintptr_t pTransaction, bool bToggle, UsbPid pid, uintptr_t pBuffer, size_t nBytes)=0
Adds a new transfer to an existent transaction.
virtual void replaySuppressedConnectionChange(size_t port)
Definition UsbHub.cc:370
virtual uintptr_t createTransaction(UsbEndpoint endpointInfo)=0
Creates a new transaction with the given endpoint data.
virtual bool portReset(uint8_t nPort, bool bErrorResponse=false)=0
Gets a UsbDevice from a given vendor:product pair.
MUST_USE_RESULT bool suppressConnectionChanges(size_t port, ConnectionChangeSuppression &suppression)
Definition UsbHub.cc:263
virtual void cancelAsyncAndDrain(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t), uintptr_t pParam)=0
virtual size_t currentRootPortGeneration(size_t port) const
Definition UsbHub.h:377
virtual MUST_USE_RESULT bool addInterruptInHandler(UsbEndpoint endpointInfo, uintptr_t pBuffer, uint16_t nBytes, void(*pCallback)(uintptr_t, ssize_t), UsbInterruptInHandle &handle, uintptr_t pParam=0)=0
Adds an owned recurring interrupt-IN transaction.
UsbHub * rootHub() const
Definition UsbHub.h:359
RootConnection rootConnectionForChild(uint8_t childPort) const
Definition UsbHub.cc:200
virtual MUST_USE_RESULT bool doAsync(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t)=0, uintptr_t pParam=0)=0
MUST_USE_RESULT bool deferConnectionChangeIfSuppressed(size_t port)
Definition UsbHub.cc:302
void attachToUpstreamHub(UsbHub *upstream, const RootConnection &connection)
Definition UsbHub.cc:249
virtual MUST_USE_RESULT bool cancelInterruptInAndDrain(const UsbInterruptInToken &token, void(*callback)(uintptr_t, ssize_t), uintptr_t parameter, bool producerAlreadyStopped)=0
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117