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