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);
1566 auto writer =
fixture.disk.writeView(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() {
1613 auto writer =
fixture.disk.writeView(2048);
1614 const bool admitted =
reader && writer;
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();
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};
1688 constexpr uint8_t CleanSyncs[] = {0x35, 0x35};
1691 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-checked-sync: fixture setup");
1694 fixture.disk.pauseBackgroundWriteback();
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));
1704 fixture.disk.unpin(CheckedSyncLocation);
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);
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));
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));
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);
1748 fixture.disk.unpin(CheckedSyncLocation);
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;
1764 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-checked-sync");
1767 "HOSTED-WAIT-TEST: FAIL scsi-checked-sync: clean writes, error retention, retry, or "
1768 "borrowed pin ownership");
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};
1788 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-all: fixture setup");
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);
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);
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);
1828 fixture.controller.beginWrites(WriteMode::PassWrite12);
1829 fixture.controller.beginSync(SyncMode::Pass10);
1830 const bool retried =
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);
1838 emptyFlushed && emptyFailure && emptyRetry && pageFailed && finalFailed && retried && cleaned;
1840 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-all");
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);
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};
1858 fixture.disk.pauseBackgroundWriteback();
1859 if (!
fixture.disk.preparePage(Keys[0]) || !
fixture.disk.preparePage(Keys[1]))
1861 for (uint64_t key : Keys)
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) {
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;
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]);
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;
1901 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch write-failure="
1902 << failWrite <<
", flush-failure=" << failFlush);
1904 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch: commit="
1905 << committed <<
", retained=" << retained <<
", retry=" << retried
1906 <<
", caller-pin=" << callerPin <<
", balanced=" << balanced);
1910struct SyncBatchContext {
1911 HostedScsiDisk* disk;
1915 bool result =
false;
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;
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};
1934 fixture.disk.pauseBackgroundWriteback();
1935 if (!
fixture.disk.preparePage(First) || !
fixture.disk.preparePage(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}};
1945 nullptr,
false,
true);
1946 producer->setName(
"hosted SCSI held batch barrier");
1947 const bool held =
fixture.controller.waitForHeldSync();
1949 bool waiting =
false;
1953 waiter->setName(
"hosted SCSI overlapping reverse batch");
1954 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1955 while (Time::getTicks() < deadline) {
1957 uintptr_t address = 0;
1958 if (
waiter->getWaitDebugInfo(info) && info.queue && info.queued &&
1959 waiter->getDebugState(address) == Thread::CallbackDrain) {
1963 if (second.returned)
1968 const bool excluded = held && waiting && !first.returned && !second.returned &&
1969 fixture.controller.writeCount() == 8 && !
fixture.disk.evictPage(First) &&
1970 !
fixture.disk.evictPage(Second);
1973 fixture.controller.releaseHeldSync();
1974 const bool firstCompleted = waitUntilSet(first.returned);
1975 const bool secondCompleted =
waiter && waitUntilSet(second.returned);
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;
1987 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-overlapping-sync-batches");
1989 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-overlapping-sync-batches: excluded="
1990 << excluded <<
", serialised=" << serialised <<
", balanced=" << balanced);
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};
2004 fixture.disk.pauseBackgroundWriteback();
2005 if (!
fixture.disk.preparePage(Key))
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);
2017 fixture.controller.hasNoDirectActivity() &&
fixture.controller.syncTraceMatches(
nullptr, 0);
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;
2026 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch-validation");
2028 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch-validation: rejected="
2029 << rejected <<
", editing=" << editing <<
", no-io=" << noIo <<
", dedup=" << deduplicated
2030 <<
", balanced=" << balanced);
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))
2044 disk.pauseBackgroundWriteback();
2045 if (!disk.preparePage(Keys[0]) || !disk.preparePage(Keys[1]))
2047 for (uint64_t key : Keys)
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;
2059 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-sync-batch-terminal");
2061 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-sync-batch-terminal: write bounds, final flush or pins");
2065bool scsiTerminalCachePage() {
2066 constexpr size_t TerminalBytes = 512;
2067 constexpr uint64_t TerminalLocation = 4 * PageBytes;
2068 constexpr uint8_t SuccessfulOpcodes[] = {0x2a, 0x2a, 0x2a, 0xaa};
2070 ScriptedScsiController controller(TerminalLocation + TerminalBytes);
2071 HostedScsiDisk disk;
2072 const bool ready = disk.initialise(&controller, 0);
2073 controller.beginRequestTrace();
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;
2086 for (
size_t i = TerminalBytes; page && i < PageBytes; ++i) {
2093 disk.unpin(TerminalLocation);
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);
2103 const bool passed = ready && payloadValid && tailZeroed && boundedWriteback;
2105 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-terminal-cache-page");
2108 "HOSTED-WAIT-TEST: FAIL scsi-terminal-cache-page: terminal read length, zero fill, or "
2109 "writeback bounds changed");
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)
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};
2130 fixture.disk.pauseBackgroundWriteback();
2131 fixture.disk.enableBufferTransfers();
2132 fixture.controller.beginWrites(WriteMode::PassWrite12);
2133 if (!
fixture.disk.preparePage(Cached))
2138 ByteSet(
static_cast<uint8_t*
>(loan.data()) + 512, 0xc7, 512);
2140 fixture.controller.beginRequestTrace();
2141 fixture.disk.beginReadBufferBatch();
2143 uint8_t output[5][514];
2144 ByteSet(output, 0x3f,
sizeof(output));
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;
2162 ByteSet(
static_cast<uint8_t*
>(loan.data()) + 512, 0xd8, 512);
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;
2169 passed =
fixture.disk.hasNoCacheLoans() && passed;
2171 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-coherence");
2173 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-coherence");
2177bool scsiProducerReadBatchFailure() {
2178 constexpr uint64_t Locations[] = {4 * PageBytes, 8 * PageBytes, 12 * PageBytes};
2182 fixture.disk.enableBufferTransfers();
2183 fixture.disk.beginReadBufferBatch(Locations[1]);
2184 uint8_t output[3][514];
2185 ByteSet(output, 0x3f,
sizeof(output));
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]);
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;
2205 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-failure");
2207 ERROR(
"HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-failure");
2211enum class ReadBatchFallback { Unsupported, Unaligned, NativeSector, PageCrossing, ShiftedOrigin };
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))
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;
2227 if (mode == ReadBatchFallback::Unaligned) {
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;
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]))
2243 loan = disk.read(locations[0]);
2246 ByteSet(loan.data(), 0xc7, PageBytes);
2247 disk.write(locations[0]);
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();
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));
2267 disk.unpin(locations[0]);
2270 "HOSTED-WAIT-TEST: FAIL scsi-producer-read-batch-fallback mode=" <<
static_cast<int>(mode));
2271 return passed && disk.hasNoCacheLoans();
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))
2282 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-read-batch-fallbacks");
2286bool scsiProducerShiftedOriginFallback() {
2287 constexpr uint64_t ShiftedOrigin = 1536;
2288 constexpr uint64_t LaterPage = 4 * PageBytes;
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] = {};
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);
2303 fixture.disk.align(ShiftedOrigin);
2304 const bool prepared =
fixture.disk.preparePage(ShiftedOrigin);
2307 static_cast<uint8_t*
>(loan.data())[0] = 0xc7;
2308 fixture.disk.write(ShiftedOrigin);
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);
2325 fixture.disk.unpin(ShiftedOrigin);
2327 const bool passed = alignedFastPath && prepared && beforeOrigin && acrossOrigin &&
2328 afterAlignedOrigin && dirtyLoanVisible && loanUpdated && crossWrite &&
2331 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-producer-shifted-origin-fallback");
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);
2342bool scsiOpticalReadAfterToc() {
2343 constexpr size_t OpticalBlockBytes = 2048;
2344 constexpr uint64_t VolumeDescriptorLocation = 16 * OpticalBlockBytes;
2346 ScriptedScsiController controller(64 * PageBytes, OpticalBlockBytes,
true);
2347 HostedScsiDisk disk;
2348 const bool ready = disk.initialise(&controller, 0);
2349 controller.beginRequestTrace();
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;
2361 disk.unpin(VolumeDescriptorLocation);
2364 const bool passed = ready && payloadValid && controller.readTocCount() == 1 &&
2365 controller.readRequestCount() == 1 && controller.readCommandCount() == 1 &&
2366 controller.lastReadOpcode() == 0x28 &&
2367 controller.lastReadBytes() == PageBytes;
2369 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-optical-read-after-toc");
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");
2378bool scsiRejectsNativeBlocksLargerThanCachePages() {
2379 ScriptedScsiController controller(64 * PageBytes, 2 * PageBytes);
2380 HostedScsiDisk disk;
2381 const bool passed = !disk.initialise(&controller, 0);
2383 NOTICE(
"HOSTED-WAIT-TEST: PASS scsi-larger-native-block-rejected");
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");
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;
2416EXPORTED_PUBLIC
bool runHostedScsiWriteRegressions() {
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;
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)