2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/machine/PciExpress.h"
4#include "pedigree/kernel/machine/PciVirtualFunctions.h"
5#include "pedigree/kernel/process/Scheduler.h"
6#include "pedigree/kernel/process/Semaphore.h"
7#include "pedigree/kernel/process/Thread.h"
8#include "pedigree/kernel/time/Time.h"
9#include "pedigree/kernel/utilities/new"
11#include "modules/Module.h"
12#include "modules/drivers/common/nvme/NvmeController.h"
13#include "modules/drivers/common/nvme/NvmeDisk.h"
14#include "modules/system/vfs/Filesystem.h"
15#include "modules/system/vfs/VFS.h"
18constexpr uint64_t DiskBytes = 32 * 1024 * 1024;
19constexpr char Magic[] =
"PEDIGREE-NVME-SMOKE-v1";
20constexpr size_t Workers = 20;
25bool hotplugFixture =
false;
27uint8_t pattern(uint64_t offset, uint8_t seed = 0x5a) {
28 return ((offset * 37) ^ (offset >> 8) ^ (offset >> 16) ^ seed) & 255;
30bool fail(
const char* reason) {
31 ERROR(
"NVME-SMOKE: FAIL " << reason);
34bool checkPattern(
const uint8_t* data, uint64_t offset,
size_t bytes, uint8_t seed = 0x5a) {
35 for (
size_t i = 0; i < bytes; ++i) {
36 if (data[i] != pattern(offset + i, seed))
37 return fail(
"data mismatch");
46int readWorker(
void* parameter) {
47 auto*
worker =
static_cast<Worker*
>(parameter);
48 worker->passed = start.acquireForCompletion(1, 30);
51 for (
size_t i = 0;
worker->passed && i < 16; ++i) {
52 const uint64_t offset =
53 !i ? 4 * 1024 * 1024 : 2 * 1024 * 1024 +
worker->index * 65536 + i * 4096;
59 worker->passed = view.size() == 4096 &&
60 checkPattern(
static_cast<const uint8_t*
>(view.data()), offset, view.size());
61 worker->disk->unpin(offset);
63 return worker->passed ? 0 : 1;
66 Worker workers[Workers]{};
67 Thread* threads[Workers]{};
68 auto* controller = disk.controller();
69 const size_t queueCount = controller->ioQueueCount();
71 size_t processors[Nvme::MaxIoQueues] = {};
72 size_t before[Nvme::MaxIoQueues] = {};
73 size_t processorCount = 0;
74 for (
size_t cpu = 0; cpu < CpuAffinityMask::MaximumCpus && processorCount < queueCount; ++cpu) {
75 if (online.contains(cpu)) {
76 processors[processorCount++] = cpu;
79 if (!queueCount || processorCount != queueCount) {
80 return fail(
"I/O queue processor placement");
82 for (
size_t i = 0; i < queueCount; ++i) {
83 before[i] = controller->ioQueueInterruptCompletions(i);
85 for (
size_t i = 0; i < Workers; ++i) {
86 workers[i] = {&disk, i,
false};
88 placement.allowed.set(processors[i % processorCount]);
90 nullptr,
false,
false,
true, &placement);
91 threads[i]->setName(
"NVMe smoke read");
92 if (!threads[i]->start())
93 FATAL(
"NVME-SMOKE: worker start failed");
95 start.release(Workers);
97 for (
size_t i = 0; i < Workers; ++i) {
98 if (!threads[i]->joinForCompletion())
99 FATAL(
"NVME-SMOKE: worker join failed");
100 passed &= workers[i].passed;
102 for (
size_t i = 0; i < queueCount; ++i) {
103 const size_t completed = controller->ioQueueInterruptCompletions(i) - before[i];
104 NOTICE(
"NVME-SMOKE: I/O queue=" <<
Dec << i + 1 <<
" interrupt-completions=" << completed);
105 passed &= completed != 0;
107 NOTICE(
"NVME-SMOKE: concurrent-read maximum-per-queue-outstanding="
108 <<
Dec << controller->maximumOutstanding() <<
" queues=" << queueCount);
109 return (passed && controller->maximumOutstanding() > 1) ||
110 fail(
"concurrent cached reads, inactive queue, or no overlapping commands");
114 if (!disk.acquireUse(lease))
115 return fail(
"scratch admission");
116 auto* controller = disk.controller();
118 const uint32_t nsid = disk.namespaceId();
119 const size_t initialInterrupts = controller->interruptCompletions();
120 uint8_t* buffer =
new uint8_t[Nvme::MaxTransfer];
121 bool passed = controller->readWrite(nsid, 0, 4096 / blockBytes, buffer, 4096,
false);
122 for (
size_t i = 0; passed && i < 4096; ++i) {
123 uint8_t expected = 0;
124 if (i <
sizeof(Magic) - 1)
128 else if (i >= 40 && i < 48)
129 expected = DiskBytes >> ((i - 40) * 8);
130 else if (i >= 48 && i < 52)
131 expected = blockBytes >> ((i - 48) * 8);
132 passed = buffer[i] == expected;
136 return fail(
"exact disposable fixture header");
138 NOTICE(
"NVME-SMOKE: PASS fixture nsid=" <<
Dec << nsid <<
" block-bytes=" << blockBytes);
139 const size_t transfer = controller->maxTransfer();
140 passed = controller->readWrite(nsid, (1024 * 1024 + 4096) / blockBytes, transfer / blockBytes,
141 buffer, transfer,
false) &&
142 checkPattern(buffer, 1024 * 1024 + 4096, transfer);
143 passed &= !controller->readWrite(nsid, disk.getBlockCount(), 1, buffer, blockBytes,
false);
145 !controller->readWrite(nsid, disk.getBlockCount() - 1, 2, buffer, 2 * blockBytes,
false);
146 passed &= !controller->readWrite(nsid, 0, 0, buffer, 0,
false);
149 disk.
unpin(DiskBytes);
152 if (!passed || !concurrency(disk)) {
154 return fail(
"read, PRP list, or range rejection");
157 passed = bool(subBlock) && subBlock.size() == 3072;
159 for (
size_t i = 0; passed && i < subBlock.size(); ++i)
160 passed = subBlock[i] == 0;
165 return fail(
"sub-block byte-offset read");
167 NOTICE(
"NVME-SMOKE: PASS reads-prps-ranges-concurrency nsid=" <<
Dec << nsid);
168 const uint64_t rawOffsets[] = {8 * 1024 * 1024, DiskBytes - blockBytes};
169 for (uint64_t offset : rawOffsets) {
170 const size_t bytes = offset == rawOffsets[0] ? transfer : blockBytes;
171 for (
size_t i = 0; i < bytes; ++i)
172 buffer[i] = pattern(offset + i, 0xa5);
174 controller->readWrite(nsid, offset / blockBytes, bytes / blockBytes, buffer, bytes,
true) &&
175 controller->flush(nsid);
177 passed = controller->readWrite(nsid, offset / blockBytes, bytes / blockBytes, buffer, bytes,
179 checkPattern(buffer, offset, bytes, 0xa5);
183 constexpr uint64_t CachedOffset = 16 * 1024 * 1024;
186 passed = bool(view) && view.size() == 4096;
188 for (
size_t i = 0; i < view.size(); ++i)
189 view[i] = pattern(CachedOffset + i, 0xa5);
190 disk.
unpin(CachedOffset);
191 passed &= disk.
syncPages(&CachedOffset, 1) && disk.
sync(CachedOffset,
false);
196 passed = bool(reread) && checkPattern(
static_cast<const uint8_t*
>(reread.data()),
197 CachedOffset, reread.size(), 0xa5);
199 disk.
unpin(CachedOffset);
203 const size_t interrupts = controller->interruptCompletions() - initialInterrupts;
204 if (!passed || !interrupts || !disk.hasNoCacheLoans())
205 return fail(
"write/flush/reread, interrupts, or leaked cache pins");
206 NOTICE(
"NVME-SMOKE: PASS writes-flush-reread nsid=" <<
Dec << nsid
207 <<
" interrupt-completions=" << interrupts);
211 if (device->getSpecificType() ==
"nvme-controller") {
213 if (
String(controller->model()) ==
"PEDIGREE-SRIOV-SMOKE") {
214 virtualizationFixture = controller;
215 }
else if (
String(controller->model()) ==
"PEDIGREE-HOTPLUG-SMOKE") {
216 hotplugFixture =
true;
219 if (device->getSpecificType() ==
String(
"nvme-disk") && device != rootDisk &&
220 static_cast<NvmeDisk*
>(device)->getSize() == DiskBytes) {
221 const String model(
static_cast<NvmeDisk*
>(device)->controller()->model());
222 if (model ==
"PEDIGREE-SRIOV-SMOKE" || model ==
"PEDIGREE-HOTPLUG-SMOKE") {
226 success = run(*
static_cast<NvmeDisk*
>(device));
233 using namespace Nvme;
234 if (!original.supportsVirtualizationManagement() || original.
getNumChildren() ||
235 !original.prepareDiskRemoval()) {
236 return fail(
"SR-IOV fixture PF must have no attached namespaces");
238 uint16_t changed = 0;
240 original.virtualizationManagement(original.controllerId(), VirtualResource::Queue,
241 VirtualizationAction::AllocatePrimary, 0, &changed) &&
243 original.virtualizationManagement(original.controllerId(), VirtualResource::Interrupt,
244 VirtualizationAction::AllocatePrimary, 0, &changed) &&
246 Device* pf = original.pciDevice();
249 if (!PciBus::instance().inspectFunction(pf, resources,
false) || (resources.command & 4U)) {
250 return fail(
"SR-IOV stopped primary resources");
254 bool read8(uint16_t offset, uint8_t& value) {
255 return PciBus::instance().readConfig8(device, offset, value);
257 bool read16(uint16_t offset, uint16_t& value) {
258 return PciBus::instance().readConfig16(device, offset, value);
260 bool read32(uint16_t offset, uint32_t& value) {
261 return PciBus::instance().readConfig32(device, offset, value);
263 bool write8(uint16_t offset, uint8_t value) {
264 return PciBus::instance().writeConfig8(device, offset, value);
266 bool write16(uint16_t offset, uint16_t value) {
267 return PciBus::instance().writeConfig16(device, offset, value);
269 bool write32(uint16_t offset, uint32_t value) {
270 return PciBus::instance().writeConfig32(device, offset, value);
273 auto delay = [](
size_t milliseconds) {
274 const auto deadline = Time::getTicks() + milliseconds * Time::Multiplier::Millisecond;
275 while (Time::getTicks() < deadline) {
276 Time::delay(Time::Multiplier::Millisecond);
280 if (!passed || PciExpress::resetFunction(config, delay) != PciExpress::FlrResult::Complete) {
281 return fail(
"SR-IOV primary resource reset");
283 if (!PciBus::instance().updateCommand(pf, 7U, 0)) {
284 return fail(
"SR-IOV primary decoding after reset");
287 for (
size_t i = resources.barCount; i; --i) {
288 if (!PciFunctionState::writeVerified32(config, 0x10 + 4 * (i - 1), resources.bars[i - 1])) {
289 return fail(
"SR-IOV primary BAR restore");
292 if (!config.write8(0x3c, resources.interruptLine) ||
293 !PciFunctionState::resourcesUnchanged(config, resources)) {
294 return fail(
"SR-IOV primary resource restore");
297 for (
size_t cycle = 0; cycle < 2; ++cycle) {
298 if (!group.enable(1)) {
299 return fail(
"SR-IOV enable");
302 passed = primary->initialiseController();
304 uint16_t secondaryId = 0xffff;
305 if (passed && primary->secondaryControllers(0, controllers)) {
306 for (
size_t i = 0; i < controllers.count; ++i) {
307 if (controllers.entries[i].virtualFunction == 1 &&
308 controllers.entries[i].primaryControllerId == primary->controllerId()) {
309 secondaryId = controllers.entries[i].controllerId;
313 passed = passed && secondaryId != 0xffff &&
314 primary->virtualizationManagement(secondaryId, VirtualResource::Queue,
315 VirtualizationAction::Offline, 0) &&
316 primary->virtualizationManagement(secondaryId, VirtualResource::Queue,
317 VirtualizationAction::Assign, 2, &changed) &&
319 primary->virtualizationManagement(secondaryId, VirtualResource::Interrupt,
320 VirtualizationAction::Assign, 2, &changed) &&
322 primary->virtualizationManagement(secondaryId, VirtualResource::Queue,
323 VirtualizationAction::Online, 0) &&
324 primary->setNamespaceAttachment(1, secondaryId,
true);
325 Device* vf = group.function(0);
327 if (passed && secondary->initialiseController()) {
329 secondary->setParent(vf);
330 passed = !group.disable() && secondary->getNumChildren() == 1 &&
331 static_cast<NvmeDisk*
>(secondary->getChild(0))->getSize() == DiskBytes &&
332 run(*
static_cast<NvmeDisk*
>(secondary->getChild(0)));
336 secondary->shutdown();
338 secondary->setParent(
nullptr);
340 if (secondaryId != 0xffff) {
341 passed &= primary->setNamespaceAttachment(1, secondaryId,
false);
342 passed &= primary->virtualizationManagement(secondaryId, VirtualResource::Queue,
343 VirtualizationAction::Offline, 0);
347 passed &= group.disable() && !group.count();
349 return fail(
"SR-IOV secondary I/O or retirement");
351 NOTICE(
"NVME-SMOKE: PASS sriov-cycle=" <<
Dec << cycle + 1);
356bool hotplugPresent(
DiskUse* use =
nullptr) {
357 bool present =
false;
359 if (device->getSpecificType() ==
"nvme-controller") {
361 present |=
String(controller->model()) ==
"PEDIGREE-HOTPLUG-SMOKE";
362 }
else if (use && !*use && device->getSpecificType() ==
"nvme-disk") {
363 auto* disk =
static_cast<NvmeDisk*
>(device);
365 String(disk->controller()->model()) ==
"PEDIGREE-HOTPLUG-SMOKE") {
366 disk->acquireUse(*use);
371 auto callback = pedigree_std::make_callable(find);
376void hotplugDelay(
size_t seconds) {
377 const auto deadline = Time::getTicks() + seconds * Time::Multiplier::Second;
378 while (Time::getTicks() < deadline) {
379 Time::delay(50 * Time::Multiplier::Millisecond);
383bool waitHotplug(
bool present) {
384 const auto deadline = Time::getTicks() + 30 * Time::Multiplier::Second;
386 if (hotplugPresent() == present) {
389 Time::delay(100 * Time::Multiplier::Millisecond);
390 }
while (Time::getTicks() < deadline);
395 for (
size_t cycle = 1; cycle <= 2; ++cycle) {
397 if (!hotplugPresent(&use) || !use) {
398 return fail(
"hotplug fixture admission");
400 auto* disk =
static_cast<NvmeDisk*
>(use.get());
404 NOTICE(
"NVME-SMOKE: READY hotplug-busy cycle=" <<
Dec << cycle);
406 constexpr uint64_t offset = 5 * 1024 * 1024;
407 uint8_t buffer[4096];
408 if (!disk->controller()->readWrite(disk->namespaceId(), offset / 512, 8, buffer,
sizeof(buffer),
410 !checkPattern(buffer, offset,
sizeof(buffer))) {
411 return fail(
"hotplug busy device lost I/O");
414 NOTICE(
"NVME-SMOKE: PASS hotplug-busy cycle=" <<
Dec << cycle);
415 NOTICE(
"NVME-SMOKE: READY hotplug-remove cycle=" <<
Dec << cycle);
416 if (!waitHotplug(
false)) {
417 return fail(
"hotplug removal timed out");
419 NOTICE(
"NVME-SMOKE: PASS hotplug-removed cycle=" <<
Dec << cycle);
423 NOTICE(
"NVME-SMOKE: READY hotplug-insert");
424 if (!waitHotplug(
true)) {
425 return fail(
"hotplug insertion timed out");
433 return fail(
"CRIPPLE_HDD must be disabled");
436 rootDisk = filesystem ? filesystem->getDisk() :
nullptr;
437 rootDisk = rootDisk ? rootDisk->physicalDisk() :
nullptr;
439 NOTICE(
"NVME-SMOKE: root namespace=" <<
Dec <<
static_cast<NvmeDisk*
>(rootDisk)->namespaceId());
441 if (success && virtualizationFixture) {
442 success = sriov(*virtualizationFixture);
444 if (success && hotplugFixture) {
447 if (!tested || !success)
448 return fail(
"scratch namespace checks");
449 NOTICE(
"NVME-SMOKE: PASS complete namespaces=" <<
Dec << tested);
455MODULE_INFO(
"nvme-smoke", &entry, &exit,
"nvme",
"mountroot",
"scsi");
static void foreach(Callback callback, Device *root=0)
void addChild(Device *pDevice)
virtual const String & getSpecificType()
void removeChild(size_t n)
size_t getSize() const override
Gets the size of the disk.
size_t getNativeBlockSize() const override
static Scheduler & instance()
virtual MUST_USE_RESULT bool retireCachePage(uint64_t location)
bool syncPages(const uint64_t *locations, size_t count) override
virtual void unpin(uint64_t location)
virtual MUST_USE_RESULT bool syncAll()
virtual BufferView read(uint64_t location)
virtual bool sync(uint64_t location, bool async)
Filesystem * getRootFilesystem() const