The Pedigree Project 0.1
usb-bot-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/Log.h"
9#include "pedigree/kernel/compiler.h"
10#include "pedigree/kernel/utilities/PointerGuard.h"
11#include "pedigree/kernel/utilities/utility.h"
12
13#include "modules/drivers/common/usb-mass-storage/UsbMassStorageDevice.h"
14#include "modules/system/usb/UsbConstants.h"
15#include "modules/system/usb/UsbDescriptors.h"
16#include "modules/system/usb/UsbHub.h"
17
19 public:
20 static void bind(UsbMassStorageDevice& device, UsbDevice::Endpoint* in,
22 device.m_nUnits = 4;
23 device.m_pInEndpoint = in;
24 device.m_pOutEndpoint = out;
25 }
26
27 static void setUnits(UsbMassStorageDevice& device, size_t units) {
28 device.m_nUnits = units;
29 }
30};
31
32namespace {
33constexpr size_t DataBytes = 512;
34constexpr size_t MaxTransactions = 64;
35constexpr size_t MaxTransfers = 8;
36constexpr size_t MaxCbws = 16;
37constexpr int AnyToggle = -1;
38constexpr uint32_t CbwSignature = HOST_TO_LITTLE32(0x43425355);
39constexpr uint32_t CswSignature = HOST_TO_LITTLE32(0x53425355);
40
41constexpr uint8_t WriteCdb[10] = {0x2a, 0, 0, 0, 0, 8, 0, 0, 1, 0};
42
43struct WireCbw {
44 uint32_t signature;
45 uint32_t tag;
46 uint32_t dataBytes;
47 uint8_t flags;
48 uint8_t lun;
49 uint8_t commandSize;
50 uint8_t command[16];
51} PACKED;
52
53struct WireCsw {
54 uint32_t signature;
55 uint32_t tag;
56 uint32_t residue;
57 uint8_t status;
58} PACKED;
59
60static_assert(sizeof(WireCbw) == 31, "BOT CBW wire size changed");
61static_assert(sizeof(WireCsw) == 13, "BOT CSW wire size changed");
62
63enum class StepKind { Cbw, DataOut, DataIn, Csw, Reset, ClearIn, ClearOut };
64enum class TagReply { Match, Wrong };
65
66struct Step {
67 Step()
68 : kind(StepKind::Cbw),
69 result(0),
70 firstToggle(AnyToggle),
71 expectedData(nullptr),
72 expectedBytes(0),
73 lun(0),
74 commandSize(0),
75 command(),
76 signature(CswSignature),
77 tagReply(TagReply::Match),
78 residue(0),
79 status(0) {}
80
81 bool writing = true;
82 StepKind kind;
83 ssize_t result;
84 int firstToggle;
85 const uint8_t* expectedData;
86 size_t expectedBytes;
87 uint8_t lun;
88 uint8_t commandSize;
89 uint8_t command[16];
90 uint32_t signature;
91 TagReply tagReply;
92 uint32_t residue;
93 uint8_t status;
94};
95
96struct RecordedTransfer {
97 RecordedTransfer() : toggle(false), pid(UsbPidOut), buffer(0), bytes(0) {}
98
99 bool toggle;
100 UsbPid pid;
101 uintptr_t buffer;
102 size_t bytes;
103};
104
105struct RecordedTransaction {
106 RecordedTransaction() : endpoint(), transfers(), transferCount(0), completed(false) {}
107
108 UsbEndpoint endpoint;
109 RecordedTransfer transfers[MaxTransfers];
110 size_t transferCount;
111 bool completed;
112};
113
114class ScriptedBotHub final : public UsbHub {
115 public:
116 ScriptedBotHub()
117 : UsbHub(),
118 m_Steps(),
119 m_StepCount(0),
120 m_Transactions(),
121 m_TransactionCount(0),
122 m_Cbws(),
123 m_CbwCount(0),
124 m_LastTag(0),
125 m_Callbacks(0),
126 m_Cancellations(0),
127 m_Valid(true) {}
128
129 void expectCbw(uint8_t lun, const uint8_t* command, uint8_t commandSize, size_t dataBytes,
130 int firstToggle, ssize_t result = 31, bool writing = true) {
131 Step* step = append(StepKind::Cbw, result, firstToggle);
132 if (!step)
133 return;
134 step->writing = writing;
135 step->lun = lun;
136 step->commandSize = commandSize;
137 step->expectedBytes = dataBytes;
138 MemoryCopy(step->command, command, commandSize);
139 }
140
141 void expectDataOut(const uint8_t* data, size_t bytes, int firstToggle, ssize_t result) {
142 Step* step = append(StepKind::DataOut, result, firstToggle);
143 if (!step)
144 return;
145 step->expectedData = data;
146 step->expectedBytes = bytes;
147 }
148
149 void expectDataIn(const uint8_t* data, size_t bytes, ssize_t result) {
150 Step* step = append(StepKind::DataIn, result, AnyToggle);
151 step->expectedData = data;
152 step->expectedBytes = bytes;
153 }
154
155 void expectCsw(ssize_t result, uint8_t status = 0, uint32_t residue = 0,
156 TagReply tagReply = TagReply::Match, uint32_t signature = CswSignature,
157 int firstToggle = AnyToggle) {
158 Step* step = append(StepKind::Csw, result, firstToggle);
159 if (!step)
160 return;
161 step->signature = signature;
162 step->tagReply = tagReply;
163 step->residue = residue;
164 step->status = status;
165 }
166
167 void expectControl(StepKind kind, ssize_t result = sizeof(UsbDevice::Setup)) {
168 append(kind, result, AnyToggle);
169 }
170
171 void addTransferToTransaction(uintptr_t transaction, bool toggle, UsbPid pid, uintptr_t buffer,
172 size_t bytes) override {
173 RecordedTransaction* recorded = transactionFor(transaction);
174 if (!recorded || recorded->transferCount >= MaxTransfers) {
175 m_Valid = false;
176 return;
177 }
178 RecordedTransfer& transfer = recorded->transfers[recorded->transferCount++];
179 transfer.toggle = toggle;
180 transfer.pid = pid;
181 transfer.buffer = buffer;
182 transfer.bytes = bytes;
183 }
184
185 uintptr_t createTransaction(UsbEndpoint endpoint) override {
186 if (m_TransactionCount >= MaxTransactions) {
187 m_Valid = false;
188 return static_cast<uintptr_t>(-1);
189 }
190 RecordedTransaction& transaction = m_Transactions[m_TransactionCount];
191 transaction.endpoint = endpoint;
192 return ++m_TransactionCount;
193 }
194
195 bool doAsync(uintptr_t transaction, void (*callback)(uintptr_t, ssize_t),
196 uintptr_t parameter) override {
197 RecordedTransaction* recorded = transactionFor(transaction);
198 if (!recorded || recorded->completed || !callback) {
199 m_Valid = false;
200 return false;
201 }
202
203 const size_t index = transaction - 1;
204 Step* step = index < m_StepCount ? &m_Steps[index] : nullptr;
205 if (!step) {
206 m_Valid = false;
207 }
208
209 // Keep old-code reds defined even when a missing recovery step shifts the
210 // script: every 13-byte Bulk-IN receives a complete, benign CSW.
211 seedCsw(*recorded, step && step->kind == StepKind::Csw ? step : nullptr);
212 if (step && !validate(*recorded, *step))
213 m_Valid = false;
214
215 recorded->completed = true;
216 for (size_t i = 0; i < recorded->transferCount; ++i)
217 recorded->transfers[i].buffer = 0;
218
219 ++m_Callbacks;
220 callback(parameter, step ? step->result : -TransactionError);
221 return true;
222 }
223
224 void cancelAsyncAndDrain(uintptr_t, void (*)(uintptr_t, ssize_t), uintptr_t) override {
225 ++m_Cancellations;
226 m_Valid = false;
227 }
228
229 bool addInterruptInHandler(UsbEndpoint, uintptr_t, uint16_t, void (*)(uintptr_t, ssize_t),
230 UsbInterruptInHandle&, uintptr_t) override {
231 m_Valid = false;
232 return false;
233 }
234
235 bool portReset(uint8_t, bool) override {
236 m_Valid = false;
237 return false;
238 }
239
240 bool complete() const {
241 if (!m_Valid || m_TransactionCount != m_StepCount || m_Callbacks != m_TransactionCount ||
242 m_Cancellations)
243 return false;
244 for (size_t i = 0; i < m_TransactionCount; ++i) {
245 if (!m_Transactions[i].completed)
246 return false;
247 for (size_t j = 0; j < m_Transactions[i].transferCount; ++j) {
248 if (m_Transactions[i].transfers[j].buffer)
249 return false;
250 }
251 }
252 return true;
253 }
254
255 bool tagsMonotonic() const {
256 if (!m_CbwCount)
257 return false;
258 uint32_t previous = LITTLE_TO_HOST32(m_Cbws[0].tag);
259 if (!previous)
260 return false;
261 for (size_t i = 1; i < m_CbwCount; ++i) {
262 const uint32_t tag = LITTLE_TO_HOST32(m_Cbws[i].tag);
263 if (tag != previous + 1)
264 return false;
265 previous = tag;
266 }
267 return true;
268 }
269
270 bool firstTagIs(uint32_t tag) const {
271 return m_CbwCount && LITTLE_TO_HOST32(m_Cbws[0].tag) == tag;
272 }
273
274 size_t transactionCount() const {
275 return m_TransactionCount;
276 }
277
278 protected:
279 bool cancelInterruptInAndDrain(const UsbInterruptInToken&, void (*)(uintptr_t, ssize_t),
280 uintptr_t, bool) override {
281 m_Valid = false;
282 return false;
283 }
284
285 private:
286 Step* append(StepKind kind, ssize_t result, int firstToggle) {
287 if (m_StepCount >= MaxTransactions) {
288 m_Valid = false;
289 return nullptr;
290 }
291 Step& step = m_Steps[m_StepCount++];
292 step.kind = kind;
293 step.result = result;
294 step.firstToggle = firstToggle;
295 return &step;
296 }
297
298 RecordedTransaction* transactionFor(uintptr_t transaction) {
299 if (!transaction || transaction > m_TransactionCount)
300 return nullptr;
301 return &m_Transactions[transaction - 1];
302 }
303
304 bool bulkShape(const RecordedTransaction& transaction, uint8_t endpoint, UsbPid pid, size_t bytes,
305 int firstToggle) const {
306 if (transaction.endpoint.nEndpoint != endpoint || !transaction.transferCount)
307 return false;
308 size_t total = 0;
309 bool toggle = firstToggle > 0;
310 for (size_t i = 0; i < transaction.transferCount; ++i) {
311 const RecordedTransfer& transfer = transaction.transfers[i];
312 if (transfer.pid != pid || (firstToggle != AnyToggle && transfer.toggle != toggle))
313 return false;
314 total += transfer.bytes;
315 toggle = !toggle;
316 }
317 return total == bytes;
318 }
319
320 bool validateCbw(const RecordedTransaction& transaction, const Step& step) {
321 if (!bulkShape(transaction, 2, UsbPidOut, sizeof(WireCbw), step.firstToggle) ||
322 transaction.transferCount != 1 || !transaction.transfers[0].buffer || m_CbwCount >= MaxCbws)
323 return false;
324
325 WireCbw& cbw = m_Cbws[m_CbwCount++];
326 MemoryCopy(&cbw, reinterpret_cast<void*>(transaction.transfers[0].buffer), sizeof(cbw));
327 m_LastTag = cbw.tag;
328 if (cbw.signature != CbwSignature || cbw.dataBytes != HOST_TO_LITTLE32(step.expectedBytes) ||
329 cbw.flags != (!step.writing && step.expectedBytes ? 0x80 : 0) || cbw.lun != step.lun ||
330 cbw.commandSize != step.commandSize ||
331 MemoryCompare(cbw.command, step.command, step.commandSize))
332 return false;
333 for (size_t i = step.commandSize; i < sizeof(cbw.command); ++i) {
334 if (cbw.command[i])
335 return false;
336 }
337 return true;
338 }
339
340 bool validateData(const RecordedTransaction& transaction, const Step& step) const {
341 const bool input = step.kind == StepKind::DataIn;
342 if (!bulkShape(transaction, input ? 3 : 2, input ? UsbPidIn : UsbPidOut, step.expectedBytes,
343 step.firstToggle))
344 return false;
345 size_t offset = 0;
346 for (size_t i = 0; i < transaction.transferCount; ++i) {
347 const RecordedTransfer& transfer = transaction.transfers[i];
348 if (!transfer.buffer)
349 return false;
350 if (input)
351 MemoryCopy(reinterpret_cast<void*>(transfer.buffer), step.expectedData + offset,
352 transfer.bytes);
353 else if (MemoryCompare(reinterpret_cast<void*>(transfer.buffer), step.expectedData + offset,
354 transfer.bytes))
355 return false;
356 offset += transfer.bytes;
357 }
358 return true;
359 }
360
361 bool validateCsw(const RecordedTransaction& transaction, const Step& step) const {
362 return bulkShape(transaction, 3, UsbPidIn, sizeof(WireCsw), step.firstToggle) &&
363 transaction.transferCount == 1 && transaction.transfers[0].buffer;
364 }
365
366 bool validateControl(const RecordedTransaction& transaction, const Step& step) const {
367 if (transaction.endpoint.nEndpoint != 0 || transaction.transferCount != 2 ||
368 transaction.transfers[0].toggle || transaction.transfers[0].pid != UsbPidSetup ||
369 transaction.transfers[0].bytes != sizeof(UsbDevice::Setup) ||
370 !transaction.transfers[0].buffer || !transaction.transfers[1].toggle ||
371 transaction.transfers[1].pid != UsbPidIn || transaction.transfers[1].bytes)
372 return false;
373
374 UsbDevice::Setup setup(0, 0, 0, 0, 0);
375 MemoryCopy(&setup, reinterpret_cast<void*>(transaction.transfers[0].buffer), sizeof(setup));
376 if (step.kind == StepKind::Reset) {
377 return setup.nRequestType ==
378 (uint8_t(UsbRequestType::Class) | uint8_t(UsbRequestRecipient::Interface)) &&
379 setup.nRequest == 0xff && setup.nValue == 0 && setup.nIndex == 4 && setup.nLength == 0;
380 }
381
382 const uint16_t endpoint = step.kind == StepKind::ClearIn ? 0x83 : 0x02;
383 return setup.nRequestType == UsbRequestRecipient::Endpoint &&
384 setup.nRequest == UsbRequest::ClearFeature && setup.nValue == 0 &&
385 setup.nIndex == endpoint && setup.nLength == 0;
386 }
387
388 bool validate(const RecordedTransaction& transaction, const Step& step) {
389 switch (step.kind) {
390 case StepKind::Cbw:
391 return validateCbw(transaction, step);
392 case StepKind::DataOut:
393 case StepKind::DataIn:
394 return validateData(transaction, step);
395 case StepKind::Csw:
396 return validateCsw(transaction, step);
397 case StepKind::Reset:
398 case StepKind::ClearIn:
399 case StepKind::ClearOut:
400 return validateControl(transaction, step);
401 }
402 return false;
403 }
404
405 void seedCsw(RecordedTransaction& transaction, const Step* step) {
406 if (transaction.endpoint.nEndpoint != 3 || transaction.transferCount != 1 ||
407 transaction.transfers[0].pid != UsbPidIn ||
408 transaction.transfers[0].bytes != sizeof(WireCsw) || !transaction.transfers[0].buffer)
409 return;
410
411 WireCsw csw = {};
412 csw.signature = step ? step->signature : CswSignature;
413 csw.tag = m_LastTag;
414 if (step && step->tagReply == TagReply::Wrong)
415 csw.tag = HOST_TO_LITTLE32(LITTLE_TO_HOST32(m_LastTag) + 1);
416 csw.residue = HOST_TO_LITTLE32(step ? step->residue : 0);
417 csw.status = step ? step->status : 0;
418 MemoryCopy(reinterpret_cast<void*>(transaction.transfers[0].buffer), &csw, sizeof(csw));
419 }
420
421 Step m_Steps[MaxTransactions];
422 size_t m_StepCount;
423 RecordedTransaction m_Transactions[MaxTransactions];
424 size_t m_TransactionCount;
425 WireCbw m_Cbws[MaxCbws];
426 size_t m_CbwCount;
427 uint32_t m_LastTag;
428 size_t m_Callbacks;
429 size_t m_Cancellations;
430 bool m_Valid;
431};
432
433UsbEndpointDescriptor endpointDescriptor(uint8_t endpoint, bool in) {
434 UsbEndpointDescriptor descriptor;
435 ByteSet(&descriptor, 0, sizeof(descriptor));
436 descriptor.nLength = sizeof(descriptor);
437 descriptor.nType = UsbDescriptor::Endpoint;
438 descriptor.nEndpoint = endpoint;
439 descriptor.bDirection = in;
440 descriptor.nTransferType = UsbDevice::Endpoint::Bulk;
441 descriptor.nMaxPacketSize = 64;
442 return descriptor;
443}
444
445UsbInterfaceDescriptor interfaceDescriptor() {
446 UsbInterfaceDescriptor descriptor;
447 ByteSet(&descriptor, 0, sizeof(descriptor));
448 descriptor.nLength = sizeof(descriptor);
449 descriptor.nType = UsbDescriptor::Interface;
450 descriptor.nInterface = 4;
451 return descriptor;
452}
453
454class BotTestDevice final : public UsbMassStorageDevice {
455 public:
456 explicit BotTestDevice(UsbDevice* device) : UsbMassStorageDevice(device) {}
457
458 void bindInterface(Interface* interface) {
459 m_pInterface = interface;
460 }
461};
462
463struct BotFixture {
464 BotFixture()
465 : hub(),
466 base(&hub, 1, HighSpeed),
467 interfaceDesc(interfaceDescriptor()),
468 interface(&interfaceDesc),
469 outDesc(endpointDescriptor(2, false)),
470 out(&outDesc, HighSpeed),
471 inDesc(endpointDescriptor(3, true)),
472 in(&inDesc, HighSpeed),
473 device(&base),
474 payload() {
475 device.bindInterface(&interface);
476 UsbMassStorageBotTestAccess::bind(device, &in, &out);
477 for (size_t i = 0; i < sizeof(payload); ++i)
478 payload[i] = static_cast<uint8_t>((i * 17) ^ 0x5a);
479 }
480
481 ScriptedBotHub hub;
482 UsbDevice base;
483 UsbInterfaceDescriptor interfaceDesc;
484 UsbDevice::Interface interface;
485 UsbEndpointDescriptor outDesc;
489 BotTestDevice device;
490 alignas(16) uint8_t payload[DataBytes];
491};
492
493void expectWrite(BotFixture& fixture, int cbwToggle, int dataToggle,
494 ssize_t dataResult = DataBytes) {
495 fixture.hub.expectCbw(3, WriteCdb, sizeof(WriteCdb), DataBytes, cbwToggle);
496 fixture.hub.expectDataOut(fixture.payload, DataBytes, dataToggle, dataResult);
497}
498
499bool sendWrite(BotFixture& fixture) {
500 return fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(WriteCdb), sizeof(WriteCdb),
501 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
502}
503
504bool completeDataOut() {
505 auto* fixtureStorage = new BotFixture;
506 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
507 BotFixture& fixture = *fixtureStorage;
508 expectWrite(fixture, 0, 1);
509 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
510 expectWrite(fixture, 1, 0);
511 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 1);
512
513 const bool first = sendWrite(fixture);
514 const bool second = sendWrite(fixture);
515 const bool passed = first && second && fixture.hub.complete() && fixture.hub.tagsMonotonic();
516 if (passed) {
517 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-data-out-complete");
518 } else {
519 ERROR("HOSTED-WAIT-TEST: FAIL usb-bot-data-out-complete: exact CBW/data/CSW or tags failed");
520 }
521 return passed;
522}
523
524bool shortDataOutResets() {
525 auto* fixtureStorage = new BotFixture;
526 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
527 BotFixture& fixture = *fixtureStorage;
528 expectWrite(fixture, 0, 1, DataBytes / 2);
529 fixture.hub.expectControl(StepKind::Reset);
530 fixture.hub.expectControl(StepKind::ClearIn);
531 fixture.hub.expectControl(StepKind::ClearOut);
532
533 const bool result = sendWrite(fixture);
534 const bool passed =
535 !result && fixture.hub.complete() && !fixture.in.bDataToggle && !fixture.out.bDataToggle;
536 if (passed) {
537 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-data-out-short-reset");
538 } else {
539 ERROR(
540 "HOSTED-WAIT-TEST: FAIL usb-bot-data-out-short-reset: short OUT was accepted or recovery "
541 "was incomplete");
542 }
543 return passed;
544}
545
546bool invalidCbwCase(ssize_t result, bool followWithValidCommand) {
547 auto* fixtureStorage = new BotFixture;
548 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
549 BotFixture& fixture = *fixtureStorage;
550 fixture.hub.expectCbw(3, WriteCdb, sizeof(WriteCdb), DataBytes, 0, result);
551 fixture.hub.expectControl(StepKind::Reset);
552 fixture.hub.expectControl(StepKind::ClearIn);
553 fixture.hub.expectControl(StepKind::ClearOut);
554 if (followWithValidCommand) {
555 expectWrite(fixture, 0, 1);
556 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
557 }
558
559 const bool failed = !sendWrite(fixture);
560 const bool followUp = !followWithValidCommand || sendWrite(fixture);
561 return failed && followUp && fixture.hub.complete() &&
562 (!followWithValidCommand || (fixture.hub.firstTagIs(1) && fixture.hub.tagsMonotonic()));
563}
564
565bool invalidCbwResets() {
566 const bool shortResult = invalidCbwCase(sizeof(WireCbw) - 1, true);
567 const bool stalledResult = invalidCbwCase(-Stall, false);
568 const bool passed = shortResult && stalledResult;
569 if (passed) {
570 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-cbw-exact-reset");
571 } else {
572 ERROR(
573 "HOSTED-WAIT-TEST: FAIL usb-bot-cbw-exact-reset: short or stalled CBW did not perform "
574 "full reset recovery");
575 }
576 return passed;
577}
578
579bool stalledDataOutUsesCsw() {
580 auto* fixtureStorage = new BotFixture;
581 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
582 BotFixture& fixture = *fixtureStorage;
583 expectWrite(fixture, 0, 1, -Stall);
584 fixture.hub.expectControl(StepKind::ClearOut);
585 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
586
587 const bool result = sendWrite(fixture);
588 const bool passed = result && fixture.hub.complete() && !fixture.out.bDataToggle;
589 if (passed) {
590 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-data-out-stall-csw");
591 } else {
592 ERROR(
593 "HOSTED-WAIT-TEST: FAIL usb-bot-data-out-stall-csw: recovered OUT STALL did not use the "
594 "authoritative CSW");
595 }
596 return passed;
597}
598
599bool stalledDataOutCswCase(uint8_t status, uint32_t residue) {
600 auto* fixtureStorage = new BotFixture;
601 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
602 BotFixture& fixture = *fixtureStorage;
603 expectWrite(fixture, 0, 1, -Stall);
604 fixture.hub.expectControl(StepKind::ClearOut);
605 fixture.hub.expectCsw(13, status, residue, TagReply::Match, CswSignature, 0);
606 return !sendWrite(fixture) && fixture.hub.complete();
607}
608
609bool stalledDataOutHonoursCswFailure() {
610 const bool statusResult = stalledDataOutCswCase(1, 0);
611 const bool residueResult = stalledDataOutCswCase(0, 1);
612 const bool passed = statusResult && residueResult;
613 if (passed) {
614 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-data-out-stall-csw-failure");
615 } else {
616 ERROR(
617 "HOSTED-WAIT-TEST: FAIL usb-bot-data-out-stall-csw-failure: recovered OUT STALL "
618 "ignored CSW failure or residue");
619 }
620 return passed;
621}
622
623bool stalledCswRetriesOnce() {
624 auto* fixtureStorage = new BotFixture;
625 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
626 BotFixture& fixture = *fixtureStorage;
627 expectWrite(fixture, 0, 1);
628 fixture.hub.expectCsw(-Stall, 0, 0, TagReply::Match, CswSignature, 0);
629 fixture.hub.expectControl(StepKind::ClearIn);
630 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
631
632 const bool result = sendWrite(fixture);
633 const bool passed = result && fixture.hub.complete() && fixture.in.bDataToggle;
634 if (passed) {
635 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-csw-stall-retry");
636 } else {
637 ERROR(
638 "HOSTED-WAIT-TEST: FAIL usb-bot-csw-stall-retry: CSW STALL was not cleared and retried "
639 "once at DATA0");
640 }
641 return passed;
642}
643
644bool secondCswStallResets() {
645 auto* fixtureStorage = new BotFixture;
646 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
647 BotFixture& fixture = *fixtureStorage;
648 expectWrite(fixture, 0, 1);
649 fixture.hub.expectCsw(-Stall, 0, 0, TagReply::Match, CswSignature, 0);
650 fixture.hub.expectControl(StepKind::ClearIn);
651 fixture.hub.expectCsw(-Stall, 0, 0, TagReply::Match, CswSignature, 0);
652 fixture.hub.expectControl(StepKind::Reset);
653 fixture.hub.expectControl(StepKind::ClearIn);
654 fixture.hub.expectControl(StepKind::ClearOut);
655
656 const bool result = sendWrite(fixture);
657 const bool passed = !result && fixture.hub.complete();
658 if (passed) {
659 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-csw-second-stall-reset");
660 } else {
661 ERROR(
662 "HOSTED-WAIT-TEST: FAIL usb-bot-csw-second-stall-reset: CSW was retried more than once "
663 "or reset recovery was incomplete");
664 }
665 return passed;
666}
667
668bool cswTruthCase(ssize_t length, uint8_t status, uint32_t residue, TagReply tagReply,
669 uint32_t signature, bool recovery) {
670 auto* fixtureStorage = new BotFixture;
671 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
672 BotFixture& fixture = *fixtureStorage;
673 expectWrite(fixture, 0, 1);
674 fixture.hub.expectCsw(length, status, residue, tagReply, signature, 0);
675 if (recovery) {
676 fixture.hub.expectControl(StepKind::Reset);
677 fixture.hub.expectControl(StepKind::ClearIn);
678 fixture.hub.expectControl(StepKind::ClearOut);
679 }
680 return !sendWrite(fixture) && fixture.hub.complete();
681}
682
683bool cswTruth() {
684 const bool length = cswTruthCase(12, 0, 0, TagReply::Match, CswSignature, true);
685 const bool signature =
686 cswTruthCase(13, 0, 0, TagReply::Match, HOST_TO_LITTLE32(0x12345678), true);
687 const bool tag = cswTruthCase(13, 0, 0, TagReply::Wrong, CswSignature, true);
688 const bool passedResidue = cswTruthCase(13, 0, 1, TagReply::Match, CswSignature, false);
689 const bool failedStatus = cswTruthCase(13, 1, 0, TagReply::Match, CswSignature, false);
690 const bool phaseError = cswTruthCase(13, 2, 0, TagReply::Match, CswSignature, true);
691 const bool invalidStatus = cswTruthCase(13, 3, 0, TagReply::Match, CswSignature, true);
692 const bool excessResidue =
693 cswTruthCase(13, 0, DataBytes + 1, TagReply::Match, CswSignature, true);
694 const bool passed = length && signature && tag && passedResidue && failedStatus && phaseError &&
695 invalidStatus && excessResidue;
696 if (passed) {
697 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-csw-truth");
698 } else {
699 ERROR(
700 "HOSTED-WAIT-TEST: FAIL usb-bot-csw-truth: invalid CSW length/signature/tag/status/residue "
701 "was accepted or mis-recovered");
702 }
703 return passed;
704}
705
706bool failedRecoveryLatches() {
707 auto* fixtureStorage = new BotFixture;
708 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
709 BotFixture& fixture = *fixtureStorage;
710 expectWrite(fixture, 0, 1);
711 fixture.hub.expectCsw(13, 0, 0, TagReply::Wrong, CswSignature, 0);
712 fixture.hub.expectControl(StepKind::Reset);
713 fixture.hub.expectControl(StepKind::ClearIn, -TransactionError);
714 fixture.hub.expectControl(StepKind::ClearOut);
715
716 fixture.hub.expectControl(StepKind::Reset);
717 fixture.hub.expectControl(StepKind::ClearIn);
718 fixture.hub.expectControl(StepKind::ClearOut);
719 expectWrite(fixture, 0, 1);
720 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
721
722 const bool first = sendWrite(fixture);
723 const bool second = sendWrite(fixture);
724 const bool passed = !first && second && fixture.hub.complete() && fixture.hub.tagsMonotonic();
725 if (passed) {
726 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-recovery-latch");
727 } else {
728 ERROR(
729 "HOSTED-WAIT-TEST: FAIL usb-bot-recovery-latch: failed recovery did not block the next CBW "
730 "until all three steps succeeded");
731 }
732 return passed;
733}
734
735bool commandBoundsDoNotReachUsb() {
736 alignas(16) uint8_t command[17] = {};
737 for (size_t i = 0; i < sizeof(command); ++i)
738 command[i] = static_cast<uint8_t>(0xa0 + i);
739
740 auto* fixtureStorage = new BotFixture;
741 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
742 BotFixture& fixture = *fixtureStorage;
743 UsbMassStorageBotTestAccess::setUnits(fixture.device, 17);
744 const bool unencodableLun =
745 fixture.device.sendCommand(16, reinterpret_cast<uintptr_t>(command), 16,
746 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
747 const bool wideLunRejectedWithoutIo = !unencodableLun && fixture.hub.transactionCount() == 0;
748 UsbMassStorageBotTestAccess::setUnits(fixture.device, 4);
749
750 const bool zeroSize =
751 fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(command), 0,
752 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
753 const bool nullCommand = fixture.device.sendCommand(
754 3, 0, 16, reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
755 const bool oversized =
756 fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(command), sizeof(command),
757 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
758 const bool unavailableLun =
759 fixture.device.sendCommand(4, reinterpret_cast<uintptr_t>(command), 16,
760 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
761 const bool nullPayload =
762 fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(command), 16, 0, DataBytes, true);
763 const bool rejectedWithoutIo = wideLunRejectedWithoutIo && !zeroSize && !nullCommand &&
764 !oversized && !unavailableLun && !nullPayload &&
765 fixture.hub.transactionCount() == 0;
766
767 fixture.hub.expectCbw(3, command, 16, DataBytes, 0);
768 fixture.hub.expectDataOut(fixture.payload, DataBytes, 1, DataBytes);
769 fixture.hub.expectCsw(13, 0, 0, TagReply::Match, CswSignature, 0);
770 const bool valid =
771 fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(command), 16,
772 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, true);
773 const bool complete = fixture.hub.complete();
774 const bool firstTag = fixture.hub.firstTagIs(1);
775 const bool passed = rejectedWithoutIo && valid && complete && firstTag;
776 if (passed) {
777 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-cdb-bounds");
778 } else {
779 ERROR(
780 "HOSTED-WAIT-TEST: FAIL usb-bot-cdb-bounds: invalid CDB/LUN/payload reached USB or "
781 "consumed the first tag; rejected="
782 << rejectedWithoutIo << ", valid=" << valid << ", complete=" << complete
783 << ", first-tag=" << firstTag);
784 }
785 return passed;
786}
787bool dataInCase(ssize_t dataResult, ssize_t cswBytes, TagReply tag, bool recovery, bool expected,
788 int cswFirstToggle) {
789 auto* fixtureStorage = new BotFixture;
790 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
791 BotFixture& fixture = *fixtureStorage;
792 alignas(16) uint8_t reference[DataBytes];
793 for (size_t i = 0; i < DataBytes; ++i)
794 reference[i] = static_cast<uint8_t>(i * 13);
795 fixture.hub.expectCbw(3, WriteCdb, sizeof(WriteCdb), DataBytes, 0, 31, false);
796 fixture.hub.expectDataIn(reference, DataBytes, dataResult);
797 if (dataResult == -Stall)
798 fixture.hub.expectControl(StepKind::ClearIn);
799 if (dataResult >= 0 || dataResult == -Stall)
800 fixture.hub.expectCsw(cswBytes, 0, 0, tag, CswSignature, cswFirstToggle);
801 if (recovery) {
802 fixture.hub.expectControl(StepKind::Reset);
803 fixture.hub.expectControl(StepKind::ClearIn);
804 fixture.hub.expectControl(StepKind::ClearOut);
805 }
806 const bool result =
807 fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(WriteCdb), sizeof(WriteCdb),
808 reinterpret_cast<uintptr_t>(fixture.payload), DataBytes, false);
809 return result == expected && fixture.hub.complete() &&
810 (!expected || !MemoryCompare(reference, fixture.payload, DataBytes));
811}
812bool dataInAndNoData() {
813 bool passed = dataInCase(DataBytes, 13, TagReply::Match, false, true, 0);
814 passed &= dataInCase(DataBytes / 2, 13, TagReply::Match, false, false, 1);
815 passed &= dataInCase(-Stall, 13, TagReply::Match, false, false, 0);
816 passed &= dataInCase(-TransactionError, 13, TagReply::Match, true, false, 0);
817 passed &= dataInCase(DataBytes, 12, TagReply::Match, true, false, 0);
818 passed &= dataInCase(DataBytes, 13, TagReply::Wrong, true, false, 0);
819 auto* fixtureStorage = new BotFixture;
820 PointerGuard<BotFixture> fixtureGuard(fixtureStorage);
821 BotFixture& fixture = *fixtureStorage;
822 fixture.hub.expectCbw(3, WriteCdb, sizeof(WriteCdb), 0, 0);
823 fixture.hub.expectCsw(13, 0, 0);
824 passed &= fixture.device.sendCommand(3, reinterpret_cast<uintptr_t>(WriteCdb), sizeof(WriteCdb),
825 0, 0, false) &&
826 fixture.hub.complete();
827 if (passed)
828 NOTICE("HOSTED-WAIT-TEST: PASS usb-bot-data-in-and-no-data");
829 else
830 ERROR("HOSTED-WAIT-TEST: FAIL usb-bot-data-in-and-no-data");
831 return passed;
832}
833
834} // namespace
835
836EXPORTED_PUBLIC bool runHostedUsbBotRegressions() {
837 const bool reads = dataInAndNoData();
838 const bool complete = completeDataOut();
839 const bool cbwExact = invalidCbwResets();
840 const bool shortReset = shortDataOutResets();
841 const bool stalledData = stalledDataOutUsesCsw();
842 const bool stalledDataFailure = stalledDataOutHonoursCswFailure();
843 const bool stalledCsw = stalledCswRetriesOnce();
844 const bool secondCswStall = secondCswStallResets();
845 const bool truth = cswTruth();
846 const bool recoveryLatch = failedRecoveryLatches();
847 const bool commandBounds = commandBoundsDoNotReachUsb();
848 return reads && complete && cbwExact && shortReset && stalledData && stalledDataFailure &&
849 stalledCsw && secondCswStall && truth && recoveryLatch && commandBounds;
850}
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 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.
virtual void cancelAsyncAndDrain(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t), uintptr_t pParam)=0
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.
virtual MUST_USE_RESULT bool doAsync(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t)=0, uintptr_t pParam=0)=0
virtual MUST_USE_RESULT bool cancelInterruptInAndDrain(const UsbInterruptInToken &token, void(*callback)(uintptr_t, ssize_t), uintptr_t parameter, bool producerAlreadyStopped)=0