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"
19#include "modules/drivers/common/ata/AtaWriteCache.h"
20#include "modules/drivers/common/scsi/ScsiController.h"
21#include "modules/drivers/common/scsi/ScsiDisk.h"
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;
48enum class WriteMode { Initialising, FailAll, PassWrite12, UnitNotReady };
49enum class SyncMode { Pass10, Pass16, FailAll, FailWhole };
50enum class RequestEvent : uint8_t { Read = 1, Direct = 2 };
54 explicit ScriptedScsiController(
size_t capacityBytes = 64 * PageBytes,
55 size_t nativeBlockBytes = 512,
bool optical =
false)
57 m_Mode(WriteMode::Initialising),
60 m_SyncMode(SyncMode::Pass10),
64 m_WritesAtLastSync(0),
66 m_SyncEntered(0, false),
67 m_SyncRelease(0, false),
68 m_SyncRequestLocations(),
69 m_SyncRequestCount(0),
72 m_DirectRequestCount(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),
86 m_RequestEventCount(0),
87 m_CapacityBytes(capacityBytes),
88 m_NativeBlockBytes(nativeBlockBytes),
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) {
101 const uint8_t opcode = *
reinterpret_cast<const uint8_t*
>(pCommand);
104 if (bWrite || pRespBuffer || nRespBytes || nCommandSize != 6) {
108 if (m_Mode != WriteMode::Initialising)
110 return m_Mode != WriteMode::UnitNotReady;
118 ByteSet(inquiry, 0,
sizeof(*inquiry));
119 inquiry->Peripheral = m_Optical ? CdDvdPeripheral : 0;
124 nCommandSize != 10 || !m_NativeBlockBytes || !m_CapacityBytes ||
125 (m_CapacityBytes % m_NativeBlockBytes)) {
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));
136 if (!m_Optical || bWrite || !pRespBuffer || nRespBytes != m_NativeBlockBytes ||
137 nCommandSize != 10) {
142 auto* toc =
reinterpret_cast<uint8_t*
>(pRespBuffer);
143 ByteSet(toc, 0, nRespBytes);
155 return handleRead(opcode, nCommandSize, pRespBuffer, nRespBytes, bWrite);
159 return handleWrite(opcode, nCommandSize, pRespBuffer, nRespBytes, bWrite);
162 if (bWrite || pRespBuffer || nRespBytes || nCommandSize != (opcode == 0x35 ? 10 : 16) ||
163 m_SyncCount >= sizeof(m_SyncOpcodes)) {
167 const uint8_t* command =
reinterpret_cast<const uint8_t*
>(pCommand);
169 for (
size_t i = 1; i < nCommandSize; ++i) {
170 whole = whole && command[i] == 0;
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()) {
182 return m_SyncMode == SyncMode::Pass10 ||
183 (m_SyncMode == SyncMode::Pass16 && opcode == 0x91) ||
184 (m_SyncMode == SyncMode::FailWhole && !whole);
192 void beginWrites(WriteMode mode) {
195 m_UnitReadyCount = 0;
196 m_LastWriteBuffer = 0;
197 m_DirectRequestCount = 0;
199 m_DirectLocation = 0;
201 m_LastWriteBytes = 0;
203 m_WritesAtLastSync = 0;
204 m_SyncRequestCount = 0;
207 void beginSync(SyncMode mode) {
210 m_WritesAtLastSync = 0;
211 m_SyncRequestCount = 0;
214 void holdNextSync() {
218 bool waitForHeldSync() {
219 return m_SyncEntered.acquireForCompletion(1, 0, 500000);
222 void releaseHeldSync() {
224 m_SyncRelease.release();
227 bool syncTraceMatches(
const uint8_t* expected,
size_t count)
const {
228 return m_Valid && m_SyncCount == count && !MemoryCompare(m_SyncOpcodes, expected, count);
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));
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));
241 void beginRequestTrace() {
242 m_ReadRequestCount = 0;
243 m_ReadCommandCount = 0;
245 m_LastReadOpcode = 0;
246 m_RequestEventCount = 0;
250 void holdNextRead() {
251 const size_t staleEntries = m_ReadEntered.drainAvailable();
252 const size_t staleReleases = m_ReadRelease.drainAvailable();
255 m_DelegateHeldRead = 0;
257 m_ReadHoldsRemaining = 1;
260 void holdFollowingRead() {
261 m_ReadHoldsRemaining += 1;
264 bool waitForHeldRead() {
265 return m_ReadEntered.acquireForCompletion(1, 0, 500000);
268 void releaseHeldRead(
bool delegate) {
269 m_DelegateHeldRead = delegate ? 1 : 0;
270 m_ReadRelease.release();
273 void disarmReadHold() {
274 m_ReadHoldsRemaining = 0;
276 const size_t staleEntries = m_ReadEntered.drainAvailable();
277 const size_t staleReleases = m_ReadRelease.drainAvailable();
278 m_ReadRelease.release();
283 size_t readRequestCount()
const {
284 return m_ReadRequestCount.value();
287 size_t readCommandCount()
const {
288 return m_ReadCommandCount.value();
291 size_t readTocCount()
const {
292 return m_ReadTocCount.value();
295 uint8_t lastReadOpcode()
const {
296 return m_LastReadOpcode;
299 size_t directRequestCount()
const {
300 return m_DirectRequestCount;
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));
308 bool writeTraceMatches(
const uint8_t* expected,
size_t count)
const {
309 return m_Valid && m_WriteCount == count && !MemoryCompare(m_WriteOpcodes, expected, count);
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;
318 bool hasNoDirectActivity()
const {
319 return m_Valid && !m_DirectRequestCount && !m_UnitReadyCount && !m_WriteCount;
322 size_t unitReadyCount()
const {
323 return m_UnitReadyCount;
326 size_t writeCount()
const {
330 size_t writesAtLastSync()
const {
331 return m_WritesAtLastSync;
334 uintptr_t lastWriteBuffer()
const {
335 return m_LastWriteBuffer;
338 size_t lastReadBytes()
const {
339 return m_LastReadBytes;
342 size_t lastWriteBytes()
const {
343 return m_LastWriteBytes;
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();
353 while (remaining && !hold) {
354 hold = m_ReadHoldsRemaining.compareAndSwap(remaining, remaining - 1);
355 remaining = m_ReadHoldsRemaining.value();
359 m_ReadEntered.release();
360 if (!m_ReadRelease.acquireForCompletion()) {
365 const bool delegate =
static_cast<size_t>(m_DelegateHeldRead) != 0;
371 if (p1 == SCSI_REQUEST_SYNC) {
372 if (m_SyncRequestCount >=
373 sizeof(m_SyncRequestLocations) /
sizeof(m_SyncRequestLocations[0])) {
377 m_SyncRequestLocations[m_SyncRequestCount++] = p3;
379 if (p1 == SCSI_REQUEST_WRITE_DIRECT) {
380 appendRequestEvent(RequestEvent::Direct);
381 ++m_DirectRequestCount;
383 m_DirectLocation = p3;
390 size_t getNumUnits()
override {
395 bool handleRead(uint8_t opcode, uint8_t commandSize, uintptr_t response, uint16_t responseBytes,
397 const uint8_t expectedSize = opcode == 0x28 ? 10 : (opcode == 0xa8 ? 12 : 16);
398 if (write || !response || !responseBytes || (responseBytes % m_NativeBlockBytes) ||
399 commandSize != expectedSize) {
404 ByteSet(
reinterpret_cast<void*
>(response), 0x3c, responseBytes);
405 m_LastReadBytes = responseBytes;
406 m_LastReadOpcode = opcode;
407 m_ReadCommandCount += 1;
411 bool handleWrite(uint8_t opcode, uint8_t commandSize, uintptr_t response, uint16_t responseBytes,
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)) {
421 m_WriteOpcodes[m_WriteCount++] = opcode;
422 m_LastWriteBuffer = response;
423 m_LastWriteBytes = responseBytes;
424 return m_Mode == WriteMode::PassWrite12 && opcode == 0xaa;
427 void appendRequestEvent(RequestEvent event) {
428 if (m_RequestEventCount >=
sizeof(m_RequestEvents) /
sizeof(m_RequestEvents[0])) {
432 m_RequestEvents[m_RequestEventCount++] = event;
436 uint8_t m_WriteOpcodes[32];
439 uint8_t m_SyncOpcodes[8];
442 size_t m_WritesAtLastSync;
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;
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;
468 size_t m_LastReadBytes;
469 size_t m_LastWriteBytes;
473class HostedScsiDisk final :
public ScsiDisk {
477 m_OverrideDirectResult(false),
479 m_ObserveUnpin(false),
481 m_LastUnpinLocation(0),
482 m_CacheFillSize(PageBytes),
483 m_BufferTransfersEnabled(false),
484 m_BufferTransferCount(0) {}
486 bool preparePage(uint64_t location) {
487 bool alreadyExisted =
false;
488 const uintptr_t buffer = getCache().insert(location, &alreadyExisted);
489 if (!buffer || alreadyExisted)
491 ByteSet(
reinterpret_cast<void*
>(buffer), 0x5a, PageBytes);
492 getCache().markNoLongerEditing(location);
496 bool prepareEditingPage(uint64_t location) {
497 bool alreadyExisted =
false;
498 return getCache().insert(location, &alreadyExisted) && !alreadyExisted;
501 bool discardEditingPage(uint64_t location) {
502 return getCache().discardEditing(location);
505 bool takeAtaQueuedWritePage(uint64_t location) {
506 const uintptr_t buffer = ataTakeQueuedWritePage(getCache(), location);
513 bool missesAtaQueuedWritePage(uint64_t location) {
514 const uintptr_t buffer = ataTakeQueuedWritePage(getCache(), location);
522 bool evictPage(uint64_t location) {
523 return getCache().evict(location);
526 void checksumPage(uint64_t location) {
527 getCache().triggerChecksum(location);
530 void pauseBackgroundWriteback() {
531 getCache().startAtomic();
534 void resumeBackgroundWriteback() {
535 getCache().endAtomic();
538 bool runBackgroundWriteback() {
539 getCache().timer(CACHE_WRITEBACK_PERIOD * 1000000ULL);
540 return CacheManager::instance().
drain();
543 bool evictRange(uint64_t location,
size_t length) {
545 for (
size_t offset = 0; offset < length; offset += PageBytes) {
546 if (hasPage(location + offset) && !getCache().evict(location + offset))
553 return getCache().retireWriteback(location, callback, context);
556 bool hasPage(uint64_t location) {
557 return getCache().exists(location, PageBytes);
560 uintptr_t pageAddress(uint64_t location) {
561 const uintptr_t page = getCache().lookup(location);
563 getCache().release(location);
567 void overrideDirectResult(uint64_t result) {
568 m_DirectResult = result;
569 m_OverrideDirectResult =
true;
572 void restoreDirectResult() {
573 m_OverrideDirectResult =
false;
576 void setCacheFillSize(
size_t fillSize) {
577 m_CacheFillSize = fillSize;
580 void enableBufferTransfers() {
581 m_BufferTransfersEnabled =
true;
584 size_t bufferTransferCount()
const {
585 return m_BufferTransferCount;
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;
595 size_t bufferBatchCount()
const {
596 return m_BufferBatchCount;
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));
604 void beginUnpinObservation() {
606 m_LastUnpinLocation = 0;
607 m_ObserveUnpin =
true;
610 size_t endUnpinObservation() {
611 m_ObserveUnpin =
false;
615 uint64_t lastUnpinLocation()
const {
616 return m_LastUnpinLocation;
619 void unpin(uint64_t location)
override {
620 if (m_ObserveUnpin) {
622 m_LastUnpinLocation = location;
628 uint64_t
doWriteDirect(uint64_t location, uintptr_t page)
override {
629 if (m_OverrideDirectResult)
630 return m_DirectResult;
635 bool supportsBufferTransfers()
const override {
636 return m_BufferTransfersEnabled;
639 bool transferBuffer(uint64_t location,
void* buffer,
size_t length,
bool writing)
override {
640 ++m_BufferTransferCount;
641 if (!writing && location == m_FailedBufferLocation)
644 ByteSet(buffer, 0xa6, length);
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);
657 return m_CacheFillSize;
661 bool m_OverrideDirectResult;
662 uint64_t m_DirectResult;
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};
675struct RetirementResult {
680RetirementResult retireAndRepair(HostedScsiDisk& disk, uint64_t location,
681 bool repairOneTransferredPin) {
682 if (disk.evictPage(location))
684 if (!repairOneTransferredPin)
685 return {
false,
false};
687 disk.unpin(location);
688 return {
false, disk.evictPage(location)};
692 Fixture() : controller(), disk(), ready(false) {
693 ready = disk.initialise(&controller, 0);
696 ScriptedScsiController controller;
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) {
705 uintptr_t address = 0;
707 thread->
getDebugState(address) == debugState && address == debugAddress) {
716 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
717 while (Time::getTicks() < deadline) {
718 if (
static_cast<size_t>(value))
722 return static_cast<size_t>(value) != 0;
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) {
730 uintptr_t address = 0;
733 admissionWait = address == location;
736 if (controller.readRequestCount() > priorReadCount) {
737 admissionWait =
false;
742 admissionWait =
false;
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) {
751 uintptr_t address = 0;
753 retirer->
getDebugState(address) == Thread::CondWait && address == location) {
754 admissionWait =
true;
757 if (!disk.pageAddress(location)) {
758 admissionWait =
false;
763 admissionWait =
false;
768 ReadContext(HostedScsiDisk* pDisk, uint64_t readLocation)
769 : disk(pDisk), location(readLocation), returned(0), result(0) {}
771 HostedScsiDisk* disk;
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);
782 context->disk->unpin(context->location);
783 context->returned += 1;
787Thread* startRead(ReadContext& context,
const char* name) {
790 thread->setName(
String(name));
794struct ReadRequestPause {
795 ReadRequestPause(HostedScsiDisk* pDisk, uint64_t pageLocation)
796 : disk(pDisk), location(pageLocation), state(0), entered(0, false), release(0, false) {}
799 const size_t staleEntries = entered.drainAvailable();
800 const size_t staleReleases = release.drainAvailable();
804 ScsiDisk::setHostedReadRequestHookForTest(pause,
this);
808 return entered.acquireForCompletion(1, 0, 500000);
816 ScsiDisk::setHostedReadRequestHookForTest(
nullptr,
nullptr);
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)) {
827 context->entered.release();
828 if (!context->release.acquireForCompletion())
833 HostedScsiDisk* disk;
840struct AtaRetirementContext {
841 AtaRetirementContext(HostedScsiDisk* pDisk, uint64_t pageLocation, uintptr_t pageBuffer)
843 location(pageLocation),
850 HostedScsiDisk* disk;
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;
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;
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);
879 AtaRetirementContext context(&fixture.disk, AtaDrainingLocation, page);
880 Thread* retirer =
nullptr;
884 retirer->setName(
"hosted ATA queued-write page retirer");
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);
896 const bool completed =
897 retirer && (
static_cast<size_t>(context.returned) || waitUntilSet(context.returned));
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;
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);
914 prepared && pinned && acquired && retired.balanced && retired.cleaned && drainingMiss;
916 NOTICE(
"HOSTED-WAIT-TEST: PASS ata-queued-write-cache-ownership");
919 "HOSTED-WAIT-TEST: FAIL ata-queued-write-cache-ownership: queued pin was not traded "
920 "for one bounded execution pin");
925struct ScsiWriteCaseResult {
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)
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};
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};
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 &&
960 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-write-result");
963 "HOSTED-WAIT-TEST: FAIL scsi-write-result: all-command failure was reported as success "
964 "or fallback/ownership changed");
969struct DirectRetirementContext {
970 DirectRetirementContext(HostedScsiDisk* pDisk, uint64_t pageLocation)
971 : disk(pDisk), location(pageLocation), returned(0), succeeded(0) {}
973 HostedScsiDisk* disk;
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;
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};
991 fixture.controller.beginWrites(WriteMode::PassWrite12);
992 const bool missing = fixture.ready && fixture.disk.retireCachePage(MissingDirectLocation) &&
993 fixture.controller.hasNoDirectActivity();
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);
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);
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();
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);
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);
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 =
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);
1080 const bool passed = missing && editingRejected && editingDiscarded && successful &&
1081 canonicalPrepared && canonicalRetired && endRejected && failedRetained &&
1082 failedTrace && failedTuple && failedRetry && unreadyPrepared &&
1083 unreadyRetry && shortPrepared && shortRetry;
1085 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-direct-retire-result");
1088 "HOSTED-WAIT-TEST: FAIL scsi-direct-retire-result: DIRECT routing, exact result, "
1089 "retry, or supplied-page write semantics changed");
1094bool scsiDirectRetireOwnership(Fixture& fixture) {
1095 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
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;
1105 nullptr,
false,
true);
1106 retirer->setName(
"hosted SCSI direct-page retirer");
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();
1114 fixture.disk.unpin(PinnedDirectLocation);
1115 const bool completed =
1116 retirer && (
static_cast<size_t>(context.returned) || waitUntilSet(context.returned));
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);
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);
1146 const bool passed = pinnedPrepared && pinned && drainPublished && blockedWithoutIo &&
1147 pinnedRetired && cancelledPrepared && cancellationRetry;
1149 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-direct-retire-ownership");
1152 "HOSTED-WAIT-TEST: FAIL scsi-direct-retire-ownership: borrowed DIRECT pages were "
1153 "used before drain or released by execution/cancellation");
1158bool scsiReadRetireAdmission(Fixture& fixture) {
1159 constexpr RequestEvent ExpectedOrder[] = {RequestEvent::Read, RequestEvent::Direct,
1160 RequestEvent::Read};
1162 fixture.disk.setCacheFillSize(PageBytes);
1163 fixture.controller.beginWrites(WriteMode::PassWrite12);
1164 fixture.controller.beginRequestTrace();
1165 fixture.controller.holdNextRead();
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);
1173 DirectRetirementContext retirement(&fixture.disk, ReadFirstLocation);
1174 Thread* retirer =
nullptr;
1175 if (retireSerialPin) {
1177 nullptr,
false,
true);
1178 retirer->setName(
"hosted SCSI queued-read retirement");
1181 bool retireAdmissionWait =
false;
1182 const bool retireStopped =
1184 waitForRetireAdmissionOrDrain(retirer, fixture.disk, ReadFirstLocation, retireAdmissionWait);
1185 const bool retireIntentQueued = retireStopped && retireAdmissionWait;
1186 const bool pageStayedVisible = retireIntentQueued && fixture.disk.pageAddress(ReadFirstLocation);
1188 DirectRetirementContext secondRetirement(&fixture.disk, ReadFirstLocation);
1189 Thread* secondRetirer =
nullptr;
1192 &secondRetirement,
nullptr,
false,
true);
1193 secondRetirer->setName(
"hosted SCSI second queued retirement");
1195 bool secondRetireAdmissionWait =
false;
1196 const bool secondRetireStopped =
1197 secondRetirer && waitForRetireAdmissionOrDrain(secondRetirer, fixture.disk, ReadFirstLocation,
1198 secondRetireAdmissionWait);
1199 const bool retiresSerialised = secondRetireStopped && secondRetireAdmissionWait;
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);
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);
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 =
1231 (
static_cast<size_t>(secondRetirement.returned) || waitUntilSet(secondRetirement.returned));
1232 if (secondReader && writerPreferred) {
1233 secondReadEntered = fixture.controller.waitForHeldRead();
1234 fixture.controller.releaseHeldRead(
true);
1236 const bool secondCompleted = secondReader && (
static_cast<size_t>(secondRead.returned) ||
1237 waitUntilSet(secondRead.returned));
1240 const bool secondRetireJoined = secondRetirer && secondRetirer->
joinForCompletion();
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);
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) {
1259 &rejectedRetirement,
nullptr,
false,
true);
1260 rejectedRetirer->setName(
"hosted SCSI rejected-read retirement");
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;
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;
1277 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-read-retire-admission");
1280 "HOSTED-WAIT-TEST: FAIL scsi-read-retire-admission: queued reads, retirement intent, "
1281 "writer preference, or rejected-request cleanup changed");
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))
1291 fixture.controller.beginWrites(WriteMode::PassWrite12);
1292 if (!fixture.disk.retireCachePage(location + offset))
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};
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();
1310 DirectRetirementContext rangeRetirement(&fixture.disk, RetireFirstInterior);
1311 Thread* rangeRetirer =
nullptr;
1312 if (interiorPinned) {
1314 &rangeRetirement,
nullptr,
false,
true);
1315 rangeRetirer->setName(
"hosted SCSI interior-page retirer");
1317 const bool rangeDrainPublished =
1318 rangeRetirer && waitUntilQueuedAt(rangeRetirer, Thread::CallbackDrain, RetireFirstInterior);
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);
1330 const bool nonoverlapJoined = nonoverlapReader && nonoverlapReader->
joinForCompletion();
1331 const bool disjointProgress =
1332 nonoverlapCompleted && nonoverlapJoined && nonoverlapRead.result && !releasedForRepair;
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");
1345 overlapReader && waitForAdmissionOrRead(overlapReader, RetireFirstExtent,
1346 fixture.controller, 1, overlapAdmissionWait);
1347 if (!overlapAdmissionWait) {
1348 overlapReadEntered = fixture.controller.waitForHeldRead();
1349 fixture.controller.releaseHeldRead(
false);
1353 if (interiorPinned && !releasedForRepair)
1354 fixture.disk.unpin(RetireFirstInterior);
1355 const bool rangeRetireCompleted =
1357 (
static_cast<size_t>(rangeRetirement.returned) || waitUntilSet(rangeRetirement.returned));
1358 const bool rangeRetireJoined = rangeRetirer && rangeRetirer->
joinForCompletion();
1360 if (overlapReader && overlapAdmissionWait) {
1361 overlapReadEntered = fixture.controller.waitForHeldRead();
1362 fixture.controller.releaseHeldRead(
true);
1364 if (overlapReader) {
1366 static_cast<size_t>(overlapRead.returned) || waitUntilSet(overlapRead.returned);
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);
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();
1388 DirectRetirementContext failedRetirement(&fixture.disk, RecheckReadLocation);
1389 Thread* failedRetirer =
nullptr;
1390 if (recheckPinned) {
1392 &failedRetirement,
nullptr,
false,
true);
1393 failedRetirer->setName(
"hosted SCSI failed retirement recheck");
1395 const bool failureDrainPublished =
1396 failedRetirer && waitUntilQueuedAt(failedRetirer, Thread::CallbackDrain, RecheckReadLocation);
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);
1412 fixture.disk.unpin(RecheckReadLocation);
1413 const bool failedRetireCompleted =
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();
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);
1436 fixture.disk.restoreDirectResult();
1437 fixture.controller.beginWrites(WriteMode::PassWrite12);
1438 const bool recheckCleaned = fixture.disk.retireCachePage(RecheckReadLocation);
1440 constexpr RequestEvent LookupPauseOrder[] = {RequestEvent::Read, RequestEvent::Direct};
1441 fixture.controller.beginWrites(WriteMode::PassWrite12);
1442 fixture.controller.beginRequestTrace();
1443 ReadRequestPause lookupPause(&fixture.disk, LookupPauseLocation);
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);
1451 ReadContext sharedRead(&fixture.disk, LookupPauseLocation);
1452 Thread* sharedReader =
nullptr;
1453 if (readPublished) {
1454 sharedReader = startRead(sharedRead,
"hosted SCSI shared cache-hit reader");
1458 const bool sharedBlockedBeforeRelease =
1459 sharedReader && waitUntilQueuedAt(sharedReader, Thread::CondWait, LookupPauseLocation) &&
1460 !
static_cast<size_t>(sharedRead.returned);
1462 DirectRetirementContext lookupRetirement(&fixture.disk, LookupPauseLocation);
1463 Thread* lookupRetirer =
nullptr;
1466 &lookupRetirement,
nullptr,
false,
true);
1467 lookupRetirer->setName(
"hosted SCSI pre-lookup retirer");
1469 bool lookupRetireAdmissionWait =
false;
1470 const bool lookupRetireStopped =
1472 waitForRetireAdmissionOrDrain(lookupRetirer, fixture.disk, LookupPauseLocation,
1473 lookupRetireAdmissionWait);
1474 const bool admissionHeldThroughLookup = lookupRetireStopped && lookupRetireAdmissionWait &&
1475 fixture.disk.pageAddress(LookupPauseLocation);
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 =
1484 (
static_cast<size_t>(lookupRetirement.returned) || waitUntilSet(lookupRetirement.returned));
1487 const bool lookupRetireJoined = lookupRetirer && lookupRetirer->
joinForCompletion();
1488 lookupPause.clear();
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);
1503 const bool passed = interiorPrepared && interiorPinned && rangeAdmission && rangeCleaned &&
1504 recheckPrepared && recheckPinned && failureRechecked && recheckCleaned &&
1505 finalLookupProtected && lookupCleaned;
1507 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-retire-read-recheck");
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);
1524bool scsiDeferredWrites() {
1526 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1528 fixture.disk.checksumPage(CheckedSyncLocation);
1529 if (!fixture.disk.runBackgroundWriteback())
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);
1547 fixture.disk.unpin(CheckedSyncLocation);
1548 const bool balanced = fixture.disk.evictPage(CheckedSyncLocation);
1549 const bool passed = deferred && written && durable && settled && callerPinRetained && balanced;
1551 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-deferred-writes");
1553 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-deferred-writes: coalescing, settling, durability or pins");
1557bool scsiScopedDiskViews() {
1559 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1561 fixture.disk.pauseBackgroundWriteback();
1562 fixture.controller.beginWrites(WriteMode::PassWrite12);
1563 fixture.controller.beginSync(SyncMode::Pass10);
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;
1577 fixture.disk.unpin(CheckedSyncLocation);
1578 if (legacy && pinned)
1579 static_cast<uint8_t*
>(legacy.data())[7] = 0xc7;
1581 fixture.disk.unpin(CheckedSyncLocation);
1583 uint32_t observed = 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;
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;
1600 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-scoped-disk-views");
1602 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-scoped-disk-views: admission="
1603 << admitted <<
", endpoint=" << endpointHeld <<
", coherent=" << coherent
1604 <<
", written=" << written <<
", settled=" << settled <<
", balanced=" << balanced);
1608bool scsiViewSurvivesAlignmentChange() {
1610 if (!fixture.ready || !fixture.disk.preparePage(0))
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);
1618 const bool balanced = fixture.disk.hasNoCacheLoans() && fixture.disk.evictPage(0);
1619 const bool passed = admitted && balanced;
1621 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-view-alignment-change");
1623 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-view-alignment-change: view released a different cache key");
1627bool scsiDeferredWriteRetry() {
1629 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
1631 fixture.disk.checksumPage(CheckedSyncLocation);
1632 if (!fixture.disk.runBackgroundWriteback())
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));
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;
1656 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-deferred-write-retry");
1659 "HOSTED-WAIT-TEST: FAIL scsi-deferred-write-retry: failed flush lost retry or leaked pins");
1663bool scsiDeferredWriteShutdown() {
1665 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation))
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));
1678 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-deferred-write-shutdown");
1680 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-deferred-write-shutdown: deferred page was not flushed");
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};
1689 if (!fixture.ready || !fixture.disk.preparePage(CheckedSyncLocation)) {
1690 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-checked-sync: fixture setup");
1693 const uintptr_t page = fixture.disk.pageAddress(CheckedSyncLocation);
1694 fixture.disk.checksumPage(CheckedSyncLocation);
1695 fixture.controller.beginWrites(WriteMode::PassWrite12);
1696 fixture.controller.beginSync(SyncMode::FailAll);
1697 fixture.disk.beginUnpinObservation();
1698 const bool failed = !fixture.disk.sync(CheckedSyncLocation,
false);
1699 const size_t failureUnpins = fixture.disk.endUnpinObservation();
1700 const bool failureReported =
1701 failed && failureUnpins == 0 &&
1702 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1703 fixture.controller.writeTraceMatches(SuccessfulWrites,
sizeof(SuccessfulWrites)) &&
1704 fixture.controller.syncTraceMatches(FailedSyncs,
sizeof(FailedSyncs));
1706 fixture.controller.beginWrites(WriteMode::PassWrite12);
1707 fixture.controller.beginSync(SyncMode::FailAll);
1708 const bool retained =
1709 !fixture.disk.evictPage(CheckedSyncLocation) && fixture.disk.hasPage(CheckedSyncLocation) &&
1710 fixture.disk.pageAddress(CheckedSyncLocation) == page &&
1711 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1712 fixture.controller.writeTraceMatches(SuccessfulWrites,
sizeof(SuccessfulWrites)) &&
1713 fixture.controller.syncTraceMatches(FailedSyncs,
sizeof(FailedSyncs));
1715 fixture.controller.beginWrites(WriteMode::PassWrite12);
1716 fixture.controller.beginSync(SyncMode::Pass16);
1717 const bool pinned = fixture.disk.pin(CheckedSyncLocation);
1718 fixture.disk.beginUnpinObservation();
1719 const bool retried = fixture.disk.sync(CheckedSyncLocation,
false);
1720 const size_t retryUnpins = fixture.disk.endUnpinObservation();
1721 const bool retryReported =
1722 retried && retryUnpins == 0 &&
1723 fixture.controller.directTupleMatches(&fixture.disk, CheckedSyncLocation, page) &&
1724 fixture.controller.writeTraceMatches(SuccessfulWrites,
sizeof(SuccessfulWrites)) &&
1725 fixture.controller.syncTraceMatches(RetriedSyncs,
sizeof(RetriedSyncs));
1726 const bool callerPinPreserved = pinned && !fixture.disk.evictPage(CheckedSyncLocation);
1728 fixture.disk.unpin(CheckedSyncLocation);
1730 fixture.controller.beginWrites(WriteMode::PassWrite12);
1731 fixture.controller.beginSync(SyncMode::Pass10);
1732 const bool cleaned =
1733 fixture.disk.evictPage(CheckedSyncLocation) && !fixture.disk.hasPage(CheckedSyncLocation);
1734 const bool passed = failureReported && retained && retryReported && callerPinPreserved && cleaned;
1736 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-checked-sync");
1739 "HOSTED-WAIT-TEST: FAIL scsi-checked-sync: error retention, retry, or borrowed pin "
1746 constexpr uint64_t WholeRequest[] = {ScsiDisk::SyncWholeDevice};
1747 constexpr uint64_t PageThenWhole[] = {CheckedSyncLocation, ScsiDisk::SyncWholeDevice};
1748 constexpr uint8_t Pass10[] = {0x35};
1749 constexpr uint8_t Pass16[] = {0x35, 0x35, 0x35, 0x91};
1750 constexpr uint8_t FailAll[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1751 constexpr uint8_t PageAndWhole[] = {0x35, 0x35};
1752 constexpr uint8_t FinalFailed[] = {0x35, 0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1753 constexpr bool WholeGeometry[] = {
true,
true,
true,
true,
true,
true};
1754 constexpr bool PageAndWholeGeometry[] = {
false,
true};
1755 constexpr bool FinalFailedGeometry[] = {
false,
true,
true,
true,
true,
true,
true};
1756 constexpr uint8_t FailedWrites[] = {0x2a, 0x2a, 0x2a, 0xaa, 0xaa, 0xaa, 0x8a, 0x8a, 0x8a};
1757 constexpr uint8_t SuccessfulWrites[] = {0x2a, 0x2a, 0x2a, 0xaa};
1759 if (!fixture.ready) {
1760 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-all: fixture setup");
1764 fixture.controller.beginWrites(WriteMode::PassWrite12);
1765 fixture.controller.beginSync(SyncMode::Pass10);
1766 const bool emptyFlushed = fixture.disk.syncAll() && fixture.controller.writeCount() == 0 &&
1767 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1768 fixture.controller.syncTraceMatches(Pass10,
sizeof(Pass10)) &&
1769 fixture.controller.syncGeometryMatches(WholeGeometry, 1);
1770 fixture.controller.beginSync(SyncMode::FailAll);
1771 const bool emptyFailure = !fixture.disk.syncAll() &&
1772 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1773 fixture.controller.syncTraceMatches(FailAll,
sizeof(FailAll)) &&
1774 fixture.controller.syncGeometryMatches(WholeGeometry, 6);
1775 fixture.controller.beginSync(SyncMode::Pass16);
1776 const bool emptyRetry = fixture.disk.syncAll() &&
1777 fixture.controller.syncRequestsMatch(WholeRequest, 1) &&
1778 fixture.controller.syncTraceMatches(Pass16,
sizeof(Pass16)) &&
1779 fixture.controller.syncGeometryMatches(WholeGeometry, 4);
1781 const bool prepared = fixture.disk.preparePage(CheckedSyncLocation);
1782 fixture.controller.beginWrites(WriteMode::FailAll);
1783 fixture.controller.beginSync(SyncMode::Pass10);
1784 const bool pageFailed =
1785 prepared && !fixture.disk.syncAll() && fixture.disk.hasPage(CheckedSyncLocation) &&
1786 fixture.controller.writeTraceMatches(FailedWrites,
sizeof(FailedWrites)) &&
1787 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1788 fixture.controller.syncTraceMatches(PageAndWhole,
sizeof(PageAndWhole)) &&
1789 fixture.controller.syncGeometryMatches(PageAndWholeGeometry, 2);
1791 fixture.controller.beginWrites(WriteMode::PassWrite12);
1792 fixture.controller.beginSync(SyncMode::FailWhole);
1793 const bool finalFailed =
1794 !fixture.disk.syncAll() && fixture.disk.hasPage(CheckedSyncLocation) &&
1795 fixture.controller.writeTraceMatches(SuccessfulWrites,
sizeof(SuccessfulWrites)) &&
1796 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1797 fixture.controller.syncTraceMatches(FinalFailed,
sizeof(FinalFailed)) &&
1798 fixture.controller.syncGeometryMatches(FinalFailedGeometry, 7);
1800 fixture.controller.beginWrites(WriteMode::PassWrite12);
1801 fixture.controller.beginSync(SyncMode::Pass10);
1802 const bool retried =
1803 fixture.disk.syncAll() &&
1804 fixture.controller.writeTraceMatches(SuccessfulWrites,
sizeof(SuccessfulWrites)) &&
1805 fixture.controller.syncRequestsMatch(PageThenWhole, 2) &&
1806 fixture.controller.syncTraceMatches(PageAndWhole,
sizeof(PageAndWhole)) &&
1807 fixture.controller.syncGeometryMatches(PageAndWholeGeometry, 2);
1808 const bool cleaned = fixture.disk.evictPage(CheckedSyncLocation);
1810 emptyFlushed && emptyFailure && emptyRetry && pageFailed && finalFailed && retried && cleaned;
1812 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-all");
1814 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-all: empty="
1815 << emptyFlushed <<
", empty-failure=" << emptyFailure <<
", empty-retry=" << emptyRetry
1816 <<
", prepared=" << prepared <<
", page-failure=" << pageFailed
1817 <<
", final-failure=" << finalFailed <<
", retry=" << retried <<
", cleanup=" << cleaned);
1822bool scsiSyncBatch(
bool failWrite,
bool failFlush) {
1823 constexpr uint64_t Keys[] = {CheckedSyncLocation, CheckedSyncLocation + PageBytes};
1824 constexpr uint8_t Flush[] = {0x35};
1825 constexpr uint8_t FailedFlush[] = {0x35, 0x35, 0x35, 0x91, 0x91, 0x91};
1826 constexpr bool Whole[] = {
true,
true,
true,
true,
true,
true};
1830 fixture.disk.pauseBackgroundWriteback();
1831 if (!fixture.disk.preparePage(Keys[0]) || !fixture.disk.preparePage(Keys[1]))
1833 for (uint64_t key : Keys)
1834 fixture.disk.write(key);
1835 fixture.controller.beginWrites(failWrite ? WriteMode::FailAll : WriteMode::PassWrite12);
1836 fixture.controller.beginSync(failFlush ? SyncMode::FailAll : SyncMode::Pass10);
1837 const bool result = fixture.disk.syncPages(Keys, 2);
1838 const size_t expectedWrites = failWrite ? 18 : 8;
1839 const bool committed =
1840 result == !(failWrite || failFlush) && fixture.controller.writeCount() == expectedWrites &&
1841 fixture.controller.writesAtLastSync() == expectedWrites &&
1842 fixture.controller.syncTraceMatches(failFlush ? FailedFlush : Flush, failFlush ? 6 : 1) &&
1843 fixture.controller.syncGeometryMatches(Whole, failFlush ? 6 : 1);
1844 bool retained =
true;
1845 if (failWrite || failFlush) {
1848 for (uint64_t key : Keys) {
1849 fixture.controller.beginWrites(WriteMode::PassWrite12);
1850 fixture.controller.beginSync(SyncMode::FailAll);
1851 retained = !fixture.disk.evictPage(key) && fixture.disk.hasPage(key) &&
1852 fixture.controller.writeCount() == 4 &&
1853 fixture.controller.syncTraceMatches(FailedFlush,
sizeof(FailedFlush)) && retained;
1856 fixture.controller.beginWrites(WriteMode::PassWrite12);
1857 fixture.controller.beginSync(SyncMode::Pass10);
1858 const bool pinned = fixture.disk.pin(Keys[0]);
1859 const bool needsRetry = failWrite || failFlush;
1860 const size_t retryWrites = needsRetry ? 8 : 0;
1861 const bool retried = fixture.disk.syncPages(Keys, 2) &&
1862 fixture.controller.writeCount() == retryWrites &&
1863 fixture.controller.writesAtLastSync() == retryWrites &&
1864 fixture.controller.syncTraceMatches(Flush, needsRetry ?
sizeof(Flush) : 0);
1865 const bool callerPin = pinned && !fixture.disk.evictPage(Keys[0]);
1867 fixture.disk.unpin(Keys[0]);
1868 const bool balanced = fixture.disk.evictPage(Keys[0]) && fixture.disk.evictPage(Keys[1]) &&
1869 fixture.controller.writeCount() == retryWrites &&
1870 fixture.controller.syncTraceMatches(Flush, needsRetry ?
sizeof(Flush) : 0);
1871 const bool passed = committed && retained && retried && callerPin && balanced;
1873 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch write-failure="
1874 << failWrite <<
", flush-failure=" << failFlush);
1876 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch: commit="
1877 << committed <<
", retained=" << retained <<
", retry=" << retried
1878 <<
", caller-pin=" << callerPin <<
", balanced=" << balanced);
1882struct SyncBatchContext {
1883 HostedScsiDisk* disk;
1887 bool result =
false;
1890int syncBatchPages(
void* parameter) {
1891 auto& context = *
static_cast<SyncBatchContext*
>(parameter);
1892 context.entered = 1;
1893 context.result = context.disk->syncPages(context.keys, 2);
1894 context.returned = 1;
1898bool scsiOverlappingSyncBatches() {
1899 constexpr uint64_t First = CheckedSyncLocation;
1900 constexpr uint64_t Second = First + PageBytes;
1901 constexpr uint8_t Flushes[] = {0x35, 0x35};
1902 constexpr bool Whole[] = {
true,
true};
1906 fixture.disk.pauseBackgroundWriteback();
1907 if (!fixture.disk.preparePage(First) || !fixture.disk.preparePage(Second))
1909 fixture.disk.write(First);
1910 fixture.disk.write(Second);
1911 fixture.controller.beginWrites(WriteMode::PassWrite12);
1912 fixture.controller.beginSync(SyncMode::Pass10);
1913 fixture.controller.holdNextSync();
1914 SyncBatchContext first{&fixture.disk, {First, Second}};
1915 SyncBatchContext second{&fixture.disk, {Second, First}};
1917 nullptr,
false,
true);
1918 producer->setName(
"hosted SCSI held batch barrier");
1919 const bool held = fixture.controller.waitForHeldSync();
1921 bool waiting =
false;
1925 waiter->setName(
"hosted SCSI overlapping reverse batch");
1926 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1927 while (Time::getTicks() < deadline) {
1929 uintptr_t address = 0;
1930 if (
waiter->getWaitDebugInfo(info) && info.queue && info.queued &&
1931 waiter->getDebugState(address) == Thread::CallbackDrain) {
1935 if (second.returned)
1940 const bool excluded = held && waiting && !first.returned && !second.returned &&
1941 fixture.controller.writeCount() == 8 && !fixture.disk.evictPage(First) &&
1942 !fixture.disk.evictPage(Second);
1943 fixture.disk.write(First);
1944 fixture.disk.write(Second);
1945 fixture.controller.releaseHeldSync();
1946 const bool firstCompleted = waitUntilSet(first.returned);
1947 const bool secondCompleted =
waiter && waitUntilSet(second.returned);
1949 const bool secondJoined =
waiter &&
waiter->joinForCompletion();
1950 const bool serialised = firstCompleted && secondCompleted && firstJoined && secondJoined &&
1951 first.result && second.result && fixture.controller.writeCount() == 16 &&
1952 fixture.controller.writesAtLastSync() == 16 &&
1953 fixture.controller.syncTraceMatches(Flushes,
sizeof(Flushes)) &&
1954 fixture.controller.syncGeometryMatches(Whole, 2);
1955 const bool balanced = fixture.disk.evictPage(First) && fixture.disk.evictPage(Second) &&
1956 fixture.controller.writeCount() == 16;
1957 const bool passed = excluded && serialised && balanced;
1959 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-overlapping-sync-batches");
1961 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-overlapping-sync-batches: excluded="
1962 << excluded <<
", serialised=" << serialised <<
", balanced=" << balanced);
1966bool scsiSyncBatchValidation() {
1967 constexpr uint64_t Key = CheckedSyncLocation;
1968 constexpr uint64_t Duplicate[] = {Key, Key + 512, Key + 1024};
1969 constexpr uint64_t Missing[] = {Key, Key + PageBytes};
1970 constexpr uint64_t Outside[] = {Key, 64 * PageBytes};
1971 constexpr uint64_t Overflow[] = {Key, ~uint64_t{0}};
1972 constexpr uint8_t Flush[] = {0x35};
1976 fixture.disk.pauseBackgroundWriteback();
1977 if (!fixture.disk.preparePage(Key))
1979 fixture.controller.beginWrites(WriteMode::PassWrite12);
1980 fixture.controller.beginSync(SyncMode::Pass10);
1981 const bool rejected = fixture.disk.syncPages(
nullptr, 0) && !fixture.disk.syncPages(
nullptr, 1) &&
1982 !fixture.disk.syncPages(Duplicate, Cache::MaxWritebackPages + 1) &&
1983 !fixture.disk.syncPages(Missing, 2) &&
1984 !fixture.disk.syncPages(Outside, 2) && !fixture.disk.syncPages(Overflow, 2);
1985 const bool editing = fixture.disk.prepareEditingPage(Key + PageBytes) &&
1986 !fixture.disk.syncPages(Missing, 2) &&
1987 fixture.disk.discardEditingPage(Key + PageBytes);
1989 fixture.controller.hasNoDirectActivity() && fixture.controller.syncTraceMatches(
nullptr, 0);
1990 fixture.disk.write(Key);
1991 const bool deduplicated = fixture.disk.syncPages(Duplicate, 3) &&
1992 fixture.controller.writeCount() == 4 &&
1993 fixture.controller.writesAtLastSync() == 4 &&
1994 fixture.controller.syncTraceMatches(Flush,
sizeof(Flush));
1995 const bool balanced = fixture.disk.evictPage(Key) && fixture.controller.writeCount() == 4;
1996 const bool passed = rejected && editing && noIo && deduplicated && balanced;
1998 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch-validation");
2000 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch-validation: rejected="
2001 << rejected <<
", editing=" << editing <<
", no-io=" << noIo <<
", dedup=" << deduplicated
2002 <<
", balanced=" << balanced);
2006bool scsiSyncBatchTerminalGeometry() {
2007 constexpr uint64_t
Terminal = 4 * PageBytes;
2008 constexpr size_t TerminalBytes = 512;
2009 constexpr uint64_t Keys[] = {0,
Terminal};
2010 constexpr uint8_t Flush[] = {0x35};
2011 constexpr bool Whole[] = {
true};
2012 ScriptedScsiController controller(
Terminal + TerminalBytes);
2013 HostedScsiDisk disk;
2014 if (!disk.initialise(&controller, 0))
2016 disk.pauseBackgroundWriteback();
2017 if (!disk.preparePage(Keys[0]) || !disk.preparePage(Keys[1]))
2019 for (uint64_t key : Keys)
2021 controller.beginWrites(WriteMode::PassWrite12);
2022 controller.beginSync(SyncMode::Pass10);
2023 const bool written =
2024 disk.syncPages(Keys, 2) && controller.writeCount() == 8 &&
2025 controller.lastWriteBytes() == TerminalBytes && controller.writesAtLastSync() == 8 &&
2026 controller.syncTraceMatches(Flush,
sizeof(Flush)) && controller.syncGeometryMatches(Whole, 1);
2027 const bool balanced =
2028 disk.evictPage(Keys[0]) && disk.evictPage(Keys[1]) && controller.writeCount() == 8;
2029 const bool passed = written && balanced;
2031 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch-terminal");
2033 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch-terminal: write bounds, final flush or pins");
2037bool scsiTerminalCachePage() {
2038 constexpr size_t TerminalBytes = 512;
2039 constexpr uint64_t TerminalLocation = 4 * PageBytes;
2040 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
2042 ScriptedScsiController controller(TerminalLocation + TerminalBytes);
2043 HostedScsiDisk disk;
2044 const bool ready = disk.initialise(&controller, 0);
2045 controller.beginRequestTrace();
2049 view && view.size() == TerminalBytes && controller.lastReadBytes() == TerminalBytes;
2050 bool tailZeroed = payloadValid;
2051 const uint8_t* page = payloadValid ?
reinterpret_cast<const uint8_t*
>(view.data()) : nullptr;
2052 for (
size_t i = 0; page && i < TerminalBytes; ++i) {
2053 if (page[i] != 0x3c) {
2054 payloadValid =
false;
2058 for (
size_t i = TerminalBytes; page && i < PageBytes; ++i) {
2065 disk.unpin(TerminalLocation);
2068 controller.beginWrites(WriteMode::PassWrite12);
2069 const bool retired = ready && disk.retireCachePage(TerminalLocation);
2070 const bool boundedWriteback =
2071 retired && controller.lastWriteBytes() == TerminalBytes &&
2072 controller.writeTraceMatches(SuccessfulOpcodes,
sizeof(SuccessfulOpcodes)) &&
2073 !disk.hasPage(TerminalLocation);
2075 const bool passed = ready && payloadValid && tailZeroed && boundedWriteback;
2077 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-terminal-cache-page");
2080 "HOSTED-WAIT-TEST: FAIL scsi-terminal-cache-page: terminal read length, zero fill, or "
2081 "writeback bounds changed");
2086bool producerBytesMatch(
const uint8_t* bytes,
size_t length, uint8_t value) {
2087 for (
size_t i = 0; i < length; ++i) {
2088 if (bytes[i] != value)
2094bool scsiProducerReadBatchCoherence() {
2095 constexpr uint64_t Cached = 4 * PageBytes;
2096 constexpr uint64_t FirstAbsent = 16 * PageBytes + 512;
2097 constexpr uint64_t SecondAbsent = 32 * PageBytes;
2098 constexpr uint64_t ExpectedPending[] = {FirstAbsent, SecondAbsent, FirstAbsent};
2102 fixture.disk.pauseBackgroundWriteback();
2103 fixture.disk.enableBufferTransfers();
2104 fixture.controller.beginWrites(WriteMode::PassWrite12);
2105 if (!fixture.disk.preparePage(Cached))
2107 const BufferView loan = fixture.disk.read(Cached);
2110 ByteSet(
static_cast<uint8_t*
>(loan.data()) + 512, 0xc7, 512);
2111 fixture.disk.write(Cached);
2112 fixture.controller.beginRequestTrace();
2113 fixture.disk.beginReadBufferBatch();
2115 uint8_t output[5][514];
2116 ByteSet(output, 0x3f,
sizeof(output));
2118 {FirstAbsent, output[1] + 1, 512,
false},
2119 {SecondAbsent, output[2] + 1, 512,
false},
2120 {FirstAbsent, output[3] + 1, 512,
false},
2121 {Cached + 1024, output[4] + 1, 512,
false}};
2122 bool passed = fixture.disk.readIntoBatch(requests, 5) &&
2123 fixture.disk.bufferBatchMatches(ExpectedPending, 3) &&
2124 fixture.disk.bufferTransferCount() == 3 && !fixture.controller.readCommandCount() &&
2125 !fixture.controller.writeCount() && fixture.disk.hasPage(Cached) &&
2126 !fixture.disk.hasPage(16 * PageBytes) && !fixture.disk.hasPage(SecondAbsent) &&
2127 !fixture.disk.hasNoCacheLoans();
2128 for (
size_t i = 0; i < 5; ++i) {
2129 const uint8_t expected = !i ? 0xc7 : i == 4 ? 0x5a : 0xa6;
2130 passed = passed && requests[i].complete && producerBytesMatch(output[i] + 1, 512, expected) &&
2131 output[i][0] == 0x3f && output[i][513] == 0x3f;
2134 ByteSet(
static_cast<uint8_t*
>(loan.data()) + 512, 0xd8, 512);
2135 fixture.disk.write(Cached);
2136 fixture.disk.beginReadBufferBatch();
2137 passed = fixture.disk.readIntoBatch(requests, 1) && requests[0].complete &&
2138 producerBytesMatch(output[0] + 1, 512, 0xd8) && !fixture.disk.bufferBatchCount() &&
2139 !fixture.disk.bufferTransferCount() && !fixture.controller.readCommandCount() && passed;
2140 fixture.disk.unpin(Cached);
2141 passed = fixture.disk.hasNoCacheLoans() && passed;
2143 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-coherence");
2145 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-coherence");
2149bool scsiProducerReadBatchFailure() {
2150 constexpr uint64_t Locations[] = {4 * PageBytes, 8 * PageBytes, 12 * PageBytes};
2154 fixture.disk.enableBufferTransfers();
2155 fixture.disk.beginReadBufferBatch(Locations[1]);
2156 uint8_t output[3][514];
2157 ByteSet(output, 0x3f,
sizeof(output));
2159 for (
size_t i = 0; i < 3; ++i)
2160 requests[i] = {Locations[i], output[i] + 1, 512,
true};
2161 bool passed = !fixture.disk.readIntoBatch(requests, 3) &&
2162 fixture.disk.bufferBatchMatches(Locations, 3) &&
2163 fixture.disk.bufferTransferCount() == 3;
2164 for (
size_t i = 0; i < 3; ++i) {
2165 passed = passed && requests[i].complete == (i != 1) &&
2166 producerBytesMatch(output[i] + 1, 512, i == 1 ? 0x3f : 0xa6) && output[i][0] == 0x3f &&
2167 output[i][513] == 0x3f && !fixture.disk.hasPage(Locations[i]);
2169 fixture.disk.beginReadBufferBatch();
2170 passed = fixture.disk.readIntoBatch(requests, 3) &&
2171 fixture.disk.bufferBatchMatches(Locations, 3) &&
2172 fixture.disk.bufferTransferCount() == 3 && passed;
2173 for (
size_t i = 0; i < 3; ++i)
2174 passed = passed && requests[i].complete && producerBytesMatch(output[i] + 1, 512, 0xa6);
2175 passed = fixture.disk.hasNoCacheLoans() && passed;
2177 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-failure");
2179 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-failure");
2183enum class ReadBatchFallback { Unsupported, Unaligned, NativeSector, PageCrossing, ShiftedOrigin };
2185bool scsiProducerReadBatchFallbackCase(ReadBatchFallback mode) {
2186 const size_t native = mode == ReadBatchFallback::NativeSector ? PageBytes : 512;
2187 ScriptedScsiController controller(64 * PageBytes, native);
2188 HostedScsiDisk disk;
2189 if (!disk.initialise(&controller, 0))
2191 disk.pauseBackgroundWriteback();
2192 if (mode != ReadBatchFallback::Unsupported)
2193 disk.enableBufferTransfers();
2194 controller.beginWrites(WriteMode::PassWrite12);
2195 uint64_t locations[] = {0, 8 * PageBytes};
2196 size_t lengths[] = {512, 512};
2197 uint8_t expected = 0x3c;
2199 if (mode == ReadBatchFallback::Unaligned) {
2202 lengths[0] = lengths[1] = 7;
2203 }
else if (mode == ReadBatchFallback::NativeSector) {
2204 locations[0] += 512;
2205 locations[1] += 1024;
2206 }
else if (mode == ReadBatchFallback::PageCrossing) {
2207 lengths[0] = PageBytes + 512;
2209 }
else if (mode == ReadBatchFallback::ShiftedOrigin) {
2210 locations[0] = 1536;
2211 locations[1] = 2048;
2212 disk.align(locations[0]);
2213 if (!disk.preparePage(locations[0]))
2215 loan = disk.read(locations[0]);
2218 ByteSet(loan.data(), 0xc7, PageBytes);
2219 disk.write(locations[0]);
2224 ByteSet(first.get(), 0x3f, lengths[0] + 2);
2225 ByteSet(second.get(), 0x3f, lengths[1] + 2);
2226 Disk::ReadBuffer requests[] = {{locations[0], first.get() + 1, lengths[0],
true},
2227 {locations[1], second.get() + 1, lengths[1],
true}};
2228 disk.beginReadBufferBatch();
2230 disk.readIntoBatch(requests, 2) && requests[0].complete && requests[1].complete &&
2231 !disk.bufferBatchCount() &&
2232 disk.bufferTransferCount() == (mode == ReadBatchFallback::PageCrossing ? 3U : 0U) &&
2233 producerBytesMatch(first.get() + 1, lengths[0], expected) &&
2234 producerBytesMatch(second.get() + 1, lengths[1], expected) && first.get()[0] == 0x3f &&
2235 first.get()[lengths[0] + 1] == 0x3f && second.get()[0] == 0x3f &&
2236 second.get()[lengths[1] + 1] == 0x3f &&
2237 (!loan || producerBytesMatch(
static_cast<uint8_t*
>(loan.data()), PageBytes, 0xc7));
2239 disk.unpin(locations[0]);
2242 "HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-fallback mode=" <<
static_cast<int>(mode));
2243 return passed && disk.hasNoCacheLoans();
2246bool scsiProducerReadBatchFallbacks() {
2247 const ReadBatchFallback modes[] = {
2248 ReadBatchFallback::Unsupported, ReadBatchFallback::Unaligned, ReadBatchFallback::NativeSector,
2249 ReadBatchFallback::PageCrossing, ReadBatchFallback::ShiftedOrigin};
2250 for (
auto mode : modes) {
2251 if (!scsiProducerReadBatchFallbackCase(mode))
2254 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-fallbacks");
2258bool scsiProducerShiftedOriginFallback() {
2259 constexpr uint64_t ShiftedOrigin = 1536;
2260 constexpr uint64_t LaterPage = 4 * PageBytes;
2264 fixture.disk.pauseBackgroundWriteback();
2265 fixture.disk.enableBufferTransfers();
2266 fixture.controller.beginWrites(WriteMode::PassWrite12);
2267 fixture.controller.beginSync(SyncMode::Pass10);
2268 uint8_t buffer[1024] = {};
2270 fixture.disk.align(PageBytes);
2271 const bool alignedFastPath = fixture.disk.readInto(LaterPage, buffer, 512) && buffer[0] == 0xa6 &&
2272 fixture.disk.bufferTransferCount() == 1 &&
2273 !fixture.disk.hasPage(LaterPage);
2275 fixture.disk.align(ShiftedOrigin);
2276 const bool prepared = fixture.disk.preparePage(ShiftedOrigin);
2279 static_cast<uint8_t*
>(loan.data())[0] = 0xc7;
2280 fixture.disk.write(ShiftedOrigin);
2284 const bool beforeOrigin = fixture.disk.readInto(0, buffer, 512);
2285 const bool acrossOrigin = fixture.disk.readInto(1024, buffer,
sizeof(buffer));
2286 const bool afterAlignedOrigin = fixture.disk.readInto(LaterPage, buffer, 512);
2287 const bool dirtyLoanVisible =
2288 loan && fixture.disk.readInto(ShiftedOrigin, buffer, 512) && buffer[0] == 0xc7;
2289 ByteSet(buffer, 0xd2,
sizeof(buffer));
2290 const bool loanUpdated = loan && fixture.disk.writeFrom(ShiftedOrigin, buffer, 512) &&
2291 static_cast<const uint8_t*
>(loan.data())[0] == 0xd2 &&
2292 static_cast<const uint8_t*
>(loan.data())[512] == 0x5a;
2293 const bool crossWrite = fixture.disk.writeFrom(1024, buffer,
sizeof(buffer));
2294 const bool legacyOnly = fixture.disk.bufferTransferCount() == 1 && fixture.disk.hasPage(0) &&
2295 fixture.disk.hasPage(ShiftedOrigin) && fixture.disk.hasPage(LaterPage);
2297 fixture.disk.unpin(ShiftedOrigin);
2299 const bool passed = alignedFastPath && prepared && beforeOrigin && acrossOrigin &&
2300 afterAlignedOrigin && dirtyLoanVisible && loanUpdated && crossWrite &&
2303 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-shifted-origin-fallback");
2305 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-producer-shifted-origin-fallback: aligned="
2306 << alignedFastPath <<
", prepared=" << prepared <<
", before=" << beforeOrigin
2307 <<
", across=" << acrossOrigin <<
", after=" << afterAlignedOrigin
2308 <<
", dirty-loan=" << dirtyLoanVisible <<
", updated=" << loanUpdated
2309 <<
", cross-write=" << crossWrite <<
", legacy=" << legacyOnly);
2314bool scsiOpticalReadAfterToc() {
2315 constexpr size_t OpticalBlockBytes = 2048;
2316 constexpr uint64_t VolumeDescriptorLocation = 16 * OpticalBlockBytes;
2318 ScriptedScsiController controller(64 * PageBytes, OpticalBlockBytes,
true);
2319 HostedScsiDisk disk;
2320 const bool ready = disk.initialise(&controller, 0);
2321 controller.beginRequestTrace();
2324 bool payloadValid = view && view.size() == PageBytes;
2325 const uint8_t* payload = payloadValid ?
reinterpret_cast<const uint8_t*
>(view.data()) : nullptr;
2326 for (
size_t i = 0; payload && i < view.size(); ++i) {
2327 if (payload[i] != 0x3c) {
2328 payloadValid =
false;
2333 disk.unpin(VolumeDescriptorLocation);
2336 const bool passed = ready && payloadValid && controller.readTocCount() == 1 &&
2337 controller.readRequestCount() == 1 && controller.readCommandCount() == 1 &&
2338 controller.lastReadOpcode() == 0x28 &&
2339 controller.lastReadBytes() == PageBytes;
2341 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-optical-read-after-toc");
2344 "HOSTED-WAIT-TEST: FAIL scsi-optical-read-after-toc: successful READ TOC suppressed "
2345 "the media READ(10) or published an empty cache page");
2350bool scsiRejectsNativeBlocksLargerThanCachePages() {
2351 ScriptedScsiController controller(64 * PageBytes, 2 * PageBytes);
2352 HostedScsiDisk disk;
2353 const bool passed = !disk.initialise(&controller, 0);
2355 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-larger-native-block-rejected");
2358 "HOSTED-WAIT-TEST: FAIL scsi-larger-native-block-rejected: a native block spanning "
2359 "independently owned cache pages was accepted without an RMW contract");
2365EXPORTED_PUBLIC
bool runHostedScsiSyncRegressions() {
2366 const bool scopedViews = scsiScopedDiskViews();
2367 const bool viewAlignment = scsiViewSurvivesAlignmentChange();
2368 const bool deferredWrites = scsiDeferredWrites();
2369 const bool deferredRetry = scsiDeferredWriteRetry();
2370 const bool deferredShutdown = scsiDeferredWriteShutdown();
2371 const bool syncAll = scsiSyncAll();
2372 const bool batch = scsiSyncBatch(
false,
false);
2373 const bool batchWriteFailure = scsiSyncBatch(
true,
false);
2374 const bool batchFlushFailure = scsiSyncBatch(
false,
true);
2375 const bool overlappingBatches = scsiOverlappingSyncBatches();
2376 const bool batchValidation = scsiSyncBatchValidation();
2377 const bool batchTerminal = scsiSyncBatchTerminalGeometry();
2378 const bool producerAlignment = scsiProducerShiftedOriginFallback();
2379 const bool producerReads = scsiProducerReadBatchCoherence();
2380 const bool producerReadFailure = scsiProducerReadBatchFailure();
2381 const bool producerReadFallbacks = scsiProducerReadBatchFallbacks();
2382 return scopedViews && viewAlignment && deferredWrites && deferredRetry && deferredShutdown &&
2383 syncAll && batch && batchWriteFailure && batchFlushFailure && overlappingBatches &&
2384 batchValidation && batchTerminal && producerAlignment && producerReads &&
2385 producerReadFailure && producerReadFallbacks;
2388EXPORTED_PUBLIC
bool runHostedScsiWriteRegressions() {
2390 const bool ataOwnership = ataQueuedWriteCacheOwnership(fixture);
2391 const bool scsiResult = scsiWriteResult(fixture);
2392 const bool directResult = scsiDirectRetireResult(fixture);
2393 const bool directOwnership = scsiDirectRetireOwnership(fixture);
2394 const bool readRetireAdmission = scsiReadRetireAdmission(fixture);
2395 const bool retireReadRecheck = scsiRetireReadRecheck(fixture);
2396 const bool checkedSync = scsiCheckedSync();
2397 const bool syncAll = runHostedScsiSyncRegressions();
2398 const bool terminalPage = scsiTerminalCachePage();
2399 const bool opticalRead = scsiOpticalReadAfterToc();
2400 const bool largerNativeRejected = scsiRejectsNativeBlocksLargerThanCachePages();
2401 return ataOwnership && scsiResult && directResult && directOwnership && readRetireAdmission &&
2402 retireReadRecheck && checkedSync && syncAll && terminalPage && opticalRead &&
2403 largerNativeRejected;
bool(* retirement_writeback_t)(uintptr_t key, uintptr_t page, void *meta)
static Scheduler & instance()
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)
virtual MUST_USE_RESULT uint64_t doWriteDirect(uint64_t location, uintptr_t page)
virtual size_t getCacheFillSize() const
static constexpr size_t getPageSize() noexcept
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)