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