8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/process/Mutex.h"
12#include "pedigree/kernel/process/OperationBarrier.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/Semaphore.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/utilities/List.h"
17#include "pedigree/kernel/utilities/utility.h"
19#include "modules/drivers/common/usb-hcd/CallbackDelivery.h"
20#include "modules/drivers/common/usb-hcd/TransferCompletion.h"
21#include "modules/system/usb/Usb.h"
27bool check(
bool condition,
const char* test,
const char* detail) {
31 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
35class TransferLifecycleModel {
37 static constexpr size_t SlotCount = 2;
40 SlotCleanup() : controller(nullptr), slot(0) {}
42 TransferLifecycleModel* controller;
48 : completion(), accepted(), linked(false), dmaOwned(false), reclaimed(false), cleanup() {}
58 TransferLifecycleModel()
68 for (
size_t i = 0; i < SlotCount; ++i) {
69 m_Slots[i].cleanup.controller =
this;
70 m_Slots[i].cleanup.slot = i;
74 ~TransferLifecycleModel() {
78 bool submit(
size_t slotIndex, Completion::Callback callback, uintptr_t parameter,
81 if (!m_Submissions.tryAcquire(submission))
86 commitEntered->release();
88 const bool allowed = allowCommit->acquireForCompletion();
92 if (m_Closing || slotIndex >= SlotCount)
95 Slot& slot = m_Slots[slotIndex];
96 if (slot.completion.state() != Completion::State::Idle)
100 if (!m_Accepted.tryAcquire(accepted))
105 slot.accepted = pedigree_std::move(accepted);
107 slot.dmaOwned =
true;
108 slot.reclaimed =
false;
109 slot.completion.arm(callback, parameter, m_Deliveries.nextGeneration());
113 bool captureNatural(
size_t slotIndex, ssize_t result) {
115 if (slotIndex >= SlotCount)
117 return m_Slots[slotIndex].completion.captureNatural(result);
120 bool cancel(
size_t slotIndex, Completion::Callback callback, uintptr_t parameter) {
122 if (!m_Cancellations.tryAcquire(cancellation))
125 DeliveryQueue::Record* record =
nullptr;
126 DeliveryQueue::Key drainKey = {0, 0};
130 if (slotIndex >= SlotCount)
133 Slot& slot = m_Slots[slotIndex];
134 Completion::Claim claim;
135 const Completion::CancellationDisposition disposition =
136 slot.completion.claimCancellation(callback, parameter, -TransactionError, claim);
137 if (disposition == Completion::CancellationDisposition::NoMatch)
139 if (disposition == Completion::CancellationDisposition::DrainPublished) {
140 drainKey = {slotIndex, claim.generation};
143 releaseDmaLocked(slot);
144 record = createRecordLocked(slotIndex, claim);
147 m_Deliveries.publish(batch);
152 m_Deliveries.deliver(record);
154 (void)m_Deliveries.drain(drainKey);
159 m_Submissions.closeAndWait();
166 for (
size_t i = 0; i < SlotCount; ++i) {
167 Slot& slot = m_Slots[i];
168 Completion::Claim claim;
169 if (!slot.completion.claimForTeardown(-TransactionError, claim)) {
173 releaseDmaLocked(slot);
174 batch.
pushBack(createRecordLocked(i, claim));
177 m_Deliveries.publish(batch);
180 while (batch.
count())
181 m_Deliveries.deliver(batch.
popFront());
184 (void)m_Deliveries.drainAll();
185 m_Cancellations.closeAndWait();
186 const bool passed = m_Deliveries.empty() && m_Accepted.isClosedAndDrained() && !m_DmaViolations;
191 bool linked(
size_t slotIndex) {
193 return slotIndex < SlotCount && m_Slots[slotIndex].linked;
196 bool reclaimed(
size_t slotIndex) {
198 return slotIndex < SlotCount && m_Slots[slotIndex].reclaimed;
202 static void reclaim(
void* parameter) {
203 auto* cleanup =
reinterpret_cast<SlotCleanup*
>(parameter);
204 TransferLifecycleModel* controller = cleanup->controller;
206 Slot& slot = controller->m_Slots[cleanup->slot];
208 controller->m_DmaViolations += 1;
210 slot.reclaimed =
true;
214 void releaseDmaLocked(Slot& slot) {
216 slot.dmaOwned =
false;
219 DeliveryQueue::Record* createRecordLocked(
size_t slotIndex,
const Completion::Claim& claim) {
220 Slot& slot = m_Slots[slotIndex];
221 assert(!slot.dmaOwned);
222 return m_Deliveries.create({slotIndex, claim.generation}, claim.callback, claim.parameter,
223 claim.result, reclaim, &slot.cleanup);
230 DeliveryQueue m_Deliveries;
231 Slot m_Slots[SlotCount];
237struct ResultContext {
238 ResultContext() : calls(0), result(0) {}
244void recordResult(uintptr_t parameter, ssize_t result) {
245 auto* context =
reinterpret_cast<ResultContext*
>(parameter);
246 context->result = result;
250struct SubmissionRaceContext {
251 SubmissionRaceContext(TransferLifecycleModel* controller)
252 : controller(controller),
259 TransferLifecycleModel* controller;
262 ResultContext callback;
267int submitPausedTransfer(
void* parameter) {
268 auto* context =
reinterpret_cast<SubmissionRaceContext*
>(parameter);
269 if (context->controller->submit(0, recordResult,
reinterpret_cast<uintptr_t
>(&context->callback),
270 &context->commitEntered, &context->allowCommit)) {
271 context->submitted += 1;
276int cancelPausedTransfer(
void* parameter) {
277 auto* context =
reinterpret_cast<SubmissionRaceContext*
>(parameter);
278 if (context->controller->cancel(0, recordResult,
279 reinterpret_cast<uintptr_t
>(&context->callback))) {
280 context->cancelled += 1;
285bool waitUntilSleeping(
Thread* thread) {
286 for (
size_t attempt = 0; attempt < 10000; ++attempt) {
287 if (thread->
getStatus() == Thread::Sleeping)
294bool cancellationCannotObserveHalfPublishedTransfer() {
295 TransferLifecycleModel controller;
296 SubmissionRaceContext context(&controller);
299 new Thread(kernelProcess, submitPausedTransfer, &context,
nullptr,
false,
true);
300 const bool commitEntered = context.commitEntered.acquireForCompletion();
302 new Thread(kernelProcess, cancelPausedTransfer, &context,
nullptr,
false,
true);
303 const bool cancellationBlocked = waitUntilSleeping(cancellation);
304 const bool callbackRanEarly =
static_cast<size_t>(context.callback.calls) != 0;
306 context.allowCommit.release();
309 const bool shutdown = controller.shutdown();
312 check(commitEntered && cancellationBlocked && !callbackRanEarly && submissionJoined &&
313 cancellationJoined && context.submitted == 1 && context.cancelled == 1 &&
314 context.callback.calls == 1 && context.callback.result == -TransactionError &&
315 controller.reclaimed(0) && !controller.linked(0) && shutdown,
316 "usb-transfer-atomic-publication",
317 "cancellation observed Active before the transfer was fully linked");
319 NOTICE(
"HOSTED-WAIT-TEST: PASS usb-transfer-atomic-publication");
323bool teardownPreservesNaturalAndFailsActive() {
324 TransferLifecycleModel controller;
325 ResultContext natural;
326 ResultContext active;
327 const bool naturalSubmitted =
328 controller.submit(0, recordResult,
reinterpret_cast<uintptr_t
>(&natural));
329 const bool activeSubmitted =
330 controller.submit(1, recordResult,
reinterpret_cast<uintptr_t
>(&active));
331 const bool captured = controller.captureNatural(0, 2718);
332 const bool shutdown = controller.shutdown();
335 check(naturalSubmitted && activeSubmitted && captured && shutdown && natural.calls == 1 &&
336 natural.result == 2718 && active.calls == 1 && active.result == -TransactionError &&
337 controller.reclaimed(0) && controller.reclaimed(1),
338 "usb-transfer-teardown-results",
339 "teardown lost a natural result or stranded an active obligation");
341 NOTICE(
"HOSTED-WAIT-TEST: PASS usb-transfer-teardown-results");
345struct NestedCancelContext {
346 NestedCancelContext(TransferLifecycleModel* controller, ResultContext* target)
347 : controller(controller), target(target), calls(0), cancelReturned(0) {}
349 TransferLifecycleModel* controller;
350 ResultContext* target;
355void cancelSecondFromFirst(uintptr_t parameter, ssize_t result) {
356 auto* context =
reinterpret_cast<NestedCancelContext*
>(parameter);
357 if (result != -TransactionError)
360 if (context->controller->cancel(1, recordResult,
reinterpret_cast<uintptr_t
>(context->target))) {
361 context->cancelReturned += 1;
365bool teardownBatchSupportsNestedCancellation() {
366 TransferLifecycleModel controller;
367 ResultContext second;
368 NestedCancelContext first(&controller, &second);
369 const bool firstSubmitted =
370 controller.submit(0, cancelSecondFromFirst,
reinterpret_cast<uintptr_t
>(&first));
371 const bool secondSubmitted =
372 controller.submit(1, recordResult,
reinterpret_cast<uintptr_t
>(&second));
373 const bool shutdown = controller.shutdown();
375 const bool passed = check(firstSubmitted && secondSubmitted && shutdown && first.calls == 1 &&
376 first.cancelReturned == 1 && second.calls == 1 &&
377 second.result == -TransactionError && controller.reclaimed(0) &&
378 controller.reclaimed(1),
379 "usb-transfer-teardown-nested-cancel",
380 "a teardown callback could not synchronously drain a later callback");
382 NOTICE(
"HOSTED-WAIT-TEST: PASS usb-transfer-teardown-nested-cancel");
387bool runHostedUsbTransferLifecycleRegressions() {
388 return cancellationCannotObserveHalfPublishedTransfer() &&
389 teardownPreservesNaturalAndFailsActive() && teardownBatchSupportsNestedCancellation();
static Scheduler & instance()
void pushBack(const T &value)