The Pedigree Project 0.1
scsi-write-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/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/TargetInfo.h"
11#include "pedigree/kernel/compiler.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Semaphore.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/time/Time.h"
16#include "pedigree/kernel/utilities/Cache.h"
17#include "pedigree/kernel/utilities/utility.h"
18
19#include "modules/drivers/common/ata/AtaWriteCache.h"
20#include "modules/drivers/common/scsi/ScsiController.h"
21#include "modules/drivers/common/scsi/ScsiDisk.h"
22
23namespace {
24constexpr size_t PageBytes = TargetInfo::getPageSize();
25constexpr uint64_t AtaOwnershipLocation = 0;
26constexpr uint64_t AtaDrainingLocation = PageBytes;
27constexpr uint64_t FailedWriteLocation = 2 * PageBytes;
28constexpr uint64_t SuccessfulWriteLocation = 3 * PageBytes;
29constexpr uint64_t MissingDirectLocation = 4 * PageBytes;
30constexpr uint64_t EditingDirectLocation = 5 * PageBytes;
31constexpr uint64_t SuccessfulDirectLocation = 6 * PageBytes;
32constexpr uint64_t FailedDirectLocation = 7 * PageBytes;
33constexpr uint64_t UnreadyDirectLocation = 8 * PageBytes;
34constexpr uint64_t ShortDirectLocation = 9 * PageBytes;
35constexpr uint64_t PinnedDirectLocation = 10 * PageBytes;
36constexpr uint64_t CancelledDirectLocation = 11 * PageBytes;
37constexpr uint64_t CanonicalDirectLocation = 12 * PageBytes;
38constexpr uint64_t CheckedSyncLocation = 13 * PageBytes;
39constexpr uint64_t ReadFirstLocation = 16 * PageBytes;
40constexpr uint64_t RejectedReadLocation = 18 * PageBytes;
41constexpr uint64_t RetireFirstExtent = 24 * PageBytes;
42constexpr uint64_t RetireFirstInterior = RetireFirstExtent + PageBytes;
43constexpr uint64_t NonoverlapReadLocation = 28 * PageBytes;
44constexpr uint64_t RecheckReadLocation = 32 * PageBytes;
45constexpr uint64_t LookupPauseLocation = 36 * PageBytes;
46constexpr uint8_t CdDvdPeripheral = 0x05;
47
48enum class WriteMode { Initialising, FailAll, PassWrite12, UnitNotReady };
49enum class SyncMode { Pass10, Pass16, FailAll, FailWhole };
50enum class RequestEvent : uint8_t { Read = 1, Direct = 2 };
51
52class ScriptedScsiController final : public ScsiController {
53 public:
54 explicit ScriptedScsiController(size_t capacityBytes = 64 * PageBytes,
55 size_t nativeBlockBytes = 512, bool optical = false)
57 m_Mode(WriteMode::Initialising),
58 m_WriteOpcodes(),
59 m_WriteCount(0),
60 m_SyncMode(SyncMode::Pass10),
61 m_SyncOpcodes(),
62 m_SyncWhole(),
63 m_SyncCount(0),
64 m_WritesAtLastSync(0),
65 m_HoldNextSync(0),
66 m_SyncEntered(0, false),
67 m_SyncRelease(0, false),
68 m_SyncRequestLocations(),
69 m_SyncRequestCount(0),
70 m_UnitReadyCount(0),
71 m_LastWriteBuffer(0),
72 m_DirectRequestCount(0),
73 m_DirectDisk(0),
74 m_DirectLocation(0),
75 m_DirectPage(0),
76 m_HoldNextRead(0),
77 m_ReadHoldsRemaining(0),
78 m_DelegateHeldRead(0),
79 m_ReadEntered(0, false),
80 m_ReadRelease(0, false),
81 m_ReadRequestCount(0),
82 m_ReadCommandCount(0),
83 m_ReadTocCount(0),
84 m_LastReadOpcode(0),
85 m_RequestEvents(),
86 m_RequestEventCount(0),
87 m_CapacityBytes(capacityBytes),
88 m_NativeBlockBytes(nativeBlockBytes),
89 m_Optical(optical),
90 m_LastReadBytes(0),
91 m_LastWriteBytes(0),
92 m_Valid(true) {}
93
94 bool sendCommand(size_t nUnit, uintptr_t pCommand, uint8_t nCommandSize, uintptr_t pRespBuffer,
95 uint16_t nRespBytes, bool bWrite) override {
96 if (nUnit || !pCommand || !nCommandSize) {
97 m_Valid = false;
98 return false;
99 }
100
101 const uint8_t opcode = *reinterpret_cast<const uint8_t*>(pCommand);
102 switch (opcode) {
103 case 0x00:
104 if (bWrite || pRespBuffer || nRespBytes || nCommandSize != 6) {
105 m_Valid = false;
106 return false;
107 }
108 if (m_Mode != WriteMode::Initialising)
109 ++m_UnitReadyCount;
110 return m_Mode != WriteMode::UnitNotReady;
111 case 0x12: {
112 if (bWrite || !pRespBuffer || nRespBytes != sizeof(ScsiDisk::Inquiry) ||
113 nCommandSize != 6) {
114 m_Valid = false;
115 return false;
116 }
117 auto* inquiry = reinterpret_cast<ScsiDisk::Inquiry*>(pRespBuffer);
118 ByteSet(inquiry, 0, sizeof(*inquiry));
119 inquiry->Peripheral = m_Optical ? CdDvdPeripheral : 0;
120 return true;
121 }
122 case 0x25: {
123 if (bWrite || !pRespBuffer || nRespBytes != sizeof(ScsiDisk::Capacity) ||
124 nCommandSize != 10 || !m_NativeBlockBytes || !m_CapacityBytes ||
125 (m_CapacityBytes % m_NativeBlockBytes)) {
126 m_Valid = false;
127 return false;
128 }
129 auto* capacity = reinterpret_cast<ScsiDisk::Capacity*>(pRespBuffer);
130 capacity->LBA =
131 HOST_TO_BIG32(static_cast<uint32_t>((m_CapacityBytes / m_NativeBlockBytes) - 1));
132 capacity->BlockSize = HOST_TO_BIG32(static_cast<uint32_t>(m_NativeBlockBytes));
133 return true;
134 }
135 case 0x43: {
136 if (!m_Optical || bWrite || !pRespBuffer || nRespBytes != m_NativeBlockBytes ||
137 nCommandSize != 10) {
138 m_Valid = false;
139 return false;
140 }
141
142 auto* toc = reinterpret_cast<uint8_t*>(pRespBuffer);
143 ByteSet(toc, 0, nRespBytes);
144 toc[1] = 10; // Header plus one eight-byte track descriptor.
145 toc[2] = 1;
146 toc[3] = 1;
147 toc[5] = 0x14; // ADR=1, data track.
148 toc[6] = 1;
149 m_ReadTocCount += 1;
150 return true;
151 }
152 case 0x28:
153 case 0xa8:
154 case 0x88:
155 return handleRead(opcode, nCommandSize, pRespBuffer, nRespBytes, bWrite);
156 case 0x2a:
157 case 0xaa:
158 case 0x8a:
159 return handleWrite(opcode, nCommandSize, pRespBuffer, nRespBytes, bWrite);
160 case 0x35:
161 case 0x91: {
162 if (bWrite || pRespBuffer || nRespBytes || nCommandSize != (opcode == 0x35 ? 10 : 16) ||
163 m_SyncCount >= sizeof(m_SyncOpcodes)) {
164 m_Valid = false;
165 return false;
166 }
167 const uint8_t* command = reinterpret_cast<const uint8_t*>(pCommand);
168 bool whole = true;
169 for (size_t i = 1; i < nCommandSize; ++i) {
170 whole = whole && command[i] == 0;
171 }
172 m_WritesAtLastSync = m_WriteCount;
173 m_SyncWhole[m_SyncCount] = whole;
174 m_SyncOpcodes[m_SyncCount++] = opcode;
175 if (m_HoldNextSync.compareAndSwap(1, 0)) {
176 m_SyncEntered.release();
177 if (!m_SyncRelease.acquireForCompletion()) {
178 m_Valid = false;
179 return false;
180 }
181 }
182 return m_SyncMode == SyncMode::Pass10 ||
183 (m_SyncMode == SyncMode::Pass16 && opcode == 0x91) ||
184 (m_SyncMode == SyncMode::FailWhole && !whole);
185 }
186 default:
187 m_Valid = false;
188 return false;
189 }
190 }
191
192 void beginWrites(WriteMode mode) {
193 m_Mode = mode;
194 m_WriteCount = 0;
195 m_UnitReadyCount = 0;
196 m_LastWriteBuffer = 0;
197 m_DirectRequestCount = 0;
198 m_DirectDisk = 0;
199 m_DirectLocation = 0;
200 m_DirectPage = 0;
201 m_LastWriteBytes = 0;
202 m_SyncCount = 0;
203 m_WritesAtLastSync = 0;
204 m_SyncRequestCount = 0;
205 }
206
207 void beginSync(SyncMode mode) {
208 m_SyncMode = mode;
209 m_SyncCount = 0;
210 m_WritesAtLastSync = 0;
211 m_SyncRequestCount = 0;
212 }
213
214 void holdNextSync() {
215 m_HoldNextSync = 1;
216 }
217
218 bool waitForHeldSync() {
219 return m_SyncEntered.acquireForCompletion(1, 0, 500000);
220 }
221
222 void releaseHeldSync() {
223 m_HoldNextSync = 0;
224 m_SyncRelease.release();
225 }
226
227 bool syncTraceMatches(const uint8_t* expected, size_t count) const {
228 return m_Valid && m_SyncCount == count && !MemoryCompare(m_SyncOpcodes, expected, count);
229 }
230
231 bool syncGeometryMatches(const bool* whole, size_t count) const {
232 return m_Valid && m_SyncCount == count &&
233 !MemoryCompare(m_SyncWhole, whole, count * sizeof(bool));
234 }
235
236 bool syncRequestsMatch(const uint64_t* locations, size_t count) const {
237 return m_Valid && m_SyncRequestCount == count &&
238 !MemoryCompare(m_SyncRequestLocations, locations, count * sizeof(uint64_t));
239 }
240
241 void beginRequestTrace() {
242 m_ReadRequestCount = 0;
243 m_ReadCommandCount = 0;
244 m_ReadTocCount = 0;
245 m_LastReadOpcode = 0;
246 m_RequestEventCount = 0;
247 m_LastReadBytes = 0;
248 }
249
250 void holdNextRead() {
251 const size_t staleEntries = m_ReadEntered.drainAvailable();
252 const size_t staleReleases = m_ReadRelease.drainAvailable();
253 (void)staleEntries;
254 (void)staleReleases;
255 m_DelegateHeldRead = 0;
256 m_HoldNextRead = 1;
257 m_ReadHoldsRemaining = 1;
258 }
259
260 void holdFollowingRead() {
261 m_ReadHoldsRemaining += 1;
262 }
263
264 bool waitForHeldRead() {
265 return m_ReadEntered.acquireForCompletion(1, 0, 500000);
266 }
267
268 void releaseHeldRead(bool delegate) {
269 m_DelegateHeldRead = delegate ? 1 : 0;
270 m_ReadRelease.release();
271 }
272
273 void disarmReadHold() {
274 m_ReadHoldsRemaining = 0;
275 m_HoldNextRead = 0;
276 const size_t staleEntries = m_ReadEntered.drainAvailable();
277 const size_t staleReleases = m_ReadRelease.drainAvailable();
278 m_ReadRelease.release();
279 (void)staleEntries;
280 (void)staleReleases;
281 }
282
283 size_t readRequestCount() const {
284 return m_ReadRequestCount.value();
285 }
286
287 size_t readCommandCount() const {
288 return m_ReadCommandCount.value();
289 }
290
291 size_t readTocCount() const {
292 return m_ReadTocCount.value();
293 }
294
295 uint8_t lastReadOpcode() const {
296 return m_LastReadOpcode;
297 }
298
299 size_t directRequestCount() const {
300 return m_DirectRequestCount;
301 }
302
303 bool requestTraceMatches(const RequestEvent* expected, size_t count) const {
304 return m_Valid && m_RequestEventCount == count &&
305 !MemoryCompare(m_RequestEvents, expected, count * sizeof(RequestEvent));
306 }
307
308 bool writeTraceMatches(const uint8_t* expected, size_t count) const {
309 return m_Valid && m_WriteCount == count && !MemoryCompare(m_WriteOpcodes, expected, count);
310 }
311
312 bool directTupleMatches(const ScsiDisk* disk, uint64_t location, uintptr_t page) const {
313 return m_Valid && m_DirectRequestCount == 1 &&
314 m_DirectDisk == reinterpret_cast<uint64_t>(disk) && m_DirectLocation == location &&
315 m_DirectPage == page;
316 }
317
318 bool hasNoDirectActivity() const {
319 return m_Valid && !m_DirectRequestCount && !m_UnitReadyCount && !m_WriteCount;
320 }
321
322 size_t unitReadyCount() const {
323 return m_UnitReadyCount;
324 }
325
326 size_t writeCount() const {
327 return m_WriteCount;
328 }
329
330 size_t writesAtLastSync() const {
331 return m_WritesAtLastSync;
332 }
333
334 uintptr_t lastWriteBuffer() const {
335 return m_LastWriteBuffer;
336 }
337
338 size_t lastReadBytes() const {
339 return m_LastReadBytes;
340 }
341
342 size_t lastWriteBytes() const {
343 return m_LastWriteBytes;
344 }
345
346 uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5,
347 uint64_t p6, uint64_t p7, uint64_t p8) override {
348 if (p1 == SCSI_REQUEST_READ) {
349 appendRequestEvent(RequestEvent::Read);
350 m_ReadRequestCount += 1;
351 size_t remaining = m_ReadHoldsRemaining.value();
352 bool hold = false;
353 while (remaining && !hold) {
354 hold = m_ReadHoldsRemaining.compareAndSwap(remaining, remaining - 1);
355 remaining = m_ReadHoldsRemaining.value();
356 }
357 if (hold) {
358 m_HoldNextRead = 2;
359 m_ReadEntered.release();
360 if (!m_ReadRelease.acquireForCompletion()) {
361 m_Valid = false;
362 m_HoldNextRead = 0;
363 return 0;
364 }
365 const bool delegate = static_cast<size_t>(m_DelegateHeldRead) != 0;
366 m_HoldNextRead = 0;
367 if (!delegate)
368 return 0;
369 }
370 }
371 if (p1 == SCSI_REQUEST_SYNC) {
372 if (m_SyncRequestCount >=
373 sizeof(m_SyncRequestLocations) / sizeof(m_SyncRequestLocations[0])) {
374 m_Valid = false;
375 return 0;
376 }
377 m_SyncRequestLocations[m_SyncRequestCount++] = p3;
378 }
379 if (p1 == SCSI_REQUEST_WRITE_DIRECT) {
380 appendRequestEvent(RequestEvent::Direct);
381 ++m_DirectRequestCount;
382 m_DirectDisk = p2;
383 m_DirectLocation = p3;
384 m_DirectPage = p4;
385 }
386 return ScsiController::executeRequest(p1, p2, p3, p4, p5, p6, p7, p8);
387 }
388
389 protected:
390 size_t getNumUnits() override {
391 return 1;
392 }
393
394 private:
395 bool handleRead(uint8_t opcode, uint8_t commandSize, uintptr_t response, uint16_t responseBytes,
396 bool write) {
397 const uint8_t expectedSize = opcode == 0x28 ? 10 : (opcode == 0xa8 ? 12 : 16);
398 if (write || !response || !responseBytes || (responseBytes % m_NativeBlockBytes) ||
399 commandSize != expectedSize) {
400 m_Valid = false;
401 return false;
402 }
403
404 ByteSet(reinterpret_cast<void*>(response), 0x3c, responseBytes);
405 m_LastReadBytes = responseBytes;
406 m_LastReadOpcode = opcode;
407 m_ReadCommandCount += 1;
408 return true;
409 }
410
411 bool handleWrite(uint8_t opcode, uint8_t commandSize, uintptr_t response, uint16_t responseBytes,
412 bool write) {
413 const uint8_t expectedSize = opcode == 0x2a ? 10 : (opcode == 0xaa ? 12 : 16);
414 if (m_Mode == WriteMode::Initialising || !write || !response || !responseBytes ||
415 (responseBytes % m_NativeBlockBytes) || commandSize != expectedSize ||
416 m_WriteCount >= sizeof(m_WriteOpcodes)) {
417 m_Valid = false;
418 return false;
419 }
420
421 m_WriteOpcodes[m_WriteCount++] = opcode;
422 m_LastWriteBuffer = response;
423 m_LastWriteBytes = responseBytes;
424 return m_Mode == WriteMode::PassWrite12 && opcode == 0xaa;
425 }
426
427 void appendRequestEvent(RequestEvent event) {
428 if (m_RequestEventCount >= sizeof(m_RequestEvents) / sizeof(m_RequestEvents[0])) {
429 m_Valid = false;
430 return;
431 }
432 m_RequestEvents[m_RequestEventCount++] = event;
433 }
434
435 WriteMode m_Mode;
436 uint8_t m_WriteOpcodes[32];
437 size_t m_WriteCount;
438 SyncMode m_SyncMode;
439 uint8_t m_SyncOpcodes[8];
440 bool m_SyncWhole[8];
441 size_t m_SyncCount;
442 size_t m_WritesAtLastSync;
443 Atomic<size_t> m_HoldNextSync;
444 Semaphore m_SyncEntered;
445 Semaphore m_SyncRelease;
446 uint64_t m_SyncRequestLocations[8];
447 size_t m_SyncRequestCount;
448 size_t m_UnitReadyCount;
449 uintptr_t m_LastWriteBuffer;
450 size_t m_DirectRequestCount;
451 uint64_t m_DirectDisk;
452 uint64_t m_DirectLocation;
453 uintptr_t m_DirectPage;
454 Atomic<size_t> m_HoldNextRead;
455 Atomic<size_t> m_ReadHoldsRemaining;
456 Atomic<size_t> m_DelegateHeldRead;
457 Semaphore m_ReadEntered;
458 Semaphore m_ReadRelease;
459 Atomic<size_t> m_ReadRequestCount;
460 Atomic<size_t> m_ReadCommandCount;
461 Atomic<size_t> m_ReadTocCount;
462 uint8_t m_LastReadOpcode;
463 RequestEvent m_RequestEvents[16];
464 size_t m_RequestEventCount;
465 size_t m_CapacityBytes;
466 size_t m_NativeBlockBytes;
467 bool m_Optical;
468 size_t m_LastReadBytes;
469 size_t m_LastWriteBytes;
470 bool m_Valid;
471};
472
473class HostedScsiDisk final : public ScsiDisk {
474 public:
475 HostedScsiDisk()
476 : ScsiDisk(),
477 m_OverrideDirectResult(false),
478 m_DirectResult(0),
479 m_ObserveUnpin(false),
480 m_UnpinCalls(0),
481 m_LastUnpinLocation(0),
482 m_CacheFillSize(PageBytes),
483 m_BufferTransfersEnabled(false),
484 m_BufferTransferCount(0) {}
485
486 bool preparePage(uint64_t location) {
487 bool alreadyExisted = false;
488 const uintptr_t buffer = getCache().insert(location, &alreadyExisted);
489 if (!buffer || alreadyExisted)
490 return false;
491 ByteSet(reinterpret_cast<void*>(buffer), 0x5a, PageBytes);
492 getCache().markNoLongerEditing(location);
493 return true;
494 }
495
496 bool prepareEditingPage(uint64_t location) {
497 bool alreadyExisted = false;
498 return getCache().insert(location, &alreadyExisted) && !alreadyExisted;
499 }
500
501 bool discardEditingPage(uint64_t location) {
502 return getCache().discardEditing(location);
503 }
504
505 bool takeAtaQueuedWritePage(uint64_t location) {
506 const uintptr_t buffer = ataTakeQueuedWritePage(getCache(), location);
507 if (!buffer)
508 return false;
509 CachePageGuard guard(getCache(), location);
510 return true;
511 }
512
513 bool missesAtaQueuedWritePage(uint64_t location) {
514 const uintptr_t buffer = ataTakeQueuedWritePage(getCache(), location);
515 if (buffer) {
516 CachePageGuard guard(getCache(), location);
517 return false;
518 }
519 return true;
520 }
521
522 bool evictPage(uint64_t location) {
523 return getCache().evict(location);
524 }
525
526 void checksumPage(uint64_t location) {
527 getCache().triggerChecksum(location);
528 }
529
530 void pauseBackgroundWriteback() {
531 getCache().startAtomic();
532 }
533
534 void resumeBackgroundWriteback() {
535 getCache().endAtomic();
536 }
537
538 bool runBackgroundWriteback() {
539 getCache().timer(CACHE_WRITEBACK_PERIOD * 1000000ULL);
540 return CacheManager::instance().drain();
541 }
542
543 bool evictRange(uint64_t location, size_t length) {
544 bool evicted = true;
545 for (size_t offset = 0; offset < length; offset += PageBytes) {
546 if (hasPage(location + offset) && !getCache().evict(location + offset))
547 evicted = false;
548 }
549 return evicted;
550 }
551
552 bool retirePage(uint64_t location, Cache::retirement_writeback_t callback, void* context) {
553 return getCache().retireWriteback(location, callback, context);
554 }
555
556 bool hasPage(uint64_t location) {
557 return getCache().exists(location, PageBytes);
558 }
559
560 uintptr_t pageAddress(uint64_t location) {
561 const uintptr_t page = getCache().lookup(location);
562 if (page)
563 getCache().release(location);
564 return page;
565 }
566
567 void overrideDirectResult(uint64_t result) {
568 m_DirectResult = result;
569 m_OverrideDirectResult = true;
570 }
571
572 void restoreDirectResult() {
573 m_OverrideDirectResult = false;
574 }
575
576 void setCacheFillSize(size_t fillSize) {
577 m_CacheFillSize = fillSize;
578 }
579
580 void enableBufferTransfers() {
581 m_BufferTransfersEnabled = true;
582 }
583
584 size_t bufferTransferCount() const {
585 return m_BufferTransferCount;
586 }
587
588 void beginReadBufferBatch(uint64_t failedLocation = ~uint64_t{0}) {
589 m_BufferTransferCount = 0;
590 m_BufferBatchCount = 0;
591 m_LastBufferBatchSize = 0;
592 m_FailedBufferLocation = failedLocation;
593 }
594
595 size_t bufferBatchCount() const {
596 return m_BufferBatchCount;
597 }
598
599 bool bufferBatchMatches(const uint64_t* locations, size_t count) const {
600 return m_BufferBatchCount == 1 && m_LastBufferBatchSize == count &&
601 !MemoryCompare(m_BufferBatchLocations, locations, count * sizeof(uint64_t));
602 }
603
604 void beginUnpinObservation() {
605 m_UnpinCalls = 0;
606 m_LastUnpinLocation = 0;
607 m_ObserveUnpin = true;
608 }
609
610 size_t endUnpinObservation() {
611 m_ObserveUnpin = false;
612 return m_UnpinCalls;
613 }
614
615 uint64_t lastUnpinLocation() const {
616 return m_LastUnpinLocation;
617 }
618
619 void unpin(uint64_t location) override {
620 if (m_ObserveUnpin) {
621 ++m_UnpinCalls;
622 m_LastUnpinLocation = location;
623 return;
624 }
625 ScsiDisk::unpin(location);
626 }
627
628 uint64_t doWriteDirect(uint64_t location, uintptr_t page) override {
629 if (m_OverrideDirectResult)
630 return m_DirectResult;
631 return ScsiDisk::doWriteDirect(location, page);
632 }
633
634 protected:
635 bool supportsBufferTransfers() const override {
636 return m_BufferTransfersEnabled;
637 }
638
639 bool transferBuffer(uint64_t location, void* buffer, size_t length, bool writing) override {
640 ++m_BufferTransferCount;
641 if (!writing && location == m_FailedBufferLocation)
642 return false;
643 if (!writing)
644 ByteSet(buffer, 0xa6, length);
645 return true;
646 }
647
648 bool transferReadBuffers(ReadBuffer* buffers, size_t count) override {
649 ++m_BufferBatchCount;
650 m_LastBufferBatchSize = count;
651 for (size_t i = 0; i < count; ++i)
652 m_BufferBatchLocations[i] = buffers[i].location;
653 return ScsiDisk::transferReadBuffers(buffers, count);
654 }
655
656 size_t getCacheFillSize() const override {
657 return m_CacheFillSize;
658 }
659
660 private:
661 bool m_OverrideDirectResult;
662 uint64_t m_DirectResult;
663 bool m_ObserveUnpin;
664 size_t m_UnpinCalls;
665 uint64_t m_LastUnpinLocation;
666 size_t m_CacheFillSize;
667 bool m_BufferTransfersEnabled;
668 size_t m_BufferTransferCount;
669 size_t m_BufferBatchCount = 0;
670 size_t m_LastBufferBatchSize = 0;
671 uint64_t m_BufferBatchLocations[Disk::MaxReadBuffers] = {};
672 uint64_t m_FailedBufferLocation = ~uint64_t{0};
673};
674
675struct RetirementResult {
676 bool balanced;
677 bool cleaned;
678};
679
680RetirementResult retireAndRepair(HostedScsiDisk& disk, uint64_t location,
681 bool repairOneTransferredPin) {
682 if (disk.evictPage(location))
683 return {true, true};
684 if (!repairOneTransferredPin)
685 return {false, false};
686
687 disk.unpin(location);
688 return {false, disk.evictPage(location)};
689}
690
691struct Fixture {
692 Fixture() : controller(), disk(), ready(false) {
693 ready = disk.initialise(&controller, 0);
694 }
695
696 ScriptedScsiController controller;
697 HostedScsiDisk disk;
698 bool ready;
699};
700
701bool waitUntilQueuedAt(Thread* thread, size_t debugState, uintptr_t debugAddress) {
702 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
703 while (Time::getTicks() < deadline) {
704 Thread::WaitDebugInfo info = {};
705 uintptr_t address = 0;
706 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
707 thread->getDebugState(address) == debugState && address == debugAddress) {
708 return true;
709 }
711 }
712 return false;
713}
714
715bool waitUntilSet(Atomic<size_t>& value) {
716 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
717 while (Time::getTicks() < deadline) {
718 if (static_cast<size_t>(value))
719 return true;
721 }
722 return static_cast<size_t>(value) != 0;
723}
724
725bool waitForAdmissionOrRead(Thread* thread, uint64_t location, ScriptedScsiController& controller,
726 size_t priorReadCount, bool& admissionWait) {
727 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
728 while (Time::getTicks() < deadline) {
729 Thread::WaitDebugInfo info = {};
730 uintptr_t address = 0;
731 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
732 thread->getDebugState(address) == Thread::CondWait) {
733 admissionWait = address == location;
734 return true;
735 }
736 if (controller.readRequestCount() > priorReadCount) {
737 admissionWait = false;
738 return true;
739 }
741 }
742 admissionWait = false;
743 return false;
744}
745
746bool waitForRetireAdmissionOrDrain(Thread* retirer, HostedScsiDisk& disk, uint64_t location,
747 bool& admissionWait) {
748 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
749 while (Time::getTicks() < deadline) {
750 Thread::WaitDebugInfo info = {};
751 uintptr_t address = 0;
752 if (retirer->getWaitDebugInfo(info) && info.queue && info.queued &&
753 retirer->getDebugState(address) == Thread::CondWait && address == location) {
754 admissionWait = true;
755 return true;
756 }
757 if (!disk.pageAddress(location)) {
758 admissionWait = false;
759 return true;
760 }
762 }
763 admissionWait = false;
764 return false;
765}
766
767struct ReadContext {
768 ReadContext(HostedScsiDisk* pDisk, uint64_t readLocation)
769 : disk(pDisk), location(readLocation), returned(0), result(0) {}
770
771 HostedScsiDisk* disk;
772 uint64_t location;
773 Atomic<size_t> returned;
774 bool result;
775};
776
777int readPage(void* parameter) {
778 auto* context = reinterpret_cast<ReadContext*>(parameter);
779 const BufferView result = context->disk->read(context->location);
780 context->result = static_cast<bool>(result);
781 if (result)
782 context->disk->unpin(context->location);
783 context->returned += 1;
784 return 0;
785}
786
787Thread* startRead(ReadContext& context, const char* name) {
788 Thread* thread = new Thread(Scheduler::instance().getKernelProcess(), readPage, &context, nullptr,
789 false, true);
790 thread->setName(String(name));
791 return thread;
792}
793
794struct ReadRequestPause {
795 ReadRequestPause(HostedScsiDisk* pDisk, uint64_t pageLocation)
796 : disk(pDisk), location(pageLocation), state(0), entered(0, false), release(0, false) {}
797
798 void arm() {
799 const size_t staleEntries = entered.drainAvailable();
800 const size_t staleReleases = release.drainAvailable();
801 (void)staleEntries;
802 (void)staleReleases;
803 state = 1;
804 ScsiDisk::setHostedReadRequestHookForTest(pause, this);
805 }
806
807 bool wait() {
808 return entered.acquireForCompletion(1, 0, 500000);
809 }
810
811 void resume() {
812 release.release();
813 }
814
815 void clear() {
816 ScsiDisk::setHostedReadRequestHookForTest(nullptr, nullptr);
817 state = 0;
818 }
819
820 static void pause(ScsiDisk* pDisk, uint64_t pageLocation, void* parameter) {
821 auto* context = reinterpret_cast<ReadRequestPause*>(parameter);
822 if (!context || pDisk != context->disk || pageLocation != context->location ||
823 !context->state.compareAndSwap(1, 2)) {
824 return;
825 }
826
827 context->entered.release();
828 if (!context->release.acquireForCompletion())
829 context->state = 0;
830 context->state = 0;
831 }
832
833 HostedScsiDisk* disk;
834 uint64_t location;
835 Atomic<size_t> state;
836 Semaphore entered;
837 Semaphore release;
838};
839
840struct AtaRetirementContext {
841 AtaRetirementContext(HostedScsiDisk* pDisk, uint64_t pageLocation, uintptr_t pageBuffer)
842 : disk(pDisk),
843 location(pageLocation),
844 page(pageBuffer),
845 callbacks(0),
846 argumentsValid(0),
847 returned(0),
848 succeeded(0) {}
849
850 HostedScsiDisk* disk;
851 uint64_t location;
852 uintptr_t page;
853 Atomic<size_t> callbacks;
854 Atomic<size_t> argumentsValid;
855 Atomic<size_t> returned;
856 Atomic<size_t> succeeded;
857};
858
859bool retireAtaPageCallback(uintptr_t key, uintptr_t page, void* parameter) {
860 auto* context = reinterpret_cast<AtaRetirementContext*>(parameter);
861 context->callbacks += 1;
862 context->argumentsValid = key == context->location && page == context->page;
863 return true;
864}
865
866int retireAtaPage(void* parameter) {
867 auto* context = reinterpret_cast<AtaRetirementContext*>(parameter);
868 if (context->disk->retirePage(context->location, retireAtaPageCallback, context))
869 context->succeeded += 1;
870 context->returned += 1;
871 return 0;
872}
873
874bool ataDrainingMissReleasesQueuedPin(Fixture& fixture) {
875 const bool prepared = fixture.ready && fixture.disk.preparePage(AtaDrainingLocation);
876 const uintptr_t page = prepared ? fixture.disk.pageAddress(AtaDrainingLocation) : 0;
877 const bool pinned = page && fixture.disk.pin(AtaDrainingLocation);
878
879 AtaRetirementContext context(&fixture.disk, AtaDrainingLocation, page);
880 Thread* retirer = nullptr;
881 if (pinned) {
882 retirer = new Thread(Scheduler::instance().getKernelProcess(), retireAtaPage, &context, nullptr,
883 false, true);
884 retirer->setName("hosted ATA queued-write page retirer");
885 }
886
887 const bool drainPublished =
888 retirer && waitUntilQueuedAt(retirer, Thread::CallbackDrain, AtaDrainingLocation);
889 const bool missed = drainPublished && fixture.disk.missesAtaQueuedWritePage(AtaDrainingLocation);
890 const bool completedWithoutRepair = missed && waitUntilSet(context.returned);
891 bool repaired = false;
892 if (pinned && !static_cast<size_t>(context.returned)) {
893 fixture.disk.unpin(AtaDrainingLocation);
894 repaired = true;
895 }
896 const bool completed =
897 retirer && (static_cast<size_t>(context.returned) || waitUntilSet(context.returned));
898 const bool joined = retirer && retirer->joinForCompletion();
899 const bool retired = completed && joined && context.succeeded == 1 && context.callbacks == 1 &&
900 context.argumentsValid == 1 && !fixture.disk.hasPage(AtaDrainingLocation);
901 return prepared && page && pinned && drainPublished && missed && completedWithoutRepair &&
902 !repaired && retired;
903}
904
905bool ataQueuedWriteCacheOwnership(Fixture& fixture) {
906 const bool prepared = fixture.ready && fixture.disk.preparePage(AtaOwnershipLocation);
907 const bool pinned = prepared && fixture.disk.pin(AtaOwnershipLocation);
908 const bool acquired = pinned && fixture.disk.takeAtaQueuedWritePage(AtaOwnershipLocation);
909 const RetirementResult retired = prepared
910 ? retireAndRepair(fixture.disk, AtaOwnershipLocation, pinned)
911 : RetirementResult{false, true};
912 const bool drainingMiss = retired.cleaned && ataDrainingMissReleasesQueuedPin(fixture);
913 const bool passed =
914 prepared && pinned && acquired && retired.balanced && retired.cleaned && drainingMiss;
915 if (passed) {
916 NOTICE("HOSTED-WAIT-TEST: PASS ata-queued-write-cache-ownership");
917 } else {
918 ERROR(
919 "HOSTED-WAIT-TEST: FAIL ata-queued-write-cache-ownership: queued pin was not traded "
920 "for one bounded execution pin");
921 }
922 return passed;
923}
924
925struct ScsiWriteCaseResult {
926 uint64_t result;
927 bool trace;
928 bool balanced;
929 bool cleaned;
930};
931
932ScsiWriteCaseResult runScsiWriteCase(Fixture& fixture, uint64_t location, WriteMode mode,
933 const uint8_t* expectedOpcodes, size_t expectedCount) {
934 const bool prepared = fixture.ready && fixture.disk.preparePage(location);
935 fixture.controller.beginWrites(mode);
936 const bool pinned = prepared && fixture.disk.pin(location);
937 const uint64_t result =
938 pinned ? fixture.controller.addRequest(0, SCSI_REQUEST_WRITE,
939 reinterpret_cast<uint64_t>(&fixture.disk), location)
940 : 0;
941 const bool trace = pinned && fixture.controller.writeTraceMatches(expectedOpcodes, expectedCount);
942 const RetirementResult retired =
943 prepared ? retireAndRepair(fixture.disk, location, pinned) : RetirementResult{false, true};
944 return {result, trace, retired.balanced, retired.cleaned};
945}
946
947bool scsiWriteResult(Fixture& fixture) {
948 constexpr uint8_t FailedOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa, 0xaa, 0xaa, 0x8a, 0x8a, 0x8a};
949 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
950
951 const ScsiWriteCaseResult failed = runScsiWriteCase(
952 fixture, FailedWriteLocation, WriteMode::FailAll, FailedOpcodes, sizeof(FailedOpcodes));
953 const ScsiWriteCaseResult successful =
954 runScsiWriteCase(fixture, SuccessfulWriteLocation, WriteMode::PassWrite12, SuccessfulOpcodes,
955 sizeof(SuccessfulOpcodes));
956 const bool passed = !failed.result && failed.trace && failed.balanced && failed.cleaned &&
957 successful.result == PageBytes && successful.trace && successful.balanced &&
958 successful.cleaned;
959 if (passed) {
960 NOTICE("HOSTED-WAIT-TEST: PASS scsi-write-result");
961 } else {
962 ERROR(
963 "HOSTED-WAIT-TEST: FAIL scsi-write-result: all-command failure was reported as success "
964 "or fallback/ownership changed");
965 }
966 return passed;
967}
968
969struct DirectRetirementContext {
970 DirectRetirementContext(HostedScsiDisk* pDisk, uint64_t pageLocation)
971 : disk(pDisk), location(pageLocation), returned(0), succeeded(0) {}
972
973 HostedScsiDisk* disk;
974 uint64_t location;
975 Atomic<size_t> returned;
976 Atomic<size_t> succeeded;
977};
978
979int retireDirectPage(void* parameter) {
980 auto* context = reinterpret_cast<DirectRetirementContext*>(parameter);
981 if (context->disk->retireCachePage(context->location))
982 context->succeeded += 1;
983 context->returned += 1;
984 return 0;
985}
986
987bool scsiDirectRetireResult(Fixture& fixture) {
988 constexpr uint8_t FailedOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa, 0xaa, 0xaa, 0x8a, 0x8a, 0x8a};
989 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
990
991 fixture.controller.beginWrites(WriteMode::PassWrite12);
992 const bool missing = fixture.ready && fixture.disk.retireCachePage(MissingDirectLocation) &&
993 fixture.controller.hasNoDirectActivity();
994
995 const bool editingPrepared = fixture.disk.prepareEditingPage(EditingDirectLocation);
996 fixture.controller.beginWrites(WriteMode::PassWrite12);
997 const bool editingRejected = editingPrepared &&
998 !fixture.disk.retireCachePage(EditingDirectLocation) &&
999 fixture.controller.hasNoDirectActivity();
1000 const bool editingDiscarded =
1001 editingPrepared && fixture.disk.discardEditingPage(EditingDirectLocation);
1002
1003 const bool successfulPrepared = fixture.disk.preparePage(SuccessfulDirectLocation);
1004 const uintptr_t successfulPage =
1005 successfulPrepared ? fixture.disk.pageAddress(SuccessfulDirectLocation) : 0;
1006 fixture.controller.beginWrites(WriteMode::PassWrite12);
1007 fixture.disk.beginUnpinObservation();
1008 const bool successfulRetired =
1009 successfulPage && fixture.disk.retireCachePage(SuccessfulDirectLocation);
1010 const size_t successfulUnpins = fixture.disk.endUnpinObservation();
1011 const bool successfulTrace =
1012 fixture.controller.writeTraceMatches(SuccessfulOpcodes, sizeof(SuccessfulOpcodes));
1013 const bool successfulTuple = fixture.controller.directTupleMatches(
1014 &fixture.disk, SuccessfulDirectLocation, successfulPage);
1015 const bool successful = successfulPrepared && successfulRetired && successfulTrace &&
1016 successfulTuple && fixture.controller.unitReadyCount() == 1 &&
1017 fixture.controller.lastWriteBuffer() == successfulPage &&
1018 successfulUnpins == 0 && !fixture.disk.hasPage(SuccessfulDirectLocation);
1019
1020 const bool canonicalPrepared = fixture.disk.preparePage(CanonicalDirectLocation);
1021 const uintptr_t canonicalPage =
1022 canonicalPrepared ? fixture.disk.pageAddress(CanonicalDirectLocation) : 0;
1023 fixture.controller.beginWrites(WriteMode::PassWrite12);
1024 const bool canonicalRetired = canonicalPage &&
1025 fixture.disk.retireCachePage(CanonicalDirectLocation + 512) &&
1026 fixture.controller.directTupleMatches(
1027 &fixture.disk, CanonicalDirectLocation, canonicalPage) &&
1028 fixture.controller.lastWriteBuffer() == canonicalPage &&
1029 !fixture.disk.hasPage(CanonicalDirectLocation);
1030 fixture.controller.beginWrites(WriteMode::PassWrite12);
1031 const bool endRejected = !fixture.disk.retireCachePage(fixture.disk.getSize()) &&
1032 fixture.controller.hasNoDirectActivity();
1033
1034 const bool failedPrepared = fixture.disk.preparePage(FailedDirectLocation);
1035 const uintptr_t failedPage = failedPrepared ? fixture.disk.pageAddress(FailedDirectLocation) : 0;
1036 fixture.controller.beginWrites(WriteMode::FailAll);
1037 const bool failedRetirement = failedPage && !fixture.disk.retireCachePage(FailedDirectLocation);
1038 const bool failedTrace =
1039 fixture.controller.writeTraceMatches(FailedOpcodes, sizeof(FailedOpcodes));
1040 const bool failedTuple =
1041 fixture.controller.directTupleMatches(&fixture.disk, FailedDirectLocation, failedPage);
1042 const bool failedRetained = failedRetirement && fixture.disk.hasPage(FailedDirectLocation);
1043 fixture.controller.beginWrites(WriteMode::PassWrite12);
1044 const bool failedRetry =
1045 failedRetained && fixture.disk.retireCachePage(FailedDirectLocation) &&
1046 fixture.controller.writeTraceMatches(SuccessfulOpcodes, sizeof(SuccessfulOpcodes)) &&
1047 !fixture.disk.hasPage(FailedDirectLocation);
1048
1049 const bool unreadyPrepared = fixture.disk.preparePage(UnreadyDirectLocation);
1050 const uintptr_t unreadyPage =
1051 unreadyPrepared ? fixture.disk.pageAddress(UnreadyDirectLocation) : 0;
1052 fixture.controller.beginWrites(WriteMode::UnitNotReady);
1053 const bool unreadyRetirement =
1054 unreadyPage && !fixture.disk.retireCachePage(UnreadyDirectLocation);
1055 const bool unready =
1056 unreadyRetirement &&
1057 fixture.controller.directTupleMatches(&fixture.disk, UnreadyDirectLocation, unreadyPage) &&
1058 fixture.controller.unitReadyCount() == 3 && fixture.controller.writeCount() == 0 &&
1059 fixture.disk.hasPage(UnreadyDirectLocation);
1060 fixture.controller.beginWrites(WriteMode::PassWrite12);
1061 const bool unreadyRetry = unready && fixture.disk.retireCachePage(UnreadyDirectLocation) &&
1062 !fixture.disk.hasPage(UnreadyDirectLocation);
1063
1064 const bool shortPrepared = fixture.disk.preparePage(ShortDirectLocation);
1065 const uintptr_t shortPage = shortPrepared ? fixture.disk.pageAddress(ShortDirectLocation) : 0;
1066 fixture.disk.overrideDirectResult(1);
1067 fixture.controller.beginWrites(WriteMode::PassWrite12);
1068 const bool shortRetirement = shortPage && !fixture.disk.retireCachePage(ShortDirectLocation);
1069 const bool shortResultRejected =
1070 shortRetirement &&
1071 fixture.controller.directTupleMatches(&fixture.disk, ShortDirectLocation, shortPage) &&
1072 fixture.controller.unitReadyCount() == 0 && fixture.controller.writeCount() == 0 &&
1073 fixture.disk.hasPage(ShortDirectLocation);
1074 fixture.disk.restoreDirectResult();
1075 fixture.controller.beginWrites(WriteMode::PassWrite12);
1076 const bool shortRetry = shortResultRejected &&
1077 fixture.disk.retireCachePage(ShortDirectLocation) &&
1078 !fixture.disk.hasPage(ShortDirectLocation);
1079
1080 const bool passed = missing && editingRejected && editingDiscarded && successful &&
1081 canonicalPrepared && canonicalRetired && endRejected && failedRetained &&
1082 failedTrace && failedTuple && failedRetry && unreadyPrepared &&
1083 unreadyRetry && shortPrepared && shortRetry;
1084 if (passed) {
1085 NOTICE("HOSTED-WAIT-TEST: PASS scsi-direct-retire-result");
1086 } else {
1087 ERROR(
1088 "HOSTED-WAIT-TEST: FAIL scsi-direct-retire-result: DIRECT routing, exact result, "
1089 "retry, or supplied-page write semantics changed");
1090 }
1091 return passed;
1092}
1093
1094bool scsiDirectRetireOwnership(Fixture& fixture) {
1095 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
1096
1097 const bool pinnedPrepared = fixture.disk.preparePage(PinnedDirectLocation);
1098 const uintptr_t pinnedPage = pinnedPrepared ? fixture.disk.pageAddress(PinnedDirectLocation) : 0;
1099 const bool pinned = pinnedPage && fixture.disk.pin(PinnedDirectLocation);
1100 fixture.controller.beginWrites(WriteMode::PassWrite12);
1101 DirectRetirementContext context(&fixture.disk, PinnedDirectLocation);
1102 Thread* retirer = nullptr;
1103 if (pinned) {
1104 retirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage, &context,
1105 nullptr, false, true);
1106 retirer->setName("hosted SCSI direct-page retirer");
1107 }
1108
1109 const bool drainPublished =
1110 retirer && waitUntilQueuedAt(retirer, Thread::CallbackDrain, PinnedDirectLocation);
1111 const bool blockedWithoutIo = drainPublished && !static_cast<size_t>(context.returned) &&
1112 fixture.controller.hasNoDirectActivity();
1113 if (pinned)
1114 fixture.disk.unpin(PinnedDirectLocation);
1115 const bool completed =
1116 retirer && (static_cast<size_t>(context.returned) || waitUntilSet(context.returned));
1117 const bool joined = retirer && retirer->joinForCompletion();
1118 const bool pinnedRetired =
1119 completed && joined && context.succeeded == 1 &&
1120 fixture.controller.directTupleMatches(&fixture.disk, PinnedDirectLocation, pinnedPage) &&
1121 fixture.controller.writeTraceMatches(SuccessfulOpcodes, sizeof(SuccessfulOpcodes)) &&
1122 fixture.controller.lastWriteBuffer() == pinnedPage &&
1123 !fixture.disk.hasPage(PinnedDirectLocation);
1124
1125 const bool cancelledPrepared = fixture.disk.preparePage(CancelledDirectLocation);
1126 const uintptr_t cancelledPage =
1127 cancelledPrepared ? fixture.disk.pageAddress(CancelledDirectLocation) : 0;
1128 const bool halted = cancelledPage && fixture.controller.halt();
1129 fixture.controller.beginWrites(WriteMode::PassWrite12);
1130 fixture.disk.beginUnpinObservation();
1131 const bool cancelled = halted && !fixture.disk.retireCachePage(CancelledDirectLocation);
1132 const size_t cancelledUnpins = fixture.disk.endUnpinObservation();
1133 const uint64_t cancelledUnpinLocation = fixture.disk.lastUnpinLocation();
1134 const bool cancellationPreserved = cancelled && fixture.controller.hasNoDirectActivity() &&
1135 cancelledUnpins == 0 && cancelledUnpinLocation == 0 &&
1136 fixture.disk.hasPage(CancelledDirectLocation);
1137 const bool resumed = halted && fixture.controller.resume();
1138 fixture.controller.beginWrites(WriteMode::PassWrite12);
1139 const bool cancellationRetry = cancellationPreserved && resumed &&
1140 fixture.disk.retireCachePage(CancelledDirectLocation) &&
1141 fixture.controller.directTupleMatches(
1142 &fixture.disk, CancelledDirectLocation, cancelledPage) &&
1143 fixture.controller.lastWriteBuffer() == cancelledPage &&
1144 !fixture.disk.hasPage(CancelledDirectLocation);
1145
1146 const bool passed = pinnedPrepared && pinned && drainPublished && blockedWithoutIo &&
1147 pinnedRetired && cancelledPrepared && cancellationRetry;
1148 if (passed) {
1149 NOTICE("HOSTED-WAIT-TEST: PASS scsi-direct-retire-ownership");
1150 } else {
1151 ERROR(
1152 "HOSTED-WAIT-TEST: FAIL scsi-direct-retire-ownership: borrowed DIRECT pages were "
1153 "used before drain or released by execution/cancellation");
1154 }
1155 return passed;
1156}
1157
1158bool scsiReadRetireAdmission(Fixture& fixture) {
1159 constexpr RequestEvent ExpectedOrder[] = {RequestEvent::Read, RequestEvent::Direct,
1160 RequestEvent::Read};
1161
1162 fixture.disk.setCacheFillSize(PageBytes);
1163 fixture.controller.beginWrites(WriteMode::PassWrite12);
1164 fixture.controller.beginRequestTrace();
1165 fixture.controller.holdNextRead();
1166
1167 ReadContext firstRead(&fixture.disk, ReadFirstLocation);
1168 Thread* firstReader = startRead(firstRead, "hosted SCSI admitted first reader");
1169 const bool firstReadEntered = firstReader && fixture.controller.waitForHeldRead();
1170 const bool prepared = firstReadEntered && fixture.disk.preparePage(ReadFirstLocation);
1171 const bool retireSerialPin = prepared && fixture.disk.pin(ReadFirstLocation);
1172
1173 DirectRetirementContext retirement(&fixture.disk, ReadFirstLocation);
1174 Thread* retirer = nullptr;
1175 if (retireSerialPin) {
1176 retirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage, &retirement,
1177 nullptr, false, true);
1178 retirer->setName("hosted SCSI queued-read retirement");
1179 }
1180
1181 bool retireAdmissionWait = false;
1182 const bool retireStopped =
1183 retirer &&
1184 waitForRetireAdmissionOrDrain(retirer, fixture.disk, ReadFirstLocation, retireAdmissionWait);
1185 const bool retireIntentQueued = retireStopped && retireAdmissionWait;
1186 const bool pageStayedVisible = retireIntentQueued && fixture.disk.pageAddress(ReadFirstLocation);
1187
1188 DirectRetirementContext secondRetirement(&fixture.disk, ReadFirstLocation);
1189 Thread* secondRetirer = nullptr;
1190 if (retirer) {
1191 secondRetirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage,
1192 &secondRetirement, nullptr, false, true);
1193 secondRetirer->setName("hosted SCSI second queued retirement");
1194 }
1195 bool secondRetireAdmissionWait = false;
1196 const bool secondRetireStopped =
1197 secondRetirer && waitForRetireAdmissionOrDrain(secondRetirer, fixture.disk, ReadFirstLocation,
1198 secondRetireAdmissionWait);
1199 const bool retiresSerialised = secondRetireStopped && secondRetireAdmissionWait;
1200
1201 fixture.controller.holdFollowingRead();
1202 ReadContext secondRead(&fixture.disk, ReadFirstLocation);
1203 Thread* secondReader = startRead(secondRead, "hosted SCSI writer-preference reader");
1204 bool secondAdmissionWait = false;
1205 const bool secondStopped =
1206 secondReader && waitForAdmissionOrRead(secondReader, ReadFirstLocation, fixture.controller, 1,
1207 secondAdmissionWait);
1208 const bool writerPreferred = secondStopped && secondAdmissionWait;
1209 const bool safeToDelegate =
1210 retireIntentQueued && pageStayedVisible && retiresSerialised && writerPreferred;
1211 fixture.controller.releaseHeldRead(safeToDelegate);
1212
1213 const bool firstCompleted =
1214 firstReader && (static_cast<size_t>(firstRead.returned) || waitUntilSet(firstRead.returned));
1215 bool secondReadEntered = false;
1216 if (!writerPreferred) {
1217 secondReadEntered = fixture.controller.waitForHeldRead();
1218 fixture.controller.releaseHeldRead(false);
1219 }
1220 const bool firstRetireDraining =
1221 safeToDelegate && firstCompleted &&
1222 waitUntilQueuedAt(retirer, Thread::CallbackDrain, ReadFirstLocation);
1223 const bool secondRetireStillQueued =
1224 firstRetireDraining && waitUntilQueuedAt(secondRetirer, Thread::CondWait, ReadFirstLocation);
1225 if (retireSerialPin)
1226 fixture.disk.unpin(ReadFirstLocation);
1227 const bool retireCompleted =
1228 retirer && (static_cast<size_t>(retirement.returned) || waitUntilSet(retirement.returned));
1229 const bool secondRetireCompleted =
1230 secondRetirer &&
1231 (static_cast<size_t>(secondRetirement.returned) || waitUntilSet(secondRetirement.returned));
1232 if (secondReader && writerPreferred) {
1233 secondReadEntered = fixture.controller.waitForHeldRead();
1234 fixture.controller.releaseHeldRead(true);
1235 }
1236 const bool secondCompleted = secondReader && (static_cast<size_t>(secondRead.returned) ||
1237 waitUntilSet(secondRead.returned));
1238 const bool firstJoined = firstReader && firstReader->joinForCompletion();
1239 const bool retireJoined = retirer && retirer->joinForCompletion();
1240 const bool secondRetireJoined = secondRetirer && secondRetirer->joinForCompletion();
1241 const bool secondJoined = secondReader && secondReader->joinForCompletion();
1242
1243 const bool ordered = fixture.controller.requestTraceMatches(
1244 ExpectedOrder, sizeof(ExpectedOrder) / sizeof(ExpectedOrder[0]));
1245 const bool queueCycleClosed =
1246 firstCompleted && firstRetireDraining && secondRetireStillQueued && retireCompleted &&
1247 secondRetireCompleted && secondCompleted && firstJoined && retireJoined &&
1248 secondRetireJoined && secondJoined && secondReadEntered && firstRead.result &&
1249 retirement.succeeded == 1 && secondRetirement.succeeded == 1 && secondRead.result &&
1250 ordered && fixture.controller.readCommandCount() == 1 &&
1251 fixture.controller.directRequestCount() == 1 && fixture.disk.hasPage(ReadFirstLocation);
1252
1253 const bool halted = fixture.controller.halt();
1254 const bool rejectedRead = halted && static_cast<bool>(fixture.disk.read(RejectedReadLocation));
1255 DirectRetirementContext rejectedRetirement(&fixture.disk, RejectedReadLocation);
1256 Thread* rejectedRetirer = nullptr;
1257 if (halted && !rejectedRead) {
1258 rejectedRetirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage,
1259 &rejectedRetirement, nullptr, false, true);
1260 rejectedRetirer->setName("hosted SCSI rejected-read retirement");
1261 }
1262 const bool rejectedCompleted =
1263 rejectedRetirer && (static_cast<size_t>(rejectedRetirement.returned) ||
1264 waitUntilSet(rejectedRetirement.returned));
1265 const bool rejectedJoined = rejectedRetirer && rejectedRetirer->joinForCompletion();
1266 const bool resumed = halted && fixture.controller.resume();
1267 const bool rejectionReleased =
1268 !rejectedRead && rejectedCompleted && rejectedJoined && rejectedRetirement.succeeded == 1;
1269
1270 fixture.controller.beginWrites(WriteMode::PassWrite12);
1271 const bool cleaned = fixture.disk.retireCachePage(ReadFirstLocation);
1272 const bool passed = fixture.ready && prepared && retireSerialPin && firstReadEntered &&
1273 retireIntentQueued && pageStayedVisible && retiresSerialised &&
1274 writerPreferred && queueCycleClosed && halted && resumed &&
1275 rejectionReleased && cleaned;
1276 if (passed) {
1277 NOTICE("HOSTED-WAIT-TEST: PASS scsi-read-retire-admission");
1278 } else {
1279 ERROR(
1280 "HOSTED-WAIT-TEST: FAIL scsi-read-retire-admission: queued reads, retirement intent, "
1281 "writer preference, or rejected-request cleanup changed");
1282 }
1283 return passed;
1284}
1285
1286bool retireCachedRange(Fixture& fixture, uint64_t location, size_t length) {
1287 bool retired = true;
1288 for (size_t offset = 0; offset < length; offset += PageBytes) {
1289 if (!fixture.disk.hasPage(location + offset))
1290 continue;
1291 fixture.controller.beginWrites(WriteMode::PassWrite12);
1292 if (!fixture.disk.retireCachePage(location + offset))
1293 retired = false;
1294 }
1295 return retired;
1296}
1297
1298bool scsiRetireReadRecheck(Fixture& fixture) {
1299 constexpr size_t WideFill = PageBytes * 2;
1300 constexpr RequestEvent RangeOrder[] = {RequestEvent::Read, RequestEvent::Direct,
1301 RequestEvent::Read};
1302 constexpr RequestEvent FailedRetirementOrder[] = {RequestEvent::Direct};
1303
1304 fixture.disk.setCacheFillSize(WideFill);
1305 const bool interiorPrepared = fixture.disk.preparePage(RetireFirstInterior);
1306 const bool interiorPinned = interiorPrepared && fixture.disk.pin(RetireFirstInterior);
1307 fixture.controller.beginWrites(WriteMode::PassWrite12);
1308 fixture.controller.beginRequestTrace();
1309
1310 DirectRetirementContext rangeRetirement(&fixture.disk, RetireFirstInterior);
1311 Thread* rangeRetirer = nullptr;
1312 if (interiorPinned) {
1313 rangeRetirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage,
1314 &rangeRetirement, nullptr, false, true);
1315 rangeRetirer->setName("hosted SCSI interior-page retirer");
1316 }
1317 const bool rangeDrainPublished =
1318 rangeRetirer && waitUntilQueuedAt(rangeRetirer, Thread::CallbackDrain, RetireFirstInterior);
1319
1320 ReadContext nonoverlapRead(&fixture.disk, NonoverlapReadLocation);
1321 Thread* nonoverlapReader = startRead(nonoverlapRead, "hosted SCSI disjoint retirement reader");
1322 bool nonoverlapCompleted = nonoverlapReader && (static_cast<size_t>(nonoverlapRead.returned) ||
1323 waitUntilSet(nonoverlapRead.returned));
1324 bool releasedForRepair = false;
1325 if (interiorPinned && !nonoverlapCompleted) {
1326 fixture.disk.unpin(RetireFirstInterior);
1327 releasedForRepair = true;
1328 nonoverlapCompleted = waitUntilSet(nonoverlapRead.returned);
1329 }
1330 const bool nonoverlapJoined = nonoverlapReader && nonoverlapReader->joinForCompletion();
1331 const bool disjointProgress =
1332 nonoverlapCompleted && nonoverlapJoined && nonoverlapRead.result && !releasedForRepair;
1333
1334 bool overlapAdmissionWait = false;
1335 bool overlapStopped = false;
1336 bool overlapReadEntered = false;
1337 bool overlapCompleted = false;
1338 bool overlapJoined = false;
1339 ReadContext overlapRead(&fixture.disk, RetireFirstExtent);
1340 Thread* overlapReader = nullptr;
1341 if (disjointProgress) {
1342 fixture.controller.holdNextRead();
1343 overlapReader = startRead(overlapRead, "hosted SCSI interior-overlap reader");
1344 overlapStopped =
1345 overlapReader && waitForAdmissionOrRead(overlapReader, RetireFirstExtent,
1346 fixture.controller, 1, overlapAdmissionWait);
1347 if (!overlapAdmissionWait) {
1348 overlapReadEntered = fixture.controller.waitForHeldRead();
1349 fixture.controller.releaseHeldRead(false);
1350 }
1351 }
1352
1353 if (interiorPinned && !releasedForRepair)
1354 fixture.disk.unpin(RetireFirstInterior);
1355 const bool rangeRetireCompleted =
1356 rangeRetirer &&
1357 (static_cast<size_t>(rangeRetirement.returned) || waitUntilSet(rangeRetirement.returned));
1358 const bool rangeRetireJoined = rangeRetirer && rangeRetirer->joinForCompletion();
1359
1360 if (overlapReader && overlapAdmissionWait) {
1361 overlapReadEntered = fixture.controller.waitForHeldRead();
1362 fixture.controller.releaseHeldRead(true);
1363 }
1364 if (overlapReader) {
1365 overlapCompleted =
1366 static_cast<size_t>(overlapRead.returned) || waitUntilSet(overlapRead.returned);
1367 overlapJoined = overlapReader->joinForCompletion();
1368 }
1369
1370 const bool rangeOrder = fixture.controller.requestTraceMatches(
1371 RangeOrder, sizeof(RangeOrder) / sizeof(RangeOrder[0]));
1372 const bool rangeAdmission =
1373 rangeDrainPublished && disjointProgress && overlapStopped && overlapAdmissionWait &&
1374 overlapReadEntered && rangeRetireCompleted && rangeRetireJoined &&
1375 rangeRetirement.succeeded == 1 && overlapCompleted && overlapJoined && overlapRead.result &&
1376 fixture.controller.readCommandCount() == 2 && rangeOrder &&
1377 fixture.disk.hasPage(RetireFirstExtent) && fixture.disk.hasPage(RetireFirstInterior);
1378 const bool rangeCleaned = retireCachedRange(fixture, RetireFirstExtent, WideFill) &&
1379 retireCachedRange(fixture, NonoverlapReadLocation, WideFill);
1380
1381 fixture.disk.setCacheFillSize(PageBytes);
1382 const bool recheckPrepared = fixture.disk.preparePage(RecheckReadLocation);
1383 const bool recheckPinned = recheckPrepared && fixture.disk.pin(RecheckReadLocation);
1384 fixture.disk.overrideDirectResult(0);
1385 fixture.controller.beginWrites(WriteMode::PassWrite12);
1386 fixture.controller.beginRequestTrace();
1387
1388 DirectRetirementContext failedRetirement(&fixture.disk, RecheckReadLocation);
1389 Thread* failedRetirer = nullptr;
1390 if (recheckPinned) {
1391 failedRetirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage,
1392 &failedRetirement, nullptr, false, true);
1393 failedRetirer->setName("hosted SCSI failed retirement recheck");
1394 }
1395 const bool failureDrainPublished =
1396 failedRetirer && waitUntilQueuedAt(failedRetirer, Thread::CallbackDrain, RecheckReadLocation);
1397
1398 fixture.controller.holdNextRead();
1399 ReadContext recheckRead(&fixture.disk, RecheckReadLocation);
1400 Thread* recheckReader = startRead(recheckRead, "hosted SCSI reopened-page reader");
1401 bool recheckAdmissionWait = false;
1402 const bool recheckStopped =
1403 recheckReader && waitForAdmissionOrRead(recheckReader, RecheckReadLocation,
1404 fixture.controller, 0, recheckAdmissionWait);
1405 bool recheckReadEntered = false;
1406 if (!recheckAdmissionWait) {
1407 recheckReadEntered = fixture.controller.waitForHeldRead();
1408 fixture.controller.releaseHeldRead(false);
1409 }
1410
1411 if (recheckPinned)
1412 fixture.disk.unpin(RecheckReadLocation);
1413 const bool failedRetireCompleted =
1414 failedRetirer &&
1415 (static_cast<size_t>(failedRetirement.returned) || waitUntilSet(failedRetirement.returned));
1416 const bool failedRetireJoined = failedRetirer && failedRetirer->joinForCompletion();
1417 bool recheckCompleted = recheckReader && (static_cast<size_t>(recheckRead.returned) ||
1418 waitUntilSet(recheckRead.returned));
1419 if (!recheckCompleted) {
1420 recheckReadEntered = fixture.controller.waitForHeldRead();
1421 fixture.controller.releaseHeldRead(false);
1422 recheckCompleted = waitUntilSet(recheckRead.returned);
1423 } else if (!fixture.controller.readRequestCount()) {
1424 fixture.controller.disarmReadHold();
1425 }
1426 const bool recheckJoined = recheckReader && recheckReader->joinForCompletion();
1427 const bool failedOrder = fixture.controller.requestTraceMatches(
1428 FailedRetirementOrder, sizeof(FailedRetirementOrder) / sizeof(FailedRetirementOrder[0]));
1429 const bool failureRechecked = failureDrainPublished && recheckStopped && recheckAdmissionWait &&
1430 !recheckReadEntered && failedRetireCompleted &&
1431 failedRetireJoined && failedRetirement.succeeded == 0 &&
1432 recheckCompleted && recheckJoined && recheckRead.result &&
1433 fixture.controller.readRequestCount() == 0 && failedOrder &&
1434 fixture.disk.hasPage(RecheckReadLocation);
1435
1436 fixture.disk.restoreDirectResult();
1437 fixture.controller.beginWrites(WriteMode::PassWrite12);
1438 const bool recheckCleaned = fixture.disk.retireCachePage(RecheckReadLocation);
1439
1440 constexpr RequestEvent LookupPauseOrder[] = {RequestEvent::Read, RequestEvent::Direct};
1441 fixture.controller.beginWrites(WriteMode::PassWrite12);
1442 fixture.controller.beginRequestTrace();
1443 ReadRequestPause lookupPause(&fixture.disk, LookupPauseLocation);
1444 lookupPause.arm();
1445
1446 ReadContext pausedRead(&fixture.disk, LookupPauseLocation);
1447 Thread* pausedReader = startRead(pausedRead, "hosted SCSI pre-lookup reader");
1448 const bool lookupPauseEntered = pausedReader && lookupPause.wait();
1449 const bool readPublished = lookupPauseEntered && fixture.disk.hasPage(LookupPauseLocation);
1450
1451 ReadContext sharedRead(&fixture.disk, LookupPauseLocation);
1452 Thread* sharedReader = nullptr;
1453 if (readPublished) {
1454 sharedReader = startRead(sharedRead, "hosted SCSI shared cache-hit reader");
1455 }
1456 // The first reader owns exclusive admission through publication and lookup.
1457 // An overlapping reader must queue ahead of retirement, then reuse its page.
1458 const bool sharedBlockedBeforeRelease =
1459 sharedReader && waitUntilQueuedAt(sharedReader, Thread::CondWait, LookupPauseLocation) &&
1460 !static_cast<size_t>(sharedRead.returned);
1461
1462 DirectRetirementContext lookupRetirement(&fixture.disk, LookupPauseLocation);
1463 Thread* lookupRetirer = nullptr;
1464 if (sharedReader) {
1465 lookupRetirer = new Thread(Scheduler::instance().getKernelProcess(), retireDirectPage,
1466 &lookupRetirement, nullptr, false, true);
1467 lookupRetirer->setName("hosted SCSI pre-lookup retirer");
1468 }
1469 bool lookupRetireAdmissionWait = false;
1470 const bool lookupRetireStopped =
1471 lookupRetirer &&
1472 waitForRetireAdmissionOrDrain(lookupRetirer, fixture.disk, LookupPauseLocation,
1473 lookupRetireAdmissionWait);
1474 const bool admissionHeldThroughLookup = lookupRetireStopped && lookupRetireAdmissionWait &&
1475 fixture.disk.pageAddress(LookupPauseLocation);
1476
1477 lookupPause.resume();
1478 const bool pausedCompleted = pausedReader && (static_cast<size_t>(pausedRead.returned) ||
1479 waitUntilSet(pausedRead.returned));
1480 const bool sharedCompleted = sharedReader && (static_cast<size_t>(sharedRead.returned) ||
1481 waitUntilSet(sharedRead.returned));
1482 const bool lookupRetireCompleted =
1483 lookupRetirer &&
1484 (static_cast<size_t>(lookupRetirement.returned) || waitUntilSet(lookupRetirement.returned));
1485 const bool pausedJoined = pausedReader && pausedReader->joinForCompletion();
1486 const bool sharedJoined = sharedReader && sharedReader->joinForCompletion();
1487 const bool lookupRetireJoined = lookupRetirer && lookupRetirer->joinForCompletion();
1488 lookupPause.clear();
1489
1490 const bool lookupOrder = fixture.controller.requestTraceMatches(
1491 LookupPauseOrder, sizeof(LookupPauseOrder) / sizeof(LookupPauseOrder[0]));
1492 const bool finalLookupProtected =
1493 lookupPauseEntered && readPublished && sharedBlockedBeforeRelease &&
1494 admissionHeldThroughLookup && pausedCompleted && sharedCompleted && lookupRetireCompleted &&
1495 pausedJoined && sharedJoined && lookupRetireJoined && pausedRead.result &&
1496 sharedRead.result && lookupRetirement.succeeded == 1 &&
1497 fixture.controller.readRequestCount() == 1 && fixture.controller.readCommandCount() == 1 &&
1498 fixture.controller.directRequestCount() == 1 && lookupOrder;
1499 fixture.controller.beginWrites(WriteMode::PassWrite12);
1500 const bool lookupCleaned = !fixture.disk.hasPage(LookupPauseLocation) ||
1501 fixture.disk.retireCachePage(LookupPauseLocation);
1502
1503 const bool passed = interiorPrepared && interiorPinned && rangeAdmission && rangeCleaned &&
1504 recheckPrepared && recheckPinned && failureRechecked && recheckCleaned &&
1505 finalLookupProtected && lookupCleaned;
1506 if (passed) {
1507 NOTICE("HOSTED-WAIT-TEST: PASS scsi-retire-read-recheck");
1508 } else {
1509 ERROR("HOSTED-WAIT-TEST: FAIL scsi-retire-read-recheck");
1510 ERROR("SCSI recheck range: prepared=" << interiorPrepared << ", pinned=" << interiorPinned
1511 << ", admission=" << rangeAdmission
1512 << ", cleanup=" << rangeCleaned);
1513 ERROR("SCSI recheck failure: prepared=" << recheckPrepared << ", pinned=" << recheckPinned
1514 << ", rechecked=" << failureRechecked
1515 << ", cleanup=" << recheckCleaned);
1516 ERROR("SCSI recheck lookup: pause=" << lookupPauseEntered << ", published=" << readPublished
1517 << ", reader-wait=" << sharedBlockedBeforeRelease
1518 << ", retire-wait=" << admissionHeldThroughLookup);
1519 ERROR("SCSI recheck result: lookup=" << finalLookupProtected << ", cleanup=" << lookupCleaned);
1520 }
1521 return passed;
1522}
1523
1524bool scsiDeferredWrites() {
1525 Fixture fixture;
1526 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1527 return false;
1528 fixture.disk.checksumPage(CheckedSyncLocation);
1529 if (!fixture.disk.runBackgroundWriteback())
1530 return false;
1531 fixture.disk.pauseBackgroundWriteback();
1532 fixture.controller.beginWrites(WriteMode::PassWrite12);
1533 fixture.controller.beginSync(SyncMode::Pass10);
1534 const bool pinned = fixture.disk.pin(CheckedSyncLocation);
1535 for (size_t i = 0; i < 128; ++i)
1536 fixture.disk.write(CheckedSyncLocation);
1537 const bool deferred = fixture.controller.drain() && fixture.controller.hasNoDirectActivity();
1538 fixture.disk.resumeBackgroundWriteback();
1539 const bool written =
1540 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1;
1541 constexpr uint8_t Flush[] = {0x35};
1542 const bool durable = fixture.controller.syncTraceMatches(Flush, sizeof(Flush));
1543 const bool settled =
1544 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1;
1545 const bool callerPinRetained = pinned && !fixture.disk.evictPage(CheckedSyncLocation);
1546 if (pinned)
1547 fixture.disk.unpin(CheckedSyncLocation);
1548 const bool balanced = fixture.disk.evictPage(CheckedSyncLocation);
1549 const bool passed = deferred && written && durable && settled && callerPinRetained && balanced;
1550 if (passed)
1551 NOTICE("HOSTED-WAIT-TEST: PASS scsi-deferred-writes");
1552 else
1553 ERROR("HOSTED-WAIT-TEST: FAIL scsi-deferred-writes: coalescing, settling, durability or pins");
1554 return passed;
1555}
1556
1557bool scsiScopedDiskViews() {
1558 Fixture fixture;
1559 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1560 return false;
1561 fixture.disk.pauseBackgroundWriteback();
1562 fixture.controller.beginWrites(WriteMode::PassWrite12);
1563 fixture.controller.beginSync(SyncMode::Pass10);
1564
1565 auto reader = fixture.disk.readView(CheckedSyncLocation);
1566 auto writer = fixture.disk.writeView(CheckedSyncLocation);
1567 const BufferView legacy = fixture.disk.read(CheckedSyncLocation);
1568 const bool pinned = fixture.disk.pin(CheckedSyncLocation);
1569 const bool admitted = reader && writer && legacy && pinned;
1570 const bool endpointHeld = admitted && !fixture.disk.tryCloseEndpoint();
1571 const uint32_t value = 0x12345678;
1572 const bool changed = writer && writer.writeAt(value, 1);
1573 auto moved = pedigree_std::move(writer);
1574 const bool transferred = !writer && moved;
1575 moved.reset();
1576 if (legacy)
1577 fixture.disk.unpin(CheckedSyncLocation);
1578 if (legacy && pinned)
1579 static_cast<uint8_t*>(legacy.data())[7] = 0xc7;
1580 if (pinned)
1581 fixture.disk.unpin(CheckedSyncLocation);
1582
1583 uint32_t observed = 0;
1584 uint8_t last = 0;
1585 const bool coherent = reader && reader.readAt(observed, 1) && observed == value &&
1586 reader.readAt(last, 7) && last == 0xc7;
1587 fixture.disk.resumeBackgroundWriteback();
1588 const bool written =
1589 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1;
1590 const bool settled =
1591 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1;
1592 reader.reset();
1593 const bool closed = fixture.disk.tryCloseEndpoint();
1594 fixture.disk.reopenEndpoint();
1595 const bool balanced =
1596 fixture.disk.hasNoCacheLoans() && fixture.disk.evictPage(CheckedSyncLocation);
1597 const bool passed = admitted && endpointHeld && changed && transferred && coherent && written &&
1598 settled && closed && balanced;
1599 if (passed)
1600 NOTICE("HOSTED-WAIT-TEST: PASS scsi-scoped-disk-views");
1601 else
1602 ERROR("HOSTED-WAIT-TEST: FAIL scsi-scoped-disk-views: admission="
1603 << admitted << ", endpoint=" << endpointHeld << ", coherent=" << coherent
1604 << ", written=" << written << ", settled=" << settled << ", balanced=" << balanced);
1605 return passed;
1606}
1607
1608bool scsiViewSurvivesAlignmentChange() {
1609 Fixture fixture;
1610 if (!fixture.ready || !fixture.disk.preparePage(0))
1611 return false;
1612 auto reader = fixture.disk.readView(2048);
1613 auto writer = fixture.disk.writeView(2048);
1614 const bool admitted = reader && writer;
1615 fixture.disk.align(1536);
1616 writer.reset();
1617 reader.reset();
1618 const bool balanced = fixture.disk.hasNoCacheLoans() && fixture.disk.evictPage(0);
1619 const bool passed = admitted && balanced;
1620 if (passed)
1621 NOTICE("HOSTED-WAIT-TEST: PASS scsi-view-alignment-change");
1622 else
1623 ERROR("HOSTED-WAIT-TEST: FAIL scsi-view-alignment-change: view released a different cache key");
1624 return passed;
1625}
1626
1627bool scsiDeferredWriteRetry() {
1628 Fixture fixture;
1629 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1630 return false;
1631 fixture.disk.checksumPage(CheckedSyncLocation);
1632 if (!fixture.disk.runBackgroundWriteback())
1633 return false;
1634 fixture.disk.pauseBackgroundWriteback();
1635 fixture.controller.beginWrites(WriteMode::PassWrite12);
1636 fixture.controller.beginSync(SyncMode::FailAll);
1637 fixture.disk.write(CheckedSyncLocation);
1638 const bool deferred = fixture.controller.drain() && fixture.controller.hasNoDirectActivity();
1639 fixture.disk.resumeBackgroundWriteback();
1640 constexpr uint8_t FailedFlushes[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1641 const bool failedFlush =
1642 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1 &&
1643 fixture.controller.syncTraceMatches(FailedFlushes, sizeof(FailedFlushes));
1644 // The failed device flush must remain retryable even though page bytes are unchanged.
1645 fixture.disk.pauseBackgroundWriteback();
1646 fixture.controller.beginWrites(WriteMode::PassWrite12);
1647 fixture.controller.beginSync(SyncMode::Pass10);
1648 fixture.disk.resumeBackgroundWriteback();
1649 const bool retried =
1650 fixture.disk.runBackgroundWriteback() && fixture.controller.directRequestCount() == 1;
1651 constexpr uint8_t Flush[] = {0x35};
1652 const bool durable = fixture.controller.syncTraceMatches(Flush, sizeof(Flush));
1653 const bool cleaned = fixture.disk.evictPage(CheckedSyncLocation);
1654 const bool passed = deferred && failedFlush && retried && durable && cleaned;
1655 if (passed)
1656 NOTICE("HOSTED-WAIT-TEST: PASS scsi-deferred-write-retry");
1657 else
1658 ERROR(
1659 "HOSTED-WAIT-TEST: FAIL scsi-deferred-write-retry: failed flush lost retry or leaked pins");
1660 return passed;
1661}
1662
1663bool scsiDeferredWriteShutdown() {
1664 Fixture fixture;
1665 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1666 return false;
1667 fixture.disk.checksumPage(CheckedSyncLocation);
1668 fixture.disk.pauseBackgroundWriteback();
1669 fixture.controller.beginWrites(WriteMode::PassWrite12);
1670 fixture.controller.beginSync(SyncMode::Pass10);
1671 fixture.disk.write(CheckedSyncLocation);
1672 const bool deferred = fixture.controller.drain() && fixture.controller.hasNoDirectActivity();
1673 fixture.disk.shutdownCache();
1674 constexpr uint8_t Flush[] = {0x35};
1675 const bool passed = deferred && fixture.controller.directRequestCount() == 1 &&
1676 fixture.controller.syncTraceMatches(Flush, sizeof(Flush));
1677 if (passed)
1678 NOTICE("HOSTED-WAIT-TEST: PASS scsi-deferred-write-shutdown");
1679 else
1680 ERROR("HOSTED-WAIT-TEST: FAIL scsi-deferred-write-shutdown: deferred page was not flushed");
1681 return passed;
1682}
1683
1684bool scsiCheckedSync() {
1685 constexpr uint8_t SuccessfulWrites[] = {0x2a, 0x2a, 0x2a, 0xaa};
1686 constexpr uint8_t FailedSyncs[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1687 constexpr uint8_t RetriedSyncs[] = {0x35, 0x35, 0x35, 0x91};
1688 constexpr uint8_t CleanSyncs[] = {0x35, 0x35};
1689 Fixture fixture;
1690 if (!fixture.ready) {
1691 ERROR("HOSTED-WAIT-TEST: FAIL scsi-checked-sync: fixture setup");
1692 return false;
1693 }
1694 fixture.disk.pauseBackgroundWriteback();
1695 const BufferView read = fixture.disk.read(CheckedSyncLocation);
1696 const uintptr_t page = read.address();
1697 fixture.controller.beginWrites(WriteMode::PassWrite12);
1698 fixture.controller.beginSync(SyncMode::Pass10);
1699 const bool cleanSynced = read && fixture.disk.sync(CheckedSyncLocation, false) &&
1700 fixture.disk.sync(CheckedSyncLocation, false) &&
1701 fixture.controller.hasNoDirectActivity() &&
1702 fixture.controller.syncTraceMatches(CleanSyncs, sizeof(CleanSyncs));
1703 if (read) {
1704 fixture.disk.unpin(CheckedSyncLocation);
1705 }
1706 fixture.disk.resumeBackgroundWriteback();
1707 const bool cleanBackground = fixture.disk.runBackgroundWriteback() &&
1708 fixture.controller.hasNoDirectActivity() &&
1709 fixture.controller.syncTraceMatches(CleanSyncs, sizeof(CleanSyncs));
1710 fixture.disk.pauseBackgroundWriteback();
1711 auto writer = fixture.disk.writeView(CheckedSyncLocation);
1712 const uint8_t value = 0x7d;
1713 const bool changed = writer && writer.writeAt(value, 0);
1714 writer.reset();
1715 fixture.controller.beginWrites(WriteMode::PassWrite12);
1716 fixture.controller.beginSync(SyncMode::FailAll);
1717 fixture.disk.beginUnpinObservation();
1718 const bool failed = !fixture.disk.sync(CheckedSyncLocation, false);
1719 const size_t failureUnpins = fixture.disk.endUnpinObservation();
1720 const bool failureReported =
1721 failed && failureUnpins == 0 &&
1722 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1723 fixture.controller.writeTraceMatches(SuccessfulWrites, sizeof(SuccessfulWrites)) &&
1724 fixture.controller.syncTraceMatches(FailedSyncs, sizeof(FailedSyncs));
1725
1726 fixture.controller.beginWrites(WriteMode::PassWrite12);
1727 fixture.controller.beginSync(SyncMode::FailAll);
1728 const bool retained =
1729 !fixture.disk.evictPage(CheckedSyncLocation) && fixture.disk.hasPage(CheckedSyncLocation) &&
1730 fixture.disk.pageAddress(CheckedSyncLocation) == page &&
1731 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1732 fixture.controller.writeTraceMatches(SuccessfulWrites, sizeof(SuccessfulWrites)) &&
1733 fixture.controller.syncTraceMatches(FailedSyncs, sizeof(FailedSyncs));
1734
1735 fixture.controller.beginWrites(WriteMode::PassWrite12);
1736 fixture.controller.beginSync(SyncMode::Pass16);
1737 const bool pinned = fixture.disk.pin(CheckedSyncLocation);
1738 fixture.disk.beginUnpinObservation();
1739 const bool retried = fixture.disk.sync(CheckedSyncLocation, false);
1740 const size_t retryUnpins = fixture.disk.endUnpinObservation();
1741 const bool retryReported =
1742 retried && retryUnpins == 0 &&
1743 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1744 fixture.controller.writeTraceMatches(SuccessfulWrites, sizeof(SuccessfulWrites)) &&
1745 fixture.controller.syncTraceMatches(RetriedSyncs, sizeof(RetriedSyncs));
1746 const bool callerPinPreserved = pinned && !fixture.disk.evictPage(CheckedSyncLocation);
1747 if (pinned) {
1748 fixture.disk.unpin(CheckedSyncLocation);
1749 }
1750 fixture.controller.beginWrites(WriteMode::PassWrite12);
1751 fixture.controller.beginSync(SyncMode::Pass10);
1752 const bool repeated = fixture.disk.sync(CheckedSyncLocation, false) &&
1753 fixture.disk.sync(CheckedSyncLocation, false) &&
1754 fixture.controller.hasNoDirectActivity() &&
1755 fixture.controller.syncTraceMatches(CleanSyncs, sizeof(CleanSyncs));
1756 fixture.disk.resumeBackgroundWriteback();
1757 const bool cleaned =
1758 fixture.disk.runBackgroundWriteback() && fixture.disk.evictPage(CheckedSyncLocation) &&
1759 !fixture.disk.hasPage(CheckedSyncLocation) && fixture.controller.hasNoDirectActivity() &&
1760 fixture.controller.syncTraceMatches(CleanSyncs, sizeof(CleanSyncs));
1761 const bool passed = cleanSynced && cleanBackground && changed && failureReported && retained &&
1762 retryReported && callerPinPreserved && repeated && cleaned;
1763 if (passed) {
1764 NOTICE("HOSTED-WAIT-TEST: PASS scsi-checked-sync");
1765 } else {
1766 ERROR(
1767 "HOSTED-WAIT-TEST: FAIL scsi-checked-sync: clean writes, error retention, retry, or "
1768 "borrowed pin ownership");
1769 }
1770 return passed;
1771}
1772
1773bool scsiSyncAll() {
1774 constexpr uint64_t WholeRequest[] = {ScsiDisk::SyncWholeDevice};
1775 constexpr uint64_t PageThenWhole[] = {CheckedSyncLocation, ScsiDisk::SyncWholeDevice};
1776 constexpr uint8_t Pass10[] = {0x35};
1777 constexpr uint8_t Pass16[] = {0x35, 0x35, 0x35, 0x91};
1778 constexpr uint8_t FailAll[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1779 constexpr uint8_t PageAndWhole[] = {0x35, 0x35};
1780 constexpr uint8_t FinalFailed[] = {0x35, 0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1781 constexpr bool WholeGeometry[] = {true, true, true, true, true, true};
1782 constexpr bool PageAndWholeGeometry[] = {false, true};
1783 constexpr bool FinalFailedGeometry[] = {false, true, true, true, true, true, true};
1784 constexpr uint8_t FailedWrites[] = {0x2a, 0x2a, 0x2a, 0xaa, 0xaa, 0xaa, 0x8a, 0x8a, 0x8a};
1785 constexpr uint8_t SuccessfulWrites[] = {0x2a, 0x2a, 0x2a, 0xaa};
1786 Fixture fixture;
1787 if (!fixture.ready) {
1788 ERROR("HOSTED-WAIT-TEST: FAIL scsi-sync-all: fixture setup");
1789 return false;
1790 }
1791
1792 fixture.controller.beginWrites(WriteMode::PassWrite12);
1793 fixture.controller.beginSync(SyncMode::Pass10);
1794 const bool emptyFlushed = fixture.disk.syncAll() && fixture.controller.writeCount() == 0 &&
1795 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1796 fixture.controller.syncTraceMatches(Pass10, sizeof(Pass10)) &&
1797 fixture.controller.syncGeometryMatches(WholeGeometry, 1);
1798 fixture.controller.beginSync(SyncMode::FailAll);
1799 const bool emptyFailure = !fixture.disk.syncAll() &&
1800 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1801 fixture.controller.syncTraceMatches(FailAll, sizeof(FailAll)) &&
1802 fixture.controller.syncGeometryMatches(WholeGeometry, 6);
1803 fixture.controller.beginSync(SyncMode::Pass16);
1804 const bool emptyRetry = fixture.disk.syncAll() &&
1805 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1806 fixture.controller.syncTraceMatches(Pass16, sizeof(Pass16)) &&
1807 fixture.controller.syncGeometryMatches(WholeGeometry, 4);
1808
1809 const bool prepared = fixture.disk.preparePage(CheckedSyncLocation);
1810 fixture.controller.beginWrites(WriteMode::FailAll);
1811 fixture.controller.beginSync(SyncMode::Pass10);
1812 const bool pageFailed =
1813 prepared && !fixture.disk.syncAll() && fixture.disk.hasPage(CheckedSyncLocation) &&
1814 fixture.controller.writeTraceMatches(FailedWrites, sizeof(FailedWrites)) &&
1815 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1816 fixture.controller.syncTraceMatches(PageAndWhole, sizeof(PageAndWhole)) &&
1817 fixture.controller.syncGeometryMatches(PageAndWholeGeometry, 2);
1818
1819 fixture.controller.beginWrites(WriteMode::PassWrite12);
1820 fixture.controller.beginSync(SyncMode::FailWhole);
1821 const bool finalFailed =
1822 !fixture.disk.syncAll() && fixture.disk.hasPage(CheckedSyncLocation) &&
1823 fixture.controller.writeTraceMatches(SuccessfulWrites, sizeof(SuccessfulWrites)) &&
1824 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1825 fixture.controller.syncTraceMatches(FinalFailed, sizeof(FinalFailed)) &&
1826 fixture.controller.syncGeometryMatches(FinalFailedGeometry, 7);
1827
1828 fixture.controller.beginWrites(WriteMode::PassWrite12);
1829 fixture.controller.beginSync(SyncMode::Pass10);
1830 const bool retried =
1831 fixture.disk.syncAll() &&
1832 fixture.controller.writeTraceMatches(SuccessfulWrites, sizeof(SuccessfulWrites)) &&
1833 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1834 fixture.controller.syncTraceMatches(PageAndWhole, sizeof(PageAndWhole)) &&
1835 fixture.controller.syncGeometryMatches(PageAndWholeGeometry, 2);
1836 const bool cleaned = fixture.disk.evictPage(CheckedSyncLocation);
1837 const bool passed =
1838 emptyFlushed && emptyFailure && emptyRetry && pageFailed && finalFailed && retried && cleaned;
1839 if (passed) {
1840 NOTICE("HOSTED-WAIT-TEST: PASS scsi-sync-all");
1841 } else {
1842 ERROR("HOSTED-WAIT-TEST: FAIL scsi-sync-all: empty="
1843 << emptyFlushed << ", empty-failure=" << emptyFailure << ", empty-retry=" << emptyRetry
1844 << ", prepared=" << prepared << ", page-failure=" << pageFailed
1845 << ", final-failure=" << finalFailed << ", retry=" << retried << ", cleanup=" << cleaned);
1846 }
1847 return passed;
1848}
1849
1850bool scsiSyncBatch(bool failWrite, bool failFlush) {
1851 constexpr uint64_t Keys[] = {CheckedSyncLocation, CheckedSyncLocation + PageBytes};
1852 constexpr uint8_t Flush[] = {0x35};
1853 constexpr uint8_t FailedFlush[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1854 constexpr bool Whole[] = {true, true, true, true, true, true};
1855 Fixture fixture;
1856 if (!fixture.ready)
1857 return false;
1858 fixture.disk.pauseBackgroundWriteback();
1859 if (!fixture.disk.preparePage(Keys[0]) || !fixture.disk.preparePage(Keys[1]))
1860 return false;
1861 for (uint64_t key : Keys)
1862 fixture.disk.write(key);
1863 fixture.controller.beginWrites(failWrite ? WriteMode::FailAll : WriteMode::PassWrite12);
1864 fixture.controller.beginSync(failFlush ? SyncMode::FailAll : SyncMode::Pass10);
1865 const bool result = fixture.disk.syncPages(Keys, 2);
1866 const size_t expectedWrites = failWrite ? 18 : 8;
1867 const bool committed =
1868 result == !(failWrite || failFlush) && fixture.controller.writeCount() == expectedWrites &&
1869 fixture.controller.writesAtLastSync() == expectedWrites &&
1870 fixture.controller.syncTraceMatches(failFlush ? FailedFlush : Flush, failFlush ? 6 : 1) &&
1871 fixture.controller.syncGeometryMatches(Whole, failFlush ? 6 : 1);
1872 bool retained = true;
1873 if (failWrite || failFlush) {
1874 // A failed common barrier keeps even successfully transferred pages dirty.
1875 // A backend attempt on each eviction also detects a leaked batch pin.
1876 for (uint64_t key : Keys) {
1877 fixture.controller.beginWrites(WriteMode::PassWrite12);
1878 fixture.controller.beginSync(SyncMode::FailAll);
1879 retained = !fixture.disk.evictPage(key) && fixture.disk.hasPage(key) &&
1880 fixture.controller.writeCount() == 4 &&
1881 fixture.controller.syncTraceMatches(FailedFlush, sizeof(FailedFlush)) && retained;
1882 }
1883 }
1884 fixture.controller.beginWrites(WriteMode::PassWrite12);
1885 fixture.controller.beginSync(SyncMode::Pass10);
1886 const bool pinned = fixture.disk.pin(Keys[0]);
1887 const bool needsRetry = failWrite || failFlush;
1888 const size_t retryWrites = needsRetry ? 8 : 0;
1889 const bool retried = fixture.disk.syncPages(Keys, 2) &&
1890 fixture.controller.writeCount() == retryWrites &&
1891 fixture.controller.writesAtLastSync() == retryWrites &&
1892 fixture.controller.syncTraceMatches(Flush, needsRetry ? sizeof(Flush) : 0);
1893 const bool callerPin = pinned && !fixture.disk.evictPage(Keys[0]);
1894 if (pinned)
1895 fixture.disk.unpin(Keys[0]);
1896 const bool balanced = fixture.disk.evictPage(Keys[0]) && fixture.disk.evictPage(Keys[1]) &&
1897 fixture.controller.writeCount() == retryWrites &&
1898 fixture.controller.syncTraceMatches(Flush, needsRetry ? sizeof(Flush) : 0);
1899 const bool passed = committed && retained && retried && callerPin && balanced;
1900 if (passed)
1901 NOTICE("HOSTED-WAIT-TEST: PASS scsi-sync-batch write-failure="
1902 << failWrite << ", flush-failure=" << failFlush);
1903 else
1904 ERROR("HOSTED-WAIT-TEST: FAIL scsi-sync-batch: commit="
1905 << committed << ", retained=" << retained << ", retry=" << retried
1906 << ", caller-pin=" << callerPin << ", balanced=" << balanced);
1907 return passed;
1908}
1909
1910struct SyncBatchContext {
1911 HostedScsiDisk* disk;
1912 uint64_t keys[2];
1913 Atomic<size_t> entered{0};
1914 Atomic<size_t> returned{0};
1915 bool result = false;
1916};
1917
1918int syncBatchPages(void* parameter) {
1919 auto& context = *static_cast<SyncBatchContext*>(parameter);
1920 context.entered = 1;
1921 context.result = context.disk->syncPages(context.keys, 2);
1922 context.returned = 1;
1923 return 0;
1924}
1925
1926bool scsiOverlappingSyncBatches() {
1927 constexpr uint64_t First = CheckedSyncLocation;
1928 constexpr uint64_t Second = First + PageBytes;
1929 constexpr uint8_t Flushes[] = {0x35, 0x35};
1930 constexpr bool Whole[] = {true, true};
1931 Fixture fixture;
1932 if (!fixture.ready)
1933 return false;
1934 fixture.disk.pauseBackgroundWriteback();
1935 if (!fixture.disk.preparePage(First) || !fixture.disk.preparePage(Second))
1936 return false;
1937 fixture.disk.write(First);
1938 fixture.disk.write(Second);
1939 fixture.controller.beginWrites(WriteMode::PassWrite12);
1940 fixture.controller.beginSync(SyncMode::Pass10);
1941 fixture.controller.holdNextSync();
1942 SyncBatchContext first{&fixture.disk, {First, Second}};
1943 SyncBatchContext second{&fixture.disk, {Second, First}};
1944 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), syncBatchPages, &first,
1945 nullptr, false, true);
1946 producer->setName("hosted SCSI held batch barrier");
1947 const bool held = fixture.controller.waitForHeldSync();
1948 Thread* waiter = nullptr;
1949 bool waiting = false;
1950 if (held) {
1951 waiter = new Thread(Scheduler::instance().getKernelProcess(), syncBatchPages, &second, nullptr,
1952 false, true);
1953 waiter->setName("hosted SCSI overlapping reverse batch");
1954 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1955 while (Time::getTicks() < deadline) {
1956 Thread::WaitDebugInfo info = {};
1957 uintptr_t address = 0;
1958 if (waiter->getWaitDebugInfo(info) && info.queue && info.queued &&
1959 waiter->getDebugState(address) == Thread::CallbackDrain) {
1960 waiting = true;
1961 break;
1962 }
1963 if (second.returned)
1964 break;
1966 }
1967 }
1968 const bool excluded = held && waiting && !first.returned && !second.returned &&
1969 fixture.controller.writeCount() == 8 && !fixture.disk.evictPage(First) &&
1970 !fixture.disk.evictPage(Second);
1971 fixture.disk.write(First);
1972 fixture.disk.write(Second);
1973 fixture.controller.releaseHeldSync();
1974 const bool firstCompleted = waitUntilSet(first.returned);
1975 const bool secondCompleted = waiter && waitUntilSet(second.returned);
1976 const bool firstJoined = producer->joinForCompletion();
1977 const bool secondJoined = waiter && waiter->joinForCompletion();
1978 const bool serialised = firstCompleted && secondCompleted && firstJoined && secondJoined &&
1979 first.result && second.result && fixture.controller.writeCount() == 16 &&
1980 fixture.controller.writesAtLastSync() == 16 &&
1981 fixture.controller.syncTraceMatches(Flushes, sizeof(Flushes)) &&
1982 fixture.controller.syncGeometryMatches(Whole, 2);
1983 const bool balanced = fixture.disk.evictPage(First) && fixture.disk.evictPage(Second) &&
1984 fixture.controller.writeCount() == 16;
1985 const bool passed = excluded && serialised && balanced;
1986 if (passed)
1987 NOTICE("HOSTED-WAIT-TEST: PASS scsi-overlapping-sync-batches");
1988 else
1989 ERROR("HOSTED-WAIT-TEST: FAIL scsi-overlapping-sync-batches: excluded="
1990 << excluded << ", serialised=" << serialised << ", balanced=" << balanced);
1991 return passed;
1992}
1993
1994bool scsiSyncBatchValidation() {
1995 constexpr uint64_t Key = CheckedSyncLocation;
1996 constexpr uint64_t Duplicate[] = {Key, Key + 512, Key + 1024};
1997 constexpr uint64_t Missing[] = {Key, Key + PageBytes};
1998 constexpr uint64_t Outside[] = {Key, 64 * PageBytes};
1999 constexpr uint64_t Overflow[] = {Key, ~uint64_t{0}};
2000 constexpr uint8_t Flush[] = {0x35};
2001 Fixture fixture;
2002 if (!fixture.ready)
2003 return false;
2004 fixture.disk.pauseBackgroundWriteback();
2005 if (!fixture.disk.preparePage(Key))
2006 return false;
2007 fixture.controller.beginWrites(WriteMode::PassWrite12);
2008 fixture.controller.beginSync(SyncMode::Pass10);
2009 const bool rejected = fixture.disk.syncPages(nullptr, 0) && !fixture.disk.syncPages(nullptr, 1) &&
2010 !fixture.disk.syncPages(Duplicate, Cache::MaxWritebackPages + 1) &&
2011 !fixture.disk.syncPages(Missing, 2) &&
2012 !fixture.disk.syncPages(Outside, 2) && !fixture.disk.syncPages(Overflow, 2);
2013 const bool editing = fixture.disk.prepareEditingPage(Key + PageBytes) &&
2014 !fixture.disk.syncPages(Missing, 2) &&
2015 fixture.disk.discardEditingPage(Key + PageBytes);
2016 const bool noIo =
2017 fixture.controller.hasNoDirectActivity() && fixture.controller.syncTraceMatches(nullptr, 0);
2018 fixture.disk.write(Key);
2019 const bool deduplicated = fixture.disk.syncPages(Duplicate, 3) &&
2020 fixture.controller.writeCount() == 4 &&
2021 fixture.controller.writesAtLastSync() == 4 &&
2022 fixture.controller.syncTraceMatches(Flush, sizeof(Flush));
2023 const bool balanced = fixture.disk.evictPage(Key) && fixture.controller.writeCount() == 4;
2024 const bool passed = rejected && editing && noIo && deduplicated && balanced;
2025 if (passed)
2026 NOTICE("HOSTED-WAIT-TEST: PASS scsi-sync-batch-validation");
2027 else
2028 ERROR("HOSTED-WAIT-TEST: FAIL scsi-sync-batch-validation: rejected="
2029 << rejected << ", editing=" << editing << ", no-io=" << noIo << ", dedup=" << deduplicated
2030 << ", balanced=" << balanced);
2031 return passed;
2032}
2033
2034bool scsiSyncBatchTerminalGeometry() {
2035 constexpr uint64_t Terminal = 4 * PageBytes;
2036 constexpr size_t TerminalBytes = 512;
2037 constexpr uint64_t Keys[] = {0, Terminal};
2038 constexpr uint8_t Flush[] = {0x35};
2039 constexpr bool Whole[] = {true};
2040 ScriptedScsiController controller(Terminal + TerminalBytes);
2041 HostedScsiDisk disk;
2042 if (!disk.initialise(&controller, 0))
2043 return false;
2044 disk.pauseBackgroundWriteback();
2045 if (!disk.preparePage(Keys[0]) || !disk.preparePage(Keys[1]))
2046 return false;
2047 for (uint64_t key : Keys)
2048 disk.write(key);
2049 controller.beginWrites(WriteMode::PassWrite12);
2050 controller.beginSync(SyncMode::Pass10);
2051 const bool written =
2052 disk.syncPages(Keys, 2) && controller.writeCount() == 8 &&
2053 controller.lastWriteBytes() == TerminalBytes && controller.writesAtLastSync() == 8 &&
2054 controller.syncTraceMatches(Flush, sizeof(Flush)) && controller.syncGeometryMatches(Whole, 1);
2055 const bool balanced =
2056 disk.evictPage(Keys[0]) && disk.evictPage(Keys[1]) && controller.writeCount() == 8;
2057 const bool passed = written && balanced;
2058 if (passed)
2059 NOTICE("HOSTED-WAIT-TEST: PASS scsi-sync-batch-terminal");
2060 else
2061 ERROR("HOSTED-WAIT-TEST: FAIL scsi-sync-batch-terminal: write bounds, final flush or pins");
2062 return passed;
2063}
2064
2065bool scsiTerminalCachePage() {
2066 constexpr size_t TerminalBytes = 512;
2067 constexpr uint64_t TerminalLocation = 4 * PageBytes;
2068 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
2069
2070 ScriptedScsiController controller(TerminalLocation + TerminalBytes);
2071 HostedScsiDisk disk;
2072 const bool ready = disk.initialise(&controller, 0);
2073 controller.beginRequestTrace();
2074 const BufferView view = ready ? disk.read(TerminalLocation) : BufferView();
2075
2076 bool payloadValid =
2077 view && view.size() == TerminalBytes && controller.lastReadBytes() == TerminalBytes;
2078 bool tailZeroed = payloadValid;
2079 const uint8_t* page = payloadValid ? reinterpret_cast<const uint8_t*>(view.data()) : nullptr;
2080 for (size_t i = 0; page && i < TerminalBytes; ++i) {
2081 if (page[i] != 0x3c) {
2082 payloadValid = false;
2083 break;
2084 }
2085 }
2086 for (size_t i = TerminalBytes; page && i < PageBytes; ++i) {
2087 if (page[i]) {
2088 tailZeroed = false;
2089 break;
2090 }
2091 }
2092 if (view) {
2093 disk.unpin(TerminalLocation);
2094 }
2095
2096 controller.beginWrites(WriteMode::PassWrite12);
2097 const bool retired = ready && disk.retireCachePage(TerminalLocation);
2098 const bool boundedWriteback =
2099 retired && controller.lastWriteBytes() == TerminalBytes &&
2100 controller.writeTraceMatches(SuccessfulOpcodes, sizeof(SuccessfulOpcodes)) &&
2101 !disk.hasPage(TerminalLocation);
2102
2103 const bool passed = ready && payloadValid && tailZeroed && boundedWriteback;
2104 if (passed) {
2105 NOTICE("HOSTED-WAIT-TEST: PASS scsi-terminal-cache-page");
2106 } else {
2107 ERROR(
2108 "HOSTED-WAIT-TEST: FAIL scsi-terminal-cache-page: terminal read length, zero fill, or "
2109 "writeback bounds changed");
2110 }
2111 return passed;
2112}
2113
2114bool producerBytesMatch(const uint8_t* bytes, size_t length, uint8_t value) {
2115 for (size_t i = 0; i < length; ++i) {
2116 if (bytes[i] != value)
2117 return false;
2118 }
2119 return true;
2120}
2121
2122bool scsiProducerReadBatchCoherence() {
2123 constexpr uint64_t Cached = 4 * PageBytes;
2124 constexpr uint64_t FirstAbsent = 16 * PageBytes + 512;
2125 constexpr uint64_t SecondAbsent = 32 * PageBytes;
2126 constexpr uint64_t ExpectedPending[] = {FirstAbsent, SecondAbsent, FirstAbsent};
2127 Fixture fixture;
2128 if (!fixture.ready)
2129 return false;
2130 fixture.disk.pauseBackgroundWriteback();
2131 fixture.disk.enableBufferTransfers();
2132 fixture.controller.beginWrites(WriteMode::PassWrite12);
2133 if (!fixture.disk.preparePage(Cached))
2134 return false;
2135 const BufferView loan = fixture.disk.read(Cached);
2136 if (!loan)
2137 return false;
2138 ByteSet(static_cast<uint8_t*>(loan.data()) + 512, 0xc7, 512);
2139 fixture.disk.write(Cached);
2140 fixture.controller.beginRequestTrace();
2141 fixture.disk.beginReadBufferBatch();
2142
2143 uint8_t output[5][514];
2144 ByteSet(output, 0x3f, sizeof(output));
2145 Disk::ReadBuffer requests[] = {{Cached + 512, output[0] + 1, 512, false},
2146 {FirstAbsent, output[1] + 1, 512, false},
2147 {SecondAbsent, output[2] + 1, 512, false},
2148 {FirstAbsent, output[3] + 1, 512, false},
2149 {Cached + 1024, output[4] + 1, 512, false}};
2150 bool passed = fixture.disk.readIntoBatch(requests, 5) &&
2151 fixture.disk.bufferBatchMatches(ExpectedPending, 3) &&
2152 fixture.disk.bufferTransferCount() == 3 && !fixture.controller.readCommandCount() &&
2153 !fixture.controller.writeCount() && fixture.disk.hasPage(Cached) &&
2154 !fixture.disk.hasPage(16 * PageBytes) && !fixture.disk.hasPage(SecondAbsent) &&
2155 !fixture.disk.hasNoCacheLoans();
2156 for (size_t i = 0; i < 5; ++i) {
2157 const uint8_t expected = !i ? 0xc7 : i == 4 ? 0x5a : 0xa6;
2158 passed = passed && requests[i].complete && producerBytesMatch(output[i] + 1, 512, expected) &&
2159 output[i][0] == 0x3f && output[i][513] == 0x3f;
2160 }
2161
2162 ByteSet(static_cast<uint8_t*>(loan.data()) + 512, 0xd8, 512);
2163 fixture.disk.write(Cached);
2164 fixture.disk.beginReadBufferBatch();
2165 passed = fixture.disk.readIntoBatch(requests, 1) && requests[0].complete &&
2166 producerBytesMatch(output[0] + 1, 512, 0xd8) && !fixture.disk.bufferBatchCount() &&
2167 !fixture.disk.bufferTransferCount() && !fixture.controller.readCommandCount() && passed;
2168 fixture.disk.unpin(Cached);
2169 passed = fixture.disk.hasNoCacheLoans() && passed;
2170 if (passed)
2171 NOTICE("HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-coherence");
2172 else
2173 ERROR("HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-coherence");
2174 return passed;
2175}
2176
2177bool scsiProducerReadBatchFailure() {
2178 constexpr uint64_t Locations[] = {4 * PageBytes, 8 * PageBytes, 12 * PageBytes};
2179 Fixture fixture;
2180 if (!fixture.ready)
2181 return false;
2182 fixture.disk.enableBufferTransfers();
2183 fixture.disk.beginReadBufferBatch(Locations[1]);
2184 uint8_t output[3][514];
2185 ByteSet(output, 0x3f, sizeof(output));
2186 Disk::ReadBuffer requests[3];
2187 for (size_t i = 0; i < 3; ++i)
2188 requests[i] = {Locations[i], output[i] + 1, 512, true};
2189 bool passed = !fixture.disk.readIntoBatch(requests, 3) &&
2190 fixture.disk.bufferBatchMatches(Locations, 3) &&
2191 fixture.disk.bufferTransferCount() == 3;
2192 for (size_t i = 0; i < 3; ++i) {
2193 passed = passed && requests[i].complete == (i != 1) &&
2194 producerBytesMatch(output[i] + 1, 512, i == 1 ? 0x3f : 0xa6) && output[i][0] == 0x3f &&
2195 output[i][513] == 0x3f && !fixture.disk.hasPage(Locations[i]);
2196 }
2197 fixture.disk.beginReadBufferBatch();
2198 passed = fixture.disk.readIntoBatch(requests, 3) &&
2199 fixture.disk.bufferBatchMatches(Locations, 3) &&
2200 fixture.disk.bufferTransferCount() == 3 && passed;
2201 for (size_t i = 0; i < 3; ++i)
2202 passed = passed && requests[i].complete && producerBytesMatch(output[i] + 1, 512, 0xa6);
2203 passed = fixture.disk.hasNoCacheLoans() && passed;
2204 if (passed)
2205 NOTICE("HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-failure");
2206 else
2207 ERROR("HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-failure");
2208 return passed;
2209}
2210
2211enum class ReadBatchFallback { Unsupported, Unaligned, NativeSector, PageCrossing, ShiftedOrigin };
2212
2213bool scsiProducerReadBatchFallbackCase(ReadBatchFallback mode) {
2214 const size_t native = mode == ReadBatchFallback::NativeSector ? PageBytes : 512;
2215 ScriptedScsiController controller(64 * PageBytes, native);
2216 HostedScsiDisk disk;
2217 if (!disk.initialise(&controller, 0))
2218 return false;
2219 disk.pauseBackgroundWriteback();
2220 if (mode != ReadBatchFallback::Unsupported)
2221 disk.enableBufferTransfers();
2222 controller.beginWrites(WriteMode::PassWrite12);
2223 uint64_t locations[] = {0, 8 * PageBytes};
2224 size_t lengths[] = {512, 512};
2225 uint8_t expected = 0x3c;
2226 BufferView loan;
2227 if (mode == ReadBatchFallback::Unaligned) {
2228 ++locations[0];
2229 locations[1] += 3;
2230 lengths[0] = lengths[1] = 7;
2231 } else if (mode == ReadBatchFallback::NativeSector) {
2232 locations[0] += 512;
2233 locations[1] += 1024;
2234 } else if (mode == ReadBatchFallback::PageCrossing) {
2235 lengths[0] = PageBytes + 512;
2236 expected = 0xa6;
2237 } else if (mode == ReadBatchFallback::ShiftedOrigin) {
2238 locations[0] = 1536;
2239 locations[1] = 2048;
2240 disk.align(locations[0]);
2241 if (!disk.preparePage(locations[0]))
2242 return false;
2243 loan = disk.read(locations[0]);
2244 if (!loan)
2245 return false;
2246 ByteSet(loan.data(), 0xc7, PageBytes);
2247 disk.write(locations[0]);
2248 expected = 0xc7;
2249 }
2250 auto first = UniqueArray<uint8_t>::allocate(lengths[0] + 2);
2251 auto second = UniqueArray<uint8_t>::allocate(lengths[1] + 2);
2252 ByteSet(first.get(), 0x3f, lengths[0] + 2);
2253 ByteSet(second.get(), 0x3f, lengths[1] + 2);
2254 Disk::ReadBuffer requests[] = {{locations[0], first.get() + 1, lengths[0], true},
2255 {locations[1], second.get() + 1, lengths[1], true}};
2256 disk.beginReadBufferBatch();
2257 const bool passed =
2258 disk.readIntoBatch(requests, 2) && requests[0].complete && requests[1].complete &&
2259 !disk.bufferBatchCount() &&
2260 disk.bufferTransferCount() == (mode == ReadBatchFallback::PageCrossing ? 3U : 0U) &&
2261 producerBytesMatch(first.get() + 1, lengths[0], expected) &&
2262 producerBytesMatch(second.get() + 1, lengths[1], expected) && first.get()[0] == 0x3f &&
2263 first.get()[lengths[0] + 1] == 0x3f && second.get()[0] == 0x3f &&
2264 second.get()[lengths[1] + 1] == 0x3f &&
2265 (!loan || producerBytesMatch(static_cast<uint8_t*>(loan.data()), PageBytes, 0xc7));
2266 if (loan)
2267 disk.unpin(locations[0]);
2268 if (!passed)
2269 ERROR(
2270 "HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-fallback mode=" << static_cast<int>(mode));
2271 return passed && disk.hasNoCacheLoans();
2272}
2273
2274bool scsiProducerReadBatchFallbacks() {
2275 const ReadBatchFallback modes[] = {
2276 ReadBatchFallback::Unsupported, ReadBatchFallback::Unaligned, ReadBatchFallback::NativeSector,
2277 ReadBatchFallback::PageCrossing, ReadBatchFallback::ShiftedOrigin};
2278 for (auto mode : modes) {
2279 if (!scsiProducerReadBatchFallbackCase(mode))
2280 return false;
2281 }
2282 NOTICE("HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-fallbacks");
2283 return true;
2284}
2285
2286bool scsiProducerShiftedOriginFallback() {
2287 constexpr uint64_t ShiftedOrigin = 1536;
2288 constexpr uint64_t LaterPage = 4 * PageBytes;
2289 Fixture fixture;
2290 if (!fixture.ready)
2291 return false;
2292 fixture.disk.pauseBackgroundWriteback();
2293 fixture.disk.enableBufferTransfers();
2294 fixture.controller.beginWrites(WriteMode::PassWrite12);
2295 fixture.controller.beginSync(SyncMode::Pass10);
2296 uint8_t buffer[1024] = {};
2297
2298 fixture.disk.align(PageBytes);
2299 const bool alignedFastPath = fixture.disk.readInto(LaterPage, buffer, 512) && buffer[0] == 0xa6 &&
2300 fixture.disk.bufferTransferCount() == 1 &&
2301 !fixture.disk.hasPage(LaterPage);
2302
2303 fixture.disk.align(ShiftedOrigin);
2304 const bool prepared = fixture.disk.preparePage(ShiftedOrigin);
2305 const BufferView loan = prepared ? fixture.disk.read(ShiftedOrigin) : BufferView();
2306 if (loan) {
2307 static_cast<uint8_t*>(loan.data())[0] = 0xc7;
2308 fixture.disk.write(ShiftedOrigin);
2309 }
2310
2311 // Even bytes before the origin can occupy a page overlapping its cache key.
2312 const bool beforeOrigin = fixture.disk.readInto(0, buffer, 512);
2313 const bool acrossOrigin = fixture.disk.readInto(1024, buffer, sizeof(buffer));
2314 const bool afterAlignedOrigin = fixture.disk.readInto(LaterPage, buffer, 512);
2315 const bool dirtyLoanVisible =
2316 loan && fixture.disk.readInto(ShiftedOrigin, buffer, 512) && buffer[0] == 0xc7;
2317 ByteSet(buffer, 0xd2, sizeof(buffer));
2318 const bool loanUpdated = loan && fixture.disk.writeFrom(ShiftedOrigin, buffer, 512) &&
2319 static_cast<const uint8_t*>(loan.data())[0] == 0xd2 &&
2320 static_cast<const uint8_t*>(loan.data())[512] == 0x5a;
2321 const bool crossWrite = fixture.disk.writeFrom(1024, buffer, sizeof(buffer));
2322 const bool legacyOnly = fixture.disk.bufferTransferCount() == 1 && fixture.disk.hasPage(0) &&
2323 fixture.disk.hasPage(ShiftedOrigin) && fixture.disk.hasPage(LaterPage);
2324 if (loan)
2325 fixture.disk.unpin(ShiftedOrigin);
2326
2327 const bool passed = alignedFastPath && prepared && beforeOrigin && acrossOrigin &&
2328 afterAlignedOrigin && dirtyLoanVisible && loanUpdated && crossWrite &&
2329 legacyOnly;
2330 if (passed) {
2331 NOTICE("HOSTED-WAIT-TEST: PASS scsi-producer-shifted-origin-fallback");
2332 } else {
2333 ERROR("HOSTED-WAIT-TEST: FAIL scsi-producer-shifted-origin-fallback: aligned="
2334 << alignedFastPath << ", prepared=" << prepared << ", before=" << beforeOrigin
2335 << ", across=" << acrossOrigin << ", after=" << afterAlignedOrigin
2336 << ", dirty-loan=" << dirtyLoanVisible << ", updated=" << loanUpdated
2337 << ", cross-write=" << crossWrite << ", legacy=" << legacyOnly);
2338 }
2339 return passed;
2340}
2341
2342bool scsiOpticalReadAfterToc() {
2343 constexpr size_t OpticalBlockBytes = 2048;
2344 constexpr uint64_t VolumeDescriptorLocation = 16 * OpticalBlockBytes;
2345
2346 ScriptedScsiController controller(64 * PageBytes, OpticalBlockBytes, true);
2347 HostedScsiDisk disk;
2348 const bool ready = disk.initialise(&controller, 0);
2349 controller.beginRequestTrace();
2350 const BufferView view = ready ? disk.read(VolumeDescriptorLocation) : BufferView();
2351
2352 bool payloadValid = view && view.size() == PageBytes;
2353 const uint8_t* payload = payloadValid ? reinterpret_cast<const uint8_t*>(view.data()) : nullptr;
2354 for (size_t i = 0; payload && i < view.size(); ++i) {
2355 if (payload[i] != 0x3c) {
2356 payloadValid = false;
2357 break;
2358 }
2359 }
2360 if (view) {
2361 disk.unpin(VolumeDescriptorLocation);
2362 }
2363
2364 const bool passed = ready && payloadValid && controller.readTocCount() == 1 &&
2365 controller.readRequestCount() == 1 && controller.readCommandCount() == 1 &&
2366 controller.lastReadOpcode() == 0x28 &&
2367 controller.lastReadBytes() == PageBytes;
2368 if (passed) {
2369 NOTICE("HOSTED-WAIT-TEST: PASS scsi-optical-read-after-toc");
2370 } else {
2371 ERROR(
2372 "HOSTED-WAIT-TEST: FAIL scsi-optical-read-after-toc: successful READ TOC suppressed "
2373 "the media READ(10) or published an empty cache page");
2374 }
2375 return passed;
2376}
2377
2378bool scsiRejectsNativeBlocksLargerThanCachePages() {
2379 ScriptedScsiController controller(64 * PageBytes, 2 * PageBytes);
2380 HostedScsiDisk disk;
2381 const bool passed = !disk.initialise(&controller, 0);
2382 if (passed) {
2383 NOTICE("HOSTED-WAIT-TEST: PASS scsi-larger-native-block-rejected");
2384 } else {
2385 ERROR(
2386 "HOSTED-WAIT-TEST: FAIL scsi-larger-native-block-rejected: a native block spanning "
2387 "independently owned cache pages was accepted without an RMW contract");
2388 }
2389 return passed;
2390}
2391} // namespace
2392
2393EXPORTED_PUBLIC bool runHostedScsiSyncRegressions() {
2394 const bool scopedViews = scsiScopedDiskViews();
2395 const bool viewAlignment = scsiViewSurvivesAlignmentChange();
2396 const bool deferredWrites = scsiDeferredWrites();
2397 const bool deferredRetry = scsiDeferredWriteRetry();
2398 const bool deferredShutdown = scsiDeferredWriteShutdown();
2399 const bool syncAll = scsiSyncAll();
2400 const bool batch = scsiSyncBatch(false, false);
2401 const bool batchWriteFailure = scsiSyncBatch(true, false);
2402 const bool batchFlushFailure = scsiSyncBatch(false, true);
2403 const bool overlappingBatches = scsiOverlappingSyncBatches();
2404 const bool batchValidation = scsiSyncBatchValidation();
2405 const bool batchTerminal = scsiSyncBatchTerminalGeometry();
2406 const bool producerAlignment = scsiProducerShiftedOriginFallback();
2407 const bool producerReads = scsiProducerReadBatchCoherence();
2408 const bool producerReadFailure = scsiProducerReadBatchFailure();
2409 const bool producerReadFallbacks = scsiProducerReadBatchFallbacks();
2410 return scopedViews && viewAlignment && deferredWrites && deferredRetry && deferredShutdown &&
2411 syncAll && batch && batchWriteFailure && batchFlushFailure && overlappingBatches &&
2412 batchValidation && batchTerminal && producerAlignment && producerReads &&
2413 producerReadFailure && producerReadFallbacks;
2414}
2415
2416EXPORTED_PUBLIC bool runHostedScsiWriteRegressions() {
2417 Fixture fixture;
2418 const bool ataOwnership = ataQueuedWriteCacheOwnership(fixture);
2419 const bool scsiResult = scsiWriteResult(fixture);
2420 const bool directResult = scsiDirectRetireResult(fixture);
2421 const bool directOwnership = scsiDirectRetireOwnership(fixture);
2422 const bool readRetireAdmission = scsiReadRetireAdmission(fixture);
2423 const bool retireReadRecheck = scsiRetireReadRecheck(fixture);
2424 const bool checkedSync = scsiCheckedSync();
2425 const bool syncAll = runHostedScsiSyncRegressions();
2426 const bool terminalPage = scsiTerminalCachePage();
2427 const bool opticalRead = scsiOpticalReadAfterToc();
2428 const bool largerNativeRejected = scsiRejectsNativeBlocksLargerThanCachePages();
2429 return ataOwnership && scsiResult && directResult && directOwnership && readRetireAdmission &&
2430 retireReadRecheck && checkedSync && syncAll && terminalPage && opticalRead &&
2431 largerNativeRejected;
2432}
bool(* retirement_writeback_t)(uintptr_t key, uintptr_t page, void *meta)
Definition Cache.h:293
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)
virtual void unpin(uint64_t location)
Definition ScsiDisk.cc:1595
virtual MUST_USE_RESULT uint64_t doWriteDirect(uint64_t location, uintptr_t page)
Definition ScsiDisk.cc:1435
virtual size_t getCacheFillSize() const
Definition ScsiDisk.h:192
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
bool joinForCompletion()
Definition Thread.cc:2750
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
Definition waits.c:9