3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/machine/Disk.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/String.h"
10#include "pedigree/kernel/utilities/new"
12#include "modules/Module.h"
13#include "modules/drivers/common/ahci/AhciController.h"
14#include "modules/drivers/common/ahci/AhciDisk.h"
15#include "modules/system/vfs/Filesystem.h"
16#include "modules/system/vfs/VFS.h"
19constexpr uint64_t DiskBytes = 32 * 1024 * 1024;
20constexpr size_t HeaderBytes = 4096;
21constexpr uint8_t BaseSeed = 0x5a;
22constexpr uint8_t WriteSeed = 0xa5;
23constexpr uint64_t ErrorOffset = 24 * 1024 * 1024;
24constexpr char Magic[] =
"PEDIGREE-AHCI-SMOKE-v1";
30constexpr Range ReadRanges[] = {
31 {4096, 4096}, {65536 - 512, 8192}, {1024 * 1024 - 512, 128 * 1024}, {DiskBytes - 4096, 4096}};
32constexpr Range WriteRanges[] = {
33 {8 * 1024 * 1024 + 512, 8192}, {16 * 1024 * 1024 - 512, 128 * 1024}, {DiskBytes - 512, 512}};
36bool duplicateScratch =
false;
37uint8_t fixtureFlags = 0;
39bool fail(
const char* reason) {
40 ERROR(
"AHCI-SMOKE: FAIL " << reason);
44uint8_t pattern(uint64_t offset, uint8_t seed) {
45 return ((offset * 37) ^ (offset >> 8) ^ (offset >> 16) ^ seed) & 255;
48uint8_t expected(uint64_t offset,
bool written) {
49 if (offset < HeaderBytes) {
50 if (offset <
sizeof(Magic) - 1)
56 if (offset >= 40 && offset < 48)
57 return (DiskBytes >> ((offset - 40) * 8)) & 255;
63 for (
const Range& range : WriteRanges) {
64 if (offset >= range.offset && offset < range.offset + range.length)
65 return pattern(offset, WriteSeed);
68 return pattern(offset, BaseSeed);
72 if (device->getSpecificType() ==
String(
"ahci-disk")) {
74 if (disk->port() == 1 && disk->
getSize() == DiskBytes) {
76 duplicateScratch =
true;
84bool checkRange(
AhciDisk& disk,
const Range& range,
bool written) {
87 if (!disk.
readViews(range.offset, range.length, views))
88 return fail(
"readViews");
90 uint64_t offset = range.offset;
91 for (
size_t viewIndex = 0; viewIndex < views.count(); ++viewIndex) {
93 for (
size_t i = 0; i < view.size(); ++i) {
94 if (view[i] != expected(offset + i, written)) {
95 ERROR(
"AHCI-SMOKE: FAIL byte mismatch at " <<
Dec << (offset + i));
100 offset += view.size();
106constexpr size_t Workers = 12;
113int readWorker(
void* parameter) {
114 auto*
reader =
static_cast<Reader*
>(parameter);
115 reader->success = startReaders.acquireForCompletion(1, 30);
116 for (
size_t i = 0;
reader->success && i < 16; ++i) {
117 const uint64_t offset =
118 i == 0 ? 2 * 1024 * 1024 : 3 * 1024 * 1024 +
reader->index * 65536 + i * 4096;
119 reader->success = checkRange(*
reader->disk, {offset, 4096},
false);
121 return reader->success ? 0 : 1;
123bool concurrentReads(
AhciDisk& disk) {
124 Reader readers[Workers]{};
125 Thread* threads[Workers]{};
126 for (
size_t i = 0; i < Workers; ++i) {
127 readers[i] = {&disk, i,
false};
129 nullptr,
false,
false,
true);
130 if (!threads[i]->start())
131 FATAL(
"AHCI-SMOKE: reader start failed");
133 startReaders.release(Workers);
135 for (
size_t i = 0; i < Workers; ++i) {
136 if (!threads[i]->joinForCompletion())
137 FATAL(
"AHCI-SMOKE: reader join failed");
138 passed &= readers[i].success;
140 const size_t maximum = disk.controller()->maximumOutstanding(disk.port());
141 if (!passed || maximum < 2)
142 return fail(
"concurrent reads did not overlap commands");
143 NOTICE(
"AHCI-SMOKE: PASS concurrent-reads maximum-outstanding=" <<
Dec << maximum);
147bool writeRange(
AhciDisk& disk,
const Range& range) {
149 uint64_t pageOffsets[40];
151 if (!disk.
readViews(range.offset, range.length, views))
152 return fail(
"write readViews");
153 const size_t count = views.count();
154 uint64_t offset = range.offset;
156 for (
size_t viewIndex = 0; viewIndex < count; ++viewIndex) {
158 pageOffsets[viewIndex] = offset;
159 for (
size_t i = 0; i < view.size(); ++i)
160 view[i] = pattern(offset + i, WriteSeed);
162 if (!viewIndex && !disk.
sync(offset,
false))
164 offset += view.size();
168 return fail(
"checked disk sync");
170 for (
size_t i = 0; i < count; ++i) {
172 return fail(
"cache retirement");
177bool checkRejectedRanges(
AhciDisk& disk) {
180 disk.
unpin(DiskBytes);
181 return fail(
"end-of-disk read accepted");
185 const Range invalid[] = {{512, ~size_t{0}}, {DiskBytes - 512, 1024}};
186 for (
const Range& range : invalid) {
187 const bool accepted = disk.
readViews(range.offset, range.length, views);
188 const bool empty = views.empty();
190 if (accepted || !empty)
191 return fail(
"invalid readViews range accepted or retained views");
193 if (!disk.hasNoCacheLoans())
194 return fail(
"cache pins retained after rejected ranges");
200 if (!root.acquireUse(rootUse))
201 return fail(
"root admission after persistence tests");
203 return fail(
"root tail-sector geometry");
204 const uint64_t rootLba = root.
getSize() / 512 - 1;
205 uint8_t rootSector[512];
206 if (!root.controller()->readWrite(root.port(), rootLba, 1, rootSector,
sizeof(rootSector),
false))
207 return fail(
"direct root reference read");
209 constexpr uint64_t RetryOffset = 28 * 1024 * 1024;
211 return fail(
"error-test cache retirement");
212 const auto started = Time::getTicks();
214 const auto elapsed = Time::getTicks() - started;
216 disk.
unpin(ErrorOffset);
217 return fail(
"injected read error returned data");
219 if (elapsed >= 5 * Time::Multiplier::Second || !disk.hasNoCacheLoans())
220 return fail(
"injected read error was slow or retained cache pins");
221 NOTICE(
"AHCI-SMOKE: PASS transport-error milliseconds="
222 <<
Dec << elapsed / Time::Multiplier::Millisecond);
224 const auto retryStarted = Time::getTicks();
226 const auto retryElapsed = Time::getTicks() - retryStarted;
228 disk.
unpin(RetryOffset);
229 return fail(
"offline scratch disk accepted an uncached read");
231 if (retryElapsed >= 5 * Time::Multiplier::Second || !disk.hasNoCacheLoans())
232 return fail(
"offline rejection was slow or retained cache pins");
233 NOTICE(
"AHCI-SMOKE: PASS offline-rejection milliseconds="
234 <<
Dec << retryElapsed / Time::Multiplier::Millisecond);
239 if (!root.controller()->readWrite(root.port(), rootLba, 1, after,
sizeof(after),
false))
240 return fail(
"direct root read after scratch error");
241 bool unchanged =
true;
242 for (
size_t i = 0; unchanged && i <
sizeof(rootSector); ++i)
243 unchanged = after[i] == rootSector[i];
245 return fail(
"root sector changed after scratch error");
246 NOTICE(
"AHCI-SMOKE: PASS root-after-error port=0 direct-sector=" <<
Dec << rootLba);
252 return fail(
"CRIPPLE_HDD must be disabled");
255 Disk* root = filesystem ? filesystem->
getDisk() :
nullptr;
256 root = root ? root->physicalDisk() :
nullptr;
258 NOTICE(
"AHCI-SMOKE: skipped (root uses another transport)");
263 if (rootDisk->port() != 0 || !controller ||
265 return fail(
"root AHCI controller or port");
268 duplicateScratch =
false;
270 if (!scratch || duplicateScratch || scratch->controller() != controller || scratch == rootDisk ||
271 (scratch->getNativeBlockSize() != 512 && scratch->getNativeBlockSize() != 4096))
272 return fail(
"unique scratch disk on AHCI port 1");
274 if (!scratch->acquireUse(use))
275 return fail(
"scratch disk admission");
276 const size_t initialCompletions = controller->interruptCompletions();
277 const BufferView fixtureHeader = scratch->read(0);
279 return fail(
"fixture header read");
280 fixtureFlags = fixtureHeader.size() > 48 ? fixtureHeader[48] : 255;
282 if (fixtureFlags > 1)
283 return fail(
"unsupported fixture flags");
285 if (!checkRange(*scratch, {0, HeaderBytes},
false))
287 NOTICE(
"AHCI-SMOKE: PASS fixture-identification");
288 if (!checkRejectedRanges(*scratch))
290 NOTICE(
"AHCI-SMOKE: PASS range-rejection");
291 for (
const Range& range : ReadRanges) {
292 if (!checkRange(*scratch, range,
false))
295 NOTICE(
"AHCI-SMOKE: PASS patterned-reads");
296 for (
size_t i = 0; i < 2; ++i) {
297 if (!scratch->sync(ReadRanges[0].offset,
false) || !scratch->syncAll()) {
298 return fail(
"clean page sync");
301 NOTICE(
"AHCI-SMOKE: PASS clean-page-sync");
302 if (!concurrentReads(*scratch))
304 for (
const Range& range : WriteRanges) {
305 if (!writeRange(*scratch, range))
308 NOTICE(
"AHCI-SMOKE: PASS writes-and-sync");
309 for (
const Range& range : WriteRanges) {
310 if (!checkRange(*scratch, range,
true))
313 for (
const Range& range : ReadRanges) {
314 if (!checkRange(*scratch, range,
true))
317 NOTICE(
"AHCI-SMOKE: PASS uncached-rereads");
318 NOTICE(
"AHCI-SMOKE: PASS ahci-root-mount port=0");
319 const size_t completions = controller->interruptCompletions() - initialCompletions;
321 return fail(
"no interrupt completions during smoke");
322 NOTICE(
"AHCI-SMOKE: PASS interrupt-completions count=" <<
Dec << completions);
323 if (fixtureFlags && !checkInjectedError(*scratch, *rootDisk))
325 NOTICE(
"AHCI-SMOKE: PASS complete");
333MODULE_INFO(
"ahci-smoke", &entry, &exit,
"ahci",
"mountroot",
"scsi");
size_t getSize() const override
Gets the size of the disk.
static void foreach(Callback callback, Device *root=0)
virtual const String & getSpecificType()
void unpinViews(uint64_t location, BufferViewSequence &views)
MUST_USE_RESULT bool readViews(uint64_t location, size_t length, BufferViewSequence &views)
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