The Pedigree Project 0.1
ScsiDisk.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "ScsiDisk.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/Log.h"
23#include "pedigree/kernel/Service.h"
24#include "pedigree/kernel/ServiceFeatures.h"
25#include "pedigree/kernel/ServiceManager.h"
26#include "pedigree/kernel/TargetInfo.h"
27#include "pedigree/kernel/machine/Pci.h"
28#include "pedigree/kernel/panic.h"
29#include "pedigree/kernel/process/Thread.h"
30#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
31#include "pedigree/kernel/processor/types.h"
32#include "pedigree/kernel/time/Time.h"
33#include "pedigree/kernel/utilities/Cache.h"
34#include "pedigree/kernel/utilities/PointerGuard.h"
35#include "pedigree/kernel/utilities/assert.h"
36#include "pedigree/kernel/utilities/utility.h"
37
38#include "ScsiCommands.h"
39#include "ScsiController.h"
40
41#ifdef SCSI_DEBUG
42#define SCSI_DEBUG_LOG DEBUG_LOG
43#else
44#define SCSI_DEBUG_LOG(...)
45#endif
46
47namespace {
48constexpr size_t ScsiCachePageBytes = TargetInfo::getPageSize();
49
50size_t cacheExtentLength(size_t validLength) {
51 if (!validLength || validLength > (~static_cast<size_t>(0) - (ScsiCachePageBytes - 1))) {
52 return 0;
53 }
54 return (validLength + ScsiCachePageBytes - 1) & ~(ScsiCachePageBytes - 1);
55}
56
57bool discardEditingRange(Cache& cache, uintptr_t key, size_t length) {
58 bool discarded = true;
59 for (size_t offset = 0; offset < length; offset += ScsiCachePageBytes) {
60 if (!cache.discardEditing(key + offset)) {
61 discarded = false;
62 }
63 }
64 return discarded;
65}
66
67class CacheFillGuard {
68 public:
69 CacheFillGuard(Cache& cache, uintptr_t key, size_t length)
70 : m_Cache(cache), m_Key(key), m_Length(length), m_Published(false) {}
71
72 ~CacheFillGuard() {
73 if (m_Published) {
74 return;
75 }
76
77 if (!discardEditingRange(m_Cache, m_Key, m_Length)) {
78 WARNING(
79 "ScsiDisk could not discard every page from a failed cache "
80 "fill at "
81 << m_Key);
82 }
83 }
84
85 void publish() {
86 m_Cache.markNoLongerEditing(m_Key, m_Length);
87 m_Published = true;
88 }
89
90 private:
91 CacheFillGuard(const CacheFillGuard&) = delete;
92 CacheFillGuard& operator=(const CacheFillGuard&) = delete;
93
94 Cache& m_Cache;
95 uintptr_t m_Key;
96 size_t m_Length;
97 bool m_Published;
98};
99} // namespace
100
101#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
102Mutex ScsiDisk::m_HostedReadRequestHookLock;
103ScsiDisk::HostedReadRequestHook ScsiDisk::m_HostedReadRequestHook = nullptr;
104void* ScsiDisk::m_HostedReadRequestHookContext = nullptr;
105
106void ScsiDisk::setHostedReadRequestHookForTest(HostedReadRequestHook hook, void* context) {
107 LockGuard<Mutex> guard(m_HostedReadRequestHookLock);
108 m_HostedReadRequestHookContext = context;
109 m_HostedReadRequestHook = hook;
110}
111#endif
112
113ScsiDisk::CacheRangeAdmission::CacheRangeAdmission(ScsiDisk& disk, uint64_t start, size_t length,
114 bool retire)
115 : m_Disk(disk),
116 m_Start(start),
117 m_Length(length),
118 m_Retire(retire),
119 m_Linked(false),
120 m_Previous(nullptr),
121 m_Next(nullptr),
122 m_TerminationDeferral(),
123 m_StackDiscardScope(&CacheRangeAdmission::discard, this) {
124 m_Disk.enterCacheRange(*this);
125}
126
127ScsiDisk::CacheRangeAdmission::~CacheRangeAdmission() {
128 m_Disk.leaveCacheRange(*this);
129}
130
131void ScsiDisk::CacheRangeAdmission::discard(void* context) {
132 CacheRangeAdmission* admission = reinterpret_cast<CacheRangeAdmission*>(context);
133 admission->m_Disk.leaveCacheRange(*admission);
134}
135
136bool ScsiDisk::cacheRangesOverlap(uint64_t firstStart, size_t firstLength, uint64_t secondStart,
137 size_t secondLength) {
138 if (!firstLength || !secondLength) {
139 return false;
140 }
141
142 if (firstStart <= secondStart) {
143 return (secondStart - firstStart) < firstLength;
144 }
145 return (firstStart - secondStart) < secondLength;
146}
147
148bool ScsiDisk::cacheRangeBlocked(const CacheRangeAdmission& admission) const {
149 for (CacheRangeAdmission* earlier = m_FirstCacheRangeAdmission; earlier != &admission;
150 earlier = earlier->m_Next) {
151 assert(earlier);
152 if ((admission.m_Retire || earlier->m_Retire) &&
153 cacheRangesOverlap(admission.m_Start, admission.m_Length, earlier->m_Start,
154 earlier->m_Length)) {
155 return true;
156 }
157 }
158 return false;
159}
160
161void ScsiDisk::enterCacheRange(CacheRangeAdmission& admission) {
162 while (true) {
163 auto guard = m_CacheRangeWaiters.acquire();
164 if (!admission.m_Linked) {
165 admission.m_Previous = m_LastCacheRangeAdmission;
166 if (m_LastCacheRangeAdmission) {
167 m_LastCacheRangeAdmission->m_Next = &admission;
168 } else {
169 m_FirstCacheRangeAdmission = &admission;
170 }
171 m_LastCacheRangeAdmission = &admission;
172 admission.m_Linked = true;
173 }
174
175 if (!cacheRangeBlocked(admission)) {
176 return;
177 }
178
179 const WaitQueue::WakeReason reason =
180 guard.waitForCompletion(WaitQueue::Channel(this), Thread::CondWait, admission.m_Start);
181 (void)reason;
182 }
183}
184
185void ScsiDisk::leaveCacheRange(CacheRangeAdmission& admission) {
186 auto guard = m_CacheRangeWaiters.acquire();
187 if (!admission.m_Linked) {
188 return;
189 }
190 if (admission.m_Previous) {
191 admission.m_Previous->m_Next = admission.m_Next;
192 } else {
193 m_FirstCacheRangeAdmission = admission.m_Next;
194 }
195 if (admission.m_Next) {
196 admission.m_Next->m_Previous = admission.m_Previous;
197 } else {
198 m_LastCacheRangeAdmission = admission.m_Previous;
199 }
200 admission.m_Linked = false;
201 admission.m_Previous = nullptr;
202 admission.m_Next = nullptr;
203 guard.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(this));
204}
205
206bool ScsiDisk::cacheCallback(CacheConstants::CallbackCause cause, uintptr_t loc, uintptr_t page,
207 void* meta) {
208 ScsiDisk* pDisk = reinterpret_cast<ScsiDisk*>(meta);
209
210 switch (cause) {
211 case CacheConstants::WriteBack: {
212 // Cache shutdown runs after external operations have been closed,
213 // so writeback must use the internal path.
214 return pDisk->flushCachePage(loc, page);
215 }
216 case CacheConstants::Eviction:
217 // no-op for ScsiDisk
218 return true;
219 default:
220 WARNING(
221 "ScsiDisk: unknown cache callback -- could indicate "
222 "potential future I/O issues.");
223 return false;
224 }
225}
226
227bool ScsiDisk::retireCachePageCallback(uintptr_t key, uintptr_t page, void* meta) {
228 ScsiDisk* disk = reinterpret_cast<ScsiDisk*>(meta);
229 if (!disk || !page) {
230 return false;
231 }
232
233 ScsiController* controller = static_cast<ScsiController*>(disk->m_pParent);
234 if (!controller) {
235 return false;
236 }
237
238 const uint64_t result =
239 controller->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_WRITE_DIRECT,
240 reinterpret_cast<uint64_t>(disk), key, page);
241 return result == disk->getCachePageValidLength(key);
242}
243
244ScsiDisk::ScsiDisk(size_t cacheConstraints)
245 : Disk(),
246 m_Cache(cacheConstraints),
247 m_Inquiry(0),
248 m_CacheRangeWaiters(),
249 m_FirstCacheRangeAdmission(nullptr),
250 m_LastCacheRangeAdmission(nullptr),
251 m_AlignmentLock(),
252 m_HasShiftedCacheAlignment(false),
253 m_NumBlocks(0),
254 m_BlockSize(ScsiCachePageBytes),
255 m_NativeBlockSize(0),
256 m_DeviceType(NoDevice) {
257 reserveEndpoint();
258 m_Cache.setDirtyTracking(Cache::DirtyTracking::Explicit);
259 m_Cache.setCallback(cacheCallback, this);
260 m_Cache.setBackgroundWriteback(syncCacheBatch);
261}
262
263ScsiDisk::~ScsiDisk() {
266}
267
269 if (!m_Cache.shutdown())
270 panic("SCSI: cache shutdown failed; unwritten data remains");
271}
272
273void ScsiDisk::shutdownDeviceCache() {
274#if !CRIPPLE_HDD
275 // Paging and direct writes can outlive every page in the software cache.
276 // Controller admission is closed, but its worker still accepts this barrier.
277 auto* controller = static_cast<ScsiController*>(m_pParent);
278 if (!controller || !controller->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_SYNC,
279 reinterpret_cast<uint64_t>(this), SyncWholeDevice))
280 panic("SCSI: shutdown aborted after final device cache flush failure");
281#endif
282}
283
284bool ScsiDisk::initialise(ScsiController* pController, size_t nUnit) {
285 m_pController = pController;
286 m_pParent = pController;
287 m_nUnit = nUnit;
288
289 m_Inquiry = new Inquiry;
290
291 // Inquire as to the device's state
293 ScsiCommand* pCommand = new ScsiCommands::Inquiry(sizeof(Inquiry), false);
294 bool success = sendCommand(pCommand, reinterpret_cast<uintptr_t>(m_Inquiry), sizeof(Inquiry));
295 if (!success) {
296 ERROR("ScsiDisk: INQUIRY failed!");
297 delete pCommand;
298 return false;
299 }
300 delete pCommand;
301
302 // Get the peripheral type out of the data.
303 m_DeviceType = static_cast<ScsiPeripheralType>(m_Inquiry->Peripheral & 0x1F);
304
305 // Ensure the unit is ready before we attempt to do anything more
306 if (!unitReady()) {
307 // Grab sense data
308 Sense* s = new Sense;
309 PointerGuard<Sense> guard2(s);
310 readSense(s);
311 SCSI_DEBUG_LOG("ScsiDisk: Unit not yet ready, sense data: [sk="
312 << s->SenseKey << ", asc=" << s->Asc << ", ascq=" << s->AscQ << "]");
313
314 if (s->SenseKey == 0x2) {
315 if (s->Asc == 0x4) {
316 if (s->AscQ == 0x2) // Logical Unit Not Ready, START UNIT Required
317 {
318 // Start the unit
319 pCommand = new ScsiCommands::StartStop(false, true, 1, true);
320 success = sendCommand(pCommand, 0, 0, true);
321 if (!success) {
322 readSense(s);
323 ERROR("ScsiDisk: unit startup failed! Sense data: [sk="
324 << s->SenseKey << ", asc=" << s->Asc << ", ascq=" << s->AscQ << "]");
325 }
326 delete pCommand;
327 }
328 }
329 }
330
331 Time::delay(100 * Time::Multiplier::Millisecond);
332
333 // Attempt to see if the unit is ready again
334 if (!unitReady()) {
335 readSense(s);
336 SCSI_DEBUG_LOG("ScsiDisk: Unit not yet ready, sense data: [sk="
337 << s->SenseKey << ", asc=" << s->Asc << ", ascq=" << s->AscQ << "]");
338
339 Time::delay(100 * Time::Multiplier::Millisecond);
340
341 if (!unitReady()) {
342 readSense(s);
343 ERROR("ScsiDisk: disk never became ready. Sense data: [sk="
344 << s->SenseKey << ", asc=" << s->Asc << ", ascq=" << s->AscQ << "]");
345 return false;
346 }
347 }
348 }
349
350 // Get the capacity of the device
351 if (!getCapacityInternal(&m_NumBlocks, &m_NativeBlockSize)) {
352 ERROR("ScsiDisk: could not determine device capacity");
353 return false;
354 }
355 if (!m_NativeBlockSize || m_NativeBlockSize > ScsiCachePageBytes ||
356 (ScsiCachePageBytes % m_NativeBlockSize)) {
357 ERROR("ScsiDisk: native block size " << m_NativeBlockSize
358 << " is incompatible with target cache pages of "
359 << ScsiCachePageBytes << " bytes");
360 return false;
361 }
362 SCSI_DEBUG_LOG("ScsiDisk: Capacity: "
363 << Dec << m_NumBlocks << " blocks, each " << m_NativeBlockSize << " bytes - "
364 << (m_NativeBlockSize * m_NumBlocks) << Hex << " bytes in total.");
365
366 publishEndpoint();
367
368 // Chat to the partition service and let it pick up that we're around now
369 ServiceFeatures* pFeatures = ServiceManager::instance().enumerateOperations(String("partition"));
370 Service* pService = ServiceManager::instance().getService(String("partition"));
371 if (pFeatures && pFeatures->provides(ServiceFeatures::touch)) {
372 NOTICE("Attempting to inform the partitioner of our presence...");
373 if (pService) {
374 if (pService->serve(ServiceFeatures::touch, static_cast<Disk*>(this),
375 sizeof(static_cast<Disk*>(this))))
376 NOTICE("Successful.");
377 else
378 ERROR("Failed.");
379 } else
380 ERROR(
381 "ScsiDisk: Couldn't tell the partition service about the new "
382 "disk presence");
383 } else
384 ERROR("ScsiDisk: Partition service doesn't appear to support touch");
385 return true;
386}
387
389 ByteSet(sense, 0xFF, sizeof(Sense));
390
391 // Maximum size of sense data is 252 bytes
392 ScsiCommand* pCommand = new ScsiCommands::ReadSense(0, sizeof(Sense));
393
394 uint8_t* response = new uint8_t[sizeof(Sense)];
395 bool success = sendCommand(pCommand, reinterpret_cast<uintptr_t>(response), sizeof(Sense));
396 if (!success) {
397 WARNING("ScsiDisk: SENSE command failed");
398 delete[] response;
399 return false;
400 }
401
403 MemoryCopy(sense, response, sizeof(Sense));
404
405 delete[] response;
406
407 return ((sense->ResponseCode & 0x70) == 0x70);
408}
409
411 ScsiCommand* pCommand = new ScsiCommands::UnitReady();
412 bool success = sendCommand(pCommand, 0, 0);
413 delete pCommand;
414
417 return success;
418}
419
420bool ScsiDisk::getCapacityInternal(size_t* blockNumber, size_t* blockSize) {
421 if (!unitReady()) {
422 WARNING(
423 "ScsiDisk::getCapacityInternal - returning to defaults, unit "
424 "not ready");
425 *blockNumber = 0;
426 *blockSize = defaultBlockSize();
427 return false;
428 }
429
430 Capacity* capacity = new Capacity;
431 PointerGuard<Capacity> guard(capacity);
432 ByteSet(capacity, 0, sizeof(Capacity));
433
435 bool success =
436 sendCommand(pCommand, reinterpret_cast<uintptr_t>(capacity), sizeof(Capacity), false);
437 delete pCommand;
438 if (!success) {
439 WARNING("ScsiDisk::getCapacityInternal - READ CAPACITY command failed");
440 return false;
441 }
442
443 *blockNumber = static_cast<size_t>(BIG_TO_HOST32(capacity->LBA)) + 1;
444 uint32_t blockSz = BIG_TO_HOST32(capacity->BlockSize);
445 *blockSize = blockSz ? blockSz : defaultBlockSize();
446
447 return true;
448}
449
450bool ScsiDisk::sendCommand(ScsiCommand* pCommand, uintptr_t pRespBuffer, uint16_t nRespBytes,
451 bool bWrite) {
452 uintptr_t pCommandBuffer = 0;
453 size_t nCommandSize = pCommand->serialise(pCommandBuffer);
454 return m_pController->sendCommand(m_nUnit, pCommandBuffer, nCommandSize, pRespBuffer, nRespBytes,
455 bWrite);
456}
457
458BufferView ScsiDisk::read(uint64_t location) {
459 TerminationDeferral lifetime;
460 DiskUse diskUse;
461 if (!acquireUse(diskUse))
462 return BufferView();
463 uint64_t token;
464 return acquireView(location, true, token);
465}
466
467BufferView ScsiDisk::acquireView(uint64_t location, bool writable, uint64_t& token) {
468 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
469 if (!pParent) {
470 return BufferView();
471 }
472
473 OperationBarrier::Lease operation;
474 if (!pParent->acquireDiskOperation(operation)) {
475 return BufferView();
476 }
477
478 const size_t fillSize = getCacheFillSize();
479 if (!fillSize || !getNativeBlockSize() || (fillSize % ScsiCachePageBytes) ||
480 (fillSize % getNativeBlockSize())) {
481 ERROR("ScsiDisk::read - invalid cache or native block size.");
482 return BufferView();
483 }
484 if (location >= getSize()) {
485 ERROR("ScsiDisk::read - location too high (" << location << " of " << getSize() << ")");
486 return BufferView();
487 }
488 size_t blockNum = location / getNativeBlockSize();
489 if (blockNum >= getBlockCount()) {
490 ERROR("ScsiDisk::read - location too high (block " << blockNum << " > " << getBlockCount()
491 << ")");
492 return BufferView();
493 }
494 const uint64_t alignPoint = getAlignmentPoint(location);
495
496 const uint64_t pageLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
497 token = pageLocation;
498 const size_t pageOffset = location - pageLocation;
499
500 // Cache extents follow the most recent alignment point, which may not be
501 // aligned to the device's cache block size.
502 size_t loc = location - ((location - alignPoint) % fillSize);
503 const size_t fillLength = getCacheFillLength(loc);
504 const size_t cacheLength = cacheExtentLength(fillLength);
505 const size_t validPageLength = getCachePageValidLength(pageLocation);
506 if (!cacheLength || pageLocation < loc || (pageLocation - loc) >= cacheLength ||
507 pageOffset >= validPageLength) {
508 return BufferView();
509 }
510
511 // A lookup can return an Editing page. Keep overlapping readers out until
512 // the transport has finished filling and publishing the entire extent.
513 CacheRangeAdmission admission(*this, loc, cacheLength, true);
514
515 uintptr_t buffer;
516 if ((buffer = m_Cache.lookup(pageLocation))) {
517 if (writable && !m_Cache.beginMutableLoan(pageLocation)) {
518 m_Cache.release(pageLocation);
519 return {};
520 }
521 return BufferView::fromAddress(buffer + pageOffset, validPageLength - pageOffset);
522 }
523
524 uint64_t numRead =
525 pParent->supportsConcurrentReads()
526 ? doRead(loc)
527 : pParent->addRequest(0, SCSI_REQUEST_READ, reinterpret_cast<uint64_t>(this), loc);
528 if (numRead < fillLength) {
529 // Failed to read for some reason, expose the failure to our caller.
530 WARNING("ScsiDisk::read - short read!");
531 return BufferView();
532 }
533#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
534 HostedReadRequestHook readRequestHook = nullptr;
535 void* readRequestHookContext = nullptr;
536 {
537 LockGuard<Mutex> guard(m_HostedReadRequestHookLock);
538 readRequestHook = m_HostedReadRequestHook;
539 readRequestHookContext = m_HostedReadRequestHookContext;
540 }
541 if (readRequestHook) {
542 readRequestHook(this, pageLocation, readRequestHookContext);
543 }
544#endif
545 buffer = m_Cache.lookup(pageLocation);
546 if (!buffer) {
547 return BufferView();
548 }
549 if (writable && !m_Cache.beginMutableLoan(pageLocation)) {
550 m_Cache.release(pageLocation);
551 return {};
552 }
553 return BufferView::fromAddress(buffer + pageOffset, validPageLength - pageOffset);
554}
555
556bool ScsiDisk::readInto(uint64_t location, void* buffer, size_t length) {
557 return transferBufferRange(location, buffer, length, false);
558}
559
560bool ScsiDisk::readIntoBatch(ReadBuffer* buffers, size_t count) {
561 if (count > MaxReadBuffers || (count && !buffers))
562 return false;
563 for (size_t i = 0; i < count; ++i)
564 buffers[i].complete = false;
565 if (!count)
566 return true;
567 TerminationDeferral lifetime;
568 DiskUse diskUse;
569 if (!acquireUse(diskUse))
570 return false;
571 auto* controller = static_cast<ScsiController*>(m_pParent);
572 OperationBarrier::Lease operation;
573 if (!controller || !controller->acquireDiskOperation(operation))
574 return false;
575
576 const size_t native = getNativeBlockSize();
577 if (!supportsBufferTransfers() || !native || ScsiCachePageBytes % native ||
578 hasShiftedCacheAlignment())
579 return Disk::readIntoBatch(buffers, count);
580
581 uint64_t keys[MaxReadBuffers];
582 uint64_t first = ~uint64_t{0};
583 uint64_t end = 0;
584 for (size_t i = 0; i < count; ++i) {
585 const auto& request = buffers[i];
586 if ((!request.buffer && request.length) || request.location > getSize() ||
587 request.length > getSize() - request.location)
588 return false;
589 // Odd sectors and ranges spanning cache pages retain the ordinary path's
590 // merge rules. File page batches use independent, sector-aligned ranges.
591 const uint64_t key = request.location - request.location % ScsiCachePageBytes;
592 if (!request.length || request.location % native || request.length % native ||
593 request.length > ScsiCachePageBytes - (request.location - key) ||
594 key > ~uint64_t{0} - ScsiCachePageBytes || key > ~uintptr_t{0})
595 return Disk::readIntoBatch(buffers, count);
596 keys[i] = key;
597 if (key < first)
598 first = key;
599 if (key + ScsiCachePageBytes > end)
600 end = key + ScsiCachePageBytes;
601 }
602 if (end - first > ~size_t{0})
603 return Disk::readIntoBatch(buffers, count);
604
605 for (size_t attempt = 0; attempt < 8; ++attempt) {
606 uint64_t retryKey = 0;
607 bool retry = false;
608 {
609 // One admission also covers repeated/sub-page extents without waiting on
610 // another range owned by this same batch. Retain it until DMA is drained.
611 CacheRangeAdmission admission(*this, first, end - first, true);
612 if (hasShiftedCacheAlignment())
613 return false;
614 ReadBuffer pending[MaxReadBuffers];
615 size_t indices[MaxReadBuffers];
616 size_t nPending = 0;
617 for (size_t i = 0; i < count; ++i) {
618 if (buffers[i].complete)
619 continue;
620 uintptr_t page = 0;
621 if (!m_Cache.lookupStable(keys[i], page)) {
622 retryKey = keys[i];
623 retry = true;
624 break;
625 }
626 if (page) {
627 CachePageGuard guard(m_Cache, keys[i]);
628 MemoryCopy(buffers[i].buffer,
629 reinterpret_cast<void*>(page + buffers[i].location - keys[i]),
630 buffers[i].length);
631 buffers[i].complete = true;
632 } else {
633 indices[nPending] = i;
634 pending[nPending++] = buffers[i];
635 }
636 }
637 if (!retry) {
638 const bool success = !nPending || transferReadBuffers(pending, nPending);
639 for (size_t i = 0; i < nPending; ++i)
640 buffers[indices[i]].complete = pending[i].complete;
641 return success;
642 }
643 }
644 // Writeback callbacks may themselves acquire range admission.
645 uintptr_t page = 0;
646 if (attempt == 7 || !m_Cache.lookupStable(retryKey, page, true))
647 return false;
648 if (page)
649 m_Cache.release(retryKey);
650 }
651 return false;
652}
653
654bool ScsiDisk::transferReadBuffers(ReadBuffer* buffers, size_t count) {
655 bool success = true;
656 for (size_t i = 0; i < count; ++i) {
657 auto& request = buffers[i];
658 request.complete = transferBuffer(request.location, request.buffer, request.length, false);
659 success &= request.complete;
660 }
661 return success;
662}
663
664bool ScsiDisk::zero(uint64_t location, size_t length) {
665 TerminationDeferral lifetime;
666 DiskUse diskUse;
667 if (!acquireUse(diskUse))
668 return false;
669 auto* controller = static_cast<ScsiController*>(m_pParent);
670 OperationBarrier::Lease operation;
671 if (!controller || !controller->acquireDiskOperation(operation) || location > getSize() ||
672 length > getSize() - location)
673 return false;
674 if (!length)
675 return true;
676 // Legacy read requests can fill larger extents: retain their existing
677 // publication rules instead of leaving a partially populated extent.
678 if (!getNativeBlockSize() || ScsiCachePageBytes % getNativeBlockSize() ||
679 getCacheFillSize() != ScsiCachePageBytes || hasShiftedCacheAlignment() ||
680 location % ScsiCachePageBytes || length % ScsiCachePageBytes)
681 return Disk::zero(location, length);
682 CacheRangeAdmission admission(*this, location, length, true);
683 while (length) {
684 const uintptr_t existing = m_Cache.lookup(location);
685 const uintptr_t page = existing ? existing : m_Cache.insert(location);
686 if (!page)
687 return false;
688 if (existing && !m_Cache.beginMutableLoan(location)) {
689 m_Cache.release(location);
690 return false;
691 }
692 ByteSet(reinterpret_cast<void*>(page), 0, ScsiCachePageBytes);
693 if (!existing)
694 m_Cache.markNoLongerEditing(location);
695 m_Cache.markDirty(location);
696 if (existing) {
697 m_Cache.endMutableLoan(location);
698 m_Cache.release(location);
699 }
700 location += ScsiCachePageBytes;
701 length -= ScsiCachePageBytes;
702 }
703 return true;
704}
705
706bool ScsiDisk::writeFrom(uint64_t location, const void* buffer, size_t length) {
707#if CRIPPLE_HDD
708 return !length && location <= getSize();
709#else
710 return transferBufferRange(location, const_cast<void*>(buffer), length, true);
711#endif
712}
713
714bool ScsiDisk::transferBuffer(uint64_t location, void* buffer, size_t length, bool writing) {
715 return false;
716}
717
718bool ScsiDisk::transferBufferRange(uint64_t location, void* buffer, size_t length, bool writing) {
719 TerminationDeferral lifetime;
720 DiskUse diskUse;
721 if (!acquireUse(diskUse))
722 return false;
723 auto* controller = static_cast<ScsiController*>(m_pParent);
724 OperationBarrier::Lease operation;
725 if (!controller || !controller->acquireDiskOperation(operation))
726 return false;
727
728 if ((!buffer && length) || location > getSize() || length > getSize() - location)
729 return false;
730 if (!length)
731 return true;
732
733 const size_t native = getNativeBlockSize();
734 // A shifted origin can leave overlapping cache keys even before the origin.
735 // Keep the whole device on its legacy path once that alignment policy exists.
736 if (!supportsBufferTransfers() || !native || ScsiCachePageBytes % native ||
737 hasShiftedCacheAlignment()) {
738 return writing ? Disk::writeFrom(location, buffer, length)
739 : Disk::readInto(location, buffer, length);
740 }
741
742 auto* bytes = static_cast<uint8_t*>(buffer);
743 while (length) {
744 const uint64_t alignment = getAlignmentPoint(location);
745 const uint64_t key = location - ((location - alignment) % ScsiCachePageBytes);
746 const size_t offset = location - key;
747 const size_t validLength = getCachePageValidLength(key);
748 if (key > ~uintptr_t{0} || offset >= validLength || key % native || validLength % native)
749 return false;
750 const size_t available = validLength - offset;
751 const size_t chunk = length < available ? length : available;
752
753 // Readers may retain writable aliases indefinitely, especially when small
754 // filesystem blocks share a page with metadata. Preserve those aliases and
755 // update their page; only an absent page can bypass the block cache.
756 bool complete = false;
757 for (size_t attempt = 0; attempt < 8 && !complete; ++attempt) {
758 {
759 CacheRangeAdmission admission(*this, key, ScsiCachePageBytes, true);
760 // align() joins admitted ranges before publishing a shifted origin.
761 // A change between chunks leaves any completed prefix retryable.
762 if (hasShiftedCacheAlignment())
763 return false;
764 uintptr_t page = 0;
765 bool ready = m_Cache.lookupStable(key, page);
766 // A partial native sector needs a complete cached sector before merging;
767 // all other absent ranges go directly to the caller's storage.
768 if (ready && !page && (location % native || chunk % native)) {
769 if (doRead(key) < validLength)
770 return false;
771 ready = m_Cache.lookupStable(key, page);
772 }
773 if (ready) {
774 if (page) {
775 CachePageGuard guard(m_Cache, key);
776 auto* cached = reinterpret_cast<uint8_t*>(page) + offset;
777 if (writing) {
778 if (!m_Cache.beginMutableLoan(key)) {
779 return false;
780 }
781 MemoryCopy(cached, bytes, chunk);
782 m_Cache.markDirty(key);
783 m_Cache.endMutableLoan(key);
784 const uintptr_t cacheKey = key;
785 if (!m_Cache.syncBatch(
786 &cacheKey, 1,
787 [](const Cache::WritebackPage* pages, size_t count, void* context) {
788 auto* disk = static_cast<ScsiDisk*>(context);
789 for (size_t i = 0; i < count; ++i) {
790 Cache::DirectWritebackLease lease;
791 PciBus::DmaMapping mapping;
792 const bool direct =
793 disk->supportsDirectCacheWrite() && disk->dmaDevice() &&
794 lease.acquire(disk->m_Cache, pages[i].key, pages[i].location) &&
795 PciBus::instance().mapDmaPage(
796 disk->dmaDevice(), lease.physical(),
797 disk->getCachePageValidLength(pages[i].key), mapping);
798 if (!direct) {
799 lease.release();
800 }
801 const uint64_t written =
802 direct ? disk->doWriteDirectPhysical(pages[i].key, pages[i].location,
803 mapping.address())
804 : disk->doWriteDirect(pages[i].key, pages[i].location);
805 if (written != disk->getCachePageValidLength(pages[i].key)) {
806 return false;
807 }
808 }
809 return true;
810 },
811 this))
812 return false;
813 } else {
814 MemoryCopy(bytes, cached, chunk);
815 }
816 } else if (!transferBuffer(location, bytes, chunk, writing)) {
817 return false;
818 }
819 complete = true;
820 }
821 }
822 if (!complete) {
823 // A cache callback may itself need range admission. Join it without
824 // that gate or a borrowed pin, then recheck identity after re-entry.
825 uintptr_t retryPage = 0;
826 if (attempt == 7 || !m_Cache.lookupStable(key, retryPage, true))
827 return false;
828 if (retryPage)
829 m_Cache.release(key);
830 }
831 }
832 bytes += chunk;
833 location += chunk;
834 length -= chunk;
835 }
836 return true;
837}
838
839bool ScsiDisk::writeFromBatch(WriteBuffer* buffers, size_t count) {
840 if (count > MaxWriteBuffers || (count && !buffers))
841 return false;
842 for (size_t i = 0; i < count; ++i)
843 buffers[i].complete = false;
844 if (!count)
845 return true;
846#if CRIPPLE_HDD
847 return false;
848#else
849 TerminationDeferral lifetime;
850 DiskUse use;
851 OperationBarrier::Lease operation;
852 auto* controller = static_cast<ScsiController*>(m_pParent);
853 if (!acquireUse(use) || !controller || !controller->acquireDiskOperation(operation))
854 return false;
855 const size_t native = getNativeBlockSize();
856 bool eligible = supportsBufferTransfers() && native && !(ScsiCachePageBytes % native) &&
857 !hasShiftedCacheAlignment();
858 uint64_t first = ~uint64_t{0}, end = 0;
859 for (size_t i = 0; i < count; ++i) {
860 const auto& b = buffers[i];
861 if (!b.buffer || !b.length || b.location >= getSize() || b.length > getSize() - b.location)
862 return false;
863 eligible &= b.location % ScsiCachePageBytes == 0 && b.length == ScsiCachePageBytes;
864 if (b.location < first)
865 first = b.location;
866 if (b.location + b.length > end)
867 end = b.location + b.length;
868 for (size_t j = 0; j < i; ++j)
869 if (b.location < buffers[j].location + buffers[j].length &&
870 buffers[j].location < b.location + b.length)
871 eligible = false;
872 }
873 if (eligible && end - first <= ~size_t{0}) {
874 CacheRangeAdmission admission(*this, first, end - first, true);
875 eligible = !hasShiftedCacheAlignment();
876 // Existing aliases must be merged by the ordinary path, not bypassed.
877 for (size_t i = 0; eligible && i < count; ++i) {
878 uintptr_t page = 0;
879 eligible = m_Cache.lookupStable(buffers[i].location, page);
880 if (page) {
881 m_Cache.release(buffers[i].location);
882 eligible = false;
883 }
884 }
885 if (eligible)
886 return transferWriteBuffers(buffers, count);
887 }
888 return Disk::writeFromBatch(buffers, count);
889#endif
890}
891
892bool ScsiDisk::transferWriteBuffers(WriteBuffer* buffers, size_t count) {
893 bool success = true;
894 for (size_t i = 0; i < count; ++i) {
895 auto& b = buffers[i];
896 b.complete = transferBuffer(b.location, const_cast<void*>(b.buffer), b.length, true);
897 success = b.complete && success;
898 }
899 return success;
900}
901
903#if CRIPPLE_HDD
904 return false;
905#else
906 TerminationDeferral lifetime;
907 DiskUse diskUse;
908 if (!acquireUse(diskUse))
909 return false;
910 auto* controller = static_cast<ScsiController*>(m_pParent);
911 OperationBarrier::Lease operation;
912 if (!controller || !controller->acquireDiskOperation(operation))
913 return false;
914 return controller->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_SYNC,
915 reinterpret_cast<uint64_t>(this), SyncWholeDevice) != 0;
916#endif
917}
918
919void ScsiDisk::write(uint64_t location) {
920 TerminationDeferral lifetime;
921 DiskUse diskUse;
922 if (!acquireUse(diskUse))
923 return;
924
925 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
926 if (!pParent) {
927 return;
928 }
929
930 OperationBarrier::Lease operation;
931 if (!pParent->acquireDiskOperation(operation)) {
932 return;
933 }
934
935#if !CRIPPLE_HDD
936 const size_t nativeBlockSize = getNativeBlockSize();
937 if (!nativeBlockSize || (ScsiCachePageBytes % nativeBlockSize)) {
938 ERROR("ScsiDisk::write - incompatible cache and native block sizes.");
939 return;
940 }
941
942 if (location >= getSize()) {
943 ERROR("ScsiDisk::write - location too high");
944 ERROR(" -> " << location << " vs " << getSize());
945 return;
946 }
947
948 if ((location / getNativeBlockSize()) >= getBlockCount()) {
949 ERROR("ScsiDisk::write - location too high");
950 ERROR(" -> block " << (location / getNativeBlockSize()) << " vs " << getBlockCount());
951 return;
952 }
953
954 const uint64_t alignPoint = getAlignmentPoint(location);
955
956 const uint64_t pageLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
957 const size_t validLength = getCachePageValidLength(pageLocation);
958 if (!validLength || (pageLocation % nativeBlockSize) || (validLength % nativeBlockSize)) {
959 ERROR("ScsiDisk::write - invalid terminal cache page geometry.");
960 return;
961 }
962
963 uintptr_t buffer;
964 if (!(buffer = m_Cache.lookup(pageLocation))) {
965 ERROR("ScsiDisk::write - no buffer!");
966 return;
967 }
968
969 // The cache owns deferred writeback and retry, so repeated changes share one
970 // pending write instead of competing with a second controller queue.
971 m_Cache.markDirty(pageLocation);
972 m_Cache.release(pageLocation);
973#endif
974}
975
976void ScsiDisk::flush(uint64_t location) {
977 if (!sync(location, false)) {
978 WARNING("ScsiDisk::flush - writeback failed");
979 }
980}
981
982bool ScsiDisk::sync(uint64_t location, bool async) {
983 TerminationDeferral lifetime;
984 DiskUse diskUse;
985 if (!acquireUse(diskUse))
986 return false;
987
988 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
989 if (!pParent) {
990 return false;
991 }
992
993 OperationBarrier::Lease operation;
994 if (!pParent->acquireDiskOperation(operation)) {
995 return false;
996 }
997
998 if (location >= getSize()) {
999 return false;
1000 }
1001 const uint64_t alignPoint = getAlignmentPoint(location);
1002 const uint64_t pageLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
1003 if (async) {
1004 return m_Cache.sync(pageLocation, true);
1005 }
1006
1007#if CRIPPLE_HDD
1008 return false;
1009#else
1010 // A filesystem cache callback can synchronously flush this lower cache.
1011 // Re-entering the shared CacheManager queue would reject that nested request.
1012 const uintptr_t page = m_Cache.lookup(pageLocation);
1013 if (!page) {
1014 return false;
1015 }
1016 CachePageGuard pageGuard(m_Cache, pageLocation);
1017 const uintptr_t key = pageLocation;
1018 struct SyncContext {
1019 ScsiDisk* disk;
1020 bool submitted;
1021 } context = {this, false};
1022 const bool succeeded = m_Cache.syncBatch(
1023 &key, 1,
1024 [](const Cache::WritebackPage* pages, size_t count, void* metadata) {
1025 auto* context = static_cast<SyncContext*>(metadata);
1026 context->submitted = true;
1027 return syncCacheBatch(pages, count, context->disk);
1028 },
1029 &context);
1030 return succeeded && (context.submitted || syncData());
1031#endif
1032}
1033
1034bool ScsiDisk::syncPages(const uint64_t* locations, size_t count) {
1035 static_assert(Disk::MaxSyncPages <= Cache::MaxWritebackPages);
1036 if (count > MaxSyncPages || (count && !locations))
1037 return false;
1038 if (!count)
1039 return true;
1040#if CRIPPLE_HDD
1041 return false;
1042#else
1043 TerminationDeferral lifetime;
1044 DiskUse diskUse;
1045 if (!acquireUse(diskUse))
1046 return false;
1047 auto* controller = static_cast<ScsiController*>(m_pParent);
1048 OperationBarrier::Lease operation;
1049 if (!controller || !controller->acquireDiskOperation(operation))
1050 return false;
1051 const size_t nativeBlockSize = getNativeBlockSize();
1052 if (!nativeBlockSize || ScsiCachePageBytes % nativeBlockSize)
1053 return false;
1054
1055 uintptr_t keys[MaxSyncPages] = {};
1056 size_t pageCount = 0;
1057 for (size_t i = 0; i < count; ++i) {
1058 const uint64_t location = locations[i];
1059 if (location >= getSize() || location % 512)
1060 return false;
1061 const uint64_t alignment = getAlignmentPoint(location);
1062 const uint64_t key = location - ((location - alignment) % ScsiCachePageBytes);
1063 const size_t length = getCachePageValidLength(key);
1064 if (key > ~uintptr_t{0} || !length || key % nativeBlockSize || length % nativeBlockSize)
1065 return false;
1066 bool duplicate = false;
1067 for (size_t j = 0; j < pageCount; ++j)
1068 duplicate |= keys[j] == key;
1069 if (!duplicate)
1070 keys[pageCount++] = key;
1071 }
1072 return m_Cache.syncBatch(keys, pageCount, syncCacheBatch, this);
1073#endif
1074}
1075
1076bool ScsiDisk::syncCacheBatch(const Cache::WritebackPage* pages, size_t count, void* context) {
1077 auto* disk = static_cast<ScsiDisk*>(context);
1078 bool succeeded = true;
1079 for (size_t first = 0; first < count; first += MaxWriteBuffers) {
1080 const size_t n = count - first < MaxWriteBuffers ? count - first : MaxWriteBuffers;
1081 WriteBuffer buffers[MaxWriteBuffers];
1082 Cache::DirectWritebackLease leases[MaxWriteBuffers];
1083 PciBus::DmaMapping mappings[MaxWriteBuffers];
1084 for (size_t i = 0; i < n; ++i) {
1085 const auto& page = pages[first + i];
1086 buffers[i] = {page.key, reinterpret_cast<const void*>(page.location),
1087 disk->getCachePageValidLength(page.key), false};
1088 if (disk->supportsDirectCacheWrite() && disk->dmaDevice() &&
1089 leases[i].acquire(disk->m_Cache, page.key, page.location)) {
1090 if (PciBus::instance().mapDmaPage(disk->dmaDevice(), leases[i].physical(),
1091 buffers[i].length, mappings[i])) {
1092 buffers[i].dmaPhysical = mappings[i].address();
1093 } else {
1094 leases[i].release();
1095 }
1096 }
1097 }
1098 if (disk->supportsBufferTransfers()) {
1099 succeeded = disk->transferWriteBuffers(buffers, n) && succeeded;
1100 } else {
1101 auto* controller = static_cast<ScsiController*>(disk->m_pParent);
1102 for (size_t i = 0; i < n; ++i) {
1103 const auto& b = buffers[i];
1104 const uint64_t written =
1105 b.dmaPhysical
1106 ? disk->doWriteDirectPhysical(b.location, reinterpret_cast<uintptr_t>(b.buffer),
1107 b.dmaPhysical)
1108 : controller->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_WRITE_DIRECT,
1109 reinterpret_cast<uint64_t>(disk), b.location,
1110 reinterpret_cast<uintptr_t>(b.buffer));
1111 succeeded = written == b.length && succeeded;
1112 }
1113 }
1114 }
1115 // A partial wave still needs its successful writes made durable.
1116 const bool durable = disk->syncData();
1117 return succeeded && durable;
1118}
1119
1121 TerminationDeferral lifetime;
1122 DiskUse diskUse;
1123 if (!acquireUse(diskUse))
1124 return false;
1125
1126#if CRIPPLE_HDD
1127 return false;
1128#else
1129 ScsiController* controller = static_cast<ScsiController*>(m_pParent);
1130 if (!controller) {
1131 return false;
1132 }
1133 OperationBarrier::Lease operation;
1134 if (!controller->acquireDiskOperation(operation)) {
1135 return false;
1136 }
1137
1138 const bool cacheSucceeded = m_Cache.syncAll(syncCacheBatch, this);
1139 // Previously submitted writes may still reside in the device even if the
1140 // cache is empty, or another page failed during this drain.
1141 const uint64_t flushed =
1142 controller->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_SYNC,
1143 reinterpret_cast<uint64_t>(this), SyncWholeDevice);
1144 return cacheSucceeded && flushed != 0;
1145#endif
1146}
1147
1148bool ScsiDisk::retireCachePage(uint64_t location) {
1149 TerminationDeferral lifetime;
1150 DiskUse diskUse;
1151 if (!acquireUse(diskUse))
1152 return false;
1153
1154 ScsiController* controller = static_cast<ScsiController*>(m_pParent);
1155 if (!controller) {
1156 return false;
1157 }
1158
1159 OperationBarrier::Lease operation;
1160 if (!controller->acquireDiskOperation(operation)) {
1161 return false;
1162 }
1163
1164 const size_t nativeBlockSize = getNativeBlockSize();
1165 if (!nativeBlockSize || (ScsiCachePageBytes % nativeBlockSize)) {
1166 return false;
1167 }
1168
1169 const uint64_t alignPoint = getAlignmentPoint(location);
1170 const uint64_t pageLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
1171 const size_t validLength = getCachePageValidLength(pageLocation);
1172 if (!validLength || (pageLocation % nativeBlockSize) || (validLength % nativeBlockSize)) {
1173 return false;
1174 }
1175
1176 CacheRangeAdmission admission(*this, pageLocation, ScsiCachePageBytes, true);
1177 return m_Cache.retireWriteback(pageLocation, retireCachePageCallback, this);
1178}
1179
1180bool ScsiDisk::flushCachePage(uint64_t location, uintptr_t page) {
1181#if !CRIPPLE_HDD
1182 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
1183 if (!pParent || !page) {
1184 return false;
1185 }
1186
1187 const size_t nativeBlockSize = getNativeBlockSize();
1188 if (!nativeBlockSize || (ScsiCachePageBytes % nativeBlockSize)) {
1189 ERROR("ScsiDisk::flush - incompatible cache and native block sizes.");
1190 return false;
1191 }
1192
1193 if (location >= getSize()) {
1194 ERROR("ScsiDisk::flush - location too high");
1195 return false;
1196 }
1197
1198 if ((location / getNativeBlockSize()) >= getBlockCount()) {
1199 ERROR("ScsiDisk::flush - location too high");
1200 return false;
1201 }
1202
1203 const uint64_t alignPoint = getAlignmentPoint(location);
1204
1205 const uint64_t pageLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
1206 const size_t validLength = getCachePageValidLength(pageLocation);
1207 if (!validLength || (pageLocation % nativeBlockSize) || (validLength % nativeBlockSize)) {
1208 ERROR("ScsiDisk::flush - invalid terminal cache page geometry.");
1209 return false;
1210 }
1211
1212 // The caller owns a pin even if retirement closes admission meanwhile.
1213 // Direct writes borrow it without another lookup or transferred reference.
1215 PciBus::DmaMapping mapping;
1216 const bool direct =
1217 supportsDirectCacheWrite() && dmaDevice() && lease.acquire(m_Cache, pageLocation, page) &&
1218 PciBus::instance().mapDmaPage(dmaDevice(), lease.physical(), validLength, mapping);
1219 const uint64_t writeResult =
1220 direct ? doWriteDirectPhysical(pageLocation, page, mapping.address())
1221 : pParent->addRequest(0, RequestQueue::NewRequest, SCSI_REQUEST_WRITE_DIRECT,
1222 reinterpret_cast<uint64_t>(this), pageLocation, page);
1223 mapping.release();
1224 lease.release();
1225 const uint64_t syncResult =
1226 pParent->addRequest(0, SCSI_REQUEST_SYNC, reinterpret_cast<uint64_t>(this), pageLocation);
1227 const bool success = writeResult == validLength && syncResult != 0;
1228 if (!success) {
1229 WARNING("ScsiDisk::flush - write or synchronise request failed");
1230 }
1231
1232 return success;
1233#else
1234 return false;
1235#endif
1236}
1237
1238void ScsiDisk::align(uint64_t location) {
1239 TerminationDeferral lifetime;
1240 DiskUse diskUse;
1241 if (!acquireUse(diskUse))
1242 return;
1243
1244 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
1245 if (!pParent) {
1246 return;
1247 }
1248
1249 OperationBarrier::Lease operation;
1250 if (!pParent->acquireDiskOperation(operation)) {
1251 return;
1252 }
1253
1254 CacheRangeAdmission admission(*this, 0, getSize(), true);
1255 LockGuard<Mutex> guard(m_AlignmentLock);
1256 for (size_t i = 0; i < m_AlignPoints.count(); ++i) {
1257 if (m_AlignPoints[i] == location) {
1258 return;
1259 }
1260 }
1261 m_HasShiftedCacheAlignment |= location % ScsiCachePageBytes != 0;
1262 m_AlignPoints.pushBack(location);
1263}
1264
1265uint64_t ScsiDisk::doRead(uint64_t location) {
1266 const size_t fillSize = getCacheFillLength(location);
1267 const size_t cacheLength = cacheExtentLength(fillSize);
1268 if (!fillSize || !cacheLength || fillSize > 0xffff || !getNativeBlockSize() ||
1269 (cacheLength % ScsiCachePageBytes) || (fillSize % getNativeBlockSize())) {
1270 return 0;
1271 }
1272
1273 // Wait for the unit to be ready before reading
1274 bool bReady = false;
1275 for (int i = 0; i < 3; i++) {
1276 if ((bReady = unitReady()))
1277 break;
1278 }
1279
1280 if (!bReady) {
1281 ERROR("ScsiDisk::doRead - unit not ready");
1282 return 0;
1283 }
1284
1285 // Handle the case where a read took place while we were waiting in the
1286 // RequestQueue - don't double up the cache.
1287 uintptr_t buffer = m_Cache.lookup(location);
1288 if (buffer) {
1289 WARNING("ScsiDisk::doRead(" << location << ") - buffer was already in cache");
1290 m_Cache.release(location);
1291 return fillSize;
1292 }
1293 bool didExist = false;
1294 buffer = m_Cache.insert(location, cacheLength, &didExist);
1295 if (!buffer) {
1296 WARNING("ScsiDisk::doRead - could not allocate a complete cache extent");
1297 return 0;
1298 }
1299 if (didExist) {
1300 return fillSize;
1301 }
1302 CacheFillGuard fillGuard(m_Cache, location, cacheLength);
1303 ByteSet(reinterpret_cast<void*>(buffer), 0, cacheLength);
1304
1305 size_t blockNum = location / getNativeBlockSize();
1306 size_t blockCount = fillSize / getNativeBlockSize();
1307
1308 bool bOk = false;
1309 ScsiCommand* pCommand;
1310
1311 // TOC?
1312 if (m_DeviceType == CdDvdDevice) {
1315 uint8_t* toc = new uint8_t[getNativeBlockSize()];
1316 PointerGuard<uint8_t> tmpBuffGuard(toc, true);
1317 const bool tocOk =
1318 sendCommand(pCommand, reinterpret_cast<uintptr_t>(toc), getNativeBlockSize());
1319 delete pCommand;
1320 if (!tocOk) {
1321 WARNING(
1322 "ScsiDisk::doRead - could not find data track (READ TOC "
1323 "failed)");
1324 return 0;
1325 }
1326
1327 uint16_t i;
1328 bool bHaveTrack = false;
1329 const size_t responseLength = static_cast<size_t>((toc[0] << 8) | toc[1]) + 2;
1330 if (responseLength < 4 || responseLength > getNativeBlockSize()) {
1331 WARNING("ScsiDisk::doRead - malformed READ TOC response");
1332 return 0;
1333 }
1335 reinterpret_cast<ScsiCommands::ReadTocCommand::TocEntry*>(toc + 4);
1336 const size_t descriptorCount = (responseLength - 4) / sizeof(*Toc);
1337 for (i = 0; i < descriptorCount; i++) {
1338 if (Toc[i].Flags & 0x04) {
1339 bHaveTrack = true;
1340 break;
1341 }
1342 }
1343
1344 if (!bHaveTrack) {
1345 WARNING("ScsiDisk::doRead - could not find data track (no data track)");
1346 return 0;
1347 }
1348
1349 uint32_t trackStart = BIG_TO_HOST32(Toc[i].TrackStart);
1350 if ((blockNum + trackStart) < blockNum) {
1351 WARNING("ScsiDisk::doRead - TOC overflow");
1352 return 0;
1353 }
1354
1355 blockNum += trackStart;
1356 }
1357
1358 for (int i = 0; i < 3 && !bOk; i++) {
1359 SCSI_DEBUG_LOG("SCSI: trying read(10)");
1360 pCommand = new ScsiCommands::Read10(blockNum, blockCount);
1361 bOk = sendCommand(pCommand, buffer, fillSize);
1362 delete pCommand;
1363 }
1364 for (int i = 0; i < 3 && !bOk; i++) {
1365 SCSI_DEBUG_LOG("SCSI: trying read(12)");
1366 pCommand = new ScsiCommands::Read12(blockNum, blockCount);
1367 bOk = sendCommand(pCommand, buffer, fillSize);
1368 delete pCommand;
1369 }
1370 for (int i = 0; i < 3 && !bOk; i++) {
1371 SCSI_DEBUG_LOG("SCSI: trying read(16)");
1372 pCommand = new ScsiCommands::Read16(blockNum, blockCount);
1373 bOk = sendCommand(pCommand, buffer, fillSize);
1374 delete pCommand;
1375 }
1376
1377 if (bOk) {
1378 fillGuard.publish();
1379 } else {
1380 ERROR("SCSI: reading failed?");
1381 return 0;
1382 }
1383
1384 return fillSize;
1385}
1386
1387size_t ScsiDisk::getCacheFillLength(uint64_t location) const {
1388 const size_t preferred = getCacheFillSize();
1389 const size_t native = getNativeBlockSize();
1390 if (!preferred || !native || (preferred % ScsiCachePageBytes) || (preferred % native) ||
1391 location >= getSize() || (location % native)) {
1392 return 0;
1393 }
1394
1395 size_t length = preferred;
1396 const uint64_t remaining = getSize() - location;
1397 if (length > remaining) {
1398 length = static_cast<size_t>(remaining);
1399 }
1400
1401 length -= length % native;
1402 return length;
1403}
1404
1405uint64_t ScsiDisk::doWrite(uint64_t location) {
1406 // Wait for the unit to be ready before writing
1407 bool bReady = false;
1408 for (int i = 0; i < 3; i++) {
1409 if ((bReady = unitReady()))
1410 break;
1411 }
1412
1413 if (!bReady) {
1414 ERROR("ScsiDisk::doWrite - unit not ready");
1415 return 0;
1416 }
1417
1418 // Handle the case where a read took place while we were waiting in the
1419 // RequestQueue - don't double up the cache.
1420 uintptr_t buffer = m_Cache.lookup(location);
1421 if (!buffer) {
1422 WARNING("ScsiDisk::doWrite(" << location << ") - buffer was not in cache");
1423 return 0;
1424 }
1425
1426 // Make sure we don't hold the refcnt once we exit this method.
1427 CachePageGuard guard(m_Cache, location);
1428
1429 const size_t validLength = getCachePageValidLength(location);
1430 return writePageBuffer(location, buffer) ? validLength : 0;
1431}
1432
1433uint64_t ScsiDisk::doWriteDirect(uint64_t location, uintptr_t page) {
1434 if (!page) {
1435 return 0;
1436 }
1437
1438 bool ready = false;
1439 for (int i = 0; i < 3; ++i) {
1440 if ((ready = unitReady())) {
1441 break;
1442 }
1443 }
1444
1445 if (!ready) {
1446 ERROR("ScsiDisk::doWriteDirect - unit not ready");
1447 return 0;
1448 }
1449
1450 const size_t validLength = getCachePageValidLength(location);
1451 return writePageBuffer(location, page) ? validLength : 0;
1452}
1453
1454uint64_t ScsiDisk::doWriteDirectPhysical(uint64_t location, uintptr_t page,
1455 physical_uintptr_t physical) {
1456 (void)physical;
1457 return doWriteDirect(location, page);
1458}
1459
1460bool ScsiDisk::writePageBuffer(uint64_t location, uintptr_t page) {
1461 const size_t nativeBlockSize = getNativeBlockSize();
1462 const size_t validLength = getCachePageValidLength(location);
1463 if (!page || !nativeBlockSize || !validLength || validLength > 0xffff ||
1464 (location % nativeBlockSize) || (validLength % nativeBlockSize)) {
1465 return false;
1466 }
1467
1468 size_t block = location / nativeBlockSize;
1469 size_t count = validLength / nativeBlockSize;
1470
1471 bool bOk = false;
1472 ScsiCommand* pCommand;
1473
1474 for (int i = 0; i < 3; i++) {
1475 SCSI_DEBUG_LOG("SCSI: trying write(10)");
1476 pCommand = new ScsiCommands::Write10(block, count);
1477 bOk = sendCommand(pCommand, page, validLength, true);
1478 delete pCommand;
1479 if (bOk)
1480 break;
1481 }
1482 if (!bOk) {
1483 for (int i = 0; i < 3; i++) {
1484 SCSI_DEBUG_LOG("SCSI: trying write(12)");
1485 pCommand = new ScsiCommands::Write12(block, count);
1486 bOk = sendCommand(pCommand, page, validLength, true);
1487 delete pCommand;
1488 if (bOk)
1489 break;
1490 }
1491 }
1492 if (!bOk) {
1493 for (int i = 0; i < 3; i++) {
1494 SCSI_DEBUG_LOG("SCSI: trying write(16)");
1495 pCommand = new ScsiCommands::Write16(block, count);
1496 bOk = sendCommand(pCommand, page, validLength, true);
1497 delete pCommand;
1498 if (bOk)
1499 break;
1500 }
1501 }
1502
1503 if (!bOk) {
1504 ERROR("SCSI: writing failed?");
1505 }
1506
1507 return bOk;
1508}
1509
1510uint64_t ScsiDisk::doSync(uint64_t location) {
1511 // Wait for the unit to be ready before writing
1512 bool bReady = false;
1513 for (int i = 0; i < 3; i++) {
1514 if ((bReady = unitReady()))
1515 break;
1516 }
1517
1518 if (!bReady) {
1519 ERROR("ScsiDisk::doSync - unit not ready");
1520 return 0;
1521 }
1522
1523 const bool wholeDevice = location == SyncWholeDevice;
1524 const size_t nativeBlockSize = getNativeBlockSize();
1525 const size_t validLength = wholeDevice ? 1 : getCachePageValidLength(location);
1526 if (!nativeBlockSize || !getSize() || !validLength ||
1527 (!wholeDevice && ((location % nativeBlockSize) || (validLength % nativeBlockSize)))) {
1528 return 0;
1529 }
1530
1531 // SBC defines zero blocks as the entire remaining medium, from LBA zero.
1532 const size_t block = wholeDevice ? 0 : location / nativeBlockSize;
1533 const size_t count = wholeDevice ? 0 : validLength / nativeBlockSize;
1534
1535 bool bOk = false;
1536 ScsiCommand* pCommand;
1537
1538 // Kick off a synchronise (this will be slow, but will ensure the data is on
1539 // disk)
1540 for (int i = 0; i < 3; i++) {
1541 SCSI_DEBUG_LOG("SCSI: trying synchronise(10)");
1542 pCommand = new ScsiCommands::Synchronise10(block, count);
1543 bOk = sendCommand(pCommand, 0, 0);
1544 delete pCommand;
1545 if (bOk)
1546 break;
1547 }
1548
1549 if (!bOk) {
1550 for (int i = 0; i < 3; i++) {
1551 SCSI_DEBUG_LOG("SCSI: trying synchronise(16)");
1552 pCommand = new ScsiCommands::Synchronise16(block, count);
1553 bOk = sendCommand(pCommand, 0, 0);
1554 delete pCommand;
1555 if (bOk)
1556 break;
1557 }
1558 }
1559
1560 return bOk ? validLength : 0;
1561}
1562
1563bool ScsiDisk::pin(uint64_t location) {
1564 TerminationDeferral lifetime;
1565 DiskUse diskUse;
1566 if (!acquireUse(diskUse))
1567 return false;
1568
1569 ScsiController* pParent = static_cast<ScsiController*>(m_pParent);
1570 if (!pParent) {
1571 return false;
1572 }
1573
1574 OperationBarrier::Lease operation;
1575 if (!pParent->acquireDiskOperation(operation)) {
1576 return false;
1577 }
1578 if (location >= getSize()) {
1579 return false;
1580 }
1581
1582 const uint64_t alignPoint = getAlignmentPoint(location);
1583
1584 const uint64_t cacheLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
1585 if (!m_Cache.pin(cacheLocation))
1586 return false;
1587 if (m_Cache.beginMutableLoan(cacheLocation))
1588 return true;
1589 m_Cache.release(cacheLocation);
1590 return false;
1591}
1592
1593void ScsiDisk::unpin(uint64_t location) {
1594 if (location >= getSize()) {
1595 return;
1596 }
1597 const uint64_t alignPoint = getAlignmentPoint(location);
1598
1599 const uint64_t cacheLocation = location - ((location - alignPoint) % ScsiCachePageBytes);
1600 releaseView(cacheLocation, true);
1601}
1602
1603void ScsiDisk::releaseView(uint64_t cacheLocation, bool writable) {
1604 // A new partition may change alignment while an older view still owns its pin.
1605 if (writable)
1606 m_Cache.endMutableLoan(cacheLocation);
1607 m_Cache.release(cacheLocation);
1608}
1609
1610size_t ScsiDisk::getCachePageValidLength(uint64_t location) const {
1611 if (location >= getSize()) {
1612 return 0;
1613 }
1614
1615 const uint64_t remaining = getSize() - location;
1616 return remaining < ScsiCachePageBytes ? static_cast<size_t>(remaining) : ScsiCachePageBytes;
1617}
1618
1619uint64_t ScsiDisk::getAlignmentPoint(uint64_t location) const {
1620 LockGuard<Mutex> guard(m_AlignmentLock);
1621 uint64_t alignPoint = 0;
1622 for (size_t i = 0; i < m_AlignPoints.count(); ++i) {
1623 if (m_AlignPoints[i] <= location && m_AlignPoints[i] > alignPoint) {
1624 alignPoint = m_AlignPoints[i];
1625 }
1626 }
1627 return alignPoint;
1628}
1629
1630bool ScsiDisk::hasShiftedCacheAlignment() const {
1631 LockGuard<Mutex> guard(m_AlignmentLock);
1632 return m_HasShiftedCacheAlignment;
1633}
Definition Cache.h:207
void setBackgroundWriteback(writeback_batch_t callback)
Definition Cache.cc:2355
MUST_USE_RESULT bool retireWriteback(uintptr_t key, retirement_writeback_t callback, void *meta)
Definition Cache.cc:1306
MUST_USE_RESULT bool syncAll()
Definition Cache.cc:1630
void setCallback(writeback_t newCallback, void *meta)
Definition Cache.cc:2331
void release(uintptr_t key)
Definition Cache.cc:1515
MUST_USE_RESULT bool lookupStable(uintptr_t key, uintptr_t &location, bool wait=false)
Definition Cache.cc:825
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
Definition Cache.cc:871
MUST_USE_RESULT bool beginMutableLoan(uintptr_t key)
Definition Cache.cc:2103
MUST_USE_RESULT bool syncBatch(const uintptr_t *keys, size_t count, writeback_batch_t callback, void *metadata)
Definition Cache.cc:1777
void markDirty(uintptr_t key)
Definition Cache.cc:2078
bool sync(uintptr_t key, bool async)
Definition Cache.cc:1570
void markNoLongerEditing(uintptr_t key, size_t length=0)
Definition Cache.cc:2587
uintptr_t lookup(uintptr_t key)
Definition Cache.cc:718
void setDirtyTracking(DirtyTracking tracking)
Definition Cache.cc:2371
bool shutdown()
Definition Cache.cc:663
MUST_USE_RESULT bool pin(uintptr_t key)
Definition Cache.cc:1489
Device * m_pParent
Definition Device.h:353
Definition Disk.h:35
virtual MUST_USE_RESULT bool readIntoBatch(ReadBuffer *buffers, size_t count)
Definition Disk.cc:199
void retireEndpoint()
Definition Disk.cc:133
virtual MUST_USE_RESULT bool zero(uint64_t location, size_t length)
Definition Disk.cc:387
virtual MUST_USE_RESULT bool writeFromBatch(WriteBuffer *buffers, size_t count)
Definition Disk.cc:285
Definition Mutex.h:56
bool mapDmaPage(Device *device, physical_uintptr_t physical, size_t bytes, DmaMapping &mapping)
MUST_USE_RESULT uint64_t addRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
virtual bool supportsConcurrentReads() const
virtual uint64_t doRead(uint64_t location)
Definition ScsiDisk.cc:1265
bool syncData() override
Definition ScsiDisk.cc:902
bool initialise(class ScsiController *pController, size_t nUnit)
Definition ScsiDisk.cc:284
virtual MUST_USE_RESULT bool retireCachePage(uint64_t location)
Definition ScsiDisk.cc:1148
static bool retireCachePageCallback(uintptr_t key, uintptr_t page, void *meta)
Definition ScsiDisk.cc:227
bool readSense(Sense *s)
Definition ScsiDisk.cc:388
size_t getCachePageValidLength(uint64_t location) const
Definition ScsiDisk.cc:1610
bool writePageBuffer(uint64_t location, uintptr_t page)
Definition ScsiDisk.cc:1460
virtual void flush(uint64_t location)
Flush a cached page to disk.
Definition ScsiDisk.cc:976
bool syncPages(const uint64_t *locations, size_t count) override
Definition ScsiDisk.cc:1034
uint64_t getAlignmentPoint(uint64_t location) const
Definition ScsiDisk.cc:1619
virtual void write(uint64_t location)
Definition ScsiDisk.cc:919
virtual size_t getSize() const
Gets the size of the disk.
Definition ScsiDisk.h:140
bool readIntoBatch(ReadBuffer *buffers, size_t count) override
Definition ScsiDisk.cc:560
virtual bool pin(uint64_t location)
Pins a cache page.
Definition ScsiDisk.cc:1563
size_t getCacheFillLength(uint64_t location) const
Definition ScsiDisk.cc:1387
virtual void unpin(uint64_t location)
Definition ScsiDisk.cc:1593
virtual void align(uint64_t location)
Sets the page boundary alignment after a specific location on the disk.
Definition ScsiDisk.cc:1238
virtual MUST_USE_RESULT uint64_t doWriteDirect(uint64_t location, uintptr_t page)
Definition ScsiDisk.cc:1433
virtual MUST_USE_RESULT bool syncAll()
Definition ScsiDisk.cc:1120
virtual size_t getCacheFillSize() const
Definition ScsiDisk.h:191
virtual size_t defaultBlockSize()
Definition ScsiDisk.h:299
virtual BufferView read(uint64_t location)
Definition ScsiDisk.cc:458
bool unitReady()
Definition ScsiDisk.cc:410
bool readInto(uint64_t location, void *buffer, size_t length) override
Definition ScsiDisk.cc:556
bool writeFromBatch(WriteBuffer *buffers, size_t count) override
Definition ScsiDisk.cc:839
bool zero(uint64_t location, size_t length) override
Definition ScsiDisk.cc:664
bool flushCachePage(uint64_t location, uintptr_t page)
Definition ScsiDisk.cc:1180
virtual bool sync(uint64_t location, bool async)
Definition ScsiDisk.cc:982
void shutdownCache()
Definition ScsiDisk.cc:268
virtual size_t getNativeBlockSize() const
Definition ScsiDisk.h:156
virtual bool provides(Type service)
Service * getService(const String &serviceName)
ServiceFeatures * enumerateOperations(const String &serviceName)
virtual bool serve(ServiceFeatures::Type type, void *pData, size_t dataLen)=0
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
#define assert(x)
Definition assert.h:39
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270