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"
15#include "modules/drivers/common/usb-hcd/PortChangeRequest.h"
16#include "modules/system/usb/UsbHub.h"
19bool check(
bool condition,
const char* detail) {
24 ERROR(
"HOSTED-WAIT-TEST: FAIL usb-hcd-port-change-publication: " << detail);
28class HostedConnectionChangeHub final :
public UsbHub {
32 HostedConnectionChangeHub()
35 publicationClosing(false),
37 rootGenerationBase(100),
41 for (
size_t i = 0; i < 16; ++i) {
46 explicit HostedConnectionChangeHub(
Device* device)
49 publicationClosing(false),
51 rootGenerationBase(100),
55 for (
size_t i = 0; i < 16; ++i) {
60 void attach(
UsbHub* upstream, uint8_t upstreamPort) {
68 bool suppress(
size_t port, Suppression& suppression) {
72 bool defer(
size_t port) {
78 publicationClosing =
false;
79 closedReplayNoops = 0;
80 for (
size_t i = 0; i < 16; ++i) {
91 bool doAsync(uintptr_t,
void (*)(uintptr_t, ssize_t), uintptr_t)
override {
117 return port + rootGenerationBase;
121 uintptr_t,
bool)
override {
122 panic(
"hosted port-change hub unexpectedly owned an interrupt subscription");
127 if (publicationClosing) {
128 closedReplayNoops += 1;
131 if (replayHoldPort == port) {
144struct ReplayOrderingContext {
145 ReplayOrderingContext(HostedConnectionChangeHub* hub,
size_t port,
149 endingSuppression(endingSuppression),
150 acquireWaitEntered(0),
151 releaseFinished(false),
152 acquisitionFinished(false),
153 acquisitionSucceeded(false) {}
155 HostedConnectionChangeHub* hub;
164ReplayOrderingContext* g_ReplayOrderingContext =
nullptr;
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();
173int releaseSuppressionForReplay(
void* parameter) {
174 auto* context =
reinterpret_cast<ReplayOrderingContext*
>(parameter);
175 context->endingSuppression->reset();
176 context->releaseFinished =
true;
180int acquireSuppressionBehindReplay(
void* parameter) {
181 auto* context =
reinterpret_cast<ReplayOrderingContext*
>(parameter);
183 context->acquisitionSucceeded = context->hub->suppress(context->port, suppression);
184 context->acquisitionFinished =
true;
188struct SuppressionHolderContext {
189 SuppressionHolderContext(HostedConnectionChangeHub* hub,
size_t port)
190 : hub(hub), port(port), entered(0), release(0), acquired(false) {}
192 HostedConnectionChangeHub* hub;
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();
211bool suppressAndReturnEarly(HostedConnectionChangeHub& hub,
size_t port) {
213 if (!hub.suppress(port, suppression)) {
216 return hub.defer(port);
219struct SuppressionBoundaryContext {
220 SuppressionBoundaryContext(HostedConnectionChangeHub* hub,
size_t port)
224 allowCompareExchange(0),
228 HostedConnectionChangeHub* hub;
236SuppressionBoundaryContext* g_SuppressionBoundaryContext =
nullptr;
238void holdBeforeConnectionChangePendingCas(
UsbHub* hub,
size_t port,
size_t observedState) {
239 SuppressionBoundaryContext* context = g_SuppressionBoundaryContext;
240 if (!context || context->hub != hub || context->port != port) {
244 context->hookCalls += 1;
246 context->hookEntered.release();
247 const bool released = context->allowCompareExchange.acquireForCompletion();
251int raceSuppressionFinalRelease(
void* parameter) {
252 auto* context =
reinterpret_cast<SuppressionBoundaryContext*
>(parameter);
253 context->deferred = context->hub->defer(context->port);
257bool runConnectionChangeSuppressionRegressions() {
259 HostedConnectionChangeHub hub(&controllerDevice);
262 HostedConnectionChangeHub downstream;
263 HostedConnectionChangeHub nested;
264 downstream.attach(&hub, 5);
265 hub.rootGenerationBase = 200;
266 nested.attach(&downstream, 7);
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");
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");
284 check(hub.replayTotal == 0,
"an inner root-port suppression release replayed too early");
286 passed &= check(hub.replayTotal == 1 && hub.replays[0] == 1,
287 "nested root-port suppression did not replay exactly once");
293 hub.suppress(1, firstPort) && hub.suppress(2, secondPort) && hub.defer(1) && hub.defer(2),
294 "independent root-port suppression could not retain observations");
296 passed &= check(hub.replays[1] == 1 && hub.replays[2] == 0,
297 "releasing one root port replayed another port");
299 passed &= check(hub.replayTotal == 2 && hub.replays[2] == 1,
300 "independent root-port observations did not replay independently");
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");
312 passed &= check(hub.suppress(4, threadOverlap),
313 "same-port overlap could not acquire its first suppression");
314 SuppressionHolderContext holderContext(&hub, 4);
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();
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");
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);
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;
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");
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) {
370 &replayContext,
nullptr,
false,
true);
371 releaser->setName(
"hosted USB suppression replay publisher");
372 replayStarted = hub.replayEntered.acquireForCompletion();
374 g_ReplayOrderingContext = &replayContext;
375 UsbHub::setConnectionChangeReplayWaitHookForTest(connectionChangeReplayWait);
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();
386 UsbHub::setConnectionChangeReplayWaitHookForTest(
nullptr);
387 g_ReplayOrderingContext =
nullptr;
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");
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;
412 passed &= check(!hub.suppress(16, invalid) && !invalid && !hub.defer(16),
413 "out-of-range root port entered suppression state");
417 "HOSTED-WAIT-TEST: PASS "
418 "usb-hcd-port-change-suppression-state");
432 :
RequestQueue(MakeConstantString(
"Hosted USB port changes")),
445 ~HostedUsbPortQueue()
override {
449 void setMaximum(
size_t maximum) {
450 m_nMaxAsyncRequests = maximum;
453 Thread* workerThread()
const {
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 {
475 PublicationCompletion completion(*request, generation);
480 if (holdWorker && generation == 1) {
490 if (controller >
Uhci || port >= 64) {
493 const uint64_t bit = 1ULL << port;
494 seen[controller] |= bit;
502 publication->cancel(request.p8);
510struct AcknowledgeWaitContext {
514 generation(generation),
517 wakeBeforeBlock(0) {}
527AcknowledgeWaitContext* g_AcknowledgeWaitContext =
nullptr;
531 AcknowledgeWaitContext* context = g_AcknowledgeWaitContext;
532 if (!context || thread != context->worker || channel.owner != context->request) {
536 context->hookCalls += 1;
538 uintptr_t debugAddress = 0;
539 const bool validPublication = queue && !channel.value && debugState == Thread::CallbackDrain &&
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);
549 context->request->acknowledge(context->generation);
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;
557 context->hookFailures += 1;
561struct DestroyContext {
562 explicit DestroyContext(HostedUsbPortQueue* queue) : queue(queue), finished(0) {}
564 HostedUsbPortQueue* queue;
568int destroyQueue(
void* parameter) {
569 auto* context =
reinterpret_cast<DestroyContext*
>(parameter);
570 context->queue->destroy();
571 context->finished += 1;
576 for (
size_t i = 0; i < count; ++i) {
577 if (!requests[i].isIdle()) {
583bool runPortChangePublicationRegressions() {
585 using Result = Publication::Result;
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");
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);
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");
611 deferred.defer(0, 1);
612 deferred.defer(2, 4);
613 deferred.defer(0, 3);
615 !deferred.empty() && deferred.release(0) == 3 && deferred.release(2) == 4 && deferred.empty(),
616 "mixed port publication passes lost a deferred generation");
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();
625 stopped.result == Result::QueueStopped && stopped.generation == 1 && !recovered.isIdle(),
626 "stopped queue discarded the pending hardware observation");
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);
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;
643 passed &= check(!invalid.configure(queue, REQUEST_QUEUE_NUM_PRIORITIES),
644 "invalid priority configured a port token");
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");
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;
662 if (!acknowledgeWait.wakeBeforeBlock) {
663 ordered.acknowledge(first.generation);
665 WaitQueue::setBeforeBlockHook(
nullptr);
666 g_AcknowledgeWaitContext =
nullptr;
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");
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;
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);
702 check(queue.drain() && earlyReturn.isIdle(),
"early worker return retained its port token");
704 Publication ehci[UsbHcd::EhciRootPortCount];
705 Publication ohci[UsbHcd::OhciRootPortCount];
706 Publication uhci[UsbHcd::UhciRootPortCount];
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);
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);
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);
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");
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) {
765 uintptr_t debugAddress = 0;
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) {
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;
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");
791 "HOSTED-WAIT-TEST: PASS "
792 "usb-hcd-port-change-stop-suppresses-republish");
796 HostedUsbPortQueue cancellationQueue;
797 cancellationQueue.initialise();
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");
817 blocker.stopAfterQuiesce();
818 cancelled.stopAfterQuiesce();
819 DestroyContext destroyContext(&cancellationQueue);
821 &destroyContext,
nullptr,
false,
true);
822 destroyer->setName(
"hosted USB port-change destroy regression");
823 while (cancellationQueue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
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");
833 NOTICE(
"HOSTED-WAIT-TEST: PASS usb-hcd-port-change-publication");
839bool runHostedUsbHcdPortChangeRegressions() {
843 const bool suppressionPassed = runConnectionChangeSuppressionRegressions();
844 const bool publicationPassed = runPortChangePublicationRegressions();
845 return suppressionPassed && publicationPassed;
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()
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
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)
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)
virtual void cancelAsyncAndDrain(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t), uintptr_t pParam)=0
virtual size_t currentRootPortGeneration(size_t port) const
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.
RootConnection rootConnectionForChild(uint8_t childPort) const
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)
void attachToUpstreamHub(UsbHub *upstream, const RootConnection &connection)
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