2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/machine/InputManager.h"
5#include "pedigree/kernel/process/Mutex.h"
6#include "pedigree/kernel/process/Semaphore.h"
7#include "pedigree/kernel/time/Time.h"
9#include "modules/Module.h"
10#include "modules/drivers/common/scsi/ScsiDisk.h"
11#include "modules/drivers/common/usb-mass-storage/UsbMassStorageDevice.h"
12#include "modules/system/usb/UsbDevice.h"
13#include "modules/system/vfs/Filesystem.h"
14#include "modules/system/vfs/VFS.h"
16extern bool runHostedUsbBotRegressions();
17extern bool runHostedHidReportRegressions();
20constexpr uint64_t DiskBytes = 32 * 1024 * 1024;
21constexpr char Magic[] =
"PEDIGREE-USB-SMOKE-v1";
22constexpr uint64_t WriteOffsets[] = {8 * 1024 * 1024, 8 * 1024 * 1024 + 4096, DiskBytes - 4096};
24UsbSpeed storageSpeed = LowSpeed;
25bool duplicate =
false;
30uint8_t pattern(uint64_t offset, uint8_t seed = 0x5a) {
31 return ((offset * 37) ^ (offset >> 8) ^ (offset >> 16) ^ seed) & 255;
33bool fail(
const char* reason) {
34 ERROR(
"USB-SMOKE: FAIL " << reason);
40 if (usb && usb->getUsbState() == UsbDevice::HasDriver && usb->getInterface() &&
41 usb->getInterface()->nClass == 3)
44 if (device->getSpecificType() ==
String(
"usb-msd-controller")) {
45 for (
size_t i = 0; i < device->getNumChildren(); ++i) {
46 auto* disk =
static_cast<ScsiDisk*
>(device->getChild(i));
47 if (disk->getSize() == DiskBytes) {
48 duplicate |= scratch !=
nullptr;
57 const auto deadline = Time::getTicks() + 30 * Time::Multiplier::Second;
63 if ((scratch && hidDevices >= 2) || duplicate)
65 Time::delay(10 * Time::Multiplier::Millisecond);
66 }
while (Time::getTicks() < deadline);
68 Disk* root = filesystem ? filesystem->getDisk() :
nullptr;
69 Disk* physicalRoot = root ? root->physicalDisk() :
nullptr;
72 NOTICE(
"USB-SMOKE: root=NVMe-4Kn");
73 if (!scratch || duplicate || hidDevices < 2 || (root && scratch == root->physicalDisk()) ||
74 (scratch->getNativeBlockSize() != 512 && scratch->getNativeBlockSize() != 4096))
75 return fail(
"unique disposable USB namespace");
77 if (!scratch->acquireUse(use))
78 return fail(
"scratch admission");
80 bool valid = bool(header) && header.size() == 4096;
81 for (
size_t i = 0; valid && i < header.size(); ++i) {
83 if (i <
sizeof(Magic) - 1)
87 else if (i >= 40 && i < 48)
88 expected = DiskBytes >> ((i - 40) * 8);
89 else if (i >= 48 && i < 52)
90 expected = scratch->getNativeBlockSize() >> ((i - 48) * 8);
91 valid = header[i] == expected;
96 return fail(
"exact disposable fixture header");
97 NOTICE(
"USB-SMOKE: PASS fixture-identification");
98 NOTICE(
"USB-SMOKE: storage-link=" << (storageSpeed >= SuperSpeed ?
"SuperSpeed" :
"USB1/2"));
99 if (storageSpeed >= SuperSpeed) {
101 for (
size_t page = 0; page < 320; ++page) {
102 const uint64_t offset = 1024 * 1024 + page * 4096;
103 const BufferView view = scratch->read(offset);
104 valid = bool(view) && view.size() == 4096;
105 for (
size_t i = 0; valid && i < view.size(); ++i)
106 valid = view[i] == pattern(offset + i);
108 scratch->unpin(offset);
109 if (!valid || !scratch->retireCachePage(offset))
110 return fail(
"uncached transfer-ring reads");
112 NOTICE(
"USB-SMOKE: PASS sustained-reads");
114 for (uint64_t offset : WriteOffsets) {
115 const BufferView view = scratch->read(offset);
116 valid = bool(view) && view.size() == 4096;
117 for (
size_t i = 0; valid && i < view.size(); ++i)
118 valid = view[i] == pattern(offset + i);
121 for (
size_t i = 0; i < view.size(); ++i)
122 view[i] = pattern(offset + i, 0xa5);
123 valid = scratch->sync(offset,
false);
125 scratch->unpin(offset);
127 if (!valid || !scratch->syncAll() || !scratch->retireCachePage(offset))
128 return fail(
"patterned write, flush or cache retirement");
129 const BufferView reread = scratch->read(offset);
130 valid = bool(reread) && reread.size() == 4096;
131 for (
size_t i = 0; valid && i < reread.size(); ++i)
132 valid = reread[i] == pattern(offset + i, 0xa5);
134 scratch->unpin(offset);
136 return fail(
"uncached reread");
138 if (!scratch->hasNoCacheLoans())
139 return fail(
"cache pins retained");
140 NOTICE(
"USB-SMOKE: PASS msd-write-flush-reread");
146 if (event.type == InputManager::RawKey && event.data.rawkey.
scancode == 4)
147 bits |=
event.data.rawkey.keyUp ? 2 : 1;
148 if (event.type == InputManager::Mouse) {
149 if (event.data.pointy.relx == 17)
151 if (event.data.pointy.rely == -9)
153 if (event.data.pointy.buttons[0])
155 else if (observed & 16)
161 NOTICE(
"USB-SMOKE: PASS input-bits=" <<
Hex <<
static_cast<size_t>(bits)
162 <<
" observed=" <<
static_cast<size_t>(observed));
168 return fail(
"CRIPPLE_HDD must be disabled");
170 if (!runHostedUsbBotRegressions() || !runHostedHidReportRegressions())
171 return fail(
"BOT or HID contract regressions");
172 NOTICE(
"USB-SMOKE: PASS bot-hid-contracts");
176 NOTICE(
"USB-SMOKE: READY input");
177 bool complete =
false;
178 while (events.acquireForCompletion(1, 30)) {
180 if (observed == 63) {
187 return fail(
"keyboard or mouse events missing");
188 NOTICE(
"USB-SMOKE: PASS complete");
194MODULE_INFO(
"usb-smoke", &entry, &exit,
"usb-hid",
"usb-mass-storage",
"mountroot",
"scsi");
static void foreach(Callback callback, Device *root=0)
virtual const String & getSpecificType()
virtual size_t getNativeBlockSize() const
Filesystem * getRootFilesystem() const